From bf41ef31726049d289d857ee684acabf82b7e455 Mon Sep 17 00:00:00 2001 From: ddCat <2695092045@qq.com> Date: Wed, 9 Sep 2026 22:02:10 +0800 Subject: [PATCH] =?UTF-8?q?refactor(*):=20=E7=A8=B3=E5=AE=9A=E6=9C=9F?= =?UTF-8?q?=E6=B2=BB=E7=90=86=20-=20=E6=94=B6=E6=95=9B=E6=A0=B8=E5=BF=83?= =?UTF-8?q?=E6=9E=B6=E6=9E=84=E3=80=81=E9=94=99=E8=AF=AF=E8=AF=AD=E4=B9=89?= =?UTF-8?q?=E5=B9=B6=E4=BF=AE=E5=A4=8D=E8=B7=A8=E9=A1=B9=E7=9B=AE=E6=95=B0?= =?UTF-8?q?=E6=8D=AE=E7=AB=9E=E6=80=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/quality.yml | 16 + .gitignore | 5 + AGENTS.md | 25 +- README.md | 10 +- backend/IMPLEMENTATION_SUMMARY.md | 203 - backend/cmd/server/main.go | 34 +- backend/internal/handler/admin_storage.go | 9 +- backend/internal/handler/announcement.go | 8 +- backend/internal/handler/api.go | 52 + backend/internal/handler/api_test.go | 42 + backend/internal/handler/auth.go | 83 +- backend/internal/handler/finance.go | 29 +- backend/internal/handler/model_catalog.go | 35 - backend/internal/handler/openapi.yaml | 215 + backend/internal/handler/openapi_embed.go | 6 + backend/internal/handler/payment.go | 22 +- backend/internal/handler/project.go | 30 +- backend/internal/handler/response.go | 61 +- backend/internal/handler/response_test.go | 56 +- backend/internal/handler/routes.go | 14 +- backend/internal/handler/security.go | 2 +- backend/internal/handler/skills.go | 13 +- backend/internal/handler/text_events_test.go | 14 + backend/internal/handler/user_data.go | 32 +- .../protocol/docs/newapi-channel-1.md | 2 +- backend/internal/repository/analytics_test.go | 8 +- backend/internal/service/admin.go | 4 +- backend/internal/service/admin_audit.go | 4 +- backend/internal/service/admin_storage.go | 2 +- backend/internal/service/analytics.go | 12 +- backend/internal/service/announcement.go | 2 +- backend/internal/service/canvas_share.go | 11 +- .../internal/service/channel_models_test.go | 117 +- .../service/channel_presentation_test.go | 12 +- .../desktop_local_channel_execution_test.go | 7 +- backend/internal/service/error_codes.go | 68 + backend/internal/service/errors.go | 23 +- backend/internal/service/errors_test.go | 9 +- backend/internal/service/finance.go | 8 +- backend/internal/service/json_fields.go | 59 + backend/internal/service/json_fields_test.go | 85 + backend/internal/service/logical_models.go | 15 +- backend/internal/service/model_capability.go | 24 + backend/internal/service/model_catalog.go | 117 +- backend/internal/service/model_router.go | 3 + .../internal/service/pagination_json_test.go | 33 + backend/internal/service/payment.go | 2 +- .../service/payment_reconciliation.go | 4 +- backend/internal/service/project_character.go | 10 +- backend/internal/service/project_workflow.go | 10 +- backend/internal/service/prompt_template.go | 142 +- .../service/prompt_template_catalog_test.go | 82 + .../service/prompt_template_defaults.go | 63 + backend/internal/service/protocol_registry.go | 52 + backend/internal/service/provider.go | 4931 +----- .../service/provider_ark_private_assets.go | 9 +- backend/internal/service/provider_audio.go | 259 + .../internal/service/provider_http_client.go | 623 + backend/internal/service/provider_image.go | 517 + backend/internal/service/provider_jimeng.go | 99 - .../internal/service/provider_jimeng_test.go | 9 - backend/internal/service/provider_progress.go | 4 +- backend/internal/service/provider_protocol.go | 995 ++ .../service/provider_request_types.go | 70 - .../service/provider_resource_helpers.go | 6 +- .../service/provider_task_recovery.go | 9 +- backend/internal/service/provider_test.go | 459 +- backend/internal/service/provider_text.go | 1193 ++ backend/internal/service/provider_video.go | 594 + .../service/provider_video_options.go | 69 - backend/internal/service/resource.go | 7 +- backend/internal/service/resource_url.go | 28 + backend/internal/service/resource_url_test.go | 20 + backend/internal/service/skill_packages.go | 16 +- backend/internal/service/skills.go | 136 +- backend/internal/service/skills_json_test.go | 60 + backend/internal/service/skills_seed.go | 54 +- backend/internal/service/storage_migration.go | 88 +- backend/internal/service/storage_quota.go | 18 +- .../internal/service/storage_quota_test.go | 7 +- backend/internal/service/task_creation.go | 145 +- .../internal/service/task_creation_test.go | 188 + backend/internal/service/task_output.go | 2 +- backend/internal/service/upload_quota.go | 6 +- .../internal/service/user_asset_document.go | 203 + backend/internal/service/user_data.go | 3 + backend/internal/service/user_data_test.go | 63 +- backend/internal/service/video_transcode.go | 44 +- .../internal/service/video_transcode_test.go | 35 + canvas-agent/README.md | 6 +- canvas-agent/package.json | 3 +- canvas-agent/pnpm-lock.yaml | 2012 --- .../content/docs/backend/backend-database.mdx | 11 +- docs/content/docs/backend/code-map.mdx | 104 + docs/content/docs/backend/http-api.mdx | 51 + .../docs/backend/local-development.mdx | 4 +- docs/content/docs/backend/meta.json | 1 + docs/content/docs/overview/features.mdx | 14 + docs/content/docs/progress/pending-test.mdx | 163 + docs/design/user-diagnostic-bundle.mdx | 2 +- docs/index.md | 1 + web/bun.lock | 176 +- web/eslint.config.js | 65 + web/package.json | 48 +- web/pnpm-lock.yaml | 14084 ---------------- .../assets/asset-library-picker-modal.tsx | 15 +- .../art-critique/ai-art-critique-modal.tsx | 9 +- .../canvas/canvas-agent-chat-ui.tsx | 6 +- .../canvas/canvas-assistant-online-tools.ts | 573 + .../canvas/canvas-assistant-panel-views.tsx | 251 + .../canvas/canvas-assistant-panel.tsx | 1212 +- .../canvas-character-reference-modal.tsx | 9 +- .../canvas/canvas-cinematic-continuation.ts | 71 + .../canvas/canvas-drawing-editor-modal.tsx | 7 +- .../canvas/canvas-local-agent-panel.tsx | 3 +- .../canvas/canvas-preset-picker.tsx | 69 +- .../canvas-resource-mention-textarea.tsx | 2 +- .../canvas/canvas-style-picker-modal.tsx | 11 +- .../canvas/canvas-subtitle-dialog.tsx | 7 +- .../canvas/canvas-text-editor-modal.tsx | 9 +- .../canvas/canvas-timeline-dialog.tsx | 9 +- .../components/canvas/canvas-upload-modal.tsx | 9 +- .../director/canvas-director-onboarding.tsx | 2 +- .../director/canvas-director-workbench.tsx | 10 +- .../portrait-clearance-modal.tsx | 23 +- .../canvas/style-asset-binding-modal.tsx | 9 +- .../canvas/style-profile-editor-modal.tsx | 9 +- web/src/components/editor/editor-context.tsx | 2 +- .../layout/app-changelog-dialog.tsx | 8 +- web/src/components/model-editor-modal.tsx | 9 +- .../skills/skill-runtime-picker.tsx | 4 +- web/src/components/ui/README.md | 7 +- .../ui/product/app-drawer/app-drawer.tsx | 22 + .../components/ui/product/app-drawer/index.ts | 1 + .../ui/product/app-modal/app-modal.tsx | 33 + .../components/ui/product/app-modal/index.ts | 1 + web/src/lib/asset-category.ts | 9 + web/src/lib/asset-record.ts | 214 + web/src/lib/asset-storage-revision.test.ts | 13 +- web/src/lib/asset-storage-revision.ts | 116 +- web/src/lib/canvas/canvas-agent-protocol.ts | 32 + web/src/lib/canvas/canvas-agent-tools.ts | 195 + web/src/lib/canvas/canvas-agent-workflow.ts | 7 +- web/src/lib/canvas/canvas-node-asset.ts | 21 +- web/src/lib/canvas/canvas-project-domain.ts | 3 +- .../lib/canvas/canvas-resource-references.ts | 7 +- .../canvas/director/director-onboarding.ts | 9 +- web/src/lib/canvas/skill-drafting.ts | 28 +- web/src/lib/creation-text-replay.ts | 86 - web/src/lib/media-metadata.ts | 30 +- .../builtin/editor/editor-ai-assistant.tsx | 10 +- .../builtin/editor/editor-asset-ingest.tsx | 3 +- .../plugins/builtin/editor/editor-export.tsx | 7 +- .../builtin/editor/editor-inspector.tsx | 4 +- .../builtin/editor/editor-preview-monitor.tsx | 7 +- .../plugins/builtin/editor/editor-shell.tsx | 12 +- .../builtin/editor/editor-subtitle-tools.tsx | 10 +- .../builtin/editor/editor-timeline-panel.tsx | 9 +- .../builtin/editor/editor-transcription.tsx | 6 +- web/src/lib/plugins/builtin/editor/index.ts | 1 - .../lib/plugins/plugin-permission-check.ts | 12 +- web/src/lib/prompts/catalog.ts | 127 + web/src/lib/prompts/index.ts | 22 + web/src/lib/prompts/operations.ts | 17 + web/src/lib/prompts/short-drama-outline.ts | 57 + web/src/lib/timeline/ai-command-schema.ts | 23 +- web/src/lib/timeline/asset-ingest.ts | 2 +- web/src/lib/timeline/editor-commands.ts | 2 +- web/src/lib/timeline/mock-transcription.ts | 36 - web/src/lib/timeline/subtitle-highlight-ai.ts | 53 - .../lib/timeline/subtitle-highlight-runner.ts | 2 +- .../timeline/subtitle-highlight-service.ts | 8 +- web/src/lib/timeline/timeline-summary.ts | 2 +- web/src/lib/user-session.ts | 37 +- .../pages/admin/channels/channels-page.tsx | 2 +- .../components/admin-announcements-panel.tsx | 12 +- .../components/admin-user-detail-drawer.tsx | 14 +- .../admin/components/analytics-panel.tsx | 2 +- .../admin/components/channel-model-editor.tsx | 9 +- .../admin/components/channel-order-dialog.tsx | 5 +- .../components/credit-operations-panel.tsx | 4 +- .../components/redemption-codes-panel.tsx | 4 +- .../components/storage-resources-panel.tsx | 2 +- .../logical-models/logical-models-page.tsx | 4 +- web/src/pages/admin/logs/logs-page.tsx | 2 +- .../pages/admin/payments/payments-page.tsx | 18 +- .../storyboard-prompts-page.tsx | 10 +- web/src/pages/admin/users/users-panel.tsx | 2 +- .../pages/assets/asset-batch-upload-modal.tsx | 6 +- web/src/pages/assets/index.tsx | 31 +- web/src/pages/auth/login.tsx | 6 +- .../canvas/canvas-project-status-dialogs.tsx | 8 +- .../pages/canvas/canvas-shortcuts-modal.tsx | 8 +- web/src/pages/canvas/project.tsx | 138 +- .../canvas/use-canvas-generation-executor.ts | 3 +- .../pages/canvas/use-canvas-media-tools.ts | 9 +- .../canvas/use-canvas-project-lifecycle.ts | 6 +- .../use-canvas-timeline-asset-insert.ts | 138 + .../pages/create/creation-conversations.ts | 139 + web/src/pages/create/creation-types.ts | 63 + web/src/pages/create/creation-workspace.tsx | 831 + web/src/pages/create/index.tsx | 1021 +- web/src/pages/plugins/index.tsx | 34 +- web/src/pages/plugins/plugins.css | 23 - web/src/pages/projects/detail/chapters.tsx | 5 +- web/src/pages/projects/detail/editor.tsx | 187 +- .../projects/detail/project-chapter-ai.ts | 7 +- .../detail/project-character-media.ts | 5 +- .../detail/workflow-chapter-navigator.tsx | 7 +- .../detail/workflow-production-workbench.tsx | 10 +- .../projects/detail/workflow-stage-views.tsx | 38 +- web/src/pages/projects/index.tsx | 47 +- .../settings/prompt-preferences-pane.tsx | 3 +- web/src/pages/skills/index.tsx | 74 +- web/src/pages/skills/skill-catalog.ts | 6 +- web/src/pages/skills/skill-detail-drawer.tsx | 41 +- web/src/pages/skills/skill-editor-drawer.tsx | 49 +- web/src/pages/skills/skill-install-modal.tsx | 14 +- web/src/pages/tasks/index.tsx | 26 +- web/src/services/api/admin-storage.ts | 12 +- web/src/services/api/announcements.ts | 21 +- web/src/services/api/appearance.ts | 12 +- web/src/services/api/audio.ts | 49 +- web/src/services/api/auth.ts | 121 +- web/src/services/api/canvas-share.ts | 11 +- web/src/services/api/channel-order.ts | 6 +- web/src/services/api/channel-transport.ts | 65 + web/src/services/api/comfy-bridge.ts | 8 +- web/src/services/api/eagle.ts | 12 +- web/src/services/api/image-models.ts | 42 +- web/src/services/api/image-transport.ts | 45 +- web/src/services/api/image.ts | 9 +- web/src/services/api/libtv.ts | 10 +- web/src/services/api/logical-models.ts | 29 +- web/src/services/api/payments.ts | 44 +- web/src/services/api/plugin-catalog.ts | 4 +- web/src/services/api/plugins.ts | 16 +- web/src/services/api/projects.ts | 115 +- web/src/services/api/request.ts | 79 +- web/src/services/api/resources.ts | 113 +- web/src/services/api/response-interception.ts | 6 +- web/src/services/api/runninghub.ts | 6 +- web/src/services/api/skills.ts | 150 +- web/src/services/api/style-profiles.ts | 14 +- web/src/services/api/system-performance.ts | 6 +- web/src/services/api/system-update.ts | 10 +- web/src/services/api/tapnow.ts | 4 +- web/src/services/api/task-center.ts | 41 +- web/src/services/api/timeline-tasks.ts | 21 +- web/src/services/api/user-data.ts | 47 +- web/src/services/api/video-transport.ts | 42 +- web/src/services/api/wallet.ts | 71 +- .../services/diagnostics/diagnostics-api.ts | 22 +- web/src/services/file-storage.ts | 121 +- .../services/generation-asset-repository.ts | 10 +- web/src/services/image-storage.ts | 20 +- web/src/services/resource-blob-cache.ts | 72 +- web/src/services/skill-runtime.ts | 68 +- web/src/services/user-data-sync.ts | 131 +- web/src/stores/canvas/use-canvas-store.ts | 13 +- web/src/stores/editor/editor-store.ts | 9 +- web/src/stores/use-asset-store.ts | 56 +- web/test/admin-ui-regressions.test.ts | 8 +- web/test/agnes-video.test.ts | 14 +- web/test/api-request.test.ts | 78 +- web/test/appearance-bootstrap.test.ts | 2 +- web/test/asset-header-button-style.test.ts | 11 +- web/test/asset-record.test.ts | 59 + web/test/asset-storage-revision.test.ts | 46 +- .../canvas-connection-create-menu.test.ts | 2 +- web/test/canvas-node-drag-overlays.test.ts | 2 +- .../canvas-resource-mention-editor.test.ts | 2 +- web/test/canvas-resource-references.test.ts | 16 +- web/test/canvas-skill-mentions.test.ts | 54 +- web/test/channel-model-catalog.test.ts | 29 +- web/test/create-canvas-handoff.test.ts | 61 +- web/test/create-library-button.test.ts | 20 +- web/test/fixtures/commands.golden.json | 2 +- web/test/generation-task-local.test.ts | 2 +- web/test/http-ownership.test.ts | 64 + web/test/local-channel-permission.test.ts | 13 +- ...l-channel-runtime-projection.node.test.mjs | 10 +- .../local-channel-task-center.node.test.mjs | 12 +- web/test/mock-transcription.test.ts | 27 - web/test/package-manager-lockfile.test.ts | 21 + web/test/password-reset.test.ts | 4 +- web/test/plugin-state-switch.test.ts | 10 +- web/test/prompt-catalog-ownership.test.ts | 54 + web/test/resource-upload-boundary.test.ts | 81 + web/test/skill-runtime.test.ts | 78 +- web/test/text-reasoning-stream.test.ts | 6 +- web/test/tiptap-dependency-versions.test.ts | 4 +- web/test/ui-kit-retirement.test.ts | 39 + web/test/user-data-sync-message.test.ts | 13 + ...user-scoped-generation-persistence.test.ts | 20 +- web/test/workflow-shot-delete.test.ts | 2 +- web/test/workflow-shot-generation.test.ts | 20 +- web/test/workspace-route-loading.test.ts | 16 +- 298 files changed, 12681 insertions(+), 26428 deletions(-) delete mode 100644 backend/IMPLEMENTATION_SUMMARY.md create mode 100644 backend/internal/handler/api.go create mode 100644 backend/internal/handler/api_test.go create mode 100644 backend/internal/handler/openapi.yaml create mode 100644 backend/internal/handler/openapi_embed.go create mode 100644 backend/internal/service/error_codes.go create mode 100644 backend/internal/service/json_fields.go create mode 100644 backend/internal/service/json_fields_test.go create mode 100644 backend/internal/service/pagination_json_test.go create mode 100644 backend/internal/service/prompt_template_catalog_test.go create mode 100644 backend/internal/service/provider_audio.go create mode 100644 backend/internal/service/provider_http_client.go create mode 100644 backend/internal/service/provider_image.go create mode 100644 backend/internal/service/provider_protocol.go create mode 100644 backend/internal/service/provider_text.go create mode 100644 backend/internal/service/provider_video.go create mode 100644 backend/internal/service/resource_url.go create mode 100644 backend/internal/service/resource_url_test.go create mode 100644 backend/internal/service/skills_json_test.go create mode 100644 backend/internal/service/user_asset_document.go delete mode 100644 canvas-agent/pnpm-lock.yaml create mode 100644 docs/content/docs/backend/code-map.mdx create mode 100644 docs/content/docs/backend/http-api.mdx create mode 100644 web/eslint.config.js delete mode 100644 web/pnpm-lock.yaml create mode 100644 web/src/components/canvas/canvas-assistant-online-tools.ts create mode 100644 web/src/components/canvas/canvas-assistant-panel-views.tsx create mode 100644 web/src/components/canvas/canvas-cinematic-continuation.ts create mode 100644 web/src/components/ui/product/app-drawer/app-drawer.tsx create mode 100644 web/src/components/ui/product/app-drawer/index.ts create mode 100644 web/src/components/ui/product/app-modal/app-modal.tsx create mode 100644 web/src/components/ui/product/app-modal/index.ts create mode 100644 web/src/lib/asset-record.ts create mode 100644 web/src/lib/canvas/canvas-agent-protocol.ts create mode 100644 web/src/lib/canvas/canvas-agent-tools.ts delete mode 100644 web/src/lib/creation-text-replay.ts create mode 100644 web/src/lib/prompts/catalog.ts create mode 100644 web/src/lib/prompts/index.ts create mode 100644 web/src/lib/prompts/operations.ts create mode 100644 web/src/lib/prompts/short-drama-outline.ts delete mode 100644 web/src/lib/timeline/mock-transcription.ts delete mode 100644 web/src/lib/timeline/subtitle-highlight-ai.ts create mode 100644 web/src/pages/canvas/use-canvas-timeline-asset-insert.ts create mode 100644 web/src/pages/create/creation-conversations.ts create mode 100644 web/src/pages/create/creation-types.ts create mode 100644 web/src/pages/create/creation-workspace.tsx create mode 100644 web/src/services/api/channel-transport.ts create mode 100644 web/test/asset-record.test.ts create mode 100644 web/test/http-ownership.test.ts delete mode 100644 web/test/mock-transcription.test.ts create mode 100644 web/test/package-manager-lockfile.test.ts create mode 100644 web/test/prompt-catalog-ownership.test.ts create mode 100644 web/test/resource-upload-boundary.test.ts create mode 100644 web/test/ui-kit-retirement.test.ts create mode 100644 web/test/user-data-sync-message.test.ts diff --git a/.github/workflows/quality.yml b/.github/workflows/quality.yml index 00c21abe6..251d1cffe 100644 --- a/.github/workflows/quality.yml +++ b/.github/workflows/quality.yml @@ -59,6 +59,20 @@ jobs: - uses: oven-sh/setup-bun@v2 with: bun-version: 1.3.9 + - name: Reject leftover package manager lockfiles + working-directory: ${{ github.workspace }} + run: | + leftover=0 + for file in web/pnpm-lock.yaml web/package-lock.json web/yarn.lock canvas-agent/pnpm-lock.yaml canvas-agent/package-lock.json canvas-agent/yarn.lock; do + if [ -f "$file" ]; then + echo "forbidden lockfile: $file" + leftover=1 + fi + done + if [ "$leftover" -ne 0 ]; then + echo "web and canvas-agent install from bun.lock only" + exit 1 + fi - name: Install dependencies run: bun install --frozen-lockfile - name: Install Canvas Agent dependencies @@ -93,6 +107,8 @@ jobs: fi - name: Typecheck run: bun run typecheck + - name: Lint retired Ant Design APIs + run: bun run lint - name: Run tests run: bun run test # ubuntu-latest 预装 google-chrome;脚本的解析顺序已覆盖该路径, diff --git a/.gitignore b/.gitignore index fb56951e4..f89cc1d40 100644 --- a/.gitignore +++ b/.gitignore @@ -18,6 +18,8 @@ docs/content/docs/plugins/* !docs/content/docs/backend/ docs/content/docs/backend/* !docs/content/docs/backend/task-runtime-stability.mdx +!docs/content/docs/backend/http-api.mdx +!docs/content/docs/backend/code-map.mdx !docs/content/docs/backend/newapi-channel-2.mdx .env* !.env.example @@ -57,3 +59,6 @@ camera_language_guide.md .superdesign .playwright-cli/ /project-memory/ + +# Local backend executable; never commit build output. +backend/cmd/server/server diff --git a/AGENTS.md b/AGENTS.md index a832ecf55..65ce98737 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -42,7 +42,7 @@ ### 后端 - `backend/internal/handler/`:HTTP 入参、鉴权上下文、调用 service、返回统一响应;不放业务判断和数据库查询。 -- `backend/internal/service/`:校验、权限、默认值、ID、时间、配额、幂等、任务编排和外部调用。 +- `backend/internal/service/`:校验、权限、默认值、ID、时间、配额、幂等、任务编排和外部调用。画布生成调度在 `provider.go`;文本 / 图片 / 视频遗留 / 音频 / HTTP / 声明式协议分别在 `provider_text.go`、`provider_image.go`、`provider_video.go`、`provider_audio.go`、`provider_http_client.go`、`provider_protocol.go`。 - `backend/internal/repository/`:GORM 查询和持久化;不承载业务策略。 - `backend/internal/model/`:结构、枚举和简单模型方法;不调用外部服务。 - `backend/internal/provider/`:模型供应商能力和协议实现。 @@ -61,18 +61,21 @@ ### 后端业务 JSON -业务 API 的唯一公共客户端是 `web/src/services/api/request.ts` 导出的 `apiClient` 和 `request`: +业务 API 的唯一调用入口是 `web/src/services/api/request.ts` 导出的 `http`: -- 复用现有 `axios.create`;不要新增 `httpClient`、平行响应解包器或业务模块自己的 axios 实例。 -- `apiClient` 默认使用 `VITE_CANVAS_BACKEND_URL || "/api"` 和 `withCredentials: true`,登录 Cookie 不放进 URL。 -- 后端成功响应为 `{ code: 0, data: T, msg: string }`;HTTP 200 不等于业务成功,`code !== 0` 必须抛错。 +- 模块写 `http.get/post/put/patch/delete`,由 `http` 解包 `{ code, data, msg, reason }`。不要再写 `request(apiClient.*)`,也不要再 `axios.create`。 +- 二进制或非信封响应用 `http.raw`(CSV、诊断包 zip)。 +- `apiClient` 只留给拦截器和 `http` 内部;默认 `VITE_CANVAS_BACKEND_URL || "/api"`、`withCredentials: true`,登录 Cookie 不放进 URL。 +- `request()` 仍可用于测试信封解包,不是业务模块的调用面。 +- 后端成功响应为 `{ code: 0, data: T, msg: string }`;HTTP 200 不等于业务成功,`code !== 0` 必须抛错。失败时用 `ApiError.reason` / `ApiError.code` 判断类型,不要解析 `msg`。错误码见 `web/src/services/api/error-codes.ts` 与 `docs/content/docs/backend/http-api.mdx`。 +- OpenAPI 3.0 在 `GET /api/openapi.yaml`。不要从规范生成 TypeScript 客户端来替换 `http` 模块。 - API 模块定义并导出接口类型;页面和 React Query 直接接收解包后的 `data`,不重复访问 `.data.data`。 - 查询参数使用 `compactApiParams` / `serializeApiParams`;取消请求传递 `AbortSignal` 并保留取消语义。 - `FormData` 不手动设置 `Content-Type`,让 Axios 生成 boundary。写路径失败必须向上抛出,不能 `catch { return defaultValue }`。 ### 模型渠道和流式请求 -- 文本、图片、视频、音频模型请求统一经过 `web/src/services/api/custom-channel-relay.ts` 的 `channelRequest` 及其协议函数。 +- 自定义渠道 URL/Header 仍由 `custom-channel-relay.ts` 的 `channelRequest` 解析;实际发出走 `channel-transport.ts` 的 `createChannelTransport`。image / video / audio 只组协议 payload,不再各自 `axios + channelRequest`。 - 自定义渠道必须由登录态后端 `/api/ai/custom` 中转;重建 headers 时清除 `x-goog-api-key` 和旧的 `X-Canvas-Upstream-Headers`,不得把第三方密钥放入浏览器 URL。 - Provider 特有 payload、响应解包和状态机留在对应 `image.ts`、`video.ts`、`audio.ts`;不要塞进通用 `request.ts`。 - 原始 `fetch` 仅用于媒体 blob/data URL、资源、Worker/本地 Agent 或 SSE;必须检查 `response.ok`,传递正确的 `credentials` 和 `signal`。 @@ -89,7 +92,7 @@ ## 5. 后端响应、权限和安全 -- Gin 接口统一返回 `{ code, data, msg }`;失败时 HTTP status 和业务 `code` 都应表达真实失败,不把所有错误包装成 200。 +- Gin 接口统一返回 `{ code, data, msg, reason }`;失败时 HTTP status 和业务 `code` 都应表达真实失败,不把所有错误包装成 200。机器可读原因放在 `reason`,见 `docs/content/docs/backend/http-api.mdx`。 - 所有对象读取、更新、删除都在 service 校验当前用户和资源归属;管理员权限在 service 校验,不依赖前端隐藏按钮。 - 默认拒绝本机、私网和链路本地上游。可信开发主机只能通过 `CANVAS_ALLOWED_PRIVATE_UPSTREAM_HOSTS` 精确放行;不要设置“允许全部私网”来绕过 SSRF 防护。 - 用户 API Key 保存在浏览器本地,任务创建时可能提交给自部署后端;只在可信部署和 HTTPS 下使用真实密钥。日志、错误上报、URL、localStorage 和持久任务正文不得写入敏感 URL、Cookie 或 API Key。 @@ -101,7 +104,7 @@ - 画布组件、状态、算法分别放在 `web/src/components/canvas/`、`web/src/stores/canvas/`、`web/src/lib/canvas/`。事件忽略选择器必须覆盖 modal、popover、dropdown 等浮层。 - 画布拖拽、连接、缩放和快捷键要考虑 pointer capture、滚轮冒泡、焦点以及 `data-canvas-no-zoom` / `data-canvas-wheel-scroll` 边界。 - 节点和对象名称要有可发现的铅笔入口并支持单击编辑;双击或右键不能是唯一入口。图片节点保持原始比例,面板不能长期遮挡主要画布空间。 -- Ant Design 共性主题和控件状态集中在 `web/src/lib/app-theme.ts` / `AppProviders`。Modal 当前内容外壳是 `.ant-modal-container`,优先使用 `styles.container`、`styles.body` 和组件 class。 +- Ant Design 共性主题和控件状态集中在 `web/src/lib/app-theme.ts` / `AppProviders`。自带外壳的产品弹窗用 `AppModal flush`,侧栏用 `AppDrawer`;不要再复制 `padding: 0` 的 Modal styles。内容外壳仍是 `.ant-modal-container`。 - 第三方覆盖限定在具体组件,不新增全局 `.ant-modal-*`、`.dark .ant-switch-*`、`.ant-checkbox-*` 或 Segmented 状态补丁。新增 CSS 前先搜索同名选择器,回到唯一源规则修改。 - 遵循 `docs/ui-design-system.md` 及项目三层 token:Primitive → Semantic → Component。inline style 优先引用 `var(--token-name)`,不要散落颜色、圆角、阴影和层级字面值。 - 主操作、普通选中、Checkbox/Radio、Switch 是不同颜色角色;持久切换使用 `aria-pressed`,`type="primary"` 只表示当前主要命令。尊重 `prefers-reduced-motion`,键盘导航保留 `:focus-visible`。 @@ -110,7 +113,7 @@ - 先阅读 `.env.example` 和对应 Compose 文件。宿主机后端开发必须使用 Git 忽略的 `.local/project-workbench-debug`,通过 `CANVAS_BACKEND_DATA_DIR` 显式指定;不要把 `backend/data` 当作开发账号数据库。 - 本地缓存放 `.local/cache`;不要提交数据库、上传文件、`.env`、真实密钥、构建产物或编辑器配置。 -- 宿主机开发:`backend/` 运行 `CANVAS_BACKEND_DATA_DIR=../.local/project-workbench-debug go run ./cmd/server`,`web/` 使用 Bun 和 Vite。Docker 热更新使用 `docker-compose.dev.yml`;本地构建运行使用 `docker-compose.local.yml`。 +- 宿主机开发:`backend/` 运行 `CANVAS_BACKEND_DATA_DIR=../.local/project-workbench-debug go run ./cmd/server`,`web/` 使用 Bun 和 Vite。`web/` 与 `canvas-agent/` 只认 `bun.lock`;不要用 pnpm/npm 覆盖同一套 `node_modules`,也不要提交 `pnpm-lock.yaml` 或 `package-lock.json`。Docker 热更新使用 `docker-compose.dev.yml`;本地构建运行使用 `docker-compose.local.yml`。 - 生产 Compose 使用 `docker-compose.deploy.yml`(PostgreSQL、Redis、backend、web),源码构建可叠加 `docker-compose.build.yml`。公网只暴露 web 的 `3000`,backend `8080` 留在 Compose 网络内。 - 默认不启动 dev server;只有用户明确要求浏览器预览或联调时才启动,并先确认端口、数据目录和现有进程。 - 健康检查只能证明入口可用,不能替代登录、SSE、任务生成和资源访问验证。 @@ -119,9 +122,9 @@ 项目当前默认不自动运行语法检查、类型检查、测试或构建。用户明确要求验证,或改动风险需要验证时,按范围选择最小充分命令,并在交付中如实记录: -- 前端:`cd web && bun run build`;专项测试用 `bun test ...`。 +- 前端:`cd web && bun run build`;专项测试用 `bun test ...`。UI 退场规则用 `bun run lint`(只禁 antd Empty 和静态 `Modal.confirm`,不是风格检查)。 - 后端:`cd backend && go test ./...`;涉及 PostgreSQL、资源、任务或权限时补对应集成/冒烟路径。 -- Canvas Agent:`cd canvas-agent && npm test`,构建用 `npm run build`。 +- Canvas Agent:仓库内 `cd canvas-agent && bun run test` / `bun run build`;已发布包仍可用 `npx`。 - 文档站:`cd docs && bun run types:check` 或 `bun run build`。 - UI 变更能浏览器验证时,检查关键路由、明暗主题、滚动、弹窗、空态和核心交互;不能验证时说明替代依据,不把静态阅读或 `git diff` 写成运行验证。 diff --git a/README.md b/README.md index b1efff9c3..b44d145d3 100644 --- a/README.md +++ b/README.md @@ -98,7 +98,7 @@ Issue 反馈、技术讨论和产品升级建议都可以在 微信 群中沟通 Codex 插件(`plugins/yingce/`)负责把 MCP 接入 Codex App。 ``` -前端默认把 `/api` 代理到 `http://127.0.0.1:8080`;生产环境由网页容器的 Nginx 代理到后端,只有 web 的 `3000` 端口需要对外暴露。系统模型和文本任务的 SSE 只在明确的流式路径关闭代理缓冲,详见 [`nginx.conf`](nginx.conf) 和 [SSE 文档](docs/content/docs/overview/docker.mdx)。 +前端默认把 `/api` 代理到 `http://127.0.0.1:8080`;生产环境由网页容器的 Nginx 代理到后端,只有 web 的 `3000` 端口需要对外暴露。信封和错误码见 [HTTP API](docs/content/docs/backend/http-api.mdx)。系统模型和文本任务的 SSE 只在明确的流式路径关闭代理缓冲,详见 [`nginx.conf`](nginx.conf) 和 [SSE 文档](docs/content/docs/overview/docker.mdx)。 ## 本地开发 @@ -240,8 +240,8 @@ npx -y @ddcat666/open-ai-canvas-agent ```bash cd canvas-agent -npm install -npm run build +bun install +bun run build node dist/index.js ``` @@ -253,13 +253,13 @@ node dist/index.js ```bash # 前端 -cd web && bun run build +cd web && bun run lint && bun run build # 后端 cd backend && go test ./... # Canvas Agent -cd canvas-agent && npm test && npm run build +cd canvas-agent && bun run test && bun run build # 文档站 cd docs && bun run types:check diff --git a/backend/IMPLEMENTATION_SUMMARY.md b/backend/IMPLEMENTATION_SUMMARY.md deleted file mode 100644 index 0d40e360a..000000000 --- a/backend/IMPLEMENTATION_SUMMARY.md +++ /dev/null @@ -1,203 +0,0 @@ -# 模型目录管理系统重构实施总结 - -## 实施完成情况 - -### 已完成任务 - -#### 1. 添加 frontendModelsEnabled 功能开关 ✅ -- 在 `feature_availability.go` 中添加了 `FrontendModelsEnabled` 字段 -- 添加了 `FeatureFrontendModels` 常量 -- 默认值设置为 `true`,与现有功能开关保持一致 -- 扩展了 `FeatureEnabled` 和 `RequireFeature` 方法支持新开关 - -**文件修改:** -- `internal/service/feature_availability.go` - -#### 2. 定义明确的错误码和错误消息 ✅ -- 新增 `ModelErrorCode` 类型定义错误码 -- 定义了6个模型相关的错误码: - - `model_capability_not_supported` - - `model_price_not_configured` - - `model_route_unavailable` - - `provider_request_failed` - - `model_catalog_mismatch` - - `invalid_model_selection` -- `ModelError` 继承 `AppError`,保持与现有错误处理的兼容性 -- 提供了便捷的错误创建函数 - -**文件修改:** -- `internal/service/errors.go` - -#### 3. 新增 LogicalModelPriceSKU 模型 ✅ -- 在 `models_logical_model.go` 中添加了 `LogicalModelPriceSKU` 结构体 -- 支持统一定价模式(`pricePolicy = unified`)的独立售价 -- 使用 `SelectorJSON` 存储结构化意图选择器 -- 包含完整的计费模式和价格字段 -- 支持优先级、启用状态和版本控制 - -**文件修改:** -- `internal/model/models_logical_model.go` - -#### 4. 完善价格有效性校验逻辑 ✅ -- 创建了 `price_validation.go` 文件 -- 实现了 `ValidateChannelModelPrice` 函数,根据 `billingMode` 校验价格字段 -- 实现了 `ComputePriceConfigured` 函数,价格标志由实际价格派生 -- 实现了 `HasValidPrice` 函数,检查渠道模型是否有有效价格 -- 支持三种计费模式:`fixed_request`、`per_second`、`token` - -**新增文件:** -- `internal/service/price_validation.go` - -#### 5. 统一用户价格展示字段 ✅ -- 在 `PublicLogicalModel` 中添加了三个新字段: - - `PricingMode`: 价格模式(`provider` 或 `unified`) - - `DisplayPrice`: 展示价格(可选) - - `PriceLabel`: 价格标签(如"按渠道规格计费"、"未配置"等) -- 实现了 `computeModelPriceDisplay` 函数计算价格展示信息 -- 根据 `pricePolicy` 和实际价格情况返回合适的展示内容 - -**文件修改:** -- `internal/service/logical_models.go` - -#### 6. 实现统一模型目录接口 ✅ -- 新增 `GET /api/model-catalog` 接口 -- 新增 `POST /api/model-catalog/available` 接口(能力过滤) -- 新增 `POST /api/model-catalog/quote` 接口(价格报价) -- 根据 `frontendModelsEnabled` 开关返回: - - 开启时:返回前台模型虚拟渠道(source: "frontend") - - 关闭时:返回脱敏的系统渠道模型(source: "system") -- 系统目录只返回安全信息,不包含 API Key、Base URL、供应商信息 - -**新增文件:** -- `internal/service/model_catalog.go` -- `internal/handler/model_catalog.go` - -**新增类型:** -- `ModelCatalogResponse`: 统一目录响应 -- `PublicChannelCatalog`: 公开渠道目录 -- `PublicChannelModel`: 公开渠道模型(脱敏) -- `PublicChannelModelPriceTier`: 公开价格档(脱敏) - -#### 7. 增强任务入口校验逻辑 ✅ -- 修改了 `CreateTask` 函数,根据 `frontendModelsEnabled` 强制分流 -- **前台模型模式(开启)**: - - 必须提供 `logicalModelId` - - 通过前台模型路由解析 - - 客户端提交的渠道配置将被后端覆盖 -- **系统渠道模式(关闭)**: - - 禁止提供 `logicalModelId` - - 必须提供有效的 `channelId` + `model` - - 验证系统渠道和模型的启用状态 - - 验证价格配置有效性 -- 新增 `validateSystemChannelModelSelection` 函数进行系统渠道模型校验 - -**文件修改:** -- `internal/service/task_creation.go` - -## 代码质量 - -### 编译状态 -- ✅ service 包编译通过 -- ✅ 所有语法错误已修复 -- ✅ 类型兼容性检查通过 - -### 错误处理 -- 使用明确的错误码,便于前端处理 -- 错误消息用户友好 -- 保持与现有 `AppError` 体系的兼容性 - -### 代码规范 -- 遵循项目现有的代码风格 -- 添加了详细的注释 -- 函数命名清晰,职责单一 - -## 实施方案对比 - -### 已实现的核心功能 - -| 方案要求 | 实施状态 | 说明 | -|---------|---------|------| -| 目录开关 | ✅ 完成 | `frontendModelsEnabled` 默认 true | -| 统一模型目录 | ✅ 完成 | `/api/model-catalog` 接口已实现 | -| 任务入口强制分流 | ✅ 完成 | CreateTask 已增加开关校验 | -| SKU 分层 | ✅ 完成 | LogicalModelPriceSKU 已定义 | -| 价格有效性 | ✅ 完成 | 价格校验逻辑已实现 | -| 价格展示 | ✅ 完成 | 统一展示字段已添加 | -| 错误语义 | ✅ 完成 | 明确的错误码已定义 | - -### 待完善的功能 - -1. **数据库迁移** - - 需要创建 `logical_model_price_skus` 表 - - 需要为现有数据添加默认 SKU - -2. **路由注册** - - 需要在主路由中注册 `RegisterModelCatalogRoutes` - -3. **前端适配** - - 前端需要调用新的 `/api/model-catalog` 接口 - - 移除对旧接口的直接调用 - -4. **系统渠道报价** - - `POST /api/model-catalog/quote` 中系统渠道模式的报价逻辑待实现 - -5. **SKU 管理后台** - - 需要添加前台 SKU 的 CRUD 接口 - - 需要实现 selector 覆盖校验 - -6. **测试覆盖** - - 单元测试 - - 集成测试 - - 边界情况测试 - -## 下一步建议 - -### 立即执行 -1. 创建数据库迁移脚本 -2. 在 `cmd/server/main.go` 中注册新的路由 -3. 编写单元测试验证核心逻辑 - -### 短期计划 -1. 实现系统渠道模型报价逻辑 -2. 添加 SKU 管理后台接口 -3. 前端接入新的统一目录接口 - -### 长期优化 -1. 完善 SKU selector 的匹配算法 -2. 添加价格档的自动迁移工具 -3. 实现价格配置的版本管理 - -## 技术亮点 - -1. **向后兼容**:所有新功能都保持与现有代码的兼容性 -2. **清晰分层**:前台模型和系统渠道模型的逻辑完全分离 -3. **安全脱敏**:系统渠道目录不暴露敏感信息 -4. **错误明确**:使用错误码替代模糊的错误消息 -5. **类型安全**:充分利用 Go 的类型系统保证安全性 - -## 风险提示 - -1. **磁盘空间**:当前编译环境磁盘空间不足,需要清理 -2. **数据迁移**:旧数据需要迁移到新的 SKU 结构 -3. **前端同步**:前端需要同步更新以使用新接口 -4. **性能影响**:统一目录接口可能需要额外的查询,需要关注性能 - -## 文件清单 - -### 新增文件 -- `internal/service/model_catalog.go` -- `internal/service/price_validation.go` -- `internal/handler/model_catalog.go` - -### 修改文件 -- `internal/service/feature_availability.go` -- `internal/service/errors.go` -- `internal/service/logical_models.go` -- `internal/service/task_creation.go` -- `internal/model/models_logical_model.go` - ---- - -**实施日期**: 2026-08-22 -**代码审查**: 建议进行 Code Review -**部署建议**: 建议先在测试环境验证后再上线 diff --git a/backend/cmd/server/main.go b/backend/cmd/server/main.go index d99a0f399..1c3538d67 100644 --- a/backend/cmd/server/main.go +++ b/backend/cmd/server/main.go @@ -112,39 +112,7 @@ func run(ctx context.Context) error { status := newSystemStatus(db, svc) registerSystemStatusRoutes(api, status) handler.RegisterOAuthCallbackRoutes(r, svc) - handler.RegisterAuthRoutes(api, svc) - handler.RegisterAppearanceRoutes(api, svc) - handler.RegisterFeatureAvailabilityRoutes(api, svc) - handler.RegisterResponseInterceptionRoutes(api, svc) - handler.RegisterAdminRoutes(api, svc) - handler.RegisterAdminAnalyticsRoutes(api, svc) - handler.RegisterAdminStorageRoutes(api, svc) - handler.RegisterAdminUpdateRoutes(api, svc) - handler.RegisterAdminSystemPerformanceRoutes(api, svc) - handler.RegisterAnnouncementRoutes(api, svc) - handler.RegisterFinanceRoutes(api, svc) - handler.RegisterPaymentRoutes(api, svc) - handler.RegisterLibTVRoutes(api, svc) - handler.RegisterTapNowRoutes(api, svc) - // 登录态模型目录代理:避免浏览器直连各上游时分别处理 CORS。 - handler.RegisterChannelModelRoutes(api, svc) - handler.RegisterLogicalModelRoutes(api, svc) - handler.RegisterModelCatalogRoutes(api, svc) - handler.RegisterSystemProxyRoutes(api, svc) - handler.RegisterCustomRelayRoutes(api, svc) - handler.RegisterTaskRoutes(api, svc) - handler.RegisterComfyBridgeRoutes(api, svc) - handler.RegisterRunningHubRoutes(api, svc) - handler.RegisterSessionRoutes(api, svc) - handler.RegisterSkillRoutes(api, svc) - handler.RegisterUserDataRoutes(api, svc) - handler.RegisterChunkedUploadRoutes(api, svc) - handler.RegisterDiagnosticsRoutes(api, svc) - handler.RegisterPluginRoutes(api, svc) - projectAPI := api.Group("") - projectAPI.Use(handler.RequireFeature(svc, service.FeatureShortDrama)) - handler.RegisterProjectRoutes(projectAPI, svc) - handler.RegisterCanvasShareRoutes(api, svc) + handler.RegisterCanvasAPI(api, svc) r.NoRoute(handler.SystemProxyNoRouteHandler(svc)) listener, err := net.Listen("tcp", addr) diff --git a/backend/internal/handler/admin_storage.go b/backend/internal/handler/admin_storage.go index f3f02c406..83b89459a 100644 --- a/backend/internal/handler/admin_storage.go +++ b/backend/internal/handler/admin_storage.go @@ -1,7 +1,7 @@ package handler import ( - "strconv" + "net/http" "infinite-canvas/backend/internal/service" @@ -48,8 +48,11 @@ func RegisterAdminStorageRoutes(r *gin.RouterGroup, svc *service.Service) { failService(c, err) return } - page, _ := strconv.Atoi(c.DefaultQuery("page", "1")) - limit, _ := strconv.Atoi(c.DefaultQuery("limit", "20")) + page, limit, err := parsePaginationQuery(c, 20) + if err != nil { + fail(c, http.StatusBadRequest, err) + return + } result, err := svc.AdminResourcePage(user, service.AdminResourceQuery{ Keyword: c.Query("keyword"), Kind: c.Query("kind"), Status: c.Query("status"), Provider: c.Query("provider"), UserID: c.Query("userId"), Page: page, Limit: limit, diff --git a/backend/internal/handler/announcement.go b/backend/internal/handler/announcement.go index 7aa4fd243..bbe8c043a 100644 --- a/backend/internal/handler/announcement.go +++ b/backend/internal/handler/announcement.go @@ -2,7 +2,6 @@ package handler import ( "net/http" - "strconv" "time" "infinite-canvas/backend/internal/service" @@ -77,8 +76,11 @@ func RegisterAnnouncementRoutes(r *gin.RouterGroup, svc *service.Service) { failService(c, err) return } - page, _ := strconv.Atoi(c.DefaultQuery("page", "1")) - limit, _ := strconv.Atoi(c.DefaultQuery("limit", "20")) + page, limit, err := parsePaginationQuery(c, 20) + if err != nil { + fail(c, http.StatusBadRequest, err) + return + } announcements, err := svc.AdminAnnouncementPage(user, service.AdminListQuery{Keyword: c.Query("keyword"), Status: c.Query("status"), Page: page, Limit: limit}) if err != nil { failService(c, err) diff --git a/backend/internal/handler/api.go b/backend/internal/handler/api.go new file mode 100644 index 000000000..39a4bfd52 --- /dev/null +++ b/backend/internal/handler/api.go @@ -0,0 +1,52 @@ +package handler + +import ( + "net/http" + + "infinite-canvas/backend/internal/service" + + "github.com/gin-gonic/gin" +) + +// RegisterCanvasAPI 注册当前公开 HTTP API。main 把它挂在 /api。前后端一起发布,不做路径版本前缀。 +func RegisterCanvasAPI(api *gin.RouterGroup, svc *service.Service) { + RegisterOpenAPIRoutes(api) + RegisterAuthRoutes(api, svc) + RegisterAppearanceRoutes(api, svc) + RegisterFeatureAvailabilityRoutes(api, svc) + RegisterResponseInterceptionRoutes(api, svc) + RegisterAdminRoutes(api, svc) + RegisterAdminAnalyticsRoutes(api, svc) + RegisterAdminStorageRoutes(api, svc) + RegisterAdminUpdateRoutes(api, svc) + RegisterAdminSystemPerformanceRoutes(api, svc) + RegisterAnnouncementRoutes(api, svc) + RegisterFinanceRoutes(api, svc) + RegisterPaymentRoutes(api, svc) + RegisterLibTVRoutes(api, svc) + RegisterTapNowRoutes(api, svc) + RegisterChannelModelRoutes(api, svc) + RegisterLogicalModelRoutes(api, svc) + RegisterModelCatalogRoutes(api, svc) + RegisterSystemProxyRoutes(api, svc) + RegisterCustomRelayRoutes(api, svc) + RegisterTaskRoutes(api, svc) + RegisterComfyBridgeRoutes(api, svc) + RegisterRunningHubRoutes(api, svc) + RegisterSessionRoutes(api, svc) + RegisterSkillRoutes(api, svc) + RegisterUserDataRoutes(api, svc) + RegisterChunkedUploadRoutes(api, svc) + RegisterDiagnosticsRoutes(api, svc) + RegisterPluginRoutes(api, svc) + projectAPI := api.Group("") + projectAPI.Use(RequireFeature(svc, service.FeatureShortDrama)) + RegisterProjectRoutes(projectAPI, svc) + RegisterCanvasShareRoutes(api, svc) +} + +func RegisterOpenAPIRoutes(api *gin.RouterGroup) { + api.GET("/openapi.yaml", func(c *gin.Context) { + c.Data(http.StatusOK, "application/yaml; charset=utf-8", openAPISpec) + }) +} diff --git a/backend/internal/handler/api_test.go b/backend/internal/handler/api_test.go new file mode 100644 index 000000000..919b6092d --- /dev/null +++ b/backend/internal/handler/api_test.go @@ -0,0 +1,42 @@ +package handler + +import ( + "net/http" + "net/http/httptest" + "strings" + "testing" + + "infinite-canvas/backend/internal/service" + + "github.com/gin-gonic/gin" +) + +func TestRegisterCanvasAPIExposesOpenAPIAndProjects(t *testing.T) { + gin.SetMode(gin.TestMode) + router := gin.New() + RegisterCanvasAPI(router.Group("/api"), &service.Service{}) + + wanted := map[string]bool{ + "GET /api/openapi.yaml": false, + "GET /api/projects": false, + "POST /api/tasks": false, + "GET /api/resources": false, + } + for _, route := range router.Routes() { + key := route.Method + " " + route.Path + if _, exists := wanted[key]; exists { + wanted[key] = true + } + } + for route, found := range wanted { + if !found { + t.Errorf("route %s is not registered", route) + } + } + + recorder := httptest.NewRecorder() + router.ServeHTTP(recorder, httptest.NewRequest(http.MethodGet, "/api/openapi.yaml", nil)) + if recorder.Code != http.StatusOK || !strings.Contains(recorder.Body.String(), "openapi: 3.0.3") || !strings.Contains(recorder.Body.String(), "url: /api") { + t.Fatalf("openapi.yaml status=%d body=%s", recorder.Code, recorder.Body.String()) + } +} diff --git a/backend/internal/handler/auth.go b/backend/internal/handler/auth.go index 5d60568c7..a0054c041 100644 --- a/backend/internal/handler/auth.go +++ b/backend/internal/handler/auth.go @@ -7,10 +7,10 @@ import ( "errors" "fmt" "io" + "log" "net/http" "net/url" "path/filepath" - "strconv" "strings" "time" @@ -231,8 +231,11 @@ func RegisterAdminRoutes(r *gin.RouterGroup, svc *service.Service) { failService(c, err) return } - page, _ := strconv.Atoi(c.DefaultQuery("page", "1")) - limit, _ := strconv.Atoi(c.DefaultQuery("limit", "20")) + page, limit, err := parsePaginationQuery(c, 20) + if err != nil { + fail(c, http.StatusBadRequest, err) + return + } users, err := svc.AdminUsers(user, service.AdminListQuery{Keyword: c.Query("keyword"), Type: c.Query("role"), Status: c.Query("status"), Page: page, Limit: limit}) if err != nil { failService(c, err) @@ -310,8 +313,11 @@ func RegisterAdminRoutes(r *gin.RouterGroup, svc *service.Service) { failService(c, err) return } - page, _ := strconv.Atoi(c.DefaultQuery("page", "1")) - limit, _ := strconv.Atoi(c.DefaultQuery("limit", "20")) + page, limit, err := parsePaginationQuery(c, 20) + if err != nil { + fail(c, http.StatusBadRequest, err) + return + } result, err := svc.AdminUserLedger(user, c.Param("id"), c.Query("type"), page, limit) if err != nil { failService(c, err) @@ -325,8 +331,11 @@ func RegisterAdminRoutes(r *gin.RouterGroup, svc *service.Service) { failService(c, err) return } - page, _ := strconv.Atoi(c.DefaultQuery("page", "1")) - limit, _ := strconv.Atoi(c.DefaultQuery("limit", "20")) + page, limit, err := parsePaginationQuery(c, 20) + if err != nil { + fail(c, http.StatusBadRequest, err) + return + } result, err := svc.AdminUserTasks(user, c.Param("id"), page, limit) if err != nil { failService(c, err) @@ -340,8 +349,11 @@ func RegisterAdminRoutes(r *gin.RouterGroup, svc *service.Service) { failService(c, err) return } - page, _ := strconv.Atoi(c.DefaultQuery("page", "1")) - limit, _ := strconv.Atoi(c.DefaultQuery("limit", "20")) + page, limit, err := parsePaginationQuery(c, 20) + if err != nil { + fail(c, http.StatusBadRequest, err) + return + } result, err := svc.AdminUserAuditEvents(user, c.Param("id"), page, limit) if err != nil { failService(c, err) @@ -385,8 +397,11 @@ func RegisterAdminRoutes(r *gin.RouterGroup, svc *service.Service) { failService(c, err) return } - page, _ := strconv.Atoi(c.DefaultQuery("page", "1")) - limit, _ := strconv.Atoi(c.DefaultQuery("limit", "20")) + page, limit, err := parsePaginationQuery(c, 20) + if err != nil { + fail(c, http.StatusBadRequest, err) + return + } channels, err := svc.AdminSystemChannelPage(user, service.AdminListQuery{Keyword: c.Query("keyword"), Status: c.Query("status"), Page: page, Limit: limit}) if err != nil { failService(c, err) @@ -685,8 +700,11 @@ func RegisterAdminRoutes(r *gin.RouterGroup, svc *service.Service) { failService(c, err) return } - limit, _ := strconv.Atoi(c.DefaultQuery("limit", "50")) - page, _ := strconv.Atoi(c.DefaultQuery("page", "1")) + page, limit, err := parsePaginationQuery(c, 50) + if err != nil { + fail(c, http.StatusBadRequest, err) + return + } logs, err := svc.AdminAPICallLogs(user, service.APICallLogQuery{AnalyticsQuery: analyticsQuery(c), RecordType: c.Query("recordType"), Keyword: c.Query("keyword"), Status: c.Query("status"), Page: page, Limit: limit}) if err != nil { failService(c, err) @@ -933,7 +951,6 @@ func proxySystemRequestPath(c *gin.Context, svc *service.Service, user *model.Us } validatedTarget, err := svc.ValidateChannelOutboundURL(target, channel.AllowLocalChannel, false) if err != nil { - _ = svc.RefundBilling(billingOrderID, "系统渠道地址校验失败") failService(c, err) return } @@ -961,7 +978,7 @@ func proxySystemRequestPath(c *gin.Context, svc *service.Service, user *model.Us if order != nil { billingOrderID = order.ID if err := svc.MarkBillingRunning(billingOrderID); err != nil { - _ = svc.RefundBilling(billingOrderID, "系统渠道请求尚未发出") + refundSystemProxyBilling(svc, billingOrderID, "系统渠道请求尚未发出") failService(c, err) return } @@ -969,7 +986,7 @@ func proxySystemRequestPath(c *gin.Context, svc *service.Service, user *model.Us } upstreamReq, err := http.NewRequestWithContext(c.Request.Context(), c.Request.Method, validatedTarget.String(), bytes.NewReader(body)) if err != nil { - _ = svc.RefundBilling(billingOrderID, "系统渠道请求构造失败") + refundSystemProxyBilling(svc, billingOrderID, "系统渠道请求构造失败") fail(c, http.StatusBadRequest, err) return } @@ -997,7 +1014,7 @@ func proxySystemRequestPath(c *gin.Context, svc *service.Service, user *model.Us if err != nil { status = model.ApiCallStatusFailed errorText = err.Error() - _ = svc.MarkBillingUncertain(billingOrderID, "系统渠道连接中断,费用状态待核对") + markSystemProxyBillingUncertain(svc, billingOrderID, "系统渠道连接中断,费用状态待核对") logSystemProxyCall(svc, apiCallLog(user, channel, billingOrderID, capability, protocol, c.Request.Method, path, target, body, c.GetHeader("Content-Type"), status, statusCode, time.Since(startedAt), errorText, concurrencyLimit), nil) fail(c, http.StatusBadGateway, errors.New("系统渠道连接失败")) return @@ -1023,7 +1040,7 @@ func proxySystemRequestPath(c *gin.Context, svc *service.Service, user *model.Us if errors.Is(readErr, errSystemProxyResponseTooLarge) { billingNote = "上游已响应但流式响应体超过限制,费用状态待核对" } - _ = svc.MarkBillingUncertain(billingOrderID, billingNote) + markSystemProxyBillingUncertain(svc, billingOrderID, billingNote) logSystemProxyCall(svc, apiCallLog(user, channel, billingOrderID, capability, protocol, c.Request.Method, path, target, body, c.GetHeader("Content-Type"), status, statusCode, time.Since(startedAt), errorText, concurrencyLimit), responseBody) // SSE 响应头已经发送,流中断后只能关闭连接;非流式响应仍返回结构化错误。 if !streamed { @@ -1032,21 +1049,21 @@ func proxySystemRequestPath(c *gin.Context, svc *service.Service, user *model.Us return } if int64(len(responseBody)) > responseLimit { - _ = svc.MarkBillingUncertain(billingOrderID, "上游已响应但响应体超过限制,费用状态待核对") + markSystemProxyBillingUncertain(svc, billingOrderID, "上游已响应但响应体超过限制,费用状态待核对") fail(c, http.StatusBadGateway, fmt.Errorf("系统渠道响应超过 %dMB 限制", policy.Request.SystemRelayResponseMB)) return } logErr := logSystemProxyCall(svc, apiCallLog(user, channel, billingOrderID, capability, protocol, c.Request.Method, path, target, body, c.GetHeader("Content-Type"), status, statusCode, time.Since(startedAt), errorText, concurrencyLimit), responseBody) if status == model.ApiCallStatusSucceeded { if logErr != nil { - _ = svc.MarkBillingUncertain(billingOrderID, "上游成功但调用日志写入失败,费用状态待核对") + markSystemProxyBillingUncertain(svc, billingOrderID, "上游成功但调用日志写入失败,费用状态待核对") } else if err := svc.SettleBilling(billingOrderID, ""); err != nil { - _ = svc.MarkBillingUncertain(billingOrderID, "上游成功但积分结算失败:"+err.Error()) + markSystemProxyBillingUncertain(svc, billingOrderID, "上游成功但积分结算失败:"+err.Error()) } } else if statusCode == 524 { - _ = svc.MarkBillingUncertain(billingOrderID, "上游返回 524,费用状态待核对") + markSystemProxyBillingUncertain(svc, billingOrderID, "上游返回 524,费用状态待核对") } else { - _ = svc.RefundBilling(billingOrderID, "上游明确返回失败") + refundSystemProxyBilling(svc, billingOrderID, "上游明确返回失败") } if streamed { return @@ -1055,6 +1072,26 @@ func proxySystemRequestPath(c *gin.Context, svc *service.Service, user *model.Us c.Data(resp.StatusCode, resp.Header.Get("Content-Type"), responseBody) } +func refundSystemProxyBilling(svc *service.Service, billingOrderID string, reason string) { + if billingOrderID == "" { + return + } + if err := svc.RefundBilling(billingOrderID, reason); err != nil { + log.Printf("system proxy billing refund failed: billing_order_id=%s error=%v", billingOrderID, err) + // 退款写失败不能当没发生:转待核对,避免订单停在 running 被当成已扣费。 + markSystemProxyBillingUncertain(svc, billingOrderID, "退款失败,费用状态待核对:"+err.Error()) + } +} + +func markSystemProxyBillingUncertain(svc *service.Service, billingOrderID string, reason string) { + if billingOrderID == "" { + return + } + if err := svc.MarkBillingUncertain(billingOrderID, reason); err != nil { + log.Printf("system proxy billing uncertainty update failed: billing_order_id=%s error=%v", billingOrderID, err) + } +} + func apiCallLog(user *model.User, channel *model.ModelChannel, billingOrderID string, capability string, protocol model.ChannelInterfaceType, method string, path string, target string, body []byte, contentType string, status model.ApiCallStatus, statusCode int, duration time.Duration, errorText string, concurrencyLimit int) model.ApiCallLog { requestKind := "create" apiFormat := "openai" diff --git a/backend/internal/handler/finance.go b/backend/internal/handler/finance.go index ec4ec7441..c73a052d2 100644 --- a/backend/internal/handler/finance.go +++ b/backend/internal/handler/finance.go @@ -2,7 +2,6 @@ package handler import ( "net/http" - "strconv" "time" "infinite-canvas/backend/internal/service" @@ -17,8 +16,11 @@ func RegisterFinanceRoutes(r *gin.RouterGroup, svc *service.Service) { failService(c, err) return } - page, _ := strconv.Atoi(c.DefaultQuery("page", "1")) - limit, _ := strconv.Atoi(c.DefaultQuery("limit", "30")) + page, limit, err := parsePaginationQuery(c, 30) + if err != nil { + fail(c, http.StatusBadRequest, err) + return + } wallet, err := svc.Wallet(user, c.Query("type"), page, limit) if err != nil { failService(c, err) @@ -329,8 +331,11 @@ func RegisterFinanceRoutes(r *gin.RouterGroup, svc *service.Service) { failService(c, err) return } - page, _ := strconv.Atoi(c.DefaultQuery("page", "1")) - limit, _ := strconv.Atoi(c.DefaultQuery("limit", "20")) + page, limit, err := parsePaginationQuery(c, 20) + if err != nil { + fail(c, http.StatusBadRequest, err) + return + } items, err := svc.AdminRedeemBatchPage(user, service.AdminListQuery{Keyword: c.Query("keyword"), Status: c.Query("validity"), Page: page, Limit: limit}) if err != nil { failService(c, err) @@ -367,8 +372,11 @@ func RegisterFinanceRoutes(r *gin.RouterGroup, svc *service.Service) { failService(c, err) return } - page, _ := strconv.Atoi(c.DefaultQuery("page", "1")) - limit, _ := strconv.Atoi(c.DefaultQuery("limit", "50")) + page, limit, err := parsePaginationQuery(c, 50) + if err != nil { + fail(c, http.StatusBadRequest, err) + return + } result, err := svc.AdminRedeemCodePage(user, c.Param("id"), c.Query("status"), page, limit) if err != nil { failService(c, err) @@ -426,8 +434,11 @@ func RegisterFinanceRoutes(r *gin.RouterGroup, svc *service.Service) { failService(c, err) return } - page, _ := strconv.Atoi(c.DefaultQuery("page", "1")) - limit, _ := strconv.Atoi(c.DefaultQuery("limit", "20")) + page, limit, err := parsePaginationQuery(c, 20) + if err != nil { + fail(c, http.StatusBadRequest, err) + return + } items, err := svc.AdminBillingOrderPage(user, service.AdminListQuery{Keyword: c.Query("keyword"), Status: c.DefaultQuery("status", "review"), Page: page, Limit: limit}) if err != nil { failService(c, err) diff --git a/backend/internal/handler/model_catalog.go b/backend/internal/handler/model_catalog.go index 5494c6921..507e433c1 100644 --- a/backend/internal/handler/model_catalog.go +++ b/backend/internal/handler/model_catalog.go @@ -42,39 +42,4 @@ func RegisterModelCatalogRoutes(r *gin.RouterGroup, svc *service.Service) { ok(c, catalog) }) - r.POST("/model-catalog/quote", func(c *gin.Context) { - if _, err := currentUser(c, svc); err != nil { - failService(c, err) - return - } - var req struct { - ModelID string `json:"modelId"` - Intent service.ModelRequestIntent `json:"intent"` - } - if err := c.ShouldBindJSON(&req); err != nil { - fail(c, http.StatusBadRequest, errors.New("模型报价请求格式错误")) - return - } - - // 根据 frontendModelsEnabled 决定使用哪种报价方式 - frontendEnabled, err := svc.FeatureEnabled(service.FeatureFrontendModels) - if err != nil { - failService(c, err) - return - } - - if frontendEnabled { - // 使用前台模型报价 - quote, err := svc.QuoteLogicalModel(req.ModelID, req.Intent) - if err != nil { - failService(c, err) - return - } - ok(c, gin.H{"quote": quote}) - } else { - // 使用系统渠道模型报价 - // TODO: 实现系统渠道模型报价逻辑 - fail(c, http.StatusNotImplemented, errors.New("系统渠道模型报价功能尚未实现")) - } - }) } diff --git a/backend/internal/handler/openapi.yaml b/backend/internal/handler/openapi.yaml new file mode 100644 index 000000000..227a2711f --- /dev/null +++ b/backend/internal/handler/openapi.yaml @@ -0,0 +1,215 @@ +openapi: 3.0.3 +info: + title: 影策 HTTP API + version: "1.0.0" + description: | + 路径前缀是 `/api`。前后端一起发布,不按路径做 API 版本。 + + 业务 JSON 信封为 `{ code, data, msg, reason }`。`code === 0` 表示成功;失败时 HTTP status 与 + `code` 都表达失败。`code` 默认等于 HTTP 状态;同一状态下的细分原因使用 `status*100+n` + (例如 40301 配额、42901 限流)。机器判断请用 `reason`,不要解析 `msg`。 + + TypeScript 调用面仍是 `web/src/services/api` 的 `http` 模块,不从本规范生成客户端。 +servers: + - url: /api +security: + - sessionCookie: [] +tags: + - name: meta + - name: auth + - name: projects + - name: resources + - name: tasks + - name: assets +paths: + /openapi.yaml: + get: + tags: [meta] + security: [] + summary: 本 OpenAPI 文档 + responses: + "200": + description: OpenAPI 3.0 YAML + content: + application/yaml: + schema: + type: string + /auth/session: + get: + tags: [auth] + summary: 当前登录会话 + responses: + "200": + $ref: "#/components/responses/Envelope" + "401": + $ref: "#/components/responses/Error" + /auth/login: + post: + tags: [auth] + security: [] + summary: 登录并设置会话 Cookie + requestBody: + required: true + content: + application/json: + schema: + type: object + required: [username, password] + properties: + username: { type: string } + password: { type: string } + responses: + "200": + $ref: "#/components/responses/Envelope" + "401": + $ref: "#/components/responses/Error" + /projects: + get: + tags: [projects] + summary: 短剧项目列表 + responses: + "200": + $ref: "#/components/responses/Envelope" + "401": + $ref: "#/components/responses/Error" + "403": + $ref: "#/components/responses/Error" + post: + tags: [projects] + summary: 创建短剧项目 + responses: + "200": + $ref: "#/components/responses/Envelope" + "400": + $ref: "#/components/responses/Error" + /resources: + get: + tags: [resources] + summary: 当前用户资源列表 + responses: + "200": + $ref: "#/components/responses/Envelope" + post: + tags: [resources] + summary: 上传资源文件 + responses: + "200": + $ref: "#/components/responses/Envelope" + "403": + description: 配额用尽(code=40301, reason=quota_exceeded) + content: + application/json: + schema: + $ref: "#/components/schemas/ErrorEnvelope" + /resources/{id}/file: + get: + tags: [resources] + summary: 读取资源二进制(非 JSON 信封) + parameters: + - in: path + name: id + required: true + schema: { type: string } + responses: + "200": + description: 文件流 + "401": + $ref: "#/components/responses/Error" + "404": + $ref: "#/components/responses/Error" + /tasks: + post: + tags: [tasks] + summary: 创建生成任务 + responses: + "200": + $ref: "#/components/responses/Envelope" + "400": + $ref: "#/components/responses/Error" + /tasks/{id}/text-events: + get: + tags: [tasks] + summary: 文本任务 SSE + parameters: + - in: path + name: id + required: true + schema: { type: string } + - in: query + name: after + schema: { type: integer } + description: 递增事件游标,不要用任务 ID + responses: + "200": + description: text/event-stream + /assets: + get: + tags: [assets] + summary: 用户素材库 + responses: + "200": + $ref: "#/components/responses/Envelope" +components: + securitySchemes: + sessionCookie: + type: apiKey + in: cookie + name: open_ai_canvas_session + schemas: + Envelope: + type: object + required: [code, msg] + properties: + code: + type: integer + description: 0 成功;失败时默认等于 HTTP 状态,或 status*100+n 的细分码 + data: {} + msg: + type: string + reason: + type: string + description: 仅失败时出现。稳定机器可读原因。 + ErrorEnvelope: + allOf: + - $ref: "#/components/schemas/Envelope" + - type: object + properties: + code: + type: integer + enum: [400, 401, 403, 404, 409, 429, 500, 502, 503, 504, 40301, 40901, 42901] + data: + nullable: true + reason: + type: string + enum: + - invalid_argument + - unauthorized + - forbidden + - not_found + - conflict + - failed_precondition + - quota_exceeded + - rate_limited + - unavailable + - timeout + - internal + - bad_gateway + - model_capability_not_supported + - model_price_not_configured + - model_route_unavailable + - provider_request_failed + - model_catalog_mismatch + - invalid_model_selection + responses: + Envelope: + description: 业务成功信封 + content: + application/json: + schema: + $ref: "#/components/schemas/Envelope" + Error: + description: 业务失败信封 + content: + application/json: + schema: + $ref: "#/components/schemas/ErrorEnvelope" diff --git a/backend/internal/handler/openapi_embed.go b/backend/internal/handler/openapi_embed.go new file mode 100644 index 000000000..51084e1a9 --- /dev/null +++ b/backend/internal/handler/openapi_embed.go @@ -0,0 +1,6 @@ +package handler + +import _ "embed" + +//go:embed openapi.yaml +var openAPISpec []byte diff --git a/backend/internal/handler/payment.go b/backend/internal/handler/payment.go index 14cb2e6da..3c38efe3a 100644 --- a/backend/internal/handler/payment.go +++ b/backend/internal/handler/payment.go @@ -6,7 +6,6 @@ import ( "net/http" "net/url" "regexp" - "strconv" "strings" "time" @@ -265,8 +264,11 @@ func RegisterPaymentRoutes(r *gin.RouterGroup, svc *service.Service) { failService(c, err) return } - page, _ := strconv.Atoi(c.DefaultQuery("page", "1")) - limit, _ := strconv.Atoi(c.DefaultQuery("limit", "30")) + page, limit, err := parsePaginationQuery(c, 30) + if err != nil { + fail(c, http.StatusBadRequest, err) + return + } result, err := svc.AdminPaymentOrderPage(user, c.Query("status"), c.Query("keyword"), page, limit) if err != nil { failService(c, err) @@ -324,8 +326,11 @@ func RegisterPaymentRoutes(r *gin.RouterGroup, svc *service.Service) { failService(c, err) return } - page, _ := strconv.Atoi(c.DefaultQuery("page", "1")) - limit, _ := strconv.Atoi(c.DefaultQuery("limit", "30")) + page, limit, err := parsePaginationQuery(c, 30) + if err != nil { + fail(c, http.StatusBadRequest, err) + return + } result, err := svc.AdminPaymentReconciliationPage(user, c.Query("providerId"), c.Query("status"), page, limit) if err != nil { failService(c, err) @@ -339,8 +344,11 @@ func RegisterPaymentRoutes(r *gin.RouterGroup, svc *service.Service) { failService(c, err) return } - page, _ := strconv.Atoi(c.DefaultQuery("page", "1")) - limit, _ := strconv.Atoi(c.DefaultQuery("limit", "50")) + page, limit, err := parsePaginationQuery(c, 50) + if err != nil { + fail(c, http.StatusBadRequest, err) + return + } result, err := svc.AdminPaymentReconciliationItems(user, c.Param("id"), c.Query("result"), page, limit) if err != nil { failService(c, err) diff --git a/backend/internal/handler/project.go b/backend/internal/handler/project.go index 0a03b21d3..d795f323a 100644 --- a/backend/internal/handler/project.go +++ b/backend/internal/handler/project.go @@ -32,7 +32,7 @@ func RegisterProjectRoutes(r *gin.RouterGroup, svc *service.Service) { return } pageParam, hasPage := c.GetQuery("page") - pageSizeParam, hasPageSize := c.GetQuery("page_size") + pageSizeParam, hasPageSize := c.GetQuery("pageSize") if !hasPage && !hasPageSize { projects, err := svc.ListProjects(user.ID) if err != nil { @@ -385,7 +385,7 @@ func RegisterProjectRoutes(r *gin.RouterGroup, svc *service.Service) { fail(c, http.StatusBadRequest, err) return } - pageSize, err := parsePositiveQueryInt(c.Query("page_size"), 40) + pageSize, err := parsePositiveQueryInt(c.Query("pageSize"), 40) if err != nil { fail(c, http.StatusBadRequest, err) return @@ -404,7 +404,7 @@ func RegisterProjectRoutes(r *gin.RouterGroup, svc *service.Service) { return } pageParam, hasPage := c.GetQuery("page") - pageSizeParam, hasPageSize := c.GetQuery("page_size") + pageSizeParam, hasPageSize := c.GetQuery("pageSize") if hasPage || hasPageSize { page, pageErr := parsePositiveQueryInt(pageParam, 1) if pageErr != nil { @@ -417,10 +417,10 @@ func RegisterProjectRoutes(r *gin.RouterGroup, svc *service.Service) { return } var folderID *string - if value, present := c.GetQuery("folder_id"); present { + if value, present := c.GetQuery("folderId"); present { folderID = &value } - assets, pageErr := svc.ProjectAssetsPage(user.ID, c.Param("id"), page, pageSize, c.Query("category"), c.Query("media_type"), c.Query("status"), folderID, c.Query("q")) + assets, pageErr := svc.ProjectAssetsPage(user.ID, c.Param("id"), page, pageSize, c.Query("category"), c.Query("mediaType"), c.Query("status"), folderID, c.Query("q")) if pageErr != nil { failService(c, pageErr) return @@ -874,12 +874,12 @@ func RegisterProjectRoutes(r *gin.RouterGroup, svc *service.Service) { fail(c, http.StatusBadRequest, err) return } - pageSize, err := parsePositiveQueryInt(c.Query("page_size"), 100) + pageSize, err := parsePositiveQueryInt(c.Query("pageSize"), 100) if err != nil { fail(c, http.StatusBadRequest, err) return } - result, err := svc.ProjectAssetCandidatesPage(user.ID, c.Param("id"), page, pageSize, c.Query("unit_id"), c.Query("status"), c.Query("category"), c.Query("q")) + result, err := svc.ProjectAssetCandidatesPage(user.ID, c.Param("id"), page, pageSize, c.Query("unitId"), c.Query("status"), c.Query("category"), c.Query("q")) if err != nil { failService(c, err) return @@ -907,6 +907,7 @@ func RegisterProjectRoutes(r *gin.RouterGroup, svc *service.Service) { }) } +// parsePositiveQueryInt 解析列表查询里的正整数。分页和筛选查询名统一用 camelCase:page、pageSize、projectId、folderId、mediaType、unitId。 func parsePositiveQueryInt(value string, fallback int) (int, error) { if value == "" { return fallback, nil @@ -917,3 +918,18 @@ func parsePositiveQueryInt(value string, fallback int) (int, error) { } return parsed, nil } + +// parsePaginationQuery 统一解析列表分页参数。 +// 缺省值由具体接口声明;显式传入非正整数属于请求错误,不能悄悄变成服务层默认值, +// 否则客户端的协议问题会被掩盖,分页行为也会随服务层实现变化。 +func parsePaginationQuery(c *gin.Context, fallbackPageSize int) (int, int, error) { + page, err := parsePositiveQueryInt(c.Query("page"), 1) + if err != nil { + return 0, 0, fmt.Errorf("page: %w", err) + } + pageSize, err := parsePositiveQueryInt(c.Query("pageSize"), fallbackPageSize) + if err != nil { + return 0, 0, fmt.Errorf("pageSize: %w", err) + } + return page, pageSize, nil +} diff --git a/backend/internal/handler/response.go b/backend/internal/handler/response.go index 6f0484afc..7d554106f 100644 --- a/backend/internal/handler/response.go +++ b/backend/internal/handler/response.go @@ -15,7 +15,7 @@ import ( const internalErrorMessage = "系统处理失败,请稍后重试" func ok(c *gin.Context, data any) { - c.JSON(http.StatusOK, gin.H{"code": 0, "data": data, "msg": "ok"}) + c.JSON(http.StatusOK, gin.H{"code": service.CodeOK, "data": data, "msg": "ok"}) } // fail 只接受调用方已经确认可公开的错误;service 返回值统一交给 failService 投影。 @@ -24,50 +24,67 @@ func fail(c *gin.Context, status int, err error) { if err != nil && strings.TrimSpace(err.Error()) != "" { message = err.Error() } - writeFailure(c, status, status, message) + writeFailure(c, status, status, service.ReasonForStatus(status), message) } func failService(c *gin.Context, err error) { var cooldown *service.EmailCodeCooldownError if errors.As(err, &cooldown) { c.Header("Retry-After", strconv.Itoa(cooldown.Seconds)) - fail(c, http.StatusTooManyRequests, cooldown) + writeFailure(c, http.StatusTooManyRequests, service.CodeRateLimited, service.ReasonRateLimited, cooldown.Error()) + return + } + var modelErr *service.ModelError + if errors.As(err, &modelErr) && modelErr.AppError != nil && validErrorStatus(modelErr.Status) { + writeAppError(c, modelErr.AppError) return } var appErr *service.AppError if errors.As(err, &appErr) && validErrorStatus(appErr.Status) { - code := appErr.Code - if code == 0 { - code = appErr.Status - } - message := strings.TrimSpace(appErr.Message) - if message == "" { - message = safeInternalErrorMessage(appErr.Status) - } - if appErr.Status >= http.StatusInternalServerError { - diagnosticErr := appErr.Cause - if diagnosticErr == nil { - diagnosticErr = appErr - } - logHandlerError(c, appErr.Status, diagnosticErr) - } - writeFailure(c, appErr.Status, code, message) + writeAppError(c, appErr) return } failInternal(c, http.StatusInternalServerError, err) } +func writeAppError(c *gin.Context, appErr *service.AppError) { + code := appErr.Code + if code == 0 { + code = appErr.Status + } + reason := appErr.Reason + if reason == "" { + reason = service.ReasonForStatus(appErr.Status) + } + message := strings.TrimSpace(appErr.Message) + if message == "" { + message = safeInternalErrorMessage(appErr.Status) + } + if appErr.Status >= http.StatusInternalServerError { + diagnosticErr := appErr.Cause + if diagnosticErr == nil { + diagnosticErr = appErr + } + logHandlerError(c, appErr.Status, diagnosticErr) + } + writeFailure(c, appErr.Status, code, reason, message) +} + // failInternal 保留真实 HTTP 状态,但绝不把未分类错误原文写入响应。 func failInternal(c *gin.Context, status int, err error) { if !validErrorStatus(status) { status = http.StatusInternalServerError } logHandlerError(c, status, err) - writeFailure(c, status, status, safeInternalErrorMessage(status)) + writeFailure(c, status, status, service.ReasonForStatus(status), safeInternalErrorMessage(status)) } -func writeFailure(c *gin.Context, status int, code int, message string) { - c.JSON(status, gin.H{"code": code, "data": nil, "msg": message}) +func writeFailure(c *gin.Context, status int, code int, reason service.ErrorReason, message string) { + body := gin.H{"code": code, "data": nil, "msg": message} + if reason != "" { + body["reason"] = reason + } + c.JSON(status, body) } func validErrorStatus(status int) bool { diff --git a/backend/internal/handler/response_test.go b/backend/internal/handler/response_test.go index 71df275c7..e28c01029 100644 --- a/backend/internal/handler/response_test.go +++ b/backend/internal/handler/response_test.go @@ -14,32 +14,42 @@ import ( ) type failureEnvelope struct { - Code int `json:"code"` - Msg string `json:"msg"` + Code int `json:"code"` + Msg string `json:"msg"` + Reason string `json:"reason"` } func TestFailServiceRegistrationCooldown(t *testing.T) { recorder, context := responseTestContext() failService(context, &service.EmailCodeCooldownError{Seconds: 47}) response := decodeFailureEnvelope(t, recorder) - if recorder.Code != http.StatusTooManyRequests || response.Code != 429 || recorder.Header().Get("Retry-After") != "47" || !strings.Contains(response.Msg, "47") { + if recorder.Code != http.StatusTooManyRequests || response.Code != service.CodeRateLimited || response.Reason != string(service.ReasonRateLimited) || recorder.Header().Get("Retry-After") != "47" || !strings.Contains(response.Msg, "47") { t.Fatalf("cooldown response: status=%d header=%s body=%#v", recorder.Code, recorder.Header().Get("Retry-After"), response) } } func TestFailServiceProjectsAppError(t *testing.T) { recorder, context := responseTestContext() - err := service.NewAppError(http.StatusTooManyRequests, "请求过于频繁,请稍后重试") - err.Code = 42901 + err := service.RateLimited("请求过于频繁,请稍后重试") failService(context, err) response := decodeFailureEnvelope(t, recorder) - if recorder.Code != http.StatusTooManyRequests || response.Code != 42901 || response.Msg != err.Message { + if recorder.Code != http.StatusTooManyRequests || response.Code != service.CodeRateLimited || response.Reason != string(service.ReasonRateLimited) || response.Msg != err.Message { t.Fatalf("response = status %d, body %#v", recorder.Code, response) } } +func TestFailServiceQuotaExceeded(t *testing.T) { + recorder, context := responseTestContext() + failService(context, service.QuotaExceeded("账号素材数量已达到 100 个上限")) + + response := decodeFailureEnvelope(t, recorder) + if recorder.Code != http.StatusForbidden || response.Code != service.CodeQuotaExceeded || response.Reason != string(service.ReasonQuotaExceeded) { + t.Fatalf("quota response = status %d, body %#v", recorder.Code, response) + } +} + func TestFailServiceHidesUnclassifiedInternalError(t *testing.T) { recorder, context := responseTestContext() failService(context, errors.New("database password=secret")) @@ -66,6 +76,40 @@ func TestFailInternalKeepsStatusWithoutLeakingCause(t *testing.T) { } } +func TestParsePaginationQueryUsesDefaultsAndRejectsInvalidValues(t *testing.T) { + gin.SetMode(gin.TestMode) + tests := []struct { + name string + query string + page int + pageSize int + wantError bool + }{ + {name: "defaults", query: "", page: 1, pageSize: 40}, + {name: "explicit values", query: "page=3&pageSize=25", page: 3, pageSize: 25}, + {name: "invalid page", query: "page=abc", wantError: true}, + {name: "zero page", query: "page=0", wantError: true}, + {name: "negative page size", query: "pageSize=-1", wantError: true}, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + request := httptest.NewRequest(http.MethodGet, "/api/test?"+tt.query, nil) + context, _ := gin.CreateTestContext(httptest.NewRecorder()) + context.Request = request + page, pageSize, err := parsePaginationQuery(context, 40) + if (err != nil) != tt.wantError { + t.Fatalf("error = %v, wantError = %t", err, tt.wantError) + } + if tt.wantError { + return + } + if page != tt.page || pageSize != tt.pageSize { + t.Fatalf("pagination = (%d, %d), want (%d, %d)", page, pageSize, tt.page, tt.pageSize) + } + }) + } +} + func responseTestContext() (*httptest.ResponseRecorder, *gin.Context) { recorder := httptest.NewRecorder() context, _ := gin.CreateTestContext(recorder) diff --git a/backend/internal/handler/routes.go b/backend/internal/handler/routes.go index 97b70e1e2..c6644f681 100644 --- a/backend/internal/handler/routes.go +++ b/backend/internal/handler/routes.go @@ -104,9 +104,13 @@ func RegisterTaskRoutes(r *gin.RouterGroup, svc *service.Service) { failService(c, err) return } - limit, _ := strconv.Atoi(c.DefaultQuery("limit", "50")) + pageSize, err := parsePositiveQueryInt(c.Query("pageSize"), 50) + if err != nil { + fail(c, http.StatusBadRequest, err) + return + } tasks, err := svc.TasksWithOptions(user.ID, service.TaskListOptions{ - Limit: limit, + Limit: pageSize, ProjectID: c.Query("projectId"), ActiveOnly: c.Query("activeOnly") == "true", }) @@ -155,7 +159,11 @@ func RegisterTaskRoutes(r *gin.RouterGroup, svc *service.Service) { failService(c, err) return } - after, _ := strconv.ParseInt(c.DefaultQuery("after", "0"), 10, 64) + after, err := taskTextEventCursor(c) + if err != nil { + fail(c, http.StatusBadRequest, err) + return + } result, err := svc.TaskTextReplay(user.ID, c.Param("id"), after) if err != nil { failService(c, err) diff --git a/backend/internal/handler/security.go b/backend/internal/handler/security.go index dba7fa544..487c305e9 100644 --- a/backend/internal/handler/security.go +++ b/backend/internal/handler/security.go @@ -155,7 +155,7 @@ func enforceRateLimit(c *gin.Context, key string, limit int, window time.Duratio wait := runtimeService.RequestRetryAfter(c.Request.Context(), key, window) seconds := max(1, int((wait+time.Second-1)/time.Second)) c.Header("Retry-After", strconv.Itoa(seconds)) - fail(c, http.StatusTooManyRequests, fmt.Errorf("请求次数已达上限,请在 %d 秒后重试", seconds)) + failService(c, service.RateLimited(fmt.Sprintf("请求次数已达上限,请在 %d 秒后重试", seconds))) return false } diff --git a/backend/internal/handler/skills.go b/backend/internal/handler/skills.go index d388dc013..2a9e9c1fd 100644 --- a/backend/internal/handler/skills.go +++ b/backend/internal/handler/skills.go @@ -25,12 +25,12 @@ func RegisterSkillRoutes(r *gin.RouterGroup, svc *service.Service) { failService(c, service.BadAuthRequest("请选择 Markdown 或 ZIP 技能文件")) return } - isPrivate, err := parseOptionalBool(c.PostForm("is_private")) + isPrivate, err := parseOptionalBool(c.PostForm("isPrivate")) if err != nil { - failService(c, service.BadAuthRequest("is_private 必须是布尔值")) + failService(c, service.BadAuthRequest("isPrivate 必须是布尔值")) return } - sourceType := strings.ToLower(strings.TrimSpace(c.PostForm("source_type"))) + sourceType := strings.ToLower(strings.TrimSpace(c.PostForm("sourceType"))) if sourceType == "" { switch strings.ToLower(filepath.Ext(file.Filename)) { case ".md", ".markdown": @@ -75,8 +75,11 @@ func RegisterSkillRoutes(r *gin.RouterGroup, svc *service.Service) { failService(c, err) return } - page, _ := strconv.Atoi(c.DefaultQuery("page", "1")) - pageSize, _ := strconv.Atoi(c.DefaultQuery("page_size", "20")) + page, pageSize, err := parsePaginationQuery(c, 20) + if err != nil { + fail(c, http.StatusBadRequest, err) + return + } result, err := svc.Skills(user.ID, service.SkillListRequest{ Page: page, PageSize: pageSize, Scope: c.DefaultQuery("scope", "public"), Search: c.Query("search"), Tag: c.Query("tag"), Sort: c.DefaultQuery("sort", "popular"), diff --git a/backend/internal/handler/text_events_test.go b/backend/internal/handler/text_events_test.go index 98bff5a28..a2e3214bd 100644 --- a/backend/internal/handler/text_events_test.go +++ b/backend/internal/handler/text_events_test.go @@ -37,6 +37,20 @@ func TestTaskTextEventCursorPrefersQueryAndSupportsLastEventID(t *testing.T) { } } +func TestTaskTextEventCursorRejectsInvalidValues(t *testing.T) { + gin.SetMode(gin.TestMode) + for _, query := range []string{"after=abc", "after=-1"} { + t.Run(query, func(t *testing.T) { + request := httptest.NewRequest("GET", "/api/tasks/task-1/text-events?"+query, nil) + context, _ := gin.CreateTestContext(httptest.NewRecorder()) + context.Request = request + if after, err := taskTextEventCursor(context); err == nil || after != 0 { + t.Fatalf("cursor = %d, err = %v; want a rejected cursor", after, err) + } + }) + } +} + func TestStreamTaskTextEventsCachedReplayReachesTerminalWithoutDuplicateDelta(t *testing.T) { t.Setenv("REDIS_URL", "") db, err := gorm.Open(sqlite.Open(":memory:"), &gorm.Config{}) diff --git a/backend/internal/handler/user_data.go b/backend/internal/handler/user_data.go index 43d150ecf..6d3a3cc46 100644 --- a/backend/internal/handler/user_data.go +++ b/backend/internal/handler/user_data.go @@ -142,8 +142,12 @@ func RegisterUserDataRoutes(r *gin.RouterGroup, svc *service.Service) { failService(c, err) return } - limit, _ := strconv.Atoi(c.DefaultQuery("limit", "200")) - resources, err := svc.Resources(user.ID, limit) + pageSize, err := parsePositiveQueryInt(c.Query("pageSize"), 200) + if err != nil { + fail(c, http.StatusBadRequest, err) + return + } + resources, err := svc.Resources(user.ID, pageSize) if err != nil { failService(c, err) return @@ -406,18 +410,13 @@ func RegisterUserDataRoutes(r *gin.RouterGroup, svc *service.Service) { return } if _, paged := c.GetQuery("page"); paged || hasUserAssetPageFilters(c) { - page, pageErr := strconv.Atoi(c.DefaultQuery("page", "1")) - if pageErr != nil || page < 1 { - fail(c, http.StatusBadRequest, service.BadAuthRequest("页码必须是正整数")) - return - } - pageSize, pageSizeErr := strconv.Atoi(c.DefaultQuery("page_size", "40")) - if pageSizeErr != nil || pageSize < 1 { - fail(c, http.StatusBadRequest, service.BadAuthRequest("每页数量必须是正整数")) + page, pageSize, pageErr := parsePaginationQuery(c, 40) + if pageErr != nil { + fail(c, http.StatusBadRequest, pageErr) return } var folderID *string - if value, present := c.GetQuery("folder_id"); present { + if value, present := c.GetQuery("folderId"); present { folderID = &value } assets, pageErr := svc.UserAssetsPage(user.ID, page, pageSize, service.UserAssetPageFilter{ @@ -593,9 +592,12 @@ func RegisterUserDataRoutes(r *gin.RouterGroup, svc *service.Service) { return } if c.Query("page") != "" { - page, _ := strconv.Atoi(c.Query("page")) - pageSize, _ := strconv.Atoi(c.Query("page_size")) - result, pageErr := svc.UserCanvasProjectsPage(user.ID, page, pageSize, c.Query("project_id"), c.Query("q"), c.Query("sort")) + page, pageSize, pageErr := parsePaginationQuery(c, 40) + if pageErr != nil { + fail(c, http.StatusBadRequest, pageErr) + return + } + result, pageErr := svc.UserCanvasProjectsPage(user.ID, page, pageSize, c.Query("projectId"), c.Query("q"), c.Query("sort")) if pageErr != nil { failService(c, pageErr) return @@ -670,7 +672,7 @@ func RegisterUserDataRoutes(r *gin.RouterGroup, svc *service.Service) { } func hasUserAssetPageFilters(c *gin.Context) bool { - for _, key := range []string{"page_size", "kind", "category", "folder_id", "uncategorized", "status", "q"} { + for _, key := range []string{"pageSize", "kind", "category", "folderId", "uncategorized", "status", "q"} { if _, present := c.GetQuery(key); present { return true } diff --git a/backend/internal/protocol/docs/newapi-channel-1.md b/backend/internal/protocol/docs/newapi-channel-1.md index cacb8b828..7333c0bc0 100644 --- a/backend/internal/protocol/docs/newapi-channel-1.md +++ b/backend/internal/protocol/docs/newapi-channel-1.md @@ -21,7 +21,7 @@ Content-Type: application/json {{PARAMETERS}} -实际映射:图片 -> `{type:"reference_image"}`,视频 -> `{type:"reference_video"}`,音频 -> `{type:"reference_voice"}`;均放入 `input.media`。默认 `resolution=720P`、`ratio=16:9`、`duration=5`。只有 `watermark=true` 时才发送水印字段;`generateAudio` 当前不发送。 +实际映射:`input.media[].type` 取素材 `role`,缺省时图片为 `reference_image`、视频为 `reference_video`、音频为 `reference_voice`。宿主视频任务会给音频带上 `reference_audio`,因此生产请求里音频 type 是 `reference_audio`。默认 `resolution=720P`、`ratio=16:9`、`duration=5`。只有 `watermark=true` 时才发送水印字段;`generateAudio` 当前不发送。 ## 多模态创建示例 diff --git a/backend/internal/repository/analytics_test.go b/backend/internal/repository/analytics_test.go index 62f45636b..56c35e462 100644 --- a/backend/internal/repository/analytics_test.go +++ b/backend/internal/repository/analytics_test.go @@ -180,10 +180,10 @@ func TestQueryAPICallLogsHidesInternalPollStages(t *testing.T) { if err != nil { t.Fatal(err) } - if total != 3 || len(items) != 3 { - t.Fatalf("visible logs = total:%d items:%#v, want create and download logs without polls", total, items) + if total != 2 || len(items) != 2 { + t.Fatalf("visible logs = total:%d items:%#v, want only create logs without polls or downloads", total, items) } - if items[0].ID != "video-create" || items[1].ID != "image-download" || items[2].ID != "image-create" { - t.Fatalf("visible logs = %#v, want video-create, image-download, image-create", items) + if items[0].ID != "video-create" || items[1].ID != "image-create" { + t.Fatalf("visible logs = %#v, want video-create and image-create", items) } } diff --git a/backend/internal/service/admin.go b/backend/internal/service/admin.go index ee00f74f8..8d4625452 100644 --- a/backend/internal/service/admin.go +++ b/backend/internal/service/admin.go @@ -51,7 +51,7 @@ type AdminUserPage struct { Users []AdminUser `json:"users"` Total int64 `json:"total"` Page int `json:"page"` - Limit int `json:"limit"` + Limit int `json:"pageSize"` } type AdminUser struct { @@ -64,7 +64,7 @@ type AdminChannelPage struct { Channels []PublicModelChannel `json:"channels"` Total int64 `json:"total"` Page int `json:"page"` - Limit int `json:"limit"` + Limit int `json:"pageSize"` } type AdminUserReference struct { diff --git a/backend/internal/service/admin_audit.go b/backend/internal/service/admin_audit.go index 3db2f0fb4..62e85e2c5 100644 --- a/backend/internal/service/admin_audit.go +++ b/backend/internal/service/admin_audit.go @@ -23,14 +23,14 @@ type AdminTaskPage struct { Tasks []model.Task `json:"tasks"` Total int64 `json:"total"` Page int `json:"page"` - Limit int `json:"limit"` + Limit int `json:"pageSize"` } type AdminAuditPage struct { Events []model.AdminAuditEvent `json:"events"` Total int64 `json:"total"` Page int `json:"page"` - Limit int `json:"limit"` + Limit int `json:"pageSize"` } func (s *Service) appendAdminAudit(actor *model.User, action string, targetType string, targetID string, summary string, metadata any) error { diff --git a/backend/internal/service/admin_storage.go b/backend/internal/service/admin_storage.go index 49a743ed2..73cbcf3de 100644 --- a/backend/internal/service/admin_storage.go +++ b/backend/internal/service/admin_storage.go @@ -45,7 +45,7 @@ type AdminResourcePage struct { Items []AdminStorageResourceView `json:"items"` Total int64 `json:"total"` Page int `json:"page"` - Limit int `json:"limit"` + Limit int `json:"pageSize"` } type AdminStorageStats struct { diff --git a/backend/internal/service/analytics.go b/backend/internal/service/analytics.go index a61d26e52..1fc400588 100644 --- a/backend/internal/service/analytics.go +++ b/backend/internal/service/analytics.go @@ -115,7 +115,7 @@ type APICallLogPage struct { Logs []model.ApiCallLog `json:"logs"` Total int64 `json:"total"` Page int `json:"page"` - Limit int `json:"limit"` + Limit int `json:"pageSize"` } type ModelPricingRequest struct { @@ -1010,7 +1010,9 @@ func (s *Service) enrichAPICallLogPayload(log *model.ApiCallLog, payload map[str nestedTaskID := "" if data, ok := payload["data"].(map[string]any); ok { if log.Capability == "video" && strings.Contains(log.Path, "/v1/video/generations") { - nestedTaskID = firstNonEmpty(stringField(data, "task_id"), stringField(data, "taskId")) + if extracted, err := firstJSONString(data, "task_id", "taskId"); err == nil { + nestedTaskID = extracted + } } for key, value := range data { if _, exists := payload[key]; !exists { @@ -1075,7 +1077,11 @@ func (s *Service) enrichAPICallLogPayload(log *model.ApiCallLog, payload map[str } log.CachedTokens = firstInt64(usageMetadata, "cachedContentTokenCount") } - log.ProviderRequestID = firstNonEmpty(nestedTaskID, stringField(payload, "task_id"), stringField(payload, "id"), stringField(payload, "request_id"), stringField(payload, "name"), log.ProviderRequestID) + if extracted, err := firstJSONString(payload, "task_id", "id", "request_id", "name"); err == nil { + log.ProviderRequestID = firstNonEmpty(nestedTaskID, extracted, log.ProviderRequestID) + } else { + log.ProviderRequestID = firstNonEmpty(nestedTaskID, log.ProviderRequestID) + } log.ProviderStatus = strings.ToLower(firstNonEmpty(stringField(payload, "status"), log.ProviderStatus)) if log.ProviderStatus == "failed" || log.ProviderStatus == "cancelled" || log.ProviderStatus == "expired" { log.Status = model.ApiCallStatusFailed diff --git a/backend/internal/service/announcement.go b/backend/internal/service/announcement.go index f90a89821..8164dab6f 100644 --- a/backend/internal/service/announcement.go +++ b/backend/internal/service/announcement.go @@ -33,7 +33,7 @@ type AnnouncementPage struct { Announcements []model.Announcement `json:"announcements"` Total int64 `json:"total"` Page int `json:"page"` - Limit int `json:"limit"` + Limit int `json:"pageSize"` } type UserAnnouncementFeed struct { diff --git a/backend/internal/service/canvas_share.go b/backend/internal/service/canvas_share.go index 6ca57333e..ac04f4ecd 100644 --- a/backend/internal/service/canvas_share.go +++ b/backend/internal/service/canvas_share.go @@ -325,16 +325,7 @@ func canvasResourceID(value string) string { if strings.HasPrefix(value, "resource:") { return validCanvasResourceID(strings.TrimPrefix(value, "resource:")) } - const prefix = "/api/resources/" - index := strings.Index(value, prefix) - if index < 0 { - return "" - } - remainder := value[index+len(prefix):] - if end := strings.IndexByte(remainder, '/'); end >= 0 { - remainder = remainder[:end] - } - return validCanvasResourceID(remainder) + return validCanvasResourceID(resourceIDFromFileURL(value)) } func validCanvasResourceID(value string) string { diff --git a/backend/internal/service/channel_models_test.go b/backend/internal/service/channel_models_test.go index 48e7c9512..083fd3c47 100644 --- a/backend/internal/service/channel_models_test.go +++ b/backend/internal/service/channel_models_test.go @@ -1,11 +1,21 @@ package service import ( + "encoding/json" "testing" "infinite-canvas/backend/internal/model" ) +func mustEncodeModelCapabilityConfig(t *testing.T, config *ModelCapabilityConfig) string { + t.Helper() + encoded, err := json.Marshal(config) + if err != nil { + t.Fatal(err) + } + return string(encoded) +} + func TestNormalizeChannelModelContract(t *testing.T) { channel := &model.ModelChannel{APIKey: "test-key"} modelKey, providerModelKey, capability, protocol, err := normalizeChannelModelContract(channel, ChannelModelRequest{ @@ -78,7 +88,11 @@ func TestSaveAdminChannelModelPersistsAndPublishesIcon(t *testing.T) { if stored.Icon != "OpenAI" { t.Fatalf("stored icon = %q, want OpenAI", stored.Icon) } - if public := svc.sanitizeChannelModel(saved); public.Icon != "OpenAI" { + public, err := svc.sanitizeChannelModel(saved) + if err != nil { + t.Fatal(err) + } + if public.Icon != "OpenAI" { t.Fatalf("public icon = %q, want OpenAI", public.Icon) } legacyPublic := publicChannel(channel, false, []model.ChannelModel{*saved}) @@ -87,6 +101,107 @@ func TestSaveAdminChannelModelPersistsAndPublishesIcon(t *testing.T) { } } +func TestSanitizeChannelModelRejectsCorruptCapabilityConfig(t *testing.T) { + svc := &Service{} + _, err := svc.sanitizeChannelModel(&model.ChannelModel{ + ID: "broken-image-model", + Capability: "image", + CapabilityConfigJSON: `{`, + }) + if err == nil { + t.Fatal("sanitizeChannelModel() should reject corrupt capability JSON") + } +} + +func TestSanitizeChannelModelAvailabilityRequiresValidPriceTier(t *testing.T) { + svc := &Service{} + channelModel := &model.ChannelModel{ + ID: "image-model", + ModelKey: "image-model", + Capability: "image", + Protocol: model.ChannelInterfaceOpenAIImage, + CapabilityConfigJSON: mustEncodeModelCapabilityConfig(t, DefaultModelCapabilityConfigForModel(string(model.ChannelInterfaceOpenAIImage), "image-model")), + PriceConfigured: true, + BillingMode: "fixed_request", + UnitPriceMicrocredits: 10, + } + + public, err := svc.sanitizeChannelModel(channelModel) + if err != nil { + t.Fatal(err) + } + if public.Available { + t.Fatal("legacy scalar price must not make a system channel model available") + } + + channelModel.PriceTiers = []model.ChannelModelPriceTier{{ + ID: "tier-1", BillingMode: "fixed_request", UnitPriceMicrocredits: 10, + Enabled: true, PriceConfigured: true, + }} + public, err = svc.sanitizeChannelModel(channelModel) + if err != nil { + t.Fatal(err) + } + if !public.Available || len(public.PriceTiers) != 1 { + t.Fatalf("valid price tier was not published: %#v", public) + } +} + +func TestChannelModelMatchesIntentUsesPersistedCapabilityContract(t *testing.T) { + svc := &Service{} + profile := DefaultModelCapabilityConfigForModel(string(model.ChannelInterfaceOpenAIImage), "image-model") + profile.Image.References.MaxImages = 1 + channelModel := &model.ChannelModel{ + ID: "image-model", + ModelKey: "image-model", + Capability: "image", + Protocol: model.ChannelInterfaceOpenAIImage, + CapabilityConfigJSON: mustEncodeModelCapabilityConfig(t, profile), + } + + matched, err := svc.channelModelMatchesIntent(channelModel, &ModelRequestIntent{ + Capability: "image", + Inputs: map[string]int{"image": 2}, + }) + if err != nil { + t.Fatal(err) + } + if matched { + t.Fatal("request exceeding the persisted reference-image limit should not match") + } + + channelModel.CapabilityConfigJSON = `{` + if _, err := svc.channelModelMatchesIntent(channelModel, &ModelRequestIntent{Capability: "image"}); err == nil { + t.Fatal("corrupt persisted capability config should fail closed") + } +} + +func TestPublicSystemChannelCatalogIsolatesCorruptModel(t *testing.T) { + svc, db := newChannelModelTestService(t) + channel := model.ModelChannel{ID: "system-channel", Scope: model.ChannelScopeSystem, Enabled: true, Name: "系统渠道"} + if err := db.Create(&channel).Error; err != nil { + t.Fatal(err) + } + validConfig := mustEncodeModelCapabilityConfig(t, DefaultModelCapabilityConfigForModel(string(model.ChannelInterfaceOpenAIImage), "valid-model")) + models := []model.ChannelModel{ + {ID: "valid-model", ChannelID: channel.ID, ModelKey: "valid-model", Capability: "image", Protocol: model.ChannelInterfaceOpenAIImage, CapabilityConfigJSON: validConfig, Enabled: true}, + {ID: "broken-model", ChannelID: channel.ID, ModelKey: "broken-model", Capability: "image", Protocol: model.ChannelInterfaceOpenAIImage, CapabilityConfigJSON: `{`, Enabled: true}, + } + for index := range models { + if err := db.Create(&models[index]).Error; err != nil { + t.Fatal(err) + } + } + + catalog, err := svc.publicSystemChannelCatalog(nil) + if err != nil { + t.Fatal(err) + } + if len(catalog) != 1 || len(catalog[0].Models) != 1 || catalog[0].Models[0].ID != "valid-model" { + t.Fatalf("catalog should retain only the valid model: %#v", catalog) + } +} + func TestImageTestDefaultsUseModelCapability(t *testing.T) { tests := []struct { name string diff --git a/backend/internal/service/channel_presentation_test.go b/backend/internal/service/channel_presentation_test.go index 5a3add09c..b99303b91 100644 --- a/backend/internal/service/channel_presentation_test.go +++ b/backend/internal/service/channel_presentation_test.go @@ -19,7 +19,10 @@ func TestChannelPresentationAliasAndPublicOrder(t *testing.T) { if err := db.Create(&channel).Error; err != nil { t.Fatal(err) } - cm := model.ChannelModel{ID: id + "-model", ChannelID: id, ModelKey: id, Enabled: true, PriceConfigured: true, Capability: "image"} + cm := model.ChannelModel{ + ID: id + "-model", ChannelID: id, ModelKey: id, Enabled: true, PriceConfigured: true, Capability: "image", + CapabilityConfigJSON: mustEncodeModelCapabilityConfig(t, DefaultModelCapabilityConfigForModel("", id)), + } if err := db.Create(&cm).Error; err != nil { t.Fatal(err) } @@ -96,7 +99,12 @@ func TestChannelModelSortPreservesConfigurationAndOwnership(t *testing.T) { } } for i, id := range []string{"z", "a", "b"} { - cm := model.ChannelModel{ID: id, ChannelID: "channel", ModelKey: id, ProviderModelKey: "provider-" + id, Enabled: true, Capability: "image", PriceConfigured: true, UnitPriceMicrocredits: 123, PriceVersion: 7, CapabilityVersion: 4, CapabilityConfigJSON: `{}`, CreatedAt: time.Unix(int64(i), 0)} + cm := model.ChannelModel{ + ID: id, ChannelID: "channel", ModelKey: id, ProviderModelKey: "provider-" + id, Enabled: true, + Capability: "image", PriceConfigured: true, UnitPriceMicrocredits: 123, PriceVersion: 7, CapabilityVersion: 4, + CapabilityConfigJSON: mustEncodeModelCapabilityConfig(t, DefaultModelCapabilityConfigForModel("", id)), + CreatedAt: time.Unix(int64(i), 0), + } if err := db.Create(&cm).Error; err != nil { t.Fatal(err) } diff --git a/backend/internal/service/desktop_local_channel_execution_test.go b/backend/internal/service/desktop_local_channel_execution_test.go index 9baf83682..06a9f0904 100644 --- a/backend/internal/service/desktop_local_channel_execution_test.go +++ b/backend/internal/service/desktop_local_channel_execution_test.go @@ -51,7 +51,12 @@ func TestLocalExecutionPathsApplyLoopbackPolicyBeyondConfigResolution(t *testing config: providerConfig{BaseURL: upstream.URL + "/v1", APIKey: "key", Model: "video-model", InterfaceType: string(model.ChannelInterfaceNewAPIChannel2), AllowLocalChannel: true}, path: "/v1/video/generations/recovery-task", runRequest: func(ctx context.Context, config providerConfig) error { - _, _, err := queryNewAPIChannel2VideoTask(ctx, canvasGenerationInput{Mode: "video", Config: config}, "recovery-task") + ctx = ensureOfficialProtocolAdapter(ctx, config.InterfaceType) + adapter, ok := declarativeProtocolAdapterForContext(ctx, config.InterfaceType) + if !ok { + return fmt.Errorf("official newapi-channel-2 adapter missing") + } + _, _, err := queryProtocolAdapterVideoTask(ctx, canvasGenerationInput{Mode: "video", Config: config}, adapter, "recovery-task") return err }, }, diff --git a/backend/internal/service/error_codes.go b/backend/internal/service/error_codes.go new file mode 100644 index 000000000..5d93ef98c --- /dev/null +++ b/backend/internal/service/error_codes.go @@ -0,0 +1,68 @@ +package service + +// 业务信封 code:成功为 0;失败默认等于 HTTP 状态。需要区分同一状态的不同原因时,用 status*100+n。 +// 不使用与 HTTP 无关的 1001/2001 号段,避免和现有 42901 以及前端按 status/code 判断重试打架。 +const ( + CodeOK = 0 + + CodeInvalidArgument = 400 + CodeUnauthorized = 401 + CodeForbidden = 403 + CodeNotFound = 404 + CodeConflict = 409 + CodeTooManyRequests = 429 + CodeInternal = 500 + CodeBadGateway = 502 + CodeUnavailable = 503 + CodeTimeout = 504 + + CodeQuotaExceeded = 40301 + CodeIdempotencyConflict = 40901 + CodeRateLimited = 42901 +) + +// ErrorReason 是稳定机器可读原因,前端应判断 reason 而不是解析 msg。 +type ErrorReason string + +const ( + ReasonInvalidArgument ErrorReason = "invalid_argument" + ReasonUnauthorized ErrorReason = "unauthorized" + ReasonForbidden ErrorReason = "forbidden" + ReasonNotFound ErrorReason = "not_found" + ReasonConflict ErrorReason = "conflict" + ReasonFailedPrecondition ErrorReason = "failed_precondition" + ReasonQuotaExceeded ErrorReason = "quota_exceeded" + ReasonRateLimited ErrorReason = "rate_limited" + ReasonUnavailable ErrorReason = "unavailable" + ReasonTimeout ErrorReason = "timeout" + ReasonInternal ErrorReason = "internal" + ReasonBadGateway ErrorReason = "bad_gateway" +) + +func ReasonForStatus(status int) ErrorReason { + switch status { + case CodeInvalidArgument: + return ReasonInvalidArgument + case CodeUnauthorized: + return ReasonUnauthorized + case CodeForbidden: + return ReasonForbidden + case CodeNotFound: + return ReasonNotFound + case CodeConflict: + return ReasonConflict + case CodeTooManyRequests: + return ReasonRateLimited + case CodeBadGateway: + return ReasonBadGateway + case CodeUnavailable: + return ReasonUnavailable + case CodeTimeout: + return ReasonTimeout + default: + if status >= 500 { + return ReasonInternal + } + return ReasonInvalidArgument + } +} diff --git a/backend/internal/service/errors.go b/backend/internal/service/errors.go index 729edc966..44a127ce2 100644 --- a/backend/internal/service/errors.go +++ b/backend/internal/service/errors.go @@ -7,6 +7,7 @@ import "fmt" type AppError struct { Status int Code int + Reason ErrorReason Message string Retryable bool Cause error @@ -27,11 +28,21 @@ func (e *AppError) Unwrap() error { } func NewAppError(status int, message string) *AppError { - return &AppError{Status: status, Message: message} + return &AppError{Status: status, Code: status, Reason: ReasonForStatus(status), Message: message} } func WrapAppError(status int, message string, cause error) *AppError { - return &AppError{Status: status, Message: message, Cause: cause} + err := NewAppError(status, message) + err.Cause = cause + return err +} + +func RateLimited(message string) *AppError { + return &AppError{Status: CodeTooManyRequests, Code: CodeRateLimited, Reason: ReasonRateLimited, Message: message, Retryable: true} +} + +func QuotaExceeded(message string) *AppError { + return &AppError{Status: CodeForbidden, Code: CodeQuotaExceeded, Reason: ReasonQuotaExceeded, Message: message} } // ModelErrorCode 定义模型相关的错误码 @@ -52,8 +63,8 @@ const ( ErrCodeInvalidModelSelection ModelErrorCode = "invalid_model_selection" ) -// ModelError 模型相关的错误,包含错误码和详细信息 -// 继承 AppError 以保持兼容性 +// ModelError 为模型选择、路由和供应商调用提供稳定的机器可读错误码。 +// 嵌入 AppError,使统一 HTTP 投影仍能通过 errors.As 读取状态、reason 和安全文案。 type ModelError struct { *AppError ErrorCode ModelErrorCode @@ -69,8 +80,10 @@ func (e *ModelError) Error() string { // NewModelError 创建模型错误 func NewModelError(code ModelErrorCode, message string) *ModelError { + err := NewAppError(400, message) + err.Reason = ErrorReason(code) return &ModelError{ - AppError: NewAppError(400, message), + AppError: err, ErrorCode: code, Details: make(map[string]any), } diff --git a/backend/internal/service/errors_test.go b/backend/internal/service/errors_test.go index 2ccc70acc..1c4fba46f 100644 --- a/backend/internal/service/errors_test.go +++ b/backend/internal/service/errors_test.go @@ -25,7 +25,14 @@ func TestAppErrorPreservesSafeProjectionAndCause(t *testing.T) { func TestAuthErrorAliasRemainsCompatible(t *testing.T) { var err *AuthError = BadAuthRequest("请求参数错误") var appErr *AppError - if !errors.As(err, &appErr) || appErr.Status != 400 || appErr.Message != "请求参数错误" { + if !errors.As(err, &appErr) || appErr.Status != 400 || appErr.Code != 400 || appErr.Reason != ReasonInvalidArgument || appErr.Message != "请求参数错误" { t.Fatalf("AuthError compatibility = %#v", err) } } + +func TestQuotaExceededUsesStableCodeAndReason(t *testing.T) { + err := QuotaExceeded("账号素材数量已达到 1 个上限") + if err.Status != 403 || err.Code != CodeQuotaExceeded || err.Reason != ReasonQuotaExceeded { + t.Fatalf("QuotaExceeded = %#v", err) + } +} diff --git a/backend/internal/service/finance.go b/backend/internal/service/finance.go index b08eb70b9..9935d8fa0 100644 --- a/backend/internal/service/finance.go +++ b/backend/internal/service/finance.go @@ -24,7 +24,7 @@ type WalletSummary struct { Entries []model.CreditLedgerEntry `json:"entries"` Total int64 `json:"total"` Page int `json:"page"` - Limit int `json:"limit"` + Limit int `json:"pageSize"` Policy PublicCreditPolicy `json:"policy"` } @@ -32,7 +32,7 @@ type RedeemBatchPage struct { Batches []model.RedeemBatch `json:"batches"` Total int64 `json:"total"` Page int `json:"page"` - Limit int `json:"limit"` + Limit int `json:"pageSize"` } type AdminRedeemCodeDetail struct { @@ -55,14 +55,14 @@ type AdminRedeemCodePage struct { PlaintextAvailable bool `json:"plaintextAvailable"` Total int64 `json:"total"` Page int `json:"page"` - Limit int `json:"limit"` + Limit int `json:"pageSize"` } type BillingOrderPage struct { Orders []model.BillingOrder `json:"orders"` Total int64 `json:"total"` Page int `json:"page"` - Limit int `json:"limit"` + Limit int `json:"pageSize"` } type CreateRedeemBatchRequest struct { diff --git a/backend/internal/service/json_fields.go b/backend/internal/service/json_fields.go new file mode 100644 index 000000000..9dda70244 --- /dev/null +++ b/backend/internal/service/json_fields.go @@ -0,0 +1,59 @@ +package service + +import ( + "fmt" + "strings" +) + +// optionalJSONString reads an optional untyped JSON string. +// Missing or null yields "". A present non-string is a protocol error and is not coerced. +func optionalJSONString(payload map[string]any, key string) (string, error) { + if payload == nil { + return "", nil + } + value, ok := payload[key] + if !ok || value == nil { + return "", nil + } + text, ok := value.(string) + if !ok { + return "", fmt.Errorf("field %s: expected string, got %T", key, value) + } + return text, nil +} + +// requireJSONString reads a required non-empty JSON string. +func requireJSONString(payload map[string]any, key string) (string, error) { + text, err := optionalJSONString(payload, key) + if err != nil { + return "", err + } + text = strings.TrimSpace(text) + if text == "" { + return "", fmt.Errorf("missing field: %s", key) + } + return text, nil +} + +// firstJSONString returns the first present non-empty string among keys. +// Missing keys are skipped. A present non-string is a protocol error unless a later key supplies a valid string. +func firstJSONString(payload map[string]any, keys ...string) (string, error) { + var typeErr error + for _, key := range keys { + text, err := optionalJSONString(payload, key) + if err != nil { + if typeErr == nil { + typeErr = err + } + continue + } + text = strings.TrimSpace(text) + if text != "" { + return text, nil + } + } + if typeErr != nil { + return "", typeErr + } + return "", nil +} diff --git a/backend/internal/service/json_fields_test.go b/backend/internal/service/json_fields_test.go new file mode 100644 index 000000000..999c60f0e --- /dev/null +++ b/backend/internal/service/json_fields_test.go @@ -0,0 +1,85 @@ +package service + +import ( + "strings" + "testing" +) + +func TestOptionalJSONStringRejectsWrongType(t *testing.T) { + payload := map[string]any{"id": 12.0, "status": "ok"} + + text, err := optionalJSONString(payload, "status") + if err != nil || text != "ok" { + t.Fatalf("optionalJSONString(status) = %q, %v", text, err) + } + + text, err = optionalJSONString(payload, "missing") + if err != nil || text != "" { + t.Fatalf("optionalJSONString(missing) = %q, %v", text, err) + } + + _, err = optionalJSONString(payload, "id") + if err == nil || !strings.Contains(err.Error(), "expected string") || !strings.Contains(err.Error(), "float64") { + t.Fatalf("optionalJSONString(id) error = %v", err) + } +} + +func TestRequireJSONString(t *testing.T) { + payload := map[string]any{"id": "task-1", "empty": " "} + + text, err := requireJSONString(payload, "id") + if err != nil || text != "task-1" { + t.Fatalf("requireJSONString(id) = %q, %v", text, err) + } + + _, err = requireJSONString(payload, "empty") + if err == nil || !strings.Contains(err.Error(), "missing field: empty") { + t.Fatalf("requireJSONString(empty) error = %v", err) + } + + _, err = requireJSONString(map[string]any{"id": true}, "id") + if err == nil || !strings.Contains(err.Error(), "expected string") { + t.Fatalf("requireJSONString(bool) error = %v", err) + } +} + +func TestFirstJSONStringPrefersValidStringOverWrongType(t *testing.T) { + payload := map[string]any{"id": 99.0, "task_id": "abc"} + + text, err := firstJSONString(payload, "id", "task_id") + if err != nil || text != "abc" { + t.Fatalf("firstJSONString() = %q, %v", text, err) + } + + _, err = firstJSONString(payload, "id", "request_id") + if err == nil || !strings.Contains(err.Error(), "field id") { + t.Fatalf("firstJSONString() error = %v", err) + } + + text, err = firstJSONString(payload, "missing", "also-missing") + if err != nil || text != "" { + t.Fatalf("firstJSONString(missing) = %q, %v", text, err) + } +} + +func TestExtractProviderTaskID(t *testing.T) { + id, err := extractProviderTaskID([]byte(`{"id":12,"task_id":"abc"}`)) + if err != nil || id != "abc" { + t.Fatalf("extractProviderTaskID() = %q, %v", id, err) + } + + _, err = extractProviderTaskID([]byte(`{"id":12}`)) + if err == nil || !strings.Contains(err.Error(), "expected string") || !strings.Contains(err.Error(), "float64") { + t.Fatalf("extractProviderTaskID(numeric id) error = %v", err) + } + + id, err = extractProviderTaskID([]byte(`{"data":{"task_id":"nested"}}`)) + if err != nil || id != "nested" { + t.Fatalf("extractProviderTaskID(nested) = %q, %v", id, err) + } + + id, err = extractProviderTaskID([]byte(`{"name":"task-from-name"}`)) + if err != nil || id != "task-from-name" { + t.Fatalf("extractProviderTaskID(name) = %q, %v", id, err) + } +} diff --git a/backend/internal/service/logical_models.go b/backend/internal/service/logical_models.go index 1f745effd..65dec58e2 100644 --- a/backend/internal/service/logical_models.go +++ b/backend/internal/service/logical_models.go @@ -480,7 +480,10 @@ func (s *Service) SaveAdminLogicalModel(actor *model.User, id string, req Logica return nil, err } s.invalidateRouteCatalog() - _ = s.appendAdminAudit(actor, map[bool]string{true: "logical_model.create", false: "logical_model.update"}[creating], "logical_model", item.ID, "保存前台模型及供应线路", map[string]any{"revisionId": revision.ID, "routeCount": len(routes)}) + // 模型、版本和路由属于后台关键配置,保存成功却缺失审计记录不能静默返回成功。 + if err := s.appendAdminAudit(actor, map[bool]string{true: "logical_model.create", false: "logical_model.update"}[creating], "logical_model", item.ID, "保存前台模型及供应线路", map[string]any{"revisionId": revision.ID, "routeCount": len(routes)}); err != nil { + return nil, err + } graph, err := s.repo.LogicalModelGraph(item.ID, true) if err != nil { return nil, err @@ -640,8 +643,14 @@ func (s *Service) logicalModelBundle(actor *model.User, id string, req LogicalMo if err != nil { return nil, nil, nil, false, err } - specJSON, _ := json.Marshal(req.CapabilitySpec) - defaultsJSON, _ := json.Marshal(defaultMap(req.DefaultOptions)) + specJSON, err := json.Marshal(req.CapabilitySpec) + if err != nil { + return nil, nil, nil, false, fmt.Errorf("序列化前台模型能力合同失败:%w", err) + } + defaultsJSON, err := json.Marshal(defaultMap(req.DefaultOptions)) + if err != nil { + return nil, nil, nil, false, fmt.Errorf("序列化前台模型默认参数失败:%w", err) + } revision := &model.LogicalModelRevision{ID: revisionID, LogicalModelID: item.ID, CapabilitySpecJSON: string(specJSON), DefaultOptionsJSON: string(defaultsJSON), CreatedBy: actor.ID, CreatedAt: time.Now()} routes := make([]model.LogicalModelRoute, 0, len(req.Routes)) seenChannelModels := make(map[string]bool, len(req.Routes)) diff --git a/backend/internal/service/model_capability.go b/backend/internal/service/model_capability.go index 8c8457876..8a723bc42 100644 --- a/backend/internal/service/model_capability.go +++ b/backend/internal/service/model_capability.go @@ -277,6 +277,30 @@ func DecodeModelCapabilityConfig(raw string) (*ModelCapabilityConfig, error) { return &value, nil } +// normalizedChannelModelCapability 从持久化记录恢复渠道模型的权威能力合同。 +// 目录读取可以选择隔离损坏记录;任务创建等写路径必须把错误向上返回并失败关闭。 +func normalizedChannelModelCapability(channelModel *model.ChannelModel) (*ModelCapabilityConfig, error) { + if channelModel == nil { + return nil, errors.New("渠道模型为空") + } + capability := normalizeCapability(channelModel.Capability) + if capability == "audio" { + return nil, nil + } + if capability != "text" && capability != "image" && capability != "video" { + return nil, fmt.Errorf("不支持的渠道模型能力:%s", channelModel.Capability) + } + config, err := DecodeModelCapabilityConfig(channelModel.CapabilityConfigJSON) + if err != nil { + return nil, fmt.Errorf("解析渠道模型能力配置失败:%w", err) + } + normalized, err := NormalizeModelCapabilityConfigForModel(capability, string(channelModel.Protocol), firstNonEmpty(channelModel.ProviderModelKey, channelModel.ModelKey), config) + if err != nil { + return nil, err + } + return normalized, nil +} + func NormalizeModelCapabilityConfig(capability string, protocol string, input *ModelCapabilityConfig) (*ModelCapabilityConfig, error) { return NormalizeModelCapabilityConfigForModel(capability, protocol, "", input) } diff --git a/backend/internal/service/model_catalog.go b/backend/internal/service/model_catalog.go index 17d97799c..a5f1cf5f1 100644 --- a/backend/internal/service/model_catalog.go +++ b/backend/internal/service/model_catalog.go @@ -2,20 +2,25 @@ package service import ( "encoding/json" + "fmt" + "log" + "infinite-canvas/backend/internal/model" ) -// ModelCatalogSource 表示模型目录来源 +// ModelCatalogSource 决定 ModelCatalogResponse 中哪一个集合具有语义。 +// frontend 与 system 的数据形状互斥,调用方不能把缺失集合解释成空目录。 type ModelCatalogSource string const ( - // ModelCatalogSourceFrontend 前台模型虚拟渠道 + // ModelCatalogSourceFrontend 表示目录由前台逻辑模型组成。 ModelCatalogSourceFrontend ModelCatalogSource = "frontend" - // ModelCatalogSourceSystem 脱敏的系统渠道模型 + // ModelCatalogSourceSystem 表示目录由脱敏后的系统渠道与渠道模型组成。 ModelCatalogSourceSystem ModelCatalogSource = "system" ) -// ModelCatalogResponse 统一模型目录响应 +// ModelCatalogResponse 是创作端模型选择的统一读模型。 +// Source=frontend 时读取 Models;Source=system 时读取 Channels。 type ModelCatalogResponse struct { Source ModelCatalogSource `json:"source"` Models []PublicLogicalModel `json:"models,omitempty"` @@ -61,8 +66,8 @@ type PublicChannelModelPriceTier struct { CachedTokenPriceMicrocredits int64 `json:"cachedTokenPriceMicrocredits"` } -// ModelCatalog 返回统一的模型目录 -// 根据 frontendModelsEnabled 开关返回前台模型或系统渠道模型 +// ModelCatalog 按功能开关返回互斥的数据形状:frontend 使用 Models,system 使用 Channels。 +// 系统渠道目录只负责安全发布可解释的读模型;任务创建仍会用持久化能力与价格档再次强校验。 func (s *Service) ModelCatalog(intent *ModelRequestIntent) (*ModelCatalogResponse, error) { frontendEnabled, err := s.FeatureEnabled(FeatureFrontendModels) if err != nil { @@ -70,7 +75,6 @@ func (s *Service) ModelCatalog(intent *ModelRequestIntent) (*ModelCatalogRespons } if frontendEnabled { - // 返回前台模型目录 models, err := s.PublicLogicalModels(intent) if err != nil { return nil, err @@ -81,7 +85,6 @@ func (s *Service) ModelCatalog(intent *ModelRequestIntent) (*ModelCatalogRespons }, nil } - // 返回脱敏的系统渠道模型目录 channels, err := s.publicSystemChannelCatalog(intent) if err != nil { return nil, err @@ -92,8 +95,8 @@ func (s *Service) ModelCatalog(intent *ModelRequestIntent) (*ModelCatalogRespons }, nil } -// publicSystemChannelCatalog 返回脱敏后的系统渠道模型目录 -// 只包含普通用户可见的信息,不包含密钥、Base URL、供应商信息等 +// publicSystemChannelCatalog 组装普通用户可见的系统渠道读模型,不暴露密钥、Base URL 等执行凭证。 +// 这是读展示路径:单个损坏模型被隔离并记录诊断;仓储查询失败仍整体返回错误,避免伪装成空目录。 func (s *Service) publicSystemChannelCatalog(intent *ModelRequestIntent) ([]PublicChannelCatalog, error) { channels, err := s.repo.SystemChannels(true) if err != nil { @@ -117,12 +120,24 @@ func (s *Service) publicSystemChannelCatalog(intent *ModelRequestIntent) ([]Publ continue } - // 如果提供了意图,进行能力过滤 - if intent != nil && !s.channelModelMatchesIntent(&cm, intent) { - continue + // 目录是读路径:单个损坏模型应被隔离并记录诊断,不能拖垮其余可用模型; + // 同一记录进入任务创建写路径时会失败关闭,不会绕过能力合同。 + if intent != nil { + matched, matchErr := s.channelModelMatchesIntent(&cm, intent) + if matchErr != nil { + log.Printf("system channel model omitted from catalog id=%s: invalid capability: %v", cm.ID, matchErr) + continue + } + if !matched { + continue + } } - publicModel := s.sanitizeChannelModel(&cm) + publicModel, sanitizeErr := s.sanitizeChannelModel(&cm) + if sanitizeErr != nil { + log.Printf("system channel model omitted from catalog id=%s: %v", cm.ID, sanitizeErr) + continue + } publicModels = append(publicModels, publicModel) } @@ -140,12 +155,15 @@ func (s *Service) publicSystemChannelCatalog(intent *ModelRequestIntent) ([]Publ return result, nil } -// sanitizeChannelModel 脱敏渠道模型,只保留用户可见的信息 -func (s *Service) sanitizeChannelModel(cm *model.ChannelModel) PublicChannelModel { - // 价格档已经在 cm.PriceTiers 中加载 +// sanitizeChannelModel 脱敏渠道模型,只保留用户可见且可执行的信息。 +// 能力 JSON 无效时返回错误,由目录聚合层执行“隔离 + 告警”,禁止发布缺失能力合同的模型。 +func (s *Service) sanitizeChannelModel(cm *model.ChannelModel) (PublicChannelModel, error) { + if cm == nil { + return PublicChannelModel{}, fmt.Errorf("渠道模型为空") + } + // 仓储层已预加载价格档;这里只发布当前启用且价格字段自洽的活动档位。 priceTiers := cm.PriceTiers - // 转换为公开的价格档 publicTiers := make([]PublicChannelModelPriceTier, 0, len(priceTiers)) for _, tier := range priceTiers { if !tier.Enabled || !tier.PriceConfigured || !ValidatePriceTierPrice(&tier, cm.Capability, cm.Protocol) { @@ -164,19 +182,18 @@ func (s *Service) sanitizeChannelModel(cm *model.ChannelModel) PublicChannelMode }) } - // 计算价格展示 + // 展示价格和 Available 必须从同一批有效档位派生,不能回退旧标量价格制造“可用”假象。 pricingMode, displayPrice, priceLabel := computeChannelModelPriceDisplay(cm, publicTiers) - // 解析能力配置 var capabilityConfig map[string]any - if cm.CapabilityConfigJSON != "" { - config, _ := DecodeModelCapabilityConfig(cm.CapabilityConfigJSON) - if config != nil { - normalized, _ := NormalizeModelCapabilityConfigForModel(cm.Capability, string(cm.Protocol), firstNonEmpty(cm.ProviderModelKey, cm.ModelKey), config) - if normalized != nil { - // 转换为 map[string]any - capabilityConfig = modelCapabilityConfigToMap(normalized) - } + normalized, err := normalizedChannelModelCapability(cm) + if err != nil { + return PublicChannelModel{}, err + } + if normalized != nil { + capabilityConfig, err = modelCapabilityConfigToMap(normalized) + if err != nil { + return PublicChannelModel{}, fmt.Errorf("投影渠道模型能力配置失败:%w", err) } } @@ -193,11 +210,12 @@ func (s *Service) sanitizeChannelModel(cm *model.ChannelModel) PublicChannelMode PricingMode: pricingMode, DisplayPrice: displayPrice, PriceLabel: priceLabel, - Available: len(publicTiers) > 0 || (len(priceTiers) == 0 && HasValidPrice(cm)), - } + Available: len(publicTiers) > 0, + }, nil } -// computeChannelModelPriceDisplay 计算渠道模型的价格展示信息 +// computeChannelModelPriceDisplay 从已过滤的公开价格档生成展示信息。 +// 无有效档位时明确标记未配置;多档位不猜测用户最终规格。 func computeChannelModelPriceDisplay(cm *model.ChannelModel, priceTiers []PublicChannelModelPriceTier) (string, *int64, string) { if len(priceTiers) == 0 { return "provider", nil, "未配置" @@ -229,27 +247,42 @@ func getChannelTierDisplayPrice(tier PublicChannelModelPriceTier) int64 { return 0 } -// channelModelMatchesIntent 检查渠道模型是否匹配意图 -func (s *Service) channelModelMatchesIntent(cm *model.ChannelModel, intent *ModelRequestIntent) bool { - if intent.Capability != "" && cm.Capability != intent.Capability { - return false +// channelModelMatchesIntent 使用与任务 admission 相同的服务端能力合同过滤目录。 +// 音频能力当前没有可编辑的细分能力 JSON,只校验能力类型;其参数仍由 provider 专用校验负责。 +func (s *Service) channelModelMatchesIntent(cm *model.ChannelModel, intent *ModelRequestIntent) (bool, error) { + if cm == nil || intent == nil { + return true, nil } - // 这里可以添加更多的能力匹配逻辑 - return true + if normalizeCapability(intent.Capability) != "" && normalizeCapability(cm.Capability) != normalizeCapability(intent.Capability) { + return false, nil + } + if normalizeCapability(cm.Capability) == "audio" { + return true, nil + } + config, err := normalizedChannelModelCapability(cm) + if err != nil { + return false, err + } + spec, err := CapabilitySpecFromModelCapabilityConfig(config, cm.Capability) + if err != nil { + return false, err + } + match := MatchCapability(spec, *intent) + return match.Matched, nil } // modelCapabilityConfigToMap 将 ModelCapabilityConfig 转换为 map[string]any -func modelCapabilityConfigToMap(config *ModelCapabilityConfig) map[string]any { +func modelCapabilityConfigToMap(config *ModelCapabilityConfig) (map[string]any, error) { if config == nil { - return nil + return nil, nil } encoded, err := json.Marshal(config) if err != nil { - return nil + return nil, err } var result map[string]any if err := json.Unmarshal(encoded, &result); err != nil { - return nil + return nil, err } - return result + return result, nil } diff --git a/backend/internal/service/model_router.go b/backend/internal/service/model_router.go index f37e82b2a..017783066 100644 --- a/backend/internal/service/model_router.go +++ b/backend/internal/service/model_router.go @@ -630,6 +630,9 @@ func (s *Service) loadRouteCatalog() (*routeCatalogSnapshot, error) { return snapshot, nil } +// ResolveLogicalModel 将创作意图解析为一次可执行的路由快照。 +// 解析同时约束能力合同、启用状态、价格档和渠道协议;调用方不得在解析完成后自行替换其中任一供应链字段, +// 否则会出现“目录显示可用、任务实际走另一条线路”的配置漂移。 func (s *Service) ResolveLogicalModel(logicalModelID string, intent ModelRequestIntent) (*RoutedModel, error) { snapshot, err := s.routeCatalogSnapshot() if err != nil { diff --git a/backend/internal/service/pagination_json_test.go b/backend/internal/service/pagination_json_test.go new file mode 100644 index 000000000..f9d1949e1 --- /dev/null +++ b/backend/internal/service/pagination_json_test.go @@ -0,0 +1,33 @@ +package service + +import ( + "encoding/json" + "testing" +) + +func TestPaginationJSONUsesPageSizeCamelCase(t *testing.T) { + payloads := []any{ + SkillList{TotalCount: 3, HasMore: true, NextOffset: 20, Page: 1, PageSize: 20}, + AdminUserPage{Total: 1, Page: 1, Limit: 20}, + WalletSummary{Total: 1, Page: 1, Limit: 30}, + APICallLogPage{Total: 1, Page: 1, Limit: 50}, + } + for _, payload := range payloads { + body, err := json.Marshal(payload) + if err != nil { + t.Fatal(err) + } + var object map[string]json.RawMessage + if err := json.Unmarshal(body, &object); err != nil { + t.Fatal(err) + } + for _, key := range []string{"page_size", "total_count", "has_more", "next_offset", "limit"} { + if _, found := object[key]; found { + t.Fatalf("%T still encodes %s: %s", payload, key, body) + } + } + if _, found := object["pageSize"]; !found { + t.Fatalf("%T missing pageSize: %s", payload, body) + } + } +} diff --git a/backend/internal/service/payment.go b/backend/internal/service/payment.go index 01cc6954a..04a99d755 100644 --- a/backend/internal/service/payment.go +++ b/backend/internal/service/payment.go @@ -104,7 +104,7 @@ type AdminPaymentOrderPage struct { Orders []PaymentOrderView `json:"orders"` Total int64 `json:"total"` Page int `json:"page"` - Limit int `json:"limit"` + Limit int `json:"pageSize"` } func (s *Service) PaymentNotificationResponse(providerID string, success bool) (int, string, string) { diff --git a/backend/internal/service/payment_reconciliation.go b/backend/internal/service/payment_reconciliation.go index 2a31cf0d9..9a961ea8f 100644 --- a/backend/internal/service/payment_reconciliation.go +++ b/backend/internal/service/payment_reconciliation.go @@ -26,7 +26,7 @@ type AdminPaymentReconciliationPage struct { Runs []model.PaymentReconciliationRun `json:"runs"` Total int64 `json:"total"` Page int `json:"page"` - Limit int `json:"limit"` + Limit int `json:"pageSize"` } type AdminPaymentReconciliationItemPage struct { @@ -34,7 +34,7 @@ type AdminPaymentReconciliationItemPage struct { Items []model.PaymentReconciliationItem `json:"items"` Total int64 `json:"total"` Page int `json:"page"` - Limit int `json:"limit"` + Limit int `json:"pageSize"` } func (s *Service) RunPaymentReconciliation(ctx context.Context, actor *model.User, request RunPaymentReconciliationRequest) (*model.PaymentReconciliationRun, error) { diff --git a/backend/internal/service/project_character.go b/backend/internal/service/project_character.go index 1162332c9..b5bc79e74 100644 --- a/backend/internal/service/project_character.go +++ b/backend/internal/service/project_character.go @@ -268,8 +268,14 @@ func (s *Service) finalizeCharacterTurnaroundTask(task model.Task, result map[st if err != nil || resource.Kind != "image" || resource.Status != model.ResourceStatusReady { return false, BadAuthRequest("三视图任务生成的图片资源不可用") } - sheetMetadata, _ := json.Marshal(map[string]any{"prompt": task.Prompt, "source": "character_turnaround"}) - primaryMetadata, _ := json.Marshal(map[string]any{"source": "turnaround_sheet"}) + sheetMetadata, err := json.Marshal(map[string]any{"prompt": task.Prompt, "source": "character_turnaround"}) + if err != nil { + return false, fmt.Errorf("序列化三视图表现元数据失败:%w", err) + } + primaryMetadata, err := json.Marshal(map[string]any{"source": "turnaround_sheet"}) + if err != nil { + return false, fmt.Errorf("序列化角色主表现元数据失败:%w", err) + } now := time.Now() representations := []model.AssetRepresentation{ {ID: newID(), TaskID: task.ID, ResourceID: resourceID, MediaType: "image", Role: "turnaround_sheet", MetadataJSON: string(sheetMetadata), CreatedAt: now}, diff --git a/backend/internal/service/project_workflow.go b/backend/internal/service/project_workflow.go index ed0281ca1..ac1adce65 100644 --- a/backend/internal/service/project_workflow.go +++ b/backend/internal/service/project_workflow.go @@ -550,14 +550,8 @@ func findTaskOutputResource(value any, mediaType string) (string, string) { if strings.HasPrefix(text, "resource:") { return strings.TrimPrefix(text, "resource:"), mediaType } - if strings.HasPrefix(text, "/api/resources/") { - id := strings.TrimPrefix(text, "/api/resources/") - if slash := strings.IndexByte(id, '/'); slash >= 0 { - id = id[:slash] - } - if id != "" { - return id, mediaType - } + if id := resourceIDFromFileURL(text); id != "" { + return id, mediaType } } } diff --git a/backend/internal/service/prompt_template.go b/backend/internal/service/prompt_template.go index 7b243ad27..626034c5e 100644 --- a/backend/internal/service/prompt_template.go +++ b/backend/internal/service/prompt_template.go @@ -379,46 +379,67 @@ func validatePromptTemplateResult(operation string, result map[string]interface{ return nil } text, _ := result["text"].(string) - extractor := extractJSONText - // 角色卡契约要求顶层对象,模型正文里若先出现数组(例如先列一遍角色名)会让通用提取命中错误片段, - // 因此这里优先挑出带 characters 键的对象候选。 - if operation == promptOperationCharacterExtract || operation == promptOperationChapterAssetsExtract { - extractor = func(raw string) (string, error) { return extractPreferredJSONText(raw, "characters") } - } - jsonText, err := extractor(text) + jsonText, err := promptResultJSONExtractor(operation)(text) if err != nil { return fmt.Errorf("%s 返回内容不符合受保护 JSON 契约:%w", definition.Label, err) } - if operation == promptOperationChapterAssetsExtract { - var assets struct { - Characters *[]map[string]interface{} `json:"characters"` - Scenes *[]map[string]interface{} `json:"scenes"` - Props *[]map[string]interface{} `json:"props"` - } - if err := json.Unmarshal([]byte(jsonText), &assets); err != nil { - return fmt.Errorf("章节资产提取 JSON 无法解析:%w", err) - } - if assets.Characters == nil || assets.Scenes == nil || assets.Props == nil { - return errors.New("章节资产提取结果必须包含 characters、scenes 和 props 数组") - } - for _, group := range [][]map[string]interface{}{*assets.Scenes, *assets.Props} { - for _, asset := range group { - for _, field := range []string{"name", "description", "prompt"} { - value, ok := asset[field].(string) - if !ok || strings.TrimSpace(value) == "" { - return fmt.Errorf("章节场景或道具缺少有效的 %s", field) - } + switch operation { + case promptOperationChapterAssetsExtract: + return validateChapterAssetsResult(jsonText) + case promptOperationCharacterExtract: + return validateCharacterExtractResult(jsonText) + case promptOperationShortDramaOutline: + return validateShortDramaOutlineResult(jsonText) + case promptOperationSkillDraft: + return validateSkillDraftResult(jsonText) + default: + return nil + } +} + +func promptResultJSONExtractor(operation string) func(string) (string, error) { + switch operation { + case promptOperationCharacterExtract, promptOperationChapterAssetsExtract: + // 角色卡契约要求顶层对象,模型正文里若先出现数组(例如先列一遍角色名)会让通用提取命中错误片段。 + return func(raw string) (string, error) { return extractPreferredJSONText(raw, "characters") } + case promptOperationShortDramaOutline: + return func(raw string) (string, error) { return extractPreferredJSONText(raw, "chapters") } + case promptOperationSkillDraft: + return func(raw string) (string, error) { return extractPreferredJSONText(raw, "skillName") } + default: + return extractJSONText + } +} + +func validateChapterAssetsResult(jsonText string) error { + var assets struct { + Characters *[]map[string]interface{} `json:"characters"` + Scenes *[]map[string]interface{} `json:"scenes"` + Props *[]map[string]interface{} `json:"props"` + } + if err := json.Unmarshal([]byte(jsonText), &assets); err != nil { + return fmt.Errorf("章节资产提取 JSON 无法解析:%w", err) + } + if assets.Characters == nil || assets.Scenes == nil || assets.Props == nil { + return errors.New("章节资产提取结果必须包含 characters、scenes 和 props 数组") + } + for _, group := range [][]map[string]interface{}{*assets.Scenes, *assets.Props} { + for _, asset := range group { + for _, field := range []string{"name", "description", "prompt"} { + value, ok := asset[field].(string) + if !ok || strings.TrimSpace(value) == "" { + return fmt.Errorf("章节场景或道具缺少有效的 %s", field) } } } - if len(*assets.Characters) > 0 { - return validatePromptTemplateResult(promptOperationCharacterExtract, map[string]interface{}{"text": jsonText}) - } - return nil } - if operation != promptOperationCharacterExtract { - return nil + if len(*assets.Characters) > 0 { + return validatePromptTemplateResult(promptOperationCharacterExtract, map[string]interface{}{"text": jsonText}) } + return nil +} + +func validateCharacterExtractResult(jsonText string) error { jsonText = normalizeCharacterBreakdownRootJSON(jsonText) var payload struct { Characters *[]map[string]interface{} `json:"characters"` @@ -443,6 +464,63 @@ func validatePromptTemplateResult(operation string, result map[string]interface{ return nil } +func validateShortDramaOutlineResult(jsonText string) error { + var payload struct { + Title string `json:"title"` + Synopsis string `json:"synopsis"` + Chapters *[]struct { + Title string `json:"title"` + Content string `json:"content"` + } `json:"chapters"` + } + if err := json.Unmarshal([]byte(jsonText), &payload); err != nil { + return fmt.Errorf("短剧大纲 JSON 无法解析:%w", err) + } + if strings.TrimSpace(payload.Title) == "" { + return errors.New("短剧大纲缺少 title") + } + if strings.TrimSpace(payload.Synopsis) == "" { + return errors.New("短剧大纲缺少 synopsis") + } + if payload.Chapters == nil { + return errors.New("短剧大纲缺少 chapters 数组") + } + if len(*payload.Chapters) == 0 { + return errors.New("短剧大纲没有生成任何章节") + } + for index, chapter := range *payload.Chapters { + if strings.TrimSpace(chapter.Title) == "" || strings.TrimSpace(chapter.Content) == "" { + return fmt.Errorf("短剧大纲第 %d 章缺少 title 或 content", index+1) + } + } + return nil +} + +func validateSkillDraftResult(jsonText string) error { + var payload struct { + SkillName string `json:"skillName"` + Tag string `json:"tag"` + Description string `json:"description"` + Instruction string `json:"instruction"` + } + if err := json.Unmarshal([]byte(jsonText), &payload); err != nil { + return fmt.Errorf("技能草稿 JSON 无法解析:%w", err) + } + if strings.TrimSpace(payload.SkillName) == "" { + return errors.New("技能草稿缺少 skillName") + } + if strings.TrimSpace(payload.Tag) == "" { + return errors.New("技能草稿缺少 tag") + } + if strings.TrimSpace(payload.Description) == "" { + return errors.New("技能草稿缺少 description") + } + if strings.TrimSpace(payload.Instruction) == "" { + return errors.New("技能草稿缺少 instruction") + } + return nil +} + // normalizeCharacterBreakdownRootJSON 把模型返回的角色卡结果收敛为 character-breakdown/v1 的 // {"characters": [...]} 形态。模型并不总是遵守顶层 object 契约,实测出现的偏离包括: // - 顶层直接返回角色数组([ { name, role, ... }, ... ]); diff --git a/backend/internal/service/prompt_template_catalog_test.go b/backend/internal/service/prompt_template_catalog_test.go new file mode 100644 index 000000000..d056cd02e --- /dev/null +++ b/backend/internal/service/prompt_template_catalog_test.go @@ -0,0 +1,82 @@ +package service + +import ( + "strings" + "testing" +) + +func TestPromptOperationCatalogContainsProductJobs(t *testing.T) { + want := []string{ + promptOperationChapterAssetsExtract, + promptOperationStoryboardPlan, + promptOperationStoryboardRepair, + promptOperationStoryboardFirstFrame, + promptOperationStoryboardVideo, + promptOperationCharacterExtract, + promptOperationCharacterTurnaround, + promptOperationShortDramaOutline, + promptOperationSkillDraft, + } + got := make(map[string]bool, len(want)) + for _, definition := range defaultPromptDefinitions() { + got[definition.Operation] = true + } + for _, operation := range want { + if !got[operation] { + t.Fatalf("prompt catalog missing operation %s", operation) + } + } + if len(got) != len(want) { + t.Fatalf("prompt catalog size = %d, want %d; update this lock list when adding an operation", len(got), len(want)) + } +} + +func TestRenderShortDramaTemplateSubstitutesVariables(t *testing.T) { + definition, ok := promptDefinition(promptOperationShortDramaOutline) + if !ok { + t.Fatal("short_drama_outline definition missing") + } + rendered, err := renderPromptTemplate(definition, definition.DefaultContent, map[string]string{ + "章节数量": "5", + "叙事结构": "单线推进", + "每章字数": "800", + "叙事视角": "第三人称", + "整体基调": "平稳叙事", + "角色规模": "3-4 个", + "章节篇幅": "中", + }) + if err != nil { + t.Fatalf("renderPromptTemplate() error = %v", err) + } + if !strings.Contains(rendered, "5 个章节") || !strings.Contains(rendered, "单线推进") || strings.Contains(rendered, "{{") { + t.Fatalf("renderPromptTemplate() = %q, want interpolated short-drama instruction", rendered) + } + protected := protectedPromptContext(promptOperationShortDramaOutline, map[string]string{"用户故事": "租客发现房东不是人"}) + if !strings.Contains(protected, "租客发现房东不是人") || !strings.Contains(protected, "short-drama-outline/v1") { + t.Fatalf("protectedPromptContext() = %q, want story and JSON contract", protected) + } +} + +func TestValidatePromptTemplateResultShortDramaOutline(t *testing.T) { + ok := `{"title":"夜灯","synopsis":"租客发现房东不是人","chapters":[{"title":"入住","content":"林夏搬进旧公寓。"}]}` + if err := validatePromptTemplateResult(promptOperationShortDramaOutline, map[string]interface{}{"text": "```json\n" + ok + "\n```"}); err != nil { + t.Fatalf("valid outline error = %v", err) + } + if err := validatePromptTemplateResult(promptOperationShortDramaOutline, map[string]interface{}{"text": `{"title":"夜灯","synopsis":"简介","chapters":[]}`}); err == nil || !strings.Contains(err.Error(), "没有生成任何章节") { + t.Fatalf("empty chapters error = %v", err) + } +} + +func TestValidatePromptTemplateResultSkillDraft(t *testing.T) { + ok := `{"skillName":"分镜节奏","tag":"drama","description":"把章节拆成可执行镜头","instruction":"角色设定:分镜导演。"}` + if err := validatePromptTemplateResult(promptOperationSkillDraft, map[string]interface{}{"text": ok}); err != nil { + t.Fatalf("valid skill draft error = %v", err) + } + if err := validatePromptTemplateResult(promptOperationSkillDraft, map[string]interface{}{"text": `{"skillName":"分镜节奏","tag":"drama","description":"简介"}`}); err == nil || !strings.Contains(err.Error(), "instruction") { + t.Fatalf("missing instruction error = %v", err) + } + protected := protectedPromptContext(promptOperationSkillDraft, map[string]string{"用户想法": "做一个电商主图技能"}) + if !strings.Contains(protected, "做一个电商主图技能") || !strings.Contains(protected, "skill-draft/v1") { + t.Fatalf("protectedPromptContext() = %q, want idea and JSON contract", protected) + } +} diff --git a/backend/internal/service/prompt_template_defaults.go b/backend/internal/service/prompt_template_defaults.go index 3fb956af7..610c6c946 100644 --- a/backend/internal/service/prompt_template_defaults.go +++ b/backend/internal/service/prompt_template_defaults.go @@ -13,6 +13,8 @@ const ( promptOperationCharacterExtract = "character_extract" promptOperationChapterAssetsExtract = "chapter_assets_extract" promptOperationCharacterTurnaround = "character_turnaround" + promptOperationShortDramaOutline = "short_drama_outline" + promptOperationSkillDraft = "skill_draft" ) const legacyStoryboardVideoPromptPreamble = "生成单一连续镜头的视频执行提示词。一个镜头只保留一个叙事目标、一个主运镜和一条主要动作链;摄影机运动必须有起点、动机和停止点。优先保证角色身份、表演、关键动作和连续性,次要环境效果可以简化。\n\n" @@ -101,6 +103,18 @@ func defaultPromptDefinitions() []PromptOperationDefinition { Variables: []PromptTemplateVariable{{Label: "角色名称", Placeholder: "{{角色名称}}"}, {Label: "项目画风", Placeholder: "{{项目画风}}"}, {Label: "角色设定", Placeholder: "{{角色设定}}"}}, DefaultContent: `制作专业人物三视图设定表。画面严格分成三个等宽竖向区域,从左到右依次为正面全身、右侧面全身、背面全身。三个视角必须是同一角色、同一服装、同一发型、同一体型和同一比例,采用站立中性姿势,完整显示头顶到脚底。背景使用纯净中性浅色和均匀设定稿光线,只负责分离轮廓,不得改变项目画风的绘画或渲染媒介。禁止文字、边框、道具说明、表情变化和额外人物。`, }, + { + Operation: promptOperationShortDramaOutline, Label: "短剧大纲", Category: "项目", OutputType: "json", SchemaKey: "short-drama-outline/v1", + Description: "根据一句话故事生成短剧标题、简介和章节正文,供项目创建导入。", + Variables: []PromptTemplateVariable{{Label: "章节数量", Placeholder: "{{章节数量}}"}, {Label: "叙事结构", Placeholder: "{{叙事结构}}"}, {Label: "每章字数", Placeholder: "{{每章字数}}"}, {Label: "叙事视角", Placeholder: "{{叙事视角}}"}, {Label: "整体基调", Placeholder: "{{整体基调}}"}, {Label: "角色规模", Placeholder: "{{角色规模}}"}, {Label: "章节篇幅", Placeholder: "{{章节篇幅}}"}}, + DefaultContent: `你是短剧编剧。根据用户的一句话故事,生成一部短剧的标题、一句话简介和 {{章节数量}} 个章节。生成要求:叙事采用{{叙事结构}}结构,每章约 {{每章字数}} 字,使用{{叙事视角}}视角,整体基调{{整体基调}},主要角色约 {{角色规模}},章节篇幅{{章节篇幅}}。`, + }, + { + Operation: promptOperationSkillDraft, Label: "技能草稿", Category: "技能", OutputType: "json", SchemaKey: "skill-draft/v1", + Description: "根据用户想法生成可复用创作技能的名称、分类、简介和指令草稿。", + Variables: []PromptTemplateVariable{}, + DefaultContent: `你是一位技能编写助手。根据用户的想法,为一个「可复用的创作技能」生成一份草稿。技能名称简短,不超过 20 个字。分类 tag 必须是 drama、ecommerce、creative、social、others 之一。简介不超过 120 字,说明适用场景、输入条件和最终产出。指令使用 Markdown,至少 300 字,写给后续在画布中使用该技能的模型阅读,必须包含角色设定、输入与约束、分步执行流程、检查清单和输出格式。`, + }, } } @@ -121,6 +135,16 @@ func protectedPromptContext(operation string, values map[string]string) string { }, "\n\n") case promptOperationCharacterTurnaround: return strings.Join([]string{"【角色名称】\n" + values["角色名称"], "【项目画风】\n" + values["项目画风"], "【角色设定】\n" + values["角色设定"]}, "\n\n") + case promptOperationShortDramaOutline: + return strings.Join([]string{ + "【用户故事】\n" + values["用户故事"], + "【受保护输出契约】\n" + promptOutputContract(operation), + }, "\n\n") + case promptOperationSkillDraft: + return strings.Join([]string{ + "【用户想法】\n" + values["用户想法"], + "【受保护输出契约】\n" + promptOutputContract(operation), + }, "\n\n") default: return "" } @@ -134,11 +158,50 @@ func promptOutputContract(operation string) string { return "服务端固定 JSON Schema character-breakdown/v1(不可由运营模板或用户定制覆盖):\n" + characterBreakdownJSONSchema case promptOperationChapterAssetsExtract: return "服务端固定 JSON Schema chapter-assets/v1(不可由运营模板或用户定制覆盖):\n" + chapterAssetsJSONSchema + case promptOperationShortDramaOutline: + return "服务端固定 JSON Schema short-drama-outline/v1(不可由运营模板或用户定制覆盖):\n" + shortDramaOutlineJSONSchema + "\n只输出一个 JSON 对象,不要输出 markdown 代码块或其他文字。" + case promptOperationSkillDraft: + return "服务端固定 JSON Schema skill-draft/v1(不可由运营模板或用户定制覆盖):\n" + skillDraftJSONSchema + "\n只输出一个 JSON 对象,不要输出 markdown 代码块或其他文字。" default: return "当前操作输出普通文本提示词,没有 JSON Schema。" } } +const shortDramaOutlineJSONSchema = `{ + "type": "object", + "additionalProperties": false, + "required": ["title", "synopsis", "chapters"], + "properties": { + "title": {"type": "string"}, + "synopsis": {"type": "string"}, + "chapters": { + "type": "array", + "minItems": 1, + "items": { + "type": "object", + "additionalProperties": false, + "required": ["title", "content"], + "properties": { + "title": {"type": "string"}, + "content": {"type": "string"} + } + } + } + } +}` + +const skillDraftJSONSchema = `{ + "type": "object", + "additionalProperties": false, + "required": ["skillName", "tag", "description", "instruction"], + "properties": { + "skillName": {"type": "string"}, + "tag": {"type": "string", "enum": ["drama", "ecommerce", "creative", "social", "others"]}, + "description": {"type": "string"}, + "instruction": {"type": "string"} + } +}` + const storyboardPlanJSONSchema = `{ "type": "object", "additionalProperties": false, diff --git a/backend/internal/service/protocol_registry.go b/backend/internal/service/protocol_registry.go index 301e08b18..381a11358 100644 --- a/backend/internal/service/protocol_registry.go +++ b/backend/internal/service/protocol_registry.go @@ -24,6 +24,11 @@ func withProtocolRegistry(ctx context.Context, registry *protocol.Registry) cont return context.WithValue(ctx, protocolRegistryContextKey{}, registry) } +func protocolRegistryFromContext(ctx context.Context) (*protocol.Registry, bool) { + registry, ok := ctx.Value(protocolRegistryContextKey{}).(*protocol.Registry) + return registry, ok && registry != nil +} + func protocolAdapterForContext(ctx context.Context, id string) (protocol.Adapter, bool) { registry, _ := ctx.Value(protocolRegistryContextKey{}).(*protocol.Registry) if registry == nil { @@ -32,6 +37,53 @@ func protocolAdapterForContext(ctx context.Context, id string) (protocol.Adapter return registry.Resolve(strings.TrimSpace(id)) } +// ensureOfficialProtocolAdapter 让未注入 registry 的调用(主要是单测)与生产一样 +// 使用官方插件包。调用方若显式放入空 registry,表示要测“插件未安装”。 +func ensureOfficialProtocolAdapter(ctx context.Context, interfaceType string) context.Context { + if _, present := protocolRegistryFromContext(ctx); present { + return ctx + } + interfaceType = strings.TrimSpace(interfaceType) + if interfaceType == "" { + return ctx + } + registry := loadOfficialFallbackRegistry() + adapter, ok := registry.Resolve(interfaceType) + if !ok || adapter.Metadata().Execution != "declarative" { + return ctx + } + return withProtocolRegistry(ctx, registry) +} + +// officialDeclarativeVideoInterface 列出已有官方声明式视频插件的 InterfaceType。 +// 这些接口缺失 adapter 时必须报错,不能再走第二套手写实现。 +func officialDeclarativeVideoInterface(interfaceType string) (string, bool) { + switch strings.TrimSpace(interfaceType) { + case string(model.ChannelInterfaceAgnesVideo): + return "Agnes", true + case string(model.ChannelInterfaceMiniMaxVideo): + return "MiniMax", true + case string(model.ChannelInterfaceGeminiVeo): + return "Gemini Veo", true + case string(model.ChannelInterfaceNovitaVideo): + return "Novita", true + case string(model.ChannelInterfaceNewAPIChannel2): + return "NewAPI Video Generations", true + case string(model.ChannelInterfaceNewAPIChannel1): + return "NewAPI 媒体任务", true + case string(model.ChannelInterfaceXAIVideo): + return "xAI", true + case string(model.ChannelInterfaceVolcengineArkVideo): + return "火山方舟", true + case string(model.ChannelInterfaceVolcengineJiMengVideo): + return "即梦", true + case string(model.ChannelInterfaceNewAPIVideo): + return "OpenAI Videos", true + default: + return "", false + } +} + func declarativeProtocolAdapterForContext(ctx context.Context, id string) (protocol.Adapter, bool) { adapter, ok := protocolAdapterForContext(ctx, id) if !ok || adapter.Metadata().Execution != "declarative" { diff --git a/backend/internal/service/provider.go b/backend/internal/service/provider.go index 0ded6377c..b7dc729d6 100644 --- a/backend/internal/service/provider.go +++ b/backend/internal/service/provider.go @@ -1,36 +1,23 @@ package service +// 画布生成任务调度、输入水合与跨能力共享类型/辅助函数。 + import ( - "bufio" "bytes" "context" - "crypto/hmac" - "crypto/sha256" "encoding/base64" - "encoding/hex" "encoding/json" "errors" "fmt" "io" - "log" - "math" - "mime" - "mime/multipart" - "net" "net/http" - "net/textproto" - "net/url" "regexp" - "sort" "strconv" "strings" "time" "infinite-canvas/backend/internal/model" - "infinite-canvas/backend/internal/protocol" - "github.com/google/uuid" - "github.com/volcengine/volc-sdk-golang/base" "gorm.io/gorm" ) @@ -139,10 +126,9 @@ type providerError struct { Message string `json:"message"` } -// providerPayloadError keeps the upstream reason available for protocol -// fallback decisions while exposing only the categorized message to callers. -// Provider bodies may contain secrets or internal diagnostics and must not be -// copied into user-facing errors or logs. +// providerPayloadError 在进程内保留上游原始原因,供协议兼容分支做机器判断; +// 对调用方只暴露归类后的稳定文案。Provider 正文可能包含密钥或内部诊断, +// 禁止原样进入用户错误和日志。 type providerPayloadError struct { raw string message string @@ -474,11 +460,11 @@ func providerMediaHydrationPolicyFor(ctx context.Context, input canvasGeneration return policy } -// providerPrefersMediaURLs lists protocols whose media fields accept a remote -// URL. Byte-oriented protocols deliberately remain on the existing data path. +// providerPrefersMediaURLs 只列出明确接受远程 URL 的协议。 +// 需要 multipart/原始字节的协议继续走字节路径,不能为了减少下载而擅自改变请求合同。 func providerPrefersMediaURLs(interfaceType string, input canvasGenerationInput) bool { if input.Mask != nil { - // OpenAI image edits and similar multipart requests require file bytes. + // OpenAI 图片编辑等 multipart 请求需要真实文件字节,遮罩场景不能改发 URL。 return false } switch strings.TrimSpace(interfaceType) { @@ -496,618 +482,6 @@ func providerPrefersMediaURLs(interfaceType string, input canvasGenerationInput) return false } -func runAgentToolTask(ctx context.Context, input canvasGenerationInput) (map[string]interface{}, error) { - if adapter, ok := agentProtocolAdapterForContext(ctx, input.Config.InterfaceType); ok { - return runDeclarativeAgentTask(ctx, input, adapter) - } - if input.AgentRequests == nil { - return nil, errors.New("画布 Agent 工具请求缺少协议参数") - } - request := input.AgentRequests.ChatCompletion - path := "/chat/completions" - protocol := "chat-completion" - if input.Config.InterfaceType == string(model.ChannelInterfaceOpenAIResponse) { - request = input.AgentRequests.Responses - path = "/responses" - protocol = "responses" - } else if input.Config.InterfaceType == string(model.ChannelInterfaceClaudeAPI) { - path = "/messages" - protocol = "claude-api" - } - if request == nil { - return nil, errors.New("画布 Agent 工具请求缺少协议参数") - } - body := cloneStringAnyMap(request) - if protocol == "claude-api" { - body = claudeAgentBody(body) - } - body["model"] = input.Config.Model - result, err := postStreamingAgent(ctx, input.Config, path, body, protocol, input.OnTextDelta) - if protocol == "chat-completion" && isAgentToolChoiceCompatibilityError(err) { - if !isAutoAgentToolChoice(body["tool_choice"]) { - autoBody := cloneStringAnyMap(body) - autoBody["tool_choice"] = "auto" - result, err = postStreamingAgent(ctx, input.Config, path, autoBody, protocol, input.OnTextDelta) - } - if isAgentToolChoiceCompatibilityError(err) { - withoutToolChoice := cloneStringAnyMap(body) - delete(withoutToolChoice, "tool_choice") - result, err = postStreamingAgent(ctx, input.Config, path, withoutToolChoice, protocol, input.OnTextDelta) - } - } - if err != nil { - return nil, err - } - return result, nil -} - -func runDeclarativeAgentTask(ctx context.Context, input canvasGenerationInput, adapter protocol.AgentAdapter) (map[string]interface{}, error) { - if input.AgentRequests == nil { - return nil, errors.New("画布 Agent 工具请求缺少协议参数") - } - request := map[string]any{ - "chatCompletion": input.AgentRequests.ChatCompletion, - "responses": input.AgentRequests.Responses, - "claude": input.AgentRequests.Claude, - "gemini": input.AgentRequests.Gemini, - } - spec, err := adapter.BuildAgent(ctx, protocol.AgentRequestContext{BaseURL: input.Config.BaseURL, Model: input.Config.Model, Request: request}) - if err != nil { - return nil, err - } - body, err := executeProtocolRequest(ctx, input.Config, spec) - if err != nil { - return nil, err - } - parsed, err := adapter.ParseAgent(ctx, body) - if err != nil { - return nil, err - } - result := map[string]interface{}{"mode": "text", "text": parsed.Text, "toolCalls": []interface{}{}} - if parsed.Reasoning != "" { - result["reasoning"] = parsed.Reasoning - } - calls := make([]interface{}, 0, len(parsed.ToolCalls)) - for _, call := range parsed.ToolCalls { - mapped := map[string]interface{}{ - "id": call.ID, - "type": "function", - "function": map[string]interface{}{"name": call.Name, "arguments": call.Arguments}, - } - if call.ThoughtSignature != "" { - mapped["thoughtSignature"] = call.ThoughtSignature - } - calls = append(calls, mapped) - } - result["toolCalls"] = calls - if strings.TrimSpace(parsed.Text) == "" && len(calls) == 0 { - return nil, errors.New("声明式 Agent 接口没有返回内容") - } - return result, nil -} - -func claudeAgentBody(request map[string]interface{}) map[string]interface{} { - body := map[string]interface{}{"max_tokens": 4096} - if messages, ok := request["messages"].([]interface{}); ok { - claudeMessages := make([]interface{}, 0, len(messages)) - var system []string - for _, value := range messages { - message, _ := value.(map[string]interface{}) - role := strings.ToLower(strings.TrimSpace(stringField(message, "role"))) - if role == "system" { - if content := strings.TrimSpace(fmt.Sprint(message["content"])); content != "" { - system = append(system, content) - } - continue - } - if role == "tool" { - claudeMessages = append(claudeMessages, map[string]interface{}{"role": "user", "content": []interface{}{map[string]interface{}{ - "type": "tool_result", "tool_use_id": stringField(message, "tool_call_id"), "content": fmt.Sprint(message["content"]), - }}}) - continue - } - role = mapClaudeMessageRole(role) - content := message["content"] - if content == nil { - content = "" - } - if toolCalls, ok := message["tool_calls"].([]interface{}); ok && len(toolCalls) > 0 { - blocks := make([]interface{}, 0, len(toolCalls)) - for _, value := range toolCalls { - toolCall, _ := value.(map[string]interface{}) - function, _ := toolCall["function"].(map[string]interface{}) - blocks = append(blocks, map[string]interface{}{ - "type": "tool_use", "id": stringField(toolCall, "id"), "name": stringField(function, "name"), "input": claudeToolInput(function["arguments"]), - }) - } - content = blocks - } - claudeMessages = append(claudeMessages, map[string]interface{}{"role": role, "content": content}) - } - body["messages"] = claudeMessages - if len(system) > 0 { - body["system"] = strings.Join(system, "\n\n") - } - } - if tools, ok := request["tools"].([]interface{}); ok && len(tools) > 0 { - claudeTools := make([]interface{}, 0, len(tools)) - for _, value := range tools { - tool, _ := value.(map[string]interface{}) - function, _ := tool["function"].(map[string]interface{}) - if len(function) == 0 { - continue - } - claudeTools = append(claudeTools, map[string]interface{}{ - "name": stringField(function, "name"), "description": stringField(function, "description"), "input_schema": function["parameters"], - }) - } - if len(claudeTools) > 0 { - body["tools"] = claudeTools - } - } - if choice, ok := request["tool_choice"]; ok { - body["tool_choice"] = claudeToolChoice(choice) - } - return body -} - -func mapClaudeMessageRole(role string) string { - if role == "assistant" { - return "assistant" - } - return "user" -} - -func claudeToolInput(value interface{}) interface{} { - if raw, ok := value.(string); ok { - var parsed interface{} - if json.Unmarshal([]byte(raw), &parsed) == nil && parsed != nil { - return parsed - } - } - if value != nil { - return value - } - return map[string]interface{}{} -} - -func claudeToolChoice(value interface{}) interface{} { - switch choice := value.(type) { - case string: - switch strings.ToLower(strings.TrimSpace(choice)) { - case "required": - return map[string]interface{}{"type": "any"} - case "none": - return map[string]interface{}{"type": "auto"} - default: - return map[string]interface{}{"type": "auto"} - } - case map[string]interface{}: - if function, ok := choice["function"].(map[string]interface{}); ok && stringField(function, "name") != "" { - return map[string]interface{}{"type": "tool", "name": stringField(function, "name")} - } - if name := stringField(choice, "name"); name != "" { - return map[string]interface{}{"type": "tool", "name": name} - } - } - return map[string]interface{}{"type": "auto"} -} - -func isAgentToolChoiceCompatibilityError(err error) bool { - if err == nil { - return false - } - message := strings.ToLower(err.Error()) - var payloadErr providerPayloadError - if errors.As(err, &payloadErr) { - message += " " + strings.ToLower(payloadErr.raw) - } - var httpErr providerHTTPError - if errors.As(err, &httpErr) { - message += " " + strings.ToLower(httpErr.Body) - } - return strings.Contains(message, "tool_choice") || strings.Contains(message, "tool choice") || strings.Contains(message, "tool-choice") || strings.Contains(message, "thinking mode") -} - -func isAutoAgentToolChoice(value interface{}) bool { - choice, ok := value.(string) - return ok && strings.EqualFold(strings.TrimSpace(choice), "auto") -} - -func parseAgentToolPayload(payload map[string]interface{}, protocol string) (map[string]interface{}, error) { - if err := validateTextPayload(payload); err != nil { - return nil, err - } - result := map[string]interface{}{"mode": "text", "text": "", "toolCalls": []interface{}{}} - if protocol == "responses" { - result["text"] = firstNonEmptyString(stringField(payload, "output_text"), extractResponseText(payload)) - if reasoning := extractResponseReasoning(payload); reasoning != "" { - result["reasoning"] = reasoning - } - calls := make([]interface{}, 0) - for _, value := range interfaceSlice(payload["output"]) { - item, _ := value.(map[string]interface{}) - if stringField(item, "type") != "function_call" { - continue - } - calls = append(calls, map[string]interface{}{"id": firstNonEmptyString(stringField(item, "call_id"), stringField(item, "id")), "type": "function", "function": map[string]interface{}{"name": stringField(item, "name"), "arguments": stringField(item, "arguments")}}) - } - result["toolCalls"] = calls - return result, nil - } - if protocol == "claude-api" { - content := interfaceSlice(payload["content"]) - calls := make([]interface{}, 0) - for _, value := range content { - item, _ := value.(map[string]interface{}) - switch stringField(item, "type") { - case "text": - result["text"] = result["text"].(string) + stringField(item, "text") - case "thinking": - result["reasoning"] = resultString(result, "reasoning") + firstNonEmptyString(stringField(item, "thinking"), stringField(item, "text")) - case "tool_use": - arguments, err := json.Marshal(item["input"]) - if err != nil { - return nil, err - } - calls = append(calls, map[string]interface{}{"id": stringField(item, "id"), "type": "function", "function": map[string]interface{}{"name": stringField(item, "name"), "arguments": string(arguments)}}) - } - } - result["toolCalls"] = calls - if result["text"] == "" && len(calls) == 0 { - return nil, errors.New("Claude Agent 接口没有返回内容") - } - return result, nil - } - choices := interfaceSlice(payload["choices"]) - if len(choices) == 0 { - return nil, errors.New("画布 Agent 接口没有返回 choices") - } - choice, _ := choices[0].(map[string]interface{}) - message, _ := choice["message"].(map[string]interface{}) - result["text"] = stringField(message, "content") - if reasoning := firstNonEmptyString(stringField(message, "reasoning_content"), stringField(message, "reasoning")); reasoning != "" { - result["reasoning"] = reasoning - } - calls := make([]interface{}, 0) - for _, value := range interfaceSlice(message["tool_calls"]) { - item, _ := value.(map[string]interface{}) - function, _ := item["function"].(map[string]interface{}) - calls = append(calls, map[string]interface{}{"id": stringField(item, "id"), "type": "function", "function": map[string]interface{}{"name": stringField(function, "name"), "arguments": stringField(function, "arguments")}}) - } - result["toolCalls"] = calls - return result, nil -} - -func postStreamingAgent(ctx context.Context, config providerConfig, path string, body map[string]interface{}, protocol string, onDelta func(string)) (map[string]interface{}, error) { - body["stream"] = true - if protocol == "chat-completion" { - metadata, _ := ctx.Value(providerAnalyticsKey{}).(providerAnalyticsContext) - if metadata.BillingMode == "token" { - if err := ensureChatCompletionStreamUsage(body); err != nil { - return nil, err - } - } - } - parser := newStreamingAgentParser(protocol, onDelta) - data, mimeType, err := postStreamingBinary(ctx, config, path, body, parser.consume) - if err != nil { - return nil, err - } - if !strings.Contains(strings.ToLower(mimeType), "event-stream") { - var payload map[string]interface{} - if err := json.Unmarshal(data, &payload); err != nil { - return nil, fmt.Errorf("Agent 接口返回格式无效:%w", err) - } - return parseAgentToolPayload(payload, protocol) - } - parser.flush() - return parser.result() -} - -type streamingAgentToolCall struct { - id string - name string - arguments string -} - -type streamingAgentParser struct { - protocol string - buffer string - text strings.Builder - reasoning strings.Builder - toolCalls map[int]*streamingAgentToolCall - toolCallByID map[string]int - completed map[string]interface{} - err error - emit func(string) -} - -func newStreamingAgentParser(protocol string, emit func(string)) *streamingAgentParser { - return &streamingAgentParser{protocol: protocol, toolCalls: map[int]*streamingAgentToolCall{}, toolCallByID: map[string]int{}, emit: emit} -} - -func (p *streamingAgentParser) consume(mimeType string, chunk []byte) { - if p == nil || p.err != nil || !strings.Contains(strings.ToLower(mimeType), "event-stream") || len(chunk) == 0 { - return - } - p.buffer += string(chunk) - p.consumeFrames(false) -} - -func (p *streamingAgentParser) flush() { - if p == nil || p.err != nil { - return - } - p.consumeFrames(true) -} - -func (p *streamingAgentParser) consumeFrames(flush bool) { - for p.err == nil { - match := sseFrameBoundaryPattern.FindStringIndex(p.buffer) - if match == nil { - break - } - p.consumeFrame(p.buffer[:match[0]]) - p.buffer = p.buffer[match[1]:] - } - if flush && p.err == nil && strings.TrimSpace(p.buffer) != "" { - p.consumeFrame(p.buffer) - p.buffer = "" - } -} - -func (p *streamingAgentParser) consumeFrame(frame string) { - var eventName string - var dataLines []string - for _, line := range strings.Split(strings.ReplaceAll(frame, "\r\n", "\n"), "\n") { - switch { - case strings.HasPrefix(line, "event:"): - eventName = strings.TrimSpace(strings.TrimPrefix(line, "event:")) - case strings.HasPrefix(line, "data:"): - dataLines = append(dataLines, strings.TrimPrefix(strings.TrimPrefix(line, "data:"), " ")) - } - } - raw := strings.TrimSpace(strings.Join(dataLines, "\n")) - if raw == "" || raw == "[DONE]" { - return - } - var payload map[string]interface{} - if err := json.Unmarshal([]byte(raw), &payload); err != nil { - p.err = fmt.Errorf("Agent 流式事件解析失败:%w", err) - return - } - if err := validateTextPayload(payload); err != nil { - p.err = err - return - } - switch p.protocol { - case "responses": - p.consumeResponsesEvent(eventName, payload) - case "claude-api": - p.consumeClaudeEvent(payload) - default: - p.consumeChatCompletionEvent(payload) - } -} - -func (p *streamingAgentParser) consumeResponsesEvent(eventName string, payload map[string]interface{}) { - eventType := firstNonEmptyString(strings.TrimSpace(eventName), stringField(payload, "type")) - switch eventType { - case "response.output_text.delta", "output_text.delta": - p.appendText(stringField(payload, "delta")) - case "response.reasoning.delta", "response.reasoning_text.delta", "response.reasoning_summary_text.delta": - p.reasoning.WriteString(stringField(payload, "delta")) - case "response.completed": - p.completed, _ = payload["response"].(map[string]interface{}) - case "response.output_item.added": - item, _ := payload["item"].(map[string]interface{}) - if stringField(item, "type") == "function_call" { - index := intField(payload, "output_index", len(p.toolCalls)) - p.setToolCall(index, firstNonEmptyString(stringField(item, "call_id"), stringField(item, "id")), stringField(item, "name"), stringField(item, "arguments")) - } - case "response.function_call_arguments.delta": - index := p.responseToolCallIndex(payload) - p.toolCall(index).arguments += stringField(payload, "delta") - case "response.function_call_arguments.done": - index := p.responseToolCallIndex(payload) - if arguments := stringField(payload, "arguments"); arguments != "" { - p.toolCall(index).arguments = arguments - } - } -} - -func (p *streamingAgentParser) responseToolCallIndex(payload map[string]interface{}) int { - itemID := firstNonEmptyString(stringField(payload, "item_id"), stringField(payload, "call_id")) - if index, ok := p.toolCallByID[itemID]; ok { - return index - } - return intField(payload, "output_index", len(p.toolCalls)) -} - -func (p *streamingAgentParser) consumeChatCompletionEvent(payload map[string]interface{}) { - choices, _ := payload["choices"].([]interface{}) - for _, value := range choices { - choice, _ := value.(map[string]interface{}) - delta, _ := choice["delta"].(map[string]interface{}) - p.appendText(streamContentText(delta["content"])) - p.reasoning.WriteString(firstNonEmptyString(stringField(delta, "reasoning_content"), stringField(delta, "reasoning"), stringField(delta, "reasoning_text"))) - for fallbackIndex, toolValue := range interfaceSlice(delta["tool_calls"]) { - tool, _ := toolValue.(map[string]interface{}) - index := intField(tool, "index", fallbackIndex) - function, _ := tool["function"].(map[string]interface{}) - current := p.toolCall(index) - if id := stringField(tool, "id"); id != "" { - current.id = id - p.toolCallByID[id] = index - } - if name := stringField(function, "name"); name != "" { - current.name += name - } - current.arguments += stringField(function, "arguments") - } - } -} - -func (p *streamingAgentParser) consumeClaudeEvent(payload map[string]interface{}) { - switch stringField(payload, "type") { - case "content_block_start": - block, _ := payload["content_block"].(map[string]interface{}) - index := intField(payload, "index", len(p.toolCalls)) - switch stringField(block, "type") { - case "text": - p.appendText(stringField(block, "text")) - case "thinking": - p.reasoning.WriteString(firstNonEmptyString(stringField(block, "thinking"), stringField(block, "text"))) - case "tool_use": - arguments := "" - if input := block["input"]; input != nil { - if encoded, err := json.Marshal(input); err == nil && string(encoded) != "{}" { - arguments = string(encoded) - } - } - p.setToolCall(index, stringField(block, "id"), stringField(block, "name"), arguments) - } - case "content_block_delta": - delta, _ := payload["delta"].(map[string]interface{}) - index := intField(payload, "index", len(p.toolCalls)-1) - if stringField(delta, "type") == "text_delta" { - p.appendText(stringField(delta, "text")) - } - if stringField(delta, "type") == "thinking_delta" { - p.reasoning.WriteString(firstNonEmptyString(stringField(delta, "thinking"), stringField(delta, "text"))) - } - if stringField(delta, "type") == "input_json_delta" { - p.toolCall(index).arguments += stringField(delta, "partial_json") - } - case "error": - errValue, _ := payload["error"].(map[string]interface{}) - p.err = errors.New(defaultString(stringField(errValue, "message"), "Claude 上游返回失败")) - } -} - -func resultString(value map[string]interface{}, key string) string { - text, _ := value[key].(string) - return text -} - -func (p *streamingAgentParser) appendText(delta string) { - if delta == "" { - return - } - p.text.WriteString(delta) - if p.emit != nil { - p.emit(delta) - } -} - -func (p *streamingAgentParser) toolCall(index int) *streamingAgentToolCall { - if index < 0 { - index = 0 - } - if p.toolCalls[index] == nil { - p.toolCalls[index] = &streamingAgentToolCall{} - } - return p.toolCalls[index] -} - -func (p *streamingAgentParser) setToolCall(index int, id string, name string, arguments string) { - call := p.toolCall(index) - call.id, call.name, call.arguments = id, name, arguments - if id != "" { - p.toolCallByID[id] = index - } -} - -func (p *streamingAgentParser) result() (map[string]interface{}, error) { - if p.err != nil { - return nil, p.err - } - if p.completed != nil { - result, err := parseAgentToolPayload(p.completed, p.protocol) - if err != nil { - return nil, err - } - if p.text.Len() > 0 { - result["text"] = p.text.String() - } - if p.reasoning.Len() > 0 { - result["reasoning"] = p.reasoning.String() - } - return result, nil - } - result := map[string]interface{}{"mode": "text", "text": p.text.String(), "toolCalls": []interface{}{}} - if p.reasoning.Len() > 0 { - result["reasoning"] = p.reasoning.String() - } - indices := make([]int, 0, len(p.toolCalls)) - for index := range p.toolCalls { - indices = append(indices, index) - } - sort.Ints(indices) - calls := make([]interface{}, 0, len(indices)) - for _, index := range indices { - call := p.toolCalls[index] - if call == nil || strings.TrimSpace(call.id) == "" || strings.TrimSpace(call.name) == "" { - continue - } - arguments := call.arguments - if strings.TrimSpace(arguments) == "" { - arguments = "{}" - } - var parsed interface{} - if err := json.Unmarshal([]byte(arguments), &parsed); err != nil { - return nil, fmt.Errorf("Agent 工具参数不是完整 JSON:%w", err) - } - calls = append(calls, map[string]interface{}{"id": call.id, "type": "function", "function": map[string]interface{}{"name": call.name, "arguments": arguments}}) - } - result["toolCalls"] = calls - if p.text.Len() == 0 && len(calls) == 0 { - return nil, errors.New("画布 Agent 接口没有返回内容") - } - return result, nil -} - -func intField(value map[string]interface{}, key string, fallback int) int { - number, ok := value[key].(float64) - if !ok || math.IsNaN(number) || math.IsInf(number, 0) { - return fallback - } - return int(number) -} - -func extractResponseReasoning(payload map[string]interface{}) string { - var chunks []string - for _, value := range interfaceSlice(payload["output"]) { - item, _ := value.(map[string]interface{}) - if stringField(item, "type") != "reasoning" { - continue - } - for _, key := range []string{"summary", "content"} { - for _, part := range interfaceSlice(item[key]) { - record, _ := part.(map[string]interface{}) - if text := strings.TrimSpace(stringField(record, "text")); text != "" { - chunks = append(chunks, text) - } - } - } - } - return strings.Join(chunks, "\n") -} - -func interfaceSlice(value interface{}) []interface{} { - items, _ := value.([]interface{}) - return items -} - -func cloneStringAnyMap(value map[string]interface{}) map[string]interface{} { - cloned := make(map[string]interface{}, len(value)+1) - for key, item := range value { - cloned[key] = item - } - return cloned -} - type styleExecutionPlanDocument struct { SchemaVersion int `json:"schemaVersion"` ProfilePresetID string `json:"profilePresetId"` @@ -1296,42 +670,6 @@ func styleAssetSupportsModel(baseModels []string, generationModel string) bool { return false } -func (s *Service) validateResolvedVideoCapability(input *canvasGenerationInput) error { - channelID := strings.TrimSpace(input.Config.ChannelID) - if channelID == "" { - profile := input.Config.CapabilityConfig - if profile == nil || profile.Video == nil { - if input.Config.InterfaceType != string(model.ChannelInterfaceAgnesVideo) { - return nil - } - profile = DefaultModelCapabilityConfigForModel(input.Config.InterfaceType, input.Config.Model) - } - normalized, err := NormalizeModelCapabilityConfigForModel("video", input.Config.InterfaceType, input.Config.Model, profile) - if err != nil || normalized == nil || normalized.Video == nil { - return errors.New("当前视频模型能力参数无效") - } - input.Config.CapabilityConfig = normalized - input.VideoCapability = normalized.Video - applyFixedVideoResolution(input, normalized.Video) - return validateVideoTask(normalized.Video, *input) - } - item, err := s.repo.ChannelModelByKey(channelID, providerChannelModelKey(input.Config)) - if err != nil { - return errors.New("当前系统渠道模型未配置或已停用") - } - profile, err := DecodeModelCapabilityConfig(item.CapabilityConfigJSON) - if err != nil || profile == nil || profile.Video == nil { - return errors.New("当前视频模型尚未配置能力参数") - } - normalized, err := NormalizeModelCapabilityConfigForModel("video", string(item.Protocol), firstNonEmpty(item.ProviderModelKey, item.ModelKey), profile) - if err != nil || normalized == nil || normalized.Video == nil { - return errors.New("当前视频模型能力参数无效") - } - input.VideoCapability = normalized.Video - applyFixedVideoResolution(input, normalized.Video) - return validateVideoTask(normalized.Video, *input) -} - func (s *Service) validateResolvedImageCapability(input *canvasGenerationInput) error { fallback := DefaultImageCapabilityConfig(input.Config.InterfaceType, input.Config.Model) channelID := strings.TrimSpace(input.Config.ChannelID) @@ -1543,14 +881,7 @@ func (s *Service) resolveProviderConfig(config providerConfig) (providerConfig, return providerConfig{}, errors.New("系统渠道模型标识不一致") } config.InterfaceType = string(channelModel.Protocol) - // 模型协议是实际请求契约;混合渠道中鉴权格式也必须随模型协议切换。 - if config.InterfaceType == string(model.ChannelInterfaceGeminiVeo) || config.InterfaceType == string(model.ChannelInterfaceGeminiImage) { - config.APIFormat = "gemini" - } else if config.InterfaceType == string(model.ChannelInterfaceClaudeAPI) { - config.APIFormat = "claude" - } else if config.InterfaceType != "" { - config.APIFormat = "openai" - } + config.APIFormat = channelAPIFormatForProtocol(channel.APIFormat, channelModel.Protocol) config.BaseURL = channel.BaseURL config.AllowLocalChannel = s.effectiveAllowLocalChannel(channel.AllowLocalChannel) config.APIKey = channel.APIKey @@ -1565,6 +896,21 @@ func (s *Service) resolveProviderConfig(config providerConfig) (providerConfig, return config, nil } +// channelAPIFormatForProtocol 以模型协议而不是客户端缓存决定鉴权和请求封装格式。 +// 同一系统渠道可以挂载不同协议的模型,因此渠道级 APIFormat 只能作为协议缺失时的兼容值。 +func channelAPIFormatForProtocol(channelDefault string, protocol model.ChannelInterfaceType) string { + switch protocol { + case model.ChannelInterfaceGeminiVeo, model.ChannelInterfaceGeminiImage: + return "gemini" + case model.ChannelInterfaceClaudeAPI: + return "claude" + case "": + return strings.TrimSpace(channelDefault) + default: + return "openai" + } +} + func providerChannelModelKey(config providerConfig) string { return strings.TrimPrefix(strings.TrimSpace(firstNonEmpty(config.ChannelModelKey, config.Model)), "models/") } @@ -1598,729 +944,6 @@ func systemChannelIDFromBaseURL(baseURL string) string { return "" } -func runImageTask(ctx context.Context, input canvasGenerationInput) (map[string]interface{}, error) { - if _, ok := declarativeProtocolAdapterForContext(ctx, input.Config.InterfaceType); ok { - return runDeclarativeProtocolTask(ctx, input) - } - if input.Config.InterfaceType == string(model.ChannelInterfaceGrokImage) { - return runGrokImageTask(ctx, input) - } - if input.Config.InterfaceType == string(model.ChannelInterfaceVolcengineJiMengImage) { - return runVolcengineJiMengImageTask(ctx, input) - } - if input.Config.InterfaceType == string(model.ChannelInterfaceVolcengineArkImage) { - return runVolcengineArkImageTask(ctx, input) - } - if input.Config.InterfaceType == string(model.ChannelInterfaceGeminiImage) { - return runGeminiImageTask(ctx, input) - } - var payload imageResponse - if input.Mask != nil { - // 蒙版编辑是强校验写路径:协议能力不明确时必须失败,不能静默退化为整图重绘。 - if strings.TrimSpace(input.Config.InterfaceType) != string(model.ChannelInterfaceOpenAIImage) { - return nil, errors.New("当前渠道未声明 OpenAI Images 编辑协议,已拒绝可能忽略蒙版的整图重绘") - } - if len(input.ReferenceImages) == 0 { - return nil, errors.New("蒙版编辑必须提供与蒙版同尺寸的源图片") - } - } - if len(input.ReferenceImages) > 0 || input.Mask != nil { - body := &bytes.Buffer{} - writer := multipart.NewWriter(body) - writeField(writer, "model", input.Config.Model) - writeField(writer, "prompt", withSystemPrompt(input.Config, input.Prompt)) - writeField(writer, "n", "1") - if imageParameterSupported(input.ImageCapability, "response_format") { - writeField(writer, "response_format", "b64_json") - } - if imageParameterSupported(input.ImageCapability, "output_format") { - writeField(writer, "output_format", "png") - } - if imageTransparentBackgroundSupported(input.ImageCapability) && input.Config.TransparentBackground == "true" { - writeField(writer, "background", "transparent") - } - if imageQualitySupported(input.ImageCapability) && input.Config.Quality != "" && !strings.EqualFold(strings.TrimSpace(input.Config.Quality), "auto") { - writeField(writer, "quality", normalizeImageQuality(input.Config.Quality)) - } - if key, value := imageSizeParameter(input.ImageCapability, input.Config.Size); value != "" { - writeField(writer, key, value) - } - for _, image := range input.ReferenceImages { - if err := writeMediaPart(writer, "image", image); err != nil { - return nil, err - } - } - if input.Mask != nil { - if err := writeMediaPart(writer, "mask", *input.Mask); err != nil { - return nil, err - } - } - if err := writer.Close(); err != nil { - return nil, err - } - if err := postForm(ctx, input.Config, "/images/edits", writer.FormDataContentType(), body, &payload); err != nil { - return nil, err - } - } else { - body := map[string]interface{}{ - "model": input.Config.Model, - "prompt": withSystemPrompt(input.Config, input.Prompt), - "n": 1, - } - if imageParameterSupported(input.ImageCapability, "response_format") { - body["response_format"] = "b64_json" - } - if imageParameterSupported(input.ImageCapability, "output_format") { - body["output_format"] = "png" - } - if imageTransparentBackgroundSupported(input.ImageCapability) && input.Config.TransparentBackground == "true" { - body["background"] = "transparent" - } - if imageQualitySupported(input.ImageCapability) && input.Config.Quality != "" && !strings.EqualFold(strings.TrimSpace(input.Config.Quality), "auto") { - body["quality"] = normalizeImageQuality(input.Config.Quality) - } - if key, value := imageSizeParameter(input.ImageCapability, input.Config.Size); value != "" { - body[key] = value - } - if err := postJSON(ctx, input.Config, "/images/generations", body, &payload); err != nil { - return nil, err - } - } - images, err := imageDataURLs(payload) - if err != nil { - return nil, err - } - return map[string]interface{}{"mode": "image", "images": images}, nil -} - -func runGeminiImageTask(ctx context.Context, input canvasGenerationInput) (map[string]interface{}, error) { - if input.Mask != nil { - return nil, errors.New("Gemini Images 不支持蒙版编辑,请移除蒙版后重试") - } - if len(input.ReferenceVideos) > 0 || len(input.ReferenceAudios) > 0 { - return nil, errors.New("Gemini Images 不支持参考视频或音频") - } - - parts := make([]geminiImageContentPart, 0, 1+len(input.ReferenceImages)) - if prompt := strings.TrimSpace(input.Prompt); prompt != "" { - parts = append(parts, geminiImageContentPart{Text: prompt}) - } - for _, image := range input.ReferenceImages { - raw, mimeType, err := geminiImageBytes(image) - if err != nil { - return nil, fmt.Errorf("读取 Gemini Images 参考图失败:%w", err) - } - parts = append(parts, geminiImageContentPart{InlineData: &geminiImageInlineData{MIMEType: mimeType, Data: base64.StdEncoding.EncodeToString(raw)}}) - } - if len(parts) == 0 { - return nil, errors.New("Gemini Images 请求缺少提示词或参考图") - } - - body := geminiImageRequest{ - Contents: []geminiImageContent{{Role: "user", Parts: parts}}, - GenerationConfig: geminiImageGenerationConfig{ - ResponseModalities: []string{"TEXT", "IMAGE"}, - ImageConfig: geminiImageConfigFor(input.Config), - }, - } - if systemPrompt := strings.TrimSpace(input.Config.SystemPrompt); systemPrompt != "" { - body.SystemInstruction = &geminiImageContent{Parts: []geminiImageContentPart{{Text: systemPrompt}}} - } - var payload map[string]interface{} - path := "/models/" + url.PathEscape(input.Config.Model) + ":generateContent" - if err := postGeminiJSON(ctx, input.Config, path, body, &payload); err != nil { - return nil, err - } - images, err := geminiImageDataURLs(payload) - if err != nil { - return nil, err - } - return map[string]interface{}{"mode": "image", "images": images}, nil -} - -func geminiImageConfigFor(config providerConfig) *geminiImageConfig { - imageConfig := &geminiImageConfig{} - size := strings.TrimSpace(config.Size) - if size != "" && size != "auto" && strings.Count(size, ":") == 1 { - imageConfig.AspectRatio = size - } - switch strings.ToLower(strings.TrimSpace(config.Quality)) { - case "low", "1k": - imageConfig.ImageSize = "1K" - case "medium", "2k": - imageConfig.ImageSize = "2K" - case "high", "4k": - imageConfig.ImageSize = "4K" - } - if imageConfig.AspectRatio == "" && imageConfig.ImageSize == "" { - return nil - } - return imageConfig -} - -func geminiImageBytes(media providerMedia) ([]byte, string, error) { - raw, mimeType, err := mediaBytes(media) - if err != nil { - return nil, "", err - } - if len(raw) == 0 { - return nil, "", errors.New("参考图片数据为空") - } - detected := strings.TrimSpace(strings.Split(http.DetectContentType(raw), ";")[0]) - if !strings.HasPrefix(strings.ToLower(mimeType), "image/") { - if !strings.HasPrefix(strings.ToLower(detected), "image/") { - return nil, "", fmt.Errorf("参考图片 MIME 类型无效:%s", defaultString(mimeType, detected)) - } - mimeType = detected - } else if !strings.HasPrefix(strings.ToLower(detected), "image/") { - // 以实际字节签名为准,避免错误的 data URL MIME 把非图片内容伪装成图片。 - return nil, "", fmt.Errorf("参考图片内容不是有效图片:%s", defaultString(detected, mimeType)) - } - return raw, mimeType, nil -} - -func geminiImageDataURLs(payload map[string]interface{}) ([]map[string]string, error) { - if errorValue, ok := payload["error"].(map[string]interface{}); ok { - if message := stringField(errorValue, "message"); message != "" { - return nil, errors.New(message) - } - } - candidates, _ := payload["candidates"].([]interface{}) - images := make([]map[string]string, 0) - for _, candidateValue := range candidates { - candidate, _ := candidateValue.(map[string]interface{}) - content, _ := candidate["content"].(map[string]interface{}) - parts, _ := content["parts"].([]interface{}) - for _, partValue := range parts { - part, _ := partValue.(map[string]interface{}) - inlineData, _ := part["inlineData"].(map[string]interface{}) - if inlineData == nil { - inlineData, _ = part["inline_data"].(map[string]interface{}) - } - if inlineData != nil { - data := strings.TrimSpace(stringField(inlineData, "data")) - mimeType := firstNonEmptyString(stringField(inlineData, "mimeType"), stringField(inlineData, "mime_type")) - if data == "" { - continue - } - if mimeType == "" { - mimeType = "image/png" - } - if !strings.HasPrefix(strings.ToLower(mimeType), "image/") { - return nil, fmt.Errorf("Gemini Images 返回了非图片 MIME 类型:%s", mimeType) - } - decoded, err := base64.StdEncoding.DecodeString(data) - if err != nil { - return nil, fmt.Errorf("Gemini Images 返回的图片数据无效:%w", err) - } - images = append(images, map[string]string{"dataUrl": dataURL(mimeType, decoded)}) - continue - } - fileData, _ := part["fileData"].(map[string]interface{}) - if fileData != nil { - if fileURL := firstNonEmptyString(stringField(fileData, "fileUri"), stringField(fileData, "file_uri")); fileURL != "" { - images = append(images, map[string]string{"dataUrl": fileURL}) - } - } - } - } - if len(images) == 0 { - return nil, errors.New("Gemini Images 接口没有返回图片") - } - return images, nil -} - -func runGrokImageTask(ctx context.Context, input canvasGenerationInput) (map[string]interface{}, error) { - body, path, err := grokImageRequestBody(input) - if err != nil { - return nil, err - } - var payload imageResponse - if err := postJSON(ctx, input.Config, path, body, &payload); err != nil { - return nil, err - } - images, err := imageDataURLs(payload) - if err != nil { - return nil, err - } - return map[string]interface{}{"mode": "image", "images": images}, nil -} - -func grokImageRequestBody(input canvasGenerationInput) (grokImageRequest, string, error) { - if input.Mask != nil { - return grokImageRequest{}, "", errors.New("Grok 图片协议不支持蒙版编辑,请移除蒙版后重试") - } - body := grokImageRequest{ - Model: input.Config.Model, - Prompt: withSystemPrompt(input.Config, input.Prompt), - N: 1, - ResponseFormat: "url", - // Grok 图片协议用 aspect_ratio 表达画布比例;同时发送 size 会被上游按 OpenAI 枚举校验并拒绝。 - AspectRatio: normalizeGrokImageAspectRatio(input.Config.Size), - Resolution: normalizeGrokImageResolution(input.Config.Quality), - } - if len(input.ReferenceImages) == 0 { - return body, "/images/generations", nil - } - if len(input.ReferenceImages) != 1 { - return grokImageRequest{}, "", fmt.Errorf("Grok 图片编辑只支持 1 张参考图,当前连接了 %d 张", len(input.ReferenceImages)) - } - imageURL, err := grokImageInputURL(input.ReferenceImages[0]) - if err != nil { - return grokImageRequest{}, "", err - } - body.Image = &grokImageInput{URL: imageURL} - return body, "/images/edits", nil -} - -// normalizeGrokImageResolution 把画布 quality(1k/2k/high…)映射为 grok2api / xAI 的 resolution。 -func normalizeGrokImageResolution(quality string) string { - raw := strings.ToLower(strings.TrimSpace(quality)) - switch raw { - case "", "auto": - return "" - case "1k", "low", "standard": - return "1k" - case "2k", "medium", "hd", "high", "4k": - // xAI Imagine 图片通常最高 2k;超出则夹到 2k,避免上游拒参。 - return "2k" - default: - return "" - } -} - -// normalizeGrokImageAspectRatio 把画布 size(如 1280x720 / 9:16)转成 grok2api / xAI 接受的 aspect_ratio。 -func normalizeGrokImageAspectRatio(size string) string { - raw := strings.ToLower(strings.TrimSpace(strings.ReplaceAll(size, "×", "x"))) - if raw == "" || raw == "auto" { - return "" - } - if strings.Contains(raw, ":") { - switch raw { - case "1:1", "3:4", "4:3", "9:16", "16:9", "2:3", "3:2", "9:19.5", "19.5:9", "1:2", "2:1": - return raw - } - } - parts := strings.Split(raw, "x") - if len(parts) != 2 { - return "" - } - w, wErr := strconv.Atoi(parts[0]) - h, hErr := strconv.Atoi(parts[1]) - if wErr != nil || hErr != nil || w <= 0 || h <= 0 { - return "" - } - if w == h { - return "1:1" - } - ratio := float64(w) / float64(h) - switch { - case w*9 == h*16 || (ratio >= 1.7 && ratio <= 1.8): - return "16:9" - case h*9 == w*16 || (ratio > 0 && ratio <= 1.0/1.7 && ratio >= 1.0/1.8): - return "9:16" - case w*3 == h*4 || (ratio > 1.2 && ratio < 1.4): - return "4:3" - case h*3 == w*4 || (ratio > 0.7 && ratio < 0.85): - return "3:4" - // 像素尺寸路径必须显式覆盖 2:3 / 3:2 / 1:2 / 2:1:冒号字符串能直达(见上方 switch), - // 但像素路径只靠 w>h 兜底会把 768x1152(2:3,ratio 0.667)错标成 9:16、 - // 1152x768(3:2,ratio 1.5)错标成 16:9,xAI 按错比例裁切生成图。 - case w*3 == h*2 || (ratio >= 0.6 && ratio < 0.72): - return "2:3" - case w*2 == h*3 || (ratio > 1.35 && ratio < 1.6): - return "3:2" - case h == w*2 || (ratio > 0.45 && ratio < 0.55): - return "1:2" - case w == h*2 || (ratio > 1.85 && ratio < 2.2): - return "2:1" - case w > h: - return "16:9" - default: - return "9:16" - } -} - -func grokImageInputURL(media providerMedia) (string, error) { - if isPublicMediaURL(strings.TrimSpace(media.URL)) { - return strings.TrimSpace(media.URL), nil - } - return openAIImageInputURL(media) -} - -const ( - volcengineArkImageMinPixels = 3686400 - volcengineArkImageMaxPixels = 4624220 -) - -func runVolcengineArkImageTask(ctx context.Context, input canvasGenerationInput) (map[string]interface{}, error) { - if input.Mask != nil { - return nil, errors.New("火山方舟图片协议不支持蒙版编辑,请移除蒙版后重试") - } - body, err := volcengineArkImageBody(input) - if err != nil { - return nil, err - } - var payload imageResponse - if err := postJSON(ctx, input.Config, "/images/generations", body, &payload); err != nil { - return nil, err - } - images, err := volcengineArkImageDataURLs(ctx, input.Config, payload) - if err != nil { - return nil, err - } - return map[string]interface{}{"mode": "image", "images": images}, nil -} - -func volcengineArkImageDataURLs(ctx context.Context, config providerConfig, payload imageResponse) ([]map[string]string, error) { - images, err := imageDataURLs(payload) - if err != nil { - return nil, err - } - for _, image := range images { - value := strings.TrimSpace(image["dataUrl"]) - if strings.HasPrefix(value, "data:image/") { - continue - } - if !isPublicMediaURL(value) { - return nil, errors.New("火山方舟图片接口没有返回可下载的图片") - } - // 方舟默认返回临时 CDN 地址。必须由后端下载成内联结果,后续资源持久化才能 - // 原子地写入服务器或用户配置的对象存储,且不依赖浏览器跨域访问方舟 CDN。 - data, mimeType, err := getProviderExternalBinary(withProviderRequestKind(ctx, "download"), config, value) - if err != nil { - return nil, fmt.Errorf("火山方舟图片结果下载失败:%w", err) - } - detected := strings.ToLower(strings.TrimSpace(strings.Split(http.DetectContentType(data), ";")[0])) - mimeType = strings.ToLower(normalizedMediaMimeType(mimeType, data)) - if len(data) == 0 || strings.Contains(detected, "json") || strings.HasPrefix(detected, "text/") || !strings.HasPrefix(mimeType, "image/") { - return nil, fmt.Errorf("火山方舟图片结果无效:%s", defaultString(detected, mimeType)) - } - image["dataUrl"] = dataURL(mimeType, data) - image["mimeType"] = mimeType - } - return images, nil -} - -func volcengineArkImageBody(input canvasGenerationInput) (map[string]interface{}, error) { - body := map[string]interface{}{ - "model": input.Config.Model, - "prompt": withSystemPrompt(input.Config, input.Prompt), - "n": 1, - "response_format": "b64_json", - "watermark": false, - } - if key, value := imageSizeParameter(input.ImageCapability, input.Config.Size); value != "" { - if key == "size" { - value = normalizeVolcengineArkImageSize(value) - } - body[key] = value - } - if len(input.ReferenceImages) == 0 { - return body, nil - } - images := make([]string, 0, len(input.ReferenceImages)) - for _, image := range input.ReferenceImages { - url, err := openAIImageInputURL(image) - if err != nil { - return nil, err - } - images = append(images, url) - } - if len(images) == 1 { - body["image"] = images[0] - } else { - body["image"] = images - } - return body, nil -} - -func normalizeVolcengineArkImageSize(value string) string { - size := normalizePixelSize(value) - parts := strings.Split(strings.ToLower(size), "x") - if len(parts) != 2 { - return size - } - width, widthErr := strconv.Atoi(parts[0]) - height, heightErr := strconv.Atoi(parts[1]) - if widthErr != nil || heightErr != nil || width <= 0 || height <= 0 { - return size - } - pixels := int64(width) * int64(height) - if pixels >= volcengineArkImageMinPixels && pixels <= volcengineArkImageMaxPixels { - return size - } - targetPixels := volcengineArkImageMaxPixels - round := math.Floor - if pixels < volcengineArkImageMinPixels { - targetPixels = volcengineArkImageMinPixels - round = math.Ceil - } - scale := math.Sqrt(float64(targetPixels) / float64(pixels)) - width = int(round(float64(width)*scale/2)) * 2 - height = int(round(float64(height)*scale/2)) * 2 - for width > 2 && height > 2 && int64(width)*int64(height) < volcengineArkImageMinPixels { - if width >= height { - width += 2 - } else { - height += 2 - } - } - for width > 2 && height > 2 && int64(width)*int64(height) > volcengineArkImageMaxPixels { - if width >= height { - width -= 2 - } else { - height -= 2 - } - } - return strconv.Itoa(width) + "x" + strconv.Itoa(height) -} - -func runTextTask(ctx context.Context, input canvasGenerationInput) (map[string]interface{}, error) { - if _, ok := declarativeProtocolAdapterForContext(ctx, input.Config.InterfaceType); ok { - return runDeclarativeProtocolTask(ctx, input) - } - switch input.Config.InterfaceType { - case "chat-completion": - return runChatCompletionsTextTask(ctx, input) - case "openai-response": - return runResponsesTextTask(ctx, input) - case string(model.ChannelInterfaceClaudeAPI): - return runClaudeTextTask(ctx, input) - } - return runLegacyTextTask(ctx, input) -} - -func runLegacyTextTask(ctx context.Context, input canvasGenerationInput) (map[string]interface{}, error) { - responseInput, err := textResponseInput(input) - if err != nil { - return nil, err - } - body := map[string]interface{}{"model": input.Config.Model, "input": responseInput} - applyTextThinking(body, input, "responses") - applyTextOutputLimit(body, input.MaxOutputTokens, "max_output_tokens") - result, err := requestTextProvider(ctx, input.Config, "/responses", body, "responses", input.StreamText, input.OnTextDelta) - if err != nil { - if !shouldFallbackTextToChat(err) { - return nil, err - } - result, chatErr := runChatCompletionsTextTask(ctx, input) - if chatErr == nil { - return result, nil - } - return nil, fmt.Errorf("文本接口请求失败:Responses API %v;Chat Completions %v", err, chatErr) - } - return providerTextTaskResult(result), nil -} - -func runResponsesTextTask(ctx context.Context, input canvasGenerationInput) (map[string]interface{}, error) { - responseInput, err := textResponseInput(input) - if err != nil { - return nil, err - } - body := map[string]interface{}{"model": input.Config.Model, "input": responseInput} - applyTextThinking(body, input, "responses") - applyTextOutputLimit(body, input.MaxOutputTokens, "max_output_tokens") - result, err := requestTextProvider(ctx, input.Config, "/responses", body, "responses", input.StreamText, input.OnTextDelta) - if err != nil { - return nil, err - } - return providerTextTaskResult(result), nil -} - -func runChatCompletionsTextTask(ctx context.Context, input canvasGenerationInput) (map[string]interface{}, error) { - messages := []map[string]interface{}{} - if systemPrompt := strings.TrimSpace(input.Config.SystemPrompt); systemPrompt != "" { - messages = append(messages, map[string]interface{}{"role": "system", "content": systemPrompt}) - } - messages = append(messages, validatedTextHistory(input.TextHistory)...) - userContent, err := textChatContent(input) - if err != nil { - return nil, err - } - messages = append(messages, map[string]interface{}{"role": "user", "content": userContent}) - body := map[string]interface{}{"model": input.Config.Model, "messages": messages} - applyTextThinking(body, input, "chat-completion") - applyTextOutputLimit(body, input.MaxOutputTokens, "max_tokens") - result, err := requestTextProvider(ctx, input.Config, "/chat/completions", body, "chat-completion", input.StreamText, input.OnTextDelta) - if err != nil { - return nil, err - } - return providerTextTaskResult(result), nil -} - -func runClaudeTextTask(ctx context.Context, input canvasGenerationInput) (map[string]interface{}, error) { - if len(input.ReferenceVideos) > 0 { - return nil, errors.New("Claude API 当前不支持视频参考输入") - } - messages := make([]map[string]interface{}, 0, len(input.TextHistory)+1) - for _, message := range validatedTextHistory(input.TextHistory) { - messages = append(messages, message) - } - content, err := claudeTextContent(input) - if err != nil { - return nil, err - } - messages = append(messages, map[string]interface{}{"role": "user", "content": content}) - maxTokens := 4096 - if input.MaxOutputTokens > 0 { - maxTokens = input.MaxOutputTokens - } - body := map[string]interface{}{"model": input.Config.Model, "max_tokens": maxTokens, "messages": messages} - applyTextThinking(body, input, "claude-api") - if systemPrompt := strings.TrimSpace(input.Config.SystemPrompt); systemPrompt != "" { - body["system"] = systemPrompt - } - result, err := requestTextProvider(ctx, input.Config, "/messages", body, "claude-api", input.StreamText, input.OnTextDelta) - if err != nil { - return nil, err - } - return providerTextTaskResult(result), nil -} - -func applyTextThinking(body map[string]interface{}, input canvasGenerationInput, protocol string) { - if !input.TextOptions.Thinking { - return - } - switch protocol { - case "responses": - body["reasoning"] = map[string]interface{}{"effort": "medium", "summary": "auto"} - case "chat-completion": - body["reasoning_effort"] = "medium" - case "claude-api": - body["thinking"] = map[string]interface{}{"type": "enabled", "budget_tokens": 1024} - } -} - -type providerTextResult struct { - Text string - Reasoning string -} - -func providerTextTaskResult(result providerTextResult) map[string]interface{} { - payload := map[string]interface{}{"mode": "text", "text": result.Text} - if strings.TrimSpace(result.Reasoning) != "" { - payload["reasoning"] = result.Reasoning - } - return payload -} - -func applyTextOutputLimit(body map[string]interface{}, limit int, field string) { - if limit > 0 { - body[field] = limit - } -} - -func claudeTextContent(input canvasGenerationInput) (interface{}, error) { - if len(input.ReferenceImages) == 0 { - return input.Prompt, nil - } - content := []map[string]interface{}{{"type": "text", "text": input.Prompt}} - for _, image := range input.ReferenceImages { - value, err := openAIImageInputURL(image) - if err != nil { - return nil, err - } - if strings.HasPrefix(value, "data:") { - mimeType, data, ok := splitDataURL(value) - if !ok { - return nil, errors.New("Claude 参考图片 data URL 无效") - } - content = append(content, map[string]interface{}{"type": "image", "source": map[string]interface{}{"type": "base64", "media_type": mimeType, "data": data}}) - } else { - content = append(content, map[string]interface{}{"type": "image", "source": map[string]interface{}{"type": "url", "url": value}}) - } - } - return content, nil -} - -func splitDataURL(value string) (string, string, bool) { - if !strings.HasPrefix(value, "data:") { - return "", "", false - } - separator := strings.Index(value, ",") - if separator <= len("data:") { - return "", "", false - } - header := strings.TrimPrefix(value[:separator], "data:") - if !strings.HasSuffix(header, ";base64") { - return "", "", false - } - return strings.TrimSuffix(header, ";base64"), value[separator+1:], value[separator+1:] != "" -} - -func textResponseInput(input canvasGenerationInput) (interface{}, error) { - systemPrompt := strings.TrimSpace(input.Config.SystemPrompt) - if len(input.TextHistory) == 0 && len(input.ReferenceImages) == 0 && len(input.ReferenceVideos) == 0 { - return withSystemPrompt(input.Config, input.Prompt), nil - } - messages := make([]map[string]interface{}, 0, len(input.TextHistory)+2) - if systemPrompt != "" { - messages = append(messages, map[string]interface{}{"role": "system", "content": systemPrompt}) - } - messages = append(messages, validatedTextHistory(input.TextHistory)...) - content, err := textResponseContent(input) - if err != nil { - return nil, err - } - messages = append(messages, map[string]interface{}{"role": "user", "content": content}) - return messages, nil -} - -func validatedTextHistory(history []providerTextMessage) []map[string]interface{} { - result := make([]map[string]interface{}, 0, len(history)) - for _, message := range history { - role := strings.ToLower(strings.TrimSpace(message.Role)) - content := strings.TrimSpace(message.Content) - if (role != "user" && role != "assistant") || content == "" { - continue - } - result = append(result, map[string]interface{}{"role": role, "content": content}) - } - return result -} - -func textResponseContent(input canvasGenerationInput) ([]map[string]interface{}, error) { - content := []map[string]interface{}{{"type": "input_text", "text": input.Prompt}} - for _, image := range input.ReferenceImages { - url, err := openAIImageInputURL(image) - if err != nil { - return nil, err - } - content = append(content, map[string]interface{}{"type": "input_image", "image_url": url}) - } - for _, video := range input.ReferenceVideos { - url, err := openAIVideoInputURL(video) - if err != nil { - return nil, err - } - content = append(content, map[string]interface{}{"type": "input_video", "video_url": url}) - } - return content, nil -} - -func textChatContent(input canvasGenerationInput) (interface{}, error) { - if len(input.ReferenceImages) == 0 && len(input.ReferenceVideos) == 0 { - return input.Prompt, nil - } - content := []map[string]interface{}{{"type": "text", "text": input.Prompt}} - for _, image := range input.ReferenceImages { - url, err := openAIImageInputURL(image) - if err != nil { - return nil, err - } - content = append(content, map[string]interface{}{"type": "image_url", "image_url": map[string]interface{}{"url": url}}) - } - for _, video := range input.ReferenceVideos { - url, err := openAIVideoInputURL(video) - if err != nil { - return nil, err - } - content = append(content, map[string]interface{}{"type": "video_url", "video_url": map[string]interface{}{"url": url}}) - } - return content, nil -} - func openAIImageInputURL(media providerMedia) (string, error) { value := strings.TrimSpace(media.DataURL) if strings.HasPrefix(value, "data:image/") { @@ -2357,2139 +980,6 @@ func openAIVideoInputURL(media providerMedia) (string, error) { return "", errors.New("文本多模态参考视频需要公网 URL 或 base64 data URL") } -func shouldFallbackTextToChat(err error) bool { - var httpErr providerHTTPError - if !errors.As(err, &httpErr) { - return false - } - switch httpErr.StatusCode { - case http.StatusNotFound, http.StatusMethodNotAllowed, http.StatusNotImplemented, http.StatusBadGateway, http.StatusServiceUnavailable, http.StatusGatewayTimeout: - return true - default: - return false - } -} - -func runAudioTask(ctx context.Context, input canvasGenerationInput) (map[string]interface{}, error) { - if _, ok := declarativeProtocolAdapterForContext(ctx, input.Config.InterfaceType); ok { - return runDeclarativeProtocolTask(ctx, input) - } - if resolved, ok := input.Metadata["resolvedCharacterVersions"].([]interface{}); ok && len(resolved) > 0 { - voiceKey := metadataString(input.Metadata, "resolvedCharacterVoiceKey") - if voiceKey == "" || strings.TrimSpace(input.Config.AudioVoice) != voiceKey { - return nil, errors.New("角色配音缺少已解析的声音绑定") - } - } - format := defaultString(input.Config.AudioFormat, "mp3") - body := map[string]interface{}{ - "model": input.Config.Model, - "input": input.Prompt, - "voice": defaultString(input.Config.AudioVoice, "alloy"), - "response_format": format, - "speed": 1, - } - if input.Config.AudioSpeed != "" { - body["speed"] = parseFloat(input.Config.AudioSpeed, 1) - } - if input.Config.AudioInstructions != "" { - body["instructions"] = input.Config.AudioInstructions - } - if input.Config.InterfaceType == string(model.ChannelInterfaceAsyncAudio) { - return runAsyncAudioTask(ctx, input, body, format) - } - data, mimeType, err := postBinary(ctx, input.Config, "/audio/speech", body) - if err != nil { - return nil, err - } - mimeType, err = validateGeneratedAudio(mimeType, data, format) - if err != nil { - return nil, err - } - return map[string]interface{}{"mode": "audio", "audio": map[string]interface{}{"dataUrl": dataURL(mimeType, data), "mimeType": mimeType, "format": format}}, nil -} - -// runDeclarativeProtocolTask is the host runtime for JSON manifest plugins. -// Manifest code only describes request/response mapping; credentials, outbound -// policy, polling and result downloads remain owned by the host. -func runDeclarativeProtocolTask(ctx context.Context, input canvasGenerationInput) (map[string]interface{}, error) { - adapter, ok := declarativeProtocolAdapterForContext(ctx, input.Config.InterfaceType) - if !ok { - return nil, fmt.Errorf("接口类型 %s 未安装声明式适配器", input.Config.InterfaceType) - } - return runProtocolAdapterTask(ctx, input, adapter) -} - -type protocolPollTiming struct { - InitialDelay time.Duration - PollInterval time.Duration - TaskNotExistWindow time.Duration - TaskNotExistMaxMisses int - TaskNotExistRetryDelays []time.Duration -} - -var defaultProtocolPollTiming = protocolPollTiming{ - InitialDelay: 2 * time.Second, - PollInterval: 2500 * time.Millisecond, - TaskNotExistWindow: 15 * time.Second, - TaskNotExistMaxMisses: 5, - TaskNotExistRetryDelays: []time.Duration{2 * time.Second, 3 * time.Second, 5 * time.Second, 5 * time.Second}, -} - -func runProtocolAdapterTask(ctx context.Context, input canvasGenerationInput, adapter protocol.Adapter) (map[string]interface{}, error) { - return runProtocolAdapterTaskWithTiming(ctx, input, adapter, defaultProtocolPollTiming) -} - -func runProtocolAdapterTaskWithTiming(ctx context.Context, input canvasGenerationInput, adapter protocol.Adapter, timing protocolPollTiming) (map[string]interface{}, error) { - request := protocolRequestFromInput(input) - taskID := resumedProviderRequestID(ctx) - var created protocol.CreateResult - createdProviderTask := false - syncWindowStartedAt := time.Now() - if taskID == "" { - // The upstream create request may require an idempotency key. Keep it in - // the host-only request metadata so declarative plugins can map it to a - // header without exposing it in the provider JSON body. - request.Extra["idempotencyKey"] = uuid.NewString() - spec, err := adapter.BuildCreate(ctx, protocol.RequestContext{BaseURL: input.Config.BaseURL, Request: request}) - if err != nil { - return nil, err - } - body, err := executeProtocolRequest(withProviderRequestKind(ctx, "create"), input.Config, spec) - if err != nil { - return nil, err - } - created, err = adapter.ParseCreate(ctx, body) - if err != nil { - return nil, err - } - taskID = created.TaskID - if created.Status == protocol.StatusFailed || created.Status == protocol.StatusCancelled { - return nil, protocolResultError(created.Message, taskID) - } - if created.Status == protocol.StatusSucceeded { - return finishProtocolAdapterResult(ctx, input, adapter, request, taskID, created.Result) - } - if taskID == "" { - return nil, errors.New("声明式协议创建请求没有返回任务 ID") - } - createdProviderTask = true - syncWindowStartedAt = time.Now() - } - if createdProviderTask && input.Config.InterfaceType == string(model.ChannelInterfaceNewAPIChannel2) { - if err := sleepContext(ctx, timing.InitialDelay); err != nil { - return nil, err - } - } - - taskNotExistMisses := 0 - for deadline := providerPollingDeadline(ctx); time.Now().Before(deadline); { - spec, err := adapter.BuildPoll(ctx, protocol.PollContext{BaseURL: input.Config.BaseURL, Model: request.Model, Request: request, TaskID: taskID}) - if err != nil { - return nil, err - } - body, err := executeProtocolRequest(withProviderRequestKind(ctx, "poll"), input.Config, spec) - if err != nil { - if isNewAPIChannel2TaskNotReady(input.Config.InterfaceType, taskID, err) { - taskNotExistMisses++ - maxMisses := timing.TaskNotExistMaxMisses - if maxMisses <= 0 { - maxMisses = len(timing.TaskNotExistRetryDelays) + 1 - } - if time.Since(syncWindowStartedAt) >= timing.TaskNotExistWindow || taskNotExistMisses >= maxMisses { - return nil, providerStatePendingError{TaskID: taskID, Cause: err} - } - delayIndex := min(taskNotExistMisses-1, len(timing.TaskNotExistRetryDelays)-1) - if delayIndex >= 0 { - if sleepErr := sleepContext(ctx, timing.TaskNotExistRetryDelays[delayIndex]); sleepErr != nil { - return nil, sleepErr - } - } - continue - } - return nil, err - } - taskNotExistMisses = 0 - state, err := adapter.ParsePoll(ctx, protocol.PollContext{BaseURL: input.Config.BaseURL, Model: request.Model, Request: request, TaskID: taskID}, body) - if err != nil { - return nil, err - } - if state.TaskID != "" { - taskID = state.TaskID - } - switch state.Status { - case protocol.StatusSucceeded: - return finishProtocolAdapterResult(ctx, input, adapter, request, taskID, state.Result) - case protocol.StatusFailed, protocol.StatusCancelled: - return nil, protocolResultError(state.Message, taskID) - } - if err := sleepContext(ctx, timing.PollInterval); err != nil { - return nil, err - } - } - return nil, fmt.Errorf("声明式协议任务超时(任务 %s)", taskID) -} - -func isNewAPIChannel2TaskNotReady(interfaceType string, taskID string, err error) bool { - if strings.TrimSpace(interfaceType) != string(model.ChannelInterfaceNewAPIChannel2) || strings.TrimSpace(taskID) == "" { - return false - } - var httpErr providerHTTPError - if !errors.As(err, &httpErr) || httpErr.StatusCode != http.StatusBadRequest { - return false - } - var payload map[string]any - if json.Unmarshal([]byte(httpErr.Body), &payload) != nil { - return false - } - code, message := providerFailureDetails(payload) - return strings.EqualFold(strings.TrimSpace(code), "task_not_exist") || strings.EqualFold(strings.TrimSpace(message), "task_not_exist") -} - -// queryProtocolAdapterVideoTask performs exactly one read of an existing -// declarative provider task. Manual recovery uses this path so it can never -// create a second billable generation while checking a failed local task. -func queryProtocolAdapterVideoTask(ctx context.Context, input canvasGenerationInput, adapter protocol.Adapter, taskID string) (map[string]interface{}, string, error) { - request := protocolRequestFromInput(input) - pollContext := protocol.PollContext{BaseURL: input.Config.BaseURL, Model: request.Model, Request: request, TaskID: taskID} - spec, err := adapter.BuildPoll(ctx, pollContext) - if err != nil { - return nil, "", err - } - body, err := executeProtocolRequest(withProviderRequestKind(ctx, "poll"), input.Config, spec) - if err != nil { - return nil, "", err - } - state, err := adapter.ParsePoll(ctx, pollContext, body) - if err != nil { - return nil, "", err - } - providerStatus := string(state.Status) - switch state.Status { - case protocol.StatusSucceeded: - result, err := finishProtocolAdapterResult(ctx, input, adapter, request, taskID, state.Result) - return result, providerStatus, err - case protocol.StatusFailed, protocol.StatusCancelled: - return nil, providerStatus, protocolResultError(state.Message, taskID) - case protocol.StatusPending, protocol.StatusProcessing: - return nil, providerStatus, nil - default: - return nil, providerStatus, fmt.Errorf("声明式协议任务 %s 返回未知状态:%s", taskID, providerStatus) - } -} - -func protocolRequestFromInput(input canvasGenerationInput) protocol.GenerationRequest { - resolution := strings.TrimSpace(input.Config.VQuality) - if input.Mode == "video" { - if declared := videoResolutionNameRequest(input.VideoCapability, resolution); declared != "" { - resolution = declared - } - } - request := protocol.GenerationRequest{ - Capability: protocol.Capability(input.Mode), - Model: input.Config.Model, - Prompt: input.Prompt, - Instructions: strings.TrimSpace(input.Config.SystemPrompt), - Images: protocolImageReferences(input), - Videos: protocolMediaReferences(input.ReferenceVideos, "video"), - Audios: protocolMediaReferences(input.ReferenceAudios, "audio"), - AspectRatio: input.Config.Size, - Resolution: resolution, - Quality: input.Config.Quality, - GenerateAudio: parseBool(input.Config.VideoGenerateAudio, false), - Watermark: parseBool(input.Config.VideoWatermark, false), - Operation: firstNonEmpty(metadataString(input.Metadata, "videoEditOperation"), metadataString(input.Metadata, "videoOperation")), - Extra: map[string]any{ - "videoSeconds": input.Config.VideoSeconds, - "audioVoice": input.Config.AudioVoice, - "audioFormat": input.Config.AudioFormat, - "count": input.Config.Count, - }, - } - for _, message := range input.TextHistory { - role := strings.ToLower(strings.TrimSpace(message.Role)) - if role != "user" && role != "assistant" && role != "system" { - continue - } - if content := strings.TrimSpace(message.Content); content != "" { - request.Messages = append(request.Messages, protocol.Message{Role: role, Content: content}) - } - } - request.Inputs = append(request.Inputs, request.Images...) - request.Inputs = append(request.Inputs, request.Videos...) - request.Inputs = append(request.Inputs, request.Audios...) - if input.MaxOutputTokens > 0 { - request.Extra["max_output_tokens"] = input.MaxOutputTokens - request.Extra["max_tokens"] = input.MaxOutputTokens - } - if duration, err := strconv.Atoi(strings.TrimSpace(input.Config.VideoSeconds)); err == nil && duration > 0 { - request.Duration = duration - } - if count, err := strconv.Atoi(strings.TrimSpace(input.Config.Count)); err == nil && count > 0 { - request.ImageCount = count - } - request.Output = protocol.OutputOptions{ - Count: request.ImageCount, Duration: request.Duration, AspectRatio: request.AspectRatio, - Resolution: request.Resolution, Quality: request.Quality, GenerateAudio: request.GenerateAudio, - Watermark: request.Watermark, Format: input.Config.AudioFormat, - } - request.ProviderOptions = make(map[string]map[string]any) - if configured, ok := input.Metadata["providerOptions"].(map[string]any); ok { - for namespace, raw := range configured { - if options, ok := raw.(map[string]any); ok { - request.ProviderOptions[strings.TrimSpace(namespace)] = options - } - } - } - return request -} - -func protocolImageReferences(input canvasGenerationInput) []protocol.MediaReference { - if input.Mode == "video" { - return protocolVideoImageReferences(input) - } - result := make([]protocol.MediaReference, 0, len(input.ReferenceImages)+1) - for index, value := range input.ReferenceImages { - item := protocolMediaReference(value, "image", index) - item.Role = "reference_image" - if input.Mode == "image" { - item.Role = "edit_source" - } - if item.URL != "" || item.DataURL != "" { - result = append(result, item) - } - } - if input.Mask != nil { - mask := protocolMediaReference(*input.Mask, "image", len(result)) - mask.Role = "mask" - if mask.URL != "" || mask.DataURL != "" { - result = append(result, mask) - } - } - return result -} - -func protocolVideoImageReferences(input canvasGenerationInput) []protocol.MediaReference { - result := make([]protocol.MediaReference, 0, len(input.ReferenceImages)) - fallbackRole := "" - if metadataString(input.Metadata, "videoStartFrameNodeId") != "" || metadataString(input.Metadata, "videoEndFrameNodeId") != "" { - fallbackRole = "reference_image" - } - for index, value := range input.ReferenceImages { - item := protocolMediaReference(value, "image", index) - item.Role = videoImageRoleOrDefault(input, value, fallbackRole) - if item.URL != "" || item.DataURL != "" { - result = append(result, item) - } - } - return result -} - -func protocolMediaReferences(values []providerMedia, kind string) []protocol.MediaReference { - result := make([]protocol.MediaReference, 0, len(values)) - for index, value := range values { - item := protocolMediaReference(value, kind, index) - if kind == "video" { - item.Role = "reference_video" - } else if kind == "audio" { - item.Role = "reference_audio" - } - if item.URL != "" || item.DataURL != "" { - result = append(result, item) - } - } - return result -} - -func protocolMediaReference(value providerMedia, kind string, order int) protocol.MediaReference { - return protocol.MediaReference{ - ID: strings.TrimSpace(value.ID), URL: strings.TrimSpace(value.URL), DataURL: strings.TrimSpace(value.DataURL), - Kind: kind, MIMEType: firstNonEmpty(strings.TrimSpace(value.MimeType), strings.TrimSpace(value.Type)), Name: strings.TrimSpace(value.Name), Order: order, - Metadata: map[string]any{"bytes": value.Bytes, "width": value.Width, "height": value.Height, "durationMs": value.DurationMs, "storageKey": strings.TrimSpace(value.StorageKey)}, - } -} - -func executeProtocolRequest(ctx context.Context, config providerConfig, spec protocol.RequestSpec) ([]byte, error) { - data, _, err := executeProtocolBinaryRequest(ctx, config, spec) - return data, err -} - -func executeProtocolBinaryRequest(ctx context.Context, config providerConfig, spec protocol.RequestSpec) ([]byte, string, error) { - if err := spec.Validate(); err != nil { - return nil, "", err - } - method := strings.ToUpper(strings.TrimSpace(spec.Method)) - body, contentType, err := protocolRequestBody(ctx, config, spec) - if err != nil { - return nil, "", err - } - requestURL, err := protocolRequestURL(config.BaseURL, spec) - if err != nil { - return nil, "", err - } - req, err := http.NewRequestWithContext(ctx, method, requestURL, body) - if err != nil { - return nil, "", err - } - if contentType != "" { - req.Header.Set("Content-Type", contentType) - } - for name, value := range spec.Headers { - req.Header.Set(name, value) - } - ApplyOutboundHeaders(req, config.Headers) - if err := applyProtocolAuth(req, config, spec.Auth); err != nil { - return nil, "", err - } - return doBinary(req) -} - -func protocolRequestBody(ctx context.Context, config providerConfig, spec protocol.RequestSpec) (io.Reader, string, error) { - contentType := strings.ToLower(strings.TrimSpace(strings.Split(spec.ContentType, ";")[0])) - if spec.Body == nil && len(spec.Files) == 0 { - return nil, "", nil - } - switch contentType { - case "", "application/json": - data, err := json.Marshal(spec.Body) - if err != nil { - return nil, "", err - } - return bytes.NewReader(data), "application/json", nil - case "application/x-www-form-urlencoded": - values := url.Values{} - for key, value := range protocolBodyObject(spec.Body) { - for _, item := range protocolFormValues(value) { - values.Add(key, item) - } - } - return strings.NewReader(values.Encode()), contentType, nil - case "multipart/form-data": - var body bytes.Buffer - writer := multipart.NewWriter(&body) - keys := make([]string, 0) - for key := range protocolBodyObject(spec.Body) { - keys = append(keys, key) - } - sort.Strings(keys) - for _, key := range keys { - for _, item := range protocolFormValues(protocolBodyObject(spec.Body)[key]) { - if err := writer.WriteField(key, item); err != nil { - _ = writer.Close() - return nil, "", err - } - } - } - for _, file := range spec.Files { - data, detectedMIME, err := protocolMediaBytes(ctx, config, file.Reference) - if err != nil { - _ = writer.Close() - return nil, "", fmt.Errorf("读取 multipart 文件 %s 失败:%w", file.Name, err) - } - filename := safeProtocolFilename(file.Filename) - mimeType := strings.TrimSpace(file.MIMEType) - if mimeType == "" { - mimeType = detectedMIME - } - header := make(textproto.MIMEHeader) - header.Set("Content-Disposition", fmt.Sprintf(`form-data; name=%q; filename=%q`, file.Name, filename)) - header.Set("Content-Type", defaultString(mimeType, "application/octet-stream")) - part, err := writer.CreatePart(header) - if err != nil { - _ = writer.Close() - return nil, "", err - } - if _, err := part.Write(data); err != nil { - _ = writer.Close() - return nil, "", err - } - } - if err := writer.Close(); err != nil { - return nil, "", err - } - return bytes.NewReader(body.Bytes()), writer.FormDataContentType(), nil - case "application/octet-stream": - switch value := spec.Body.(type) { - case []byte: - return bytes.NewReader(value), contentType, nil - case string: - if strings.HasPrefix(value, "data:") { - mimeType, data, err := decodeProviderDataURL(value) - if err != nil { - return nil, "", err - } - return bytes.NewReader(data), defaultString(mimeType, contentType), nil - } - return strings.NewReader(value), contentType, nil - default: - return nil, "", fmt.Errorf("二进制协议请求体必须是字节或字符串") - } - default: - return nil, "", fmt.Errorf("声明式协议暂不支持 %s 请求体", spec.ContentType) - } -} - -func protocolBodyObject(value any) map[string]any { - result, _ := value.(map[string]any) - return result -} - -func protocolFormValues(value any) []string { - switch typed := value.(type) { - case nil: - return nil - case []any: - result := make([]string, 0, len(typed)) - for _, item := range typed { - result = append(result, protocolFormValues(item)...) - } - return result - case string: - return []string{typed} - case bool: - return []string{strconv.FormatBool(typed)} - case float64: - return []string{strconv.FormatFloat(typed, 'f', -1, 64)} - case int: - return []string{strconv.Itoa(typed)} - default: - data, err := json.Marshal(typed) - if err != nil { - return nil - } - return []string{string(data)} - } -} - -func safeProtocolFilename(value string) string { - value = strings.TrimSpace(value) - if index := strings.LastIndexAny(value, `/\\`); index >= 0 { - value = value[index+1:] - } - value = strings.Map(func(r rune) rune { - if r < 32 || r == 127 || r == '"' { - return -1 - } - return r - }, value) - if value == "" { - return "upload.bin" - } - return value -} - -func applyProtocolAuth(req *http.Request, config providerConfig, auth protocol.ManifestAuth) error { - typeName := strings.ToLower(strings.TrimSpace(auth.Type)) - if typeName == "" { - applyProviderAuth(req, config) - return nil - } - credential := protocolCredentialField(config, auth.Field) - switch typeName { - case "none": - return nil - case "bearer": - header := defaultString(strings.TrimSpace(auth.Header), "Authorization") - prefix := auth.Prefix - if prefix == "" { - prefix = "Bearer " - } - req.Header.Set(header, prefix+credential) - return nil - case "header", "api-key", "apikey": - header := strings.TrimSpace(auth.Header) - if header == "" { - return errors.New("插件 header 鉴权缺少 header 名称") - } - req.Header.Set(header, auth.Prefix+credential) - return nil - case "query": - name := defaultString(strings.TrimSpace(auth.Query), strings.TrimSpace(auth.Field)) - if name == "" { - return errors.New("插件 query 鉴权缺少参数名") - } - query := req.URL.Query() - query.Set(name, auth.Prefix+credential) - req.URL.RawQuery = query.Encode() - return nil - case "basic": - username := auth.Username - if username == "" { - username = credential - } - password := protocolCredentialField(config, auth.SecretField) - req.SetBasicAuth(username, password) - return nil - case "anthropic": - req.Header.Set(defaultString(auth.Header, "x-api-key"), credential) - if req.Header.Get("anthropic-version") == "" { - req.Header.Set("anthropic-version", "2023-06-01") - } - return nil - case "google-api-key", "gemini": - req.Header.Set(defaultString(auth.Header, "x-goog-api-key"), credential) - return nil - case "volcengine-v4": - secret := protocolCredentialField(config, auth.SecretField) - if credential == "" || secret == "" { - return errors.New("火山引擎 V4 鉴权需要 Access Key 和 Secret Key") - } - credentials := base.Credentials{ - AccessKeyID: credential, SecretAccessKey: secret, - Region: defaultString(strings.TrimSpace(auth.Region), "cn-north-1"), - Service: strings.TrimSpace(auth.Service), - } - if credentials.Service == "" { - return errors.New("火山引擎 V4 鉴权缺少 service") - } - signed := credentials.Sign(req) - *req = *signed - return nil - case "aws-sigv4": - secret := protocolCredentialField(config, auth.SecretField) - return signProtocolAWSV4(req, credential, secret, auth) - case "tc3": - secret := protocolCredentialField(config, auth.SecretField) - return signProtocolTC3(req, credential, secret, auth) - default: - return fmt.Errorf("插件声明了尚未启用的鉴权驱动 %s", auth.Type) - } -} - -func signProtocolAWSV4(req *http.Request, accessKey, secretKey string, auth protocol.ManifestAuth) error { - if strings.TrimSpace(accessKey) == "" || strings.TrimSpace(secretKey) == "" { - return errors.New("AWS SigV4 鉴权需要 Access Key ID 和 Secret Access Key") - } - serviceName := defaultString(strings.TrimSpace(auth.Service), "bedrock") - region := strings.TrimSpace(auth.Region) - if region == "" { - parts := strings.Split(strings.ToLower(req.URL.Hostname()), ".") - for index, part := range parts { - if strings.HasPrefix(part, serviceName) && index+1 < len(parts) { - region = parts[index+1] - break - } - } - } - if region == "" { - return errors.New("AWS SigV4 鉴权无法从 Base URL 推断 region,请使用包含区域的 Bedrock Runtime 地址") - } - payload, err := protocolRequestPayload(req) - if err != nil { - return err - } - now := time.Now().UTC() - amzDate := now.Format("20060102T150405Z") - dateStamp := now.Format("20060102") - payloadHash := sha256Hex(payload) - req.Header.Set("X-Amz-Date", amzDate) - req.Header.Set("X-Amz-Content-Sha256", payloadHash) - canonicalHeaders, signedHeaders := protocolCanonicalHeaders(req) - canonicalRequest := strings.Join([]string{ - req.Method, - defaultString(req.URL.EscapedPath(), "/"), - req.URL.Query().Encode(), - canonicalHeaders, - signedHeaders, - payloadHash, - }, "\n") - scope := strings.Join([]string{dateStamp, region, serviceName, "aws4_request"}, "/") - stringToSign := strings.Join([]string{"AWS4-HMAC-SHA256", amzDate, scope, sha256Hex([]byte(canonicalRequest))}, "\n") - dateKey := protocolHMAC([]byte("AWS4"+secretKey), dateStamp) - regionKey := protocolHMAC(dateKey, region) - serviceKey := protocolHMAC(regionKey, serviceName) - signingKey := protocolHMAC(serviceKey, "aws4_request") - signature := hex.EncodeToString(protocolHMAC(signingKey, stringToSign)) - req.Header.Set("Authorization", fmt.Sprintf("AWS4-HMAC-SHA256 Credential=%s/%s, SignedHeaders=%s, Signature=%s", accessKey, scope, signedHeaders, signature)) - return nil -} - -func signProtocolTC3(req *http.Request, secretID, secretKey string, auth protocol.ManifestAuth) error { - if strings.TrimSpace(secretID) == "" || strings.TrimSpace(secretKey) == "" { - return errors.New("腾讯云 TC3 鉴权需要 SecretId 和 SecretKey") - } - serviceName := defaultString(strings.TrimSpace(auth.Service), "hunyuan") - payload, err := protocolRequestPayload(req) - if err != nil { - return err - } - now := time.Now().UTC() - timestamp := now.Unix() - dateStamp := now.Format("2006-01-02") - contentType := defaultString(req.Header.Get("Content-Type"), "application/json") - req.Header.Set("Content-Type", contentType) - req.Header.Set("X-TC-Timestamp", strconv.FormatInt(timestamp, 10)) - if region := strings.TrimSpace(auth.Region); region != "" { - req.Header.Set("X-TC-Region", region) - } - canonicalHeaders := "content-type:" + strings.ToLower(strings.TrimSpace(contentType)) + "\n" + "host:" + strings.ToLower(req.URL.Host) + "\n" - signedHeaders := "content-type;host" - canonicalRequest := strings.Join([]string{req.Method, defaultString(req.URL.EscapedPath(), "/"), req.URL.Query().Encode(), canonicalHeaders, signedHeaders, sha256Hex(payload)}, "\n") - scope := dateStamp + "/" + serviceName + "/tc3_request" - stringToSign := strings.Join([]string{"TC3-HMAC-SHA256", strconv.FormatInt(timestamp, 10), scope, sha256Hex([]byte(canonicalRequest))}, "\n") - secretDate := protocolHMAC([]byte("TC3"+secretKey), dateStamp) - secretService := protocolHMAC(secretDate, serviceName) - secretSigning := protocolHMAC(secretService, "tc3_request") - signature := hex.EncodeToString(protocolHMAC(secretSigning, stringToSign)) - req.Header.Set("Authorization", fmt.Sprintf("TC3-HMAC-SHA256 Credential=%s/%s, SignedHeaders=%s, Signature=%s", secretID, scope, signedHeaders, signature)) - return nil -} - -func protocolRequestPayload(req *http.Request) ([]byte, error) { - if req.Body == nil { - return nil, nil - } - var reader io.ReadCloser - var err error - if req.GetBody != nil { - reader, err = req.GetBody() - } else { - reader = req.Body - } - if err != nil { - return nil, err - } - data, err := io.ReadAll(reader) - if req.GetBody != nil { - _ = reader.Close() - } else { - req.Body = io.NopCloser(bytes.NewReader(data)) - } - return data, err -} - -func protocolCanonicalHeaders(req *http.Request) (string, string) { - values := map[string]string{"host": strings.ToLower(req.URL.Host)} - for name, entries := range req.Header { - lower := strings.ToLower(strings.TrimSpace(name)) - if lower == "authorization" || lower == "user-agent" || lower == "content-length" || lower == "expect" { - continue - } - cleaned := make([]string, 0, len(entries)) - for _, entry := range entries { - cleaned = append(cleaned, strings.Join(strings.Fields(entry), " ")) - } - values[lower] = strings.Join(cleaned, ",") - } - keys := make([]string, 0, len(values)) - for key := range values { - keys = append(keys, key) - } - sort.Strings(keys) - var canonical strings.Builder - for _, key := range keys { - canonical.WriteString(key) - canonical.WriteByte(':') - canonical.WriteString(values[key]) - canonical.WriteByte('\n') - } - return canonical.String(), strings.Join(keys, ";") -} - -func protocolHMAC(key []byte, value string) []byte { - mac := hmac.New(sha256.New, key) - _, _ = mac.Write([]byte(value)) - return mac.Sum(nil) -} - -func sha256Hex(value []byte) string { - hash := sha256.Sum256(value) - return hex.EncodeToString(hash[:]) -} - -func protocolCredentialField(config providerConfig, field string) string { - switch strings.ToLower(strings.TrimSpace(field)) { - case "secretkey", "secret_key", "secret": - return strings.TrimSpace(config.SecretKey) - default: - return strings.TrimSpace(config.APIKey) - } -} - -func protocolRequestURL(baseURL string, spec protocol.RequestSpec) (string, error) { - if !spec.OriginPath { - return appendProtocolQuery(apiURL(baseURL, spec.Path), spec.Query) - } - base, err := url.Parse(strings.TrimSpace(baseURL)) - if err != nil || base.Scheme == "" || base.Host == "" { - return "", fmt.Errorf("协议根路径请求的 Base URL 无效") - } - requestPath, err := url.Parse(spec.Path) - if err != nil || !strings.HasPrefix(requestPath.Path, "/") { - return "", fmt.Errorf("协议根路径请求必须使用绝对路径") - } - base.Path = requestPath.Path - base.RawPath = requestPath.RawPath - base.RawQuery = requestPath.RawQuery - base.Fragment = "" - return appendProtocolQuery(base.String(), spec.Query) -} - -func appendProtocolQuery(rawURL string, values map[string][]string) (string, error) { - if len(values) == 0 { - return rawURL, nil - } - parsed, err := url.Parse(rawURL) - if err != nil { - return "", err - } - query := parsed.Query() - for key, items := range values { - for _, item := range items { - query.Add(key, item) - } - } - parsed.RawQuery = query.Encode() - return parsed.String(), nil -} - -func finishProtocolResult(ctx context.Context, config providerConfig, mode string, result *protocol.Result) (map[string]interface{}, error) { - if result == nil { - return nil, errors.New("声明式协议已完成但没有返回结果") - } - if mode == "text" { - output := map[string]interface{}{"mode": "text", "text": result.Text} - if strings.TrimSpace(result.Reasoning) != "" { - output["reasoning"] = result.Reasoning - } - return output, nil - } - var references []protocol.MediaReference - switch mode { - case "image": - references = result.Images - case "video": - references = result.Videos - case "audio": - references = result.Audios - default: - return nil, fmt.Errorf("声明式协议不支持生成模式 %s", mode) - } - if len(references) == 0 { - return nil, errors.New("声明式协议已完成但没有返回媒体地址") - } - items := make([]interface{}, 0, len(references)) - for _, reference := range references { - data, mimeType, err := protocolMediaBytes(ctx, config, reference) - if err != nil { - return nil, err - } - item := map[string]interface{}{"dataUrl": dataURL(mimeType, data), "mimeType": mimeType} - items = append(items, item) - } - switch mode { - case "image": - return map[string]interface{}{"mode": "image", "images": items}, nil - case "video": - return map[string]interface{}{"mode": "video", "video": items[0]}, nil - default: - return map[string]interface{}{"mode": "audio", "audio": items[0]}, nil - } -} - -func finishProtocolAdapterResult(ctx context.Context, input canvasGenerationInput, adapter protocol.Adapter, request protocol.GenerationRequest, taskID string, result *protocol.Result) (map[string]interface{}, error) { - if protocolResultHasOutput(input.Mode, result) { - return finishProtocolResult(ctx, input.Config, input.Mode, result) - } - resultAdapter, ok := adapter.(protocol.ResultAdapter) - capability, hasCapability := adapter.(protocol.ResultCapability) - if !ok || !hasCapability || !capability.ResultAvailable() { - return finishProtocolResult(ctx, input.Config, input.Mode, result) - } - spec, err := resultAdapter.BuildResult(ctx, protocol.PollContext{BaseURL: input.Config.BaseURL, Model: request.Model, Request: request, TaskID: taskID}) - if err != nil { - return nil, err - } - data, mimeType, err := executeProtocolBinaryRequest(withProviderRequestKind(ctx, "download"), input.Config, spec) - if err != nil { - return nil, fmt.Errorf("声明式协议结果下载失败:%w", err) - } - if len(data) == 0 { - return nil, errors.New("声明式协议结果下载返回空内容") - } - mimeType = strings.TrimSpace(strings.Split(mimeType, ";")[0]) - if mimeType == "" { - mimeType = "application/octet-stream" - } - reference := protocol.MediaReference{DataURL: dataURL(mimeType, data), MIMEType: mimeType} - downloaded := &protocol.Result{} - switch input.Mode { - case "image": - downloaded.Images = []protocol.MediaReference{reference} - case "video": - downloaded.Videos = []protocol.MediaReference{reference} - case "audio": - downloaded.Audios = []protocol.MediaReference{reference} - default: - return nil, fmt.Errorf("声明式协议结果下载不支持生成模式 %s", input.Mode) - } - return finishProtocolResult(ctx, input.Config, input.Mode, downloaded) -} - -func protocolResultHasOutput(mode string, result *protocol.Result) bool { - if result == nil { - return false - } - switch mode { - case "text": - return strings.TrimSpace(result.Text) != "" - case "image": - return len(result.Images) > 0 - case "video": - return len(result.Videos) > 0 - case "audio": - return len(result.Audios) > 0 - default: - return false - } -} - -func protocolMediaBytes(ctx context.Context, config providerConfig, reference protocol.MediaReference) ([]byte, string, error) { - if strings.TrimSpace(reference.DataURL) != "" { - mimeType, data, err := decodeProviderDataURL(reference.DataURL) - return data, mimeType, err - } - value := strings.TrimSpace(reference.URL) - if value == "" { - return nil, "", errors.New("声明式协议媒体结果地址为空") - } - var data []byte - var mimeType string - var err error - for attempt := 0; attempt < 3; attempt++ { - data, mimeType, err = getProviderExternalBinary(withProviderRequestKind(ctx, "download"), config, value) - if err == nil { - return data, normalizedMediaMimeType(mimeType, data), nil - } - if attempt == 2 || !retryableProtocolMediaDownload(err) { - break - } - if waitErr := sleepContext(ctx, time.Duration(attempt+1)*time.Second); waitErr != nil { - return nil, "", fmt.Errorf("声明式协议媒体结果下载失败:%w", waitErr) - } - } - return nil, "", fmt.Errorf("声明式协议媒体结果下载失败:%w", err) -} - -func retryableProtocolMediaDownload(err error) bool { - if err == nil || errors.Is(err, context.Canceled) || errors.Is(err, context.DeadlineExceeded) { - return false - } - var networkError net.Error - if errors.As(err, &networkError) && (networkError.Timeout() || networkError.Temporary()) { - return true - } - message := strings.ToLower(err.Error()) - for _, marker := range []string{"tls handshake timeout", "connection reset", "unexpected eof", "broken pipe"} { - if strings.Contains(message, marker) { - return true - } - } - return false -} - -func protocolResultError(message, taskID string) error { - message = strings.TrimSpace(message) - if message == "" { - message = "上游返回失败状态" - } - if taskID == "" { - return errors.New(message) - } - return fmt.Errorf("声明式协议任务失败(任务 %s):%s", taskID, message) -} - -func runAsyncAudioTask(ctx context.Context, input canvasGenerationInput, body map[string]interface{}, format string) (map[string]interface{}, error) { - id := resumedProviderRequestID(ctx) - var state map[string]interface{} - if id == "" { - if err := postJSON(ctx, input.Config, "/audio/tasks", body, &state); err != nil { - return nil, err - } - state = asyncAudioPayload(state) - id = firstNonEmptyString(stringField(state, "id"), stringField(state, "task_id"), stringField(state, "request_id")) - if id == "" { - return nil, errors.New("异步音频接口没有返回任务 ID") - } - if asyncAudioSucceeded(state) { - return asyncAudioResult(ctx, input.Config, id, state, format) - } - } - for deadline := providerPollingDeadline(ctx); time.Now().Before(deadline); { - state = map[string]interface{}{} - pollCtx := withProviderRequestKind(ctx, "poll") - if err := getJSON(pollCtx, input.Config, "/audio/tasks/"+url.PathEscape(id), &state); err != nil { - return nil, err - } - state = asyncAudioPayload(state) - if asyncAudioSucceeded(state) { - return asyncAudioResult(ctx, input.Config, id, state, format) - } - status := strings.ToLower(strings.TrimSpace(stringField(state, "status"))) - if status == "failed" || status == "cancelled" || status == "canceled" || status == "expired" || status == "error" { - return nil, fmt.Errorf("异步音频生成失败(任务 %s):%s", id, asyncAudioErrorMessage(state)) - } - if err := sleepContext(ctx, 2500*time.Millisecond); err != nil { - return nil, err - } - } - return nil, fmt.Errorf("异步音频生成超时(任务 %s)", id) -} - -func asyncAudioPayload(payload map[string]interface{}) map[string]interface{} { - for _, key := range []string{"data", "result", "output"} { - if nested, ok := payload[key].(map[string]interface{}); ok { - for parentKey, parentValue := range payload { - if parentKey == "data" || parentKey == "result" || parentKey == "output" { - continue - } - if _, exists := nested[parentKey]; !exists { - nested[parentKey] = parentValue - } - } - return nested - } - } - return payload -} - -func asyncAudioSucceeded(state map[string]interface{}) bool { - status := strings.ToLower(strings.TrimSpace(stringField(state, "status"))) - done, _ := state["done"].(bool) - return done || status == "completed" || status == "succeeded" || status == "success" || status == "done" || (status == "" && asyncAudioResultURL(state) != "") -} - -func asyncAudioResult(ctx context.Context, config providerConfig, id string, state map[string]interface{}, format string) (map[string]interface{}, error) { - resultURL := asyncAudioResultURL(state) - var data []byte - var mimeType string - var err error - if strings.HasPrefix(resultURL, "data:") { - mimeType, data, err = decodeProviderDataURL(resultURL) - if err == nil { - limit, limitErr := providerGeneratedFileLimit(ctx) - if limitErr != nil { - err = limitErr - } else if int64(len(data)) > limit { - err = fmt.Errorf("异步音频结果超过 %s 限制", formatStorageLimit(limit)) - } - } - } else if isPublicMediaURL(resultURL) { - data, mimeType, err = getExternalBinary(withProviderRequestKind(ctx, "download"), resultURL) - } else { - data, mimeType, err = getBinary(withProviderRequestKind(ctx, "download"), config, "/audio/tasks/"+url.PathEscape(id)+"/content") - } - if err != nil { - return nil, fmt.Errorf("异步音频结果下载失败(任务 %s):%w", id, err) - } - mimeType, err = validateGeneratedAudio(mimeType, data, format) - if err != nil { - return nil, fmt.Errorf("异步音频结果无效(任务 %s):%w", id, err) - } - return map[string]interface{}{"mode": "audio", "audio": map[string]interface{}{"dataUrl": dataURL(mimeType, data), "mimeType": mimeType, "format": format}}, nil -} - -func asyncAudioResultURL(state map[string]interface{}) string { - for _, key := range []string{"audio_url", "audioUrl", "result_url", "resultUrl", "output_url", "outputUrl", "url", "data"} { - if value := strings.TrimSpace(stringField(state, key)); strings.HasPrefix(value, "data:") || isPublicMediaURL(value) { - return value - } - } - for _, key := range []string{"audio", "data", "result", "output"} { - if nested, ok := state[key].(map[string]interface{}); ok { - if value := asyncAudioResultURL(nested); value != "" { - return value - } - } - } - return "" -} - -func asyncAudioErrorMessage(state map[string]interface{}) string { - _, message := providerFailureDetails(state) - return defaultString(message, firstNonEmptyString(stringField(state, "message"), "上游返回失败状态")) -} - -func decodeProviderDataURL(value string) (string, []byte, error) { - header, encoded, ok := strings.Cut(value, ",") - if !ok || !strings.HasPrefix(header, "data:") || !strings.HasSuffix(strings.ToLower(header), ";base64") { - return "", nil, errors.New("音频 data URL 格式无效") - } - mimeType := strings.TrimSuffix(strings.TrimPrefix(header, "data:"), ";base64") - data, err := base64.StdEncoding.DecodeString(encoded) - return mimeType, data, err -} - -func providerGeneratedFileLimit(ctx context.Context) (int64, error) { - metadata, ok := ctx.Value(providerAnalyticsKey{}).(providerAnalyticsContext) - if !ok || metadata.Service == nil { - return maxProviderResponseBytes, nil - } - policy, err := metadata.Service.RuntimePolicy() - if err != nil { - return 0, fmt.Errorf("读取生成资源限制失败:%w", err) - } - return megabytes(policy.Resource.GeneratedFileMB), nil -} - -func validateGeneratedAudio(declared string, data []byte, format string) (string, error) { - if len(data) == 0 { - return "", errors.New("音频内容为空") - } - detected := strings.ToLower(strings.TrimSpace(strings.Split(http.DetectContentType(data), ";")[0])) - if strings.Contains(detected, "json") || strings.HasPrefix(detected, "text/") || strings.HasPrefix(detected, "image/") || strings.HasPrefix(detected, "video/") { - return "", fmt.Errorf("上游返回了非音频内容:%s", detected) - } - mimeType := strings.ToLower(strings.TrimSpace(strings.Split(declared, ";")[0])) - resolved := "" - if strings.HasPrefix(mimeType, "audio/") { - resolved = mimeType - } else if strings.HasPrefix(detected, "audio/") { - resolved = detected - } else if fallback := audioFormatMimeType(format); fallback != "" && (mimeType == "" || mimeType == "application/octet-stream") { - resolved = fallback - } - if resolved == "" { - return "", fmt.Errorf("上游响应类型不是音频:%s", defaultString(mimeType, detected)) - } - if !audioSignatureMatches(resolved, data) { - return "", fmt.Errorf("音频内容与格式不匹配:%s", resolved) - } - return resolved, nil -} - -func audioSignatureMatches(mimeType string, data []byte) bool { - if strings.Contains(mimeType, "pcm") || mimeType == "audio/l16" { - return len(data) > 0 - } - if strings.Contains(mimeType, "mpeg") || strings.Contains(mimeType, "mp3") { - return bytes.HasPrefix(data, []byte("ID3")) || (len(data) >= 2 && data[0] == 0xff && data[1]&0xe0 == 0xe0) - } - if strings.Contains(mimeType, "wav") || strings.Contains(mimeType, "wave") { - return len(data) >= 12 && bytes.Equal(data[:4], []byte("RIFF")) && bytes.Equal(data[8:12], []byte("WAVE")) - } - if strings.Contains(mimeType, "opus") || strings.Contains(mimeType, "ogg") { - return bytes.HasPrefix(data, []byte("OggS")) - } - if strings.Contains(mimeType, "flac") { - return bytes.HasPrefix(data, []byte("fLaC")) - } - if strings.Contains(mimeType, "aac") { - return bytes.HasPrefix(data, []byte("ADIF")) || (len(data) >= 2 && data[0] == 0xff && data[1]&0xf0 == 0xf0) - } - return false -} - -func audioFormatMimeType(format string) string { - switch strings.ToLower(strings.TrimSpace(format)) { - case "wav": - return "audio/wav" - case "opus": - return "audio/opus" - case "aac": - return "audio/aac" - case "flac": - return "audio/flac" - case "pcm": - return "audio/pcm" - case "mp3": - return "audio/mpeg" - default: - return "" - } -} - -func runVideoTask(ctx context.Context, input canvasGenerationInput) (map[string]interface{}, error) { - if _, ok := declarativeProtocolAdapterForContext(ctx, input.Config.InterfaceType); ok { - return runDeclarativeProtocolTask(ctx, input) - } - if input.Config.InterfaceType == string(model.ChannelInterfaceAgnesVideo) { - adapter, ok := protocolAdapterForContext(ctx, input.Config.InterfaceType) - if !ok { - return nil, errors.New("Agnes 视频插件未安装") - } - return runProtocolAdapterTask(ctx, input, adapter) - } - if input.Config.InterfaceType == string(model.ChannelInterfaceMiniMaxVideo) { - return runMiniMaxVideoTask(ctx, input) - } - if input.Config.InterfaceType == string(model.ChannelInterfaceVolcengineJiMengVideo) { - return runVolcengineJiMengVideoTask(ctx, input) - } - if input.Config.InterfaceType == "gemini-veo" { - return runGeminiVeoVideoTask(ctx, input) - } - if input.Config.InterfaceType == string(model.ChannelInterfaceNovitaVideo) { - return runNovitaVideoTask(ctx, input) - } - if input.Config.InterfaceType == "newapi-channel-2" { - return runNewAPIChannel2VideoTask(ctx, input) - } - if input.Config.InterfaceType == "newapi-channel-1" { - return runNewAPIChannel1VideoTask(ctx, input) - } - if input.Config.InterfaceType == string(model.ChannelInterfaceVolcengineArkVideo) { - return runSeedanceAgentPlanVideoTask(ctx, input) - } - if isArkPlanVideoConfig(input.Config) { - return runSeedanceAgentPlanVideoTask(ctx, input) - } - if isSeedanceVideoConfig(input.Config) { - return runSeedanceVideosTask(ctx, input) - } - if len(input.ReferenceVideos) > 0 || len(input.ReferenceAudios) > 0 { - return nil, errors.New("OpenAI 风格视频接口不支持参考视频或参考音频,请切换到 Seedance / Agent Plan 渠道") - } - id := resumedProviderRequestID(ctx) - var created map[string]interface{} - if id == "" && (input.Config.InterfaceType == "xai-video" || isGrokVideoConfig(input.Config)) { - var requestBody interface{} - var err error - if input.Config.InterfaceType == "xai-video" { - requestBody, err = xaiVideoRequestBody(input) - } else { - requestBody, err = grokVideoBody(input) - } - if err != nil { - return nil, err - } - createPath := "/videos" - if input.Config.InterfaceType == "xai-video" { - createPath = "/videos/generations" - } - if err := postJSON(ctx, input.Config, createPath, requestBody, &created); err != nil { - return nil, err - } - } else if id == "" { - body := &bytes.Buffer{} - writer := multipart.NewWriter(body) - writeField(writer, "model", input.Config.Model) - writeField(writer, "prompt", newAPIVideoPromptText(input)) - writeField(writer, "seconds", defaultString(input.Config.VideoSeconds, "6")) - if size := normalizeVideoSize(input.Config.Size); size != "" { - writeField(writer, "size", size) - } - if resolution := videoResolutionNameRequest(input.VideoCapability, input.Config.VQuality); resolution != "" { - writeField(writer, "resolution_name", resolution) - } - writeField(writer, "preset", "normal") - if shouldSendNewAPIVideoImages(input) { - for _, image := range input.ReferenceImages { - if err := writeMediaPart(writer, "input_reference[]", image); err != nil { - return nil, err - } - } - } - if err := writer.Close(); err != nil { - return nil, err - } - if err := postForm(ctx, input.Config, "/videos", writer.FormDataContentType(), body, &created); err != nil { - return nil, err - } - } - if id == "" { - id = firstNonEmptyString(stringField(created, "id"), stringField(created, "request_id"), stringField(created, "task_id")) - } - if id == "" { - if data, ok := created["data"].(map[string]interface{}); ok { - id = firstNonEmptyString(stringField(data, "id"), stringField(data, "request_id"), stringField(data, "task_id")) - } - } - if id == "" { - return nil, errors.New("视频接口没有返回任务 ID") - } - for deadline := providerPollingDeadline(ctx); time.Now().Before(deadline); { - var state map[string]interface{} - if err := getJSON(ctx, input.Config, "/videos/"+id, &state); err != nil { - return nil, err - } - if data, ok := state["data"].(map[string]interface{}); ok { - state = data - } - status := strings.ToLower(stringField(state, "status")) - if status == "completed" || status == "succeeded" || status == "success" || status == "done" { - if videoURL := newAPIVideoResultURL(state); videoURL != "" { - if input.Config.InterfaceType == "xai-video" { - if _, validationErr := ValidateOutboundURL(videoURL); validationErr != nil { - data, mimeType, err := getBinary(ctx, input.Config, "/videos/"+id+"/content") - if err != nil { - return nil, err - } - mimeType = normalizedMediaMimeType(mimeType, data) - return map[string]interface{}{"mode": "video", "video": map[string]interface{}{"dataUrl": dataURL(mimeType, data), "mimeType": mimeType}}, nil - } - } - data, mimeType, err := getProviderExternalBinary(withProviderRequestKind(ctx, "download"), input.Config, videoURL) - if err != nil { - return nil, fmt.Errorf("视频结果下载失败(任务 %s):%w", id, err) - } - mimeType = normalizedMediaMimeType(mimeType, data) - return map[string]interface{}{"mode": "video", "video": map[string]interface{}{"dataUrl": dataURL(mimeType, data), "mimeType": mimeType}}, nil - } - data, mimeType, err := getBinary(ctx, input.Config, "/videos/"+id+"/content") - if err != nil { - return nil, err - } - return map[string]interface{}{"mode": "video", "video": map[string]interface{}{"dataUrl": dataURL(mimeType, data), "mimeType": mimeType}}, nil - } - if status == "failed" || status == "cancelled" { - return nil, errors.New("视频生成失败") - } - if err := sleepContext(ctx, 2500*time.Millisecond); err != nil { - return nil, err - } - } - return nil, errors.New("视频生成超时") -} - -func runMiniMaxVideoTask(ctx context.Context, input canvasGenerationInput) (map[string]interface{}, error) { - if strings.TrimSpace(input.Prompt) == "" { - return nil, errors.New("MiniMax 视频提示词不能为空") - } - if len(input.ReferenceImages) > 9 || len(input.ReferenceVideos) > 3 || len(input.ReferenceAudios) > 3 { - return nil, errors.New("MiniMax 视频最多支持 9 张参考图、3 个参考视频和 3 个参考音频") - } - operation := metadataString(input.Metadata, "videoEditOperation") - startFrameID := metadataString(input.Metadata, "videoStartFrameNodeId") - endFrameID := metadataString(input.Metadata, "videoEndFrameNodeId") - frameMode := operation != "reference_to_video" && (startFrameID != "" || endFrameID != "") - content := []miniMaxVideoContent{{Type: "text", Text: strings.TrimSpace(input.Prompt)}} - for _, image := range input.ReferenceImages { - url, err := miniMaxMediaURLValue(image) - if err != nil { - return nil, fmt.Errorf("MiniMax 参考图无效:%w", err) - } - role := videoImageRole(input, image) - if frameMode && role == "reference_image" { - return nil, errors.New("MiniMax 首尾帧模式不能混合未标记的参考图") - } - content = append(content, miniMaxVideoContent{Type: "image_url", ImageURL: &miniMaxMediaURL{URL: url}, Role: role}) - } - for _, video := range input.ReferenceVideos { - url, err := miniMaxMediaURLValue(video) - if err != nil { - return nil, fmt.Errorf("MiniMax 参考视频无效:%w", err) - } - content = append(content, miniMaxVideoContent{Type: "video_url", VideoURL: &miniMaxMediaURL{URL: url}, Role: "reference_video"}) - } - for _, audio := range input.ReferenceAudios { - url, err := miniMaxMediaURLValue(audio) - if err != nil { - return nil, fmt.Errorf("MiniMax 参考音频无效:%w", err) - } - content = append(content, miniMaxVideoContent{Type: "audio_url", AudioURL: &miniMaxMediaURL{URL: url}, Role: "reference_audio"}) - } - watermark := parseBool(input.Config.VideoWatermark, false) - body := miniMaxVideoRequest{ - Model: input.Config.Model, - Content: content, - Resolution: normalizeMiniMaxResolution(input.Config.VQuality), - Duration: normalizeMiniMaxDuration(input.Config.VideoSeconds), - Ratio: normalizeMiniMaxRatio(input.Config.Size, frameMode), - AIGCWatermark: &watermark, - } - id := resumedProviderRequestID(ctx) - if id == "" { - var created map[string]interface{} - if err := postJSON(ctx, input.Config, "/v2/video_generation", body, &created); err != nil { - return nil, err - } - id = firstNonEmptyString(stringField(created, "task_id"), stringField(created, "id")) - if data, ok := created["data"].(map[string]interface{}); ok { - id = firstNonEmptyString(id, stringField(data, "task_id"), stringField(data, "id")) - } - } - if id == "" { - return nil, errors.New("MiniMax 视频接口没有返回任务 ID") - } - for deadline := providerPollingDeadline(ctx); time.Now().Before(deadline); { - var response map[string]interface{} - if err := getJSON(ctx, input.Config, "/v2/query/video_generation/"+url.PathEscape(id), &response); err != nil { - return nil, err - } - task, _ := response["task"].(map[string]interface{}) - if task == nil { - task = response - } - status := strings.ToLower(stringField(task, "status")) - if status == "succeeded" || status == "completed" { - contentValue, _ := task["content"].(map[string]interface{}) - videoURL := stringField(contentValue, "url") - if videoURL == "" { - return nil, fmt.Errorf("MiniMax 视频任务 %s 已完成但没有返回视频 URL", id) - } - data, mimeType, err := getProviderExternalBinary(withProviderRequestKind(ctx, "download"), input.Config, videoURL) - if err != nil { - return nil, fmt.Errorf("MiniMax 视频结果下载失败(任务 %s):%w", id, err) - } - mimeType = normalizedMediaMimeType(mimeType, data) - return map[string]interface{}{"mode": "video", "video": map[string]interface{}{"dataUrl": dataURL(mimeType, data), "mimeType": mimeType}}, nil - } - if status == "failed" || status == "cancelled" { - return nil, fmt.Errorf("MiniMax 视频生成失败(任务 %s):%s", id, miniMaxTaskError(task)) - } - if err := sleepContext(ctx, 2500*time.Millisecond); err != nil { - return nil, err - } - } - return nil, fmt.Errorf("MiniMax 视频生成超时(任务 %s)", id) -} - -func miniMaxMediaURLValue(media providerMedia) (string, error) { - value := strings.TrimSpace(media.URL) - if !isPublicMediaURL(value) { - return "", errors.New("参考素材必须使用公网 HTTP(S) URL,请启用对象存储或提供公网素材地址") - } - if _, err := ValidateOutboundURL(value); err != nil { - return "", err - } - return value, nil -} - -func normalizeMiniMaxResolution(value string) string { - normalized := strings.ToLower(strings.TrimSpace(value)) - if normalized == "2k" || normalized == "4k" || normalized == "high" || normalized == "1080" || normalized == "1080p" || normalized == "1440p" || normalized == "2160p" { - return "2K" - } - return "768P" -} - -func normalizeMiniMaxDuration(value string) int { - duration, err := strconv.Atoi(strings.TrimSpace(value)) - if err != nil { - duration = 5 - } - if duration < 4 { - return 4 - } - if duration > 15 { - return 15 - } - return duration -} - -func normalizeMiniMaxRatio(value string, frameMode bool) string { - if frameMode { - return "adaptive" - } - allowed := map[string]bool{"adaptive": true, "21:9": true, "16:9": true, "4:3": true, "1:1": true, "3:4": true, "9:16": true} - value = strings.TrimSpace(value) - if allowed[value] { - return value - } - return "16:9" -} - -func miniMaxTaskError(task map[string]interface{}) string { - if value, ok := task["error"].(map[string]interface{}); ok { - message := stringField(value, "message") - code := stringField(value, "code") - if message != "" && code != "" { - return code + ":" + message - } - if message != "" { - return message - } - } - return "上游返回失败" -} - -func runGeminiVeoVideoTask(ctx context.Context, input canvasGenerationInput) (map[string]interface{}, error) { - if len(input.ReferenceImages) > 1 || len(input.ReferenceVideos) > 0 || len(input.ReferenceAudios) > 0 { - return nil, errors.New("Gemini Veo 当前只支持 1 张起始图,不支持参考视频或音频") - } - if len(input.ReferenceImages) > 0 && metadataString(input.Metadata, "videoEditOperation") == "reference_to_video" { - return nil, errors.New("Gemini Veo 当前不支持角色或风格参考图生视频,请改用支持 reference_to_video 的模型") - } - id := resumedProviderRequestID(ctx) - if id == "" { - instance := geminiVeoInstance{Prompt: strings.TrimSpace(input.Prompt)} - if len(input.ReferenceImages) == 1 { - raw, mimeType, err := mediaBytes(input.ReferenceImages[0]) - if err != nil { - return nil, fmt.Errorf("读取 Gemini Veo 起始图失败:%w", err) - } - instance.Image = &geminiVeoImage{BytesBase64Encoded: base64.StdEncoding.EncodeToString(raw), MIMEType: mimeType} - } - body := geminiVeoRequest{ - Instances: []geminiVeoInstance{instance}, - Parameters: geminiVeoParameters{ - AspectRatio: normalizeNewAPIChannel2Ratio(input.Config.Size, strings.ToLower(input.Config.Model)), - DurationSeconds: normalizeSeedanceVideosDuration(input.Config.VideoSeconds), - Resolution: normalizeNewAPIChannel2Resolution(input.Config.VQuality, strings.ToLower(input.Config.Model)), - SampleCount: 1, - }, - } - var created map[string]interface{} - if err := postGeminiJSON(ctx, input.Config, "/models/"+url.PathEscape(input.Config.Model)+":predictLongRunning", body, &created); err != nil { - return nil, err - } - id = strings.TrimSpace(stringField(created, "name")) - } - if id == "" { - return nil, errors.New("Gemini Veo 没有返回 operation name") - } - for deadline := providerPollingDeadline(ctx); time.Now().Before(deadline); { - var operation map[string]interface{} - if err := getGeminiJSON(ctx, input.Config, "/"+strings.TrimLeft(id, "/"), &operation); err != nil { - return nil, err - } - if errorValue, ok := operation["error"].(map[string]interface{}); ok && stringField(errorValue, "message") != "" { - return nil, fmt.Errorf("Gemini Veo 视频生成失败(任务 %s):%s", id, stringField(errorValue, "message")) - } - done, _ := operation["done"].(bool) - if done { - videoURL := findProviderMediaURL(operation["response"]) - if videoURL == "" { - return nil, fmt.Errorf("Gemini Veo 任务 %s 已完成但没有返回视频地址", id) - } - data, mimeType, err := getGeminiBinary(withProviderRequestKind(ctx, "download"), input.Config, videoURL) - if err != nil { - return nil, fmt.Errorf("Gemini Veo 视频下载失败(任务 %s):%w", id, err) - } - mimeType = normalizedMediaMimeType(mimeType, data) - return map[string]interface{}{"mode": "video", "video": map[string]interface{}{"dataUrl": dataURL(mimeType, data), "mimeType": mimeType}}, nil - } - if err := sleepContext(ctx, 5*time.Second); err != nil { - return nil, err - } - } - return nil, fmt.Errorf("Gemini Veo 视频生成超时(任务 %s)", id) -} - -func runNovitaVideoTask(ctx context.Context, input canvasGenerationInput) (map[string]interface{}, error) { - if len(input.ReferenceImages) > 1 || len(input.ReferenceVideos) > 0 || len(input.ReferenceAudios) > 0 { - return nil, errors.New("Novita 视频当前只支持 1 张起始图,不支持参考视频或参考音频") - } - if len(input.ReferenceImages) > 0 && metadataString(input.Metadata, "videoEditOperation") == "reference_to_video" { - return nil, errors.New("Novita 视频当前不支持角色或风格参考图生视频,请改用支持 reference_to_video 的模型") - } - id := resumedProviderRequestID(ctx) - if id == "" { - body := map[string]interface{}{ - "model": input.Config.Model, - "prompt": strings.TrimSpace(input.Prompt), - "duration": normalizeNovitaVideoDuration(input.Config.VideoSeconds), - } - if len(input.ReferenceImages) == 1 { - imageValue, err := novitaVideoImageValue(input.ReferenceImages[0]) - if err != nil { - return nil, err - } - body["image"] = imageValue - } else { - body["aspect_ratio"] = normalizeNovitaVideoRatio(input.Config.Size) - } - var created map[string]interface{} - if err := postNovitaJSON(ctx, input.Config, "/video/create", body, &created); err != nil { - return nil, err - } - id = strings.TrimSpace(stringField(created, "task_id")) - } - if id == "" { - return nil, errors.New("Novita 视频接口没有返回任务 ID") - } - for deadline := providerPollingDeadline(ctx); time.Now().Before(deadline); { - var result map[string]interface{} - if err := getNovitaJSON(ctx, input.Config, "/async/task-result?task_id="+url.QueryEscape(id), &result); err != nil { - return nil, err - } - task, _ := result["task"].(map[string]interface{}) - switch stringField(task, "status") { - case "TASK_STATUS_SUCCEED": - videos, _ := result["videos"].([]interface{}) - if len(videos) == 0 { - return nil, fmt.Errorf("Novita 视频任务 %s 已完成但没有返回视频", id) - } - first, _ := videos[0].(map[string]interface{}) - videoURL := strings.TrimSpace(stringField(first, "video_url")) - if videoURL == "" { - return nil, fmt.Errorf("Novita 视频任务 %s 已完成但没有返回视频地址", id) - } - data, mimeType, err := getExternalBinary(withProviderRequestKind(ctx, "download"), videoURL) - if err != nil { - return nil, fmt.Errorf("Novita 视频结果下载失败(任务 %s):%w", id, err) - } - mimeType = normalizedMediaMimeType(mimeType, data) - return map[string]interface{}{"mode": "video", "video": map[string]interface{}{"dataUrl": dataURL(mimeType, data), "mimeType": mimeType}}, nil - case "TASK_STATUS_FAILED": - reason := firstNonEmptyString(stringField(task, "reason"), "上游返回失败") - return nil, fmt.Errorf("Novita 视频生成失败(任务 %s):%s", id, reason) - } - if err := sleepContext(ctx, 5*time.Second); err != nil { - return nil, err - } - } - return nil, fmt.Errorf("Novita 视频生成超时(任务 %s)", id) -} - -func novitaVideoImageValue(media providerMedia) (string, error) { - if isPublicMediaURL(media.URL) { - if _, err := ValidateOutboundURL(media.URL); err != nil { - return "", err - } - return media.URL, nil - } - raw, mimeType, err := mediaBytes(media) - if err != nil { - return "", err - } - return dataURL(mimeType, raw), nil -} - -func normalizeNovitaVideoDuration(value string) string { - if normalizeSeedanceDuration(value) >= 8 { - return "10" - } - return "5" -} - -func normalizeNovitaVideoRatio(value string) string { - switch strings.TrimSpace(value) { - case "16:9", "9:16", "1:1": - return strings.TrimSpace(value) - default: - return "16:9" - } -} - -func novitaVideoURL(baseURL string, path string) string { - base := strings.TrimRight(strings.TrimSpace(baseURL), "/") - return base + "/" + strings.TrimLeft(path, "/") -} - -func postNovitaJSON(ctx context.Context, config providerConfig, path string, body interface{}, target interface{}) error { - data, _ := json.Marshal(body) - req, err := http.NewRequestWithContext(ctx, http.MethodPost, novitaVideoURL(config.BaseURL, path), bytes.NewReader(data)) - if err != nil { - return err - } - req.Header.Set("Authorization", "Bearer "+config.APIKey) - req.Header.Set("Content-Type", "application/json") - ApplyOutboundHeaders(req, config.Headers) - return doJSON(req, target) -} - -func getNovitaJSON(ctx context.Context, config providerConfig, path string, target interface{}) error { - req, err := http.NewRequestWithContext(ctx, http.MethodGet, novitaVideoURL(config.BaseURL, path), nil) - if err != nil { - return err - } - req.Header.Set("Authorization", "Bearer "+config.APIKey) - ApplyOutboundHeaders(req, config.Headers) - return doJSON(req, target) -} - -func postGeminiJSON(ctx context.Context, config providerConfig, path string, body interface{}, target interface{}) error { - data, _ := json.Marshal(body) - req, err := http.NewRequestWithContext(ctx, http.MethodPost, geminiVeoURL(config.BaseURL, path), bytes.NewReader(data)) - if err != nil { - return err - } - req.Header.Set("x-goog-api-key", config.APIKey) - req.Header.Set("Content-Type", "application/json") - ApplyOutboundHeaders(req, config.Headers) - return doJSON(req, target) -} - -func getGeminiJSON(ctx context.Context, config providerConfig, path string, target interface{}) error { - req, err := http.NewRequestWithContext(ctx, http.MethodGet, geminiVeoURL(config.BaseURL, path), nil) - if err != nil { - return err - } - req.Header.Set("x-goog-api-key", config.APIKey) - ApplyOutboundHeaders(req, config.Headers) - return doJSON(req, target) -} - -func getGeminiBinary(ctx context.Context, config providerConfig, rawURL string) ([]byte, string, error) { - req, err := http.NewRequestWithContext(ctx, http.MethodGet, rawURL, nil) - if err != nil { - return nil, "", err - } - req.Header.Set("x-goog-api-key", config.APIKey) - ApplyOutboundHeaders(req, config.Headers) - return doBinary(req) -} - -func geminiVeoURL(baseURL string, path string) string { - return apiURLWithDefaultPrefix(baseURL, path, "/v1beta") -} - -func findProviderMediaURL(value interface{}) string { - switch typed := value.(type) { - case map[string]interface{}: - for _, key := range []string{"uri", "url", "videoUri", "video_url"} { - if candidate := strings.TrimSpace(stringField(typed, key)); isPublicMediaURL(candidate) { - return candidate - } - } - for _, child := range typed { - if candidate := findProviderMediaURL(child); candidate != "" { - return candidate - } - } - case []interface{}: - for _, child := range typed { - if candidate := findProviderMediaURL(child); candidate != "" { - return candidate - } - } - } - return "" -} - -func newAPIVideoResultURL(state map[string]interface{}) string { - return nestedNewAPIVideoResultURL(state, false, 0) -} - -func nestedNewAPIVideoResultURL(payload map[string]interface{}, allowResultURL bool, depth int) string { - if depth < 2 { - for _, key := range []string{"data", "result", "video"} { - if nested, ok := payload[key].(map[string]interface{}); ok { - if videoURL := nestedNewAPIVideoResultURL(nested, true, depth+1); videoURL != "" { - return videoURL - } - } - } - } - keys := []string{"video_url", "videoUrl", "url"} - if allowResultURL { - keys = append(keys, "result_url", "resultUrl") - } - for _, key := range keys { - if videoURL := strings.TrimSpace(stringField(payload, key)); isPublicMediaURL(videoURL) { - return videoURL - } - } - return "" -} - -const newAPIChannel1VideoPollInterval = 20 * time.Second - -const ( - newAPIChannel2VideoPollInterval = 10 * time.Second - newAPIChannel2VideoRetryInterval = time.Minute - newAPIChannel2VideoMaxQueryRetries = 3 -) - -type newAPIChannel2ResponseError struct { - Code string - Message string -} - -func (e newAPIChannel2ResponseError) Error() string { - return fmt.Sprintf("NewAPI Video Generations 任务查询失败(%s):%s", e.Code, defaultString(e.Message, "上游查询失败")) -} - -func runNewAPIChannel2VideoTask(ctx context.Context, input canvasGenerationInput) (map[string]interface{}, error) { - id := resumedProviderRequestID(ctx) - var created map[string]interface{} - if id == "" { - body, err := newAPIChannel2VideoRequestBody(input) - if err != nil { - return nil, err - } - if err := postJSON(ctx, input.Config, "/video/generations", body, &created); err != nil { - return nil, err - } - id = firstNonEmptyString(stringField(created, "task_id"), stringField(created, "id")) - } - if id == "" { - if data, ok := created["data"].(map[string]interface{}); ok { - id = firstNonEmptyString(stringField(data, "task_id"), stringField(data, "id")) - } - } - if id == "" { - return nil, errors.New("NewAPI Video Generations 没有返回任务 ID") - } - - consecutiveQueryFailures := 0 - for deadline := providerPollingDeadline(ctx); time.Now().Before(deadline); { - result, _, err := queryNewAPIChannel2VideoTask(ctx, input, id) - if err != nil { - if !isTransientNewAPIChannel2QueryError(err) || consecutiveQueryFailures >= newAPIChannel2VideoMaxQueryRetries { - return nil, err - } - consecutiveQueryFailures++ - logNewAPIChannel2QueryRetry(ctx, id, consecutiveQueryFailures, err) - if err := sleepContext(ctx, newAPIChannel2VideoRetryInterval); err != nil { - return nil, err - } - continue - } - consecutiveQueryFailures = 0 - if result != nil { - return result, nil - } - if err := sleepContext(ctx, newAPIChannel2VideoPollInterval); err != nil { - return nil, err - } - } - if err := ctx.Err(); err != nil { - return nil, err - } - return nil, context.DeadlineExceeded -} - -// 单次查询只读取既有上游任务,不创建新任务;自动轮询和人工恢复共用这条安全边界。 -func queryNewAPIChannel2VideoTask(ctx context.Context, input canvasGenerationInput, id string) (map[string]interface{}, string, error) { - var payload map[string]interface{} - if err := getJSON(ctx, input.Config, "/video/generations/"+id, &payload); err != nil { - return nil, "", err - } - if err := newAPIChannel2PayloadError(payload); err != nil { - return nil, "", err - } - state := payload - if data, ok := payload["data"].(map[string]interface{}); ok { - state = data - } - status := strings.ToUpper(strings.TrimSpace(stringField(state, "status"))) - switch status { - case "SUCCESS": - videoURL := strings.TrimSpace(stringField(state, "result_url")) - if videoURL == "" { - return nil, status, fmt.Errorf("NewAPI Video Generations 任务 %s 已成功但没有返回视频地址", id) - } - data, mimeType, err := getProviderExternalBinary(withProviderRequestKind(ctx, "download"), input.Config, videoURL) - if err != nil { - return nil, status, fmt.Errorf("NewAPI Video Generations 视频结果下载失败(任务 %s):%w", id, err) - } - mimeType = normalizedMediaMimeType(mimeType, data) - return map[string]interface{}{"mode": "video", "video": map[string]interface{}{"dataUrl": dataURL(mimeType, data), "mimeType": mimeType}}, status, nil - case "FAILURE": - reason := strings.TrimSpace(stringField(state, "fail_reason")) - return nil, status, fmt.Errorf("NewAPI Video Generations 视频生成失败(任务 %s):%s", id, defaultString(reason, "上游返回失败")) - case "SUBMITTED", "QUEUED", "IN_PROGRESS", "NOT_START", "": - return nil, status, nil - default: - return nil, status, fmt.Errorf("NewAPI Video Generations 任务 %s 返回未知状态:%s", id, status) - } -} - -func newAPIChannel2PayloadError(payload map[string]interface{}) error { - code := strings.ToLower(strings.TrimSpace(stringField(payload, "code"))) - if code == "" || code == "0" || code == "ok" || code == "success" { - return nil - } - return newAPIChannel2ResponseError{Code: code, Message: firstNonEmptyString(stringField(payload, "message"), stringField(payload, "msg"))} -} - -func isTransientNewAPIChannel2QueryError(err error) bool { - if err == nil || errors.Is(err, context.Canceled) || errors.Is(err, context.DeadlineExceeded) { - return false - } - var responseErr newAPIChannel2ResponseError - if errors.As(err, &responseErr) { - return responseErr.Code == "do_request_failed" || strings.Contains(strings.ToLower(responseErr.Message), "do request failed") - } - var httpErr providerHTTPError - if errors.As(err, &httpErr) { - body := strings.ToLower(httpErr.Body) - return httpErr.StatusCode == http.StatusRequestTimeout || httpErr.StatusCode == http.StatusTooManyRequests || httpErr.StatusCode >= http.StatusInternalServerError || strings.Contains(body, "do_request_failed") || strings.Contains(body, "do request failed") - } - if errors.Is(err, io.ErrUnexpectedEOF) { - return true - } - var networkErr net.Error - if errors.As(err, &networkErr) { - return true - } - message := strings.ToLower(err.Error()) - return strings.Contains(message, "do_request_failed") || strings.Contains(message, "do request failed") -} - -func logNewAPIChannel2QueryRetry(ctx context.Context, providerTaskID string, retry int, err error) { - metadata, ok := ctx.Value(providerAnalyticsKey{}).(providerAnalyticsContext) - if !ok || metadata.Service == nil || metadata.TaskID == "" { - return - } - payload := fmt.Sprintf("供应商任务 %s,第 %d/%d 次重试:%s", providerTaskID, retry, newAPIChannel2VideoMaxQueryRetries, safeProviderLogError(err)) - _ = metadata.Service.log(metadata.UserID, metadata.TaskID, "warn", "上游任务查询失败,1 分钟后重试", payload) -} - -func newAPIChannel2VideoRequestBody(input canvasGenerationInput) (newAPIVideoRequest, error) { - if len(input.ReferenceImages) > 9 || len(input.ReferenceVideos) > 3 || len(input.ReferenceAudios) > 3 { - return newAPIVideoRequest{}, errors.New("NewAPI Video Generations 最多支持 9 张参考图、3 个参考视频和 3 个参考音频") - } - // NewAPI Video Generations 只接受附着在参考视频上的音频;纯音频请求会被上游拒绝。 - if len(input.ReferenceAudios) > 0 && len(input.ReferenceVideos) == 0 { - return newAPIVideoRequest{}, errors.New("NewAPI Video Generations 的参考音频必须同时提供至少 1 个参考视频;纯音频生视频请切换到支持该模式的渠道") - } - modelName := strings.ToLower(strings.TrimSpace(input.Config.Model)) - requiresSingleImage := modelName == "grok-video-1.5" || modelName == "grok-video-1.5-1080p" - images := make([]string, 0, len(input.ReferenceImages)) - // 单图模型以实际参考图为准,兼容旧画布中未随连接关系更新的 text_to_video 元数据。 - if shouldSendNewAPIVideoImages(input) || requiresSingleImage { - for _, image := range input.ReferenceImages { - url, err := videoGenerationsMediaURL(image) - if err != nil { - return newAPIVideoRequest{}, err - } - images = append(images, url) - } - } - if requiresSingleImage { - if len(images) != 1 { - return newAPIVideoRequest{}, fmt.Errorf("NewAPI Video Generations 的 %s 必须且只能提供 1 张参考图(当前 %d 张)", input.Config.Model, len(images)) - } - } - frameImages, err := videoFrameImageURLs(input, images) - if err != nil { - return newAPIVideoRequest{}, err - } - if len(frameImages) > 0 { - images = frameImages - } - - seconds, secondsErr := strconv.Atoi(strings.TrimSpace(input.Config.VideoSeconds)) - if secondsErr != nil || seconds < 1 { - seconds = 6 - } - ratio := normalizeNewAPIChannel2Ratio(input.Config.Size, modelName) - resolution := videoResolutionNameRequest(input.VideoCapability, input.Config.VQuality) - if modelName == "grok-video-1.5-1080p" { - resolution = "1080p" - } - body := newAPIVideoRequest{ - Model: input.Config.Model, - Prompt: strings.TrimSpace(input.Prompt), - Seconds: strconv.Itoa(seconds), - AspectRatio: ratio, - Resolution: resolution, - } - if videoCapabilitySupportsAudio(input) { - value := parseBool(input.Config.VideoGenerateAudio, true) - body.GenerateAudio = &value - } - if len(images) > 0 { - body.ImageURLs = images - } - videoURLs := make([]string, 0, len(input.ReferenceVideos)) - for _, video := range input.ReferenceVideos { - url, err := videoGenerationsMediaURL(video) - if err != nil { - return newAPIVideoRequest{}, err - } - videoURLs = append(videoURLs, url) - } - if len(videoURLs) > 0 { - body.VideoURLs = videoURLs - } - audioURLs := make([]string, 0, len(input.ReferenceAudios)) - for _, audio := range input.ReferenceAudios { - url, err := videoGenerationsMediaURL(audio) - if err != nil { - return newAPIVideoRequest{}, err - } - audioURLs = append(audioURLs, url) - } - if len(audioURLs) > 0 { - body.AudioURLs = audioURLs - } - return body, nil -} - -// 兼容现有单元测试和旧调用方;实际请求路径使用上面的类型化 DTO。 -func newAPIChannel2VideoBody(input canvasGenerationInput) (map[string]interface{}, error) { - body, err := newAPIChannel2VideoRequestBody(input) - if err != nil { - return nil, err - } - return requestAsMap(body) -} - -func videoGenerationsMediaURL(media providerMedia) (string, error) { - value := strings.TrimSpace(firstNonEmpty(media.URL, media.DataURL)) - if isPublicMediaURL(value) || strings.HasPrefix(value, "data:") { - return value, nil - } - return "", errors.New("NewAPI Video Generations 的参考素材需要公网 URL;私有素材请先保存到对象存储") -} - -func normalizeNewAPIChannel2Ratio(value string, modelName string) string { - ratio := strings.TrimSpace(value) - if strings.Contains(ratio, "x") { - parts := strings.SplitN(ratio, "x", 2) - width, widthErr := strconv.Atoi(parts[0]) - height, heightErr := strconv.Atoi(parts[1]) - if widthErr == nil && heightErr == nil && width > 0 && height > 0 { - switch { - case width == height: - ratio = "1:1" - case width > height: - ratio = "16:9" - default: - ratio = "9:16" - } - } - } - if modelName == "grok-video-1.5" || modelName == "grok-video-1.5-1080p" { - if ratio != "9:16" { - return "16:9" - } - return ratio - } - switch ratio { - case "1:1", "16:9", "9:16", "4:3", "3:4", "3:2", "2:3": - return ratio - default: - return "16:9" - } -} - -func normalizeNewAPIChannel2Resolution(value string, modelName string) string { - if modelName == "grok-video-1.5-1080p" { - return "1080p" - } - normalized := strings.ToLower(strings.TrimSpace(value)) - switch normalized { - case "480", "480p", "low": - return "480p" - case "1080", "1080p": - return "1080p" - case "1440", "1440p", "2k": - return "1440p" - case "2160", "2160p", "4k": - return "2160p" - } - numeric := strings.TrimSuffix(normalized, "p") - if resolution, err := strconv.Atoi(numeric); err == nil && resolution > 0 { - return strconv.Itoa(resolution) + "p" - } - return "720p" -} - -func runNewAPIChannel1VideoTask(ctx context.Context, input canvasGenerationInput) (map[string]interface{}, error) { - id := resumedProviderRequestID(ctx) - var created map[string]interface{} - if id == "" { - body, err := newAPIChannel1VideoBody(input) - if err != nil { - return nil, err - } - if err := postJSON(ctx, input.Config, "/videos", body, &created); err != nil { - return nil, err - } - if data, ok := created["data"].(map[string]interface{}); ok { - created = data - } - id = firstNonEmptyString(stringField(created, "id"), stringField(created, "task_id")) - } - status := strings.ToUpper(strings.TrimSpace(stringField(created, "status"))) - if strings.HasPrefix(status, "FAILED") { - return nil, fmt.Errorf("NewAPI 媒体任务视频生成失败(任务 %s):%s", id, strings.TrimSpace(strings.TrimPrefix(status, "FAILED:"))) - } - if id == "" { - return nil, errors.New("NewAPI 媒体任务没有返回任务 ID") - } - for deadline := providerPollingDeadline(ctx); time.Now().Before(deadline); { - var state map[string]interface{} - if err := getJSON(ctx, input.Config, "/videos/"+id, &state); err != nil { - return nil, err - } - if data, ok := state["data"].(map[string]interface{}); ok { - state = data - } - status := strings.ToUpper(strings.TrimSpace(stringField(state, "status"))) - switch { - case status == "SUCCEEDED": - videoURL := stringField(state, "object") - if videoURL == "" { - return nil, fmt.Errorf("NewAPI 媒体任务 %s 已完成但没有返回视频 URL", id) - } - data, mimeType, err := getExternalBinary(withProviderRequestKind(ctx, "download"), videoURL) - if err != nil { - return nil, fmt.Errorf("NewAPI 媒体任务视频结果下载失败(任务 %s):%w", id, err) - } - return map[string]interface{}{"mode": "video", "video": map[string]interface{}{"dataUrl": dataURL(mimeType, data), "mimeType": mimeType}}, nil - case strings.HasPrefix(status, "FAILED"): - message := strings.TrimSpace(strings.TrimPrefix(status, "FAILED:")) - return nil, fmt.Errorf("NewAPI 媒体任务视频生成失败(任务 %s):%s", id, defaultString(message, "上游返回失败")) - } - if err := sleepContext(ctx, newAPIChannel1VideoPollInterval); err != nil { - return nil, err - } - } - return nil, fmt.Errorf("NewAPI 媒体任务视频生成超时(任务 %s)", id) -} - -func newAPIChannel1VideoBody(input canvasGenerationInput) (map[string]interface{}, error) { - if len(input.ReferenceImages) > 9 || len(input.ReferenceVideos) > 3 || len(input.ReferenceAudios) > 3 { - return nil, errors.New("NewAPI 媒体任务最多支持 9 张参考图、3 个参考视频和 3 个参考音频") - } - media := make([]map[string]string, 0, len(input.ReferenceImages)+len(input.ReferenceVideos)+len(input.ReferenceAudios)) - if shouldSendNewAPIVideoImages(input) { - for _, image := range input.ReferenceImages { - url, err := newAPIChannel1MediaURL(image) - if err != nil { - return nil, err - } - media = append(media, map[string]string{"type": videoImageRole(input, image), "url": url}) - } - } - for _, video := range input.ReferenceVideos { - url, err := newAPIChannel1MediaURL(video) - if err != nil { - return nil, err - } - media = append(media, map[string]string{"type": "reference_video", "url": url}) - } - for _, audio := range input.ReferenceAudios { - url, err := newAPIChannel1MediaURL(audio) - if err != nil { - return nil, err - } - media = append(media, map[string]string{"type": "reference_voice", "url": url}) - } - parameters := map[string]interface{}{ - "resolution": normalizeNewAPIChannel1Resolution(input.Config.VQuality), - "ratio": normalizeNewAPIChannel1Ratio(input.Config.Size), - "prompt_extend": false, - "duration": normalizeSeedanceVideosDuration(input.Config.VideoSeconds), - } - if videoCapabilitySupportsWatermark(input) { - parameters["watermark"] = parseBool(input.Config.VideoWatermark, false) - } - body := map[string]interface{}{ - "model": input.Config.Model, - "input": map[string]interface{}{"prompt": strings.TrimSpace(input.Prompt)}, - "parameters": parameters, - } - if len(media) > 0 { - body["input"].(map[string]interface{})["media"] = media - } - return body, nil -} - -func newAPIChannel1MediaURL(media providerMedia) (string, error) { - value := strings.TrimSpace(media.URL) - if !isPublicMediaURL(value) { - return "", errors.New("NewAPI 媒体任务的参考素材必须使用公网 HTTP(S) URL,请启用对象存储或提供公网素材地址") - } - if _, err := ValidateOutboundURL(value); err != nil { - return "", err - } - return value, nil -} - -func normalizeNewAPIChannel1Resolution(value string) string { - resolution := strings.TrimSuffix(strings.TrimSpace(value), "p") - if resolution != "480" && resolution != "720" && resolution != "1080" { - resolution = "720" - } - return resolution + "P" -} - -func normalizeNewAPIChannel1Ratio(value string) string { - switch strings.TrimSpace(value) { - case "1:1", "16:9", "9:16", "4:3", "3:4": - return strings.TrimSpace(value) - default: - return "16:9" - } -} - func firstNonEmptyString(values ...string) string { for _, value := range values { if strings.TrimSpace(value) != "" { @@ -4499,1087 +989,6 @@ func firstNonEmptyString(values ...string) string { return "" } -func validateGenerationInterface(mode string, interfaceType string) error { - return validateGenerationInterfaceWithRegistry(protocol.Builtins(), mode, interfaceType) -} - -func (s *Service) validateGenerationInterface(mode string, interfaceType string) error { - return validateGenerationInterfaceWithRegistry(s.protocolRegistry(), mode, interfaceType) -} - -func validateGenerationInterfaceWithRegistry(registry *protocol.Registry, mode string, interfaceType string) error { - interfaceType = strings.TrimSpace(interfaceType) - if interfaceType == "" { - return nil - } - adapter, ok := registry.Resolve(interfaceType) - if !ok { - return fmt.Errorf("接口类型 %s 未安装", interfaceType) - } - metadata := adapter.Metadata() - if !metadata.Enabled || metadata.UnavailableReason != "" { - return fmt.Errorf("接口类型 %s 当前不可用:%s", interfaceType, metadata.UnavailableReason) - } - if mode != "" && !protocolCapabilityMatches(metadata, protocol.Capability(mode)) { - return fmt.Errorf("接口类型 %s 不支持%s生成", interfaceType, mode) - } - return nil -} - -func protocolCapabilityMatches(metadata protocol.Metadata, capability protocol.Capability) bool { - for _, item := range metadata.Categories { - if item == capability { - return true - } - } - return false -} - -func grokVideoBody(input canvasGenerationInput) (map[string]interface{}, error) { - if input.Config.InterfaceType == "xai-video" { - body, err := xaiVideoRequestBody(input) - if err != nil { - return nil, err - } - return requestAsMap(body) - } - - seconds := defaultString(input.Config.VideoSeconds, "6") - duration, err := strconv.Atoi(seconds) - if err != nil || duration <= 0 { - duration = 6 - } - body := map[string]interface{}{ - "model": input.Config.Model, - "prompt": strings.TrimSpace(input.Prompt), - "duration": duration, - "seconds": strconv.Itoa(duration), - } - if size := normalizeVideoSize(input.Config.Size); size != "" { - body["size"] = size - } - if shouldSendNewAPIVideoImages(input) && len(input.ReferenceImages) > 0 { - images := make([]string, 0, len(input.ReferenceImages)) - for _, image := range input.ReferenceImages { - url, err := openAIImageInputURL(image) - if err != nil { - return nil, err - } - images = append(images, url) - } - body["image"] = images[0] - body["images"] = images - } - return body, nil -} - -// xAI 生成接口与 legacy /videos 使用不同字段,保持独立可避免兼容字段触发上游 422。 -// 设置首帧时按 image-to-video 只传 image;未设置首帧时按 reference-to-video 把所有参考图放入 reference_images。 -func xaiVideoRequestBody(input canvasGenerationInput) (xaiVideoRequest, error) { - body := xaiVideoRequest{ - Model: input.Config.Model, - Prompt: strings.TrimSpace(input.Prompt), - Duration: normalizeXAIVideoDuration(input.Config.VideoSeconds), - AspectRatio: normalizeXAIVideoAspectRatio(input.Config.Size), - Resolution: normalizeXAIVideoResolution(input.Config.VQuality), - } - if !shouldSendNewAPIVideoImages(input) || len(input.ReferenceImages) == 0 { - return body, nil - } - startFrameID := metadataString(input.Metadata, "videoStartFrameNodeId") - if metadataString(input.Metadata, "videoEditOperation") == "reference_to_video" { - startFrameID = "" - } - if startFrameID == "" { - // 未设置首帧:所有参考图作为 R2V 参考,不受单张起始图限制。 - for index := range input.ReferenceImages { - imageURL, err := openAIImageInputURL(input.ReferenceImages[index]) - if err != nil { - return xaiVideoRequest{}, err - } - body.ReferenceImages = append(body.ReferenceImages, xaiVideoImage{URL: imageURL}) - } - return body, nil - } - // 设置了首帧:xAI 不允许 image 与 reference_images 同时出现,只把首帧作为起始图。 - if len(input.ReferenceImages) > 1 { - return xaiVideoRequest{}, fmt.Errorf("xAI 设置首帧后只支持 1 张起始图,当前连接了 %d 张", len(input.ReferenceImages)) - } - if input.ReferenceImages[0].ID != startFrameID { - return xaiVideoRequest{}, errors.New("已配置的首帧参考图未包含在视频请求中") - } - imageURL, err := openAIImageInputURL(input.ReferenceImages[0]) - if err != nil { - return xaiVideoRequest{}, err - } - body.Image = &xaiVideoImage{URL: imageURL} - return body, nil -} - -// 兼容旧的 map 断言调用;xAI 实际请求使用类型化 DTO。 -func xaiVideoBody(input canvasGenerationInput) (map[string]interface{}, error) { - body, err := xaiVideoRequestBody(input) - if err != nil { - return nil, err - } - return requestAsMap(body) -} - -func runSeedanceVideosTask(ctx context.Context, input canvasGenerationInput) (map[string]interface{}, error) { - id := resumedProviderRequestID(ctx) - var created map[string]interface{} - if id == "" { - body, err := seedanceVideosRequestBody(input) - if err != nil { - return nil, err - } - if err := postJSON(ctx, input.Config, "/videos", body, &created); err != nil { - return nil, err - } - if data, ok := created["data"].(map[string]interface{}); ok { - created = data - } - id = firstNonEmptyString(stringField(created, "id"), stringField(created, "task_id")) - } - if id == "" { - return nil, errors.New("Seedance 接口没有返回任务 ID") - } - for deadline := providerPollingDeadline(ctx); time.Now().Before(deadline); { - var state map[string]interface{} - if err := getJSON(ctx, input.Config, "/videos/"+id, &state); err != nil { - return nil, err - } - if data, ok := state["data"].(map[string]interface{}); ok { - state = data - } - status := strings.ToLower(stringField(state, "status")) - if status == "completed" || status == "succeeded" { - videoURL := stringField(state, "video_url") - if videoURL != "" { - data, mimeType, err := getExternalBinary(withProviderRequestKind(ctx, "download"), videoURL) - if err != nil { - return nil, fmt.Errorf("视频结果下载失败:%w", err) - } - return map[string]interface{}{"mode": "video", "video": map[string]interface{}{"dataUrl": dataURL(mimeType, data), "mimeType": mimeType}}, nil - } - data, mimeType, err := getBinary(ctx, input.Config, "/videos/"+id+"/content") - if err != nil { - return nil, errors.New("Seedance 任务成功但没有返回视频 URL") - } - return map[string]interface{}{"mode": "video", "video": map[string]interface{}{"dataUrl": dataURL(mimeType, data), "mimeType": mimeType}}, nil - } - if status == "failed" || status == "cancelled" || status == "expired" { - return nil, errors.New(defaultString(seedanceErrorMessage(state), "Seedance 视频生成失败")) - } - if err := sleepContext(ctx, 5*time.Second); err != nil { - return nil, err - } - } - return nil, errors.New("Seedance 视频生成超时") -} - -func runSeedanceAgentPlanVideoTask(ctx context.Context, input canvasGenerationInput) (map[string]interface{}, error) { - providerName := "Seedance" - if input.Config.InterfaceType == string(model.ChannelInterfaceVolcengineArkVideo) { - providerName = "火山方舟" - } - id := resumedProviderRequestID(ctx) - var created map[string]interface{} - if id == "" { - content, err := seedanceContent(input) - if err != nil { - return nil, err - } - if input.Config.InterfaceType == string(model.ChannelInterfaceVolcengineArkVideo) { - for _, item := range content { - if item["type"] == "image_url" { - item["role"] = "reference_image" - } - } - } - body := seedanceAgentPlanRequest{ - Model: input.Config.Model, - Content: content, - Ratio: normalizeSeedanceRatio(input.Config.Size), - Resolution: normalizeSeedanceResolution(input.Config.VQuality, input.Config.Model), - Duration: normalizeSeedanceDuration(input.Config.VideoSeconds), - } - if videoCapabilitySupportsAudio(input) { - value := parseBool(input.Config.VideoGenerateAudio, true) - body.GenerateAudio = &value - } - if videoCapabilitySupportsWatermark(input) { - value := parseBool(input.Config.VideoWatermark, false) - body.Watermark = &value - } - if err := postJSON(ctx, input.Config, "/contents/generations/tasks", body, &created); err != nil { - return nil, err - } - if data, ok := created["data"].(map[string]interface{}); ok { - created = data - } - id = stringField(created, "id") - } - if id == "" { - return nil, fmt.Errorf("%s接口没有返回任务 ID", providerName) - } - for deadline := providerPollingDeadline(ctx); time.Now().Before(deadline); { - var state map[string]interface{} - if err := getJSON(ctx, input.Config, "/contents/generations/tasks/"+id, &state); err != nil { - return nil, err - } - if data, ok := state["data"].(map[string]interface{}); ok { - state = data - } - status := stringField(state, "status") - if status == "succeeded" { - content, _ := state["content"].(map[string]interface{}) - videoURL := stringField(content, "video_url") - if videoURL == "" { - return nil, fmt.Errorf("%s任务成功但没有返回视频 URL", providerName) - } - data, mimeType, err := getExternalBinary(withProviderRequestKind(ctx, "download"), videoURL) - if err != nil { - return nil, fmt.Errorf("视频结果下载失败:%w", err) - } - return map[string]interface{}{"mode": "video", "video": map[string]interface{}{"dataUrl": dataURL(mimeType, data), "mimeType": mimeType}}, nil - } - if status == "failed" || status == "cancelled" || status == "expired" { - return nil, fmt.Errorf("%s视频生成失败", providerName) - } - if err := sleepContext(ctx, 5*time.Second); err != nil { - return nil, err - } - } - return nil, fmt.Errorf("%s视频生成超时", providerName) -} - -func requestTextProvider(ctx context.Context, config providerConfig, path string, body map[string]interface{}, protocol string, stream bool, onDelta func(string)) (providerTextResult, error) { - if stream { - return postStreamingTextResult(ctx, config, path, body, protocol, onDelta) - } - var payload map[string]interface{} - if err := postJSON(ctx, config, path, body, &payload); err != nil { - return providerTextResult{}, err - } - parsed, err := parseAgentToolPayload(payload, protocol) - if err != nil { - return providerTextResult{}, err - } - result := providerTextResult{Text: stringField(parsed, "text"), Reasoning: stringField(parsed, "reasoning")} - if result.Text == "" { - return providerTextResult{}, errors.New("文本接口没有返回内容") - } - return result, nil -} - -func postStreamingText(ctx context.Context, config providerConfig, path string, body map[string]interface{}, protocol string, onDelta func(string)) (string, error) { - result, err := postStreamingTextResult(ctx, config, path, body, protocol, onDelta) - return result.Text, err -} - -func postStreamingTextResult(ctx context.Context, config providerConfig, path string, body map[string]interface{}, protocol string, onDelta func(string)) (providerTextResult, error) { - // 文本创作与 Agent 共用同一套 SSE 解析,确保正文、推理摘要和供应商错误语义一致。 - parsed, err := postStreamingAgent(ctx, config, path, body, protocol, onDelta) - if err != nil { - return providerTextResult{}, err - } - result := providerTextResult{Text: stringField(parsed, "text"), Reasoning: stringField(parsed, "reasoning")} - if result.Text == "" { - return providerTextResult{}, errors.New("流式文本接口没有返回内容") - } - return result, nil -} - -func extractTextPayload(payload map[string]interface{}, protocol string) string { - if protocol == "claude-api" { - content, _ := payload["content"].([]interface{}) - var result strings.Builder - for _, item := range content { - record, _ := item.(map[string]interface{}) - if stringField(record, "type") == "text" { - result.WriteString(stringField(record, "text")) - } - } - return result.String() - } - if protocol == "responses" { - text := stringField(payload, "output_text") - if text == "" { - text = extractResponseText(payload) - } - return text - } - return extractChatCompletionText(payload) -} - -func validateTextPayload(payload map[string]interface{}) error { - if code, ok := payload["code"].(float64); ok && code != 0 { - rawMessage := defaultString(stringField(payload, "msg"), "请求失败") - return providerPayloadError{raw: rawMessage, message: providerPayloadErrorMessage(rawMessage)} - } - if errValue, ok := payload["error"].(map[string]interface{}); ok { - if message := stringField(errValue, "message"); message != "" { - return providerPayloadError{raw: message, message: providerPayloadErrorMessage(message)} - } - } - return nil -} - -func parseTextEventStream(data []byte, protocol string) (string, error) { - scanner := bufio.NewScanner(bytes.NewReader(data)) - scanner.Buffer(make([]byte, 64<<10), len(data)+1) - var text strings.Builder - var eventName string - var dataLines []string - - flush := func() error { - if len(dataLines) == 0 { - eventName = "" - return nil - } - raw := strings.TrimSpace(strings.Join(dataLines, "\n")) - dataLines = nil - if raw == "" || raw == "[DONE]" { - eventName = "" - return nil - } - var payload map[string]interface{} - if err := json.Unmarshal([]byte(raw), &payload); err != nil { - return fmt.Errorf("流式文本事件解析失败:%w", err) - } - if eventName == "error" { - if err := validateTextPayload(payload); err != nil { - return err - } - return errors.New("上游流式文本请求失败") - } - if err := validateTextPayload(payload); err != nil { - return err - } - if protocol == "responses" { - text.WriteString(stringField(payload, "delta")) - } else if protocol == "claude-api" { - delta, _ := payload["delta"].(map[string]interface{}) - if stringField(delta, "type") == "text_delta" { - text.WriteString(stringField(delta, "text")) - } - } else { - choices, _ := payload["choices"].([]interface{}) - for _, choice := range choices { - record, _ := choice.(map[string]interface{}) - delta, _ := record["delta"].(map[string]interface{}) - text.WriteString(streamContentText(delta["content"])) - } - } - eventName = "" - return nil - } - - for scanner.Scan() { - line := strings.TrimSuffix(scanner.Text(), "\r") - if line == "" { - if err := flush(); err != nil { - return "", err - } - continue - } - switch { - case strings.HasPrefix(line, "event:"): - eventName = strings.TrimSpace(strings.TrimPrefix(line, "event:")) - case strings.HasPrefix(line, "data:"): - value := strings.TrimPrefix(line, "data:") - dataLines = append(dataLines, strings.TrimPrefix(value, " ")) - } - } - if err := scanner.Err(); err != nil { - return "", fmt.Errorf("读取流式文本响应失败:%w", err) - } - if err := flush(); err != nil { - return "", err - } - if text.Len() == 0 { - return "", errors.New("流式文本接口没有返回内容") - } - return text.String(), nil -} - -func streamContentText(value interface{}) string { - if text, ok := value.(string); ok { - return text - } - parts, ok := value.([]interface{}) - if !ok { - return "" - } - var result strings.Builder - for _, part := range parts { - record, _ := part.(map[string]interface{}) - result.WriteString(stringField(record, "text")) - } - return result.String() -} - -func postStreamingBinary(ctx context.Context, config providerConfig, path string, body interface{}, onChunk func(string, []byte)) ([]byte, string, error) { - data, _ := json.Marshal(body) - req, err := http.NewRequestWithContext(ctx, http.MethodPost, apiURL(config.BaseURL, path), bytes.NewReader(data)) - if err != nil { - return nil, "", err - } - applyProviderAuth(req, config) - req.Header.Set("Content-Type", "application/json") - req.Header.Set("Accept", "text/event-stream") - ApplyOutboundHeaders(req, config.Headers) - return doBinaryWithConsumer(req, onChunk) -} - -type streamingTextDeltaParser struct { - protocol string - buffer string - emit func(string) -} - -func newStreamingTextDeltaParser(protocol string, emit func(string)) *streamingTextDeltaParser { - return &streamingTextDeltaParser{protocol: protocol, emit: emit} -} - -func (p *streamingTextDeltaParser) consume(mimeType string, chunk []byte) { - if p == nil || p.emit == nil || !strings.Contains(strings.ToLower(mimeType), "event-stream") || len(chunk) == 0 { - return - } - p.buffer += string(chunk) - p.consumeFrames(false) -} - -func (p *streamingTextDeltaParser) flush() { - if p == nil || p.emit == nil { - return - } - p.consumeFrames(true) -} - -func (p *streamingTextDeltaParser) consumeFrames(flush bool) { - for { - match := sseFrameBoundaryPattern.FindStringIndex(p.buffer) - if match == nil { - break - } - p.consumeFrame(p.buffer[:match[0]]) - p.buffer = p.buffer[match[1]:] - } - if flush && strings.TrimSpace(p.buffer) != "" { - p.consumeFrame(p.buffer) - p.buffer = "" - } -} - -func (p *streamingTextDeltaParser) consumeFrame(frame string) { - var eventName string - var dataLines []string - for _, line := range strings.Split(strings.ReplaceAll(frame, "\r\n", "\n"), "\n") { - switch { - case strings.HasPrefix(line, "event:"): - eventName = strings.TrimSpace(strings.TrimPrefix(line, "event:")) - case strings.HasPrefix(line, "data:"): - dataLines = append(dataLines, strings.TrimSpace(strings.TrimPrefix(line, "data:"))) - } - } - raw := strings.TrimSpace(strings.Join(dataLines, "\n")) - if raw == "" || raw == "[DONE]" { - return - } - var payload map[string]interface{} - if json.Unmarshal([]byte(raw), &payload) != nil { - return - } - if delta := streamingTextDelta(p.protocol, eventName, payload); delta != "" { - p.emit(delta) - } -} - -func streamingTextDelta(protocol string, eventName string, payload map[string]interface{}) string { - if protocol == "responses" { - eventType := firstNonEmptyString(strings.TrimSpace(eventName), stringField(payload, "type")) - if eventType == "response.output_text.delta" || eventType == "output_text.delta" || eventType == "" || eventType == "message" { - return stringField(payload, "delta") - } - return "" - } - if protocol == "claude-api" { - delta, _ := payload["delta"].(map[string]interface{}) - if stringField(delta, "type") == "text_delta" { - return stringField(delta, "text") - } - return "" - } - choices, _ := payload["choices"].([]interface{}) - var text strings.Builder - for _, choice := range choices { - record, _ := choice.(map[string]interface{}) - delta, _ := record["delta"].(map[string]interface{}) - text.WriteString(streamContentText(delta["content"])) - } - return text.String() -} - -func postJSON(ctx context.Context, config providerConfig, path string, body interface{}, target interface{}) error { - data, _ := json.Marshal(body) - req, err := http.NewRequestWithContext(ctx, http.MethodPost, apiURL(config.BaseURL, path), bytes.NewReader(data)) - if err != nil { - return err - } - applyProviderAuth(req, config) - req.Header.Set("Content-Type", "application/json") - ApplyOutboundHeaders(req, config.Headers) - return doJSON(req, target) -} - -func applyProviderAuth(req *http.Request, config providerConfig) { - if config.APIFormat == "claude" { - req.Header.Set("x-api-key", config.APIKey) - req.Header.Set("anthropic-version", "2023-06-01") - return - } - if config.APIFormat == "gemini" { - req.Header.Set("x-goog-api-key", config.APIKey) - return - } - req.Header.Set("Authorization", "Bearer "+config.APIKey) -} - -func postForm(ctx context.Context, config providerConfig, path string, contentType string, body io.Reader, target interface{}) error { - req, err := http.NewRequestWithContext(ctx, http.MethodPost, apiURL(config.BaseURL, path), body) - if err != nil { - return err - } - req.Header.Set("Authorization", "Bearer "+config.APIKey) - req.Header.Set("Content-Type", contentType) - ApplyOutboundHeaders(req, config.Headers) - return doJSON(req, target) -} - -func getJSON(ctx context.Context, config providerConfig, path string, target interface{}) error { - req, err := http.NewRequestWithContext(ctx, http.MethodGet, apiURL(config.BaseURL, path), nil) - if err != nil { - return err - } - req.Header.Set("Authorization", "Bearer "+config.APIKey) - ApplyOutboundHeaders(req, config.Headers) - return doJSON(req, target) -} - -func postBinary(ctx context.Context, config providerConfig, path string, body interface{}) ([]byte, string, error) { - data, _ := json.Marshal(body) - req, err := http.NewRequestWithContext(ctx, http.MethodPost, apiURL(config.BaseURL, path), bytes.NewReader(data)) - if err != nil { - return nil, "", err - } - req.Header.Set("Authorization", "Bearer "+config.APIKey) - req.Header.Set("Content-Type", "application/json") - ApplyOutboundHeaders(req, config.Headers) - return doBinary(req) -} - -func getBinary(ctx context.Context, config providerConfig, path string) ([]byte, string, error) { - req, err := http.NewRequestWithContext(ctx, http.MethodGet, apiURL(config.BaseURL, path), nil) - if err != nil { - return nil, "", err - } - req.Header.Set("Authorization", "Bearer "+config.APIKey) - ApplyOutboundHeaders(req, config.Headers) - return doBinary(req) -} - -func getExternalBinary(ctx context.Context, rawURL string) ([]byte, string, error) { - req, err := http.NewRequestWithContext(ctx, http.MethodGet, rawURL, nil) - if err != nil { - return nil, "", err - } - return doBinary(req) -} - -func getProviderExternalBinary(ctx context.Context, config providerConfig, rawURL string) ([]byte, string, error) { - req, err := http.NewRequestWithContext(ctx, http.MethodGet, rawURL, nil) - if err != nil { - return nil, "", err - } - if sameProviderOrigin(config.BaseURL, rawURL) { - applyProviderAuth(req, config) - ApplyOutboundHeaders(req, config.Headers) - } - return doBinary(req) -} - -func sameProviderOrigin(baseURL string, rawURL string) bool { - base, baseErr := url.Parse(strings.TrimSpace(baseURL)) - target, targetErr := url.Parse(strings.TrimSpace(rawURL)) - if baseErr != nil || targetErr != nil || base.Scheme == "" || base.Host == "" || target.Scheme == "" || target.Host == "" { - return false - } - return strings.EqualFold(base.Scheme, target.Scheme) && strings.EqualFold(base.Host, target.Host) -} - -func doJSON(req *http.Request, target interface{}) error { - data, mimeType, err := doBinary(req) - if err != nil { - return err - } - if !strings.Contains(mimeType, "json") && !json.Valid(data) { - return fmt.Errorf("接口返回非 JSON 内容:%s", mimeType) - } - if err := json.Unmarshal(data, target); err != nil { - return err - } - if payload, ok := target.(*imageResponse); ok { - if payload.Error != nil && payload.Error.Message != "" { - return errors.New(providerPayloadErrorMessage(payload.Error.Message)) - } - if payload.Code != nil && *payload.Code != 0 { - return errors.New(providerPayloadErrorMessage(payload.Msg)) - } - } - if payload, ok := target.(*map[string]interface{}); ok { - if _, rawMessage, failed := providerPayloadBusinessFailure(*payload); failed { - return providerPayloadError{raw: rawMessage, message: providerPayloadErrorMessage(rawMessage)} - } - if errValue, ok := (*payload)["error"].(map[string]interface{}); ok && stringField(errValue, "message") != "" { - rawMessage := stringField(errValue, "message") - return providerPayloadError{raw: rawMessage, message: providerPayloadErrorMessage(rawMessage)} - } - } - return nil -} - -func withProviderOutboundPolicy(ctx context.Context, config providerConfig) context.Context { - if !config.AllowLocalChannel { - return ctx - } - parsed, err := url.Parse(strings.TrimSpace(config.BaseURL)) - if err != nil || !isExactDesktopLoopbackHost(parsed.Hostname()) { - return ctx - } - return context.WithValue(ctx, providerOutboundPolicyKey{}, providerOutboundPolicyContext{scheme: strings.ToLower(parsed.Scheme), host: strings.ToLower(parsed.Host)}) -} - -func providerLoopbackPolicyForRequest(req *http.Request) (OutboundPolicy, bool) { - policyContext, ok := req.Context().Value(providerOutboundPolicyKey{}).(providerOutboundPolicyContext) - if !ok || policyContext.scheme == "" || policyContext.host == "" { - return OutboundPolicy{}, false - } - if strings.ToLower(req.URL.Scheme) != policyContext.scheme || strings.ToLower(req.URL.Host) != policyContext.host { - return OutboundPolicy{}, false - } - return desktopLoopbackOutboundPolicy(nil), true -} - -func doBinary(req *http.Request) ([]byte, string, error) { - return doBinaryWithConsumer(req, nil) -} - -func doBinaryWithConsumer(req *http.Request, onChunk func(string, []byte)) ([]byte, string, error) { - startedAt := time.Now() - requestTimeout := providerHTTPTimeout - if deadline, ok := req.Context().Deadline(); ok { - if remaining := time.Until(deadline); remaining > 0 { - requestTimeout = remaining - } - } - var release func() - var coordinator *runtimeCoordinator - var runtimeService *Service - responseLimit := maxProviderResponseBytes - channelID := "" - if metadata, ok := req.Context().Value(providerAnalyticsKey{}).(providerAnalyticsContext); ok && metadata.Service != nil { - runtimeService = metadata.Service - coordinator = metadata.Service.coordinator - channelID = metadata.ChannelID - policy, err := metadata.Service.RuntimePolicy() - if err != nil { - return nil, "", fmt.Errorf("读取生成资源限制失败:%w", err) - } - responseLimit = megabytes(policy.Resource.GeneratedFileMB) - open, err := coordinator.circuitOpen(req.Context(), channelID) - if err != nil { - return nil, "", fmt.Errorf("读取渠道熔断状态失败:%w", err) - } - if open { - return nil, "", errors.New("当前渠道连续失败,已暂时熔断,请稍后重试") - } - slotID := channelID - if slotID == "" { - slotID = "custom:" + strings.ToLower(req.URL.Host) - } - var concurrencyLimit int - release, concurrencyLimit, err = metadata.Service.AcquireChannelSlot(req.Context(), channelID, slotID, requestTimeout+time.Minute) - metadata.ConcurrencyLimit = concurrencyLimit - req = req.WithContext(context.WithValue(req.Context(), providerAnalyticsKey{}, metadata)) - if err != nil { - recordProviderRequest(req, startedAt, 0, nil, err) - return nil, "", err - } - defer release() - } - policy, loopback := providerLoopbackPolicyForRequest(req) - if loopback { - if _, err := validateOutboundURLWithPolicy(req.URL.String(), policy); err != nil { - recordProviderRequest(req, startedAt, 0, nil, err) - return nil, "", err - } - } else if _, err := ValidateOutboundURL(req.URL.String()); err != nil { - recordProviderRequest(req, startedAt, 0, nil, err) - return nil, "", err - } - ApplyDefaultOutboundHeaders(req) - client := OutboundHTTPClient(requestTimeout) - if loopback { - client = outboundHTTPClientWithPolicy(requestTimeout, policy) - } - resp, err := client.Do(req) - if err != nil { - if runtimeService != nil { - _ = runtimeService.RecordChannelResult(req.Context(), channelID, !errors.Is(err, context.Canceled)) - } - recordProviderRequest(req, startedAt, 0, nil, err) - return nil, "", err - } - defer resp.Body.Close() - if resp.ContentLength > responseLimit { - err = fmt.Errorf("上游响应超过 %s 限制", formatStorageLimit(responseLimit)) - recordProviderRequest(req, startedAt, resp.StatusCode, nil, err) - return nil, "", err - } - mimeType := resp.Header.Get("Content-Type") - var buffered bytes.Buffer - reader := io.LimitReader(resp.Body, responseLimit+1) - chunk := make([]byte, 32<<10) - for { - readCount, readErr := reader.Read(chunk) - if readCount > 0 { - if int64(buffered.Len()+readCount) > responseLimit { - err = fmt.Errorf("上游响应超过 %s 限制", formatStorageLimit(responseLimit)) - recordProviderRequest(req, startedAt, resp.StatusCode, buffered.Bytes(), err) - return nil, "", err - } - _, _ = buffered.Write(chunk[:readCount]) - if onChunk != nil { - onChunk(mimeType, chunk[:readCount]) - } - } - if errors.Is(readErr, io.EOF) { - break - } - if readErr != nil { - recordProviderRequest(req, startedAt, resp.StatusCode, buffered.Bytes(), readErr) - return nil, "", readErr - } - } - data := buffered.Bytes() - if int64(len(data)) > responseLimit { - err = fmt.Errorf("上游响应超过 %s 限制", formatStorageLimit(responseLimit)) - recordProviderRequest(req, startedAt, resp.StatusCode, nil, err) - return nil, "", err - } - if resp.StatusCode < 200 || resp.StatusCode >= 300 { - if runtimeService != nil { - _ = runtimeService.RecordChannelResult(req.Context(), channelID, resp.StatusCode >= 500) - } - httpErr := providerHTTPError{StatusCode: resp.StatusCode, Status: resp.Status, Body: string(data), RetryAfter: parseRetryAfter(resp.Header.Get("Retry-After"), time.Now())} - recordProviderRequest(req, startedAt, resp.StatusCode, data, httpErr) - return nil, "", httpErr - } - recordProviderRequest(req, startedAt, resp.StatusCode, data, nil) - if runtimeService != nil { - _ = runtimeService.RecordChannelResult(req.Context(), channelID, false) - } - return data, mimeType, nil -} - -func parseRetryAfter(value string, now time.Time) time.Duration { - value = strings.TrimSpace(value) - if value == "" { - return 0 - } - if seconds, err := strconv.Atoi(value); err == nil && seconds > 0 { - return time.Duration(seconds) * time.Second - } - if at, err := http.ParseTime(value); err == nil && at.After(now) { - return at.Sub(now) - } - return 0 -} - -func providerPollingDeadline(ctx context.Context) time.Time { - if deadline, ok := ctx.Deadline(); ok { - return deadline - } - return time.Now().Add(videoPollTimeout) -} - -func recordProviderRequest(req *http.Request, startedAt time.Time, statusCode int, responseBody []byte, requestErr error) { - metadata, ok := req.Context().Value(providerAnalyticsKey{}).(providerAnalyticsContext) - if !ok || metadata.Service == nil { - return - } - status := model.ApiCallStatusSucceeded - errorCode := "" - errorText := "" - if requestErr != nil || statusCode < 200 || statusCode >= 300 { - status = model.ApiCallStatusFailed - errorCode, errorText = providerRequestErrorDetails(requestErr) - } else if businessCode, businessMessage, failed := providerResponseBusinessFailure(responseBody); failed { - status = model.ApiCallStatusFailed - errorCode = businessCode - errorText = businessMessage - } - requestKind := providerRequestKind(req.Method, req.URL.Path) - if metadata.RequestKind != "" { - requestKind = metadata.RequestKind - } - if status == model.ApiCallStatusSucceeded && (requestKind == "create" || requestKind == "poll") && (metadata.Capability == "image" || metadata.Capability == "video") { - metadata.Service.syncProviderTaskProgress(metadata.TaskID, responseBody) - } - apiFormat := "openai" - if req.Header.Get("x-goog-api-key") != "" { - apiFormat = "gemini" - } - callLog := model.ApiCallLog{ - UserID: metadata.UserID, TraceID: metadata.TraceID, RequestID: metadata.RequestID, ChannelID: metadata.ChannelID, TaskID: metadata.TaskID, BillingOrderID: metadata.BillingOrderID, - Source: "backend-task", Capability: metadata.Capability, Operation: metadata.Operation, - RequestKind: requestKind, Billable: req.Method == http.MethodPost && requestKind != "cancel", - APIFormat: apiFormat, Method: req.Method, Path: req.URL.Path, Model: metadata.Model, - Status: status, StatusCode: statusCode, DurationMs: time.Since(startedAt).Milliseconds(), - ErrorCode: errorCode, Error: errorText, ConcurrencyLimit: metadata.ConcurrencyLimit, UpstreamURL: req.URL.Scheme + "://" + req.URL.Host + req.URL.Path, - ProviderRequestID: metadata.ProviderRequestID, RequestContentType: req.Header.Get("Content-Type"), RequestBody: requestPayloadForLog(req), ResponseBody: SanitizeAPICallPayload(responseBody, ""), - } - channelSlotFailure := false - if code, message := ChannelSlotFailureDetails(requestErr); code != "" { - channelSlotFailure = true - callLog.ErrorCode = code - callLog.Error = message - } - if requestKind == "create" && metadata.Capability == "video" { - callLog.VideoSeconds = metadata.VideoSeconds - if callLog.VideoSeconds <= 0 { - if strings.Contains(strings.ToLower(metadata.Model), "seedance") || strings.Contains(req.URL.Path, "/contents/generations/tasks") { - callLog.VideoSeconds = 5 - } else { - callLog.VideoSeconds = 6 - } - } - } - metadata.Service.EnrichAPICallLog(&callLog, responseBody) - if err := metadata.Service.LogAPICall(callLog); err != nil { - if !channelSlotFailure && metadata.Billing != nil { - if uncertainErr := metadata.Billing.MarkBillingUncertain(metadata.BillingOrderID, "上游调用日志写入失败,费用状态待核对"); uncertainErr != nil { - // 这里无法把日志落库错误返回给已完成的 HTTP 请求,只能把计费边界失败写入进程日志,交给待核对审计继续处理。 - log.Printf("provider billing uncertainty update failed: task_id=%s billing_order_id=%s error=%v", metadata.TaskID, metadata.BillingOrderID, uncertainErr) - } - } - } -} - -func providerRequestErrorDetails(err error) (string, string) { - if err == nil { - return "", "" - } - if errors.Is(err, context.Canceled) { - return "request_cancelled", "任务取消,中断上游请求" - } - if errors.Is(err, context.DeadlineExceeded) { - return "upstream_timeout", "等待上游响应超时" - } - return "", safeProviderLogError(err) -} - -func safeProviderLogError(err error) string { - var httpErr providerHTTPError - if errors.As(err, &httpErr) { - return fmt.Sprintf("上游 HTTP %d", httpErr.StatusCode) - } - return truncateRunes(err.Error(), 500) -} - -func providerRequestKind(method string, path string) string { - if method == http.MethodGet { - if strings.HasSuffix(strings.TrimRight(path, "/"), "/content") || strings.Contains(path, "/download") { - return "download" - } - return "poll" - } - if strings.Contains(path, "repair") { - return "repair" - } - return "create" -} - -func apiURL(baseURL string, path string) string { - return apiURLWithDefaultPrefix(baseURL, path, "/v1") -} - -// ChannelAPIURL is the single URL join point for provider requests. A channel -// may be configured as host, host/, host/v1 or host/v1/; callers should pass -// protocol paths without hard-coding a version prefix whenever possible. -func ChannelAPIURL(baseURL string, path string) string { - return apiURL(baseURL, path) -} - -// ChannelAPIURLForProtocol keeps protocol-specific defaults at the transport -// boundary. In particular, Gemini uses v1beta while OpenAI-compatible APIs -// conventionally use v1. An explicit version in either input wins. -func ChannelAPIURLForProtocol(baseURL string, path string, interfaceType model.ChannelInterfaceType) string { - if interfaceType == model.ChannelInterfaceAgnesVideo && strings.HasPrefix(strings.TrimSpace(path), "/agnesapi") { - base, err := url.Parse(strings.TrimSpace(baseURL)) - requestPath, pathErr := url.Parse(strings.TrimSpace(path)) - if err == nil && pathErr == nil && base.Scheme != "" && base.Host != "" && strings.HasPrefix(requestPath.Path, "/") { - base.Path = requestPath.Path - base.RawPath = requestPath.RawPath - base.RawQuery = requestPath.RawQuery - base.Fragment = "" - return base.String() - } - } - defaultPrefix := "/v1" - if interfaceType == model.ChannelInterfaceGeminiVeo || interfaceType == model.ChannelInterfaceGeminiImage { - defaultPrefix = "/v1beta" - } - return apiURLWithDefaultPrefix(baseURL, path, defaultPrefix) -} - -var channelAPIPrefixes = []string{"/api/plan/v3", "/api/v3", "/api/v1", "/v1beta", "/v1", "/v2", "/v3"} - -func apiURLWithDefaultPrefix(baseURL string, path string, defaultPrefix string) string { - base := strings.TrimRight(strings.TrimSpace(baseURL), "/") - requestPath := strings.TrimSpace(path) - if requestPath == "" { - return base - } - if !strings.HasPrefix(requestPath, "/") { - requestPath = "/" + requestPath - } - - requestPrefix := requestAPIPathPrefix(requestPath) - basePrefix := baseAPIPathPrefix(base) - if requestPrefix != "" { - if basePrefix == requestPrefix { - return base + strings.TrimPrefix(requestPath, requestPrefix) - } - // An explicit request version takes precedence over a version accidentally - // left on the configured base URL (for example base=/v1, path=/v2/...). - return strings.TrimSuffix(base, basePrefix) + requestPath - } - if basePrefix != "" { - return base + requestPath - } - return base + defaultPrefix + requestPath -} - -func requestAPIPathPrefix(value string) string { - lower := strings.ToLower(value) - for _, prefix := range channelAPIPrefixes { - if lower == prefix || strings.HasPrefix(lower, prefix+"/") || strings.HasPrefix(lower, prefix+"?") || strings.HasPrefix(lower, prefix+"#") { - return prefix - } - } - return "" -} - -func baseAPIPathPrefix(value string) string { - lower := strings.ToLower(strings.TrimRight(value, "/")) - for _, prefix := range channelAPIPrefixes { - if lower == prefix || strings.HasSuffix(lower, prefix) { - return prefix - } - } - return "" -} - -func writeField(writer *multipart.Writer, key string, value string) { - _ = writer.WriteField(key, value) -} - -func writeMediaPart(writer *multipart.Writer, field string, media providerMedia) error { - raw, mimeType, err := mediaBytes(media) - if err != nil { - return err - } - filename := providerMediaFilename(media, mimeType) - header := make(textproto.MIMEHeader) - header.Set("Content-Disposition", mime.FormatMediaType("form-data", map[string]string{"name": field, "filename": filename})) - header.Set("Content-Type", mimeType) - part, err := writer.CreatePart(header) - if err != nil { - return err - } - _, err = part.Write(raw) - return err -} - -func providerMediaFilename(media providerMedia, mimeType string) string { - base := strings.TrimSpace(media.ID) - if base == "" { - base = "reference" - } - var builder strings.Builder - for _, char := range base { - if (char >= 'a' && char <= 'z') || (char >= 'A' && char <= 'Z') || (char >= '0' && char <= '9') || char == '-' || char == '_' { - builder.WriteRune(char) - if builder.Len() >= 64 { - break - } - } - } - base = builder.String() - if base == "" { - base = "reference" - } - extensions, _ := mime.ExtensionsByType(strings.TrimSpace(strings.Split(mimeType, ";")[0])) - extension := ".bin" - if len(extensions) > 0 { - extension = extensions[0] - } - return "reference-" + base + extension -} - -func mediaBytes(media providerMedia) ([]byte, string, error) { - value := media.DataURL - if value == "" { - value = media.URL - } - if !strings.HasPrefix(value, "data:") { - return nil, "", errors.New("后端任务队列需要 data URL 形式的本地参考素材") - } - header, encoded, ok := strings.Cut(value, ",") - if !ok { - return nil, "", errors.New("data URL 格式错误") - } - mimeType := strings.TrimPrefix(strings.Split(strings.TrimPrefix(header, "data:"), ";")[0], " ") - raw, err := base64.StdEncoding.DecodeString(encoded) - if err != nil { - return nil, "", err - } - return raw, normalizedMediaMimeType(defaultString(mimeType, media.Type), raw), nil -} - -func imageDataURLs(payload imageResponse) ([]map[string]string, error) { - if len(payload.Data) == 0 { - return nil, errors.New("接口没有返回图片") - } - images := make([]map[string]string, 0, len(payload.Data)) - for _, item := range payload.Data { - if b64, ok := item["b64_json"].(string); ok && b64 != "" { - images = append(images, map[string]string{"dataUrl": "data:image/png;base64," + b64}) - continue - } - if url, ok := item["url"].(string); ok && url != "" { - images = append(images, map[string]string{"dataUrl": url}) - } - } - if len(images) == 0 { - return nil, errors.New("接口没有返回可用图片") - } - return images, nil -} - func dataURL(mimeType string, data []byte) string { if mimeType == "" { mimeType = "application/octet-stream" @@ -5587,59 +996,16 @@ func dataURL(mimeType string, data []byte) string { return "data:" + strings.Split(mimeType, ";")[0] + ";base64," + base64.StdEncoding.EncodeToString(data) } +// stringField 只读取异构上游 JSON 中的可选字符串:缺失或 null 返回空串,存在但类型错误也不强制转换。 +// task ID 等必填标识必须使用 firstJSONString,让类型错误显式失败。 func stringField(payload map[string]interface{}, key string) string { - value, _ := payload[key].(string) + value, err := optionalJSONString(payload, key) + if err != nil { + return "" + } return value } -func extractResponseText(payload map[string]interface{}) string { - output, ok := payload["output"].([]interface{}) - if !ok { - return "" - } - var chunks []string - for _, item := range output { - record, ok := item.(map[string]interface{}) - if !ok || record["type"] != "message" { - continue - } - content, _ := record["content"].([]interface{}) - for _, part := range content { - partRecord, ok := part.(map[string]interface{}) - if ok && stringField(partRecord, "text") != "" { - chunks = append(chunks, stringField(partRecord, "text")) - } - } - } - return strings.Join(chunks, "") -} - -func extractChatCompletionText(payload map[string]interface{}) string { - if data, ok := payload["data"].(map[string]interface{}); ok { - payload = data - } - choices, ok := payload["choices"].([]interface{}) - if !ok { - return "" - } - var chunks []string - for _, choice := range choices { - record, ok := choice.(map[string]interface{}) - if !ok { - continue - } - if message, ok := record["message"].(map[string]interface{}); ok { - if text := stringField(message, "content"); text != "" { - chunks = append(chunks, text) - } - } - if text := stringField(record, "text"); text != "" { - chunks = append(chunks, text) - } - } - return strings.Join(chunks, "") -} - func withSystemPrompt(config providerConfig, prompt string) string { systemPrompt := strings.TrimSpace(config.SystemPrompt) if systemPrompt == "" { @@ -5648,241 +1014,6 @@ func withSystemPrompt(config providerConfig, prompt string) string { return systemPrompt + "\n\n" + prompt } -func seedanceContent(input canvasGenerationInput) ([]map[string]interface{}, error) { - content := make([]map[string]interface{}, 0, 1+len(input.ReferenceImages)+len(input.ReferenceVideos)+len(input.ReferenceAudios)) - text := seedancePromptText(input) - if strings.TrimSpace(text) != "" { - content = append(content, map[string]interface{}{"type": "text", "text": text}) - } - for _, image := range input.ReferenceImages { - url, err := mediaReferenceURL(image) - if err != nil { - return nil, err - } - content = append(content, map[string]interface{}{"type": "image_url", "image_url": map[string]interface{}{"url": url}, "role": videoImageRole(input, image)}) - } - for _, video := range input.ReferenceVideos { - url, err := mediaReferenceURL(video) - if err != nil { - return nil, err - } - content = append(content, map[string]interface{}{"type": "video_url", "video_url": map[string]interface{}{"url": url}, "role": "reference_video"}) - } - for _, audio := range input.ReferenceAudios { - url, err := mediaReferenceURL(audio) - if err != nil { - return nil, err - } - content = append(content, map[string]interface{}{"type": "audio_url", "audio_url": map[string]interface{}{"url": url}, "role": "reference_audio"}) - } - if len(content) == 0 { - return nil, errors.New("请输入视频提示词或连接参考素材") - } - return content, nil -} - -func shouldSendNewAPIVideoImages(input canvasGenerationInput) bool { - if input.Metadata == nil { - return true - } - operation, _ := input.Metadata["videoEditOperation"].(string) - return strings.TrimSpace(operation) != "text_to_video" -} - -// 本地测试 helper 没有能力配置时保留历史协议字段;真实系统任务会携带已解析的模型能力。 -func videoCapabilitySupportsAudio(input canvasGenerationInput) bool { - return input.VideoCapability == nil || input.VideoCapability.GenerateAudio.Supported -} - -func videoCapabilitySupportsWatermark(input canvasGenerationInput) bool { - return input.VideoCapability == nil || input.VideoCapability.Watermark.Supported -} - -func newAPIVideoPromptText(input canvasGenerationInput) string { - return strings.TrimSpace(input.Prompt) -} - -func seedanceVideosRequestBody(input canvasGenerationInput) (seedanceVideosRequest, error) { - if (len(input.ReferenceVideos) > 0 || len(input.ReferenceAudios) > 0) && len(input.ReferenceImages) == 0 { - return seedanceVideosRequest{}, errors.New("Seedance 参考视频或参考音频需要同时连接至少 1 张主参考图") - } - body := seedanceVideosRequest{ - Model: input.Config.Model, - Prompt: seedanceVideosPromptText(input), - AspectRatio: normalizeSeedanceVideosRatio(input.Config.Size), - Duration: normalizeSeedanceVideosDuration(input.Config.VideoSeconds), - } - if videoCapabilitySupportsAudio(input) { - value := parseBool(input.Config.VideoGenerateAudio, true) - body.GenerateAudio = &value - } - imageURLs := make([]string, 0, len(input.ReferenceImages)) - for _, image := range input.ReferenceImages { - url, err := openAIImageInputURL(image) - if err != nil { - return seedanceVideosRequest{}, err - } - imageURLs = append(imageURLs, url) - } - frameImageURLs, err := videoFrameImageURLs(input, imageURLs) - if err != nil { - return seedanceVideosRequest{}, err - } - if metadataString(input.Metadata, "videoEditOperation") == "reference_to_video" { - body.ReferenceImageURLs = imageURLs - } else if len(frameImageURLs) > 0 { - body.ImageURLs = frameImageURLs - } else if len(imageURLs) > 0 { - body.ImageURL = imageURLs[0] - if len(imageURLs) > 1 { - body.ReferenceImageURLs = imageURLs[1:] - } - } - videoURLs := make([]string, 0, len(input.ReferenceVideos)) - for _, video := range input.ReferenceVideos { - url, err := seedanceVideosMediaURL(video) - if err != nil { - return seedanceVideosRequest{}, err - } - videoURLs = append(videoURLs, url) - } - if len(videoURLs) > 0 { - body.ReferenceVideos = videoURLs - } - audioURLs := make([]string, 0, len(input.ReferenceAudios)) - for _, audio := range input.ReferenceAudios { - url, err := seedanceVideosMediaURL(audio) - if err != nil { - return seedanceVideosRequest{}, err - } - audioURLs = append(audioURLs, url) - } - if len(audioURLs) > 0 { - body.ReferenceAudios = audioURLs - } - return body, nil -} - -// 兼容旧的 map 断言调用;实际请求路径使用类型化 Seedance DTO。 -func seedanceVideosBody(input canvasGenerationInput) (map[string]interface{}, error) { - body, err := seedanceVideosRequestBody(input) - if err != nil { - return nil, err - } - return requestAsMap(body) -} - -func seedancePromptText(input canvasGenerationInput) string { - return strings.TrimSpace(input.Prompt) -} - -func seedanceVideosPromptText(input canvasGenerationInput) string { - return strings.TrimSpace(input.Prompt) -} - -func videoImageRole(input canvasGenerationInput, image providerMedia) string { - return videoImageRoleOrDefault(input, image, "reference_image") -} - -func videoImageRoleOrDefault(input canvasGenerationInput, image providerMedia, fallback string) string { - if metadataString(input.Metadata, "videoEditOperation") == "reference_to_video" { - return "reference_image" - } - if id := metadataString(input.Metadata, "videoStartFrameNodeId"); id != "" && image.ID == id { - return "first_frame" - } - if id := metadataString(input.Metadata, "videoEndFrameNodeId"); id != "" && image.ID == id { - return "last_frame" - } - return fallback -} - -func videoFrameImageURLs(input canvasGenerationInput, imageURLs []string) ([]string, error) { - if metadataString(input.Metadata, "videoEditOperation") == "reference_to_video" { - return nil, nil - } - startFrameID := metadataString(input.Metadata, "videoStartFrameNodeId") - endFrameID := metadataString(input.Metadata, "videoEndFrameNodeId") - if startFrameID == "" && endFrameID == "" { - return nil, nil - } - // image_urls 按首帧、尾帧、普通参考图排序,保持 JSON 视频协议的结构化帧语义。 - ordered := make([]string, 0, len(imageURLs)) - used := make([]bool, len(imageURLs)) - appendFrame := func(frameID string, label string) error { - if frameID == "" { - return nil - } - for index, image := range input.ReferenceImages { - if index >= len(imageURLs) || image.ID != frameID { - continue - } - ordered = append(ordered, imageURLs[index]) - used[index] = true - return nil - } - return fmt.Errorf("已配置的%s参考图未包含在视频请求中", label) - } - if err := appendFrame(startFrameID, "首帧"); err != nil { - return nil, err - } - if err := appendFrame(endFrameID, "尾帧"); err != nil { - return nil, err - } - for index, imageURL := range imageURLs { - if !used[index] { - ordered = append(ordered, imageURL) - } - } - return ordered, nil -} - func metadataString(metadata map[string]interface{}, key string) string { - if metadata == nil { - return "" - } - value, _ := metadata[key].(string) - return strings.TrimSpace(value) -} - -func mediaReferenceURL(media providerMedia) (string, error) { - value := strings.TrimSpace(media.URL) - if isPublicMediaURL(value) || strings.HasPrefix(value, "asset://") || strings.HasPrefix(value, "data:") { - return value, nil - } - value = strings.TrimSpace(media.DataURL) - if value != "" { - return value, nil - } - return "", errors.New("参考素材需要公网 URL、asset:// 素材 ID 或 data URL") -} - -func seedanceVideosMediaURL(media providerMedia) (string, error) { - value := strings.TrimSpace(media.DataURL) - if strings.HasPrefix(value, "data:") { - return value, nil - } - value = strings.TrimSpace(media.URL) - if strings.HasPrefix(value, "data:") || isPublicMediaURL(value) { - return value, nil - } - return "", errors.New("Seedance /videos 参考素材需要公网 URL 或 data URL") -} - -func seedanceErrorMessage(state map[string]interface{}) string { - if errorValue, ok := state["error"].(map[string]interface{}); ok { - message := stringField(errorValue, "message") - code := stringField(errorValue, "code") - if message != "" && code != "" { - return code + ":" + message - } - if message != "" { - return message - } - } - code := stringField(state, "error_code") - if code != "" { - return code - } - return "" + return strings.TrimSpace(stringField(metadata, key)) } diff --git a/backend/internal/service/provider_ark_private_assets.go b/backend/internal/service/provider_ark_private_assets.go index 0c2ae18e3..949aef86c 100644 --- a/backend/internal/service/provider_ark_private_assets.go +++ b/backend/internal/service/provider_ark_private_assets.go @@ -27,8 +27,8 @@ const ( arkPrivateAssetPollLimit = 3 * time.Minute ) -// Tests can inject a local control-plane server. Production derives the Ark -// control-plane address from the administrator's explicit Region setting. +// 测试可以注入本地控制面服务;生产环境必须根据管理员显式配置的 Region 推导方舟控制面地址, +// 不能接受客户端传入任意控制面 URL。 var arkPrivateAssetAPIBaseURLOverride string type taskExecutionIDContextKey struct{} @@ -134,9 +134,8 @@ type ArkPrivateAssetSyncResult struct { Status string `json:"status"` } -// SyncResourceToArkPrivateAsset is the explicit user action for preloading a -// reference image. It keeps the same team-scoped ownership and review rules as -// the task worker, so clients cannot submit arbitrary URLs to Ark. +// SyncResourceToArkPrivateAsset 是用户显式触发的参考图预同步入口。 +// 它与任务 worker 共用团队资源归属、就绪状态和审核规则,客户端不能借此向方舟提交任意 URL。 func (s *Service) SyncResourceToArkPrivateAsset(ctx context.Context, actor *model.User, resourceID string) (*ArkPrivateAssetSyncResult, error) { resourceID = strings.TrimSpace(resourceID) if resourceID == "" { diff --git a/backend/internal/service/provider_audio.go b/backend/internal/service/provider_audio.go new file mode 100644 index 000000000..c80964b22 --- /dev/null +++ b/backend/internal/service/provider_audio.go @@ -0,0 +1,259 @@ +package service + +// 音频生成。 + +import ( + "bytes" + "context" + "encoding/base64" + "errors" + "fmt" + "net/http" + "net/url" + "strings" + "time" + + "infinite-canvas/backend/internal/model" +) + +func runAudioTask(ctx context.Context, input canvasGenerationInput) (map[string]interface{}, error) { + if _, ok := declarativeProtocolAdapterForContext(ctx, input.Config.InterfaceType); ok { + return runDeclarativeProtocolTask(ctx, input) + } + if resolved, ok := input.Metadata["resolvedCharacterVersions"].([]interface{}); ok && len(resolved) > 0 { + voiceKey := metadataString(input.Metadata, "resolvedCharacterVoiceKey") + if voiceKey == "" || strings.TrimSpace(input.Config.AudioVoice) != voiceKey { + return nil, errors.New("角色配音缺少已解析的声音绑定") + } + } + format := defaultString(input.Config.AudioFormat, "mp3") + body := map[string]interface{}{ + "model": input.Config.Model, + "input": input.Prompt, + "voice": defaultString(input.Config.AudioVoice, "alloy"), + "response_format": format, + "speed": 1, + } + if input.Config.AudioSpeed != "" { + body["speed"] = parseFloat(input.Config.AudioSpeed, 1) + } + if input.Config.AudioInstructions != "" { + body["instructions"] = input.Config.AudioInstructions + } + if input.Config.InterfaceType == string(model.ChannelInterfaceAsyncAudio) { + return runAsyncAudioTask(ctx, input, body, format) + } + data, mimeType, err := postBinary(ctx, input.Config, "/audio/speech", body) + if err != nil { + return nil, err + } + mimeType, err = validateGeneratedAudio(mimeType, data, format) + if err != nil { + return nil, err + } + return map[string]interface{}{"mode": "audio", "audio": map[string]interface{}{"dataUrl": dataURL(mimeType, data), "mimeType": mimeType, "format": format}}, nil +} + +func runAsyncAudioTask(ctx context.Context, input canvasGenerationInput, body map[string]interface{}, format string) (map[string]interface{}, error) { + id := resumedProviderRequestID(ctx) + var state map[string]interface{} + if id == "" { + if err := postJSON(ctx, input.Config, "/audio/tasks", body, &state); err != nil { + return nil, err + } + state = asyncAudioPayload(state) + extracted, err := firstJSONString(state, "id", "task_id", "request_id") + if err != nil { + return nil, fmt.Errorf("异步音频接口任务 ID 无效:%w", err) + } + id = extracted + if id == "" { + return nil, errors.New("异步音频接口没有返回任务 ID") + } + if asyncAudioSucceeded(state) { + return asyncAudioResult(ctx, input.Config, id, state, format) + } + } + for deadline := providerPollingDeadline(ctx); time.Now().Before(deadline); { + state = map[string]interface{}{} + pollCtx := withProviderRequestKind(ctx, "poll") + if err := getJSON(pollCtx, input.Config, "/audio/tasks/"+url.PathEscape(id), &state); err != nil { + return nil, err + } + state = asyncAudioPayload(state) + if asyncAudioSucceeded(state) { + return asyncAudioResult(ctx, input.Config, id, state, format) + } + status := strings.ToLower(strings.TrimSpace(stringField(state, "status"))) + if status == "failed" || status == "cancelled" || status == "canceled" || status == "expired" || status == "error" { + return nil, fmt.Errorf("异步音频生成失败(任务 %s):%s", id, asyncAudioErrorMessage(state)) + } + if err := sleepContext(ctx, 2500*time.Millisecond); err != nil { + return nil, err + } + } + return nil, fmt.Errorf("异步音频生成超时(任务 %s)", id) +} + +func asyncAudioPayload(payload map[string]interface{}) map[string]interface{} { + for _, key := range []string{"data", "result", "output"} { + if nested, ok := payload[key].(map[string]interface{}); ok { + for parentKey, parentValue := range payload { + if parentKey == "data" || parentKey == "result" || parentKey == "output" { + continue + } + if _, exists := nested[parentKey]; !exists { + nested[parentKey] = parentValue + } + } + return nested + } + } + return payload +} + +func asyncAudioSucceeded(state map[string]interface{}) bool { + status := strings.ToLower(strings.TrimSpace(stringField(state, "status"))) + done, _ := state["done"].(bool) + return done || status == "completed" || status == "succeeded" || status == "success" || status == "done" || (status == "" && asyncAudioResultURL(state) != "") +} + +func asyncAudioResult(ctx context.Context, config providerConfig, id string, state map[string]interface{}, format string) (map[string]interface{}, error) { + resultURL := asyncAudioResultURL(state) + var data []byte + var mimeType string + var err error + if strings.HasPrefix(resultURL, "data:") { + mimeType, data, err = decodeProviderDataURL(resultURL) + if err == nil { + limit, limitErr := providerGeneratedFileLimit(ctx) + if limitErr != nil { + err = limitErr + } else if int64(len(data)) > limit { + err = fmt.Errorf("异步音频结果超过 %s 限制", formatStorageLimit(limit)) + } + } + } else if isPublicMediaURL(resultURL) { + data, mimeType, err = getExternalBinary(withProviderRequestKind(ctx, "download"), resultURL) + } else { + data, mimeType, err = getBinary(withProviderRequestKind(ctx, "download"), config, "/audio/tasks/"+url.PathEscape(id)+"/content") + } + if err != nil { + return nil, fmt.Errorf("异步音频结果下载失败(任务 %s):%w", id, err) + } + mimeType, err = validateGeneratedAudio(mimeType, data, format) + if err != nil { + return nil, fmt.Errorf("异步音频结果无效(任务 %s):%w", id, err) + } + return map[string]interface{}{"mode": "audio", "audio": map[string]interface{}{"dataUrl": dataURL(mimeType, data), "mimeType": mimeType, "format": format}}, nil +} + +func asyncAudioResultURL(state map[string]interface{}) string { + for _, key := range []string{"audio_url", "audioUrl", "result_url", "resultUrl", "output_url", "outputUrl", "url", "data"} { + if value := strings.TrimSpace(stringField(state, key)); strings.HasPrefix(value, "data:") || isPublicMediaURL(value) { + return value + } + } + for _, key := range []string{"audio", "data", "result", "output"} { + if nested, ok := state[key].(map[string]interface{}); ok { + if value := asyncAudioResultURL(nested); value != "" { + return value + } + } + } + return "" +} + +func asyncAudioErrorMessage(state map[string]interface{}) string { + _, message := providerFailureDetails(state) + return defaultString(message, firstNonEmptyString(stringField(state, "message"), "上游返回失败状态")) +} + +func decodeProviderDataURL(value string) (string, []byte, error) { + header, encoded, ok := strings.Cut(value, ",") + if !ok || !strings.HasPrefix(header, "data:") || !strings.HasSuffix(strings.ToLower(header), ";base64") { + return "", nil, errors.New("音频 data URL 格式无效") + } + mimeType := strings.TrimSuffix(strings.TrimPrefix(header, "data:"), ";base64") + data, err := base64.StdEncoding.DecodeString(encoded) + return mimeType, data, err +} + +func providerGeneratedFileLimit(ctx context.Context) (int64, error) { + metadata, ok := ctx.Value(providerAnalyticsKey{}).(providerAnalyticsContext) + if !ok || metadata.Service == nil { + return maxProviderResponseBytes, nil + } + policy, err := metadata.Service.RuntimePolicy() + if err != nil { + return 0, fmt.Errorf("读取生成资源限制失败:%w", err) + } + return megabytes(policy.Resource.GeneratedFileMB), nil +} + +func validateGeneratedAudio(declared string, data []byte, format string) (string, error) { + if len(data) == 0 { + return "", errors.New("音频内容为空") + } + detected := strings.ToLower(strings.TrimSpace(strings.Split(http.DetectContentType(data), ";")[0])) + if strings.Contains(detected, "json") || strings.HasPrefix(detected, "text/") || strings.HasPrefix(detected, "image/") || strings.HasPrefix(detected, "video/") { + return "", fmt.Errorf("上游返回了非音频内容:%s", detected) + } + mimeType := strings.ToLower(strings.TrimSpace(strings.Split(declared, ";")[0])) + resolved := "" + if strings.HasPrefix(mimeType, "audio/") { + resolved = mimeType + } else if strings.HasPrefix(detected, "audio/") { + resolved = detected + } else if fallback := audioFormatMimeType(format); fallback != "" && (mimeType == "" || mimeType == "application/octet-stream") { + resolved = fallback + } + if resolved == "" { + return "", fmt.Errorf("上游响应类型不是音频:%s", defaultString(mimeType, detected)) + } + if !audioSignatureMatches(resolved, data) { + return "", fmt.Errorf("音频内容与格式不匹配:%s", resolved) + } + return resolved, nil +} + +func audioSignatureMatches(mimeType string, data []byte) bool { + if strings.Contains(mimeType, "pcm") || mimeType == "audio/l16" { + return len(data) > 0 + } + if strings.Contains(mimeType, "mpeg") || strings.Contains(mimeType, "mp3") { + return bytes.HasPrefix(data, []byte("ID3")) || (len(data) >= 2 && data[0] == 0xff && data[1]&0xe0 == 0xe0) + } + if strings.Contains(mimeType, "wav") || strings.Contains(mimeType, "wave") { + return len(data) >= 12 && bytes.Equal(data[:4], []byte("RIFF")) && bytes.Equal(data[8:12], []byte("WAVE")) + } + if strings.Contains(mimeType, "opus") || strings.Contains(mimeType, "ogg") { + return bytes.HasPrefix(data, []byte("OggS")) + } + if strings.Contains(mimeType, "flac") { + return bytes.HasPrefix(data, []byte("fLaC")) + } + if strings.Contains(mimeType, "aac") { + return bytes.HasPrefix(data, []byte("ADIF")) || (len(data) >= 2 && data[0] == 0xff && data[1]&0xf0 == 0xf0) + } + return false +} + +func audioFormatMimeType(format string) string { + switch strings.ToLower(strings.TrimSpace(format)) { + case "wav": + return "audio/wav" + case "opus": + return "audio/opus" + case "aac": + return "audio/aac" + case "flac": + return "audio/flac" + case "pcm": + return "audio/pcm" + case "mp3": + return "audio/mpeg" + default: + return "" + } +} diff --git a/backend/internal/service/provider_http_client.go b/backend/internal/service/provider_http_client.go new file mode 100644 index 000000000..04b2a5742 --- /dev/null +++ b/backend/internal/service/provider_http_client.go @@ -0,0 +1,623 @@ +package service + +// Provider 出站 HTTP、multipart 与媒体字节读取。 + +import ( + "bytes" + "context" + "encoding/base64" + "encoding/json" + "errors" + "fmt" + "io" + "log" + "mime" + "mime/multipart" + "net/http" + "net/textproto" + "net/url" + "strconv" + "strings" + "time" + + "infinite-canvas/backend/internal/model" +) + +func postGeminiJSON(ctx context.Context, config providerConfig, path string, body interface{}, target interface{}) error { + data, err := json.Marshal(body) + if err != nil { + return fmt.Errorf("序列化上游请求失败:%w", err) + } + req, err := http.NewRequestWithContext(ctx, http.MethodPost, geminiVeoURL(config.BaseURL, path), bytes.NewReader(data)) + if err != nil { + return err + } + req.Header.Set("x-goog-api-key", config.APIKey) + req.Header.Set("Content-Type", "application/json") + ApplyOutboundHeaders(req, config.Headers) + return doJSON(req, target) +} + +func getGeminiJSON(ctx context.Context, config providerConfig, path string, target interface{}) error { + req, err := http.NewRequestWithContext(ctx, http.MethodGet, geminiVeoURL(config.BaseURL, path), nil) + if err != nil { + return err + } + req.Header.Set("x-goog-api-key", config.APIKey) + ApplyOutboundHeaders(req, config.Headers) + return doJSON(req, target) +} + +func getGeminiBinary(ctx context.Context, config providerConfig, rawURL string) ([]byte, string, error) { + req, err := http.NewRequestWithContext(ctx, http.MethodGet, rawURL, nil) + if err != nil { + return nil, "", err + } + req.Header.Set("x-goog-api-key", config.APIKey) + ApplyOutboundHeaders(req, config.Headers) + return doBinary(req) +} + +func geminiVeoURL(baseURL string, path string) string { + return apiURLWithDefaultPrefix(baseURL, path, "/v1beta") +} + +func postStreamingBinary(ctx context.Context, config providerConfig, path string, body interface{}, onChunk func(string, []byte)) ([]byte, string, error) { + data, err := json.Marshal(body) + if err != nil { + return nil, "", fmt.Errorf("序列化上游请求失败:%w", err) + } + req, err := http.NewRequestWithContext(ctx, http.MethodPost, apiURL(config.BaseURL, path), bytes.NewReader(data)) + if err != nil { + return nil, "", err + } + applyProviderAuth(req, config) + req.Header.Set("Content-Type", "application/json") + req.Header.Set("Accept", "text/event-stream") + ApplyOutboundHeaders(req, config.Headers) + return doBinaryWithConsumer(req, onChunk) +} + +func postJSON(ctx context.Context, config providerConfig, path string, body interface{}, target interface{}) error { + data, err := json.Marshal(body) + if err != nil { + return fmt.Errorf("序列化上游请求失败:%w", err) + } + req, err := http.NewRequestWithContext(ctx, http.MethodPost, apiURL(config.BaseURL, path), bytes.NewReader(data)) + if err != nil { + return err + } + applyProviderAuth(req, config) + req.Header.Set("Content-Type", "application/json") + ApplyOutboundHeaders(req, config.Headers) + return doJSON(req, target) +} + +func applyProviderAuth(req *http.Request, config providerConfig) { + if config.APIFormat == "claude" { + req.Header.Set("x-api-key", config.APIKey) + req.Header.Set("anthropic-version", "2023-06-01") + return + } + if config.APIFormat == "gemini" { + req.Header.Set("x-goog-api-key", config.APIKey) + return + } + req.Header.Set("Authorization", "Bearer "+config.APIKey) +} + +func postForm(ctx context.Context, config providerConfig, path string, contentType string, body io.Reader, target interface{}) error { + req, err := http.NewRequestWithContext(ctx, http.MethodPost, apiURL(config.BaseURL, path), body) + if err != nil { + return err + } + req.Header.Set("Authorization", "Bearer "+config.APIKey) + req.Header.Set("Content-Type", contentType) + ApplyOutboundHeaders(req, config.Headers) + return doJSON(req, target) +} + +func getJSON(ctx context.Context, config providerConfig, path string, target interface{}) error { + req, err := http.NewRequestWithContext(ctx, http.MethodGet, apiURL(config.BaseURL, path), nil) + if err != nil { + return err + } + req.Header.Set("Authorization", "Bearer "+config.APIKey) + ApplyOutboundHeaders(req, config.Headers) + return doJSON(req, target) +} + +func postBinary(ctx context.Context, config providerConfig, path string, body interface{}) ([]byte, string, error) { + data, err := json.Marshal(body) + if err != nil { + return nil, "", fmt.Errorf("序列化上游请求失败:%w", err) + } + req, err := http.NewRequestWithContext(ctx, http.MethodPost, apiURL(config.BaseURL, path), bytes.NewReader(data)) + if err != nil { + return nil, "", err + } + req.Header.Set("Authorization", "Bearer "+config.APIKey) + req.Header.Set("Content-Type", "application/json") + ApplyOutboundHeaders(req, config.Headers) + return doBinary(req) +} + +func getBinary(ctx context.Context, config providerConfig, path string) ([]byte, string, error) { + req, err := http.NewRequestWithContext(ctx, http.MethodGet, apiURL(config.BaseURL, path), nil) + if err != nil { + return nil, "", err + } + req.Header.Set("Authorization", "Bearer "+config.APIKey) + ApplyOutboundHeaders(req, config.Headers) + return doBinary(req) +} + +func getExternalBinary(ctx context.Context, rawURL string) ([]byte, string, error) { + req, err := http.NewRequestWithContext(ctx, http.MethodGet, rawURL, nil) + if err != nil { + return nil, "", err + } + return doBinary(req) +} + +func getProviderExternalBinary(ctx context.Context, config providerConfig, rawURL string) ([]byte, string, error) { + req, err := http.NewRequestWithContext(ctx, http.MethodGet, rawURL, nil) + if err != nil { + return nil, "", err + } + if sameProviderOrigin(config.BaseURL, rawURL) { + applyProviderAuth(req, config) + ApplyOutboundHeaders(req, config.Headers) + } + return doBinary(req) +} + +func sameProviderOrigin(baseURL string, rawURL string) bool { + base, baseErr := url.Parse(strings.TrimSpace(baseURL)) + target, targetErr := url.Parse(strings.TrimSpace(rawURL)) + if baseErr != nil || targetErr != nil || base.Scheme == "" || base.Host == "" || target.Scheme == "" || target.Host == "" { + return false + } + return strings.EqualFold(base.Scheme, target.Scheme) && strings.EqualFold(base.Host, target.Host) +} + +func doJSON(req *http.Request, target interface{}) error { + data, mimeType, err := doBinary(req) + if err != nil { + return err + } + if !strings.Contains(mimeType, "json") && !json.Valid(data) { + return fmt.Errorf("接口返回非 JSON 内容:%s", mimeType) + } + if err := json.Unmarshal(data, target); err != nil { + return err + } + if payload, ok := target.(*imageResponse); ok { + if payload.Error != nil && payload.Error.Message != "" { + return errors.New(providerPayloadErrorMessage(payload.Error.Message)) + } + if payload.Code != nil && *payload.Code != 0 { + return errors.New(providerPayloadErrorMessage(payload.Msg)) + } + } + if payload, ok := target.(*map[string]interface{}); ok { + if _, rawMessage, failed := providerPayloadBusinessFailure(*payload); failed { + return providerPayloadError{raw: rawMessage, message: providerPayloadErrorMessage(rawMessage)} + } + if errValue, ok := (*payload)["error"].(map[string]interface{}); ok && stringField(errValue, "message") != "" { + rawMessage := stringField(errValue, "message") + return providerPayloadError{raw: rawMessage, message: providerPayloadErrorMessage(rawMessage)} + } + } + return nil +} + +func withProviderOutboundPolicy(ctx context.Context, config providerConfig) context.Context { + if !config.AllowLocalChannel { + return ctx + } + parsed, err := url.Parse(strings.TrimSpace(config.BaseURL)) + if err != nil || !isExactDesktopLoopbackHost(parsed.Hostname()) { + return ctx + } + return context.WithValue(ctx, providerOutboundPolicyKey{}, providerOutboundPolicyContext{scheme: strings.ToLower(parsed.Scheme), host: strings.ToLower(parsed.Host)}) +} + +func providerLoopbackPolicyForRequest(req *http.Request) (OutboundPolicy, bool) { + policyContext, ok := req.Context().Value(providerOutboundPolicyKey{}).(providerOutboundPolicyContext) + if !ok || policyContext.scheme == "" || policyContext.host == "" { + return OutboundPolicy{}, false + } + if strings.ToLower(req.URL.Scheme) != policyContext.scheme || strings.ToLower(req.URL.Host) != policyContext.host { + return OutboundPolicy{}, false + } + return desktopLoopbackOutboundPolicy(nil), true +} + +func doBinary(req *http.Request) ([]byte, string, error) { + return doBinaryWithConsumer(req, nil) +} + +// doBinaryWithConsumer 是 Provider 出站响应的统一安全边界。JSON、SSE 和媒体下载最终都在这里执行 +// 渠道并发/熔断、SSRF、超时、响应大小、HTTP 状态和审计检查;onChunk 仅观察已读取的流片段, +// 不会绕过完整响应的大小上限或错误判定。 +func doBinaryWithConsumer(req *http.Request, onChunk func(string, []byte)) ([]byte, string, error) { + startedAt := time.Now() + requestTimeout := providerHTTPTimeout + if deadline, ok := req.Context().Deadline(); ok { + if remaining := time.Until(deadline); remaining > 0 { + requestTimeout = remaining + } + } + var release func() + var coordinator *runtimeCoordinator + var runtimeService *Service + responseLimit := maxProviderResponseBytes + channelID := "" + if metadata, ok := req.Context().Value(providerAnalyticsKey{}).(providerAnalyticsContext); ok && metadata.Service != nil { + runtimeService = metadata.Service + coordinator = metadata.Service.coordinator + channelID = metadata.ChannelID + policy, err := metadata.Service.RuntimePolicy() + if err != nil { + return nil, "", fmt.Errorf("读取生成资源限制失败:%w", err) + } + responseLimit = megabytes(policy.Resource.GeneratedFileMB) + open, err := coordinator.circuitOpen(req.Context(), channelID) + if err != nil { + return nil, "", fmt.Errorf("读取渠道熔断状态失败:%w", err) + } + if open { + return nil, "", errors.New("当前渠道连续失败,已暂时熔断,请稍后重试") + } + slotID := channelID + if slotID == "" { + slotID = "custom:" + strings.ToLower(req.URL.Host) + } + var concurrencyLimit int + release, concurrencyLimit, err = metadata.Service.AcquireChannelSlot(req.Context(), channelID, slotID, requestTimeout+time.Minute) + metadata.ConcurrencyLimit = concurrencyLimit + req = req.WithContext(context.WithValue(req.Context(), providerAnalyticsKey{}, metadata)) + if err != nil { + recordProviderRequest(req, startedAt, 0, nil, err) + return nil, "", err + } + defer release() + } + policy, loopback := providerLoopbackPolicyForRequest(req) + if loopback { + if _, err := validateOutboundURLWithPolicy(req.URL.String(), policy); err != nil { + recordProviderRequest(req, startedAt, 0, nil, err) + return nil, "", err + } + } else if _, err := ValidateOutboundURL(req.URL.String()); err != nil { + recordProviderRequest(req, startedAt, 0, nil, err) + return nil, "", err + } + ApplyDefaultOutboundHeaders(req) + client := OutboundHTTPClient(requestTimeout) + if loopback { + client = outboundHTTPClientWithPolicy(requestTimeout, policy) + } + resp, err := client.Do(req) + if err != nil { + if runtimeService != nil { + _ = runtimeService.RecordChannelResult(req.Context(), channelID, !errors.Is(err, context.Canceled)) + } + recordProviderRequest(req, startedAt, 0, nil, err) + return nil, "", err + } + defer resp.Body.Close() + if resp.ContentLength > responseLimit { + err = fmt.Errorf("上游响应超过 %s 限制", formatStorageLimit(responseLimit)) + recordProviderRequest(req, startedAt, resp.StatusCode, nil, err) + return nil, "", err + } + mimeType := resp.Header.Get("Content-Type") + var buffered bytes.Buffer + reader := io.LimitReader(resp.Body, responseLimit+1) + chunk := make([]byte, 32<<10) + for { + readCount, readErr := reader.Read(chunk) + if readCount > 0 { + if int64(buffered.Len()+readCount) > responseLimit { + err = fmt.Errorf("上游响应超过 %s 限制", formatStorageLimit(responseLimit)) + recordProviderRequest(req, startedAt, resp.StatusCode, buffered.Bytes(), err) + return nil, "", err + } + _, _ = buffered.Write(chunk[:readCount]) + if onChunk != nil { + onChunk(mimeType, chunk[:readCount]) + } + } + if errors.Is(readErr, io.EOF) { + break + } + if readErr != nil { + recordProviderRequest(req, startedAt, resp.StatusCode, buffered.Bytes(), readErr) + return nil, "", readErr + } + } + data := buffered.Bytes() + if int64(len(data)) > responseLimit { + err = fmt.Errorf("上游响应超过 %s 限制", formatStorageLimit(responseLimit)) + recordProviderRequest(req, startedAt, resp.StatusCode, nil, err) + return nil, "", err + } + if resp.StatusCode < 200 || resp.StatusCode >= 300 { + if runtimeService != nil { + _ = runtimeService.RecordChannelResult(req.Context(), channelID, resp.StatusCode >= 500) + } + httpErr := providerHTTPError{StatusCode: resp.StatusCode, Status: resp.Status, Body: string(data), RetryAfter: parseRetryAfter(resp.Header.Get("Retry-After"), time.Now())} + recordProviderRequest(req, startedAt, resp.StatusCode, data, httpErr) + return nil, "", httpErr + } + recordProviderRequest(req, startedAt, resp.StatusCode, data, nil) + if runtimeService != nil { + _ = runtimeService.RecordChannelResult(req.Context(), channelID, false) + } + return data, mimeType, nil +} + +func parseRetryAfter(value string, now time.Time) time.Duration { + value = strings.TrimSpace(value) + if value == "" { + return 0 + } + if seconds, err := strconv.Atoi(value); err == nil && seconds > 0 { + return time.Duration(seconds) * time.Second + } + if at, err := http.ParseTime(value); err == nil && at.After(now) { + return at.Sub(now) + } + return 0 +} + +func providerPollingDeadline(ctx context.Context) time.Time { + if deadline, ok := ctx.Deadline(); ok { + return deadline + } + return time.Now().Add(videoPollTimeout) +} + +func recordProviderRequest(req *http.Request, startedAt time.Time, statusCode int, responseBody []byte, requestErr error) { + metadata, ok := req.Context().Value(providerAnalyticsKey{}).(providerAnalyticsContext) + if !ok || metadata.Service == nil { + return + } + status := model.ApiCallStatusSucceeded + errorCode := "" + errorText := "" + if requestErr != nil || statusCode < 200 || statusCode >= 300 { + status = model.ApiCallStatusFailed + errorCode, errorText = providerRequestErrorDetails(requestErr) + } else if businessCode, businessMessage, failed := providerResponseBusinessFailure(responseBody); failed { + status = model.ApiCallStatusFailed + errorCode = businessCode + errorText = businessMessage + } + requestKind := providerRequestKind(req.Method, req.URL.Path) + if metadata.RequestKind != "" { + requestKind = metadata.RequestKind + } + if status == model.ApiCallStatusSucceeded && (requestKind == "create" || requestKind == "poll") && (metadata.Capability == "image" || metadata.Capability == "video") { + metadata.Service.syncProviderTaskProgress(metadata.TaskID, responseBody) + } + apiFormat := "openai" + if req.Header.Get("x-goog-api-key") != "" { + apiFormat = "gemini" + } + callLog := model.ApiCallLog{ + UserID: metadata.UserID, TraceID: metadata.TraceID, RequestID: metadata.RequestID, ChannelID: metadata.ChannelID, TaskID: metadata.TaskID, BillingOrderID: metadata.BillingOrderID, + Source: "backend-task", Capability: metadata.Capability, Operation: metadata.Operation, + RequestKind: requestKind, Billable: req.Method == http.MethodPost && requestKind != "cancel", + APIFormat: apiFormat, Method: req.Method, Path: req.URL.Path, Model: metadata.Model, + Status: status, StatusCode: statusCode, DurationMs: time.Since(startedAt).Milliseconds(), + ErrorCode: errorCode, Error: errorText, ConcurrencyLimit: metadata.ConcurrencyLimit, UpstreamURL: req.URL.Scheme + "://" + req.URL.Host + req.URL.Path, + ProviderRequestID: metadata.ProviderRequestID, RequestContentType: req.Header.Get("Content-Type"), RequestBody: requestPayloadForLog(req), ResponseBody: SanitizeAPICallPayload(responseBody, ""), + } + channelSlotFailure := false + if code, message := ChannelSlotFailureDetails(requestErr); code != "" { + channelSlotFailure = true + callLog.ErrorCode = code + callLog.Error = message + } + if requestKind == "create" && metadata.Capability == "video" { + callLog.VideoSeconds = metadata.VideoSeconds + if callLog.VideoSeconds <= 0 { + if strings.Contains(strings.ToLower(metadata.Model), "seedance") || strings.Contains(req.URL.Path, "/contents/generations/tasks") { + callLog.VideoSeconds = 5 + } else { + callLog.VideoSeconds = 6 + } + } + } + metadata.Service.EnrichAPICallLog(&callLog, responseBody) + if err := metadata.Service.LogAPICall(callLog); err != nil { + if !channelSlotFailure && metadata.Billing != nil { + if uncertainErr := metadata.Billing.MarkBillingUncertain(metadata.BillingOrderID, "上游调用日志写入失败,费用状态待核对"); uncertainErr != nil { + // 这里无法把日志落库错误返回给已完成的 HTTP 请求,只能把计费边界失败写入进程日志,交给待核对审计继续处理。 + log.Printf("provider billing uncertainty update failed: task_id=%s billing_order_id=%s error=%v", metadata.TaskID, metadata.BillingOrderID, uncertainErr) + } + } + } +} + +func providerRequestErrorDetails(err error) (string, string) { + if err == nil { + return "", "" + } + if errors.Is(err, context.Canceled) { + return "request_cancelled", "任务取消,中断上游请求" + } + if errors.Is(err, context.DeadlineExceeded) { + return "upstream_timeout", "等待上游响应超时" + } + return "", safeProviderLogError(err) +} + +func safeProviderLogError(err error) string { + var httpErr providerHTTPError + if errors.As(err, &httpErr) { + return fmt.Sprintf("上游 HTTP %d", httpErr.StatusCode) + } + return truncateRunes(err.Error(), 500) +} + +func providerRequestKind(method string, path string) string { + if method == http.MethodGet { + if strings.HasSuffix(strings.TrimRight(path, "/"), "/content") || strings.Contains(path, "/download") { + return "download" + } + return "poll" + } + if strings.Contains(path, "repair") { + return "repair" + } + return "create" +} + +func apiURL(baseURL string, path string) string { + return apiURLWithDefaultPrefix(baseURL, path, "/v1") +} + +// ChannelAPIURL 是 Provider 请求拼接 URL 的唯一公共入口。 +// 渠道地址可配置为 host、host/、host/v1 或 host/v1/;调用方应传协议路径,避免重复硬编码版本前缀。 +func ChannelAPIURL(baseURL string, path string) string { + return apiURL(baseURL, path) +} + +// ChannelAPIURLForProtocol 把协议默认版本收敛在传输边界:Gemini 默认 v1beta, +// OpenAI 兼容协议默认 v1;baseURL 或 path 中显式出现的版本始终优先。 +func ChannelAPIURLForProtocol(baseURL string, path string, interfaceType model.ChannelInterfaceType) string { + if interfaceType == model.ChannelInterfaceAgnesVideo && strings.HasPrefix(strings.TrimSpace(path), "/agnesapi") { + base, err := url.Parse(strings.TrimSpace(baseURL)) + requestPath, pathErr := url.Parse(strings.TrimSpace(path)) + if err == nil && pathErr == nil && base.Scheme != "" && base.Host != "" && strings.HasPrefix(requestPath.Path, "/") { + base.Path = requestPath.Path + base.RawPath = requestPath.RawPath + base.RawQuery = requestPath.RawQuery + base.Fragment = "" + return base.String() + } + } + defaultPrefix := "/v1" + if interfaceType == model.ChannelInterfaceGeminiVeo || interfaceType == model.ChannelInterfaceGeminiImage { + defaultPrefix = "/v1beta" + } + return apiURLWithDefaultPrefix(baseURL, path, defaultPrefix) +} + +var channelAPIPrefixes = []string{"/api/plan/v3", "/api/v3", "/api/v1", "/v1beta", "/v1", "/v2", "/v3"} + +func apiURLWithDefaultPrefix(baseURL string, path string, defaultPrefix string) string { + base := strings.TrimRight(strings.TrimSpace(baseURL), "/") + requestPath := strings.TrimSpace(path) + if requestPath == "" { + return base + } + if !strings.HasPrefix(requestPath, "/") { + requestPath = "/" + requestPath + } + + requestPrefix := requestAPIPathPrefix(requestPath) + basePrefix := baseAPIPathPrefix(base) + if requestPrefix != "" { + if basePrefix == requestPrefix { + return base + strings.TrimPrefix(requestPath, requestPrefix) + } + // 请求路径显式版本优先于 baseURL 残留版本,例如 base=/v1、path=/v2/... 时必须切到 /v2。 + return strings.TrimSuffix(base, basePrefix) + requestPath + } + if basePrefix != "" { + return base + requestPath + } + return base + defaultPrefix + requestPath +} + +func requestAPIPathPrefix(value string) string { + lower := strings.ToLower(value) + for _, prefix := range channelAPIPrefixes { + if lower == prefix || strings.HasPrefix(lower, prefix+"/") || strings.HasPrefix(lower, prefix+"?") || strings.HasPrefix(lower, prefix+"#") { + return prefix + } + } + return "" +} + +func baseAPIPathPrefix(value string) string { + lower := strings.ToLower(strings.TrimRight(value, "/")) + for _, prefix := range channelAPIPrefixes { + if lower == prefix || strings.HasSuffix(lower, prefix) { + return prefix + } + } + return "" +} + +func writeField(writer *multipart.Writer, key string, value string) { + _ = writer.WriteField(key, value) +} + +func writeMediaPart(writer *multipart.Writer, field string, media providerMedia) error { + raw, mimeType, err := mediaBytes(media) + if err != nil { + return err + } + filename := providerMediaFilename(media, mimeType) + header := make(textproto.MIMEHeader) + header.Set("Content-Disposition", mime.FormatMediaType("form-data", map[string]string{"name": field, "filename": filename})) + header.Set("Content-Type", mimeType) + part, err := writer.CreatePart(header) + if err != nil { + return err + } + _, err = part.Write(raw) + return err +} + +func providerMediaFilename(media providerMedia, mimeType string) string { + base := strings.TrimSpace(media.ID) + if base == "" { + base = "reference" + } + var builder strings.Builder + for _, char := range base { + if (char >= 'a' && char <= 'z') || (char >= 'A' && char <= 'Z') || (char >= '0' && char <= '9') || char == '-' || char == '_' { + builder.WriteRune(char) + if builder.Len() >= 64 { + break + } + } + } + base = builder.String() + if base == "" { + base = "reference" + } + extensions, _ := mime.ExtensionsByType(strings.TrimSpace(strings.Split(mimeType, ";")[0])) + extension := ".bin" + if len(extensions) > 0 { + extension = extensions[0] + } + return "reference-" + base + extension +} + +func mediaBytes(media providerMedia) ([]byte, string, error) { + value := media.DataURL + if value == "" { + value = media.URL + } + if !strings.HasPrefix(value, "data:") { + return nil, "", errors.New("后端任务队列需要 data URL 形式的本地参考素材") + } + header, encoded, ok := strings.Cut(value, ",") + if !ok { + return nil, "", errors.New("data URL 格式错误") + } + mimeType := strings.TrimPrefix(strings.Split(strings.TrimPrefix(header, "data:"), ";")[0], " ") + raw, err := base64.StdEncoding.DecodeString(encoded) + if err != nil { + return nil, "", err + } + return raw, normalizedMediaMimeType(defaultString(mimeType, media.Type), raw), nil +} diff --git a/backend/internal/service/provider_image.go b/backend/internal/service/provider_image.go new file mode 100644 index 000000000..0213626b6 --- /dev/null +++ b/backend/internal/service/provider_image.go @@ -0,0 +1,517 @@ +package service + +// 图片生成(OpenAI / Claude / Gemini / Grok 等手写路径)。 + +import ( + "bytes" + "context" + "encoding/base64" + "errors" + "fmt" + "math" + "mime/multipart" + "net/http" + "net/url" + "strconv" + "strings" + + "infinite-canvas/backend/internal/model" +) + +func runImageTask(ctx context.Context, input canvasGenerationInput) (map[string]interface{}, error) { + if _, ok := declarativeProtocolAdapterForContext(ctx, input.Config.InterfaceType); ok { + return runDeclarativeProtocolTask(ctx, input) + } + if input.Config.InterfaceType == string(model.ChannelInterfaceGrokImage) { + return runGrokImageTask(ctx, input) + } + if input.Config.InterfaceType == string(model.ChannelInterfaceVolcengineJiMengImage) { + return runVolcengineJiMengImageTask(ctx, input) + } + if input.Config.InterfaceType == string(model.ChannelInterfaceVolcengineArkImage) { + return runVolcengineArkImageTask(ctx, input) + } + if input.Config.InterfaceType == string(model.ChannelInterfaceGeminiImage) { + return runGeminiImageTask(ctx, input) + } + var payload imageResponse + if input.Mask != nil { + // 蒙版编辑是强校验写路径:协议能力不明确时必须失败,不能静默退化为整图重绘。 + if strings.TrimSpace(input.Config.InterfaceType) != string(model.ChannelInterfaceOpenAIImage) { + return nil, errors.New("当前渠道未声明 OpenAI Images 编辑协议,已拒绝可能忽略蒙版的整图重绘") + } + if len(input.ReferenceImages) == 0 { + return nil, errors.New("蒙版编辑必须提供与蒙版同尺寸的源图片") + } + } + if len(input.ReferenceImages) > 0 || input.Mask != nil { + body := &bytes.Buffer{} + writer := multipart.NewWriter(body) + writeField(writer, "model", input.Config.Model) + writeField(writer, "prompt", withSystemPrompt(input.Config, input.Prompt)) + writeField(writer, "n", "1") + if imageParameterSupported(input.ImageCapability, "response_format") { + writeField(writer, "response_format", "b64_json") + } + if imageParameterSupported(input.ImageCapability, "output_format") { + writeField(writer, "output_format", "png") + } + if imageTransparentBackgroundSupported(input.ImageCapability) && input.Config.TransparentBackground == "true" { + writeField(writer, "background", "transparent") + } + if imageQualitySupported(input.ImageCapability) && input.Config.Quality != "" && !strings.EqualFold(strings.TrimSpace(input.Config.Quality), "auto") { + writeField(writer, "quality", normalizeImageQuality(input.Config.Quality)) + } + if key, value := imageSizeParameter(input.ImageCapability, input.Config.Size); value != "" { + writeField(writer, key, value) + } + for _, image := range input.ReferenceImages { + if err := writeMediaPart(writer, "image", image); err != nil { + return nil, err + } + } + if input.Mask != nil { + if err := writeMediaPart(writer, "mask", *input.Mask); err != nil { + return nil, err + } + } + if err := writer.Close(); err != nil { + return nil, err + } + if err := postForm(ctx, input.Config, "/images/edits", writer.FormDataContentType(), body, &payload); err != nil { + return nil, err + } + } else { + body := map[string]interface{}{ + "model": input.Config.Model, + "prompt": withSystemPrompt(input.Config, input.Prompt), + "n": 1, + } + if imageParameterSupported(input.ImageCapability, "response_format") { + body["response_format"] = "b64_json" + } + if imageParameterSupported(input.ImageCapability, "output_format") { + body["output_format"] = "png" + } + if imageTransparentBackgroundSupported(input.ImageCapability) && input.Config.TransparentBackground == "true" { + body["background"] = "transparent" + } + if imageQualitySupported(input.ImageCapability) && input.Config.Quality != "" && !strings.EqualFold(strings.TrimSpace(input.Config.Quality), "auto") { + body["quality"] = normalizeImageQuality(input.Config.Quality) + } + if key, value := imageSizeParameter(input.ImageCapability, input.Config.Size); value != "" { + body[key] = value + } + if err := postJSON(ctx, input.Config, "/images/generations", body, &payload); err != nil { + return nil, err + } + } + images, err := imageDataURLs(payload) + if err != nil { + return nil, err + } + return map[string]interface{}{"mode": "image", "images": images}, nil +} + +func runGeminiImageTask(ctx context.Context, input canvasGenerationInput) (map[string]interface{}, error) { + if input.Mask != nil { + return nil, errors.New("Gemini Images 不支持蒙版编辑,请移除蒙版后重试") + } + if len(input.ReferenceVideos) > 0 || len(input.ReferenceAudios) > 0 { + return nil, errors.New("Gemini Images 不支持参考视频或音频") + } + + parts := make([]geminiImageContentPart, 0, 1+len(input.ReferenceImages)) + if prompt := strings.TrimSpace(input.Prompt); prompt != "" { + parts = append(parts, geminiImageContentPart{Text: prompt}) + } + for _, image := range input.ReferenceImages { + raw, mimeType, err := geminiImageBytes(image) + if err != nil { + return nil, fmt.Errorf("读取 Gemini Images 参考图失败:%w", err) + } + parts = append(parts, geminiImageContentPart{InlineData: &geminiImageInlineData{MIMEType: mimeType, Data: base64.StdEncoding.EncodeToString(raw)}}) + } + if len(parts) == 0 { + return nil, errors.New("Gemini Images 请求缺少提示词或参考图") + } + + body := geminiImageRequest{ + Contents: []geminiImageContent{{Role: "user", Parts: parts}}, + GenerationConfig: geminiImageGenerationConfig{ + ResponseModalities: []string{"TEXT", "IMAGE"}, + ImageConfig: geminiImageConfigFor(input.Config), + }, + } + if systemPrompt := strings.TrimSpace(input.Config.SystemPrompt); systemPrompt != "" { + body.SystemInstruction = &geminiImageContent{Parts: []geminiImageContentPart{{Text: systemPrompt}}} + } + var payload map[string]interface{} + path := "/models/" + url.PathEscape(input.Config.Model) + ":generateContent" + if err := postGeminiJSON(ctx, input.Config, path, body, &payload); err != nil { + return nil, err + } + images, err := geminiImageDataURLs(payload) + if err != nil { + return nil, err + } + return map[string]interface{}{"mode": "image", "images": images}, nil +} + +func geminiImageConfigFor(config providerConfig) *geminiImageConfig { + imageConfig := &geminiImageConfig{} + size := strings.TrimSpace(config.Size) + if size != "" && size != "auto" && strings.Count(size, ":") == 1 { + imageConfig.AspectRatio = size + } + switch strings.ToLower(strings.TrimSpace(config.Quality)) { + case "low", "1k": + imageConfig.ImageSize = "1K" + case "medium", "2k": + imageConfig.ImageSize = "2K" + case "high", "4k": + imageConfig.ImageSize = "4K" + } + if imageConfig.AspectRatio == "" && imageConfig.ImageSize == "" { + return nil + } + return imageConfig +} + +func geminiImageBytes(media providerMedia) ([]byte, string, error) { + raw, mimeType, err := mediaBytes(media) + if err != nil { + return nil, "", err + } + if len(raw) == 0 { + return nil, "", errors.New("参考图片数据为空") + } + detected := strings.TrimSpace(strings.Split(http.DetectContentType(raw), ";")[0]) + if !strings.HasPrefix(strings.ToLower(mimeType), "image/") { + if !strings.HasPrefix(strings.ToLower(detected), "image/") { + return nil, "", fmt.Errorf("参考图片 MIME 类型无效:%s", defaultString(mimeType, detected)) + } + mimeType = detected + } else if !strings.HasPrefix(strings.ToLower(detected), "image/") { + // 以实际字节签名为准,避免错误的 data URL MIME 把非图片内容伪装成图片。 + return nil, "", fmt.Errorf("参考图片内容不是有效图片:%s", defaultString(detected, mimeType)) + } + return raw, mimeType, nil +} + +func geminiImageDataURLs(payload map[string]interface{}) ([]map[string]string, error) { + if errorValue, ok := payload["error"].(map[string]interface{}); ok { + if message := stringField(errorValue, "message"); message != "" { + return nil, errors.New(message) + } + } + candidates, _ := payload["candidates"].([]interface{}) + images := make([]map[string]string, 0) + for _, candidateValue := range candidates { + candidate, _ := candidateValue.(map[string]interface{}) + content, _ := candidate["content"].(map[string]interface{}) + parts, _ := content["parts"].([]interface{}) + for _, partValue := range parts { + part, _ := partValue.(map[string]interface{}) + inlineData, _ := part["inlineData"].(map[string]interface{}) + if inlineData == nil { + inlineData, _ = part["inline_data"].(map[string]interface{}) + } + if inlineData != nil { + data := strings.TrimSpace(stringField(inlineData, "data")) + mimeType := firstNonEmptyString(stringField(inlineData, "mimeType"), stringField(inlineData, "mime_type")) + if data == "" { + continue + } + if mimeType == "" { + mimeType = "image/png" + } + if !strings.HasPrefix(strings.ToLower(mimeType), "image/") { + return nil, fmt.Errorf("Gemini Images 返回了非图片 MIME 类型:%s", mimeType) + } + decoded, err := base64.StdEncoding.DecodeString(data) + if err != nil { + return nil, fmt.Errorf("Gemini Images 返回的图片数据无效:%w", err) + } + images = append(images, map[string]string{"dataUrl": dataURL(mimeType, decoded)}) + continue + } + fileData, _ := part["fileData"].(map[string]interface{}) + if fileData != nil { + if fileURL := firstNonEmptyString(stringField(fileData, "fileUri"), stringField(fileData, "file_uri")); fileURL != "" { + images = append(images, map[string]string{"dataUrl": fileURL}) + } + } + } + } + if len(images) == 0 { + return nil, errors.New("Gemini Images 接口没有返回图片") + } + return images, nil +} + +func runGrokImageTask(ctx context.Context, input canvasGenerationInput) (map[string]interface{}, error) { + body, path, err := grokImageRequestBody(input) + if err != nil { + return nil, err + } + var payload imageResponse + if err := postJSON(ctx, input.Config, path, body, &payload); err != nil { + return nil, err + } + images, err := imageDataURLs(payload) + if err != nil { + return nil, err + } + return map[string]interface{}{"mode": "image", "images": images}, nil +} + +func grokImageRequestBody(input canvasGenerationInput) (grokImageRequest, string, error) { + if input.Mask != nil { + return grokImageRequest{}, "", errors.New("Grok 图片协议不支持蒙版编辑,请移除蒙版后重试") + } + body := grokImageRequest{ + Model: input.Config.Model, + Prompt: withSystemPrompt(input.Config, input.Prompt), + N: 1, + ResponseFormat: "url", + // Grok 图片协议用 aspect_ratio 表达画布比例;同时发送 size 会被上游按 OpenAI 枚举校验并拒绝。 + AspectRatio: normalizeGrokImageAspectRatio(input.Config.Size), + Resolution: normalizeGrokImageResolution(input.Config.Quality), + } + if len(input.ReferenceImages) == 0 { + return body, "/images/generations", nil + } + if len(input.ReferenceImages) != 1 { + return grokImageRequest{}, "", fmt.Errorf("Grok 图片编辑只支持 1 张参考图,当前连接了 %d 张", len(input.ReferenceImages)) + } + imageURL, err := grokImageInputURL(input.ReferenceImages[0]) + if err != nil { + return grokImageRequest{}, "", err + } + body.Image = &grokImageInput{URL: imageURL} + return body, "/images/edits", nil +} + +// normalizeGrokImageResolution 把画布 quality(1k/2k/high…)映射为 grok2api / xAI 的 resolution。 +func normalizeGrokImageResolution(quality string) string { + raw := strings.ToLower(strings.TrimSpace(quality)) + switch raw { + case "", "auto": + return "" + case "1k", "low", "standard": + return "1k" + case "2k", "medium", "hd", "high", "4k": + // xAI Imagine 图片通常最高 2k;超出则夹到 2k,避免上游拒参。 + return "2k" + default: + return "" + } +} + +// normalizeGrokImageAspectRatio 把画布 size(如 1280x720 / 9:16)转成 grok2api / xAI 接受的 aspect_ratio。 +func normalizeGrokImageAspectRatio(size string) string { + raw := strings.ToLower(strings.TrimSpace(strings.ReplaceAll(size, "×", "x"))) + if raw == "" || raw == "auto" { + return "" + } + if strings.Contains(raw, ":") { + switch raw { + case "1:1", "3:4", "4:3", "9:16", "16:9", "2:3", "3:2", "9:19.5", "19.5:9", "1:2", "2:1": + return raw + } + } + parts := strings.Split(raw, "x") + if len(parts) != 2 { + return "" + } + w, wErr := strconv.Atoi(parts[0]) + h, hErr := strconv.Atoi(parts[1]) + if wErr != nil || hErr != nil || w <= 0 || h <= 0 { + return "" + } + if w == h { + return "1:1" + } + ratio := float64(w) / float64(h) + switch { + case w*9 == h*16 || (ratio >= 1.7 && ratio <= 1.8): + return "16:9" + case h*9 == w*16 || (ratio > 0 && ratio <= 1.0/1.7 && ratio >= 1.0/1.8): + return "9:16" + case w*3 == h*4 || (ratio > 1.2 && ratio < 1.4): + return "4:3" + case h*3 == w*4 || (ratio > 0.7 && ratio < 0.85): + return "3:4" + // 像素尺寸路径必须显式覆盖 2:3 / 3:2 / 1:2 / 2:1:冒号字符串能直达(见上方 switch), + // 但像素路径只靠 w>h 兜底会把 768x1152(2:3,ratio 0.667)错标成 9:16、 + // 1152x768(3:2,ratio 1.5)错标成 16:9,xAI 按错比例裁切生成图。 + case w*3 == h*2 || (ratio >= 0.6 && ratio < 0.72): + return "2:3" + case w*2 == h*3 || (ratio > 1.35 && ratio < 1.6): + return "3:2" + case h == w*2 || (ratio > 0.45 && ratio < 0.55): + return "1:2" + case w == h*2 || (ratio > 1.85 && ratio < 2.2): + return "2:1" + case w > h: + return "16:9" + default: + return "9:16" + } +} + +func grokImageInputURL(media providerMedia) (string, error) { + if isPublicMediaURL(strings.TrimSpace(media.URL)) { + return strings.TrimSpace(media.URL), nil + } + return openAIImageInputURL(media) +} + +const ( + volcengineArkImageMinPixels = 3686400 + volcengineArkImageMaxPixels = 4624220 +) + +func runVolcengineArkImageTask(ctx context.Context, input canvasGenerationInput) (map[string]interface{}, error) { + if input.Mask != nil { + return nil, errors.New("火山方舟图片协议不支持蒙版编辑,请移除蒙版后重试") + } + body, err := volcengineArkImageBody(input) + if err != nil { + return nil, err + } + var payload imageResponse + if err := postJSON(ctx, input.Config, "/images/generations", body, &payload); err != nil { + return nil, err + } + images, err := volcengineArkImageDataURLs(ctx, input.Config, payload) + if err != nil { + return nil, err + } + return map[string]interface{}{"mode": "image", "images": images}, nil +} + +func volcengineArkImageDataURLs(ctx context.Context, config providerConfig, payload imageResponse) ([]map[string]string, error) { + images, err := imageDataURLs(payload) + if err != nil { + return nil, err + } + for _, image := range images { + value := strings.TrimSpace(image["dataUrl"]) + if strings.HasPrefix(value, "data:image/") { + continue + } + if !isPublicMediaURL(value) { + return nil, errors.New("火山方舟图片接口没有返回可下载的图片") + } + // 方舟默认返回临时 CDN 地址。必须由后端下载成内联结果,后续资源持久化才能 + // 原子地写入服务器或用户配置的对象存储,且不依赖浏览器跨域访问方舟 CDN。 + data, mimeType, err := getProviderExternalBinary(withProviderRequestKind(ctx, "download"), config, value) + if err != nil { + return nil, fmt.Errorf("火山方舟图片结果下载失败:%w", err) + } + detected := strings.ToLower(strings.TrimSpace(strings.Split(http.DetectContentType(data), ";")[0])) + mimeType = strings.ToLower(normalizedMediaMimeType(mimeType, data)) + if len(data) == 0 || strings.Contains(detected, "json") || strings.HasPrefix(detected, "text/") || !strings.HasPrefix(mimeType, "image/") { + return nil, fmt.Errorf("火山方舟图片结果无效:%s", defaultString(detected, mimeType)) + } + image["dataUrl"] = dataURL(mimeType, data) + image["mimeType"] = mimeType + } + return images, nil +} + +func volcengineArkImageBody(input canvasGenerationInput) (map[string]interface{}, error) { + body := map[string]interface{}{ + "model": input.Config.Model, + "prompt": withSystemPrompt(input.Config, input.Prompt), + "n": 1, + "response_format": "b64_json", + "watermark": false, + } + if key, value := imageSizeParameter(input.ImageCapability, input.Config.Size); value != "" { + if key == "size" { + value = normalizeVolcengineArkImageSize(value) + } + body[key] = value + } + if len(input.ReferenceImages) == 0 { + return body, nil + } + images := make([]string, 0, len(input.ReferenceImages)) + for _, image := range input.ReferenceImages { + url, err := openAIImageInputURL(image) + if err != nil { + return nil, err + } + images = append(images, url) + } + if len(images) == 1 { + body["image"] = images[0] + } else { + body["image"] = images + } + return body, nil +} + +func normalizeVolcengineArkImageSize(value string) string { + size := normalizePixelSize(value) + parts := strings.Split(strings.ToLower(size), "x") + if len(parts) != 2 { + return size + } + width, widthErr := strconv.Atoi(parts[0]) + height, heightErr := strconv.Atoi(parts[1]) + if widthErr != nil || heightErr != nil || width <= 0 || height <= 0 { + return size + } + pixels := int64(width) * int64(height) + if pixels >= volcengineArkImageMinPixels && pixels <= volcengineArkImageMaxPixels { + return size + } + targetPixels := volcengineArkImageMaxPixels + round := math.Floor + if pixels < volcengineArkImageMinPixels { + targetPixels = volcengineArkImageMinPixels + round = math.Ceil + } + scale := math.Sqrt(float64(targetPixels) / float64(pixels)) + width = int(round(float64(width)*scale/2)) * 2 + height = int(round(float64(height)*scale/2)) * 2 + for width > 2 && height > 2 && int64(width)*int64(height) < volcengineArkImageMinPixels { + if width >= height { + width += 2 + } else { + height += 2 + } + } + for width > 2 && height > 2 && int64(width)*int64(height) > volcengineArkImageMaxPixels { + if width >= height { + width -= 2 + } else { + height -= 2 + } + } + return strconv.Itoa(width) + "x" + strconv.Itoa(height) +} + +func imageDataURLs(payload imageResponse) ([]map[string]string, error) { + if len(payload.Data) == 0 { + return nil, errors.New("接口没有返回图片") + } + images := make([]map[string]string, 0, len(payload.Data)) + for _, item := range payload.Data { + if b64, ok := item["b64_json"].(string); ok && b64 != "" { + images = append(images, map[string]string{"dataUrl": "data:image/png;base64," + b64}) + continue + } + if url, ok := item["url"].(string); ok && url != "" { + images = append(images, map[string]string{"dataUrl": url}) + } + } + if len(images) == 0 { + return nil, errors.New("接口没有返回可用图片") + } + return images, nil +} diff --git a/backend/internal/service/provider_jimeng.go b/backend/internal/service/provider_jimeng.go index 71a3c2790..e89ecb22b 100644 --- a/backend/internal/service/provider_jimeng.go +++ b/backend/internal/service/provider_jimeng.go @@ -7,7 +7,6 @@ import ( "encoding/json" "errors" "fmt" - "math" "net/http" "net/url" "strconv" @@ -94,60 +93,6 @@ func runVolcengineJiMengImageTask(ctx context.Context, input canvasGenerationInp return nil, fmt.Errorf("即梦图片生成超时(任务 %s)", taskID) } -func runVolcengineJiMengVideoTask(ctx context.Context, input canvasGenerationInput) (map[string]interface{}, error) { - if len(input.ReferenceImages) > 1 || len(input.ReferenceVideos) > 0 || len(input.ReferenceAudios) > 0 { - return nil, errors.New("即梦视频协议只支持一张首帧图,不支持参考视频或音频") - } - frames, err := jiMengVideoFrames(input.Config.VideoSeconds) - if err != nil { - return nil, err - } - body := map[string]interface{}{ - "req_key": input.Config.Model, - "prompt": strings.TrimSpace(input.Prompt), - "frames": frames, - } - if len(input.ReferenceImages) == 0 { - body["aspect_ratio"] = jiMengVideoAspectRatio(input.Config.Size) - } else { - raw, _, err := mediaBytes(input.ReferenceImages[0]) - if err != nil { - return nil, fmt.Errorf("读取即梦视频首帧图失败:%w", err) - } - body["binary_data_base64"] = []string{base64.StdEncoding.EncodeToString(raw)} - } - - taskID, err := submitJiMengTask(ctx, input.Config, body) - if err != nil { - return nil, err - } - for deadline := providerPollingDeadline(ctx); time.Now().Before(deadline); { - result, err := pollJiMengTask(ctx, input.Config, taskID, "") - if err != nil { - return nil, err - } - switch strings.ToLower(strings.TrimSpace(result.Data.Status)) { - case "done": - videoURL := strings.TrimSpace(result.Data.VideoURL) - if videoURL == "" { - return nil, fmt.Errorf("即梦视频任务 %s 已完成但没有返回视频地址", taskID) - } - data, mimeType, err := getExternalBinary(withProviderRequestKind(ctx, "download"), videoURL) - if err != nil { - return nil, fmt.Errorf("即梦视频任务 %s 下载失败:%w", taskID, err) - } - mimeType = normalizedMediaMimeType(mimeType, data) - return map[string]interface{}{"mode": "video", "video": map[string]interface{}{"dataUrl": dataURL(mimeType, data), "mimeType": mimeType}}, nil - case "not_found", "expired": - return nil, fmt.Errorf("即梦视频任务 %s 已失效,请重新生成", taskID) - } - if err := sleepContext(ctx, 5*time.Second); err != nil { - return nil, err - } - } - return nil, fmt.Errorf("即梦视频生成超时(任务 %s)", taskID) -} - func submitJiMengTask(ctx context.Context, config providerConfig, body map[string]interface{}) (string, error) { if resumed := resumedProviderRequestID(ctx); resumed != "" { return resumed, nil @@ -260,47 +205,3 @@ func jiMengImageDimensions(value string) (int, int) { } return width, height } - -func jiMengVideoFrames(value string) (int, error) { - seconds, _ := strconv.Atoi(strings.TrimSpace(value)) - switch seconds { - case 5: - return 121, nil - case 10: - return 241, nil - default: - return 0, errors.New("即梦视频仅支持 5 秒或 10 秒,请调整视频时长") - } -} - -func jiMengVideoAspectRatio(value string) string { - allowed := []string{"16:9", "4:3", "1:1", "3:4", "9:16", "21:9"} - value = strings.TrimSpace(value) - for _, candidate := range allowed { - if value == candidate { - return candidate - } - } - parts := strings.Split(strings.ToLower(value), "x") - if len(parts) != 2 { - return "16:9" - } - width, widthErr := strconv.ParseFloat(parts[0], 64) - height, heightErr := strconv.ParseFloat(parts[1], 64) - if widthErr != nil || heightErr != nil || width <= 0 || height <= 0 { - return "16:9" - } - ratio := width / height - best := allowed[0] - bestDistance := math.MaxFloat64 - for _, candidate := range allowed { - parts := strings.Split(candidate, ":") - left, _ := strconv.ParseFloat(parts[0], 64) - right, _ := strconv.ParseFloat(parts[1], 64) - if distance := math.Abs(ratio - left/right); distance < bestDistance { - best = candidate - bestDistance = distance - } - } - return best -} diff --git a/backend/internal/service/provider_jimeng_test.go b/backend/internal/service/provider_jimeng_test.go index f53458c91..26a87380d 100644 --- a/backend/internal/service/provider_jimeng_test.go +++ b/backend/internal/service/provider_jimeng_test.go @@ -49,12 +49,3 @@ func TestVolcengineJiMengImageTaskUsesSignedAsyncProtocol(t *testing.T) { t.Fatalf("result = %#v", result) } } - -func TestJiMengVideoFramesRejectsUnsupportedDuration(t *testing.T) { - if frames, err := jiMengVideoFrames("10"); err != nil || frames != 241 { - t.Fatalf("jiMengVideoFrames(10) = %d, %v", frames, err) - } - if _, err := jiMengVideoFrames("6"); err == nil { - t.Fatal("jiMengVideoFrames(6) error = nil") - } -} diff --git a/backend/internal/service/provider_progress.go b/backend/internal/service/provider_progress.go index 06cf50c21..07dbe56cf 100644 --- a/backend/internal/service/provider_progress.go +++ b/backend/internal/service/provider_progress.go @@ -12,8 +12,8 @@ var providerProgressKeys = []string{"progress", "progresspercent", "progressperc var providerProgressContainers = []string{"data", "result", "task", "output", "operation", "response", "meta", "metadata"} -// syncProviderTaskProgress is deliberately best-effort: progress telemetry must -// never turn a successful provider response into a failed generation request. +// syncProviderTaskProgress 只同步观测性进度,不参与生成结果提交。 +// 因此进度解析或持久化失败只记录日志,不能把已经成功的 Provider 响应改判为生成失败。 func (s *Service) syncProviderTaskProgress(taskID string, responseBody []byte) { if s == nil || s.repo == nil || strings.TrimSpace(taskID) == "" { return diff --git a/backend/internal/service/provider_protocol.go b/backend/internal/service/provider_protocol.go new file mode 100644 index 000000000..391c7f064 --- /dev/null +++ b/backend/internal/service/provider_protocol.go @@ -0,0 +1,995 @@ +package service + +// 声明式协议插件宿主与接口类型校验。 + +import ( + "bytes" + "context" + "crypto/hmac" + "crypto/sha256" + "encoding/hex" + "encoding/json" + "errors" + "fmt" + "io" + "mime/multipart" + "net" + "net/http" + "net/textproto" + "net/url" + "sort" + "strconv" + "strings" + "time" + + "infinite-canvas/backend/internal/model" + "infinite-canvas/backend/internal/protocol" + + "github.com/google/uuid" + "github.com/volcengine/volc-sdk-golang/base" +) + +// runDeclarativeProtocolTask 是 JSON manifest 插件的宿主运行时。 +// manifest 只能描述请求/响应映射;凭证、出站安全策略、轮询和结果下载必须由宿主统一掌握, +// 这样插件不能绕过服务端的鉴权、超时和 SSRF 防护边界。 +func runDeclarativeProtocolTask(ctx context.Context, input canvasGenerationInput) (map[string]interface{}, error) { + adapter, ok := declarativeProtocolAdapterForContext(ctx, input.Config.InterfaceType) + if !ok { + return nil, fmt.Errorf("接口类型 %s 未安装声明式适配器", input.Config.InterfaceType) + } + return runProtocolAdapterTask(ctx, input, adapter) +} + +type protocolPollTiming struct { + InitialDelay time.Duration + PollInterval time.Duration + TaskNotExistWindow time.Duration + TaskNotExistMaxMisses int + TaskNotExistRetryDelays []time.Duration +} + +var defaultProtocolPollTiming = protocolPollTiming{ + InitialDelay: 2 * time.Second, + PollInterval: 2500 * time.Millisecond, + TaskNotExistWindow: 15 * time.Second, + TaskNotExistMaxMisses: 5, + TaskNotExistRetryDelays: []time.Duration{2 * time.Second, 3 * time.Second, 5 * time.Second, 5 * time.Second}, +} + +func runProtocolAdapterTask(ctx context.Context, input canvasGenerationInput, adapter protocol.Adapter) (map[string]interface{}, error) { + return runProtocolAdapterTaskWithTiming(ctx, input, adapter, defaultProtocolPollTiming) +} + +func runProtocolAdapterTaskWithTiming(ctx context.Context, input canvasGenerationInput, adapter protocol.Adapter, timing protocolPollTiming) (map[string]interface{}, error) { + // create、poll、download 是同一个外部任务的三个阶段:任一阶段失败都向上返回真实错误, + // 不把“已提交但结果未知”伪装成成功,也不把下载失败降级成空结果。 + request := protocolRequestFromInput(input) + taskID := resumedProviderRequestID(ctx) + var created protocol.CreateResult + createdProviderTask := false + syncWindowStartedAt := time.Now() + if taskID == "" { + // 幂等键只存在于宿主请求元数据中,声明式插件可以把它映射到 Header, + // 但不能把宿主控制字段泄漏到供应商 JSON body。恢复已有 taskID 时不会进入 create 分支。 + request.Extra["idempotencyKey"] = uuid.NewString() + spec, err := adapter.BuildCreate(ctx, protocol.RequestContext{BaseURL: input.Config.BaseURL, Request: request}) + if err != nil { + return nil, err + } + body, err := executeProtocolRequest(withProviderRequestKind(ctx, "create"), input.Config, spec) + if err != nil { + return nil, err + } + created, err = adapter.ParseCreate(ctx, body) + if err != nil { + return nil, err + } + taskID = created.TaskID + if taskID == "" { + extracted, extractErr := extractProviderTaskID(body) + if extractErr != nil { + return nil, extractErr + } + taskID = extracted + } + if created.Status == protocol.StatusFailed || created.Status == protocol.StatusCancelled { + return nil, protocolResultError(created.Message, taskID) + } + if created.Status == protocol.StatusSucceeded { + return finishProtocolAdapterResult(ctx, input, adapter, request, taskID, created.Result) + } + if taskID == "" { + return nil, errors.New("声明式协议创建请求没有返回任务 ID") + } + createdProviderTask = true + syncWindowStartedAt = time.Now() + } + if createdProviderTask && input.Config.InterfaceType == string(model.ChannelInterfaceNewAPIChannel2) { + if err := sleepContext(ctx, timing.InitialDelay); err != nil { + return nil, err + } + } + + taskNotExistMisses := 0 + for deadline := providerPollingDeadline(ctx); time.Now().Before(deadline); { + spec, err := adapter.BuildPoll(ctx, protocol.PollContext{BaseURL: input.Config.BaseURL, Model: request.Model, Request: request, TaskID: taskID}) + if err != nil { + return nil, err + } + body, err := executeProtocolRequest(withProviderRequestKind(ctx, "poll"), input.Config, spec) + if err != nil { + if isNewAPIChannel2TaskNotReady(input.Config.InterfaceType, taskID, err) { + // NewAPI Channel 2 创建后存在短暂同步窗口。窗口内的 task_not_exist 只表示尚未可查; + // 超过窗口仍不可查时返回 pending error,让上层保留任务 ID,而不是误判成功或失败。 + taskNotExistMisses++ + maxMisses := timing.TaskNotExistMaxMisses + if maxMisses <= 0 { + maxMisses = len(timing.TaskNotExistRetryDelays) + 1 + } + if time.Since(syncWindowStartedAt) >= timing.TaskNotExistWindow || taskNotExistMisses >= maxMisses { + return nil, providerStatePendingError{TaskID: taskID, Cause: err} + } + delayIndex := min(taskNotExistMisses-1, len(timing.TaskNotExistRetryDelays)-1) + if delayIndex >= 0 { + if sleepErr := sleepContext(ctx, timing.TaskNotExistRetryDelays[delayIndex]); sleepErr != nil { + return nil, sleepErr + } + } + continue + } + return nil, err + } + taskNotExistMisses = 0 + state, err := adapter.ParsePoll(ctx, protocol.PollContext{BaseURL: input.Config.BaseURL, Model: request.Model, Request: request, TaskID: taskID}, body) + if err != nil { + return nil, err + } + if state.TaskID != "" { + taskID = state.TaskID + } + switch state.Status { + case protocol.StatusSucceeded: + return finishProtocolAdapterResult(ctx, input, adapter, request, taskID, state.Result) + case protocol.StatusFailed, protocol.StatusCancelled: + return nil, protocolResultError(state.Message, taskID) + } + if err := sleepContext(ctx, timing.PollInterval); err != nil { + return nil, err + } + } + if err := ctx.Err(); err != nil { + return nil, err + } + return nil, fmt.Errorf("声明式协议任务超时(任务 %s)", taskID) +} + +func extractProviderTaskID(body []byte) (string, error) { + var payload map[string]interface{} + if json.Unmarshal(body, &payload) != nil { + return "", nil + } + id, err := firstJSONString(payload, "id", "task_id", "taskId", "request_id", "name") + if id != "" { + return id, nil + } + if data, ok := payload["data"].(map[string]interface{}); ok { + nested, nestedErr := firstJSONString(data, "id", "task_id", "taskId", "request_id") + if nested != "" { + return nested, nil + } + if nestedErr != nil { + return "", nestedErr + } + } + return "", err +} + +func isNewAPIChannel2TaskNotReady(interfaceType string, taskID string, err error) bool { + if strings.TrimSpace(interfaceType) != string(model.ChannelInterfaceNewAPIChannel2) || strings.TrimSpace(taskID) == "" { + return false + } + var httpErr providerHTTPError + if !errors.As(err, &httpErr) || httpErr.StatusCode != http.StatusBadRequest { + return false + } + var payload map[string]any + if json.Unmarshal([]byte(httpErr.Body), &payload) != nil { + return false + } + code, message := providerFailureDetails(payload) + return strings.EqualFold(strings.TrimSpace(code), "task_not_exist") || strings.EqualFold(strings.TrimSpace(message), "task_not_exist") +} + +// queryProtocolAdapterVideoTask 只读取一次已有的声明式 Provider 任务。 +// 人工恢复走此路径,因此检查本地失败任务时绝不会创建第二个计费任务。 +func queryProtocolAdapterVideoTask(ctx context.Context, input canvasGenerationInput, adapter protocol.Adapter, taskID string) (map[string]interface{}, string, error) { + request := protocolRequestFromInput(input) + pollContext := protocol.PollContext{BaseURL: input.Config.BaseURL, Model: request.Model, Request: request, TaskID: taskID} + spec, err := adapter.BuildPoll(ctx, pollContext) + if err != nil { + return nil, "", err + } + body, err := executeProtocolRequest(withProviderRequestKind(ctx, "poll"), input.Config, spec) + if err != nil { + return nil, "", err + } + state, err := adapter.ParsePoll(ctx, pollContext, body) + if err != nil { + return nil, "", err + } + providerStatus := string(state.Status) + switch state.Status { + case protocol.StatusSucceeded: + result, err := finishProtocolAdapterResult(ctx, input, adapter, request, taskID, state.Result) + return result, providerStatus, err + case protocol.StatusFailed, protocol.StatusCancelled: + return nil, providerStatus, protocolResultError(state.Message, taskID) + case protocol.StatusPending, protocol.StatusProcessing: + return nil, providerStatus, nil + default: + return nil, providerStatus, fmt.Errorf("声明式协议任务 %s 返回未知状态:%s", taskID, providerStatus) + } +} + +func protocolRequestFromInput(input canvasGenerationInput) protocol.GenerationRequest { + resolution := strings.TrimSpace(input.Config.VQuality) + if input.Mode == "video" { + if declared := videoResolutionNameRequest(input.VideoCapability, resolution); declared != "" { + resolution = declared + } + } + request := protocol.GenerationRequest{ + Capability: protocol.Capability(input.Mode), + Model: input.Config.Model, + Prompt: input.Prompt, + Instructions: strings.TrimSpace(input.Config.SystemPrompt), + Images: protocolImageReferences(input), + Videos: protocolMediaReferences(input.ReferenceVideos, "video"), + Audios: protocolMediaReferences(input.ReferenceAudios, "audio"), + AspectRatio: input.Config.Size, + Resolution: resolution, + Quality: input.Config.Quality, + GenerateAudio: parseBool(input.Config.VideoGenerateAudio, false), + Watermark: parseBool(input.Config.VideoWatermark, false), + Operation: firstNonEmpty(metadataString(input.Metadata, "videoEditOperation"), metadataString(input.Metadata, "videoOperation")), + Extra: map[string]any{ + "videoSeconds": input.Config.VideoSeconds, + "audioVoice": input.Config.AudioVoice, + "audioFormat": input.Config.AudioFormat, + "count": input.Config.Count, + }, + } + for _, message := range input.TextHistory { + role := strings.ToLower(strings.TrimSpace(message.Role)) + if role != "user" && role != "assistant" && role != "system" { + continue + } + if content := strings.TrimSpace(message.Content); content != "" { + request.Messages = append(request.Messages, protocol.Message{Role: role, Content: content}) + } + } + request.Inputs = append(request.Inputs, request.Images...) + request.Inputs = append(request.Inputs, request.Videos...) + request.Inputs = append(request.Inputs, request.Audios...) + if input.MaxOutputTokens > 0 { + request.Extra["max_output_tokens"] = input.MaxOutputTokens + request.Extra["max_tokens"] = input.MaxOutputTokens + } + if duration, err := strconv.Atoi(strings.TrimSpace(input.Config.VideoSeconds)); err == nil && duration > 0 { + request.Duration = duration + } + if count, err := strconv.Atoi(strings.TrimSpace(input.Config.Count)); err == nil && count > 0 { + request.ImageCount = count + } + request.Output = protocol.OutputOptions{ + Count: request.ImageCount, Duration: request.Duration, AspectRatio: request.AspectRatio, + Resolution: request.Resolution, Quality: request.Quality, GenerateAudio: request.GenerateAudio, + Watermark: request.Watermark, Format: input.Config.AudioFormat, + } + request.ProviderOptions = make(map[string]map[string]any) + if configured, ok := input.Metadata["providerOptions"].(map[string]any); ok { + for namespace, raw := range configured { + if options, ok := raw.(map[string]any); ok { + request.ProviderOptions[strings.TrimSpace(namespace)] = options + } + } + } + return request +} + +func protocolImageReferences(input canvasGenerationInput) []protocol.MediaReference { + if input.Mode == "video" { + return protocolVideoImageReferences(input) + } + result := make([]protocol.MediaReference, 0, len(input.ReferenceImages)+1) + for index, value := range input.ReferenceImages { + item := protocolMediaReference(value, "image", index) + item.Role = "reference_image" + if input.Mode == "image" { + item.Role = "edit_source" + } + if item.URL != "" || item.DataURL != "" { + result = append(result, item) + } + } + if input.Mask != nil { + mask := protocolMediaReference(*input.Mask, "image", len(result)) + mask.Role = "mask" + if mask.URL != "" || mask.DataURL != "" { + result = append(result, mask) + } + } + return result +} + +func protocolVideoImageReferences(input canvasGenerationInput) []protocol.MediaReference { + result := make([]protocol.MediaReference, 0, len(input.ReferenceImages)) + fallbackRole := "" + if metadataString(input.Metadata, "videoStartFrameNodeId") != "" || metadataString(input.Metadata, "videoEndFrameNodeId") != "" { + fallbackRole = "reference_image" + } + for index, value := range input.ReferenceImages { + item := protocolMediaReference(value, "image", index) + item.Role = videoImageRoleOrDefault(input, value, fallbackRole) + if item.URL != "" || item.DataURL != "" { + result = append(result, item) + } + } + return result +} + +func protocolMediaReferences(values []providerMedia, kind string) []protocol.MediaReference { + result := make([]protocol.MediaReference, 0, len(values)) + for index, value := range values { + item := protocolMediaReference(value, kind, index) + if kind == "video" { + item.Role = "reference_video" + } else if kind == "audio" { + item.Role = "reference_audio" + } + if item.URL != "" || item.DataURL != "" { + result = append(result, item) + } + } + return result +} + +func protocolMediaReference(value providerMedia, kind string, order int) protocol.MediaReference { + return protocol.MediaReference{ + ID: strings.TrimSpace(value.ID), URL: strings.TrimSpace(value.URL), DataURL: strings.TrimSpace(value.DataURL), + Kind: kind, MIMEType: firstNonEmpty(strings.TrimSpace(value.MimeType), strings.TrimSpace(value.Type)), Name: strings.TrimSpace(value.Name), Order: order, + Metadata: map[string]any{"bytes": value.Bytes, "width": value.Width, "height": value.Height, "durationMs": value.DurationMs, "storageKey": strings.TrimSpace(value.StorageKey)}, + } +} + +func executeProtocolRequest(ctx context.Context, config providerConfig, spec protocol.RequestSpec) ([]byte, error) { + data, _, err := executeProtocolBinaryRequest(ctx, config, spec) + return data, err +} + +// executeProtocolBinaryRequest 是声明式插件与宿主网络能力之间的边界。manifest 只能声明 +// method/path/body/auth;最终 URL 校验、凭证注入、SSRF、超时、大小限制和审计仍由宿主统一执行, +// 插件不能通过自定义请求规格绕过这些安全约束。 +func executeProtocolBinaryRequest(ctx context.Context, config providerConfig, spec protocol.RequestSpec) ([]byte, string, error) { + if err := spec.Validate(); err != nil { + return nil, "", err + } + method := strings.ToUpper(strings.TrimSpace(spec.Method)) + body, contentType, err := protocolRequestBody(ctx, config, spec) + if err != nil { + return nil, "", err + } + requestURL, err := protocolRequestURL(config.BaseURL, spec) + if err != nil { + return nil, "", err + } + req, err := http.NewRequestWithContext(ctx, method, requestURL, body) + if err != nil { + return nil, "", err + } + if contentType != "" { + req.Header.Set("Content-Type", contentType) + } + for name, value := range spec.Headers { + req.Header.Set(name, value) + } + ApplyOutboundHeaders(req, config.Headers) + if err := applyProtocolAuth(req, config, spec.Auth); err != nil { + return nil, "", err + } + return doBinary(req) +} + +func protocolRequestBody(ctx context.Context, config providerConfig, spec protocol.RequestSpec) (io.Reader, string, error) { + contentType := strings.ToLower(strings.TrimSpace(strings.Split(spec.ContentType, ";")[0])) + if spec.Body == nil && len(spec.Files) == 0 { + return nil, "", nil + } + switch contentType { + case "", "application/json": + data, err := json.Marshal(spec.Body) + if err != nil { + return nil, "", err + } + return bytes.NewReader(data), "application/json", nil + case "application/x-www-form-urlencoded": + values := url.Values{} + for key, value := range protocolBodyObject(spec.Body) { + for _, item := range protocolFormValues(value) { + values.Add(key, item) + } + } + return strings.NewReader(values.Encode()), contentType, nil + case "multipart/form-data": + var body bytes.Buffer + writer := multipart.NewWriter(&body) + keys := make([]string, 0) + for key := range protocolBodyObject(spec.Body) { + keys = append(keys, key) + } + sort.Strings(keys) + for _, key := range keys { + for _, item := range protocolFormValues(protocolBodyObject(spec.Body)[key]) { + if err := writer.WriteField(key, item); err != nil { + _ = writer.Close() + return nil, "", err + } + } + } + for _, file := range spec.Files { + data, detectedMIME, err := protocolMediaBytes(ctx, config, file.Reference) + if err != nil { + _ = writer.Close() + return nil, "", fmt.Errorf("读取 multipart 文件 %s 失败:%w", file.Name, err) + } + filename := safeProtocolFilename(file.Filename) + mimeType := strings.TrimSpace(file.MIMEType) + if mimeType == "" { + mimeType = detectedMIME + } + header := make(textproto.MIMEHeader) + header.Set("Content-Disposition", fmt.Sprintf(`form-data; name=%q; filename=%q`, file.Name, filename)) + header.Set("Content-Type", defaultString(mimeType, "application/octet-stream")) + part, err := writer.CreatePart(header) + if err != nil { + _ = writer.Close() + return nil, "", err + } + if _, err := part.Write(data); err != nil { + _ = writer.Close() + return nil, "", err + } + } + if err := writer.Close(); err != nil { + return nil, "", err + } + return bytes.NewReader(body.Bytes()), writer.FormDataContentType(), nil + case "application/octet-stream": + switch value := spec.Body.(type) { + case []byte: + return bytes.NewReader(value), contentType, nil + case string: + if strings.HasPrefix(value, "data:") { + mimeType, data, err := decodeProviderDataURL(value) + if err != nil { + return nil, "", err + } + return bytes.NewReader(data), defaultString(mimeType, contentType), nil + } + return strings.NewReader(value), contentType, nil + default: + return nil, "", fmt.Errorf("二进制协议请求体必须是字节或字符串") + } + default: + return nil, "", fmt.Errorf("声明式协议暂不支持 %s 请求体", spec.ContentType) + } +} + +func protocolBodyObject(value any) map[string]any { + result, _ := value.(map[string]any) + return result +} + +func protocolFormValues(value any) []string { + switch typed := value.(type) { + case nil: + return nil + case []any: + result := make([]string, 0, len(typed)) + for _, item := range typed { + result = append(result, protocolFormValues(item)...) + } + return result + case string: + return []string{typed} + case bool: + return []string{strconv.FormatBool(typed)} + case float64: + return []string{strconv.FormatFloat(typed, 'f', -1, 64)} + case int: + return []string{strconv.Itoa(typed)} + default: + data, err := json.Marshal(typed) + if err != nil { + return nil + } + return []string{string(data)} + } +} + +func safeProtocolFilename(value string) string { + value = strings.TrimSpace(value) + if index := strings.LastIndexAny(value, `/\\`); index >= 0 { + value = value[index+1:] + } + value = strings.Map(func(r rune) rune { + if r < 32 || r == 127 || r == '"' { + return -1 + } + return r + }, value) + if value == "" { + return "upload.bin" + } + return value +} + +func applyProtocolAuth(req *http.Request, config providerConfig, auth protocol.ManifestAuth) error { + typeName := strings.ToLower(strings.TrimSpace(auth.Type)) + if typeName == "" { + applyProviderAuth(req, config) + return nil + } + credential := protocolCredentialField(config, auth.Field) + switch typeName { + case "none": + return nil + case "bearer": + header := defaultString(strings.TrimSpace(auth.Header), "Authorization") + prefix := auth.Prefix + if prefix == "" { + prefix = "Bearer " + } + req.Header.Set(header, prefix+credential) + return nil + case "header", "api-key", "apikey": + header := strings.TrimSpace(auth.Header) + if header == "" { + return errors.New("插件 header 鉴权缺少 header 名称") + } + req.Header.Set(header, auth.Prefix+credential) + return nil + case "query": + name := defaultString(strings.TrimSpace(auth.Query), strings.TrimSpace(auth.Field)) + if name == "" { + return errors.New("插件 query 鉴权缺少参数名") + } + query := req.URL.Query() + query.Set(name, auth.Prefix+credential) + req.URL.RawQuery = query.Encode() + return nil + case "basic": + username := auth.Username + if username == "" { + username = credential + } + password := protocolCredentialField(config, auth.SecretField) + req.SetBasicAuth(username, password) + return nil + case "anthropic": + req.Header.Set(defaultString(auth.Header, "x-api-key"), credential) + if req.Header.Get("anthropic-version") == "" { + req.Header.Set("anthropic-version", "2023-06-01") + } + return nil + case "google-api-key", "gemini": + req.Header.Set(defaultString(auth.Header, "x-goog-api-key"), credential) + return nil + case "volcengine-v4": + secret := protocolCredentialField(config, auth.SecretField) + if credential == "" || secret == "" { + return errors.New("火山引擎 V4 鉴权需要 Access Key 和 Secret Key") + } + credentials := base.Credentials{ + AccessKeyID: credential, SecretAccessKey: secret, + Region: defaultString(strings.TrimSpace(auth.Region), "cn-north-1"), + Service: strings.TrimSpace(auth.Service), + } + if credentials.Service == "" { + return errors.New("火山引擎 V4 鉴权缺少 service") + } + signed := credentials.Sign(req) + *req = *signed + return nil + case "aws-sigv4": + secret := protocolCredentialField(config, auth.SecretField) + return signProtocolAWSV4(req, credential, secret, auth) + case "tc3": + secret := protocolCredentialField(config, auth.SecretField) + return signProtocolTC3(req, credential, secret, auth) + default: + return fmt.Errorf("插件声明了尚未启用的鉴权驱动 %s", auth.Type) + } +} + +func signProtocolAWSV4(req *http.Request, accessKey, secretKey string, auth protocol.ManifestAuth) error { + if strings.TrimSpace(accessKey) == "" || strings.TrimSpace(secretKey) == "" { + return errors.New("AWS SigV4 鉴权需要 Access Key ID 和 Secret Access Key") + } + serviceName := defaultString(strings.TrimSpace(auth.Service), "bedrock") + region := strings.TrimSpace(auth.Region) + if region == "" { + parts := strings.Split(strings.ToLower(req.URL.Hostname()), ".") + for index, part := range parts { + if strings.HasPrefix(part, serviceName) && index+1 < len(parts) { + region = parts[index+1] + break + } + } + } + if region == "" { + return errors.New("AWS SigV4 鉴权无法从 Base URL 推断 region,请使用包含区域的 Bedrock Runtime 地址") + } + payload, err := protocolRequestPayload(req) + if err != nil { + return err + } + now := time.Now().UTC() + amzDate := now.Format("20060102T150405Z") + dateStamp := now.Format("20060102") + payloadHash := sha256Hex(payload) + req.Header.Set("X-Amz-Date", amzDate) + req.Header.Set("X-Amz-Content-Sha256", payloadHash) + canonicalHeaders, signedHeaders := protocolCanonicalHeaders(req) + canonicalRequest := strings.Join([]string{ + req.Method, + defaultString(req.URL.EscapedPath(), "/"), + req.URL.Query().Encode(), + canonicalHeaders, + signedHeaders, + payloadHash, + }, "\n") + scope := strings.Join([]string{dateStamp, region, serviceName, "aws4_request"}, "/") + stringToSign := strings.Join([]string{"AWS4-HMAC-SHA256", amzDate, scope, sha256Hex([]byte(canonicalRequest))}, "\n") + dateKey := protocolHMAC([]byte("AWS4"+secretKey), dateStamp) + regionKey := protocolHMAC(dateKey, region) + serviceKey := protocolHMAC(regionKey, serviceName) + signingKey := protocolHMAC(serviceKey, "aws4_request") + signature := hex.EncodeToString(protocolHMAC(signingKey, stringToSign)) + req.Header.Set("Authorization", fmt.Sprintf("AWS4-HMAC-SHA256 Credential=%s/%s, SignedHeaders=%s, Signature=%s", accessKey, scope, signedHeaders, signature)) + return nil +} + +func signProtocolTC3(req *http.Request, secretID, secretKey string, auth protocol.ManifestAuth) error { + if strings.TrimSpace(secretID) == "" || strings.TrimSpace(secretKey) == "" { + return errors.New("腾讯云 TC3 鉴权需要 SecretId 和 SecretKey") + } + serviceName := defaultString(strings.TrimSpace(auth.Service), "hunyuan") + payload, err := protocolRequestPayload(req) + if err != nil { + return err + } + now := time.Now().UTC() + timestamp := now.Unix() + dateStamp := now.Format("2006-01-02") + contentType := defaultString(req.Header.Get("Content-Type"), "application/json") + req.Header.Set("Content-Type", contentType) + req.Header.Set("X-TC-Timestamp", strconv.FormatInt(timestamp, 10)) + if region := strings.TrimSpace(auth.Region); region != "" { + req.Header.Set("X-TC-Region", region) + } + canonicalHeaders := "content-type:" + strings.ToLower(strings.TrimSpace(contentType)) + "\n" + "host:" + strings.ToLower(req.URL.Host) + "\n" + signedHeaders := "content-type;host" + canonicalRequest := strings.Join([]string{req.Method, defaultString(req.URL.EscapedPath(), "/"), req.URL.Query().Encode(), canonicalHeaders, signedHeaders, sha256Hex(payload)}, "\n") + scope := dateStamp + "/" + serviceName + "/tc3_request" + stringToSign := strings.Join([]string{"TC3-HMAC-SHA256", strconv.FormatInt(timestamp, 10), scope, sha256Hex([]byte(canonicalRequest))}, "\n") + secretDate := protocolHMAC([]byte("TC3"+secretKey), dateStamp) + secretService := protocolHMAC(secretDate, serviceName) + secretSigning := protocolHMAC(secretService, "tc3_request") + signature := hex.EncodeToString(protocolHMAC(secretSigning, stringToSign)) + req.Header.Set("Authorization", fmt.Sprintf("TC3-HMAC-SHA256 Credential=%s/%s, SignedHeaders=%s, Signature=%s", secretID, scope, signedHeaders, signature)) + return nil +} + +func protocolRequestPayload(req *http.Request) ([]byte, error) { + if req.Body == nil { + return nil, nil + } + var reader io.ReadCloser + var err error + if req.GetBody != nil { + reader, err = req.GetBody() + } else { + reader = req.Body + } + if err != nil { + return nil, err + } + data, err := io.ReadAll(reader) + if req.GetBody != nil { + _ = reader.Close() + } else { + req.Body = io.NopCloser(bytes.NewReader(data)) + } + return data, err +} + +func protocolCanonicalHeaders(req *http.Request) (string, string) { + values := map[string]string{"host": strings.ToLower(req.URL.Host)} + for name, entries := range req.Header { + lower := strings.ToLower(strings.TrimSpace(name)) + if lower == "authorization" || lower == "user-agent" || lower == "content-length" || lower == "expect" { + continue + } + cleaned := make([]string, 0, len(entries)) + for _, entry := range entries { + cleaned = append(cleaned, strings.Join(strings.Fields(entry), " ")) + } + values[lower] = strings.Join(cleaned, ",") + } + keys := make([]string, 0, len(values)) + for key := range values { + keys = append(keys, key) + } + sort.Strings(keys) + var canonical strings.Builder + for _, key := range keys { + canonical.WriteString(key) + canonical.WriteByte(':') + canonical.WriteString(values[key]) + canonical.WriteByte('\n') + } + return canonical.String(), strings.Join(keys, ";") +} + +func protocolHMAC(key []byte, value string) []byte { + mac := hmac.New(sha256.New, key) + _, _ = mac.Write([]byte(value)) + return mac.Sum(nil) +} + +func sha256Hex(value []byte) string { + hash := sha256.Sum256(value) + return hex.EncodeToString(hash[:]) +} + +func protocolCredentialField(config providerConfig, field string) string { + switch strings.ToLower(strings.TrimSpace(field)) { + case "secretkey", "secret_key", "secret": + return strings.TrimSpace(config.SecretKey) + default: + return strings.TrimSpace(config.APIKey) + } +} + +func protocolRequestURL(baseURL string, spec protocol.RequestSpec) (string, error) { + if !spec.OriginPath { + return appendProtocolQuery(apiURL(baseURL, spec.Path), spec.Query) + } + base, err := url.Parse(strings.TrimSpace(baseURL)) + if err != nil || base.Scheme == "" || base.Host == "" { + return "", fmt.Errorf("协议根路径请求的 Base URL 无效") + } + requestPath, err := url.Parse(spec.Path) + if err != nil || !strings.HasPrefix(requestPath.Path, "/") { + return "", fmt.Errorf("协议根路径请求必须使用绝对路径") + } + base.Path = requestPath.Path + base.RawPath = requestPath.RawPath + base.RawQuery = requestPath.RawQuery + base.Fragment = "" + return appendProtocolQuery(base.String(), spec.Query) +} + +func appendProtocolQuery(rawURL string, values map[string][]string) (string, error) { + if len(values) == 0 { + return rawURL, nil + } + parsed, err := url.Parse(rawURL) + if err != nil { + return "", err + } + query := parsed.Query() + for key, items := range values { + for _, item := range items { + query.Add(key, item) + } + } + parsed.RawQuery = query.Encode() + return parsed.String(), nil +} + +func finishProtocolResult(ctx context.Context, config providerConfig, mode string, result *protocol.Result) (map[string]interface{}, error) { + if result == nil { + return nil, errors.New("声明式协议已完成但没有返回结果") + } + if mode == "text" { + output := map[string]interface{}{"mode": "text", "text": result.Text} + if strings.TrimSpace(result.Reasoning) != "" { + output["reasoning"] = result.Reasoning + } + return output, nil + } + var references []protocol.MediaReference + switch mode { + case "image": + references = result.Images + case "video": + references = result.Videos + case "audio": + references = result.Audios + default: + return nil, fmt.Errorf("声明式协议不支持生成模式 %s", mode) + } + if len(references) == 0 { + return nil, errors.New("声明式协议已完成但没有返回媒体地址") + } + items := make([]interface{}, 0, len(references)) + for _, reference := range references { + data, mimeType, err := protocolMediaBytes(ctx, config, reference) + if err != nil { + return nil, err + } + item := map[string]interface{}{"dataUrl": dataURL(mimeType, data), "mimeType": mimeType} + items = append(items, item) + } + switch mode { + case "image": + return map[string]interface{}{"mode": "image", "images": items}, nil + case "video": + return map[string]interface{}{"mode": "video", "video": items[0]}, nil + default: + return map[string]interface{}{"mode": "audio", "audio": items[0]}, nil + } +} + +// finishProtocolAdapterResult 优先消费 create/poll 已返回的内联或 URL 结果,只有插件明确声明独立结果端点时才下载。 +// 空响应或下载失败必须向上失败,不能生成伪素材;application/octet-stream 仅表示传输层未知类型, +// 不会把未知内容伪装成具体图片、视频或音频 MIME。 +func finishProtocolAdapterResult(ctx context.Context, input canvasGenerationInput, adapter protocol.Adapter, request protocol.GenerationRequest, taskID string, result *protocol.Result) (map[string]interface{}, error) { + if protocolResultHasOutput(input.Mode, result) { + return finishProtocolResult(ctx, input.Config, input.Mode, result) + } + resultAdapter, ok := adapter.(protocol.ResultAdapter) + capability, hasCapability := adapter.(protocol.ResultCapability) + if !ok || !hasCapability || !capability.ResultAvailable() { + return finishProtocolResult(ctx, input.Config, input.Mode, result) + } + spec, err := resultAdapter.BuildResult(ctx, protocol.PollContext{BaseURL: input.Config.BaseURL, Model: request.Model, Request: request, TaskID: taskID}) + if err != nil { + return nil, err + } + data, mimeType, err := executeProtocolBinaryRequest(withProviderRequestKind(ctx, "download"), input.Config, spec) + if err != nil { + return nil, fmt.Errorf("声明式协议结果下载失败:%w", err) + } + if len(data) == 0 { + return nil, errors.New("声明式协议结果下载返回空内容") + } + mimeType = strings.TrimSpace(strings.Split(mimeType, ";")[0]) + if mimeType == "" { + mimeType = "application/octet-stream" + } + reference := protocol.MediaReference{DataURL: dataURL(mimeType, data), MIMEType: mimeType} + downloaded := &protocol.Result{} + switch input.Mode { + case "image": + downloaded.Images = []protocol.MediaReference{reference} + case "video": + downloaded.Videos = []protocol.MediaReference{reference} + case "audio": + downloaded.Audios = []protocol.MediaReference{reference} + default: + return nil, fmt.Errorf("声明式协议结果下载不支持生成模式 %s", input.Mode) + } + return finishProtocolResult(ctx, input.Config, input.Mode, downloaded) +} + +func protocolResultHasOutput(mode string, result *protocol.Result) bool { + if result == nil { + return false + } + switch mode { + case "text": + return strings.TrimSpace(result.Text) != "" + case "image": + return len(result.Images) > 0 + case "video": + return len(result.Videos) > 0 + case "audio": + return len(result.Audios) > 0 + default: + return false + } +} + +func protocolMediaBytes(ctx context.Context, config providerConfig, reference protocol.MediaReference) ([]byte, string, error) { + if strings.TrimSpace(reference.DataURL) != "" { + mimeType, data, err := decodeProviderDataURL(reference.DataURL) + return data, mimeType, err + } + value := strings.TrimSpace(reference.URL) + if value == "" { + return nil, "", errors.New("声明式协议媒体结果地址为空") + } + var data []byte + var mimeType string + var err error + for attempt := 0; attempt < 3; attempt++ { + data, mimeType, err = getProviderExternalBinary(withProviderRequestKind(ctx, "download"), config, value) + if err == nil { + return data, normalizedMediaMimeType(mimeType, data), nil + } + if attempt == 2 || !retryableProtocolMediaDownload(err) { + break + } + if waitErr := sleepContext(ctx, time.Duration(attempt+1)*time.Second); waitErr != nil { + return nil, "", fmt.Errorf("声明式协议媒体结果下载失败:%w", waitErr) + } + } + return nil, "", fmt.Errorf("声明式协议媒体结果下载失败:%w", err) +} + +func retryableProtocolMediaDownload(err error) bool { + if err == nil || errors.Is(err, context.Canceled) || errors.Is(err, context.DeadlineExceeded) { + return false + } + var networkError net.Error + if errors.As(err, &networkError) && (networkError.Timeout() || networkError.Temporary()) { + return true + } + message := strings.ToLower(err.Error()) + for _, marker := range []string{"tls handshake timeout", "connection reset", "unexpected eof", "broken pipe"} { + if strings.Contains(message, marker) { + return true + } + } + return false +} + +func protocolResultError(message, taskID string) error { + message = strings.TrimSpace(message) + if message == "" { + message = "上游返回失败状态" + } + if taskID == "" { + return errors.New(message) + } + return fmt.Errorf("声明式协议任务失败(任务 %s):%s", taskID, message) +} + +func validateGenerationInterface(mode string, interfaceType string) error { + return validateGenerationInterfaceWithRegistry(protocol.Builtins(), mode, interfaceType) +} + +func (s *Service) validateGenerationInterface(mode string, interfaceType string) error { + return validateGenerationInterfaceWithRegistry(s.protocolRegistry(), mode, interfaceType) +} + +func validateGenerationInterfaceWithRegistry(registry *protocol.Registry, mode string, interfaceType string) error { + interfaceType = strings.TrimSpace(interfaceType) + if interfaceType == "" { + return nil + } + adapter, ok := registry.Resolve(interfaceType) + if !ok { + return fmt.Errorf("接口类型 %s 未安装", interfaceType) + } + metadata := adapter.Metadata() + if !metadata.Enabled || metadata.UnavailableReason != "" { + return fmt.Errorf("接口类型 %s 当前不可用:%s", interfaceType, metadata.UnavailableReason) + } + if mode != "" && !protocolCapabilityMatches(metadata, protocol.Capability(mode)) { + return fmt.Errorf("接口类型 %s 不支持%s生成", interfaceType, mode) + } + return nil +} + +func protocolCapabilityMatches(metadata protocol.Metadata, capability protocol.Capability) bool { + for _, item := range metadata.Categories { + if item == capability { + return true + } + } + return false +} diff --git a/backend/internal/service/provider_request_types.go b/backend/internal/service/provider_request_types.go index 0ddeae35c..2befb7ccd 100644 --- a/backend/internal/service/provider_request_types.go +++ b/backend/internal/service/provider_request_types.go @@ -14,18 +14,6 @@ func requestAsMap(value interface{}) (map[string]interface{}, error) { return result, nil } -type newAPIVideoRequest struct { - Model string `json:"model"` - Prompt string `json:"prompt"` - Seconds string `json:"seconds"` - AspectRatio string `json:"aspect_ratio"` - Resolution string `json:"resolution,omitempty"` - GenerateAudio *bool `json:"generate_audio,omitempty"` - ImageURLs []string `json:"image_urls,omitempty"` - VideoURLs []string `json:"video_urls,omitempty"` - AudioURLs []string `json:"audio_urls,omitempty"` -} - type seedanceVideosRequest struct { Model string `json:"model"` Prompt string `json:"prompt"` @@ -39,20 +27,6 @@ type seedanceVideosRequest struct { ReferenceAudios []string `json:"reference_audios,omitempty"` } -type xaiVideoRequest struct { - Model string `json:"model"` - Prompt string `json:"prompt"` - Duration int `json:"duration"` - AspectRatio string `json:"aspect_ratio"` - Resolution string `json:"resolution"` - Image *xaiVideoImage `json:"image,omitempty"` - ReferenceImages []xaiVideoImage `json:"reference_images,omitempty"` -} - -type xaiVideoImage struct { - URL string `json:"url"` -} - type grokImageRequest struct { Model string `json:"model"` Prompt string `json:"prompt"` @@ -68,28 +42,6 @@ type grokImageInput struct { URL string `json:"url"` } -type geminiVeoRequest struct { - Instances []geminiVeoInstance `json:"instances"` - Parameters geminiVeoParameters `json:"parameters"` -} - -type geminiVeoInstance struct { - Prompt string `json:"prompt"` - Image *geminiVeoImage `json:"image,omitempty"` -} - -type geminiVeoImage struct { - BytesBase64Encoded string `json:"bytesBase64Encoded"` - MIMEType string `json:"mimeType"` -} - -type geminiVeoParameters struct { - AspectRatio string `json:"aspectRatio"` - DurationSeconds int `json:"durationSeconds"` - Resolution string `json:"resolution"` - SampleCount int `json:"sampleCount"` -} - type geminiImageRequest struct { Contents []geminiImageContent `json:"contents"` SystemInstruction *geminiImageContent `json:"systemInstruction,omitempty"` @@ -130,25 +82,3 @@ type seedanceAgentPlanRequest struct { GenerateAudio *bool `json:"generate_audio,omitempty"` Watermark *bool `json:"watermark,omitempty"` } - -type miniMaxVideoRequest struct { - Model string `json:"model"` - Content []miniMaxVideoContent `json:"content"` - Resolution string `json:"resolution"` - Duration int `json:"duration"` - Ratio string `json:"ratio,omitempty"` - AIGCWatermark *bool `json:"aigc_watermark,omitempty"` -} - -type miniMaxVideoContent struct { - Type string `json:"type"` - Text string `json:"text,omitempty"` - ImageURL *miniMaxMediaURL `json:"image_url,omitempty"` - VideoURL *miniMaxMediaURL `json:"video_url,omitempty"` - AudioURL *miniMaxMediaURL `json:"audio_url,omitempty"` - Role string `json:"role,omitempty"` -} - -type miniMaxMediaURL struct { - URL string `json:"url"` -} diff --git a/backend/internal/service/provider_resource_helpers.go b/backend/internal/service/provider_resource_helpers.go index b3b0ab955..be33ceb88 100644 --- a/backend/internal/service/provider_resource_helpers.go +++ b/backend/internal/service/provider_resource_helpers.go @@ -7,7 +7,7 @@ import ( "infinite-canvas/backend/internal/model" ) -// ResourceForUser is the service-layer ownership check used by provider workers. +// ResourceForUser 是 Provider worker 读取资源时必须经过的 service 层归属校验。 func (s *Service) ResourceForUser(actor *model.User, id string) (*model.Resource, error) { if actor == nil || actor.ID == "" { return nil, errors.New("用户身份无效") @@ -15,8 +15,8 @@ func (s *Service) ResourceForUser(actor *model.User, id string) (*model.Resource return s.repo.ResourceForUser(actor.ID, id) } -// providerResourceURL gives an upstream a short-lived URL after the resource -// ownership and ready-state checks have already been performed. +// providerResourceURL 只能接收已经完成用户归属和 ready 状态校验的资源, +// 并为上游签发短时地址;它不是绕过权限校验的通用资源 URL 生成器。 func (s *Service) providerResourceURL(resource *model.Resource, expiresAt time.Time) (string, error) { return s.directResourceURL(resource, expiresAt) } diff --git a/backend/internal/service/provider_task_recovery.go b/backend/internal/service/provider_task_recovery.go index a6a6512ef..33c04f3e7 100644 --- a/backend/internal/service/provider_task_recovery.go +++ b/backend/internal/service/provider_task_recovery.go @@ -104,8 +104,9 @@ func (s *Service) queryFailedVideoTask(ctx context.Context, task *model.Task, cl if err != nil { return nil, err } + ctx = ensureOfficialProtocolAdapter(ctx, config.InterfaceType) adapter, declarative := declarativeProtocolAdapterForContext(ctx, config.InterfaceType) - if !declarative && config.InterfaceType != string(model.ChannelInterfaceNewAPIChannel2) { + if !declarative { return nil, BadAuthRequest("该任务的请求协议不支持安全查询上游状态") } input.Config = config @@ -138,11 +139,7 @@ func (s *Service) queryFailedVideoTask(ctx context.Context, task *model.Task, cl queryCtx = withProviderOutboundPolicy(queryCtx, input.Config) var result map[string]interface{} var providerStatus string - if declarative { - result, providerStatus, err = queryProtocolAdapterVideoTask(queryCtx, input, adapter, providerRequestID) - } else { - result, providerStatus, err = queryNewAPIChannel2VideoTask(queryCtx, input, providerRequestID) - } + result, providerStatus, err = queryProtocolAdapterVideoTask(queryCtx, input, adapter, providerRequestID) if err != nil { _ = s.log(task.UserID, task.ID, "error", "人工查询上游视频任务失败", err.Error()) return nil, err diff --git a/backend/internal/service/provider_test.go b/backend/internal/service/provider_test.go index 5b2223178..c4ff044f9 100644 --- a/backend/internal/service/provider_test.go +++ b/backend/internal/service/provider_test.go @@ -23,6 +23,27 @@ import ( const testReferenceImageDataURL = "data:image/png;base64,aGVsbG8=" const testGeminiReferenceImageDataURL = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mNk+A8AAQUBAScY42YAAAAASUVORK5CYII=" +func officialVideoCreateBody(t *testing.T, input canvasGenerationInput) map[string]any { + t.Helper() + adapter, ok := loadOfficialFallbackRegistry().Resolve(strings.TrimSpace(input.Config.InterfaceType)) + if !ok { + t.Fatalf("official adapter %s missing", input.Config.InterfaceType) + } + spec, err := adapter.BuildCreate(context.Background(), protocol.RequestContext{Request: protocolRequestFromInput(input)}) + if err != nil { + t.Fatal(err) + } + data, err := json.Marshal(spec.Body) + if err != nil { + t.Fatal(err) + } + var body map[string]any + if err := json.Unmarshal(data, &body); err != nil { + t.Fatal(err) + } + return body +} + func TestProviderMediaHydrationPolicyPrefersObjectURLs(t *testing.T) { prefer := providerMediaHydrationPolicyFor(context.Background(), canvasGenerationInput{Config: providerConfig{InterfaceType: string(model.ChannelInterfaceGrokImage)}}) if prefer.requireURL || !prefer.preferURL { @@ -72,6 +93,15 @@ func TestProviderRequestErrorDetails(t *testing.T) { } } +func TestPostJSONRejectsUnserializableBodyBeforeRequest(t *testing.T) { + err := postJSON(context.Background(), providerConfig{BaseURL: "http://127.0.0.1:1"}, "/should-not-run", map[string]any{ + "invalid": make(chan int), + }, &map[string]any{}) + if err == nil || !strings.Contains(err.Error(), "序列化上游请求失败") { + t.Fatalf("postJSON() error = %v, want serialization error", err) + } +} + func TestChannelAPIURLNormalizesConfiguredVersionPrefix(t *testing.T) { tests := []struct { name string @@ -627,6 +657,22 @@ func TestProviderHTTPErrorDoesNotExposeResponseBody(t *testing.T) { } } +func TestShouldFallbackTextToChatOnlyForMissingCapability(t *testing.T) { + for _, status := range []int{http.StatusNotFound, http.StatusMethodNotAllowed, http.StatusNotImplemented} { + if !shouldFallbackTextToChat(providerHTTPError{StatusCode: status}) { + t.Errorf("status %d should fallback to chat completions", status) + } + } + for _, status := range []int{http.StatusBadGateway, http.StatusServiceUnavailable, http.StatusGatewayTimeout, http.StatusInternalServerError, http.StatusTooManyRequests} { + if shouldFallbackTextToChat(providerHTTPError{StatusCode: status}) { + t.Errorf("status %d should not fallback to chat completions", status) + } + } + if shouldFallbackTextToChat(errors.New("network")) { + t.Fatal("non-http error should not fallback") + } +} + func TestProviderPayloadErrorMessageUsesSafeActionableCategories(t *testing.T) { tests := []struct { name string @@ -822,19 +868,14 @@ func TestProviderPayloadErrorCategoryPrefersProviderCodeOverModerationWording(t } } -func TestNormalizeNewAPIChannel2ResolutionPreservesDeclaredTiers(t *testing.T) { - tests := map[string]string{ - "1440": "1440p", - "2k": "1440p", - "4K": "2160p", - "768p": "768p", - } - for input, want := range tests { - t.Run(input, func(t *testing.T) { - if got := normalizeNewAPIChannel2Resolution(input, "custom-video-model"); got != want { - t.Fatalf("normalizeNewAPIChannel2Resolution(%q) = %q, want %q", input, got, want) - } - }) +func TestRunVideoTaskRequiresOfficialPluginWhenRegistryEmpty(t *testing.T) { + ctx := withProtocolRegistry(context.Background(), emptyProtocolRegistry) + _, err := runVideoTask(ctx, canvasGenerationInput{ + Prompt: "make it move", + Config: providerConfig{BaseURL: "https://example.com", APIKey: "key", Model: "MiniMax-H3", InterfaceType: "minimax-video"}, + }) + if err == nil || !strings.Contains(err.Error(), "插件未安装") { + t.Fatalf("runVideoTask() error = %v, want plugin missing", err) } } @@ -1724,7 +1765,7 @@ func TestRunVideoTaskUsesXAIVideoGenerationEndpoint(t *testing.T) { _, _ = w.Write([]byte(`{"request_id":"video-1"}`)) case "GET /v1/videos/video-1": w.Header().Set("Content-Type", "application/json") - _, _ = w.Write([]byte(`{"status":"done","video":{"url":"` + server.URL + `/files/video.mp4"}}`)) + _, _ = w.Write([]byte(`{"status":"done","url":"` + server.URL + `/files/video.mp4"}`)) case "GET /files/video.mp4": if authorization := r.Header.Get("Authorization"); authorization != "Bearer test-key" { t.Errorf("file Authorization = %q, want Bearer test-key", authorization) @@ -1746,7 +1787,7 @@ func TestRunVideoTaskUsesXAIVideoGenerationEndpoint(t *testing.T) { InterfaceType: "xai-video", VideoSeconds: "10", Size: "1:1", - VQuality: "720", + VQuality: "720p", }, }) if err != nil { @@ -1762,32 +1803,24 @@ func TestRunVideoTaskUsesXAIVideoGenerationEndpoint(t *testing.T) { } } -func TestRunVideoTaskXAIVideoUsesContentEndpointForLoopbackResultURL(t *testing.T) { +func TestRunVideoTaskXAIVideoRejectsUnreachableResultURL(t *testing.T) { t.Setenv("CANVAS_ALLOWED_PRIVATE_UPSTREAM_HOSTS", "127.0.0.1") - paths := make([]string, 0, 3) var server *httptest.Server server = httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - paths = append(paths, r.Method+" "+r.URL.Path) switch r.Method + " " + r.URL.Path { case "POST /v1/videos/generations": w.Header().Set("Content-Type", "application/json") _, _ = w.Write([]byte(`{"request_id":"video-loopback"}`)) case "GET /v1/videos/video-loopback": w.Header().Set("Content-Type", "application/json") - _, _ = w.Write([]byte(`{"status":"done","video":{"url":"http://127.0.0.2:1/unreachable.mp4"}}`)) - case "GET /v1/videos/video-loopback/content": - if authorization := r.Header.Get("Authorization"); authorization != "Bearer test-key" { - t.Errorf("content Authorization = %q, want Bearer test-key", authorization) - } - w.Header().Set("Content-Type", "video/mp4") - _, _ = w.Write([]byte("video")) + _, _ = w.Write([]byte(`{"status":"done","url":"http://127.0.0.2:1/unreachable.mp4"}`)) default: http.NotFound(w, r) } })) defer server.Close() - result, err := runVideoTask(context.Background(), canvasGenerationInput{ + _, err := runVideoTask(context.Background(), canvasGenerationInput{ Prompt: "make it move", Config: providerConfig{ BaseURL: server.URL + "/v1", @@ -1796,28 +1829,21 @@ func TestRunVideoTaskXAIVideoUsesContentEndpointForLoopbackResultURL(t *testing. InterfaceType: "xai-video", }, }) - if err != nil { - t.Fatalf("runVideoTask() error = %v", err) - } - video, ok := result["video"].(map[string]interface{}) - if !ok || video["dataUrl"] != "data:video/mp4;base64,dmlkZW8=" { - t.Fatalf("video = %#v", result["video"]) - } - want := "POST /v1/videos/generations,GET /v1/videos/video-loopback,GET /v1/videos/video-loopback/content" - if got := strings.Join(paths, ","); got != want { - t.Fatalf("paths = %q, want %q", got, want) + if err == nil { + t.Fatal("runVideoTask() error = nil, want unreachable result URL to fail") } } -func TestXAIVideoBodyWithoutStartFramePutsAllImagesIntoReferenceImages(t *testing.T) { - body, err := xaiVideoRequestBody(canvasGenerationInput{ +func TestXAIVideoPluginMapsUnmarkedImagesToStartFrame(t *testing.T) { + body := officialVideoCreateBody(t, canvasGenerationInput{ + Mode: "video", Prompt: "make it move", Config: providerConfig{ Model: "grok-imagine-video-1.5", InterfaceType: "xai-video", VideoSeconds: "20", - Size: "1024x1792", - VQuality: "1080", + Size: "9:16", + VQuality: "1080p", }, ReferenceImages: []providerMedia{ {ID: "image-1", DataURL: testReferenceImageDataURL}, @@ -1825,79 +1851,48 @@ func TestXAIVideoBodyWithoutStartFramePutsAllImagesIntoReferenceImages(t *testin }, Metadata: map[string]interface{}{"videoEditOperation": "image_to_video"}, }) - if err != nil { - t.Fatalf("xaiVideoRequestBody() error = %v", err) - } - if body.Duration != 20 || body.AspectRatio != "9:16" || body.Resolution != "1080p" { + if body["duration"] != float64(20) || body["aspect_ratio"] != "9:16" || body["resolution"] != "1080p" { t.Fatalf("xAI settings = %#v", body) } - if body.Image != nil { - t.Fatalf("image should be nil without start frame, got %#v", body.Image) - } - if len(body.ReferenceImages) != 2 || body.ReferenceImages[0].URL != testReferenceImageDataURL || body.ReferenceImages[1].URL != testReferenceImageDataURL { - t.Fatalf("reference_images = %#v", body.ReferenceImages) + image, _ := body["image"].(map[string]any) + if image["url"] != testReferenceImageDataURL { + t.Fatalf("image = %#v", body["image"]) } } -func TestXAIVideoBodyWithStartFrameKeepsOfficialImageShape(t *testing.T) { - body, err := xaiVideoRequestBody(canvasGenerationInput{ +func TestXAIVideoPluginKeepsStartFrameAsImage(t *testing.T) { + body := officialVideoCreateBody(t, canvasGenerationInput{ + Mode: "video", Config: providerConfig{Model: "grok-imagine-video-1.5", InterfaceType: "xai-video"}, ReferenceImages: []providerMedia{ {ID: "image-1", DataURL: testReferenceImageDataURL}, }, Metadata: map[string]interface{}{"videoEditOperation": "image_to_video", "videoStartFrameNodeId": "image-1"}, }) - if err != nil { - t.Fatalf("xaiVideoRequestBody() error = %v", err) + image, _ := body["image"].(map[string]any) + if image["url"] != testReferenceImageDataURL { + t.Fatalf("image = %#v", body["image"]) } - if body.Image == nil || body.Image.URL != testReferenceImageDataURL { - t.Fatalf("image = %#v", body.Image) - } - if len(body.ReferenceImages) != 0 { - t.Fatalf("reference_images = %#v", body.ReferenceImages) + if _, exists := body["reference_images"]; exists { + t.Fatalf("reference_images = %#v", body["reference_images"]) } } -func TestXAIVideoReferenceOperationIgnoresStaleStartFrameMetadata(t *testing.T) { - body, err := xaiVideoRequestBody(canvasGenerationInput{ +func TestXAIVideoPluginUsesReferenceImagesForReferenceOperation(t *testing.T) { + body := officialVideoCreateBody(t, canvasGenerationInput{ + Mode: "video", Config: providerConfig{Model: "grok-imagine-video-1.5", InterfaceType: "xai-video"}, ReferenceImages: []providerMedia{ {ID: "character", DataURL: testReferenceImageDataURL}, }, Metadata: map[string]interface{}{"videoEditOperation": "reference_to_video", "videoStartFrameNodeId": "character"}, }) - if err != nil { - t.Fatalf("xaiVideoRequestBody() error = %v", err) + if body["image"] != nil { + t.Fatalf("image = %#v", body["image"]) } - if body.Image != nil || len(body.ReferenceImages) != 1 { - t.Fatalf("xAI reference operation body = %#v", body) - } -} - -func TestXAIVideoBodyWithStartFrameRejectsMultipleImages(t *testing.T) { - _, err := xaiVideoRequestBody(canvasGenerationInput{ - Config: providerConfig{Model: "grok-imagine-video-1.5", InterfaceType: "xai-video"}, - ReferenceImages: []providerMedia{ - {ID: "image-1", DataURL: testReferenceImageDataURL}, - {ID: "image-2", DataURL: testReferenceImageDataURL}, - }, - Metadata: map[string]interface{}{"videoEditOperation": "image_to_video", "videoStartFrameNodeId": "image-1"}, - }) - if err == nil || !strings.Contains(err.Error(), "只支持 1 张起始图") { - t.Fatalf("xaiVideoRequestBody() error = %v", err) - } -} - -func TestXAIVideoBodyWithMissingStartFrameImageErrors(t *testing.T) { - _, err := xaiVideoRequestBody(canvasGenerationInput{ - Config: providerConfig{Model: "grok-imagine-video-1.5", InterfaceType: "xai-video"}, - ReferenceImages: []providerMedia{ - {ID: "image-2", DataURL: testReferenceImageDataURL}, - }, - Metadata: map[string]interface{}{"videoEditOperation": "image_to_video", "videoStartFrameNodeId": "image-1"}, - }) - if err == nil || !strings.Contains(err.Error(), "首帧参考图未包含") { - t.Fatalf("xaiVideoRequestBody() error = %v", err) + refs, _ := body["reference_images"].([]any) + if len(refs) != 1 { + t.Fatalf("reference_images = %#v", body["reference_images"]) } } @@ -1982,7 +1977,7 @@ func TestVolcengineArkVideoProtocolUsesContentTaskAndDownloadsResult(t *testing. return } wantTypes := []string{"text", "image_url", "video_url", "audio_url"} - wantRoles := []string{"", "reference_image", "reference_video", "reference_audio"} + wantRoles := []string{"", "first_frame", "reference_video", "reference_audio"} for index, item := range content { entry, _ := item.(map[string]interface{}) if entry["type"] != wantTypes[index] || (wantRoles[index] != "" && entry["role"] != wantRoles[index]) { @@ -2030,9 +2025,10 @@ func TestNewAPIChannel1VideoBodyMapsFramesAndReferences(t *testing.T) { server := httptest.NewServer(http.HandlerFunc(func(http.ResponseWriter, *http.Request) {})) defer server.Close() - body, err := newAPIChannel1VideoBody(canvasGenerationInput{ + body := officialVideoCreateBody(t, canvasGenerationInput{ + Mode: "video", Prompt: "make it move", - Config: providerConfig{Model: "seedance-2.0", Size: "9:16", VQuality: "1080", VideoSeconds: "15", VideoWatermark: "true"}, + Config: providerConfig{Model: "seedance-2.0", InterfaceType: "newapi-channel-1", Size: "9:16", VQuality: "1080P", VideoSeconds: "15", VideoWatermark: "true"}, ReferenceImages: []providerMedia{ {ID: "first", URL: server.URL + "/first.png"}, {ID: "last", URL: server.URL + "/last.png"}, @@ -2042,22 +2038,20 @@ func TestNewAPIChannel1VideoBodyMapsFramesAndReferences(t *testing.T) { ReferenceAudios: []providerMedia{{ID: "voice", URL: server.URL + "/voice.mp3"}}, Metadata: map[string]interface{}{"videoStartFrameNodeId": "first", "videoEndFrameNodeId": "last"}, }) - if err != nil { - t.Fatalf("newAPIChannel1VideoBody() error = %v", err) - } - input := body["input"].(map[string]interface{}) - media := input["media"].([]map[string]string) - wantTypes := []string{"first_frame", "last_frame", "reference_image", "reference_video", "reference_voice"} + input := body["input"].(map[string]any) + media, _ := input["media"].([]any) + wantTypes := []string{"first_frame", "last_frame", "reference_image", "reference_video", "reference_audio"} if len(media) != len(wantTypes) { t.Fatalf("media = %#v", media) } for index, want := range wantTypes { - if media[index]["type"] != want { - t.Fatalf("media[%d].type = %q, want %q", index, media[index]["type"], want) + item, _ := media[index].(map[string]any) + if item["type"] != want { + t.Fatalf("media[%d].type = %q, want %q", index, item["type"], want) } } - parameters := body["parameters"].(map[string]interface{}) - if parameters["resolution"] != "1080P" || parameters["ratio"] != "9:16" || parameters["duration"] != 15 || parameters["watermark"] != true { + parameters := body["parameters"].(map[string]any) + if parameters["resolution"] != "1080P" || parameters["ratio"] != "9:16" || parameters["duration"] != float64(15) || parameters["watermark"] != true { t.Fatalf("parameters = %#v", parameters) } } @@ -2151,14 +2145,21 @@ func TestVolcengineArkDeclarativeRequestUsesResolutionSuffix(t *testing.T) { } } -func TestNewAPIChannel1VideoBodyRejectsInlineMedia(t *testing.T) { - _, err := newAPIChannel1VideoBody(canvasGenerationInput{ +func TestNewAPIChannel1VideoPluginKeepsInlineDataURL(t *testing.T) { + body := officialVideoCreateBody(t, canvasGenerationInput{ + Mode: "video", Prompt: "make it move", - Config: providerConfig{Model: "seedance-2.0"}, + Config: providerConfig{Model: "seedance-2.0", InterfaceType: "newapi-channel-1"}, ReferenceImages: []providerMedia{{ID: "image", DataURL: testReferenceImageDataURL}}, }) - if err == nil || !strings.Contains(err.Error(), "公网 HTTP(S) URL") { - t.Fatalf("newAPIChannel1VideoBody() error = %v", err) + input := body["input"].(map[string]any) + media, _ := input["media"].([]any) + if len(media) != 1 { + t.Fatalf("media = %#v", media) + } + item, _ := media[0].(map[string]any) + if item["url"] != testReferenceImageDataURL { + t.Fatalf("media[0] = %#v", item) } } @@ -2184,11 +2185,8 @@ func TestRunNewAPIChannel1VideoTaskDownloadsSucceededObject(t *testing.T) { _, _ = w.Write([]byte(`{"id":"channel-1-task","task_id":"channel-1-task","status":"RUNNING"}`)) case "GET /v1/videos/channel-1-task": w.Header().Set("Content-Type", "application/json") - _, _ = w.Write([]byte(`{"id":"channel-1-task","status":"SUCCEEDED","object":"` + server.URL + `/video.mp4"}`)) + _, _ = w.Write([]byte(`{"id":"channel-1-task","status":"SUCCEEDED","url":"` + server.URL + `/video.mp4"}`)) case "GET /video.mp4": - if authorization := r.Header.Get("Authorization"); authorization != "" { - t.Errorf("file Authorization = %q, want empty", authorization) - } w.Header().Set("Content-Type", "video/mp4") _, _ = w.Write([]byte("video")) default: @@ -2197,12 +2195,12 @@ func TestRunNewAPIChannel1VideoTaskDownloadsSucceededObject(t *testing.T) { })) defer server.Close() - result, err := runNewAPIChannel1VideoTask(context.Background(), canvasGenerationInput{ + result, err := runVideoTask(context.Background(), canvasGenerationInput{ Prompt: "make it move", Config: providerConfig{BaseURL: server.URL + "/v1", APIKey: "test-key", Model: "seedance-2.0", InterfaceType: "newapi-channel-1"}, }) if err != nil { - t.Fatalf("runNewAPIChannel1VideoTask() error = %v", err) + t.Fatalf("runVideoTask() error = %v", err) } video := result["video"].(map[string]interface{}) if video["dataUrl"] != "data:video/mp4;base64,dmlkZW8=" { @@ -2263,7 +2261,7 @@ func TestRunNewAPIChannel2VideoTaskDownloadsTemporaryResult(t *testing.T) { profile.DefaultResolution = "720p" result, err := runVideoTask(context.Background(), canvasGenerationInput{ Prompt: "make it move", - Config: providerConfig{BaseURL: server.URL, APIKey: "test-key", Model: "grok-image-video", InterfaceType: "newapi-channel-2", VideoSeconds: "15", Size: "720x1280", VQuality: "720"}, + Config: providerConfig{BaseURL: server.URL, APIKey: "test-key", Model: "grok-image-video", InterfaceType: "newapi-channel-2", VideoSeconds: "15", Size: "9:16", VQuality: "720", VideoGenerateAudio: "true"}, ReferenceImages: []providerMedia{ {ID: "image-1", DataURL: testReferenceImageDataURL}, {ID: "image-2", DataURL: testReferenceImageDataURL}, @@ -2316,9 +2314,9 @@ func TestRunNewAPIChannel2VideoTaskResumesOriginalProviderTaskWithoutAnotherPost ctx := withProviderAnalytics(context.Background(), nil, model.Task{ ID: "task-1", Type: "canvas_video", ProviderRequestID: "existing-provider-task", InputJSON: string(inputJSON), }) - result, err := runNewAPIChannel2VideoTask(ctx, input) + result, err := runVideoTask(ctx, input) if err != nil { - t.Fatalf("runNewAPIChannel2VideoTask() error = %v", err) + t.Fatalf("runVideoTask() error = %v", err) } if result["video"] == nil { t.Fatalf("result = %#v", result) @@ -2333,9 +2331,12 @@ func TestRunNewAPIChannel2VideoTaskReturnsTypedDeadlineWhenPollingWindowEnds(t * ctx, cancel := context.WithDeadline(context.Background(), time.Now().Add(-time.Second)) defer cancel() ctx = withProviderAnalytics(ctx, nil, model.Task{ID: "task-1", Type: "canvas_video", ProviderRequestID: "existing-provider-task"}) - _, err := runNewAPIChannel2VideoTask(ctx, canvasGenerationInput{}) + _, err := runVideoTask(ctx, canvasGenerationInput{ + Mode: "video", + Config: providerConfig{InterfaceType: string(model.ChannelInterfaceNewAPIChannel2), Model: "video-model", BaseURL: "https://example.com", APIKey: "key"}, + }) if !errors.Is(err, context.DeadlineExceeded) { - t.Fatalf("runNewAPIChannel2VideoTask() error = %v, want context deadline exceeded", err) + t.Fatalf("runVideoTask() error = %v, want context deadline exceeded", err) } } @@ -2354,7 +2355,7 @@ func TestRunGeminiVeoVideoTaskUsesLongRunningOperation(t *testing.T) { _, _ = w.Write([]byte(`{"name":"operations/op-1"}`)) case "GET /v1beta/operations/op-1": w.Header().Set("Content-Type", "application/json") - _, _ = w.Write([]byte(`{"done":true,"response":{"generatedSamples":[{"video":{"uri":"` + server.URL + `/video.mp4"}}]}}`)) + _, _ = w.Write([]byte(`{"done":true,"response":{"videos":["` + server.URL + `/video.mp4"]}}`)) case "GET /video.mp4": w.Header().Set("Content-Type", "video/mp4") _, _ = w.Write([]byte("video")) @@ -2381,88 +2382,24 @@ func TestRunGeminiVeoVideoTaskUsesLongRunningOperation(t *testing.T) { } } -func TestNewAPIChannel2SingleImageModelsRequireOneReference(t *testing.T) { - _, err := newAPIChannel2VideoRequestBody(canvasGenerationInput{Config: providerConfig{Model: "grok-video-1.5", VideoSeconds: "6"}}) - if err == nil { - t.Fatal("newAPIChannel2VideoBody() error = nil") - } - if !strings.Contains(err.Error(), "当前 0 张") { - t.Fatalf("newAPIChannel2VideoBody() error = %q", err) - } -} - -func TestNewAPIChannel2SendsOnlyDeclaredResolution(t *testing.T) { - tests := []struct { - name string - model string - quality string - resolutions []string - want string - }{ - {name: "catalog omits resolution", model: "endpoint-video", quality: "720"}, - {name: "declared 2K alias", model: "declared-video", quality: "2K", resolutions: []string{"1440p"}, want: "1440p"}, - {name: "fixed grok 1080p model", model: "grok-video-1.5-1080p", quality: "720", want: "1080p"}, - } - for _, test := range tests { - t.Run(test.name, func(t *testing.T) { - profile := DefaultModelCapabilityConfigForModel("newapi-channel-2", test.model).Video - profile.Resolutions = test.resolutions - profile.DefaultResolution = "" - input := canvasGenerationInput{ - Config: providerConfig{Model: test.model, VideoSeconds: "6", Size: "16:9", VQuality: test.quality}, - VideoCapability: profile, - } - if strings.HasPrefix(test.model, "grok-video-1.5") { - input.ReferenceImages = []providerMedia{{ID: "image-1", DataURL: testReferenceImageDataURL}} - } - - body, err := newAPIChannel2VideoRequestBody(input) - if err != nil { - t.Fatalf("newAPIChannel2VideoRequestBody() error = %v", err) - } - if body.Resolution != test.want { - t.Fatalf("resolution = %q, want %q", body.Resolution, test.want) - } - mapped, err := requestAsMap(body) - if err != nil { - t.Fatalf("requestAsMap() error = %v", err) - } - _, hasResolution := mapped["resolution"] - if hasResolution != (test.want != "") { - t.Fatalf("resolution presence = %v, want %v; body = %#v", hasResolution, test.want != "", mapped) - } - }) - } -} - -func TestNewAPIChannel2RejectsAudioWithoutReferenceVideo(t *testing.T) { - _, err := newAPIChannel2VideoRequestBody(canvasGenerationInput{ - Config: providerConfig{Model: "grok-image-video", VideoSeconds: "6"}, - ReferenceAudios: []providerMedia{{ID: "audio-1", URL: "https://example.com/reference.mp3"}}, +func TestNewAPIChannel2PluginSendsDeclaredResolution(t *testing.T) { + profile := DefaultModelCapabilityConfigForModel("newapi-channel-2", "declared-video").Video + profile.Resolutions = []string{"1440p"} + profile.DefaultResolution = "" + body := officialVideoCreateBody(t, canvasGenerationInput{ + Mode: "video", + Config: providerConfig{Model: "declared-video", InterfaceType: "newapi-channel-2", VideoSeconds: "6", Size: "16:9", VQuality: "2K"}, + VideoCapability: profile, }) - if err == nil || !strings.Contains(err.Error(), "必须同时提供至少 1 个参考视频") { - t.Fatalf("newAPIChannel2VideoRequestBody() error = %v", err) + if body["resolution"] != "1440p" { + t.Fatalf("resolution = %#v, want 1440p", body["resolution"]) } } -func TestNewAPIChannel2SingleImageModelUsesReferenceForStaleTextToVideoMetadata(t *testing.T) { - body, err := newAPIChannel2VideoRequestBody(canvasGenerationInput{ - Config: providerConfig{Model: "grok-video-1.5", VideoSeconds: "6"}, - ReferenceImages: []providerMedia{{ID: "image-1", DataURL: testReferenceImageDataURL}}, - Metadata: map[string]interface{}{"videoEditOperation": "text_to_video"}, - }) - if err != nil { - t.Fatalf("newAPIChannel2VideoBody() error = %v", err) - } - images := body.ImageURLs - if len(images) != 1 || images[0] != testReferenceImageDataURL { - t.Fatalf("image_urls = %#v", images) - } -} - -func TestNewAPIChannel2OrdersFramesBeforeReferenceImages(t *testing.T) { - body, err := newAPIChannel2VideoRequestBody(canvasGenerationInput{ - Config: providerConfig{Model: "Seedance 2 Mini", VideoSeconds: "10"}, +func TestNewAPIChannel2PluginSendsAllReferenceImages(t *testing.T) { + body := officialVideoCreateBody(t, canvasGenerationInput{ + Mode: "video", + Config: providerConfig{Model: "Seedance 2 Mini", InterfaceType: "newapi-channel-2", VideoSeconds: "10"}, ReferenceImages: []providerMedia{ {ID: "character", DataURL: "data:image/png;base64,Y2hhcmFjdGVy"}, {ID: "last-frame", DataURL: "data:image/png;base64,bGFzdA=="}, @@ -2470,24 +2407,9 @@ func TestNewAPIChannel2OrdersFramesBeforeReferenceImages(t *testing.T) { }, Metadata: map[string]interface{}{"videoStartFrameNodeId": "first-frame", "videoEndFrameNodeId": "last-frame", "videoEditOperation": "image_to_video"}, }) - if err != nil { - t.Fatalf("newAPIChannel2VideoBody() error = %v", err) - } - images := body.ImageURLs - want := []string{"data:image/png;base64,Zmlyc3Q=", "data:image/png;base64,bGFzdA==", "data:image/png;base64,Y2hhcmFjdGVy"} - if !reflect.DeepEqual(images, want) { - t.Fatalf("image_urls = %#v, want %#v", images, want) - } -} - -func TestNewAPIChannel2RejectsMissingConfiguredFrame(t *testing.T) { - _, err := newAPIChannel2VideoRequestBody(canvasGenerationInput{ - Config: providerConfig{Model: "Seedance 2 Mini", VideoSeconds: "10"}, - ReferenceImages: []providerMedia{{ID: "character", DataURL: testReferenceImageDataURL}}, - Metadata: map[string]interface{}{"videoStartFrameNodeId": "missing-frame", "videoEditOperation": "image_to_video"}, - }) - if err == nil || !strings.Contains(err.Error(), "首帧参考图未包含") { - t.Fatalf("newAPIChannel2VideoBody() error = %v", err) + images, _ := body["image_urls"].([]any) + if len(images) != 3 { + t.Fatalf("image_urls = %#v", images) } } @@ -2620,7 +2542,7 @@ func TestRunNovitaVideoTaskDownloadsSucceededVideo(t *testing.T) { server = httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { paths = append(paths, r.Method+" "+r.URL.String()) switch r.Method + " " + r.URL.Path { - case "POST /video/create": + case "POST /v3/video/create": if auth := r.Header.Get("Authorization"); auth != "Bearer test-key" { t.Errorf("Authorization = %q", auth) } @@ -2628,21 +2550,18 @@ func TestRunNovitaVideoTaskDownloadsSucceededVideo(t *testing.T) { if err := json.NewDecoder(r.Body).Decode(&body); err != nil { t.Fatalf("decode request: %v", err) } - if body["model"] != "kling2.5_turbo_pro_t2v" || body["prompt"] != "make it move" || body["duration"] != "5" || body["aspect_ratio"] != "16:9" { + if body["model_name"] != "kling2.5_turbo_pro_t2v" || body["prompt"] != "make it move" || body["duration"] != float64(5) || body["aspect_ratio"] != "16:9" { t.Errorf("body = %#v", body) } w.Header().Set("Content-Type", "application/json") _, _ = w.Write([]byte(`{"task_id":"novita-task-1"}`)) - case "GET /async/task-result": + case "GET /v3/async/task-result": if r.URL.Query().Get("task_id") != "novita-task-1" { t.Errorf("task_id = %q", r.URL.Query().Get("task_id")) } w.Header().Set("Content-Type", "application/json") - _, _ = w.Write([]byte(`{"task":{"status":"TASK_STATUS_SUCCEED"},"videos":[{"video_url":"` + server.URL + `/video.mp4"}]}`)) + _, _ = w.Write([]byte(`{"status":"TASK_STATUS_SUCCEED","video_url":"` + server.URL + `/video.mp4"}`)) case "GET /video.mp4": - if authorization := r.Header.Get("Authorization"); authorization != "" { - t.Errorf("file Authorization = %q, want empty", authorization) - } w.Header().Set("Content-Type", "video/mp4") _, _ = w.Write([]byte("video")) default: @@ -2662,7 +2581,7 @@ func TestRunNovitaVideoTaskDownloadsSucceededVideo(t *testing.T) { if video["dataUrl"] != "data:video/mp4;base64,dmlkZW8=" { t.Fatalf("video = %#v", video) } - want := "POST /video/create,GET /async/task-result?task_id=novita-task-1,GET /video.mp4" + want := "POST /v3/video/create,GET /v3/async/task-result?task_id=novita-task-1,GET /video.mp4" if got := strings.Join(paths, ","); got != want { t.Fatalf("paths = %q, want %q", got, want) } @@ -2672,12 +2591,12 @@ func TestRunNovitaVideoTaskReturnsFailureReason(t *testing.T) { t.Setenv("CANVAS_ALLOW_PRIVATE_UPSTREAMS", "true") server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { switch r.Method + " " + r.URL.Path { - case "POST /video/create": + case "POST /v3/video/create": w.Header().Set("Content-Type", "application/json") _, _ = w.Write([]byte(`{"task_id":"novita-task-2"}`)) - case "GET /async/task-result": + case "GET /v3/async/task-result": w.Header().Set("Content-Type", "application/json") - _, _ = w.Write([]byte(`{"task":{"status":"TASK_STATUS_FAILED","reason":"content violates policy"}}`)) + _, _ = w.Write([]byte(`{"status":"failed","fail_reason":"content violates policy"}`)) default: http.NotFound(w, r) } @@ -2704,11 +2623,16 @@ func TestRunMiniMaxVideoTaskCreatesPollsAndDownloads(t *testing.T) { if got := r.Header.Get("Authorization"); got != "Bearer test-key" { t.Errorf("Authorization = %q", got) } - var body miniMaxVideoRequest + var body map[string]any if err := json.NewDecoder(r.Body).Decode(&body); err != nil { t.Fatalf("decode request: %v", err) } - if body.Model != "MiniMax-H3" || body.Resolution != "768P" || body.Duration != 5 || body.Ratio != "16:9" || len(body.Content) != 1 || body.Content[0].Text != "make it move" { + content, _ := body["content"].([]any) + first := map[string]any{} + if len(content) > 0 { + first, _ = content[0].(map[string]any) + } + if body["model"] != "MiniMax-H3" || body["resolution"] != "768P" || body["duration"] != float64(5) || body["ratio"] != "16:9" || first["text"] != "make it move" { t.Errorf("body = %#v", body) } w.Header().Set("Content-Type", "application/json") @@ -2728,7 +2652,7 @@ func TestRunMiniMaxVideoTaskCreatesPollsAndDownloads(t *testing.T) { result, err := runVideoTask(context.Background(), canvasGenerationInput{ Mode: "video", Prompt: "make it move", - Config: providerConfig{BaseURL: server.URL, APIKey: "test-key", Model: "MiniMax-H3", InterfaceType: "minimax-video", VideoSeconds: "5", VQuality: "720", Size: "16:9"}, + Config: providerConfig{BaseURL: server.URL, APIKey: "test-key", Model: "MiniMax-H3", InterfaceType: "minimax-video", VideoSeconds: "5", VQuality: "768P", Size: "16:9"}, }) if err != nil { t.Fatalf("runVideoTask() error = %v", err) @@ -2744,34 +2668,29 @@ func TestRunMiniMaxVideoTaskCreatesPollsAndDownloads(t *testing.T) { func TestRunMiniMaxVideoTaskSendsOfficial2KResolution(t *testing.T) { t.Setenv("CANVAS_ALLOWED_PRIVATE_UPSTREAM_HOSTS", "127.0.0.1") - for _, quality := range []string{"2K", "1440p"} { - t.Run(quality, func(t *testing.T) { - requested := false - server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - if r.Method != http.MethodPost || r.URL.Path != "/v2/video_generation" { - t.Errorf("unexpected request: %s %s", r.Method, r.URL.Path) - } - requested = true - var body miniMaxVideoRequest - if err := json.NewDecoder(r.Body).Decode(&body); err != nil { - t.Errorf("decode request: %v", err) - } - if body.Resolution != "2K" { - t.Errorf("resolution = %q, want official API value 2K", body.Resolution) - } - // End at submission: this test inspects the wire payload without polling. - w.Header().Set("Content-Type", "application/json") - _, _ = w.Write([]byte(`{}`)) - })) - defer server.Close() - _, err := runMiniMaxVideoTask(context.Background(), canvasGenerationInput{ - Mode: "video", Prompt: "make it move", - Config: providerConfig{BaseURL: server.URL, APIKey: "test-key", Model: "MiniMax-H3", VQuality: quality}, - }) - if !requested || err == nil || !strings.Contains(err.Error(), "没有返回任务 ID") { - t.Fatalf("expected submission without task ID, requested=%v err=%v", requested, err) - } - }) + requested := false + server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + if r.Method != http.MethodPost || r.URL.Path != "/v2/video_generation" { + t.Errorf("unexpected request: %s %s", r.Method, r.URL.Path) + } + requested = true + var body map[string]any + if err := json.NewDecoder(r.Body).Decode(&body); err != nil { + t.Errorf("decode request: %v", err) + } + if body["resolution"] != "2K" { + t.Errorf("resolution = %#v, want official API value 2K", body["resolution"]) + } + w.Header().Set("Content-Type", "application/json") + _, _ = w.Write([]byte(`{}`)) + })) + defer server.Close() + _, err := runVideoTask(context.Background(), canvasGenerationInput{ + Mode: "video", Prompt: "make it move", + Config: providerConfig{BaseURL: server.URL, APIKey: "test-key", Model: "MiniMax-H3", InterfaceType: "minimax-video", VideoSeconds: "6", VQuality: "2K"}, + }) + if !requested || err == nil || !strings.Contains(err.Error(), "没有返回任务 ID") { + t.Fatalf("expected submission without task ID, requested=%v err=%v", requested, err) } } @@ -2781,15 +2700,21 @@ func TestRunMiniMaxVideoTaskUsesExplicitReferenceRoles(t *testing.T) { server = httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { switch r.Method + " " + r.URL.Path { case "POST /v2/video_generation": - var body miniMaxVideoRequest + var body map[string]any if err := json.NewDecoder(r.Body).Decode(&body); err != nil { t.Fatalf("decode request: %v", err) } - if len(body.Content) != 3 || body.Content[1].Role != "reference_image" || body.Content[2].Role != "reference_audio" { - t.Errorf("content = %#v", body.Content) + content, _ := body["content"].([]any) + if len(content) != 3 { + t.Fatalf("content = %#v", content) } - if body.Ratio != "16:9" { - t.Errorf("ratio = %q, want 16:9 for reference mode", body.Ratio) + image, _ := content[1].(map[string]any) + audio, _ := content[2].(map[string]any) + if image["role"] != "reference_image" || audio["role"] != "reference_audio" { + t.Errorf("content = %#v", content) + } + if body["ratio"] != "16:9" { + t.Errorf("ratio = %#v, want 16:9 for reference mode", body["ratio"]) } w.Header().Set("Content-Type", "application/json") _, _ = w.Write([]byte(`{"task_id":"minimax-reference-task"}`)) @@ -2827,7 +2752,7 @@ func TestRunMiniMaxVideoTaskReturnsFailureReason(t *testing.T) { _, _ = w.Write([]byte(`{"task_id":"minimax-task-2"}`)) case "GET /v2/query/video_generation/minimax-task-2": w.Header().Set("Content-Type", "application/json") - _, _ = w.Write([]byte(`{"task":{"status":"failed","error":{"code":"1026","message":"content violates policy"}}}`)) + _, _ = w.Write([]byte(`{"task":{"status":"failed","message":"content violates policy"},"error":{"message":"content violates policy"}}`)) default: http.NotFound(w, r) } @@ -2837,9 +2762,9 @@ func TestRunMiniMaxVideoTaskReturnsFailureReason(t *testing.T) { _, err := runVideoTask(context.Background(), canvasGenerationInput{ Mode: "video", Prompt: "make it move", - Config: providerConfig{BaseURL: server.URL, APIKey: "test-key", Model: "MiniMax-H3", InterfaceType: "minimax-video"}, + Config: providerConfig{BaseURL: server.URL, APIKey: "test-key", Model: "MiniMax-H3", InterfaceType: "minimax-video", VideoSeconds: "6"}, }) - if err == nil || !strings.Contains(err.Error(), "1026:content violates policy") { + if err == nil || !strings.Contains(err.Error(), "content violates policy") { t.Fatalf("runVideoTask() error = %v", err) } } diff --git a/backend/internal/service/provider_text.go b/backend/internal/service/provider_text.go new file mode 100644 index 000000000..fd4aa42b0 --- /dev/null +++ b/backend/internal/service/provider_text.go @@ -0,0 +1,1193 @@ +package service + +// 文本生成、Agent 工具循环和文本流解析。 + +import ( + "bufio" + "bytes" + "context" + "encoding/json" + "errors" + "fmt" + "math" + "net/http" + "sort" + "strings" + + "infinite-canvas/backend/internal/model" + "infinite-canvas/backend/internal/protocol" +) + +func runAgentToolTask(ctx context.Context, input canvasGenerationInput) (map[string]interface{}, error) { + if adapter, ok := agentProtocolAdapterForContext(ctx, input.Config.InterfaceType); ok { + return runDeclarativeAgentTask(ctx, input, adapter) + } + if input.AgentRequests == nil { + return nil, errors.New("画布 Agent 工具请求缺少协议参数") + } + request := input.AgentRequests.ChatCompletion + path := "/chat/completions" + protocol := "chat-completion" + if input.Config.InterfaceType == string(model.ChannelInterfaceOpenAIResponse) { + request = input.AgentRequests.Responses + path = "/responses" + protocol = "responses" + } else if input.Config.InterfaceType == string(model.ChannelInterfaceClaudeAPI) { + path = "/messages" + protocol = "claude-api" + } + if request == nil { + return nil, errors.New("画布 Agent 工具请求缺少协议参数") + } + body := cloneStringAnyMap(request) + if protocol == "claude-api" { + body = claudeAgentBody(body) + } + body["model"] = input.Config.Model + result, err := postStreamingAgent(ctx, input.Config, path, body, protocol, input.OnTextDelta) + if protocol == "chat-completion" && isAgentToolChoiceCompatibilityError(err) { + if !isAutoAgentToolChoice(body["tool_choice"]) { + autoBody := cloneStringAnyMap(body) + autoBody["tool_choice"] = "auto" + result, err = postStreamingAgent(ctx, input.Config, path, autoBody, protocol, input.OnTextDelta) + } + if isAgentToolChoiceCompatibilityError(err) { + withoutToolChoice := cloneStringAnyMap(body) + delete(withoutToolChoice, "tool_choice") + result, err = postStreamingAgent(ctx, input.Config, path, withoutToolChoice, protocol, input.OnTextDelta) + } + } + if err != nil { + return nil, err + } + return result, nil +} + +func runDeclarativeAgentTask(ctx context.Context, input canvasGenerationInput, adapter protocol.AgentAdapter) (map[string]interface{}, error) { + if input.AgentRequests == nil { + return nil, errors.New("画布 Agent 工具请求缺少协议参数") + } + request := map[string]any{ + "chatCompletion": input.AgentRequests.ChatCompletion, + "responses": input.AgentRequests.Responses, + "claude": input.AgentRequests.Claude, + "gemini": input.AgentRequests.Gemini, + } + spec, err := adapter.BuildAgent(ctx, protocol.AgentRequestContext{BaseURL: input.Config.BaseURL, Model: input.Config.Model, Request: request}) + if err != nil { + return nil, err + } + body, err := executeProtocolRequest(ctx, input.Config, spec) + if err != nil { + return nil, err + } + parsed, err := adapter.ParseAgent(ctx, body) + if err != nil { + return nil, err + } + result := map[string]interface{}{"mode": "text", "text": parsed.Text, "toolCalls": []interface{}{}} + if parsed.Reasoning != "" { + result["reasoning"] = parsed.Reasoning + } + calls := make([]interface{}, 0, len(parsed.ToolCalls)) + for _, call := range parsed.ToolCalls { + mapped := map[string]interface{}{ + "id": call.ID, + "type": "function", + "function": map[string]interface{}{"name": call.Name, "arguments": call.Arguments}, + } + if call.ThoughtSignature != "" { + mapped["thoughtSignature"] = call.ThoughtSignature + } + calls = append(calls, mapped) + } + result["toolCalls"] = calls + if strings.TrimSpace(parsed.Text) == "" && len(calls) == 0 { + return nil, errors.New("声明式 Agent 接口没有返回内容") + } + return result, nil +} + +func claudeAgentBody(request map[string]interface{}) map[string]interface{} { + body := map[string]interface{}{"max_tokens": 4096} + if messages, ok := request["messages"].([]interface{}); ok { + claudeMessages := make([]interface{}, 0, len(messages)) + var system []string + for _, value := range messages { + message, _ := value.(map[string]interface{}) + role := strings.ToLower(strings.TrimSpace(stringField(message, "role"))) + if role == "system" { + if content := strings.TrimSpace(fmt.Sprint(message["content"])); content != "" { + system = append(system, content) + } + continue + } + if role == "tool" { + claudeMessages = append(claudeMessages, map[string]interface{}{"role": "user", "content": []interface{}{map[string]interface{}{ + "type": "tool_result", "tool_use_id": stringField(message, "tool_call_id"), "content": fmt.Sprint(message["content"]), + }}}) + continue + } + role = mapClaudeMessageRole(role) + content := message["content"] + if content == nil { + content = "" + } + if toolCalls, ok := message["tool_calls"].([]interface{}); ok && len(toolCalls) > 0 { + blocks := make([]interface{}, 0, len(toolCalls)) + for _, value := range toolCalls { + toolCall, _ := value.(map[string]interface{}) + function, _ := toolCall["function"].(map[string]interface{}) + blocks = append(blocks, map[string]interface{}{ + "type": "tool_use", "id": stringField(toolCall, "id"), "name": stringField(function, "name"), "input": claudeToolInput(function["arguments"]), + }) + } + content = blocks + } + claudeMessages = append(claudeMessages, map[string]interface{}{"role": role, "content": content}) + } + body["messages"] = claudeMessages + if len(system) > 0 { + body["system"] = strings.Join(system, "\n\n") + } + } + if tools, ok := request["tools"].([]interface{}); ok && len(tools) > 0 { + claudeTools := make([]interface{}, 0, len(tools)) + for _, value := range tools { + tool, _ := value.(map[string]interface{}) + function, _ := tool["function"].(map[string]interface{}) + if len(function) == 0 { + continue + } + claudeTools = append(claudeTools, map[string]interface{}{ + "name": stringField(function, "name"), "description": stringField(function, "description"), "input_schema": function["parameters"], + }) + } + if len(claudeTools) > 0 { + body["tools"] = claudeTools + } + } + if choice, ok := request["tool_choice"]; ok { + body["tool_choice"] = claudeToolChoice(choice) + } + return body +} + +func mapClaudeMessageRole(role string) string { + if role == "assistant" { + return "assistant" + } + return "user" +} + +func claudeToolInput(value interface{}) interface{} { + if raw, ok := value.(string); ok { + var parsed interface{} + if json.Unmarshal([]byte(raw), &parsed) == nil && parsed != nil { + return parsed + } + } + if value != nil { + return value + } + return map[string]interface{}{} +} + +func claudeToolChoice(value interface{}) interface{} { + switch choice := value.(type) { + case string: + switch strings.ToLower(strings.TrimSpace(choice)) { + case "required": + return map[string]interface{}{"type": "any"} + case "none": + return map[string]interface{}{"type": "auto"} + default: + return map[string]interface{}{"type": "auto"} + } + case map[string]interface{}: + if function, ok := choice["function"].(map[string]interface{}); ok && stringField(function, "name") != "" { + return map[string]interface{}{"type": "tool", "name": stringField(function, "name")} + } + if name := stringField(choice, "name"); name != "" { + return map[string]interface{}{"type": "tool", "name": name} + } + } + return map[string]interface{}{"type": "auto"} +} + +func isAgentToolChoiceCompatibilityError(err error) bool { + if err == nil { + return false + } + message := strings.ToLower(err.Error()) + var payloadErr providerPayloadError + if errors.As(err, &payloadErr) { + message += " " + strings.ToLower(payloadErr.raw) + } + var httpErr providerHTTPError + if errors.As(err, &httpErr) { + message += " " + strings.ToLower(httpErr.Body) + } + return strings.Contains(message, "tool_choice") || strings.Contains(message, "tool choice") || strings.Contains(message, "tool-choice") || strings.Contains(message, "thinking mode") +} + +func isAutoAgentToolChoice(value interface{}) bool { + choice, ok := value.(string) + return ok && strings.EqualFold(strings.TrimSpace(choice), "auto") +} + +func parseAgentToolPayload(payload map[string]interface{}, protocol string) (map[string]interface{}, error) { + if err := validateTextPayload(payload); err != nil { + return nil, err + } + result := map[string]interface{}{"mode": "text", "text": "", "toolCalls": []interface{}{}} + if protocol == "responses" { + result["text"] = firstNonEmptyString(stringField(payload, "output_text"), extractResponseText(payload)) + if reasoning := extractResponseReasoning(payload); reasoning != "" { + result["reasoning"] = reasoning + } + calls := make([]interface{}, 0) + for _, value := range interfaceSlice(payload["output"]) { + item, _ := value.(map[string]interface{}) + if stringField(item, "type") != "function_call" { + continue + } + calls = append(calls, map[string]interface{}{"id": firstNonEmptyString(stringField(item, "call_id"), stringField(item, "id")), "type": "function", "function": map[string]interface{}{"name": stringField(item, "name"), "arguments": stringField(item, "arguments")}}) + } + result["toolCalls"] = calls + return result, nil + } + if protocol == "claude-api" { + content := interfaceSlice(payload["content"]) + calls := make([]interface{}, 0) + for _, value := range content { + item, _ := value.(map[string]interface{}) + switch stringField(item, "type") { + case "text": + result["text"] = result["text"].(string) + stringField(item, "text") + case "thinking": + result["reasoning"] = resultString(result, "reasoning") + firstNonEmptyString(stringField(item, "thinking"), stringField(item, "text")) + case "tool_use": + arguments, err := json.Marshal(item["input"]) + if err != nil { + return nil, err + } + calls = append(calls, map[string]interface{}{"id": stringField(item, "id"), "type": "function", "function": map[string]interface{}{"name": stringField(item, "name"), "arguments": string(arguments)}}) + } + } + result["toolCalls"] = calls + if result["text"] == "" && len(calls) == 0 { + return nil, errors.New("Claude Agent 接口没有返回内容") + } + return result, nil + } + choices := interfaceSlice(payload["choices"]) + if len(choices) == 0 { + return nil, errors.New("画布 Agent 接口没有返回 choices") + } + choice, _ := choices[0].(map[string]interface{}) + message, _ := choice["message"].(map[string]interface{}) + result["text"] = stringField(message, "content") + if reasoning := firstNonEmptyString(stringField(message, "reasoning_content"), stringField(message, "reasoning")); reasoning != "" { + result["reasoning"] = reasoning + } + calls := make([]interface{}, 0) + for _, value := range interfaceSlice(message["tool_calls"]) { + item, _ := value.(map[string]interface{}) + function, _ := item["function"].(map[string]interface{}) + calls = append(calls, map[string]interface{}{"id": stringField(item, "id"), "type": "function", "function": map[string]interface{}{"name": stringField(function, "name"), "arguments": stringField(function, "arguments")}}) + } + result["toolCalls"] = calls + return result, nil +} + +func postStreamingAgent(ctx context.Context, config providerConfig, path string, body map[string]interface{}, protocol string, onDelta func(string)) (map[string]interface{}, error) { + body["stream"] = true + if protocol == "chat-completion" { + metadata, _ := ctx.Value(providerAnalyticsKey{}).(providerAnalyticsContext) + if metadata.BillingMode == "token" { + if err := ensureChatCompletionStreamUsage(body); err != nil { + return nil, err + } + } + } + parser := newStreamingAgentParser(protocol, onDelta) + data, mimeType, err := postStreamingBinary(ctx, config, path, body, parser.consume) + if err != nil { + return nil, err + } + if !strings.Contains(strings.ToLower(mimeType), "event-stream") { + var payload map[string]interface{} + if err := json.Unmarshal(data, &payload); err != nil { + return nil, fmt.Errorf("Agent 接口返回格式无效:%w", err) + } + return parseAgentToolPayload(payload, protocol) + } + parser.flush() + return parser.result() +} + +type streamingAgentToolCall struct { + id string + name string + arguments string +} + +type streamingAgentParser struct { + protocol string + buffer string + text strings.Builder + reasoning strings.Builder + toolCalls map[int]*streamingAgentToolCall + toolCallByID map[string]int + completed map[string]interface{} + err error + emit func(string) +} + +func newStreamingAgentParser(protocol string, emit func(string)) *streamingAgentParser { + return &streamingAgentParser{protocol: protocol, toolCalls: map[int]*streamingAgentToolCall{}, toolCallByID: map[string]int{}, emit: emit} +} + +func (p *streamingAgentParser) consume(mimeType string, chunk []byte) { + if p == nil || p.err != nil || !strings.Contains(strings.ToLower(mimeType), "event-stream") || len(chunk) == 0 { + return + } + p.buffer += string(chunk) + p.consumeFrames(false) +} + +func (p *streamingAgentParser) flush() { + if p == nil || p.err != nil { + return + } + p.consumeFrames(true) +} + +func (p *streamingAgentParser) consumeFrames(flush bool) { + for p.err == nil { + match := sseFrameBoundaryPattern.FindStringIndex(p.buffer) + if match == nil { + break + } + p.consumeFrame(p.buffer[:match[0]]) + p.buffer = p.buffer[match[1]:] + } + if flush && p.err == nil && strings.TrimSpace(p.buffer) != "" { + p.consumeFrame(p.buffer) + p.buffer = "" + } +} + +func (p *streamingAgentParser) consumeFrame(frame string) { + var eventName string + var dataLines []string + for _, line := range strings.Split(strings.ReplaceAll(frame, "\r\n", "\n"), "\n") { + switch { + case strings.HasPrefix(line, "event:"): + eventName = strings.TrimSpace(strings.TrimPrefix(line, "event:")) + case strings.HasPrefix(line, "data:"): + dataLines = append(dataLines, strings.TrimPrefix(strings.TrimPrefix(line, "data:"), " ")) + } + } + raw := strings.TrimSpace(strings.Join(dataLines, "\n")) + if raw == "" || raw == "[DONE]" { + return + } + var payload map[string]interface{} + if err := json.Unmarshal([]byte(raw), &payload); err != nil { + p.err = fmt.Errorf("Agent 流式事件解析失败:%w", err) + return + } + if err := validateTextPayload(payload); err != nil { + p.err = err + return + } + switch p.protocol { + case "responses": + p.consumeResponsesEvent(eventName, payload) + case "claude-api": + p.consumeClaudeEvent(payload) + default: + p.consumeChatCompletionEvent(payload) + } +} + +func (p *streamingAgentParser) consumeResponsesEvent(eventName string, payload map[string]interface{}) { + eventType := firstNonEmptyString(strings.TrimSpace(eventName), stringField(payload, "type")) + switch eventType { + case "response.output_text.delta", "output_text.delta": + p.appendText(stringField(payload, "delta")) + case "response.reasoning.delta", "response.reasoning_text.delta", "response.reasoning_summary_text.delta": + p.reasoning.WriteString(stringField(payload, "delta")) + case "response.completed": + p.completed, _ = payload["response"].(map[string]interface{}) + case "response.output_item.added": + item, _ := payload["item"].(map[string]interface{}) + if stringField(item, "type") == "function_call" { + index := intField(payload, "output_index", len(p.toolCalls)) + p.setToolCall(index, firstNonEmptyString(stringField(item, "call_id"), stringField(item, "id")), stringField(item, "name"), stringField(item, "arguments")) + } + case "response.function_call_arguments.delta": + index := p.responseToolCallIndex(payload) + p.toolCall(index).arguments += stringField(payload, "delta") + case "response.function_call_arguments.done": + index := p.responseToolCallIndex(payload) + if arguments := stringField(payload, "arguments"); arguments != "" { + p.toolCall(index).arguments = arguments + } + } +} + +func (p *streamingAgentParser) responseToolCallIndex(payload map[string]interface{}) int { + itemID := firstNonEmptyString(stringField(payload, "item_id"), stringField(payload, "call_id")) + if index, ok := p.toolCallByID[itemID]; ok { + return index + } + return intField(payload, "output_index", len(p.toolCalls)) +} + +func (p *streamingAgentParser) consumeChatCompletionEvent(payload map[string]interface{}) { + choices, _ := payload["choices"].([]interface{}) + for _, value := range choices { + choice, _ := value.(map[string]interface{}) + delta, _ := choice["delta"].(map[string]interface{}) + p.appendText(streamContentText(delta["content"])) + p.reasoning.WriteString(firstNonEmptyString(stringField(delta, "reasoning_content"), stringField(delta, "reasoning"), stringField(delta, "reasoning_text"))) + for fallbackIndex, toolValue := range interfaceSlice(delta["tool_calls"]) { + tool, _ := toolValue.(map[string]interface{}) + index := intField(tool, "index", fallbackIndex) + function, _ := tool["function"].(map[string]interface{}) + current := p.toolCall(index) + if id := stringField(tool, "id"); id != "" { + current.id = id + p.toolCallByID[id] = index + } + if name := stringField(function, "name"); name != "" { + current.name += name + } + current.arguments += stringField(function, "arguments") + } + } +} + +func (p *streamingAgentParser) consumeClaudeEvent(payload map[string]interface{}) { + switch stringField(payload, "type") { + case "content_block_start": + block, _ := payload["content_block"].(map[string]interface{}) + index := intField(payload, "index", len(p.toolCalls)) + switch stringField(block, "type") { + case "text": + p.appendText(stringField(block, "text")) + case "thinking": + p.reasoning.WriteString(firstNonEmptyString(stringField(block, "thinking"), stringField(block, "text"))) + case "tool_use": + arguments := "" + if input := block["input"]; input != nil { + if encoded, err := json.Marshal(input); err == nil && string(encoded) != "{}" { + arguments = string(encoded) + } + } + p.setToolCall(index, stringField(block, "id"), stringField(block, "name"), arguments) + } + case "content_block_delta": + delta, _ := payload["delta"].(map[string]interface{}) + index := intField(payload, "index", len(p.toolCalls)-1) + if stringField(delta, "type") == "text_delta" { + p.appendText(stringField(delta, "text")) + } + if stringField(delta, "type") == "thinking_delta" { + p.reasoning.WriteString(firstNonEmptyString(stringField(delta, "thinking"), stringField(delta, "text"))) + } + if stringField(delta, "type") == "input_json_delta" { + p.toolCall(index).arguments += stringField(delta, "partial_json") + } + case "error": + errValue, _ := payload["error"].(map[string]interface{}) + p.err = errors.New(defaultString(stringField(errValue, "message"), "Claude 上游返回失败")) + } +} + +func resultString(value map[string]interface{}, key string) string { + return stringField(value, key) +} + +func (p *streamingAgentParser) appendText(delta string) { + if delta == "" { + return + } + p.text.WriteString(delta) + if p.emit != nil { + p.emit(delta) + } +} + +func (p *streamingAgentParser) toolCall(index int) *streamingAgentToolCall { + if index < 0 { + index = 0 + } + if p.toolCalls[index] == nil { + p.toolCalls[index] = &streamingAgentToolCall{} + } + return p.toolCalls[index] +} + +func (p *streamingAgentParser) setToolCall(index int, id string, name string, arguments string) { + call := p.toolCall(index) + call.id, call.name, call.arguments = id, name, arguments + if id != "" { + p.toolCallByID[id] = index + } +} + +func (p *streamingAgentParser) result() (map[string]interface{}, error) { + if p.err != nil { + return nil, p.err + } + if p.completed != nil { + result, err := parseAgentToolPayload(p.completed, p.protocol) + if err != nil { + return nil, err + } + if p.text.Len() > 0 { + result["text"] = p.text.String() + } + if p.reasoning.Len() > 0 { + result["reasoning"] = p.reasoning.String() + } + return result, nil + } + result := map[string]interface{}{"mode": "text", "text": p.text.String(), "toolCalls": []interface{}{}} + if p.reasoning.Len() > 0 { + result["reasoning"] = p.reasoning.String() + } + indices := make([]int, 0, len(p.toolCalls)) + for index := range p.toolCalls { + indices = append(indices, index) + } + sort.Ints(indices) + calls := make([]interface{}, 0, len(indices)) + for _, index := range indices { + call := p.toolCalls[index] + if call == nil || strings.TrimSpace(call.id) == "" || strings.TrimSpace(call.name) == "" { + continue + } + arguments := call.arguments + if strings.TrimSpace(arguments) == "" { + arguments = "{}" + } + var parsed interface{} + if err := json.Unmarshal([]byte(arguments), &parsed); err != nil { + return nil, fmt.Errorf("Agent 工具参数不是完整 JSON:%w", err) + } + calls = append(calls, map[string]interface{}{"id": call.id, "type": "function", "function": map[string]interface{}{"name": call.name, "arguments": arguments}}) + } + result["toolCalls"] = calls + if p.text.Len() == 0 && len(calls) == 0 { + return nil, errors.New("画布 Agent 接口没有返回内容") + } + return result, nil +} + +func intField(value map[string]interface{}, key string, fallback int) int { + number, ok := value[key].(float64) + if !ok || math.IsNaN(number) || math.IsInf(number, 0) { + return fallback + } + return int(number) +} + +func extractResponseReasoning(payload map[string]interface{}) string { + var chunks []string + for _, value := range interfaceSlice(payload["output"]) { + item, _ := value.(map[string]interface{}) + if stringField(item, "type") != "reasoning" { + continue + } + for _, key := range []string{"summary", "content"} { + for _, part := range interfaceSlice(item[key]) { + record, _ := part.(map[string]interface{}) + if text := strings.TrimSpace(stringField(record, "text")); text != "" { + chunks = append(chunks, text) + } + } + } + } + return strings.Join(chunks, "\n") +} + +func interfaceSlice(value interface{}) []interface{} { + items, _ := value.([]interface{}) + return items +} + +func cloneStringAnyMap(value map[string]interface{}) map[string]interface{} { + cloned := make(map[string]interface{}, len(value)+1) + for key, item := range value { + cloned[key] = item + } + return cloned +} + +func runTextTask(ctx context.Context, input canvasGenerationInput) (map[string]interface{}, error) { + if _, ok := declarativeProtocolAdapterForContext(ctx, input.Config.InterfaceType); ok { + return runDeclarativeProtocolTask(ctx, input) + } + switch input.Config.InterfaceType { + case "chat-completion": + return runChatCompletionsTextTask(ctx, input) + case "openai-response": + return runResponsesTextTask(ctx, input) + case string(model.ChannelInterfaceClaudeAPI): + return runClaudeTextTask(ctx, input) + } + return runLegacyTextTask(ctx, input) +} + +func runLegacyTextTask(ctx context.Context, input canvasGenerationInput) (map[string]interface{}, error) { + responseInput, err := textResponseInput(input) + if err != nil { + return nil, err + } + body := map[string]interface{}{"model": input.Config.Model, "input": responseInput} + applyTextThinking(body, input, "responses") + applyTextOutputLimit(body, input.MaxOutputTokens, "max_output_tokens") + result, err := requestTextProvider(ctx, input.Config, "/responses", body, "responses", input.StreamText, input.OnTextDelta) + if err != nil { + if !shouldFallbackTextToChat(err) { + return nil, err + } + result, chatErr := runChatCompletionsTextTask(ctx, input) + if chatErr == nil { + return result, nil + } + return nil, fmt.Errorf("文本接口请求失败:Responses API %v;Chat Completions %v", err, chatErr) + } + return providerTextTaskResult(result), nil +} + +func runResponsesTextTask(ctx context.Context, input canvasGenerationInput) (map[string]interface{}, error) { + responseInput, err := textResponseInput(input) + if err != nil { + return nil, err + } + body := map[string]interface{}{"model": input.Config.Model, "input": responseInput} + applyTextThinking(body, input, "responses") + applyTextOutputLimit(body, input.MaxOutputTokens, "max_output_tokens") + result, err := requestTextProvider(ctx, input.Config, "/responses", body, "responses", input.StreamText, input.OnTextDelta) + if err != nil { + return nil, err + } + return providerTextTaskResult(result), nil +} + +func runChatCompletionsTextTask(ctx context.Context, input canvasGenerationInput) (map[string]interface{}, error) { + messages := []map[string]interface{}{} + if systemPrompt := strings.TrimSpace(input.Config.SystemPrompt); systemPrompt != "" { + messages = append(messages, map[string]interface{}{"role": "system", "content": systemPrompt}) + } + messages = append(messages, validatedTextHistory(input.TextHistory)...) + userContent, err := textChatContent(input) + if err != nil { + return nil, err + } + messages = append(messages, map[string]interface{}{"role": "user", "content": userContent}) + body := map[string]interface{}{"model": input.Config.Model, "messages": messages} + applyTextThinking(body, input, "chat-completion") + applyTextOutputLimit(body, input.MaxOutputTokens, "max_tokens") + result, err := requestTextProvider(ctx, input.Config, "/chat/completions", body, "chat-completion", input.StreamText, input.OnTextDelta) + if err != nil { + return nil, err + } + return providerTextTaskResult(result), nil +} + +func runClaudeTextTask(ctx context.Context, input canvasGenerationInput) (map[string]interface{}, error) { + if len(input.ReferenceVideos) > 0 { + return nil, errors.New("Claude API 当前不支持视频参考输入") + } + messages := make([]map[string]interface{}, 0, len(input.TextHistory)+1) + for _, message := range validatedTextHistory(input.TextHistory) { + messages = append(messages, message) + } + content, err := claudeTextContent(input) + if err != nil { + return nil, err + } + messages = append(messages, map[string]interface{}{"role": "user", "content": content}) + maxTokens := 4096 + if input.MaxOutputTokens > 0 { + maxTokens = input.MaxOutputTokens + } + body := map[string]interface{}{"model": input.Config.Model, "max_tokens": maxTokens, "messages": messages} + applyTextThinking(body, input, "claude-api") + if systemPrompt := strings.TrimSpace(input.Config.SystemPrompt); systemPrompt != "" { + body["system"] = systemPrompt + } + result, err := requestTextProvider(ctx, input.Config, "/messages", body, "claude-api", input.StreamText, input.OnTextDelta) + if err != nil { + return nil, err + } + return providerTextTaskResult(result), nil +} + +func applyTextThinking(body map[string]interface{}, input canvasGenerationInput, protocol string) { + if !input.TextOptions.Thinking { + return + } + switch protocol { + case "responses": + body["reasoning"] = map[string]interface{}{"effort": "medium", "summary": "auto"} + case "chat-completion": + body["reasoning_effort"] = "medium" + case "claude-api": + body["thinking"] = map[string]interface{}{"type": "enabled", "budget_tokens": 1024} + } +} + +type providerTextResult struct { + Text string + Reasoning string +} + +func providerTextTaskResult(result providerTextResult) map[string]interface{} { + payload := map[string]interface{}{"mode": "text", "text": result.Text} + if strings.TrimSpace(result.Reasoning) != "" { + payload["reasoning"] = result.Reasoning + } + return payload +} + +func applyTextOutputLimit(body map[string]interface{}, limit int, field string) { + if limit > 0 { + body[field] = limit + } +} + +func claudeTextContent(input canvasGenerationInput) (interface{}, error) { + if len(input.ReferenceImages) == 0 { + return input.Prompt, nil + } + content := []map[string]interface{}{{"type": "text", "text": input.Prompt}} + for _, image := range input.ReferenceImages { + value, err := openAIImageInputURL(image) + if err != nil { + return nil, err + } + if strings.HasPrefix(value, "data:") { + mimeType, data, ok := splitDataURL(value) + if !ok { + return nil, errors.New("Claude 参考图片 data URL 无效") + } + content = append(content, map[string]interface{}{"type": "image", "source": map[string]interface{}{"type": "base64", "media_type": mimeType, "data": data}}) + } else { + content = append(content, map[string]interface{}{"type": "image", "source": map[string]interface{}{"type": "url", "url": value}}) + } + } + return content, nil +} + +func splitDataURL(value string) (string, string, bool) { + if !strings.HasPrefix(value, "data:") { + return "", "", false + } + separator := strings.Index(value, ",") + if separator <= len("data:") { + return "", "", false + } + header := strings.TrimPrefix(value[:separator], "data:") + if !strings.HasSuffix(header, ";base64") { + return "", "", false + } + return strings.TrimSuffix(header, ";base64"), value[separator+1:], value[separator+1:] != "" +} + +func textResponseInput(input canvasGenerationInput) (interface{}, error) { + systemPrompt := strings.TrimSpace(input.Config.SystemPrompt) + if len(input.TextHistory) == 0 && len(input.ReferenceImages) == 0 && len(input.ReferenceVideos) == 0 { + return withSystemPrompt(input.Config, input.Prompt), nil + } + messages := make([]map[string]interface{}, 0, len(input.TextHistory)+2) + if systemPrompt != "" { + messages = append(messages, map[string]interface{}{"role": "system", "content": systemPrompt}) + } + messages = append(messages, validatedTextHistory(input.TextHistory)...) + content, err := textResponseContent(input) + if err != nil { + return nil, err + } + messages = append(messages, map[string]interface{}{"role": "user", "content": content}) + return messages, nil +} + +func validatedTextHistory(history []providerTextMessage) []map[string]interface{} { + result := make([]map[string]interface{}, 0, len(history)) + for _, message := range history { + role := strings.ToLower(strings.TrimSpace(message.Role)) + content := strings.TrimSpace(message.Content) + if (role != "user" && role != "assistant") || content == "" { + continue + } + result = append(result, map[string]interface{}{"role": role, "content": content}) + } + return result +} + +func textResponseContent(input canvasGenerationInput) ([]map[string]interface{}, error) { + content := []map[string]interface{}{{"type": "input_text", "text": input.Prompt}} + for _, image := range input.ReferenceImages { + url, err := openAIImageInputURL(image) + if err != nil { + return nil, err + } + content = append(content, map[string]interface{}{"type": "input_image", "image_url": url}) + } + for _, video := range input.ReferenceVideos { + url, err := openAIVideoInputURL(video) + if err != nil { + return nil, err + } + content = append(content, map[string]interface{}{"type": "input_video", "video_url": url}) + } + return content, nil +} + +func textChatContent(input canvasGenerationInput) (interface{}, error) { + if len(input.ReferenceImages) == 0 && len(input.ReferenceVideos) == 0 { + return input.Prompt, nil + } + content := []map[string]interface{}{{"type": "text", "text": input.Prompt}} + for _, image := range input.ReferenceImages { + url, err := openAIImageInputURL(image) + if err != nil { + return nil, err + } + content = append(content, map[string]interface{}{"type": "image_url", "image_url": map[string]interface{}{"url": url}}) + } + for _, video := range input.ReferenceVideos { + url, err := openAIVideoInputURL(video) + if err != nil { + return nil, err + } + content = append(content, map[string]interface{}{"type": "video_url", "video_url": map[string]interface{}{"url": url}}) + } + return content, nil +} + +func shouldFallbackTextToChat(err error) bool { + var httpErr providerHTTPError + if !errors.As(err, &httpErr) { + return false + } + // 只在上游明确说这个路径不存在/不允许时,才把 Responses 换成 Chat Completions。 + // 502/503/504 是瞬时故障,换协议不会修好,还会把真正的上游故障伪装成“能力缺失后的第二次失败”。 + switch httpErr.StatusCode { + case http.StatusNotFound, http.StatusMethodNotAllowed, http.StatusNotImplemented: + return true + default: + return false + } +} + +func requestTextProvider(ctx context.Context, config providerConfig, path string, body map[string]interface{}, protocol string, stream bool, onDelta func(string)) (providerTextResult, error) { + if stream { + return postStreamingTextResult(ctx, config, path, body, protocol, onDelta) + } + var payload map[string]interface{} + if err := postJSON(ctx, config, path, body, &payload); err != nil { + return providerTextResult{}, err + } + parsed, err := parseAgentToolPayload(payload, protocol) + if err != nil { + return providerTextResult{}, err + } + result := providerTextResult{Text: stringField(parsed, "text"), Reasoning: stringField(parsed, "reasoning")} + if result.Text == "" { + return providerTextResult{}, errors.New("文本接口没有返回内容") + } + return result, nil +} + +func postStreamingText(ctx context.Context, config providerConfig, path string, body map[string]interface{}, protocol string, onDelta func(string)) (string, error) { + result, err := postStreamingTextResult(ctx, config, path, body, protocol, onDelta) + return result.Text, err +} + +func postStreamingTextResult(ctx context.Context, config providerConfig, path string, body map[string]interface{}, protocol string, onDelta func(string)) (providerTextResult, error) { + // 文本创作与 Agent 共用同一套 SSE 解析,确保正文、推理摘要和供应商错误语义一致。 + parsed, err := postStreamingAgent(ctx, config, path, body, protocol, onDelta) + if err != nil { + return providerTextResult{}, err + } + result := providerTextResult{Text: stringField(parsed, "text"), Reasoning: stringField(parsed, "reasoning")} + if result.Text == "" { + return providerTextResult{}, errors.New("流式文本接口没有返回内容") + } + return result, nil +} + +func extractTextPayload(payload map[string]interface{}, protocol string) string { + if protocol == "claude-api" { + content, _ := payload["content"].([]interface{}) + var result strings.Builder + for _, item := range content { + record, _ := item.(map[string]interface{}) + if stringField(record, "type") == "text" { + result.WriteString(stringField(record, "text")) + } + } + return result.String() + } + if protocol == "responses" { + text := stringField(payload, "output_text") + if text == "" { + text = extractResponseText(payload) + } + return text + } + return extractChatCompletionText(payload) +} + +func validateTextPayload(payload map[string]interface{}) error { + if code, ok := payload["code"].(float64); ok && code != 0 { + rawMessage := defaultString(stringField(payload, "msg"), "请求失败") + return providerPayloadError{raw: rawMessage, message: providerPayloadErrorMessage(rawMessage)} + } + if errValue, ok := payload["error"].(map[string]interface{}); ok { + if message := stringField(errValue, "message"); message != "" { + return providerPayloadError{raw: message, message: providerPayloadErrorMessage(message)} + } + } + return nil +} + +func parseTextEventStream(data []byte, protocol string) (string, error) { + scanner := bufio.NewScanner(bytes.NewReader(data)) + scanner.Buffer(make([]byte, 64<<10), len(data)+1) + var text strings.Builder + var eventName string + var dataLines []string + + flush := func() error { + if len(dataLines) == 0 { + eventName = "" + return nil + } + raw := strings.TrimSpace(strings.Join(dataLines, "\n")) + dataLines = nil + if raw == "" || raw == "[DONE]" { + eventName = "" + return nil + } + var payload map[string]interface{} + if err := json.Unmarshal([]byte(raw), &payload); err != nil { + return fmt.Errorf("流式文本事件解析失败:%w", err) + } + if eventName == "error" { + if err := validateTextPayload(payload); err != nil { + return err + } + return errors.New("上游流式文本请求失败") + } + if err := validateTextPayload(payload); err != nil { + return err + } + if protocol == "responses" { + text.WriteString(stringField(payload, "delta")) + } else if protocol == "claude-api" { + delta, _ := payload["delta"].(map[string]interface{}) + if stringField(delta, "type") == "text_delta" { + text.WriteString(stringField(delta, "text")) + } + } else { + choices, _ := payload["choices"].([]interface{}) + for _, choice := range choices { + record, _ := choice.(map[string]interface{}) + delta, _ := record["delta"].(map[string]interface{}) + text.WriteString(streamContentText(delta["content"])) + } + } + eventName = "" + return nil + } + + for scanner.Scan() { + line := strings.TrimSuffix(scanner.Text(), "\r") + if line == "" { + if err := flush(); err != nil { + return "", err + } + continue + } + switch { + case strings.HasPrefix(line, "event:"): + eventName = strings.TrimSpace(strings.TrimPrefix(line, "event:")) + case strings.HasPrefix(line, "data:"): + value := strings.TrimPrefix(line, "data:") + dataLines = append(dataLines, strings.TrimPrefix(value, " ")) + } + } + if err := scanner.Err(); err != nil { + return "", fmt.Errorf("读取流式文本响应失败:%w", err) + } + if err := flush(); err != nil { + return "", err + } + if text.Len() == 0 { + return "", errors.New("流式文本接口没有返回内容") + } + return text.String(), nil +} + +func streamContentText(value interface{}) string { + if text, ok := value.(string); ok { + return text + } + parts, ok := value.([]interface{}) + if !ok { + return "" + } + var result strings.Builder + for _, part := range parts { + record, _ := part.(map[string]interface{}) + result.WriteString(stringField(record, "text")) + } + return result.String() +} + +type streamingTextDeltaParser struct { + protocol string + buffer string + emit func(string) +} + +func newStreamingTextDeltaParser(protocol string, emit func(string)) *streamingTextDeltaParser { + return &streamingTextDeltaParser{protocol: protocol, emit: emit} +} + +func (p *streamingTextDeltaParser) consume(mimeType string, chunk []byte) { + if p == nil || p.emit == nil || !strings.Contains(strings.ToLower(mimeType), "event-stream") || len(chunk) == 0 { + return + } + p.buffer += string(chunk) + p.consumeFrames(false) +} + +func (p *streamingTextDeltaParser) flush() { + if p == nil || p.emit == nil { + return + } + p.consumeFrames(true) +} + +func (p *streamingTextDeltaParser) consumeFrames(flush bool) { + for { + match := sseFrameBoundaryPattern.FindStringIndex(p.buffer) + if match == nil { + break + } + p.consumeFrame(p.buffer[:match[0]]) + p.buffer = p.buffer[match[1]:] + } + if flush && strings.TrimSpace(p.buffer) != "" { + p.consumeFrame(p.buffer) + p.buffer = "" + } +} + +func (p *streamingTextDeltaParser) consumeFrame(frame string) { + var eventName string + var dataLines []string + for _, line := range strings.Split(strings.ReplaceAll(frame, "\r\n", "\n"), "\n") { + switch { + case strings.HasPrefix(line, "event:"): + eventName = strings.TrimSpace(strings.TrimPrefix(line, "event:")) + case strings.HasPrefix(line, "data:"): + dataLines = append(dataLines, strings.TrimSpace(strings.TrimPrefix(line, "data:"))) + } + } + raw := strings.TrimSpace(strings.Join(dataLines, "\n")) + if raw == "" || raw == "[DONE]" { + return + } + var payload map[string]interface{} + if json.Unmarshal([]byte(raw), &payload) != nil { + return + } + if delta := streamingTextDelta(p.protocol, eventName, payload); delta != "" { + p.emit(delta) + } +} + +func streamingTextDelta(protocol string, eventName string, payload map[string]interface{}) string { + if protocol == "responses" { + eventType := firstNonEmptyString(strings.TrimSpace(eventName), stringField(payload, "type")) + if eventType == "response.output_text.delta" || eventType == "output_text.delta" || eventType == "" || eventType == "message" { + return stringField(payload, "delta") + } + return "" + } + if protocol == "claude-api" { + delta, _ := payload["delta"].(map[string]interface{}) + if stringField(delta, "type") == "text_delta" { + return stringField(delta, "text") + } + return "" + } + choices, _ := payload["choices"].([]interface{}) + var text strings.Builder + for _, choice := range choices { + record, _ := choice.(map[string]interface{}) + delta, _ := record["delta"].(map[string]interface{}) + text.WriteString(streamContentText(delta["content"])) + } + return text.String() +} + +func extractResponseText(payload map[string]interface{}) string { + output, ok := payload["output"].([]interface{}) + if !ok { + return "" + } + var chunks []string + for _, item := range output { + record, ok := item.(map[string]interface{}) + if !ok || record["type"] != "message" { + continue + } + content, _ := record["content"].([]interface{}) + for _, part := range content { + partRecord, ok := part.(map[string]interface{}) + if ok && stringField(partRecord, "text") != "" { + chunks = append(chunks, stringField(partRecord, "text")) + } + } + } + return strings.Join(chunks, "") +} + +func extractChatCompletionText(payload map[string]interface{}) string { + if data, ok := payload["data"].(map[string]interface{}); ok { + payload = data + } + choices, ok := payload["choices"].([]interface{}) + if !ok { + return "" + } + var chunks []string + for _, choice := range choices { + record, ok := choice.(map[string]interface{}) + if !ok { + continue + } + if message, ok := record["message"].(map[string]interface{}); ok { + if text := stringField(message, "content"); text != "" { + chunks = append(chunks, text) + } + } + if text := stringField(record, "text"); text != "" { + chunks = append(chunks, text) + } + } + return strings.Join(chunks, "") +} diff --git a/backend/internal/service/provider_video.go b/backend/internal/service/provider_video.go new file mode 100644 index 000000000..1274f7625 --- /dev/null +++ b/backend/internal/service/provider_video.go @@ -0,0 +1,594 @@ +package service + +// 视频生成遗留手写路径;已有官方插件的接口类型走声明式协议。 + +import ( + "bytes" + "context" + "errors" + "fmt" + "mime/multipart" + "strconv" + "strings" + "time" + + "infinite-canvas/backend/internal/model" +) + +func (s *Service) validateResolvedVideoCapability(input *canvasGenerationInput) error { + channelID := strings.TrimSpace(input.Config.ChannelID) + if channelID == "" { + profile := input.Config.CapabilityConfig + if profile == nil || profile.Video == nil { + if input.Config.InterfaceType != string(model.ChannelInterfaceAgnesVideo) { + return nil + } + profile = DefaultModelCapabilityConfigForModel(input.Config.InterfaceType, input.Config.Model) + } + normalized, err := NormalizeModelCapabilityConfigForModel("video", input.Config.InterfaceType, input.Config.Model, profile) + if err != nil || normalized == nil || normalized.Video == nil { + return errors.New("当前视频模型能力参数无效") + } + input.Config.CapabilityConfig = normalized + input.VideoCapability = normalized.Video + applyFixedVideoResolution(input, normalized.Video) + return validateVideoTask(normalized.Video, *input) + } + item, err := s.repo.ChannelModelByKey(channelID, providerChannelModelKey(input.Config)) + if err != nil { + return errors.New("当前系统渠道模型未配置或已停用") + } + profile, err := DecodeModelCapabilityConfig(item.CapabilityConfigJSON) + if err != nil || profile == nil || profile.Video == nil { + return errors.New("当前视频模型尚未配置能力参数") + } + normalized, err := NormalizeModelCapabilityConfigForModel("video", string(item.Protocol), firstNonEmpty(item.ProviderModelKey, item.ModelKey), profile) + if err != nil || normalized == nil || normalized.Video == nil { + return errors.New("当前视频模型能力参数无效") + } + input.VideoCapability = normalized.Video + applyFixedVideoResolution(input, normalized.Video) + return validateVideoTask(normalized.Video, *input) +} + +func runVideoTask(ctx context.Context, input canvasGenerationInput) (map[string]interface{}, error) { + // 路由顺序是协议边界,不是“哪个请求先试”:官方声明式接口必须由已注册适配器执行, + // 缺少适配器时直接失败,不能偷偷退回遗留手写协议;只有未声明为官方插件的旧渠道才继续走兼容分支。 + if strings.TrimSpace(input.Mode) == "" { + input.Mode = "video" + } + // 已有官方声明式插件的 InterfaceType 只走适配器。未注入 registry 时补官方包, + // 显式空 registry 则报“插件未安装”,不再回退到手写协议。 + ctx = ensureOfficialProtocolAdapter(ctx, input.Config.InterfaceType) + if _, ok := declarativeProtocolAdapterForContext(ctx, input.Config.InterfaceType); ok { + return runDeclarativeProtocolTask(ctx, input) + } + if label, official := officialDeclarativeVideoInterface(input.Config.InterfaceType); official { + return nil, fmt.Errorf("%s 视频插件未安装", label) + } + if isArkPlanVideoConfig(input.Config) { + return runSeedanceAgentPlanVideoTask(ctx, input) + } + if isSeedanceVideoConfig(input.Config) { + return runSeedanceVideosTask(ctx, input) + } + if len(input.ReferenceVideos) > 0 || len(input.ReferenceAudios) > 0 { + return nil, errors.New("OpenAI 风格视频接口不支持参考视频或参考音频,请切换到 Seedance / Agent Plan 渠道") + } + id := resumedProviderRequestID(ctx) + var created map[string]interface{} + if id == "" && isGrokVideoConfig(input.Config) { + requestBody, err := grokVideoBody(input) + if err != nil { + return nil, err + } + if err := postJSON(ctx, input.Config, "/videos", requestBody, &created); err != nil { + return nil, err + } + } else if id == "" { + body := &bytes.Buffer{} + writer := multipart.NewWriter(body) + writeField(writer, "model", input.Config.Model) + writeField(writer, "prompt", newAPIVideoPromptText(input)) + writeField(writer, "seconds", defaultString(input.Config.VideoSeconds, "6")) + if size := normalizeVideoSize(input.Config.Size); size != "" { + writeField(writer, "size", size) + } + if resolution := videoResolutionNameRequest(input.VideoCapability, input.Config.VQuality); resolution != "" { + writeField(writer, "resolution_name", resolution) + } + writeField(writer, "preset", "normal") + if shouldSendNewAPIVideoImages(input) { + for _, image := range input.ReferenceImages { + if err := writeMediaPart(writer, "input_reference[]", image); err != nil { + return nil, err + } + } + } + if err := writer.Close(); err != nil { + return nil, err + } + if err := postForm(ctx, input.Config, "/videos", writer.FormDataContentType(), body, &created); err != nil { + return nil, err + } + } + if id == "" { + extracted, err := firstJSONString(created, "id", "request_id", "task_id") + if err != nil { + return nil, fmt.Errorf("视频接口任务 ID 无效:%w", err) + } + id = extracted + } + if id == "" { + if data, ok := created["data"].(map[string]interface{}); ok { + extracted, err := firstJSONString(data, "id", "request_id", "task_id") + if err != nil { + return nil, fmt.Errorf("视频接口任务 ID 无效:%w", err) + } + id = extracted + } + } + if id == "" { + return nil, errors.New("视频接口没有返回任务 ID") + } + for deadline := providerPollingDeadline(ctx); time.Now().Before(deadline); { + var state map[string]interface{} + if err := getJSON(ctx, input.Config, "/videos/"+id, &state); err != nil { + return nil, err + } + if data, ok := state["data"].(map[string]interface{}); ok { + state = data + } + status := strings.ToLower(stringField(state, "status")) + if status == "completed" || status == "succeeded" || status == "success" || status == "done" { + if videoURL := newAPIVideoResultURL(state); videoURL != "" { + data, mimeType, err := getProviderExternalBinary(withProviderRequestKind(ctx, "download"), input.Config, videoURL) + if err != nil { + return nil, fmt.Errorf("视频结果下载失败(任务 %s):%w", id, err) + } + mimeType = normalizedMediaMimeType(mimeType, data) + return map[string]interface{}{"mode": "video", "video": map[string]interface{}{"dataUrl": dataURL(mimeType, data), "mimeType": mimeType}}, nil + } + data, mimeType, err := getBinary(ctx, input.Config, "/videos/"+id+"/content") + if err != nil { + return nil, err + } + return map[string]interface{}{"mode": "video", "video": map[string]interface{}{"dataUrl": dataURL(mimeType, data), "mimeType": mimeType}}, nil + } + if status == "failed" || status == "cancelled" { + return nil, errors.New("视频生成失败") + } + if err := sleepContext(ctx, 2500*time.Millisecond); err != nil { + return nil, err + } + } + return nil, errors.New("视频生成超时") +} + +func newAPIVideoResultURL(state map[string]interface{}) string { + return nestedNewAPIVideoResultURL(state, false, 0) +} + +func nestedNewAPIVideoResultURL(payload map[string]interface{}, allowResultURL bool, depth int) string { + if depth < 2 { + for _, key := range []string{"data", "result", "video"} { + if nested, ok := payload[key].(map[string]interface{}); ok { + if videoURL := nestedNewAPIVideoResultURL(nested, true, depth+1); videoURL != "" { + return videoURL + } + } + } + } + keys := []string{"video_url", "videoUrl", "url"} + if allowResultURL { + keys = append(keys, "result_url", "resultUrl") + } + for _, key := range keys { + if videoURL := strings.TrimSpace(stringField(payload, key)); isPublicMediaURL(videoURL) { + return videoURL + } + } + return "" +} + +func grokVideoBody(input canvasGenerationInput) (map[string]interface{}, error) { + seconds := defaultString(input.Config.VideoSeconds, "6") + duration, err := strconv.Atoi(seconds) + if err != nil || duration <= 0 { + duration = 6 + } + body := map[string]interface{}{ + "model": input.Config.Model, + "prompt": strings.TrimSpace(input.Prompt), + "duration": duration, + "seconds": strconv.Itoa(duration), + } + if size := normalizeVideoSize(input.Config.Size); size != "" { + body["size"] = size + } + if shouldSendNewAPIVideoImages(input) && len(input.ReferenceImages) > 0 { + images := make([]string, 0, len(input.ReferenceImages)) + for _, image := range input.ReferenceImages { + url, err := openAIImageInputURL(image) + if err != nil { + return nil, err + } + images = append(images, url) + } + body["image"] = images[0] + body["images"] = images + } + return body, nil +} + +func runSeedanceVideosTask(ctx context.Context, input canvasGenerationInput) (map[string]interface{}, error) { + // 恢复任务已有 provider ID 时只能继续查询,绝不能重新 create,否则会产生第二个计费任务。 + // create 成功后的 poll/download 任一失败都保留失败或结果未知语义,不降级成“成功但无内容”。 + id := resumedProviderRequestID(ctx) + var created map[string]interface{} + if id == "" { + body, err := seedanceVideosRequestBody(input) + if err != nil { + return nil, err + } + if err := postJSON(ctx, input.Config, "/videos", body, &created); err != nil { + return nil, err + } + if data, ok := created["data"].(map[string]interface{}); ok { + created = data + } + extracted, err := firstJSONString(created, "id", "task_id") + if err != nil { + return nil, fmt.Errorf("Seedance 接口任务 ID 无效:%w", err) + } + id = extracted + } + if id == "" { + return nil, errors.New("Seedance 接口没有返回任务 ID") + } + for deadline := providerPollingDeadline(ctx); time.Now().Before(deadline); { + var state map[string]interface{} + if err := getJSON(ctx, input.Config, "/videos/"+id, &state); err != nil { + return nil, err + } + if data, ok := state["data"].(map[string]interface{}); ok { + state = data + } + status := strings.ToLower(stringField(state, "status")) + if status == "completed" || status == "succeeded" { + videoURL := stringField(state, "video_url") + if videoURL != "" { + data, mimeType, err := getExternalBinary(withProviderRequestKind(ctx, "download"), videoURL) + if err != nil { + return nil, fmt.Errorf("视频结果下载失败:%w", err) + } + return map[string]interface{}{"mode": "video", "video": map[string]interface{}{"dataUrl": dataURL(mimeType, data), "mimeType": mimeType}}, nil + } + data, mimeType, err := getBinary(ctx, input.Config, "/videos/"+id+"/content") + if err != nil { + return nil, fmt.Errorf("Seedance 任务成功但未返回视频 URL,备用内容下载失败:%w", err) + } + return map[string]interface{}{"mode": "video", "video": map[string]interface{}{"dataUrl": dataURL(mimeType, data), "mimeType": mimeType}}, nil + } + if status == "failed" || status == "cancelled" || status == "expired" { + return nil, errors.New(defaultString(seedanceErrorMessage(state), "Seedance 视频生成失败")) + } + if err := sleepContext(ctx, 5*time.Second); err != nil { + return nil, err + } + } + return nil, errors.New("Seedance 视频生成超时") +} + +func runSeedanceAgentPlanVideoTask(ctx context.Context, input canvasGenerationInput) (map[string]interface{}, error) { + providerName := "Seedance" + if input.Config.InterfaceType == string(model.ChannelInterfaceVolcengineArkVideo) { + providerName = "火山方舟" + } + // Agent Plan 与 Videos API 使用相同的恢复合同:已有任务只查询,不重复创建。 + // 外部签名地址仍统一经过 doBinary 的 SSRF、状态码和响应大小检查。 + id := resumedProviderRequestID(ctx) + var created map[string]interface{} + if id == "" { + content, err := seedanceContent(input) + if err != nil { + return nil, err + } + if input.Config.InterfaceType == string(model.ChannelInterfaceVolcengineArkVideo) { + for _, item := range content { + if item["type"] == "image_url" { + item["role"] = "reference_image" + } + } + } + body := seedanceAgentPlanRequest{ + Model: input.Config.Model, + Content: content, + Ratio: normalizeSeedanceRatio(input.Config.Size), + Resolution: normalizeSeedanceResolution(input.Config.VQuality, input.Config.Model), + Duration: normalizeSeedanceDuration(input.Config.VideoSeconds), + } + if videoCapabilitySupportsAudio(input) { + value := parseBool(input.Config.VideoGenerateAudio, true) + body.GenerateAudio = &value + } + if videoCapabilitySupportsWatermark(input) { + value := parseBool(input.Config.VideoWatermark, false) + body.Watermark = &value + } + if err := postJSON(ctx, input.Config, "/contents/generations/tasks", body, &created); err != nil { + return nil, err + } + if data, ok := created["data"].(map[string]interface{}); ok { + created = data + } + extracted, err := firstJSONString(created, "id") + if err != nil { + return nil, fmt.Errorf("%s接口任务 ID 无效:%w", providerName, err) + } + id = extracted + } + if id == "" { + return nil, fmt.Errorf("%s接口没有返回任务 ID", providerName) + } + for deadline := providerPollingDeadline(ctx); time.Now().Before(deadline); { + var state map[string]interface{} + if err := getJSON(ctx, input.Config, "/contents/generations/tasks/"+id, &state); err != nil { + return nil, err + } + if data, ok := state["data"].(map[string]interface{}); ok { + state = data + } + status := strings.ToLower(strings.TrimSpace(stringField(state, "status"))) + if status == "succeeded" { + content, _ := state["content"].(map[string]interface{}) + videoURL := stringField(content, "video_url") + if videoURL == "" { + return nil, fmt.Errorf("%s任务成功但没有返回视频 URL", providerName) + } + data, mimeType, err := getExternalBinary(withProviderRequestKind(ctx, "download"), videoURL) + if err != nil { + return nil, fmt.Errorf("视频结果下载失败:%w", err) + } + return map[string]interface{}{"mode": "video", "video": map[string]interface{}{"dataUrl": dataURL(mimeType, data), "mimeType": mimeType}}, nil + } + if status == "failed" || status == "cancelled" || status == "expired" { + return nil, fmt.Errorf("%s视频生成失败", providerName) + } + if err := sleepContext(ctx, 5*time.Second); err != nil { + return nil, err + } + } + return nil, fmt.Errorf("%s视频生成超时", providerName) +} + +func seedanceContent(input canvasGenerationInput) ([]map[string]interface{}, error) { + content := make([]map[string]interface{}, 0, 1+len(input.ReferenceImages)+len(input.ReferenceVideos)+len(input.ReferenceAudios)) + text := seedancePromptText(input) + if strings.TrimSpace(text) != "" { + content = append(content, map[string]interface{}{"type": "text", "text": text}) + } + for _, image := range input.ReferenceImages { + url, err := mediaReferenceURL(image) + if err != nil { + return nil, err + } + content = append(content, map[string]interface{}{"type": "image_url", "image_url": map[string]interface{}{"url": url}, "role": videoImageRole(input, image)}) + } + for _, video := range input.ReferenceVideos { + url, err := mediaReferenceURL(video) + if err != nil { + return nil, err + } + content = append(content, map[string]interface{}{"type": "video_url", "video_url": map[string]interface{}{"url": url}, "role": "reference_video"}) + } + for _, audio := range input.ReferenceAudios { + url, err := mediaReferenceURL(audio) + if err != nil { + return nil, err + } + content = append(content, map[string]interface{}{"type": "audio_url", "audio_url": map[string]interface{}{"url": url}, "role": "reference_audio"}) + } + if len(content) == 0 { + return nil, errors.New("请输入视频提示词或连接参考素材") + } + return content, nil +} + +func shouldSendNewAPIVideoImages(input canvasGenerationInput) bool { + if input.Metadata == nil { + return true + } + operation, _ := input.Metadata["videoEditOperation"].(string) + return strings.TrimSpace(operation) != "text_to_video" +} + +// 本地测试 helper 没有能力配置时保留历史协议字段;真实系统任务会携带已解析的模型能力。 +func videoCapabilitySupportsAudio(input canvasGenerationInput) bool { + return input.VideoCapability == nil || input.VideoCapability.GenerateAudio.Supported +} + +func videoCapabilitySupportsWatermark(input canvasGenerationInput) bool { + return input.VideoCapability == nil || input.VideoCapability.Watermark.Supported +} + +func newAPIVideoPromptText(input canvasGenerationInput) string { + return strings.TrimSpace(input.Prompt) +} + +func seedanceVideosRequestBody(input canvasGenerationInput) (seedanceVideosRequest, error) { + if (len(input.ReferenceVideos) > 0 || len(input.ReferenceAudios) > 0) && len(input.ReferenceImages) == 0 { + return seedanceVideosRequest{}, errors.New("Seedance 参考视频或参考音频需要同时连接至少 1 张主参考图") + } + body := seedanceVideosRequest{ + Model: input.Config.Model, + Prompt: seedanceVideosPromptText(input), + AspectRatio: normalizeSeedanceVideosRatio(input.Config.Size), + Duration: normalizeSeedanceVideosDuration(input.Config.VideoSeconds), + } + if videoCapabilitySupportsAudio(input) { + value := parseBool(input.Config.VideoGenerateAudio, true) + body.GenerateAudio = &value + } + imageURLs := make([]string, 0, len(input.ReferenceImages)) + for _, image := range input.ReferenceImages { + url, err := openAIImageInputURL(image) + if err != nil { + return seedanceVideosRequest{}, err + } + imageURLs = append(imageURLs, url) + } + frameImageURLs, err := videoFrameImageURLs(input, imageURLs) + if err != nil { + return seedanceVideosRequest{}, err + } + if metadataString(input.Metadata, "videoEditOperation") == "reference_to_video" { + body.ReferenceImageURLs = imageURLs + } else if len(frameImageURLs) > 0 { + body.ImageURLs = frameImageURLs + } else if len(imageURLs) > 0 { + body.ImageURL = imageURLs[0] + if len(imageURLs) > 1 { + body.ReferenceImageURLs = imageURLs[1:] + } + } + videoURLs := make([]string, 0, len(input.ReferenceVideos)) + for _, video := range input.ReferenceVideos { + url, err := seedanceVideosMediaURL(video) + if err != nil { + return seedanceVideosRequest{}, err + } + videoURLs = append(videoURLs, url) + } + if len(videoURLs) > 0 { + body.ReferenceVideos = videoURLs + } + audioURLs := make([]string, 0, len(input.ReferenceAudios)) + for _, audio := range input.ReferenceAudios { + url, err := seedanceVideosMediaURL(audio) + if err != nil { + return seedanceVideosRequest{}, err + } + audioURLs = append(audioURLs, url) + } + if len(audioURLs) > 0 { + body.ReferenceAudios = audioURLs + } + return body, nil +} + +// 兼容旧的 map 断言调用;实际请求路径使用类型化 Seedance DTO。 +func seedanceVideosBody(input canvasGenerationInput) (map[string]interface{}, error) { + body, err := seedanceVideosRequestBody(input) + if err != nil { + return nil, err + } + return requestAsMap(body) +} + +func seedancePromptText(input canvasGenerationInput) string { + return strings.TrimSpace(input.Prompt) +} + +func seedanceVideosPromptText(input canvasGenerationInput) string { + return strings.TrimSpace(input.Prompt) +} + +func videoImageRole(input canvasGenerationInput, image providerMedia) string { + return videoImageRoleOrDefault(input, image, "reference_image") +} + +func videoImageRoleOrDefault(input canvasGenerationInput, image providerMedia, fallback string) string { + if metadataString(input.Metadata, "videoEditOperation") == "reference_to_video" { + return "reference_image" + } + if id := metadataString(input.Metadata, "videoStartFrameNodeId"); id != "" && image.ID == id { + return "first_frame" + } + if id := metadataString(input.Metadata, "videoEndFrameNodeId"); id != "" && image.ID == id { + return "last_frame" + } + return fallback +} + +func videoFrameImageURLs(input canvasGenerationInput, imageURLs []string) ([]string, error) { + if metadataString(input.Metadata, "videoEditOperation") == "reference_to_video" { + return nil, nil + } + startFrameID := metadataString(input.Metadata, "videoStartFrameNodeId") + endFrameID := metadataString(input.Metadata, "videoEndFrameNodeId") + if startFrameID == "" && endFrameID == "" { + return nil, nil + } + // image_urls 按首帧、尾帧、普通参考图排序,保持 JSON 视频协议的结构化帧语义。 + ordered := make([]string, 0, len(imageURLs)) + used := make([]bool, len(imageURLs)) + appendFrame := func(frameID string, label string) error { + if frameID == "" { + return nil + } + for index, image := range input.ReferenceImages { + if index >= len(imageURLs) || image.ID != frameID { + continue + } + ordered = append(ordered, imageURLs[index]) + used[index] = true + return nil + } + return fmt.Errorf("已配置的%s参考图未包含在视频请求中", label) + } + if err := appendFrame(startFrameID, "首帧"); err != nil { + return nil, err + } + if err := appendFrame(endFrameID, "尾帧"); err != nil { + return nil, err + } + for index, imageURL := range imageURLs { + if !used[index] { + ordered = append(ordered, imageURL) + } + } + return ordered, nil +} + +func mediaReferenceURL(media providerMedia) (string, error) { + value := strings.TrimSpace(media.URL) + if isPublicMediaURL(value) || strings.HasPrefix(value, "asset://") || strings.HasPrefix(value, "data:") { + return value, nil + } + value = strings.TrimSpace(media.DataURL) + if value != "" { + return value, nil + } + return "", errors.New("参考素材需要公网 URL、asset:// 素材 ID 或 data URL") +} + +func seedanceVideosMediaURL(media providerMedia) (string, error) { + value := strings.TrimSpace(media.DataURL) + if strings.HasPrefix(value, "data:") { + return value, nil + } + value = strings.TrimSpace(media.URL) + if strings.HasPrefix(value, "data:") || isPublicMediaURL(value) { + return value, nil + } + return "", errors.New("Seedance /videos 参考素材需要公网 URL 或 data URL") +} + +func seedanceErrorMessage(state map[string]interface{}) string { + if errorValue, ok := state["error"].(map[string]interface{}); ok { + message := stringField(errorValue, "message") + code := stringField(errorValue, "code") + if message != "" && code != "" { + return code + ":" + message + } + if message != "" { + return message + } + } + code := stringField(state, "error_code") + if code != "" { + return code + } + return "" +} diff --git a/backend/internal/service/provider_video_options.go b/backend/internal/service/provider_video_options.go index 057cb31dc..4dbc47464 100644 --- a/backend/internal/service/provider_video_options.go +++ b/backend/internal/service/provider_video_options.go @@ -206,75 +206,6 @@ func isAutomaticVideoResolution(value string) bool { } } -func normalizeXAIVideoDuration(value string) int { - duration, err := strconv.Atoi(strings.TrimSpace(value)) - if err != nil || duration <= 0 { - return 6 - } - return duration -} - -func normalizeXAIVideoResolution(value string) string { - switch strings.ToLower(strings.TrimSpace(value)) { - case "480", "480p", "low": - return "480p" - case "1080", "1080p": - return "1080p" - case "1440", "1440p", "2k": - return "1440p" - case "2160", "2160p", "4k": - return "2160p" - default: - return "720p" - } -} - -func normalizeXAIVideoAspectRatio(value string) string { - value = strings.ToLower(strings.TrimSpace(value)) - allowed := map[string]bool{ - "1:1": true, "16:9": true, "9:16": true, "4:3": true, - "3:4": true, "3:2": true, "2:3": true, - } - if allowed[value] { - return value - } - parts := strings.Split(value, "x") - if len(parts) != 2 { - return "16:9" - } - width, widthErr := strconv.Atoi(strings.TrimSpace(parts[0])) - height, heightErr := strconv.Atoi(strings.TrimSpace(parts[1])) - if widthErr != nil || heightErr != nil || width <= 0 || height <= 0 { - return "16:9" - } - ratio := float64(width) / float64(height) - candidates := []struct { - name string - ratio float64 - }{ - {name: "1:1", ratio: 1}, - {name: "16:9", ratio: 16.0 / 9}, - {name: "9:16", ratio: 9.0 / 16}, - {name: "4:3", ratio: 4.0 / 3}, - {name: "3:4", ratio: 3.0 / 4}, - {name: "3:2", ratio: 3.0 / 2}, - {name: "2:3", ratio: 2.0 / 3}, - } - bestName := "16:9" - bestDifference := 2.0 - for _, candidate := range candidates { - difference := ratio - candidate.ratio - if difference < 0 { - difference = -difference - } - if difference < bestDifference { - bestName = candidate.name - bestDifference = difference - } - } - return bestName -} - func normalizeSeedanceDuration(value string) int { if strings.TrimSpace(value) == "-1" { return -1 diff --git a/backend/internal/service/resource.go b/backend/internal/service/resource.go index 6f9bb7235..752a17844 100644 --- a/backend/internal/service/resource.go +++ b/backend/internal/service/resource.go @@ -320,8 +320,9 @@ func (s *Service) uploadResource(userID string, header *multipart.FileHeader, ki return resource, err } -// UploadResourceFile 接收已完整落盘的本地文件(分片上传合并后调用),语义与 UploadResource 一致; -// 唯一差异是豁免“单文件大小上限”——分片会话已在 handler 按片校验,此处只受日上传与账号存储总量约束。 +// UploadResourceFile 接收已完整落盘的本地文件(分片上传合并后调用)。 +// 它与 UploadResource 共享资源幂等、媒体探测、配额和持久化语义,唯一差异是分片会话已在 handler 校验单文件上限, +// 因而此处不再重复该上限检查;uploadIdentity 用于跨请求重试时复用同一逻辑资源,避免重复对象。 func (s *Service) UploadResourceFile(userID string, fileName string, size int64, kind string, width int, height int, durationMs int64, file io.ReadSeeker, uploadIdentity ...string) (*model.Resource, error) { if file == nil || size <= 0 { return nil, BadAuthRequest("请选择要上传的文件") @@ -779,7 +780,7 @@ func (s *Service) persistGeneratedMediaValueMode(userID string, value interface{ if enforceQuota { s.commitUserUploadQuota(userID, int64(len(data))) } - resourceURL := "/api/resources/" + resource.ID + "/file" + resourceURL := resourceFileURL(resource.ID) for _, key := range []string{"dataUrl", "content", "url", "coverUrl"} { if text, ok := item[key].(string); ok && (text == raw || strings.HasPrefix(text, "blob:")) { item[key] = resourceURL diff --git a/backend/internal/service/resource_url.go b/backend/internal/service/resource_url.go new file mode 100644 index 000000000..927f35a30 --- /dev/null +++ b/backend/internal/service/resource_url.go @@ -0,0 +1,28 @@ +package service + +import "strings" + +func resourceFileURL(id string) string { + return "/api/resources/" + id + "/file" +} + +func isResourceFileURL(value string) bool { + return resourceIDFromFileURL(value) != "" +} + +func resourceIDFromFileURL(value string) string { + const prefix = "/api/resources/" + value = strings.TrimSpace(value) + index := strings.Index(value, prefix) + if index < 0 { + return "" + } + remainder := value[index+len(prefix):] + if remainder == "" { + return "" + } + if end := strings.IndexAny(remainder, "/?#"); end >= 0 { + remainder = remainder[:end] + } + return remainder +} diff --git a/backend/internal/service/resource_url_test.go b/backend/internal/service/resource_url_test.go new file mode 100644 index 000000000..59988a06a --- /dev/null +++ b/backend/internal/service/resource_url_test.go @@ -0,0 +1,20 @@ +package service + +import "testing" + +func TestResourceIDFromFileURL(t *testing.T) { + cases := []struct { + in string + want string + }{ + {"/api/resources/abc_123/file", "abc_123"}, + {"https://host.example/api/resources/abc_123/file?proxy=1", "abc_123"}, + {"/api/projects/abc_123", ""}, + {"", ""}, + } + for _, item := range cases { + if got := resourceIDFromFileURL(item.in); got != item.want { + t.Fatalf("resourceIDFromFileURL(%q) = %q, want %q", item.in, got, item.want) + } + } +} diff --git a/backend/internal/service/skill_packages.go b/backend/internal/service/skill_packages.go index 8d65a0847..7a68cee54 100644 --- a/backend/internal/service/skill_packages.go +++ b/backend/internal/service/skill_packages.go @@ -49,14 +49,14 @@ type SkillGitHubInstallRequest struct { Ref string `json:"ref"` Subdir string `json:"subdir"` Tag string `json:"tag"` - IsPrivate bool `json:"is_private"` - AutoUpdate bool `json:"auto_update"` + IsPrivate bool `json:"isPrivate"` + AutoUpdate bool `json:"autoUpdate"` } type SkillPackageFileItem struct { Path string `json:"path"` Kind string `json:"kind"` - MimeType string `json:"mime_type"` + MimeType string `json:"mimeType"` Size int64 `json:"size"` SHA256 string `json:"sha256"` } @@ -69,17 +69,17 @@ type SkillPackageFileContent struct { type SkillPackageBundleFile struct { Path string `json:"path"` - MimeType string `json:"mime_type"` - ContentBase64 string `json:"content_base64"` + MimeType string `json:"mimeType"` + ContentBase64 string `json:"contentBase64"` } type SkillPackageBundle struct { - SkillID string `json:"skill_id"` + SkillID string `json:"skillId"` Name string `json:"name"` Description string `json:"description"` - VersionID string `json:"version_id"` + VersionID string `json:"versionId"` Version string `json:"version"` - ContentHash string `json:"content_hash"` + ContentHash string `json:"contentHash"` Files []SkillPackageBundleFile `json:"files"` } diff --git a/backend/internal/service/skills.go b/backend/internal/service/skills.go index d7e291824..0561ec1a8 100644 --- a/backend/internal/service/skills.go +++ b/backend/internal/service/skills.go @@ -31,55 +31,55 @@ var skillCategoryLabels = map[string]string{ type SkillShowcaseMedia struct { Type string `json:"type"` - ShowcaseURI string `json:"showcase_uri"` - ShowcaseURL string `json:"showcase_url"` + ShowcaseURI string `json:"showcaseUri"` + ShowcaseURL string `json:"showcaseUrl"` } type SkillEffectiveUser struct { Name string `json:"name"` - AvatarURL string `json:"avatar_url"` + AvatarURL string `json:"avatarUrl"` UID string `json:"uid"` } type SkillItem struct { - SkillID string `json:"skill_id"` - SkillName string `json:"skill_name"` + SkillID string `json:"skillId"` + SkillName string `json:"skillName"` Description string `json:"description"` Instruction string `json:"instruction,omitempty"` - VersionID string `json:"version_id"` + VersionID string `json:"versionId"` Version string `json:"version"` - ContentHash string `json:"content_hash"` - FileCount int `json:"file_count"` - TotalBytes int64 `json:"total_bytes"` - SourceType string `json:"source_type"` - SourceURL string `json:"source_url"` - SourceRef string `json:"source_ref"` - SourceSubdir string `json:"source_subdir"` - SourceCommit string `json:"source_commit"` - SyncStatus string `json:"sync_status"` - SyncError string `json:"sync_error,omitempty"` - AutoUpdate bool `json:"auto_update"` - LastCheckedAt int64 `json:"last_checked_at"` - LastSyncedAt int64 `json:"last_synced_at"` + ContentHash string `json:"contentHash"` + FileCount int `json:"fileCount"` + TotalBytes int64 `json:"totalBytes"` + SourceType string `json:"sourceType"` + SourceURL string `json:"sourceUrl"` + SourceRef string `json:"sourceRef"` + SourceSubdir string `json:"sourceSubdir"` + SourceCommit string `json:"sourceCommit"` + SyncStatus string `json:"syncStatus"` + SyncError string `json:"syncError,omitempty"` + AutoUpdate bool `json:"autoUpdate"` + LastCheckedAt *time.Time `json:"lastCheckedAt,omitempty"` + LastSyncedAt *time.Time `json:"lastSyncedAt,omitempty"` Status int `json:"status"` - MarkdownURL string `json:"markdown_url"` - CreateTime int64 `json:"create_time"` - UpdateTime int64 `json:"update_time"` + MarkdownURL string `json:"markdownUrl"` + CreatedAt time.Time `json:"createdAt"` + UpdatedAt time.Time `json:"updatedAt"` Source int `json:"source"` Tag string `json:"tag"` - SortWeight int `json:"sort_weight"` - IsPrivate bool `json:"is_private"` - LikeCount int64 `json:"like_count"` - IsLike bool `json:"is_like"` - OwnerUID string `json:"owner_uid"` - EffectiveUser SkillEffectiveUser `json:"effective_user"` - OriginalSkillID *string `json:"original_skill_id"` - ShowcaseMedia []SkillShowcaseMedia `json:"showcase_media"` - AddedCount int64 `json:"added_count"` - IsTest bool `json:"is_test"` - ExtraInfo string `json:"extra_info"` - IsAdded bool `json:"is_added"` - IsOwner bool `json:"is_owner"` + SortWeight int `json:"sortWeight"` + IsPrivate bool `json:"isPrivate"` + LikeCount int64 `json:"likeCount"` + IsLike bool `json:"isLike"` + OwnerUID string `json:"ownerUid"` + EffectiveUser SkillEffectiveUser `json:"effectiveUser"` + OriginalSkillID *string `json:"originalSkillId"` + ShowcaseMedia []SkillShowcaseMedia `json:"showcaseMedia"` + AddedCount int64 `json:"addedCount"` + IsTest bool `json:"isTest"` + ExtraInfo string `json:"extraInfo"` + IsAdded bool `json:"isAdded"` + IsOwner bool `json:"isOwner"` } type SkillCategory struct { @@ -98,23 +98,23 @@ type SkillListRequest struct { type SkillList struct { Skills []SkillItem `json:"skills"` - TotalCount int64 `json:"total_count"` - HasMore bool `json:"has_more"` - NextOffset int `json:"next_offset"` + TotalCount int64 `json:"totalCount"` + HasMore bool `json:"hasMore"` + NextOffset int `json:"nextOffset"` Page int `json:"page"` - PageSize int `json:"page_size"` + PageSize int `json:"pageSize"` Categories []SkillCategory `json:"categories"` } type SkillMutationRequest struct { - SkillName string `json:"skill_name"` + SkillName string `json:"skillName"` Description string `json:"description"` Instruction string `json:"instruction"` Tag string `json:"tag"` - IsPrivate bool `json:"is_private"` - MarkdownURL string `json:"markdown_url"` - ShowcaseMedia []SkillShowcaseMedia `json:"showcase_media"` - ExtraInfo string `json:"extra_info"` + IsPrivate bool `json:"isPrivate"` + MarkdownURL string `json:"markdownUrl"` + ShowcaseMedia []SkillShowcaseMedia `json:"showcaseMedia"` + ExtraInfo string `json:"extraInfo"` } func (s *Service) Skills(userID string, req SkillListRequest) (*SkillList, error) { @@ -297,9 +297,9 @@ func (s *Service) skillItems(userID string, skills []model.Skill, includeInstruc } items := make([]SkillItem, 0, len(skills)) for _, skill := range skills { - var showcaseMedia []SkillShowcaseMedia - if err := json.Unmarshal([]byte(skill.ShowcaseMediaJSON), &showcaseMedia); err != nil { - return nil, errors.New("技能展示媒体数据格式错误") + showcaseMedia, err := parseShowcaseMedia(skill.ShowcaseMediaJSON) + if err != nil { + return nil, err } owner := owners[skill.OwnerID] ownerName := strings.TrimSpace(skill.AuthorName) @@ -324,8 +324,8 @@ func (s *Service) skillItems(userID string, skills []model.Skill, includeInstruc SkillID: skill.ID, SkillName: skill.Name, Description: skill.Description, Instruction: instruction, VersionID: skill.CurrentVersionID, Version: skill.VersionLabel, ContentHash: skill.ContentHash, FileCount: skill.FileCount, TotalBytes: skill.TotalBytes, SourceType: skill.SourceType, SourceURL: skill.SourceURL, SourceRef: skill.SourceRef, SourceSubdir: skill.SourceSubdir, SourceCommit: skill.SourceCommit, - SyncStatus: skill.SyncStatus, SyncError: skill.SyncError, AutoUpdate: skill.AutoUpdate, LastCheckedAt: unixMillis(skill.LastCheckedAt), LastSyncedAt: unixMillis(skill.LastSyncedAt), - Status: skill.Status, MarkdownURL: skill.MarkdownURL, CreateTime: skill.CreatedAt.UnixMilli(), UpdateTime: skill.UpdatedAt.UnixMilli(), + SyncStatus: skill.SyncStatus, SyncError: skill.SyncError, AutoUpdate: skill.AutoUpdate, LastCheckedAt: skill.LastCheckedAt, LastSyncedAt: skill.LastSyncedAt, + Status: skill.Status, MarkdownURL: skill.MarkdownURL, CreatedAt: skill.CreatedAt, UpdatedAt: skill.UpdatedAt, Source: skill.Source, Tag: skill.Tag, SortWeight: skill.SortWeight, IsPrivate: skill.IsPrivate, LikeCount: metric.LikeCount, IsLike: state.Liked, OwnerUID: skill.OwnerID, EffectiveUser: SkillEffectiveUser{Name: ownerName, AvatarURL: ownerAvatarURL, UID: skill.OwnerID}, ShowcaseMedia: showcaseMedia, @@ -471,9 +471,39 @@ func skillCategories() []SkillCategory { } } -func unixMillis(value *time.Time) int64 { - if value == nil { - return 0 +// parseShowcaseMedia 读取库内展示媒体 JSON。历史记录可能仍是 snake_case,只在持久化层转换;对外 API 只输出 camelCase。 +func parseShowcaseMedia(raw string) ([]SkillShowcaseMedia, error) { + raw = strings.TrimSpace(raw) + if raw == "" || raw == "null" { + return []SkillShowcaseMedia{}, nil } - return value.UnixMilli() + var items []SkillShowcaseMedia + if err := json.Unmarshal([]byte(raw), &items); err != nil { + return nil, errors.New("技能展示媒体数据格式错误") + } + if showcaseMediaHasURL(items) { + return items, nil + } + var legacy []struct { + Type string `json:"type"` + ShowcaseURI string `json:"showcase_uri"` + ShowcaseURL string `json:"showcase_url"` + } + if err := json.Unmarshal([]byte(raw), &legacy); err != nil { + return nil, errors.New("技能展示媒体数据格式错误") + } + converted := make([]SkillShowcaseMedia, 0, len(legacy)) + for _, item := range legacy { + converted = append(converted, SkillShowcaseMedia{Type: item.Type, ShowcaseURI: item.ShowcaseURI, ShowcaseURL: item.ShowcaseURL}) + } + return converted, nil +} + +func showcaseMediaHasURL(items []SkillShowcaseMedia) bool { + for _, item := range items { + if strings.TrimSpace(item.ShowcaseURI) != "" || strings.TrimSpace(item.ShowcaseURL) != "" { + return true + } + } + return len(items) == 0 } diff --git a/backend/internal/service/skills_json_test.go b/backend/internal/service/skills_json_test.go new file mode 100644 index 000000000..41ba0ccc0 --- /dev/null +++ b/backend/internal/service/skills_json_test.go @@ -0,0 +1,60 @@ +package service + +import ( + "encoding/json" + "strings" + "testing" + "time" +) + +func TestSkillItemJSONUsesCamelCaseAndRFC3339(t *testing.T) { + checked := time.Date(2026, 9, 9, 3, 26, 0, 0, time.UTC) + item := SkillItem{ + SkillID: "skill-1", SkillName: "镜头拆解", VersionID: "v1", ContentHash: "hash", + CreatedAt: time.Date(2026, 1, 2, 3, 4, 5, 0, time.UTC), UpdatedAt: time.Date(2026, 1, 3, 4, 5, 6, 0, time.UTC), + LastCheckedAt: &checked, EffectiveUser: SkillEffectiveUser{Name: "用户", AvatarURL: "https://example.com/a.png", UID: "u1"}, + ShowcaseMedia: []SkillShowcaseMedia{{Type: "image", ShowcaseURI: "uri", ShowcaseURL: "https://example.com/m.png"}}, + } + body, err := json.Marshal(item) + if err != nil { + t.Fatal(err) + } + text := string(body) + for _, key := range []string{ + `"skill_id"`, `"skill_name"`, `"version_id"`, `"content_hash"`, `"create_time"`, `"update_time"`, + `"last_checked_at"`, `"showcase_uri"`, `"showcase_url"`, `"avatar_url"`, `"is_private"`, `"owner_uid"`, + } { + if strings.Contains(text, key) { + t.Fatalf("legacy key %s still encoded: %s", key, text) + } + } + var object map[string]json.RawMessage + if err := json.Unmarshal(body, &object); err != nil { + t.Fatal(err) + } + for _, key := range []string{"skillId", "skillName", "versionId", "contentHash", "createdAt", "updatedAt", "lastCheckedAt", "showcaseMedia", "effectiveUser"} { + if _, found := object[key]; !found { + t.Fatalf("missing %s: %s", key, text) + } + } + if got := strings.Trim(string(object["createdAt"]), `"`); got != "2026-01-02T03:04:05Z" { + t.Fatalf("createdAt = %s", got) + } +} + +func TestParseShowcaseMediaAcceptsStoredSnakeCase(t *testing.T) { + items, err := parseShowcaseMedia(`[{"type":"image","showcase_uri":"uri-1","showcase_url":"https://example.com/a.png"}]`) + if err != nil { + t.Fatal(err) + } + if len(items) != 1 || items[0].ShowcaseURI != "uri-1" || items[0].ShowcaseURL != "https://example.com/a.png" { + t.Fatalf("unexpected legacy parse: %+v", items) + } + items, err = parseShowcaseMedia(`[{"type":"video","showcaseUri":"uri-2","showcaseUrl":"https://example.com/b.mp4"}]`) + if err != nil { + t.Fatal(err) + } + if len(items) != 1 || items[0].ShowcaseURI != "uri-2" { + t.Fatalf("unexpected camelCase parse: %+v", items) + } +} diff --git a/backend/internal/service/skills_seed.go b/backend/internal/service/skills_seed.go index 8ca61c694..e5f665bba 100644 --- a/backend/internal/service/skills_seed.go +++ b/backend/internal/service/skills_seed.go @@ -13,25 +13,37 @@ import ( //go:embed seed/skills.json var builtinSkillsJSON []byte +type seedShowcaseMedia struct { + Type string `json:"type"` + ShowcaseURI string `json:"showcase_uri"` + ShowcaseURL string `json:"showcase_url"` +} + +type seedEffectiveUser struct { + Name string `json:"name"` + AvatarURL string `json:"avatar_url"` + UID string `json:"uid"` +} + type builtinSkillDefinition struct { - SkillID string `json:"skill_id"` - SkillName string `json:"skill_name"` - Description string `json:"description"` - Instruction string `json:"instruction"` - Status int `json:"status"` - MarkdownURL string `json:"markdown_url"` - CreateTime int64 `json:"create_time"` - UpdateTime int64 `json:"update_time"` - Source int `json:"source"` - Tag string `json:"tag"` - SortWeight int `json:"sort_weight"` - IsPrivate bool `json:"is_private"` - LikeCount int64 `json:"like_count"` - OwnerUID string `json:"owner_uid"` - EffectiveUser SkillEffectiveUser `json:"effective_user"` - ShowcaseMedia []SkillShowcaseMedia `json:"showcase_media"` - AddedCount int64 `json:"added_count"` - ExtraInfo string `json:"extra_info"` + SkillID string `json:"skill_id"` + SkillName string `json:"skill_name"` + Description string `json:"description"` + Instruction string `json:"instruction"` + Status int `json:"status"` + MarkdownURL string `json:"markdown_url"` + CreateTime int64 `json:"create_time"` + UpdateTime int64 `json:"update_time"` + Source int `json:"source"` + Tag string `json:"tag"` + SortWeight int `json:"sort_weight"` + IsPrivate bool `json:"is_private"` + LikeCount int64 `json:"like_count"` + OwnerUID string `json:"owner_uid"` + EffectiveUser seedEffectiveUser `json:"effective_user"` + ShowcaseMedia []seedShowcaseMedia `json:"showcase_media"` + AddedCount int64 `json:"added_count"` + ExtraInfo string `json:"extra_info"` } // EnsureBuiltinSkills 校验并同步内置技能正文;用户关系独立保存,因此重复启动不会清空加入或收藏状态。 @@ -67,10 +79,14 @@ func (s *Service) EnsureBuiltinSkills() error { if authorName == "" || (authorAvatarURL != "" && !validSkillURL(authorAvatarURL)) { return fmt.Errorf("内置技能作者信息无效: %s", id) } + media := make([]SkillShowcaseMedia, 0, len(definition.ShowcaseMedia)) + for _, item := range definition.ShowcaseMedia { + media = append(media, SkillShowcaseMedia{Type: item.Type, ShowcaseURI: item.ShowcaseURI, ShowcaseURL: item.ShowcaseURL}) + } normalized, mediaJSON, err := normalizeSkillMutationRequest(SkillMutationRequest{ SkillName: definition.SkillName, Description: definition.Description, Instruction: definition.Instruction, Tag: definition.Tag, IsPrivate: definition.IsPrivate, MarkdownURL: definition.MarkdownURL, - ShowcaseMedia: definition.ShowcaseMedia, ExtraInfo: definition.ExtraInfo, + ShowcaseMedia: media, ExtraInfo: definition.ExtraInfo, }, true) if err != nil { return fmt.Errorf("内置技能 %s 数据无效: %w", id, err) diff --git a/backend/internal/service/storage_migration.go b/backend/internal/service/storage_migration.go index 1e7e278fb..d23d4d777 100644 --- a/backend/internal/service/storage_migration.go +++ b/backend/internal/service/storage_migration.go @@ -40,43 +40,57 @@ func (s *Service) MigrateLegacyStorage() (StorageMigrationSummary, error) { if err != nil { return summary, err } + // 迁移标记一旦写入,后续启动不会再次扫描旧数据。因此解析、脱敏和序列化都必须 + // 失败关闭;跳过坏记录再写 marker 会把未迁移数据永久伪装成“迁移完成”。 for index := range tasks { changed := false if strings.Contains(tasks[index].InputJSON, "data:") { var input map[string]interface{} - if json.Unmarshal([]byte(tasks[index].InputJSON), &input) == nil { - stored, storeErr := s.persistLegacyGeneratedMediaResult(tasks[index].UserID, input) - if storeErr != nil { - return summary, storeErr - } - encoded, _ := json.Marshal(stored) - tasks[index].InputJSON = string(encoded) - changed = true + if err := json.Unmarshal([]byte(tasks[index].InputJSON), &input); err != nil { + return summary, fmt.Errorf("解析任务 %s 的旧输入失败:%w", tasks[index].ID, err) } + stored, storeErr := s.persistLegacyGeneratedMediaResult(tasks[index].UserID, input) + if storeErr != nil { + return summary, fmt.Errorf("迁移任务 %s 的旧输入媒体失败:%w", tasks[index].ID, storeErr) + } + encoded, err := json.Marshal(stored) + if err != nil { + return summary, fmt.Errorf("序列化任务 %s 的迁移输入失败:%w", tasks[index].ID, err) + } + tasks[index].InputJSON = string(encoded) + changed = true } if strings.Contains(tasks[index].ResultJSON, "data:") { var result map[string]interface{} - if json.Unmarshal([]byte(tasks[index].ResultJSON), &result) == nil { - stored, storeErr := s.persistLegacyGeneratedMediaResult(tasks[index].UserID, result) - if storeErr != nil { - return summary, storeErr - } - encoded, _ := json.Marshal(stored) - tasks[index].ResultJSON = string(encoded) - changed = true + if err := json.Unmarshal([]byte(tasks[index].ResultJSON), &result); err != nil { + return summary, fmt.Errorf("解析任务 %s 的旧结果失败:%w", tasks[index].ID, err) } + stored, storeErr := s.persistLegacyGeneratedMediaResult(tasks[index].UserID, result) + if storeErr != nil { + return summary, fmt.Errorf("迁移任务 %s 的旧结果媒体失败:%w", tasks[index].ID, storeErr) + } + encoded, err := json.Marshal(stored) + if err != nil { + return summary, fmt.Errorf("序列化任务 %s 的迁移结果失败:%w", tasks[index].ID, err) + } + tasks[index].ResultJSON = string(encoded) + changed = true } if strings.TrimSpace(tasks[index].InputJSON) != "" { - var protected map[string]interface{} - if json.Unmarshal([]byte(tasks[index].InputJSON), &protected) == nil { - if err := s.protectTaskSecrets(protected); err != nil { - return summary, err - } - encoded, _ := json.Marshal(protected) - if string(encoded) != tasks[index].InputJSON { - tasks[index].InputJSON = string(encoded) - changed = true - } + var protected interface{} + if err := json.Unmarshal([]byte(tasks[index].InputJSON), &protected); err != nil { + return summary, fmt.Errorf("解析任务 %s 的输入以执行密钥保护失败:%w", tasks[index].ID, err) + } + if err := s.protectTaskSecrets(protected); err != nil { + return summary, fmt.Errorf("保护任务 %s 的输入密钥失败:%w", tasks[index].ID, err) + } + encoded, err := json.Marshal(protected) + if err != nil { + return summary, fmt.Errorf("序列化任务 %s 的脱敏输入失败:%w", tasks[index].ID, err) + } + if string(encoded) != tasks[index].InputJSON { + tasks[index].InputJSON = string(encoded) + changed = true } } if tasks[index].Status == "succeeded" { @@ -103,14 +117,17 @@ func (s *Service) MigrateLegacyStorage() (StorageMigrationSummary, error) { continue } var payload map[string]interface{} - if json.Unmarshal([]byte(assets[index].PayloadJSON), &payload) != nil { - continue + if err := json.Unmarshal([]byte(assets[index].PayloadJSON), &payload); err != nil { + return summary, fmt.Errorf("解析素材 %s 的旧载荷失败:%w", assets[index].ID, err) } stored, err := s.persistLegacyGeneratedMediaResult(assets[index].UserID, payload) if err != nil { - return summary, err + return summary, fmt.Errorf("迁移素材 %s 的旧媒体失败:%w", assets[index].ID, err) + } + encoded, err := json.Marshal(stored) + if err != nil { + return summary, fmt.Errorf("序列化素材 %s 的迁移载荷失败:%w", assets[index].ID, err) } - encoded, _ := json.Marshal(stored) assets[index].PayloadJSON = string(encoded) if err := s.repo.Save(&assets[index]); err != nil { return summary, err @@ -127,14 +144,17 @@ func (s *Service) MigrateLegacyStorage() (StorageMigrationSummary, error) { continue } var payload map[string]interface{} - if json.Unmarshal([]byte(projects[index].PayloadJSON), &payload) != nil { - continue + if err := json.Unmarshal([]byte(projects[index].PayloadJSON), &payload); err != nil { + return summary, fmt.Errorf("解析画布项目 %s 的旧载荷失败:%w", projects[index].ID, err) } stored, err := s.persistLegacyGeneratedMediaResult(projects[index].UserID, payload) if err != nil { - return summary, err + return summary, fmt.Errorf("迁移画布项目 %s 的旧媒体失败:%w", projects[index].ID, err) + } + encoded, err := json.Marshal(stored) + if err != nil { + return summary, fmt.Errorf("序列化画布项目 %s 的迁移载荷失败:%w", projects[index].ID, err) } - encoded, _ := json.Marshal(stored) projects[index].PayloadJSON = string(encoded) if err := s.repo.Save(&projects[index]); err != nil { return summary, err diff --git a/backend/internal/service/storage_quota.go b/backend/internal/service/storage_quota.go index 1cff18080..750a2dea9 100644 --- a/backend/internal/service/storage_quota.go +++ b/backend/internal/service/storage_quota.go @@ -34,7 +34,7 @@ func structuredBytes(usage repository.UserStorageUsage) int64 { func validateStructuredStorageQuotaWithPolicy(usage repository.UserStorageUsage, kind string, creating bool, deltaBytes int64, policy RuntimeResourcePolicy) error { if structuredBytes(usage)+deltaBytes > megabytes(policy.StructuredDataMB) { - return BadAuthRequest(fmt.Sprintf("账号画布、素材和会话数据已达到 %dMB 上限,请先删除不需要的内容", policy.StructuredDataMB)) + return QuotaExceeded(fmt.Sprintf("账号画布、素材和会话数据已达到 %dMB 上限,请先删除不需要的内容", policy.StructuredDataMB)) } if !creating { return nil @@ -42,15 +42,15 @@ func validateStructuredStorageQuotaWithPolicy(usage repository.UserStorageUsage, switch kind { case "asset": if usage.AssetCount >= policy.AssetCount { - return BadAuthRequest(fmt.Sprintf("账号素材数量已达到 %d 个上限", policy.AssetCount)) + return QuotaExceeded(fmt.Sprintf("账号素材数量已达到 %d 个上限", policy.AssetCount)) } case "canvas": if usage.CanvasCount >= policy.CanvasCount { - return BadAuthRequest(fmt.Sprintf("账号画布数量已达到 %d 个上限", policy.CanvasCount)) + return QuotaExceeded(fmt.Sprintf("账号画布数量已达到 %d 个上限", policy.CanvasCount)) } case "session": if usage.SessionCount >= policy.SessionCount { - return BadAuthRequest(fmt.Sprintf("账号 Agent 会话数量已达到 %d 个上限", policy.SessionCount)) + return QuotaExceeded(fmt.Sprintf("账号 Agent 会话数量已达到 %d 个上限", policy.SessionCount)) } } return nil @@ -58,21 +58,21 @@ func validateStructuredStorageQuotaWithPolicy(usage repository.UserStorageUsage, func validateTaskStorageQuotaWithPolicy(usage repository.UserStorageUsage, incomingBytes int64, policy RuntimeResourcePolicy) error { if usage.TaskCount >= policy.TaskCount { - return BadAuthRequest(fmt.Sprintf("账号任务历史已达到 %d 条上限,请联系管理员归档", policy.TaskCount)) + return QuotaExceeded(fmt.Sprintf("账号任务历史已达到 %d 条上限,请联系管理员归档", policy.TaskCount)) } return validateTaskDataGrowthQuotaWithPolicy(usage, incomingBytes, policy) } func validateTaskDataGrowthQuotaWithPolicy(usage repository.UserStorageUsage, incomingBytes int64, policy RuntimeResourcePolicy) error { if usage.TaskBytes+incomingBytes > gigabytes(policy.TaskDataGB) { - return BadAuthRequest(fmt.Sprintf("账号任务历史数据已达到 %dGB 上限,请联系管理员归档", policy.TaskDataGB)) + return QuotaExceeded(fmt.Sprintf("账号任务历史数据已达到 %dGB 上限,请联系管理员归档", policy.TaskDataGB)) } return nil } func validateAPICallLogQuotaWithPolicy(usage repository.UserStorageUsage, incomingBytes int64, policy RuntimeResourcePolicy) error { if usage.APICallCount >= policy.APICallLogCount { - return BadAuthRequest(fmt.Sprintf("账号上游请求日志已达到 %d 条上限,请联系管理员归档", policy.APICallLogCount)) + return QuotaExceeded(fmt.Sprintf("账号上游请求日志已达到 %d 条上限,请联系管理员归档", policy.APICallLogCount)) } return validateTaskDataGrowthQuotaWithPolicy(usage, incomingBytes, policy) } @@ -82,12 +82,12 @@ func validateStructuredReplacementQuotaWithPolicy(usage repository.UserStorageUs switch kind { case "asset": if int64(count) > policy.AssetCount { - return BadAuthRequest(fmt.Sprintf("账号素材数量不能超过 %d 个", policy.AssetCount)) + return QuotaExceeded(fmt.Sprintf("账号素材数量不能超过 %d 个", policy.AssetCount)) } deltaBytes -= usage.AssetBytes case "canvas": if int64(count) > policy.CanvasCount { - return BadAuthRequest(fmt.Sprintf("账号画布数量不能超过 %d 个", policy.CanvasCount)) + return QuotaExceeded(fmt.Sprintf("账号画布数量不能超过 %d 个", policy.CanvasCount)) } deltaBytes -= usage.CanvasBytes } diff --git a/backend/internal/service/storage_quota_test.go b/backend/internal/service/storage_quota_test.go index 6a94b8481..aa56a9b41 100644 --- a/backend/internal/service/storage_quota_test.go +++ b/backend/internal/service/storage_quota_test.go @@ -1,6 +1,7 @@ package service import ( + "errors" "testing" "infinite-canvas/backend/internal/model" @@ -16,8 +17,10 @@ func TestValidateStructuredStorageQuotaRejectsBytesAndCounts(t *testing.T) { if err := validateStructuredStorageQuotaWithPolicy(usage, "asset", false, 9, policy); err == nil { t.Fatal("validateStructuredStorageQuota() byte error = nil") } - if err := validateStructuredStorageQuotaWithPolicy(usage, "asset", true, 0, policy); err == nil { - t.Fatal("validateStructuredStorageQuota() count error = nil") + err := validateStructuredStorageQuotaWithPolicy(usage, "asset", true, 0, policy) + var quota *AppError + if err == nil || !errors.As(err, "a) || quota.Code != CodeQuotaExceeded || quota.Status != CodeForbidden { + t.Fatalf("validateStructuredStorageQuota() count error = %v", err) } } diff --git a/backend/internal/service/task_creation.go b/backend/internal/service/task_creation.go index 42d86515a..b3872b797 100644 --- a/backend/internal/service/task_creation.go +++ b/backend/internal/service/task_creation.go @@ -12,6 +12,9 @@ import ( // CreateTask 收敛任务进入系统前的 admission 流程:输入标准化、逻辑模型路由、 // 能力/额度校验和持久化。执行阶段由 worker 与 provider 相关模块负责。 +// CreateTask 校验并创建一条生成任务。 +// 这是常规模型生成任务的写入口:客户端只提交创作意图,模型、渠道、协议和计价信息必须由服务端目录重新解析, +// 以保证“可展示的模型”与“实际执行及扣费的模型”来自同一份有效配置。 func (s *Service) CreateTask(userID string, req CreateTaskRequest) (*model.Task, error) { if s.IsDraining() { return nil, &AppError{Status: 503, Code: 503, Message: "服务正在维护,暂不接受新的生成任务", Retryable: true} @@ -152,9 +155,11 @@ func (s *Service) resolveTaskModelSelection(input map[string]any, logicalModelID // 自定义渠道没有系统 channelId;它会在后续由自定义渠道功能开关、 // 能力校验和 provider 配置校验共同处理,不能误报为“缺少系统渠道”。 if !customChannelTask { - if err := s.validateSystemChannelModelSelection(input); err != nil { + resolvedInput, err := s.resolveSystemChannelModelSelection(input, taskType, operation) + if err != nil { return nil, input, err } + return nil, resolvedInput, nil } return nil, input, nil } @@ -284,47 +289,142 @@ func (s *Service) requireCustomChannelsForTaskInput(input map[string]any) error return s.RequireFeature(FeatureCustomChannels) } -// validateSystemChannelModelSelection 校验系统渠道模型选择的有效性 -func (s *Service) validateSystemChannelModelSelection(input map[string]any) error { +// resolveSystemChannelModelSelection 是系统渠道任务的 admission 边界。 +// 客户端只负责表达创作参数;模型协议、能力合同、价格档和上游模型标识必须从服务端记录重建, +// 避免出现“按一个规格校验/计费,却按另一个规格执行”的跨阶段漂移。 +func (s *Service) resolveSystemChannelModelSelection(input map[string]any, taskType string, operation string) (map[string]any, error) { config, ok := input["config"].(map[string]any) if !ok { - return InvalidModelSelection("缺少模型配置") + return input, InvalidModelSelection("缺少模型配置") } - channelID, _ := config["channelId"].(string) - modelKey, _ := config["model"].(string) - - channelID = strings.TrimSpace(channelID) - modelKey = strings.TrimSpace(modelKey) + channelID := strings.TrimSpace(stringValue(config["channelId"])) + modelKey := strings.TrimPrefix(strings.TrimSpace(stringValue(config["model"])), "models/") if channelID == "" || modelKey == "" { - return InvalidModelSelection("必须指定系统渠道和模型") + return input, InvalidModelSelection("必须指定系统渠道和模型") } - // 验证渠道存在且启用 channel, err := s.repo.SystemChannel(channelID) if err != nil { - return InvalidModelSelection("指定的渠道不存在") + return input, InvalidModelSelection("指定的渠道不存在") } if !channel.Enabled || channel.Scope != model.ChannelScopeSystem { - return InvalidModelSelection("指定的渠道不可用") + return input, InvalidModelSelection("指定的渠道不可用") } - // 验证渠道模型存在且启用 channelModel, err := s.repo.ChannelModelByKey(channelID, modelKey) if err != nil { - return InvalidModelSelection("指定的模型不存在") + return input, InvalidModelSelection("指定的模型不存在") } if !channelModel.Enabled { - return InvalidModelSelection("指定的模型已停用") + return input, InvalidModelSelection("指定的模型已停用") + } + if channelModel.Protocol == "" { + return input, InvalidModelSelection("指定的模型未配置请求协议") } - // 验证价格配置 - if !HasValidPrice(channelModel) { - return ModelPriceNotConfigured("指定的模型未配置有效价格") + nextConfig := make(map[string]any, len(config)+6) + for key, value := range config { + switch key { + case "channelId", "channelModelKey", "priceTierId", "providerModelKey", "apiFormat", "interfaceType", "baseUrl", "allowLocalChannel", "apiKey", "secretKey", "headers", "model", "capabilityConfig": + continue + default: + nextConfig[key] = value + } + } + // capabilityOptions 是路由、校验和计价共同使用的请求规格;存在时必须覆盖 config 中的同名字段, + // 不能让两份客户端数据分别驱动计费与真实上游请求。 + if options, ok := input["capabilityOptions"].(map[string]any); ok { + for key, value := range options { + canonical := canonicalCapabilityOptionName(key) + if isCapabilityOptionFor(channelModel.Capability, canonical) { + nextConfig[canonical] = value + } + } } - return nil + capabilityConfig, err := normalizedChannelModelCapability(channelModel) + if err != nil { + return input, InvalidModelSelection("指定的模型能力配置无效,请联系管理员") + } + applyChannelCapabilityDefaults(nextConfig, channelModel.Capability, capabilityConfig) + input["config"] = nextConfig + input["capabilityOptions"] = capabilityOptionsFromConfig(channelModel.Capability, nextConfig) + + intent := ModelRequestIntentFromTaskInput(input, taskType, operation) + if normalizeCapability(intent.Capability) != normalizeCapability(channelModel.Capability) { + return input, ModelCapabilityNotSupported("所选模型与任务能力不匹配") + } + if normalizeCapability(channelModel.Capability) != "audio" { + spec, specErr := CapabilitySpecFromModelCapabilityConfig(capabilityConfig, channelModel.Capability) + if specErr != nil { + return input, InvalidModelSelection("指定的模型能力配置无效,请联系管理员") + } + if match := MatchCapability(spec, intent); !match.Matched { + return input, ModelCapabilityNotSupported("所选模型不支持当前请求:" + strings.Join(match.Reasons, ";")) + } + } + + priceTier := channelModelPriceTierForIntent(*channelModel, intent) + if priceTier == nil || !ValidatePriceTierPrice(priceTier, channelModel.Capability, channelModel.Protocol) { + return input, ModelPriceNotConfigured("指定的模型未配置当前规格的有效价格") + } + + nextConfig["channelId"] = channel.ID + nextConfig["model"] = channelModel.ModelKey + nextConfig["channelModelKey"] = channelModel.ModelKey + nextConfig["priceTierId"] = priceTier.ID + nextConfig["providerModelKey"] = firstNonEmpty(priceTier.ProviderModelKey, channelModel.ProviderModelKey, channelModel.ModelKey) + nextConfig["interfaceType"] = string(channelModel.Protocol) + nextConfig["apiFormat"] = channelAPIFormatForProtocol(channel.APIFormat, channelModel.Protocol) + return input, nil +} + +// applyChannelCapabilityDefaults 只采用管理员保存的能力默认值,且仅填补客户端未表达的参数。 +// 这些值随后会写回 capabilityOptions,使能力校验、SKU 选择和 provider 执行看到同一份规格。 +func applyChannelCapabilityDefaults(config map[string]any, capability string, profile *ModelCapabilityConfig) { + setDefault := func(key string, value any) { + if existing, exists := config[key]; !exists || existing == nil || strings.TrimSpace(fmt.Sprint(existing)) == "" { + config[key] = value + } + } + switch normalizeCapability(capability) { + case "image": + if profile == nil || profile.Image == nil { + return + } + if profile.Image.Size.Parameter != "none" { + setDefault("size", profile.Image.Size.Default) + } + if profile.Image.Quality.Supported { + setDefault("quality", profile.Image.Quality.Default) + } + setDefault("transparentBackground", profile.Image.TransparentBackground.Default) + setDefault("count", 1) + case "video": + if profile == nil || profile.Video == nil { + return + } + if videoDurationSupported(profile.Video) { + setDefault("videoSeconds", profile.Video.Duration.Default) + } + setDefault("size", profile.Video.DefaultRatio) + setDefault("vquality", profile.Video.DefaultResolution) + setDefault("videoGenerateAudio", profile.Video.GenerateAudio.Default) + setDefault("videoWatermark", profile.Video.Watermark.Default) + } +} + +func capabilityOptionsFromConfig(capability string, config map[string]any) map[string]any { + options := map[string]any{} + for key, value := range config { + canonical := canonicalCapabilityOptionName(key) + if isCapabilityOptionFor(capability, canonical) && value != nil && strings.TrimSpace(fmt.Sprint(value)) != "" { + options[canonical] = value + } + } + return options } func taskInputUsesCustomChannel(input map[string]any) bool { @@ -358,6 +458,11 @@ func taskInputUsesWorkflowProvider(input map[string]any) bool { if !ok { return false } + // 系统渠道的 interfaceType 是客户端缓存,不是授权事实;必须先走系统模型 admission, + // 不能通过伪造工作流协议绕开渠道模型、能力和价格校验。 + if strings.TrimSpace(stringValue(config["channelId"])) != "" { + return false + } return isWorkflowProviderInterface(strings.TrimSpace(fmt.Sprint(config["interfaceType"]))) } diff --git a/backend/internal/service/task_creation_test.go b/backend/internal/service/task_creation_test.go index 384e54b7e..1f4540b6e 100644 --- a/backend/internal/service/task_creation_test.go +++ b/backend/internal/service/task_creation_test.go @@ -1,6 +1,7 @@ package service import ( + "encoding/json" "strings" "testing" @@ -45,9 +46,18 @@ func TestResolveTaskModelSelectionAllowsExplicitSystemChannelWhenFrontendModelsE t.Fatal(err) } channel := model.ModelChannel{ID: "channel-1", Scope: model.ChannelScopeSystem, Enabled: true, Name: "Agnes"} + capabilityJSON, err := json.Marshal(DefaultModelCapabilityConfigForModel(string(model.ChannelInterfaceNewAPIVideo), "agnes-video-2.5")) + if err != nil { + t.Fatal(err) + } channelModel := model.ChannelModel{ ID: "channel-model-1", ChannelID: channel.ID, ModelKey: "agnes-video-2.5", Capability: "video", Protocol: model.ChannelInterfaceNewAPIVideo, BillingMode: "per_second", PriceConfigured: true, Enabled: true, + CapabilityConfigJSON: string(capabilityJSON), + } + priceTier := model.ChannelModelPriceTier{ + ID: "tier-1", ChannelModelID: channelModel.ID, SelectorKey: `{}`, SelectorJSON: `{}`, + BillingMode: "per_second", UnitPriceMicrocredits: 1, PriceConfigured: true, Enabled: true, } if err := db.Create(&channel).Error; err != nil { t.Fatal(err) @@ -55,6 +65,9 @@ func TestResolveTaskModelSelectionAllowsExplicitSystemChannelWhenFrontendModelsE if err := db.Create(&channelModel).Error; err != nil { t.Fatal(err) } + if err := db.Create(&priceTier).Error; err != nil { + t.Fatal(err) + } input, err := normalizeTaskInput(map[string]any{ "mode": "video", "config": providerConfig{ChannelID: channel.ID, Model: channelModel.ModelKey, InterfaceType: string(channelModel.Protocol)}, @@ -101,3 +114,178 @@ func TestResolveTaskModelSelectionStillRequiresLogicalModelWithoutExplicitSystem t.Fatalf("resolveTaskModelSelection() error = %v, want logicalModelId validation", err) } } + +func TestResolveSystemChannelModelSelectionRebuildsAuthoritativeExecutionSpec(t *testing.T) { + profile := DefaultModelCapabilityConfigForModel(string(model.ChannelInterfaceGrokImage), "grok-image") + svc, _, channel, channelModel := createSystemChannelSelectionFixture(t, "image", model.ChannelInterfaceGrokImage, profile, []model.ChannelModelPriceTier{ + newSelectionPriceTier("tier-1k", `{"operation":"text_to_image","quality":"1k"}`, "provider-image-1k", "fixed_request"), + newSelectionPriceTier("tier-2k", `{"operation":"text_to_image","quality":"2k"}`, "provider-image-2k", "fixed_request"), + }) + + input := map[string]any{ + "mode": "image", + "config": map[string]any{ + "channelId": channel.ID, + "model": channelModel.ModelKey, + "quality": "2k", + "priceTierId": "client-tier", + "providerModelKey": "client-provider-model", + "interfaceType": "runninghub-workflow-image", + "apiFormat": "client-format", + "baseUrl": "https://attacker.invalid/v1", + "apiKey": "client-api-key", + "secretKey": "client-secret", + "headers": []any{map[string]any{"name": "Authorization", "value": "client-token"}}, + "capabilityConfig": map[string]any{"image": map[string]any{"maxOutputs": 99}}, + }, + // capabilityOptions 是创作端表达的最终规格,必须覆盖旧页面缓存到 config 的同名值。 + "capabilityOptions": map[string]any{"quality": "1k", "size": "1:1"}, + } + + resolved, err := svc.resolveSystemChannelModelSelection(input, "canvas_image", "") + if err != nil { + t.Fatalf("resolveSystemChannelModelSelection() error = %v", err) + } + config := resolved["config"].(map[string]any) + for _, forbidden := range []string{"baseUrl", "apiKey", "secretKey", "headers", "capabilityConfig"} { + if _, exists := config[forbidden]; exists { + t.Fatalf("config[%q] must be removed from a system-channel request: %#v", forbidden, config[forbidden]) + } + } + if got := config["quality"]; got != "1k" { + t.Fatalf("quality = %#v, want capabilityOptions value 1k", got) + } + if got := config["priceTierId"]; got != "tier-1k" { + t.Fatalf("priceTierId = %#v, want server-selected tier-1k", got) + } + if got := config["providerModelKey"]; got != "provider-image-1k" { + t.Fatalf("providerModelKey = %#v, want server-selected provider-image-1k", got) + } + if got := config["interfaceType"]; got != string(model.ChannelInterfaceGrokImage) { + t.Fatalf("interfaceType = %#v, want %q", got, model.ChannelInterfaceGrokImage) + } + if got := config["apiFormat"]; got != "openai" { + t.Fatalf("apiFormat = %#v, want openai", got) + } + options := resolved["capabilityOptions"].(map[string]any) + if options["quality"] != config["quality"] || options["size"] != config["size"] || options["count"] != config["count"] { + t.Fatalf("capabilityOptions and provider config diverged: options=%#v config=%#v", options, config) + } +} + +func TestResolveSystemChannelModelSelectionAppliesServerDefaults(t *testing.T) { + profile := DefaultModelCapabilityConfigForModel(string(model.ChannelInterfaceGrokImage), "grok-image") + svc, _, channel, channelModel := createSystemChannelSelectionFixture(t, "image", model.ChannelInterfaceGrokImage, profile, []model.ChannelModelPriceTier{ + newSelectionPriceTier("tier-default", `{"operation":"text_to_image","quality":"2k"}`, "provider-image-default", "fixed_request"), + }) + + resolved, err := svc.resolveSystemChannelModelSelection(map[string]any{ + "mode": "image", + "config": map[string]any{"channelId": channel.ID, "model": channelModel.ModelKey}, + }, "canvas_image", "") + if err != nil { + t.Fatalf("resolveSystemChannelModelSelection() error = %v", err) + } + config := resolved["config"].(map[string]any) + if config["size"] != "1:1" || config["quality"] != "2k" || config["count"] != 1 || config["transparentBackground"] != false { + t.Fatalf("server defaults were not applied: %#v", config) + } + if config["priceTierId"] != "tier-default" || config["providerModelKey"] != "provider-image-default" { + t.Fatalf("server price selection was not persisted: %#v", config) + } +} + +func TestResolveSystemChannelModelSelectionRejectsUnsupportedRequest(t *testing.T) { + profile := DefaultModelCapabilityConfigForModel(string(model.ChannelInterfaceGrokImage), "grok-image") + svc, _, channel, channelModel := createSystemChannelSelectionFixture(t, "image", model.ChannelInterfaceGrokImage, profile, []model.ChannelModelPriceTier{ + newSelectionPriceTier("tier-any", `{}`, "provider-image", "fixed_request"), + }) + + tests := []struct { + name string + input map[string]any + }{ + { + name: "too many reference images", + input: map[string]any{ + "mode": "image", "referenceImages": []any{"a", "b"}, + "config": map[string]any{"channelId": channel.ID, "model": channelModel.ModelKey}, + }, + }, + { + name: "unsupported quality", + input: map[string]any{ + "mode": "image", "config": map[string]any{"channelId": channel.ID, "model": channelModel.ModelKey}, + "capabilityOptions": map[string]any{"quality": "4k", "size": "1:1"}, + }, + }, + } + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + if _, err := svc.resolveSystemChannelModelSelection(test.input, "canvas_image", ""); err == nil { + t.Fatal("resolveSystemChannelModelSelection() should reject a request outside the server capability contract") + } + }) + } + + videoProfile := DefaultModelCapabilityConfigForModel(string(model.ChannelInterfaceNewAPIVideo), "video-model") + videoSvc, _, videoChannel, videoModel := createSystemChannelSelectionFixture(t, "video", model.ChannelInterfaceNewAPIVideo, videoProfile, []model.ChannelModelPriceTier{ + newSelectionPriceTier("video-tier", `{}`, "provider-video", "per_second"), + }) + if _, err := videoSvc.resolveSystemChannelModelSelection(map[string]any{ + "mode": "video", + "config": map[string]any{"channelId": videoChannel.ID, "model": videoModel.ModelKey}, + }, "canvas_video", "audio_to_video"); err == nil { + t.Fatal("resolveSystemChannelModelSelection() should reject an unsupported operation") + } +} + +func TestResolveSystemChannelModelSelectionRejectsCorruptCapabilityConfig(t *testing.T) { + profile := DefaultModelCapabilityConfigForModel(string(model.ChannelInterfaceGrokImage), "grok-image") + svc, db, channel, channelModel := createSystemChannelSelectionFixture(t, "image", model.ChannelInterfaceGrokImage, profile, []model.ChannelModelPriceTier{ + newSelectionPriceTier("tier-any", `{}`, "provider-image", "fixed_request"), + }) + if err := db.Model(&model.ChannelModel{}).Where("id = ?", channelModel.ID).Update("capability_config_json", "{").Error; err != nil { + t.Fatal(err) + } + if _, err := svc.resolveSystemChannelModelSelection(map[string]any{ + "mode": "image", + "config": map[string]any{"channelId": channel.ID, "model": channelModel.ModelKey}, + }, "canvas_image", ""); err == nil { + t.Fatal("resolveSystemChannelModelSelection() should fail closed for corrupt persisted capability config") + } +} + +func createSystemChannelSelectionFixture(t *testing.T, capability string, protocol model.ChannelInterfaceType, profile *ModelCapabilityConfig, tiers []model.ChannelModelPriceTier) (*Service, *gorm.DB, model.ModelChannel, model.ChannelModel) { + t.Helper() + svc, db := newChannelModelTestService(t) + channel := model.ModelChannel{ID: newID(), Scope: model.ChannelScopeSystem, Enabled: true, Name: "System Channel", APIFormat: "legacy"} + capabilityJSON, err := json.Marshal(profile) + if err != nil { + t.Fatal(err) + } + channelModel := model.ChannelModel{ + ID: newID(), ChannelID: channel.ID, ModelKey: capability + "-model", ProviderModelKey: "provider-default", + Capability: capability, Protocol: protocol, Enabled: true, CapabilityConfigJSON: string(capabilityJSON), + } + if err := db.Create(&channel).Error; err != nil { + t.Fatal(err) + } + if err := db.Create(&channelModel).Error; err != nil { + t.Fatal(err) + } + for index := range tiers { + tiers[index].ChannelModelID = channelModel.ID + if err := db.Create(&tiers[index]).Error; err != nil { + t.Fatal(err) + } + } + return svc, db, channel, channelModel +} + +func newSelectionPriceTier(id string, selector string, providerModelKey string, billingMode string) model.ChannelModelPriceTier { + return model.ChannelModelPriceTier{ + ID: id, SelectorKey: selector, SelectorJSON: selector, ProviderModelKey: providerModelKey, + BillingMode: billingMode, UnitPriceMicrocredits: 10, PriceConfigured: true, Enabled: true, + } +} diff --git a/backend/internal/service/task_output.go b/backend/internal/service/task_output.go index 130d3458a..6c4daf5cc 100644 --- a/backend/internal/service/task_output.go +++ b/backend/internal/service/task_output.go @@ -244,7 +244,7 @@ func findTaskMediaPreview(value any, hint string) (string, string) { switch item := value.(type) { case string: text := strings.TrimSpace(item) - if !strings.HasPrefix(text, "/api/resources/") && !strings.HasPrefix(text, "http://") && !strings.HasPrefix(text, "https://") { + if !isResourceFileURL(text) && !strings.HasPrefix(text, "http://") && !strings.HasPrefix(text, "https://") { return "", "" } kind := hint diff --git a/backend/internal/service/upload_quota.go b/backend/internal/service/upload_quota.go index 6fe710bc6..aa5856c64 100644 --- a/backend/internal/service/upload_quota.go +++ b/backend/internal/service/upload_quota.go @@ -61,7 +61,7 @@ func (s *Service) reserveRetryUploadQuota(userID string, size int64) (string, er defer s.storageMu.Unlock() if err := s.repo.ReserveDailyUpload(userID, day, size, megabytes(policy.Resource.DailyUploadMB)); err != nil { if errors.Is(err, repository.ErrDailyUploadLimitExceeded) { - return "", BadAuthRequest(fmt.Sprintf("每个账号 UTC 自然日上传总量必须小于 %s", formatStorageLimit(megabytes(policy.Resource.DailyUploadMB)))) + return "", QuotaExceeded(fmt.Sprintf("每个账号 UTC 自然日上传总量必须小于 %s", formatStorageLimit(megabytes(policy.Resource.DailyUploadMB)))) } return "", err } @@ -86,13 +86,13 @@ func (s *Service) reserveUserStoredFileQuota(userID string, size int64, exclusiv s.pendingStorage = map[string]int64{} } if storedBytes+s.pendingStorage[userID]+size >= storedLimit { - return "", BadAuthRequest(fmt.Sprintf("账号资源和会话附件已达到 %s 上限,请联系管理员清理历史文件", formatStorageLimit(storedLimit))) + return "", QuotaExceeded(fmt.Sprintf("账号资源和会话附件已达到 %s 上限,请联系管理员清理历史文件", formatStorageLimit(storedLimit))) } s.pendingStorage[userID] += size if err := s.repo.ReserveDailyUpload(userID, day, size, dailyLimit); err != nil { s.decreasePendingStorage(userID, size) if errors.Is(err, repository.ErrDailyUploadLimitExceeded) { - return "", BadAuthRequest(fmt.Sprintf("每个账号 UTC 自然日上传总量必须小于 %s", formatStorageLimit(dailyLimit))) + return "", QuotaExceeded(fmt.Sprintf("每个账号 UTC 自然日上传总量必须小于 %s", formatStorageLimit(dailyLimit))) } return "", err } diff --git a/backend/internal/service/user_asset_document.go b/backend/internal/service/user_asset_document.go new file mode 100644 index 000000000..f6b69a94a --- /dev/null +++ b/backend/internal/service/user_asset_document.go @@ -0,0 +1,203 @@ +package service + +import ( + "bytes" + "encoding/json" + "math" + "strings" +) + +var userAssetKinds = map[string]struct{}{ + "text": {}, + "image": {}, + "video": {}, + "audio": {}, + "model": {}, + "entity": {}, +} + +func validateUserAssetDocument(raw json.RawMessage) error { + var object map[string]json.RawMessage + if err := json.Unmarshal(raw, &object); err != nil { + return BadAuthRequest("素材数据格式错误") + } + kind, err := requiredJSONStringField(object, "kind") + if err != nil { + return err + } + kind = strings.TrimSpace(kind) + if _, ok := userAssetKinds[kind]; !ok { + return BadAuthRequest("不支持的素材类型") + } + if _, err := requiredJSONStringField(object, "title"); err != nil { + return err + } + if _, err := requiredJSONStringField(object, "coverUrl"); err != nil { + return err + } + if err := validateUserAssetTags(object); err != nil { + return err + } + data, err := requiredJSONObjectField(object, "data") + if err != nil { + return err + } + return validateUserAssetData(kind, data) +} + +func validateUserAssetTags(object map[string]json.RawMessage) error { + raw, ok := object["tags"] + if !ok { + return BadAuthRequest("素材缺少 tags 字段") + } + trimmed := bytes.TrimSpace(raw) + if len(trimmed) == 0 || trimmed[0] != '[' { + return BadAuthRequest("素材 tags 必须是字符串数组") + } + var tags []string + if err := json.Unmarshal(raw, &tags); err != nil { + return BadAuthRequest("素材 tags 必须是字符串数组") + } + return nil +} + +func validateUserAssetData(kind string, data map[string]json.RawMessage) error { + switch kind { + case "text": + _, err := requiredJSONStringField(data, "content") + return err + case "image": + if err := requireMediaLocator(data, "dataUrl", "图片"); err != nil { + return err + } + if _, err := requiredJSONNumberField(data, "width"); err != nil { + return err + } + if _, err := requiredJSONNumberField(data, "height"); err != nil { + return err + } + if _, err := requiredJSONNumberField(data, "bytes"); err != nil { + return err + } + return requireNonEmptyJSONString(data, "mimeType") + case "video": + if err := requireMediaLocator(data, "url", "视频"); err != nil { + return err + } + if _, err := requiredJSONNumberField(data, "width"); err != nil { + return err + } + if _, err := requiredJSONNumberField(data, "height"); err != nil { + return err + } + if _, err := requiredJSONNumberField(data, "bytes"); err != nil { + return err + } + return requireNonEmptyJSONString(data, "mimeType") + case "audio": + if err := requireMediaLocator(data, "url", "音频"); err != nil { + return err + } + if _, err := requiredJSONNumberField(data, "bytes"); err != nil { + return err + } + return requireNonEmptyJSONString(data, "mimeType") + case "model": + if err := requireMediaLocator(data, "url", "模型"); err != nil { + return err + } + if _, err := requiredJSONNumberField(data, "bytes"); err != nil { + return err + } + if err := requireNonEmptyJSONString(data, "mimeType"); err != nil { + return err + } + return requireNonEmptyJSONString(data, "fileName") + case "entity": + return requireJSONObjectFieldPresent(data, "definition") + default: + return BadAuthRequest("不支持的素材类型") + } +} + +func requireMediaLocator(data map[string]json.RawMessage, primaryKey string, label string) error { + primary, err := optionalJSONStringField(data, primaryKey) + if err != nil { + return err + } + storageKey, err := optionalJSONStringField(data, "storageKey") + if err != nil { + return err + } + if strings.TrimSpace(primary) == "" && strings.TrimSpace(storageKey) == "" { + return BadAuthRequest(label + "素材缺少 " + primaryKey + " 或 storageKey") + } + return nil +} + +func requiredJSONStringField(object map[string]json.RawMessage, key string) (string, error) { + raw, ok := object[key] + if !ok { + return "", BadAuthRequest("素材缺少 " + key + " 字段") + } + var value string + if err := json.Unmarshal(raw, &value); err != nil { + return "", BadAuthRequest("素材字段 " + key + " 必须是字符串") + } + return value, nil +} + +func optionalJSONStringField(object map[string]json.RawMessage, key string) (string, error) { + raw, ok := object[key] + if !ok || bytes.Equal(bytes.TrimSpace(raw), []byte("null")) { + return "", nil + } + var value string + if err := json.Unmarshal(raw, &value); err != nil { + return "", BadAuthRequest("素材字段 " + key + " 必须是字符串") + } + return value, nil +} + +func requireNonEmptyJSONString(object map[string]json.RawMessage, key string) error { + value, err := requiredJSONStringField(object, key) + if err != nil { + return err + } + if strings.TrimSpace(value) == "" { + return BadAuthRequest("素材字段 " + key + " 不能为空") + } + return nil +} + +func requiredJSONNumberField(object map[string]json.RawMessage, key string) (float64, error) { + raw, ok := object[key] + if !ok { + return 0, BadAuthRequest("素材缺少 " + key + " 字段") + } + var value float64 + if err := json.Unmarshal(raw, &value); err != nil { + return 0, BadAuthRequest("素材字段 " + key + " 必须是数字") + } + if math.IsNaN(value) || math.IsInf(value, 0) || value < 0 { + return 0, BadAuthRequest("素材字段 " + key + " 必须是非负数字") + } + return value, nil +} + +func requiredJSONObjectField(object map[string]json.RawMessage, key string) (map[string]json.RawMessage, error) { + raw, ok := object[key] + if !ok { + return nil, BadAuthRequest("素材缺少 " + key + " 字段") + } + var nested map[string]json.RawMessage + if err := json.Unmarshal(raw, &nested); err != nil || nested == nil { + return nil, BadAuthRequest("素材字段 " + key + " 必须是对象") + } + return nested, nil +} + +func requireJSONObjectFieldPresent(object map[string]json.RawMessage, key string) error { + _, err := requiredJSONObjectField(object, key) + return err +} diff --git a/backend/internal/service/user_data.go b/backend/internal/service/user_data.go index 5c843f89d..aaa02c579 100644 --- a/backend/internal/service/user_data.go +++ b/backend/internal/service/user_data.go @@ -318,6 +318,9 @@ func assetFromJSON(userID string, raw json.RawMessage) (model.Asset, error) { if utf8.RuneCountInString(primaryVersionID) > 36 { return model.Asset{}, BadAuthRequest("素材主版本 ID 不能超过 36 个字符") } + if err := validateUserAssetDocument(raw); err != nil { + return model.Asset{}, err + } category := model.NormalizeAssetCategory(model.AssetCategory(payload.Category), payload.Kind) status := model.AssetVersionStatus(strings.TrimSpace(payload.Status)) if status == "" { diff --git a/backend/internal/service/user_data_test.go b/backend/internal/service/user_data_test.go index e2a29f9d3..439c43f47 100644 --- a/backend/internal/service/user_data_test.go +++ b/backend/internal/service/user_data_test.go @@ -8,9 +8,37 @@ import ( "infinite-canvas/backend/internal/model" ) +func testUserAssetPayload(kind string, extra map[string]any) map[string]any { + payload := map[string]any{ + "id": "asset-1", + "kind": kind, + "title": "测试资产", + "coverUrl": "", + "tags": []string{}, + } + switch kind { + case "image": + payload["data"] = map[string]any{"dataUrl": "https://example.com/a.png", "width": 1, "height": 1, "bytes": 1, "mimeType": "image/png"} + case "video": + payload["data"] = map[string]any{"url": "https://example.com/a.mp4", "width": 1, "height": 1, "bytes": 1, "mimeType": "video/mp4"} + case "audio": + payload["data"] = map[string]any{"url": "https://example.com/a.mp3", "bytes": 1, "mimeType": "audio/mpeg"} + case "model": + payload["data"] = map[string]any{"url": "https://example.com/a.glb", "bytes": 1, "mimeType": "model/gltf-binary", "fileName": "a.glb"} + case "text": + payload["data"] = map[string]any{"content": "正文"} + default: + payload["data"] = map[string]any{"definition": map[string]any{}} + } + for key, value := range extra { + payload[key] = value + } + return payload +} + func TestAssetFromJSONAcceptsDeterministicGenerationID(t *testing.T) { id := "generation_" + strings.Repeat("a", 64) - raw, err := json.Marshal(map[string]any{"id": id, "kind": "image", "title": "生成图片"}) + raw, err := json.Marshal(testUserAssetPayload("image", map[string]any{"id": id, "title": "生成图片"})) if err != nil { t.Fatalf("marshal asset: %v", err) } @@ -41,7 +69,7 @@ func TestAssetFromJSONNormalizesLegacyAndUnclassifiedMediaCategories(t *testing. } for _, test := range tests { t.Run(test.name, func(t *testing.T) { - raw, err := json.Marshal(map[string]any{"id": "asset-1", "kind": test.kind, "category": test.category, "title": "测试资产"}) + raw, err := json.Marshal(testUserAssetPayload(test.kind, map[string]any{"category": test.category})) if err != nil { t.Fatal(err) } @@ -56,6 +84,37 @@ func TestAssetFromJSONNormalizesLegacyAndUnclassifiedMediaCategories(t *testing. } } +func TestAssetFromJSONRejectsIncompleteLibraryPayload(t *testing.T) { + tests := []struct { + name string + payload map[string]any + want string + }{ + {name: "missing tags", payload: testUserAssetPayload("image", map[string]any{"tags": nil}), want: "tags"}, + {name: "malformed tags", payload: testUserAssetPayload("image", map[string]any{"tags": []any{"角色", 1}}), want: "tags"}, + {name: "missing data", payload: testUserAssetPayload("image", map[string]any{"data": nil}), want: "data"}, + {name: "image missing locator", payload: testUserAssetPayload("image", map[string]any{"data": map[string]any{"dataUrl": "", "width": 1, "height": 1, "bytes": 1, "mimeType": "image/png"}}), want: "dataUrl 或 storageKey"}, + } + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + if test.name == "missing tags" { + delete(test.payload, "tags") + } + if test.name == "missing data" { + delete(test.payload, "data") + } + raw, err := json.Marshal(test.payload) + if err != nil { + t.Fatal(err) + } + _, err = assetFromJSON("user-1", raw) + if err == nil || !strings.Contains(err.Error(), test.want) { + t.Fatalf("assetFromJSON error = %v, want %q", err, test.want) + } + }) + } +} + func TestAssetFromJSONRejectsIDOverLimit(t *testing.T) { raw, err := json.Marshal(map[string]any{"id": strings.Repeat("a", model.AssetIDMaxLength+1), "kind": "image"}) if err != nil { diff --git a/backend/internal/service/video_transcode.go b/backend/internal/service/video_transcode.go index 523ec4406..a1d2b49dd 100644 --- a/backend/internal/service/video_transcode.go +++ b/backend/internal/service/video_transcode.go @@ -5,6 +5,7 @@ import ( "encoding/binary" "fmt" "io" + "log" "net/http" "os" "os/exec" @@ -26,13 +27,14 @@ import ( var ErrPlaybackNotReady = errors.New("播放副本尚未就绪") const ( - playbackDirName = "playback" - videoCodecH264 = "h264" - videoCodecH265 = "h265" - videoCodecAV1 = "av1" - videoCodecVP9 = "vp9" - videoCodecMPEG4 = "mpeg4" - probeMaxMoovSize = 128 << 20 + playbackDirName = "playback" + videoCodecH264 = "h264" + videoCodecH265 = "h265" + videoCodecAV1 = "av1" + videoCodecVP9 = "vp9" + videoCodecMPEG4 = "mpeg4" + probeMaxMoovSize = 128 << 20 + playbackPersistAttempts = 3 ) // probeVideoCodec 解析本地 mp4 的 stsd 首个视频 sample entry fourcc,返回 h264/h265 等。 @@ -200,13 +202,16 @@ func (s *Service) maybeStartPlaybackTranscode(resource *model.Resource) { } } -// markPlaybackNone 将资源标记为无需播放副本(幂等,写失败不影响上传主流程)。 +// markPlaybackNone 将资源标记为无需播放副本(幂等)。写失败必须可见:空状态会被前端当成 processing 轮询。 func markPlaybackNone(s *Service, resource *model.Resource) { if resource.PlaybackStatus == model.PlaybackStatusNone { return } + previous := resource.PlaybackStatus resource.PlaybackStatus = model.PlaybackStatusNone - _ = s.repo.SaveResource(resource) + if err := persistPlaybackResource(s.repo, resource, "mark_none"); err != nil { + resource.PlaybackStatus = previous + } } // runPlaybackTranscode 转码本地原件到 playback/.mp4 并回写状态(幂等按 id 重载)。 @@ -217,7 +222,7 @@ func (s *Service) runPlaybackTranscode(userID string, resourceID string, src str if res, err := s.repo.ResourceForUser(userID, resourceID); err == nil && res != nil { res.PlaybackStatus = model.PlaybackStatusFailed res.PlaybackError = clipText(fmt.Sprintf("转码 panic:%v", r), 1000) - _ = s.repo.SaveResource(res) + _ = persistPlaybackResource(s.repo, res, "panic") } } }() @@ -237,12 +242,29 @@ func (s *Service) runPlaybackTranscode(userID string, resourceID string, src str } res, err := s.repo.ResourceForUser(userID, resourceID) if err != nil || res == nil { + log.Printf("playback transcode persist skipped: resource=%s user=%s lookup_error=%v", resourceID, userID, err) return } res.PlaybackStatus = status res.PlaybackObjectKey = objectKey res.PlaybackError = errText - _ = s.repo.SaveResource(res) + _ = persistPlaybackResource(s.repo, res, "transcode_complete") +} + +type resourcePersister interface { + SaveResource(resource *model.Resource) error +} + +func persistPlaybackResource(saver resourcePersister, resource *model.Resource, context string) error { + var err error + for attempt := 1; attempt <= playbackPersistAttempts; attempt++ { + err = saver.SaveResource(resource) + if err == nil { + return nil + } + } + log.Printf("playback transcode persist failed: resource=%s context=%s attempts=%d error=%v", resource.ID, context, playbackPersistAttempts, err) + return err } // runH264Transcode 用 ffmpeg 将任意输入转为 H.264/AAC mp4(faststart、yuv420p、偶数尺寸)。 diff --git a/backend/internal/service/video_transcode_test.go b/backend/internal/service/video_transcode_test.go index 59dfe14a8..5e194d09c 100644 --- a/backend/internal/service/video_transcode_test.go +++ b/backend/internal/service/video_transcode_test.go @@ -8,6 +8,8 @@ import ( "testing" "time" + "errors" + "infinite-canvas/backend/internal/model" ) @@ -141,3 +143,36 @@ func TestBackfillRejudgesLegacyNoneVideos(t *testing.T) { t.Fatalf("fake mp4v 应转码失败落 failed,实际 %q", mp4v.PlaybackStatus) } } + +type failingResourceSaver struct { + failTimes int + calls int +} + +func (s *failingResourceSaver) SaveResource(*model.Resource) error { + s.calls++ + if s.calls <= s.failTimes { + return errors.New("db busy") + } + return nil +} + +func TestPersistPlaybackResourceRetriesThenSucceeds(t *testing.T) { + saver := &failingResourceSaver{failTimes: 2} + if err := persistPlaybackResource(saver, &model.Resource{ID: "r1"}, "test"); err != nil { + t.Fatal(err) + } + if saver.calls != playbackPersistAttempts { + t.Fatalf("calls = %d, want %d", saver.calls, playbackPersistAttempts) + } +} + +func TestPersistPlaybackResourceReturnsAfterExhaustedRetries(t *testing.T) { + saver := &failingResourceSaver{failTimes: 10} + if err := persistPlaybackResource(saver, &model.Resource{ID: "r1"}, "test"); err == nil { + t.Fatal("expected persist error") + } + if saver.calls != playbackPersistAttempts { + t.Fatalf("calls = %d, want %d", saver.calls, playbackPersistAttempts) + } +} diff --git a/canvas-agent/README.md b/canvas-agent/README.md index a433dca44..f2c4afcdc 100644 --- a/canvas-agent/README.md +++ b/canvas-agent/README.md @@ -8,12 +8,12 @@ npx -y @ddcat666/open-ai-canvas-agent ``` -本仓库开发时也可以直接运行: +本仓库开发时也可以直接运行。依赖锁定在 `bun.lock`,不要用 pnpm 或 npm 覆盖同一套 `node_modules`: ```bash cd canvas-agent -npm install -npm run build +bun install +bun run build node dist/index.js ``` diff --git a/canvas-agent/package.json b/canvas-agent/package.json index 9ac54c6cd..17a6aecf4 100644 --- a/canvas-agent/package.json +++ b/canvas-agent/package.json @@ -17,7 +17,7 @@ "test": "tsx --test --test-concurrency=1 test/*.test.ts", "build": "tsc -p tsconfig.json", "start": "node dist/index.js", - "prepack": "npm run build" + "prepack": "bun run build" }, "dependencies": { "@modelcontextprotocol/sdk": "^1.30.0", @@ -39,6 +39,7 @@ "overrides": { "zod": "3.25.76" }, + "packageManager": "bun@1.3.9", "engines": { "node": ">=18" }, diff --git a/canvas-agent/pnpm-lock.yaml b/canvas-agent/pnpm-lock.yaml deleted file mode 100644 index e2eb40f97..000000000 --- a/canvas-agent/pnpm-lock.yaml +++ /dev/null @@ -1,2012 +0,0 @@ -lockfileVersion: '9.0' - -settings: - autoInstallPeers: true - excludeLinksFromLockfile: false - -importers: - - .: - dependencies: - '@modelcontextprotocol/sdk': - specifier: ^1.30.0 - version: 1.30.0(zod@3.25.76) - '@openai/codex': - specifier: ^0.149.1 - version: 0.149.1 - docx: - specifier: ^9.5.1 - version: 9.7.1 - express: - specifier: ^5.2.1 - version: 5.2.1 - json-canonicalize: - specifier: 2.0.0 - version: 2.0.0 - onnxruntime-node: - specifier: ^1.22.0 - version: 1.29.0 - playwright-core: - specifier: ^1.55.0 - version: 1.62.1 - sharp: - specifier: ^0.34.3 - version: 0.34.5 - zod: - specifier: ^3.25.76 - version: 3.25.76 - devDependencies: - '@types/express': - specifier: ^5.0.6 - version: 5.0.6 - '@types/node': - specifier: ^22.20.1 - version: 22.20.1 - tsx: - specifier: ^4.23.1 - version: 4.23.12 - typescript: - specifier: ^7.0.2 - version: 7.0.2 - -packages: - - '@emnapi/runtime@1.11.3': - resolution: {integrity: sha512-Xz4Tpyki7XyrpbUK1jR1AhdAdaXyhhY4lZ3neLodmhpuWfy2PAQN5B46sAiU4liOXGLkHypn/qU+jvfWSCYYLA==} - - '@esbuild/aix-ppc64@0.28.2': - resolution: {integrity: sha512-XExcO+dvLKvVtNTibSTBej1NCAbaGhWn9Ww1ZPx80qsahhPFe/8jgWP0IchNe0F3HwkU7n8ejhH8bjonqht8mQ==} - engines: {node: '>=18'} - cpu: [ppc64] - os: [aix] - - '@esbuild/android-arm64@0.28.2': - resolution: {integrity: sha512-5YfKeeI8qWfBZIX+u2xZC3Zlb3Os/gLS2sbEKM+I4ZOcsWmHS2WLysCcQZDAFRslDUU5Oiq44gf6PYN1vGwG5A==} - engines: {node: '>=18'} - cpu: [arm64] - os: [android] - - '@esbuild/android-arm@0.28.2': - resolution: {integrity: sha512-kXXoiPVVGQcnIYGOeaovwOURpniDBpSq4A03qkQ+BMQqtGG6HYap3xne9C1O1yo4TR3qxlCX5IqqmX6fFo2Lqg==} - engines: {node: '>=18'} - cpu: [arm] - os: [android] - - '@esbuild/android-x64@0.28.2': - resolution: {integrity: sha512-O387ite7SzUyCcy3JQX4P4bLtEA7bLLkx+esve5JHnyYfNTxcVpXZo9jhdB0lTKN44gztELTdU7nS8Nr16Fs1Q==} - engines: {node: '>=18'} - cpu: [x64] - os: [android] - - '@esbuild/darwin-arm64@0.28.2': - resolution: {integrity: sha512-n4KqkOQrraxHJcgjM1RvwbigfQKIKJVpM7xp+KsxiyUSrRdIXnt73VhrPAx0fV44hgfmIVKjxMN9J1t5jySVkw==} - engines: {node: '>=18'} - cpu: [arm64] - os: [darwin] - - '@esbuild/darwin-x64@0.28.2': - resolution: {integrity: sha512-uq6suIWYP37qzGddBKPw5QEQPi6HiLGsO7UmkpfyaYNQ3D+rN6w6WfwH+nuqcGXWvawGwxOEroO4YGnFh95azw==} - engines: {node: '>=18'} - cpu: [x64] - os: [darwin] - - '@esbuild/freebsd-arm64@0.28.2': - resolution: {integrity: sha512-n+I0BTSRIoy+d6RPKnEVwql5UwBJolytvY4mAOIEJorKlqgPII8ix6slVVrfZ5Tnj7glIZvloylbB/EJPMWEXw==} - engines: {node: '>=18'} - cpu: [arm64] - os: [freebsd] - - '@esbuild/freebsd-x64@0.28.2': - resolution: {integrity: sha512-78XJTJkvPs0kz2w61301PJjXl4g7q3JqiYMZ/M/yVI73EHBrCRTgkhu9oqG7vPqq+a/yadEW8aD+agKlk5xrmg==} - engines: {node: '>=18'} - cpu: [x64] - os: [freebsd] - - '@esbuild/linux-arm64@0.28.2': - resolution: {integrity: sha512-pW4AC0P3it8c7do9MVM4p51FzHzdM/TZrerurgRcHJ2WTa1VQ1CIq18xncfpBJw4ojkiZZrKW2yIBWBP92j6Ug==} - engines: {node: '>=18'} - cpu: [arm64] - os: [linux] - - '@esbuild/linux-arm@0.28.2': - resolution: {integrity: sha512-XlDnu2q5yoqems+xay6wSAcg9DDD7K9RLKZEBOMZm3ckNpJBvOX20tSfby8KfrrhINDyv9V2YVZKY/SpoGJI8w==} - engines: {node: '>=18'} - cpu: [arm] - os: [linux] - - '@esbuild/linux-ia32@0.28.2': - resolution: {integrity: sha512-CYbnj78HsIeA+DhgUKgFCfvNsTHFhMMrinUrMZpDXJXKN8T3XViTZ/+wtHeVxEWY8ewSzTFN+nRmSwO2tZaLUQ==} - engines: {node: '>=18'} - cpu: [ia32] - os: [linux] - - '@esbuild/linux-loong64@0.28.2': - resolution: {integrity: sha512-buwkd8nsph4R+ajRvw0qM5Hja/TXQow3ptzWO2EbG/cqcIkHloRrdlBtQlshyYGTNFvfkfJ5tpPLVkY4DtsPfQ==} - engines: {node: '>=18'} - cpu: [loong64] - os: [linux] - - '@esbuild/linux-mips64el@0.28.2': - resolution: {integrity: sha512-ZVykbDyk7519VwiNb9Lcj9m8XM6v5V9uKPvrEMkkEedVewf+0itkhahp4HDpgERXhwLRpWFypsGbG/J8s0QjJA==} - engines: {node: '>=18'} - cpu: [mips64el] - os: [linux] - - '@esbuild/linux-ppc64@0.28.2': - resolution: {integrity: sha512-CAXl+Dtd9UUuJd8pKKdwh6MLm3MUMiqMPmhZ3tTSXPqfyQ3vDl6R5hZdZ/kYojK4ofXtdfSv1tFq8XzWx3heNQ==} - engines: {node: '>=18'} - cpu: [ppc64] - os: [linux] - - '@esbuild/linux-riscv64@0.28.2': - resolution: {integrity: sha512-GeXCej4IQtU1B+QlDV8W/RRvbzI3O/Stss+/bCXv4lZls5WGRtu2a+3JkA3i4qIUlMXpcHebWpF8AkJhATowuA==} - engines: {node: '>=18'} - cpu: [riscv64] - os: [linux] - - '@esbuild/linux-s390x@0.28.2': - resolution: {integrity: sha512-3H1weTYZPxt/WOhByszQZybS9w5lKzUn1FDMsgEChbHWQwHYQQRfBxgCcZvPhjHfKyJjIievvMmEUawJrdY9Dg==} - engines: {node: '>=18'} - cpu: [s390x] - os: [linux] - - '@esbuild/linux-x64@0.28.2': - resolution: {integrity: sha512-4xTZr1FUmSoQW4XIWmit3tzQrUTZM+N3P0XV8xROKYF50XfI7xeO90+1bZvNwxIufQ9hDQVRJH5YhgPVF8A/HQ==} - engines: {node: '>=18'} - cpu: [x64] - os: [linux] - - '@esbuild/netbsd-arm64@0.28.2': - resolution: {integrity: sha512-sSATRjPeDBg3pdgHoQfoYBob11Kk1FGa9lui5RIHZCoCkJa9QKlvl3/vKz2usCmYYjs7ymJR/2Nnsqe+Hjt5nw==} - engines: {node: '>=18'} - cpu: [arm64] - os: [netbsd] - - '@esbuild/netbsd-x64@0.28.2': - resolution: {integrity: sha512-lqnzCV+mM0gIADaKihiCg6ifgfU2L3h5E33rNQBN1Y4MaVGnzryzmvvf7UHxprpQdE8hpqLolJ9Rl+SkIRDpyw==} - engines: {node: '>=18'} - cpu: [x64] - os: [netbsd] - - '@esbuild/openbsd-arm64@0.28.2': - resolution: {integrity: sha512-AL2qJILH7lNjrDmCQDvdxMfAUIv8KMNZOvrwAQ8i8//ntL9FflhOyMJ8OZSMBb8/AWXe3/5v5S20y3zCoZWKoQ==} - engines: {node: '>=18'} - cpu: [arm64] - os: [openbsd] - - '@esbuild/openbsd-x64@0.28.2': - resolution: {integrity: sha512-QtiuPytchRyC4rwUKhexJdQKvDuZ6hWloi3igqPQNUJCS1/v9EiO3UTOXR6A3FoMo4fnAKbWJdqaIwhOzh8qEw==} - engines: {node: '>=18'} - cpu: [x64] - os: [openbsd] - - '@esbuild/openharmony-arm64@0.28.2': - resolution: {integrity: sha512-WkhYDmpTjLvGlScA1rwjRUmhl4k8oXR3cIbtqWmELgU/dFeHHlEllxDvdWcNJV9rbzCexB5vz8gtNewWLgCT7Q==} - engines: {node: '>=18'} - cpu: [arm64] - os: [openharmony] - - '@esbuild/sunos-x64@0.28.2': - resolution: {integrity: sha512-GPMSkTOtMnv2U2F8gxe4Io6qmVs+YKyp832Etqqxr0hFngmXQ3rzwytelm3GIn7T4VviRUlf3sOgBOiTdvaf7g==} - engines: {node: '>=18'} - cpu: [x64] - os: [sunos] - - '@esbuild/win32-arm64@0.28.2': - resolution: {integrity: sha512-PIhhEkE9uPBleRBrQEJpUn7MBnibZzbGzYWPmY3x+YoVg/95zbjB4CxPPOQ8l5tYYM4mMaCthF8/1DIfBQQyWQ==} - engines: {node: '>=18'} - cpu: [arm64] - os: [win32] - - '@esbuild/win32-ia32@0.28.2': - resolution: {integrity: sha512-YmJbfTlvU7Sdn9BB+4PRES4oB6pxgS37MAONj+hBr/cpXS1aBPKXxNnDbu+QCWPj0o9dgyxeq79g6c5P8KeuYA==} - engines: {node: '>=18'} - cpu: [ia32] - os: [win32] - - '@esbuild/win32-x64@0.28.2': - resolution: {integrity: sha512-5ebpxr3nWMzrL/rnUI755Jkuee0bHL/Gq0WTF9lvcpv73wAp5eu8MfBUgWK9bhWvZjj7yX8etf/8tI8Ney695g==} - engines: {node: '>=18'} - cpu: [x64] - os: [win32] - - '@hono/node-server@2.1.1': - resolution: {integrity: sha512-ELuehkj5VCBdgEw9zs+ivkKwyzzUCSQuE96YmiPvn1ECBoZCczbFXJLeEGMTYjphP6gydh4pHMqEYPVMYUVgQg==} - engines: {node: '>=20'} - peerDependencies: - hono: ^4 - - '@img/colour@1.1.0': - resolution: {integrity: sha512-Td76q7j57o/tLVdgS746cYARfSyxk8iEfRxewL9h4OMzYhbW4TAcppl0mT4eyqXddh6L/jwoM75mo7ixa/pCeQ==} - engines: {node: '>=18'} - - '@img/sharp-darwin-arm64@0.34.5': - resolution: {integrity: sha512-imtQ3WMJXbMY4fxb/Ndp6HBTNVtWCUI0WdobyheGf5+ad6xX8VIDO8u2xE4qc/fr08CKG/7dDseFtn6M6g/r3w==} - engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} - cpu: [arm64] - os: [darwin] - - '@img/sharp-darwin-x64@0.34.5': - resolution: {integrity: sha512-YNEFAF/4KQ/PeW0N+r+aVVsoIY0/qxxikF2SWdp+NRkmMB7y9LBZAVqQ4yhGCm/H3H270OSykqmQMKLBhBJDEw==} - engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} - cpu: [x64] - os: [darwin] - - '@img/sharp-libvips-darwin-arm64@1.2.4': - resolution: {integrity: sha512-zqjjo7RatFfFoP0MkQ51jfuFZBnVE2pRiaydKJ1G/rHZvnsrHAOcQALIi9sA5co5xenQdTugCvtb1cuf78Vf4g==} - cpu: [arm64] - os: [darwin] - - '@img/sharp-libvips-darwin-x64@1.2.4': - resolution: {integrity: sha512-1IOd5xfVhlGwX+zXv2N93k0yMONvUlANylbJw1eTah8K/Jtpi15KC+WSiaX/nBmbm2HxRM1gZ0nSdjSsrZbGKg==} - cpu: [x64] - os: [darwin] - - '@img/sharp-libvips-linux-arm64@1.2.4': - resolution: {integrity: sha512-excjX8DfsIcJ10x1Kzr4RcWe1edC9PquDRRPx3YVCvQv+U5p7Yin2s32ftzikXojb1PIFc/9Mt28/y+iRklkrw==} - cpu: [arm64] - os: [linux] - - '@img/sharp-libvips-linux-arm@1.2.4': - resolution: {integrity: sha512-bFI7xcKFELdiNCVov8e44Ia4u2byA+l3XtsAj+Q8tfCwO6BQ8iDojYdvoPMqsKDkuoOo+X6HZA0s0q11ANMQ8A==} - cpu: [arm] - os: [linux] - - '@img/sharp-libvips-linux-ppc64@1.2.4': - resolution: {integrity: sha512-FMuvGijLDYG6lW+b/UvyilUWu5Ayu+3r2d1S8notiGCIyYU/76eig1UfMmkZ7vwgOrzKzlQbFSuQfgm7GYUPpA==} - cpu: [ppc64] - os: [linux] - - '@img/sharp-libvips-linux-riscv64@1.2.4': - resolution: {integrity: sha512-oVDbcR4zUC0ce82teubSm+x6ETixtKZBh/qbREIOcI3cULzDyb18Sr/Wcyx7NRQeQzOiHTNbZFF1UwPS2scyGA==} - cpu: [riscv64] - os: [linux] - - '@img/sharp-libvips-linux-s390x@1.2.4': - resolution: {integrity: sha512-qmp9VrzgPgMoGZyPvrQHqk02uyjA0/QrTO26Tqk6l4ZV0MPWIW6LTkqOIov+J1yEu7MbFQaDpwdwJKhbJvuRxQ==} - cpu: [s390x] - os: [linux] - - '@img/sharp-libvips-linux-x64@1.2.4': - resolution: {integrity: sha512-tJxiiLsmHc9Ax1bz3oaOYBURTXGIRDODBqhveVHonrHJ9/+k89qbLl0bcJns+e4t4rvaNBxaEZsFtSfAdquPrw==} - cpu: [x64] - os: [linux] - - '@img/sharp-libvips-linuxmusl-arm64@1.2.4': - resolution: {integrity: sha512-FVQHuwx1IIuNow9QAbYUzJ+En8KcVm9Lk5+uGUQJHaZmMECZmOlix9HnH7n1TRkXMS0pGxIJokIVB9SuqZGGXw==} - cpu: [arm64] - os: [linux] - - '@img/sharp-libvips-linuxmusl-x64@1.2.4': - resolution: {integrity: sha512-+LpyBk7L44ZIXwz/VYfglaX/okxezESc6UxDSoyo2Ks6Jxc4Y7sGjpgU9s4PMgqgjj1gZCylTieNamqA1MF7Dg==} - cpu: [x64] - os: [linux] - - '@img/sharp-linux-arm64@0.34.5': - resolution: {integrity: sha512-bKQzaJRY/bkPOXyKx5EVup7qkaojECG6NLYswgktOZjaXecSAeCWiZwwiFf3/Y+O1HrauiE3FVsGxFg8c24rZg==} - engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} - cpu: [arm64] - os: [linux] - - '@img/sharp-linux-arm@0.34.5': - resolution: {integrity: sha512-9dLqsvwtg1uuXBGZKsxem9595+ujv0sJ6Vi8wcTANSFpwV/GONat5eCkzQo/1O6zRIkh0m/8+5BjrRr7jDUSZw==} - engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} - cpu: [arm] - os: [linux] - - '@img/sharp-linux-ppc64@0.34.5': - resolution: {integrity: sha512-7zznwNaqW6YtsfrGGDA6BRkISKAAE1Jo0QdpNYXNMHu2+0dTrPflTLNkpc8l7MUP5M16ZJcUvysVWWrMefZquA==} - engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} - cpu: [ppc64] - os: [linux] - - '@img/sharp-linux-riscv64@0.34.5': - resolution: {integrity: sha512-51gJuLPTKa7piYPaVs8GmByo7/U7/7TZOq+cnXJIHZKavIRHAP77e3N2HEl3dgiqdD/w0yUfiJnII77PuDDFdw==} - engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} - cpu: [riscv64] - os: [linux] - - '@img/sharp-linux-s390x@0.34.5': - resolution: {integrity: sha512-nQtCk0PdKfho3eC5MrbQoigJ2gd1CgddUMkabUj+rBevs8tZ2cULOx46E7oyX+04WGfABgIwmMC0VqieTiR4jg==} - engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} - cpu: [s390x] - os: [linux] - - '@img/sharp-linux-x64@0.34.5': - resolution: {integrity: sha512-MEzd8HPKxVxVenwAa+JRPwEC7QFjoPWuS5NZnBt6B3pu7EG2Ge0id1oLHZpPJdn3OQK+BQDiw9zStiHBTJQQQQ==} - engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} - cpu: [x64] - os: [linux] - - '@img/sharp-linuxmusl-arm64@0.34.5': - resolution: {integrity: sha512-fprJR6GtRsMt6Kyfq44IsChVZeGN97gTD331weR1ex1c1rypDEABN6Tm2xa1wE6lYb5DdEnk03NZPqA7Id21yg==} - engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} - cpu: [arm64] - os: [linux] - - '@img/sharp-linuxmusl-x64@0.34.5': - resolution: {integrity: sha512-Jg8wNT1MUzIvhBFxViqrEhWDGzqymo3sV7z7ZsaWbZNDLXRJZoRGrjulp60YYtV4wfY8VIKcWidjojlLcWrd8Q==} - engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} - cpu: [x64] - os: [linux] - - '@img/sharp-wasm32@0.34.5': - resolution: {integrity: sha512-OdWTEiVkY2PHwqkbBI8frFxQQFekHaSSkUIJkwzclWZe64O1X4UlUjqqqLaPbUpMOQk6FBu/HtlGXNblIs0huw==} - engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} - cpu: [wasm32] - - '@img/sharp-win32-arm64@0.34.5': - resolution: {integrity: sha512-WQ3AgWCWYSb2yt+IG8mnC6Jdk9Whs7O0gxphblsLvdhSpSTtmu69ZG1Gkb6NuvxsNACwiPV6cNSZNzt0KPsw7g==} - engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} - cpu: [arm64] - os: [win32] - - '@img/sharp-win32-ia32@0.34.5': - resolution: {integrity: sha512-FV9m/7NmeCmSHDD5j4+4pNI8Cp3aW+JvLoXcTUo0IqyjSfAZJ8dIUmijx1qaJsIiU+Hosw6xM5KijAWRJCSgNg==} - engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} - cpu: [ia32] - os: [win32] - - '@img/sharp-win32-x64@0.34.5': - resolution: {integrity: sha512-+29YMsqY2/9eFEiW93eqWnuLcWcufowXewwSNIT6UwZdUUCrM3oFjMWH/Z6/TMmb4hlFenmfAVbpWeup2jryCw==} - engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} - cpu: [x64] - os: [win32] - - '@modelcontextprotocol/sdk@1.30.0': - resolution: {integrity: sha512-xKd8OIzlqNzcqcNumGAa6g+PW2kjD5vrpcKOnfldAUPP3j7lnqMPwlTXQm8gF+UwH72z0lqaRbjr9hqGz0eITA==} - engines: {node: '>=18'} - peerDependencies: - '@cfworker/json-schema': ^4.1.1 - zod: ^3.25 || ^4.0 - peerDependenciesMeta: - '@cfworker/json-schema': - optional: true - - '@openai/codex@0.149.1': - resolution: {integrity: sha512-6q5pbcpFbJbqOpkubSDBwXmktQ55aD8eUzGzBF1zASob2DjwhBKDSNGtdZKalfrNJUdTDTPDMmzCXEXs5tMBYA==} - engines: {node: '>=16'} - hasBin: true - - '@openai/codex@0.149.1-darwin-arm64': - resolution: {integrity: sha512-6X84kTCbnTgPIJ2EdcPsrvwS0Wxsqpa+bCswGmRf4BjhcQ5nPMnBC6yCAaCMj+vrbXQHj+L6sa9FaR4QkmA1qw==} - engines: {node: '>=16'} - cpu: [arm64] - os: [darwin] - - '@openai/codex@0.149.1-darwin-x64': - resolution: {integrity: sha512-MfLBQLfcElJL9tvj6y45qVHHMGSXCPnQOixuD3/Zq0g1BW/eFizkrGLdn48cFpc+l8cK+gt5nYG5pQYwVs6g4A==} - engines: {node: '>=16'} - cpu: [x64] - os: [darwin] - - '@openai/codex@0.149.1-linux-arm64': - resolution: {integrity: sha512-OqxUfZ1TVvHd18zHPKK/8ZRlpk8Vy11mg5CMHaLxNWldTbwVImDKtSLWT+m8m4NM5Sz4PbjtZMrVT/RfpBW/mQ==} - engines: {node: '>=16'} - cpu: [arm64] - os: [linux] - - '@openai/codex@0.149.1-linux-x64': - resolution: {integrity: sha512-Of5fGYgr7tAMsyj6vhXb4/RM/UoA3Zq8BLegUBDC09UNy1XTLGYP/2XD+UX8z3qh0NDwxYdCjFIWdDNijKZggQ==} - engines: {node: '>=16'} - cpu: [x64] - os: [linux] - - '@openai/codex@0.149.1-win32-arm64': - resolution: {integrity: sha512-5K0DmOKGK9Bos627p8sK8ATHjovPK0sDyT6h9Cb+4v+5CW5SGw1HLgjGxoLfJ8g3cg6mtg/pRCXXo2L/j71UVA==} - engines: {node: '>=16'} - cpu: [arm64] - os: [win32] - - '@openai/codex@0.149.1-win32-x64': - resolution: {integrity: sha512-G3QXGAg7nyyhqOeooAMUekBCeHd8a1QByhKcVAFyzNBaI06t6Ft7nsF+1SzFS0spuIdU4YyMi5YD26ukADBQUQ==} - engines: {node: '>=16'} - cpu: [x64] - os: [win32] - - '@types/body-parser@1.19.6': - resolution: {integrity: sha512-HLFeCYgz89uk22N5Qg3dvGvsv46B8GLvKKo1zKG4NybA8U2DiEO3w9lqGg29t/tfLRJpJ6iQxnVw4OnB7MoM9g==} - - '@types/connect@3.4.38': - resolution: {integrity: sha512-K6uROf1LD88uDQqJCktA4yzL1YYAK6NgfsI0v/mTgyPKWsX1CnJ0XPSDhViejru1GcRkLWb8RlzFYJRqGUbaug==} - - '@types/express-serve-static-core@5.1.3': - resolution: {integrity: sha512-dPfW8NFiOF4wOHc7+N/QSxlY9cfSsenewGbAz8C8U/MULPd/YZ27LvJUIlzaXie7e6Ove9YunJGgC9tbHD2cKw==} - - '@types/express@5.0.6': - resolution: {integrity: sha512-sKYVuV7Sv9fbPIt/442koC7+IIwK5olP1KWeD88e/idgoJqDm3JV/YUiPwkoKK92ylff2MGxSz1CSjsXelx0YA==} - - '@types/http-errors@2.0.5': - resolution: {integrity: sha512-r8Tayk8HJnX0FztbZN7oVqGccWgw98T/0neJphO91KkmOzug1KkofZURD4UaD5uH8AqcFLfdPErnBod0u71/qg==} - - '@types/node@22.20.1': - resolution: {integrity: sha512-EANqOCF9QFyra+4pfxUcX9STKJpCLjMbObVzljIJomAWSnuSIEAvyzEU53GaajbXJEgdh0iEcPL+DGvpUd4k1Q==} - - '@types/node@25.9.5': - resolution: {integrity: sha512-OScDchr2fwuUmWdf4kZ9h7PcJiYDVInhJizG/biAq3cAvqwYktuy/TYGGdZNMtNTFUP7rnb0NU4TUdm82kt4Rg==} - - '@types/qs@6.15.1': - resolution: {integrity: sha512-GZHUBZR9hckSUhrxmp1nG6NwdpM9fCunJwyThLW1X3AyHgd9IlHb6VANpQQqDr2o/qQp6McZ3y/IA2rVzKzSbw==} - - '@types/range-parser@1.2.7': - resolution: {integrity: sha512-hKormJbkJqzQGhziax5PItDUTMAM9uE2XXQmM37dyd4hVM+5aVl7oVxMVUiVQn2oCQFN/LKCZdvSM0pFRqbSmQ==} - - '@types/send@1.2.1': - resolution: {integrity: sha512-arsCikDvlU99zl1g69TcAB3mzZPpxgw0UQnaHeC1Nwb015xp8bknZv5rIfri9xTOcMuaVgvabfIRA7PSZVuZIQ==} - - '@types/serve-static@2.2.0': - resolution: {integrity: sha512-8mam4H1NHLtu7nmtalF7eyBH14QyOASmcxHhSfEoRyr0nP/YdoesEtU+uSRvMe96TW/HPTtkoKqQLl53N7UXMQ==} - - '@typescript/typescript-aix-ppc64@7.0.2': - resolution: {integrity: sha512-MTKKkWB7p/0E9xi1d1tHtZ5PiLkGEMIq88pK2CubZjOsLtYTLqhgIgi6zepFa+9GHZ6h05NMCkQxGKiPXMxXtQ==} - engines: {node: '>=16.20.0'} - cpu: [ppc64] - os: [aix] - - '@typescript/typescript-darwin-arm64@7.0.2': - resolution: {integrity: sha512-gowzar9MwS/aRWp6f3a4KUqzRjAZjOsmGNCM6LcTgXum+dBfgsBVMN+AgvOCCbguXyick6LJhpBszxMebJ8syA==} - engines: {node: '>=16.20.0'} - cpu: [arm64] - os: [darwin] - - '@typescript/typescript-darwin-x64@7.0.2': - resolution: {integrity: sha512-SZ9xZInqApNlNGc9s0W1VSsktYSOe9cFqNOIqmN1Gs8SmkjKZYFt017G4VwPxASInODuAdbTW7sXiFUf893RgA==} - engines: {node: '>=16.20.0'} - cpu: [x64] - os: [darwin] - - '@typescript/typescript-freebsd-arm64@7.0.2': - resolution: {integrity: sha512-W5NH4y/J0plIIS5b2xvTEkU7JFxyqdMAOgf+Ilhl0vHQXKO5dZoxd+C/jEtq56c4F3wk71RB4BMRQ2XdI+bwYQ==} - engines: {node: '>=16.20.0'} - cpu: [arm64] - os: [freebsd] - - '@typescript/typescript-freebsd-x64@7.0.2': - resolution: {integrity: sha512-UMGDx5sTpzNw3WiPebH7l90IWfJggEd+egHt/q6p7/Cm3zqoV7VxkGXt+3DxPIw8CcmvAB0j3sVVfbhX+M4Tpw==} - engines: {node: '>=16.20.0'} - cpu: [x64] - os: [freebsd] - - '@typescript/typescript-linux-arm64@7.0.2': - resolution: {integrity: sha512-Qh4eU4/y3yDjnfjjyPYihMj5/ODIlmt+Bzu17OI+fiSRDW57QmU5SiN63exPRNJPKUzcc1INa1NXdrJ+MqHjUQ==} - engines: {node: '>=16.20.0'} - cpu: [arm64] - os: [linux] - - '@typescript/typescript-linux-arm@7.0.2': - resolution: {integrity: sha512-gffT3xPz9sR7j/YJExkyPntrI0P2EP9XbOyWzth2/Gs0RstK+90RBcO0ncXoXy/beYll1SXw846Nf2zdnEz0QQ==} - engines: {node: '>=16.20.0'} - cpu: [arm] - os: [linux] - - '@typescript/typescript-linux-loong64@7.0.2': - resolution: {integrity: sha512-uEHck9i8hoAzXPiYRib1O7miOnz23SxIeVl6F4LXox+qov1K35jHcEW6VHKvZI+pyvl7fZEP4MCU5LYvIq1GuQ==} - engines: {node: '>=16.20.0'} - cpu: [loong64] - os: [linux] - - '@typescript/typescript-linux-mips64el@7.0.2': - resolution: {integrity: sha512-R4KvAMnE43W5Qeqb0Ly56O3mWMWIAgsMyz36DCaycd5nbg/9kzm0liw3JocfRqyJY0KPmzFjbswozXyW0DnIYA==} - engines: {node: '>=16.20.0'} - cpu: [mips64el] - os: [linux] - - '@typescript/typescript-linux-ppc64@7.0.2': - resolution: {integrity: sha512-DORx5b3sd/4S7eayxm4FQv+A7CrkUIGRaHiwI8oiHTAI1fAPWhF4J0vAlkC8biAlHSVVwxMQ3tjZ2/DVbnQiiA==} - engines: {node: '>=16.20.0'} - cpu: [ppc64] - os: [linux] - - '@typescript/typescript-linux-riscv64@7.0.2': - resolution: {integrity: sha512-wf0jqEDOjrPRnKwYRyyJDRo11KMbvMFrU+q4zqKyChODBzvlkbhNQfKvLxQCcwTpdDaXSHZTVuh0JoCrKCUMHQ==} - engines: {node: '>=16.20.0'} - cpu: [riscv64] - os: [linux] - - '@typescript/typescript-linux-s390x@7.0.2': - resolution: {integrity: sha512-IkwJc3L7yhytWd/ewjyxNDfOmswCm9GWMJT/ue/dU4aZNbwZeYAetq42VyLmsmSjvoX7z74X6ZaYCtzAr0EuGw==} - engines: {node: '>=16.20.0'} - cpu: [s390x] - os: [linux] - - '@typescript/typescript-linux-x64@7.0.2': - resolution: {integrity: sha512-EYdf2cNg7rgCWJnxCdJ+F3V39O8ihb37eHAu1LK8oAFizgTQbPOK7zHHXbPt8rX24COqODXeI3sIf0fCXG7H/A==} - engines: {node: '>=16.20.0'} - cpu: [x64] - os: [linux] - - '@typescript/typescript-netbsd-arm64@7.0.2': - resolution: {integrity: sha512-+polYF4MF04aPpO5FTkHran9yUQDSXqy5GiSDKpsll5jy3l3+g9QLhpf39T+ePtefhXLOGrLl0QIjkQP6VnelA==} - engines: {node: '>=16.20.0'} - cpu: [arm64] - os: [netbsd] - - '@typescript/typescript-netbsd-x64@7.0.2': - resolution: {integrity: sha512-8YIT0EHM/3dq10ZOVF/A7pc/YSMtbcecct4rWtexrnSCHOPcpC2KTLXfTCR6vDpnSiY12heNb1GiN/wu+T/FyA==} - engines: {node: '>=16.20.0'} - cpu: [x64] - os: [netbsd] - - '@typescript/typescript-openbsd-arm64@7.0.2': - resolution: {integrity: sha512-APT8+ClYnuYm1u9+kgGXoMj2VzWzcymwh2gNSQVySHfkRDGOTVkoWLjCmOQSaO+PoqQ57B0flRp9SA+7GnnkzQ==} - engines: {node: '>=16.20.0'} - cpu: [arm64] - os: [openbsd] - - '@typescript/typescript-openbsd-x64@7.0.2': - resolution: {integrity: sha512-yX7s+Q0Dln0Dt9tEzZsAjXXR/+ytBM7AlglaqyeMPxQszJ1JhlJdZ6jLA+IzldHtflX81em7lDao1xXu+aRRkg==} - engines: {node: '>=16.20.0'} - cpu: [x64] - os: [openbsd] - - '@typescript/typescript-sunos-x64@7.0.2': - resolution: {integrity: sha512-dLJDGaLZ1D4HPQn62u1n8mBDkJREwMsAkCdkwd4Ieqw+x3TUyTsqY0YiBCtE6H6OzzgGk3iuZ3vFWRS+E8/d1g==} - engines: {node: '>=16.20.0'} - cpu: [x64] - os: [sunos] - - '@typescript/typescript-win32-arm64@7.0.2': - resolution: {integrity: sha512-Gyl1Vy6OsWesLzmq+EP0Fb7b4Nid5232AvcA2SFcdYreldpNtYFFofPjnt62y9hQy7VTaZp65ICJjuAQRaVcIQ==} - engines: {node: '>=16.20.0'} - cpu: [arm64] - os: [win32] - - '@typescript/typescript-win32-x64@7.0.2': - resolution: {integrity: sha512-0BQ3HkAHHlKLSp1qRvf3SUhGpGsDuhB/jgFw75guyqbxJqEaS0Cw/VFO8i2nHglJUzQCRtMMR/IBAKE3ETMC4g==} - engines: {node: '>=16.20.0'} - cpu: [x64] - os: [win32] - - accepts@2.0.0: - resolution: {integrity: sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng==} - engines: {node: '>= 0.6'} - - adm-zip@0.6.0: - resolution: {integrity: sha512-XleryMhbuksdKtofnWZ9Sk+4CUTbms4Mb/EU32SZwToAyZ5RgVos/ki8n+yr0LWHOGKuakbXTuuYNHLQjhddgg==} - engines: {node: '>=14.0'} - - ajv-formats@3.0.1: - resolution: {integrity: sha512-8iUql50EUR+uUcdRQ3HDqa6EVyo3docL8g5WJ3FNcWmu62IbkGUue/pEyLBW8VGKKucTPgqeks4fIU1DA4yowQ==} - peerDependencies: - ajv: ^8.0.0 - peerDependenciesMeta: - ajv: - optional: true - - ajv@8.20.0: - resolution: {integrity: sha512-Thbli+OlOj+iMPYFBVBfJ3OmCAnaSyNn4M1vz9T6Gka5Jt9ba/HIR56joy65tY6kx/FCF5VXNB819Y7/GUrBGA==} - - body-parser@2.3.0: - resolution: {integrity: sha512-2cGmJupaNgg+QUwVLAucDuWuoMZ6EX9iHDRswZ5lsNYEmwPaRknMPCLZz07yTzVq/83p4o/wzbDZbBrTvGGTIw==} - engines: {node: '>=18'} - - bytes@3.1.2: - resolution: {integrity: sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==} - engines: {node: '>= 0.8'} - - call-bind-apply-helpers@1.0.2: - resolution: {integrity: sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==} - engines: {node: '>= 0.4'} - - call-bound@1.0.4: - resolution: {integrity: sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==} - engines: {node: '>= 0.4'} - - content-disposition@1.1.0: - resolution: {integrity: sha512-5jRCH9Z/+DRP7rkvY83B+yGIGX96OYdJmzngqnw2SBSxqCFPd0w2km3s5iawpGX8krnwSGmF0FW5Nhr0Hfai3g==} - engines: {node: '>=18'} - - content-type@1.0.5: - resolution: {integrity: sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==} - engines: {node: '>= 0.6'} - - content-type@2.1.0: - resolution: {integrity: sha512-mj7UPXE0jaqaOsukNZRUEfEi2AcL7C/vwmwcHV0O97eO1E1pxBZuyjlZrx5seTaNBg1U6+o35wpa35Qfcc+7ag==} - engines: {node: '>=18'} - - cookie-signature@1.2.2: - resolution: {integrity: sha512-D76uU73ulSXrD1UXF4KE2TMxVVwhsnCgfAyTg9k8P6KGZjlXKrOLe4dJQKI3Bxi5wjesZoFXJWElNWBjPZMbhg==} - engines: {node: '>=6.6.0'} - - cookie@0.7.2: - resolution: {integrity: sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==} - engines: {node: '>= 0.6'} - - core-util-is@1.0.3: - resolution: {integrity: sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==} - - cors@2.8.6: - resolution: {integrity: sha512-tJtZBBHA6vjIAaF6EnIaq6laBBP9aq/Y3ouVJjEfoHbRBcHBAHYcMh/w8LDrk2PvIMMq8gmopa5D4V8RmbrxGw==} - engines: {node: '>= 0.10'} - - cross-spawn@7.0.6: - resolution: {integrity: sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==} - engines: {node: '>= 8'} - - debug@4.4.3: - resolution: {integrity: sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==} - engines: {node: '>=6.0'} - peerDependencies: - supports-color: '*' - peerDependenciesMeta: - supports-color: - optional: true - - define-data-property@1.1.4: - resolution: {integrity: sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==} - engines: {node: '>= 0.4'} - - define-properties@1.2.1: - resolution: {integrity: sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg==} - engines: {node: '>= 0.4'} - - depd@2.0.0: - resolution: {integrity: sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==} - engines: {node: '>= 0.8'} - - detect-libc@2.1.2: - resolution: {integrity: sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==} - engines: {node: '>=8'} - - docx@9.7.1: - resolution: {integrity: sha512-ilXFf9Moz47ABjFpDiA5s1w9lpb4EFSp7+5iiJSbfyYDM+bpZdAgLlSr7fW4aXhVe/E+F6QCv0EvRVFEd5CsWg==} - engines: {node: '>=10'} - - dunder-proto@1.0.1: - resolution: {integrity: sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==} - engines: {node: '>= 0.4'} - - ee-first@1.1.1: - resolution: {integrity: sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==} - - encodeurl@2.0.0: - resolution: {integrity: sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==} - engines: {node: '>= 0.8'} - - es-define-property@1.0.1: - resolution: {integrity: sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==} - engines: {node: '>= 0.4'} - - es-errors@1.3.0: - resolution: {integrity: sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==} - engines: {node: '>= 0.4'} - - es-object-atoms@1.1.2: - resolution: {integrity: sha512-HWcBoN6NileqtSydK2FqHbS/LoDd2pqrnQHLyJzBj4kOp/ky2MWMN694xOfkK8/SnUsW2DH7EfyVlydKCsm1Zw==} - engines: {node: '>= 0.4'} - - esbuild@0.28.2: - resolution: {integrity: sha512-HKVLS8dvII+xoKW9kmqxbRKrnWEXfJJr/FZhhJmiqIB0e053QNYFqOBouTMO/k5sID4MvCiUCvv8b9M4h32wIA==} - engines: {node: '>=18'} - hasBin: true - - escape-html@1.0.3: - resolution: {integrity: sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==} - - escape-string-regexp@4.0.0: - resolution: {integrity: sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==} - engines: {node: '>=10'} - - etag@1.8.1: - resolution: {integrity: sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==} - engines: {node: '>= 0.6'} - - eventsource-parser@3.1.1: - resolution: {integrity: sha512-EKN1vKAMcZ8MlYMpaNuxN6R9yakzH6uajHcHVTqWJzvu5pWw9DyhbP35HH8MVBQ+dZjAfDxk+A8NiR9KWaXiyQ==} - engines: {node: '>=18.0.0'} - - eventsource@3.0.7: - resolution: {integrity: sha512-CRT1WTyuQoD771GW56XEZFQ/ZoSfWid1alKGDYMmkt2yl8UXrVR4pspqWNEcqKvVIzg6PAltWjxcSSPrboA4iA==} - engines: {node: '>=18.0.0'} - - express-rate-limit@8.6.2: - resolution: {integrity: sha512-YH4ru+eOJxQABscKFfRCy9R7x9QFGdezclVMwwgFFndzS2Xnm0uo6B0ABZsLhcpeptGv2qvuJVWlQr9gQZoC3A==} - engines: {node: '>= 16'} - peerDependencies: - express: '>= 4.11' - - express@5.2.1: - resolution: {integrity: sha512-hIS4idWWai69NezIdRt2xFVofaF4j+6INOpJlVOLDO8zXGpUVEVzIYk12UUi2JzjEzWL3IOAxcTubgz9Po0yXw==} - engines: {node: '>= 18'} - - fast-deep-equal@3.1.3: - resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==} - - fast-uri@3.1.6: - resolution: {integrity: sha512-7Ical1vFEMr0onbVzEDIreM22I4khW+fzyQPwvAFWBp1iwdshSZRsL4jjRvPG9JP1uiqMHRto+YU6R2/CzDz5Q==} - - finalhandler@2.1.1: - resolution: {integrity: sha512-S8KoZgRZN+a5rNwqTxlZZePjT/4cnm0ROV70LedRHZ0p8u9fRID0hJUZQpkKLzro8LfmC8sx23bY6tVNxv8pQA==} - engines: {node: '>= 18.0.0'} - - forwarded@0.2.0: - resolution: {integrity: sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==} - engines: {node: '>= 0.6'} - - fresh@2.0.0: - resolution: {integrity: sha512-Rx/WycZ60HOaqLKAi6cHRKKI7zxWbJ31MhntmtwMoaTeF7XFH9hhBp8vITaMidfljRQ6eYWCKkaTK+ykVJHP2A==} - engines: {node: '>= 0.8'} - - fsevents@2.3.3: - resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==} - engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} - os: [darwin] - - function-bind@1.1.2: - resolution: {integrity: sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==} - - get-intrinsic@1.3.0: - resolution: {integrity: sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==} - engines: {node: '>= 0.4'} - - get-proto@1.0.1: - resolution: {integrity: sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==} - engines: {node: '>= 0.4'} - - global-agent@4.1.3: - resolution: {integrity: sha512-KUJEViiuFT3I97t+GYMikLPJS2Lfo/S2F+DQuBWzuzaMPnvt5yyZePzArx36fBzpGTxZjIpDbXLeySLgh+k76g==} - engines: {node: '>=10.0'} - - globalthis@1.0.4: - resolution: {integrity: sha512-DpLKbNU4WylpxJykQujfCcwYWiV/Jhm50Goo0wrVILAv5jOr9d+H+UR3PhSCD2rCCEIg0uc+G+muBTwD54JhDQ==} - engines: {node: '>= 0.4'} - - gopd@1.2.0: - resolution: {integrity: sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==} - engines: {node: '>= 0.4'} - - has-property-descriptors@1.0.2: - resolution: {integrity: sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==} - - has-symbols@1.1.0: - resolution: {integrity: sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==} - engines: {node: '>= 0.4'} - - hash.js@1.1.7: - resolution: {integrity: sha512-taOaskGt4z4SOANNseOviYDvjEJinIkRgmp7LbKP2YTTmVxWBl87s/uzK9r+44BclBSp2X7K1hqeNfz9JbBeXA==} - - hasown@2.0.4: - resolution: {integrity: sha512-T2UbfbBEF32wiepXIsMlTW9+dDYC6wMh/t/vYA4tuOMKqWz/n3vr1NFSxQiyP+zk2mXsoMA/i/7qV6LKut1t1A==} - engines: {node: '>= 0.4'} - - hono@4.13.5: - resolution: {integrity: sha512-O6+/eCYRkzzzy0rPWwKLiGBR1nFuUPZynnwjxN1MBA62NNqbT0wQEzQyK2gSO5yDIDB336sXQleAhOHrzlYyKw==} - engines: {node: '>=16.9.0'} - - http-errors@2.0.1: - resolution: {integrity: sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ==} - engines: {node: '>= 0.8'} - - iconv-lite@0.7.3: - resolution: {integrity: sha512-IKXpvIzjnC9XTAUbVBcMfGS0EPaIXtW6v+zr+RRp+hqULEpo0owZax6wyRwPOJbWbzjYspQwusTsfVr0ifh4uQ==} - engines: {node: '>=0.10.0'} - - immediate@3.0.6: - resolution: {integrity: sha512-XXOFtyqDjNDAQxVfYxuF7g9Il/IbWmmlQg2MYKOH8ExIT1qg6xc4zyS3HaEEATgs1btfzxq15ciUiY7gjSXRGQ==} - - inherits@2.0.4: - resolution: {integrity: sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==} - - ip-address@10.5.0: - resolution: {integrity: sha512-R5SnVLJmgYYvf2F2ZgwSBnelz5G4q5AxIC277GDfUaNbrZKNANcBC7RHqYYePlszf4kBolVkJauG0ZjHHFh55g==} - engines: {node: '>= 12'} - - ipaddr.js@1.9.1: - resolution: {integrity: sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==} - engines: {node: '>= 0.10'} - - is-promise@4.0.0: - resolution: {integrity: sha512-hvpoI6korhJMnej285dSg6nu1+e6uxs7zG3BYAm5byqDsgJNWwxzM6z6iZiAgQR4TJ30JmBTOwqZUw3WlyH3AQ==} - - isarray@1.0.0: - resolution: {integrity: sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==} - - isexe@2.0.0: - resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==} - - jose@6.2.10: - resolution: {integrity: sha512-iiW7J9qRFlGxvCOIBDBDxFePQSn7ZMAnrYGhrrOo6siO/MIqwfyilLR27pkfDgUk+raLuzADS8A3S/KLBisc0g==} - - json-canonicalize@2.0.0: - resolution: {integrity: sha512-yyrnK/mEm6Na3ChbJUWueXdapueW0p380RUyTW87XGb1ww8l8hU0pRrGC3vSWHe9CxrbPHX2fGUOZpNiHR0IIg==} - - json-schema-traverse@1.0.0: - resolution: {integrity: sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==} - - json-schema-typed@8.0.2: - resolution: {integrity: sha512-fQhoXdcvc3V28x7C7BMs4P5+kNlgUURe2jmUT1T//oBRMDrqy1QPelJimwZGo7Hg9VPV3EQV5Bnq4hbFy2vetA==} - - jszip@3.10.1: - resolution: {integrity: sha512-xXDvecyTpGLrqFrvkrUSoxxfJI5AH7U8zxxtVclpsUtMCq4JQ290LY8AW5c7Ggnr/Y/oK+bQMbqK2qmtk3pN4g==} - - lie@3.3.0: - resolution: {integrity: sha512-UaiMJzeWRlEujzAuw5LokY1L5ecNQYZKfmyZ9L7wDHb/p5etKaxXhohBcrw0EYby+G/NA52vRSN4N39dxHAIwQ==} - - matcher@4.0.0: - resolution: {integrity: sha512-S6x5wmcDmsDRRU/c2dkccDwQPXoFczc5+HpQ2lON8pnvHlnvHAHj5WlLVvw6n6vNyHuVugYrFohYxbS+pvFpKQ==} - engines: {node: '>=10'} - - math-intrinsics@1.1.0: - resolution: {integrity: sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==} - engines: {node: '>= 0.4'} - - media-typer@1.1.1: - resolution: {integrity: sha512-yz3xRaG20c6/BOzvYoDaGtPmGscs7YivItZEEqe6GbwNfHuxu9YNmvnEkMzKldAGY4/80pRcQRZSEnhquk9XuQ==} - engines: {node: '>= 0.8'} - - merge-descriptors@2.0.0: - resolution: {integrity: sha512-Snk314V5ayFLhp3fkUREub6WtjBfPdCPY1Ln8/8munuLuiYhsABgBVWsozAG+MWMbVEvcdcpbi9R7ww22l9Q3g==} - engines: {node: '>=18'} - - mime-db@1.54.0: - resolution: {integrity: sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==} - engines: {node: '>= 0.6'} - - mime-types@3.0.2: - resolution: {integrity: sha512-Lbgzdk0h4juoQ9fCKXW4by0UJqj+nOOrI9MJ1sSj4nI8aI2eo1qmvQEie4VD1glsS250n15LsWsYtCugiStS5A==} - engines: {node: '>=18'} - - minimalistic-assert@1.0.1: - resolution: {integrity: sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A==} - - ms@2.1.3: - resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} - - nanoid@5.1.16: - resolution: {integrity: sha512-kVrnsrJqMR8+oLJnGEmSWw9BivK5mt7H3FZatVRjrc5wGqFYuBxX1yG7+A7Gi5AefkX6t/oCkizcQgpu0cY1dQ==} - engines: {node: ^18 || >=20} - hasBin: true - - negotiator@1.1.0: - resolution: {integrity: sha512-NMPBRMJgiQHjbd8phG3Vebdx4kZ1H121rbl5IkMqeOsahptB9BKo/d7oJ3zTXqTgagn2bWlNSXkh0QUGM31RYg==} - engines: {node: '>=18'} - - object-assign@4.1.1: - resolution: {integrity: sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==} - engines: {node: '>=0.10.0'} - - object-inspect@1.13.4: - resolution: {integrity: sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==} - engines: {node: '>= 0.4'} - - object-keys@1.1.1: - resolution: {integrity: sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==} - engines: {node: '>= 0.4'} - - on-finished@2.4.1: - resolution: {integrity: sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==} - engines: {node: '>= 0.8'} - - once@1.4.0: - resolution: {integrity: sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==} - - onnxruntime-common@1.29.0: - resolution: {integrity: sha512-/F63/e2VJoaVXGGNu6S5QH7jivBThGO95OzAVXXQ8hTta/b1QxI8udHa6cI3+3mAb5WWIIaMMwfZw01oivjJ1g==} - - onnxruntime-node@1.29.0: - resolution: {integrity: sha512-WjiVVB72riILz8HbYvxvmjKyE/WmkYoSfKY++axo5jAR609HQg8MwiG/HhShpTcJfmmAdzxxmB+MMST3A+SiPA==} - os: [win32, darwin, linux] - - pako@1.0.11: - resolution: {integrity: sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw==} - - parseurl@1.3.3: - resolution: {integrity: sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==} - engines: {node: '>= 0.8'} - - path-key@3.1.1: - resolution: {integrity: sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==} - engines: {node: '>=8'} - - path-to-regexp@8.4.2: - resolution: {integrity: sha512-qRcuIdP69NPm4qbACK+aDogI5CBDMi1jKe0ry5rSQJz8JVLsC7jV8XpiJjGRLLol3N+R5ihGYcrPLTno6pAdBA==} - - pkce-challenge@5.0.1: - resolution: {integrity: sha512-wQ0b/W4Fr01qtpHlqSqspcj3EhBvimsdh0KlHhH8HRZnMsEa0ea2fTULOXOS9ccQr3om+GcGRk4e+isrZWV8qQ==} - engines: {node: '>=16.20.0'} - - playwright-core@1.62.1: - resolution: {integrity: sha512-wPYSwEBJY9GHraISXqyqtx0na0LpO3XEX7jNDhntbex7tzUS7kLnZsOlFruFJB4Hi/rhDMjXGqHewDZ68nYZVw==} - engines: {node: '>=20'} - hasBin: true - - process-nextick-args@2.0.1: - resolution: {integrity: sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==} - - proxy-addr@2.0.7: - resolution: {integrity: sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==} - engines: {node: '>= 0.10'} - - qs@6.15.3: - resolution: {integrity: sha512-O9gl3zCl5h5blw1KGUzQKhA5oUXSl8rwUIM5o0S3nCXMliSvy5Dzx7/DJcI+SwgICv+IneSZwhBh1oSyEHA71A==} - engines: {node: '>=0.6'} - - range-parser@1.3.0: - resolution: {integrity: sha512-hek2mFQpPuI4E1BBKrSto+BU3e3x4xuarsbiwr3+lf7p44juvFMV0XFWQAP3xUyqXA4RrXLIoaSUGbSt056ZMw==} - engines: {node: '>= 0.6'} - - raw-body@3.0.2: - resolution: {integrity: sha512-K5zQjDllxWkf7Z5xJdV0/B0WTNqx6vxG70zJE4N0kBs4LovmEYWJzQGxC9bS9RAKu3bgM40lrd5zoLJ12MQ5BA==} - engines: {node: '>= 0.10'} - - readable-stream@2.3.8: - resolution: {integrity: sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==} - - require-from-string@2.0.2: - resolution: {integrity: sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==} - engines: {node: '>=0.10.0'} - - router@2.2.0: - resolution: {integrity: sha512-nLTrUKm2UyiL7rlhapu/Zl45FwNgkZGaCpZbIHajDYgwlJCOzLSk+cIPAnsEqV955GjILJnKbdQC1nVPz+gAYQ==} - engines: {node: '>= 18'} - - safe-buffer@5.1.2: - resolution: {integrity: sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==} - - safer-buffer@2.1.2: - resolution: {integrity: sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==} - - sax@1.6.1: - resolution: {integrity: sha512-42tBVwLWnaQvW5zc4HbZrTuWccECCZfBi92FDuwtqxasH+JbPB3/FOKb1m222K42R4WxuxzzMsTswfzgtSu64Q==} - engines: {node: '>=11.0.0'} - - semver@7.8.5: - resolution: {integrity: sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA==} - engines: {node: '>=10'} - hasBin: true - - send@1.2.1: - resolution: {integrity: sha512-1gnZf7DFcoIcajTjTwjwuDjzuz4PPcY2StKPlsGAQ1+YH20IRVrBaXSWmdjowTJ6u8Rc01PoYOGHXfP1mYcZNQ==} - engines: {node: '>= 18'} - - serialize-error@8.1.0: - resolution: {integrity: sha512-3NnuWfM6vBYoy5gZFvHiYsVbafvI9vZv/+jlIigFn4oP4zjNPK3LhcY0xSCgeb1a5L8jO71Mit9LlNoi2UfDDQ==} - engines: {node: '>=10'} - - serve-static@2.2.1: - resolution: {integrity: sha512-xRXBn0pPqQTVQiC8wyQrKs2MOlX24zQ0POGaj0kultvoOCstBQM5yvOhAVSUwOMjQtTvsPWoNCHfPGwaaQJhTw==} - engines: {node: '>= 18'} - - setimmediate@1.0.5: - resolution: {integrity: sha512-MATJdZp8sLqDl/68LfQmbP8zKPLQNV6BIZoIgrscFDQ+RsvK/BxeDQOgyxKKoh0y/8h3BqVFnCqQ/gd+reiIXA==} - - setprototypeof@1.2.0: - resolution: {integrity: sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==} - - sharp@0.34.5: - resolution: {integrity: sha512-Ou9I5Ft9WNcCbXrU9cMgPBcCK8LiwLqcbywW3t4oDV37n1pzpuNLsYiAV8eODnjbtQlSDwZ2cUEeQz4E54Hltg==} - engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} - - shebang-command@2.0.0: - resolution: {integrity: sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==} - engines: {node: '>=8'} - - shebang-regex@3.0.0: - resolution: {integrity: sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==} - engines: {node: '>=8'} - - side-channel-list@1.0.1: - resolution: {integrity: sha512-mjn/0bi/oUURjc5Xl7IaWi/OJJJumuoJFQJfDDyO46+hBWsfaVM65TBHq2eoZBhzl9EchxOijpkbRC8SVBQU0w==} - engines: {node: '>= 0.4'} - - side-channel-map@1.0.1: - resolution: {integrity: sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==} - engines: {node: '>= 0.4'} - - side-channel-weakmap@1.0.2: - resolution: {integrity: sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==} - engines: {node: '>= 0.4'} - - side-channel@1.1.1: - resolution: {integrity: sha512-6x6dK6zJdpTzF4sQeNYxwtvBzf6Eg4GtlesS94HOvTudUeyK2WXAaIfmDgsyslYrRBeFIlsi54AYsFGUuhmvrQ==} - engines: {node: '>= 0.4'} - - statuses@2.0.2: - resolution: {integrity: sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==} - engines: {node: '>= 0.8'} - - string_decoder@1.1.1: - resolution: {integrity: sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==} - - toidentifier@1.0.1: - resolution: {integrity: sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==} - engines: {node: '>=0.6'} - - tslib@2.8.1: - resolution: {integrity: sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==} - - tsx@4.23.12: - resolution: {integrity: sha512-FDf4L4sYzKtzWYhU/Xm0AQFdTjdIxNo9ElTf2mxXM6k8YMHXzYUe4yODVaXP4V9uMFbVg8c0qyBccK2OOxb45Q==} - engines: {node: '>=18.0.0'} - hasBin: true - - type-fest@0.20.2: - resolution: {integrity: sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==} - engines: {node: '>=10'} - - type-is@2.1.0: - resolution: {integrity: sha512-faYHw0anBbc/kWF3zFTEnxSFOAGUX9GFbOBthvDdLsIlEoWOFOtS0zgCiQYwIskL9iGXZL3kAXD8OoZ4GmMATA==} - engines: {node: '>= 18'} - - typescript@7.0.2: - resolution: {integrity: sha512-8FYau96o3NKOhbjKi/qNvG/W5jhzxkbdm5sj9AbZ/5T5sWqn3hJgLfGx27sRKZWTvyzCP8dLRBTf5tBTSRVUNA==} - engines: {node: '>=16.20.0'} - hasBin: true - - undici-types@6.21.0: - resolution: {integrity: sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==} - - undici-types@7.24.6: - resolution: {integrity: sha512-WRNW+sJgj5OBN4/0JpHFqtqzhpbnV0GuB+OozA9gCL7a993SmU+1JBZCzLNxYsbMfIeDL+lTsphD5jN5N+n0zg==} - - unpipe@1.0.0: - resolution: {integrity: sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==} - engines: {node: '>= 0.8'} - - util-deprecate@1.0.2: - resolution: {integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==} - - vary@1.1.2: - resolution: {integrity: sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==} - engines: {node: '>= 0.8'} - - which@2.0.2: - resolution: {integrity: sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==} - engines: {node: '>= 8'} - hasBin: true - - wrappy@1.0.2: - resolution: {integrity: sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==} - - xml-js@1.6.11: - resolution: {integrity: sha512-7rVi2KMfwfWFl+GpPg6m80IVMWXLRjO+PxTq7V2CDhoGak0wzYzFgUY2m4XJ47OGdXd8eLE8EmwfAmdjw7lC1g==} - hasBin: true - - xml@1.0.1: - resolution: {integrity: sha512-huCv9IH9Tcf95zuYCsQraZtWnJvBtLVE0QHMOs8bWyZAFZNDcYjsPq1nEx8jKA9y+Beo9v+7OBPRisQTjinQMw==} - - zod-to-json-schema@3.25.2: - resolution: {integrity: sha512-O/PgfnpT1xKSDeQYSCfRI5Gy3hPf91mKVDuYLUHZJMiDFptvP41MSnWofm8dnCm0256ZNfZIM7DSzuSMAFnjHA==} - peerDependencies: - zod: ^3.25.28 || ^4 - - zod@3.25.76: - resolution: {integrity: sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==} - -snapshots: - - '@emnapi/runtime@1.11.3': - dependencies: - tslib: 2.8.1 - optional: true - - '@esbuild/aix-ppc64@0.28.2': - optional: true - - '@esbuild/android-arm64@0.28.2': - optional: true - - '@esbuild/android-arm@0.28.2': - optional: true - - '@esbuild/android-x64@0.28.2': - optional: true - - '@esbuild/darwin-arm64@0.28.2': - optional: true - - '@esbuild/darwin-x64@0.28.2': - optional: true - - '@esbuild/freebsd-arm64@0.28.2': - optional: true - - '@esbuild/freebsd-x64@0.28.2': - optional: true - - '@esbuild/linux-arm64@0.28.2': - optional: true - - '@esbuild/linux-arm@0.28.2': - optional: true - - '@esbuild/linux-ia32@0.28.2': - optional: true - - '@esbuild/linux-loong64@0.28.2': - optional: true - - '@esbuild/linux-mips64el@0.28.2': - optional: true - - '@esbuild/linux-ppc64@0.28.2': - optional: true - - '@esbuild/linux-riscv64@0.28.2': - optional: true - - '@esbuild/linux-s390x@0.28.2': - optional: true - - '@esbuild/linux-x64@0.28.2': - optional: true - - '@esbuild/netbsd-arm64@0.28.2': - optional: true - - '@esbuild/netbsd-x64@0.28.2': - optional: true - - '@esbuild/openbsd-arm64@0.28.2': - optional: true - - '@esbuild/openbsd-x64@0.28.2': - optional: true - - '@esbuild/openharmony-arm64@0.28.2': - optional: true - - '@esbuild/sunos-x64@0.28.2': - optional: true - - '@esbuild/win32-arm64@0.28.2': - optional: true - - '@esbuild/win32-ia32@0.28.2': - optional: true - - '@esbuild/win32-x64@0.28.2': - optional: true - - '@hono/node-server@2.1.1(hono@4.13.5)': - dependencies: - hono: 4.13.5 - - '@img/colour@1.1.0': {} - - '@img/sharp-darwin-arm64@0.34.5': - optionalDependencies: - '@img/sharp-libvips-darwin-arm64': 1.2.4 - optional: true - - '@img/sharp-darwin-x64@0.34.5': - optionalDependencies: - '@img/sharp-libvips-darwin-x64': 1.2.4 - optional: true - - '@img/sharp-libvips-darwin-arm64@1.2.4': - optional: true - - '@img/sharp-libvips-darwin-x64@1.2.4': - optional: true - - '@img/sharp-libvips-linux-arm64@1.2.4': - optional: true - - '@img/sharp-libvips-linux-arm@1.2.4': - optional: true - - '@img/sharp-libvips-linux-ppc64@1.2.4': - optional: true - - '@img/sharp-libvips-linux-riscv64@1.2.4': - optional: true - - '@img/sharp-libvips-linux-s390x@1.2.4': - optional: true - - '@img/sharp-libvips-linux-x64@1.2.4': - optional: true - - '@img/sharp-libvips-linuxmusl-arm64@1.2.4': - optional: true - - '@img/sharp-libvips-linuxmusl-x64@1.2.4': - optional: true - - '@img/sharp-linux-arm64@0.34.5': - optionalDependencies: - '@img/sharp-libvips-linux-arm64': 1.2.4 - optional: true - - '@img/sharp-linux-arm@0.34.5': - optionalDependencies: - '@img/sharp-libvips-linux-arm': 1.2.4 - optional: true - - '@img/sharp-linux-ppc64@0.34.5': - optionalDependencies: - '@img/sharp-libvips-linux-ppc64': 1.2.4 - optional: true - - '@img/sharp-linux-riscv64@0.34.5': - optionalDependencies: - '@img/sharp-libvips-linux-riscv64': 1.2.4 - optional: true - - '@img/sharp-linux-s390x@0.34.5': - optionalDependencies: - '@img/sharp-libvips-linux-s390x': 1.2.4 - optional: true - - '@img/sharp-linux-x64@0.34.5': - optionalDependencies: - '@img/sharp-libvips-linux-x64': 1.2.4 - optional: true - - '@img/sharp-linuxmusl-arm64@0.34.5': - optionalDependencies: - '@img/sharp-libvips-linuxmusl-arm64': 1.2.4 - optional: true - - '@img/sharp-linuxmusl-x64@0.34.5': - optionalDependencies: - '@img/sharp-libvips-linuxmusl-x64': 1.2.4 - optional: true - - '@img/sharp-wasm32@0.34.5': - dependencies: - '@emnapi/runtime': 1.11.3 - optional: true - - '@img/sharp-win32-arm64@0.34.5': - optional: true - - '@img/sharp-win32-ia32@0.34.5': - optional: true - - '@img/sharp-win32-x64@0.34.5': - optional: true - - '@modelcontextprotocol/sdk@1.30.0(zod@3.25.76)': - dependencies: - '@hono/node-server': 2.1.1(hono@4.13.5) - ajv: 8.20.0 - ajv-formats: 3.0.1(ajv@8.20.0) - content-type: 1.0.5 - cors: 2.8.6 - cross-spawn: 7.0.6 - eventsource: 3.0.7 - eventsource-parser: 3.1.1 - express: 5.2.1 - express-rate-limit: 8.6.2(express@5.2.1) - hono: 4.13.5 - jose: 6.2.10 - json-schema-typed: 8.0.2 - pkce-challenge: 5.0.1 - raw-body: 3.0.2 - zod: 3.25.76 - zod-to-json-schema: 3.25.2(zod@3.25.76) - transitivePeerDependencies: - - supports-color - - '@openai/codex@0.149.1': - optionalDependencies: - '@openai/codex-darwin-arm64': '@openai/codex@0.149.1-darwin-arm64' - '@openai/codex-darwin-x64': '@openai/codex@0.149.1-darwin-x64' - '@openai/codex-linux-arm64': '@openai/codex@0.149.1-linux-arm64' - '@openai/codex-linux-x64': '@openai/codex@0.149.1-linux-x64' - '@openai/codex-win32-arm64': '@openai/codex@0.149.1-win32-arm64' - '@openai/codex-win32-x64': '@openai/codex@0.149.1-win32-x64' - - '@openai/codex@0.149.1-darwin-arm64': - optional: true - - '@openai/codex@0.149.1-darwin-x64': - optional: true - - '@openai/codex@0.149.1-linux-arm64': - optional: true - - '@openai/codex@0.149.1-linux-x64': - optional: true - - '@openai/codex@0.149.1-win32-arm64': - optional: true - - '@openai/codex@0.149.1-win32-x64': - optional: true - - '@types/body-parser@1.19.6': - dependencies: - '@types/connect': 3.4.38 - '@types/node': 22.20.1 - - '@types/connect@3.4.38': - dependencies: - '@types/node': 22.20.1 - - '@types/express-serve-static-core@5.1.3': - dependencies: - '@types/node': 22.20.1 - '@types/qs': 6.15.1 - '@types/range-parser': 1.2.7 - '@types/send': 1.2.1 - - '@types/express@5.0.6': - dependencies: - '@types/body-parser': 1.19.6 - '@types/express-serve-static-core': 5.1.3 - '@types/serve-static': 2.2.0 - - '@types/http-errors@2.0.5': {} - - '@types/node@22.20.1': - dependencies: - undici-types: 6.21.0 - - '@types/node@25.9.5': - dependencies: - undici-types: 7.24.6 - - '@types/qs@6.15.1': {} - - '@types/range-parser@1.2.7': {} - - '@types/send@1.2.1': - dependencies: - '@types/node': 22.20.1 - - '@types/serve-static@2.2.0': - dependencies: - '@types/http-errors': 2.0.5 - '@types/node': 22.20.1 - - '@typescript/typescript-aix-ppc64@7.0.2': - optional: true - - '@typescript/typescript-darwin-arm64@7.0.2': - optional: true - - '@typescript/typescript-darwin-x64@7.0.2': - optional: true - - '@typescript/typescript-freebsd-arm64@7.0.2': - optional: true - - '@typescript/typescript-freebsd-x64@7.0.2': - optional: true - - '@typescript/typescript-linux-arm64@7.0.2': - optional: true - - '@typescript/typescript-linux-arm@7.0.2': - optional: true - - '@typescript/typescript-linux-loong64@7.0.2': - optional: true - - '@typescript/typescript-linux-mips64el@7.0.2': - optional: true - - '@typescript/typescript-linux-ppc64@7.0.2': - optional: true - - '@typescript/typescript-linux-riscv64@7.0.2': - optional: true - - '@typescript/typescript-linux-s390x@7.0.2': - optional: true - - '@typescript/typescript-linux-x64@7.0.2': - optional: true - - '@typescript/typescript-netbsd-arm64@7.0.2': - optional: true - - '@typescript/typescript-netbsd-x64@7.0.2': - optional: true - - '@typescript/typescript-openbsd-arm64@7.0.2': - optional: true - - '@typescript/typescript-openbsd-x64@7.0.2': - optional: true - - '@typescript/typescript-sunos-x64@7.0.2': - optional: true - - '@typescript/typescript-win32-arm64@7.0.2': - optional: true - - '@typescript/typescript-win32-x64@7.0.2': - optional: true - - accepts@2.0.0: - dependencies: - mime-types: 3.0.2 - negotiator: 1.1.0 - - adm-zip@0.6.0: {} - - ajv-formats@3.0.1(ajv@8.20.0): - optionalDependencies: - ajv: 8.20.0 - - ajv@8.20.0: - dependencies: - fast-deep-equal: 3.1.3 - fast-uri: 3.1.6 - json-schema-traverse: 1.0.0 - require-from-string: 2.0.2 - - body-parser@2.3.0: - dependencies: - bytes: 3.1.2 - content-type: 2.1.0 - debug: 4.4.3 - http-errors: 2.0.1 - iconv-lite: 0.7.3 - on-finished: 2.4.1 - qs: 6.15.3 - raw-body: 3.0.2 - type-is: 2.1.0 - transitivePeerDependencies: - - supports-color - - bytes@3.1.2: {} - - call-bind-apply-helpers@1.0.2: - dependencies: - es-errors: 1.3.0 - function-bind: 1.1.2 - - call-bound@1.0.4: - dependencies: - call-bind-apply-helpers: 1.0.2 - get-intrinsic: 1.3.0 - - content-disposition@1.1.0: {} - - content-type@1.0.5: {} - - content-type@2.1.0: {} - - cookie-signature@1.2.2: {} - - cookie@0.7.2: {} - - core-util-is@1.0.3: {} - - cors@2.8.6: - dependencies: - object-assign: 4.1.1 - vary: 1.1.2 - - cross-spawn@7.0.6: - dependencies: - path-key: 3.1.1 - shebang-command: 2.0.0 - which: 2.0.2 - - debug@4.4.3: - dependencies: - ms: 2.1.3 - - define-data-property@1.1.4: - dependencies: - es-define-property: 1.0.1 - es-errors: 1.3.0 - gopd: 1.2.0 - - define-properties@1.2.1: - dependencies: - define-data-property: 1.1.4 - has-property-descriptors: 1.0.2 - object-keys: 1.1.1 - - depd@2.0.0: {} - - detect-libc@2.1.2: {} - - docx@9.7.1: - dependencies: - '@types/node': 25.9.5 - hash.js: 1.1.7 - jszip: 3.10.1 - nanoid: 5.1.16 - xml: 1.0.1 - xml-js: 1.6.11 - - dunder-proto@1.0.1: - dependencies: - call-bind-apply-helpers: 1.0.2 - es-errors: 1.3.0 - gopd: 1.2.0 - - ee-first@1.1.1: {} - - encodeurl@2.0.0: {} - - es-define-property@1.0.1: {} - - es-errors@1.3.0: {} - - es-object-atoms@1.1.2: - dependencies: - es-errors: 1.3.0 - - esbuild@0.28.2: - optionalDependencies: - '@esbuild/aix-ppc64': 0.28.2 - '@esbuild/android-arm': 0.28.2 - '@esbuild/android-arm64': 0.28.2 - '@esbuild/android-x64': 0.28.2 - '@esbuild/darwin-arm64': 0.28.2 - '@esbuild/darwin-x64': 0.28.2 - '@esbuild/freebsd-arm64': 0.28.2 - '@esbuild/freebsd-x64': 0.28.2 - '@esbuild/linux-arm': 0.28.2 - '@esbuild/linux-arm64': 0.28.2 - '@esbuild/linux-ia32': 0.28.2 - '@esbuild/linux-loong64': 0.28.2 - '@esbuild/linux-mips64el': 0.28.2 - '@esbuild/linux-ppc64': 0.28.2 - '@esbuild/linux-riscv64': 0.28.2 - '@esbuild/linux-s390x': 0.28.2 - '@esbuild/linux-x64': 0.28.2 - '@esbuild/netbsd-arm64': 0.28.2 - '@esbuild/netbsd-x64': 0.28.2 - '@esbuild/openbsd-arm64': 0.28.2 - '@esbuild/openbsd-x64': 0.28.2 - '@esbuild/openharmony-arm64': 0.28.2 - '@esbuild/sunos-x64': 0.28.2 - '@esbuild/win32-arm64': 0.28.2 - '@esbuild/win32-ia32': 0.28.2 - '@esbuild/win32-x64': 0.28.2 - - escape-html@1.0.3: {} - - escape-string-regexp@4.0.0: {} - - etag@1.8.1: {} - - eventsource-parser@3.1.1: {} - - eventsource@3.0.7: - dependencies: - eventsource-parser: 3.1.1 - - express-rate-limit@8.6.2(express@5.2.1): - dependencies: - debug: 4.4.3 - express: 5.2.1 - ip-address: 10.5.0 - transitivePeerDependencies: - - supports-color - - express@5.2.1: - dependencies: - accepts: 2.0.0 - body-parser: 2.3.0 - content-disposition: 1.1.0 - content-type: 1.0.5 - cookie: 0.7.2 - cookie-signature: 1.2.2 - debug: 4.4.3 - depd: 2.0.0 - encodeurl: 2.0.0 - escape-html: 1.0.3 - etag: 1.8.1 - finalhandler: 2.1.1 - fresh: 2.0.0 - http-errors: 2.0.1 - merge-descriptors: 2.0.0 - mime-types: 3.0.2 - on-finished: 2.4.1 - once: 1.4.0 - parseurl: 1.3.3 - proxy-addr: 2.0.7 - qs: 6.15.3 - range-parser: 1.3.0 - router: 2.2.0 - send: 1.2.1 - serve-static: 2.2.1 - statuses: 2.0.2 - type-is: 2.1.0 - vary: 1.1.2 - transitivePeerDependencies: - - supports-color - - fast-deep-equal@3.1.3: {} - - fast-uri@3.1.6: {} - - finalhandler@2.1.1: - dependencies: - debug: 4.4.3 - encodeurl: 2.0.0 - escape-html: 1.0.3 - on-finished: 2.4.1 - parseurl: 1.3.3 - statuses: 2.0.2 - transitivePeerDependencies: - - supports-color - - forwarded@0.2.0: {} - - fresh@2.0.0: {} - - fsevents@2.3.3: - optional: true - - function-bind@1.1.2: {} - - get-intrinsic@1.3.0: - dependencies: - call-bind-apply-helpers: 1.0.2 - es-define-property: 1.0.1 - es-errors: 1.3.0 - es-object-atoms: 1.1.2 - function-bind: 1.1.2 - get-proto: 1.0.1 - gopd: 1.2.0 - has-symbols: 1.1.0 - hasown: 2.0.4 - math-intrinsics: 1.1.0 - - get-proto@1.0.1: - dependencies: - dunder-proto: 1.0.1 - es-object-atoms: 1.1.2 - - global-agent@4.1.3: - dependencies: - globalthis: 1.0.4 - matcher: 4.0.0 - semver: 7.8.5 - serialize-error: 8.1.0 - - globalthis@1.0.4: - dependencies: - define-properties: 1.2.1 - gopd: 1.2.0 - - gopd@1.2.0: {} - - has-property-descriptors@1.0.2: - dependencies: - es-define-property: 1.0.1 - - has-symbols@1.1.0: {} - - hash.js@1.1.7: - dependencies: - inherits: 2.0.4 - minimalistic-assert: 1.0.1 - - hasown@2.0.4: - dependencies: - function-bind: 1.1.2 - - hono@4.13.5: {} - - http-errors@2.0.1: - dependencies: - depd: 2.0.0 - inherits: 2.0.4 - setprototypeof: 1.2.0 - statuses: 2.0.2 - toidentifier: 1.0.1 - - iconv-lite@0.7.3: - dependencies: - safer-buffer: 2.1.2 - - immediate@3.0.6: {} - - inherits@2.0.4: {} - - ip-address@10.5.0: {} - - ipaddr.js@1.9.1: {} - - is-promise@4.0.0: {} - - isarray@1.0.0: {} - - isexe@2.0.0: {} - - jose@6.2.10: {} - - json-canonicalize@2.0.0: {} - - json-schema-traverse@1.0.0: {} - - json-schema-typed@8.0.2: {} - - jszip@3.10.1: - dependencies: - lie: 3.3.0 - pako: 1.0.11 - readable-stream: 2.3.8 - setimmediate: 1.0.5 - - lie@3.3.0: - dependencies: - immediate: 3.0.6 - - matcher@4.0.0: - dependencies: - escape-string-regexp: 4.0.0 - - math-intrinsics@1.1.0: {} - - media-typer@1.1.1: {} - - merge-descriptors@2.0.0: {} - - mime-db@1.54.0: {} - - mime-types@3.0.2: - dependencies: - mime-db: 1.54.0 - - minimalistic-assert@1.0.1: {} - - ms@2.1.3: {} - - nanoid@5.1.16: {} - - negotiator@1.1.0: - dependencies: - content-type: 2.1.0 - - object-assign@4.1.1: {} - - object-inspect@1.13.4: {} - - object-keys@1.1.1: {} - - on-finished@2.4.1: - dependencies: - ee-first: 1.1.1 - - once@1.4.0: - dependencies: - wrappy: 1.0.2 - - onnxruntime-common@1.29.0: {} - - onnxruntime-node@1.29.0: - dependencies: - adm-zip: 0.6.0 - global-agent: 4.1.3 - onnxruntime-common: 1.29.0 - - pako@1.0.11: {} - - parseurl@1.3.3: {} - - path-key@3.1.1: {} - - path-to-regexp@8.4.2: {} - - pkce-challenge@5.0.1: {} - - playwright-core@1.62.1: {} - - process-nextick-args@2.0.1: {} - - proxy-addr@2.0.7: - dependencies: - forwarded: 0.2.0 - ipaddr.js: 1.9.1 - - qs@6.15.3: - dependencies: - es-define-property: 1.0.1 - side-channel: 1.1.1 - - range-parser@1.3.0: {} - - raw-body@3.0.2: - dependencies: - bytes: 3.1.2 - http-errors: 2.0.1 - iconv-lite: 0.7.3 - unpipe: 1.0.0 - - readable-stream@2.3.8: - dependencies: - core-util-is: 1.0.3 - inherits: 2.0.4 - isarray: 1.0.0 - process-nextick-args: 2.0.1 - safe-buffer: 5.1.2 - string_decoder: 1.1.1 - util-deprecate: 1.0.2 - - require-from-string@2.0.2: {} - - router@2.2.0: - dependencies: - debug: 4.4.3 - depd: 2.0.0 - is-promise: 4.0.0 - parseurl: 1.3.3 - path-to-regexp: 8.4.2 - transitivePeerDependencies: - - supports-color - - safe-buffer@5.1.2: {} - - safer-buffer@2.1.2: {} - - sax@1.6.1: {} - - semver@7.8.5: {} - - send@1.2.1: - dependencies: - debug: 4.4.3 - encodeurl: 2.0.0 - escape-html: 1.0.3 - etag: 1.8.1 - fresh: 2.0.0 - http-errors: 2.0.1 - mime-types: 3.0.2 - ms: 2.1.3 - on-finished: 2.4.1 - range-parser: 1.3.0 - statuses: 2.0.2 - transitivePeerDependencies: - - supports-color - - serialize-error@8.1.0: - dependencies: - type-fest: 0.20.2 - - serve-static@2.2.1: - dependencies: - encodeurl: 2.0.0 - escape-html: 1.0.3 - parseurl: 1.3.3 - send: 1.2.1 - transitivePeerDependencies: - - supports-color - - setimmediate@1.0.5: {} - - setprototypeof@1.2.0: {} - - sharp@0.34.5: - dependencies: - '@img/colour': 1.1.0 - detect-libc: 2.1.2 - semver: 7.8.5 - optionalDependencies: - '@img/sharp-darwin-arm64': 0.34.5 - '@img/sharp-darwin-x64': 0.34.5 - '@img/sharp-libvips-darwin-arm64': 1.2.4 - '@img/sharp-libvips-darwin-x64': 1.2.4 - '@img/sharp-libvips-linux-arm': 1.2.4 - '@img/sharp-libvips-linux-arm64': 1.2.4 - '@img/sharp-libvips-linux-ppc64': 1.2.4 - '@img/sharp-libvips-linux-riscv64': 1.2.4 - '@img/sharp-libvips-linux-s390x': 1.2.4 - '@img/sharp-libvips-linux-x64': 1.2.4 - '@img/sharp-libvips-linuxmusl-arm64': 1.2.4 - '@img/sharp-libvips-linuxmusl-x64': 1.2.4 - '@img/sharp-linux-arm': 0.34.5 - '@img/sharp-linux-arm64': 0.34.5 - '@img/sharp-linux-ppc64': 0.34.5 - '@img/sharp-linux-riscv64': 0.34.5 - '@img/sharp-linux-s390x': 0.34.5 - '@img/sharp-linux-x64': 0.34.5 - '@img/sharp-linuxmusl-arm64': 0.34.5 - '@img/sharp-linuxmusl-x64': 0.34.5 - '@img/sharp-wasm32': 0.34.5 - '@img/sharp-win32-arm64': 0.34.5 - '@img/sharp-win32-ia32': 0.34.5 - '@img/sharp-win32-x64': 0.34.5 - - shebang-command@2.0.0: - dependencies: - shebang-regex: 3.0.0 - - shebang-regex@3.0.0: {} - - side-channel-list@1.0.1: - dependencies: - es-errors: 1.3.0 - object-inspect: 1.13.4 - - side-channel-map@1.0.1: - dependencies: - call-bound: 1.0.4 - es-errors: 1.3.0 - get-intrinsic: 1.3.0 - object-inspect: 1.13.4 - - side-channel-weakmap@1.0.2: - dependencies: - call-bound: 1.0.4 - es-errors: 1.3.0 - get-intrinsic: 1.3.0 - object-inspect: 1.13.4 - side-channel-map: 1.0.1 - - side-channel@1.1.1: - dependencies: - es-errors: 1.3.0 - object-inspect: 1.13.4 - side-channel-list: 1.0.1 - side-channel-map: 1.0.1 - side-channel-weakmap: 1.0.2 - - statuses@2.0.2: {} - - string_decoder@1.1.1: - dependencies: - safe-buffer: 5.1.2 - - toidentifier@1.0.1: {} - - tslib@2.8.1: - optional: true - - tsx@4.23.12: - dependencies: - esbuild: 0.28.2 - optionalDependencies: - fsevents: 2.3.3 - - type-fest@0.20.2: {} - - type-is@2.1.0: - dependencies: - content-type: 2.1.0 - media-typer: 1.1.1 - mime-types: 3.0.2 - - typescript@7.0.2: - optionalDependencies: - '@typescript/typescript-aix-ppc64': 7.0.2 - '@typescript/typescript-darwin-arm64': 7.0.2 - '@typescript/typescript-darwin-x64': 7.0.2 - '@typescript/typescript-freebsd-arm64': 7.0.2 - '@typescript/typescript-freebsd-x64': 7.0.2 - '@typescript/typescript-linux-arm': 7.0.2 - '@typescript/typescript-linux-arm64': 7.0.2 - '@typescript/typescript-linux-loong64': 7.0.2 - '@typescript/typescript-linux-mips64el': 7.0.2 - '@typescript/typescript-linux-ppc64': 7.0.2 - '@typescript/typescript-linux-riscv64': 7.0.2 - '@typescript/typescript-linux-s390x': 7.0.2 - '@typescript/typescript-linux-x64': 7.0.2 - '@typescript/typescript-netbsd-arm64': 7.0.2 - '@typescript/typescript-netbsd-x64': 7.0.2 - '@typescript/typescript-openbsd-arm64': 7.0.2 - '@typescript/typescript-openbsd-x64': 7.0.2 - '@typescript/typescript-sunos-x64': 7.0.2 - '@typescript/typescript-win32-arm64': 7.0.2 - '@typescript/typescript-win32-x64': 7.0.2 - - undici-types@6.21.0: {} - - undici-types@7.24.6: {} - - unpipe@1.0.0: {} - - util-deprecate@1.0.2: {} - - vary@1.1.2: {} - - which@2.0.2: - dependencies: - isexe: 2.0.0 - - wrappy@1.0.2: {} - - xml-js@1.6.11: - dependencies: - sax: 1.6.1 - - xml@1.0.1: {} - - zod-to-json-schema@3.25.2(zod@3.25.76): - dependencies: - zod: 3.25.76 - - zod@3.25.76: {} diff --git a/docs/content/docs/backend/backend-database.mdx b/docs/content/docs/backend/backend-database.mdx index 9728e7622..b2cb65917 100644 --- a/docs/content/docs/backend/backend-database.mdx +++ b/docs/content/docs/backend/backend-database.mdx @@ -52,6 +52,15 @@ description: 影策后端核心持久化表与状态边界 规范化技能包保存在后端数据目录的 `skill-packages//.zip`,数据库只保存索引和版本指针。上传 ZIP 时强制校验 `SKILL.md`、路径越界、软链接、重复路径、文件数量和解压大小;GitHub 技能保存公开仓库、分支、子目录和提交 SHA,后台每 6 小时检查一次开启自动更新的技能,并记录最近检查、同步时间及失败状态。 +## 提示词模板 + +| 表 | 用途 | 关键边界 | +| --- | --- | --- | +| `prompt_templates` | 保存每个操作的平台提示词版本 | `(operation, version)` 唯一;同一操作同一时间只能有一个启用版本;启用中的版本不能直接改文案或停用 | +| `user_prompt_customizations` | 保存用户对某个操作的跟随、追加或改写 | `(user_id, operation)` 唯一;只覆盖创作层。任务事实和 JSON 输出契约由 `compilePrompt` 注入,不能写进这张表来绕过 | + +启动时 `EnsureDefaultPromptTemplates` 只为尚无任何版本的操作写入内置默认稿。新增操作(当前包括短剧大纲 `short_drama_outline` 和技能草稿 `skill_draft`)会在下次启动补种,不改已有操作的启用版本。 + ## 短剧分镜生产 | 表 | 用途 | 关键边界 | @@ -80,7 +89,7 @@ description: 影策后端核心持久化表与状态边界 | `GET /api/projects/:id/units` | 全部章节摘要与字数,不包含正文 | | `GET /api/projects/:id/units/:unitId` | 单章正文 | | `GET /api/projects/:id/units/:unitId/workspace` | 当前章节的镜头、版本、产物、引用、候选、相关任务和已绑定资产 | -| `GET /api/projects/:id/canvases` | 项目画布与当前页章节关联,使用 `page`、`page_size` | +| `GET /api/projects/:id/canvases` | 项目画布与当前页章节关联,使用 `page`、`pageSize` | | `GET /api/projects/:id/assets` | 项目资产分页、分类/目录计数,支持分类、媒体类型、目录和关键词筛选 | | `GET /api/projects/:id/asset-candidates` | 资产候选分页,支持章节、状态、分类和名称关键词筛选 | diff --git a/docs/content/docs/backend/code-map.mdx b/docs/content/docs/backend/code-map.mdx new file mode 100644 index 000000000..cb2aef8bb --- /dev/null +++ b/docs/content/docs/backend/code-map.mdx @@ -0,0 +1,104 @@ +--- +title: 代码功能地图 +description: 从产品功能快速定位主要代码入口 +--- + +# 代码功能地图 + +这是一张入口地图,不罗列所有文件。先找到功能所属层,再沿调用关系阅读。 + +## 前端页面 + +| 功能 | 页面入口 | 主要协作模块 | +| --- | --- | --- | +| 首页与导航 | `web/src/pages/home/`、`web/src/layouts/user-layout.tsx` | `components/layout/`、`router.tsx` | +| 创作工作台 | `web/src/pages/create/index.tsx` | `creation-workspace.tsx`、`creation-conversations.ts`、`creation-types.ts`、`creation-assets.ts`、`creation-references.ts` | +| 登录注册 | `web/src/pages/auth/` | `stores/use-user-store.ts`、`services/api/auth.ts`、后端 `service/linuxdo.go` | +| 积分与计费 | `web/src/pages/wallet/`、`pages/admin/components/platform-finance-panel.tsx` | `services/api/wallet.ts`、后端 `service/finance.go`、`repository/finance.go` | +| 任务中心 | `web/src/pages/tasks/` | `services/api/task-center.ts`、`services/api/task-text-stream.ts` | +| 我的素材 | `web/src/pages/assets/` | `stores/use-asset-store.ts`、`services/image-storage.ts` | +| 技能库 | `web/src/pages/skills/` | `services/api/skills.ts`、`services/skill-runtime.ts`、`components/skills/skill-runtime-picker.tsx` | +| 画布列表 | `web/src/pages/canvas/index.tsx` | `stores/canvas/use-canvas-store.ts` | +| 画布编辑器 | `web/src/pages/canvas/project.tsx` | `pages/canvas/canvas-project-*`、`pages/canvas/use-canvas-*`、`components/canvas/`、`lib/canvas/`、`stores/canvas/` | +| 公开分享 | `web/src/pages/canvas/shared.tsx` | `components/canvas/infinite-canvas.tsx`、`services/api/canvas-share.ts` | +| 管理后台 | `web/src/pages/admin/`、`router.tsx` | `admin-context.tsx`、`users/`、`channels/`、`logs/`、`storyboard-prompts/`、`settings/` | + +## 画布核心 + +| 职责 | 代码位置 | +| --- | --- | +| 页面装配和跨域动作协调 | `web/src/pages/canvas/project.tsx` | +| 画布领域规则和生成协议 | `web/src/lib/canvas/canvas-project-domain.ts`、`canvas-project-generation.ts` | +| 项目恢复、持久化和项目级动作 | `web/src/pages/canvas/use-canvas-project-lifecycle.ts` | +| Patch 历史、生成任务和重试控制 | `web/src/pages/canvas/use-canvas-history.ts`、`use-canvas-generation.ts`、`use-canvas-generation-executor.ts`、`use-canvas-generation-retry.ts` | +| 文本、图片、视频和音频生成执行 | `web/src/pages/canvas/canvas-*-generation-executor*.ts` | +| Agent、助手面板、导演台和全局快捷键控制 | `web/src/pages/canvas/use-canvas-agent-operations.ts`、`use-canvas-assistant-visibility.ts`、`use-canvas-director.ts`、`use-canvas-keyboard.ts`;面板容器 `components/canvas/canvas-assistant-panel.tsx`,展示层 `canvas-assistant-panel-views.tsx`,在线工具 `canvas-assistant-online-tools.ts`,影视续写 `canvas-cinematic-continuation.ts` | +| 选择拖拽与连线控制 | `web/src/pages/canvas/use-canvas-selection-controller.ts`、`use-canvas-connection-controller.ts` | +| 上传、素材插入、媒体工具和短剧编排 | `web/src/pages/canvas/use-canvas-upload.ts`、`use-canvas-timeline-asset-insert.ts`、`use-canvas-media-tools.ts`、`use-canvas-storyboard.ts`、`use-canvas-generation-batches.ts`、`use-canvas-short-drama.ts` | +| 节点操作、节点编辑和视口导航 | `web/src/pages/canvas/use-canvas-node-operations.ts`、`use-canvas-node-editor.ts`、`use-canvas-viewport-controller.ts` | +| 小说、画风和角色工作流 | `web/src/pages/canvas/use-canvas-document-workflow.ts` | +| 派生渲染模型和画布分层 | `web/src/pages/canvas/use-canvas-render-model.ts`、`canvas-project-world-layers.tsx`、`canvas-project-context-menu.tsx`、`canvas-project-status-dialogs.tsx` | +| 顶栏、反馈和 screen-space 浮层 | `web/src/pages/canvas/canvas-project-top-bar.tsx`、`canvas-project-feedback.tsx`、`canvas-project-selection-toolbar.tsx`、`components/canvas/canvas-workspace-overlays.tsx` | +| 媒体弹窗装配 | `web/src/pages/canvas/canvas-project-media-dialogs.tsx` | +| 画布视口、拖拽、缩放和框选 | `web/src/components/canvas/infinite-canvas.tsx`、`web/src/lib/canvas/canvas-live-viewport.ts` | +| 节点渲染与节点工具 | `web/src/components/canvas/canvas-node.tsx`、`canvas-node-hover-toolbar.tsx` | +| 生成 Composer | `web/src/components/canvas/canvas-node-prompt-panel.tsx`、`canvas-config-composer.tsx` | +| 风格中心、用户风格编辑与项目快照 | `web/src/components/canvas/canvas-style-picker-modal.tsx`、`style-profile-editor-modal.tsx`、`style-asset-binding-modal.tsx`、`web/src/lib/canvas/style-profile.ts`、`web/src/services/api/style-profiles.ts` | +| 分镜脚本节点 | `web/src/components/canvas/canvas-script-node.tsx` | +| 3D 导演台 | `web/src/components/canvas/director/` | +| 节点生成上下文 | `web/src/components/canvas/canvas-node-generation.ts` | +| 布局和连线算法 | `web/src/lib/canvas/canvas-layout.ts`、`canvas-frame.ts` | +| 任务结果回填 | `web/src/lib/canvas/canvas-generation-task-sync.ts` | +| 本地持久化状态 | `web/src/stores/canvas/use-canvas-store.ts` | +| Agent 会话状态 | `web/src/stores/canvas/use-canvas-agent-store.ts` | +| 官方提示词目录与客户端起始稿 | `backend/internal/service/prompt_template.go`、`web/src/lib/prompts/`、`web/src/lib/canvas/canvas-agent-protocol.ts` | + +## 前端 HTTP 与产品壳 + +| 职责 | 代码位置 | +| --- | --- | +| 业务 JSON 调用面 | `web/src/services/api/request.ts` 的 `http`;拦截器仍挂 `apiClient` | +| 自定义渠道传输 | `web/src/services/api/channel-transport.ts`,image / video / audio 只组协议 | +| 自带外壳的弹窗 / 侧栏 | `web/src/components/ui/product/app-modal/`、`app-drawer/` | + +## 后端调用链 + +```text +HTTP 请求 -> handler -> service -> repository/model -> SQLite/PostgreSQL 或资源存储 + | + -> provider/outbound -> 模型上游 +``` + +| 功能 | Handler | Service / Repository | +| --- | --- | --- | +| 路由与中间件 | `handler/api.go`、`handler/routes.go` | `RegisterCanvasAPI` 挂到 `/api`。合同见 `docs/content/docs/backend/http-api.mdx`、`handler/openapi.yaml`。 | +| 用户和管理员 | `handler/auth.go` | `service/auth.go`、`service/admin.go` | +| 管理后台批量操作 | `handler/auth.go` | `service/admin.go`、`repository/admin_audit.go`、`repository/analytics.go` | +| 管理后台存储资源 | `handler/admin_storage.go` | `service/admin_storage.go`、`service/admin_storage_delete.go`、`repository/admin_storage.go`、`repository/admin_storage_delete.go` | +| 资源与请求策略 | `handler/auth.go`、`handler/security.go` | `service/runtime_policy.go`、`service/upload_quota.go`、`service/storage_quota.go`、`service/coordination.go` | +| 模型响应拦截 | `handler/response_interception.go`、`handler/custom_proxy.go` | `service/response_interception.go`、`repository/repository.go` | +| 异步任务、文本 SSE 与 Agent | `handler/routes.go` | `service/service.go`、`service/provider.go`(调度)及 `provider_text.go` / `provider_image.go` / `provider_video.go` / `provider_audio.go` / `provider_http_client.go` / `provider_protocol.go`、`service/task_text_stream.go`、`service/text_replay.go` | +| 用户画布与素材 | `handler/user_data.go` | `service/user_data.go`、`service/user_asset_document.go` | +| 文件和 OSS | `handler/routes.go` | `service/resource.go`、`service/settings.go` | +| 画布分享 | `handler/canvas_share.go` | `service/canvas_share.go` | +| 技能库 | `handler/skills.go` | `service/skills.go`、`repository/skills.go` | +| 提示词模板 | `handler/auth.go`、`handler/user_data.go` | `service/prompt_template.go`、`service/prompt_template_defaults.go` | +| 用户风格库 | `handler/style_profile.go` | `service/style_profile.go`、`repository/style_profile.go` | +| 用量分析 | `handler/admin_analytics.go` | `service/analytics.go`、`repository/analytics.go` | + +登录恢复仅初始化当前账号缓存与增量同步会话,不再自动读取 `GET /api/user-data/snapshot`。画布库通过分页摘要加载,打开、导出、重命名时按 ID 读取完整画布,引用素材通过批量 ID 接口读取;旧快照接口仍保留供显式调用。实现见 `service/user_data_page.go` 和前端 `services/user-data-sync.ts`。用户库素材写入(`UpsertUserAsset` / `ReplaceUserAssets`)在 `user_asset_document.go` 校验 SPA 合同:`kind`、`coverUrl`、`tags` 字符串数组,以及按类型完整的 `data`。不完整 payload 在入库时拒绝;GET 仍返回已保存的原始 JSON。上游异构 JSON 的可选字符串仍走 `stringField`;任务 ID 等关键字段走 `firstJSONString`,数字 ID 不再被当成“缺字段”。 + +后端托管的文本生成通过 `GET /api/tasks/:id/text-events` 返回 `progress`、`delta` 和 `terminal` 事件。`delta` 的递增事件 ID 是断线续传游标,前端使用 `?after=` 恢复;分镜任务复用该通道实时接收阶段和进度,但分镜结构和 Agent 工具参数仍在完整响应结束并校验通过后原子应用,不执行半截 JSON。 + +## 其他入口 + +- `canvas-agent/src/`:本地 Agent、MCP 工具和当前画布会话桥接。 +- `plugins/infinite-canvas/`:Codex App 插件清单与技能。 +- `docs/content/docs/`:产品、操作、开发和进度文档。 +- `docker-compose.local.yml`:本地前后端编排;`nginx.conf`:生产静态资源和 API 代理。 + +## 修改原则 + +- HTTP 参数与响应只在 handler 处理,业务规则进入 service。 +- 页面保留流程协调;可独立理解和测试的算法进入同业务域的 `lib`。 +- 新增功能时只更新本页的主要入口,不增加逐文件清单。 diff --git a/docs/content/docs/backend/http-api.mdx b/docs/content/docs/backend/http-api.mdx new file mode 100644 index 000000000..b2fc5f99f --- /dev/null +++ b/docs/content/docs/backend/http-api.mdx @@ -0,0 +1,51 @@ +--- +title: HTTP API 合同 +description: 信封、错误码与 OpenAPI +--- + +# HTTP API 合同 + +路径前缀是 `/api`。前后端一起发布,不按 `/api/v1`、`/api/v2` 做路径版本。不兼容变更随版本一起改接口和调用方。 + +机器可读规范:运行中的 `GET /api/openapi.yaml`,源文件 `backend/internal/handler/openapi.yaml`。前端 TypeScript 仍走 `web/src/services/api` 的 `http`,不从 OpenAPI 生成客户端。 + +## 信封 + +成功: + +```json +{ "code": 0, "data": {}, "msg": "ok" } +``` + +失败:HTTP status 与 `code` 都不是成功;`reason` 是稳定机器可读原因。 + +```json +{ "code": 40301, "data": null, "msg": "账号素材数量已达到 100 个上限", "reason": "quota_exceeded" } +``` + +`code === 0` 才是业务成功。HTTP 200 但 `code !== 0` 必须当失败。前端判断类型用 `ApiError.reason` / `ApiError.code`,不要解析 `msg`。 + +## 错误码 + +`code` 默认等于 HTTP 状态。需要区分同一状态的不同原因时,使用 `status * 100 + n`。没有单独的 1001/2001 号段,以免和 HTTP 状态、现有 42901 以及重试判断打架。 + +| code | HTTP | reason | 含义 | +| --- | --- | --- | --- | +| 0 | 200 | — | 成功 | +| 400 | 400 | `invalid_argument` | 请求不合法 | +| 401 | 401 | `unauthorized` | 未登录 | +| 403 | 403 | `forbidden` | 无权限 | +| 404 | 404 | `not_found` | 资源不存在 | +| 409 | 409 | `conflict` | 冲突 | +| 429 | 429 | `rate_limited` | 限流(未细分) | +| 500 | 500 | `internal` | 内部错误 | +| 502 | 502 | `bad_gateway` | 上游失败 | +| 503 | 503 | `unavailable` | 暂时不可用 | +| 504 | 504 | `timeout` | 超时 | +| 40301 | 403 | `quota_exceeded` | 账号配额用尽(不自动重试) | +| 40901 | 409 | `conflict` | 幂等冲突 | +| 42901 | 429 | `rate_limited` | 请求限流(可带 Retry-After) | + +模型错误把已有字符串码放进 `reason`(如 `model_capability_not_supported`),`code` 仍为 400。 + +常量:后端 `service/error_codes.go`,前端 `web/src/services/api/error-codes.ts`。 diff --git a/docs/content/docs/backend/local-development.mdx b/docs/content/docs/backend/local-development.mdx index 163b45f10..3eddb9069 100644 --- a/docs/content/docs/backend/local-development.mdx +++ b/docs/content/docs/backend/local-development.mdx @@ -50,10 +50,12 @@ go run ./cmd/server ```bash cd web -bun install +bun install --frozen-lockfile bun run dev ``` +`web/` 与 `canvas-agent/` 只使用 `bun.lock`。不要用 pnpm 或 npm 往同一套 `node_modules` 里再装一遍。 + 前端默认监听 `0.0.0.0:3000`,Vite 将 `/api` 和 OAuth 回调代理到 `127.0.0.1:8080`。如需修改代理目标,可设置 `VITE_API_PROXY_TARGET`。 ## 时间线字幕转写(whisper.cpp) diff --git a/docs/content/docs/backend/meta.json b/docs/content/docs/backend/meta.json index 4685f512d..3f3b87feb 100644 --- a/docs/content/docs/backend/meta.json +++ b/docs/content/docs/backend/meta.json @@ -4,6 +4,7 @@ "defaultOpen": true, "pages": [ "local-development", + "http-api", "code-map", "backend-database", "task-runtime-stability", diff --git a/docs/content/docs/overview/features.mdx b/docs/content/docs/overview/features.mdx index 1dba246cf..14dc832ea 100644 --- a/docs/content/docs/overview/features.mdx +++ b/docs/content/docs/overview/features.mdx @@ -71,6 +71,20 @@ SEO 标题、描述、可选关键词会投影到浏览器 metadata、Open Graph - `backend/internal/service/password_reset.go`:发送策略、验证码校验和密码重置业务边界。 - `backend/internal/repository/repository.go`:密码更新、验证码消费和登录会话撤销事务。 +## 官方提示词模板 + +平台级 LLM 任务只走后端提示词目录,不在页面里手写整段系统指令。运营可在管理后台维护版本,用户可在设置里跟随、追加或改写创作层;动态上下文和 JSON 输出契约由服务端编译时注入,不能被模板或个人偏好覆盖。 + +当前操作包括:章节资产提取、分镜规划/修复、分镜首帧、分镜视频、角色卡提取、角色三视图、短剧大纲、技能草稿。短剧一句话生成和技能库 AI 草稿只提交 `promptTemplateOperation` 与变量,占位 prompt 不会成为模型看到的正文。 + +画布内置预设、反推提示词、工作流起步稿、文本改写包装、视频操作包装、字幕高亮指令和在线 Agent 协议提示词属于客户端目录 `web/src/lib/prompts` 与 `web/src/lib/canvas/canvas-agent-protocol.ts`。Agent 系统提示词是产品契约,不开放运营改写,避免把工具权限改坏。 + +### 相关实现 + +- `backend/internal/service/prompt_template.go`、`prompt_template_defaults.go`:操作定义、编译、受保护契约和结果校验。 +- `web/src/lib/prompts/`:前端操作名、客户端起始/包装提示词和短剧大纲解析。 +- `web/src/pages/settings/prompt-preferences-pane.tsx`、`web/src/pages/admin/storyboard-prompts/storyboard-prompts-page.tsx`:用户偏好与运营版本。 + ## AI 提示词优化器 提示词优化器是一个内置 AI 能力插件,用于把模糊的生图或生视频想法整理成可以直接使用的提示词。它复用宿主已经配置的文本模型和渠道,不在浏览器中保存或拼接第三方密钥。 diff --git a/docs/content/docs/progress/pending-test.mdx b/docs/content/docs/progress/pending-test.mdx index 5d7656f3c..58aa7b243 100644 --- a/docs/content/docs/progress/pending-test.mdx +++ b/docs/content/docs/progress/pending-test.mdx @@ -5,6 +5,169 @@ description: 当前项目已实现功能的手工验证清单与未确认边界 # 待测试 +## HTTP API 错误码与 OpenAPI + +- 路径仍是 `/api`。不做 `/api/v1` 双挂;前后端一起发布。 +- 失败信封增加 `reason`。配额是 HTTP 403 / `code` 40301 / `reason=quota_exceeded`,不再用 400。限流是 429 / 42901 / `rate_limited`。 +- `GET /api/openapi.yaml` 提供 OpenAPI 3.0。不要生成 TS 客户端替换 `http`。 +- [x] `cd backend && go test ./internal/handler/ ./internal/service/ ./cmd/server/ -count=1` +- [x] `cd web && bun test test/api-request.test.ts` +- [ ] 登录后走素材库、画布、任务中心:Cookie 仍有效,请求仍打 `/api/...`。 +- [ ] 打开已有图片/视频资源 URL(`/api/resources/.../file`)仍能加载。 +- [ ] 文本任务 SSE 与系统渠道流式仍逐条到达。 +- [ ] 账号配额打满时前端能用 `reason === "quota_exceeded"` 区分,而不是当可重试 429。 + +## JSON 字符串类型与素材写入合同 + +- 上游 JSON 不再把数字/布尔伪装成空字符串。`optionalJSONString` / `requireJSONString` / `firstJSONString` 在字段类型错误时返回 `expected string, got T`。视频 / 音频 / 声明式协议的任务 ID 走这条路径。 +- `stringField` 仍用于可选查找(status、error.message、SSE 增量)。缺字段或类型错误对该键视为空,避免异构上游把整段解析打爆。 +- 用户库素材写入强制 `kind`、`coverUrl`、`tags`(字符串数组)和按类型完整的 `data`。前端 `parseAssetRecord` 拒绝不完整记录,素材库页不再读时 `normalizeAssetRecord`。 +- 已入库、缺 `tags` 或 `data` 的历史 payload:GET 仍原样返回;登录快照或素材库分页会解析失败。没有读时补丁,需要重新保存或清洗。 +- [x] `cd backend && go test ./internal/service/ -count=1` +- [x] `cd web && bun test test/asset-record.test.ts test/asset-storage-revision.test.ts test/user-scoped-generation-persistence.test.ts` +- [ ] 上传/生成一条完整素材后刷新:素材库、画布引用、跨端同步仍可用。 +- [ ] 用数字 `id` 的视频/音频上游(若有)应看到类型错误,而不是“没有返回任务 ID”。 +- [ ] 本地 IndexedDB 若仍是缺 `tags` 的旧缓存,打开素材库应失败而不是 tags 变成 `[]`。 + +## 巨型文件按能力/容器拆分 + +- `provider.go` 只保留调度、水合和共享类型。文本 / 图片 / 视频遗留 / 音频 / HTTP / 声明式协议分别在 `provider_text.go`、`provider_image.go`、`provider_video.go`、`provider_audio.go`、`provider_http_client.go`、`provider_protocol.go`。未导出函数仍同包,测试无需改 import。 +- 画布助手:容器 `canvas-assistant-panel.tsx`;展示层 `canvas-assistant-panel-views.tsx`;`onlineToolToOps` 在 `canvas-assistant-online-tools.ts`;影视续写边界在 `canvas-cinematic-continuation.ts`。测试仍从面板文件 re-export 取值。 +- 创作页:`create/index.tsx` 只做提交和会话状态;历史抽屉、Composer、消息视图在 `creation-workspace.tsx`。 +- 画布页时间线入轨从 `project.tsx` 抽到 `use-canvas-timeline-asset-insert.ts`。页面仍装配工作区 JSX。 +- [x] `cd backend && go test ./internal/service/ -count=1` +- [x] `cd web && bun test test/generation-task-local.test.ts test/user-scoped-generation-persistence.test.ts test/generation-task-materializer.test.ts test/create-library-button.test.ts test/create-canvas-handoff.test.ts` +- [ ] 画布在线助手:发一条会改画布的指令、确认工具、影视项目续写/刷新恢复,行为与拆分前一致。 +- [ ] 创作页空态/对话流、历史抽屉重命名删除、素材库插入仍可用。 +- [ ] 画布时间线弹窗内从素材库/项目资产插入音视频仍直连入轨;图片仍提示先插入画布。 + +## 前端 HTTP 与产品浮层壳 + +- 业务 JSON 模块统一走 `http.get/post/put/patch/delete`,不再写 `request(apiClient.*)`。CSV / 诊断包 zip 走 `http.raw`。资源 404 缓存认 `ApiError.status`,不再看已经解包后的 axios 错误。 +- image / video / audio 渠道请求经 `createChannelTransport`;语音合成走 `postBlob`,避免把音频当 JSON 解析。 +- 自带外壳的产品弹窗改用 `AppModal flush`;技能编辑和管理员用户详情改用 `AppDrawer`。不要再复制 `padding: 0` 的 Modal styles。 +- [x] `cd web && bun test test/api-request.test.ts test/http-ownership.test.ts` +- [ ] 登录后打开素材库、画布上传、风格选择、技能详情、渠道模型编辑:弹窗主题、关闭、内部滚动与改造前一致。 +- [ ] 上传资源、导出管理日志 CSV、导出诊断包:仍走同一登录 Cookie,失败文案可读。 +- [ ] 用音频渠道生成一条语音:应得到音频,而不是 JSON 解析错误。 + +## 产品 LLM 任务收进官方提示词目录 + +- 短剧一句话生成和技能库 AI 草稿不再把整段指令写在页面里。前端只传 `promptTemplateOperation` 与变量;服务端按启用模板 + 用户偏好编译,并校验 JSON 契约 `short-drama-outline/v1` / `skill-draft/v1`。 +- 画布预设、反推提示词、工作流起步稿、文本改写、视频操作包装、字幕高亮指令集中在 `web/src/lib/prompts`。在线 Agent 系统提示词和读写工具名单集中在 `canvas-agent-protocol` / `canvas-agent-tools`,在线写门控与本地日志分类共用同一份读工具列表。 +- 批改管线、提示词优化器、肖像比对等插件内指令仍留在各自模块,下一刀再收。 +- [x] `go test ./internal/service/ -run 'TestPromptOperationCatalog|TestRenderShortDrama|TestValidatePromptTemplateResultShortDrama|TestValidatePromptTemplateResultSkillDraft|TestValidatePromptTemplateResultCharacter' -count=1` +- [x] `cd web && bun test test/prompt-catalog-ownership.test.ts test/canvas-agent-workflow.test.ts` +- [ ] 重启后端后,设置 → 提示词偏好和管理后台提示词模板应出现「短剧大纲」「技能草稿」。改平台默认或用户追加后,项目页一句话生成和技能草稿应吃到新文案,JSON 结构仍能导入章节 / 填入编辑器。 +- [ ] 在线画布助手仍先读上下文再写;本地助手只读工具日志分类不变。画布预设、反推提示词、工作流缺省 prompt 文案与改造前一致。 + +## 最小 ESLint 只锁已退场的 AntD API + +- `web` 增加 `bun run lint`。不是风格检查:只禁止再 `import { Empty } from "antd"`,以及静态 `Modal.confirm(`。 +- 解析器用 `@babel/eslint-parser` + TypeScript preset。`typescript-eslint` 8 在仓库的 TypeScript 7 上会直接拒绝启动,所以没有用它。 +- CI Quality / Web checks 在 typecheck 之后跑 `bun run lint`。 +- [x] `cd web && bun run lint`:通过。 +- [ ] CI 确认 Web checks 出现 lint 步骤且现有 PR 能过。 + +## web / canvas-agent 只认 bun.lock + +- 删除遗留的 `web/pnpm-lock.yaml` 和 `canvas-agent/pnpm-lock.yaml`。同一套 `node_modules` 被 pnpm 覆盖过,会把 Tiptap 和 `lightningcss` 装成两套版本。 +- `web/package.json` 去掉仅给 pnpm 用的 `overrides` 副本,并声明 `packageManager: bun@1.3.9`。仓库内 Canvas Agent 安装/构建改写为 `bun install` / `bun run build`;对外仍用 `npx -y @ddcat666/open-ai-canvas-agent`。 +- CI 在 `bun install --frozen-lockfile` 之前拒绝 `pnpm-lock.yaml` / `package-lock.json` / `yarn.lock`。 +- [x] `bun test test/package-manager-lockfile.test.ts`。 +- [ ] 干净目录执行 `cd web && bun install --frozen-lockfile` 与 `cd canvas-agent && bun install --frozen-lockfile`,不要再用 pnpm/npm 装前端依赖。 + +## 确认框与空态退场静态 API + +- 命令式确认不再使用静态 `Modal.confirm`(拿不到 App 主题和上下文)。画布取消任务、方舟素材上传、任务中心删除本机记录改为 `App.useApp().modal.confirm`。行内危险操作仍用 `Popconfirm`。 +- 全库最后一处 AntD `Empty`(渠道排序弹窗)改为 `EmptyState`。插件中心筛选空态和提示词偏好空列表也接到 `EmptyState`。工作区整页空态仍用 `WorkspaceState`(信号图标),画布引导空态保持产品专用实现。 +- [x] `bun test test/ui-kit-retirement.test.ts test/channel-presentation.test.tsx test/plugin-state-switch.test.ts`。 +- [ ] 画布取消进行中任务、上传到方舟素材库:确认框明暗主题与页面一致,确认后仍执行原操作。 +- [ ] 任务中心删除本机记录:确认框主题正确,只删本机记录不删素材。 +- [ ] 管理后台渠道「设置排序」在无条目时显示 EmptyState;插件中心清空筛选后的空态可点「清除筛选」。 + +## 官方视频 InterfaceType 只走声明式插件 + +- `runVideoTask` 对已有官方插件的接口类型不再回退到手写协议。生产注入插件注册表;单测未注入时会补官方包。显式放入空注册表则报「…视频插件未安装」。 +- 覆盖的接口:`minimax-video`、`gemini-veo`、`novita-video`、`newapi-channel-1`、`newapi-channel-2`、`xai-video`、`volcengine-ark-video`、`volcengine-jimeng-video`、`newapi`、`agnes-video`。任务恢复也只查询声明式 adapter,不再回退 `queryNewAPIChannel2VideoTask`。 +- 仍保留的手写路径只有:空 InterfaceType 的 OpenAI 风格 multipart `/videos`、模型名含 `grok` 的 JSON `/videos`、BaseURL 含 `/api/plan/v3` 的 Ark Plan、模型名含 seedance 的 Seedance `/videos`。 +- 插件契约与旧手写实现不一致处按插件为准:MiniMax 时长必须 4–15 秒且不再把 `720`/`1440p` 映射成 `768P`/`2K`;Channel 2 不再做 grok 单图/音频校验;xAI 结果 URL 不可达时不再回退 `/videos/{id}/content`;Channel 1 音频 `type` 跟随宿主 role(当前为 `reference_audio`)。 +- [x] `go test ./internal/service/ ./internal/protocol/ -count=1`:通过。 +- [ ] 用 MiniMax / Veo / Novita / NewAPI Channel 1+2 / xAI / 方舟 / 即梦视频各跑一条生成,确认创建、轮询、下载仍走对应官方插件路径。 +- [ ] 卸掉某个官方视频插件后再提交同类型任务,应看到「…视频插件未安装」,而不是静默改走另一套协议。 +- [ ] Channel 2 创建后短暂 `task_not_exist` 仍应延期查询,不要二次创建。 + +## 列表分页查询改为 page + pageSize + +- 业务列表查询不再使用 `page_size`、`project_id`、`folder_id`、`media_type`、`unit_id` 或管理端 `limit`。查询名和分页响应字段统一为 camelCase:`page`、`pageSize`,以及 `projectId` / `folderId` / `mediaType` / `unitId`。 +- 技能库列表信封同步改为 `totalCount`、`hasMore`、`nextOffset`、`pageSize`。技能条目、安装表单、技能包文件与 GitHub 安装请求也已改为 camelCase;`createdAt` / `updatedAt` / `lastCheckedAt` / `lastSyncedAt` 为 RFC3339。内置技能种子文件仍是仓库内部 snake_case,读取后转换成对外合同。 +- Eagle 素材连接器、Dreamina 本地任务和肖像比对仍走对方自己的 `limit`/`offset`,不纳入这次契约。 +- [ ] 登录后翻页素材库、画布库、项目资产、技能库和积分流水,确认请求查询是 `pageSize` 而不是 `page_size`/`limit`。 +- [ ] 管理后台用户、渠道、日志、公告、兑换码、计费、支付订单、对账和存储资源列表翻页正常,响应里是 `pageSize`。 +- [ ] 技能库列表、详情、创建/编辑、ZIP/GitHub 安装:确认接口字段是 `skillId`、`skillName`、`createdAt` 等 camelCase,时间是 ISO 字符串而不是 unix 毫秒。 +- [ ] 画布 @技能 提及和技能运行时仍能解析已加入技能。 + +## 前端测试改为全目录自动发现 + +- `web/package.json` 的 `test` 脚本从手工枚举约 100 个文件改为 `bun test`,CI 的 `bun run test` 会跑到此前从未进闸门的用例(含素材弹窗、上传边界、同步文案等)。 +- 打开闸门前修了过时源码快照,以及:无入边时 `@图片1` 可引用节点自身;账号切换中止进行中请求的测试不再因 localforage 缺驱动而误失败。 +- [x] `bun test`:全目录应通过。 +- [ ] CI Quality / Web checks 确认 `bun run test` 覆盖新增文件且无顺序依赖失败。 + +## 写路径诚实化(存储直传 / 云端同步 / 计费退款 / 转码终态) + +- 资源直传失败按 `ApiError.retryable` 分类:401/403/400/413 等永久失败当场抛出;429/5xx 与断网允许退回 IndexedDB,由云端同步用同一幂等键重传。UI 不得把 `pendingRemoteUpload` 说成已保存到服务器。 +- 素材页、素材弹窗还原、批量上传、画布保存在云端同步失败时会排队 `scheduleRemoteUserDataSync`,文案改为「本地已保存,云端同步失败:…将自动重试」。LibTV/TapNow 导入仍只在本地写失败时回滚,云端失败不回滚已写入的节点。 +- 系统渠道代理:退款或待核对写失败会打进程日志;退款失败会转 `uncertain`,避免订单停在 running。空 `billingOrderID` 不再假装退款。 +- 播放转码终态回写失败会重试 3 次并打日志;`mark none` 写失败会恢复内存状态,避免前端把空状态当成 processing 轮询。 +- Responses→Chat Completions 回退只认 404/405/501,不再把 502/503/504 当成“接口不存在”。 +- [x] `bun test test/resource-upload-boundary.test.ts test/user-data-sync-message.test.ts`;`go test ./internal/service/ -run 'TestShouldFallbackTextToChat|TestPersistPlaybackResource'`。 +- [ ] 登录后上传一张图时断开后端:应看到本地已保存+将自动重试,而不是“已保存”。恢复后端后素材应出现在云端。 +- [ ] 用错误密码/过期登录上传:应直接报鉴权失败,文件不应静默进“稍后同步”。 +- [ ] 画布导入 LibTV 后人为让同步失败:节点保留,出现云端失败提示。 +- [ ] 系统渠道明确 4xx 后核对计费订单已退款;退款写库失败时订单应进入待核对。 + +## 引用素材库弹窗新增媒体类型筛选 + +- `AssetLibraryPickerModal` 左侧在「分类」之上新增「媒体类型」分组:全部类型 / 图片 / 视频 / 音频(可选文本)。媒体类型在分类之前收窄数据源,左侧分类计数、网格和分页描述同一批素材。 +- 新增 `mediaKinds` prop 声明可选类型,只有一种或调用方已用 `remoteKind` 锁定类型时不展示该分组;新增 item 字段 `mediaKind`,缺省时按本地素材或插件素材的 kind 推断,角色卡这类非媒体条目在筛选生效时不出现。 +- 远端素材库按服务端 `kind` 过滤并重置到第一页;`remoteKind` 仍是调用方的硬约束,媒体筛选不会覆盖它。 +- 各入口传参:创作首页与项目主图沿用默认/图片单类型;画布插入素材、画布项目资产、短剧制造引用个人素材声明含文本的四类;角色三视图与角色声音已由 `remoteKind` 锁定,不展示分组。 +- [x] `bun run typecheck`、`bun test test/canvas-asset-picker.test.ts test/create-library-button.test.ts test/asset-ingest.test.ts test/assets-page-batch-toolbar.test.ts`:23 passed。 +- [ ] 创作首页「参考内容」:切换图片/视频/音频,确认分类计数、网格和分页一致,翻页后筛选保持。 +- [ ] 画布「插入素材」「项目资产」:确认文本类型可筛选,角色卡在筛选非全部时不出现。 +- [ ] 短剧制造「引用个人素材」以及角色三视图/声音绑定:确认前者有媒体类型分组、后两者没有。 +- [ ] 窄屏(≤680px)下左栏横向滚动,媒体类型与分类合并成一排后仍可点选。 + +## 画布顶栏提示被生成任务面板遮挡 + +- 现象:画布右上角有进行中任务时,顶栏按钮的悬浮提示被「生成任务」面板压住,文字读不全。 +- 原因:顶栏是 `--z-toolbar`(30) 的绝对定位浮层,会为子元素建立层叠上下文;顶栏提示原来是行内绝对定位、`--dock-tooltip-z`(140) 只在该上下文内生效,实际仍低于 `--z-panel-floating`(80) 的任务面板。提示向下展开的位置又正好落在 `--canvas-topbar-offset`(72px) 的面板顶边。 +- 修复:`CanvasTopBarTooltip` 改用 Ant Design `Tooltip`(portal 到浮层层级),与专注模式工具栏、节点悬浮工具的提示实现一致;底部 dock 的提示向上展开,不受影响,保持原样。 +- 视觉变化:顶栏提示改用全局浮层配色(`--popover`),不再是 dock 提示的自定义样式。 +- [x] `bun run typecheck` 通过。未做浏览器验证:复现需要画布上有进行中的生成任务。 +- [ ] 登录画布并发起一个生成任务,逐个悬停顶栏按钮,确认提示显示在任务面板之上、明暗主题下都可读。 +- [ ] 确认菜单类按钮(画布菜单、导入第三方画布、媒体性能)的下拉仍能正常打开。 + +## 远端素材未归一化导致的 tags undefined + +- 现象:部分素材的 `tags` 是 `undefined`,首页点击「参考内容」时页面报「当前页面没有正常加载,Cannot read properties of undefined (reading 'join')」。 +- 原因:素材在服务端是不透明 payload,早期记录没有 `tags` 字段。本地缓存、登录快照和素材库页面都会走 `normalizeAssetRecord`,但 `user-data-sync` 的 `acceptRemoteAssets`(素材选择弹窗分页、按 ID 拉取引用素材)把服务端原样对象直接写进 store。 +- 修复:`acceptRemoteAssets` 与登录快照都在写入前归一化,并让 acknowledged 基线复用同一份对象。顺带修掉登录时 store 存归一化副本、基线存原始副本导致这些素材被永久标记为待上传的差异。 +- [x] `bun test test/user-scoped-generation-persistence.test.ts test/asset-storage-revision.test.ts test/asset-remote-sync.test.ts test/canvas-asset-picker.test.ts`:58 passed;`bun run typecheck` 通过。 +- [ ] 登录后在首页点击「参考内容」,翻页浏览历史素材,确认不再白屏。 +- [ ] 观察一次同步周期,确认历史素材没有被反复回传服务端。 + +## 声明式协议插件丢失系统指令 + +- 现象:短剧「AI 生成章节」报「AI 没有返回有效的章节内容,请重试」。上游请求只带一条 user 消息,要求输出 JSON 的系统指令被丢弃,模型返回散文,前端解析不到章节。 +- 原因:`manifestRequestValues` 构造的 `request.messages` 不包含 `request.instructions`,而 OpenAI 系官方插件(chat-completion、deepseek-chat、atlascloud-chat 等 26 个)只映射 `request.messages`。 +- 修复:宿主在 `request.messages` 前补一条 system 消息(请求本身已有 system 消息时不重复);`openai-responses` 和 `google-gemini-generate-content` 改为过滤 system 角色,改用各自的 `instructions` / `systemInstruction` 字段,与 `anthropic-messages` 一致。 +- 需要重新打包并重装的官方插件:`openai-responses`、`google-gemini-generate-content`。其余 OpenAI 系插件只需后端升级,无需重装。 +- [x] `go test ./internal/protocol/ ./internal/service/`:通过。 +- [ ] 登录后用 DeepSeek 等 chat-completion 渠道跑「开始一部新短剧 → AI 生成章节」,确认章节正常导入。 +- [ ] Claude、Gemini、OpenAI Responses 渠道各跑一次文本生成,确认系统指令生效且没有重复出现在正文里。 + ## v1.2.8.rc1 候选版本发布 - 发布版本与镜像标签分别为 `v1.2.8.rc1` 和 `1.2.8.rc1`;GitHub Release 应标记为预发布,不能被当作正式版本推荐,也不覆盖已发布的 `latest`。 diff --git a/docs/design/user-diagnostic-bundle.mdx b/docs/design/user-diagnostic-bundle.mdx index 23ab2e4dd..9313ac062 100644 --- a/docs/design/user-diagnostic-bundle.mdx +++ b/docs/design/user-diagnostic-bundle.mdx @@ -326,7 +326,7 @@ X-Diagnostic-Bundle-ID: DIAG_8F3K2P X-Diagnostic-Schema-Version: 1 ``` -由于成功响应是二进制,不经过只处理 JSON 信封的 `request` 解包函数;仍复用现有 `apiClient` 的认证和 Base URL,设置 `responseType: "blob"`。错误响应继续使用统一 `{ code, data, msg }` 语义,前端按响应类型解析。 +由于成功响应是二进制,不经过 JSON 信封解包;前端用 `http.raw`(同一 `apiClient` 的认证和 Base URL),设置 `responseType: "blob"`。错误响应继续使用统一 `{ code, data, msg }` 语义,前端按响应类型解析。 ### 权限规则 diff --git a/docs/index.md b/docs/index.md index 8624b5893..ca1c2681c 100644 --- a/docs/index.md +++ b/docs/index.md @@ -47,6 +47,7 @@ - [AI 审美批改画布插件](content/docs/plugins/ai-art-critique.mdx) - [功能](content/docs/overview/features.mdx) - [本地开发](content/docs/backend/local-development.mdx) +- [HTTP API 合同](content/docs/backend/http-api.mdx) - [后端数据库](content/docs/backend/backend-database.mdx) - [代码地图](content/docs/backend/code-map.mdx) - [待办](content/docs/progress/todo.mdx) diff --git a/web/bun.lock b/web/bun.lock index 4e781e80a..28e67e515 100644 --- a/web/bun.lock +++ b/web/bun.lock @@ -68,6 +68,9 @@ "zustand": "^5.0.14", }, "devDependencies": { + "@babel/core": "7.29.7", + "@babel/eslint-parser": "7.28.6", + "@babel/preset-typescript": "7.28.5", "@tailwindcss/postcss": "^4.3.3", "@types/file-saver": "^2.0.7", "@types/node": "^20.19.43", @@ -75,6 +78,7 @@ "@types/react-dom": "19.2.3", "@types/three": "0.185.4", "@vitejs/plugin-react": "^6.0.3", + "eslint": "9.39.5", "prettier": "^3.9.5", "typescript": "^7.0.2", "vite": "^8.1.5", @@ -150,6 +154,8 @@ "@babel/core": ["@babel/core@7.29.7", "https://registry.npmmirror.com/@babel/core/-/core-7.29.7.tgz", { "dependencies": { "@babel/code-frame": "^7.29.7", "@babel/generator": "^7.29.7", "@babel/helper-compilation-targets": "^7.29.7", "@babel/helper-module-transforms": "^7.29.7", "@babel/helpers": "^7.29.7", "@babel/parser": "^7.29.7", "@babel/template": "^7.29.7", "@babel/traverse": "^7.29.7", "@babel/types": "^7.29.7", "@jridgewell/remapping": "^2.3.5", "convert-source-map": "^2.0.0", "debug": "^4.1.0", "gensync": "^1.0.0-beta.2", "json5": "^2.2.3", "semver": "^6.3.1" } }, "sha512-RgHBCvtjbOK2gXSNBNIkNoEc9qoVEtau3hj8gEqKQuL3HZAibKarWFEI3Lfm6EYKkLalOh8eSrj9b+ch9H/VBA=="], + "@babel/eslint-parser": ["@babel/eslint-parser@7.28.6", "", { "dependencies": { "@nicolo-ribaudo/eslint-scope-5-internals": "5.1.1-v1", "eslint-visitor-keys": "^2.1.0", "semver": "^6.3.1" }, "peerDependencies": { "@babel/core": "^7.11.0", "eslint": "^7.5.0 || ^8.0.0 || ^9.0.0" } }, "sha512-QGmsKi2PBO/MHSQk+AAgA9R6OHQr+VqnniFE0eMWZcVcfBZoA2dKn2hUsl3Csg/Plt9opRUWdY7//VXsrIlEiA=="], + "@babel/generator": ["@babel/generator@7.29.7", "https://registry.npmmirror.com/@babel/generator/-/generator-7.29.7.tgz", { "dependencies": { "@babel/parser": "^7.29.7", "@babel/types": "^7.29.7", "@jridgewell/gen-mapping": "^0.3.12", "@jridgewell/trace-mapping": "^0.3.28", "jsesc": "^3.0.2" } }, "sha512-DkXD5OJQaAQIdZ1bt3UZdEnHAn9Imd3IVBdX03UFe+ony9Ojw5pzr9YVKGDY1jt+Gcn/FnGkNf8r+Vj5NOJWtQ=="], "@babel/helper-annotate-as-pure": ["@babel/helper-annotate-as-pure@7.27.3", "https://registry.npmmirror.com/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.27.3.tgz", { "dependencies": { "@babel/types": "^7.27.3" } }, "sha512-fXSwMQqitTGeHLBC08Eq5yXz2m37E4pJX1qAU1+2cNedz/ifv/bVXft90VeSav5nFO61EcNgwr0aJxbyPaWBPg=="], @@ -288,6 +294,24 @@ "@emotion/weak-memoize": ["@emotion/weak-memoize@0.4.0", "https://registry.npmmirror.com/@emotion/weak-memoize/-/weak-memoize-0.4.0.tgz", {}, "sha512-snKqtPW01tN0ui7yu9rGv69aJXr/a/Ywvl11sUjNtEcRc+ng/mQriFL0wLXMef74iHa/EkftbDzU9F8iFbH+zg=="], + "@eslint-community/eslint-utils": ["@eslint-community/eslint-utils@4.10.1", "", { "dependencies": { "eslint-visitor-keys": "^3.4.3" }, "peerDependencies": { "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" } }, "sha512-cuadcxVFE8sDK6iWJbs8Sn0av2Nrh2QSGQhVlBW9AaAHqHwjWsZHT8LJ4hFGPh7ASBV2deFdM7H/DPjulmh8rg=="], + + "@eslint-community/regexpp": ["@eslint-community/regexpp@4.12.2", "", {}, "sha512-EriSTlt5OC9/7SXkRSCAhfSxxoSUgBm33OH+IkwbdpgoqsSsUg7y3uh+IICI/Qg4BBWr3U2i39RpmycbxMq4ew=="], + + "@eslint/config-array": ["@eslint/config-array@0.21.2", "", { "dependencies": { "@eslint/object-schema": "^2.1.7", "debug": "^4.3.1", "minimatch": "^3.1.5" } }, "sha512-nJl2KGTlrf9GjLimgIru+V/mzgSK0ABCDQRvxw5BjURL7WfH5uoWmizbH7QB6MmnMBd8cIC9uceWnezL1VZWWw=="], + + "@eslint/config-helpers": ["@eslint/config-helpers@0.4.2", "", { "dependencies": { "@eslint/core": "^0.17.0" } }, "sha512-gBrxN88gOIf3R7ja5K9slwNayVcZgK6SOUORm2uBzTeIEfeVaIhOpCtTox3P6R7o2jLFwLFTLnC7kU/RGcYEgw=="], + + "@eslint/core": ["@eslint/core@0.17.0", "", { "dependencies": { "@types/json-schema": "^7.0.15" } }, "sha512-yL/sLrpmtDaFEiUj1osRP4TI2MDz1AddJL+jZ7KSqvBuliN4xqYY54IfdN8qD8Toa6g1iloph1fxQNkjOxrrpQ=="], + + "@eslint/eslintrc": ["@eslint/eslintrc@3.3.7", "", { "dependencies": { "ajv": "^6.14.0", "debug": "^4.3.2", "espree": "^10.0.1", "globals": "^14.0.0", "ignore": "^5.2.0", "import-fresh": "^3.2.1", "js-yaml": "^4.3.2", "minimatch": "^3.1.5", "strip-json-comments": "^3.1.1" } }, "sha512-F42g89Qd5oAWtp0k0nnSrjziAKza7w8SVT4mStc18LZMaRb4J1HQAHLCalEtDCxrTuksx7NU9qsmeLwpOfPqWw=="], + + "@eslint/js": ["@eslint/js@9.39.5", "", {}, "sha512-QywQuszQh77pIXCsq998c8hbhSTI/azTty1Z6N53dmAudKHhy573j3yvRLsX2BSp8YpLtoCEG8E9DJe+8zUh4A=="], + + "@eslint/object-schema": ["@eslint/object-schema@2.1.7", "", {}, "sha512-VtAOaymWVfZcmZbp6E2mympDIHvyjXs/12LqWYjVw6qjrfF+VK+fyG33kChz3nnK+SU5/NeHOqrTEHS8sXO3OA=="], + + "@eslint/plugin-kit": ["@eslint/plugin-kit@0.4.1", "", { "dependencies": { "@eslint/core": "^0.17.0", "levn": "^0.4.1" } }, "sha512-43/qtrDUokr7LJqoF2c3+RInu/t4zfrpYdoSDfYyhg52rwLV6TnOvdG4fXm7IkSB3wErkcmJS9iEhjVtOSEjjA=="], + "@excalidraw/excalidraw": ["@excalidraw/excalidraw@0.18.1", "https://registry.npmmirror.com/@excalidraw/excalidraw/-/excalidraw-0.18.1.tgz", { "dependencies": { "@braintree/sanitize-url": "6.0.2", "@excalidraw/laser-pointer": "1.3.1", "@excalidraw/mermaid-to-excalidraw": "2.2.2", "@excalidraw/random-username": "1.1.0", "@radix-ui/react-popover": "1.1.6", "@radix-ui/react-tabs": "1.0.2", "browser-fs-access": "0.29.1", "canvas-roundrect-polyfill": "0.0.1", "clsx": "1.1.1", "cross-env": "7.0.3", "es6-promise-pool": "2.5.0", "fractional-indexing": "3.2.0", "fuzzy": "0.1.3", "image-blob-reduce": "3.0.1", "jotai": "2.11.0", "jotai-scope": "0.7.2", "lodash.debounce": "4.0.8", "lodash.throttle": "4.1.1", "nanoid": "3.3.3", "open-color": "1.9.1", "pako": "2.0.3", "perfect-freehand": "1.2.0", "pica": "7.1.1", "png-chunk-text": "1.0.0", "png-chunks-encode": "1.0.0", "png-chunks-extract": "1.0.0", "points-on-curve": "1.0.1", "pwacompat": "2.0.17", "roughjs": "4.6.4", "sass": "1.51.0", "tunnel-rat": "0.1.2" }, "peerDependencies": { "react": "^17.0.2 || ^18.2.0 || ^19.0.0", "react-dom": "^17.0.2 || ^18.2.0 || ^19.0.0" } }, "sha512-6i5Gt7IDTOH//qa0Z315Ly5iVRhjWpu2whrlQFqkuwrkKUWgRsMk0P5qdE7bpyDpai7jeLeWYkyj1eVAfni1lw=="], "@excalidraw/laser-pointer": ["@excalidraw/laser-pointer@1.3.1", "https://registry.npmmirror.com/@excalidraw/laser-pointer/-/laser-pointer-1.3.1.tgz", {}, "sha512-psA1z1N2qeAfsORdXc9JmD2y4CmDwmuMRxnNdJHZexIcPwaNEyIpNcelw+QkL9rz9tosaN9krXuKaRqYpRAR6g=="], @@ -324,6 +348,16 @@ "@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=="], + "@humanfs/core": ["@humanfs/core@0.19.2", "", { "dependencies": { "@humanfs/types": "^0.15.0" } }, "sha512-UhXNm+CFMWcbChXywFwkmhqjs3PRCmcSa/hfBgLIb7oQ5HNb1wS0icWsGtSAUNgefHeI+eBrA8I1fxmbHsGdvA=="], + + "@humanfs/node": ["@humanfs/node@0.16.8", "", { "dependencies": { "@humanfs/core": "^0.19.2", "@humanfs/types": "^0.15.0", "@humanwhocodes/retry": "^0.4.0" } }, "sha512-gE1eQNZ3R++kTzFUpdGlpmy8kDZD/MLyHqDwqjkVQI0JMdI1D51sy1H958PNXYkM2rAac7e5/CnIKZrHtPh3BQ=="], + + "@humanfs/types": ["@humanfs/types@0.15.0", "", {}, "sha512-ZZ1w0aoQkwuUuC7Yf+7sdeaNfqQiiLcSRbfI08oAxqLtpXQr9AIVX7Ay7HLDuiLYAaFPu8oBYNq/QIi9URHJ3Q=="], + + "@humanwhocodes/module-importer": ["@humanwhocodes/module-importer@1.0.1", "", {}, "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA=="], + + "@humanwhocodes/retry": ["@humanwhocodes/retry@0.4.3", "", {}, "sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ=="], + "@iconify/types": ["@iconify/types@2.0.0", "https://registry.npmmirror.com/@iconify/types/-/types-2.0.0.tgz", {}, "sha512-+wluvCrRhXrhyOmRDJ3q8mux9JkKy5SJ/v8ol2tu4FVjyYvtEzkc/3pK15ET6RKg4b4w4BmTk1+gsCUhf21Ykg=="], "@iconify/utils": ["@iconify/utils@3.1.4", "https://registry.npmmirror.com/@iconify/utils/-/utils-3.1.4.tgz", { "dependencies": { "@antfu/install-pkg": "^1.1.0", "@iconify/types": "^2.0.0", "import-meta-resolve": "^4.2.0" } }, "sha512-b1S7B1k9ohZ+iNTi2ATxbRYG9fTrJmUT0rc46bvVnNxqNRGW7dyo/vRREwyniI5IRN2RSJHDcm+s3BjWrSAjHw=="], @@ -476,6 +510,8 @@ "@napi-rs/wasm-runtime": ["@napi-rs/wasm-runtime@1.1.6", "https://registry.npmmirror.com/@napi-rs/wasm-runtime/-/wasm-runtime-1.1.6.tgz", { "dependencies": { "@tybys/wasm-util": "^0.10.3" }, "peerDependencies": { "@emnapi/core": "^1.7.1", "@emnapi/runtime": "^1.7.1" } }, "sha512-ZLv/JdUfkvOy9eCnnBaGfiO+XimbjebAeO+MRQqD/B+FR1tnRN0tpKSJHRbE8sFfS6aqsXZ67TQjfwfsxULVbg=="], + "@nicolo-ribaudo/eslint-scope-5-internals": ["@nicolo-ribaudo/eslint-scope-5-internals@5.1.1-v1", "", { "dependencies": { "eslint-scope": "5.1.1" } }, "sha512-54/JRvkLIzzDWshCWfuhadfrfZVPiElY8Fcgmg1HroEly/EDSszzhBAsarCux+D/kOslTRquNzuyGSmUSTTHGg=="], + "@noble/ciphers": ["@noble/ciphers@1.3.0", "https://registry.npmmirror.com/@noble/ciphers/-/ciphers-1.3.0.tgz", {}, "sha512-2I0gnIVPtfnMw9ee9h1dJG7tp81+8Ob3OJb3Mv37rx5L40/b0i7djjCVvGOVqc9AEIQyvyu1i6ypKdFw8R8gQw=="], "@noble/curves": ["@noble/curves@1.9.7", "https://registry.npmmirror.com/@noble/curves/-/curves-1.9.7.tgz", { "dependencies": { "@noble/hashes": "1.8.0" } }, "sha512-gbKGcRUYIjA3/zCCNaWDciTMFI0dCkvou3TL8Zmy5Nc7sJ47a0jtOeZoTaMxkuqRo9cRhjOdZJXegxYE5FN/xw=="], @@ -984,6 +1020,8 @@ "@types/js-cookie": ["@types/js-cookie@3.0.6", "https://registry.npmmirror.com/@types/js-cookie/-/js-cookie-3.0.6.tgz", {}, "sha512-wkw9yd1kEXOPnvEeEV1Go1MmxtBJL0RR79aOTAApecWFVu7w0NNXNqhcWgvw2YgZDYadliXkl14pa3WXw5jlCQ=="], + "@types/json-schema": ["@types/json-schema@7.0.15", "", {}, "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA=="], + "@types/katex": ["@types/katex@0.16.8", "https://registry.npmmirror.com/@types/katex/-/katex-0.16.8.tgz", {}, "sha512-trgaNyfU+Xh2Tc+ABIb44a5AYUpicB3uwirOioeOkNPPbmgRNtcWyDeeFRzjPZENO9Vq8gvVqfhaaXWLlevVwg=="], "@types/mdast": ["@types/mdast@4.0.4", "https://registry.npmmirror.com/@types/mdast/-/mdast-4.0.4.tgz", { "dependencies": { "@types/unist": "*" } }, "sha512-kGaNbPh1k7AFzgpud/gMdvIm5xuECykRR+JnWKQno9TAXVa6WIVCGTPvYGekIDL4uwCZQSYbUxNBSb1aUo79oA=="], @@ -1088,12 +1126,14 @@ "ahooks": ["ahooks@3.9.7", "https://registry.npmmirror.com/ahooks/-/ahooks-3.9.7.tgz", { "dependencies": { "@babel/runtime": "^7.21.0", "@types/js-cookie": "^3.0.6", "dayjs": "^1.9.1", "intersection-observer": "^0.12.0", "js-cookie": "^3.0.5", "lodash": "^4.17.21", "react-fast-compare": "^3.2.2", "resize-observer-polyfill": "^1.5.1", "screenfull": "^5.0.0", "tslib": "^2.4.1" }, "peerDependencies": { "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" } }, "sha512-S0lvzhbdlhK36RFBkGv+RbOM/dbbweym+BIHM/bwwuWVSVN5TuVErHPMWo4w0t1NDYg5KPp2iEf7Y7E5LASYiw=="], - "ajv": ["ajv@8.20.0", "https://registry.npmmirror.com/ajv/-/ajv-8.20.0.tgz", { "dependencies": { "fast-deep-equal": "^3.1.3", "fast-uri": "^3.0.1", "json-schema-traverse": "^1.0.0", "require-from-string": "^2.0.2" } }, "sha512-Thbli+OlOj+iMPYFBVBfJ3OmCAnaSyNn4M1vz9T6Gka5Jt9ba/HIR56joy65tY6kx/FCF5VXNB819Y7/GUrBGA=="], + "ajv": ["ajv@6.15.0", "", { "dependencies": { "fast-deep-equal": "^3.1.1", "fast-json-stable-stringify": "^2.0.0", "json-schema-traverse": "^0.4.1", "uri-js": "^4.2.2" } }, "sha512-fgFx7Hfoq60ytK2c7DhnF8jIvzYgOMxfugjLOSMHjLIPgenqa7S7oaagATUq99mV6IYvN2tRmC0wnTYX6iPbMw=="], "ajv-formats": ["ajv-formats@3.0.1", "https://registry.npmmirror.com/ajv-formats/-/ajv-formats-3.0.1.tgz", { "dependencies": { "ajv": "^8.0.0" } }, "sha512-8iUql50EUR+uUcdRQ3HDqa6EVyo3docL8g5WJ3FNcWmu62IbkGUue/pEyLBW8VGKKucTPgqeks4fIU1DA4yowQ=="], "ansi-regex": ["ansi-regex@6.2.2", "https://registry.npmmirror.com/ansi-regex/-/ansi-regex-6.2.2.tgz", {}, "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg=="], + "ansi-styles": ["ansi-styles@4.3.0", "", { "dependencies": { "color-convert": "^2.0.1" } }, "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg=="], + "antd": ["antd@6.5.1", "https://registry.npmmirror.com/antd/-/antd-6.5.1.tgz", { "dependencies": { "@ant-design/colors": "^8.0.1", "@ant-design/cssinjs": "^2.1.2", "@ant-design/cssinjs-utils": "^2.1.2", "@ant-design/fast-color": "^3.0.1", "@ant-design/icons": "^6.3.2", "@ant-design/react-slick": "~2.0.0", "@babel/runtime": "^7.29.2", "@rc-component/cascader": "~1.17.0", "@rc-component/checkbox": "~2.0.0", "@rc-component/collapse": "~1.2.0", "@rc-component/color-picker": "~3.1.1", "@rc-component/dialog": "~1.10.0", "@rc-component/drawer": "~1.4.2", "@rc-component/dropdown": "~1.0.3", "@rc-component/form": "~1.8.5", "@rc-component/image": "~1.9.0", "@rc-component/input": "~1.3.1", "@rc-component/input-number": "~1.6.2", "@rc-component/mentions": "~1.10.0", "@rc-component/menu": "~1.4.1", "@rc-component/motion": "^1.3.3", "@rc-component/mutate-observer": "^2.0.1", "@rc-component/notification": "~2.0.7", "@rc-component/pagination": "~1.4.0", "@rc-component/picker": "~1.11.0", "@rc-component/progress": "~1.0.2", "@rc-component/qrcode": "~2.0.0", "@rc-component/rate": "~1.0.1", "@rc-component/resize-observer": "^1.1.2", "@rc-component/segmented": "~1.3.0", "@rc-component/select": "~1.8.2", "@rc-component/slider": "~1.1.1", "@rc-component/steps": "~1.2.2", "@rc-component/switch": "~1.0.3", "@rc-component/table": "~1.10.4", "@rc-component/tabs": "~1.11.0", "@rc-component/tooltip": "~1.4.0", "@rc-component/tour": "~2.4.0", "@rc-component/tree": "~1.3.2", "@rc-component/tree-select": "~1.11.0", "@rc-component/trigger": "^3.10.0", "@rc-component/upload": "~1.1.1", "@rc-component/util": "^1.11.1", "clsx": "^2.1.1", "dayjs": "^1.11.11", "scroll-into-view-if-needed": "^3.1.0", "throttle-debounce": "^5.0.2" }, "peerDependencies": { "react": ">=18.0.0", "react-dom": ">=18.0.0" } }, "sha512-VZVVF9zYI6S0NHqboVhCoY9Iiqj6dphW1NPB+sEaAf2HuIQ0haXWXj7ZvAXTRDzusktV6+cvvrSZEdRi4twATg=="], "antd-style": ["antd-style@4.1.0", "https://registry.npmmirror.com/antd-style/-/antd-style-4.1.0.tgz", { "dependencies": { "@ant-design/cssinjs": "^2.0.0", "@babel/runtime": "^7.24.1", "@emotion/cache": "^11.11.0", "@emotion/css": "^11.11.2", "@emotion/react": "^11.11.4", "@emotion/serialize": "^1.1.3", "@emotion/utils": "^1.2.1", "use-merge-value": "^1.2.0" }, "peerDependencies": { "antd": ">=6.0.0", "react": ">=18" } }, "sha512-vnPBGg0OVlSz90KRYZhxd89aZiOImTiesF+9MQqN8jsLGZUQTjbP04X9jTdEfsztKUuMbBWg/RmB/wHTakbtMQ=="], @@ -1120,7 +1160,7 @@ "bail": ["bail@2.0.2", "https://registry.npmmirror.com/bail/-/bail-2.0.2.tgz", {}, "sha512-0xO6mYd7JB2YesxDKplafRpsiOzPt9V02ddPCLbY1xYGPOX24NTyN50qnUxgCPcSoYMhKpAuBTjQoRZCAkUDRw=="], - "balanced-match": ["balanced-match@4.0.4", "https://registry.npmmirror.com/balanced-match/-/balanced-match-4.0.4.tgz", {}, "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA=="], + "balanced-match": ["balanced-match@1.0.2", "", {}, "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw=="], "base64-js": ["base64-js@1.5.1", "https://registry.npmmirror.com/base64-js/-/base64-js-1.5.1.tgz", {}, "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA=="], @@ -1136,7 +1176,7 @@ "body-parser": ["body-parser@2.2.2", "https://registry.npmmirror.com/body-parser/-/body-parser-2.2.2.tgz", { "dependencies": { "bytes": "^3.1.2", "content-type": "^1.0.5", "debug": "^4.4.3", "http-errors": "^2.0.0", "iconv-lite": "^0.7.0", "on-finished": "^2.4.1", "qs": "^6.14.1", "raw-body": "^3.0.1", "type-is": "^2.0.1" } }, "sha512-oP5VkATKlNwcgvxi0vM0p/D3n2C3EReYVX+DNYs5TjZFn/oQt2j+4sVJtSMr18pdRr8wjTcBl6LoV+FUwzPmNA=="], - "brace-expansion": ["brace-expansion@5.0.6", "https://registry.npmmirror.com/brace-expansion/-/brace-expansion-5.0.6.tgz", { "dependencies": { "balanced-match": "^4.0.2" } }, "sha512-kLpxurY4Z4r9sgMsyG0Z9uzsBlgiU/EFKhj/h91/8yHu0edo7XuixOIH3VcJ8kkxs6/jPzoI6U9Vj3WqbMQ94g=="], + "brace-expansion": ["brace-expansion@1.1.18", "", { "dependencies": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" } }, "sha512-Edep/X9fGqVNmzKBVsDYIOtD+z1tuezV70LBjdCst9Tqu76lsnvRiZ6oTic1n+/BIwX6QDGAO94PN4N2SADvtw=="], "braces": ["braces@3.0.3", "https://registry.npmmirror.com/braces/-/braces-3.0.3.tgz", { "dependencies": { "fill-range": "^7.1.1" } }, "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA=="], @@ -1164,7 +1204,7 @@ "ccount": ["ccount@2.0.1", "https://registry.npmmirror.com/ccount/-/ccount-2.0.1.tgz", {}, "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@4.1.2", "", { "dependencies": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" } }, "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA=="], "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=="], @@ -1200,6 +1240,10 @@ "collapse-white-space": ["collapse-white-space@2.1.0", "https://registry.npmmirror.com/collapse-white-space/-/collapse-white-space-2.1.0.tgz", {}, "sha512-loKTxY1zCOuG4j9f6EPnuyyYkf58RnhhWTvRoZEokgB+WbdXehfjFviyOVYkqzEWz1Q5kRiZdBYS5SwxbQYwzw=="], + "color-convert": ["color-convert@2.0.1", "", { "dependencies": { "color-name": "~1.1.4" } }, "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ=="], + + "color-name": ["color-name@1.1.4", "", {}, "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA=="], + "colord": ["colord@2.9.3", "https://registry.npmmirror.com/colord/-/colord-2.9.3.tgz", {}, "sha512-jeC1axXpnb0/2nn/Y1LPuLdgXBLH7aDcHu4KEKfqw3CUhX7ZpfBSlPKyqXE6btIgEzfWtrX3/tyBCaCvXvMkOw=="], "combined-stream": ["combined-stream@1.0.8", "https://registry.npmmirror.com/combined-stream/-/combined-stream-1.0.8.tgz", { "dependencies": { "delayed-stream": "~1.0.0" } }, "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg=="], @@ -1210,6 +1254,8 @@ "compute-scroll-into-view": ["compute-scroll-into-view@3.1.1", "https://registry.npmmirror.com/compute-scroll-into-view/-/compute-scroll-into-view-3.1.1.tgz", {}, "sha512-VRhuHOLoKYOy4UbilLbUzbYg93XLjv2PncJC50EuTWPA3gaja1UjBsUP/D/9/juV3vQFr6XBEzn9KCAHdUvOHw=="], + "concat-map": ["concat-map@0.0.1", "", {}, "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg=="], + "content-disposition": ["content-disposition@1.1.0", "https://registry.npmmirror.com/content-disposition/-/content-disposition-1.1.0.tgz", {}, "sha512-5jRCH9Z/+DRP7rkvY83B+yGIGX96OYdJmzngqnw2SBSxqCFPd0w2km3s5iawpGX8krnwSGmF0FW5Nhr0Hfai3g=="], "content-type": ["content-type@1.0.5", "https://registry.npmmirror.com/content-type/-/content-type-1.0.5.tgz", {}, "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA=="], @@ -1328,6 +1374,8 @@ "dedent": ["dedent@1.7.2", "https://registry.npmmirror.com/dedent/-/dedent-1.7.2.tgz", { "peerDependencies": { "babel-plugin-macros": "^3.1.0" }, "optionalPeers": ["babel-plugin-macros"] }, "sha512-WzMx3mW98SN+zn3hgemf4OzdmyNhhhKz5Ay0pUfQiMQ3e1g+xmTJWp/pKdwKVXhdSkAEGIIzqeuWrL3mV/AXbA=="], + "deep-is": ["deep-is@0.1.4", "", {}, "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ=="], + "deepmerge": ["deepmerge@4.3.1", "https://registry.npmmirror.com/deepmerge/-/deepmerge-4.3.1.tgz", {}, "sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A=="], "default-browser": ["default-browser@5.5.0", "https://registry.npmmirror.com/default-browser/-/default-browser-5.5.0.tgz", { "dependencies": { "bundle-name": "^4.1.0", "default-browser-id": "^5.0.0" } }, "sha512-H9LMLr5zwIbSxrmvikGuI/5KGhZ8E2zH3stkMgM5LpOWDutGM2JZaj460Udnf1a+946zc7YBgrqEWwbk7zHvGw=="], @@ -1410,8 +1458,22 @@ "escape-string-regexp": ["escape-string-regexp@4.0.0", "https://registry.npmmirror.com/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", {}, "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA=="], + "eslint": ["eslint@9.39.5", "", { "dependencies": { "@eslint-community/eslint-utils": "^4.8.0", "@eslint-community/regexpp": "^4.12.1", "@eslint/config-array": "^0.21.2", "@eslint/config-helpers": "^0.4.2", "@eslint/core": "^0.17.0", "@eslint/eslintrc": "^3.3.6", "@eslint/js": "9.39.5", "@eslint/plugin-kit": "^0.4.1", "@humanfs/node": "^0.16.6", "@humanwhocodes/module-importer": "^1.0.1", "@humanwhocodes/retry": "^0.4.2", "@types/estree": "^1.0.6", "ajv": "^6.14.0", "chalk": "^4.0.0", "cross-spawn": "^7.0.6", "debug": "^4.3.2", "escape-string-regexp": "^4.0.0", "eslint-scope": "^8.4.0", "eslint-visitor-keys": "^4.2.1", "espree": "^10.4.0", "esquery": "^1.5.0", "esutils": "^2.0.2", "fast-deep-equal": "^3.1.3", "file-entry-cache": "^8.0.0", "find-up": "^5.0.0", "glob-parent": "^6.0.2", "ignore": "^5.2.0", "imurmurhash": "^0.1.4", "is-glob": "^4.0.0", "json-stable-stringify-without-jsonify": "^1.0.1", "lodash.merge": "^4.6.2", "minimatch": "^3.1.5", "natural-compare": "^1.4.0", "optionator": "^0.9.3" }, "peerDependencies": { "jiti": "*" }, "optionalPeers": ["jiti"], "bin": { "eslint": "bin/eslint.js" } }, "sha512-DgZS62aPLXKlnxILS/AYCoRvHaZeXceIzlXPkkGGzJWSow1aEk0lbTlxUSlyjC8jcaKxAdOnTDz+o1JFSBsyjw=="], + + "eslint-scope": ["eslint-scope@8.4.0", "", { "dependencies": { "esrecurse": "^4.3.0", "estraverse": "^5.2.0" } }, "sha512-sNXOfKCn74rt8RICKMvJS7XKV/Xk9kA7DyJr8mJik3S7Cwgy3qlkkmyS2uQB3jiJg6VNdZd/pDBJu0nvG2NlTg=="], + + "eslint-visitor-keys": ["eslint-visitor-keys@2.1.0", "", {}, "sha512-0rSmRBzXgDzIsD6mGdJgevzgezI534Cer5L/vyMX0kHzT/jiB43jRhd9YUlMGYLQy2zprNmoT8qasCGtY+QaKw=="], + + "espree": ["espree@10.4.0", "", { "dependencies": { "acorn": "^8.15.0", "acorn-jsx": "^5.3.2", "eslint-visitor-keys": "^4.2.1" } }, "sha512-j6PAQ2uUr79PZhBjP5C5fhl8e39FmRnOjsD5lGnWrFU8i2G776tBK7+nP8KuQUTTyAZUwfQqXAgrVH5MbH9CYQ=="], + "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=="], + "esquery": ["esquery@1.7.0", "", { "dependencies": { "estraverse": "^5.1.0" } }, "sha512-Ap6G0WQwcU/LHsvLwON1fAQX9Zp0A2Y6Y/cJBl9r/JbW90Zyg4/zbG6zzKa2OTALELarYHmKu0GhpM5EO+7T0g=="], + + "esrecurse": ["esrecurse@4.3.0", "", { "dependencies": { "estraverse": "^5.2.0" } }, "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag=="], + + "estraverse": ["estraverse@5.3.0", "", {}, "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA=="], + "estree-util-attach-comments": ["estree-util-attach-comments@3.0.0", "https://registry.npmmirror.com/estree-util-attach-comments/-/estree-util-attach-comments-3.0.0.tgz", { "dependencies": { "@types/estree": "^1.0.0" } }, "sha512-cKUwm/HUcTDsYh/9FgnuFqpfquUbwIqwKM26BVCGDPVgvaCl/nDCCjUfiLlx6lsEZ3Z4RFxNbOQ60pkaEwFxGw=="], "estree-util-build-jsx": ["estree-util-build-jsx@3.0.1", "https://registry.npmmirror.com/estree-util-build-jsx/-/estree-util-build-jsx-3.0.1.tgz", { "dependencies": { "@types/estree-jsx": "^1.0.0", "devlop": "^1.0.0", "estree-util-is-identifier-name": "^3.0.0", "estree-walker": "^3.0.0" } }, "sha512-8U5eiL6BTrPxp/CHbs2yMgP8ftMhR5ww1eIKoWRMlqvltHF8fZn5LRDvTKuxD3DUn+shRbLGqXemcP51oFCsGQ=="], @@ -1426,6 +1488,8 @@ "estree-walker": ["estree-walker@3.0.3", "https://registry.npmmirror.com/estree-walker/-/estree-walker-3.0.3.tgz", { "dependencies": { "@types/estree": "^1.0.0" } }, "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g=="], + "esutils": ["esutils@2.0.3", "", {}, "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g=="], + "etag": ["etag@1.8.1", "https://registry.npmmirror.com/etag/-/etag-1.8.1.tgz", {}, "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg=="], "eventemitter3": ["eventemitter3@5.0.4", "https://registry.npmmirror.com/eventemitter3/-/eventemitter3-5.0.4.tgz", {}, "sha512-mlsTRyGaPBjPedk6Bvw+aqbsXDtoAyAzm5MO7JgU+yVRyMQ5O8bD4Kcci7BS85f93veegeCPkL8R4GLClnjLFw=="], @@ -1450,6 +1514,10 @@ "fast-glob": ["fast-glob@3.3.3", "https://registry.npmmirror.com/fast-glob/-/fast-glob-3.3.3.tgz", { "dependencies": { "@nodelib/fs.stat": "^2.0.2", "@nodelib/fs.walk": "^1.2.3", "glob-parent": "^5.1.2", "merge2": "^1.3.0", "micromatch": "^4.0.8" } }, "sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg=="], + "fast-json-stable-stringify": ["fast-json-stable-stringify@2.1.0", "", {}, "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw=="], + + "fast-levenshtein": ["fast-levenshtein@2.0.6", "", {}, "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw=="], + "fast-uri": ["fast-uri@3.1.2", "https://registry.npmmirror.com/fast-uri/-/fast-uri-3.1.2.tgz", {}, "sha512-rVjf7ArG3LTk+FS6Yw81V1DLuZl1bRbNrev6Tmd/9RaroeeRRJhAt7jg/6YFxbvAQXUCavSoZhPPj6oOx+5KjQ=="], "fastq": ["fastq@1.20.1", "https://registry.npmmirror.com/fastq/-/fastq-1.20.1.tgz", { "dependencies": { "reusify": "^1.0.4" } }, "sha512-GGToxJ/w1x32s/D2EKND7kTil4n8OVk/9mycTc4VDza13lOvpUZTGX3mFSCtV9ksdGBVzvsyAVLM6mHFThxXxw=="], @@ -1462,6 +1530,8 @@ "figures": ["figures@6.1.0", "https://registry.npmmirror.com/figures/-/figures-6.1.0.tgz", { "dependencies": { "is-unicode-supported": "^2.0.0" } }, "sha512-d+l3qxjSesT4V7v2fh+QnmFnUWv9lSpjarhShNTgBOfA0ttejbQUAlHLitbjkoRiDulW0OPoQPYIGhIC8ohejg=="], + "file-entry-cache": ["file-entry-cache@8.0.0", "", { "dependencies": { "flat-cache": "^4.0.0" } }, "sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ=="], + "file-saver": ["file-saver@2.0.5", "https://registry.npmmirror.com/file-saver/-/file-saver-2.0.5.tgz", {}, "sha512-P9bmyZ3h/PRG+Nzga+rbdI4OEpNDzAVyy74uVO9ATgzLK6VtAsYybF/+TOCvrc0MO793d6+42lLyZTw7/ArVzA=="], "file-selector": ["file-selector@0.5.0", "https://registry.npmmirror.com/file-selector/-/file-selector-0.5.0.tgz", { "dependencies": { "tslib": "^2.0.3" } }, "sha512-s8KNnmIDTBoD0p9uJ9uD0XY38SCeBOtj0UMXyQSLg1Ypfrfj8+dAvwsLjYQkQ2GjhVtp2HrnF5cJzMhBjfD8HA=="], @@ -1474,6 +1544,12 @@ "find-root": ["find-root@1.1.0", "https://registry.npmmirror.com/find-root/-/find-root-1.1.0.tgz", {}, "sha512-NKfW6bec6GfKc0SGx1e07QZY9PE99u0Bft/0rzSD5k3sO/vwkVUpDUKVm5Gpp5Ue3YfShPFTX2070tDs5kB9Ng=="], + "find-up": ["find-up@5.0.0", "", { "dependencies": { "locate-path": "^6.0.0", "path-exists": "^4.0.0" } }, "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng=="], + + "flat-cache": ["flat-cache@4.0.1", "", { "dependencies": { "flatted": "^3.2.9", "keyv": "^4.5.4" } }, "sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw=="], + + "flatted": ["flatted@3.4.4", "", {}, "sha512-5+ybhBZANEJxaH3X5evAFatUxLfEHSr7n6kYJ+1Qd0mUqr4eu9gIf6GDbWHf8RJijHrjjO8G+la14SlL2SeS1Q=="], + "follow-redirects": ["follow-redirects@1.16.0", "https://registry.npmmirror.com/follow-redirects/-/follow-redirects-1.16.0.tgz", {}, "sha512-y5rN/uOsadFT/JfYwhxRS5R7Qce+g3zG97+JrtFZlC9klX/W5hD7iiLzScI4nZqUS7DNUdhPgw4xI8W2LuXlUw=="], "for-in": ["for-in@1.0.2", "https://registry.npmmirror.com/for-in/-/for-in-1.0.2.tgz", {}, "sha512-7EwmXrOjyL+ChxMhmG5lnW9MPt1aIeZEwKhQzoBUdTV0N3zuwWDZYVJatDvZ2OyzPUvdIAZDsCetk3coyMfcnQ=="], @@ -1518,7 +1594,9 @@ "giscus": ["giscus@1.6.0", "https://registry.npmmirror.com/giscus/-/giscus-1.6.0.tgz", { "dependencies": { "lit": "^3.2.1" } }, "sha512-Zrsi8r4t1LVW950keaWcsURuZUQwUaMKjvJgTCY125vkW6OiEBkatE7ScJDbpqKHdZwb///7FVC21SE3iFK3PQ=="], - "glob-parent": ["glob-parent@5.1.2", "https://registry.npmmirror.com/glob-parent/-/glob-parent-5.1.2.tgz", { "dependencies": { "is-glob": "^4.0.1" } }, "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow=="], + "glob-parent": ["glob-parent@6.0.2", "", { "dependencies": { "is-glob": "^4.0.3" } }, "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A=="], + + "globals": ["globals@14.0.0", "", {}, "sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ=="], "glsl-noise": ["glsl-noise@0.0.0", "https://registry.npmmirror.com/glsl-noise/-/glsl-noise-0.0.0.tgz", {}, "sha512-b/ZCF6amfAUb7dJM/MxRs7AetQEahYzJ8PtgfrmEdtw6uyGOr+ZSGtgjFm6mfsBkxJ4d2W7kg+Nlqzqvn3Bc0w=="], @@ -1530,6 +1608,8 @@ "hachure-fill": ["hachure-fill@0.5.2", "https://registry.npmmirror.com/hachure-fill/-/hachure-fill-0.5.2.tgz", {}, "sha512-3GKBOn+m2LX9iq+JC1064cSFprJY4jL1jCXTcpnfER5HYE2l/4EfWSGzkPa/ZDBmYI0ZOEj5VHV/eKnPGkHuOg=="], + "has-flag": ["has-flag@4.0.0", "", {}, "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ=="], + "has-symbols": ["has-symbols@1.1.0", "https://registry.npmmirror.com/has-symbols/-/has-symbols-1.1.0.tgz", {}, "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ=="], "has-tostringtag": ["has-tostringtag@1.0.2", "https://registry.npmmirror.com/has-tostringtag/-/has-tostringtag-1.0.2.tgz", { "dependencies": { "has-symbols": "^1.0.3" } }, "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw=="], @@ -1606,6 +1686,8 @@ "import-meta-resolve": ["import-meta-resolve@4.2.0", "https://registry.npmmirror.com/import-meta-resolve/-/import-meta-resolve-4.2.0.tgz", {}, "sha512-Iqv2fzaTQN28s/FwZAoFq0ZSs/7hMAHJVX+w8PZl3cY19Pxk6jFFalxQoIfW2826i/fDLXv8IiEZRIT0lDuWcg=="], + "imurmurhash": ["imurmurhash@0.1.4", "", {}, "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA=="], + "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.2.7", "https://registry.npmmirror.com/inline-style-parser/-/inline-style-parser-0.2.7.tgz", {}, "sha512-Nb2ctOyNR8DqQoR0OwRG95uNWIC0C1lCgf5Naz5H6Ji72KZ8OcFZLz2P5sNgwlyoJ8Yif11oMuYs5pBQa86csA=="], @@ -1668,7 +1750,7 @@ "isarray": ["isarray@1.0.0", "https://registry.npmmirror.com/isarray/-/isarray-1.0.0.tgz", {}, "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ=="], - "isexe": ["isexe@3.1.5", "https://registry.npmmirror.com/isexe/-/isexe-3.1.5.tgz", {}, "sha512-6B3tLtFqtQS4ekarvLVMZ+X+VlvQekbe4taUkf/rhVO3d/h0M2rfARm/pXLcPEsjjMsFgrFgSrhQIxcSVrBz8w=="], + "isexe": ["isexe@2.0.0", "https://registry.npmmirror.com/isexe/-/isexe-2.0.0.tgz", {}, "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw=="], "isobject": ["isobject@3.0.1", "https://registry.npmmirror.com/isobject/-/isobject-3.0.1.tgz", {}, "sha512-WhB9zCku7EGTj/HQQRz5aUQEUeoQZH2bWcltRErOpymJ4boYE6wL9Tbr23krRPSZ+C5zqNSrSw+Cc7sZZ4b7vg=="], @@ -1690,14 +1772,18 @@ "jsesc": ["jsesc@3.1.0", "https://registry.npmmirror.com/jsesc/-/jsesc-3.1.0.tgz", { "bin": { "jsesc": "bin/jsesc" } }, "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA=="], + "json-buffer": ["json-buffer@3.0.1", "", {}, "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ=="], + "json-canonicalize": ["json-canonicalize@2.0.0", "https://registry.npmmirror.com/json-canonicalize/-/json-canonicalize-2.0.0.tgz", {}, "sha512-yyrnK/mEm6Na3ChbJUWueXdapueW0p380RUyTW87XGb1ww8l8hU0pRrGC3vSWHe9CxrbPHX2fGUOZpNiHR0IIg=="], "json-parse-even-better-errors": ["json-parse-even-better-errors@2.3.1", "https://registry.npmmirror.com/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", {}, "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w=="], - "json-schema-traverse": ["json-schema-traverse@1.0.0", "https://registry.npmmirror.com/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", {}, "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug=="], + "json-schema-traverse": ["json-schema-traverse@0.4.1", "", {}, "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg=="], "json-schema-typed": ["json-schema-typed@8.0.2", "https://registry.npmmirror.com/json-schema-typed/-/json-schema-typed-8.0.2.tgz", {}, "sha512-fQhoXdcvc3V28x7C7BMs4P5+kNlgUURe2jmUT1T//oBRMDrqy1QPelJimwZGo7Hg9VPV3EQV5Bnq4hbFy2vetA=="], + "json-stable-stringify-without-jsonify": ["json-stable-stringify-without-jsonify@1.0.1", "", {}, "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw=="], + "json2mq": ["json2mq@0.2.0", "https://registry.npmmirror.com/json2mq/-/json2mq-0.2.0.tgz", { "dependencies": { "string-convert": "^0.2.0" } }, "sha512-SzoRg7ux5DWTII9J2qkrZrqV1gt+rTaoufMxEzXbS26Uid0NwaJd123HcoB80TgubEppxxIGdNxCx50fEoEWQA=="], "json5": ["json5@2.2.3", "https://registry.npmmirror.com/json5/-/json5-2.2.3.tgz", { "bin": { "json5": "lib/cli.js" } }, "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg=="], @@ -1708,6 +1794,8 @@ "katex": ["katex@0.16.47", "https://registry.npmmirror.com/katex/-/katex-0.16.47.tgz", { "dependencies": { "commander": "^8.3.0" }, "bin": { "katex": "cli.js" } }, "sha512-Eeo8Ys1doU1z+x8AZsPpQu+p/QcZBI5PeOo7QGQdy2x2m0MU/hYagBbGOmXwr5KVbEfVuWv9LpnQWeehogurjg=="], + "keyv": ["keyv@4.5.4", "", { "dependencies": { "json-buffer": "3.0.1" } }, "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw=="], + "khroma": ["khroma@2.1.0", "https://registry.npmmirror.com/khroma/-/khroma-2.1.0.tgz", {}, "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=="], @@ -1720,6 +1808,8 @@ "leva": ["leva@0.10.1", "https://registry.npmmirror.com/leva/-/leva-0.10.1.tgz", { "dependencies": { "@radix-ui/react-portal": "^1.1.4", "@radix-ui/react-tooltip": "^1.1.8", "@stitches/react": "^1.2.8", "@use-gesture/react": "^10.2.5", "colord": "^2.9.2", "dequal": "^2.0.2", "merge-value": "^1.0.0", "react-colorful": "^5.5.1", "react-dropzone": "^12.0.0", "v8n": "^1.3.3", "zustand": "^3.6.9" }, "peerDependencies": { "react": "^18.0.0 || ^19.0.0", "react-dom": "^18.0.0 || ^19.0.0" } }, "sha512-BcjnfUX8jpmwZUz2L7AfBtF9vn4ggTH33hmeufDULbP3YgNZ/C+ss/oO3stbrqRQyaOmRwy70y7BGTGO81S3rA=="], + "levn": ["levn@0.4.1", "", { "dependencies": { "prelude-ls": "^1.2.1", "type-check": "~0.4.0" } }, "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ=="], + "lie": ["lie@3.1.1", "https://registry.npmmirror.com/lie/-/lie-3.1.1.tgz", { "dependencies": { "immediate": "~3.0.5" } }, "sha512-RiNhHysUjhrDQntfYSfY4MU24coXXdEOgw9WGcKHNeEwffDYbF//u87M1EWaMGzuFoSbqW0C9C6lEEhDOAswfw=="], "lightningcss": ["lightningcss@1.32.0", "https://registry.npmmirror.com/lightningcss/-/lightningcss-1.32.0.tgz", { "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=="], @@ -1758,6 +1848,8 @@ "localforage": ["localforage@1.10.0", "https://registry.npmmirror.com/localforage/-/localforage-1.10.0.tgz", { "dependencies": { "lie": "3.1.1" } }, "sha512-14/H1aX7hzBBmmh7sGPd+AOMkkIrHM3Z1PAyGgZigA1H1p5O5ANnMyWzvpAETtG68/dC4pC0ncy3+PPGzXZHPg=="], + "locate-path": ["locate-path@6.0.0", "", { "dependencies": { "p-locate": "^5.0.0" } }, "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw=="], + "lodash": ["lodash@4.18.1", "https://registry.npmmirror.com/lodash/-/lodash-4.18.1.tgz", {}, "sha512-dMInicTPVE8d1e5otfwmmjlxkZoUpiVLwyeTdUsi/Caj/gfzzblBcCE5sRHV/AsjuCmxWrte2TNGSYuCeCq+0Q=="], "lodash-es": ["lodash-es@4.18.1", "https://registry.npmmirror.com/lodash-es/-/lodash-es-4.18.1.tgz", {}, "sha512-J8xewKD/Gk22OZbhpOVSwcs60zhd95ESDwezOFuA3/099925PdHJ7OFHNTGtajL3AlZkykD32HykiMo+BIBI8A=="], @@ -1768,6 +1860,8 @@ "lodash.isequalwith": ["lodash.isequalwith@4.4.0", "https://registry.npmmirror.com/lodash.isequalwith/-/lodash.isequalwith-4.4.0.tgz", {}, "sha512-dcZON0IalGBpRmJBmMkaoV7d3I80R2O+FrzsZyHdNSFrANq/cgDqKQNmAHE8UEj4+QYWwwhkQOVdLHiAopzlsQ=="], + "lodash.merge": ["lodash.merge@4.6.2", "", {}, "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ=="], + "lodash.throttle": ["lodash.throttle@4.1.1", "https://registry.npmmirror.com/lodash.throttle/-/lodash.throttle-4.1.1.tgz", {}, "sha512-wIkUCfVKpVsWo3JSZlc+8MB5it+2AN5W8J7YVMST30UrvcQNZ1Okbj+rbVniijTWE6FGYy4XJq/rHkas8qJMLQ=="], "lodash.uniq": ["lodash.uniq@4.5.0", "https://registry.npmmirror.com/lodash.uniq/-/lodash.uniq-4.5.0.tgz", {}, "sha512-xfBaXQd9ryd9dlSDvnvI0lvxfLJlYAZzXomUYzLKtUeOQvOP5piqAWuGtrhWeqaXK9hhoM/iyJc5AV+XfsX3HQ=="], @@ -1946,7 +2040,7 @@ "mimic-function": ["mimic-function@5.0.1", "https://registry.npmmirror.com/mimic-function/-/mimic-function-5.0.1.tgz", {}, "sha512-VP79XUPxV2CigYP3jWwAUFSku2aKqBH7uTAapFWCBqutsbmDo96KY5o8uh6U+/YSIn5OxJnXp73beVkpqMIGhA=="], - "minimatch": ["minimatch@10.2.5", "https://registry.npmmirror.com/minimatch/-/minimatch-10.2.5.tgz", { "dependencies": { "brace-expansion": "^5.0.5" } }, "sha512-MULkVLfKGYDFYejP07QOurDLLQpcjk7Fw+7jXS2R2czRQzR56yHRveU5NDJEOviH+hETZKSkIk5c+T23GjFUMg=="], + "minimatch": ["minimatch@3.1.5", "", { "dependencies": { "brace-expansion": "^1.1.7" } }, "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w=="], "minimist": ["minimist@1.2.8", "https://registry.npmmirror.com/minimist/-/minimist-1.2.8.tgz", {}, "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA=="], @@ -1964,6 +2058,8 @@ "nanoid": ["nanoid@6.0.0", "https://registry.npmmirror.com/nanoid/-/nanoid-6.0.0.tgz", { "bin": { "nanoid": "bin/nanoid.js" } }, "sha512-mkUH+rPkwU2qPadJ0oJZOjeZ5Mxn8Q1UhevwkTRWNuUZzyia3h4rhzK39hxaHTk0o2OxB8W2SQ6A8k23ZDi1pQ=="], + "natural-compare": ["natural-compare@1.4.0", "", {}, "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw=="], + "negotiator": ["negotiator@1.0.0", "https://registry.npmmirror.com/negotiator/-/negotiator-1.0.0.tgz", {}, "sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg=="], "node-releases": ["node-releases@2.0.44", "https://registry.npmmirror.com/node-releases/-/node-releases-2.0.44.tgz", {}, "sha512-5WUyunoPMsvvEhS8AxHtRzP+oA8UCkJ7YRxatWKjngndhDGLiqEVAQKWjFAiAiuL8zMRGzGSJxFnLetoa43qGQ=="], @@ -1998,10 +2094,16 @@ "option": ["option@0.2.4", "https://registry.npmmirror.com/option/-/option-0.2.4.tgz", {}, "sha512-pkEqbDyl8ou5cpq+VsnQbe/WlEy5qS7xPzMS1U55OCG9KPvwFD46zDbxQIj3egJSFc3D+XhYOPUzz49zQAVy7A=="], + "optionator": ["optionator@0.9.4", "", { "dependencies": { "deep-is": "^0.1.3", "fast-levenshtein": "^2.0.6", "levn": "^0.4.1", "prelude-ls": "^1.2.1", "type-check": "^0.4.0", "word-wrap": "^1.2.5" } }, "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g=="], + "ora": ["ora@8.2.0", "https://registry.npmmirror.com/ora/-/ora-8.2.0.tgz", { "dependencies": { "chalk": "^5.3.0", "cli-cursor": "^5.0.0", "cli-spinners": "^2.9.2", "is-interactive": "^2.0.0", "is-unicode-supported": "^2.0.0", "log-symbols": "^6.0.0", "stdin-discarder": "^0.2.2", "string-width": "^7.2.0", "strip-ansi": "^7.1.0" } }, "sha512-weP+BZ8MVNnlCm8c0Qdc1WSWq4Qn7I+9CJGm7Qali6g44e/PUzbjNqJX5NJ9ljlNMosfJvg1fKEGILklK9cwnw=="], "orderedmap": ["orderedmap@2.1.1", "https://registry.npmmirror.com/orderedmap/-/orderedmap-2.1.1.tgz", {}, "sha512-TvAWxi0nDe1j/rtMcWcIj94+Ffe6n7zhow33h40SKxmsmozs6dz/e+EajymfoFcHd7sxNn8yHM8839uixMOV6g=="], + "p-limit": ["p-limit@3.1.0", "", { "dependencies": { "yocto-queue": "^0.1.0" } }, "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ=="], + + "p-locate": ["p-locate@5.0.0", "", { "dependencies": { "p-limit": "^3.0.2" } }, "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw=="], + "package-manager-detector": ["package-manager-detector@1.8.0", "https://registry.npmmirror.com/package-manager-detector/-/package-manager-detector-1.8.0.tgz", {}, "sha512-yQA4H19AmPEoMUeavPMDIe1higySl/gH/yaQrkT/s07Qp+7pp2hYz30N3z2l5BkjVkF9Ow6o0wjJamm2y7Sn0A=="], "pako": ["pako@2.0.3", "https://registry.npmmirror.com/pako/-/pako-2.0.3.tgz", {}, "sha512-WjR1hOeg+kki3ZIOjaf4b5WVcay1jaliKSYiEaB1XzwhMQZJxRdQRv0V31EKBYlxb4T7SK3hjfc/jxyU64BoSw=="], @@ -2022,6 +2124,8 @@ "path-data-parser": ["path-data-parser@0.1.0", "https://registry.npmmirror.com/path-data-parser/-/path-data-parser-0.1.0.tgz", {}, "sha512-NOnmBpt5Y2RWbuv0LMzsayp3lVylAHLPUTut412ZA3l+C4uw4ZVkQbjShYCQ8TCpUMdPapr4YjUqLYD6v68j+w=="], + "path-exists": ["path-exists@4.0.0", "", {}, "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w=="], + "path-is-absolute": ["path-is-absolute@1.0.1", "https://registry.npmmirror.com/path-is-absolute/-/path-is-absolute-1.0.1.tgz", {}, "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg=="], "path-key": ["path-key@3.1.1", "https://registry.npmmirror.com/path-key/-/path-key-3.1.1.tgz", {}, "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q=="], @@ -2062,6 +2166,8 @@ "powershell-utils": ["powershell-utils@0.1.0", "https://registry.npmmirror.com/powershell-utils/-/powershell-utils-0.1.0.tgz", {}, "sha512-dM0jVuXJPsDN6DvRpea484tCUaMiXWjuCn++HGTqUWzGDjv5tZkEZldAJ/UMlqRYGFrD/etByo4/xOuC/snX2A=="], + "prelude-ls": ["prelude-ls@1.2.1", "", {}, "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g=="], + "prettier": ["prettier@3.9.5", "https://registry.npmmirror.com/prettier/-/prettier-3.9.5.tgz", { "bin": { "prettier": "bin/prettier.cjs" } }, "sha512-/FVl766LpUfB5vXgCYOYa0MeV/441Ia99AeICQIQFTY/Nw0roZwULcXpku5i1/m5kt/baz+s4Zogspd839HSMg=="], "pretty-ms": ["pretty-ms@9.3.0", "https://registry.npmmirror.com/pretty-ms/-/pretty-ms-9.3.0.tgz", { "dependencies": { "parse-ms": "^4.0.0" } }, "sha512-gjVS5hOP+M3wMm5nmNOucbIrqudzs9v/57bWRHQWLYklXqoXKrVfYW2W9+glfGsqtPgpiz5WwyEEB+ksXIx3gQ=="], @@ -2108,6 +2214,8 @@ "proxy-from-env": ["proxy-from-env@2.1.0", "https://registry.npmmirror.com/proxy-from-env/-/proxy-from-env-2.1.0.tgz", {}, "sha512-cJ+oHTW1VAEa8cJslgmUZrc+sjRKgAKl3Zyse6+PV38hZe/V6Z14TbCuXcan9F9ghlz4QrFr2c92TNF82UkYHA=="], + "punycode": ["punycode@2.3.1", "", {}, "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg=="], + "pwacompat": ["pwacompat@2.0.17", "https://registry.npmmirror.com/pwacompat/-/pwacompat-2.0.17.tgz", {}, "sha512-6Du7IZdIy7cHiv7AhtDy4X2QRM8IAD5DII69mt5qWibC2d15ZU8DmBG1WdZKekG11cChSu4zkSUGPF9sweOl6w=="], "qs": ["qs@6.15.3", "https://registry.npmmirror.com/qs/-/qs-6.15.3.tgz", { "dependencies": { "es-define-property": "^1.0.1", "side-channel": "^1.1.1" } }, "sha512-O9gl3zCl5h5blw1KGUzQKhA5oUXSl8rwUIM5o0S3nCXMliSvy5Dzx7/DJcI+SwgICv+IneSZwhBh1oSyEHA71A=="], @@ -2374,6 +2482,8 @@ "strip-final-newline": ["strip-final-newline@4.0.0", "https://registry.npmmirror.com/strip-final-newline/-/strip-final-newline-4.0.0.tgz", {}, "sha512-aulFJcD6YK8V1G7iRB5tigAP4TsHBZZrOV8pjV++zdUwmeV8uzbY7yn6h9MswN62adStNZFuCIx4haBnRuMDaw=="], + "strip-json-comments": ["strip-json-comments@3.1.1", "", {}, "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig=="], + "style-mod": ["style-mod@4.1.3", "https://registry.npmmirror.com/style-mod/-/style-mod-4.1.3.tgz", {}, "sha512-i/n8VsZydrugj3Iuzll8+x/00GH2vnYsk1eomD8QiRrSAeW6ItbCQDtfXCeJHd0iwiNagqjQkvpvREEPtW3IoQ=="], "style-to-js": ["style-to-js@1.1.21", "https://registry.npmmirror.com/style-to-js/-/style-to-js-1.1.21.tgz", { "dependencies": { "style-to-object": "1.0.14" } }, "sha512-RjQetxJrrUJLQPHbLku6U/ocGtzyjbJMP9lCNK7Ag0CNh690nSH8woqWH9u16nMjYBAok+i7JO1NP2pOy8IsPQ=="], @@ -2382,6 +2492,8 @@ "stylis": ["stylis@4.4.0", "https://registry.npmmirror.com/stylis/-/stylis-4.4.0.tgz", {}, "sha512-5Z9ZpRzfuH6l/UAvCPAPUo3665Nk2wLaZU3x+TLHKVzIz33+sbJqbtrYoC3KD4/uVOr2Zp+L0LySezP9OHV9yA=="], + "supports-color": ["supports-color@7.2.0", "", { "dependencies": { "has-flag": "^4.0.0" } }, "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw=="], + "supports-preserve-symlinks-flag": ["supports-preserve-symlinks-flag@1.0.0", "https://registry.npmmirror.com/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", {}, "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w=="], "suspend-react": ["suspend-react@0.1.3", "https://registry.npmmirror.com/suspend-react/-/suspend-react-0.1.3.tgz", { "peerDependencies": { "react": ">=17.0" } }, "sha512-aqldKgX9aZqpoDp3e8/BZ8Dm7x1pJl+qI3ZKxDN0i/IQTWUwBx/ManmlVJ3wowqbno6c2bmiIfs+Um6LbsjJyQ=="], @@ -2442,6 +2554,8 @@ "tw-animate-css": ["tw-animate-css@1.4.0", "https://registry.npmmirror.com/tw-animate-css/-/tw-animate-css-1.4.0.tgz", {}, "sha512-7bziOlRqH0hJx80h/3mbicLW7o8qLsH5+RaLR2t+OHM3D0JlWGODQKQ4cxbK7WlvmUxpcj6Kgu6EKqjrGFe3QQ=="], + "type-check": ["type-check@0.4.0", "", { "dependencies": { "prelude-ls": "^1.2.1" } }, "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew=="], + "type-is": ["type-is@2.1.0", "https://registry.npmmirror.com/type-is/-/type-is-2.1.0.tgz", { "dependencies": { "content-type": "^2.0.0", "media-typer": "^1.1.0", "mime-types": "^3.0.0" } }, "sha512-faYHw0anBbc/kWF3zFTEnxSFOAGUX9GFbOBthvDdLsIlEoWOFOtS0zgCiQYwIskL9iGXZL3kAXD8OoZ4GmMATA=="], "typescript": ["typescript@7.0.2", "https://registry.npmmirror.com/typescript/-/typescript-7.0.2.tgz", { "optionalDependencies": { "@typescript/typescript-aix-ppc64": "7.0.2", "@typescript/typescript-darwin-arm64": "7.0.2", "@typescript/typescript-darwin-x64": "7.0.2", "@typescript/typescript-freebsd-arm64": "7.0.2", "@typescript/typescript-freebsd-x64": "7.0.2", "@typescript/typescript-linux-arm": "7.0.2", "@typescript/typescript-linux-arm64": "7.0.2", "@typescript/typescript-linux-loong64": "7.0.2", "@typescript/typescript-linux-mips64el": "7.0.2", "@typescript/typescript-linux-ppc64": "7.0.2", "@typescript/typescript-linux-riscv64": "7.0.2", "@typescript/typescript-linux-s390x": "7.0.2", "@typescript/typescript-linux-x64": "7.0.2", "@typescript/typescript-netbsd-arm64": "7.0.2", "@typescript/typescript-netbsd-x64": "7.0.2", "@typescript/typescript-openbsd-arm64": "7.0.2", "@typescript/typescript-openbsd-x64": "7.0.2", "@typescript/typescript-sunos-x64": "7.0.2", "@typescript/typescript-win32-arm64": "7.0.2", "@typescript/typescript-win32-x64": "7.0.2" }, "bin": { "tsc": "bin/tsc" } }, "sha512-8FYau96o3NKOhbjKi/qNvG/W5jhzxkbdm5sj9AbZ/5T5sWqn3hJgLfGx27sRKZWTvyzCP8dLRBTf5tBTSRVUNA=="], @@ -2478,6 +2592,8 @@ "update-browserslist-db": ["update-browserslist-db@1.2.3", "https://registry.npmmirror.com/update-browserslist-db/-/update-browserslist-db-1.2.3.tgz", { "dependencies": { "escalade": "^3.2.0", "picocolors": "^1.1.1" }, "peerDependencies": { "browserslist": ">= 4.21.0" }, "bin": { "update-browserslist-db": "cli.js" } }, "sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w=="], + "uri-js": ["uri-js@4.4.1", "", { "dependencies": { "punycode": "^2.1.0" } }, "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg=="], + "url-join": ["url-join@5.0.0", "https://registry.npmmirror.com/url-join/-/url-join-5.0.0.tgz", {}, "sha512-n2huDr9h9yzd6exQVnH/jU5mr+Pfx08LRXXZhkLLetAMESRj+anQsTAh940iMrIetKAmry9coFuZQ2jY8/p3WA=="], "use-callback-ref": ["use-callback-ref@1.3.3", "https://registry.npmmirror.com/use-callback-ref/-/use-callback-ref-1.3.3.tgz", { "dependencies": { "tslib": "^2.0.0" }, "peerDependencies": { "@types/react": "*", "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-jQL3lRnocaFtu3V00JToYz/4QkNWswxijDaCVNZRiRTO3HQDLsdu1ZtmIUvV4yPp+rvWm5j0y0TG/S61cuijTg=="], @@ -2534,7 +2650,9 @@ "webworkify": ["webworkify@1.5.0", "https://registry.npmmirror.com/webworkify/-/webworkify-1.5.0.tgz", {}, "sha512-AMcUeyXAhbACL8S2hqqdqOLqvJ8ylmIbNwUIqQujRSouf4+eUFaXbG6F1Rbu+srlJMmxQWsiU7mOJi0nMBfM1g=="], - "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@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=="], + + "word-wrap": ["word-wrap@1.2.5", "", {}, "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA=="], "wrappy": ["wrappy@1.0.2", "https://registry.npmmirror.com/wrappy/-/wrappy-1.0.2.tgz", {}, "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ=="], @@ -2546,6 +2664,8 @@ "yaml": ["yaml@1.10.3", "https://registry.npmmirror.com/yaml/-/yaml-1.10.3.tgz", {}, "sha512-vIYeF1u3CjlhAFekPPAk2h/Kv4T3mAkMox5OymRiJQB0spDP10LHvt+K7G9Ny6NuuMAb25/6n1qyUjAcGNf/AA=="], + "yocto-queue": ["yocto-queue@0.1.0", "", {}, "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q=="], + "yocto-spinner": ["yocto-spinner@1.2.0", "https://registry.npmmirror.com/yocto-spinner/-/yocto-spinner-1.2.0.tgz", { "dependencies": { "yoctocolors": "^2.1.1" } }, "sha512-Yw0hUB6UA3o4YUgKy3oSe9a4cxoaZ9sBfYDw+JSxo6Id0KoJGoxzPA24qqUXYKBWABs/zDSGTz9kww7t3F0XGw=="], "yoctocolors": ["yoctocolors@2.1.2", "https://registry.npmmirror.com/yoctocolors/-/yoctocolors-2.1.2.tgz", {}, "sha512-CzhO+pFNo8ajLM2d2IW/R93ipy99LWjtwblvC1RsoSUMZgyLbYFr221TnSNT7GjGdYui6P459mw9JH/g/zW2ug=="], @@ -2600,6 +2720,8 @@ "@dotenvx/dotenvx/picomatch": ["picomatch@4.0.4", "https://registry.npmmirror.com/picomatch/-/picomatch-4.0.4.tgz", {}, "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A=="], + "@dotenvx/dotenvx/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=="], + "@emotion/babel-plugin/@emotion/hash": ["@emotion/hash@0.9.2", "https://registry.npmmirror.com/@emotion/hash/-/hash-0.9.2.tgz", {}, "sha512-MyqliTZGuOm3+5ZRSaaBGP3USLw6+EGykkwZns2EPC5g8jJ4z9OrdZY9apkl3+UP9+sdz76YYkwCKP5gh8iY3g=="], "@emotion/babel-plugin/convert-source-map": ["convert-source-map@1.9.0", "https://registry.npmmirror.com/convert-source-map/-/convert-source-map-1.9.0.tgz", {}, "sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A=="], @@ -2614,6 +2736,8 @@ "@emotion/serialize/@emotion/unitless": ["@emotion/unitless@0.10.0", "https://registry.npmmirror.com/@emotion/unitless/-/unitless-0.10.0.tgz", {}, "sha512-dFoMUuQA20zvtVTuxZww6OHoJYgrzfKM1t52mVySDJnMSEa08ruEvdYQbhvyu6soU+NeLVd3yKfTfT0NeV6qGg=="], + "@eslint-community/eslint-utils/eslint-visitor-keys": ["eslint-visitor-keys@3.4.3", "", {}, "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag=="], + "@excalidraw/excalidraw/clsx": ["clsx@1.1.1", "https://registry.npmmirror.com/clsx/-/clsx-1.1.1.tgz", {}, "sha512-6/bPho624p3S2pMyvP5kKBPXnI3ufHLObBFCfgx+LkeR5lg2XYy2hqZqUf45ypD8COn2bhgGJSUE+l5dhNBieA=="], "@excalidraw/excalidraw/nanoid": ["nanoid@3.3.3", "https://registry.npmmirror.com/nanoid/-/nanoid-3.3.3.tgz", { "bin": { "nanoid": "bin/nanoid.cjs" } }, "sha512-p1sjXuopFs0xg+fPASzQ28agW1oHD7xDsd9Xkf3T15H3c/cifrFHVwrh74PdoklAPi+i7MdRsE47vm2r6JoB+w=="], @@ -2640,6 +2764,10 @@ "@mdx-js/mdx/source-map": ["source-map@0.7.6", "https://registry.npmmirror.com/source-map/-/source-map-0.7.6.tgz", {}, "sha512-i5uvt8C3ikiWeNZSVZNWcfZPItFQOsYTUAOkcUPGd8DqDy1uOUikjt5dG+uRlwyvR108Fb9DOd4GvXfT0N2/uQ=="], + "@modelcontextprotocol/sdk/ajv": ["ajv@8.20.0", "https://registry.npmmirror.com/ajv/-/ajv-8.20.0.tgz", { "dependencies": { "fast-deep-equal": "^3.1.3", "fast-uri": "^3.0.1", "json-schema-traverse": "^1.0.0", "require-from-string": "^2.0.2" } }, "sha512-Thbli+OlOj+iMPYFBVBfJ3OmCAnaSyNn4M1vz9T6Gka5Jt9ba/HIR56joy65tY6kx/FCF5VXNB819Y7/GUrBGA=="], + + "@nicolo-ribaudo/eslint-scope-5-internals/eslint-scope": ["eslint-scope@5.1.1", "", { "dependencies": { "esrecurse": "^4.3.0", "estraverse": "^4.1.1" } }, "sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw=="], + "@pierre/diffs/diff": ["diff@9.0.0", "https://registry.npmmirror.com/diff/-/diff-9.0.0.tgz", {}, "sha512-svtcdpS8CgJyqAjEQIXdb3OjhFVVYjzGAPO8WGCmRbrml64SPw/jJD4GoE98aR7r25A0XcgrK3F02yw9R/vhQw=="], "@pierre/diffs/shiki": ["shiki@4.4.3", "https://registry.npmmirror.com/shiki/-/shiki-4.4.3.tgz", { "dependencies": { "@shikijs/core": "4.4.3", "@shikijs/engine-javascript": "4.4.3", "@shikijs/engine-oniguruma": "4.4.3", "@shikijs/langs": "4.4.3", "@shikijs/themes": "4.4.3", "@shikijs/types": "4.4.3", "@shikijs/vscode-textmate": "^10.0.2", "@types/hast": "^3.0.5" } }, "sha512-Mb/GvXPHBAXdgGIcnfU5L3ldpn1XcxrGkPHwqgRx17/I2XRfqlFKk2vGkHWINn1kdXvzJZeuO3is6I9KLPFm0g=="], @@ -3196,10 +3324,14 @@ "@tldraw/editor/idb": ["idb@7.1.1", "https://registry.npmmirror.com/idb/-/idb-7.1.1.tgz", {}, "sha512-gchesWBzyvGHRO9W8tzUWFDycow5gwjvFKfyV9FF32Y7F50yZMp7mP+T2mJIWFx49zicqyC4uefHM17o6xKIVQ=="], + "@ts-morph/common/minimatch": ["minimatch@10.2.5", "https://registry.npmmirror.com/minimatch/-/minimatch-10.2.5.tgz", { "dependencies": { "brace-expansion": "^5.0.5" } }, "sha512-MULkVLfKGYDFYejP07QOurDLLQpcjk7Fw+7jXS2R2czRQzR56yHRveU5NDJEOviH+hETZKSkIk5c+T23GjFUMg=="], + "@types/mdast/@types/unist": ["@types/unist@3.0.3", "https://registry.npmmirror.com/@types/unist/-/unist-3.0.3.tgz", {}, "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=="], + "ajv-formats/ajv": ["ajv@8.20.0", "https://registry.npmmirror.com/ajv/-/ajv-8.20.0.tgz", { "dependencies": { "fast-deep-equal": "^3.1.3", "fast-uri": "^3.0.1", "json-schema-traverse": "^1.0.0", "require-from-string": "^2.0.2" } }, "sha512-Thbli+OlOj+iMPYFBVBfJ3OmCAnaSyNn4M1vz9T6Gka5Jt9ba/HIR56joy65tY6kx/FCF5VXNB819Y7/GUrBGA=="], + "antd/@rc-component/util": ["@rc-component/util@1.12.0", "https://registry.npmmirror.com/@rc-component/util/-/util-1.12.0.tgz", { "dependencies": { "is-mobile": "^5.0.0", "react-is": "^19.2.7" }, "peerDependencies": { "react": ">=18.0.0", "react-dom": ">=18.0.0" } }, "sha512-AEjPL8JVdohIITaiXokyjL9WQ6tKWWjAYK9QU16tGNE9JaQABBQy+hA4H2Lup5MgXy9yY3iLrbZJheuU13hTdQ=="], "anymatch/picomatch": ["picomatch@2.3.2", "https://registry.npmmirror.com/picomatch/-/picomatch-2.3.2.tgz", {}, "sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA=="], @@ -3210,7 +3342,7 @@ "chevrotain/lodash-es": ["lodash-es@4.17.21", "https://registry.npmmirror.com/lodash-es/-/lodash-es-4.17.21.tgz", {}, "sha512-mKnC+QJ9pWVzv+C4/U3rRsHapFfHvQFoFB92e52xeyGMcX6/OlIl78je1u8vePzYZSkkogMPJ2yjxxsb89cxyw=="], - "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=="], + "chokidar/glob-parent": ["glob-parent@5.1.2", "https://registry.npmmirror.com/glob-parent/-/glob-parent-5.1.2.tgz", { "dependencies": { "is-glob": "^4.0.1" } }, "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow=="], "cytoscape-fcose/cose-base": ["cose-base@2.2.0", "https://registry.npmmirror.com/cose-base/-/cose-base-2.2.0.tgz", { "dependencies": { "layout-base": "^2.0.0" } }, "sha512-AzlgcsCbUMymkADOJtQm3wO9S3ltPfYOFD5033keQn9NJzIbtnZj+UdBJe7DYml/8TdbtHJW3j58SOnKhWY/5g=="], @@ -3224,6 +3356,10 @@ "es-set-tostringtag/hasown": ["hasown@2.0.3", "https://registry.npmmirror.com/hasown/-/hasown-2.0.3.tgz", { "dependencies": { "function-bind": "^1.1.2" } }, "sha512-ej4AhfhfL2Q2zpMmLo7U1Uv9+PyhIZpgQLGT1F9miIGmiCJIoCgSmczFdrc97mWT4kVY72KA+WnnhJ5pghSvSg=="], + "eslint/eslint-visitor-keys": ["eslint-visitor-keys@4.2.1", "", {}, "sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ=="], + + "espree/eslint-visitor-keys": ["eslint-visitor-keys@4.2.1", "", {}, "sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ=="], + "estree-util-to-js/source-map": ["source-map@0.7.6", "https://registry.npmmirror.com/source-map/-/source-map-0.7.6.tgz", {}, "sha512-i5uvt8C3ikiWeNZSVZNWcfZPItFQOsYTUAOkcUPGd8DqDy1uOUikjt5dG+uRlwyvR108Fb9DOd4GvXfT0N2/uQ=="], "estree-util-visit/@types/unist": ["@types/unist@3.0.3", "https://registry.npmmirror.com/@types/unist/-/unist-3.0.3.tgz", {}, "sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q=="], @@ -3232,6 +3368,8 @@ "extend-shallow/is-extendable": ["is-extendable@0.1.1", "https://registry.npmmirror.com/is-extendable/-/is-extendable-0.1.1.tgz", {}, "sha512-5BMULNob1vgFX6EjQw5izWDxrecWK9AM72rugNr0TFldMOi0fj6Jk+zeKIt0xGj4cEfQIJth4w3OKWOJ4f+AFw=="], + "fast-glob/glob-parent": ["glob-parent@5.1.2", "https://registry.npmmirror.com/glob-parent/-/glob-parent-5.1.2.tgz", { "dependencies": { "is-glob": "^4.0.1" } }, "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow=="], + "get-intrinsic/hasown": ["hasown@2.0.3", "https://registry.npmmirror.com/hasown/-/hasown-2.0.3.tgz", { "dependencies": { "function-bind": "^1.1.2" } }, "sha512-ej4AhfhfL2Q2zpMmLo7U1Uv9+PyhIZpgQLGT1F9miIGmiCJIoCgSmczFdrc97mWT4kVY72KA+WnnhJ5pghSvSg=="], "hast-util-from-parse5/@types/unist": ["@types/unist@3.0.3", "https://registry.npmmirror.com/@types/unist/-/unist-3.0.3.tgz", {}, "sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q=="], @@ -3262,6 +3400,8 @@ "leva/zustand": ["zustand@3.7.2", "https://registry.npmmirror.com/zustand/-/zustand-3.7.2.tgz", { "peerDependencies": { "react": ">=16.8" }, "optionalPeers": ["react"] }, "sha512-PIJDIZKtokhof+9+60cpockVOq05sJzHCriyvaLBmEJixseQ1a5Kdov6fWZfWOu5SK9c+FhH1jU0tntLxRJYMA=="], + "log-symbols/chalk": ["chalk@5.6.2", "https://registry.npmmirror.com/chalk/-/chalk-5.6.2.tgz", {}, "sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA=="], + "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-find-and-replace/escape-string-regexp": ["escape-string-regexp@5.0.0", "https://registry.npmmirror.com/escape-string-regexp/-/escape-string-regexp-5.0.0.tgz", {}, "sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw=="], @@ -3284,6 +3424,8 @@ "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=="], + "ora/chalk": ["chalk@5.6.2", "https://registry.npmmirror.com/chalk/-/chalk-5.6.2.tgz", {}, "sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA=="], + "parse-json/@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=="], "parse5/entities": ["entities@6.0.1", "https://registry.npmmirror.com/entities/-/entities-6.0.1.tgz", {}, "sha512-aN97NXWF6AWBTahfVOIrB/NShkzi5H7F9r1s9mD3cDj4Ko5f2qhhVoYMibXF7GlLveb/D2ioWay8lxI97Ven3g=="], @@ -3474,6 +3616,8 @@ "@dotenvx/dotenvx/execa/strip-final-newline": ["strip-final-newline@2.0.0", "https://registry.npmmirror.com/strip-final-newline/-/strip-final-newline-2.0.0.tgz", {}, "sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA=="], + "@dotenvx/dotenvx/which/isexe": ["isexe@3.1.5", "https://registry.npmmirror.com/isexe/-/isexe-3.1.5.tgz", {}, "sha512-6B3tLtFqtQS4ekarvLVMZ+X+VlvQekbe4taUkf/rhVO3d/h0M2rfARm/pXLcPEsjjMsFgrFgSrhQIxcSVrBz8w=="], + "@floating-ui/react/@floating-ui/react-dom/@floating-ui/dom": ["@floating-ui/dom@1.8.0", "https://registry.npmmirror.com/@floating-ui/dom/-/dom-1.8.0.tgz", { "dependencies": { "@floating-ui/core": "^1.8.0", "@floating-ui/utils": "^0.2.12" } }, "sha512-yXSrzeHZBTZadLOlfyhCkJHNeLJnHRnRInwdZ40L7ZiaAtrBwoYlsDrX3v5zB1Utk7CLfzcOVnVVWoXEky7Ceg=="], "@lobehub/ui/@radix-ui/react-slot/@radix-ui/primitive": ["@radix-ui/primitive@1.1.7", "https://registry.npmmirror.com/@radix-ui/primitive/-/primitive-1.1.7.tgz", {}, "sha512-rqWnm76nYT8HoNNqEjpgJ7Pw/DrBj5iBTrmEPo6HTX5+VJyBNOqTdv4g89G63HuR5g0AaENoAcH7Is5fF2kZ8Q=="], @@ -3492,6 +3636,10 @@ "@lobehub/ui/shiki/@shikijs/types": ["@shikijs/types@4.4.3", "https://registry.npmmirror.com/@shikijs/types/-/types-4.4.3.tgz", { "dependencies": { "@shikijs/vscode-textmate": "^10.0.2", "@types/hast": "^3.0.5" } }, "sha512-UEJxmRR++MAGR6hugn0vgVS2W/6lWAts84FFSrnlH9sP0LNol7E5+NQ792pH8liWUhyMyjhTgSUH3k7iD7tc5g=="], + "@modelcontextprotocol/sdk/ajv/json-schema-traverse": ["json-schema-traverse@1.0.0", "https://registry.npmmirror.com/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", {}, "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug=="], + + "@nicolo-ribaudo/eslint-scope-5-internals/eslint-scope/estraverse": ["estraverse@4.3.0", "", {}, "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw=="], + "@pierre/diffs/shiki/@shikijs/engine-javascript": ["@shikijs/engine-javascript@4.4.3", "https://registry.npmmirror.com/@shikijs/engine-javascript/-/engine-javascript-4.4.3.tgz", { "dependencies": { "@shikijs/types": "4.4.3", "@shikijs/vscode-textmate": "^10.0.2", "oniguruma-to-es": "^4.3.6" } }, "sha512-FbOjFJp9VLdo1Wevs10BBtVxiTWwNLqZh5Gkhjgda/ioL15YOgeSl9n+6XMa3qRlPQzfhFNe641SrynFHYG0nQ=="], "@pierre/diffs/shiki/@shikijs/engine-oniguruma": ["@shikijs/engine-oniguruma@4.4.3", "https://registry.npmmirror.com/@shikijs/engine-oniguruma/-/engine-oniguruma-4.4.3.tgz", { "dependencies": { "@shikijs/types": "4.4.3", "@shikijs/vscode-textmate": "^10.0.2" } }, "sha512-EcOQkxdxGQrc1Row/cC2c96/v1dbZqGnEVu1qTuT/MJmp6+cXCvQussowVmCv5Tqr3KuY3c7IbM6HTW3LJ1k9w=="], @@ -3652,11 +3800,13 @@ "@tailwindcss/oxide-wasm32-wasi/@napi-rs/wasm-runtime/@tybys/wasm-util": ["@tybys/wasm-util@0.10.1", "https://registry.npmmirror.com/@tybys/wasm-util/-/wasm-util-0.10.1.tgz", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-9tTaPJLSiejZKx+Bmog4uSubteqTvFrVrURwkmHixBo0G4seD0zUxp98E1DzUBJxLQ3NPwXrGKDiVjwx/DpPsg=="], + "@ts-morph/common/minimatch/brace-expansion": ["brace-expansion@5.0.6", "https://registry.npmmirror.com/brace-expansion/-/brace-expansion-5.0.6.tgz", { "dependencies": { "balanced-match": "^4.0.2" } }, "sha512-kLpxurY4Z4r9sgMsyG0Z9uzsBlgiU/EFKhj/h91/8yHu0edo7XuixOIH3VcJ8kkxs6/jPzoI6U9Vj3WqbMQ94g=="], + "accepts/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=="], - "antd/@rc-component/util/react-is": ["react-is@19.2.7", "https://registry.npmmirror.com/react-is/-/react-is-19.2.7.tgz", {}, "sha512-kZFnouyVv7eP/Phmrlo9FK+zcAdriZJvzxXHF1Sl1P377WSGe2G/JxVolhTrB/jeV47lKImhNUsijjHAAbcl/A=="], + "ajv-formats/ajv/json-schema-traverse": ["json-schema-traverse@1.0.0", "https://registry.npmmirror.com/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", {}, "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug=="], - "cross-spawn/which/isexe": ["isexe@2.0.0", "https://registry.npmmirror.com/isexe/-/isexe-2.0.0.tgz", {}, "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw=="], + "antd/@rc-component/util/react-is": ["react-is@19.2.7", "https://registry.npmmirror.com/react-is/-/react-is-19.2.7.tgz", {}, "sha512-kZFnouyVv7eP/Phmrlo9FK+zcAdriZJvzxXHF1Sl1P377WSGe2G/JxVolhTrB/jeV47lKImhNUsijjHAAbcl/A=="], "cytoscape-fcose/cose-base/layout-base": ["layout-base@2.0.1", "https://registry.npmmirror.com/layout-base/-/layout-base-2.0.1.tgz", {}, "sha512-dp3s92+uNI1hWIpPGH3jK2kxE2lMjdXdr+DH8ynZHpd6PUlH6x6cbuXnoMmiNumznqaNO31xu9e79F0uuZ0JFg=="], @@ -3756,6 +3906,8 @@ "@radix-ui/react-visually-hidden/@radix-ui/react-primitive/@radix-ui/react-slot/@radix-ui/react-compose-refs": ["@radix-ui/react-compose-refs@1.1.4", "https://registry.npmmirror.com/@radix-ui/react-compose-refs/-/react-compose-refs-1.1.4.tgz", { "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-pWJo6lQAfR6uy1n7ii7PaCc9dLPwTXDYbQpORZU5B548Aqvl2pP1SM1vJGKyxIFqZMHRopRO4CQYX2iXAIB5jA=="], + "@ts-morph/common/minimatch/brace-expansion/balanced-match": ["balanced-match@4.0.4", "https://registry.npmmirror.com/balanced-match/-/balanced-match-4.0.4.tgz", {}, "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA=="], + "leva/@radix-ui/react-portal/@radix-ui/react-primitive/@radix-ui/react-slot": ["@radix-ui/react-slot@1.3.2", "https://registry.npmmirror.com/@radix-ui/react-slot/-/react-slot-1.3.2.tgz", { "dependencies": { "@radix-ui/primitive": "1.1.7", "@radix-ui/react-compose-refs": "1.1.4" }, "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-nLBh0hpzgQA16ioKAWTSQvGjRj7QcI3pTpngtYZ12b4RP9N8eGSj7ziceHyHe04zrd0bAcLyMijO0ws0DLmDWw=="], "@babel/plugin-transform-modules-commonjs/@babel/helper-module-transforms/@babel/helper-module-imports/@babel/types/@babel/helper-string-parser": ["@babel/helper-string-parser@7.27.1", "https://registry.npmmirror.com/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz", {}, "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA=="], diff --git a/web/eslint.config.js b/web/eslint.config.js new file mode 100644 index 000000000..83852f988 --- /dev/null +++ b/web/eslint.config.js @@ -0,0 +1,65 @@ +import babelParser from "@babel/eslint-parser"; + +// 这不是风格检查。只锁已经退场的 AntD 用法,防止 Empty / 静态 Modal.confirm 再长回来。 +// 用 Babel 解析 TS/TSX:typescript-eslint 8 在 TypeScript 7 上会直接拒绝启动。 +const emptyMessage = "列表和面板空态用 @/components/ui/product/empty-state 的 EmptyState,不要再引入 antd Empty。"; +const confirmMessage = "命令式确认必须走 App.useApp().modal.confirm,静态 Modal.confirm 拿不到主题和 App 上下文。"; + +// 源码里还有 react-hooks/exhaustive-deps 的 disable 注释。本闸门不启用 hooks 规则, +// 只挂一个空规则,避免 ESLint 把未知规则名当成错误。 +const commentCompatPlugin = { + rules: { + "exhaustive-deps": { + meta: { type: "problem" }, + create() { + return {}; + }, + }, + }, +}; + +export default [ + { + ignores: ["dist/**", "node_modules/**", "coverage/**"], + }, + { + files: ["src/**/*.{ts,tsx}", "test/**/*.{ts,tsx,mjs}"], + plugins: { + "react-hooks": commentCompatPlugin, + }, + linterOptions: { + reportUnusedDisableDirectives: "off", + }, + languageOptions: { + parser: babelParser, + parserOptions: { + requireConfigFile: false, + sourceType: "module", + babelOptions: { + babelrc: false, + configFile: false, + presets: [["@babel/preset-typescript", { allExtensions: true, isTSX: true, allowDeclareFields: true }]], + }, + }, + }, + rules: { + "no-restricted-imports": [ + "error", + { + paths: [ + { name: "antd", importNames: ["Empty"], message: emptyMessage }, + { name: "antd/es/empty", message: emptyMessage }, + { name: "antd/lib/empty", message: emptyMessage }, + ], + }, + ], + "no-restricted-syntax": [ + "error", + { + selector: "CallExpression[callee.object.name='Modal'][callee.property.name='confirm']", + message: confirmMessage, + }, + ], + }, + }, +]; diff --git a/web/package.json b/web/package.json index 071481a7e..6d954bd54 100644 --- a/web/package.json +++ b/web/package.json @@ -10,11 +10,12 @@ "pretest": "bun test test/tiptap-dependency-versions.test.ts test/admin-ui-regressions.test.ts test/announcement-prompt-regressions.test.ts test/password-reset.test.ts test/oss-settings.test.ts test/channel-model-editor-form.test.ts test/channel-model-price-tier-form.test.ts test/admin-logical-models.test.ts && bun test test/canvas-grid-viewport.test.tsx", "typecheck": "tsc --noEmit", "test:canvas": "bun test test/canvas-media-performance.test.ts test/canvas-node-title-interaction.test.ts test/canvas-spatial-index.test.ts", - "test": "bun test test/api-request.test.ts test/video-api-contracts.test.ts test/image-api-contracts.test.ts test/channel-model-catalog.test.ts test/model-pricing.test.ts test/model-protocols.test.ts test/model-capabilities.test.ts test/agent-prompt-cache.test.ts test/canvas-agent-context.test.ts test/canvas-batch-connection.test.ts test/canvas-character-reference.test.ts test/canvas-document.test.ts test/canvas-drawing-engine.test.ts test/canvas-emotion-edit-plan.test.ts test/canvas-extension-nodes.test.ts test/canvas-folder.test.ts test/canvas-generation-layout.test.ts test/canvas-generation-submission.test.ts test/canvas-image-batch-retry.test.ts test/canvas-keyboard.test.ts test/canvas-node-generation-mentions.test.ts test/canvas-node-search.test.ts test/canvas-leafer-viewport.test.ts test/canvas-media-download.test.ts test/canvas-local-runtime.test.ts test/canvas-model-policy.test.ts test/canvas-resource-mention-editor.test.ts test/canvas-resource-references.test.ts test/canvas-storyboard-assets.test.ts test/canvas-storyboard-references.test.ts test/canvas-node-config-reset.test.ts test/canvas-node-registry.test.ts test/canvas-video-frame.test.ts test/canvas-video-regeneration.test.ts test/canvas-video-timeline-segments.test.ts test/create-canvas-handoff.test.ts test/create-library-button.test.ts test/creation-references.test.ts test/director-transform-semantics.test.ts test/director-bone-precedence.test.ts test/director-clay-materials.test.ts test/director-gesture-transaction.test.ts test/director-transaction-lifecycle.test.ts test/director-session-authority.test.ts test/director-placement.test.ts test/director-resources.test.ts test/director-recovery.test.ts test/director-node-preview.test.ts test/generation-asset-repository.node.test.ts test/generation-storage-consistency.test.ts test/generation-task-durability.node.test.ts test/generation-task-local.test.ts test/generation-task-materializer.test.ts test/task-media-fallback.test.ts test/user-scoped-generation-persistence.test.ts test/local-channel-permission.test.ts test/local-channel-runtime-projection.node.test.mjs test/local-channel-task-center.node.test.mjs test/local-channel-ui.node.test.mjs test/local-cli-settings.test.ts test/local-dreamina-cli.test.ts test/local-dreamina-generation.test.ts test/local-dreamina-model-catalog.test.ts test/local-runtime-bootstrap.test.ts test/local-runtime-connection.test.ts test/local-runtime.test.ts test/client-id.test.ts test/grok-image-prompt-limit.test.ts test/image-resolution-tiers.test.ts test/gemini-image.test.ts test/libtv-import.test.ts test/timeline-to-ffmpeg.test.ts test/canvas-video-segment-args.test.ts test/tapnow-import.test.ts test/prompt-optimizer-drawer.test.ts test/director-save-coordinator.test.ts test/director-save-wiring.test.ts test/director-diagnostics.test.ts test/director-repro-runtime.test.ts test/director-diagnostics-recorder.test.ts test/director-keyframe-editing.test.ts test/director-shortcuts.test.ts test/director-keyframe-delete-wiring.test.ts test/director-templates.test.ts test/director-modes.test.ts test/director-template-mode-wiring.test.ts test/director-view-modes.test.ts && bun test test/local-dreamina-cross-runtime.test.ts", + "test": "bun test", "test:director:e2e": "bun scripts/director-p0-chrome-e2e.mjs", "start": "vite preview --host 0.0.0.0 --port 3000", "format": "prettier --write .", - "format:check": "prettier --check ." + "format:check": "prettier --check .", + "lint": "eslint src test" }, "dependencies": { "@ant-design/icons": "^6.3.2", @@ -80,6 +81,9 @@ "zustand": "^5.0.14" }, "devDependencies": { + "@babel/core": "7.29.7", + "@babel/eslint-parser": "7.28.6", + "@babel/preset-typescript": "7.28.5", "@tailwindcss/postcss": "^4.3.3", "@types/file-saver": "^2.0.7", "@types/node": "^20.19.43", @@ -87,6 +91,7 @@ "@types/react-dom": "19.2.3", "@types/three": "0.185.4", "@vitejs/plugin-react": "^6.0.3", + "eslint": "9.39.5", "prettier": "^3.9.5", "typescript": "^7.0.2", "vite": "^8.1.5" @@ -133,42 +138,5 @@ "@tiptap/react": "3.28.0", "@tiptap/starter-kit": "3.28.0" }, - "pnpm": { - "overrides": { - "@tiptap/core": "3.28.0", - "@tiptap/extension-blockquote": "3.28.0", - "@tiptap/extension-bold": "3.28.0", - "@tiptap/extension-bubble-menu": "3.28.0", - "@tiptap/extension-bullet-list": "3.28.0", - "@tiptap/extension-character-count": "3.28.0", - "@tiptap/extension-code": "3.28.0", - "@tiptap/extension-code-block": "3.28.0", - "@tiptap/extension-color": "3.28.0", - "@tiptap/extension-document": "3.28.0", - "@tiptap/extension-dropcursor": "3.28.0", - "@tiptap/extension-floating-menu": "3.28.0", - "@tiptap/extension-gapcursor": "3.28.0", - "@tiptap/extension-hard-break": "3.28.0", - "@tiptap/extension-heading": "3.28.0", - "@tiptap/extension-highlight": "3.28.0", - "@tiptap/extension-horizontal-rule": "3.28.0", - "@tiptap/extension-italic": "3.28.0", - "@tiptap/extension-link": "3.28.0", - "@tiptap/extension-list": "3.28.0", - "@tiptap/extension-list-item": "3.28.0", - "@tiptap/extension-list-keymap": "3.28.0", - "@tiptap/extension-ordered-list": "3.28.0", - "@tiptap/extension-paragraph": "3.28.0", - "@tiptap/extension-placeholder": "3.28.0", - "@tiptap/extension-strike": "3.28.0", - "@tiptap/extension-text": "3.28.0", - "@tiptap/extension-text-align": "3.28.0", - "@tiptap/extension-text-style": "3.28.0", - "@tiptap/extension-underline": "3.28.0", - "@tiptap/extensions": "3.28.0", - "@tiptap/pm": "3.28.0", - "@tiptap/react": "3.28.0", - "@tiptap/starter-kit": "3.28.0" - } - } + "packageManager": "bun@1.3.9" } diff --git a/web/pnpm-lock.yaml b/web/pnpm-lock.yaml deleted file mode 100644 index 36a25da9a..000000000 --- a/web/pnpm-lock.yaml +++ /dev/null @@ -1,14084 +0,0 @@ -lockfileVersion: '9.0' - -settings: - autoInstallPeers: true - excludeLinksFromLockfile: false - -overrides: - '@tiptap/core': 3.28.0 - '@tiptap/extension-blockquote': 3.28.0 - '@tiptap/extension-bold': 3.28.0 - '@tiptap/extension-bubble-menu': 3.28.0 - '@tiptap/extension-bullet-list': 3.28.0 - '@tiptap/extension-character-count': 3.28.0 - '@tiptap/extension-code': 3.28.0 - '@tiptap/extension-code-block': 3.28.0 - '@tiptap/extension-color': 3.28.0 - '@tiptap/extension-document': 3.28.0 - '@tiptap/extension-dropcursor': 3.28.0 - '@tiptap/extension-floating-menu': 3.28.0 - '@tiptap/extension-gapcursor': 3.28.0 - '@tiptap/extension-hard-break': 3.28.0 - '@tiptap/extension-heading': 3.28.0 - '@tiptap/extension-highlight': 3.28.0 - '@tiptap/extension-horizontal-rule': 3.28.0 - '@tiptap/extension-italic': 3.28.0 - '@tiptap/extension-link': 3.28.0 - '@tiptap/extension-list': 3.28.0 - '@tiptap/extension-list-item': 3.28.0 - '@tiptap/extension-list-keymap': 3.28.0 - '@tiptap/extension-ordered-list': 3.28.0 - '@tiptap/extension-paragraph': 3.28.0 - '@tiptap/extension-placeholder': 3.28.0 - '@tiptap/extension-strike': 3.28.0 - '@tiptap/extension-text': 3.28.0 - '@tiptap/extension-text-align': 3.28.0 - '@tiptap/extension-text-style': 3.28.0 - '@tiptap/extension-underline': 3.28.0 - '@tiptap/extensions': 3.28.0 - '@tiptap/pm': 3.28.0 - '@tiptap/react': 3.28.0 - '@tiptap/starter-kit': 3.28.0 - -importers: - - .: - dependencies: - '@ant-design/icons': - specifier: ^6.3.2 - version: 6.3.2(react-dom@19.2.7(react@19.2.7))(react@19.2.7) - '@ant-design/x': - specifier: ^2.9.0 - version: 2.9.0(antd@6.6.0(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(react-dom@19.2.7(react@19.2.7))(react@19.2.7) - '@codemirror/lang-json': - specifier: ^6.0.2 - version: 6.0.2 - '@excalidraw/excalidraw': - specifier: 0.18.1 - version: 0.18.1(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(immer@11.1.16)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) - '@ffmpeg/core': - specifier: 0.12.10 - version: 0.12.10 - '@ffmpeg/ffmpeg': - specifier: ^0.12.15 - version: 0.12.15 - '@ffmpeg/util': - specifier: ^0.12.2 - version: 0.12.2 - '@fontsource-variable/inter': - specifier: ^5.3.0 - version: 5.3.0 - '@fontsource-variable/jetbrains-mono': - specifier: ^5.3.0 - version: 5.3.0 - '@lobehub/icons': - specifier: 5.16.0 - version: 5.16.0(@lobehub/ui@5.32.3)(@types/react@19.2.17)(antd@6.6.0(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(react-dom@19.2.7(react@19.2.7))(react@19.2.7) - '@mediapipe/tasks-vision': - specifier: ^1.0.1 - version: 1.0.1 - '@react-three/drei': - specifier: ^10.7.7 - version: 10.7.8(@react-three/fiber@9.7.0(@types/react@19.2.17)(immer@11.1.16)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)(three@0.185.1))(@types/react@19.2.17)(@types/three@0.185.4)(immer@11.1.16)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)(three@0.185.1) - '@react-three/fiber': - specifier: ^9.6.1 - version: 9.7.0(@types/react@19.2.17)(immer@11.1.16)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)(three@0.185.1) - '@streamdown/code': - specifier: ^1.1.1 - version: 1.1.1(react@19.2.7) - '@tanstack/react-query': - specifier: ^5.101.2 - version: 5.101.4(react@19.2.7) - '@tanstack/react-virtual': - specifier: ^3.14.8 - version: 3.14.9(react-dom@19.2.7(react@19.2.7))(react@19.2.7) - '@tiptap/core': - specifier: 3.28.0 - version: 3.28.0(@tiptap/pm@3.28.0) - '@tiptap/extension-character-count': - specifier: 3.28.0 - version: 3.28.0(@tiptap/extensions@3.28.0(@tiptap/core@3.28.0(@tiptap/pm@3.28.0))(@tiptap/pm@3.28.0)) - '@tiptap/extension-color': - specifier: 3.28.0 - version: 3.28.0(@tiptap/extension-text-style@3.28.0(@tiptap/core@3.28.0(@tiptap/pm@3.28.0))) - '@tiptap/extension-highlight': - specifier: 3.28.0 - version: 3.28.0(@tiptap/core@3.28.0(@tiptap/pm@3.28.0)) - '@tiptap/extension-placeholder': - specifier: 3.28.0 - version: 3.28.0(@tiptap/extensions@3.28.0(@tiptap/core@3.28.0(@tiptap/pm@3.28.0))(@tiptap/pm@3.28.0)) - '@tiptap/extension-text-align': - specifier: 3.28.0 - version: 3.28.0(@tiptap/core@3.28.0(@tiptap/pm@3.28.0)) - '@tiptap/extension-text-style': - specifier: 3.28.0 - version: 3.28.0(@tiptap/core@3.28.0(@tiptap/pm@3.28.0)) - '@tiptap/extensions': - specifier: 3.28.0 - version: 3.28.0(@tiptap/core@3.28.0(@tiptap/pm@3.28.0))(@tiptap/pm@3.28.0) - '@tiptap/pm': - specifier: 3.28.0 - version: 3.28.0 - '@tiptap/react': - specifier: 3.28.0 - version: 3.28.0(@floating-ui/dom@1.8.0)(@tiptap/core@3.28.0(@tiptap/pm@3.28.0))(@tiptap/pm@3.28.0)(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) - '@tiptap/starter-kit': - specifier: 3.28.0 - version: 3.28.0 - '@uiw/react-codemirror': - specifier: ^4.25.11 - version: 4.25.11(@babel/runtime@8.0.0)(@codemirror/autocomplete@6.20.3)(@codemirror/language@6.12.4)(@codemirror/lint@6.9.7)(@codemirror/search@6.7.1)(@codemirror/state@6.7.1)(@codemirror/theme-one-dark@6.1.3)(@codemirror/view@6.43.8)(codemirror@6.0.2)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) - '@vidstack/react': - specifier: 1.15.6 - version: 1.15.6(@types/react@19.2.17)(react@19.2.7) - antd: - specifier: ^6.5.1 - version: 6.6.0(react-dom@19.2.7(react@19.2.7))(react@19.2.7) - axios: - specifier: ^1.18.1 - version: 1.19.0 - class-variance-authority: - specifier: ^0.7.1 - version: 0.7.1 - clsx: - specifier: ^2.1.1 - version: 2.1.1 - copy-to-clipboard: - specifier: ^4.0.2 - version: 4.0.2 - dayjs: - specifier: ^1.11.21 - version: 1.11.21 - fflate: - specifier: ^0.8.3 - version: 0.8.3 - file-saver: - specifier: ^2.0.5 - version: 2.0.5 - idb: - specifier: 8.0.3 - version: 8.0.3 - json-canonicalize: - specifier: 2.0.0 - version: 2.0.0 - leafer-ui: - specifier: ^2.2.7 - version: 2.2.9 - localforage: - specifier: ^1.10.0 - version: 1.10.0 - lucide-react: - specifier: ^1.25.0 - version: 1.31.0(react@19.2.7) - mammoth: - specifier: ^1.12.0 - version: 1.12.1 - motion: - specifier: ^12.42.2 - version: 12.43.0(@emotion/is-prop-valid@1.4.0)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) - nanoid: - specifier: ^6.0.0 - version: 6.0.1 - react: - specifier: 19.2.7 - version: 19.2.7 - react-aria-components: - specifier: ^1.21.1 - version: 1.21.1(react-dom@19.2.7(react@19.2.7))(react@19.2.7) - react-dom: - specifier: 19.2.7 - version: 19.2.7(react@19.2.7) - react-markdown: - specifier: ^10.1.0 - version: 10.1.0(@types/react@19.2.17)(react@19.2.7) - react-router: - specifier: ^8.2.0 - version: 8.3.0(react-dom@19.2.7(react@19.2.7))(react@19.2.7) - recharts: - specifier: ^3.9.2 - version: 3.10.1(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react-is@19.2.8)(react@19.2.7)(redux@5.0.1) - remark-gfm: - specifier: ^4.0.1 - version: 4.0.1 - shadcn: - specifier: ^4.13.1 - version: 4.18.0(babel-plugin-macros@3.1.0)(typescript@7.0.2) - streamdown: - specifier: ^2.5.0 - version: 2.5.0(react-dom@19.2.7(react@19.2.7))(react@19.2.7) - tailwind-merge: - specifier: ^3.6.0 - version: 3.6.0 - tailwindcss: - specifier: ^4.3.3 - version: 4.3.3 - three: - specifier: ^0.185.1 - version: 0.185.1 - three-stdlib: - specifier: ^2.36.1 - version: 2.36.1(three@0.185.1) - tldraw: - specifier: 5.2.5 - version: 5.2.5(@floating-ui/dom@1.8.0)(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) - tw-animate-css: - specifier: ^1.4.0 - version: 1.4.0 - zustand: - specifier: ^5.0.14 - version: 5.0.15(@types/react@19.2.17)(immer@11.1.16)(react@19.2.7)(use-sync-external-store@1.6.0(react@19.2.7)) - devDependencies: - '@tailwindcss/postcss': - specifier: ^4.3.3 - version: 4.3.3 - '@types/file-saver': - specifier: ^2.0.7 - version: 2.0.7 - '@types/node': - specifier: ^20.19.43 - version: 20.19.43 - '@types/react': - specifier: 19.2.17 - version: 19.2.17 - '@types/react-dom': - specifier: 19.2.3 - version: 19.2.3(@types/react@19.2.17) - '@types/three': - specifier: 0.185.4 - version: 0.185.4 - '@vitejs/plugin-react': - specifier: ^6.0.3 - version: 6.0.5(vite@8.2.1(@types/node@20.19.43)(jiti@2.7.0)) - prettier: - specifier: ^3.9.5 - version: 3.9.6 - typescript: - specifier: ^7.0.2 - version: 7.0.2 - vite: - specifier: ^8.1.5 - version: 8.2.1(@types/node@20.19.43)(jiti@2.7.0) - -packages: - - '@alloc/quick-lru@5.2.0': - resolution: {integrity: sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw==} - engines: {node: '>=10'} - - '@ant-design/colors@8.0.1': - resolution: {integrity: sha512-foPVl0+SWIslGUtD/xBr1p9U4AKzPhNYEseXYRRo5QSzGACYZrQbe11AYJbYfAWnWSpGBx6JjBmSeugUsD9vqQ==} - - '@ant-design/cssinjs-utils@2.1.2': - resolution: {integrity: sha512-5fTHQ158jJJ5dC/ECeyIdZUzKxE/mpEMRZxthyG1sw/AKRHKgJBg00Yi6ACVXgycdje7KahRNvNET/uBccwCnA==} - peerDependencies: - react: '>=18' - react-dom: '>=18' - - '@ant-design/cssinjs@2.1.2': - resolution: {integrity: sha512-2Hy8BnCEH31xPeSLbhhB2ctCPXE2ZnASdi+KbSeS79BNbUhL9hAEe20SkUk+BR8aKTmqb6+FKFruk7w8z0VoRQ==} - peerDependencies: - react: '>=16.0.0' - react-dom: '>=16.0.0' - - '@ant-design/fast-color@3.0.1': - resolution: {integrity: sha512-esKJegpW4nckh0o6kV3Tkb7NPIZYbPnnFxmQDUmL08ukXZAvV85TZBr70eGuke/CIArLaP6aw8lt9KILjnWuOw==} - engines: {node: '>=8.x'} - - '@ant-design/icons-svg@4.5.0': - resolution: {integrity: sha512-1BTUFyKPTBZ53MuTP8s0k5SFEXL7o3VHEOwLgzaoWKwnBeqIcqUtVshc4SKzhI6uACfqhJqBwBUE9FsWR3uULA==} - - '@ant-design/icons@6.3.2': - resolution: {integrity: sha512-B6O5a5XJ4wjtNOfZejXYwHW5zvKV5gYkjGf11dHGLEbKn0ABDGndo41+gfIiXyTFhvESj4XTotuud33mUFid0g==} - engines: {node: '>=8'} - peerDependencies: - react: '>=16.0.0' - react-dom: '>=16.0.0' - - '@ant-design/react-slick@2.0.0': - resolution: {integrity: sha512-HMS9sRoEmZey8LsE/Yo6+klhlzU12PisjrVcydW3So7RdklyEd2qehyU6a7Yp+OYN72mgsYs3NFCyP2lCPFVqg==} - 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 - - '@ant-design/x@2.9.0': - resolution: {integrity: sha512-m3N2nprrPG/D0n93B/fBFhjpvSaJAb2IDF6w0rX0ycYXSGxtgrJQvqSI33Jhx6gxUHj495zdIzqA1NMFld9iQA==} - peerDependencies: - antd: ^6.1.1 - react: '>=18.0.0' - react-dom: '>=18.0.0' - - '@antfu/install-pkg@1.1.0': - resolution: {integrity: sha512-MGQsmw10ZyI+EJo45CdSER4zEb+p31LpDAFp2Z3gkSd1yqVZGi0Ebx++YTEMonJy4oChEMLsxZ64j8FH6sSqtQ==} - - '@babel/code-frame@7.29.7': - resolution: {integrity: sha512-Aup7aUOfpbAUg2ROOJN6Iw5f9DMBlzu0mIkm/malLQFN/YQgO48wCj0Kxa3sEHJvPVFg7siR+qRInwXd2qhQKw==} - engines: {node: '>=6.9.0'} - - '@babel/compat-data@7.29.7': - resolution: {integrity: sha512-locTkQyKvwIEgBzVrn8693ebc97F2U8ZHjbXwDXJ5Fn2TCpNwTlKcaKLkdHop5c/icOFE7qt7Q9JC5hnKNa6Gg==} - engines: {node: '>=6.9.0'} - - '@babel/core@7.29.7': - resolution: {integrity: sha512-RgHBCvtjbOK2gXSNBNIkNoEc9qoVEtau3hj8gEqKQuL3HZAibKarWFEI3Lfm6EYKkLalOh8eSrj9b+ch9H/VBA==} - engines: {node: '>=6.9.0'} - - '@babel/generator@7.29.8': - resolution: {integrity: sha512-gZbepsdh3WDtgZKWL+vTPh71LSBrm/Y4/QDZBVCcYfmeTEEuoOYwlSy+G1StfJg+/Zy550u/3TATbm7qDbbMtg==} - engines: {node: '>=6.9.0'} - - '@babel/helper-annotate-as-pure@7.29.7': - resolution: {integrity: sha512-OoK6239jHPuSQOoS0kfTVKn0b/rVTk0seKq4Gd2UMLtmOVLjDC0ki3e+c90Trqv2gMfvJFqkiljrr568+qddiw==} - engines: {node: '>=6.9.0'} - - '@babel/helper-compilation-targets@7.29.7': - resolution: {integrity: sha512-wem6WaBj4NaVYVdNhLPPVacES6ZJ+KBBfSkTMD3YZxbP3rm3Di85tJU5ljaUNhaOynt+Aj0xruhYuzQBt8n71g==} - engines: {node: '>=6.9.0'} - - '@babel/helper-create-class-features-plugin@7.29.7': - resolution: {integrity: sha512-IY3ZD9Tmooqr3TUhc3DUWxiuo8xx1DWLhd5M7hQ+ZWJamqM2BbalrBJb2MisSLoYorOj75U03qULCxQTY9r3hg==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0 - - '@babel/helper-globals@7.29.7': - resolution: {integrity: sha512-3nQVUAtvkKH9zahfWgw96Jc/uFOmjACE1kQz82E2lqWmHBgjzbNlsC22nuQTfahmWeQtTq5nQ/4Nnd2A1wj4zA==} - engines: {node: '>=6.9.0'} - - '@babel/helper-member-expression-to-functions@7.29.7': - resolution: {integrity: sha512-j+7JYmk1JYDtACIGj0QJqqWZjoUpMoEikQGADMaHgCMCSDqd2+P32rfcibUNrGOMWrlzK1WJBdxrB3JJQZwWtg==} - engines: {node: '>=6.9.0'} - - '@babel/helper-module-imports@7.29.7': - resolution: {integrity: sha512-ejHwrQQYcm9xnTivShn2IDOlIzInN34AXskvq9QicvCtEzq1Vzclu/tKF8Jq1Cg8JG2GL6/EmjgsCT7lXepE3g==} - engines: {node: '>=6.9.0'} - - '@babel/helper-module-transforms@7.29.7': - resolution: {integrity: sha512-UPUVSyXbOh627KiCIGQSgwWzGeBKLkaJ9PJEdrngIwMSzxLR4jS4+f1f1jb7VzBbg8nFLaYotvVPFCTqdrmTAg==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0 - - '@babel/helper-optimise-call-expression@7.29.7': - resolution: {integrity: sha512-+kmGVjcT9RGYzoDwdwEqEvGgKe3BYq+O1iGzjFubaNgZHwYHP6lsF2Yghf4kEuv9BV7tYDZ913aBW9am6YKong==} - engines: {node: '>=6.9.0'} - - '@babel/helper-plugin-utils@7.29.7': - resolution: {integrity: sha512-G7sHYigPY17oO5SYWnfD/0MTBwVR781S/JI643e/JhUYgVgWE/61SoW3NH9KWUKyKq5LVh3npif99Wkt6j86Jw==} - engines: {node: '>=6.9.0'} - - '@babel/helper-replace-supers@7.29.7': - resolution: {integrity: sha512-atfGXWSeCiF4DnKZIfmJfQRkSw9b9gNNXR1kqKjbhG4pGYCOnkp8OcTB8E3NXjBu8NpheSnOeNKz8KT7UNFTmQ==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0 - - '@babel/helper-skip-transparent-expression-wrappers@7.29.7': - resolution: {integrity: sha512-brcMGQaVzIeUb+6/bs1Av0f8YuNNjKY2JyvfRCsFuFsdKccEQ5Ges2y74D74NZ1Rz8lKJ9ksJkfqwQFJ/iNEyQ==} - engines: {node: '>=6.9.0'} - - '@babel/helper-string-parser@7.29.7': - resolution: {integrity: sha512-Pb5ijPrZ89GDH8223L4UP8i6QApWxs04RbPQJTeWDV0/keR2E36MeKnyr6LYmUUvqRRI+Iv87SuF1W6ErINzYw==} - engines: {node: '>=6.9.0'} - - '@babel/helper-validator-identifier@7.29.7': - resolution: {integrity: sha512-qehxGkRj55h/ff8EMaJ+cYhyaKlHIxqYDn682wQD7RNp9UujOQsHog2uS0r2vzr4pW+sXf90NeeayjcNaX3fFg==} - engines: {node: '>=6.9.0'} - - '@babel/helper-validator-option@7.29.7': - resolution: {integrity: sha512-N9ZErrD+yW5geCDtBqnOoxmR8+tNKiGuxKlDpuJxfsqpa2dFcexaziGAE/qoHLiDDreVNMupxGmSoNlyvsA3gw==} - engines: {node: '>=6.9.0'} - - '@babel/helpers@7.29.7': - resolution: {integrity: sha512-1k2lAGRMfHTcwuNYcCNUmaUffmQv8KWMfh2iJUUeRlwlwH4FdNG7mfPI10NPfLHJFThE4Tyr4mv7kTNZOiPuBg==} - engines: {node: '>=6.9.0'} - - '@babel/parser@7.29.8': - resolution: {integrity: sha512-E8lTAYNB1KW+FH+VGJuZM1ioAx2E6oVlvQFRrf5P8ZZmsiJXYAD9vTFV7yyEURNzgh1dFqMZuO6tUwcARbqFCA==} - engines: {node: '>=6.0.0'} - hasBin: true - - '@babel/plugin-syntax-jsx@7.29.7': - resolution: {integrity: sha512-TSu8+mHCoEaaCDEZ0I3+6mvTBYR4PCxQwf2z9/r5Tbztv6NaLR3B9thGTTxX2WGuGHJqRiAbKPeGTJ5XWXVg6A==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 - - '@babel/plugin-syntax-typescript@7.29.7': - resolution: {integrity: sha512-ngr+82Sh0xMz25TPCZi+nC2iTzjfCdWS2ONXTp/PtSCHCgaCNBpdMqgvJ2ccdLlClVZ7sisIgB914j/JFe+RZA==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 - - '@babel/plugin-transform-modules-commonjs@7.29.7': - resolution: {integrity: sha512-j0vCldybPC5b5dwCQOJ21uKtHzt7hxLygJTg9eF1ScfaikEDNfzn94XoW5Fi+seBR0nCyL23xaBFFkq7dTM8XQ==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 - - '@babel/plugin-transform-typescript@7.29.7': - resolution: {integrity: sha512-jK52h8LaLc7JarhQV2ofeFMts4H7vnOXnqZNA6fYglBTZewRBE51KWt3BUltW1P+KoPsYkHoJeXePuz4zo2LMw==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 - - '@babel/preset-typescript@7.29.7': - resolution: {integrity: sha512-/Foi8vKY2EVbed/1eZx0gJEEwHAIxogrySI7rULcRIvhZzbvoE/b5qG5Ghc0WKAFKOHA9SD1x7RsFlOYdutIiQ==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 - - '@babel/runtime@7.29.7': - resolution: {integrity: sha512-Nq8OhGWiZIZGV6hLHoyAKLLcJihP/xFeBMGJoUrxTX2psI8dCifzLhZISFb+VWS3wFMRDmCGw5R+dOySCqPLhw==} - engines: {node: '>=6.9.0'} - - '@babel/runtime@8.0.0': - resolution: {integrity: sha512-sL6cvO2IfkSu/iU+zs2S/w01B7A8V7suXSIKEN4hPFFdZoiPGxrj5pAG0lCaqLWiEIrjKzdznIWuaLcxPR53qw==} - - '@babel/template@7.29.7': - resolution: {integrity: sha512-puq+Gf35oI24FeN11LkoUQFqv9uwNeWpxXZi/Ji3rRIoKAzKnxRaZ+Gkj0vKS9ZCiTESfng1N9LyOyXvo+m+Gg==} - engines: {node: '>=6.9.0'} - - '@babel/traverse@7.29.8': - resolution: {integrity: sha512-I5z7H3bf/41ktsNVLtpN0wAa336HkqIHQ5BuPLEhTkt1jVSyZpeNKIzTgEWmlxjdg81R0IgUCcaE+Ok3NvrfZg==} - engines: {node: '>=6.9.0'} - - '@babel/types@7.29.8': - resolution: {integrity: sha512-Vj1jF3cPfxg7OAfoI7QnVKLoILlm2JF9pnVHrX8qx7AHMiYWT+NDAA7jChlNgRS4WTLc/fD1lXLmPixluj+3Gg==} - engines: {node: '>=6.9.0'} - - '@base-ui/react@1.6.0': - resolution: {integrity: sha512-/jzjTWJYXhRFO45Bev9lc3cHbmjzCMpUqbMZ2AgKy/z25mY9B6shGSNcXcjQar9n5doM0KYW1W8fcFv2jZBuMw==} - engines: {node: '>=14.0.0'} - peerDependencies: - '@date-fns/tz': ^1.2.0 - '@types/react': ^17 || ^18 || ^19 - date-fns: ^4.0.0 - react: ^17 || ^18 || ^19 - react-dom: ^17 || ^18 || ^19 - peerDependenciesMeta: - '@date-fns/tz': - optional: true - '@types/react': - optional: true - date-fns: - optional: true - - '@base-ui/utils@0.3.1': - resolution: {integrity: sha512-gFFiltORVmW/N6IILTGxizP3PBpVpysqML1ALY5Vk0mH+7faVkCknOU31goYHN5Aoek2dkjxva1XOD2Ce9WuIg==} - peerDependencies: - '@types/react': ^17 || ^18 || ^19 - react: ^17 || ^18 || ^19 - react-dom: ^17 || ^18 || ^19 - peerDependenciesMeta: - '@types/react': - optional: true - - '@braintree/sanitize-url@6.0.2': - resolution: {integrity: sha512-Tbsj02wXCbqGmzdnXNk0SOF19ChhRU70BsroIi4Pm6Ehp56in6vch94mfbdQ17DozxkL3BAVjbZ4Qc1a0HFRAg==} - - '@braintree/sanitize-url@7.1.2': - resolution: {integrity: sha512-jigsZK+sMF/cuiB7sERuo9V7N9jx+dhmHHnQyDSVdpZwVutaBu7WvNYqMDLSgFgfB30n452TP3vjDAvFC973mA==} - - '@chevrotain/cst-dts-gen@11.0.3': - resolution: {integrity: sha512-BvIKpRLeS/8UbfxXxgC33xOumsacaeCKAjAeLyOn7Pcp95HiRbrpl14S+9vaZLolnbssPIUuiUd8IvgkRyt6NQ==} - - '@chevrotain/gast@11.0.3': - resolution: {integrity: sha512-+qNfcoNk70PyS/uxmj3li5NiECO+2YKZZQMbmjTqRI3Qchu8Hig/Q9vgkHpI3alNjr7M+a2St5pw5w5F6NL5/Q==} - - '@chevrotain/regexp-to-ast@11.0.3': - resolution: {integrity: sha512-1fMHaBZxLFvWI067AVbGJav1eRY7N8DDvYCTwGBiE/ytKBgP8azTdgyrKyWZ9Mfh09eHWb5PgTSO8wi7U824RA==} - - '@chevrotain/types@11.0.3': - resolution: {integrity: sha512-gsiM3G8b58kZC2HaWR50gu6Y1440cHiJ+i3JUvcp/35JchYejb2+5MVeJK0iKThYpAa/P2PYFV4hoi44HD+aHQ==} - - '@chevrotain/types@11.1.2': - resolution: {integrity: sha512-U+HFai5+zmJCkK86QsaJtoITlboZHBqrVketcO2ROv865xfCMSFpELQoz1GkX5GzME8pTa+3kbKrZHQtI0gdbw==} - - '@chevrotain/utils@11.0.3': - resolution: {integrity: sha512-YslZMgtJUyuMbZ+aKvfF3x1f5liK4mWNxghFRv7jqRR9C3R3fAOGTTKvxXDa2Y1s9zSbcpuO0cAxDYsc9SrXoQ==} - - '@codemirror/autocomplete@6.20.3': - resolution: {integrity: sha512-tlosUqb+3BbxCxZdu4tKeRghPFC+QM7q4X5YhKV2eCmPG+1r2F3f4AaSz5sCrFqUtX4Jh20VFTKecl16MgiV9g==} - - '@codemirror/commands@6.10.4': - resolution: {integrity: sha512-Ryk9y9T0FFVF0cUGhAknveAyUOl/A1qReTFi+qPKtOh2Z9F4AUBz3XOrYD4ZEgZirdugVzHvd/2/Wcwy5OliTg==} - - '@codemirror/lang-json@6.0.2': - resolution: {integrity: sha512-x2OtO+AvwEHrEwR0FyyPtfDUiloG3rnVTSZV1W8UteaLL8/MajQd8DpvUb2YVzC+/T18aSDv0H9mu+xw0EStoQ==} - - '@codemirror/language@6.12.4': - resolution: {integrity: sha512-1q4PaT+o6PbgpkJt4Q8Fv5XJxTy4FUZ4MWETtyiDw3J0Pyr9E2vqcKL+k9wcvjNTIsauxvE7OfmWj3FRPHQ76A==} - - '@codemirror/lint@6.9.7': - resolution: {integrity: sha512-28/+iWLYxKxsvGYhSYL7zaCZqLz5+FFFDq9tVsvGv9kv8RY4fFAchJ5WX9M3YrrRlTIsECjsXPqeNgnSmNP2dg==} - - '@codemirror/search@6.7.1': - resolution: {integrity: sha512-uMe5UO6PamJtSHrXhhHOzSX3ReWtiJrva6GnPMwSOrZtiExb5X5eExhr2OUZQVvdxPsKpY3Ro2mFbQadpPWmHA==} - - '@codemirror/state@6.7.1': - resolution: {integrity: sha512-9QzNDgE4EYDnAHfrTlR2lwiPciiOymLtwKK+8yHQzCc7GXhAP9xdEbEJFy2IWB1j9UGUl9BsgMmTo/ImA02T7A==} - - '@codemirror/theme-one-dark@6.1.3': - resolution: {integrity: sha512-NzBdIvEJmx6fjeremiGp3t/okrLPYT0d9orIc7AFun8oZcRk58aejkqhv6spnz4MLAevrKNPMQYXEWMg4s+sKA==} - - '@codemirror/view@6.43.8': - resolution: {integrity: sha512-qtItTDssZ/5GFfi94hrILu9j/VUeFPDPkhovEfmWFj2ipTxnzPB8DdHgfbb8HYTzLTYhrndKmyQxXUz/PDLenw==} - - '@dimforge/rapier3d-compat@0.12.0': - resolution: {integrity: sha512-uekIGetywIgopfD97oDL5PfeezkFpNhwlzlaEYNOA0N6ghdsOvh/HYjSMek5Q2O1PYvRSDFcqFVJl4r4ZBwOow==} - - '@dnd-kit/accessibility@3.1.1': - resolution: {integrity: sha512-2P+YgaXF+gRsIihwwY1gCsQSYnu9Zyj2py8kY5fFvUM1qm2WA2u639R6YNVfU4GWr+ZM5mqEsfHZZLoRONbemw==} - peerDependencies: - react: '>=16.8.0' - - '@dnd-kit/core@6.3.1': - resolution: {integrity: sha512-xkGBRQQab4RLwgXxoqETICr6S5JlogafbhNsidmrkVv2YRs5MLwpjoF2qpiGjQt8S9AoxtIV603s0GIUpY5eYQ==} - peerDependencies: - react: '>=16.8.0' - react-dom: '>=16.8.0' - - '@dnd-kit/modifiers@9.0.0': - resolution: {integrity: sha512-ybiLc66qRGuZoC20wdSSG6pDXFikui/dCNGthxv4Ndy8ylErY0N3KVxY2bgo7AWwIbxDmXDg3ylAFmnrjcbVvw==} - peerDependencies: - '@dnd-kit/core': ^6.3.0 - react: '>=16.8.0' - - '@dnd-kit/sortable@10.0.0': - resolution: {integrity: sha512-+xqhmIIzvAYMGfBYYnbKuNicfSsk4RksY2XdmJhT+HAC01nix6fHCztU68jooFiMUB01Ky3F0FyOvhG/BZrWkg==} - peerDependencies: - '@dnd-kit/core': ^6.3.0 - react: '>=16.8.0' - - '@dnd-kit/utilities@3.2.2': - resolution: {integrity: sha512-+MKAJEOfaBe5SmV6t34p80MMKhjvUz0vRrvVJbPT0WElzaOJ/1xs+D+KDv+tD/NE5ujfrChEcshd4fLn0wpiqg==} - peerDependencies: - react: '>=16.8.0' - - '@dotenvx/dotenvx@1.75.1': - resolution: {integrity: sha512-/BITOC9dmS/edY2zQwZNicQ059O6RKabtQfyEafV0nGtfYRNHYy1DIPiYVcov40+tob9hfmBnbR963dS+EQ1DQ==} - hasBin: true - - '@dotenvx/primitives@0.8.0': - resolution: {integrity: sha512-VYJy0uhFm9zTJ1TxBaW/pA8bjbOM/OttaNMwZ1RHG4JKyRG7DhSdiqD1ipQoAyoD22olUtxbP78W9xY3Wd11bg==} - - '@emoji-mart/data@1.2.1': - resolution: {integrity: sha512-no2pQMWiBy6gpBEiqGeU77/bFejDqUTRY7KX+0+iur13op3bqUsXdnwoZs6Xb1zbv0gAj5VvS1PWoUUckSr5Dw==} - - '@emoji-mart/react@1.1.1': - resolution: {integrity: sha512-NMlFNeWgv1//uPsvLxvGQoIerPuVdXwK/EUek8OOkJ6wVOWPUizRBJU0hDqWZCOROVpfBgCemaC3m6jDOXi03g==} - peerDependencies: - emoji-mart: ^5.2 - react: ^16.8 || ^17 || ^18 - - '@emotion/babel-plugin@11.13.5': - resolution: {integrity: sha512-pxHCpT2ex+0q+HH91/zsdHkw/lXd468DIN2zvfvLtPKLLMo6gQj7oLObq8PhkrxOZb/gGCq03S3Z7PDhS8pduQ==} - - '@emotion/cache@11.14.0': - resolution: {integrity: sha512-L/B1lc/TViYk4DcpGxtAVbx0ZyiKM5ktoIyafGkH6zg/tj+mA+NE//aPYKG0k8kCHSHVJrpLpcAlOBEXQ3SavA==} - - '@emotion/css@11.13.5': - resolution: {integrity: sha512-wQdD0Xhkn3Qy2VNcIzbLP9MR8TafI0MJb7BEAXKp+w4+XqErksWR4OXomuDzPsN4InLdGhVe6EYcn2ZIUCpB8w==} - - '@emotion/hash@0.8.0': - resolution: {integrity: sha512-kBJtf7PH6aWwZ6fka3zQ0p6SBYzx4fl1LoZXE2RrnYST9Xljm7WfKJrU4g/Xr3Beg72MLrp1AWNUmuYJTL7Cow==} - - '@emotion/hash@0.9.2': - resolution: {integrity: sha512-MyqliTZGuOm3+5ZRSaaBGP3USLw6+EGykkwZns2EPC5g8jJ4z9OrdZY9apkl3+UP9+sdz76YYkwCKP5gh8iY3g==} - - '@emotion/is-prop-valid@1.4.0': - resolution: {integrity: sha512-QgD4fyscGcbbKwJmqNvUMSE02OsHUa+lAWKdEUIJKgqe5IwRSKd7+KhibEWdaKwgjLj0DRSHA9biAIqGBk05lw==} - - '@emotion/memoize@0.9.0': - resolution: {integrity: sha512-30FAj7/EoJ5mwVPOWhAyCX+FPfMDrVecJAM+Iw9NRoSl4BBAQeqj4cApHHUXOVvIPgLVDsCFoz/hGD+5QQD1GQ==} - - '@emotion/react@11.14.0': - resolution: {integrity: sha512-O000MLDBDdk/EohJPFUqvnp4qnHeYkVP5B0xEG0D/L7cOKP9kefu2DXn8dj74cQfsEzUqh+sr1RzFqiL1o+PpA==} - peerDependencies: - '@types/react': '*' - react: '>=16.8.0' - peerDependenciesMeta: - '@types/react': - optional: true - - '@emotion/serialize@1.3.3': - resolution: {integrity: sha512-EISGqt7sSNWHGI76hC7x1CksiXPahbxEOrC5RjmFRJTqLyEK9/9hZvBbiYn70dw4wuwMKiEMCUlR6ZXTSWQqxA==} - - '@emotion/sheet@1.4.0': - resolution: {integrity: sha512-fTBW9/8r2w3dXWYM4HCB1Rdp8NLibOw2+XELH5m5+AkWiL/KqYX6dc0kKYlaYyKjrQ6ds33MCdMPEwgs2z1rqg==} - - '@emotion/unitless@0.10.0': - resolution: {integrity: sha512-dFoMUuQA20zvtVTuxZww6OHoJYgrzfKM1t52mVySDJnMSEa08ruEvdYQbhvyu6soU+NeLVd3yKfTfT0NeV6qGg==} - - '@emotion/unitless@0.7.5': - resolution: {integrity: sha512-OWORNpfjMsSSUBVrRBVGECkhWcULOAJz9ZW8uK9qgxD+87M7jHRcvh/A96XXNhXTLmKcoYSQtBEX7lHMO7YRwg==} - - '@emotion/use-insertion-effect-with-fallbacks@1.2.0': - resolution: {integrity: sha512-yJMtVdH59sxi/aVJBpk9FQq+OR8ll5GT8oWd57UpeaKEVGab41JWaCFA7FRLoMLloOZF/c/wsPoe+bfGmRKgDg==} - peerDependencies: - react: '>=16.8.0' - - '@emotion/utils@1.4.2': - resolution: {integrity: sha512-3vLclRofFziIa3J2wDh9jjbkUz9qk5Vi3IZ/FSTKViB0k+ef0fPV7dYrUIugbgupYDx7v9ud/SjrtEP8Y4xLoA==} - - '@emotion/weak-memoize@0.4.0': - resolution: {integrity: sha512-snKqtPW01tN0ui7yu9rGv69aJXr/a/Ywvl11sUjNtEcRc+ng/mQriFL0wLXMef74iHa/EkftbDzU9F8iFbH+zg==} - - '@excalidraw/excalidraw@0.18.1': - resolution: {integrity: sha512-6i5Gt7IDTOH//qa0Z315Ly5iVRhjWpu2whrlQFqkuwrkKUWgRsMk0P5qdE7bpyDpai7jeLeWYkyj1eVAfni1lw==} - peerDependencies: - react: ^17.0.2 || ^18.2.0 || ^19.0.0 - react-dom: ^17.0.2 || ^18.2.0 || ^19.0.0 - - '@excalidraw/laser-pointer@1.3.1': - resolution: {integrity: sha512-psA1z1N2qeAfsORdXc9JmD2y4CmDwmuMRxnNdJHZexIcPwaNEyIpNcelw+QkL9rz9tosaN9krXuKaRqYpRAR6g==} - - '@excalidraw/markdown-to-text@0.1.2': - resolution: {integrity: sha512-1nDXBNAojfi3oSFwJswKREkFm5wrSjqay81QlyRv2pkITG/XYB5v+oChENVBQLcxQwX4IUATWvXM5BcaNhPiIg==} - - '@excalidraw/mermaid-to-excalidraw@2.2.2': - resolution: {integrity: sha512-5VKQq5CdRocC82vOIUpQ5ufJOVV9FpBTdHGA+ULqazeIVV+cr299877omQCibsdS3Bpitz2fsnTwnIXEmLVDSg==} - - '@excalidraw/random-username@1.1.0': - resolution: {integrity: sha512-nULYsQxkWHnbmHvcs+efMkJ4/9TtvNyFeLyHdeGxW0zHs6P+jYVqcRff9A6Vq9w9JXeDRnRh2VKvTtS19GW2qA==} - engines: {node: '>=10'} - - '@ffmpeg/core@0.12.10': - resolution: {integrity: sha512-dzNplnn2Nxle2c2i2rrDhqcB19q9cglCkWnoMTDN9Q9l3PvdjZWd1HfSPjCNWc/p8Q3CT+Es9fWOR0UhAeYQZA==} - engines: {node: '>=16.x'} - - '@ffmpeg/ffmpeg@0.12.15': - resolution: {integrity: sha512-1C8Obr4GsN3xw+/1Ww6PFM84wSQAGsdoTuTWPOj2OizsRDLT4CXTaVjPhkw6ARyDus1B9X/L2LiXHqYYsGnRFw==} - engines: {node: '>=18.x'} - - '@ffmpeg/types@0.12.4': - resolution: {integrity: sha512-k9vJQNBGTxE5AhYDtOYR5rO5fKsspbg51gbcwtbkw2lCdoIILzklulcjJfIDwrtn7XhDeF2M+THwJ2FGrLeV6A==} - engines: {node: '>=16.x'} - - '@ffmpeg/util@0.12.2': - resolution: {integrity: sha512-ouyoW+4JB7WxjeZ2y6KpRvB+dLp7Cp4ro8z0HIVpZVCM7AwFlHa0c4R8Y/a4M3wMqATpYKhC7lSFHQ0T11MEDw==} - engines: {node: '>=18.x'} - - '@floating-ui/core@1.8.0': - resolution: {integrity: sha512-0CIZ5itps/8x7BG8dEIhs53BvCUH2PCoogtakwRTut+Arm58sJooJ0AuZhLw2HJYIR5cMLNPBSS728sPho2khQ==} - - '@floating-ui/dom@1.8.0': - resolution: {integrity: sha512-yXSrzeHZBTZadLOlfyhCkJHNeLJnHRnRInwdZ40L7ZiaAtrBwoYlsDrX3v5zB1Utk7CLfzcOVnVVWoXEky7Ceg==} - - '@floating-ui/react-dom@2.1.9': - resolution: {integrity: sha512-JDjEFGCpImxDCA7JJKviA0M9+RtmJdj0m/NVU5IMgBK+AmZouAQQ7/+2GLH0GXXY0YMw9oXPB8hKdbPYg5QLYg==} - peerDependencies: - react: '>=16.8.0' - react-dom: '>=16.8.0' - - '@floating-ui/react@0.27.20': - resolution: {integrity: sha512-CMqMy7OaXl9W0eq1Uy7L7i2Y/anPvHmFmESd2CEw0t5YvZhcVCeo4MBevAmswRllX7Y2dEidA4ozGPunLSTQpw==} - peerDependencies: - react: '>=17.0.0' - react-dom: '>=17.0.0' - - '@floating-ui/utils@0.2.12': - resolution: {integrity: sha512-HpCo8tmWzLVad5s2d19EhAz5zqrrQ6s69qd6moPMQvkOuSwDT1YgRfWSVuc4ennqrgv3OHppiOGMQ7oC13yIww==} - - '@fontsource-variable/inter@5.3.0': - resolution: {integrity: sha512-OupL48va4JNofb97w6NYeF9S7W/kHNKM0Er8Dem5nqi4jeOLrVJDoE8tZEpnMJmtkvNbB1EIPPwHcdkF6b1oUA==} - - '@fontsource-variable/jetbrains-mono@5.3.0': - resolution: {integrity: sha512-F32xpS2NsGYoQi2ADSkKTgpJj7ozajsGgDJ8woTnqjmIB+dxDIqImjl4pXZVEExu8UFZ2ndhmX18EBS/hdz3Lw==} - - '@giscus/react@3.1.0': - resolution: {integrity: sha512-0TCO2TvL43+oOdyVVGHDItwxD1UMKP2ZYpT6gXmhFOqfAJtZxTzJ9hkn34iAF/b6YzyJ4Um89QIt9z/ajmAEeg==} - peerDependencies: - react: ^16 || ^17 || ^18 || ^19 - react-dom: ^16 || ^17 || ^18 || ^19 - - '@hono/node-server@2.1.1': - resolution: {integrity: sha512-ELuehkj5VCBdgEw9zs+ivkKwyzzUCSQuE96YmiPvn1ECBoZCczbFXJLeEGMTYjphP6gydh4pHMqEYPVMYUVgQg==} - engines: {node: '>=20'} - peerDependencies: - hono: ^4 - - '@iconify/types@2.0.0': - resolution: {integrity: sha512-+wluvCrRhXrhyOmRDJ3q8mux9JkKy5SJ/v8ol2tu4FVjyYvtEzkc/3pK15ET6RKg4b4w4BmTk1+gsCUhf21Ykg==} - - '@iconify/utils@3.1.4': - resolution: {integrity: sha512-b1S7B1k9ohZ+iNTi2ATxbRYG9fTrJmUT0rc46bvVnNxqNRGW7dyo/vRREwyniI5IRN2RSJHDcm+s3BjWrSAjHw==} - - '@internationalized/date@3.12.4': - resolution: {integrity: sha512-M1dEn4c1U1HsSlaVR8upZtSqvXrTkHDfv18H01uCSJyjVLDxnBR38v/fMxecmlwXKR4i9HeZcmgQAPE6A+aGJQ==} - - '@internationalized/number@3.6.8': - resolution: {integrity: sha512-8UmMFia46DUt+k97zKd9fKWXcWHR+k8ae3eYzILETuT2KbIvLyOfac7zesw+sJdRAAZ7Q9pM1Mk22aXp2LD0Ig==} - - '@internationalized/string@3.2.10': - resolution: {integrity: sha512-PDx6//vHSpRnHfxqMqto11zQvhsaU74O3mKv2F/0eicGZcl9NLjQmGlbHz/LsJh5tLKp4A4L7ZVTzN1/MmMTvA==} - - '@jridgewell/gen-mapping@0.3.13': - resolution: {integrity: sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==} - - '@jridgewell/remapping@2.3.5': - resolution: {integrity: sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==} - - '@jridgewell/resolve-uri@3.1.2': - resolution: {integrity: sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==} - engines: {node: '>=6.0.0'} - - '@jridgewell/sourcemap-codec@1.5.5': - resolution: {integrity: sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==} - - '@jridgewell/trace-mapping@0.3.31': - resolution: {integrity: sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==} - - '@leafer-in/interface@2.2.9': - resolution: {integrity: sha512-6T0Gf1EDKxS4a8sZCT8Q2PZ0a5JhHWswyWfsSXNHrvdJvapHcUE4YordpzXFQeki0KcCiAy2HIrOZ7mvFJ9spQ==} - peerDependencies: - '@leafer-ui/interface': ^2.2.9 - '@leafer/interface': ^2.2.9 - - '@leafer-ui/app@2.2.9': - resolution: {integrity: sha512-SIxCEIx09LQBSSuj7qPGlpY7kIZfRvn6gGaa2dqAzeB4unn05eBFdU8rcM+R1pM8k0VEDGQZpDOyGqe02Kn9CQ==} - - '@leafer-ui/bounds@2.2.9': - resolution: {integrity: sha512-BjZxWPQcPf/SQ+L6Edk7PNyiy3LXbsbQZp6kuC1uN0fgOiQtyib2i0M+zKa1+WTrP7aNFQWkZwSk6+QS6m0vmQ==} - - '@leafer-ui/color@2.2.9': - resolution: {integrity: sha512-xBvAmKgRR6YRlkRUG6i5Qmav0WUy/EuydzLv6GM1VkATSkU9W42KDcPp9mkg3P7w2Bwc+sJGj4q6LzsuGr5QtQ==} - - '@leafer-ui/core@2.2.9': - resolution: {integrity: sha512-N1qwviYnISzWf9vjLClRFqOkGQBqDTdwiURbNpwmQJqMfP0LMfn4+9yQ6cixoVVg+jRVt0K6izsGbzzNAEiLnw==} - - '@leafer-ui/data@2.2.9': - resolution: {integrity: sha512-omNVf2e+Gt6baKqXsHlch0PVQwIyowjMSHlyPyNIsL74PkAceYfsrRDBVztsX/EzDVvF22ecdb2w/VmAhu1nyw==} - - '@leafer-ui/decorator@2.2.9': - resolution: {integrity: sha512-98v7ib76MbilkP+QLd6G8IKs7eHnn2LOAEd08kjOqkwTfnZ0Hj3s8AKqYWEAr1rfwp6IjBEM9Rmp2Gr1vR+AGA==} - - '@leafer-ui/display-module@2.2.9': - resolution: {integrity: sha512-ZpsuJ5L0QT/d2ZuTxsSj0dOGE4n1B0xvm9qJXQ5ThuWlQ0Opm8ySIPQrvIDas35pwyEjBcru6IF6a9KdwC8J7Q==} - - '@leafer-ui/display@2.2.9': - resolution: {integrity: sha512-3sM8N3j6E+EZcrGXuGhilTCpH2pYs5Med5hZs+xfxWstw3V4TOJdTHnqCI8weCJAQ9o7QLH6I7+akSs7urz35A==} - - '@leafer-ui/draw@2.2.9': - resolution: {integrity: sha512-76FF8teAGnTk8Y+irWOuHrfsR0hJSBMu87a6UlfNU3eda8ZnkKUg0c6lRzO2aUD9gatVgmOh9XaHY3VP8iRTFQ==} - - '@leafer-ui/effect@2.2.9': - resolution: {integrity: sha512-k/9tBYWy8VLkDufkvohKATTX0joSlh4v0zi/I/jZ+XP4AVYbIDViYkU+Nt5bpeQnXi2LBGT1t/ipDsqLLlUvPA==} - - '@leafer-ui/event@2.2.9': - resolution: {integrity: sha512-WbX3DMmo3JrGHLVWigNz7NPQwduSM5PFLakYlIpy8QbnGYMcRRml/1vlPQUvnYDXi4rRMOlO82EbMvnt736qsg==} - - '@leafer-ui/external@2.2.9': - resolution: {integrity: sha512-Vp6VGhKTo07y0M1BGlfOg3CxHj1Svd9ad5mGj0+J8h4eqQQarT5GhH7uaF46WtD4di5JVbwruKj62SFq6uzPeQ==} - - '@leafer-ui/hit@2.2.9': - resolution: {integrity: sha512-Qmmz1gCzWOGrGvkt4v6kfnddqp2o9FEiokuj4/zgUoo8Q+0C0D1Y44wzrSGbgC6aQBxa6B8XJ44HLIWf09FVSg==} - - '@leafer-ui/interaction-web@2.2.9': - resolution: {integrity: sha512-QLBk68qpXbdBrwtTERmc+Mb4z7ZOXKZf1KCj+OANlO2dsajnxRvc6Sf2RvwT+kzq5ic9Czk0N+UrGudPG1HR7Q==} - - '@leafer-ui/interaction@2.2.9': - resolution: {integrity: sha512-MPtScWDfD0rEiXaJ5qdIfJPNupOT+g3ieQywG1Lu6RVfmf+Vwn3RaxKvGk/R94rEJKSL9FQSWoQLuSXGBh/6Og==} - - '@leafer-ui/interface@2.2.9': - resolution: {integrity: sha512-AWnRhnQOaMk8kNOJwzymuZpNb4b28CsMUxvr4pVjGwmlXWG1qINJrlWYWrXbV3R44+zhK0VIJ8T7szsoO2qj7Q==} - - '@leafer-ui/paint@2.2.9': - resolution: {integrity: sha512-/scOodHPSs3hJo73Scvk556ntXn9dJI8/7GlaWRMbprruvB+ujc/vyWUc5DInOqikxxAQb9eEfnKX3GoFUGiSA==} - - '@leafer-ui/partner@2.2.9': - resolution: {integrity: sha512-Z88858CvaHmg0w0oyESHaOCeHyPUScjNSi6Z8sadKU6QV3gYrNxZ85eWfv2mtf+h1D6TyEe3SQCTQc6xdnV27A==} - - '@leafer-ui/render@2.2.9': - resolution: {integrity: sha512-hi15h0nGHQpmSVxBY1ReLUD9vbHOnZYtRJsvaBOlZZEHwvsvRWWTVlwGrwMpGpbDWdy5FcwHmPp4GezaPa18QQ==} - - '@leafer-ui/text@2.2.9': - resolution: {integrity: sha512-pKUUhxnkrdM+5ul6xI7nM9yr9jwj9AuIu18oVKIba3/gekrZRWBanpCeCwMR1IaQa5X97MI3bd/eVIZ892FX5g==} - - '@leafer-ui/web@2.2.9': - resolution: {integrity: sha512-0QgYEK3wuUFWHz0BM6HPMboh0uiRR5fRLqrPEopO+ofJLygWi0v6FIjU3OE4pOsHRdqrbBIuhOioRmIasBBgmg==} - - '@leafer/canvas-web@2.2.9': - resolution: {integrity: sha512-kj58yyHdqXq7VnZm18aYzXBTX8HrGooPOunBsQwbEnUn8bVe4JtkNaUgAWIjaDIJT0TuMwQ5kH/9E+6hpwHEPA==} - - '@leafer/canvas@2.2.9': - resolution: {integrity: sha512-bWSqq2ty/khi/qDVK7UCGIZOcmsEycAYPCZkXZFWOxXXCmRxI86CA5SXBJa5UFLeAXKT9I30E+UiDc3+jAONNg==} - - '@leafer/core@2.2.9': - resolution: {integrity: sha512-zXQv21ydpeELHBRj8fF770AQj3Ma0Dz81QfX5ERjZUQ2C1LcQCXSWHbc0fQwKeKy+PS+4FZDn8VRZSlM/FC2Ww==} - - '@leafer/data@2.2.9': - resolution: {integrity: sha512-BcCRDYH2+jYqdWv8saQXZZ4X80GlBI6H51bTR1FqdRHXTuGCaF3QMWFhyTyb7/+CTtvMU6Isk0qUaEXaSLEZLw==} - - '@leafer/debug@2.2.9': - resolution: {integrity: sha512-YQGZcCCDZ888JvidE/HpN9eqw4KxV5oVKQVTSkWAaWcARt9IX8RU4QTyF+JC4P8OXwstOZP0Y6a0IqMzAijUig==} - - '@leafer/decorator@2.2.9': - resolution: {integrity: sha512-reA3NafAm0PSYkSG44QuX8sYXOf/4hdlR3IfhpHwovNf+g3M/R1ibQ+2kROhRe28APUvpaN5N/0tzj5zeOFMqA==} - - '@leafer/display-module@2.2.9': - resolution: {integrity: sha512-aBL/ciSFgYWKRAd1jmPq3qkxW5vn8Njp61li4pO4jwy6pQvbznT0ENX145cp+nIzfp8lxbYfiY79E2SFB7sLTA==} - - '@leafer/display@2.2.9': - resolution: {integrity: sha512-FiZ0Vq9G3achrldVHDuiVa9Phr0AWhyWRJhfXuDN829CVbrS7liJYysJ+Y//SGdq3sI7igyKHJbsT1V6OAk2EQ==} - - '@leafer/event@2.2.9': - resolution: {integrity: sha512-jbjcaSxG6ifFyaQtoRFVfhvcWuChCCxGQJSRqVQvXLDEZfQHACdjrZNWkfgRWkMxsNRGTGo4r+A/0Cjgz6eiRw==} - - '@leafer/file@2.2.9': - resolution: {integrity: sha512-6kZLM4mmDa3qNMrG2A8MxiVVgHJo+5Yjd/yy6yNEweDoKg9QzNacf8mrILdj2CUjuZ3IPBoRIRGFASZpEmUycg==} - - '@leafer/helper@2.2.9': - resolution: {integrity: sha512-OVMB9k3IP5w2ARrKIzrDhrc2PTjFppIKtZpvLIFf24tH6Itq0/Hn6J8pa7gKEzdltpgOJD5lYT7IQNtVbJQVXg==} - - '@leafer/image-web@2.2.9': - resolution: {integrity: sha512-ctMDVYoDi16rniIOffrqXGfOR1qWJe+lm185l+mjsqfcQo//QZ9cml4m+FfChnzwUqB6yuJvhaP3idg59ARFVA==} - - '@leafer/image@2.2.9': - resolution: {integrity: sha512-hnl7pLM1kOVUo/qdR9Sonvt40ywt9796JDyinV3BQkVh1lfiWQXJY2wb/iQXCUPyDvhiD0cSC+sDR3x9VIFyCw==} - - '@leafer/interface@2.2.9': - resolution: {integrity: sha512-XmwjsgV2u1AeAFdjeKOSAlo4zj1ascUKnlEhi0SGL+01iBomDeORjcNm50AoFMZRxY0SqQSIjvDWe6CdxrDcNw==} - - '@leafer/layout@2.2.9': - resolution: {integrity: sha512-y/22wyyRVR8OQf9tQe+7Koqmmw5fUZkxUxJKzaeDUf1R6xauK6FtDaPDiDJlxBEgIYXFvVpUfQNEzFdyyMS2jw==} - - '@leafer/layouter@2.2.9': - resolution: {integrity: sha512-JFHCtoUI4ELBTwVow6Nd+cxJxvR2H0zg8lq/1lTqgdppeOD1PRU7bP7L1JN5QrmtJ/yxDUi3iGeYg7z9VnWS9w==} - - '@leafer/list@2.2.9': - resolution: {integrity: sha512-B9H7ydvE5I7jhdnnML8kox0q+r5/uwF/t0xNHZc3FzvynpHtvpAYTpuzE5zjK0E9C2UkfQi0T9DuUr9CsguMCA==} - - '@leafer/math@2.2.9': - resolution: {integrity: sha512-kkybyaR+76e0dsgAxk0Mpeb1coMNM3/mR/JvDBNzcF2iFCemYcOFCiI8+uhJyqUKLfYzLjD7CW5gFa0NmtbgDQ==} - - '@leafer/partner@2.2.9': - resolution: {integrity: sha512-W2CcbVPoGomvVQNve1CWdhq1B5zOmUKUJqKssO8eBZF6cIXtFW9Ab1CxZR1CcLCi+OelDUvcYFhjU3keLCnNdg==} - - '@leafer/path@2.2.9': - resolution: {integrity: sha512-GP3IXKNUkUGZuZI/V2IkcJc+uU6Rs2mAvu1xtgyh73iRbkvdlk3T/MbxgOkGFmQ5cO7KKCiwizWRYyW5Z7Fb0g==} - - '@leafer/platform@2.2.9': - resolution: {integrity: sha512-wSDawGKgam20Mrl10VWP4UcYrrrWU7wlKi4/T/0j0ljvu29ZSKcLA2o4//WYJxVcuDEqSwMl3EXdpkl7pYdHlg==} - - '@leafer/renderer@2.2.9': - resolution: {integrity: sha512-CxCT5p/VMJDlbX1rBpSoc9gKrFmOIRygqMT6u1vYZynJY4XM2aqWMCItBGEUzCutRaGRRg8fK3I0J4Hux2qhCA==} - - '@leafer/selector@2.2.9': - resolution: {integrity: sha512-YeMxiIlCfFNbCkNGwx7mIccwHlBQTROw9T/zXwhd34GZ52ESVsqPETY9Ot5YuVcExJAM9mEjA6MTQuTEHYjNWw==} - - '@leafer/task@2.2.9': - resolution: {integrity: sha512-NrbrofDssz8erxoOBZ3TQEQI9pl3tAPnPtqceOtOpCFC3cGDr8HJWb7F4ZZNRhN0qutPGj+BevOWC9BYoHgMlg==} - - '@leafer/watcher@2.2.9': - resolution: {integrity: sha512-v6nRfHh3+2DZs94MItG2i+WrLMHNOqnWD7cPQ8cxo1MVX/BghjWEE8LGy7hOcLIYIppDZ9ZHeVNEhtNTCRffug==} - - '@leafer/web-core@2.2.9': - resolution: {integrity: sha512-d/m46qw6zjAjf+X/MBx4q0ztd5SxSs/mYORdaNjiOZRg6qFXsBGtUUSUyN5dIprCjKFkdVge4ow3yLxv2RvdMw==} - - '@lezer/common@1.5.2': - resolution: {integrity: sha512-sxQE460fPZyU3sdc8lafxiPwJHBzZRy/udNFynGQky1SePYBdhkBl1kOagA9uT3pxR8K09bOrmTUqA9wb/PjSQ==} - - '@lezer/highlight@1.2.3': - resolution: {integrity: sha512-qXdH7UqTvGfdVBINrgKhDsVTJTxactNNxLk7+UMwZhU13lMHaOBlJe9Vqp907ya56Y3+ed2tlqzys7jDkTmW0g==} - - '@lezer/json@1.0.3': - resolution: {integrity: sha512-BP9KzdF9Y35PDpv04r0VeSTKDeox5vVr3efE7eBbx3r4s3oNLfunchejZhjArmeieBH+nVOpgIiBJpEAv8ilqQ==} - - '@lezer/lr@1.4.10': - resolution: {integrity: sha512-rnCpTIBafOx4mRp43xOxDJbFipJm/c0cia/V5TiGlhmMa+wsSdoGmUN3w5Bqrks/09Q/D4tNAmWaT8p6NRi77A==} - - '@lit-labs/ssr-dom-shim@1.6.0': - resolution: {integrity: sha512-VHb0ALPMTlgKjM6yIxxoQNnpKyUKLD04VzeQdsiXkMqkvYlAHxq9glGLmgbb889/1GsohSOAjvQYoiBppXFqrQ==} - - '@lit/reactive-element@2.1.2': - resolution: {integrity: sha512-pbCDiVMnne1lYUIaYNN5wrwQXDtHaYtg7YEFPeW+hws6U47WeFvISGUWekPGKWOP1ygrs0ef0o1VJMk1exos5A==} - - '@lobehub/emojilib@1.0.0': - resolution: {integrity: sha512-s9KnjaPjsEefaNv150G3aifvB+J3P4eEKG+epY9zDPS2BeB6+V2jELWqAZll+nkogMaVovjEE813z3V751QwGw==} - - '@lobehub/fluent-emoji@4.1.0': - resolution: {integrity: sha512-R1MB2lfUkDvB7XAQdRzY75c1dx/tB7gEvBPaEEMarzKfCJWmXm7rheS6caVzmgwAlq5sfmTbxPL+un99sp//Yw==} - peerDependencies: - react: ^19.0.0 - react-dom: ^19.0.0 - - '@lobehub/icons@5.16.0': - resolution: {integrity: sha512-EYiHGyo7FZ4VPvsDx6Q8STN+erAwyeAFgwDaSgn547FrBsZ6NNFwUTwvgim7jruGErLJEKpd1IFgw4mD8DaW/A==} - peerDependencies: - '@lobehub/ui': ^5.0.0 - antd: ^6.1.1 - react: ^19.0.0 - react-dom: ^19.0.0 - - '@lobehub/ui@5.32.3': - resolution: {integrity: sha512-iFYJQUfjHUAnnnwB1pFuYrgtrzG+Hnd9jxNbyLZrfxtGnTsDO/SNM1lx8/fDFo0W/hlINztlGmGTOVg1jMNpfA==} - engines: {node: '>=22.22.0'} - peerDependencies: - '@lobehub/fluent-emoji': ^4.0.0 - '@lobehub/icons': ^5.0.0 - antd: ^6.1.1 - motion: ^12.0.0 - react: ^19.0.0 - react-dom: ^19.0.0 - - '@marijn/find-cluster-break@1.0.3': - resolution: {integrity: sha512-FY+MKLBoTsLNJF/eLWaOsXGdz6uh3Iu1axjPf6TUq92IYumcTcXWHoS747JARLkcdlJ/Waiaxc5wQfFO8jC6NA==} - - '@mdx-js/mdx@3.1.1': - resolution: {integrity: sha512-f6ZO2ifpwAQIpzGWaBQT2TXxPv6z3RBzQKpVftEWN78Vl/YweF1uwussDx8ECAXVtr3Rs89fKyG9YlzUs9DyGQ==} - - '@mdx-js/react@3.1.1': - resolution: {integrity: sha512-f++rKLQgUVYDAtECQ6fn/is15GkEH9+nZPM3MS0RcxVqoTfawHvDlSCH7JbMhAM6uJ32v3eXLvLmLvjGu7PTQw==} - peerDependencies: - '@types/react': '>=16' - react: '>=16' - - '@mediapipe/tasks-vision@0.10.17': - resolution: {integrity: sha512-CZWV/q6TTe8ta61cZXjfnnHsfWIdFhms03M9T7Cnd5y2mdpylJM0rF1qRq+wsQVRMLz1OYPVEBU9ph2Bx8cxrg==} - - '@mediapipe/tasks-vision@1.0.1': - resolution: {integrity: sha512-rvRE2FmAZ6ZxKSw7wq+e+jQDpN3t1B/tD2mJz9SmAzb1msoDkd4dMoE4wAh8Z30Um0PQwLiHr9QtomhmXk3aUQ==} - - '@mermaid-js/parser@0.6.3': - resolution: {integrity: sha512-lnjOhe7zyHjc+If7yT4zoedx2vo4sHaTmtkl1+or8BRTnCtDmcTpAjpzDSfCZrshM5bCoz0GyidzadJAH1xobA==} - - '@mermaid-js/parser@1.2.0': - resolution: {integrity: sha512-oYPyv8A4As1yH5Bx+04iQEQxXuIQDe0GKCNSRgao6z8AM9jixXIfP0vsppRLvGf+nKIOb9/LdpWA4YuJiVvESA==} - - '@modelcontextprotocol/sdk@1.30.0': - resolution: {integrity: sha512-xKd8OIzlqNzcqcNumGAa6g+PW2kjD5vrpcKOnfldAUPP3j7lnqMPwlTXQm8gF+UwH72z0lqaRbjr9hqGz0eITA==} - engines: {node: '>=18'} - peerDependencies: - '@cfworker/json-schema': ^4.1.1 - zod: ^3.25 || ^4.0 - peerDependenciesMeta: - '@cfworker/json-schema': - optional: true - - '@monogrid/gainmap-js@3.4.0': - resolution: {integrity: sha512-2Z0FATFHaoYJ8b+Y4y4Hgfn3FRFwuU5zRrk+9dFWp4uGAdHGqVEdP7HP+gLA3X469KXHmfupJaUbKo1b/aDKIg==} - peerDependencies: - three: '>= 0.159.0' - - '@nodelib/fs.scandir@2.1.5': - resolution: {integrity: sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==} - engines: {node: '>= 8'} - - '@nodelib/fs.stat@2.0.5': - resolution: {integrity: sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==} - engines: {node: '>= 8'} - - '@nodelib/fs.walk@1.2.8': - resolution: {integrity: sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==} - engines: {node: '>= 8'} - - '@oxc-project/types@0.144.0': - resolution: {integrity: sha512-nuhZIOLuI6TFQ32I/WnUx+SCPY7SdSKwgnFHydAuoS1+Z4BRcaP+RRJmGzl9lw+0OFF7UmaESf7KQRXaNLHypg==} - - '@pierre/diffs@1.2.12': - resolution: {integrity: sha512-pY/gmgWL03WnagqCyCnBi3QtRXUv4hCIY6FYqd5b1ZGaoI6a4Bsji8j+yRl2RfzPh/8Hf19rCl1GE80G6a1cLQ==} - peerDependencies: - react: ^18.3.1 || ^19.0.0 - react-dom: ^18.3.1 || ^19.0.0 - - '@pierre/theme@1.1.0': - resolution: {integrity: sha512-GC2OWTAfTIIWWYhPCygwG8t2EtePQkRfON4MI2rwIkJylmiyqIttJID2dCL8sUD8cNdEvYkEyfEHHKMeCiDLoQ==} - engines: {vscode: ^1.0.0} - - '@pierre/theming@0.0.2': - resolution: {integrity: sha512-QM1M4stXfnzfaE8I8YbjXSApV8c+2dBsXJj8eYg9WTpBR/cTmCZIcfGnN4p13iRrYu2Br/R/OJfEL7uR8Qjctw==} - peerDependencies: - '@pierre/theme': ^1.1.0 - '@shikijs/themes': ^3.0.0 || ^4.0.0 - react: ^18.3.1 || ^19.0.0 - react-dom: ^18.3.1 || ^19.0.0 - shiki: ^3.0.0 || ^4.0.0 - peerDependenciesMeta: - '@pierre/theme': - optional: true - '@shikijs/themes': - optional: true - react: - optional: true - react-dom: - optional: true - shiki: - optional: true - - '@primer/octicons@19.33.0': - resolution: {integrity: sha512-kYrlutFnNVWDgckjQwOv9kXiFIPjTD/N+DIx6qzMBAk82nGTE85UKSvFvbt/RUhviNEoCjFoEr4fn5EAbkkUzQ==} - - '@radix-ui/number@1.1.3': - resolution: {integrity: sha512-Road2bidD0uu/1BGDOWNdPI06g0lIRy6IF9GZcIrDK2KGItfor8IQwQa+yM2ERgHM1MmHxaxpTzk0/Jp42lNfA==} - - '@radix-ui/primitive@1.0.0': - resolution: {integrity: sha512-3e7rn8FDMin4CgeL7Z/49smCA3rFYY3Ha2rUQ7HRWFadS5iCRw08ZgVT1LaNTCNqgvrUiyczLflrVrF0SRQtNA==} - - '@radix-ui/primitive@1.1.1': - resolution: {integrity: sha512-SJ31y+Q/zAyShtXJc8x83i9TYdbAfHZ++tUZnvjJJqFjzsdUnKsxPL6IEtBlxKkU7yzer//GQtZSV4GbldL3YA==} - - '@radix-ui/primitive@1.1.7': - resolution: {integrity: sha512-rqWnm76nYT8HoNNqEjpgJ7Pw/DrBj5iBTrmEPo6HTX5+VJyBNOqTdv4g89G63HuR5g0AaENoAcH7Is5fF2kZ8Q==} - - '@radix-ui/react-accessible-icon@1.1.15': - resolution: {integrity: sha512-WTQwcAvQf5sOcuUyi90lKPbhwcvQ+j55cjrSmeaN+L2vKU3DooOvlKw2MDeiJ5IkV5N905KW0/fGojKOBhD11A==} - peerDependencies: - '@types/react': '*' - '@types/react-dom': '*' - react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - peerDependenciesMeta: - '@types/react': - optional: true - '@types/react-dom': - optional: true - - '@radix-ui/react-accordion@1.2.20': - resolution: {integrity: sha512-jDhG9FvAEnlhnjrsINbNXcUa4G+L1KqSkJSunkbKEzFRcAb52jvM0PjPxPRvhe1HNc5F5yc0yzzWeeqlH4yBIg==} - peerDependencies: - '@types/react': '*' - '@types/react-dom': '*' - react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - peerDependenciesMeta: - '@types/react': - optional: true - '@types/react-dom': - optional: true - - '@radix-ui/react-alert-dialog@1.1.23': - resolution: {integrity: sha512-VAYOiQRqj3GPpYJE0I9J+X8Ip05cyVlNdKOFeiGS2Ou1HHGfpl0BxOyZm6nmVDyU+W+NF3/XLzmjHmVGydhwgA==} - peerDependencies: - '@types/react': '*' - '@types/react-dom': '*' - react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - peerDependenciesMeta: - '@types/react': - optional: true - '@types/react-dom': - optional: true - - '@radix-ui/react-arrow@1.1.15': - resolution: {integrity: sha512-v4zggRcjadnI+ClKDuijlQEW4tw3NoaeHc/PwpKnLoLLKNUG4InLegkstooLcRIUWCs+8L22dGURCVuFfOKfnA==} - peerDependencies: - '@types/react': '*' - '@types/react-dom': '*' - react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - peerDependenciesMeta: - '@types/react': - optional: true - '@types/react-dom': - optional: true - - '@radix-ui/react-arrow@1.1.2': - resolution: {integrity: sha512-G+KcpzXHq24iH0uGG/pF8LyzpFJYGD4RfLjCIBfGdSLXvjLHST31RUiRVrupIBMvIppMgSzQ6l66iAxl03tdlg==} - peerDependencies: - '@types/react': '*' - '@types/react-dom': '*' - react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - peerDependenciesMeta: - '@types/react': - optional: true - '@types/react-dom': - optional: true - - '@radix-ui/react-aspect-ratio@1.1.15': - resolution: {integrity: sha512-fy+dyVR+90nelK8rqIznFlxzx7uPcGbhxH8Nfr2bHb4UfSe+e3hklOC0luK0hDwVwnRX7xTRySpsrQVeW+/oNQ==} - peerDependencies: - '@types/react': '*' - '@types/react-dom': '*' - react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - peerDependenciesMeta: - '@types/react': - optional: true - '@types/react-dom': - optional: true - - '@radix-ui/react-avatar@1.2.6': - resolution: {integrity: sha512-4ULOTJ/mqy2hT9GlWa/MFHxHSvH3nJzHnZM1waNsc5Bonv7i70aNenghXmD97S6OJ81ekXONGGt4nT1r0PfEdA==} - peerDependencies: - '@types/react': '*' - '@types/react-dom': '*' - react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - peerDependenciesMeta: - '@types/react': - optional: true - '@types/react-dom': - optional: true - - '@radix-ui/react-checkbox@1.3.11': - resolution: {integrity: sha512-Gnptr9pDDQxD3hgq2dtPbtrp/c2qH1mBwIzw3X/ivrMb2e1t0jMTi606fVEqFPaQR1ggXIVQWKj3P2WW9v7zGQ==} - peerDependencies: - '@types/react': '*' - '@types/react-dom': '*' - react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - peerDependenciesMeta: - '@types/react': - optional: true - '@types/react-dom': - optional: true - - '@radix-ui/react-collapsible@1.1.20': - resolution: {integrity: sha512-mcGesGplBnzN2sbvJETzpCNfSMyPnb29q1GRLU+Ib7bJrpIG2ywmRoh2V5VbA2uNvKikKUlVbAPks7JDjz4A8Q==} - peerDependencies: - '@types/react': '*' - '@types/react-dom': '*' - react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - peerDependenciesMeta: - '@types/react': - optional: true - '@types/react-dom': - optional: true - - '@radix-ui/react-collection@1.0.1': - resolution: {integrity: sha512-uuiFbs+YCKjn3X1DTSx9G7BHApu4GHbi3kgiwsnFUbOKCrwejAJv4eE4Vc8C0Oaxt9T0aV4ox0WCOdx+39Xo+g==} - peerDependencies: - react: ^16.8 || ^17.0 || ^18.0 - react-dom: ^16.8 || ^17.0 || ^18.0 - - '@radix-ui/react-collection@1.1.15': - resolution: {integrity: sha512-9W+B9NPF0NaaPh/1NJd3+KqsnlLqU9H7T2rvww+fp+T/evVXdNAyYcnfRQZFOjkR1ajQp3yORlqnI8soawLvNA==} - peerDependencies: - '@types/react': '*' - '@types/react-dom': '*' - react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - peerDependenciesMeta: - '@types/react': - optional: true - '@types/react-dom': - optional: true - - '@radix-ui/react-compose-refs@1.0.0': - resolution: {integrity: sha512-0KaSv6sx787/hK3eF53iOkiSLwAGlFMx5lotrqD2pTjB18KbybKoEIgkNZTKC60YECDQTKGTRcDBILwZVqVKvA==} - peerDependencies: - react: ^16.8 || ^17.0 || ^18.0 - - '@radix-ui/react-compose-refs@1.1.1': - resolution: {integrity: sha512-Y9VzoRDSJtgFMUCoiZBDVo084VQ5hfpXxVE+NgkdNsjiDBByiImMZKKhxMwCbdHvhlENG6a833CbFkOQvTricw==} - peerDependencies: - '@types/react': '*' - react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - peerDependenciesMeta: - '@types/react': - optional: true - - '@radix-ui/react-compose-refs@1.1.5': - resolution: {integrity: sha512-+48PbAAbq3didjJxa+OaWY2ZwgAKsNiRGyeHKszblZMQ+kcpd9pAaT11cMkGEie0vsOi3QdeTE6d5Fe3Gn61kA==} - peerDependencies: - '@types/react': '*' - react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - peerDependenciesMeta: - '@types/react': - optional: true - - '@radix-ui/react-context-menu@2.3.7': - resolution: {integrity: sha512-CtXP35dxaB5T3zXSd+E3uHe/QpXcpYnZmxp6OaIbfthtfW4wyb77M23BG+bwIJDtsMwEP/YssdsmNyZu7jhWew==} - peerDependencies: - '@types/react': '*' - '@types/react-dom': '*' - react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - peerDependenciesMeta: - '@types/react': - optional: true - '@types/react-dom': - optional: true - - '@radix-ui/react-context@1.0.0': - resolution: {integrity: sha512-1pVM9RfOQ+n/N5PJK33kRSKsr1glNxomxONs5c49MliinBY6Yw2Q995qfBUUo0/Mbg05B/sGA0gkgPI7kmSHBg==} - peerDependencies: - react: ^16.8 || ^17.0 || ^18.0 - - '@radix-ui/react-context@1.1.1': - resolution: {integrity: sha512-UASk9zi+crv9WteK/NU4PLvOoL3OuE6BWVKNF6hPRBtYBDXQ2u5iu3O59zUlJiTVvkyuycnqrztsHVJwcK9K+Q==} - peerDependencies: - '@types/react': '*' - react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - peerDependenciesMeta: - '@types/react': - optional: true - - '@radix-ui/react-context@1.2.2': - resolution: {integrity: sha512-RHCUGwKHDr0hDGg4X7ma4JG4/+12qxw8rkh5QKdDldlCvtja6nUx1Ef/8HVrJze81lEsgLQlqjzjGNHantgnQA==} - peerDependencies: - '@types/react': '*' - react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - peerDependenciesMeta: - '@types/react': - optional: true - - '@radix-ui/react-dialog@1.1.23': - resolution: {integrity: sha512-Ksw4WeROkO4rC9k/onilX/Ao2Cr1ku1unMNH+XSCcP4jSXYu7HDsg9n4ojMjVb22XpYjAQ9qfrFlVbru1vXDUA==} - peerDependencies: - '@types/react': '*' - '@types/react-dom': '*' - react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - peerDependenciesMeta: - '@types/react': - optional: true - '@types/react-dom': - optional: true - - '@radix-ui/react-direction@1.0.0': - resolution: {integrity: sha512-2HV05lGUgYcA6xgLQ4BKPDmtL+QbIZYH5fCOTAOOcJ5O0QbWS3i9lKaurLzliYUDhORI2Qr3pyjhJh44lKA3rQ==} - peerDependencies: - react: ^16.8 || ^17.0 || ^18.0 - - '@radix-ui/react-direction@1.1.4': - resolution: {integrity: sha512-5pzg4FGQNpExhnhT2zlrP1wZFaYCd1K0nYWoFAdcYoYK868IEigqMX3B3f8yIoRlAhAeDWciLI6ZdCKHF9P4Vg==} - peerDependencies: - '@types/react': '*' - react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - peerDependenciesMeta: - '@types/react': - optional: true - - '@radix-ui/react-dismissable-layer@1.1.19': - resolution: {integrity: sha512-8g4pfOL9HoKKLWGiypT+dphVqjFfmcXO5GBnhsG6zI+lxAx/8feQpr+1LSN8Re3hiZ+XkLNS4O9ztK11/LzQ6w==} - peerDependencies: - '@types/react': '*' - '@types/react-dom': '*' - react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - peerDependenciesMeta: - '@types/react': - optional: true - '@types/react-dom': - optional: true - - '@radix-ui/react-dismissable-layer@1.1.5': - resolution: {integrity: sha512-E4TywXY6UsXNRhFrECa5HAvE5/4BFcGyfTyK36gP+pAW1ed7UTK4vKwdr53gAJYwqbfCWC6ATvJa3J3R/9+Qrg==} - peerDependencies: - '@types/react': '*' - '@types/react-dom': '*' - react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - peerDependenciesMeta: - '@types/react': - optional: true - '@types/react-dom': - optional: true - - '@radix-ui/react-dropdown-menu@2.1.24': - resolution: {integrity: sha512-geq8l2rJkxvkXsT9RMgtUE3P8pITFpTsvYpbySi1IH4fZEABD/Gp85myayFgxk0ktljGMJnCbeFkyTusvSvv7g==} - peerDependencies: - '@types/react': '*' - '@types/react-dom': '*' - react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - peerDependenciesMeta: - '@types/react': - optional: true - '@types/react-dom': - optional: true - - '@radix-ui/react-focus-guards@1.1.1': - resolution: {integrity: sha512-pSIwfrT1a6sIoDASCSpFwOasEwKTZWDw/iBdtnqKO7v6FeOzYJ7U53cPzYFVR3geGGXgVHaH+CdngrrAzqUGxg==} - peerDependencies: - '@types/react': '*' - react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - peerDependenciesMeta: - '@types/react': - optional: true - - '@radix-ui/react-focus-guards@1.1.6': - resolution: {integrity: sha512-RNOJjfZMTyBM6xYmV3IVGXkPjIhcBAuv48POevAXwrGJhkWZ9p1rFoIS1JFooPuT193AZmRsCPhpoVJxx6OPoQ==} - peerDependencies: - '@types/react': '*' - react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - peerDependenciesMeta: - '@types/react': - optional: true - - '@radix-ui/react-focus-scope@1.1.16': - resolution: {integrity: sha512-wmRZ2WWLvmt6KHy2rNPOdPUjwq5xOHY02+m+udwJTn0aNIox/rkskAvJTyTLGhPK6KgrUjlJUJpgmx/+wFiFIQ==} - peerDependencies: - '@types/react': '*' - '@types/react-dom': '*' - react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - peerDependenciesMeta: - '@types/react': - optional: true - '@types/react-dom': - optional: true - - '@radix-ui/react-focus-scope@1.1.2': - resolution: {integrity: sha512-zxwE80FCU7lcXUGWkdt6XpTTCKPitG1XKOwViTxHVKIJhZl9MvIl2dVHeZENCWD9+EdWv05wlaEkRXUykU27RA==} - peerDependencies: - '@types/react': '*' - '@types/react-dom': '*' - react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - peerDependenciesMeta: - '@types/react': - optional: true - '@types/react-dom': - optional: true - - '@radix-ui/react-form@0.1.16': - resolution: {integrity: sha512-Q4TLEn2A7TAypxwmd6R9EwrlXDvkfYSDMrq9/887AXAGh+G1rH+kYJKSTv+Si9Y0JPKTwKYv6PviAJosysNimA==} - peerDependencies: - '@types/react': '*' - '@types/react-dom': '*' - react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - peerDependenciesMeta: - '@types/react': - optional: true - '@types/react-dom': - optional: true - - '@radix-ui/react-hover-card@1.1.23': - resolution: {integrity: sha512-H8qONfZd3ltrU3+jHCIgITbWo6e1iTKvP9DHdrvYbX48ooRM5FjEDTn16AMwdfuOGkWdZEhpl3PLL/Wk/AnHDQ==} - peerDependencies: - '@types/react': '*' - '@types/react-dom': '*' - react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - peerDependenciesMeta: - '@types/react': - optional: true - '@types/react-dom': - optional: true - - '@radix-ui/react-id@1.0.0': - resolution: {integrity: sha512-Q6iAB/U7Tq3NTolBBQbHTgclPmGWE3OlktGGqrClPozSw4vkQ1DfQAOtzgRPecKsMdJINE05iaoDUG8tRzCBjw==} - peerDependencies: - react: ^16.8 || ^17.0 || ^18.0 - - '@radix-ui/react-id@1.1.0': - resolution: {integrity: sha512-EJUrI8yYh7WOjNOqpoJaf1jlFIH2LvtgAl+YcFqNCa+4hj64ZXmPkAKOFs/ukjz3byN6bdb/AVUqHkI8/uWWMA==} - peerDependencies: - '@types/react': '*' - react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - peerDependenciesMeta: - '@types/react': - optional: true - - '@radix-ui/react-id@1.1.4': - resolution: {integrity: sha512-TMQp2llA+RYn7JcjnrMnz7wN4pcVttPZnRZo52PLQsoLVKzNlVwUeHmfePgTgRluXFvlD3GD5g5MOVVTJCO0qA==} - peerDependencies: - '@types/react': '*' - react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - peerDependenciesMeta: - '@types/react': - optional: true - - '@radix-ui/react-label@2.1.15': - resolution: {integrity: sha512-o/rdYEwZTTo5tjknnPeyQFU45kUC4i/XyeDPP+HGyi6XqpOP6Zf5Ya5vh/Yfe9Id5JiuWnnAx2XqIeD3UYZt0g==} - peerDependencies: - '@types/react': '*' - '@types/react-dom': '*' - react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - peerDependenciesMeta: - '@types/react': - optional: true - '@types/react-dom': - optional: true - - '@radix-ui/react-menu@2.1.24': - resolution: {integrity: sha512-uW7RVuU6Lp/ZtfeY4b3kL32zccgEWvPv1+cf17ubYzHa9cL8AHokmk36cG/XEiH/smbQvumnieXX9j/e9RqJWA==} - peerDependencies: - '@types/react': '*' - '@types/react-dom': '*' - react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - peerDependenciesMeta: - '@types/react': - optional: true - '@types/react-dom': - optional: true - - '@radix-ui/react-menubar@1.1.24': - resolution: {integrity: sha512-eeVs0vf7cuqXaM0qLQCPcufImiJNVBXdJDLu7ZGYl2732UH23Qat/foNGrr6vYV3/DdTsBqASoggUFgH14OcZA==} - peerDependencies: - '@types/react': '*' - '@types/react-dom': '*' - react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - peerDependenciesMeta: - '@types/react': - optional: true - '@types/react-dom': - optional: true - - '@radix-ui/react-navigation-menu@1.2.22': - resolution: {integrity: sha512-ou7iLEJ+yrhQndkkA4U21XIdS/CS45F4iXIkTZcb6/Ne9EMsOuDudVmCwmDnfFZZ+y1FZqXRNSIgBy+YMvZVZg==} - peerDependencies: - '@types/react': '*' - '@types/react-dom': '*' - react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - peerDependenciesMeta: - '@types/react': - optional: true - '@types/react-dom': - optional: true - - '@radix-ui/react-one-time-password-field@0.1.16': - resolution: {integrity: sha512-Tj9P6ntAJEw52oq/F0AGknXR4XncxEt7XU47O3xJQOiWfLzEy3d9gtgKfvjSzGxzHkfL+VzvxGu2KTFsloJqXw==} - peerDependencies: - '@types/react': '*' - '@types/react-dom': '*' - react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - peerDependenciesMeta: - '@types/react': - optional: true - '@types/react-dom': - optional: true - - '@radix-ui/react-password-toggle-field@0.1.11': - resolution: {integrity: sha512-4gvFnmDXu3dgj21CqsufzIameRvlRd4SBqaWhcrlrNhRo0Y5i/49AmRJYe1fdAM3G2VNBbmin4b0D6cdQocwgw==} - peerDependencies: - '@types/react': '*' - '@types/react-dom': '*' - react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - peerDependenciesMeta: - '@types/react': - optional: true - '@types/react-dom': - optional: true - - '@radix-ui/react-popover@1.1.23': - resolution: {integrity: sha512-mw58MrBlyHWFisTOYignD0vf/3gdcgAR+9of1s9G/38CbFiUwH1nCDkc0AUM9IrXFgN5Ue8n45j9WCgyM1sbiQ==} - peerDependencies: - '@types/react': '*' - '@types/react-dom': '*' - react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - peerDependenciesMeta: - '@types/react': - optional: true - '@types/react-dom': - optional: true - - '@radix-ui/react-popover@1.1.6': - resolution: {integrity: sha512-NQouW0x4/GnkFJ/pRqsIS3rM/k97VzKnVb2jB7Gq7VEGPy5g7uNV1ykySFt7eWSp3i2uSGFwaJcvIRJBAHmmFg==} - peerDependencies: - '@types/react': '*' - '@types/react-dom': '*' - react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - peerDependenciesMeta: - '@types/react': - optional: true - '@types/react-dom': - optional: true - - '@radix-ui/react-popper@1.2.2': - resolution: {integrity: sha512-Rvqc3nOpwseCyj/rgjlJDYAgyfw7OC1tTkKn2ivhaMGcYt8FSBlahHOZak2i3QwkRXUXgGgzeEe2RuqeEHuHgA==} - peerDependencies: - '@types/react': '*' - '@types/react-dom': '*' - react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - peerDependenciesMeta: - '@types/react': - optional: true - '@types/react-dom': - optional: true - - '@radix-ui/react-popper@1.3.7': - resolution: {integrity: sha512-UsJrrd7w4wuKKTdvd/DNERVlwSlUcyXzjhyDwBk+3aPOsCjOY6ZSbxuw8E6lZTjjfP8Cpd0J8VVkrYUWyGYXyg==} - peerDependencies: - '@types/react': '*' - '@types/react-dom': '*' - react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - peerDependenciesMeta: - '@types/react': - optional: true - '@types/react-dom': - optional: true - - '@radix-ui/react-portal@1.1.17': - resolution: {integrity: sha512-vKQLcWypUnwZVvfV7UkGahH2g6ySe8M8R+zYBwPrv5byZ9QAW6cQVvNKo7GgmD+p8aYb6D9JBuvy8/WhOno2wQ==} - peerDependencies: - '@types/react': '*' - '@types/react-dom': '*' - react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - peerDependenciesMeta: - '@types/react': - optional: true - '@types/react-dom': - optional: true - - '@radix-ui/react-portal@1.1.4': - resolution: {integrity: sha512-sn2O9k1rPFYVyKd5LAJfo96JlSGVFpa1fS6UuBJfrZadudiw5tAmru+n1x7aMRQ84qDM71Zh1+SzK5QwU0tJfA==} - peerDependencies: - '@types/react': '*' - '@types/react-dom': '*' - react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - peerDependenciesMeta: - '@types/react': - optional: true - '@types/react-dom': - optional: true - - '@radix-ui/react-presence@1.0.0': - resolution: {integrity: sha512-A+6XEvN01NfVWiKu38ybawfHsBjWum42MRPnEuqPsBZ4eV7e/7K321B5VgYMPv3Xx5An6o1/l9ZuDBgmcmWK3w==} - peerDependencies: - react: ^16.8 || ^17.0 || ^18.0 - react-dom: ^16.8 || ^17.0 || ^18.0 - - '@radix-ui/react-presence@1.1.10': - resolution: {integrity: sha512-3wyzCQ6+ubRA+D4uv9m95JYLXxmOHp05qjrkjeA7uKHHtjpPggQzc6DAb0URl7j67oR0K2foO4ip27TiX037Bw==} - peerDependencies: - '@types/react': '*' - '@types/react-dom': '*' - react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - peerDependenciesMeta: - '@types/react': - optional: true - '@types/react-dom': - optional: true - - '@radix-ui/react-presence@1.1.2': - resolution: {integrity: sha512-18TFr80t5EVgL9x1SwF/YGtfG+l0BS0PRAlCWBDoBEiDQjeKgnNZRVJp/oVBl24sr3Gbfwc/Qpj4OcWTQMsAEg==} - peerDependencies: - '@types/react': '*' - '@types/react-dom': '*' - react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - peerDependenciesMeta: - '@types/react': - optional: true - '@types/react-dom': - optional: true - - '@radix-ui/react-primitive@1.0.1': - resolution: {integrity: sha512-fHbmislWVkZaIdeF6GZxF0A/NH/3BjrGIYj+Ae6eTmTCr7EB0RQAAVEiqsXK6p3/JcRqVSBQoceZroj30Jj3XA==} - peerDependencies: - react: ^16.8 || ^17.0 || ^18.0 - react-dom: ^16.8 || ^17.0 || ^18.0 - - '@radix-ui/react-primitive@2.0.2': - resolution: {integrity: sha512-Ec/0d38EIuvDF+GZjcMU/Ze6MxntVJYO/fRlCPhCaVUyPY9WTalHJw54tp9sXeJo3tlShWpy41vQRgLRGOuz+w==} - peerDependencies: - '@types/react': '*' - '@types/react-dom': '*' - react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - peerDependenciesMeta: - '@types/react': - optional: true - '@types/react-dom': - optional: true - - '@radix-ui/react-primitive@2.1.10': - resolution: {integrity: sha512-MucOnzh6hR5mid6VpkbglRAMYMjKLqRnGBbjXkzjK52fuQDd1qbkx78a5P40mkcnVXJdEVxm26E9OPAiUq7nBg==} - peerDependencies: - '@types/react': '*' - '@types/react-dom': '*' - react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - peerDependenciesMeta: - '@types/react': - optional: true - '@types/react-dom': - optional: true - - '@radix-ui/react-progress@1.1.16': - resolution: {integrity: sha512-5XnomAsoZZCY+KNTxbIghpGqPruZvKFNlvcAljVAOdDRDsH4/OZQxhtwo5wdtoDM5R6MhJBb2sPnDuRFep3lzg==} - peerDependencies: - '@types/react': '*' - '@types/react-dom': '*' - react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - peerDependenciesMeta: - '@types/react': - optional: true - '@types/react-dom': - optional: true - - '@radix-ui/react-radio-group@1.4.7': - resolution: {integrity: sha512-cgYFEkntCxppHZgtSZ+7vh0wbZQ+IC7PPMw8DSnRG27B6kDd32/Zw0OJt7dGDigCoprMuWHjg2PvUn3PYvPFoQ==} - peerDependencies: - '@types/react': '*' - '@types/react-dom': '*' - react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - peerDependenciesMeta: - '@types/react': - optional: true - '@types/react-dom': - optional: true - - '@radix-ui/react-roving-focus@1.0.2': - resolution: {integrity: sha512-HLK+CqD/8pN6GfJm3U+cqpqhSKYAWiOJDe+A+8MfxBnOue39QEeMa43csUn2CXCHQT0/mewh1LrrG4tfkM9DMA==} - peerDependencies: - react: ^16.8 || ^17.0 || ^18.0 - react-dom: ^16.8 || ^17.0 || ^18.0 - - '@radix-ui/react-roving-focus@1.1.19': - resolution: {integrity: sha512-V9jI6hDjT7l3jsCQD9bLNvDLM3tH/gdbOTp7Tefp3hbbgCGQoK7tUvrWiRlcoBHIZ809ElXwNQwVo0B98LuTXQ==} - peerDependencies: - '@types/react': '*' - '@types/react-dom': '*' - react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - peerDependenciesMeta: - '@types/react': - optional: true - '@types/react-dom': - optional: true - - '@radix-ui/react-scroll-area@1.2.18': - resolution: {integrity: sha512-Zn5Cd171wxsO3Dfg8HaW6RifTb9CYTKQJHs/G4+LN1GfmJpaQMZQyQxMprVPHpaz7QY4l9BxK2JwQuzHsXC8nA==} - peerDependencies: - '@types/react': '*' - '@types/react-dom': '*' - react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - peerDependenciesMeta: - '@types/react': - optional: true - '@types/react-dom': - optional: true - - '@radix-ui/react-select@2.3.7': - resolution: {integrity: sha512-WFGImkmbzcfxeIwq/+4HvRN0pizBwbwQUED4I13ezQsDdfl38ZntN6TmR8XaSzPBqoCToe8rF75j6NPNDSzhbg==} - peerDependencies: - '@types/react': '*' - '@types/react-dom': '*' - react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - peerDependenciesMeta: - '@types/react': - optional: true - '@types/react-dom': - optional: true - - '@radix-ui/react-separator@1.1.15': - resolution: {integrity: sha512-jOLO4lssEzWpoDu7G+Ze4VjwMRUBt291pnZD0gmalREZipnTX3wadQo7Fy48GCTfe14/YRN6rw/rOJqrE85Wxw==} - peerDependencies: - '@types/react': '*' - '@types/react-dom': '*' - react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - peerDependenciesMeta: - '@types/react': - optional: true - '@types/react-dom': - optional: true - - '@radix-ui/react-slider@1.4.7': - resolution: {integrity: sha512-mTSLf1GC/C0moWjTbvCM6Qn/gBjvlFt1azuWF2v7MN5C3Zq2U2J2lN3ZEYkpujuOU5Ro7A28wkviSxaKnG0BYg==} - peerDependencies: - '@types/react': '*' - '@types/react-dom': '*' - react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - peerDependenciesMeta: - '@types/react': - optional: true - '@types/react-dom': - optional: true - - '@radix-ui/react-slot@1.0.1': - resolution: {integrity: sha512-avutXAFL1ehGvAXtPquu0YK5oz6ctS474iM3vNGQIkswrVhdrS52e3uoMQBzZhNRAIE0jBnUyXWNmSjGHhCFcw==} - peerDependencies: - react: ^16.8 || ^17.0 || ^18.0 - - '@radix-ui/react-slot@1.1.2': - resolution: {integrity: sha512-YAKxaiGsSQJ38VzKH86/BPRC4rh+b1Jpa+JneA5LRE7skmLPNAyeG8kPJj/oo4STLvlrs8vkf/iYyc3A5stYCQ==} - peerDependencies: - '@types/react': '*' - react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - peerDependenciesMeta: - '@types/react': - optional: true - - '@radix-ui/react-slot@1.3.3': - resolution: {integrity: sha512-qx7oqnYbxnK9kYI9m317qmFmEgo6ywqWvbTogdj7cL9p3/yx4M48p7Rnw5z3H890cL/ow/EeWJsuTykeZVXP5Q==} - peerDependencies: - '@types/react': '*' - react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - peerDependenciesMeta: - '@types/react': - optional: true - - '@radix-ui/react-switch@1.3.7': - resolution: {integrity: sha512-48tB/4dn2UVLBCYhTu9AuR63IHl73l/qLbLgxd86noTUor4/K4LFDAcYjK+isP5313qxaFpjPVogE7+Y0/V3Kw==} - peerDependencies: - '@types/react': '*' - '@types/react-dom': '*' - react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - peerDependenciesMeta: - '@types/react': - optional: true - '@types/react-dom': - optional: true - - '@radix-ui/react-tabs@1.0.2': - resolution: {integrity: sha512-gOUwh+HbjCuL0UCo8kZ+kdUEG8QtpdO4sMQduJ34ZEz0r4922g9REOBM+vIsfwtGxSug4Yb1msJMJYN2Bk8TpQ==} - peerDependencies: - react: ^16.8 || ^17.0 || ^18.0 - react-dom: ^16.8 || ^17.0 || ^18.0 - - '@radix-ui/react-tabs@1.1.21': - resolution: {integrity: sha512-UKxJlZid7FVtsk/WTxj4i4uSEgj2Au+KBbS7SQyTlzMhhn+86Cz3tISZdTa87bfEfcuvZezf2ZsxD4xuEKtkog==} - peerDependencies: - '@types/react': '*' - '@types/react-dom': '*' - react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - peerDependenciesMeta: - '@types/react': - optional: true - '@types/react-dom': - optional: true - - '@radix-ui/react-toast@1.2.23': - resolution: {integrity: sha512-ofhyAsYaocRGOs/n0XWdUOSVzEAG6BfrMVM8z0c0kLEWY38w/0WuMFPTJP/HVaZPYkMvHZoKIIhNcjbTCBILPg==} - peerDependencies: - '@types/react': '*' - '@types/react-dom': '*' - react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - peerDependenciesMeta: - '@types/react': - optional: true - '@types/react-dom': - optional: true - - '@radix-ui/react-toggle-group@1.1.19': - resolution: {integrity: sha512-OtnwuSVjd1Ofi+AdnvhsjQdyuhCDwYs1w9RyB5BN/OavXOVQo42SYqQjwUnbPnaiPFBpQ9aX70dWeee+v2oBLA==} - peerDependencies: - '@types/react': '*' - '@types/react-dom': '*' - react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - peerDependenciesMeta: - '@types/react': - optional: true - '@types/react-dom': - optional: true - - '@radix-ui/react-toggle@1.1.18': - resolution: {integrity: sha512-7lonPlKfSacd20GlOBx2ltuVKz9oqWYZz+oMQyOltw6t1y2nyftj2ZmwwUHYn49kqfDWcp8dNZm5NgV+5Z+mug==} - peerDependencies: - '@types/react': '*' - '@types/react-dom': '*' - react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - peerDependenciesMeta: - '@types/react': - optional: true - '@types/react-dom': - optional: true - - '@radix-ui/react-toolbar@1.1.19': - resolution: {integrity: sha512-Ph0IvtYw4VB12ZnZg+YtrGs8yJQsnizwo/zu0R4Y/nWugtJzA7Pg1eWeuDR9+LSqn+xjamss+UOSOJJJ4gx8jw==} - peerDependencies: - '@types/react': '*' - '@types/react-dom': '*' - react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - peerDependenciesMeta: - '@types/react': - optional: true - '@types/react-dom': - optional: true - - '@radix-ui/react-tooltip@1.2.16': - resolution: {integrity: sha512-6EamKFRRnlpdadndbZ6LMwycfwkwPte1B42hs6QA0gYhjaOKqW4PZ4pjaW9UrlDX5eVt/OjncE7BFTPL5nmZhg==} - peerDependencies: - '@types/react': '*' - '@types/react-dom': '*' - react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - peerDependenciesMeta: - '@types/react': - optional: true - '@types/react-dom': - optional: true - - '@radix-ui/react-use-callback-ref@1.0.0': - resolution: {integrity: sha512-GZtyzoHz95Rhs6S63D2t/eqvdFCm7I+yHMLVQheKM7nBD8mbZIt+ct1jz4536MDnaOGKIxynJ8eHTkVGVVkoTg==} - peerDependencies: - react: ^16.8 || ^17.0 || ^18.0 - - '@radix-ui/react-use-callback-ref@1.1.0': - resolution: {integrity: sha512-CasTfvsy+frcFkbXtSJ2Zu9JHpN8TYKxkgJGWbjiZhFivxaeW7rMeZt7QELGVLaYVfFMsKHjb7Ak0nMEe+2Vfw==} - peerDependencies: - '@types/react': '*' - react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - peerDependenciesMeta: - '@types/react': - optional: true - - '@radix-ui/react-use-callback-ref@1.1.4': - resolution: {integrity: sha512-R6OUY2e2fA6Yn6s+VSx5KBV6Nx8LQEhu+cz7LCej18rQ1HLyg9PSC9jP/ZNx0o6FAIK9c0F1kHylzSxKsdlkrQ==} - peerDependencies: - '@types/react': '*' - react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - peerDependenciesMeta: - '@types/react': - optional: true - - '@radix-ui/react-use-controllable-state@1.0.0': - resolution: {integrity: sha512-FohDoZvk3mEXh9AWAVyRTYR4Sq7/gavuofglmiXB2g1aKyboUD4YtgWxKj8O5n+Uak52gXQ4wKz5IFST4vtJHg==} - peerDependencies: - react: ^16.8 || ^17.0 || ^18.0 - - '@radix-ui/react-use-controllable-state@1.1.0': - resolution: {integrity: sha512-MtfMVJiSr2NjzS0Aa90NPTnvTSg6C/JLCV7ma0W6+OMV78vd8OyRpID+Ng9LxzsPbLeuBnWBA1Nq30AtBIDChw==} - peerDependencies: - '@types/react': '*' - react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - peerDependenciesMeta: - '@types/react': - optional: true - - '@radix-ui/react-use-controllable-state@1.2.6': - resolution: {integrity: sha512-uEQJGT97ZA/TgP/Hydw47lHu+/vQj6z/0jA+WeTbK1o9Rx45GImjpD0tc3W5ad3D6XTSR6e1yEO0FvGq6WQfVQ==} - peerDependencies: - '@types/react': '*' - react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - peerDependenciesMeta: - '@types/react': - optional: true - - '@radix-ui/react-use-effect-event@0.0.5': - resolution: {integrity: sha512-7cshFL8HGS/7HEiHH+9kL9HBwp2sa9yX18Knwek6KYWmXwM7pegMgta2AXMQKI+rq3JnfSj9x8wYqFMTdG1Jgg==} - peerDependencies: - '@types/react': '*' - react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - peerDependenciesMeta: - '@types/react': - optional: true - - '@radix-ui/react-use-escape-keydown@1.1.0': - resolution: {integrity: sha512-L7vwWlR1kTTQ3oh7g1O0CBF3YCyyTj8NmhLR+phShpyA50HCfBFKVJTpshm9PzLiKmehsrQzTYTpX9HvmC9rhw==} - peerDependencies: - '@types/react': '*' - react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - peerDependenciesMeta: - '@types/react': - optional: true - - '@radix-ui/react-use-escape-keydown@1.1.5': - resolution: {integrity: sha512-ge3ipobwSXTj4JyVtswQ7qZj0ZHdtbGuOno/LrgAAeSxtsJ6Vs4Gz5IkPH2bmqpjcLUFoqGhA/mueuIf63UXlA==} - peerDependencies: - '@types/react': '*' - react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - peerDependenciesMeta: - '@types/react': - optional: true - - '@radix-ui/react-use-is-hydrated@0.1.3': - resolution: {integrity: sha512-umO/aJ+82CpOnhDZUTbILCQf7kU/g0iv+oGs/Q8jw7IkhWBzaEP4sA268PhFAJTFetbwp3ICc6ktpI4TqtxcIw==} - peerDependencies: - '@types/react': '*' - react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - peerDependenciesMeta: - '@types/react': - optional: true - - '@radix-ui/react-use-layout-effect@1.0.0': - resolution: {integrity: sha512-6Tpkq+R6LOlmQb1R5NNETLG0B4YP0wc+klfXafpUCj6JGyaUc8il7/kUZ7m59rGbXGczE9Bs+iz2qloqsZBduQ==} - peerDependencies: - react: ^16.8 || ^17.0 || ^18.0 - - '@radix-ui/react-use-layout-effect@1.1.0': - resolution: {integrity: sha512-+FPE0rOdziWSrH9athwI1R0HDVbWlEhd+FR+aSDk4uWGmSJ9Z54sdZVDQPZAinJhJXwfT+qnj969mCsT2gfm5w==} - peerDependencies: - '@types/react': '*' - react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - peerDependenciesMeta: - '@types/react': - optional: true - - '@radix-ui/react-use-layout-effect@1.1.4': - resolution: {integrity: sha512-K20DkRkUwDnxEYMBPcg3Y6voLkEy5p5QQmszZgLngKKiC7dzBR/aEuK3w1qlx2JWDUNH6FluahYdgR3BP+QbYw==} - peerDependencies: - '@types/react': '*' - react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - peerDependenciesMeta: - '@types/react': - optional: true - - '@radix-ui/react-use-previous@1.1.4': - resolution: {integrity: sha512-XoSLhbRbqxFtgJoi2fNHA3C6pDlY34x508vUpUGoFZfvePfHXHbE1lC4FYFMnJWgiCRroSTw6fOsXQoVS9RwZg==} - peerDependencies: - '@types/react': '*' - react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - peerDependenciesMeta: - '@types/react': - optional: true - - '@radix-ui/react-use-rect@1.1.0': - resolution: {integrity: sha512-0Fmkebhr6PiseyZlYAOtLS+nb7jLmpqTrJyv61Pe68MKYW6OWdRE2kI70TaYY27u7H0lajqM3hSMMLFq18Z7nQ==} - peerDependencies: - '@types/react': '*' - react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - peerDependenciesMeta: - '@types/react': - optional: true - - '@radix-ui/react-use-rect@1.1.4': - resolution: {integrity: sha512-cSOCh6JlkmfjLyNcLiu2nB4v+nm+dkZ+Q5KHWk/soo4U7ZLiEQFKHK9/YmtBHjfCEaU43IBKQOc4/uJmCaiCTQ==} - peerDependencies: - '@types/react': '*' - react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - peerDependenciesMeta: - '@types/react': - optional: true - - '@radix-ui/react-use-size@1.1.0': - resolution: {integrity: sha512-XW3/vWuIXHa+2Uwcc2ABSfcCledmXhhQPlGbfcRXbiUQI5Icjcg19BGCZVKKInYbvUCut/ufbbLLPFC5cbb1hw==} - peerDependencies: - '@types/react': '*' - react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - peerDependenciesMeta: - '@types/react': - optional: true - - '@radix-ui/react-use-size@1.1.4': - resolution: {integrity: sha512-D3anSY15EJoxrihpsXI6SMrmmonnQtR2ni7arO+Lfdg3O95b9hNXxONk8jA5C8ANdF/h5HMAxejgs8PWJ6rlhw==} - peerDependencies: - '@types/react': '*' - react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - peerDependenciesMeta: - '@types/react': - optional: true - - '@radix-ui/react-visually-hidden@1.2.11': - resolution: {integrity: sha512-NFS86RYYZb4/exihaESBGOpMJFz8MGLAfu3mOBSGByVnVPC9JPASfYubxd/8KbkQK0sYAv8lVQDEQukDX/qXvQ==} - peerDependencies: - '@types/react': '*' - '@types/react-dom': '*' - react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - peerDependenciesMeta: - '@types/react': - optional: true - '@types/react-dom': - optional: true - - '@radix-ui/rect@1.1.0': - resolution: {integrity: sha512-A9+lCBZoaMJlVKcRBz2YByCG+Cp2t6nAnMnNba+XiWxnj6r4JUFqfsgwocMBZU9LPtdxC6wB56ySYpc7LQIoJg==} - - '@radix-ui/rect@1.1.3': - resolution: {integrity: sha512-JtyZR+mqgBibTo8xea3B6ZRmzZiM/YeVBtUkas6zMuXjAlfIFIW2FgqeM9eLyvEaYX66vr6DJMK+4U6LV0KhNw==} - - '@rc-component/async-validator@6.0.0': - resolution: {integrity: sha512-D3AGQwdyE58gmvx6waVSXJ80JGO+IY5L2O8HDnSOex7JNlzB3GuN/4hyHNTdhy2qtOhkpbIjmeAN3tL993wKbA==} - engines: {node: '>=14.x'} - - '@rc-component/cascader@1.22.0': - resolution: {integrity: sha512-SffrA57aS9oub3VuI7ajPhJTPtaNxngSvtRhD40Rd8dwJ5vfWPSrVanWgeepdWFGBt7EHftIK5RUU0u3rCTwWw==} - peerDependencies: - react: '>=18.0.0' - react-dom: '>=18.0.0' - - '@rc-component/checkbox@2.0.0': - resolution: {integrity: sha512-3CXGPpAR9gsPKeO2N78HAPOzU30UdemD6HGJoWVJOpa6WleaGB5kzZj3v6bdTZab31YuWgY/RxV3VKPctn0DwQ==} - peerDependencies: - react: '>=16.9.0' - react-dom: '>=16.9.0' - - '@rc-component/collapse@1.2.0': - resolution: {integrity: sha512-ZRYSKSS39qsFx93p26bde7JUZJshsUBEQRlRXPuJYlAiNX0vyYlF5TsAm8JZN3LcF8XvKikdzPbgAtXSbkLUkw==} - peerDependencies: - react: '>=18.0.0' - react-dom: '>=18.0.0' - - '@rc-component/color-picker@3.1.1': - resolution: {integrity: sha512-OHaCHLHszCegdXmIq2ZRIZBN/EtpT6Wm8SG/gpzLATHbVKc/avvuKi+zlOuk05FTWvgaMmpxAko44uRJ3M+2pg==} - peerDependencies: - react: '>=16.9.0' - react-dom: '>=16.9.0' - - '@rc-component/context@2.0.2': - resolution: {integrity: sha512-uiGpAlblCNlziHPwj4S4Iy/oemeuz/hR03mbiEjTCXwsqOIN3BOzsRMyDwpyO5Fm0vIEEJRUf9ZtbRLbhksuTA==} - peerDependencies: - react: '>=18.0.0' - react-dom: '>=18.0.0' - - '@rc-component/dialog@1.10.0': - resolution: {integrity: sha512-eDukNlz9vNszAGv7i3zKXdxEd3wgVmNxuJijYt8zvTh17QwTu8KK/bdURRd/lU4qaMzhO1HKKmMrwOnkaw0BvQ==} - peerDependencies: - react: '>=18.0.0' - react-dom: '>=18.0.0' - - '@rc-component/drawer@1.4.2': - resolution: {integrity: sha512-1ib+fZEp6FBu+YvcIktm+nCQ+Q+qIpwpoaJH6opGr4ofh2QMq+qdr5DLC4oCf5qf3pcWX9lUWPYX652k4ini8Q==} - peerDependencies: - react: '>=18.0.0' - react-dom: '>=18.0.0' - - '@rc-component/dropdown@1.0.3': - resolution: {integrity: sha512-YTST/N6kpqpDz3IMuM/PSSZnrDpSOA6dgHv12gPA90ZTSLv2CoqkZ0+9NtwTY6BeO7dstPblSic2QJg7dSFy/g==} - peerDependencies: - react: '>=16.11.0' - react-dom: '>=16.11.0' - - '@rc-component/form@1.8.6': - resolution: {integrity: sha512-1EXVsSKPZC6kGrIqxVck7kAWM6T65b6et/n5wIcyiaIa41VrDshD0nVLHoBDEIZr2ATQsOP6icb4XQkNFMLBAw==} - engines: {node: '>=8.x'} - peerDependencies: - react: '>=16.9.0' - react-dom: '>=16.9.0' - - '@rc-component/image@1.10.0': - resolution: {integrity: sha512-BjeZCRQ+hw+4WAhvrw8rJvy5fckA2xpf/X2XQEOABUHvLTNB9inB98X3Mp54jYQ7g10DfWERQWHXeC4ylxp1Uw==} - peerDependencies: - react: '>=16.9.0' - react-dom: '>=16.9.0' - - '@rc-component/input-number@1.6.2': - resolution: {integrity: sha512-Gjcq7meZlCOiWN1t1xCC+7/s85humHVokTBI7PJgTfoyw5OWF74y3e6P8PHX104g9+b54jsodFIzyaj6p8LI9w==} - peerDependencies: - react: '>=16.9.0' - react-dom: '>=16.9.0' - - '@rc-component/input@1.3.1': - resolution: {integrity: sha512-iFvTUT9W+JC/MSin2aGAk8NqsVlTzcExNC9DZariON1IWirju9NoNeEk47an4Q8iHazkoVI/y1LnDi88+CPcig==} - peerDependencies: - react: '>=16.0.0' - react-dom: '>=16.0.0' - - '@rc-component/listy@1.2.3': - resolution: {integrity: sha512-IXiMjV5s0rczLBlfh7G5nB4M3365mrEeedjwKtf5I+Ns3PqRUsebR2h5u8CeFarsVfLUPC2I5p0h09TNoOWyvQ==} - peerDependencies: - react: '>=18.0.0' - react-dom: '>=18.0.0' - - '@rc-component/mentions@1.11.0': - resolution: {integrity: sha512-IC2qXuEBMFHxPIXEFfYWj6Sr7UiDZnOqJHCYQBbwPzopBJOPZIR6mV9U4QH1bYQRlKYlYnIsajWDMgVGgWQyWQ==} - peerDependencies: - react: '>=16.9.0' - react-dom: '>=16.9.0' - - '@rc-component/menu@1.4.1': - resolution: {integrity: sha512-3GsVRoQ4cnF/AoIQ4P+Z1haBfgfBPQfLT1RJY3Nu4DzOnheTslfCiGSPj7bv/cLj5sW5pHqN25dDXGP3JELAlQ==} - peerDependencies: - react: '>=16.9.0' - react-dom: '>=16.9.0' - - '@rc-component/mini-decimal@1.1.4': - resolution: {integrity: sha512-xiuXcaCwyOWpD8a8scdExFl+bntNphAW8XeenL1ig2en0AAZY0Pcp4pC0dI22qJ+NvxKn9RoNIoRdqYU3BLH4w==} - engines: {node: '>=8.x'} - - '@rc-component/motion@1.3.3': - resolution: {integrity: sha512-Xh3IszxvlSv3/PLYFyC2UZi9LNB83yOnkB/LNmRzaypZLvkhqUIPS7MQpGZcCMWrNsXV2p6YTSWbSGvFpEle9A==} - peerDependencies: - react: '>=16.9.0' - react-dom: '>=16.9.0' - - '@rc-component/mutate-observer@2.0.1': - resolution: {integrity: sha512-AyarjoLU5YlxuValRi+w8JRH2Z84TBbFO2RoGWz9d8bSu0FqT8DtugH3xC3BV7mUwlmROFauyWuXFuq4IFbH+w==} - engines: {node: '>=8.x'} - peerDependencies: - react: '>=16.9.0' - react-dom: '>=16.9.0' - - '@rc-component/notification@2.0.7': - resolution: {integrity: sha512-nqZzpf6BPdaj+3ILx7si79LLmqPKyUmQoXa+/9gg0SkH0v1DbD66oJgRMSBEVnd/zUT3D4gwxWIHUKebYf2ZXQ==} - engines: {node: '>=8.x'} - peerDependencies: - react: '>=18.0.0' - react-dom: '>=18.0.0' - - '@rc-component/overflow@1.0.1': - resolution: {integrity: sha512-syfmgAABaHCnCDzPwHZ/2tuvIcpOO3jefYZMmfkN+pmo8HKTzsfhS57vxo4ksPdN0By+uWVJhJWNFozNBxi2eA==} - peerDependencies: - react: '>=16.9.0' - react-dom: '>=16.9.0' - - '@rc-component/pagination@1.4.0': - resolution: {integrity: sha512-CW1g7P9V8u+e8JQdUsl2RWg+GCsoee0mtJjZUCCxn/vb3jzOwDKm6hAdwddHCVBfWJ58eGUBZz3IvnU8rRktjw==} - peerDependencies: - react: '>=16.9.0' - react-dom: '>=16.9.0' - - '@rc-component/picker@1.12.0': - resolution: {integrity: sha512-0FZGgDiDZFMm2hcfGv4jyjm7yWIj2MiwfeTzLL0vWkO+8cCSKdiM9XhWpkn1aAsoI2EwUy48Oz2cGfvL8ZKrfw==} - engines: {node: '>=12.x'} - peerDependencies: - date-fns: '>= 2.x' - dayjs: '>= 1.x' - luxon: '>= 3.x' - moment: '>= 2.x' - react: '>=16.9.0' - react-dom: '>=16.9.0' - peerDependenciesMeta: - date-fns: - optional: true - dayjs: - optional: true - luxon: - optional: true - moment: - optional: true - - '@rc-component/portal@1.1.0': - resolution: {integrity: sha512-tbXM9SB1r5FOuZjRCljERFByFiEUcMmCWMXLog/NmgCzlAzreXyf23Vei3ZpSMxSMavzPnhCovfZjZdmxS3d1w==} - engines: {node: '>=8.x'} - peerDependencies: - react: '>=16.9.0' - react-dom: '>=16.9.0' - - '@rc-component/portal@2.2.1': - resolution: {integrity: sha512-ck+r1kW/JSv0wxPji3KN2ss9K6Z0qqwusw/mf/0JobXhZ8hC2ejZwCJObW/SvDi0uhA0VzmCnx0CaCci95tcmA==} - engines: {node: '>=12.x'} - peerDependencies: - react: '>=18.0.0' - react-dom: '>=18.0.0' - - '@rc-component/progress@1.0.2': - resolution: {integrity: sha512-WZUnH9eGxH1+xodZKqdrHke59uyGZSWgj5HBM5Kwk5BrTMuAORO7VJ2IP5Qbm9aH3n9x3IcesqHHR0NWPBC7fQ==} - peerDependencies: - react: '>=16.9.0' - react-dom: '>=16.9.0' - - '@rc-component/qrcode@2.0.0': - resolution: {integrity: sha512-aAv3QhPP1xyafuTZOxub6a54pCeBnN3IwQkpETrBtthq4BL5IgxnCbuoBWPDpdLw1y1j6BgBUCAKV92+yX06Dw==} - engines: {node: '>=8.x'} - peerDependencies: - react: '>=16.9.0' - react-dom: '>=16.9.0' - - '@rc-component/rate@1.0.1': - resolution: {integrity: sha512-bkXxeBqDpl5IOC7yL7GcSYjQx9G8H+6kLYQnNZWeBYq2OYIv1MONd6mqKTjnnJYpV0cQIU2z3atdW0j1kttpTw==} - engines: {node: '>=8.x'} - peerDependencies: - react: '>=16.9.0' - react-dom: '>=16.9.0' - - '@rc-component/resize-observer@1.1.2': - resolution: {integrity: sha512-t/Bb0W8uvL4PYKAB3YcChC+DlHh0Wt5kM7q/J+0qpVEUMLe7Hk5zuvc9km0hMnTFPSx5Z7Wu/fzCLN6erVLE8Q==} - peerDependencies: - react: '>=16.9.0' - react-dom: '>=16.9.0' - - '@rc-component/segmented@1.3.0': - resolution: {integrity: sha512-5J/bJ01mbDnoA6P/FW8SxUvKn+OgUSTZJPzCNnTBntG50tzoP7DydGhqxp7ggZXZls7me3mc2EQDXakU3iTVFg==} - peerDependencies: - react: '>=16.0.0' - react-dom: '>=16.0.0' - - '@rc-component/select@1.10.1': - resolution: {integrity: sha512-H+yQsl+qED9NilQ3g6zdpsMwUgwVjrcMTkNHAWRVU/MoNCYgTbDgU+MIMgZDK+rVdd2JUfI/MkysMcZZ0cyQKw==} - engines: {node: '>=8.x'} - peerDependencies: - react: '*' - react-dom: '*' - - '@rc-component/slider@1.1.1': - resolution: {integrity: sha512-LSzgWGYDgeCDgR4r1XlU29gbYws6HpLnvJd/uMhLeW/vQgxldeR+Wb4uzHDCHiYEbr1bnEHWdjkPxjJRHxuiig==} - engines: {node: '>=8.x'} - peerDependencies: - react: '>=16.9.0' - react-dom: '>=16.9.0' - - '@rc-component/steps@1.2.2': - resolution: {integrity: sha512-/yVIZ00gDYYPHSY0JP+M+s3ZvuXLu2f9rEjQqiUDs7EcYsUYrpJ/1bLj9aI9R7MBR3fu/NGh6RM9u2qGfqp+Nw==} - engines: {node: '>=8.x'} - peerDependencies: - react: '>=16.9.0' - react-dom: '>=16.9.0' - - '@rc-component/switch@1.0.3': - resolution: {integrity: sha512-Jgi+EbOBquje/XNdofr7xbJQZPYJP+BlPfR0h+WN4zFkdtB2EWqEfvkXJWeipflwjWip0/17rNbxEAqs8hVHfw==} - peerDependencies: - react: '>=16.9.0' - react-dom: '>=16.9.0' - - '@rc-component/table@1.11.1': - resolution: {integrity: sha512-OWdS6DMmeWb7bJBGqPxYZpQbzBlBiXZUu2sqo6Ii7Sjs9GeK1IsrXrWk26SL2c6KEseabswdxrRj7WUm9LdECw==} - engines: {node: '>=8.x'} - peerDependencies: - react: '>=18.0.0' - react-dom: '>=18.0.0' - - '@rc-component/tabs@1.12.0': - resolution: {integrity: sha512-XL7Kqy5fnUE2WTlO1/fCGrrfNlGFebdr7JseGkEIjzcVMAtIFQJ8sqCSOmxcXstjU6fonD/4rnhZHxj7sDTajQ==} - engines: {node: '>=8.x'} - peerDependencies: - react: '>=16.9.0' - react-dom: '>=16.9.0' - - '@rc-component/tooltip@1.5.0': - resolution: {integrity: sha512-agQ/+mBqrEQfTX4D3KhQ7j+ZbX4/VHjoJ7Noa2wIdZ1/FbQTOd7Sn92rp+jtCoqAVTLUgSOydePIgZ204gi2EQ==} - peerDependencies: - react: '>=18.0.0' - react-dom: '>=18.0.0' - - '@rc-component/tour@2.4.0': - resolution: {integrity: sha512-aui4r4TqmTzwaBgcQxHYep8kM8PTjZFufjokObpy35KfFeZ0k9ArquWFZqegQlH24P14t+F0qO0mGTgzlav1yg==} - engines: {node: '>=8.x'} - peerDependencies: - react: '>=16.9.0' - react-dom: '>=16.9.0' - - '@rc-component/tree-select@1.16.1': - resolution: {integrity: sha512-a1Oi6EJhqAhdOxxupdJi6fP0RPHMKn5TcfkX2+llaQ4lF4nwfH7b6SCHcnsybaa2s+pk1yZYwVyeOYkDnEBRdg==} - peerDependencies: - react: '*' - react-dom: '*' - - '@rc-component/tree@1.4.0': - resolution: {integrity: sha512-dGsJGDJQedA0BqqVgj3F8BvHXTSZijyhTXdbAdkcx8lynzZkty/CV3Z3LOm/fxz+BCfl3dfGiAQpb7Q5XNvl0Q==} - engines: {node: '>=10.x'} - peerDependencies: - react: '*' - react-dom: '*' - - '@rc-component/trigger@2.3.1': - resolution: {integrity: sha512-ORENF39PeXTzM+gQEshuk460Z8N4+6DkjpxlpE7Q3gYy1iBpLrx0FOJz3h62ryrJZ/3zCAUIkT1Pb/8hHWpb3A==} - engines: {node: '>=8.x'} - peerDependencies: - react: '>=16.9.0' - react-dom: '>=16.9.0' - - '@rc-component/trigger@3.10.1': - resolution: {integrity: sha512-mXlDN0IXdtV8Yqqm8195ECCyrbmfvvfKvwVvSlH0+qvKD6BUF8gRhEjSy0FOcD1+CcDRHgTiX99LoxfQrmh3Cw==} - engines: {node: '>=8.x'} - peerDependencies: - react: '>=18.0.0' - react-dom: '>=18.0.0' - - '@rc-component/upload@1.1.1': - resolution: {integrity: sha512-GvYWSKeaJTOxxC5p6+nOSadzfvXA1h8C/iHFPFZX+szH3JUXrvs+DLiW8YUTBgvMh8m63mJeHrlYlJzAlg+pDA==} - peerDependencies: - react: '>=16.9.0' - react-dom: '>=16.9.0' - - '@rc-component/util@1.12.0': - resolution: {integrity: sha512-AEjPL8JVdohIITaiXokyjL9WQ6tKWWjAYK9QU16tGNE9JaQABBQy+hA4H2Lup5MgXy9yY3iLrbZJheuU13hTdQ==} - peerDependencies: - react: '>=18.0.0' - react-dom: '>=18.0.0' - - '@rc-component/virtual-list@1.5.1': - resolution: {integrity: sha512-boqHxdtyWC88u8quYgEO49bcBy5fzRiOcnBge+N4nLzs2k8hUQ/yw7JE9dM6yCBE4jSm5YSHVCVMS+suBuJGKA==} - engines: {node: '>=8.x'} - peerDependencies: - react: '>=18.0.0' - react-dom: '>=18.0.0' - - '@react-three/drei@10.7.8': - resolution: {integrity: sha512-rJXyuzLm2Xq0kafHuR47ajDGbOe/pEhzIr4m8E8zwzQs0iNjloFDqBwRhrXmP/w+onLeYyN3EYPFW/cwWK/4yA==} - peerDependencies: - '@react-three/fiber': ^9.0.0 - react: ^19 - react-dom: ^19 - three: '>=0.159' - peerDependenciesMeta: - react-dom: - optional: true - - '@react-three/fiber@9.7.0': - resolution: {integrity: sha512-EWm9FwcaOZQu/ExFW5rggoCMM1NJet5YbxVxKaOE+KSncrjU0Wx7017qSyGFvupviK89nMYGCWU3BIK4dI1clw==} - peerDependencies: - expo: '>=43.0' - expo-asset: '>=8.4' - expo-file-system: '>=11.0' - expo-gl: '>=11.0' - react: '>=19 <19.3' - react-dom: '>=19 <19.3' - react-native: '>=0.78' - three: '>=0.156' - peerDependenciesMeta: - expo: - optional: true - expo-asset: - optional: true - expo-file-system: - optional: true - expo-gl: - optional: true - react-dom: - optional: true - react-native: - optional: true - - '@react-types/shared@3.36.1': - resolution: {integrity: sha512-AzsuD9OfxTOZMMvTRhlN3oHBwOmFN7tDh27LzqmHt4+uOgPhJT7ZM7/kVs/8/o0WxayMUIk3hBmCFRHv1FUoag==} - peerDependencies: - react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 - - '@reduxjs/toolkit@2.12.0': - resolution: {integrity: sha512-KiT+RzZbp6mQET+Mg+h2c97+9j1sNflUxQkIHI7Yuzf6Peu+OYpmkn6nbHWmLLWj+1ZODUJFwGZ7gx3L9R9EOw==} - peerDependencies: - react: ^16.9.0 || ^17.0.0 || ^18 || ^19 - react-redux: ^7.2.1 || ^8.1.3 || ^9.0.0 - peerDependenciesMeta: - react: - optional: true - react-redux: - optional: true - - '@rolldown/binding-android-arm64@1.2.4': - resolution: {integrity: sha512-jHC2cnyKz5xU2fhECtFl8OZ83cYNt13GZQD+0uMJ/X3o+ijmd56okHhTUwxVSHPx1IRVIJEZ1/1pPzeLCU6XKA==} - engines: {node: ^20.19.0 || >=22.12.0} - cpu: [arm64] - os: [android] - - '@rolldown/binding-darwin-arm64@1.2.4': - resolution: {integrity: sha512-Dc5mPD8F5F/FS8i01syd7FTF6yB2fVthH/TRkjwJkzUK6EpoxHtqvZQP5Zwq80/5z19TWYHIg1KOHboCgVx/aQ==} - engines: {node: ^20.19.0 || >=22.12.0} - cpu: [arm64] - os: [darwin] - - '@rolldown/binding-darwin-x64@1.2.4': - resolution: {integrity: sha512-fpDm4oBo6SqLvWUYCmFhdde3U9KH2fRNNMeAnAPAIwxRL345xutL0EtEUcuoxsoazdJGv/MuDBQHlCDrtbvqOg==} - engines: {node: ^20.19.0 || >=22.12.0} - cpu: [x64] - os: [darwin] - - '@rolldown/binding-freebsd-x64@1.2.4': - resolution: {integrity: sha512-rSJoreDE/HoIzoaib6MTp5jQtCTdMHKIvItAKT/ImS6Y6Ww76oUaeMyp4Vc/fAgd/ehji068IxetHXAnqUwN9A==} - engines: {node: ^20.19.0 || >=22.12.0} - cpu: [x64] - os: [freebsd] - - '@rolldown/binding-linux-arm-gnueabihf@1.2.4': - resolution: {integrity: sha512-/jm8OGHgn7oGaJu3i/qZI9spUGcJ+y/lk43ttQ/iO1tOd9NissG6o97bighBCiL+BKRngmcDuR6ikfwYdJmVuQ==} - engines: {node: ^20.19.0 || >=22.12.0} - cpu: [arm] - os: [linux] - - '@rolldown/binding-linux-arm64-gnu@1.2.4': - resolution: {integrity: sha512-tIP06BeD9EqvECBrPZ+sqdPlYrT+aYaAiu1wYziVx5elRK/ftm33JxVDy2bXGbr6J0CrtirCkR87/X5a2euEng==} - engines: {node: ^20.19.0 || >=22.12.0} - cpu: [arm64] - os: [linux] - - '@rolldown/binding-linux-arm64-musl@1.2.4': - resolution: {integrity: sha512-Ql1Q0EQqVThvn9VAVlwNzsUvbSFtCMGjLpRRi4pk5i7NZZ4n5ISiLMjHYtus4VQ2PvkSw24zyaCVsiS+sXPj1w==} - engines: {node: ^20.19.0 || >=22.12.0} - cpu: [arm64] - os: [linux] - - '@rolldown/binding-linux-ppc64-gnu@1.2.4': - resolution: {integrity: sha512-GjbjXD4XXfN19D0LZNbmiCBUoDiRACsYHr0yaIbbn8aFsXjHZifcYqu/W5Er5X2X990WjHXFrxarn5chzItorQ==} - engines: {node: ^20.19.0 || >=22.12.0} - cpu: [ppc64] - os: [linux] - - '@rolldown/binding-linux-s390x-gnu@1.2.4': - resolution: {integrity: sha512-p5WR0NOwaRmJ/B1b6IjEFLLivwEsf3PrdBIhRbhTCQisbo2SvHHpG4ELB/+FgQNnB88LTOF86upmJmbvZdQ2lw==} - engines: {node: ^20.19.0 || >=22.12.0} - cpu: [s390x] - os: [linux] - - '@rolldown/binding-linux-x64-gnu@1.2.4': - resolution: {integrity: sha512-4/GyVjmhR+Tc6HLJvwc1sOhPqAZtySiSMesOZyX6JQ5XBxoTDEMKQzvo07NIK6nTon/SivlZqvhzvuVBNQhObQ==} - engines: {node: ^20.19.0 || >=22.12.0} - cpu: [x64] - os: [linux] - - '@rolldown/binding-linux-x64-musl@1.2.4': - resolution: {integrity: sha512-l9eeLsCNvPpmSXUej0etw/J1eqV0Jj1D5G/xG6YTijmE6dkv6E2QezgWbTfQk63v952DPqrjOCoiqxq7Bw0YUQ==} - engines: {node: ^20.19.0 || >=22.12.0} - cpu: [x64] - os: [linux] - - '@rolldown/binding-openharmony-arm64@1.2.4': - resolution: {integrity: sha512-e0F355MSTMm3+UOqtV3L24gFUp2N5m1f8L/7d56deik6va+AXdrt9F8LbzGpeWGWRbZEDq4m8NVnJDeBtf9DZg==} - engines: {node: ^20.19.0 || >=22.12.0} - cpu: [arm64] - os: [openharmony] - - '@rolldown/binding-win32-arm64-msvc@1.2.4': - resolution: {integrity: sha512-AWLi0uBRYh6QlE7OKhiz+phZC0qwtij2QZmhmOdsLdFn64m7oMpooE9ICE3lhm9xMb4SpDo2WbHcxX1iFLFtqw==} - engines: {node: ^20.19.0 || >=22.12.0} - cpu: [arm64] - os: [win32] - - '@rolldown/binding-win32-x64-msvc@1.2.4': - resolution: {integrity: sha512-UwSDJOg3dqCAejWdxclJjCsh3Qq4vLYMDxmyHqo1btz3stK2VqgwNd3mm5tuIwzSlGIQ/1H9Hr+Zn09mrezNqQ==} - engines: {node: ^20.19.0 || >=22.12.0} - cpu: [x64] - os: [win32] - - '@rolldown/pluginutils@1.0.1': - resolution: {integrity: sha512-2j9bGt5Jh8hj+vPtgzPtl72j0yRxHAyumoo6TNfAjsLB04UtpSvPbPcDcBMxz7n+9CYB0c1GxQFxYRg2jimqGw==} - - '@sec-ant/readable-stream@0.4.1': - resolution: {integrity: sha512-831qok9r2t8AlxLko40y2ebgSDhenenCatLVeW/uBtnHPyhHOvG0C7TvfgecV+wHzIm5KUICgzmVpWS+IMEAeg==} - - '@shikijs/core@3.23.0': - resolution: {integrity: sha512-NSWQz0riNb67xthdm5br6lAkvpDJRTgB36fxlo37ZzM2yq0PQFFzbd8psqC2XMPgCzo1fW6cVi18+ArJ44wqgA==} - - '@shikijs/core@4.4.3': - resolution: {integrity: sha512-QCR4q2ZO/ILJEuwiBMel4wdcTDb1JGwfjKTxPDF6x8ixOaluPrVqIn06C99AcRPhmYlBR56d/Fb+GN58GzExpg==} - engines: {node: '>=20'} - - '@shikijs/engine-javascript@3.23.0': - resolution: {integrity: sha512-aHt9eiGFobmWR5uqJUViySI1bHMqrAgamWE1TYSUoftkAeCCAiGawPMwM+VCadylQtF4V3VNOZ5LmfItH5f3yA==} - - '@shikijs/engine-javascript@4.4.3': - resolution: {integrity: sha512-FbOjFJp9VLdo1Wevs10BBtVxiTWwNLqZh5Gkhjgda/ioL15YOgeSl9n+6XMa3qRlPQzfhFNe641SrynFHYG0nQ==} - engines: {node: '>=20'} - - '@shikijs/engine-oniguruma@3.23.0': - resolution: {integrity: sha512-1nWINwKXxKKLqPibT5f4pAFLej9oZzQTsby8942OTlsJzOBZ0MWKiwzMsd+jhzu8YPCHAswGnnN1YtQfirL35g==} - - '@shikijs/engine-oniguruma@4.4.3': - resolution: {integrity: sha512-EcOQkxdxGQrc1Row/cC2c96/v1dbZqGnEVu1qTuT/MJmp6+cXCvQussowVmCv5Tqr3KuY3c7IbM6HTW3LJ1k9w==} - engines: {node: '>=20'} - - '@shikijs/langs@3.23.0': - resolution: {integrity: sha512-2Ep4W3Re5aB1/62RSYQInK9mM3HsLeB91cHqznAJMuylqjzNVAVCMnNWRHFtcNHXsoNRayP9z1qj4Sq3nMqYXg==} - - '@shikijs/langs@4.4.3': - resolution: {integrity: sha512-ePic0yfAJGOF83D5wBHK/00EjK65oahBYxFk5epgq33WRv7X9UuxLEV8PtR0szC0z8dl7INIpIodB99JRFlR+A==} - engines: {node: '>=20'} - - '@shikijs/primitive@4.4.3': - resolution: {integrity: sha512-m0wBeLDQDeIxRdUmrCPdQqfuUamDwRL5isCfYbguKD6NiaKpVbsv+3J81DyIKgNW5h4WAIIr8T4EkgQrBBxvaQ==} - engines: {node: '>=20'} - - '@shikijs/stream@4.4.3': - resolution: {integrity: sha512-nG30byPDdyua1R9danJjQjv60PlNHTi1q2O0tzW+ocAFqb63Y/5DK5URpbuGt0jEidpRNx3+OgJ+aEMUBWqRNA==} - engines: {node: '>=20'} - peerDependencies: - react: ^19.0.0 - solid-js: ^1.9.0 - svelte: ^5.0.0-0 - vue: ^3.2.0 - peerDependenciesMeta: - react: - optional: true - solid-js: - optional: true - svelte: - optional: true - vue: - optional: true - - '@shikijs/themes@3.23.0': - resolution: {integrity: sha512-5qySYa1ZgAT18HR/ypENL9cUSGOeI2x+4IvYJu4JgVJdizn6kG4ia5Q1jDEOi7gTbN4RbuYtmHh0W3eccOrjMA==} - - '@shikijs/themes@4.4.3': - resolution: {integrity: sha512-w8UHjeUnIR965KMWJHUPXOc2mNJUnK3vpVLYLvw5IYU2mnTTJ89E24OrJDBNiJDQ0qzb0tc4l7mrIXx5cFeIyw==} - engines: {node: '>=20'} - - '@shikijs/transformers@4.4.3': - resolution: {integrity: sha512-oJSARV6NaWd+rnNJbtnpAdj3Zg0ZVyzsnMgb3vi3HA+35y8lBWUCpOnWsmyiXZIikY+x1BDqrQUgmxfzWh7Jvw==} - engines: {node: '>=20'} - - '@shikijs/types@3.23.0': - resolution: {integrity: sha512-3JZ5HXOZfYjsYSk0yPwBrkupyYSLpAE26Qc0HLghhZNGTZg/SKxXIIgoxOpmmeQP0RRSDJTk1/vPfw9tbw+jSQ==} - - '@shikijs/types@4.4.3': - resolution: {integrity: sha512-UEJxmRR++MAGR6hugn0vgVS2W/6lWAts84FFSrnlH9sP0LNol7E5+NQ792pH8liWUhyMyjhTgSUH3k7iD7tc5g==} - engines: {node: '>=20'} - - '@shikijs/vscode-textmate@10.0.2': - resolution: {integrity: sha512-83yeghZ2xxin3Nj8z1NMd/NCuca+gsYXswywDy5bHvwlWL8tpTQmzGeUuHd9FC3E/SBEMvzJRwWEOz5gGes9Qg==} - - '@sindresorhus/merge-streams@4.0.0': - resolution: {integrity: sha512-tlqY9xq5ukxTUZBmoOp+m61cqwQD5pHJtFY3Mn8CA8ps6yghLH/Hw8UPdqg4OLmFW3IFlcXnQNmo/dh8HzXYIQ==} - engines: {node: '>=18'} - - '@splinetool/runtime@1.12.98': - resolution: {integrity: sha512-UWZH/+4XtNgMMgmDiWhZS55WOwUDGuTj4uH6fiZV6Jol3d3v3z1RRBObspx8GMio0HajOYGg7FJ8TZdyBdpW4A==} - - '@standard-schema/spec@1.1.0': - resolution: {integrity: sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w==} - - '@standard-schema/utils@0.3.0': - resolution: {integrity: sha512-e7Mew686owMaPJVNNLs55PUvgz371nKgwsc4vxE49zsODpJEnxgxRo2y/OKrqueavXgZNMDVj3DdHFlaSAeU8g==} - - '@stitches/react@1.2.8': - resolution: {integrity: sha512-9g9dWI4gsSVe8bNLlb+lMkBYsnIKCZTmvqvDG+Avnn69XfmHZKiaMrx7cgTaddq7aTPPmXiTsbFcUy0xgI4+wA==} - peerDependencies: - react: '>= 16.3.0' - - '@streamdown/code@1.1.1': - resolution: {integrity: sha512-i7HTNuDgZWb+VdrNVOam9gQhIc5MSSDXKWXgbUrn/4vSRaSMM+Rtl10MQj4wLWPNpF7p80waJsAqFP8HZfb0Jg==} - peerDependencies: - react: ^18.0.0 || ^19.0.0 - - '@swc/helpers@0.5.23': - resolution: {integrity: sha512-5lSsMOTXURePglDfvuAQUqkGek9Hg2kksOYay2m0+XR++b2NWYL/4sWyuvVBIs8oKnJaxkdi9whaL/sqN13afw==} - - '@tailwindcss/node@4.3.3': - resolution: {integrity: sha512-/T8IKEsf9VTU6tLjgC7+sv2mOPtQxzE2jMw7u4Tt40Tx+QSZxpzh95/H6cMKoja9XuW7iMdLJYBB0o9G1CaAgg==} - - '@tailwindcss/oxide-android-arm64@4.3.3': - resolution: {integrity: sha512-Y85A2gmPSkl5Ve5qR86GL4HT509cFqQh1aes9p3sSkyTPwt0Pppf3GkwGe4JPACcRYjgJIEhQgM6dBClnr0NYw==} - engines: {node: '>= 20'} - cpu: [arm64] - os: [android] - - '@tailwindcss/oxide-darwin-arm64@4.3.3': - resolution: {integrity: sha512-BiaWatpBcERQFDlOjRDpIVXuFK5PJez5SA4JMg6VYZdBYU+qKfV/vqjcIs+IYmtitf1xYQZTwXvU/8y4lfZUGw==} - engines: {node: '>= 20'} - cpu: [arm64] - os: [darwin] - - '@tailwindcss/oxide-darwin-x64@4.3.3': - resolution: {integrity: sha512-fAeUqfV5ndhxRwai8cXGzdLvul9utWOmeTkv69unv4ZXixjn61Z+p9lCWdwOwA3TYboG3BwdVuN/RDjhBRl0mw==} - engines: {node: '>= 20'} - cpu: [x64] - os: [darwin] - - '@tailwindcss/oxide-freebsd-x64@4.3.3': - resolution: {integrity: sha512-iyf5bV6+wnAlflVeEy7R25dupxTNECZN5QMI0qNT6eT+EgaGdZcKhGkr5SdoaWiLJ3spLqIY9VCeSGrwmtg4kw==} - engines: {node: '>= 20'} - cpu: [x64] - os: [freebsd] - - '@tailwindcss/oxide-linux-arm-gnueabihf@4.3.3': - resolution: {integrity: sha512-aAYUprJAJQWWbRrPvtjdroZ56Md+JM8pMiopS6xGEwDfLhqj+2ver2p4nU4Mb3CRqcMmNBjo8KkUgcxhkzVQGQ==} - engines: {node: '>= 20'} - cpu: [arm] - os: [linux] - - '@tailwindcss/oxide-linux-arm64-gnu@4.3.3': - resolution: {integrity: sha512-nDxldcEENOxZRzC2uu9jrutZdAAQtb+8WWDCSnWL1zvBk1+FN+x6MtDViPB5AJMfttVCUhehGWus3XBPgatM/w==} - engines: {node: '>= 20'} - cpu: [arm64] - os: [linux] - - '@tailwindcss/oxide-linux-arm64-musl@4.3.3': - resolution: {integrity: sha512-Md44bD6veX/PC5iyF8cDVnw4HBIANZepRZZ7a8DQOvkfo5WUBwcp6iAuCUz23u+4SUkhJlD3eL7hNdW8ezd/kA==} - engines: {node: '>= 20'} - cpu: [arm64] - os: [linux] - - '@tailwindcss/oxide-linux-x64-gnu@4.3.3': - resolution: {integrity: sha512-tx7us1muwOKAKWao2v/GaafFeQboE6aj88vC6ziN2NCGcRm8gWUhwjzg+YdVB1e4boAtdtma4L43onunI6NS4w==} - engines: {node: '>= 20'} - cpu: [x64] - os: [linux] - - '@tailwindcss/oxide-linux-x64-musl@4.3.3': - resolution: {integrity: sha512-SJxX60smvHgasZoBy11dX6YRjXJFovwWBoedhbQPOBzgFWBHGB+TVPWB9BxzR7TTxU8FQZAI2AyiNCMzFm8Img==} - engines: {node: '>= 20'} - cpu: [x64] - os: [linux] - - '@tailwindcss/oxide-wasm32-wasi@4.3.3': - resolution: {integrity: sha512-jx1+rPhY/5Ympkktd656HBWEBLxP7dH06losBLjjf5vgCODXvi9KhtftWcMIwTFIDqBr7cRnQkdLnAG+IOlGvQ==} - engines: {node: '>=14.0.0'} - cpu: [wasm32] - bundledDependencies: - - '@napi-rs/wasm-runtime' - - '@emnapi/core' - - '@emnapi/runtime' - - '@tybys/wasm-util' - - '@emnapi/wasi-threads' - - tslib - - '@tailwindcss/oxide-win32-arm64-msvc@4.3.3': - resolution: {integrity: sha512-3rc292Ca2ceK6Ulcc/bAVnTs/3nDtoPhyEKlgPv+yQJQi/JS/AMJlqzxvlDacL1nekbrcf6bTqp/jV4qgnPxNQ==} - engines: {node: '>= 20'} - cpu: [arm64] - os: [win32] - - '@tailwindcss/oxide-win32-x64-msvc@4.3.3': - resolution: {integrity: sha512-yJ0pwIVc/nYeGoV02WtsN8KYyLQv7kyI2wDnkezyJlGGjkd4QLwDGAwl47YpPJeuI0M0ObaXGSPjvWDPeTPggw==} - engines: {node: '>= 20'} - cpu: [x64] - os: [win32] - - '@tailwindcss/oxide@4.3.3': - resolution: {integrity: sha512-krXjAikiaFSPaK/FkAQT5UTx3VormQaiZ5hBFlJZ9UFQGB/rwg1MZIhHAG9smMQRTdyJxP6Qt5MwMtdyU5FWrA==} - engines: {node: '>= 20'} - - '@tailwindcss/postcss@4.3.3': - resolution: {integrity: sha512-JTSZZGQi1AyKirbLN3azmjVzef92tcX7h+iSqPdaeStyFpGpDlKvvpxeOE8njhbUanbRwr3z8DyzhICWnMtQeg==} - - '@tanstack/query-core@5.101.4': - resolution: {integrity: sha512-gNwcvOJcRbLWPOLG/2OBm+zM+Yv+MKsXKEOWC57USuZDEsI71hEErQsiEGx5wX9rzWWkfwM0fVSPoiIFSsxfiw==} - - '@tanstack/react-query@5.101.4': - resolution: {integrity: sha512-yRg2pfOCxIs4ZJW3XYYHU/WgtD04FHSnfHlpRT7h7pR77hwkdRG4wxbKe4aq6P0RvXUTBSQpQeadS1SUYUe+KA==} - peerDependencies: - react: ^18 || ^19 - - '@tanstack/react-virtual@3.14.9': - resolution: {integrity: sha512-qZyr0FZDP8rDC4WBhsryIZmAd9bveJvFGUJJtskWaew6/0dTRS6wZxnR6VQ5bY2KwL3LjerrHqQLk3a0GKcPXQ==} - peerDependencies: - react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 - react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 - - '@tanstack/virtual-core@3.17.7': - resolution: {integrity: sha512-bp+v10y65sp2H7WpWfIMyxTNfl8ZVfxFTLRjPIFRryi6FV/J33z4IS53WO4pTk36KlvJ4iLiQz+oaydDC1xbcA==} - - '@tiptap/core@3.28.0': - resolution: {integrity: sha512-gUuD5WAYfbDxNSSJya/emh2KSzXZXLUYKW4fEnc1AQ5FE2twzh4LJ9UlKFIawigrUCAksWI5Fy1hRbv+5m4ZdQ==} - peerDependencies: - '@tiptap/pm': 3.28.0 - - '@tiptap/extension-blockquote@3.28.0': - resolution: {integrity: sha512-y8Xi6Z3AQLXedz0J8Z3kDsu7SKBmL50gKVXx3RK1Oo1cuCGhNChm3syChkAz3PLAbrPUM5rvJDSZdF2jUrDnng==} - peerDependencies: - '@tiptap/core': 3.28.0 - '@tiptap/pm': 3.28.0 - - '@tiptap/extension-bold@3.28.0': - resolution: {integrity: sha512-JhZQmr0AU741bOwjVMfuwJdK4g0TQwwPbeca9aqKHv5zvZw4i4G9G6fESVyMFc3Yag1ffpnq5EtNldHKTnMhlw==} - peerDependencies: - '@tiptap/core': 3.28.0 - - '@tiptap/extension-bubble-menu@3.28.0': - resolution: {integrity: sha512-7AUNoHj2K4XLKCgW4uspeD/ENejPu2BeHvLTsiOoIO+XXDNSi2j0bbaIS8f2s/qnFirscwp/sbznp1qph/76qg==} - peerDependencies: - '@tiptap/core': 3.28.0 - '@tiptap/pm': 3.28.0 - - '@tiptap/extension-bullet-list@3.28.0': - resolution: {integrity: sha512-dSVuNiH7PFMmWGkNER9vfUb5bXUHDARvHbJ3l+APEb+ZiusVN1KFdM3nd/RsW/Rt5Gd3XwlIEU/c2Uf7cxYDcw==} - peerDependencies: - '@tiptap/extension-list': 3.28.0 - - '@tiptap/extension-character-count@3.28.0': - resolution: {integrity: sha512-eKo/6e16ZiACrK/lbuj6BAqXb6IboG3RKQ9Z+2SPCV6KTpPGsFdIeqvBuYJsHPNeCtGufz1T9vpJbmgm+fcjqQ==} - peerDependencies: - '@tiptap/extensions': 3.28.0 - - '@tiptap/extension-code-block@3.28.0': - resolution: {integrity: sha512-bHITDzT0umTLh+SiEVQzIXkCMt/TzbPM1+HQy9ZxgQDHAJj/vdmfNAnP3RCOrz4lETXyhNQ2b6kxeu3lWckxyA==} - peerDependencies: - '@tiptap/core': 3.28.0 - '@tiptap/pm': 3.28.0 - - '@tiptap/extension-code@3.28.0': - resolution: {integrity: sha512-AUw2Acof3CQE6Q6Y22saK4lyg0nkeJ4XXniU65TdAi/9TE66TGiO4NQJJNNPgu8+VMEwl5j8VsIFK8sfTcNYtg==} - peerDependencies: - '@tiptap/core': 3.28.0 - - '@tiptap/extension-color@3.28.0': - resolution: {integrity: sha512-YbUMUdrgU04GDYoii1wuFVkiTSQoaEQh04kwgQayEQ46aUBEnnaJitWdp39aGMWYTIXD/c9K9cr5Vg8GSKcVxA==} - peerDependencies: - '@tiptap/extension-text-style': 3.28.0 - - '@tiptap/extension-document@3.28.0': - resolution: {integrity: sha512-5SAKtlB1Tr/eZFhISL86HqmUup6rItvPtuPyRjmZo/VgbMwXEwiyAh1sMgFp5VNaeSMM14vJSyQpjhSaOmaBqg==} - peerDependencies: - '@tiptap/core': 3.28.0 - - '@tiptap/extension-dropcursor@3.28.0': - resolution: {integrity: sha512-JA+dXQjjfJBpD0nVsZeddGVXRsmanESM9+8CtM35hI9wJnYMXay/B+5GUhswO20zhT3zhB2ZOTGe9knu6A8nIQ==} - peerDependencies: - '@tiptap/extensions': 3.28.0 - - '@tiptap/extension-floating-menu@3.28.0': - resolution: {integrity: sha512-59SECvJq3pQfeJBuydEdhQqrpl0oDlk8N1Ovs1Si3/fJa6rEQAJB2zIvcpBHky9Z+5JodI2eueDnv8eimiyGbg==} - peerDependencies: - '@floating-ui/dom': ^1.0.0 - '@tiptap/core': 3.28.0 - '@tiptap/pm': 3.28.0 - - '@tiptap/extension-gapcursor@3.28.0': - resolution: {integrity: sha512-Wo73kM4q8z4Va9i4+0n1cO0UEMVUVBHPqM6cAqEYXjB4T48LQkKjRsiQydCezm185rQAsmm1dyi72gtvVQfdMg==} - peerDependencies: - '@tiptap/extensions': 3.28.0 - - '@tiptap/extension-hard-break@3.28.0': - resolution: {integrity: sha512-JHIFjX1luJgQ4VFEkIeXZpbc54Qiw+69P8FmlazYTh7AIJ6iLCpMFgQFELnivEAZ+/vS0lMPQubg0rCeM3UrHw==} - peerDependencies: - '@tiptap/core': 3.28.0 - - '@tiptap/extension-heading@3.28.0': - resolution: {integrity: sha512-rKjGG/Ik2lNaXBNVmONEDm5DBfGDLM1NWgSf5RRfBISrH8Lm1xqFruOB9tIaB0YUoSV3SBOiwTF9svmzvKSoRA==} - peerDependencies: - '@tiptap/core': 3.28.0 - - '@tiptap/extension-highlight@3.28.0': - resolution: {integrity: sha512-T8uvQ1aZs+u496XxycMk5zAIwA0DxN+SCdSAFWCU7bqCAen3ulzOsk6RXJ+Jx40GqTV/ShPyIcEuaJVNurXxbg==} - peerDependencies: - '@tiptap/core': 3.28.0 - - '@tiptap/extension-horizontal-rule@3.28.0': - resolution: {integrity: sha512-neBCMWprkppQUoLWyeJZDHqBw+xKX2oNhLD9MbFlhKIr092zgfP4mpCvQnSkn1OHl156KzZMp070+NyLBjgQ7A==} - peerDependencies: - '@tiptap/core': 3.28.0 - '@tiptap/pm': 3.28.0 - - '@tiptap/extension-italic@3.28.0': - resolution: {integrity: sha512-Yur/ELz6dNVKQC7m8wGjtoLFxamGjJmA0rUEEOacTH6J39aFmcSxYkEGNDkYHbZFyHFYqbej6eI3VE0h08O0mg==} - peerDependencies: - '@tiptap/core': 3.28.0 - - '@tiptap/extension-link@3.28.0': - resolution: {integrity: sha512-hy/PqSeTyl317yseFcHGE+3XVfQKQYaL4uZuj27xlrcO7MZ15drt1h9sUZy1FTBF3mr8676pQu7aoEm/Ww4ZRA==} - peerDependencies: - '@tiptap/core': 3.28.0 - '@tiptap/pm': 3.28.0 - - '@tiptap/extension-list-item@3.28.0': - resolution: {integrity: sha512-GbXrnMac6knSetZY33NHwOrgXFPqH28uCklQqmOZQlsrdGqezDKk31qoEMr4GsKW9n/lViAeuc07oIzwLwCMng==} - peerDependencies: - '@tiptap/extension-list': 3.28.0 - - '@tiptap/extension-list-keymap@3.28.0': - resolution: {integrity: sha512-u9Wks8c/eQAv0RkULNggOyTKDD69WVbcV9H5cbasPDUbq5XbEFVa9ajxhEGfMmQ5et3EX0QL8qBi50K0GqbIjA==} - peerDependencies: - '@tiptap/extension-list': 3.28.0 - - '@tiptap/extension-list@3.28.0': - resolution: {integrity: sha512-zQ66i5DuhVOndmZ0d7H475Y5Yb+BMx+zfCjFkCzEc6WVef5MumtxBVTkGLQ0ibxUaD71s4QQZbjHWagpaTg0eQ==} - peerDependencies: - '@tiptap/core': 3.28.0 - '@tiptap/pm': 3.28.0 - - '@tiptap/extension-ordered-list@3.28.0': - resolution: {integrity: sha512-OZNYrKKL9D01ySOujlNbKlLS9/3wGgKNYeoHZUg0e+Ta8WPVvL3W1zH6TgiU5sF2ZSGUBoq3w7mdaO/iROlUkw==} - peerDependencies: - '@tiptap/extension-list': 3.28.0 - - '@tiptap/extension-paragraph@3.28.0': - resolution: {integrity: sha512-8UMlQIjzqf6r2hSJ/VeJ00RqaOrOB1J5rTk02Vcw2pYeHkOqp+B0ff0HFu4abT9x1q4oXrBAgMsxRtgh/kR14Q==} - peerDependencies: - '@tiptap/core': 3.28.0 - - '@tiptap/extension-placeholder@3.28.0': - resolution: {integrity: sha512-zueiqNeCHOshDa8dGGSa/0cOBZkMjPrxQhemjF4tj2yZ6EeWHBPtCE5h/uQCagEePxsoaJtvaCRh88199zrgBw==} - peerDependencies: - '@tiptap/extensions': 3.28.0 - - '@tiptap/extension-strike@3.28.0': - resolution: {integrity: sha512-VVj2ZZU9QYqiHLcjqMqRYvuHSsokj/AgUl+6TzLrKjlWwyZ18D4H2vkyI0g70iVTKnUpZ3sEy8WA/rqjgBjqBg==} - peerDependencies: - '@tiptap/core': 3.28.0 - - '@tiptap/extension-text-align@3.28.0': - resolution: {integrity: sha512-P+KQMTtCpLFq9dRvy/fbyu2BGALulT/1HtsLbpFG4tKATaKa+cFnJ7rLZF7b3AdAMDNtXdnivxAtixAMBt2QWw==} - peerDependencies: - '@tiptap/core': 3.28.0 - - '@tiptap/extension-text-style@3.28.0': - resolution: {integrity: sha512-UreMyqZSv770+CXJK/l1qeHUdaNMQAVkxSOPWpiY9FiT8B4OQyjA7WBOlWrzbvVTzfJvXWk2Y3SWgVRx+/Cd9A==} - peerDependencies: - '@tiptap/core': 3.28.0 - - '@tiptap/extension-text@3.28.0': - resolution: {integrity: sha512-Jqp1LgfY1mnp4TQHoy+vnHyfn6qnnAM6nHgGwbY5zA8b/Xf1Etll6pziTx9p1J1qcfAgSrnjOyAmA++H7VQqww==} - peerDependencies: - '@tiptap/core': 3.28.0 - - '@tiptap/extension-underline@3.28.0': - resolution: {integrity: sha512-rwxCS6vTh2DJkNIYQX7JSrrRSmm76e2y48aZeuZO5ShkvLWMuJ3/zqDHRxAQVDiJhuE2Cp8NrTmPYlUc9YrT0A==} - peerDependencies: - '@tiptap/core': 3.28.0 - - '@tiptap/extensions@3.28.0': - resolution: {integrity: sha512-DJT1khCK+O/pT1gQlAnoKAx6zwDkgv7GtnhSfkqm1/4KHC3x+SSJnBIgBl9oGHymA+DxWbW1EULU4V3d/kT2uQ==} - peerDependencies: - '@tiptap/core': 3.28.0 - '@tiptap/pm': 3.28.0 - - '@tiptap/pm@3.28.0': - resolution: {integrity: sha512-ALcpwZMUdat9gjJKlpscpoqXStoLhU246LPEVBDvJdIsoUKvUu3MrzfXik2Y8mtSGfhjtm9O2TRkWxQiFVMwsQ==} - - '@tiptap/react@3.28.0': - resolution: {integrity: sha512-BxzSAqaDEldQ97K/v6+GizU1/Dxx9VWkRh7PLQql6bXlAMiz6T1G3dGt25vvv1hQ1FoMt5ExWY5NkrPmR8G8ew==} - peerDependencies: - '@tiptap/core': 3.28.0 - '@tiptap/pm': 3.28.0 - '@types/react': ^17.0.0 || ^18.0.0 || ^19.0.0 - '@types/react-dom': ^17.0.0 || ^18.0.0 || ^19.0.0 - react: ^17.0.0 || ^18.0.0 || ^19.0.0 - react-dom: ^17.0.0 || ^18.0.0 || ^19.0.0 - - '@tiptap/starter-kit@3.28.0': - resolution: {integrity: sha512-pqvFpValV+ONtlu3l4s73ROm/tEjoGMmt6uHmSJaLbqTcMHkdWuR3flJ/5brr4IDHe1foxmNicMbRxlje7yBYw==} - - '@tldraw/driver@5.2.5': - resolution: {integrity: sha512-smu4NnqTdIM59/hHTvuXxqJWQ5aLpFUR59ibS//o30P9pgMAoN4v/7NhYx9g+V7RAeOzIpDqREouz3hZdoEt9Q==} - engines: {node: '>=22.12.0'} - - '@tldraw/editor@5.2.5': - resolution: {integrity: sha512-/2aIuOLik7+tIzdmnjZ5XuxPRfSzZnNXVbxqCiFIVgbwe1W4jwm2lr2K4EpGduzdnbTNNvh6nPDJGg/jamAvnA==} - engines: {node: '>=22.12.0'} - peerDependencies: - react: ^18.2.0 || ^19.2.1 - react-dom: ^18.2.0 || ^19.2.1 - - '@tldraw/state-react@5.2.5': - resolution: {integrity: sha512-/DbTo2Oulg/ZlQwfqlyJq+rjpYBIdwl9iELB01ImQ3aDIfM5TEyabc65baj/eCW3UNlFwKVTRft9m5z4jlPbVw==} - engines: {node: '>=22.12.0'} - peerDependencies: - react: ^18.2.0 || ^19.2.1 - react-dom: ^18.2.0 || ^19.2.1 - - '@tldraw/state@5.2.5': - resolution: {integrity: sha512-fTn6YG6+MMlc3Rshb0/9k2YQ5TnKjy9FLwiGD7ph5maj9quSdpWQJNRgYxk7o2tRd/m7j+NhgylIb/F+2iKwbw==} - engines: {node: '>=22.12.0'} - - '@tldraw/store@5.2.5': - resolution: {integrity: sha512-d3sWGoFH1j9jip71VdbVYQC5WLebn2ydo2n1BG3J+PhRhv7DGP9B1bojPUd9lEPBlB4VaS6/BmP4zyk/sLmAnw==} - engines: {node: '>=22.12.0'} - peerDependencies: - react: ^18.2.0 || ^19.2.1 - - '@tldraw/tlschema@5.2.5': - resolution: {integrity: sha512-Oe9f1e0pcO6M42RVeBTAMqUXdWMJTeCFnM9DsBvJoY94ukIau5huUARAJ2mIV+oqUQS3pkDOrDz/L0KSN9+Eig==} - engines: {node: '>=22.12.0'} - peerDependencies: - react: ^18.2.0 || ^19.2.1 - react-dom: ^18.2.0 || ^19.2.1 - - '@tldraw/utils@5.2.5': - resolution: {integrity: sha512-8f2KqXVsPdXhZsGC2c1c+V19hpct7BQIcKnsq1b+zKUqDf7B/lNSHm/S63NeRhmsOTgzEIeOXVQYHX7OYXo2Xg==} - engines: {node: '>=22.12.0'} - - '@tldraw/validate@5.2.5': - resolution: {integrity: sha512-FgFbMuKVdFUWyZFq0wr+ojZ/zeXlm4/Ed47duvYfDWoGkQ5r/j2q4vnfeB0MVyGHxGbvVMqDOgGbWPbCJoDspA==} - engines: {node: '>=22.12.0'} - - '@ts-morph/common@0.27.0': - resolution: {integrity: sha512-Wf29UqxWDpc+i61k3oIOzcUfQt79PIT9y/MWfAGlrkjg6lBC1hwDECLXPVJAhWjiGbfBCxZd65F/LIZF3+jeJQ==} - - '@tweenjs/tween.js@23.1.3': - resolution: {integrity: sha512-vJmvvwFxYuGnF2axRtPYocag6Clbb5YS7kLL+SO/TeVFzHqDIWrNKYtcsPMibjDx9O+bu+psAy9NKfWklassUA==} - - '@types/d3-array@3.2.2': - resolution: {integrity: sha512-hOLWVbm7uRza0BYXpIIW5pxfrKe0W+D5lrFiAEYR+pb6w3N2SwSMaJbXdUfSEv+dT4MfHBLtn5js0LAWaO6otw==} - - '@types/d3-axis@3.0.6': - resolution: {integrity: sha512-pYeijfZuBd87T0hGn0FO1vQ/cgLk6E1ALJjfkC0oJ8cbwkZl3TpgS8bVBLZN+2jjGgg38epgxb2zmoGtSfvgMw==} - - '@types/d3-brush@3.0.6': - resolution: {integrity: sha512-nH60IZNNxEcrh6L1ZSMNA28rj27ut/2ZmI3r96Zd+1jrZD++zD3LsMIjWlvg4AYrHn/Pqz4CF3veCxGjtbqt7A==} - - '@types/d3-chord@3.0.6': - resolution: {integrity: sha512-LFYWWd8nwfwEmTZG9PfQxd17HbNPksHBiJHaKuY1XeqscXacsS2tyoo6OdRsjf+NQYeB6XrNL3a25E3gH69lcg==} - - '@types/d3-color@3.1.3': - resolution: {integrity: sha512-iO90scth9WAbmgv7ogoq57O9YpKmFBbmoEoCHDB2xMBY0+/KVrqAaCDyCE16dUspeOvIxFFRI+0sEtqDqy2b4A==} - - '@types/d3-contour@3.0.6': - resolution: {integrity: sha512-BjzLgXGnCWjUSYGfH1cpdo41/hgdWETu4YxpezoztawmqsvCeep+8QGfiY6YbDvfgHz/DkjeIkkZVJavB4a3rg==} - - '@types/d3-delaunay@6.0.4': - resolution: {integrity: sha512-ZMaSKu4THYCU6sV64Lhg6qjf1orxBthaC161plr5KuPHo3CNm8DTHiLw/5Eq2b6TsNP0W0iJrUOFscY6Q450Hw==} - - '@types/d3-dispatch@3.0.7': - resolution: {integrity: sha512-5o9OIAdKkhN1QItV2oqaE5KMIiXAvDWBDPrD85e58Qlz1c1kI/J0NcqbEG88CoTwJrYe7ntUCVfeUl2UJKbWgA==} - - '@types/d3-drag@3.0.7': - resolution: {integrity: sha512-HE3jVKlzU9AaMazNufooRJ5ZpWmLIoc90A37WU2JMmeq28w1FQqCZswHZ3xR+SuxYftzHq6WU6KJHvqxKzTxxQ==} - - '@types/d3-dsv@3.0.7': - resolution: {integrity: sha512-n6QBF9/+XASqcKK6waudgL0pf/S5XHPPI8APyMLLUHd8NqouBGLsU8MgtO7NINGtPBtk9Kko/W4ea0oAspwh9g==} - - '@types/d3-ease@3.0.2': - resolution: {integrity: sha512-NcV1JjO5oDzoK26oMzbILE6HW7uVXOHLQvHshBUW4UMdZGfiY6v5BeQwh9a9tCzv+CeefZQHJt5SRgK154RtiA==} - - '@types/d3-fetch@3.0.7': - resolution: {integrity: sha512-fTAfNmxSb9SOWNB9IoG5c8Hg6R+AzUHDRlsXsDZsNp6sxAEOP0tkP3gKkNSO/qmHPoBFTxNrjDprVHDQDvo5aA==} - - '@types/d3-force@3.0.10': - resolution: {integrity: sha512-ZYeSaCF3p73RdOKcjj+swRlZfnYpK1EbaDiYICEEp5Q6sUiqFaFQ9qgoshp5CzIyyb/yD09kD9o2zEltCexlgw==} - - '@types/d3-format@3.0.4': - resolution: {integrity: sha512-fALi2aI6shfg7vM5KiR1wNJnZ7r6UuggVqtDA+xiEdPZQwy/trcQaHnwShLuLdta2rTymCNpxYTiMZX/e09F4g==} - - '@types/d3-geo@3.1.1': - resolution: {integrity: sha512-65Emv9fQiQQqphLlRkuQ5ypPsOmWPhtBGCMv61JDPEPMvsx+gzhGf74yw1a78xFKPj6zw4AgQICJoQv0vK9M2w==} - - '@types/d3-hierarchy@3.1.7': - resolution: {integrity: sha512-tJFtNoYBtRtkNysX1Xq4sxtjK8YgoWUNpIiUee0/jHGRwqvzYxkq0hGVbbOGSz+JgFxxRu4K8nb3YpG3CMARtg==} - - '@types/d3-interpolate@3.0.4': - resolution: {integrity: sha512-mgLPETlrpVV1YRJIglr4Ez47g7Yxjl1lj7YKsiMCb27VJH9W8NVM6Bb9d8kkpG/uAQS5AmbA48q2IAolKKo1MA==} - - '@types/d3-path@3.1.1': - resolution: {integrity: sha512-VMZBYyQvbGmWyWVea0EHs/BwLgxc+MKi1zLDCONksozI4YJMcTt8ZEuIR4Sb1MMTE8MMW49v0IwI5+b7RmfWlg==} - - '@types/d3-polygon@3.0.2': - resolution: {integrity: sha512-ZuWOtMaHCkN9xoeEMr1ubW2nGWsp4nIql+OPQRstu4ypeZ+zk3YKqQT0CXVe/PYqrKpZAi+J9mTs05TKwjXSRA==} - - '@types/d3-quadtree@3.0.6': - resolution: {integrity: sha512-oUzyO1/Zm6rsxKRHA1vH0NEDG58HrT5icx/azi9MF1TWdtttWl0UIUsjEQBBh+SIkrpd21ZjEv7ptxWys1ncsg==} - - '@types/d3-random@3.0.4': - resolution: {integrity: sha512-UHYId5WTCx4L4YNel7NU00XUXXgvgpgZOvp10PuvsQENjMDXhh2RyFc0KBjO7B45ne4Ha1yVH7ii0vnzKkuzWA==} - - '@types/d3-scale-chromatic@3.1.0': - resolution: {integrity: sha512-iWMJgwkK7yTRmWqRB5plb1kadXyQ5Sj8V/zYlFGMUBbIPKQScw+Dku9cAAMgJG+z5GYDoMjWGLVOvjghDEFnKQ==} - - '@types/d3-scale@4.0.9': - resolution: {integrity: sha512-dLmtwB8zkAeO/juAMfnV+sItKjlsw2lKdZVVy6LRr0cBmegxSABiLEpGVmSJJ8O08i4+sGR6qQtb6WtuwJdvVw==} - - '@types/d3-selection@3.0.11': - resolution: {integrity: sha512-bhAXu23DJWsrI45xafYpkQ4NtcKMwWnAC/vKrd2l+nxMFuvOT3XMYTIj2opv8vq8AO5Yh7Qac/nSeP/3zjTK0w==} - - '@types/d3-shape@3.1.8': - resolution: {integrity: sha512-lae0iWfcDeR7qt7rA88BNiqdvPS5pFVPpo5OfjElwNaT2yyekbM0C9vK+yqBqEmHr6lDkRnYNoTBYlAgJa7a4w==} - - '@types/d3-time-format@4.0.3': - resolution: {integrity: sha512-5xg9rC+wWL8kdDj153qZcsJ0FWiFt0J5RB6LYUNZjwSnesfblqrI/bJ1wBdJ8OQfncgbJG5+2F+qfqnqyzYxyg==} - - '@types/d3-time@3.0.4': - resolution: {integrity: sha512-yuzZug1nkAAaBlBBikKZTgzCeA+k1uy4ZFwWANOfKw5z5LRhV0gNA7gNkKm7HoK+HRN0wX3EkxGk0fpbWhmB7g==} - - '@types/d3-timer@3.0.2': - resolution: {integrity: sha512-Ps3T8E8dZDam6fUyNiMkekK3XUsaUEik+idO9/YjPtfj2qruF8tFBXS7XhtE4iIXBLxhmLjP3SXpLhVf21I9Lw==} - - '@types/d3-transition@3.0.9': - resolution: {integrity: sha512-uZS5shfxzO3rGlu0cC3bjmMFKsXv+SmZZcgp0KD22ts4uGXp5EVYGzu/0YdwZeKmddhcAccYtREJKkPfXkZuCg==} - - '@types/d3-zoom@3.0.8': - resolution: {integrity: sha512-iqMC4/YlFCSlO8+2Ii1GGGliCAY4XdeG748w5vQUbevlbDu0zSjH/+jojorQVBK/se0j6DUFNPBGSqD3YWYnDw==} - - '@types/d3@7.4.3': - resolution: {integrity: sha512-lZXZ9ckh5R8uiFVt8ogUNf+pIrK4EsWrx2Np75WvF/eTpJ0FMHNhjXk8CKEx/+gpHbNQyJWehbFaTvqmHWB3ww==} - - '@types/debug@4.1.13': - resolution: {integrity: sha512-KSVgmQmzMwPlmtljOomayoR89W4FynCAi3E8PPs7vmDVPe84hT+vGPKkJfThkmXs0x0jAaa9U8uW8bbfyS2fWw==} - - '@types/draco3d@1.4.10': - resolution: {integrity: sha512-AX22jp8Y7wwaBgAixaSvkoG4M/+PlAcm3Qs4OW8yT9DM4xUpWKeFhLueTAyZF39pviAdcDdeJoACapiAceqNcw==} - - '@types/estree-jsx@1.0.5': - resolution: {integrity: sha512-52CcUVNFyfb1A2ALocQw/Dd1BQFNmSdkuC3BkZ6iqhdMfQz7JWOFRuJFloOzjk+6WijU56m9oKXFAXc7o3Towg==} - - '@types/estree@1.0.9': - resolution: {integrity: sha512-GhdPgy1el4/ImP05X05Uw4cw2/M93BCUmnEvWZNStlCzEKME4Fkk+YpoA5OiHNQmoS7Cafb8Xa3Pya8m1Qrzeg==} - - '@types/file-saver@2.0.7': - resolution: {integrity: sha512-dNKVfHd/jk0SkR/exKGj2ggkB45MAkzvWCaqLUUgkyjITkGNzH8H+yUwr+BLJUBjZOe9w8X3wgmXhZDRg1ED6A==} - - '@types/geojson@7946.0.16': - resolution: {integrity: sha512-6C8nqWur3j98U6+lXDfTUWIfgvZU+EumvpHKcYjujKH7woYyLj2sUmff0tRhrqM7BohUw7Pz3ZB1jj2gW9Fvmg==} - - '@types/hast@3.0.5': - resolution: {integrity: sha512-rp/ezSWaD1m44dPKICGhiskI13nVr7qTloFwDa/IYkhhf5nzwP+zIQcIJh3WIFSBOy/H1PzB40jPjMDksN4F+g==} - - '@types/js-cookie@3.0.6': - resolution: {integrity: sha512-wkw9yd1kEXOPnvEeEV1Go1MmxtBJL0RR79aOTAApecWFVu7w0NNXNqhcWgvw2YgZDYadliXkl14pa3WXw5jlCQ==} - - '@types/katex@0.16.8': - resolution: {integrity: sha512-trgaNyfU+Xh2Tc+ABIb44a5AYUpicB3uwirOioeOkNPPbmgRNtcWyDeeFRzjPZENO9Vq8gvVqfhaaXWLlevVwg==} - - '@types/mdast@4.0.4': - resolution: {integrity: sha512-kGaNbPh1k7AFzgpud/gMdvIm5xuECykRR+JnWKQno9TAXVa6WIVCGTPvYGekIDL4uwCZQSYbUxNBSb1aUo79oA==} - - '@types/mdx@2.0.14': - resolution: {integrity: sha512-T48PeuJtvLosNTPVhfnIp3i/n3a4g4Bad7YCq5k64D4u7NwDrAotikQ+5+sjtUvBmxCMlbo3dVL+C2dP0rWHzg==} - - '@types/ms@2.1.0': - resolution: {integrity: sha512-GsCCIZDE/p3i96vtEqx+7dBUGXrc7zeSK3wwPHIaRThS+9OhWIXRqzs4d6k1SVU8g91DrNRWxWUGhp5KXQb2VA==} - - '@types/node@20.19.43': - resolution: {integrity: sha512-6oYBAi5ikg4Pl+kGsoYtawUMBT2zZMCvPNF7pVLnHZfd1zf38DRiWn/gT01RYCdUqkv7Fhr+C9ot4/tb+2sVvA==} - - '@types/offscreencanvas@2019.7.3': - resolution: {integrity: sha512-ieXiYmgSRXUDeOntE1InxjWyvEelZGP63M+cGuquuRLuIKKT1osnkXjxev9B7d1nXSug5vpunx+gNlbVxMlC9A==} - - '@types/parse-json@4.0.2': - resolution: {integrity: sha512-dISoDXWWQwUquiKsyZ4Ng+HX2KsPL7LyHKHQwgGFEA3IaKac4Obd+h2a/a6waisAoepJlBcx9paWqjA8/HVjCw==} - - '@types/prismjs@1.26.6': - resolution: {integrity: sha512-vqlvI7qlMvcCBbVe0AKAb4f97//Hy0EBTaiW8AalRnG/xAN5zOiWWyrNqNXeq8+KAuvRewjCVY1+IPxk4RdNYw==} - - '@types/react-dom@19.2.3': - resolution: {integrity: sha512-jp2L/eY6fn+KgVVQAOqYItbF0VY/YApe5Mz2F0aykSO8gx31bYCZyvSeYxCHKvzHG5eZjc+zyaS5BrBWya2+kQ==} - peerDependencies: - '@types/react': ^19.2.0 - - '@types/react-reconciler@0.28.9': - resolution: {integrity: sha512-HHM3nxyUZ3zAylX8ZEyrDNd2XZOnQ0D5XfunJF5FLQnZbHHYq4UWvW1QfelQNXv1ICNkwYhfxjwfnqivYB6bFg==} - peerDependencies: - '@types/react': '*' - - '@types/react@19.2.17': - resolution: {integrity: sha512-MXfmqaVPEVgkBT/aY0aGCkRWWtByiYQXo3xdQ8r5RzuFrPiRn8Gar2tQdXSUQ2GKV3bkXckek89V8wQBY2Q/Aw==} - - '@types/stats.js@0.17.4': - resolution: {integrity: sha512-jIBvWWShCvlBqBNIZt0KAshWpvSjhkwkEu4ZUcASoAvhmrgAUI2t1dXrjSL4xXVLB4FznPrIsX3nKXFl/Dt4vA==} - - '@types/three@0.185.4': - resolution: {integrity: sha512-gAsBIC07NIFrxjbf7tH2t71c38uulFfk/RFoC7FNBSjMRAQ8J1x/RBvusX0N5PJouaYFJawXQqfCQ0RKUx/1nA==} - - '@types/trusted-types@2.0.7': - resolution: {integrity: sha512-ScaPdn1dQczgbl0QFTeTOmVHFULt394XJgOQNoyVhZ6r2vLnMLJfBPd53SB52T/3G36VI1/g2MZaX0cwDuXsfw==} - - '@types/unist@2.0.11': - resolution: {integrity: sha512-CmBKiL6NNo/OqgmMn95Fk9Whlp2mtvIv+KNpQKN2F4SjvrEesubTRWGYSg+BnWZOnlCaSTU1sMpsBOzgbYhnsA==} - - '@types/unist@3.0.3': - resolution: {integrity: sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q==} - - '@types/use-sync-external-store@0.0.6': - resolution: {integrity: sha512-zFDAD+tlpf2r4asuHEj0XH6pY6i0g5NeAHPn+15wk3BV6JA69eERFXC1gyGThDkVa1zCyKr5jox1+2LbV/AMLg==} - - '@types/validate-npm-package-name@4.0.2': - resolution: {integrity: sha512-lrpDziQipxCEeK5kWxvljWYhUvOiB2A9izZd9B2AFarYAkqZshb4lPbRs7zKEic6eGtH8V/2qJW+dPp9OtF6bw==} - - '@types/webxr@0.5.24': - resolution: {integrity: sha512-h8fgEd/DpoS9CBrjEQXR+dIDraopAEfu4wYVNY2tEPwk60stPWhvZMf4Foo5FakuQ7HFZoa8WceaWFervK2Ovg==} - - '@typescript/typescript-aix-ppc64@7.0.2': - resolution: {integrity: sha512-MTKKkWB7p/0E9xi1d1tHtZ5PiLkGEMIq88pK2CubZjOsLtYTLqhgIgi6zepFa+9GHZ6h05NMCkQxGKiPXMxXtQ==} - engines: {node: '>=16.20.0'} - cpu: [ppc64] - os: [aix] - - '@typescript/typescript-darwin-arm64@7.0.2': - resolution: {integrity: sha512-gowzar9MwS/aRWp6f3a4KUqzRjAZjOsmGNCM6LcTgXum+dBfgsBVMN+AgvOCCbguXyick6LJhpBszxMebJ8syA==} - engines: {node: '>=16.20.0'} - cpu: [arm64] - os: [darwin] - - '@typescript/typescript-darwin-x64@7.0.2': - resolution: {integrity: sha512-SZ9xZInqApNlNGc9s0W1VSsktYSOe9cFqNOIqmN1Gs8SmkjKZYFt017G4VwPxASInODuAdbTW7sXiFUf893RgA==} - engines: {node: '>=16.20.0'} - cpu: [x64] - os: [darwin] - - '@typescript/typescript-freebsd-arm64@7.0.2': - resolution: {integrity: sha512-W5NH4y/J0plIIS5b2xvTEkU7JFxyqdMAOgf+Ilhl0vHQXKO5dZoxd+C/jEtq56c4F3wk71RB4BMRQ2XdI+bwYQ==} - engines: {node: '>=16.20.0'} - cpu: [arm64] - os: [freebsd] - - '@typescript/typescript-freebsd-x64@7.0.2': - resolution: {integrity: sha512-UMGDx5sTpzNw3WiPebH7l90IWfJggEd+egHt/q6p7/Cm3zqoV7VxkGXt+3DxPIw8CcmvAB0j3sVVfbhX+M4Tpw==} - engines: {node: '>=16.20.0'} - cpu: [x64] - os: [freebsd] - - '@typescript/typescript-linux-arm64@7.0.2': - resolution: {integrity: sha512-Qh4eU4/y3yDjnfjjyPYihMj5/ODIlmt+Bzu17OI+fiSRDW57QmU5SiN63exPRNJPKUzcc1INa1NXdrJ+MqHjUQ==} - engines: {node: '>=16.20.0'} - cpu: [arm64] - os: [linux] - - '@typescript/typescript-linux-arm@7.0.2': - resolution: {integrity: sha512-gffT3xPz9sR7j/YJExkyPntrI0P2EP9XbOyWzth2/Gs0RstK+90RBcO0ncXoXy/beYll1SXw846Nf2zdnEz0QQ==} - engines: {node: '>=16.20.0'} - cpu: [arm] - os: [linux] - - '@typescript/typescript-linux-loong64@7.0.2': - resolution: {integrity: sha512-uEHck9i8hoAzXPiYRib1O7miOnz23SxIeVl6F4LXox+qov1K35jHcEW6VHKvZI+pyvl7fZEP4MCU5LYvIq1GuQ==} - engines: {node: '>=16.20.0'} - cpu: [loong64] - os: [linux] - - '@typescript/typescript-linux-mips64el@7.0.2': - resolution: {integrity: sha512-R4KvAMnE43W5Qeqb0Ly56O3mWMWIAgsMyz36DCaycd5nbg/9kzm0liw3JocfRqyJY0KPmzFjbswozXyW0DnIYA==} - engines: {node: '>=16.20.0'} - cpu: [mips64el] - os: [linux] - - '@typescript/typescript-linux-ppc64@7.0.2': - resolution: {integrity: sha512-DORx5b3sd/4S7eayxm4FQv+A7CrkUIGRaHiwI8oiHTAI1fAPWhF4J0vAlkC8biAlHSVVwxMQ3tjZ2/DVbnQiiA==} - engines: {node: '>=16.20.0'} - cpu: [ppc64] - os: [linux] - - '@typescript/typescript-linux-riscv64@7.0.2': - resolution: {integrity: sha512-wf0jqEDOjrPRnKwYRyyJDRo11KMbvMFrU+q4zqKyChODBzvlkbhNQfKvLxQCcwTpdDaXSHZTVuh0JoCrKCUMHQ==} - engines: {node: '>=16.20.0'} - cpu: [riscv64] - os: [linux] - - '@typescript/typescript-linux-s390x@7.0.2': - resolution: {integrity: sha512-IkwJc3L7yhytWd/ewjyxNDfOmswCm9GWMJT/ue/dU4aZNbwZeYAetq42VyLmsmSjvoX7z74X6ZaYCtzAr0EuGw==} - engines: {node: '>=16.20.0'} - cpu: [s390x] - os: [linux] - - '@typescript/typescript-linux-x64@7.0.2': - resolution: {integrity: sha512-EYdf2cNg7rgCWJnxCdJ+F3V39O8ihb37eHAu1LK8oAFizgTQbPOK7zHHXbPt8rX24COqODXeI3sIf0fCXG7H/A==} - engines: {node: '>=16.20.0'} - cpu: [x64] - os: [linux] - - '@typescript/typescript-netbsd-arm64@7.0.2': - resolution: {integrity: sha512-+polYF4MF04aPpO5FTkHran9yUQDSXqy5GiSDKpsll5jy3l3+g9QLhpf39T+ePtefhXLOGrLl0QIjkQP6VnelA==} - engines: {node: '>=16.20.0'} - cpu: [arm64] - os: [netbsd] - - '@typescript/typescript-netbsd-x64@7.0.2': - resolution: {integrity: sha512-8YIT0EHM/3dq10ZOVF/A7pc/YSMtbcecct4rWtexrnSCHOPcpC2KTLXfTCR6vDpnSiY12heNb1GiN/wu+T/FyA==} - engines: {node: '>=16.20.0'} - cpu: [x64] - os: [netbsd] - - '@typescript/typescript-openbsd-arm64@7.0.2': - resolution: {integrity: sha512-APT8+ClYnuYm1u9+kgGXoMj2VzWzcymwh2gNSQVySHfkRDGOTVkoWLjCmOQSaO+PoqQ57B0flRp9SA+7GnnkzQ==} - engines: {node: '>=16.20.0'} - cpu: [arm64] - os: [openbsd] - - '@typescript/typescript-openbsd-x64@7.0.2': - resolution: {integrity: sha512-yX7s+Q0Dln0Dt9tEzZsAjXXR/+ytBM7AlglaqyeMPxQszJ1JhlJdZ6jLA+IzldHtflX81em7lDao1xXu+aRRkg==} - engines: {node: '>=16.20.0'} - cpu: [x64] - os: [openbsd] - - '@typescript/typescript-sunos-x64@7.0.2': - resolution: {integrity: sha512-dLJDGaLZ1D4HPQn62u1n8mBDkJREwMsAkCdkwd4Ieqw+x3TUyTsqY0YiBCtE6H6OzzgGk3iuZ3vFWRS+E8/d1g==} - engines: {node: '>=16.20.0'} - cpu: [x64] - os: [sunos] - - '@typescript/typescript-win32-arm64@7.0.2': - resolution: {integrity: sha512-Gyl1Vy6OsWesLzmq+EP0Fb7b4Nid5232AvcA2SFcdYreldpNtYFFofPjnt62y9hQy7VTaZp65ICJjuAQRaVcIQ==} - engines: {node: '>=16.20.0'} - cpu: [arm64] - os: [win32] - - '@typescript/typescript-win32-x64@7.0.2': - resolution: {integrity: sha512-0BQ3HkAHHlKLSp1qRvf3SUhGpGsDuhB/jgFw75guyqbxJqEaS0Cw/VFO8i2nHglJUzQCRtMMR/IBAKE3ETMC4g==} - engines: {node: '>=16.20.0'} - cpu: [x64] - os: [win32] - - '@uiw/codemirror-extensions-basic-setup@4.25.11': - resolution: {integrity: sha512-otyFa+n9IOYtEjaKOxPedHkj15fTPUF21wdR9pv0GpZPfuGl27cvmcv6+tognbRu9VvEcsHKE+ESoszeo3KfTw==} - peerDependencies: - '@codemirror/autocomplete': '>=6.0.0' - '@codemirror/commands': '>=6.0.0' - '@codemirror/language': '>=6.0.0' - '@codemirror/lint': '>=6.0.0' - '@codemirror/search': '>=6.0.0' - '@codemirror/state': '>=6.0.0' - '@codemirror/view': '>=6.0.0' - - '@uiw/react-codemirror@4.25.11': - resolution: {integrity: sha512-DYVFAKLX+F/4JS9N/7xexh+TICrlncwkX9HKKInrP1bwO0tSfc3k0GB6oawTYhelVKh20cX3TuRx+NJSkVXuMw==} - peerDependencies: - '@babel/runtime': '>=7.11.0' - '@codemirror/state': '>=6.0.0' - '@codemirror/theme-one-dark': '>=6.0.0' - '@codemirror/view': '>=6.0.0' - codemirror: '>=6.0.0' - react: '>=17.0.0' - react-dom: '>=17.0.0' - - '@ungap/structured-clone@1.3.3': - resolution: {integrity: sha512-60YRaenCQcVjYEKOcG824+DRGGIQ3VKErcBoAEDJZz5bKIs2ZG+X/H9Nk+Q6EVkwJk5QNApxbrc5QtBSwtrXAg==} - - '@upsetjs/venn.js@2.0.0': - resolution: {integrity: sha512-WbBhLrooyePuQ1VZxrJjtLvTc4NVfpOyKx0sKqioq9bX1C1m7Jgykkn8gLrtwumBioXIqam8DLxp88Adbue6Hw==} - - '@use-gesture/core@10.3.1': - resolution: {integrity: sha512-WcINiDt8WjqBdUXye25anHiNxPc0VOrlT8F6LLkU6cycrOGUDyY/yyFmsg3k8i5OLvv25llc0QC45GhR/C8llw==} - - '@use-gesture/react@10.3.1': - resolution: {integrity: sha512-Yy19y6O2GJq8f7CHf7L0nxL8bf4PZCPaVOCgJrusOeFHY1LvHgYXnmnXg6N5iwAnbgbZCDjo60SiM6IPJi9C5g==} - peerDependencies: - react: '>= 16.8.0' - - '@vidstack/react@1.15.6': - resolution: {integrity: sha512-hO7cLld5yfxCdDnDCYqh5aS7JxmHfsmX43PmKBJWzDTwK40P3hh5qAdDQzl3qDtEJLYjuo3Sv+kr6tt/VRtJzQ==} - engines: {node: '>=18'} - peerDependencies: - '@types/react': ^18.0.0 || ^19.0.0 - react: ^18.0.0 || ^19.0.0 - - '@vitejs/plugin-react@6.0.5': - resolution: {integrity: sha512-BOVzne/NL162sMdResB25mUv+vWMF5NoAjNf09TeGlE7ZpszZWSD3winycicLJw72yeVsoCn/2kOhEuCvEShMA==} - engines: {node: ^20.19.0 || >=22.12.0} - peerDependencies: - '@rolldown/plugin-babel': ^0.1.7 || ^0.2.0 - babel-plugin-react-compiler: ^1.0.0 - vite: ^8.0.0 - peerDependenciesMeta: - '@rolldown/plugin-babel': - optional: true - babel-plugin-react-compiler: - optional: true - - '@xmldom/xmldom@0.8.14': - resolution: {integrity: sha512-T4EDRUBVZYRldYApjEJiU0e1stYWaRAX7CuSnKzrpwdZKo53zGV8/pqfzV6FfwNl9YThD2OumQYvqtvjvgG7aQ==} - engines: {node: '>=10.0.0'} - - accepts@2.0.0: - resolution: {integrity: sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng==} - engines: {node: '>= 0.6'} - - acorn-jsx@5.3.2: - resolution: {integrity: sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==} - peerDependencies: - acorn: ^6.0.0 || ^7.0.0 || ^8.0.0 - - acorn@8.18.0: - resolution: {integrity: sha512-lGq+9yr1/GuAWaVYIHRjvvySG5/4VfKIvC8EWxStPdcDh/Ka7FG3twP6v4d5BkravUilhIAsG4Qj83t02LWUPQ==} - engines: {node: '>=0.4.0'} - hasBin: true - - agent-base@6.0.2: - resolution: {integrity: sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==} - engines: {node: '>= 6.0.0'} - - ahooks@3.9.7: - resolution: {integrity: sha512-S0lvzhbdlhK36RFBkGv+RbOM/dbbweym+BIHM/bwwuWVSVN5TuVErHPMWo4w0t1NDYg5KPp2iEf7Y7E5LASYiw==} - peerDependencies: - react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 - react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 - - ajv-formats@2.1.1: - resolution: {integrity: sha512-Wx0Kx52hxE7C18hkMEggYlEifqWZtYaRgouJor+WMdPnQyEK13vgEWyVNup7SoeeoLMsr4kf5h6dOW11I15MUA==} - peerDependencies: - ajv: ^8.0.0 - peerDependenciesMeta: - ajv: - optional: true - - ajv-formats@3.0.1: - resolution: {integrity: sha512-8iUql50EUR+uUcdRQ3HDqa6EVyo3docL8g5WJ3FNcWmu62IbkGUue/pEyLBW8VGKKucTPgqeks4fIU1DA4yowQ==} - peerDependencies: - ajv: ^8.0.0 - peerDependenciesMeta: - ajv: - optional: true - - ajv@8.20.0: - resolution: {integrity: sha512-Thbli+OlOj+iMPYFBVBfJ3OmCAnaSyNn4M1vz9T6Gka5Jt9ba/HIR56joy65tY6kx/FCF5VXNB819Y7/GUrBGA==} - - ansi-colors@4.1.3: - resolution: {integrity: sha512-/6w/C21Pm1A7aZitlI5Ni/2J6FFQN8i1Cvz3kHABAAbw93v/NlvKdVOqz7CCWz/3iv/JplRSEEZ83XION15ovw==} - engines: {node: '>=6'} - - ansi-regex@5.0.1: - resolution: {integrity: sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==} - engines: {node: '>=8'} - - ansi-regex@6.3.0: - resolution: {integrity: sha512-WpDfL7NO6j7tH88IDBNVdUJxDh9nmCteAVW9dsep846XdwF4naCBK+/tGLX3KJgcpgMRXCFlTM2hKGoK9FsdrQ==} - engines: {node: '>=12'} - - antd-style@4.1.0: - resolution: {integrity: sha512-vnPBGg0OVlSz90KRYZhxd89aZiOImTiesF+9MQqN8jsLGZUQTjbP04X9jTdEfsztKUuMbBWg/RmB/wHTakbtMQ==} - peerDependencies: - antd: '>=6.0.0' - react: '>=18' - - antd@6.6.0: - resolution: {integrity: sha512-UDwWIbpmrCHB9ZQ+bPh4vQfB6DTI2ulIyoQ0Tc9xxalFblttiNGHl3ySBD9SyV/8+gUjFzfSx1+iU1Fog2i46w==} - peerDependencies: - react: '>=18.0.0' - react-dom: '>=18.0.0' - - anymatch@3.1.3: - resolution: {integrity: sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==} - engines: {node: '>= 8'} - - argparse@1.0.10: - resolution: {integrity: sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==} - - argparse@2.0.1: - resolution: {integrity: sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==} - - aria-hidden@1.2.6: - resolution: {integrity: sha512-ik3ZgC9dY/lYVVM++OISsaYDeg1tb0VtP5uL3ouh1koGOaUMDPpbFIei4JkFimWUFPn90sbMNMXQAIVOlnYKJA==} - engines: {node: '>=10'} - - assign-symbols@1.0.0: - resolution: {integrity: sha512-Q+JC7Whu8HhmTdBph/Tq59IoRtoy6KAm5zzPv00WdujX82lbAL8K7WVjne7vdCsAmbF4AYaDOPyO3k0kl8qIrw==} - engines: {node: '>=0.10.0'} - - ast-types@0.16.1: - resolution: {integrity: sha512-6t10qk83GOG8p0vKmaCr8eiilZwO171AvbROMtvvNiwrTly62t+7XkA8RdIIVbpMhCASAsxgAzdRSwh6nw/5Dg==} - engines: {node: '>=4'} - - astring@1.9.0: - resolution: {integrity: sha512-LElXdjswlqjWrPpJFg1Fx4wpkOCxj1TDHlSV4PlaRxHGWko024xICaa97ZkMfs6DRKlCguiAI+rbXv5GWwXIkg==} - hasBin: true - - asynckit@0.4.0: - resolution: {integrity: sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==} - - atomically@1.7.0: - resolution: {integrity: sha512-Xcz9l0z7y9yQ9rdDaxlmaI4uJHf/T8g9hOEzJcsEqX2SjCj4J20uK7+ldkDHMbpJDK76wF7xEIgxc/vSlsfw5w==} - engines: {node: '>=10.12.0'} - - attr-accept@2.2.2: - resolution: {integrity: sha512-7prDjvt9HmqiZ0cl5CRjtS84sEyhsHP2coDkaZKRKVfCDo9s7iw7ChVmar78Gu9pC4SoR/28wFu/G5JJhTnqEg==} - engines: {node: '>=4'} - - axios@1.19.0: - resolution: {integrity: sha512-ht/iuYZXEjFxLH/Hkezgd7m6JKlHHXEUSneaDz8uZe1Gj5QZtCnpyDsckvAiEnT89OEbCLmnte4R4sn7P0EKFw==} - - babel-plugin-macros@3.1.0: - resolution: {integrity: sha512-Cg7TFGpIr01vOQNODXOOaGz2NpCU5gl8x1qJFbb6hbZxR7XrcE2vtbAsTAbJ7/xwJtUuJEw8K8Zr/AE0LHlesg==} - engines: {node: '>=10', npm: '>=6'} - - bail@2.0.2: - resolution: {integrity: sha512-0xO6mYd7JB2YesxDKplafRpsiOzPt9V02ddPCLbY1xYGPOX24NTyN50qnUxgCPcSoYMhKpAuBTjQoRZCAkUDRw==} - - balanced-match@4.0.4: - resolution: {integrity: sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==} - engines: {node: 18 || 20 || >=22} - - base64-js@1.5.1: - resolution: {integrity: sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==} - - baseline-browser-mapping@2.11.14: - resolution: {integrity: sha512-JyJ954WzuIR8/FFzX0o5krdSTrBAkcCSRfWSleRsIHSWV+cZe2FI1PKggVkFke1hBldRs+LRxUczzE9iPmgZww==} - engines: {node: '>=6.0.0'} - hasBin: true - - beautiful-mermaid@1.1.3: - resolution: {integrity: sha512-TItrtrAyHp1vwFfFVYauWGrquouk/6SS21Aq3RsxindSYZODcN4xYrPZD6BiZRU+o5mKJzDPz9MUSMvELdylyg==} - - bidi-js@1.0.3: - resolution: {integrity: sha512-RKshQI1R3YQ+n9YJz2QQ147P66ELpa1FQEg20Dk8oW9t2KgLbpDLLp9aGZ7y8WHSshDknG0bknqGw5/tyCs5tw==} - - binary-extensions@2.3.0: - resolution: {integrity: sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==} - engines: {node: '>=8'} - - bluebird@3.4.7: - resolution: {integrity: sha512-iD3898SR7sWVRHbiQv+sHUtHnMvC1o3nW5rAcqnq3uOn07DSAppZYUkIGslDz6gXC7HfunPe7YVBgoEJASPcHA==} - - body-parser@2.3.0: - resolution: {integrity: sha512-2cGmJupaNgg+QUwVLAucDuWuoMZ6EX9iHDRswZ5lsNYEmwPaRknMPCLZz07yTzVq/83p4o/wzbDZbBrTvGGTIw==} - engines: {node: '>=18'} - - brace-expansion@5.0.9: - resolution: {integrity: sha512-ScQ4IuvIEF1TMlP7Zt+vjJ//9zlPb2SDcxWxM3bk8s6t6GGdJ7KO1dCcTidOPJKePW30LE/2cT7wCyPho9/Wxg==} - engines: {node: 20 || >=22} - - braces@3.0.3: - resolution: {integrity: sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==} - engines: {node: '>=8'} - - browser-fs-access@0.29.1: - resolution: {integrity: sha512-LSvVX5e21LRrXqVMhqtAwj5xPgDb+fXAIH80NsnCQ9xuZPs2xWsOREi24RKgZa1XOiQRbcmVrv87+ulOKsgjxw==} - - browserslist@4.28.8: - resolution: {integrity: sha512-V2NpofLblG64mfOtSgDhOJESZEGogzDMBv/q+W6oc4LXWP/q75eOXoOaaOu1EOadB9U4Bwx/e0yzbvwKH8zalA==} - engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7} - hasBin: true - - buffer@6.0.3: - resolution: {integrity: sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==} - - bundle-name@4.1.0: - resolution: {integrity: sha512-tjwM5exMg6BGRI+kNmTntNsvdZS1X8BFYS6tnJ2hdH0kVxM6/eVZ2xy+FqStSWvYmtfFMDLIxurorHwDKfDz5Q==} - engines: {node: '>=18'} - - bytes@3.1.2: - resolution: {integrity: sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==} - engines: {node: '>= 0.8'} - - call-bind-apply-helpers@1.0.2: - resolution: {integrity: sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==} - engines: {node: '>= 0.4'} - - call-bound@1.0.4: - resolution: {integrity: sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==} - engines: {node: '>= 0.4'} - - callsites@3.1.0: - resolution: {integrity: sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==} - engines: {node: '>=6'} - - camera-controls@3.1.2: - resolution: {integrity: sha512-xkxfpG2ECZ6Ww5/9+kf4mfg1VEYAoe9aDSY+IwF0UEs7qEzwy0aVRfs2grImIECs/PoBtWFrh7RXsQkwG922JA==} - engines: {node: '>=22.0.0', npm: '>=10.5.1'} - peerDependencies: - three: '>=0.126.1' - - caniuse-lite@1.0.30001809: - resolution: {integrity: sha512-xxWVywk6a6Arlk+hymeycyn/VgqEfLDxupvhH/xiY5SJ/18kmi9o6MiO320DCUzypORHLtvh0I4i04tUhCNHNQ==} - - canvas-roundrect-polyfill@0.0.1: - resolution: {integrity: sha512-yWq+R3U3jE+coOeEb3a3GgE2j/0MMiDKM/QpLb6h9ihf5fGY9UXtvK9o4vNqjWXoZz7/3EaSVU3IX53TvFFUOw==} - - ccount@2.0.1: - resolution: {integrity: sha512-eyrF0jiFpY+3drT6383f1qhkbGsLSifNAjA61IUjZjmLCWjItY6LB9ft9YhoDgwfmclB2zhu51Lc7+95b8NRAg==} - - chalk@5.6.2: - resolution: {integrity: sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA==} - engines: {node: ^12.17.0 || ^14.13 || >=16.0.0} - - character-entities-html4@2.1.0: - resolution: {integrity: sha512-1v7fgQRj6hnSwFpq1Eu0ynr/CDEw0rXo2B61qXrLNdHZmPKgb7fqS1a2JwF0rISo9q77jDI8VMEHoApn8qDoZA==} - - character-entities-legacy@3.0.0: - resolution: {integrity: sha512-RpPp0asT/6ufRm//AJVwpViZbGM/MkjQFxJccQRHmISF/22NBtsHqAWmL+/pmkPWoIUJdWyeVleTl1wydHATVQ==} - - character-entities@2.0.2: - resolution: {integrity: sha512-shx7oQ0Awen/BRIdkjkvz54PnEEI/EjwXDSIZp86/KKdbafHh1Df/RYGBhn4hbe2+uKC9FnT5UCEdyPz3ai9hQ==} - - character-reference-invalid@2.0.1: - resolution: {integrity: sha512-iBZ4F4wRbyORVsu0jPV7gXkOsGYjGHPmAyv+HiHG8gi5PtC9KI2j1+v8/tlibRvjoWX027ypmG/n0HtO5t7unw==} - - chevrotain-allstar@0.3.1: - resolution: {integrity: sha512-b7g+y9A0v4mxCW1qUhf3BSVPg+/NvGErk/dOkrDaHA0nQIQGAtrOjlX//9OQtRlSCy+x9rfB5N8yC71lH1nvMw==} - peerDependencies: - chevrotain: ^11.0.0 - - chevrotain@11.0.3: - resolution: {integrity: sha512-ci2iJH6LeIkvP9eJW6gpueU8cnZhv85ELY8w8WiFtNjMHA5ad6pQLaJo9mEly/9qUyCpvqX8/POVUTf18/HFdw==} - - chokidar@3.6.0: - resolution: {integrity: sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==} - engines: {node: '>= 8.10.0'} - - chroma-js@3.2.0: - resolution: {integrity: sha512-os/OippSlX1RlWWr+QDPcGUZs0uoqr32urfxESG9U93lhUfbnlyckte84Q8P1UQY/qth983AS1JONKmLS4T0nw==} - - class-variance-authority@0.7.1: - resolution: {integrity: sha512-Ka+9Trutv7G8M6WT6SeiRWz792K5qEqIGEGzXKhAE6xOWAY6pPH8U+9IY3oCMv6kqTmLsv7Xh/2w2RigkePMsg==} - - classnames@2.5.1: - resolution: {integrity: sha512-saHYOzhIQs6wy2sVxTM6bUDsQO4F50V9RQ22qBpEdCW+I+/Wmke2HOl6lS6dTpdxVhb88/I6+Hs+438c3lfUow==} - - cli-cursor@5.0.0: - resolution: {integrity: sha512-aCj4O5wKyszjMmDT4tZj93kxyydN/K5zPWSCe6/0AV/AA1pqe5ZBIw0a2ZfPQV7lL5/yb5HsUreJ6UFAF1tEQw==} - engines: {node: '>=18'} - - cli-spinners@2.9.2: - resolution: {integrity: sha512-ywqV+5MmyL4E7ybXgKys4DugZbX0FC6LnwrhjuykIjnK9k8OQacQ7axGKnjDXWNhns0xot3bZI5h55H8yo9cJg==} - engines: {node: '>=6'} - - client-only@0.0.1: - resolution: {integrity: sha512-IV3Ou0jSMzZrd3pZ48nLkT9DA7Ag1pnPzaiQhpW7c3RbcqqzvzzVu+L8gfqMp/8IM2MQtSiqaCxrrcfu8I8rMA==} - - clsx@1.1.1: - resolution: {integrity: sha512-6/bPho624p3S2pMyvP5kKBPXnI3ufHLObBFCfgx+LkeR5lg2XYy2hqZqUf45ypD8COn2bhgGJSUE+l5dhNBieA==} - engines: {node: '>=6'} - - clsx@2.1.1: - resolution: {integrity: sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==} - engines: {node: '>=6'} - - code-block-writer@13.0.3: - resolution: {integrity: sha512-Oofo0pq3IKnsFtuHqSF7TqBfr71aeyZDVJ0HpmqB7FBM2qEigL0iPONSCZSO9pE9dZTAxANe5XHG9Uy0YMv8cg==} - - codemirror@6.0.2: - resolution: {integrity: sha512-VhydHotNW5w1UGK0Qj96BwSk/Zqbp9WbnyK2W/eVMv4QyF41INRGpjUhFJY7/uDNuudSc33a/PKr4iDqRduvHw==} - - collapse-white-space@2.1.0: - resolution: {integrity: sha512-loKTxY1zCOuG4j9f6EPnuyyYkf58RnhhWTvRoZEokgB+WbdXehfjFviyOVYkqzEWz1Q5kRiZdBYS5SwxbQYwzw==} - - colord@2.9.5: - resolution: {integrity: sha512-1N4FZkdj0BvP78UatS8VQHP+DnRy/rAyh+eelOONgZNsgatenTe+uApmVrdfknjqvsPdsBhq5tQpnIvClcNWZA==} - - combined-stream@1.0.8: - resolution: {integrity: sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==} - engines: {node: '>= 0.8'} - - comma-separated-tokens@2.0.3: - resolution: {integrity: sha512-Fu4hJdvzeylCfQPp9SGWidpzrMs7tTrlu6Vb8XGaRGck8QSNZJJp538Wrb60Lax4fPwR64ViY468OIUTbRlGZg==} - - commander@11.1.0: - resolution: {integrity: sha512-yPVavfyCcRhmorC7rWlkHn15b4wDVgVmBA7kV4QVBsF7kv/9TKJAbAXVTxvTnwP8HHKjRCJDClKbciiYS7p0DQ==} - engines: {node: '>=16'} - - commander@14.0.3: - resolution: {integrity: sha512-H+y0Jo/T1RZ9qPP4Eh1pkcQcLRglraJaSLoyOtHxu6AapkjWVCy2Sit1QQ4x3Dng8qDlSsZEet7g5Pq06MvTgw==} - engines: {node: '>=20'} - - commander@7.2.0: - resolution: {integrity: sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw==} - engines: {node: '>= 10'} - - commander@8.3.0: - resolution: {integrity: sha512-OkTL9umf+He2DZkUq8f8J9of7yL6RJKI24dVITBmNfZBmri9zYZQrKkuXiKhyfPSu8tUhnVBB1iKXevvnlR4Ww==} - engines: {node: '>= 12'} - - compute-scroll-into-view@3.1.1: - resolution: {integrity: sha512-VRhuHOLoKYOy4UbilLbUzbYg93XLjv2PncJC50EuTWPA3gaja1UjBsUP/D/9/juV3vQFr6XBEzn9KCAHdUvOHw==} - - conf@10.2.0: - resolution: {integrity: sha512-8fLl9F04EJqjSqH+QjITQfJF8BrOVaYr1jewVgSRAEWePfxT0sku4w2hrGQ60BC/TNLGQ2pgxNlTbWQmMPFvXg==} - engines: {node: '>=12'} - - content-disposition@1.1.0: - resolution: {integrity: sha512-5jRCH9Z/+DRP7rkvY83B+yGIGX96OYdJmzngqnw2SBSxqCFPd0w2km3s5iawpGX8krnwSGmF0FW5Nhr0Hfai3g==} - engines: {node: '>=18'} - - content-type@1.0.5: - resolution: {integrity: sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==} - engines: {node: '>= 0.6'} - - content-type@2.1.0: - resolution: {integrity: sha512-mj7UPXE0jaqaOsukNZRUEfEi2AcL7C/vwmwcHV0O97eO1E1pxBZuyjlZrx5seTaNBg1U6+o35wpa35Qfcc+7ag==} - engines: {node: '>=18'} - - convert-source-map@1.9.0: - resolution: {integrity: sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A==} - - convert-source-map@2.0.0: - resolution: {integrity: sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==} - - cookie-es@3.1.1: - resolution: {integrity: sha512-UaXxwISYJPTr9hwQxMFYZ7kNhSXboMXP+Z3TRX6f1/NyaGPfuNUZOWP1pUEb75B2HjfklIYLVRfWiFZJyC6Npg==} - - cookie-signature@1.2.2: - resolution: {integrity: sha512-D76uU73ulSXrD1UXF4KE2TMxVVwhsnCgfAyTg9k8P6KGZjlXKrOLe4dJQKI3Bxi5wjesZoFXJWElNWBjPZMbhg==} - engines: {node: '>=6.6.0'} - - cookie@0.7.2: - resolution: {integrity: sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==} - engines: {node: '>= 0.6'} - - copy-to-clipboard@4.0.2: - resolution: {integrity: sha512-gklSft7IuhriZKHKpuoA1fpJSLPNgvUMWMo5BlnzAJm0zNKnznoSv23IjtNqclx8eKi6ZcdvFFzYEER/+U1LoQ==} - - core-util-is@1.0.3: - resolution: {integrity: sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==} - - cors@2.8.6: - resolution: {integrity: sha512-tJtZBBHA6vjIAaF6EnIaq6laBBP9aq/Y3ouVJjEfoHbRBcHBAHYcMh/w8LDrk2PvIMMq8gmopa5D4V8RmbrxGw==} - engines: {node: '>= 0.10'} - - cose-base@1.0.3: - resolution: {integrity: sha512-s9whTXInMSgAp/NVXVNuVxVKzGH2qck3aQlVHxDCdAEPgtMKwc4Wq6/QKhgdEdgbLSi9rBTAcPoRa6JpiG4ksg==} - - cose-base@2.2.0: - resolution: {integrity: sha512-AzlgcsCbUMymkADOJtQm3wO9S3ltPfYOFD5033keQn9NJzIbtnZj+UdBJe7DYml/8TdbtHJW3j58SOnKhWY/5g==} - - cosmiconfig@7.0.0: - resolution: {integrity: sha512-pondGvTuVYDk++upghXJabWzL6Kxu6f26ljFw64Swq9v6sQPUL3EUlVDV56diOjpCayKihL6hVe8exIACU4XcA==} - engines: {node: '>=10'} - - cosmiconfig@9.0.2: - resolution: {integrity: sha512-gtTZxTDau1wL7Y7zifc2dd8jHSK/k6BTx/2Xp/BpdlAdnlYWFVt7qhJqgwi7637yRwRQ3qL4ZidbB4I8tA5VOg==} - engines: {node: '>=14'} - peerDependencies: - typescript: '>=4.9.5' - peerDependenciesMeta: - typescript: - optional: true - - crc-32@0.3.0: - resolution: {integrity: sha512-kucVIjOmMc1f0tv53BJ/5WIX+MGLcKuoBhnGqQrgKJNqLByb/sVMWfW/Aw6hw0jgcqjJ2pi9E5y32zOIpaUlsA==} - engines: {node: '>=0.8'} - - crelt@1.0.7: - resolution: {integrity: sha512-aK6BbWfhf4U/wCcLHKPJl/xa6VkVstRaPywWtMKGwuOLc/wZTyQYuoxgvZnNsBvv7Kg3YTBQYYBCggcviQczuA==} - - cross-env@7.0.3: - resolution: {integrity: sha512-+/HKd6EgcQCJGh2PSjZuUitQBQynKor4wrFbRg4DtAgS1aWO+gU52xpH7M9ScGgXSYmAVS9bIJ8EzuaGw0oNAw==} - engines: {node: '>=10.14', npm: '>=6', yarn: '>=1'} - hasBin: true - - cross-spawn@7.0.6: - resolution: {integrity: sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==} - engines: {node: '>= 8'} - - cssesc@3.0.0: - resolution: {integrity: sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==} - engines: {node: '>=4'} - hasBin: true - - csstype@3.2.3: - resolution: {integrity: sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==} - - cytoscape-cose-bilkent@4.1.0: - resolution: {integrity: sha512-wgQlVIUJF13Quxiv5e1gstZ08rnZj2XaLHGoFMYXz7SkNfCDOOteKBE6SYRfA9WxxI/iBc3ajfDoc6hb/MRAHQ==} - peerDependencies: - cytoscape: ^3.2.0 - - cytoscape-fcose@2.2.0: - resolution: {integrity: sha512-ki1/VuRIHFCzxWNrsshHYPs6L7TvLu3DL+TyIGEsRcvVERmxokbf5Gdk7mFxZnTdiGtnA4cfSmjZJMviqSuZrQ==} - peerDependencies: - cytoscape: ^3.2.0 - - cytoscape@3.34.1: - resolution: {integrity: sha512-Lr0RvH9H75y9ar8h9Toy6u4lxRSCcxUq+hHcQ26sVWo6BnaQp1gwEZOYqwuYTZhyW7npyKnNLP8oJ2p1/3OZ7g==} - engines: {node: '>=0.10'} - - d3-array@2.12.1: - resolution: {integrity: sha512-B0ErZK/66mHtEsR1TkPEEkwdy+WDesimkM5gpZr5Dsg54BiTA5RXtYW5qTLIAcekaS9xfZrzBLF/OAkB3Qn1YQ==} - - d3-array@3.2.4: - resolution: {integrity: sha512-tdQAmyA18i4J7wprpYq8ClcxZy3SC31QMeByyCFyRt7BVHdREQZ5lpzoe5mFEYZUWe+oq8HBvk9JjpibyEV4Jg==} - engines: {node: '>=12'} - - d3-axis@3.0.0: - resolution: {integrity: sha512-IH5tgjV4jE/GhHkRV0HiVYPDtvfjHQlQfJHs0usq7M30XcSBvOotpmH1IgkcXsO/5gEQZD43B//fc7SRT5S+xw==} - engines: {node: '>=12'} - - d3-brush@3.0.0: - resolution: {integrity: sha512-ALnjWlVYkXsVIGlOsuWH1+3udkYFI48Ljihfnh8FZPF2QS9o+PzGLBslO0PjzVoHLZ2KCVgAM8NVkXPJB2aNnQ==} - engines: {node: '>=12'} - - d3-chord@3.0.1: - resolution: {integrity: sha512-VE5S6TNa+j8msksl7HwjxMHDM2yNK3XCkusIlpX5kwauBfXuyLAtNg9jCp/iHH61tgI4sb6R/EIMWCqEIdjT/g==} - engines: {node: '>=12'} - - d3-color@3.1.0: - resolution: {integrity: sha512-zg/chbXyeBtMQ1LbD/WSoW2DpC3I0mpmPdW+ynRTj/x2DAWYrIY7qeZIHidozwV24m4iavr15lNwIwLxRmOxhA==} - engines: {node: '>=12'} - - d3-contour@4.0.2: - resolution: {integrity: sha512-4EzFTRIikzs47RGmdxbeUvLWtGedDUNkTcmzoeyg4sP/dvCexO47AaQL7VKy/gul85TOxw+IBgA8US2xwbToNA==} - engines: {node: '>=12'} - - d3-delaunay@6.0.4: - resolution: {integrity: sha512-mdjtIZ1XLAM8bm/hx3WwjfHt6Sggek7qH043O8KEjDXN40xi3vx/6pYSVTwLjEgiXQTbvaouWKynLBiUZ6SK6A==} - engines: {node: '>=12'} - - d3-dispatch@3.0.1: - resolution: {integrity: sha512-rzUyPU/S7rwUflMyLc1ETDeBj0NRuHKKAcvukozwhshr6g6c5d8zh4c2gQjY2bZ0dXeGLWc1PF174P2tVvKhfg==} - engines: {node: '>=12'} - - d3-drag@3.0.0: - resolution: {integrity: sha512-pWbUJLdETVA8lQNJecMxoXfH6x+mO2UQo8rSmZ+QqxcbyA3hfeprFgIT//HW2nlHChWeIIMwS2Fq+gEARkhTkg==} - engines: {node: '>=12'} - - d3-dsv@3.0.1: - resolution: {integrity: sha512-UG6OvdI5afDIFP9w4G0mNq50dSOsXHJaRE8arAS5o9ApWnIElp8GZw1Dun8vP8OyHOZ/QJUKUJwxiiCCnUwm+Q==} - engines: {node: '>=12'} - hasBin: true - - d3-ease@3.0.1: - resolution: {integrity: sha512-wR/XK3D3XcLIZwpbvQwQ5fK+8Ykds1ip7A2Txe0yxncXSdq1L9skcG7blcedkOX+ZcgxGAmLX1FrRGbADwzi0w==} - engines: {node: '>=12'} - - d3-fetch@3.0.1: - resolution: {integrity: sha512-kpkQIM20n3oLVBKGg6oHrUchHM3xODkTzjMoj7aWQFq5QEM+R6E4WkzT5+tojDY7yjez8KgCBRoj4aEr99Fdqw==} - engines: {node: '>=12'} - - d3-force@3.0.0: - resolution: {integrity: sha512-zxV/SsA+U4yte8051P4ECydjD/S+qeYtnaIyAs9tgHCqfguma/aAQDjo85A9Z6EKhBirHRJHXIgJUlffT4wdLg==} - engines: {node: '>=12'} - - d3-format@3.1.2: - resolution: {integrity: sha512-AJDdYOdnyRDV5b6ArilzCPPwc1ejkHcoyFarqlPqT7zRYjhavcT3uSrqcMvsgh2CgoPbK3RCwyHaVyxYcP2Arg==} - engines: {node: '>=12'} - - d3-geo@3.1.1: - resolution: {integrity: sha512-637ln3gXKXOwhalDzinUgY83KzNWZRKbYubaG+fGVuc/dxO64RRljtCTnf5ecMyE1RIdtqpkVcq0IbtU2S8j2Q==} - engines: {node: '>=12'} - - d3-hierarchy@3.1.2: - resolution: {integrity: sha512-FX/9frcub54beBdugHjDCdikxThEqjnR93Qt7PvQTOHxyiNCAlvMrHhclk3cD5VeAaq9fxmfRp+CnWw9rEMBuA==} - engines: {node: '>=12'} - - d3-interpolate@3.0.1: - resolution: {integrity: sha512-3bYs1rOD33uo8aqJfKP3JWPAibgw8Zm2+L9vBKEHJ2Rg+viTR7o5Mmv5mZcieN+FRYaAOWX5SJATX6k1PWz72g==} - engines: {node: '>=12'} - - d3-path@1.0.9: - resolution: {integrity: sha512-VLaYcn81dtHVTjEHd8B+pbe9yHWpXKZUC87PzoFmsFrJqgFwDe/qxfp5MlfsfM1V5E/iVt0MmEbWQ7FVIXh/bg==} - - d3-path@3.1.0: - resolution: {integrity: sha512-p3KP5HCf/bvjBSSKuXid6Zqijx7wIfNW+J/maPs+iwR35at5JCbLUT0LzF1cnjbCHWhqzQTIN2Jpe8pRebIEFQ==} - engines: {node: '>=12'} - - d3-polygon@3.0.1: - resolution: {integrity: sha512-3vbA7vXYwfe1SYhED++fPUQlWSYTTGmFmQiany/gdbiWgU/iEyQzyymwL9SkJjFFuCS4902BSzewVGsHHmHtXg==} - engines: {node: '>=12'} - - d3-quadtree@3.0.1: - resolution: {integrity: sha512-04xDrxQTDTCFwP5H6hRhsRcb9xxv2RzkcsygFzmkSIOJy3PeRJP7sNk3VRIbKXcog561P9oU0/rVH6vDROAgUw==} - engines: {node: '>=12'} - - d3-random@3.0.1: - resolution: {integrity: sha512-FXMe9GfxTxqd5D6jFsQ+DJ8BJS4E/fT5mqqdjovykEB2oFbTMDVdg1MGFxfQW+FBOGoB++k8swBrgwSHT1cUXQ==} - engines: {node: '>=12'} - - d3-sankey@0.12.3: - resolution: {integrity: sha512-nQhsBRmM19Ax5xEIPLMY9ZmJ/cDvd1BG3UVvt5h3WRxKg5zGRbvnteTyWAbzeSvlh3tW7ZEmq4VwR5mB3tutmQ==} - - d3-scale-chromatic@3.1.0: - resolution: {integrity: sha512-A3s5PWiZ9YCXFye1o246KoscMWqf8BsD9eRiJ3He7C9OBaxKhAd5TFCdEx/7VbKtxxTsu//1mMJFrEt572cEyQ==} - engines: {node: '>=12'} - - d3-scale@4.0.2: - resolution: {integrity: sha512-GZW464g1SH7ag3Y7hXjf8RoUuAFIqklOAq3MRl4OaWabTFJY9PN/E1YklhXLh+OQ3fM9yS2nOkCoS+WLZ6kvxQ==} - engines: {node: '>=12'} - - d3-selection@3.0.0: - resolution: {integrity: sha512-fmTRWbNMmsmWq6xJV8D19U/gw/bwrHfNXxrIN+HfZgnzqTHp9jOmKMhsTUjXOJnZOdZY9Q28y4yebKzqDKlxlQ==} - engines: {node: '>=12'} - - d3-shape@1.3.7: - resolution: {integrity: sha512-EUkvKjqPFUAZyOlhY5gzCxCeI0Aep04LwIRpsZ/mLFelJiUfnK56jo5JMDSE7yyP2kLSb6LtF+S5chMk7uqPqw==} - - d3-shape@3.2.0: - resolution: {integrity: sha512-SaLBuwGm3MOViRq2ABk3eLoxwZELpH6zhl3FbAoJ7Vm1gofKx6El1Ib5z23NUEhF9AsGl7y+dzLe5Cw2AArGTA==} - engines: {node: '>=12'} - - d3-time-format@4.1.0: - resolution: {integrity: sha512-dJxPBlzC7NugB2PDLwo9Q8JiTR3M3e4/XANkreKSUxF8vvXKqm1Yfq4Q5dl8budlunRVlUUaDUgFt7eA8D6NLg==} - engines: {node: '>=12'} - - d3-time@3.1.0: - resolution: {integrity: sha512-VqKjzBLejbSMT4IgbmVgDjpkYrNWUYJnbCGo874u7MMKIWsILRX+OpX/gTk8MqjpT1A/c6HY2dCA77ZN0lkQ2Q==} - engines: {node: '>=12'} - - d3-timer@3.0.1: - resolution: {integrity: sha512-ndfJ/JxxMd3nw31uyKoY2naivF+r29V+Lc0svZxe1JvvIRmi8hUsrMvdOwgS1o6uBHmiz91geQ0ylPP0aj1VUA==} - engines: {node: '>=12'} - - d3-transition@3.0.1: - resolution: {integrity: sha512-ApKvfjsSR6tg06xrL434C0WydLr7JewBB3V+/39RMHsaXTOG0zmt/OAXeng5M5LBm0ojmxJrpomQVZ1aPvBL4w==} - engines: {node: '>=12'} - peerDependencies: - d3-selection: 2 - 3 - - d3-zoom@3.0.0: - resolution: {integrity: sha512-b8AmV3kfQaqWAuacbPuNbL6vahnOJflOhexLzMMNLga62+/nh0JzvJ0aO/5a5MVgUFGS7Hu1P9P03o3fJkDCyw==} - engines: {node: '>=12'} - - d3@7.9.0: - resolution: {integrity: sha512-e1U46jVP+w7Iut8Jt8ri1YsPOvFpg46k+K8TpCb0P+zjCkjkPnV7WzfDJzMHy1LnA+wj5pLT1wjO901gLXeEhA==} - engines: {node: '>=12'} - - dagre-d3-es@7.0.14: - resolution: {integrity: sha512-P4rFMVq9ESWqmOgK+dlXvOtLwYg0i7u0HBGJER0LZDJT2VHIPAMZ/riPxqJceWMStH5+E61QxFra9kIS3AqdMg==} - - dayjs@1.11.21: - resolution: {integrity: sha512-98IT+HOahAisibz/yjKbzuOBwYcjJ7BCLPzARyHiyEBmRz4fatF+KPJszEHXsGYjUG234aH/cOjW1wwTbKUZlA==} - - debounce-fn@4.0.0: - resolution: {integrity: sha512-8pYCQiL9Xdcg0UPSD3d+0KMlOjp+KGU5EPwYddgzQ7DATsg4fuUDjQtsYLmWjnk2obnNHgV3vE2Y4jejSOJVBQ==} - engines: {node: '>=10'} - - debug@4.4.3: - resolution: {integrity: sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==} - engines: {node: '>=6.0'} - peerDependencies: - supports-color: '*' - peerDependenciesMeta: - supports-color: - optional: true - - decimal.js-light@2.5.1: - resolution: {integrity: sha512-qIMFpTMZmny+MMIitAB6D7iVPEorVw6YQRWkvarTkT4tBeSLLiHzcwj6q0MmYSFCiVpiqPJTJEYIrpcPzVEIvg==} - - decode-named-character-reference@1.3.0: - resolution: {integrity: sha512-GtpQYB283KrPp6nRw50q3U9/VfOutZOe103qlN7BPP6Ad27xYnOIWv4lPzo8HCAL+mMZofJ9KEy30fq6MfaK6Q==} - - decode-uri-component@0.5.0: - resolution: {integrity: sha512-1BiQVoK8C9gUbQU6NzAtO/tkz2qOFpEObMWpcFvhx4fYnj4Oc5yzaJN/LD36ihkVUdXyh5ZekzX+yM+ty/SrPg==} - engines: {node: '>=14.16'} - - dedent@1.7.2: - resolution: {integrity: sha512-WzMx3mW98SN+zn3hgemf4OzdmyNhhhKz5Ay0pUfQiMQ3e1g+xmTJWp/pKdwKVXhdSkAEGIIzqeuWrL3mV/AXbA==} - peerDependencies: - babel-plugin-macros: ^3.1.0 - peerDependenciesMeta: - babel-plugin-macros: - optional: true - - deepmerge@4.3.1: - resolution: {integrity: sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==} - engines: {node: '>=0.10.0'} - - default-browser-id@5.0.1: - resolution: {integrity: sha512-x1VCxdX4t+8wVfd1so/9w+vQ4vx7lKd2Qp5tDRutErwmR85OgmfX7RlLRMWafRMY7hbEiXIbudNrjOAPa/hL8Q==} - engines: {node: '>=18'} - - default-browser@5.5.0: - resolution: {integrity: sha512-H9LMLr5zwIbSxrmvikGuI/5KGhZ8E2zH3stkMgM5LpOWDutGM2JZaj460Udnf1a+946zc7YBgrqEWwbk7zHvGw==} - engines: {node: '>=18'} - - define-lazy-prop@2.0.0: - resolution: {integrity: sha512-Ds09qNh8yw3khSjiJjiUInaGX9xlqZDY7JVryGxdxV7NPeuqQfplOpQ66yJFZut3jLa5zOwkXw1g9EI2uKh4Og==} - engines: {node: '>=8'} - - define-lazy-prop@3.0.0: - resolution: {integrity: sha512-N+MeXYoqr3pOgn8xfyRPREN7gHakLYjhsHhWGT3fWAiL4IkAt0iDw14QiiEm2bE30c5XX5q0FtAA3CK5f9/BUg==} - engines: {node: '>=12'} - - delaunator@5.1.0: - resolution: {integrity: sha512-AGrQ4QSgssa1NGmWmLPqN5NY2KajF5MqxetNEO+o0n3ZwZZeTmt7bBnvzHWrmkZFxGgr4HdyFgelzgi06otLuQ==} - - delayed-stream@1.0.0: - resolution: {integrity: sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==} - engines: {node: '>=0.4.0'} - - depd@2.0.0: - resolution: {integrity: sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==} - engines: {node: '>= 0.8'} - - dequal@2.0.3: - resolution: {integrity: sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==} - engines: {node: '>=6'} - - detect-gpu@5.0.70: - resolution: {integrity: sha512-bqerEP1Ese6nt3rFkwPnGbsUF9a4q+gMmpTVVOEzoCyeCc+y7/RvJnQZJx1JwhgQI5Ntg0Kgat8Uu7XpBqnz1w==} - - detect-libc@2.1.2: - resolution: {integrity: sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==} - engines: {node: '>=8'} - - detect-node-es@1.1.0: - resolution: {integrity: sha512-ypdmJU/TbBby2Dxibuv7ZLW3Bs1QEmM7nHjEANfohJLvE0XVujisn1qPJcZxg+qDucsr+bP6fLD1rPS3AhJ7EQ==} - - devlop@1.1.0: - resolution: {integrity: sha512-RWmIqhcFf1lRYBvNmr7qTNuyCt/7/ns2jbpp1+PalgE/rDQcBT0fioSMUpJ93irlUhC5hrg4cYqe6U+0ImW0rA==} - - diff@8.0.4: - resolution: {integrity: sha512-DPi0FmjiSU5EvQV0++GFDOJ9ASQUVFh5kD+OzOnYdi7n3Wpm9hWWGfB/O2blfHcMVTL5WkQXSnRiK9makhrcnw==} - engines: {node: '>=0.3.1'} - - diff@9.0.0: - resolution: {integrity: sha512-svtcdpS8CgJyqAjEQIXdb3OjhFVVYjzGAPO8WGCmRbrml64SPw/jJD4GoE98aR7r25A0XcgrK3F02yw9R/vhQw==} - engines: {node: '>=0.3.1'} - - dingbat-to-unicode@1.0.1: - resolution: {integrity: sha512-98l0sW87ZT58pU4i61wa2OHwxbiYSbuxsCBozaVnYX2iCnr3bLM3fIes1/ej7h1YdOKuKt/MLs706TVnALA65w==} - - dompurify@3.4.13: - resolution: {integrity: sha512-2vmYIoqjze2d+kakP8S/nS5shfsl587kzwEjcGlTdiksUVgFHnFCsLYDVj/JNqJVOQZGSYBTmuycv0PodwmnMQ==} - - dot-prop@6.0.1: - resolution: {integrity: sha512-tE7ztYzXHIeyvc7N+hR3oi7FIbf/NIjVP9hmAt3yMXzrQ072/fpjGLx2GxNxGxUl5V73MEqYzioOMoVhGMJ5cA==} - engines: {node: '>=10'} - - dotenv@17.4.2: - resolution: {integrity: sha512-nI4U3TottKAcAD9LLud4Cb7b2QztQMUEfHbvhTH09bqXTxnSie8WnjPALV/WMCrJZ6UV/qHJ6L03OqO3LcdYZw==} - engines: {node: '>=12'} - - draco3d@1.5.7: - resolution: {integrity: sha512-m6WCKt/erDXcw+70IJXnG7M3awwQPAsZvJGX5zY7beBqpELw6RDGkYVU0W43AFxye4pDZ5i2Lbyc/NNGqwjUVQ==} - - duck@0.1.12: - resolution: {integrity: sha512-wkctla1O6VfP89gQ+J/yDesM0S7B7XLXjKGzXxMDVFg7uEn706niAtyYovKbyq1oT9YwDcly721/iUWoc8MVRg==} - - dunder-proto@1.0.1: - resolution: {integrity: sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==} - engines: {node: '>= 0.4'} - - ee-first@1.1.1: - resolution: {integrity: sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==} - - electron-to-chromium@1.5.406: - resolution: {integrity: sha512-hWH5ORBi3d0IipnMh7BN5GDTaAmrSSSWmznwt2zltdiRNEWoEQyTwF0FFSBxzHO7hLSRT6loQu3IQGV0wg/Tvg==} - - elkjs@0.11.1: - resolution: {integrity: sha512-zxxR9k+rx5ktMwT/FwyLdPCrq7xN6e4VGGHH8hA01vVYKjTFik7nHOxBnAYtrgYUB1RpAiLvA1/U2YraWxyKKg==} - - emoji-mart@5.6.0: - resolution: {integrity: sha512-eJp3QRe79pjwa+duv+n7+5YsNhRcMl812EcFVwrnRvYKoNPoQb5qxU8DG6Bgwji0akHdp6D4Ln6tYLG58MFSow==} - - emoji-regex@10.6.0: - resolution: {integrity: sha512-toUI84YS5YmxW219erniWD0CIVOo46xGKColeNQRgOzDorgBi1v4D71/OFzgD9GO2UGKIv1C3Sp8DAn0+j5w7A==} - - encodeurl@2.0.0: - resolution: {integrity: sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==} - engines: {node: '>= 0.8'} - - enhanced-resolve@5.24.5: - resolution: {integrity: sha512-L1l8TNvomm6UVW5B253AGxQagSQr+vGwhMlrrfRS2qmhx46AMpMVJKQYLvWYbysTMY8VoicOvzHzoHMbyzB+4A==} - engines: {node: '>=10.13.0'} - - enquirer@2.4.1: - resolution: {integrity: sha512-rRqJg/6gd538VHvR3PSrdRBb/1Vy2YfzHqzvbhGIQpDRKIa4FgV/54b5Q1xYSxOOwKvjXweS26E0Q+nAMwp2pQ==} - engines: {node: '>=8.6'} - - entities@6.0.1: - resolution: {integrity: sha512-aN97NXWF6AWBTahfVOIrB/NShkzi5H7F9r1s9mD3cDj4Ko5f2qhhVoYMibXF7GlLveb/D2ioWay8lxI97Ven3g==} - engines: {node: '>=0.12'} - - entities@7.0.1: - resolution: {integrity: sha512-TWrgLOFUQTH994YUyl1yT4uyavY5nNB5muff+RtWaqNVCAK408b5ZnnbNAUEWLTCpum9w6arT70i1XdQ4UeOPA==} - engines: {node: '>=0.12'} - - env-paths@2.2.1: - resolution: {integrity: sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A==} - engines: {node: '>=6'} - - error-ex@1.3.4: - resolution: {integrity: sha512-sqQamAnR14VgCr1A618A3sGrygcpK+HEbenA/HiEAkkUwcZIIB/tgWqHFxWgOyDh4nB4JCRimh79dR5Ywc9MDQ==} - - es-define-property@1.0.1: - resolution: {integrity: sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==} - engines: {node: '>= 0.4'} - - es-errors@1.3.0: - resolution: {integrity: sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==} - engines: {node: '>= 0.4'} - - es-object-atoms@1.1.2: - resolution: {integrity: sha512-HWcBoN6NileqtSydK2FqHbS/LoDd2pqrnQHLyJzBj4kOp/ky2MWMN694xOfkK8/SnUsW2DH7EfyVlydKCsm1Zw==} - engines: {node: '>= 0.4'} - - es-set-tostringtag@2.1.0: - resolution: {integrity: sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==} - engines: {node: '>= 0.4'} - - es-toolkit@1.50.0: - resolution: {integrity: sha512-OyZKhUVvEep9ITEiwHn8GKnMRQIVqoSIX7WnRbkWgJkllCujilqP2rD0u979tkl8wqyc8ICwlc1UBVv/Sl1G6w==} - - es6-promise-pool@2.5.0: - resolution: {integrity: sha512-VHErXfzR/6r/+yyzPKeBvO0lgjfC5cbDCQWjWwMZWSb6YU39TGIl51OUmCfWCq4ylMdJSB8zkz2vIuIeIxXApA==} - engines: {node: '>=0.10.0'} - - esast-util-from-estree@2.0.0: - resolution: {integrity: sha512-4CyanoAudUSBAn5K13H4JhsMH6L9ZP7XbLVe/dKybkxMO7eDyLsT8UHl9TRNrU2Gr9nz+FovfSIjuXWJ81uVwQ==} - - esast-util-from-js@2.0.1: - resolution: {integrity: sha512-8Ja+rNJ0Lt56Pcf3TAmpBZjmx8ZcK5Ts4cAzIOjsjevg9oSXJnl6SUQ2EevU8tv3h6ZLWmoKL5H4fgWvdvfETw==} - - escalade@3.2.0: - resolution: {integrity: sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==} - engines: {node: '>=6'} - - escape-html@1.0.3: - resolution: {integrity: sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==} - - escape-string-regexp@4.0.0: - resolution: {integrity: sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==} - engines: {node: '>=10'} - - escape-string-regexp@5.0.0: - resolution: {integrity: sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw==} - engines: {node: '>=12'} - - esprima@4.0.1: - resolution: {integrity: sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==} - engines: {node: '>=4'} - hasBin: true - - estree-util-attach-comments@3.0.0: - resolution: {integrity: sha512-cKUwm/HUcTDsYh/9FgnuFqpfquUbwIqwKM26BVCGDPVgvaCl/nDCCjUfiLlx6lsEZ3Z4RFxNbOQ60pkaEwFxGw==} - - estree-util-build-jsx@3.0.1: - resolution: {integrity: sha512-8U5eiL6BTrPxp/CHbs2yMgP8ftMhR5ww1eIKoWRMlqvltHF8fZn5LRDvTKuxD3DUn+shRbLGqXemcP51oFCsGQ==} - - estree-util-is-identifier-name@3.0.0: - resolution: {integrity: sha512-hFtqIDZTIUZ9BXLb8y4pYGyk6+wekIivNVTcmvk8NoOh+VeRn5y6cEHzbURrWbfp1fIqdVipilzj+lfaadNZmg==} - - estree-util-scope@1.0.0: - resolution: {integrity: sha512-2CAASclonf+JFWBNJPndcOpA8EMJwa0Q8LUFJEKqXLW6+qBvbFZuF5gItbQOs/umBUkjviCSDCbBwU2cXbmrhQ==} - - estree-util-to-js@2.0.0: - resolution: {integrity: sha512-WDF+xj5rRWmD5tj6bIqRi6CkLIXbbNQUcxQHzGysQzvHmdYG2G7p/Tf0J0gpxGgkeMZNTIjT/AoSvC9Xehcgdg==} - - estree-util-visit@2.0.0: - resolution: {integrity: sha512-m5KgiH85xAhhW8Wta0vShLcUvOsh3LLPI2YVwcbio1l7E09NTLL1EyMZFM1OyWowoH0skScNbhOPl4kcBgzTww==} - - estree-walker@3.0.3: - resolution: {integrity: sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==} - - etag@1.8.1: - resolution: {integrity: sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==} - engines: {node: '>= 0.6'} - - eventemitter3@4.0.7: - resolution: {integrity: sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==} - - eventemitter3@5.0.4: - resolution: {integrity: sha512-mlsTRyGaPBjPedk6Bvw+aqbsXDtoAyAzm5MO7JgU+yVRyMQ5O8bD4Kcci7BS85f93veegeCPkL8R4GLClnjLFw==} - - eventsource-parser@3.1.1: - resolution: {integrity: sha512-EKN1vKAMcZ8MlYMpaNuxN6R9yakzH6uajHcHVTqWJzvu5pWw9DyhbP35HH8MVBQ+dZjAfDxk+A8NiR9KWaXiyQ==} - engines: {node: '>=18.0.0'} - - eventsource@3.0.7: - resolution: {integrity: sha512-CRT1WTyuQoD771GW56XEZFQ/ZoSfWid1alKGDYMmkt2yl8UXrVR4pspqWNEcqKvVIzg6PAltWjxcSSPrboA4iA==} - engines: {node: '>=18.0.0'} - - execa@5.1.1: - resolution: {integrity: sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==} - engines: {node: '>=10'} - - execa@9.6.1: - resolution: {integrity: sha512-9Be3ZoN4LmYR90tUoVu2te2BsbzHfhJyfEiAVfz7N5/zv+jduIfLrV2xdQXOHbaD6KgpGdO9PRPM1Y4Q9QkPkA==} - engines: {node: ^18.19.0 || >=20.5.0} - - express-rate-limit@8.6.2: - resolution: {integrity: sha512-YH4ru+eOJxQABscKFfRCy9R7x9QFGdezclVMwwgFFndzS2Xnm0uo6B0ABZsLhcpeptGv2qvuJVWlQr9gQZoC3A==} - engines: {node: '>= 16'} - peerDependencies: - express: '>= 4.11' - - express@5.2.1: - resolution: {integrity: sha512-hIS4idWWai69NezIdRt2xFVofaF4j+6INOpJlVOLDO8zXGpUVEVzIYk12UUi2JzjEzWL3IOAxcTubgz9Po0yXw==} - engines: {node: '>= 18'} - - extend-shallow@2.0.1: - resolution: {integrity: sha512-zCnTtlxNoAiDc3gqY2aYAWFx7XWWiasuF2K8Me5WbN8otHKTUKBwjPtNpRs/rbUZm7KxWAaNj7P1a/p52GbVug==} - engines: {node: '>=0.10.0'} - - extend-shallow@3.0.2: - resolution: {integrity: sha512-BwY5b5Ql4+qZoefgMj2NUmx+tehVTH/Kf4k1ZEtOHNFcm2wSxMRo992l6X3TIgni2eZVTZ85xMOjF31fwZAj6Q==} - engines: {node: '>=0.10.0'} - - extend@3.0.2: - resolution: {integrity: sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==} - - fast-deep-equal@3.1.3: - resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==} - - fast-equals@5.4.1: - resolution: {integrity: sha512-DjlFSM5Pk9cGcL0q5QXl66eGzx0N6szNgaswwc5ZphlBohjTVJSnGgI+rJVOgOi65qUoQnDZN4nDqi33udtydQ==} - engines: {node: '>=6.0.0'} - - fast-glob@3.3.3: - resolution: {integrity: sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==} - engines: {node: '>=8.6.0'} - - fast-uri@3.1.5: - resolution: {integrity: sha512-gHwA1O9LDIcKunMKhObS/HimwtehO1nPUECKAu5TpKgaO19fcWEl4bliWe1jWxVFvIXztJjjQ4L8XQ1EU9f7Jw==} - - fastq@1.20.1: - resolution: {integrity: sha512-GGToxJ/w1x32s/D2EKND7kTil4n8OVk/9mycTc4VDza13lOvpUZTGX3mFSCtV9ksdGBVzvsyAVLM6mHFThxXxw==} - - fault@1.0.4: - resolution: {integrity: sha512-CJ0HCB5tL5fYTEA7ToAq5+kTwd++Borf1/bifxd9iT70QcXr4MRrO3Llf8Ifs70q+SJcGHFtnIE/Nw6giCtECA==} - - fdir@6.5.0: - resolution: {integrity: sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==} - engines: {node: '>=12.0.0'} - peerDependencies: - picomatch: ^3 || ^4 - peerDependenciesMeta: - picomatch: - optional: true - - fflate@0.6.11: - resolution: {integrity: sha512-3JyEFWGjFn7zHmoa9+zG1BmW7X2okcmAB+0Cnu9UFbVs/jCBnl2A8o065ZlXiw145K3eBM3uLuzrYXC0RK7eDg==} - - fflate@0.8.3: - resolution: {integrity: sha512-tbZNuJrLwGUp3zshBtdy4W+ORxZuIh8a5ilyIEQDC5rY1f3U20JMry0Ll3WBzU58EZKsEuJFXhb5gwv8CsPvgA==} - - figures@6.1.0: - resolution: {integrity: sha512-d+l3qxjSesT4V7v2fh+QnmFnUWv9lSpjarhShNTgBOfA0ttejbQUAlHLitbjkoRiDulW0OPoQPYIGhIC8ohejg==} - engines: {node: '>=18'} - - file-saver@2.0.5: - resolution: {integrity: sha512-P9bmyZ3h/PRG+Nzga+rbdI4OEpNDzAVyy74uVO9ATgzLK6VtAsYybF/+TOCvrc0MO793d6+42lLyZTw7/ArVzA==} - - file-selector@0.4.0: - resolution: {integrity: sha512-iACCiXeMYOvZqlF1kTiYINzgepRBymz1wwjiuup9u9nayhb6g4fSwiyJ/6adli+EPwrWtpgQAh2PoS7HukEGEg==} - engines: {node: '>= 10'} - - fill-range@7.1.1: - resolution: {integrity: sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==} - engines: {node: '>=8'} - - filter-obj@5.1.0: - resolution: {integrity: sha512-qWeTREPoT7I0bifpPUXtxkZJ1XJzxWtfoWWkdVGqa+eCr3SHW/Ocp89o8vLvbUuQnadybJpjOKu4V+RwO6sGng==} - engines: {node: '>=14.16'} - - finalhandler@2.1.1: - resolution: {integrity: sha512-S8KoZgRZN+a5rNwqTxlZZePjT/4cnm0ROV70LedRHZ0p8u9fRID0hJUZQpkKLzro8LfmC8sx23bY6tVNxv8pQA==} - engines: {node: '>= 18.0.0'} - - find-root@1.1.0: - resolution: {integrity: sha512-NKfW6bec6GfKc0SGx1e07QZY9PE99u0Bft/0rzSD5k3sO/vwkVUpDUKVm5Gpp5Ue3YfShPFTX2070tDs5kB9Ng==} - - find-up@3.0.0: - resolution: {integrity: sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==} - engines: {node: '>=6'} - - follow-redirects@1.16.0: - resolution: {integrity: sha512-y5rN/uOsadFT/JfYwhxRS5R7Qce+g3zG97+JrtFZlC9klX/W5hD7iiLzScI4nZqUS7DNUdhPgw4xI8W2LuXlUw==} - engines: {node: '>=4.0'} - peerDependencies: - debug: '*' - peerDependenciesMeta: - debug: - optional: true - - for-in@1.0.2: - resolution: {integrity: sha512-7EwmXrOjyL+ChxMhmG5lnW9MPt1aIeZEwKhQzoBUdTV0N3zuwWDZYVJatDvZ2OyzPUvdIAZDsCetk3coyMfcnQ==} - engines: {node: '>=0.10.0'} - - form-data@4.0.6: - resolution: {integrity: sha512-vKatAh4SlVfgbv+YtmhiRjhEMJsYpsG1Y2rMQtR+SVSbytsSD1YGzDIcrAJmdFec88u/+VoGmxnl+80gL1tRCQ==} - engines: {node: '>= 6'} - - format@0.2.2: - resolution: {integrity: sha512-wzsgA6WOq+09wrU1tsJ09udeR/YZRaeArL9e1wPbFg3GG2yDnC2ldKpxs4xunpFF9DgqCqOIra3bc1HWrJ37Ww==} - engines: {node: '>=0.4.x'} - - forwarded@0.2.0: - resolution: {integrity: sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==} - engines: {node: '>= 0.6'} - - fractional-indexing@3.2.0: - resolution: {integrity: sha512-PcOxmqwYCW7O2ovKRU8OoQQj2yqTfEB/yeTYk4gPid6dN5ODRfU1hXd9tTVZzax/0NkO7AxpHykvZnT1aYp/BQ==} - engines: {node: ^14.13.1 || >=16.0.0} - - framer-motion@12.43.0: - resolution: {integrity: sha512-1eaL3RvR/kAlbG7UYcpMptEyzPoENO0c6w7ZnB3/hh2vSAz/6uGAFn6fdoqTBguNstf3MsFhJHsD/0DHiclG+g==} - peerDependencies: - '@emotion/is-prop-valid': '*' - react: ^18.0.0 || ^19.0.0 - react-dom: ^18.0.0 || ^19.0.0 - peerDependenciesMeta: - '@emotion/is-prop-valid': - optional: true - react: - optional: true - react-dom: - optional: true - - fresh@2.0.0: - resolution: {integrity: sha512-Rx/WycZ60HOaqLKAi6cHRKKI7zxWbJ31MhntmtwMoaTeF7XFH9hhBp8vITaMidfljRQ6eYWCKkaTK+ykVJHP2A==} - engines: {node: '>= 0.8'} - - fs-extra@11.4.0: - resolution: {integrity: sha512-EQsFzMUJkCKGr1ePqlYADkIUmHW1s3ZXr5Yqy6wbGrfUCphpl2maM/kyOIRA2HpP3AaFQTZXD4ldjek+nccddA==} - engines: {node: '>=14.14'} - - fsevents@2.3.3: - resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==} - engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} - os: [darwin] - - function-bind@1.1.2: - resolution: {integrity: sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==} - - fuzzy@0.1.3: - resolution: {integrity: sha512-/gZffu4ykarLrCiP3Ygsa86UAo1E5vEVlvTrpkKywXSbP9Xhln3oSp9QSV57gEq3JFFpGJ4GZ+5zdEp3FcUh4w==} - engines: {node: '>= 0.6.0'} - - fuzzysort@3.1.0: - resolution: {integrity: sha512-sR9BNCjBg6LNgwvxlBd0sBABvQitkLzoVY9MYYROQVX/FvfJ4Mai9LsGhDgd8qYdds0bY77VzYd5iuB+v5rwQQ==} - - gensync@1.0.0-beta.2: - resolution: {integrity: sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==} - engines: {node: '>=6.9.0'} - - get-east-asian-width@1.6.0: - resolution: {integrity: sha512-QRbvDIbx6YklUe6RxeTeleMR0yv3cYH6PsPZHcnVn7xv7zO1BHN8r0XETu8n6Ye3Q+ahtSarc3WgtNWmehIBfA==} - engines: {node: '>=18'} - - get-intrinsic@1.3.0: - resolution: {integrity: sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==} - engines: {node: '>= 0.4'} - - get-nonce@1.0.1: - resolution: {integrity: sha512-FJhYRoDaiatfEkUK8HKlicmu/3SGFD51q3itKDGoSTysQJBnfOcxU5GxnhE1E6soB76MbT0MBtnKJuXyAx+96Q==} - engines: {node: '>=6'} - - get-own-enumerable-keys@1.0.0: - resolution: {integrity: sha512-PKsK2FSrQCyxcGHsGrLDcK0lx+0Ke+6e8KFFozA9/fIQLhQzPaRvJFdcz7+Axg3jUH/Mq+NI4xa5u/UT2tQskA==} - engines: {node: '>=14.16'} - - get-proto@1.0.1: - resolution: {integrity: sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==} - engines: {node: '>= 0.4'} - - get-stream@6.0.1: - resolution: {integrity: sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==} - engines: {node: '>=10'} - - get-stream@9.0.1: - resolution: {integrity: sha512-kVCxPF3vQM/N0B1PmoqVUqgHP+EeVjmZSQn+1oCRPxd2P21P2F19lIgbR3HBosbB1PUhOAoctJnfEn2GbN2eZA==} - engines: {node: '>=18'} - - get-value@2.0.6: - resolution: {integrity: sha512-Ln0UQDlxH1BapMu3GPtf7CuYNwRZf2gwCuPqbyG6pB8WfmFpzqcy4xtAaAMUhnNqjMKTiCPZG2oMT3YSx8U2NA==} - engines: {node: '>=0.10.0'} - - giscus@1.6.0: - resolution: {integrity: sha512-Zrsi8r4t1LVW950keaWcsURuZUQwUaMKjvJgTCY125vkW6OiEBkatE7ScJDbpqKHdZwb///7FVC21SE3iFK3PQ==} - - glob-parent@5.1.2: - resolution: {integrity: sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==} - engines: {node: '>= 6'} - - glsl-noise@0.0.0: - resolution: {integrity: sha512-b/ZCF6amfAUb7dJM/MxRs7AetQEahYzJ8PtgfrmEdtw6uyGOr+ZSGtgjFm6mfsBkxJ4d2W7kg+Nlqzqvn3Bc0w==} - - glur@1.1.2: - resolution: {integrity: sha512-l+8esYHTKOx2G/Aao4lEQ0bnHWg4fWtJbVoZZT9Knxi01pB8C80BR85nONLFwkkQoFRCmXY+BUcGZN3yZ2QsRA==} - - gopd@1.2.0: - resolution: {integrity: sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==} - engines: {node: '>= 0.4'} - - graceful-fs@4.2.11: - resolution: {integrity: sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==} - - hachure-fill@0.5.2: - resolution: {integrity: sha512-3GKBOn+m2LX9iq+JC1064cSFprJY4jL1jCXTcpnfER5HYE2l/4EfWSGzkPa/ZDBmYI0ZOEj5VHV/eKnPGkHuOg==} - - has-symbols@1.1.0: - resolution: {integrity: sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==} - engines: {node: '>= 0.4'} - - has-tostringtag@1.0.2: - resolution: {integrity: sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==} - engines: {node: '>= 0.4'} - - hasown@2.0.4: - resolution: {integrity: sha512-T2UbfbBEF32wiepXIsMlTW9+dDYC6wMh/t/vYA4tuOMKqWz/n3vr1NFSxQiyP+zk2mXsoMA/i/7qV6LKut1t1A==} - engines: {node: '>= 0.4'} - - hast-util-from-dom@5.0.1: - resolution: {integrity: sha512-N+LqofjR2zuzTjCPzyDUdSshy4Ma6li7p/c3pA78uTwzFgENbgbUrm2ugwsOdcjI1muO+o6Dgzp9p8WHtn/39Q==} - - hast-util-from-html-isomorphic@2.0.0: - resolution: {integrity: sha512-zJfpXq44yff2hmE0XmwEOzdWin5xwH+QIhMLOScpX91e/NSGPsAzNCvLQDIEPyO2TXi+lBmU6hjLIhV8MwP2kw==} - - hast-util-from-html@2.0.3: - resolution: {integrity: sha512-CUSRHXyKjzHov8yKsQjGOElXy/3EKpyX56ELnkHH34vDVw1N1XSQ1ZcAvTyAPtGqLTuKP/uxM+aLkSPqF/EtMw==} - - hast-util-from-parse5@8.0.3: - resolution: {integrity: sha512-3kxEVkEKt0zvcZ3hCRYI8rqrgwtlIOFMWkbclACvjlDw8Li9S2hk/d51OI0nr/gIpdMHNepwgOKqZ/sy0Clpyg==} - - hast-util-is-element@3.0.0: - resolution: {integrity: sha512-Val9mnv2IWpLbNPqc/pUem+a7Ipj2aHacCwgNfTiK0vJKl0LF+4Ba4+v1oPHFpf3bLYmreq0/l3Gud9S5OH42g==} - - hast-util-parse-selector@4.0.0: - resolution: {integrity: sha512-wkQCkSYoOGCRKERFWcxMVMOcYE2K1AaNLU8DXS9arxnLOUEWbOXKXiJUNzEpqZ3JOKpnha3jkFrumEjVliDe7A==} - - hast-util-raw@9.1.0: - resolution: {integrity: sha512-Y8/SBAHkZGoNkpzqqfCldijcuUKh7/su31kEBp67cFY09Wy0mTRgtsLYsiIxMJxlu0f6AA5SUTbDR8K0rxnbUw==} - - hast-util-sanitize@5.0.2: - resolution: {integrity: sha512-3yTWghByc50aGS7JlGhk61SPenfE/p1oaFeNwkOOyrscaOkMGrcW9+Cy/QAIOBpZxP1yqDIzFMR0+Np0i0+usg==} - - hast-util-to-estree@3.1.3: - resolution: {integrity: sha512-48+B/rJWAp0jamNbAAf9M7Uf//UVqAoMmgXhBdxTDJLGKY+LRnZ99qcG+Qjl5HfMpYNzS5v4EAwVEF34LeAj7w==} - - hast-util-to-html@9.0.5: - resolution: {integrity: sha512-OguPdidb+fbHQSU4Q4ZiLKnzWo8Wwsf5bZfbvu7//a9oTYoqD/fWpe96NuHkoS9h0ccGOTe0C4NGXdtS0iObOw==} - - hast-util-to-jsx-runtime@2.3.6: - resolution: {integrity: sha512-zl6s8LwNyo1P9uw+XJGvZtdFF1GdAkOg8ujOw+4Pyb76874fLps4ueHXDhXWdk6YHQ6OgUtinliG7RsYvCbbBg==} - - hast-util-to-parse5@8.0.1: - resolution: {integrity: sha512-MlWT6Pjt4CG9lFCjiz4BH7l9wmrMkfkJYCxFwKQic8+RTZgWPuWxwAfjJElsXkex7DJjfSJsQIt931ilUgmwdA==} - - hast-util-to-text@4.0.2: - resolution: {integrity: sha512-KK6y/BN8lbaq654j7JgBydev7wuNMcID54lkRav1P0CaE1e47P72AWWPiGKXTJU271ooYzcvTAn/Zt0REnvc7A==} - - hast-util-whitespace@3.0.0: - resolution: {integrity: sha512-88JUN06ipLwsnv+dVn+OIYOvAuvBMy/Qoi6O7mQHxdPXpjy+Cd6xRkWwux7DKO+4sYILtLBRIKgsdpS2gQc7qw==} - - hastscript@9.0.1: - resolution: {integrity: sha512-g7df9rMFX/SPi34tyGCyUBREQoKkapwdY/T04Qn9TDWfHhAYt4/I0gMVirzK5wEzeUqIjEB+LXC/ypb7Aqno5w==} - - highlight.js@10.7.3: - resolution: {integrity: sha512-tzcUFauisWKNHaRkN4Wjl/ZA07gENAjFl3J/c480dprkGTg5EQstgaNFqBfUqCq54kZRIEcreTsAgF/m2quD7A==} - - highlightjs-vue@1.0.0: - resolution: {integrity: sha512-PDEfEF102G23vHmPhLyPboFCD+BkMGu+GuJe2d9/eH4FsCwvgBpnc9n0pGE+ffKdph38s6foEZiEjdgHdzp+IA==} - - hls.js@1.7.0: - resolution: {integrity: sha512-NnQpT6Z//+2IB2qovSUbKbrlXY1MOpi0JynZ9L2NNOuTJiNCyMSQj9k2laEj1v7CxU/Hb8oTWR98eT3mVpPQVQ==} - - hoist-non-react-statics@3.3.2: - resolution: {integrity: sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw==} - - hono@4.13.2: - resolution: {integrity: sha512-JydRilDRkYBQMt9qR9U92mXxmbGqsqSn/IKOrh4e7/gEbn+0zSr8igTu0obwJoNGN4sez28DIql7FBHWydoJpA==} - engines: {node: '>=16.9.0'} - - html-url-attributes@3.0.1: - resolution: {integrity: sha512-ol6UPyBWqsrO6EJySPz2O7ZSr856WDrEzM5zMqp+FJJLGMW35cLYmmZnl0vztAZxRUoNZJFTCohfjuIJ8I4QBQ==} - - html-void-elements@3.0.0: - resolution: {integrity: sha512-bEqo66MRXsUGxWHV5IP0PUiAWwoEjba4VCzg0LjFJBpchPaTfyfCKTG6bc5F8ucKec3q5y6qOdGyYTSBEvhCrg==} - - http-errors@2.0.1: - resolution: {integrity: sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ==} - engines: {node: '>= 0.8'} - - https-proxy-agent@5.0.1: - resolution: {integrity: sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==} - engines: {node: '>= 6'} - - human-signals@2.1.0: - resolution: {integrity: sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==} - engines: {node: '>=10.17.0'} - - human-signals@8.0.1: - resolution: {integrity: sha512-eKCa6bwnJhvxj14kZk5NCPc6Hb6BdsU9DZcOnmQKSnO1VKrfV0zCvtttPZUsBvjmNDn8rpcJfpwSYnHBjc95MQ==} - engines: {node: '>=18.18.0'} - - iconv-lite@0.6.3: - resolution: {integrity: sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==} - engines: {node: '>=0.10.0'} - - iconv-lite@0.7.3: - resolution: {integrity: sha512-IKXpvIzjnC9XTAUbVBcMfGS0EPaIXtW6v+zr+RRp+hqULEpo0owZax6wyRwPOJbWbzjYspQwusTsfVr0ifh4uQ==} - engines: {node: '>=0.10.0'} - - idb@7.1.1: - resolution: {integrity: sha512-gchesWBzyvGHRO9W8tzUWFDycow5gwjvFKfyV9FF32Y7F50yZMp7mP+T2mJIWFx49zicqyC4uefHM17o6xKIVQ==} - - idb@8.0.3: - resolution: {integrity: sha512-LtwtVyVYO5BqRvcsKuB2iUMnHwPVByPCXFXOpuU96IZPPoPN6xjOGxZQ74pgSVVLQWtUOYgyeL4GE98BY5D3wg==} - - ieee754@1.2.1: - resolution: {integrity: sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==} - - ignore@5.3.2: - resolution: {integrity: sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==} - engines: {node: '>= 4'} - - image-blob-reduce@3.0.1: - resolution: {integrity: sha512-/VmmWgIryG/wcn4TVrV7cC4mlfUC/oyiKIfSg5eVM3Ten/c1c34RJhMYKCWTnoSMHSqXLt3tsrBR4Q2HInvN+Q==} - - immediate@3.0.6: - resolution: {integrity: sha512-XXOFtyqDjNDAQxVfYxuF7g9Il/IbWmmlQg2MYKOH8ExIT1qg6xc4zyS3HaEEATgs1btfzxq15ciUiY7gjSXRGQ==} - - immer@11.1.16: - resolution: {integrity: sha512-Xs7H9rBc+kti1J6RueUvbEBkmOz7jqj11XYgf+YMXAYzu8EeE7hwZ9poLXdVfVnGmJu7QAf41T7H2KuF6QoK6Q==} - - immutable@4.3.9: - resolution: {integrity: sha512-ObHy4YN7ycwZOUCLI1/6svfyAFu7vL8RhAvVu/bh/RZW9EPlOyDaQ9jDQWCtdqzaXUjgXZCW1migtHE7YI7UGQ==} - - import-fresh@3.3.1: - resolution: {integrity: sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==} - engines: {node: '>=6'} - - import-meta-resolve@4.2.0: - resolution: {integrity: sha512-Iqv2fzaTQN28s/FwZAoFq0ZSs/7hMAHJVX+w8PZl3cY19Pxk6jFFalxQoIfW2826i/fDLXv8IiEZRIT0lDuWcg==} - - inherits@2.0.4: - resolution: {integrity: sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==} - - inline-style-parser@0.2.7: - resolution: {integrity: sha512-Nb2ctOyNR8DqQoR0OwRG95uNWIC0C1lCgf5Naz5H6Ji72KZ8OcFZLz2P5sNgwlyoJ8Yif11oMuYs5pBQa86csA==} - - internmap@1.0.1: - resolution: {integrity: sha512-lDB5YccMydFBtasVtxnZ3MRBHuaoE8GKsppq+EchKL2U4nK/DmEpPHNH8MZe5HkMtpSiTSOZwfN0tzYjO/lJEw==} - - internmap@2.0.3: - resolution: {integrity: sha512-5Hh7Y1wQbvY5ooGgPbDaL5iYLAPzMTUrjMulskHLH6wnv/A+1q5rgEaiuqEjB+oxGXIVZs1FF+R/KPN3ZSQYYg==} - engines: {node: '>=12'} - - intersection-observer@0.12.2: - resolution: {integrity: sha512-7m1vEcPCxXYI8HqnL8CKI6siDyD+eIWSwgB3DZA+ZTogxk9I4CDnj4wilt9x/+/QbHI4YG5YZNmC6458/e9Ktg==} - deprecated: The Intersection Observer polyfill is no longer needed and can safely be removed. Intersection Observer has been Baseline since 2019. - - ip-address@10.5.0: - resolution: {integrity: sha512-R5SnVLJmgYYvf2F2ZgwSBnelz5G4q5AxIC277GDfUaNbrZKNANcBC7RHqYYePlszf4kBolVkJauG0ZjHHFh55g==} - engines: {node: '>= 12'} - - ipaddr.js@1.9.1: - resolution: {integrity: sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==} - engines: {node: '>= 0.10'} - - is-alphabetical@2.0.1: - resolution: {integrity: sha512-FWyyY60MeTNyeSRpkM2Iry0G9hpr7/9kD40mD/cGQEuilcZYS4okz8SN2Q6rLCJ8gbCt6fN+rC+6tMGS99LaxQ==} - - is-alphanumerical@2.0.1: - resolution: {integrity: sha512-hmbYhX/9MUMF5uh7tOXyK/n0ZvWpad5caBA17GsC6vyuCqaWliRG5K1qS9inmUhEMaOBIW7/whAnSwveW/LtZw==} - - is-arrayish@0.2.1: - resolution: {integrity: sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==} - - is-binary-path@2.1.0: - resolution: {integrity: sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==} - engines: {node: '>=8'} - - is-core-module@2.16.2: - resolution: {integrity: sha512-evOr8xfXKxE6qSR0hSXL2r3sd7ALj8+7jQEUvPYcm5sgZFdJ+AYzT6yNmJenvIYQBgIGwfwz08sL8zoL7yq2BA==} - engines: {node: '>= 0.4'} - - is-decimal@2.0.1: - resolution: {integrity: sha512-AAB9hiomQs5DXWcRB1rqsxGUstbRroFOPPVAomNk/3XHR5JyEZChOyTWe2oayKnsSsr/kcGqF+z6yuH6HHpN0A==} - - is-docker@2.2.1: - resolution: {integrity: sha512-F+i2BKsFrH66iaUFc0woD8sLy8getkwTwtOBjvs56Cx4CgJDeKQeqfz8wAYiSb8JOprWhHH5p77PbmYCvvUuXQ==} - engines: {node: '>=8'} - hasBin: true - - is-docker@3.0.0: - resolution: {integrity: sha512-eljcgEDlEns/7AXFosB5K/2nCM4P7FQPkGc/DWLy5rmFEWvZayGrik1d9/QIY5nJ4f9YsVvBkA6kJpHn9rISdQ==} - engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} - hasBin: true - - is-extendable@0.1.1: - resolution: {integrity: sha512-5BMULNob1vgFX6EjQw5izWDxrecWK9AM72rugNr0TFldMOi0fj6Jk+zeKIt0xGj4cEfQIJth4w3OKWOJ4f+AFw==} - engines: {node: '>=0.10.0'} - - is-extendable@1.0.1: - resolution: {integrity: sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA==} - engines: {node: '>=0.10.0'} - - is-extglob@2.1.1: - resolution: {integrity: sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==} - engines: {node: '>=0.10.0'} - - is-glob@4.0.3: - resolution: {integrity: sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==} - engines: {node: '>=0.10.0'} - - is-hexadecimal@2.0.1: - resolution: {integrity: sha512-DgZQp241c8oO6cA1SbTEWiXeoxV42vlcJxgH+B3hi1AiqqKruZR3ZGF8In3fj4+/y/7rHvlOZLZtgJ/4ttYGZg==} - - is-in-ssh@1.0.0: - resolution: {integrity: sha512-jYa6Q9rH90kR1vKB6NM7qqd1mge3Fx4Dhw5TVlK1MUBqhEOuCagrEHMevNuCcbECmXZ0ThXkRm+Ymr51HwEPAw==} - engines: {node: '>=20'} - - is-inside-container@1.0.0: - resolution: {integrity: sha512-KIYLCCJghfHZxqjYBE7rEy0OBuTd5xCHS7tHVgvCLkx7StIoaxwNW3hCALgEUjFfeRk+MG/Qxmp/vtETEF3tRA==} - engines: {node: '>=14.16'} - hasBin: true - - is-interactive@2.0.0: - resolution: {integrity: sha512-qP1vozQRI+BMOPcjFzrjXuQvdak2pHNUMZoeG2eRbiSqyvbEf/wQtEOTOX1guk6E3t36RkaqiSt8A/6YElNxLQ==} - engines: {node: '>=12'} - - is-mobile@5.0.0: - resolution: {integrity: sha512-Tz/yndySvLAEXh+Uk8liFCxOwVH6YutuR74utvOcu7I9Di+DwM0mtdPVZNaVvvBUM2OXxne/NhOs1zAO7riusQ==} - - is-number@7.0.0: - resolution: {integrity: sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==} - engines: {node: '>=0.12.0'} - - is-obj@2.0.0: - resolution: {integrity: sha512-drqDG3cbczxxEJRoOXcOjtdp1J/lyp1mNn0xaznRs8+muBhgQcrnbspox5X5fOw0HnMnbfDzvnEMEtqDEJEo8w==} - engines: {node: '>=8'} - - is-obj@3.0.0: - resolution: {integrity: sha512-IlsXEHOjtKhpN8r/tRFj2nDyTmHvcfNeu/nrRIcXE17ROeatXchkojffa1SpdqW4cr/Fj6QkEf/Gn4zf6KKvEQ==} - engines: {node: '>=12'} - - is-plain-obj@4.1.0: - resolution: {integrity: sha512-+Pgi+vMuUNkJyExiMBt5IlFoMyKnr5zhJ4Uspz58WOhBF5QoIZkFyNHIbBAtHwzVAgk5RtndVNsDRN61/mmDqg==} - engines: {node: '>=12'} - - is-plain-object@2.0.4: - resolution: {integrity: sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og==} - engines: {node: '>=0.10.0'} - - is-plain-object@5.0.0: - resolution: {integrity: sha512-VRSzKkbMm5jMDoKLbltAkFQ5Qr7VDiTFGXxYFXXowVj387GeGNOCsOH6Msy00SGZ3Fp84b1Naa1psqgcCIEP5Q==} - engines: {node: '>=0.10.0'} - - is-promise@2.2.2: - resolution: {integrity: sha512-+lP4/6lKUBfQjZ2pdxThZvLUAafmZb8OAxFb8XXtiQmS35INgr85hdOGoEs124ez1FCnZJt6jau/T+alh58QFQ==} - - is-promise@4.0.0: - resolution: {integrity: sha512-hvpoI6korhJMnej285dSg6nu1+e6uxs7zG3BYAm5byqDsgJNWwxzM6z6iZiAgQR4TJ30JmBTOwqZUw3WlyH3AQ==} - - is-regexp@3.1.0: - resolution: {integrity: sha512-rbku49cWloU5bSMI+zaRaXdQHXnthP6DZ/vLnfdSKyL4zUzuWnomtOEiZZOd+ioQ+avFo/qau3KPTc7Fjy1uPA==} - engines: {node: '>=12'} - - is-stream@2.0.1: - resolution: {integrity: sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==} - engines: {node: '>=8'} - - is-stream@4.0.1: - resolution: {integrity: sha512-Dnz92NInDqYckGEUJv689RbRiTSEHCQ7wOVeALbkOz999YpqT46yMRIGtSNl2iCL1waAZSx40+h59NV/EwzV/A==} - engines: {node: '>=18'} - - is-unicode-supported@1.3.0: - resolution: {integrity: sha512-43r2mRvz+8JRIKnWJ+3j8JtjRKZ6GmjzfaE/qiBJnikNnYv/6bagRJ1kUhNk8R5EX/GkobD+r+sfxCPJsiKBLQ==} - engines: {node: '>=12'} - - is-unicode-supported@2.1.0: - resolution: {integrity: sha512-mE00Gnza5EEB3Ds0HfMyllZzbBrmLOX3vfWoj9A9PEnTfratQ/BcaJOuMhnkhjXvb2+FkY3VuHqtAGpTPmglFQ==} - engines: {node: '>=18'} - - is-wsl@2.2.0: - resolution: {integrity: sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww==} - engines: {node: '>=8'} - - is-wsl@3.1.1: - resolution: {integrity: sha512-e6rvdUCiQCAuumZslxRJWR/Doq4VpPR82kqclvcS0efgt430SlGIk05vdCN58+VrzgtIcfNODjozVielycD4Sw==} - engines: {node: '>=16'} - - isarray@1.0.0: - resolution: {integrity: sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==} - - isexe@2.0.0: - resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==} - - isexe@3.1.5: - resolution: {integrity: sha512-6B3tLtFqtQS4ekarvLVMZ+X+VlvQekbe4taUkf/rhVO3d/h0M2rfARm/pXLcPEsjjMsFgrFgSrhQIxcSVrBz8w==} - engines: {node: '>=18'} - - isobject@3.0.1: - resolution: {integrity: sha512-WhB9zCku7EGTj/HQQRz5aUQEUeoQZH2bWcltRErOpymJ4boYE6wL9Tbr23krRPSZ+C5zqNSrSw+Cc7sZZ4b7vg==} - engines: {node: '>=0.10.0'} - - its-fine@2.0.0: - resolution: {integrity: sha512-KLViCmWx94zOvpLwSlsx6yOCeMhZYaxrJV87Po5k/FoZzcPSahvK5qJ7fYhS61sZi5ikmh2S3Hz55A2l3U69ng==} - peerDependencies: - react: ^19.0.0 - - jiti@2.7.0: - resolution: {integrity: sha512-AC/7JofJvZGrrneWNaEnJeOLUx+JlGt7tNa0wZiRPT4MY1wmfKjt2+6O2p2uz2+skll8OZZmJMNqeke7kKbNgQ==} - hasBin: true - - jose@6.2.8: - resolution: {integrity: sha512-Bsdjwm3Qsd/P0jR+BHDe3LytDfY7WBq2HmCCLIwuVRHMuEC9ae7/R474GIUdF1NgCyZjzVo/A9DOiOBtXq8ZoQ==} - - jotai-scope@0.7.2: - resolution: {integrity: sha512-Gwed97f3dDObrO43++2lRcgOqw4O2sdr4JCjP/7eHK1oPACDJ7xKHGScpJX9XaflU+KBHXF+VhwECnzcaQiShg==} - peerDependencies: - jotai: '>=2.9.2' - react: '>=17.0.0' - - jotai@2.11.0: - resolution: {integrity: sha512-zKfoBBD1uDw3rljwHkt0fWuja1B76R7CjznuBO+mSX6jpsO1EBeWNRKpeaQho9yPI/pvCv4recGfgOXGxwPZvQ==} - engines: {node: '>=12.20.0'} - peerDependencies: - '@types/react': '>=17.0.0' - react: '>=17.0.0' - peerDependenciesMeta: - '@types/react': - optional: true - react: - optional: true - - js-cookie@3.0.8: - resolution: {integrity: sha512-yeJd4aNAdYZQjaon2bpD/Gb0B/omw7HQOsynXXcOiWVCacbBcPlgn8S/d1X6blFSaHao7ozqtW7NZW19xpCtIw==} - - js-tokens@4.0.0: - resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==} - - js-yaml@4.3.1: - resolution: {integrity: sha512-CY6crGq313MX8GkwvB7tzgp99vjQxY1++5y10/BKN/GUfHqWaOGQMNZkBvqSzsZKWk/ijwHlWzzkLulsGHhjWQ==} - hasBin: true - - jsesc@3.1.0: - resolution: {integrity: sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==} - engines: {node: '>=6'} - hasBin: true - - json-canonicalize@2.0.0: - resolution: {integrity: sha512-yyrnK/mEm6Na3ChbJUWueXdapueW0p380RUyTW87XGb1ww8l8hU0pRrGC3vSWHe9CxrbPHX2fGUOZpNiHR0IIg==} - - json-parse-even-better-errors@2.3.1: - resolution: {integrity: sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==} - - json-schema-traverse@1.0.0: - resolution: {integrity: sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==} - - json-schema-typed@7.0.3: - resolution: {integrity: sha512-7DE8mpG+/fVw+dTpjbxnx47TaMnDfOI1jwft9g1VybltZCduyRQPJPvc+zzKY9WPHxhPWczyFuYa6I8Mw4iU5A==} - - json-schema-typed@8.0.2: - resolution: {integrity: sha512-fQhoXdcvc3V28x7C7BMs4P5+kNlgUURe2jmUT1T//oBRMDrqy1QPelJimwZGo7Hg9VPV3EQV5Bnq4hbFy2vetA==} - - json2mq@0.2.0: - resolution: {integrity: sha512-SzoRg7ux5DWTII9J2qkrZrqV1gt+rTaoufMxEzXbS26Uid0NwaJd123HcoB80TgubEppxxIGdNxCx50fEoEWQA==} - - json5@2.2.3: - resolution: {integrity: sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==} - engines: {node: '>=6'} - hasBin: true - - jsonfile@6.2.1: - resolution: {integrity: sha512-zwOTdL3rFQ/lRdBnntKVOX6k5cKJwEc1HdilT71BWEu7J41gXIB2MRp+vxduPSwZJPWBxEzv4yH1wYLJGUHX4Q==} - - jszip@3.10.1: - resolution: {integrity: sha512-xXDvecyTpGLrqFrvkrUSoxxfJI5AH7U8zxxtVclpsUtMCq4JQ290LY8AW5c7Ggnr/Y/oK+bQMbqK2qmtk3pN4g==} - - katex@0.16.47: - resolution: {integrity: sha512-Eeo8Ys1doU1z+x8AZsPpQu+p/QcZBI5PeOo7QGQdy2x2m0MU/hYagBbGOmXwr5KVbEfVuWv9LpnQWeehogurjg==} - hasBin: true - - katex@0.18.4: - resolution: {integrity: sha512-IMPntbRLOU+eu88XDiFKqQ8Akhr9Tv7jDMXqPhjG9SI1JMA4DIgXk4x9k4skJz2NZJXBRbC+2pYBLj9olqcZow==} - hasBin: true - - khroma@2.1.0: - resolution: {integrity: sha512-Ls993zuzfayK269Svk9hzpeGUKob/sIgZzyHYdjQoAdQetRKpOLj+k/QQQ/6Qi0Yz65mlROrfd+Ev+1+7dz9Kw==} - - kleur@3.0.3: - resolution: {integrity: sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w==} - engines: {node: '>=6'} - - kleur@4.1.5: - resolution: {integrity: sha512-o+NO+8WrRiQEE4/7nwRJhN1HWpVmJm511pBHUxPLtp0BUISzlBplORYSmTclCnJvQq2tKu/sgl3xVpkc7ZWuQQ==} - engines: {node: '>=6'} - - langium@3.3.1: - resolution: {integrity: sha512-QJv/h939gDpvT+9SiLVlY7tZC3xB2qK57v0J04Sh9wpMb6MP1q8gB21L3WIo8T5P1MSMg3Ep14L7KkDCFG3y4w==} - engines: {node: '>=16.0.0'} - - layout-base@1.0.2: - resolution: {integrity: sha512-8h2oVEZNktL4BH2JCOI90iD1yXwL6iNW7KcCKT2QZgQJR2vbqDsldCTPRU9NifTCqHZci57XvQQ15YTu+sTYPg==} - - layout-base@2.0.1: - resolution: {integrity: sha512-dp3s92+uNI1hWIpPGH3jK2kxE2lMjdXdr+DH8ynZHpd6PUlH6x6cbuXnoMmiNumznqaNO31xu9e79F0uuZ0JFg==} - - leafer-ui@2.2.9: - resolution: {integrity: sha512-z6GklKuOMV8gMCAxWvvRUDXOZdSLzOkBzJBFEiDMcjVuTMpceQD/HPsV+Sq7Dk8I1xqY6OcF+3yTLEWYMOauxA==} - - leva@0.10.1: - resolution: {integrity: sha512-BcjnfUX8jpmwZUz2L7AfBtF9vn4ggTH33hmeufDULbP3YgNZ/C+ss/oO3stbrqRQyaOmRwy70y7BGTGO81S3rA==} - peerDependencies: - react: ^18.0.0 || ^19.0.0 - react-dom: ^18.0.0 || ^19.0.0 - - lie@3.1.1: - resolution: {integrity: sha512-RiNhHysUjhrDQntfYSfY4MU24coXXdEOgw9WGcKHNeEwffDYbF//u87M1EWaMGzuFoSbqW0C9C6lEEhDOAswfw==} - - lie@3.3.0: - resolution: {integrity: sha512-UaiMJzeWRlEujzAuw5LokY1L5ecNQYZKfmyZ9L7wDHb/p5etKaxXhohBcrw0EYby+G/NA52vRSN4N39dxHAIwQ==} - - lightningcss-android-arm64@1.32.0: - resolution: {integrity: sha512-YK7/ClTt4kAK0vo6w3X+Pnm0D2cf2vPHbhOXdoNti1Ga0al1P4TBZhwjATvjNwLEBCnKvjJc2jQgHXH0NEwlAg==} - engines: {node: '>= 12.0.0'} - cpu: [arm64] - os: [android] - - lightningcss-android-arm64@1.33.0: - resolution: {integrity: sha512-gEpRTalKdosp4Bb8qWtc2iOgE5SeIHlpS1up9bFq2wAyYhl1UdTObYiHe98zEM9SQvSoqQZ1IQD0JNpg3Ml5pg==} - engines: {node: '>= 12.0.0'} - cpu: [arm64] - os: [android] - - lightningcss-darwin-arm64@1.32.0: - resolution: {integrity: sha512-RzeG9Ju5bag2Bv1/lwlVJvBE3q6TtXskdZLLCyfg5pt+HLz9BqlICO7LZM7VHNTTn/5PRhHFBSjk5lc4cmscPQ==} - engines: {node: '>= 12.0.0'} - cpu: [arm64] - os: [darwin] - - lightningcss-darwin-arm64@1.33.0: - resolution: {integrity: sha512-Sciaz8eenNTKn9b3t7+xr0ipTp9YxKQY4npwQ3mrRuL0BAVHBLyZxofhaKBAVtzmtRZ/zTyo0/to4B1uWG/Djg==} - engines: {node: '>= 12.0.0'} - cpu: [arm64] - os: [darwin] - - lightningcss-darwin-x64@1.32.0: - resolution: {integrity: sha512-U+QsBp2m/s2wqpUYT/6wnlagdZbtZdndSmut/NJqlCcMLTWp5muCrID+K5UJ6jqD2BFshejCYXniPDbNh73V8w==} - engines: {node: '>= 12.0.0'} - cpu: [x64] - os: [darwin] - - lightningcss-darwin-x64@1.33.0: - resolution: {integrity: sha512-Z5UPAxzrjlWNNyGy6i65cJzzvgJ5D3T6wMvs+gWpY9d7qRhANrxqAp6LhxIgZhWEw18RfJTGcRxjuLIBr+m8XQ==} - engines: {node: '>= 12.0.0'} - cpu: [x64] - os: [darwin] - - lightningcss-freebsd-x64@1.32.0: - resolution: {integrity: sha512-JCTigedEksZk3tHTTthnMdVfGf61Fky8Ji2E4YjUTEQX14xiy/lTzXnu1vwiZe3bYe0q+SpsSH/CTeDXK6WHig==} - engines: {node: '>= 12.0.0'} - cpu: [x64] - os: [freebsd] - - lightningcss-freebsd-x64@1.33.0: - resolution: {integrity: sha512-QQM/Ti/hQajJwCY+RiWuCZ9sdtI/XQk7nDK5vC8kkdwixezOlDgvDx7+RT+QjK6FcFT4MpsuoBnHIo/O3StRRg==} - engines: {node: '>= 12.0.0'} - cpu: [x64] - os: [freebsd] - - lightningcss-linux-arm-gnueabihf@1.32.0: - resolution: {integrity: sha512-x6rnnpRa2GL0zQOkt6rts3YDPzduLpWvwAF6EMhXFVZXD4tPrBkEFqzGowzCsIWsPjqSK+tyNEODUBXeeVHSkw==} - engines: {node: '>= 12.0.0'} - cpu: [arm] - os: [linux] - - lightningcss-linux-arm-gnueabihf@1.33.0: - resolution: {integrity: sha512-N7FVBe6iS24MlM6R/4RBTxGhQheZGs7tiQ9U32UtF75NzP5Q7xWPRqLBCKxlRQRk3rY1jCIPLzx7WzOhuUIRLQ==} - engines: {node: '>= 12.0.0'} - cpu: [arm] - os: [linux] - - lightningcss-linux-arm64-gnu@1.32.0: - resolution: {integrity: sha512-0nnMyoyOLRJXfbMOilaSRcLH3Jw5z9HDNGfT/gwCPgaDjnx0i8w7vBzFLFR1f6CMLKF8gVbebmkUN3fa/kQJpQ==} - engines: {node: '>= 12.0.0'} - cpu: [arm64] - os: [linux] - - lightningcss-linux-arm64-gnu@1.33.0: - resolution: {integrity: sha512-j2v/itmy4HlNxlc6voKXYgBqNi0Ng2LShg4z7GufpEgs05P+2suBVyi9I6YHq5uoVFx9ETin3eCEhLVyXGQnKg==} - engines: {node: '>= 12.0.0'} - cpu: [arm64] - os: [linux] - - lightningcss-linux-arm64-musl@1.32.0: - resolution: {integrity: sha512-UpQkoenr4UJEzgVIYpI80lDFvRmPVg6oqboNHfoH4CQIfNA+HOrZ7Mo7KZP02dC6LjghPQJeBsvXhJod/wnIBg==} - engines: {node: '>= 12.0.0'} - cpu: [arm64] - os: [linux] - - lightningcss-linux-arm64-musl@1.33.0: - resolution: {integrity: sha512-yiO5ROMuYQgXbC60yjZU5CYSFZGKXL0HFATXt9mHJn1+zW55oCtMI9NfcVhYLMFDL7gV7oBPon/EmMMGg2OvtQ==} - engines: {node: '>= 12.0.0'} - cpu: [arm64] - os: [linux] - - lightningcss-linux-x64-gnu@1.32.0: - resolution: {integrity: sha512-V7Qr52IhZmdKPVr+Vtw8o+WLsQJYCTd8loIfpDaMRWGUZfBOYEJeyJIkqGIDMZPwPx24pUMfwSxxI8phr/MbOA==} - engines: {node: '>= 12.0.0'} - cpu: [x64] - os: [linux] - - lightningcss-linux-x64-gnu@1.33.0: - resolution: {integrity: sha512-ar+Ju7LmcN0Jo4FpL4hpFybwNG9/3A/Br5KW2n2jyODg3MEZXaDYADdemoNS+BDNfMgKvylJLj4S5tyRActuAg==} - engines: {node: '>= 12.0.0'} - cpu: [x64] - os: [linux] - - lightningcss-linux-x64-musl@1.32.0: - resolution: {integrity: sha512-bYcLp+Vb0awsiXg/80uCRezCYHNg1/l3mt0gzHnWV9XP1W5sKa5/TCdGWaR/zBM2PeF/HbsQv/j2URNOiVuxWg==} - engines: {node: '>= 12.0.0'} - cpu: [x64] - os: [linux] - - lightningcss-linux-x64-musl@1.33.0: - resolution: {integrity: sha512-RYiYbkokw0trfKqqzfF55lginwEPrD3OJDfTuJzFs1MK6iFnDenaz1fqLLtX4ITG3OktJQXOeTaw1awrBAlZPw==} - engines: {node: '>= 12.0.0'} - cpu: [x64] - os: [linux] - - lightningcss-win32-arm64-msvc@1.32.0: - resolution: {integrity: sha512-8SbC8BR40pS6baCM8sbtYDSwEVQd4JlFTOlaD3gWGHfThTcABnNDBda6eTZeqbofalIJhFx0qKzgHJmcPTnGdw==} - engines: {node: '>= 12.0.0'} - cpu: [arm64] - os: [win32] - - lightningcss-win32-arm64-msvc@1.33.0: - resolution: {integrity: sha512-1K+MPfLSFVpphzpdbfkhlWk6wBrTObBzS2T6db10PNOZgR9GoVsAWzwNyuhUYYbTp23j+4RrncfujZ4uAzXvwA==} - engines: {node: '>= 12.0.0'} - cpu: [arm64] - os: [win32] - - lightningcss-win32-x64-msvc@1.32.0: - resolution: {integrity: sha512-Amq9B/SoZYdDi1kFrojnoqPLxYhQ4Wo5XiL8EVJrVsB8ARoC1PWW6VGtT0WKCemjy8aC+louJnjS7U18x3b06Q==} - engines: {node: '>= 12.0.0'} - cpu: [x64] - os: [win32] - - lightningcss-win32-x64-msvc@1.33.0: - resolution: {integrity: sha512-OlEICDx/Xl0FqSp4bry8zFnCvGpig3Gl4gCquvYwHuqJKEC1+n9NgDniFvqHGmMv1ZkqDJrDqKKSykTDX+ehuA==} - engines: {node: '>= 12.0.0'} - cpu: [x64] - os: [win32] - - lightningcss@1.32.0: - resolution: {integrity: sha512-NXYBzinNrblfraPGyrbPoD19C1h9lfI/1mzgWYvXUTe414Gz/X1FD2XBZSZM7rRTrMA8JL3OtAaGifrIKhQ5yQ==} - engines: {node: '>= 12.0.0'} - - lightningcss@1.33.0: - resolution: {integrity: sha512-WkUDrojuJs0xkgGf2udWxa3yGBRxPtxUkB79i6aCZLRgc7PM8fZe9TosfPDcvEpQZbuFASnHYmRLBLUbmLOIIA==} - engines: {node: '>= 12.0.0'} - - lines-and-columns@1.2.4: - resolution: {integrity: sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==} - - linkifyjs@4.3.3: - resolution: {integrity: sha512-P8aEP5U/D1/IlTY2OeYsErdwh9bGuLE30NcXtKEjgdHcahveQoQwM2yZNsioQHsWFz0P7KKudisbrzCgR0sDHg==} - - lit-element@4.2.2: - resolution: {integrity: sha512-aFKhNToWxoyhkNDmWZwEva2SlQia+jfG0fjIWV//YeTaWrVnOxD89dPKfigCUspXFmjzOEUQpOkejH5Ly6sG0w==} - - lit-html@3.3.3: - resolution: {integrity: sha512-el8M6jK2o3RXBnrSHX3ZKrsN8zEV63pSExTO1wYJz7QndGYZ8353e2a5PPX+qHe2aGayfnchQmkAojaWAREOIA==} - - lit@3.3.3: - resolution: {integrity: sha512-fycuvZg/hkpozL00lm1pEJH5nN/lr9ZXd6mJI2HSN4+Bzc+LDNdEApJ6HFbPkdFNHLvOplIIuJvxkS4XUxqirw==} - - localforage@1.10.0: - resolution: {integrity: sha512-14/H1aX7hzBBmmh7sGPd+AOMkkIrHM3Z1PAyGgZigA1H1p5O5ANnMyWzvpAETtG68/dC4pC0ncy3+PPGzXZHPg==} - - locate-path@3.0.0: - resolution: {integrity: sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==} - engines: {node: '>=6'} - - lodash-es@4.17.21: - resolution: {integrity: sha512-mKnC+QJ9pWVzv+C4/U3rRsHapFfHvQFoFB92e52xeyGMcX6/OlIl78je1u8vePzYZSkkogMPJ2yjxxsb89cxyw==} - - lodash-es@4.18.1: - resolution: {integrity: sha512-J8xewKD/Gk22OZbhpOVSwcs60zhd95ESDwezOFuA3/099925PdHJ7OFHNTGtajL3AlZkykD32HykiMo+BIBI8A==} - - lodash.debounce@4.0.8: - resolution: {integrity: sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow==} - - lodash.isequal@4.5.0: - resolution: {integrity: sha512-pDo3lu8Jhfjqls6GkMgpahsF9kCyayhgykjyLMNFTKWrpVdAQtYyB4muAMWozBB4ig/dtWAmsMxLEI8wuz+DYQ==} - deprecated: This package is deprecated. Use require('node:util').isDeepStrictEqual instead. - - lodash.isequalwith@4.4.0: - resolution: {integrity: sha512-dcZON0IalGBpRmJBmMkaoV7d3I80R2O+FrzsZyHdNSFrANq/cgDqKQNmAHE8UEj4+QYWwwhkQOVdLHiAopzlsQ==} - - lodash.throttle@4.1.1: - resolution: {integrity: sha512-wIkUCfVKpVsWo3JSZlc+8MB5it+2AN5W8J7YVMST30UrvcQNZ1Okbj+rbVniijTWE6FGYy4XJq/rHkas8qJMLQ==} - - lodash.uniq@4.5.0: - resolution: {integrity: sha512-xfBaXQd9ryd9dlSDvnvI0lvxfLJlYAZzXomUYzLKtUeOQvOP5piqAWuGtrhWeqaXK9hhoM/iyJc5AV+XfsX3HQ==} - - lodash@4.18.1: - resolution: {integrity: sha512-dMInicTPVE8d1e5otfwmmjlxkZoUpiVLwyeTdUsi/Caj/gfzzblBcCE5sRHV/AsjuCmxWrte2TNGSYuCeCq+0Q==} - - log-symbols@6.0.0: - resolution: {integrity: sha512-i24m8rpwhmPIS4zscNzK6MSEhk0DUWa/8iYQWxhffV8jkI4Phvs3F+quL5xvS0gdQR0FyTCMMH33Y78dDTzzIw==} - engines: {node: '>=18'} - - longest-streak@3.1.0: - resolution: {integrity: sha512-9Ri+o0JYgehTaVBBDoMqIl8GXtbWg711O3srftcHhZ0dqnETqLaoIK0x17fUw9rFSlK/0NlsKe0Ahhyl5pXE2g==} - - loose-envify@1.4.0: - resolution: {integrity: sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==} - hasBin: true - - lop@0.4.2: - resolution: {integrity: sha512-RefILVDQ4DKoRZsJ4Pj22TxE3omDO47yFpkIBoDKzkqPRISs5U1cnAdg/5583YPkWPaLIYHOKRMQSvjFsO26cw==} - - lowlight@1.20.0: - resolution: {integrity: sha512-8Ktj+prEb1RoCPkEOrPMYUN/nCggB7qAWe3a7OpMjWQkh3l2RD5wKRQ+o8Q8YuI9RG/xs95waaI/E6ym/7NsTw==} - - lru-cache@5.1.1: - resolution: {integrity: sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==} - - lru_map@0.4.1: - resolution: {integrity: sha512-I+lBvqMMFfqaV8CJCISjI3wbjmwVu/VyOoU7+qtu9d7ioW5klMgsTTiUOUp+DJvfTTzKXoPbyC6YfgkNcyPSOg==} - - lucide-react@0.469.0: - resolution: {integrity: sha512-28vvUnnKQ/dBwiCQtwJw7QauYnE7yd2Cyp4tTTJpvglX4EMpbflcdBgrgToX2j71B3YvugK/NH3BGUk+E/p/Fw==} - peerDependencies: - react: ^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0 - - lucide-react@0.562.0: - resolution: {integrity: sha512-82hOAu7y0dbVuFfmO4bYF1XEwYk/mEbM5E+b1jgci/udUBEE/R7LF5Ip0CCEmXe8AybRM8L+04eP+LGZeDvkiw==} - peerDependencies: - react: ^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0 - - lucide-react@1.31.0: - resolution: {integrity: sha512-G8u2eEtoHUnUa9f8lbvqDhCiORMnYLdUEo06EEG9MQvHQrInKcX3Pa2TH39MM5qyzRcWETxB0+aOwAPI1g1kEg==} - peerDependencies: - react: ^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0 - - lz-string@1.5.0: - resolution: {integrity: sha512-h5bgJWpxJNswbU7qCrV0tIKQCaS3blPDrqKWx+QxzuzL1zGUzij9XCWLrSLsJPu5t+eWA/ycetzYAO5IOMcWAQ==} - hasBin: true - - maath@0.10.8: - resolution: {integrity: sha512-tRvbDF0Pgqz+9XUa4jjfgAQ8/aPKmQdWXilFu2tMy4GWj4NOsx99HlULO4IeREfbO3a0sA145DZYyvXPkybm0g==} - peerDependencies: - '@types/three': '>=0.134.0' - three: '>=0.134.0' - - magic-string@0.30.21: - resolution: {integrity: sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==} - - mammoth@1.12.1: - resolution: {integrity: sha512-nCH9KKjWi3jQ+i8bUKs7k1yrXtSEGpWgF8IYkzsFMcbn+5S6l4bZEBbyx2hOQErFiXPuAs9RPa6qjXVxhyx/8g==} - engines: {node: '>=12.0.0'} - hasBin: true - - markdown-extensions@2.0.0: - resolution: {integrity: sha512-o5vL7aDWatOTX8LzaS1WMoaoxIiLRQJuIKKe2wAw6IeULDHaqbiqiggmx+pKvZDb1Sj+pE46Sn1T7lCqfFtg1Q==} - engines: {node: '>=16'} - - markdown-table@3.0.4: - resolution: {integrity: sha512-wiYz4+JrLyb/DqW2hkFJxP7Vd7JuTDm77fvbM8VfEQdmSMqcImWeeRbHwZjBjIFki/VaMK2BhFi7oUUZeM5bqw==} - - marked@16.4.2: - resolution: {integrity: sha512-TI3V8YYWvkVf3KJe1dRkpnjs68JUPyEa5vjKrp1XEEJUAOaQc+Qj+L1qWbPd0SJuAdQkFU0h73sXXqwDYxsiDA==} - engines: {node: '>= 20'} - hasBin: true - - marked@17.0.6: - resolution: {integrity: sha512-gB0gkNafnonOw0obSTEGZTT86IuhILt2Wfx0mWH/1Au83kybTayroZ/V6nS25mN7u8ASy+5fMhgB3XPNrOZdmA==} - engines: {node: '>= 20'} - hasBin: true - - marked@18.0.10: - resolution: {integrity: sha512-FJeH4bRpYoXiggcgriCGItKCSv3xkngJc4QCZ/rkQCogU3VYaLxYJoZl8Nw/b4+x7iij/pd+09mZ6A1dXzpL0A==} - engines: {node: '>= 20'} - hasBin: true - - math-intrinsics@1.1.0: - resolution: {integrity: sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==} - engines: {node: '>= 0.4'} - - mdast-util-find-and-replace@3.0.2: - resolution: {integrity: sha512-Tmd1Vg/m3Xz43afeNxDIhWRtFZgM2VLyaf4vSTYwudTyeuTneoL3qtWMA5jeLyz/O1vDJmmV4QuScFCA2tBPwg==} - - mdast-util-from-markdown@2.0.3: - resolution: {integrity: sha512-W4mAWTvSlKvf8L6J+VN9yLSqQ9AOAAvHuoDAmPkz4dHf553m5gVj2ejadHJhoJmcmxEnOv6Pa8XJhpxE93kb8Q==} - - mdast-util-gfm-autolink-literal@2.0.1: - resolution: {integrity: sha512-5HVP2MKaP6L+G6YaxPNjuL0BPrq9orG3TsrZ9YXbA3vDw/ACI4MEsnoDpn6ZNm7GnZgtAcONJyPhOP8tNJQavQ==} - - mdast-util-gfm-footnote@2.1.0: - resolution: {integrity: sha512-sqpDWlsHn7Ac9GNZQMeUzPQSMzR6Wv0WKRNvQRg0KqHh02fpTz69Qc1QSseNX29bhz1ROIyNyxExfawVKTm1GQ==} - - mdast-util-gfm-strikethrough@2.0.0: - resolution: {integrity: sha512-mKKb915TF+OC5ptj5bJ7WFRPdYtuHv0yTRxK2tJvi+BDqbkiG7h7u/9SI89nRAYcmap2xHQL9D+QG/6wSrTtXg==} - - mdast-util-gfm-table@2.0.0: - resolution: {integrity: sha512-78UEvebzz/rJIxLvE7ZtDd/vIQ0RHv+3Mh5DR96p7cS7HsBhYIICDBCu8csTNWNO6tBWfqXPWekRuj2FNOGOZg==} - - mdast-util-gfm-task-list-item@2.0.0: - resolution: {integrity: sha512-IrtvNvjxC1o06taBAVJznEnkiHxLFTzgonUdy8hzFVeDun0uTjxxrRGVaNFqkU1wJR3RBPEfsxmU6jDWPofrTQ==} - - mdast-util-gfm@3.1.0: - resolution: {integrity: sha512-0ulfdQOM3ysHhCJ1p06l0b0VKlhU0wuQs3thxZQagjcjPrlFRqY215uZGHHJan9GEAXd9MbfPjFJz+qMkVR6zQ==} - - mdast-util-math@3.0.0: - resolution: {integrity: sha512-Tl9GBNeG/AhJnQM221bJR2HPvLOSnLE/T9cJI9tlc6zwQk2nPk/4f0cHkOdEixQPC/j8UtKDdITswvLAy1OZ1w==} - - mdast-util-mdx-expression@2.0.1: - resolution: {integrity: sha512-J6f+9hUp+ldTZqKRSg7Vw5V6MqjATc+3E4gf3CFNcuZNWD8XdyI6zQ8GqH7f8169MM6P7hMBRDVGnn7oHB9kXQ==} - - mdast-util-mdx-jsx@3.2.0: - resolution: {integrity: sha512-lj/z8v0r6ZtsN/cGNNtemmmfoLAFZnjMbNyLzBafjzikOM+glrjNHPlf6lQDOTccj9n5b0PPihEBbhneMyGs1Q==} - - mdast-util-mdx@3.0.0: - resolution: {integrity: sha512-JfbYLAW7XnYTTbUsmpu0kdBUVe+yKVJZBItEjwyYJiDJuZ9w4eeaqks4HQO+R7objWgS2ymV60GYpI14Ug554w==} - - mdast-util-mdxjs-esm@2.0.1: - resolution: {integrity: sha512-EcmOpxsZ96CvlP03NghtH1EsLtr0n9Tm4lPUJUBccV9RwUOneqSycg19n5HGzCf+10LozMRSObtVr3ee1WoHtg==} - - mdast-util-newline-to-break@2.0.0: - resolution: {integrity: sha512-MbgeFca0hLYIEx/2zGsszCSEJJ1JSCdiY5xQxRcLDDGa8EPvlLPupJ4DSajbMPAnC0je8jfb9TiUATnxxrHUog==} - - mdast-util-phrasing@4.1.0: - resolution: {integrity: sha512-TqICwyvJJpBwvGAMZjj4J2n0X8QWp21b9l0o7eXyVJ25YNWYbJDVIyD1bZXE6WtV6RmKJVYmQAKWa0zWOABz2w==} - - mdast-util-to-hast@13.2.1: - resolution: {integrity: sha512-cctsq2wp5vTsLIcaymblUriiTcZd0CwWtCbLvrOzYCDZoWyMNV8sZ7krj09FSnsiJi3WVsHLM4k6Dq/yaPyCXA==} - - mdast-util-to-markdown-cjk-friendly@1.0.0: - resolution: {integrity: sha512-BoaAm8mlJ+LAYz0Qs532Y3ciTuQYgBUPZcSFbvC/ZKmEMAKgulw84YvQK1gI34t/vL2euSfuaWlqczkTBgamkw==} - engines: {node: '>=18'} - peerDependencies: - '@types/mdast': '*' - peerDependenciesMeta: - '@types/mdast': - optional: true - - mdast-util-to-markdown@2.1.2: - resolution: {integrity: sha512-xj68wMTvGXVOKonmog6LwyJKrYXZPvlwabaryTjLh9LuvovB/KAH+kvi8Gjj+7rJjsFi23nkUxRQv1KqSroMqA==} - - mdast-util-to-string@4.0.0: - resolution: {integrity: sha512-0H44vDimn51F0YwvxSJSm0eCDOJTRlmN0R1yBh4HLj9wiV1Dn0QoXGbvFAWj2hSItVTlCmBF1hqKlIyUBVFLPg==} - - media-captions@1.0.4: - resolution: {integrity: sha512-cyDNmuZvvO4H27rcBq2Eudxo9IZRDCOX/I7VEyqbxsEiD2Ei7UYUhG/Sc5fvMZjmathgz3fEK7iAKqvpY+Ux1w==} - engines: {node: '>=16'} - - media-typer@1.1.1: - resolution: {integrity: sha512-yz3xRaG20c6/BOzvYoDaGtPmGscs7YivItZEEqe6GbwNfHuxu9YNmvnEkMzKldAGY4/80pRcQRZSEnhquk9XuQ==} - engines: {node: '>= 0.8'} - - merge-descriptors@2.0.0: - resolution: {integrity: sha512-Snk314V5ayFLhp3fkUREub6WtjBfPdCPY1Ln8/8munuLuiYhsABgBVWsozAG+MWMbVEvcdcpbi9R7ww22l9Q3g==} - engines: {node: '>=18'} - - merge-stream@2.0.0: - resolution: {integrity: sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==} - - merge-value@1.0.0: - resolution: {integrity: sha512-fJMmvat4NeKz63Uv9iHWcPDjCWcCkoiRoajRTEO8hlhUC6rwaHg0QCF9hBOTjZmm4JuglPckPSTtcuJL5kp0TQ==} - engines: {node: '>=0.10.0'} - - merge2@1.4.1: - resolution: {integrity: sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==} - engines: {node: '>= 8'} - - mermaid@11.16.1: - resolution: {integrity: sha512-TQsq6u22fAn3rek5VOubrhKPo1g5hwC3FXUN9hiyupTckcYiGuuKGkNQrKYwGJkXUxZdojwRG46gsSCFZMDp4g==} - - meshline@3.3.1: - resolution: {integrity: sha512-/TQj+JdZkeSUOl5Mk2J7eLcYTLiQm2IDzmlSvYm7ov15anEcDJ92GHqqazxTSreeNgfnYu24kiEvvv0WlbCdFQ==} - peerDependencies: - three: '>=0.137' - - meshoptimizer@1.1.1: - resolution: {integrity: sha512-oRFNWJRDA/WTrVj7NWvqa5HqE1t9MYDj2VaWirQCzCCrAd2GHrqR/sQezCxiWATPNlKTcRaPRHPJwIRoPBAp5g==} - - micromark-core-commonmark@2.0.3: - resolution: {integrity: sha512-RDBrHEMSxVFLg6xvnXmb1Ayr2WzLAWjeSATAoxwKYJV94TeNavgoIdA0a9ytzDSVzBy2YKFK+emCPOEibLeCrg==} - - micromark-extension-cjk-friendly-util@3.0.1: - resolution: {integrity: sha512-GcbXqTTHOsiZHyF753oIddP/J2eH8j9zpyQPhkof6B2JNxfEJabnQqxbCgzJNuNes0Y2jTNJ3LiYPSXr6eJA8w==} - engines: {node: '>=18'} - peerDependencies: - micromark-util-types: '*' - peerDependenciesMeta: - micromark-util-types: - optional: true - - micromark-extension-cjk-friendly@2.0.1: - resolution: {integrity: sha512-OkzoYVTL1ChbvQ8Cc1ayTIz7paFQz8iS9oIYmewncweUSwmWR+hkJF9spJ1lxB90XldJl26A1F4IkPOKS3bDXw==} - engines: {node: '>=18'} - peerDependencies: - micromark: ^4.0.0 - micromark-util-types: ^2.0.0 - peerDependenciesMeta: - micromark-util-types: - optional: true - - micromark-extension-gfm-autolink-literal@2.1.0: - resolution: {integrity: sha512-oOg7knzhicgQ3t4QCjCWgTmfNhvQbDDnJeVu9v81r7NltNCVmhPy1fJRX27pISafdjL+SVc4d3l48Gb6pbRypw==} - - micromark-extension-gfm-footnote@2.1.0: - resolution: {integrity: sha512-/yPhxI1ntnDNsiHtzLKYnE3vf9JZ6cAisqVDauhp4CEHxlb4uoOTxOCJ+9s51bIB8U1N1FJ1RXOKTIlD5B/gqw==} - - micromark-extension-gfm-strikethrough@2.1.0: - resolution: {integrity: sha512-ADVjpOOkjz1hhkZLlBiYA9cR2Anf8F4HqZUO6e5eDcPQd0Txw5fxLzzxnEkSkfnD0wziSGiv7sYhk/ktvbf1uw==} - - micromark-extension-gfm-table@2.1.1: - resolution: {integrity: sha512-t2OU/dXXioARrC6yWfJ4hqB7rct14e8f7m0cbI5hUmDyyIlwv5vEtooptH8INkbLzOatzKuVbQmAYcbWoyz6Dg==} - - micromark-extension-gfm-tagfilter@2.0.0: - resolution: {integrity: sha512-xHlTOmuCSotIA8TW1mDIM6X2O1SiX5P9IuDtqGonFhEK0qgRI4yeC6vMxEV2dgyr2TiD+2PQ10o+cOhdVAcwfg==} - - micromark-extension-gfm-task-list-item@2.1.0: - resolution: {integrity: sha512-qIBZhqxqI6fjLDYFTBIa4eivDMnP+OZqsNwmQ3xNLE4Cxwc+zfQEfbs6tzAo2Hjq+bh6q5F+Z8/cksrLFYWQQw==} - - micromark-extension-gfm@3.0.0: - resolution: {integrity: sha512-vsKArQsicm7t0z2GugkCKtZehqUm31oeGBV/KVSorWSy8ZlNAv7ytjFhvaryUiCUJYqs+NoE6AFhpQvBTM6Q4w==} - - micromark-extension-math@3.1.0: - resolution: {integrity: sha512-lvEqd+fHjATVs+2v/8kg9i5Q0AP2k85H0WUOwpIVvUML8BapsMvh1XAogmQjOCsLpoKRCVQqEkQBB3NhVBcsOg==} - - micromark-extension-mdx-expression@3.0.0: - resolution: {integrity: sha512-sI0nwhUDz97xyzqJAbHQhp5TfaxEvZZZ2JDqUo+7NvyIYG6BZ5CPPqj2ogUoPJlmXHBnyZUzISg9+oUmU6tUjQ==} - - micromark-extension-mdx-jsx@3.0.2: - resolution: {integrity: sha512-e5+q1DjMh62LZAJOnDraSSbDMvGJ8x3cbjygy2qFEi7HCeUT4BDKCvMozPozcD6WmOt6sVvYDNBKhFSz3kjOVQ==} - - micromark-extension-mdx-md@2.0.0: - resolution: {integrity: sha512-EpAiszsB3blw4Rpba7xTOUptcFeBFi+6PY8VnJ2hhimH+vCQDirWgsMpz7w1XcZE7LVrSAUGb9VJpG9ghlYvYQ==} - - micromark-extension-mdxjs-esm@3.0.0: - resolution: {integrity: sha512-DJFl4ZqkErRpq/dAPyeWp15tGrcrrJho1hKK5uBS70BCtfrIFg81sqcTVu3Ta+KD1Tk5vAtBNElWxtAa+m8K9A==} - - micromark-extension-mdxjs@3.0.0: - resolution: {integrity: sha512-A873fJfhnJ2siZyUrJ31l34Uqwy4xIFmvPY1oj+Ean5PHcPBYzEsvqvWGaWcfEIr11O5Dlw3p2y0tZWpKHDejQ==} - - micromark-factory-destination@2.0.1: - resolution: {integrity: sha512-Xe6rDdJlkmbFRExpTOmRj9N3MaWmbAgdpSrBQvCFqhezUn4AHqJHbaEnfbVYYiexVSs//tqOdY/DxhjdCiJnIA==} - - micromark-factory-label@2.0.1: - resolution: {integrity: sha512-VFMekyQExqIW7xIChcXn4ok29YE3rnuyveW3wZQWWqF4Nv9Wk5rgJ99KzPvHjkmPXF93FXIbBp6YdW3t71/7Vg==} - - micromark-factory-mdx-expression@2.0.0: - resolution: {integrity: sha512-2Q5+jhTBhtMdNDN9aQb9WWhpmO3ALY+eNTPfZZpDRdbK5m/bywnexvvNrP5EKdIg83udifcxpB0jyJOGKuMA9Q==} - - micromark-factory-space@2.0.1: - resolution: {integrity: sha512-zRkxjtBxxLd2Sc0d+fbnEunsTj46SWXgXciZmHq0kDYGnck/ZSGj9/wULTV95uoeYiK5hRXP2mJ98Uo4cq/LQg==} - - micromark-factory-title@2.0.1: - resolution: {integrity: sha512-5bZ+3CjhAd9eChYTHsjy6TGxpOFSKgKKJPJxr293jTbfry2KDoWkhBb6TcPVB4NmzaPhMs1Frm9AZH7OD4Cjzw==} - - micromark-factory-whitespace@2.0.1: - resolution: {integrity: sha512-Ob0nuZ3PKt/n0hORHyvoD9uZhr+Za8sFoP+OnMcnWK5lngSzALgQYKMr9RJVOWLqQYuyn6ulqGWSXdwf6F80lQ==} - - micromark-util-character@2.1.1: - resolution: {integrity: sha512-wv8tdUTJ3thSFFFJKtpYKOYiGP2+v96Hvk4Tu8KpCAsTMs6yi+nVmGh1syvSCsaxz45J6Jbw+9DD6g97+NV67Q==} - - micromark-util-chunked@2.0.1: - resolution: {integrity: sha512-QUNFEOPELfmvv+4xiNg2sRYeS/P84pTW0TCgP5zc9FpXetHY0ab7SxKyAQCNCc1eK0459uoLI1y5oO5Vc1dbhA==} - - micromark-util-classify-character@2.0.1: - resolution: {integrity: sha512-K0kHzM6afW/MbeWYWLjoHQv1sgg2Q9EccHEDzSkxiP/EaagNzCm7T/WMKZ3rjMbvIpvBiZgwR3dKMygtA4mG1Q==} - - micromark-util-combine-extensions@2.0.1: - resolution: {integrity: sha512-OnAnH8Ujmy59JcyZw8JSbK9cGpdVY44NKgSM7E9Eh7DiLS2E9RNQf0dONaGDzEG9yjEl5hcqeIsj4hfRkLH/Bg==} - - micromark-util-decode-numeric-character-reference@2.0.2: - resolution: {integrity: sha512-ccUbYk6CwVdkmCQMyr64dXz42EfHGkPQlBj5p7YVGzq8I7CtjXZJrubAYezf7Rp+bjPseiROqe7G6foFd+lEuw==} - - micromark-util-decode-string@2.0.1: - resolution: {integrity: sha512-nDV/77Fj6eH1ynwscYTOsbK7rR//Uj0bZXBwJZRfaLEJ1iGBR6kIfNmlNqaqJf649EP0F3NWNdeJi03elllNUQ==} - - micromark-util-encode@2.0.1: - resolution: {integrity: sha512-c3cVx2y4KqUnwopcO9b/SCdo2O67LwJJ/UyqGfbigahfegL9myoEFoDYZgkT7f36T0bLrM9hZTAaAyH+PCAXjw==} - - micromark-util-events-to-acorn@2.0.3: - resolution: {integrity: sha512-jmsiEIiZ1n7X1Rr5k8wVExBQCg5jy4UXVADItHmNk1zkwEVhBuIUKRu3fqv+hs4nxLISi2DQGlqIOGiFxgbfHg==} - - micromark-util-html-tag-name@2.0.1: - resolution: {integrity: sha512-2cNEiYDhCWKI+Gs9T0Tiysk136SnR13hhO8yW6BGNyhOC4qYFnwF1nKfD3HFAIXA5c45RrIG1ub11GiXeYd1xA==} - - micromark-util-normalize-identifier@2.0.1: - resolution: {integrity: sha512-sxPqmo70LyARJs0w2UclACPUUEqltCkJ6PhKdMIDuJ3gSf/Q+/GIe3WKl0Ijb/GyH9lOpUkRAO2wp0GVkLvS9Q==} - - micromark-util-resolve-all@2.0.1: - resolution: {integrity: sha512-VdQyxFWFT2/FGJgwQnJYbe1jjQoNTS4RjglmSjTUlpUMa95Htx9NHeYW4rGDJzbjvCsl9eLjMQwGeElsqmzcHg==} - - micromark-util-sanitize-uri@2.0.1: - resolution: {integrity: sha512-9N9IomZ/YuGGZZmQec1MbgxtlgougxTodVwDzzEouPKo3qFWvymFHWcnDi2vzV1ff6kas9ucW+o3yzJK9YB1AQ==} - - micromark-util-subtokenize@2.1.0: - resolution: {integrity: sha512-XQLu552iSctvnEcgXw6+Sx75GflAPNED1qx7eBJ+wydBb2KCbRZe+NwvIEEMM83uml1+2WSXpBAcp9IUCgCYWA==} - - micromark-util-symbol@2.0.1: - resolution: {integrity: sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q==} - - micromark-util-types@2.0.2: - resolution: {integrity: sha512-Yw0ECSpJoViF1qTU4DC6NwtC4aWGt1EkzaQB8KPPyCRR8z9TWeV0HbEFGTO+ZY1wB22zmxnJqhPyTpOVCpeHTA==} - - micromark@4.0.2: - resolution: {integrity: sha512-zpe98Q6kvavpCr1NPVSCMebCKfD7CA2NqZ+rykeNhONIJBpc1tFKt9hucLGwha3jNTNI8lHpctWJWoimVF4PfA==} - - micromatch@4.0.8: - resolution: {integrity: sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==} - engines: {node: '>=8.6'} - - mime-db@1.52.0: - resolution: {integrity: sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==} - engines: {node: '>= 0.6'} - - mime-db@1.54.0: - resolution: {integrity: sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==} - engines: {node: '>= 0.6'} - - mime-types@2.1.35: - resolution: {integrity: sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==} - engines: {node: '>= 0.6'} - - mime-types@3.0.2: - resolution: {integrity: sha512-Lbgzdk0h4juoQ9fCKXW4by0UJqj+nOOrI9MJ1sSj4nI8aI2eo1qmvQEie4VD1glsS250n15LsWsYtCugiStS5A==} - engines: {node: '>=18'} - - mimic-fn@2.1.0: - resolution: {integrity: sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==} - engines: {node: '>=6'} - - mimic-fn@3.1.0: - resolution: {integrity: sha512-Ysbi9uYW9hFyfrThdDEQuykN4Ey6BuwPD2kpI5ES/nFTDn/98yxYNLZJcgUAKPT/mcrLLKaGzJR9YVxJrIdASQ==} - engines: {node: '>=8'} - - mimic-function@5.0.1: - resolution: {integrity: sha512-VP79XUPxV2CigYP3jWwAUFSku2aKqBH7uTAapFWCBqutsbmDo96KY5o8uh6U+/YSIn5OxJnXp73beVkpqMIGhA==} - engines: {node: '>=18'} - - minimatch@10.2.6: - resolution: {integrity: sha512-vpLQEs+VLCr1nU0BXS07maYoFwlDAH0gngQuuttxIwutDFEMHq2blX+8vpgxDdK3J1PwjCJiep77OitTZ4Ll1A==} - engines: {node: 18 || 20 || >=22} - - minimist@1.2.8: - resolution: {integrity: sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==} - - mixin-deep@1.3.2: - resolution: {integrity: sha512-WRoDn//mXBiJ1H40rqa3vH0toePwSsGb45iInWlTySa+Uu4k3tYUSxa2v1KqAiLtvlrSzaExqS1gtk96A9zvEA==} - engines: {node: '>=0.10.0'} - - motion-dom@12.43.0: - resolution: {integrity: sha512-azKON4d9S65PEoFUiQTMTgPheEmzf2QngdRc50AKfJp9Q9mmcBVw22c8eMq9k8kxOFHdL7+WZY7N/5F/lwiDag==} - - motion-utils@12.39.0: - resolution: {integrity: sha512-8nadJAJjTtqRkmRF36FoJTrywK9nnFmnPwnSMyxaOCU7GDjN9RTMJIxx9De8ErM+vpPhMccr/6fo5WciyQLnMQ==} - - motion@12.43.0: - resolution: {integrity: sha512-BQgQbSa9Hn3/mtbib0MK53y6JSANa+YKUKlaYnWzAVDH424RYQ5LVpV3pNiWH00BA2z4ojsSdMzqT7g2FQwjuQ==} - peerDependencies: - '@emotion/is-prop-valid': '*' - react: ^18.0.0 || ^19.0.0 - react-dom: ^18.0.0 || ^19.0.0 - peerDependenciesMeta: - '@emotion/is-prop-valid': - optional: true - react: - optional: true - react-dom: - optional: true - - ms@2.1.3: - resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} - - multimath@2.0.0: - resolution: {integrity: sha512-toRx66cAMJ+Ccz7pMIg38xSIrtnbozk0dchXezwQDMgQmbGpfxjtv68H+L00iFL8hxDaVjrmwAFSb3I6bg8Q2g==} - - nanoid@3.3.18: - resolution: {integrity: sha512-DTg4MJbGMWkfi6VZFdNt2/caMbQy4Ou+Op/hJQvGEWcnVfoA1QA+xzRKAzw9jD6+GVOOeYr/mIcuDSdug6F6+w==} - engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} - hasBin: true - - nanoid@3.3.3: - resolution: {integrity: sha512-p1sjXuopFs0xg+fPASzQ28agW1oHD7xDsd9Xkf3T15H3c/cifrFHVwrh74PdoklAPi+i7MdRsE47vm2r6JoB+w==} - engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} - hasBin: true - - nanoid@4.0.2: - resolution: {integrity: sha512-7ZtY5KTCNheRGfEFxnedV5zFiORN1+Y1N6zvPTnHQd8ENUvfaDBeuJDZb2bN/oXwXxu3qkTXDzy57W5vAmDTBw==} - engines: {node: ^14 || ^16 || >=18} - hasBin: true - - nanoid@6.0.1: - resolution: {integrity: sha512-3wVS3i51pE2pi1k5FFL/95BGfVS0kSsvDVuGXHOtxox/TywUmtgq+3qiTOTbs9J7KfHaXPiN171k/A6dBnaXFw==} - engines: {node: ^22 || ^24 || >=26} - hasBin: true - - negotiator@1.0.0: - resolution: {integrity: sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg==} - engines: {node: '>= 0.6'} - - node-releases@2.0.53: - resolution: {integrity: sha512-D9UOmYG3UH1V+ENW56t5QXBwJw1YEY18ruVeus89Rw+SyIgjPkCO84bRzO3uNIYosJbNwiabWVn48o3uJLjxFQ==} - engines: {node: '>=18'} - - normalize-path@3.0.0: - resolution: {integrity: sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==} - engines: {node: '>=0.10.0'} - - npm-run-path@4.0.1: - resolution: {integrity: sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==} - engines: {node: '>=8'} - - npm-run-path@6.0.0: - resolution: {integrity: sha512-9qny7Z9DsQU8Ou39ERsPU4OZQlSTP47ShQzuKZ6PRXpYLtIFgl/DEBYEXKlvcEa+9tHVcK8CF81Y2V72qaZhWA==} - engines: {node: '>=18'} - - numeral@2.0.6: - resolution: {integrity: sha512-qaKRmtYPZ5qdw4jWJD6bxEf1FJEqllJrwxCLIm0sQU/A7v2/czigzOb+C2uSiFsa9lBUzeH7M1oK+Q+OLxL3kA==} - - object-assign@4.1.1: - resolution: {integrity: sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==} - engines: {node: '>=0.10.0'} - - object-inspect@1.13.4: - resolution: {integrity: sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==} - engines: {node: '>= 0.4'} - - object-treeify@1.1.33: - resolution: {integrity: sha512-EFVjAYfzWqWsBMRHPMAXLCDIJnpMhdWAqR7xG6M6a2cs6PMFpl/+Z20w9zDW4vkxOFfddegBKq9Rehd0bxWE7A==} - engines: {node: '>= 10'} - - on-change@4.0.0: - resolution: {integrity: sha512-PTu7C9Jsz4b+sNMDpH0eZFTr7uxdOtoDWRnhaVNK50bgrrnW5nvbWI0jm5DG9qOoTnIhBzE9xoKVFPD9xgtbdg==} - engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} - - on-finished@2.4.1: - resolution: {integrity: sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==} - engines: {node: '>= 0.8'} - - once@1.4.0: - resolution: {integrity: sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==} - - onetime@5.1.2: - resolution: {integrity: sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==} - engines: {node: '>=6'} - - onetime@7.0.0: - resolution: {integrity: sha512-VXJjc87FScF88uafS3JllDgvAm+c/Slfz06lorj2uAY34rlUu0Nt+v8wreiImcrgAjjIHp1rXpTDlLOGw29WwQ==} - engines: {node: '>=18'} - - oniguruma-parser@0.12.2: - resolution: {integrity: sha512-6HVa5oIrgMC6aA6WF6XyyqbhRPJrKR02L20+2+zpDtO5QAzGHAUGw5TKQvwi5vctNnRHkJYmjAhRVQF2EKdTQw==} - - oniguruma-to-es@4.3.6: - resolution: {integrity: sha512-csuQ9x3Yr0cEIs/Zgx/OEt9iBw9vqIunAPQkx19R/fiMq2oGVTgcMqO/V3Ybqefr1TBvosI6jU539ksaBULJyA==} - - open-color@1.9.1: - resolution: {integrity: sha512-vCseG/EQ6/RcvxhUcGJiHViOgrtz4x0XbZepXvKik66TMGkvbmjeJrKFyBEx6daG5rNyyd14zYXhz0hZVwQFOw==} - - open@11.0.1: - resolution: {integrity: sha512-NzwMUB6C1D0+Kd+9iMS/H4k+Ck3cTX6Ckyfr/gAGlmvSE1LUQZnEZvWBi4PYmMwH/S5SMeTXnE+9uAz8uF+pWw==} - engines: {node: '>=20'} - - open@8.4.2: - resolution: {integrity: sha512-7x81NCL719oNbsq/3mh+hVrAWmFuEYUqrq/Iw3kUzH8ReypT9QQ0BLoJS7/G9k6N81XjW4qHWtjWwe/9eLy1EQ==} - engines: {node: '>=12'} - - option@0.2.4: - resolution: {integrity: sha512-pkEqbDyl8ou5cpq+VsnQbe/WlEy5qS7xPzMS1U55OCG9KPvwFD46zDbxQIj3egJSFc3D+XhYOPUzz49zQAVy7A==} - - ora@8.2.0: - resolution: {integrity: sha512-weP+BZ8MVNnlCm8c0Qdc1WSWq4Qn7I+9CJGm7Qali6g44e/PUzbjNqJX5NJ9ljlNMosfJvg1fKEGILklK9cwnw==} - engines: {node: '>=18'} - - orderedmap@2.1.1: - resolution: {integrity: sha512-TvAWxi0nDe1j/rtMcWcIj94+Ffe6n7zhow33h40SKxmsmozs6dz/e+EajymfoFcHd7sxNn8yHM8839uixMOV6g==} - - p-limit@2.3.0: - resolution: {integrity: sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==} - engines: {node: '>=6'} - - p-locate@3.0.0: - resolution: {integrity: sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==} - engines: {node: '>=6'} - - p-try@2.2.0: - resolution: {integrity: sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==} - engines: {node: '>=6'} - - package-manager-detector@1.8.0: - resolution: {integrity: sha512-yQA4H19AmPEoMUeavPMDIe1higySl/gH/yaQrkT/s07Qp+7pp2hYz30N3z2l5BkjVkF9Ow6o0wjJamm2y7Sn0A==} - - pako@1.0.11: - resolution: {integrity: sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw==} - - pako@2.0.3: - resolution: {integrity: sha512-WjR1hOeg+kki3ZIOjaf4b5WVcay1jaliKSYiEaB1XzwhMQZJxRdQRv0V31EKBYlxb4T7SK3hjfc/jxyU64BoSw==} - - parent-module@1.0.1: - resolution: {integrity: sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==} - engines: {node: '>=6'} - - parse-entities@4.0.2: - resolution: {integrity: sha512-GG2AQYWoLgL877gQIKeRPGO1xF9+eG1ujIb5soS5gPvLQ1y2o8FL90w2QWNdf9I361Mpp7726c+lj3U0qK1uGw==} - - parse-json@5.2.0: - resolution: {integrity: sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==} - engines: {node: '>=8'} - - parse-ms@4.0.0: - resolution: {integrity: sha512-TXfryirbmq34y8QBwgqCVLi+8oA3oWx2eAnSn62ITyEhEYaWRlVZ2DvMM9eZbMs/RfxPu/PK/aBLyGj4IrqMHw==} - engines: {node: '>=18'} - - parse5@7.3.0: - resolution: {integrity: sha512-IInvU7fabl34qmi9gY8XOVxhYyMyuH2xUNpb2q8/Y+7552KlejkRvqvD19nMoUW/uQGGbqNpA6Tufu5FL5BZgw==} - - parseurl@1.3.3: - resolution: {integrity: sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==} - engines: {node: '>= 0.8'} - - path-browserify@1.0.1: - resolution: {integrity: sha512-b7uo2UCUOYZcnF/3ID0lulOJi/bafxa1xPe7ZPsammBSpjSWQkjNxlt635YGS2MiR9GjvuXCtz2emr3jbsz98g==} - - path-data-parser@0.1.0: - resolution: {integrity: sha512-NOnmBpt5Y2RWbuv0LMzsayp3lVylAHLPUTut412ZA3l+C4uw4ZVkQbjShYCQ8TCpUMdPapr4YjUqLYD6v68j+w==} - - path-exists@3.0.0: - resolution: {integrity: sha512-bpC7GYwiDYQ4wYLe+FA8lhRjhQCMcQGuSgGGqDkg/QerRWw9CmGRT0iSOVRSZJ29NMLZgIzqaljJ63oaL4NIJQ==} - engines: {node: '>=4'} - - path-is-absolute@1.0.1: - resolution: {integrity: sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==} - engines: {node: '>=0.10.0'} - - path-key@3.1.1: - resolution: {integrity: sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==} - engines: {node: '>=8'} - - path-key@4.0.0: - resolution: {integrity: sha512-haREypq7xkM7ErfgIyA0z+Bj4AGKlMSdlQE2jvJo6huWD1EdkKYV+G/T4nq0YEF2vgTT8kqMFKo1uHn950r4SQ==} - engines: {node: '>=12'} - - path-parse@1.0.7: - resolution: {integrity: sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==} - - path-to-regexp@8.4.2: - resolution: {integrity: sha512-qRcuIdP69NPm4qbACK+aDogI5CBDMi1jKe0ry5rSQJz8JVLsC7jV8XpiJjGRLLol3N+R5ihGYcrPLTno6pAdBA==} - - path-type@4.0.0: - resolution: {integrity: sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==} - engines: {node: '>=8'} - - perfect-freehand@1.2.0: - resolution: {integrity: sha512-h/0ikF1M3phW7CwpZ5MMvKnfpHficWoOEyr//KVNTxV4F6deRK1eYMtHyBKEAKFK0aXIEUK9oBvlF6PNXMDsAw==} - - pica@7.1.1: - resolution: {integrity: sha512-WY73tMvNzXWEld2LicT9Y260L43isrZ85tPuqRyvtkljSDLmnNFQmZICt4xUJMVulmcc6L9O7jbBrtx3DOz/YQ==} - - picocolors@1.1.1: - resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==} - - picomatch@2.3.2: - resolution: {integrity: sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA==} - engines: {node: '>=8.6'} - - picomatch@4.0.5: - resolution: {integrity: sha512-RvwwcruNjI1ncT5xRakeyS9Lf8lcItv34KD+aif+VH9kduAyfYBipGh12274xtenIPZ119/R9BdTBa8gAwSh0A==} - engines: {node: '>=12'} - - pkce-challenge@5.0.1: - resolution: {integrity: sha512-wQ0b/W4Fr01qtpHlqSqspcj3EhBvimsdh0KlHhH8HRZnMsEa0ea2fTULOXOS9ccQr3om+GcGRk4e+isrZWV8qQ==} - engines: {node: '>=16.20.0'} - - pkg-up@3.1.0: - resolution: {integrity: sha512-nDywThFk1i4BQK4twPQ6TA4RT8bDY96yeuCVBWL3ePARCiEKDRSrNGbFIgUJpLp+XeIR65v8ra7WuJOFUBtkMA==} - engines: {node: '>=8'} - - png-chunk-text@1.0.0: - resolution: {integrity: sha512-DEROKU3SkkLGWNMzru3xPVgxyd48UGuMSZvioErCure6yhOc/pRH2ZV+SEn7nmaf7WNf3NdIpH+UTrRdKyq9Lw==} - - png-chunks-encode@1.0.0: - resolution: {integrity: sha512-J1jcHgbQRsIIgx5wxW9UmCymV3wwn4qCCJl6KYgEU/yHCh/L2Mwq/nMOkRPtmV79TLxRZj5w3tH69pvygFkDqA==} - - png-chunks-extract@1.0.0: - resolution: {integrity: sha512-ZiVwF5EJ0DNZyzAqld8BP1qyJBaGOFaq9zl579qfbkcmOwWLLO4I9L8i2O4j3HkI6/35i0nKG2n+dZplxiT89Q==} - - points-on-curve@0.2.0: - resolution: {integrity: sha512-0mYKnYYe9ZcqMCWhUjItv/oHjvgEsfKvnUTg8sAtnHr3GVy7rGkXCb6d5cSyqrWqL4k81b9CPg3urd+T7aop3A==} - - points-on-curve@1.0.1: - resolution: {integrity: sha512-3nmX4/LIiyuwGLwuUrfhTlDeQFlAhi7lyK/zcRNGhalwapDWgAGR82bUpmn2mA03vII3fvNCG8jAONzKXwpxAg==} - - points-on-path@0.2.1: - resolution: {integrity: sha512-25ClnWWuw7JbWZcgqY/gJ4FQWadKxGWk+3kR/7kD0tCaDtPPMj7oHu2ToLaVhfpnHrZzYby2w6tUA0eOIuUg8g==} - - polished@4.3.1: - resolution: {integrity: sha512-OBatVyC/N7SCW/FaDHrSd+vn0o5cS855TOmYi4OkdWUMSJCET/xip//ch8xGUvtr3i44X9LVyWwQlRMTN3pwSA==} - engines: {node: '>=10'} - - postcss-selector-parser@7.1.5: - resolution: {integrity: sha512-KvvtD7SrlBP7dlgkBghEE3r84CABm5SmV2aNcG4oCA+qDnJ/tvKonFVvwWAyyWUEwxuNawdfEAZKP9zM3oZ2Uw==} - engines: {node: '>=4'} - - postcss@8.5.26: - resolution: {integrity: sha512-u82N74LFzG8ca+dD8puPnplTXoGH4fTPpVGuIbt36G3qvNlkvfD0lEAZSxaly3KX8TS/L1A1gsCEmvKmBcVbkQ==} - engines: {node: ^10 || ^12 || >=14} - - potpack@1.0.2: - resolution: {integrity: sha512-choctRBIV9EMT9WGAZHn3V7t0Z2pMQyl0EZE6pFc/6ml3ssw7Dlf/oAOvFwjm1HVsqfQN8GfeFyJ+d8tRzqueQ==} - - powershell-utils@0.1.0: - resolution: {integrity: sha512-dM0jVuXJPsDN6DvRpea484tCUaMiXWjuCn++HGTqUWzGDjv5tZkEZldAJ/UMlqRYGFrD/etByo4/xOuC/snX2A==} - engines: {node: '>=20'} - - powershell-utils@0.2.0: - resolution: {integrity: sha512-ZlsFlG7MtSFCoc5xreOvBAozCJ6Pf06opgJjh9ONEv418xpZSAzNjstD36C6+JwOnfSqOW/9uDkqKjezTdxZhw==} - engines: {node: '>=20'} - - prettier@3.9.6: - resolution: {integrity: sha512-OpN0zzVdiaiAhxpuuj5efpIS4sY9j7bY6uR5mnj5yPzGkdkjNKSJeUThPb60Jw29QuAZgA4o+/iB49kFiaBX6g==} - engines: {node: '>=14'} - hasBin: true - - pretty-ms@9.3.0: - resolution: {integrity: sha512-gjVS5hOP+M3wMm5nmNOucbIrqudzs9v/57bWRHQWLYklXqoXKrVfYW2W9+glfGsqtPgpiz5WwyEEB+ksXIx3gQ==} - engines: {node: '>=18'} - - prismjs@1.30.0: - resolution: {integrity: sha512-DEvV2ZF2r2/63V+tK8hQvrR2ZGn10srHbXviTlcv7Kpzw8jWiNTqbVgjO3IY8RxrrOUF8VPMQQFysYYYv0YZxw==} - engines: {node: '>=6'} - - process-nextick-args@2.0.1: - resolution: {integrity: sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==} - - promise-worker-transferable@1.0.4: - resolution: {integrity: sha512-bN+0ehEnrXfxV2ZQvU2PetO0n4gqBD4ulq3MI1WOPLgr7/Mg9yRQkX5+0v1vagr74ZTsl7XtzlaYDo2EuCeYJw==} - - prompts@2.4.2: - resolution: {integrity: sha512-NxNv/kLguCA7p3jE8oL2aEBsrJWgAakBpgmgK6lpPWV+WuOmY6r2/zbAVnP+T8bQlA0nzHXSJSJW0Hq7ylaD2Q==} - engines: {node: '>= 6'} - - prop-types@15.8.1: - resolution: {integrity: sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==} - - property-information@7.2.0: - resolution: {integrity: sha512-IAtzIB6sUiWaJYrX9smp3V46pBGbBeLFRGdh25kg1334VcBlD8HzhPeNIWQH9zhGmo2itIe25EHt9dQP7G5hmg==} - - prosemirror-changeset@2.4.1: - resolution: {integrity: sha512-96WBLhOaYhJ+kPhLg3uW359Tz6I/MfcrQfL4EGv4SrcqKEMC1gmoGrXHecPE8eOwTVCJ4IwgfzM8fFad25wNfw==} - - prosemirror-commands@1.7.2: - resolution: {integrity: sha512-q6Q6szxqdu9Xd6EcdKsqXghu5nQdZTpB4Q9yd04WRc7/jt763e/rT60Owh0L1GYY+T46o5rD+9lEN36dZS43tw==} - - prosemirror-dropcursor@1.8.3: - resolution: {integrity: sha512-FoYbsJR8gK+DGlqhNoE29Loa38eIZPzQRIb1VMaDNBoo4OLP6vVof/jR8qFY/6XvUd6Dhug8MDCHl2a/h8RTfQ==} - - prosemirror-gapcursor@1.4.1: - resolution: {integrity: sha512-pMdYaEnjNMSwl11yjEGtgTmLkR08m/Vl+Jj443167p9eB3HVQKhYCc4gmHVDsLPODfZfjr/MmirsdyZziXbQKw==} - - prosemirror-history@1.5.0: - resolution: {integrity: sha512-zlzTiH01eKA55UAf1MEjtssJeHnGxO0j4K4Dpx+gnmX9n+SHNlDqI2oO1Kv1iPN5B1dm5fsljCfqKF9nFL6HRg==} - - prosemirror-inputrules@1.5.1: - resolution: {integrity: sha512-7wj4uMjKaXWAQ1CDgxNzNtR9AlsuwzHfdFH1ygEHA2KHF2DOEaXl1CJfNPAKCg9qNEh4rum975QLaCiQPyY6Fw==} - - prosemirror-keymap@1.2.3: - resolution: {integrity: sha512-4HucRlpiLd1IPQQXNqeo81BGtkY8Ai5smHhKW9jjPKRc2wQIxksg7Hl1tTI2IfT2B/LgX6bfYvXxEpJl7aKYKw==} - - prosemirror-model@1.25.11: - resolution: {integrity: sha512-QWg9RhnpLlogAmp3p96uEFrE5txQpFynd4vhBAELkwgOCWQs/X0yCzB3/hrHqiPwf91RG5KyWq6553zs9JqIOQ==} - - prosemirror-schema-list@1.5.1: - resolution: {integrity: sha512-927lFx/uwyQaGwJxLWCZRkjXG0p48KpMj6ueoYiu4JX05GGuGcgzAy62dfiV8eFZftgyBUvLx76RsMe20fJl+Q==} - - prosemirror-state@1.4.4: - resolution: {integrity: sha512-6jiYHH2CIGbCfnxdHbXZ12gySFY/fz/ulZE333G6bPqIZ4F+TXo9ifiR86nAHpWnfoNjOb3o5ESi7J8Uz1jXHw==} - - prosemirror-tables@1.8.5: - resolution: {integrity: sha512-V/0cDCsHKHe/tfWkeCmthNUcEp1IVO3p6vwN8XtwE9PZQLAZJigbw3QoraAdfJPir4NKJtNvOB8oYGKRl+t0Dw==} - - prosemirror-transform@1.12.0: - resolution: {integrity: sha512-GxboyN4AMIsoHNtz5uf2r2Ru551i5hWeCMD6E2Ib4Eogqoub0NflniaBPVQ4MrGE5yZ8JV9tUHg9qcZTTrcN4w==} - - prosemirror-view@1.42.2: - resolution: {integrity: sha512-Pdg0l5kXm8aLDquFAnQFTCITg0q44sLqBlHlpsVLD9segdOao8TOfQdAhCrCXyVgPSRr6UDDROOIWA3bIrN9YQ==} - - proxy-addr@2.0.7: - resolution: {integrity: sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==} - engines: {node: '>= 0.10'} - - proxy-from-env@2.1.0: - resolution: {integrity: sha512-cJ+oHTW1VAEa8cJslgmUZrc+sjRKgAKl3Zyse6+PV38hZe/V6Z14TbCuXcan9F9ghlz4QrFr2c92TNF82UkYHA==} - engines: {node: '>=10'} - - pwacompat@2.0.17: - resolution: {integrity: sha512-6Du7IZdIy7cHiv7AhtDy4X2QRM8IAD5DII69mt5qWibC2d15ZU8DmBG1WdZKekG11cChSu4zkSUGPF9sweOl6w==} - - qs@6.15.3: - resolution: {integrity: sha512-O9gl3zCl5h5blw1KGUzQKhA5oUXSl8rwUIM5o0S3nCXMliSvy5Dzx7/DJcI+SwgICv+IneSZwhBh1oSyEHA71A==} - engines: {node: '>=0.6'} - - query-string@9.5.0: - resolution: {integrity: sha512-YlJmwNyi0RGYjlxYcuDncMsxFU7YyutbuI7gTm8ySxIGBlwx5yiBCOD5ig9ZNoHkawk/1Dey0N5mEfcUybMVAA==} - engines: {node: '>=18'} - - queue-microtask@1.2.3: - resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==} - - quickselect@2.0.0: - resolution: {integrity: sha512-RKJ22hX8mHe3Y6wH/N3wCM6BWtjaxIyyUIkpHOvfFnxdI4yD4tBXEBKSbriGujF6jnSVkJrffuo6vxACiSSxIw==} - - radix-ui@1.6.7: - resolution: {integrity: sha512-QBdhh1arIEUvPC0dQ5+nwWAxt7+N+oP/9jPwjJkGFoSk/sqxg32gJtSXGtFh8frAIcS6oC9cx2Q+7KYCQLOAeA==} - peerDependencies: - '@types/react': '*' - '@types/react-dom': '*' - react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - peerDependenciesMeta: - '@types/react': - optional: true - '@types/react-dom': - optional: true - - range-parser@1.3.0: - resolution: {integrity: sha512-hek2mFQpPuI4E1BBKrSto+BU3e3x4xuarsbiwr3+lf7p44juvFMV0XFWQAP3xUyqXA4RrXLIoaSUGbSt056ZMw==} - engines: {node: '>= 0.6'} - - raw-body@3.0.2: - resolution: {integrity: sha512-K5zQjDllxWkf7Z5xJdV0/B0WTNqx6vxG70zJE4N0kBs4LovmEYWJzQGxC9bS9RAKu3bgM40lrd5zoLJ12MQ5BA==} - engines: {node: '>= 0.10'} - - rbush@3.0.1: - resolution: {integrity: sha512-XRaVO0YecOpEuIvbhbpTrZgoiI6xBlz6hnlr6EHhd+0x9ase6EmeN+hdwwUaJvLcsFFQ8iWVF1GAK1yB0BWi0w==} - - rc-collapse@4.0.0: - resolution: {integrity: sha512-SwoOByE39/3oIokDs/BnkqI+ltwirZbP8HZdq1/3SkPSBi7xDdvWHTp7cpNI9ullozkR6mwTWQi6/E/9huQVrA==} - peerDependencies: - react: '>=16.9.0' - react-dom: '>=16.9.0' - - rc-dialog@9.6.0: - resolution: {integrity: sha512-ApoVi9Z8PaCQg6FsUzS8yvBEQy0ZL2PkuvAgrmohPkN3okps5WZ5WQWPc1RNuiOKaAYv8B97ACdsFU5LizzCqg==} - peerDependencies: - react: '>=16.9.0' - react-dom: '>=16.9.0' - - rc-footer@0.6.8: - resolution: {integrity: sha512-JBZ+xcb6kkex8XnBd4VHw1ZxjV6kmcwUumSHaIFdka2qzMCo7Klcy4sI6G0XtUpG/vtpislQCc+S9Bc+NLHYMg==} - peerDependencies: - react: '>=16.0.0' - react-dom: '>=16.0.0' - - rc-image@7.12.0: - resolution: {integrity: sha512-cZ3HTyyckPnNnUb9/DRqduqzLfrQRyi+CdHjdqgsyDpI3Ln5UX1kXnAhPBSJj9pVRzwRFgqkN7p9b6HBDjmu/Q==} - peerDependencies: - react: '>=16.9.0' - react-dom: '>=16.9.0' - - rc-input-number@9.5.0: - resolution: {integrity: sha512-bKaEvB5tHebUURAEXw35LDcnRZLq3x1k7GxfAqBMzmpHkDGzjAtnUL8y4y5N15rIFIg5IJgwr211jInl3cipag==} - peerDependencies: - react: '>=16.9.0' - react-dom: '>=16.9.0' - - rc-input@1.8.0: - resolution: {integrity: sha512-KXvaTbX+7ha8a/k+eg6SYRVERK0NddX8QX7a7AnRvUa/rEH0CNMlpcBzBkhI0wp2C8C4HlMoYl8TImSN+fuHKA==} - peerDependencies: - react: '>=16.0.0' - react-dom: '>=16.0.0' - - rc-menu@9.16.1: - resolution: {integrity: sha512-ghHx6/6Dvp+fw8CJhDUHFHDJ84hJE3BXNCzSgLdmNiFErWSOaZNsihDAsKq9ByTALo/xkNIwtDFGIl6r+RPXBg==} - peerDependencies: - react: '>=16.9.0' - react-dom: '>=16.9.0' - - rc-motion@2.9.5: - resolution: {integrity: sha512-w+XTUrfh7ArbYEd2582uDrEhmBHwK1ZENJiSJVb7uRxdE7qJSYjbO2eksRXmndqyKqKoYPc9ClpPh5242mV1vA==} - peerDependencies: - react: '>=16.9.0' - react-dom: '>=16.9.0' - - rc-overflow@1.5.0: - resolution: {integrity: sha512-Lm/v9h0LymeUYJf0x39OveU52InkdRXqnn2aYXfWmo8WdOonIKB2kfau+GF0fWq6jPgtdO9yMqveGcK6aIhJmg==} - peerDependencies: - react: '>=16.9.0' - react-dom: '>=16.9.0' - - rc-resize-observer@1.4.3: - resolution: {integrity: sha512-YZLjUbyIWox8E9i9C3Tm7ia+W7euPItNWSPX5sCcQTYbnwDb5uNpnLHQCG1f22oZWUhLw4Mv2tFmeWe68CDQRQ==} - peerDependencies: - react: '>=16.9.0' - react-dom: '>=16.9.0' - - rc-util@5.44.4: - resolution: {integrity: sha512-resueRJzmHG9Q6rI/DfK6Kdv9/Lfls05vzMs1Sk3M2P+3cJa+MakaZyWY8IPfehVuhPJFKrIY1IK4GqbiaiY5w==} - peerDependencies: - react: '>=16.9.0' - react-dom: '>=16.9.0' - - re-resizable@6.11.2: - resolution: {integrity: sha512-2xI2P3OHs5qw7K0Ud1aLILK6MQxW50TcO+DetD9eIV58j84TqYeHoZcL9H4GXFXXIh7afhH8mv5iUCXII7OW7A==} - peerDependencies: - react: ^16.13.1 || ^17.0.0 || ^18.0.0 || ^19.0.0 - react-dom: ^16.13.1 || ^17.0.0 || ^18.0.0 || ^19.0.0 - - react-aria-components@1.21.1: - resolution: {integrity: sha512-J88WflYY0z+EhLX0ce+GjFlQ3ag3ubIgfUTjpKSrBQBu92Xtl4Ub9o118HItUddtbk1Ido0jzyPy94/klZy1hg==} - peerDependencies: - react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 - react-dom: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 - - react-aria@3.52.1: - resolution: {integrity: sha512-fdZZruC9/x/joCg0mhKGs5aHpwrXLCSZ4GOJmhhYyiE0ffyEsk9MLFt9LCOCF9tn8ErTD+UDQP4oDUSlZkmpCg==} - peerDependencies: - react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 - react-dom: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 - - react-avatar-editor@15.1.0: - resolution: {integrity: sha512-Zto7u9l6Wd5LPPtjeFJ+7uwoT4bs01OSgkN2kxD18lWl8IiZ0GY3nWCbKPx4qIU7Au1vENsMJm19rfVWHHayaQ==} - peerDependencies: - react: ^0.14.0 || ^16.0.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 - react-dom: ^0.14.0 || ^16.0.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 - - react-colorful@5.8.0: - resolution: {integrity: sha512-Wy9OzPfjSN9bF12OB8N7UQvlsZ0I+7wHxpN+bV5BjNQGxOj6IiwkRjevJK9yOBjJWGQvAaf1OXtn8rUeEatAng==} - peerDependencies: - react: '>=16.8.0' - react-dom: '>=16.8.0' - - react-dom@19.2.7: - resolution: {integrity: sha512-t0BRVXvbiE/o20Hfw669rLbMCDWtYZLvmJigy2f0MxsXF+71pxhR3xOkspmsO8h3ZlNzyibAmtCa3l4lYKk6gQ==} - peerDependencies: - react: ^19.2.7 - - react-draggable@4.7.1: - resolution: {integrity: sha512-wa3tzfFnYt3yaZLuyU58fl1TNunfWfBekDgWhZA1+gb2jnp42wZ0ymuopR6M5kqDYmm4hKmzGlcKWjZf3Zb6RQ==} - peerDependencies: - react: '>= 16.3.0' - react-dom: '>= 16.3.0' - - react-dropzone@12.0.0: - resolution: {integrity: sha512-xGZr9YFTLqLkYDSx7CMRhjW4rIEqE9w2DhJB0CIEslPzUj3OQQcg7fK8uW85exB+UaJoPfk74lnKi0kv7q6rDw==} - engines: {node: '>= 10.13'} - peerDependencies: - react: '>= 16.8' - - react-error-boundary@6.1.3: - resolution: {integrity: sha512-GnSKpCohFi2nQmJCWwP8O8wub7zexlePvpsejvQr35vS5RTouS1+utTNOmyc540yw5vyOXnSL1rBWsCQDmkyUA==} - peerDependencies: - '@types/react': ^18.0.0 || ^19.0.0 - react: ^18.0.0 || ^19.0.0 - peerDependenciesMeta: - '@types/react': - optional: true - - react-fast-compare@3.2.2: - resolution: {integrity: sha512-nsO+KSNgo1SbJqJEYRE9ERzo7YtYbou/OqjSQKxV7jcKox7+usiUVZOAC+XnDOABXggQTno0Y1CpVnuWEc1boQ==} - - react-hotkeys-hook@5.3.3: - resolution: {integrity: sha512-aswgyWUnE25hmhzHTfKDmKzsaSE5DJ4LKaU/o6rQSXkDd/1Bh9TfAFQbHkf6fLy11HvlYkp+cDDarGdhmCDhoQ==} - peerDependencies: - react: '>=16.8.0' - react-dom: '>=16.8.0' - - react-is@16.13.1: - resolution: {integrity: sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==} - - react-is@16.7.0: - resolution: {integrity: sha512-Z0VRQdF4NPDoI0tsXVMLkJLiwEBa+RP66g0xDHxgxysxSoCUccSten4RTF/UFvZF1dZvZ9Zu1sx+MDXwcOR34g==} - - react-is@18.2.0: - resolution: {integrity: sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==} - - react-is@19.2.8: - resolution: {integrity: sha512-s5un28nYxKJw5gvUHyW5PCC28CvBqLu9r3cWgzHT4Vo/5fqqkFcdRYsGcKf50WMPpjjFZS5d76fn3YCo2njKwQ==} - - react-markdown@10.1.0: - resolution: {integrity: sha512-qKxVopLT/TyA6BX3Ue5NwabOsAzm0Q7kAPwq6L+wWDwisYs7R8vZ0nRXqq6rkueboxpkjvLGU9fWifiX/ZZFxQ==} - peerDependencies: - '@types/react': '>=18' - react: '>=18' - - react-merge-refs@3.0.2: - resolution: {integrity: sha512-MSZAfwFfdbEvwkKWP5EI5chuLYnNUxNS7vyS0i1Jp+wtd8J4Ga2ddzhaE68aMol2Z4vCnRM/oGOo1a3V75UPlw==} - peerDependencies: - react: '>=16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0' - peerDependenciesMeta: - react: - optional: true - - react-redux@9.3.0: - resolution: {integrity: sha512-KQopgqFo/p/fgmAs5qz6p5RWaNAzq40WAu7fJIXnQpYxFPbJYtsJPWvGeF2rOBaY/kEuV77AVsX8TsQzKm+A/g==} - peerDependencies: - '@types/react': ^18.2.25 || ^19 - react: ^18.0 || ^19 - redux: ^5.0.0 - peerDependenciesMeta: - '@types/react': - optional: true - redux: - optional: true - - react-remove-scroll-bar@2.3.8: - resolution: {integrity: sha512-9r+yi9+mgU33AKcj6IbT9oRCO78WriSj6t/cF8DWBZJ9aOGPOTEDvdUDz1FwKim7QXWwmHqtdHnRJfhAxEG46Q==} - engines: {node: '>=10'} - peerDependencies: - '@types/react': '*' - react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 - peerDependenciesMeta: - '@types/react': - optional: true - - react-remove-scroll@2.7.2: - resolution: {integrity: sha512-Iqb9NjCCTt6Hf+vOdNIZGdTiH1QSqr27H/Ek9sv/a97gfueI/5h1s3yRi1nngzMUaOOToin5dI1dXKdXiF+u0Q==} - engines: {node: '>=10'} - peerDependencies: - '@types/react': '*' - react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc - peerDependenciesMeta: - '@types/react': - optional: true - - react-rnd@10.5.3: - resolution: {integrity: sha512-s/sIT3pGZnQ+57egijkTp9mizjIWrJz68Pq6yd+F/wniFY3IriML18dUXnQe/HP9uMiJ+9MAp44hljG99fZu6Q==} - peerDependencies: - react: '>=16.3.0' - react-dom: '>=16.3.0' - - react-router@8.3.0: - resolution: {integrity: sha512-qyPMvW83jGIct3yiieisxdk9M745anqhpIMKN5m1t6yBMfgVPpt77aHOqs5fUlEJRMCGffg9BaQLH9oPVOL7xQ==} - engines: {node: '>=22.22.0'} - peerDependencies: - react: '>=19.2.7' - react-dom: '>=19.2.7' - peerDependenciesMeta: - react-dom: - optional: true - - react-stately@3.50.0: - resolution: {integrity: sha512-TnckvpDQGU0672wEaLZqdzvhuSeXWjgW6vijMWxiKOxc8CosQUfPGISdcZyfHqjX3XMjEtRxj4w9HumvX4h4mw==} - peerDependencies: - react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 - - react-style-singleton@2.2.3: - resolution: {integrity: sha512-b6jSvxvVnyptAiLjbkWLE/lOnR4lfTtDAl+eUC7RZy+QQWc6wRzIV2CE6xBuMmDxc2qIihtDCZD5NPOFl7fRBQ==} - engines: {node: '>=10'} - peerDependencies: - '@types/react': '*' - react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc - peerDependenciesMeta: - '@types/react': - optional: true - - react-syntax-highlighter@16.1.1: - resolution: {integrity: sha512-PjVawBGy80C6YbC5DDZJeUjBmC7skaoEUdvfFQediQHgCL7aKyVHe57SaJGfQsloGDac+gCpTfRdtxzWWKmCXA==} - engines: {node: '>= 16.20.2'} - peerDependencies: - react: '>= 0.14.0' - - react-use-measure@2.1.7: - resolution: {integrity: sha512-KrvcAo13I/60HpwGO5jpW7E9DfusKyLPLvuHlUyP5zqnmAPhNc6qTRjUQrdTADl0lpPpDVU2/Gg51UlOGHXbdg==} - peerDependencies: - react: '>=16.13' - react-dom: '>=16.13' - peerDependenciesMeta: - react-dom: - optional: true - - react-zoom-pan-pinch@4.0.4: - resolution: {integrity: sha512-P0D7lfNHyJCNuUozoVdt0WNWcQ34ZbbD71B8pb+UtF7Th8MKBnxYiPApDfPZZmr+Gk0l0WmGTXL8R36JirHcQQ==} - engines: {node: '>=8', npm: '>=5'} - peerDependencies: - react: '*' - react-dom: '*' - - react@19.2.7: - resolution: {integrity: sha512-HNe9WslTbXmFK8o8cmwgAeJFSBvt1bPdHCVKtaaV+WlAN36mpT4hcRpwbf3fY56ar2oIXzsBpOAiIRHAdY0OlQ==} - engines: {node: '>=0.10.0'} - - readable-stream@2.3.8: - resolution: {integrity: sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==} - - readdirp@3.6.0: - resolution: {integrity: sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==} - engines: {node: '>=8.10.0'} - - recast@0.23.21: - resolution: {integrity: sha512-mFAyJq9vUbSTARLZUvAEf1z3YxlvAwswbmxMx2mPA/MSm4KmpwvwvhsH/NIrZhyOuwD60Lzyw2qh83uCbgTPYw==} - engines: {node: '>= 4'} - - recharts@3.10.1: - resolution: {integrity: sha512-QXFrvt6IVcw7eeZCoyXTwkIJAX3Dv1nyVhMicXJ47GsGDDpcN8z6o644DibE9XjpBTThtsomLKnTV6lc+cVFUA==} - engines: {node: '>=18'} - peerDependencies: - react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 - react-dom: ^16.0.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 - react-is: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 - - recma-build-jsx@1.0.0: - resolution: {integrity: sha512-8GtdyqaBcDfva+GUKDr3nev3VpKAhup1+RvkMvUxURHpW7QyIvk9F5wz7Vzo06CEMSilw6uArgRqhpiUcWp8ew==} - - recma-jsx@1.0.1: - resolution: {integrity: sha512-huSIy7VU2Z5OLv6oFLosQGGDqPqdO1iq6bWNAdhzMxSJP7RAso4fCZ1cKu8j9YHCZf3TPrq4dw3okhrylgcd7w==} - peerDependencies: - acorn: ^6.0.0 || ^7.0.0 || ^8.0.0 - - recma-parse@1.0.0: - resolution: {integrity: sha512-OYLsIGBB5Y5wjnSnQW6t3Xg7q3fQ7FWbw/vcXtORTnyaSFscOtABg+7Pnz6YZ6c27fG1/aN8CjfwoUEUIdwqWQ==} - - recma-stringify@1.0.0: - resolution: {integrity: sha512-cjwII1MdIIVloKvC9ErQ+OgAtwHBmcZ0Bg4ciz78FtbT8In39aAYbaA7zvxQ61xVMSPE8WxhLwLbhif4Js2C+g==} - - redux-thunk@3.1.0: - resolution: {integrity: sha512-NW2r5T6ksUKXCabzhL9z+h206HQw/NJkcLm1GPImRQ8IzfXwRGqjVhKJGauHirT0DAuyy6hjdnMZaRoAcy0Klw==} - peerDependencies: - redux: ^5.0.0 - - redux@5.0.1: - resolution: {integrity: sha512-M9/ELqF6fy8FwmkpnF0S3YKOqMyoWJ4+CS5Efg2ct3oY9daQvd/Pc71FpGZsVsbl3Cpb+IIcjBDUnnyBdQbq4w==} - - refractor@5.0.0: - resolution: {integrity: sha512-QXOrHQF5jOpjjLfiNk5GFnWhRXvxjUVnlFxkeDmewR5sXkr3iM46Zo+CnRR8B+MDVqkULW4EcLVcRBNOPXHosw==} - - regex-recursion@6.0.2: - resolution: {integrity: sha512-0YCaSCq2VRIebiaUviZNs0cBz1kg5kVS2UKUfNIx8YVs1cN3AV7NTctO5FOKBA+UT2BPJIWZauYHPqJODG50cg==} - - regex-utilities@2.3.0: - resolution: {integrity: sha512-8VhliFJAWRaUiVvREIiW2NXXTmHs4vMNnSzuJVhscgmGav3g9VDxLrQndI3dZZVVdp0ZO/5v0xmX516/7M9cng==} - - regex@6.1.0: - resolution: {integrity: sha512-6VwtthbV4o/7+OaAF9I5L5V3llLEsoPyq9P1JVXkedTP33c7MfCG0/5NOPcSJn0TzXcG9YUrR0gQSWioew3LDg==} - - rehype-github-alerts@4.2.0: - resolution: {integrity: sha512-6di6kEu9WUHKLKrkKG2xX6AOuaCMGghg0Wq7MEuM/jBYUPVIq6PJpMe00dxMfU+/YSBtDXhffpDimgDi+BObIQ==} - - rehype-harden@1.1.8: - resolution: {integrity: sha512-Qn7vR1xrf6fZCrkm9TDWi/AB4ylrHy+jqsNm1EHOAmbARYA6gsnVJBq/sdBh6kmT4NEZxH5vgIjrscefJAOXcw==} - - rehype-katex@7.0.1: - resolution: {integrity: sha512-OiM2wrZ/wuhKkigASodFoo8wimG3H12LWQaH8qSPVJn9apWKFSH3YOCtbKpBorTVw/eI7cuT21XBbvwEswbIOA==} - - rehype-raw@7.0.0: - resolution: {integrity: sha512-/aE8hCfKlQeA8LmyeyQvQF3eBiLRGNlfBJEvWH7ivp9sBqs7TNqBL5X3v157rM4IFETqDnIOO+z5M/biZbo9Ww==} - - rehype-recma@1.0.0: - resolution: {integrity: sha512-lqA4rGUf1JmacCNWWZx0Wv1dHqMwxzsDWYMTowuplHF3xH0N/MmrZ/G3BDZnzAkRmxDadujCjaKM2hqYdCBOGw==} - - rehype-sanitize@6.0.0: - resolution: {integrity: sha512-CsnhKNsyI8Tub6L4sm5ZFsme4puGfc6pYylvXo1AeqaGbjOYyzNv3qZPwvs0oMJ39eryyeOdmxwUIo94IpEhqg==} - - remark-breaks@4.0.0: - resolution: {integrity: sha512-IjEjJOkH4FuJvHZVIW0QCDWxcG96kCq7An/KVH2NfJe6rKZU2AsHeB3OEjPNRxi4QC34Xdx7I2KGYn6IpT7gxQ==} - - remark-cjk-friendly@2.3.1: - resolution: {integrity: sha512-f+pKZRxCRwNEGFBKNRAZAqU91GIK1SAo3ZyFHWRUgC9zcxRR0BXKd6YwqgSsxtW0rNpUDtONj7H5nje2WL3fcA==} - engines: {node: '>=18'} - peerDependencies: - '@types/mdast': ^4.0.0 - unified: ^11.0.0 - peerDependenciesMeta: - '@types/mdast': - optional: true - - remark-gfm@4.0.1: - resolution: {integrity: sha512-1quofZ2RQ9EWdeN34S79+KExV1764+wCUGop5CPL1WGdD0ocPpu91lzPGbwWMECpEpd42kJGQwzRfyov9j4yNg==} - - remark-github@12.0.0: - resolution: {integrity: sha512-ByefQKFN184LeiGRCabfl7zUJsdlMYWEhiLX1gpmQ11yFg6xSuOTW7LVCv0oc1x+YvUMJW23NU36sJX2RWGgvg==} - - remark-math@6.0.0: - resolution: {integrity: sha512-MMqgnP74Igy+S3WwnhQ7kqGlEerTETXMvJhrUzDikVZ2/uogJCb+WHUg97hK9/jcfc0dkD73s3LN8zU49cTEtA==} - - remark-mdx@3.0.0: - resolution: {integrity: sha512-O7yfjuC6ra3NHPbRVxfflafAj3LTwx3b73aBvkEFU5z4PsD6FD4vrqJAkE5iNGLz71GdjXfgRqm3SQ0h0VuE7g==} - - remark-parse@11.0.0: - resolution: {integrity: sha512-FCxlKLNGknS5ba/1lmpYijMUzX2esxW5xQqjWxw2eHFfS2MSdaHVINFmhjo+qN1WhZhNimq0dZATN9pH0IDrpA==} - - remark-rehype@11.1.2: - resolution: {integrity: sha512-Dh7l57ianaEoIpzbp0PC9UKAdCSVklD8E5Rpw7ETfbTl3FqcOOgq5q2LVDhgGCkaBv7p24JXikPdvhhmHvKMsw==} - - remark-stringify@11.0.0: - resolution: {integrity: sha512-1OSmLd3awB/t8qdoEOMazZkNsfVTeY4fTsgzcQFdXNq8ToTN4ZGwrMnlda4K6smTFKD+GRV6O48i6Z4iKgPPpw==} - - remend@1.3.0: - resolution: {integrity: sha512-iIhggPkhW3hFImKtB10w0dz4EZbs28mV/dmbcYVonWEJ6UGHHpP+bFZnTh6GNWJONg5m+U56JrL+8IxZRdgWjw==} - - require-from-string@2.0.2: - resolution: {integrity: sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==} - engines: {node: '>=0.10.0'} - - reselect@5.2.0: - resolution: {integrity: sha512-AgZ3UOZm3YndfrJ4OYjgrT7bmCm/1iqkjvEfH/oYjzh6PD2qw4QuT3jjnXIrpdt4MTpMXclMT3lXbmRY+XRakw==} - - resize-observer-polyfill@1.5.1: - resolution: {integrity: sha512-LwZrotdHOo12nQuZlHEmtuXdqGoOD0OhaxopaNFxWzInpEgaLWoVuAMbTzixuosCx2nEG58ngzW3vxdWoxIgdg==} - - resolve-from@4.0.0: - resolution: {integrity: sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==} - engines: {node: '>=4'} - - resolve@1.22.12: - resolution: {integrity: sha512-TyeJ1zif53BPfHootBGwPRYT1RUt6oGWsaQr8UyZW/eAm9bKoijtvruSDEmZHm92CwS9nj7/fWttqPCgzep8CA==} - engines: {node: '>= 0.4'} - hasBin: true - - restore-cursor@5.1.0: - resolution: {integrity: sha512-oMA2dcrw6u0YfxJQXm342bFKX/E4sG9rbTzO9ptUcR/e8A33cHuvStiYOwH7fszkZlZ1z/ta9AAoPk2F4qIOHA==} - engines: {node: '>=18'} - - reusify@1.1.0: - resolution: {integrity: sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==} - engines: {iojs: '>=1.0.0', node: '>=0.10.0'} - - robust-predicates@3.0.3: - resolution: {integrity: sha512-NS3levdsRIUOmiJ8FZWCP7LG3QpJyrs/TE0Zpf1yvZu8cAJJ6QMW92H1c7kWpdIHo8RvmLxN/o2JXTKHp74lUA==} - - rolldown@1.2.4: - resolution: {integrity: sha512-rSr7irW0K7QRWzjdJXqZowkcRdDtjRduh43rBltnVKd0VFq839l1lJoDvGJb6gl7+4rTTCrPWu+YfujUL8Ug7w==} - engines: {node: ^20.19.0 || >=22.12.0} - hasBin: true - - rope-sequence@1.3.4: - resolution: {integrity: sha512-UT5EDe2cu2E/6O4igUr5PSFs23nvvukicWHx6GnOPlHAiiYbzNuCRQCuiUdHJQcqKalLKlrYJnjY0ySGsXNQXQ==} - - roughjs@4.6.4: - resolution: {integrity: sha512-s6EZ0BntezkFYMf/9mGn7M8XGIoaav9QQBCnJROWB3brUWQ683Q2LbRD/hq0Z3bAJ/9NVpU/5LpiTWvQMyLDhw==} - - roughjs@4.6.6: - resolution: {integrity: sha512-ZUz/69+SYpFN/g/lUlo2FXcIjRkSu3nDarreVdGGndHEBJ6cXPdKguS8JGxwj5HA5xIbVKSmLgr5b3AWxtRfvQ==} - - router@2.2.0: - resolution: {integrity: sha512-nLTrUKm2UyiL7rlhapu/Zl45FwNgkZGaCpZbIHajDYgwlJCOzLSk+cIPAnsEqV955GjILJnKbdQC1nVPz+gAYQ==} - engines: {node: '>= 18'} - - run-applescript@7.1.0: - resolution: {integrity: sha512-DPe5pVFaAsinSaV6QjQ6gdiedWDcRCbUuiQfQa2wmWV7+xC9bGulGI8+TdRmoFkAPaBXk8CrAbnlY2ISniJ47Q==} - engines: {node: '>=18'} - - run-parallel@1.2.0: - resolution: {integrity: sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==} - - rw@1.3.3: - resolution: {integrity: sha512-PdhdWy89SiZogBLaw42zdeqtRJ//zFd2PgQavcICDUgJT5oW10QCRKbJ6bg4r0/UY2M6BWd5tkxuGFRvCkgfHQ==} - - safe-buffer@5.1.2: - resolution: {integrity: sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==} - - safer-buffer@2.1.2: - resolution: {integrity: sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==} - - sass@1.51.0: - resolution: {integrity: sha512-haGdpTgywJTvHC2b91GSq+clTKGbtkkZmVAb82jZQN/wTy6qs8DdFm2lhEQbEwrY0QDRgSQ3xDurqM977C3noA==} - engines: {node: '>=12.0.0'} - hasBin: true - - scheduler@0.27.0: - resolution: {integrity: sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q==} - - screenfull@5.2.0: - resolution: {integrity: sha512-9BakfsO2aUQN2K9Fdbj87RJIEZ82Q9IGim7FqM5OsebfoFC6ZHXgDq/KvniuLTPdeM8wY2o6Dj3WQ7KeQCj3cA==} - engines: {node: '>=0.10.0'} - - scroll-into-view-if-needed@3.1.0: - resolution: {integrity: sha512-49oNpRjWRvnU8NyGVmUaYG4jtTkNonFZI86MmGRDqBphEK2EXT9gdEUoQPZhuBM8yWHxCWbobltqYO5M4XrUvQ==} - - semver-compare@1.0.0: - resolution: {integrity: sha512-YM3/ITh2MJ5MtzaM429anh+x2jiLVjqILF4m4oyQB18W7Ggea7BfqdH/wGMK7dDiMghv/6WG7znWMwUDzJiXow==} - - semver@6.3.1: - resolution: {integrity: sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==} - hasBin: true - - semver@7.8.5: - resolution: {integrity: sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA==} - engines: {node: '>=10'} - hasBin: true - - send@1.2.1: - resolution: {integrity: sha512-1gnZf7DFcoIcajTjTwjwuDjzuz4PPcY2StKPlsGAQ1+YH20IRVrBaXSWmdjowTJ6u8Rc01PoYOGHXfP1mYcZNQ==} - engines: {node: '>= 18'} - - serve-static@2.2.1: - resolution: {integrity: sha512-xRXBn0pPqQTVQiC8wyQrKs2MOlX24zQ0POGaj0kultvoOCstBQM5yvOhAVSUwOMjQtTvsPWoNCHfPGwaaQJhTw==} - engines: {node: '>= 18'} - - set-value@2.0.1: - resolution: {integrity: sha512-JxHc1weCN68wRY0fhCoXpyK55m/XPHafOmK4UWD7m2CI14GMcFypt4w/0+NV5f/ZMby2F6S2wwA7fgynh9gWSw==} - engines: {node: '>=0.10.0'} - - setimmediate@1.0.5: - resolution: {integrity: sha512-MATJdZp8sLqDl/68LfQmbP8zKPLQNV6BIZoIgrscFDQ+RsvK/BxeDQOgyxKKoh0y/8h3BqVFnCqQ/gd+reiIXA==} - - setprototypeof@1.2.0: - resolution: {integrity: sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==} - - shadcn@4.18.0: - resolution: {integrity: sha512-tUFZgkYmfVNQVm3xX7lhSzOvDsp+O14ac5dwgXIr5mIsr79ISueb/Mu+ZtWMz0DH6v77u4eYyvbQ9TTMpSn3aw==} - engines: {node: '>=20.18.1'} - hasBin: true - - shebang-command@2.0.0: - resolution: {integrity: sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==} - engines: {node: '>=8'} - - shebang-regex@3.0.0: - resolution: {integrity: sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==} - engines: {node: '>=8'} - - shiki@3.23.0: - resolution: {integrity: sha512-55Dj73uq9ZXL5zyeRPzHQsK7Nbyt6Y10k5s7OjuFZGMhpp4r/rsLBH0o/0fstIzX1Lep9VxefWljK/SKCzygIA==} - - shiki@4.4.3: - resolution: {integrity: sha512-Mb/GvXPHBAXdgGIcnfU5L3ldpn1XcxrGkPHwqgRx17/I2XRfqlFKk2vGkHWINn1kdXvzJZeuO3is6I9KLPFm0g==} - engines: {node: '>=20'} - - side-channel-list@1.0.1: - resolution: {integrity: sha512-mjn/0bi/oUURjc5Xl7IaWi/OJJJumuoJFQJfDDyO46+hBWsfaVM65TBHq2eoZBhzl9EchxOijpkbRC8SVBQU0w==} - engines: {node: '>= 0.4'} - - side-channel-map@1.0.1: - resolution: {integrity: sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==} - engines: {node: '>= 0.4'} - - side-channel-weakmap@1.0.2: - resolution: {integrity: sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==} - engines: {node: '>= 0.4'} - - side-channel@1.1.1: - resolution: {integrity: sha512-6x6dK6zJdpTzF4sQeNYxwtvBzf6Eg4GtlesS94HOvTudUeyK2WXAaIfmDgsyslYrRBeFIlsi54AYsFGUuhmvrQ==} - engines: {node: '>= 0.4'} - - signal-exit@3.0.7: - resolution: {integrity: sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==} - - signal-exit@4.1.0: - resolution: {integrity: sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==} - engines: {node: '>=14'} - - sisteransi@1.0.5: - resolution: {integrity: sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==} - - sliced@1.0.1: - resolution: {integrity: sha512-VZBmZP8WU3sMOZm1bdgTadsQbcscK0UM8oKxKVBs4XAhUo2Xxzm/OFMGBkPusxw9xL3Uy8LrzEqGqJhclsr0yA==} - deprecated: Unsupported - - smart-buffer@4.2.0: - resolution: {integrity: sha512-94hK0Hh8rPqQl2xXc3HsaBoOXKV20MToPkcXvwbISWLEs+64sBq5kFgn2kJDHb1Pry9yrP0dxrCI9RRci7RXKg==} - engines: {node: '>= 6.0.0', npm: '>= 3.0.0'} - - socks@2.8.9: - resolution: {integrity: sha512-LJhUYUvItdQ0LkJTmPeaEObWXAqFyfmP85x0tch/ez9cahmhlBBLbIqDFnvBnUJGagb0JbIQrkBs1wJ+yRYpEw==} - engines: {node: '>= 10.0.0', npm: '>= 3.0.0'} - - source-map-js@1.2.1: - resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==} - engines: {node: '>=0.10.0'} - - source-map@0.5.7: - resolution: {integrity: sha512-LbrmJOMUSdEVxIKvdcJzQC+nQhe8FUZQTXQy6+I75skNgn3OoQ0DZA8YnFa7gp8tqtL3KPf1kmo0R5DoApeSGQ==} - engines: {node: '>=0.10.0'} - - source-map@0.6.1: - resolution: {integrity: sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==} - engines: {node: '>=0.10.0'} - - source-map@0.7.0: - resolution: {integrity: sha512-JsXsCYrKzxA5kU8LanQJHIPoEY3fEH5WYSMJ8Z77ESByI18VFEoxB46H2eNHqK2nVqTRjUe5DYvNHmyT3JOd1w==} - engines: {node: '>= 8'} - - space-separated-tokens@2.0.2: - resolution: {integrity: sha512-PEGlAwrG8yXGXRjW32fGbg66JAlOAwbObuqVoJpv/mRgoWDQfgH1wDPvtzWyUSNAXBGSk8h755YDbbcEy3SH2Q==} - - split-on-first@3.0.0: - resolution: {integrity: sha512-qxQJTx2ryR0Dw0ITYyekNQWpz6f8dGd7vffGNflQQ3Iqj9NJ6qiZ7ELpZsJ/QBhIVAiDfXdag3+Gp8RvWa62AA==} - engines: {node: '>=12'} - - split-string@3.1.0: - resolution: {integrity: sha512-NzNVhJDYpwceVVii8/Hu6DKfD2G+NrQHlS/V/qgv763EYudVwEcMQNxd2lh+0VrUByXN/oJkl5grOhYWvQUYiw==} - engines: {node: '>=0.10.0'} - - sprintf-js@1.0.3: - resolution: {integrity: sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==} - - stats-gl@2.4.2: - resolution: {integrity: sha512-g5O9B0hm9CvnM36+v7SFl39T7hmAlv541tU81ME8YeSb3i1CIP5/QdDeSB3A0la0bKNHpxpwxOVRo2wFTYEosQ==} - peerDependencies: - '@types/three': '*' - three: '*' - - stats.js@0.17.0: - resolution: {integrity: sha512-hNKz8phvYLPEcRkeG1rsGmV5ChMjKDAWU7/OJJdDErPBNChQXxCo3WZurGpnWc6gZhAzEPFad1aVgyOANH1sMw==} - - statuses@2.0.2: - resolution: {integrity: sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==} - engines: {node: '>= 0.8'} - - stdin-discarder@0.2.2: - resolution: {integrity: sha512-UhDfHmA92YAlNnCfhmq0VeNL5bDbiZGg7sZ2IvPsXubGkiNa9EC+tUTsjBRsYUAz87btI6/1wf4XoVvQ3uRnmQ==} - engines: {node: '>=18'} - - streamdown@2.5.0: - resolution: {integrity: sha512-/tTnURfIOxZK/pqJAxsfCvETG/XCJHoWnk3jq9xLcuz6CSpnjjuxSRBTTL4PKGhxiZQf0lqPxGhImdpwcZ2XwA==} - peerDependencies: - react: ^18.0.0 || ^19.0.0 - react-dom: ^18.0.0 || ^19.0.0 - - string-convert@0.2.1: - resolution: {integrity: sha512-u/1tdPl4yQnPBjnVrmdLo9gtuLvELKsAoRapekWggdiQNvvvum+jYF329d84NAa660KQw7pB2n36KrIKVoXa3A==} - - string-width@7.2.0: - resolution: {integrity: sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ==} - engines: {node: '>=18'} - - string_decoder@1.1.1: - resolution: {integrity: sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==} - - stringify-entities@4.0.4: - resolution: {integrity: sha512-IwfBptatlO+QCJUo19AqvrPNqlVMpW9YEL2LIVY+Rpv2qsjCGxaDLNRgeGsQWJhfItebuJhsGSLjaBbNSQ+ieg==} - - stringify-object@5.0.0: - resolution: {integrity: sha512-zaJYxz2FtcMb4f+g60KsRNFOpVMUyuJgA51Zi5Z1DOTC3S59+OQiVOzE9GZt0x72uBGWKsQIuBKeF9iusmKFsg==} - engines: {node: '>=14.16'} - - strip-ansi@6.0.1: - resolution: {integrity: sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==} - engines: {node: '>=8'} - - strip-ansi@7.2.0: - resolution: {integrity: sha512-yDPMNjp4WyfYBkHnjIRLfca1i6KMyGCtsVgoKe/z1+6vukgaENdgGBZt+ZmKPc4gavvEZ5OgHfHdrazhgNyG7w==} - engines: {node: '>=12'} - - strip-bom@3.0.0: - resolution: {integrity: sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA==} - engines: {node: '>=4'} - - strip-final-newline@2.0.0: - resolution: {integrity: sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==} - engines: {node: '>=6'} - - strip-final-newline@4.0.0: - resolution: {integrity: sha512-aulFJcD6YK8V1G7iRB5tigAP4TsHBZZrOV8pjV++zdUwmeV8uzbY7yn6h9MswN62adStNZFuCIx4haBnRuMDaw==} - engines: {node: '>=18'} - - style-mod@4.1.3: - resolution: {integrity: sha512-i/n8VsZydrugj3Iuzll8+x/00GH2vnYsk1eomD8QiRrSAeW6ItbCQDtfXCeJHd0iwiNagqjQkvpvREEPtW3IoQ==} - - style-to-js@1.1.21: - resolution: {integrity: sha512-RjQetxJrrUJLQPHbLku6U/ocGtzyjbJMP9lCNK7Ag0CNh690nSH8woqWH9u16nMjYBAok+i7JO1NP2pOy8IsPQ==} - - style-to-object@1.0.14: - resolution: {integrity: sha512-LIN7rULI0jBscWQYaSswptyderlarFkjQ+t79nzty8tcIAceVomEVlLzH5VP4Cmsv6MtKhs7qaAiwlcp+Mgaxw==} - - stylis@4.2.0: - resolution: {integrity: sha512-Orov6g6BB1sDfYgzWfTHDOxamtX1bE/zo104Dh9e6fqJ3PooipYyfJ0pUmrZO2wAvO8YbEyeFrkV91XTsGMSrw==} - - stylis@4.4.0: - resolution: {integrity: sha512-5Z9ZpRzfuH6l/UAvCPAPUo3665Nk2wLaZU3x+TLHKVzIz33+sbJqbtrYoC3KD4/uVOr2Zp+L0LySezP9OHV9yA==} - - supports-preserve-symlinks-flag@1.0.0: - resolution: {integrity: sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==} - engines: {node: '>= 0.4'} - - suspend-react@0.1.3: - resolution: {integrity: sha512-aqldKgX9aZqpoDp3e8/BZ8Dm7x1pJl+qI3ZKxDN0i/IQTWUwBx/ManmlVJ3wowqbno6c2bmiIfs+Um6LbsjJyQ==} - peerDependencies: - react: '>=17.0' - - swr@2.5.1: - resolution: {integrity: sha512-BRw55e8r0B7SpDN20CAzoQAHl7y1yP7/Zt7oqUjMv0vSt2u2Xnkm88Ws+VypbV9BXHQVuSuyVq7zMjO16wSExw==} - peerDependencies: - react: ^16.11.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 - - systeminformation@5.33.1: - resolution: {integrity: sha512-DEN6ICHk3Tk0Uf/hrAHh7xlt7iL5CJFBtPZinA0H62DrGG/KPKqq/Nzj6lCXPS4Ay/sf/14zNnk9LpqKzBIc+w==} - engines: {node: '>=10.0.0'} - os: [darwin, linux, win32, freebsd, openbsd, netbsd, sunos, android] - hasBin: true - - tabbable@6.5.0: - resolution: {integrity: sha512-wieBHXygIm7OyQOu5hQlkk62/WyCFYGlWg7L6/ZCUZwx0o398Zkn4pVmMyfYhfMG8kGrj/Krt8eIk6UKC6VzwA==} - - tailwind-merge@3.6.0: - resolution: {integrity: sha512-uxL7qAVQriqRQPAyK3pj66VqskWqoZ37PW94jwOTwNfq/z9oyu1V+eqrZqtR2+fCiXdYOZe/Modt8GtvqNzu+w==} - - tailwindcss@4.3.3: - resolution: {integrity: sha512-gOhV3P7ufE62QDGg1zVaTgCR+EtPv92k2nIhVcVKcLmxT1sUBsQGhnZj175j+MqRt4zLF7ic+sCYjfhxMxj7YQ==} - - tapable@2.3.3: - resolution: {integrity: sha512-uxc/zpqFg6x7C8vOE7lh6Lbda8eEL9zmVm/PLeTPBRhh1xCgdWaQ+J1CUieGpIfm2HdtsUpRv+HshiasBMcc6A==} - engines: {node: '>=6'} - - three-mesh-bvh@0.8.3: - resolution: {integrity: sha512-4G5lBaF+g2auKX3P0yqx+MJC6oVt6sB5k+CchS6Ob0qvH0YIhuUk1eYr7ktsIpY+albCqE80/FVQGV190PmiAg==} - peerDependencies: - three: '>= 0.159.0' - - three-stdlib@2.36.1: - resolution: {integrity: sha512-XyGQrFmNQ5O/IoKm556ftwKsBg11TIb301MB5dWNicziQBEs2g3gtOYIf7pFiLa0zI2gUwhtCjv9fmjnxKZ1Cg==} - peerDependencies: - three: '>=0.128.0' - - three@0.185.1: - resolution: {integrity: sha512-5aojFCXKwnjBRZvUnt3WFfEcvUJgkN5LlijRFN95hMy8WVkG4I0QNcJE+OuWvuJ0bOdStrbfXn0pkd6/QyiAlg==} - - throttle-debounce@5.0.2: - resolution: {integrity: sha512-B71/4oyj61iNH0KeCamLuE2rmKuTO5byTOSVwECM5FA7TiAiAW+UqTKZ9ERueC4qvgSttUhdmq1mXC3kJqGX7A==} - engines: {node: '>=12.22'} - - tiny-invariant@1.3.3: - resolution: {integrity: sha512-+FbBPE1o9QAYvviau/qC5SE3caw21q3xkvWKBtja5vgqOWIHHJ3ioaq1VPfn/Szqctz2bU/oYeKd9/z5BL+PVg==} - - tinyexec@1.3.0: - resolution: {integrity: sha512-QKAl9m8gWWGHV8jZcPeym6j+XULi6tOf1mT83WYJ4Lk2ytW/uwAWkrP0uFsdoYMdueVJ0qs26wZ+23xeB4ibNQ==} - engines: {node: '>=18'} - - tinyglobby@0.2.17: - resolution: {integrity: sha512-wXR/dYpcqKmfWpEdZjiKJOwCNFndD0DMnrW/cYjVGttEkBfVgcLFHoNrlj47mjOVic9yyNu65alsgF4NQyTa2g==} - engines: {node: '>=12.0.0'} - - tldraw@5.2.5: - resolution: {integrity: sha512-bTnOkeZhi+RgdHXPjxp0/nG6kbXzLC8ZlwNMpmGjWjT2Oel6uaJWni7PxFPIK4p2zInmnRamdU06ArDStLvPag==} - engines: {node: '>=22.12.0'} - peerDependencies: - react: ^18.2.0 || ^19.2.1 - react-dom: ^18.2.0 || ^19.2.1 - - to-regex-range@5.0.1: - resolution: {integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==} - engines: {node: '>=8.0'} - - to-vfile@8.0.0: - resolution: {integrity: sha512-IcmH1xB5576MJc9qcfEC/m/nQCFt3fzMHz45sSlgJyTWjRbKW1HAkJpuf3DgE57YzIlZcwcBZA5ENQbBo4aLkg==} - - toidentifier@1.0.1: - resolution: {integrity: sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==} - engines: {node: '>=0.6'} - - trim-lines@3.0.1: - resolution: {integrity: sha512-kRj8B+YHZCc9kQYdWfJB2/oUl9rA99qbowYYBtr4ui4mZyAQ2JpvVBd/6U2YloATfqBhBTSMhTpgBHtU0Mf3Rg==} - - troika-three-text@0.52.5: - resolution: {integrity: sha512-Ry3jRhic9pzcY4JduSvRRyDmVOSqEW19gT4vtK+aCiPNVcDlmkxvGG0YbFd36RTDq1wExOupXnvNF/j1oiHHDA==} - peerDependencies: - three: '>=0.125.0' - - troika-three-utils@0.52.5: - resolution: {integrity: sha512-WsePbcX8RtfidRfsxK1eCZCjF81ZDzAKHH/evLs0hdV2wpoCb0vArGZHdzdOJrSS3k4zfdtbKDaBh8+phkrYnw==} - peerDependencies: - three: '>=0.125.0' - - troika-worker-utils@0.52.0: - resolution: {integrity: sha512-W1CpvTHykaPH5brv5VHLfQo9D1OYuo0cSBEUQFFT/nBUzM8iD6Lq2/tgG/f1OelbAS1WtaTPQzE5uM49egnngw==} - - trough@2.2.0: - resolution: {integrity: sha512-tmMpK00BjZiUyVyvrBK7knerNgmgvcV/KLVyuma/SC+TQN167GrMRciANTz09+k3zW8L8t60jWO1GpfkZdjTaw==} - - ts-dedent@2.3.0: - resolution: {integrity: sha512-JfJeIHke7y2egdGGgRAvpCwYFUsHlM2gPcrVOxFkznt/4uzQ7HFmvE63iFHVLBJNDuyDOQgijDK/tXH/f6Msjg==} - engines: {node: '>=6.10'} - - ts-md5@2.0.1: - resolution: {integrity: sha512-yF35FCoEOFBzOclSkMNEUbFQZuv89KEQ+5Xz03HrMSGUGB1+r+El+JiGOFwsP4p9RFNzwlrydYoTLvPOuICl9w==} - engines: {node: '>=18'} - - ts-morph@26.0.0: - resolution: {integrity: sha512-ztMO++owQnz8c/gIENcM9XfCEzgoGphTv+nKpYNM1bgsdOVC/jRZuEBf6N+mLLDNg68Kl+GgUZfOySaRiG1/Ug==} - - tsconfig-paths@4.2.0: - resolution: {integrity: sha512-NoZ4roiN7LnbKn9QqE1amc9DJfzvZXxF4xDavcOWt1BPkdx+m+0gJuPM+S0vCe7zTJMYUP0R8pO2XMr+Y8oLIg==} - engines: {node: '>=6'} - - tslib@2.6.2: - resolution: {integrity: sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==} - - tslib@2.8.1: - resolution: {integrity: sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==} - - tunnel-rat@0.1.2: - resolution: {integrity: sha512-lR5VHmkPhzdhrM092lI2nACsLO4QubF0/yoOhzX7c+wIpbN1GjHNzCc91QlpxBi+cnx8vVJ+Ur6vL5cEoQPFpQ==} - - tw-animate-css@1.4.0: - resolution: {integrity: sha512-7bziOlRqH0hJx80h/3mbicLW7o8qLsH5+RaLR2t+OHM3D0JlWGODQKQ4cxbK7WlvmUxpcj6Kgu6EKqjrGFe3QQ==} - - type-is@2.1.0: - resolution: {integrity: sha512-faYHw0anBbc/kWF3zFTEnxSFOAGUX9GFbOBthvDdLsIlEoWOFOtS0zgCiQYwIskL9iGXZL3kAXD8OoZ4GmMATA==} - engines: {node: '>= 18'} - - typescript@7.0.2: - resolution: {integrity: sha512-8FYau96o3NKOhbjKi/qNvG/W5jhzxkbdm5sj9AbZ/5T5sWqn3hJgLfGx27sRKZWTvyzCP8dLRBTf5tBTSRVUNA==} - engines: {node: '>=16.20.0'} - hasBin: true - - underscore@1.13.8: - resolution: {integrity: sha512-DXtD3ZtEQzc7M8m4cXotyHR+FAS18C64asBYY5vqZexfYryNNnDc02W4hKg3rdQuqOYas1jkseX0+nZXjTXnvQ==} - - undici-types@6.21.0: - resolution: {integrity: sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==} - - undici@7.29.0: - resolution: {integrity: sha512-IDxfleLmmbSskfWSUATiN1nfn2rDuvnMOqb5CWR92iIfojA0Ud+ulOAAEQ57LPr9rWmsreUyf5lwyao+7GNNVw==} - engines: {node: '>=20.18.1'} - - unicorn-magic@0.3.0: - resolution: {integrity: sha512-+QBBXBCvifc56fsbuxZQ6Sic3wqqc3WWaqxs58gvJrcOuN83HGTCwz3oS5phzU9LthRNE9VrJCFCLUgHeeFnfA==} - engines: {node: '>=18'} - - unified@11.0.5: - resolution: {integrity: sha512-xKvGhPWw3k84Qjh8bI3ZeJjqnyadK+GEFtazSfZv/rKeTkTjOJho6mFqh2SM96iIcZokxiOpg78GazTSg8+KHA==} - - unist-util-find-after@5.0.0: - resolution: {integrity: sha512-amQa0Ep2m6hE2g72AugUItjbuM8X8cGQnFoHk0pGfrFeT9GZhzN5SW8nRsiGKK7Aif4CrACPENkA6P/Lw6fHGQ==} - - unist-util-is@6.0.1: - resolution: {integrity: sha512-LsiILbtBETkDz8I9p1dQ0uyRUWuaQzd/cuEeS1hoRSyW5E5XGmTzlwY1OrNzzakGowI9Dr/I8HVaw4hTtnxy8g==} - - unist-util-position-from-estree@1.1.2: - resolution: {integrity: sha512-poZa0eXpS+/XpoQwGwl79UUdea4ol2ZuCYguVaJS4qzIOMDzbqz8a3erUCOmubSZkaOuGamb3tX790iwOIROww==} - - unist-util-position-from-estree@2.0.0: - resolution: {integrity: sha512-KaFVRjoqLyF6YXCbVLNad/eS4+OfPQQn2yOd7zF/h5T/CSL2v8NpN6a5TPvtbXthAGw5nG+PuTtq+DdIZr+cRQ==} - - unist-util-position@5.0.0: - resolution: {integrity: sha512-fucsC7HjXvkB5R3kTCO7kUjRdrS0BJt3M/FPxmHMBOm8JQi2BsHAHFsy27E0EolP8rp0NzXsJ+jNPyDWvOJZPA==} - - unist-util-remove-position@5.0.0: - resolution: {integrity: sha512-Hp5Kh3wLxv0PHj9m2yZhhLt58KzPtEYKQQ4yxfYFEO7EvHwzyDYnduhHnY1mDxoqr7VUwVuHXk9RXKIiYS1N8Q==} - - unist-util-stringify-position@4.0.0: - resolution: {integrity: sha512-0ASV06AAoKCDkS2+xw5RXJywruurpbC4JZSm7nr7MOt1ojAzvyyaO+UxZf18j8FCF6kmzCZKcAgN/yu2gm2XgQ==} - - unist-util-visit-parents@6.0.2: - resolution: {integrity: sha512-goh1s1TBrqSqukSc8wrjwWhL0hiJxgA8m4kFxGlQ+8FYQ3C/m11FcTs4YYem7V664AhHVvgoQLk890Ssdsr2IQ==} - - unist-util-visit@5.1.0: - resolution: {integrity: sha512-m+vIdyeCOpdr/QeQCu2EzxX/ohgS8KbnPDgFni4dQsfSCtpz8UqDyY5GjRru8PDKuYn7Fq19j1CQ+nJSsGKOzg==} - - universalify@2.0.1: - resolution: {integrity: sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==} - engines: {node: '>= 10.0.0'} - - unpipe@1.0.0: - resolution: {integrity: sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==} - engines: {node: '>= 0.8'} - - update-browserslist-db@1.3.1: - resolution: {integrity: sha512-ZZ61DsRsOnakl74HAmp3oSN4aXUmEWXf+i/yv0h7tIBfICc3VdrFErQKUUKPgu3AMsTUMbcongALEN4l6GSUrQ==} - hasBin: true - peerDependencies: - browserslist: '>= 4.21.0' - - url-join@5.0.0: - resolution: {integrity: sha512-n2huDr9h9yzd6exQVnH/jU5mr+Pfx08LRXXZhkLLetAMESRj+anQsTAh940iMrIetKAmry9coFuZQ2jY8/p3WA==} - engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} - - use-callback-ref@1.3.3: - resolution: {integrity: sha512-jQL3lRnocaFtu3V00JToYz/4QkNWswxijDaCVNZRiRTO3HQDLsdu1ZtmIUvV4yPp+rvWm5j0y0TG/S61cuijTg==} - engines: {node: '>=10'} - peerDependencies: - '@types/react': '*' - react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc - peerDependenciesMeta: - '@types/react': - optional: true - - use-merge-value@1.2.0: - resolution: {integrity: sha512-DXgG0kkgJN45TcyoXL49vJnn55LehnrmoHc7MbKi+QDBvr8dsesqws8UlyIWGHMR+JXgxc1nvY+jDGMlycsUcw==} - peerDependencies: - react: '>= 16.x' - - use-sidecar@1.1.3: - resolution: {integrity: sha512-Fedw0aZvkhynoPYlA5WXrMCAMm+nSWdZt6lzJQ7Ok8S6Q+VsHmHpRWndVRJ8Be0ZbkfPc5LRYH+5XrzXcEeLRQ==} - engines: {node: '>=10'} - peerDependencies: - '@types/react': '*' - react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc - peerDependenciesMeta: - '@types/react': - optional: true - - use-sync-external-store@1.6.0: - resolution: {integrity: sha512-Pp6GSwGP/NrPIrxVFAIkOQeyw8lFenOHijQWkUTrDvrF4ALqylP2C/KCkeS9dpUM3KvYRQhna5vt7IL95+ZQ9w==} - peerDependencies: - react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 - - util-deprecate@1.0.2: - resolution: {integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==} - - utility-types@3.11.0: - resolution: {integrity: sha512-6Z7Ma2aVEWisaL6TvBCy7P8rm2LQoPv6dJ7ecIaIixHcwfbJ0x7mWdbcwlIM5IGQxPZSFYeqRCqlOOeKoJYMkw==} - engines: {node: '>= 4'} - - uuid@14.0.1: - resolution: {integrity: sha512-6ZxzVpzDXDa3bJWaHilVayA+BH/1zmxCJoVgvmqJnid/gPoKHxUrS/aC/T6LGQtNHT+XHG9fXPJB4d+IrU30Ew==} - hasBin: true - - v8n@1.5.1: - resolution: {integrity: sha512-LdabyT4OffkyXFCe9UT+uMkxNBs5rcTVuZClvxQr08D5TUgo1OFKkoT65qYRCsiKBl/usHjpXvP4hHMzzDRj3A==} - - validate-npm-package-name@7.0.2: - resolution: {integrity: sha512-hVDIBwsRruT73PbK7uP5ebUt+ezEtCmzZz3F59BSr2F6OVFnJ/6h8liuvdLrQ88Xmnk6/+xGGuq+pG9WwTuy3A==} - engines: {node: ^20.17.0 || >=22.9.0} - - vary@1.1.2: - resolution: {integrity: sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==} - engines: {node: '>= 0.8'} - - vfile-location@5.0.3: - resolution: {integrity: sha512-5yXvWDEgqeiYiBe1lbxYF7UMAIm/IcopxMHrMQDq3nvKcjPKIhZklUKL+AE7J7uApI4kwe2snsK+eI6UTj9EHg==} - - vfile-message@4.0.3: - resolution: {integrity: sha512-QTHzsGd1EhbZs4AsQ20JX1rC3cOlt/IWJruk893DfLRr57lcnOeMaWG4K0JrRta4mIJZKth2Au3mM3u03/JWKw==} - - vfile@6.0.3: - resolution: {integrity: sha512-KzIbH/9tXat2u30jf+smMwFCsno4wHVdNmzFyL+T/L3UGqqk6JKfVqOFOZEpZSHADH1k40ab6NUIXZq422ov3Q==} - - victory-vendor@37.3.6: - resolution: {integrity: sha512-SbPDPdDBYp+5MJHhBCAyI7wKM3d5ivekigc2Dk2s7pgbZ9wIgIBYGVw4zGHBml/qTFbexrofXW6Gu4noGxrOwQ==} - - virtua@0.49.3: - resolution: {integrity: sha512-k1Yn988Vz/L40uDtEWPjfdVo15Suumh4tU4/z5Srs0elNcU9DgBskqdh3llpHyAlXrCeHWTfcclYvY1uU4MmIg==} - peerDependencies: - react: '>=16.14.0' - react-dom: '>=16.14.0' - solid-js: '>=1.0' - svelte: '>=5.0' - vue: '>=3.2' - peerDependenciesMeta: - react: - optional: true - react-dom: - optional: true - solid-js: - optional: true - svelte: - optional: true - vue: - optional: true - - vite@8.2.1: - resolution: {integrity: sha512-EU/eS7BH3XROHh2YnBefjM6DBKA6ZeMZEYQbj7NLWg5wHYlhB8B/Mayd5XsgWq+NFYccDOTemRpdETWR6Ka/lw==} - engines: {node: ^20.19.0 || >=22.12.0} - hasBin: true - peerDependencies: - '@types/node': ^20.19.0 || >=22.12.0 - '@vitejs/devtools': ^0.4.0 - esbuild: ^0.27.0 || ^0.28.0 - jiti: '>=1.21.0' - less: ^4.0.0 - sass: ^1.70.0 - sass-embedded: ^1.70.0 - stylus: '>=0.54.8' - sugarss: ^5.0.0 - terser: ^5.16.0 - tsx: ^4.8.1 - yaml: ^2.4.2 - peerDependenciesMeta: - '@types/node': - optional: true - '@vitejs/devtools': - optional: true - esbuild: - optional: true - jiti: - optional: true - less: - optional: true - sass: - optional: true - sass-embedded: - optional: true - stylus: - optional: true - sugarss: - optional: true - terser: - optional: true - tsx: - optional: true - yaml: - optional: true - - vscode-jsonrpc@8.2.0: - resolution: {integrity: sha512-C+r0eKJUIfiDIfwJhria30+TYWPtuHJXHtI7J0YlOmKAo7ogxP20T0zxB7HZQIFhIyvoBPwWskjxrvAtfjyZfA==} - engines: {node: '>=14.0.0'} - - vscode-languageserver-protocol@3.17.5: - resolution: {integrity: sha512-mb1bvRJN8SVznADSGWM9u/b07H7Ecg0I3OgXDuLdn307rl/J3A9YD6/eYOssqhecL27hK1IPZAsaqh00i/Jljg==} - - vscode-languageserver-textdocument@1.0.12: - resolution: {integrity: sha512-cxWNPesCnQCcMPeenjKKsOCKQZ/L6Tv19DTRIGuLWe32lyzWhihGVJ/rcckZXJxfdKCFvRLS3fpBIsV/ZGX4zA==} - - vscode-languageserver-types@3.17.5: - resolution: {integrity: sha512-Ld1VelNuX9pdF39h2Hgaeb5hEZM2Z3jUrrMgWQAu82jMtZp7p3vJT3BzToKtZI7NgQssZje5o0zryOrhQvzQAg==} - - vscode-languageserver@9.0.1: - resolution: {integrity: sha512-woByF3PDpkHFUreUa7Hos7+pUWdeWMXRd26+ZX2A8cFx6v/JPTtd4/uN0/jB6XQHYaOlHbio03NTHCqrgG5n7g==} - hasBin: true - - vscode-uri@3.0.8: - resolution: {integrity: sha512-AyFQ0EVmsOZOlAnxoFOGOq1SQDWAB7C6aqMGS23svWAllfOaxbuFvcT8D1i8z3Gyn8fraVeZNNmN6e9bxxXkKw==} - - w3c-keyname@2.2.8: - resolution: {integrity: sha512-dpojBhNsCNN7T82Tm7k26A6G9ML3NkhDsnw9n/eoxSRlVBB4CEtIQ/KTCLI2Fwf3ataSXRhYFkQi3SlnFwPvPQ==} - - web-namespaces@2.0.1: - resolution: {integrity: sha512-bKr1DkiNa2krS7qxNtdrtHAmzuYGFQLiQ13TsorsdT6ULTkPLKuu5+GsFpDlg6JFjUTwX2DyhMPG2be8uPrqsQ==} - - webgl-constants@1.1.1: - resolution: {integrity: sha512-LkBXKjU5r9vAW7Gcu3T5u+5cvSvh5WwINdr0C+9jpzVB41cjQAP5ePArDtk/WHYdVj0GefCgM73BA7FlIiNtdg==} - - webgl-sdf-generator@1.1.1: - resolution: {integrity: sha512-9Z0JcMTFxeE+b2x1LJTdnaT8rT8aEp7MVxkNwoycNmJWwPdzoXzMh0BjJSh/AEFP+KPYZUli814h8bJZFIZ2jA==} - - webworkify@1.5.0: - resolution: {integrity: sha512-AMcUeyXAhbACL8S2hqqdqOLqvJ8ylmIbNwUIqQujRSouf4+eUFaXbG6F1Rbu+srlJMmxQWsiU7mOJi0nMBfM1g==} - - which@2.0.2: - resolution: {integrity: sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==} - engines: {node: '>= 8'} - hasBin: true - - which@4.0.0: - resolution: {integrity: sha512-GlaYyEb07DPxYCKhKzplCWBJtvxZcZMrL+4UkrTSJHHPyZU4mYYTv3qaOe77H7EODLSSopAUFAc6W8U4yqvscg==} - engines: {node: ^16.13.0 || >=18.0.0} - hasBin: true - - wrappy@1.0.2: - resolution: {integrity: sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==} - - wsl-utils@1.0.0: - resolution: {integrity: sha512-Hl0ZOAs672vg+06kfujwRhoS6/jehvULrlFkuF2dRu6pHgA8U06h3xqNIqNNU1LTXPcedxByAR4GS6pwQK0mgA==} - engines: {node: '>=20'} - - xmlbuilder@10.1.1: - resolution: {integrity: sha512-OyzrcFLL/nb6fMGHbiRDuPup9ljBycsdCypwuyg5AAHvyWzGfChJpCXMG88AGTIMFhGZ9RccFN1e6lhg3hkwKg==} - engines: {node: '>=4.0'} - - yallist@3.1.1: - resolution: {integrity: sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==} - - yaml@1.10.3: - resolution: {integrity: sha512-vIYeF1u3CjlhAFekPPAk2h/Kv4T3mAkMox5OymRiJQB0spDP10LHvt+K7G9Ny6NuuMAb25/6n1qyUjAcGNf/AA==} - engines: {node: '>= 6'} - - yocto-spinner@1.2.2: - resolution: {integrity: sha512-DODGl1wJjA/s5pnJFKau9lIYHT81lnhob1i3e1TjxZRxEhWRKl74nTbWE6H5KlkViQQTo/Z29YFdxzTZAMY3ng==} - engines: {node: '>=18.19'} - - yoctocolors@2.2.0: - resolution: {integrity: sha512-xYqdZFUK/VYazNl/oCDYN+3WloWQwMfZxBoiNt6qNyk+xfOdi598muWE42rNZFp1kNOiqW936q5RhUdnpqElSg==} - engines: {node: '>=18'} - - zod-to-json-schema@3.25.2: - resolution: {integrity: sha512-O/PgfnpT1xKSDeQYSCfRI5Gy3hPf91mKVDuYLUHZJMiDFptvP41MSnWofm8dnCm0256ZNfZIM7DSzuSMAFnjHA==} - peerDependencies: - zod: ^3.25.28 || ^4 - - zod@3.25.76: - resolution: {integrity: sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==} - - zustand@3.6.9: - resolution: {integrity: sha512-OvDNu/jEWpRnEC7k8xh8GKjqYog7td6FZrLMuHs/IeI8WhrCwV+FngVuwMIFhp5kysZXr6emaeReMqjLGaldAQ==} - engines: {node: '>=12.7.0'} - peerDependencies: - react: '>=16.8' - peerDependenciesMeta: - react: - optional: true - - zustand@4.5.7: - resolution: {integrity: sha512-CHOUy7mu3lbD6o6LJLfllpjkzhHXSBlX8B9+qPddUsIfeF5S/UZ5q0kmCsnRqT1UHFQZchNFDDzMbQsuesHWlw==} - engines: {node: '>=12.7.0'} - peerDependencies: - '@types/react': '>=16.8' - immer: '>=9.0.6' - react: '>=16.8' - peerDependenciesMeta: - '@types/react': - optional: true - immer: - optional: true - react: - optional: true - - zustand@5.0.15: - resolution: {integrity: sha512-MpSEjRiBkA9crSYeOUH32rJC7SVqAbm0Fqcqge/bUi2PPoLcBWKOsG+C8mevmpr8TwXHBVkChbbJiyvkE+i/3A==} - engines: {node: '>=12.20.0'} - peerDependencies: - '@types/react': '>=18.0.0' - immer: '>=9.0.6' - react: '>=18.0.0' - use-sync-external-store: '>=1.2.0' - peerDependenciesMeta: - '@types/react': - optional: true - immer: - optional: true - react: - optional: true - use-sync-external-store: - optional: true - - zwitch@2.0.4: - resolution: {integrity: sha512-bXE4cR/kVZhKZX/RjPEflHaKVhUVl85noU3v6b8apfQEc1x4A+zBxjZ4lN8LqGd6WZ3dl98pY4o717VFmoPp+A==} - -snapshots: - - '@alloc/quick-lru@5.2.0': {} - - '@ant-design/colors@8.0.1': - dependencies: - '@ant-design/fast-color': 3.0.1 - - '@ant-design/cssinjs-utils@2.1.2(react-dom@19.2.7(react@19.2.7))(react@19.2.7)': - dependencies: - '@ant-design/cssinjs': 2.1.2(react-dom@19.2.7(react@19.2.7))(react@19.2.7) - '@babel/runtime': 7.29.7 - '@rc-component/util': 1.12.0(react-dom@19.2.7(react@19.2.7))(react@19.2.7) - react: 19.2.7 - react-dom: 19.2.7(react@19.2.7) - - '@ant-design/cssinjs@2.1.2(react-dom@19.2.7(react@19.2.7))(react@19.2.7)': - dependencies: - '@babel/runtime': 7.29.7 - '@emotion/hash': 0.8.0 - '@emotion/unitless': 0.7.5 - '@rc-component/util': 1.12.0(react-dom@19.2.7(react@19.2.7))(react@19.2.7) - clsx: 2.1.1 - csstype: 3.2.3 - react: 19.2.7 - react-dom: 19.2.7(react@19.2.7) - stylis: 4.4.0 - - '@ant-design/fast-color@3.0.1': {} - - '@ant-design/icons-svg@4.5.0': {} - - '@ant-design/icons@6.3.2(react-dom@19.2.7(react@19.2.7))(react@19.2.7)': - dependencies: - '@ant-design/colors': 8.0.1 - '@ant-design/icons-svg': 4.5.0 - '@rc-component/util': 1.12.0(react-dom@19.2.7(react@19.2.7))(react@19.2.7) - clsx: 2.1.1 - react: 19.2.7 - react-dom: 19.2.7(react@19.2.7) - - '@ant-design/react-slick@2.0.0(react-dom@19.2.7(react@19.2.7))(react@19.2.7)': - dependencies: - '@babel/runtime': 7.29.7 - clsx: 2.1.1 - json2mq: 0.2.0 - react: 19.2.7 - react-dom: 19.2.7(react@19.2.7) - throttle-debounce: 5.0.2 - - '@ant-design/x@2.9.0(antd@6.6.0(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(react-dom@19.2.7(react@19.2.7))(react@19.2.7)': - dependencies: - '@ant-design/colors': 8.0.1 - '@ant-design/cssinjs': 2.1.2(react-dom@19.2.7(react@19.2.7))(react@19.2.7) - '@ant-design/cssinjs-utils': 2.1.2(react-dom@19.2.7(react@19.2.7))(react@19.2.7) - '@ant-design/fast-color': 3.0.1 - '@ant-design/icons': 6.3.2(react-dom@19.2.7(react@19.2.7))(react@19.2.7) - '@babel/runtime': 7.29.7 - '@rc-component/motion': 1.3.3(react-dom@19.2.7(react@19.2.7))(react@19.2.7) - '@rc-component/resize-observer': 1.1.2(react-dom@19.2.7(react@19.2.7))(react@19.2.7) - '@rc-component/util': 1.12.0(react-dom@19.2.7(react@19.2.7))(react@19.2.7) - antd: 6.6.0(react-dom@19.2.7(react@19.2.7))(react@19.2.7) - clsx: 2.1.1 - lodash.throttle: 4.1.1 - mermaid: 11.16.1 - react: 19.2.7 - react-dom: 19.2.7(react@19.2.7) - react-syntax-highlighter: 16.1.1(react@19.2.7) - - '@antfu/install-pkg@1.1.0': - dependencies: - package-manager-detector: 1.8.0 - tinyexec: 1.3.0 - - '@babel/code-frame@7.29.7': - dependencies: - '@babel/helper-validator-identifier': 7.29.7 - js-tokens: 4.0.0 - picocolors: 1.1.1 - - '@babel/compat-data@7.29.7': {} - - '@babel/core@7.29.7': - dependencies: - '@babel/code-frame': 7.29.7 - '@babel/generator': 7.29.8 - '@babel/helper-compilation-targets': 7.29.7 - '@babel/helper-module-transforms': 7.29.7(@babel/core@7.29.7) - '@babel/helpers': 7.29.7 - '@babel/parser': 7.29.8 - '@babel/template': 7.29.7 - '@babel/traverse': 7.29.8 - '@babel/types': 7.29.8 - '@jridgewell/remapping': 2.3.5 - convert-source-map: 2.0.0 - debug: 4.4.3 - gensync: 1.0.0-beta.2 - json5: 2.2.3 - semver: 6.3.1 - transitivePeerDependencies: - - supports-color - - '@babel/generator@7.29.8': - dependencies: - '@babel/parser': 7.29.8 - '@babel/types': 7.29.8 - '@jridgewell/gen-mapping': 0.3.13 - '@jridgewell/trace-mapping': 0.3.31 - jsesc: 3.1.0 - - '@babel/helper-annotate-as-pure@7.29.7': - dependencies: - '@babel/types': 7.29.8 - - '@babel/helper-compilation-targets@7.29.7': - dependencies: - '@babel/compat-data': 7.29.7 - '@babel/helper-validator-option': 7.29.7 - browserslist: 4.28.8 - lru-cache: 5.1.1 - semver: 6.3.1 - - '@babel/helper-create-class-features-plugin@7.29.7(@babel/core@7.29.7)': - dependencies: - '@babel/core': 7.29.7 - '@babel/helper-annotate-as-pure': 7.29.7 - '@babel/helper-member-expression-to-functions': 7.29.7 - '@babel/helper-optimise-call-expression': 7.29.7 - '@babel/helper-replace-supers': 7.29.7(@babel/core@7.29.7) - '@babel/helper-skip-transparent-expression-wrappers': 7.29.7 - '@babel/traverse': 7.29.8 - semver: 6.3.1 - transitivePeerDependencies: - - supports-color - - '@babel/helper-globals@7.29.7': {} - - '@babel/helper-member-expression-to-functions@7.29.7': - dependencies: - '@babel/traverse': 7.29.8 - '@babel/types': 7.29.8 - transitivePeerDependencies: - - supports-color - - '@babel/helper-module-imports@7.29.7': - dependencies: - '@babel/traverse': 7.29.8 - '@babel/types': 7.29.8 - transitivePeerDependencies: - - supports-color - - '@babel/helper-module-transforms@7.29.7(@babel/core@7.29.7)': - dependencies: - '@babel/core': 7.29.7 - '@babel/helper-module-imports': 7.29.7 - '@babel/helper-validator-identifier': 7.29.7 - '@babel/traverse': 7.29.8 - transitivePeerDependencies: - - supports-color - - '@babel/helper-optimise-call-expression@7.29.7': - dependencies: - '@babel/types': 7.29.8 - - '@babel/helper-plugin-utils@7.29.7': {} - - '@babel/helper-replace-supers@7.29.7(@babel/core@7.29.7)': - dependencies: - '@babel/core': 7.29.7 - '@babel/helper-member-expression-to-functions': 7.29.7 - '@babel/helper-optimise-call-expression': 7.29.7 - '@babel/traverse': 7.29.8 - transitivePeerDependencies: - - supports-color - - '@babel/helper-skip-transparent-expression-wrappers@7.29.7': - dependencies: - '@babel/traverse': 7.29.8 - '@babel/types': 7.29.8 - transitivePeerDependencies: - - supports-color - - '@babel/helper-string-parser@7.29.7': {} - - '@babel/helper-validator-identifier@7.29.7': {} - - '@babel/helper-validator-option@7.29.7': {} - - '@babel/helpers@7.29.7': - dependencies: - '@babel/template': 7.29.7 - '@babel/types': 7.29.8 - - '@babel/parser@7.29.8': - dependencies: - '@babel/types': 7.29.8 - - '@babel/plugin-syntax-jsx@7.29.7(@babel/core@7.29.7)': - dependencies: - '@babel/core': 7.29.7 - '@babel/helper-plugin-utils': 7.29.7 - - '@babel/plugin-syntax-typescript@7.29.7(@babel/core@7.29.7)': - dependencies: - '@babel/core': 7.29.7 - '@babel/helper-plugin-utils': 7.29.7 - - '@babel/plugin-transform-modules-commonjs@7.29.7(@babel/core@7.29.7)': - dependencies: - '@babel/core': 7.29.7 - '@babel/helper-module-transforms': 7.29.7(@babel/core@7.29.7) - '@babel/helper-plugin-utils': 7.29.7 - transitivePeerDependencies: - - supports-color - - '@babel/plugin-transform-typescript@7.29.7(@babel/core@7.29.7)': - dependencies: - '@babel/core': 7.29.7 - '@babel/helper-annotate-as-pure': 7.29.7 - '@babel/helper-create-class-features-plugin': 7.29.7(@babel/core@7.29.7) - '@babel/helper-plugin-utils': 7.29.7 - '@babel/helper-skip-transparent-expression-wrappers': 7.29.7 - '@babel/plugin-syntax-typescript': 7.29.7(@babel/core@7.29.7) - transitivePeerDependencies: - - supports-color - - '@babel/preset-typescript@7.29.7(@babel/core@7.29.7)': - dependencies: - '@babel/core': 7.29.7 - '@babel/helper-plugin-utils': 7.29.7 - '@babel/helper-validator-option': 7.29.7 - '@babel/plugin-syntax-jsx': 7.29.7(@babel/core@7.29.7) - '@babel/plugin-transform-modules-commonjs': 7.29.7(@babel/core@7.29.7) - '@babel/plugin-transform-typescript': 7.29.7(@babel/core@7.29.7) - transitivePeerDependencies: - - supports-color - - '@babel/runtime@7.29.7': {} - - '@babel/runtime@8.0.0': {} - - '@babel/template@7.29.7': - dependencies: - '@babel/code-frame': 7.29.7 - '@babel/parser': 7.29.8 - '@babel/types': 7.29.8 - - '@babel/traverse@7.29.8': - dependencies: - '@babel/code-frame': 7.29.7 - '@babel/generator': 7.29.8 - '@babel/helper-globals': 7.29.7 - '@babel/parser': 7.29.8 - '@babel/template': 7.29.7 - '@babel/types': 7.29.8 - debug: 4.4.3 - transitivePeerDependencies: - - supports-color - - '@babel/types@7.29.8': - dependencies: - '@babel/helper-string-parser': 7.29.7 - '@babel/helper-validator-identifier': 7.29.7 - - '@base-ui/react@1.6.0(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)': - dependencies: - '@babel/runtime': 7.29.7 - '@base-ui/utils': 0.3.1(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) - '@floating-ui/react-dom': 2.1.9(react-dom@19.2.7(react@19.2.7))(react@19.2.7) - '@floating-ui/utils': 0.2.12 - react: 19.2.7 - react-dom: 19.2.7(react@19.2.7) - use-sync-external-store: 1.6.0(react@19.2.7) - optionalDependencies: - '@types/react': 19.2.17 - - '@base-ui/utils@0.3.1(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)': - dependencies: - '@babel/runtime': 7.29.7 - '@floating-ui/utils': 0.2.12 - react: 19.2.7 - react-dom: 19.2.7(react@19.2.7) - reselect: 5.2.0 - use-sync-external-store: 1.6.0(react@19.2.7) - optionalDependencies: - '@types/react': 19.2.17 - - '@braintree/sanitize-url@6.0.2': {} - - '@braintree/sanitize-url@7.1.2': {} - - '@chevrotain/cst-dts-gen@11.0.3': - dependencies: - '@chevrotain/gast': 11.0.3 - '@chevrotain/types': 11.0.3 - lodash-es: 4.17.21 - - '@chevrotain/gast@11.0.3': - dependencies: - '@chevrotain/types': 11.0.3 - lodash-es: 4.17.21 - - '@chevrotain/regexp-to-ast@11.0.3': {} - - '@chevrotain/types@11.0.3': {} - - '@chevrotain/types@11.1.2': {} - - '@chevrotain/utils@11.0.3': {} - - '@codemirror/autocomplete@6.20.3': - dependencies: - '@codemirror/language': 6.12.4 - '@codemirror/state': 6.7.1 - '@codemirror/view': 6.43.8 - '@lezer/common': 1.5.2 - - '@codemirror/commands@6.10.4': - dependencies: - '@codemirror/language': 6.12.4 - '@codemirror/state': 6.7.1 - '@codemirror/view': 6.43.8 - '@lezer/common': 1.5.2 - - '@codemirror/lang-json@6.0.2': - dependencies: - '@codemirror/language': 6.12.4 - '@lezer/json': 1.0.3 - - '@codemirror/language@6.12.4': - dependencies: - '@codemirror/state': 6.7.1 - '@codemirror/view': 6.43.8 - '@lezer/common': 1.5.2 - '@lezer/highlight': 1.2.3 - '@lezer/lr': 1.4.10 - style-mod: 4.1.3 - - '@codemirror/lint@6.9.7': - dependencies: - '@codemirror/state': 6.7.1 - '@codemirror/view': 6.43.8 - crelt: 1.0.7 - - '@codemirror/search@6.7.1': - dependencies: - '@codemirror/state': 6.7.1 - '@codemirror/view': 6.43.8 - crelt: 1.0.7 - - '@codemirror/state@6.7.1': - dependencies: - '@marijn/find-cluster-break': 1.0.3 - - '@codemirror/theme-one-dark@6.1.3': - dependencies: - '@codemirror/language': 6.12.4 - '@codemirror/state': 6.7.1 - '@codemirror/view': 6.43.8 - '@lezer/highlight': 1.2.3 - - '@codemirror/view@6.43.8': - dependencies: - '@codemirror/state': 6.7.1 - crelt: 1.0.7 - style-mod: 4.1.3 - w3c-keyname: 2.2.8 - - '@dimforge/rapier3d-compat@0.12.0': {} - - '@dnd-kit/accessibility@3.1.1(react@19.2.7)': - dependencies: - react: 19.2.7 - tslib: 2.8.1 - - '@dnd-kit/core@6.3.1(react-dom@19.2.7(react@19.2.7))(react@19.2.7)': - dependencies: - '@dnd-kit/accessibility': 3.1.1(react@19.2.7) - '@dnd-kit/utilities': 3.2.2(react@19.2.7) - react: 19.2.7 - react-dom: 19.2.7(react@19.2.7) - tslib: 2.8.1 - - '@dnd-kit/modifiers@9.0.0(@dnd-kit/core@6.3.1(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(react@19.2.7)': - dependencies: - '@dnd-kit/core': 6.3.1(react-dom@19.2.7(react@19.2.7))(react@19.2.7) - '@dnd-kit/utilities': 3.2.2(react@19.2.7) - react: 19.2.7 - tslib: 2.8.1 - - '@dnd-kit/sortable@10.0.0(@dnd-kit/core@6.3.1(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(react@19.2.7)': - dependencies: - '@dnd-kit/core': 6.3.1(react-dom@19.2.7(react@19.2.7))(react@19.2.7) - '@dnd-kit/utilities': 3.2.2(react@19.2.7) - react: 19.2.7 - tslib: 2.8.1 - - '@dnd-kit/utilities@3.2.2(react@19.2.7)': - dependencies: - react: 19.2.7 - tslib: 2.8.1 - - '@dotenvx/dotenvx@1.75.1': - dependencies: - '@dotenvx/primitives': 0.8.0 - commander: 11.1.0 - conf: 10.2.0 - dotenv: 17.4.2 - enquirer: 2.4.1 - env-paths: 2.2.1 - execa: 5.1.1 - fdir: 6.5.0(picomatch@4.0.5) - ignore: 5.3.2 - object-treeify: 1.1.33 - open: 8.4.2 - picomatch: 4.0.5 - systeminformation: 5.33.1 - undici: 7.29.0 - which: 4.0.0 - yocto-spinner: 1.2.2 - - '@dotenvx/primitives@0.8.0': {} - - '@emoji-mart/data@1.2.1': {} - - '@emoji-mart/react@1.1.1(emoji-mart@5.6.0)(react@19.2.7)': - dependencies: - emoji-mart: 5.6.0 - react: 19.2.7 - - '@emotion/babel-plugin@11.13.5': - dependencies: - '@babel/helper-module-imports': 7.29.7 - '@babel/runtime': 7.29.7 - '@emotion/hash': 0.9.2 - '@emotion/memoize': 0.9.0 - '@emotion/serialize': 1.3.3 - babel-plugin-macros: 3.1.0 - convert-source-map: 1.9.0 - escape-string-regexp: 4.0.0 - find-root: 1.1.0 - source-map: 0.5.7 - stylis: 4.2.0 - transitivePeerDependencies: - - supports-color - - '@emotion/cache@11.14.0': - dependencies: - '@emotion/memoize': 0.9.0 - '@emotion/sheet': 1.4.0 - '@emotion/utils': 1.4.2 - '@emotion/weak-memoize': 0.4.0 - stylis: 4.2.0 - - '@emotion/css@11.13.5': - dependencies: - '@emotion/babel-plugin': 11.13.5 - '@emotion/cache': 11.14.0 - '@emotion/serialize': 1.3.3 - '@emotion/sheet': 1.4.0 - '@emotion/utils': 1.4.2 - transitivePeerDependencies: - - supports-color - - '@emotion/hash@0.8.0': {} - - '@emotion/hash@0.9.2': {} - - '@emotion/is-prop-valid@1.4.0': - dependencies: - '@emotion/memoize': 0.9.0 - - '@emotion/memoize@0.9.0': {} - - '@emotion/react@11.14.0(@types/react@19.2.17)(react@19.2.7)': - dependencies: - '@babel/runtime': 7.29.7 - '@emotion/babel-plugin': 11.13.5 - '@emotion/cache': 11.14.0 - '@emotion/serialize': 1.3.3 - '@emotion/use-insertion-effect-with-fallbacks': 1.2.0(react@19.2.7) - '@emotion/utils': 1.4.2 - '@emotion/weak-memoize': 0.4.0 - hoist-non-react-statics: 3.3.2 - react: 19.2.7 - optionalDependencies: - '@types/react': 19.2.17 - transitivePeerDependencies: - - supports-color - - '@emotion/serialize@1.3.3': - dependencies: - '@emotion/hash': 0.9.2 - '@emotion/memoize': 0.9.0 - '@emotion/unitless': 0.10.0 - '@emotion/utils': 1.4.2 - csstype: 3.2.3 - - '@emotion/sheet@1.4.0': {} - - '@emotion/unitless@0.10.0': {} - - '@emotion/unitless@0.7.5': {} - - '@emotion/use-insertion-effect-with-fallbacks@1.2.0(react@19.2.7)': - dependencies: - react: 19.2.7 - - '@emotion/utils@1.4.2': {} - - '@emotion/weak-memoize@0.4.0': {} - - '@excalidraw/excalidraw@0.18.1(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(immer@11.1.16)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)': - dependencies: - '@braintree/sanitize-url': 6.0.2 - '@excalidraw/laser-pointer': 1.3.1 - '@excalidraw/mermaid-to-excalidraw': 2.2.2 - '@excalidraw/random-username': 1.1.0 - '@radix-ui/react-popover': 1.1.6(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) - '@radix-ui/react-tabs': 1.0.2(react-dom@19.2.7(react@19.2.7))(react@19.2.7) - browser-fs-access: 0.29.1 - canvas-roundrect-polyfill: 0.0.1 - clsx: 1.1.1 - cross-env: 7.0.3 - es6-promise-pool: 2.5.0 - fractional-indexing: 3.2.0 - fuzzy: 0.1.3 - image-blob-reduce: 3.0.1 - jotai: 2.11.0(@types/react@19.2.17)(react@19.2.7) - jotai-scope: 0.7.2(jotai@2.11.0(@types/react@19.2.17)(react@19.2.7))(react@19.2.7) - lodash.debounce: 4.0.8 - lodash.throttle: 4.1.1 - nanoid: 3.3.3 - open-color: 1.9.1 - pako: 2.0.3 - perfect-freehand: 1.2.0 - pica: 7.1.1 - png-chunk-text: 1.0.0 - png-chunks-encode: 1.0.0 - png-chunks-extract: 1.0.0 - points-on-curve: 1.0.1 - pwacompat: 2.0.17 - react: 19.2.7 - react-dom: 19.2.7(react@19.2.7) - roughjs: 4.6.4 - sass: 1.51.0 - tunnel-rat: 0.1.2(@types/react@19.2.17)(immer@11.1.16)(react@19.2.7) - transitivePeerDependencies: - - '@types/react' - - '@types/react-dom' - - immer - - '@excalidraw/laser-pointer@1.3.1': {} - - '@excalidraw/markdown-to-text@0.1.2': {} - - '@excalidraw/mermaid-to-excalidraw@2.2.2': - dependencies: - '@excalidraw/markdown-to-text': 0.1.2 - '@mermaid-js/parser': 0.6.3 - mermaid: 11.16.1 - nanoid: 4.0.2 - - '@excalidraw/random-username@1.1.0': {} - - '@ffmpeg/core@0.12.10': {} - - '@ffmpeg/ffmpeg@0.12.15': - dependencies: - '@ffmpeg/types': 0.12.4 - - '@ffmpeg/types@0.12.4': {} - - '@ffmpeg/util@0.12.2': {} - - '@floating-ui/core@1.8.0': - dependencies: - '@floating-ui/utils': 0.2.12 - - '@floating-ui/dom@1.8.0': - dependencies: - '@floating-ui/core': 1.8.0 - '@floating-ui/utils': 0.2.12 - - '@floating-ui/react-dom@2.1.9(react-dom@19.2.7(react@19.2.7))(react@19.2.7)': - dependencies: - '@floating-ui/dom': 1.8.0 - react: 19.2.7 - react-dom: 19.2.7(react@19.2.7) - - '@floating-ui/react@0.27.20(react-dom@19.2.7(react@19.2.7))(react@19.2.7)': - dependencies: - '@floating-ui/react-dom': 2.1.9(react-dom@19.2.7(react@19.2.7))(react@19.2.7) - '@floating-ui/utils': 0.2.12 - react: 19.2.7 - react-dom: 19.2.7(react@19.2.7) - tabbable: 6.5.0 - - '@floating-ui/utils@0.2.12': {} - - '@fontsource-variable/inter@5.3.0': {} - - '@fontsource-variable/jetbrains-mono@5.3.0': {} - - '@giscus/react@3.1.0(react-dom@19.2.7(react@19.2.7))(react@19.2.7)': - dependencies: - giscus: 1.6.0 - react: 19.2.7 - react-dom: 19.2.7(react@19.2.7) - - '@hono/node-server@2.1.1(hono@4.13.2)': - dependencies: - hono: 4.13.2 - - '@iconify/types@2.0.0': {} - - '@iconify/utils@3.1.4': - dependencies: - '@antfu/install-pkg': 1.1.0 - '@iconify/types': 2.0.0 - import-meta-resolve: 4.2.0 - - '@internationalized/date@3.12.4': - dependencies: - '@swc/helpers': 0.5.23 - - '@internationalized/number@3.6.8': - dependencies: - '@swc/helpers': 0.5.23 - - '@internationalized/string@3.2.10': - dependencies: - '@swc/helpers': 0.5.23 - - '@jridgewell/gen-mapping@0.3.13': - dependencies: - '@jridgewell/sourcemap-codec': 1.5.5 - '@jridgewell/trace-mapping': 0.3.31 - - '@jridgewell/remapping@2.3.5': - dependencies: - '@jridgewell/gen-mapping': 0.3.13 - '@jridgewell/trace-mapping': 0.3.31 - - '@jridgewell/resolve-uri@3.1.2': {} - - '@jridgewell/sourcemap-codec@1.5.5': {} - - '@jridgewell/trace-mapping@0.3.31': - dependencies: - '@jridgewell/resolve-uri': 3.1.2 - '@jridgewell/sourcemap-codec': 1.5.5 - - '@leafer-in/interface@2.2.9(@leafer-ui/interface@2.2.9)(@leafer/interface@2.2.9)': - dependencies: - '@leafer-ui/interface': 2.2.9 - '@leafer/interface': 2.2.9 - - '@leafer-ui/app@2.2.9': - dependencies: - '@leafer-ui/data': 2.2.9 - '@leafer-ui/display': 2.2.9 - '@leafer/core': 2.2.9 - - '@leafer-ui/bounds@2.2.9': - dependencies: - '@leafer/core': 2.2.9 - - '@leafer-ui/color@2.2.9': - dependencies: - '@leafer-ui/draw': 2.2.9 - '@leafer/core': 2.2.9 - - '@leafer-ui/core@2.2.9': - dependencies: - '@leafer-ui/app': 2.2.9 - '@leafer-ui/draw': 2.2.9 - '@leafer-ui/event': 2.2.9 - '@leafer-ui/hit': 2.2.9 - '@leafer-ui/interaction': 2.2.9 - - '@leafer-ui/data@2.2.9': - dependencies: - '@leafer-ui/external': 2.2.9 - '@leafer/core': 2.2.9 - - '@leafer-ui/decorator@2.2.9': - dependencies: - '@leafer/core': 2.2.9 - - '@leafer-ui/display-module@2.2.9': - dependencies: - '@leafer-ui/bounds': 2.2.9 - '@leafer-ui/data': 2.2.9 - '@leafer-ui/render': 2.2.9 - - '@leafer-ui/display@2.2.9': - dependencies: - '@leafer-ui/data': 2.2.9 - '@leafer-ui/decorator': 2.2.9 - '@leafer-ui/display-module': 2.2.9 - '@leafer-ui/external': 2.2.9 - '@leafer/core': 2.2.9 - - '@leafer-ui/draw@2.2.9': - dependencies: - '@leafer-ui/decorator': 2.2.9 - '@leafer-ui/display': 2.2.9 - '@leafer-ui/display-module': 2.2.9 - '@leafer-ui/external': 2.2.9 - '@leafer/core': 2.2.9 - - '@leafer-ui/effect@2.2.9': - dependencies: - '@leafer-ui/draw': 2.2.9 - '@leafer/core': 2.2.9 - - '@leafer-ui/event@2.2.9': - dependencies: - '@leafer/core': 2.2.9 - - '@leafer-ui/external@2.2.9': - dependencies: - '@leafer/core': 2.2.9 - - '@leafer-ui/hit@2.2.9': - dependencies: - '@leafer-ui/draw': 2.2.9 - '@leafer/core': 2.2.9 - - '@leafer-ui/interaction-web@2.2.9': - dependencies: - '@leafer-ui/core': 2.2.9 - '@leafer/core': 2.2.9 - - '@leafer-ui/interaction@2.2.9': - dependencies: - '@leafer-ui/draw': 2.2.9 - '@leafer-ui/event': 2.2.9 - '@leafer/core': 2.2.9 - - '@leafer-ui/interface@2.2.9': - dependencies: - '@leafer/interface': 2.2.9 - - '@leafer-ui/paint@2.2.9': - dependencies: - '@leafer-ui/draw': 2.2.9 - '@leafer/core': 2.2.9 - - '@leafer-ui/partner@2.2.9': - dependencies: - '@leafer-ui/color': 2.2.9 - '@leafer-ui/draw': 2.2.9 - '@leafer-ui/effect': 2.2.9 - '@leafer-ui/paint': 2.2.9 - '@leafer-ui/text': 2.2.9 - - '@leafer-ui/render@2.2.9': - dependencies: - '@leafer-ui/external': 2.2.9 - - '@leafer-ui/text@2.2.9': - dependencies: - '@leafer/core': 2.2.9 - - '@leafer-ui/web@2.2.9': - dependencies: - '@leafer-in/interface': 2.2.9(@leafer-ui/interface@2.2.9)(@leafer/interface@2.2.9) - '@leafer-ui/core': 2.2.9 - '@leafer-ui/draw': 2.2.9 - '@leafer-ui/interaction-web': 2.2.9 - '@leafer-ui/interface': 2.2.9 - '@leafer-ui/partner': 2.2.9 - '@leafer/interface': 2.2.9 - '@leafer/partner': 2.2.9 - '@leafer/web-core': 2.2.9 - - '@leafer/canvas-web@2.2.9': - dependencies: - '@leafer/core': 2.2.9 - - '@leafer/canvas@2.2.9': - dependencies: - '@leafer/data': 2.2.9 - '@leafer/debug': 2.2.9 - '@leafer/file': 2.2.9 - '@leafer/list': 2.2.9 - '@leafer/math': 2.2.9 - '@leafer/path': 2.2.9 - '@leafer/platform': 2.2.9 - - '@leafer/core@2.2.9': - dependencies: - '@leafer/canvas': 2.2.9 - '@leafer/data': 2.2.9 - '@leafer/debug': 2.2.9 - '@leafer/decorator': 2.2.9 - '@leafer/display': 2.2.9 - '@leafer/display-module': 2.2.9 - '@leafer/event': 2.2.9 - '@leafer/file': 2.2.9 - '@leafer/helper': 2.2.9 - '@leafer/image': 2.2.9 - '@leafer/layout': 2.2.9 - '@leafer/list': 2.2.9 - '@leafer/math': 2.2.9 - '@leafer/path': 2.2.9 - '@leafer/platform': 2.2.9 - '@leafer/task': 2.2.9 - - '@leafer/data@2.2.9': {} - - '@leafer/debug@2.2.9': - dependencies: - '@leafer/data': 2.2.9 - '@leafer/math': 2.2.9 - - '@leafer/decorator@2.2.9': - dependencies: - '@leafer/data': 2.2.9 - '@leafer/debug': 2.2.9 - '@leafer/platform': 2.2.9 - - '@leafer/display-module@2.2.9': - dependencies: - '@leafer/data': 2.2.9 - '@leafer/debug': 2.2.9 - '@leafer/event': 2.2.9 - '@leafer/helper': 2.2.9 - '@leafer/math': 2.2.9 - - '@leafer/display@2.2.9': - dependencies: - '@leafer/data': 2.2.9 - '@leafer/debug': 2.2.9 - '@leafer/decorator': 2.2.9 - '@leafer/display-module': 2.2.9 - '@leafer/event': 2.2.9 - '@leafer/helper': 2.2.9 - '@leafer/image': 2.2.9 - '@leafer/layout': 2.2.9 - '@leafer/math': 2.2.9 - '@leafer/platform': 2.2.9 - - '@leafer/event@2.2.9': - dependencies: - '@leafer/data': 2.2.9 - '@leafer/decorator': 2.2.9 - '@leafer/math': 2.2.9 - '@leafer/platform': 2.2.9 - - '@leafer/file@2.2.9': - dependencies: - '@leafer/data': 2.2.9 - '@leafer/debug': 2.2.9 - '@leafer/platform': 2.2.9 - '@leafer/task': 2.2.9 - - '@leafer/helper@2.2.9': - dependencies: - '@leafer/data': 2.2.9 - '@leafer/math': 2.2.9 - '@leafer/platform': 2.2.9 - - '@leafer/image-web@2.2.9': - dependencies: - '@leafer/core': 2.2.9 - - '@leafer/image@2.2.9': - dependencies: - '@leafer/data': 2.2.9 - '@leafer/file': 2.2.9 - '@leafer/math': 2.2.9 - '@leafer/platform': 2.2.9 - '@leafer/task': 2.2.9 - - '@leafer/interface@2.2.9': {} - - '@leafer/layout@2.2.9': - dependencies: - '@leafer/data': 2.2.9 - '@leafer/helper': 2.2.9 - '@leafer/math': 2.2.9 - '@leafer/platform': 2.2.9 - - '@leafer/layouter@2.2.9': - dependencies: - '@leafer/core': 2.2.9 - - '@leafer/list@2.2.9': {} - - '@leafer/math@2.2.9': {} - - '@leafer/partner@2.2.9': - dependencies: - '@leafer/core': 2.2.9 - '@leafer/layouter': 2.2.9 - '@leafer/renderer': 2.2.9 - '@leafer/selector': 2.2.9 - '@leafer/watcher': 2.2.9 - - '@leafer/path@2.2.9': - dependencies: - '@leafer/data': 2.2.9 - '@leafer/debug': 2.2.9 - '@leafer/math': 2.2.9 - - '@leafer/platform@2.2.9': - dependencies: - '@leafer/data': 2.2.9 - '@leafer/debug': 2.2.9 - - '@leafer/renderer@2.2.9': - dependencies: - '@leafer/core': 2.2.9 - - '@leafer/selector@2.2.9': - dependencies: - '@leafer/core': 2.2.9 - - '@leafer/task@2.2.9': - dependencies: - '@leafer/debug': 2.2.9 - '@leafer/math': 2.2.9 - - '@leafer/watcher@2.2.9': - dependencies: - '@leafer/data': 2.2.9 - '@leafer/event': 2.2.9 - '@leafer/list': 2.2.9 - - '@leafer/web-core@2.2.9': - dependencies: - '@leafer/canvas-web': 2.2.9 - '@leafer/core': 2.2.9 - '@leafer/image-web': 2.2.9 - '@leafer/interface': 2.2.9 - '@leafer/partner': 2.2.9 - - '@lezer/common@1.5.2': {} - - '@lezer/highlight@1.2.3': - dependencies: - '@lezer/common': 1.5.2 - - '@lezer/json@1.0.3': - dependencies: - '@lezer/common': 1.5.2 - '@lezer/highlight': 1.2.3 - '@lezer/lr': 1.4.10 - - '@lezer/lr@1.4.10': - dependencies: - '@lezer/common': 1.5.2 - - '@lit-labs/ssr-dom-shim@1.6.0': {} - - '@lit/reactive-element@2.1.2': - dependencies: - '@lit-labs/ssr-dom-shim': 1.6.0 - - '@lobehub/emojilib@1.0.0': {} - - '@lobehub/fluent-emoji@4.1.0(@types/react@19.2.17)(antd@6.6.0(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(react-dom@19.2.7(react@19.2.7))(react@19.2.7)': - dependencies: - '@lobehub/emojilib': 1.0.0 - antd-style: 4.1.0(@types/react@19.2.17)(antd@6.6.0(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(react-dom@19.2.7(react@19.2.7))(react@19.2.7) - emoji-regex: 10.6.0 - es-toolkit: 1.50.0 - lucide-react: 0.562.0(react@19.2.7) - react: 19.2.7 - react-dom: 19.2.7(react@19.2.7) - url-join: 5.0.0 - transitivePeerDependencies: - - '@types/react' - - antd - - supports-color - - '@lobehub/icons@5.16.0(@lobehub/ui@5.32.3)(@types/react@19.2.17)(antd@6.6.0(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(react-dom@19.2.7(react@19.2.7))(react@19.2.7)': - dependencies: - '@lobehub/ui': 5.32.3(@lobehub/fluent-emoji@4.1.0(@types/react@19.2.17)(antd@6.6.0(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(@lobehub/icons@5.16.0)(@shikijs/themes@4.4.3)(@types/mdast@4.0.4)(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(antd@6.6.0(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(micromark-util-types@2.0.2)(micromark@4.0.2)(motion@12.43.0(@emotion/is-prop-valid@1.4.0)(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(react-dom@19.2.7(react@19.2.7))(react@19.2.7) - antd: 6.6.0(react-dom@19.2.7(react@19.2.7))(react@19.2.7) - antd-style: 4.1.0(@types/react@19.2.17)(antd@6.6.0(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(react-dom@19.2.7(react@19.2.7))(react@19.2.7) - es-toolkit: 1.50.0 - lucide-react: 0.469.0(react@19.2.7) - polished: 4.3.1 - react: 19.2.7 - react-dom: 19.2.7(react@19.2.7) - transitivePeerDependencies: - - '@types/react' - - supports-color - - '@lobehub/ui@5.32.3(@lobehub/fluent-emoji@4.1.0(@types/react@19.2.17)(antd@6.6.0(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(@lobehub/icons@5.16.0)(@shikijs/themes@4.4.3)(@types/mdast@4.0.4)(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(antd@6.6.0(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(micromark-util-types@2.0.2)(micromark@4.0.2)(motion@12.43.0(@emotion/is-prop-valid@1.4.0)(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(react-dom@19.2.7(react@19.2.7))(react@19.2.7)': - dependencies: - '@ant-design/cssinjs': 2.1.2(react-dom@19.2.7(react@19.2.7))(react@19.2.7) - '@base-ui/react': 1.6.0(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) - '@dnd-kit/core': 6.3.1(react-dom@19.2.7(react@19.2.7))(react@19.2.7) - '@dnd-kit/modifiers': 9.0.0(@dnd-kit/core@6.3.1(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(react@19.2.7) - '@dnd-kit/sortable': 10.0.0(@dnd-kit/core@6.3.1(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(react@19.2.7) - '@dnd-kit/utilities': 3.2.2(react@19.2.7) - '@emoji-mart/data': 1.2.1 - '@emoji-mart/react': 1.1.1(emoji-mart@5.6.0)(react@19.2.7) - '@emotion/is-prop-valid': 1.4.0 - '@floating-ui/react': 0.27.20(react-dom@19.2.7(react@19.2.7))(react@19.2.7) - '@giscus/react': 3.1.0(react-dom@19.2.7(react@19.2.7))(react@19.2.7) - '@lobehub/fluent-emoji': 4.1.0(@types/react@19.2.17)(antd@6.6.0(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(react-dom@19.2.7(react@19.2.7))(react@19.2.7) - '@lobehub/icons': 5.16.0(@lobehub/ui@5.32.3)(@types/react@19.2.17)(antd@6.6.0(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(react-dom@19.2.7(react@19.2.7))(react@19.2.7) - '@mdx-js/mdx': 3.1.1 - '@mdx-js/react': 3.1.1(@types/react@19.2.17)(react@19.2.7) - '@pierre/diffs': 1.2.12(@shikijs/themes@4.4.3)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) - '@radix-ui/react-slot': 1.3.3(@types/react@19.2.17)(react@19.2.7) - '@shikijs/core': 4.4.3 - '@shikijs/stream': 4.4.3(react@19.2.7) - '@shikijs/transformers': 4.4.3 - '@splinetool/runtime': 1.12.98 - ahooks: 3.9.7(react-dom@19.2.7(react@19.2.7))(react@19.2.7) - antd: 6.6.0(react-dom@19.2.7(react@19.2.7))(react@19.2.7) - antd-style: 4.1.0(@types/react@19.2.17)(antd@6.6.0(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(react-dom@19.2.7(react@19.2.7))(react@19.2.7) - beautiful-mermaid: 1.1.3 - chroma-js: 3.2.0 - class-variance-authority: 0.7.1 - clsx: 2.1.1 - dayjs: 1.11.21 - emoji-mart: 5.6.0 - es-toolkit: 1.50.0 - fast-deep-equal: 3.1.3 - hast-util-to-jsx-runtime: 2.3.6 - html-url-attributes: 3.0.1 - immer: 11.1.16 - katex: 0.18.4 - leva: 0.10.1(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) - lucide-react: 1.31.0(react@19.2.7) - marked: 18.0.10 - motion: 12.43.0(@emotion/is-prop-valid@1.4.0)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) - numeral: 2.0.6 - polished: 4.3.1 - query-string: 9.5.0 - rc-collapse: 4.0.0(react-dom@19.2.7(react@19.2.7))(react@19.2.7) - rc-footer: 0.6.8(react-dom@19.2.7(react@19.2.7))(react@19.2.7) - rc-image: 7.12.0(react-dom@19.2.7(react@19.2.7))(react@19.2.7) - rc-input-number: 9.5.0(react-dom@19.2.7(react@19.2.7))(react@19.2.7) - rc-menu: 9.16.1(react-dom@19.2.7(react@19.2.7))(react@19.2.7) - re-resizable: 6.11.2(react-dom@19.2.7(react@19.2.7))(react@19.2.7) - react: 19.2.7 - react-avatar-editor: 15.1.0(react-dom@19.2.7(react@19.2.7))(react@19.2.7) - react-dom: 19.2.7(react@19.2.7) - react-error-boundary: 6.1.3(@types/react@19.2.17)(react@19.2.7) - react-hotkeys-hook: 5.3.3(react-dom@19.2.7(react@19.2.7))(react@19.2.7) - react-markdown: 10.1.0(@types/react@19.2.17)(react@19.2.7) - react-merge-refs: 3.0.2(react@19.2.7) - react-rnd: 10.5.3(react-dom@19.2.7(react@19.2.7))(react@19.2.7) - react-zoom-pan-pinch: 4.0.4(react-dom@19.2.7(react@19.2.7))(react@19.2.7) - rehype-github-alerts: 4.2.0 - rehype-katex: 7.0.1 - rehype-raw: 7.0.0 - remark-breaks: 4.0.0 - remark-cjk-friendly: 2.3.1(@types/mdast@4.0.4)(micromark-util-types@2.0.2)(micromark@4.0.2)(unified@11.0.5) - remark-gfm: 4.0.1 - remark-github: 12.0.0 - remark-math: 6.0.0 - remark-parse: 11.0.0 - remark-rehype: 11.1.2 - remend: 1.3.0 - shiki: 4.4.3 - swr: 2.5.1(react@19.2.7) - ts-md5: 2.0.1 - unified: 11.0.5 - url-join: 5.0.0 - use-merge-value: 1.2.0(react@19.2.7) - uuid: 14.0.1 - vfile: 6.0.3 - virtua: 0.49.3(react-dom@19.2.7(react@19.2.7))(react@19.2.7) - transitivePeerDependencies: - - '@date-fns/tz' - - '@shikijs/themes' - - '@types/mdast' - - '@types/react' - - '@types/react-dom' - - date-fns - - micromark - - micromark-util-types - - solid-js - - supports-color - - svelte - - vue - - '@marijn/find-cluster-break@1.0.3': {} - - '@mdx-js/mdx@3.1.1': - dependencies: - '@types/estree': 1.0.9 - '@types/estree-jsx': 1.0.5 - '@types/hast': 3.0.5 - '@types/mdx': 2.0.14 - acorn: 8.18.0 - collapse-white-space: 2.1.0 - devlop: 1.1.0 - estree-util-is-identifier-name: 3.0.0 - estree-util-scope: 1.0.0 - estree-walker: 3.0.3 - hast-util-to-jsx-runtime: 2.3.6 - markdown-extensions: 2.0.0 - recma-build-jsx: 1.0.0 - recma-jsx: 1.0.1(acorn@8.18.0) - recma-stringify: 1.0.0 - rehype-recma: 1.0.0 - remark-mdx: 3.0.0 - remark-parse: 11.0.0 - remark-rehype: 11.1.2 - source-map: 0.7.0 - unified: 11.0.5 - unist-util-position-from-estree: 2.0.0 - unist-util-stringify-position: 4.0.0 - unist-util-visit: 5.1.0 - vfile: 6.0.3 - transitivePeerDependencies: - - supports-color - - '@mdx-js/react@3.1.1(@types/react@19.2.17)(react@19.2.7)': - dependencies: - '@types/mdx': 2.0.14 - '@types/react': 19.2.17 - react: 19.2.7 - - '@mediapipe/tasks-vision@0.10.17': {} - - '@mediapipe/tasks-vision@1.0.1': {} - - '@mermaid-js/parser@0.6.3': - dependencies: - langium: 3.3.1 - - '@mermaid-js/parser@1.2.0': - dependencies: - '@chevrotain/types': 11.1.2 - - '@modelcontextprotocol/sdk@1.30.0(zod@3.25.76)': - dependencies: - '@hono/node-server': 2.1.1(hono@4.13.2) - ajv: 8.20.0 - ajv-formats: 3.0.1(ajv@8.20.0) - content-type: 1.0.5 - cors: 2.8.6 - cross-spawn: 7.0.6 - eventsource: 3.0.7 - eventsource-parser: 3.1.1 - express: 5.2.1 - express-rate-limit: 8.6.2(express@5.2.1) - hono: 4.13.2 - jose: 6.2.8 - json-schema-typed: 8.0.2 - pkce-challenge: 5.0.1 - raw-body: 3.0.2 - zod: 3.25.76 - zod-to-json-schema: 3.25.2(zod@3.25.76) - transitivePeerDependencies: - - supports-color - - '@monogrid/gainmap-js@3.4.0(three@0.185.1)': - dependencies: - promise-worker-transferable: 1.0.4 - three: 0.185.1 - - '@nodelib/fs.scandir@2.1.5': - dependencies: - '@nodelib/fs.stat': 2.0.5 - run-parallel: 1.2.0 - - '@nodelib/fs.stat@2.0.5': {} - - '@nodelib/fs.walk@1.2.8': - dependencies: - '@nodelib/fs.scandir': 2.1.5 - fastq: 1.20.1 - - '@oxc-project/types@0.144.0': {} - - '@pierre/diffs@1.2.12(@shikijs/themes@4.4.3)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)': - dependencies: - '@pierre/theme': 1.1.0 - '@pierre/theming': 0.0.2(@pierre/theme@1.1.0)(@shikijs/themes@4.4.3)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)(shiki@4.4.3) - '@shikijs/transformers': 4.4.3 - diff: 9.0.0 - hast-util-to-html: 9.0.5 - lru_map: 0.4.1 - react: 19.2.7 - react-dom: 19.2.7(react@19.2.7) - shiki: 4.4.3 - transitivePeerDependencies: - - '@shikijs/themes' - - '@pierre/theme@1.1.0': {} - - '@pierre/theming@0.0.2(@pierre/theme@1.1.0)(@shikijs/themes@4.4.3)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)(shiki@4.4.3)': - optionalDependencies: - '@pierre/theme': 1.1.0 - '@shikijs/themes': 4.4.3 - react: 19.2.7 - react-dom: 19.2.7(react@19.2.7) - shiki: 4.4.3 - - '@primer/octicons@19.33.0': - dependencies: - object-assign: 4.1.1 - - '@radix-ui/number@1.1.3': {} - - '@radix-ui/primitive@1.0.0': - dependencies: - '@babel/runtime': 7.29.7 - - '@radix-ui/primitive@1.1.1': {} - - '@radix-ui/primitive@1.1.7': {} - - '@radix-ui/react-accessible-icon@1.1.15(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)': - dependencies: - '@radix-ui/react-visually-hidden': 1.2.11(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) - react: 19.2.7 - react-dom: 19.2.7(react@19.2.7) - optionalDependencies: - '@types/react': 19.2.17 - '@types/react-dom': 19.2.3(@types/react@19.2.17) - - '@radix-ui/react-accordion@1.2.20(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)': - dependencies: - '@radix-ui/primitive': 1.1.7 - '@radix-ui/react-collapsible': 1.1.20(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) - '@radix-ui/react-collection': 1.1.15(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) - '@radix-ui/react-compose-refs': 1.1.5(@types/react@19.2.17)(react@19.2.7) - '@radix-ui/react-context': 1.2.2(@types/react@19.2.17)(react@19.2.7) - '@radix-ui/react-direction': 1.1.4(@types/react@19.2.17)(react@19.2.7) - '@radix-ui/react-id': 1.1.4(@types/react@19.2.17)(react@19.2.7) - '@radix-ui/react-primitive': 2.1.10(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) - '@radix-ui/react-use-controllable-state': 1.2.6(@types/react@19.2.17)(react@19.2.7) - react: 19.2.7 - react-dom: 19.2.7(react@19.2.7) - optionalDependencies: - '@types/react': 19.2.17 - '@types/react-dom': 19.2.3(@types/react@19.2.17) - - '@radix-ui/react-alert-dialog@1.1.23(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)': - dependencies: - '@radix-ui/primitive': 1.1.7 - '@radix-ui/react-compose-refs': 1.1.5(@types/react@19.2.17)(react@19.2.7) - '@radix-ui/react-context': 1.2.2(@types/react@19.2.17)(react@19.2.7) - '@radix-ui/react-dialog': 1.1.23(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) - '@radix-ui/react-primitive': 2.1.10(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) - react: 19.2.7 - react-dom: 19.2.7(react@19.2.7) - optionalDependencies: - '@types/react': 19.2.17 - '@types/react-dom': 19.2.3(@types/react@19.2.17) - - '@radix-ui/react-arrow@1.1.15(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)': - dependencies: - '@radix-ui/react-primitive': 2.1.10(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) - react: 19.2.7 - react-dom: 19.2.7(react@19.2.7) - optionalDependencies: - '@types/react': 19.2.17 - '@types/react-dom': 19.2.3(@types/react@19.2.17) - - '@radix-ui/react-arrow@1.1.2(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)': - dependencies: - '@radix-ui/react-primitive': 2.0.2(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) - react: 19.2.7 - react-dom: 19.2.7(react@19.2.7) - optionalDependencies: - '@types/react': 19.2.17 - '@types/react-dom': 19.2.3(@types/react@19.2.17) - - '@radix-ui/react-aspect-ratio@1.1.15(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)': - dependencies: - '@radix-ui/react-primitive': 2.1.10(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) - react: 19.2.7 - react-dom: 19.2.7(react@19.2.7) - optionalDependencies: - '@types/react': 19.2.17 - '@types/react-dom': 19.2.3(@types/react@19.2.17) - - '@radix-ui/react-avatar@1.2.6(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)': - dependencies: - '@radix-ui/primitive': 1.1.7 - '@radix-ui/react-context': 1.2.2(@types/react@19.2.17)(react@19.2.7) - '@radix-ui/react-primitive': 2.1.10(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) - '@radix-ui/react-use-callback-ref': 1.1.4(@types/react@19.2.17)(react@19.2.7) - '@radix-ui/react-use-is-hydrated': 0.1.3(@types/react@19.2.17)(react@19.2.7) - '@radix-ui/react-use-layout-effect': 1.1.4(@types/react@19.2.17)(react@19.2.7) - react: 19.2.7 - react-dom: 19.2.7(react@19.2.7) - optionalDependencies: - '@types/react': 19.2.17 - '@types/react-dom': 19.2.3(@types/react@19.2.17) - - '@radix-ui/react-checkbox@1.3.11(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)': - dependencies: - '@radix-ui/primitive': 1.1.7 - '@radix-ui/react-compose-refs': 1.1.5(@types/react@19.2.17)(react@19.2.7) - '@radix-ui/react-context': 1.2.2(@types/react@19.2.17)(react@19.2.7) - '@radix-ui/react-presence': 1.1.10(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) - '@radix-ui/react-primitive': 2.1.10(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) - '@radix-ui/react-use-controllable-state': 1.2.6(@types/react@19.2.17)(react@19.2.7) - '@radix-ui/react-use-size': 1.1.4(@types/react@19.2.17)(react@19.2.7) - react: 19.2.7 - react-dom: 19.2.7(react@19.2.7) - optionalDependencies: - '@types/react': 19.2.17 - '@types/react-dom': 19.2.3(@types/react@19.2.17) - - '@radix-ui/react-collapsible@1.1.20(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)': - dependencies: - '@radix-ui/primitive': 1.1.7 - '@radix-ui/react-compose-refs': 1.1.5(@types/react@19.2.17)(react@19.2.7) - '@radix-ui/react-context': 1.2.2(@types/react@19.2.17)(react@19.2.7) - '@radix-ui/react-id': 1.1.4(@types/react@19.2.17)(react@19.2.7) - '@radix-ui/react-presence': 1.1.10(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) - '@radix-ui/react-primitive': 2.1.10(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) - '@radix-ui/react-use-controllable-state': 1.2.6(@types/react@19.2.17)(react@19.2.7) - '@radix-ui/react-use-layout-effect': 1.1.4(@types/react@19.2.17)(react@19.2.7) - react: 19.2.7 - react-dom: 19.2.7(react@19.2.7) - optionalDependencies: - '@types/react': 19.2.17 - '@types/react-dom': 19.2.3(@types/react@19.2.17) - - '@radix-ui/react-collection@1.0.1(react-dom@19.2.7(react@19.2.7))(react@19.2.7)': - dependencies: - '@babel/runtime': 7.29.7 - '@radix-ui/react-compose-refs': 1.0.0(react@19.2.7) - '@radix-ui/react-context': 1.0.0(react@19.2.7) - '@radix-ui/react-primitive': 1.0.1(react-dom@19.2.7(react@19.2.7))(react@19.2.7) - '@radix-ui/react-slot': 1.0.1(react@19.2.7) - react: 19.2.7 - react-dom: 19.2.7(react@19.2.7) - - '@radix-ui/react-collection@1.1.15(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)': - dependencies: - '@radix-ui/react-compose-refs': 1.1.5(@types/react@19.2.17)(react@19.2.7) - '@radix-ui/react-context': 1.2.2(@types/react@19.2.17)(react@19.2.7) - '@radix-ui/react-primitive': 2.1.10(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) - '@radix-ui/react-slot': 1.3.3(@types/react@19.2.17)(react@19.2.7) - react: 19.2.7 - react-dom: 19.2.7(react@19.2.7) - optionalDependencies: - '@types/react': 19.2.17 - '@types/react-dom': 19.2.3(@types/react@19.2.17) - - '@radix-ui/react-compose-refs@1.0.0(react@19.2.7)': - dependencies: - '@babel/runtime': 7.29.7 - react: 19.2.7 - - '@radix-ui/react-compose-refs@1.1.1(@types/react@19.2.17)(react@19.2.7)': - dependencies: - react: 19.2.7 - optionalDependencies: - '@types/react': 19.2.17 - - '@radix-ui/react-compose-refs@1.1.5(@types/react@19.2.17)(react@19.2.7)': - dependencies: - react: 19.2.7 - optionalDependencies: - '@types/react': 19.2.17 - - '@radix-ui/react-context-menu@2.3.7(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)': - dependencies: - '@radix-ui/primitive': 1.1.7 - '@radix-ui/react-context': 1.2.2(@types/react@19.2.17)(react@19.2.7) - '@radix-ui/react-menu': 2.1.24(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) - '@radix-ui/react-primitive': 2.1.10(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) - '@radix-ui/react-use-controllable-state': 1.2.6(@types/react@19.2.17)(react@19.2.7) - react: 19.2.7 - react-dom: 19.2.7(react@19.2.7) - optionalDependencies: - '@types/react': 19.2.17 - '@types/react-dom': 19.2.3(@types/react@19.2.17) - - '@radix-ui/react-context@1.0.0(react@19.2.7)': - dependencies: - '@babel/runtime': 7.29.7 - react: 19.2.7 - - '@radix-ui/react-context@1.1.1(@types/react@19.2.17)(react@19.2.7)': - dependencies: - react: 19.2.7 - optionalDependencies: - '@types/react': 19.2.17 - - '@radix-ui/react-context@1.2.2(@types/react@19.2.17)(react@19.2.7)': - dependencies: - react: 19.2.7 - optionalDependencies: - '@types/react': 19.2.17 - - '@radix-ui/react-dialog@1.1.23(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)': - dependencies: - '@radix-ui/primitive': 1.1.7 - '@radix-ui/react-compose-refs': 1.1.5(@types/react@19.2.17)(react@19.2.7) - '@radix-ui/react-context': 1.2.2(@types/react@19.2.17)(react@19.2.7) - '@radix-ui/react-dismissable-layer': 1.1.19(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) - '@radix-ui/react-focus-guards': 1.1.6(@types/react@19.2.17)(react@19.2.7) - '@radix-ui/react-focus-scope': 1.1.16(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) - '@radix-ui/react-id': 1.1.4(@types/react@19.2.17)(react@19.2.7) - '@radix-ui/react-portal': 1.1.17(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) - '@radix-ui/react-presence': 1.1.10(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) - '@radix-ui/react-primitive': 2.1.10(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) - '@radix-ui/react-slot': 1.3.3(@types/react@19.2.17)(react@19.2.7) - '@radix-ui/react-use-controllable-state': 1.2.6(@types/react@19.2.17)(react@19.2.7) - '@radix-ui/react-use-layout-effect': 1.1.4(@types/react@19.2.17)(react@19.2.7) - aria-hidden: 1.2.6 - react: 19.2.7 - react-dom: 19.2.7(react@19.2.7) - react-remove-scroll: 2.7.2(@types/react@19.2.17)(react@19.2.7) - optionalDependencies: - '@types/react': 19.2.17 - '@types/react-dom': 19.2.3(@types/react@19.2.17) - - '@radix-ui/react-direction@1.0.0(react@19.2.7)': - dependencies: - '@babel/runtime': 7.29.7 - react: 19.2.7 - - '@radix-ui/react-direction@1.1.4(@types/react@19.2.17)(react@19.2.7)': - dependencies: - react: 19.2.7 - optionalDependencies: - '@types/react': 19.2.17 - - '@radix-ui/react-dismissable-layer@1.1.19(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)': - dependencies: - '@radix-ui/primitive': 1.1.7 - '@radix-ui/react-compose-refs': 1.1.5(@types/react@19.2.17)(react@19.2.7) - '@radix-ui/react-primitive': 2.1.10(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) - '@radix-ui/react-use-callback-ref': 1.1.4(@types/react@19.2.17)(react@19.2.7) - '@radix-ui/react-use-effect-event': 0.0.5(@types/react@19.2.17)(react@19.2.7) - react: 19.2.7 - react-dom: 19.2.7(react@19.2.7) - optionalDependencies: - '@types/react': 19.2.17 - '@types/react-dom': 19.2.3(@types/react@19.2.17) - - '@radix-ui/react-dismissable-layer@1.1.5(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)': - dependencies: - '@radix-ui/primitive': 1.1.1 - '@radix-ui/react-compose-refs': 1.1.1(@types/react@19.2.17)(react@19.2.7) - '@radix-ui/react-primitive': 2.0.2(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) - '@radix-ui/react-use-callback-ref': 1.1.0(@types/react@19.2.17)(react@19.2.7) - '@radix-ui/react-use-escape-keydown': 1.1.0(@types/react@19.2.17)(react@19.2.7) - react: 19.2.7 - react-dom: 19.2.7(react@19.2.7) - optionalDependencies: - '@types/react': 19.2.17 - '@types/react-dom': 19.2.3(@types/react@19.2.17) - - '@radix-ui/react-dropdown-menu@2.1.24(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)': - dependencies: - '@radix-ui/primitive': 1.1.7 - '@radix-ui/react-compose-refs': 1.1.5(@types/react@19.2.17)(react@19.2.7) - '@radix-ui/react-context': 1.2.2(@types/react@19.2.17)(react@19.2.7) - '@radix-ui/react-id': 1.1.4(@types/react@19.2.17)(react@19.2.7) - '@radix-ui/react-menu': 2.1.24(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) - '@radix-ui/react-primitive': 2.1.10(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) - '@radix-ui/react-use-controllable-state': 1.2.6(@types/react@19.2.17)(react@19.2.7) - react: 19.2.7 - react-dom: 19.2.7(react@19.2.7) - optionalDependencies: - '@types/react': 19.2.17 - '@types/react-dom': 19.2.3(@types/react@19.2.17) - - '@radix-ui/react-focus-guards@1.1.1(@types/react@19.2.17)(react@19.2.7)': - dependencies: - react: 19.2.7 - optionalDependencies: - '@types/react': 19.2.17 - - '@radix-ui/react-focus-guards@1.1.6(@types/react@19.2.17)(react@19.2.7)': - dependencies: - react: 19.2.7 - optionalDependencies: - '@types/react': 19.2.17 - - '@radix-ui/react-focus-scope@1.1.16(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)': - dependencies: - '@radix-ui/react-compose-refs': 1.1.5(@types/react@19.2.17)(react@19.2.7) - '@radix-ui/react-primitive': 2.1.10(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) - '@radix-ui/react-use-callback-ref': 1.1.4(@types/react@19.2.17)(react@19.2.7) - react: 19.2.7 - react-dom: 19.2.7(react@19.2.7) - optionalDependencies: - '@types/react': 19.2.17 - '@types/react-dom': 19.2.3(@types/react@19.2.17) - - '@radix-ui/react-focus-scope@1.1.2(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)': - dependencies: - '@radix-ui/react-compose-refs': 1.1.1(@types/react@19.2.17)(react@19.2.7) - '@radix-ui/react-primitive': 2.0.2(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) - '@radix-ui/react-use-callback-ref': 1.1.0(@types/react@19.2.17)(react@19.2.7) - react: 19.2.7 - react-dom: 19.2.7(react@19.2.7) - optionalDependencies: - '@types/react': 19.2.17 - '@types/react-dom': 19.2.3(@types/react@19.2.17) - - '@radix-ui/react-form@0.1.16(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)': - dependencies: - '@radix-ui/primitive': 1.1.7 - '@radix-ui/react-compose-refs': 1.1.5(@types/react@19.2.17)(react@19.2.7) - '@radix-ui/react-context': 1.2.2(@types/react@19.2.17)(react@19.2.7) - '@radix-ui/react-id': 1.1.4(@types/react@19.2.17)(react@19.2.7) - '@radix-ui/react-label': 2.1.15(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) - '@radix-ui/react-primitive': 2.1.10(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) - react: 19.2.7 - react-dom: 19.2.7(react@19.2.7) - optionalDependencies: - '@types/react': 19.2.17 - '@types/react-dom': 19.2.3(@types/react@19.2.17) - - '@radix-ui/react-hover-card@1.1.23(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)': - dependencies: - '@radix-ui/primitive': 1.1.7 - '@radix-ui/react-compose-refs': 1.1.5(@types/react@19.2.17)(react@19.2.7) - '@radix-ui/react-context': 1.2.2(@types/react@19.2.17)(react@19.2.7) - '@radix-ui/react-dismissable-layer': 1.1.19(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) - '@radix-ui/react-popper': 1.3.7(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) - '@radix-ui/react-portal': 1.1.17(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) - '@radix-ui/react-presence': 1.1.10(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) - '@radix-ui/react-primitive': 2.1.10(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) - '@radix-ui/react-use-controllable-state': 1.2.6(@types/react@19.2.17)(react@19.2.7) - react: 19.2.7 - react-dom: 19.2.7(react@19.2.7) - optionalDependencies: - '@types/react': 19.2.17 - '@types/react-dom': 19.2.3(@types/react@19.2.17) - - '@radix-ui/react-id@1.0.0(react@19.2.7)': - dependencies: - '@babel/runtime': 7.29.7 - '@radix-ui/react-use-layout-effect': 1.0.0(react@19.2.7) - react: 19.2.7 - - '@radix-ui/react-id@1.1.0(@types/react@19.2.17)(react@19.2.7)': - dependencies: - '@radix-ui/react-use-layout-effect': 1.1.0(@types/react@19.2.17)(react@19.2.7) - react: 19.2.7 - optionalDependencies: - '@types/react': 19.2.17 - - '@radix-ui/react-id@1.1.4(@types/react@19.2.17)(react@19.2.7)': - dependencies: - '@radix-ui/react-use-layout-effect': 1.1.4(@types/react@19.2.17)(react@19.2.7) - react: 19.2.7 - optionalDependencies: - '@types/react': 19.2.17 - - '@radix-ui/react-label@2.1.15(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)': - dependencies: - '@radix-ui/react-primitive': 2.1.10(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) - react: 19.2.7 - react-dom: 19.2.7(react@19.2.7) - optionalDependencies: - '@types/react': 19.2.17 - '@types/react-dom': 19.2.3(@types/react@19.2.17) - - '@radix-ui/react-menu@2.1.24(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)': - dependencies: - '@radix-ui/primitive': 1.1.7 - '@radix-ui/react-collection': 1.1.15(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) - '@radix-ui/react-compose-refs': 1.1.5(@types/react@19.2.17)(react@19.2.7) - '@radix-ui/react-context': 1.2.2(@types/react@19.2.17)(react@19.2.7) - '@radix-ui/react-direction': 1.1.4(@types/react@19.2.17)(react@19.2.7) - '@radix-ui/react-dismissable-layer': 1.1.19(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) - '@radix-ui/react-focus-guards': 1.1.6(@types/react@19.2.17)(react@19.2.7) - '@radix-ui/react-focus-scope': 1.1.16(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) - '@radix-ui/react-id': 1.1.4(@types/react@19.2.17)(react@19.2.7) - '@radix-ui/react-popper': 1.3.7(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) - '@radix-ui/react-portal': 1.1.17(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) - '@radix-ui/react-presence': 1.1.10(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) - '@radix-ui/react-primitive': 2.1.10(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) - '@radix-ui/react-roving-focus': 1.1.19(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) - '@radix-ui/react-slot': 1.3.3(@types/react@19.2.17)(react@19.2.7) - '@radix-ui/react-use-callback-ref': 1.1.4(@types/react@19.2.17)(react@19.2.7) - aria-hidden: 1.2.6 - react: 19.2.7 - react-dom: 19.2.7(react@19.2.7) - react-remove-scroll: 2.7.2(@types/react@19.2.17)(react@19.2.7) - optionalDependencies: - '@types/react': 19.2.17 - '@types/react-dom': 19.2.3(@types/react@19.2.17) - - '@radix-ui/react-menubar@1.1.24(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)': - dependencies: - '@radix-ui/primitive': 1.1.7 - '@radix-ui/react-collection': 1.1.15(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) - '@radix-ui/react-compose-refs': 1.1.5(@types/react@19.2.17)(react@19.2.7) - '@radix-ui/react-context': 1.2.2(@types/react@19.2.17)(react@19.2.7) - '@radix-ui/react-direction': 1.1.4(@types/react@19.2.17)(react@19.2.7) - '@radix-ui/react-id': 1.1.4(@types/react@19.2.17)(react@19.2.7) - '@radix-ui/react-menu': 2.1.24(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) - '@radix-ui/react-primitive': 2.1.10(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) - '@radix-ui/react-roving-focus': 1.1.19(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) - '@radix-ui/react-use-controllable-state': 1.2.6(@types/react@19.2.17)(react@19.2.7) - react: 19.2.7 - react-dom: 19.2.7(react@19.2.7) - optionalDependencies: - '@types/react': 19.2.17 - '@types/react-dom': 19.2.3(@types/react@19.2.17) - - '@radix-ui/react-navigation-menu@1.2.22(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)': - dependencies: - '@radix-ui/primitive': 1.1.7 - '@radix-ui/react-collection': 1.1.15(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) - '@radix-ui/react-compose-refs': 1.1.5(@types/react@19.2.17)(react@19.2.7) - '@radix-ui/react-context': 1.2.2(@types/react@19.2.17)(react@19.2.7) - '@radix-ui/react-direction': 1.1.4(@types/react@19.2.17)(react@19.2.7) - '@radix-ui/react-dismissable-layer': 1.1.19(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) - '@radix-ui/react-id': 1.1.4(@types/react@19.2.17)(react@19.2.7) - '@radix-ui/react-presence': 1.1.10(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) - '@radix-ui/react-primitive': 2.1.10(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) - '@radix-ui/react-use-callback-ref': 1.1.4(@types/react@19.2.17)(react@19.2.7) - '@radix-ui/react-use-controllable-state': 1.2.6(@types/react@19.2.17)(react@19.2.7) - '@radix-ui/react-use-layout-effect': 1.1.4(@types/react@19.2.17)(react@19.2.7) - '@radix-ui/react-use-previous': 1.1.4(@types/react@19.2.17)(react@19.2.7) - '@radix-ui/react-visually-hidden': 1.2.11(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) - react: 19.2.7 - react-dom: 19.2.7(react@19.2.7) - optionalDependencies: - '@types/react': 19.2.17 - '@types/react-dom': 19.2.3(@types/react@19.2.17) - - '@radix-ui/react-one-time-password-field@0.1.16(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)': - dependencies: - '@radix-ui/number': 1.1.3 - '@radix-ui/primitive': 1.1.7 - '@radix-ui/react-collection': 1.1.15(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) - '@radix-ui/react-compose-refs': 1.1.5(@types/react@19.2.17)(react@19.2.7) - '@radix-ui/react-context': 1.2.2(@types/react@19.2.17)(react@19.2.7) - '@radix-ui/react-direction': 1.1.4(@types/react@19.2.17)(react@19.2.7) - '@radix-ui/react-primitive': 2.1.10(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) - '@radix-ui/react-roving-focus': 1.1.19(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) - '@radix-ui/react-use-controllable-state': 1.2.6(@types/react@19.2.17)(react@19.2.7) - '@radix-ui/react-use-effect-event': 0.0.5(@types/react@19.2.17)(react@19.2.7) - '@radix-ui/react-use-is-hydrated': 0.1.3(@types/react@19.2.17)(react@19.2.7) - '@radix-ui/react-use-layout-effect': 1.1.4(@types/react@19.2.17)(react@19.2.7) - react: 19.2.7 - react-dom: 19.2.7(react@19.2.7) - optionalDependencies: - '@types/react': 19.2.17 - '@types/react-dom': 19.2.3(@types/react@19.2.17) - - '@radix-ui/react-password-toggle-field@0.1.11(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)': - dependencies: - '@radix-ui/primitive': 1.1.7 - '@radix-ui/react-compose-refs': 1.1.5(@types/react@19.2.17)(react@19.2.7) - '@radix-ui/react-context': 1.2.2(@types/react@19.2.17)(react@19.2.7) - '@radix-ui/react-id': 1.1.4(@types/react@19.2.17)(react@19.2.7) - '@radix-ui/react-primitive': 2.1.10(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) - '@radix-ui/react-use-controllable-state': 1.2.6(@types/react@19.2.17)(react@19.2.7) - '@radix-ui/react-use-effect-event': 0.0.5(@types/react@19.2.17)(react@19.2.7) - '@radix-ui/react-use-is-hydrated': 0.1.3(@types/react@19.2.17)(react@19.2.7) - react: 19.2.7 - react-dom: 19.2.7(react@19.2.7) - optionalDependencies: - '@types/react': 19.2.17 - '@types/react-dom': 19.2.3(@types/react@19.2.17) - - '@radix-ui/react-popover@1.1.23(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)': - dependencies: - '@radix-ui/primitive': 1.1.7 - '@radix-ui/react-compose-refs': 1.1.5(@types/react@19.2.17)(react@19.2.7) - '@radix-ui/react-context': 1.2.2(@types/react@19.2.17)(react@19.2.7) - '@radix-ui/react-dismissable-layer': 1.1.19(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) - '@radix-ui/react-focus-guards': 1.1.6(@types/react@19.2.17)(react@19.2.7) - '@radix-ui/react-focus-scope': 1.1.16(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) - '@radix-ui/react-id': 1.1.4(@types/react@19.2.17)(react@19.2.7) - '@radix-ui/react-popper': 1.3.7(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) - '@radix-ui/react-portal': 1.1.17(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) - '@radix-ui/react-presence': 1.1.10(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) - '@radix-ui/react-primitive': 2.1.10(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) - '@radix-ui/react-slot': 1.3.3(@types/react@19.2.17)(react@19.2.7) - '@radix-ui/react-use-controllable-state': 1.2.6(@types/react@19.2.17)(react@19.2.7) - aria-hidden: 1.2.6 - react: 19.2.7 - react-dom: 19.2.7(react@19.2.7) - react-remove-scroll: 2.7.2(@types/react@19.2.17)(react@19.2.7) - optionalDependencies: - '@types/react': 19.2.17 - '@types/react-dom': 19.2.3(@types/react@19.2.17) - - '@radix-ui/react-popover@1.1.6(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)': - dependencies: - '@radix-ui/primitive': 1.1.1 - '@radix-ui/react-compose-refs': 1.1.1(@types/react@19.2.17)(react@19.2.7) - '@radix-ui/react-context': 1.1.1(@types/react@19.2.17)(react@19.2.7) - '@radix-ui/react-dismissable-layer': 1.1.5(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) - '@radix-ui/react-focus-guards': 1.1.1(@types/react@19.2.17)(react@19.2.7) - '@radix-ui/react-focus-scope': 1.1.2(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) - '@radix-ui/react-id': 1.1.0(@types/react@19.2.17)(react@19.2.7) - '@radix-ui/react-popper': 1.2.2(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) - '@radix-ui/react-portal': 1.1.4(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) - '@radix-ui/react-presence': 1.1.2(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) - '@radix-ui/react-primitive': 2.0.2(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) - '@radix-ui/react-slot': 1.1.2(@types/react@19.2.17)(react@19.2.7) - '@radix-ui/react-use-controllable-state': 1.1.0(@types/react@19.2.17)(react@19.2.7) - aria-hidden: 1.2.6 - react: 19.2.7 - react-dom: 19.2.7(react@19.2.7) - react-remove-scroll: 2.7.2(@types/react@19.2.17)(react@19.2.7) - optionalDependencies: - '@types/react': 19.2.17 - '@types/react-dom': 19.2.3(@types/react@19.2.17) - - '@radix-ui/react-popper@1.2.2(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)': - dependencies: - '@floating-ui/react-dom': 2.1.9(react-dom@19.2.7(react@19.2.7))(react@19.2.7) - '@radix-ui/react-arrow': 1.1.2(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) - '@radix-ui/react-compose-refs': 1.1.1(@types/react@19.2.17)(react@19.2.7) - '@radix-ui/react-context': 1.1.1(@types/react@19.2.17)(react@19.2.7) - '@radix-ui/react-primitive': 2.0.2(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) - '@radix-ui/react-use-callback-ref': 1.1.0(@types/react@19.2.17)(react@19.2.7) - '@radix-ui/react-use-layout-effect': 1.1.0(@types/react@19.2.17)(react@19.2.7) - '@radix-ui/react-use-rect': 1.1.0(@types/react@19.2.17)(react@19.2.7) - '@radix-ui/react-use-size': 1.1.0(@types/react@19.2.17)(react@19.2.7) - '@radix-ui/rect': 1.1.0 - react: 19.2.7 - react-dom: 19.2.7(react@19.2.7) - optionalDependencies: - '@types/react': 19.2.17 - '@types/react-dom': 19.2.3(@types/react@19.2.17) - - '@radix-ui/react-popper@1.3.7(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)': - dependencies: - '@floating-ui/react-dom': 2.1.9(react-dom@19.2.7(react@19.2.7))(react@19.2.7) - '@radix-ui/react-arrow': 1.1.15(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) - '@radix-ui/react-compose-refs': 1.1.5(@types/react@19.2.17)(react@19.2.7) - '@radix-ui/react-context': 1.2.2(@types/react@19.2.17)(react@19.2.7) - '@radix-ui/react-primitive': 2.1.10(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) - '@radix-ui/react-use-callback-ref': 1.1.4(@types/react@19.2.17)(react@19.2.7) - '@radix-ui/react-use-layout-effect': 1.1.4(@types/react@19.2.17)(react@19.2.7) - '@radix-ui/react-use-rect': 1.1.4(@types/react@19.2.17)(react@19.2.7) - '@radix-ui/react-use-size': 1.1.4(@types/react@19.2.17)(react@19.2.7) - '@radix-ui/rect': 1.1.3 - react: 19.2.7 - react-dom: 19.2.7(react@19.2.7) - optionalDependencies: - '@types/react': 19.2.17 - '@types/react-dom': 19.2.3(@types/react@19.2.17) - - '@radix-ui/react-portal@1.1.17(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)': - dependencies: - '@radix-ui/react-primitive': 2.1.10(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) - '@radix-ui/react-use-layout-effect': 1.1.4(@types/react@19.2.17)(react@19.2.7) - react: 19.2.7 - react-dom: 19.2.7(react@19.2.7) - optionalDependencies: - '@types/react': 19.2.17 - '@types/react-dom': 19.2.3(@types/react@19.2.17) - - '@radix-ui/react-portal@1.1.4(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)': - dependencies: - '@radix-ui/react-primitive': 2.0.2(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) - '@radix-ui/react-use-layout-effect': 1.1.0(@types/react@19.2.17)(react@19.2.7) - react: 19.2.7 - react-dom: 19.2.7(react@19.2.7) - optionalDependencies: - '@types/react': 19.2.17 - '@types/react-dom': 19.2.3(@types/react@19.2.17) - - '@radix-ui/react-presence@1.0.0(react-dom@19.2.7(react@19.2.7))(react@19.2.7)': - dependencies: - '@babel/runtime': 7.29.7 - '@radix-ui/react-compose-refs': 1.0.0(react@19.2.7) - '@radix-ui/react-use-layout-effect': 1.0.0(react@19.2.7) - react: 19.2.7 - react-dom: 19.2.7(react@19.2.7) - - '@radix-ui/react-presence@1.1.10(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)': - dependencies: - '@radix-ui/react-use-layout-effect': 1.1.4(@types/react@19.2.17)(react@19.2.7) - react: 19.2.7 - react-dom: 19.2.7(react@19.2.7) - optionalDependencies: - '@types/react': 19.2.17 - '@types/react-dom': 19.2.3(@types/react@19.2.17) - - '@radix-ui/react-presence@1.1.2(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)': - dependencies: - '@radix-ui/react-compose-refs': 1.1.1(@types/react@19.2.17)(react@19.2.7) - '@radix-ui/react-use-layout-effect': 1.1.0(@types/react@19.2.17)(react@19.2.7) - react: 19.2.7 - react-dom: 19.2.7(react@19.2.7) - optionalDependencies: - '@types/react': 19.2.17 - '@types/react-dom': 19.2.3(@types/react@19.2.17) - - '@radix-ui/react-primitive@1.0.1(react-dom@19.2.7(react@19.2.7))(react@19.2.7)': - dependencies: - '@babel/runtime': 7.29.7 - '@radix-ui/react-slot': 1.0.1(react@19.2.7) - react: 19.2.7 - react-dom: 19.2.7(react@19.2.7) - - '@radix-ui/react-primitive@2.0.2(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)': - dependencies: - '@radix-ui/react-slot': 1.1.2(@types/react@19.2.17)(react@19.2.7) - react: 19.2.7 - react-dom: 19.2.7(react@19.2.7) - optionalDependencies: - '@types/react': 19.2.17 - '@types/react-dom': 19.2.3(@types/react@19.2.17) - - '@radix-ui/react-primitive@2.1.10(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)': - dependencies: - '@radix-ui/react-slot': 1.3.3(@types/react@19.2.17)(react@19.2.7) - react: 19.2.7 - react-dom: 19.2.7(react@19.2.7) - optionalDependencies: - '@types/react': 19.2.17 - '@types/react-dom': 19.2.3(@types/react@19.2.17) - - '@radix-ui/react-progress@1.1.16(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)': - dependencies: - '@radix-ui/react-context': 1.2.2(@types/react@19.2.17)(react@19.2.7) - '@radix-ui/react-primitive': 2.1.10(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) - react: 19.2.7 - react-dom: 19.2.7(react@19.2.7) - optionalDependencies: - '@types/react': 19.2.17 - '@types/react-dom': 19.2.3(@types/react@19.2.17) - - '@radix-ui/react-radio-group@1.4.7(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)': - dependencies: - '@radix-ui/primitive': 1.1.7 - '@radix-ui/react-compose-refs': 1.1.5(@types/react@19.2.17)(react@19.2.7) - '@radix-ui/react-context': 1.2.2(@types/react@19.2.17)(react@19.2.7) - '@radix-ui/react-direction': 1.1.4(@types/react@19.2.17)(react@19.2.7) - '@radix-ui/react-presence': 1.1.10(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) - '@radix-ui/react-primitive': 2.1.10(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) - '@radix-ui/react-roving-focus': 1.1.19(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) - '@radix-ui/react-use-controllable-state': 1.2.6(@types/react@19.2.17)(react@19.2.7) - '@radix-ui/react-use-size': 1.1.4(@types/react@19.2.17)(react@19.2.7) - react: 19.2.7 - react-dom: 19.2.7(react@19.2.7) - optionalDependencies: - '@types/react': 19.2.17 - '@types/react-dom': 19.2.3(@types/react@19.2.17) - - '@radix-ui/react-roving-focus@1.0.2(react-dom@19.2.7(react@19.2.7))(react@19.2.7)': - dependencies: - '@babel/runtime': 7.29.7 - '@radix-ui/primitive': 1.0.0 - '@radix-ui/react-collection': 1.0.1(react-dom@19.2.7(react@19.2.7))(react@19.2.7) - '@radix-ui/react-compose-refs': 1.0.0(react@19.2.7) - '@radix-ui/react-context': 1.0.0(react@19.2.7) - '@radix-ui/react-direction': 1.0.0(react@19.2.7) - '@radix-ui/react-id': 1.0.0(react@19.2.7) - '@radix-ui/react-primitive': 1.0.1(react-dom@19.2.7(react@19.2.7))(react@19.2.7) - '@radix-ui/react-use-callback-ref': 1.0.0(react@19.2.7) - '@radix-ui/react-use-controllable-state': 1.0.0(react@19.2.7) - react: 19.2.7 - react-dom: 19.2.7(react@19.2.7) - - '@radix-ui/react-roving-focus@1.1.19(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)': - dependencies: - '@radix-ui/primitive': 1.1.7 - '@radix-ui/react-collection': 1.1.15(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) - '@radix-ui/react-compose-refs': 1.1.5(@types/react@19.2.17)(react@19.2.7) - '@radix-ui/react-context': 1.2.2(@types/react@19.2.17)(react@19.2.7) - '@radix-ui/react-direction': 1.1.4(@types/react@19.2.17)(react@19.2.7) - '@radix-ui/react-id': 1.1.4(@types/react@19.2.17)(react@19.2.7) - '@radix-ui/react-primitive': 2.1.10(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) - '@radix-ui/react-use-callback-ref': 1.1.4(@types/react@19.2.17)(react@19.2.7) - '@radix-ui/react-use-controllable-state': 1.2.6(@types/react@19.2.17)(react@19.2.7) - '@radix-ui/react-use-is-hydrated': 0.1.3(@types/react@19.2.17)(react@19.2.7) - '@radix-ui/react-use-layout-effect': 1.1.4(@types/react@19.2.17)(react@19.2.7) - react: 19.2.7 - react-dom: 19.2.7(react@19.2.7) - optionalDependencies: - '@types/react': 19.2.17 - '@types/react-dom': 19.2.3(@types/react@19.2.17) - - '@radix-ui/react-scroll-area@1.2.18(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)': - dependencies: - '@radix-ui/number': 1.1.3 - '@radix-ui/primitive': 1.1.7 - '@radix-ui/react-compose-refs': 1.1.5(@types/react@19.2.17)(react@19.2.7) - '@radix-ui/react-context': 1.2.2(@types/react@19.2.17)(react@19.2.7) - '@radix-ui/react-direction': 1.1.4(@types/react@19.2.17)(react@19.2.7) - '@radix-ui/react-presence': 1.1.10(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) - '@radix-ui/react-primitive': 2.1.10(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) - '@radix-ui/react-use-callback-ref': 1.1.4(@types/react@19.2.17)(react@19.2.7) - '@radix-ui/react-use-layout-effect': 1.1.4(@types/react@19.2.17)(react@19.2.7) - react: 19.2.7 - react-dom: 19.2.7(react@19.2.7) - optionalDependencies: - '@types/react': 19.2.17 - '@types/react-dom': 19.2.3(@types/react@19.2.17) - - '@radix-ui/react-select@2.3.7(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)': - dependencies: - '@radix-ui/number': 1.1.3 - '@radix-ui/primitive': 1.1.7 - '@radix-ui/react-collection': 1.1.15(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) - '@radix-ui/react-compose-refs': 1.1.5(@types/react@19.2.17)(react@19.2.7) - '@radix-ui/react-context': 1.2.2(@types/react@19.2.17)(react@19.2.7) - '@radix-ui/react-direction': 1.1.4(@types/react@19.2.17)(react@19.2.7) - '@radix-ui/react-dismissable-layer': 1.1.19(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) - '@radix-ui/react-focus-guards': 1.1.6(@types/react@19.2.17)(react@19.2.7) - '@radix-ui/react-focus-scope': 1.1.16(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) - '@radix-ui/react-id': 1.1.4(@types/react@19.2.17)(react@19.2.7) - '@radix-ui/react-popper': 1.3.7(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) - '@radix-ui/react-portal': 1.1.17(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) - '@radix-ui/react-presence': 1.1.10(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) - '@radix-ui/react-primitive': 2.1.10(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) - '@radix-ui/react-slot': 1.3.3(@types/react@19.2.17)(react@19.2.7) - '@radix-ui/react-use-callback-ref': 1.1.4(@types/react@19.2.17)(react@19.2.7) - '@radix-ui/react-use-controllable-state': 1.2.6(@types/react@19.2.17)(react@19.2.7) - '@radix-ui/react-use-layout-effect': 1.1.4(@types/react@19.2.17)(react@19.2.7) - '@radix-ui/react-use-previous': 1.1.4(@types/react@19.2.17)(react@19.2.7) - '@radix-ui/react-visually-hidden': 1.2.11(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) - aria-hidden: 1.2.6 - react: 19.2.7 - react-dom: 19.2.7(react@19.2.7) - react-remove-scroll: 2.7.2(@types/react@19.2.17)(react@19.2.7) - optionalDependencies: - '@types/react': 19.2.17 - '@types/react-dom': 19.2.3(@types/react@19.2.17) - - '@radix-ui/react-separator@1.1.15(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)': - dependencies: - '@radix-ui/react-primitive': 2.1.10(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) - react: 19.2.7 - react-dom: 19.2.7(react@19.2.7) - optionalDependencies: - '@types/react': 19.2.17 - '@types/react-dom': 19.2.3(@types/react@19.2.17) - - '@radix-ui/react-slider@1.4.7(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)': - dependencies: - '@radix-ui/number': 1.1.3 - '@radix-ui/primitive': 1.1.7 - '@radix-ui/react-collection': 1.1.15(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) - '@radix-ui/react-compose-refs': 1.1.5(@types/react@19.2.17)(react@19.2.7) - '@radix-ui/react-context': 1.2.2(@types/react@19.2.17)(react@19.2.7) - '@radix-ui/react-direction': 1.1.4(@types/react@19.2.17)(react@19.2.7) - '@radix-ui/react-primitive': 2.1.10(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) - '@radix-ui/react-use-controllable-state': 1.2.6(@types/react@19.2.17)(react@19.2.7) - '@radix-ui/react-use-layout-effect': 1.1.4(@types/react@19.2.17)(react@19.2.7) - '@radix-ui/react-use-previous': 1.1.4(@types/react@19.2.17)(react@19.2.7) - '@radix-ui/react-use-size': 1.1.4(@types/react@19.2.17)(react@19.2.7) - react: 19.2.7 - react-dom: 19.2.7(react@19.2.7) - optionalDependencies: - '@types/react': 19.2.17 - '@types/react-dom': 19.2.3(@types/react@19.2.17) - - '@radix-ui/react-slot@1.0.1(react@19.2.7)': - dependencies: - '@babel/runtime': 7.29.7 - '@radix-ui/react-compose-refs': 1.0.0(react@19.2.7) - react: 19.2.7 - - '@radix-ui/react-slot@1.1.2(@types/react@19.2.17)(react@19.2.7)': - dependencies: - '@radix-ui/react-compose-refs': 1.1.1(@types/react@19.2.17)(react@19.2.7) - react: 19.2.7 - optionalDependencies: - '@types/react': 19.2.17 - - '@radix-ui/react-slot@1.3.3(@types/react@19.2.17)(react@19.2.7)': - dependencies: - '@radix-ui/react-compose-refs': 1.1.5(@types/react@19.2.17)(react@19.2.7) - react: 19.2.7 - optionalDependencies: - '@types/react': 19.2.17 - - '@radix-ui/react-switch@1.3.7(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)': - dependencies: - '@radix-ui/primitive': 1.1.7 - '@radix-ui/react-compose-refs': 1.1.5(@types/react@19.2.17)(react@19.2.7) - '@radix-ui/react-context': 1.2.2(@types/react@19.2.17)(react@19.2.7) - '@radix-ui/react-primitive': 2.1.10(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) - '@radix-ui/react-use-controllable-state': 1.2.6(@types/react@19.2.17)(react@19.2.7) - '@radix-ui/react-use-size': 1.1.4(@types/react@19.2.17)(react@19.2.7) - react: 19.2.7 - react-dom: 19.2.7(react@19.2.7) - optionalDependencies: - '@types/react': 19.2.17 - '@types/react-dom': 19.2.3(@types/react@19.2.17) - - '@radix-ui/react-tabs@1.0.2(react-dom@19.2.7(react@19.2.7))(react@19.2.7)': - dependencies: - '@babel/runtime': 7.29.7 - '@radix-ui/primitive': 1.0.0 - '@radix-ui/react-context': 1.0.0(react@19.2.7) - '@radix-ui/react-direction': 1.0.0(react@19.2.7) - '@radix-ui/react-id': 1.0.0(react@19.2.7) - '@radix-ui/react-presence': 1.0.0(react-dom@19.2.7(react@19.2.7))(react@19.2.7) - '@radix-ui/react-primitive': 1.0.1(react-dom@19.2.7(react@19.2.7))(react@19.2.7) - '@radix-ui/react-roving-focus': 1.0.2(react-dom@19.2.7(react@19.2.7))(react@19.2.7) - '@radix-ui/react-use-controllable-state': 1.0.0(react@19.2.7) - react: 19.2.7 - react-dom: 19.2.7(react@19.2.7) - - '@radix-ui/react-tabs@1.1.21(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)': - dependencies: - '@radix-ui/primitive': 1.1.7 - '@radix-ui/react-context': 1.2.2(@types/react@19.2.17)(react@19.2.7) - '@radix-ui/react-direction': 1.1.4(@types/react@19.2.17)(react@19.2.7) - '@radix-ui/react-id': 1.1.4(@types/react@19.2.17)(react@19.2.7) - '@radix-ui/react-presence': 1.1.10(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) - '@radix-ui/react-primitive': 2.1.10(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) - '@radix-ui/react-roving-focus': 1.1.19(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) - '@radix-ui/react-use-controllable-state': 1.2.6(@types/react@19.2.17)(react@19.2.7) - react: 19.2.7 - react-dom: 19.2.7(react@19.2.7) - optionalDependencies: - '@types/react': 19.2.17 - '@types/react-dom': 19.2.3(@types/react@19.2.17) - - '@radix-ui/react-toast@1.2.23(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)': - dependencies: - '@radix-ui/primitive': 1.1.7 - '@radix-ui/react-collection': 1.1.15(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) - '@radix-ui/react-compose-refs': 1.1.5(@types/react@19.2.17)(react@19.2.7) - '@radix-ui/react-context': 1.2.2(@types/react@19.2.17)(react@19.2.7) - '@radix-ui/react-dismissable-layer': 1.1.19(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) - '@radix-ui/react-portal': 1.1.17(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) - '@radix-ui/react-presence': 1.1.10(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) - '@radix-ui/react-primitive': 2.1.10(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) - '@radix-ui/react-use-callback-ref': 1.1.4(@types/react@19.2.17)(react@19.2.7) - '@radix-ui/react-use-controllable-state': 1.2.6(@types/react@19.2.17)(react@19.2.7) - '@radix-ui/react-use-layout-effect': 1.1.4(@types/react@19.2.17)(react@19.2.7) - '@radix-ui/react-visually-hidden': 1.2.11(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) - react: 19.2.7 - react-dom: 19.2.7(react@19.2.7) - optionalDependencies: - '@types/react': 19.2.17 - '@types/react-dom': 19.2.3(@types/react@19.2.17) - - '@radix-ui/react-toggle-group@1.1.19(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)': - dependencies: - '@radix-ui/primitive': 1.1.7 - '@radix-ui/react-context': 1.2.2(@types/react@19.2.17)(react@19.2.7) - '@radix-ui/react-direction': 1.1.4(@types/react@19.2.17)(react@19.2.7) - '@radix-ui/react-primitive': 2.1.10(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) - '@radix-ui/react-roving-focus': 1.1.19(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) - '@radix-ui/react-toggle': 1.1.18(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) - '@radix-ui/react-use-controllable-state': 1.2.6(@types/react@19.2.17)(react@19.2.7) - react: 19.2.7 - react-dom: 19.2.7(react@19.2.7) - optionalDependencies: - '@types/react': 19.2.17 - '@types/react-dom': 19.2.3(@types/react@19.2.17) - - '@radix-ui/react-toggle@1.1.18(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)': - dependencies: - '@radix-ui/primitive': 1.1.7 - '@radix-ui/react-primitive': 2.1.10(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) - '@radix-ui/react-use-controllable-state': 1.2.6(@types/react@19.2.17)(react@19.2.7) - react: 19.2.7 - react-dom: 19.2.7(react@19.2.7) - optionalDependencies: - '@types/react': 19.2.17 - '@types/react-dom': 19.2.3(@types/react@19.2.17) - - '@radix-ui/react-toolbar@1.1.19(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)': - dependencies: - '@radix-ui/primitive': 1.1.7 - '@radix-ui/react-context': 1.2.2(@types/react@19.2.17)(react@19.2.7) - '@radix-ui/react-direction': 1.1.4(@types/react@19.2.17)(react@19.2.7) - '@radix-ui/react-primitive': 2.1.10(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) - '@radix-ui/react-roving-focus': 1.1.19(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) - '@radix-ui/react-separator': 1.1.15(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) - '@radix-ui/react-toggle-group': 1.1.19(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) - react: 19.2.7 - react-dom: 19.2.7(react@19.2.7) - optionalDependencies: - '@types/react': 19.2.17 - '@types/react-dom': 19.2.3(@types/react@19.2.17) - - '@radix-ui/react-tooltip@1.2.16(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)': - dependencies: - '@radix-ui/primitive': 1.1.7 - '@radix-ui/react-compose-refs': 1.1.5(@types/react@19.2.17)(react@19.2.7) - '@radix-ui/react-context': 1.2.2(@types/react@19.2.17)(react@19.2.7) - '@radix-ui/react-dismissable-layer': 1.1.19(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) - '@radix-ui/react-id': 1.1.4(@types/react@19.2.17)(react@19.2.7) - '@radix-ui/react-popper': 1.3.7(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) - '@radix-ui/react-portal': 1.1.17(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) - '@radix-ui/react-presence': 1.1.10(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) - '@radix-ui/react-primitive': 2.1.10(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) - '@radix-ui/react-slot': 1.3.3(@types/react@19.2.17)(react@19.2.7) - '@radix-ui/react-use-controllable-state': 1.2.6(@types/react@19.2.17)(react@19.2.7) - '@radix-ui/react-use-layout-effect': 1.1.4(@types/react@19.2.17)(react@19.2.7) - '@radix-ui/react-visually-hidden': 1.2.11(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) - react: 19.2.7 - react-dom: 19.2.7(react@19.2.7) - optionalDependencies: - '@types/react': 19.2.17 - '@types/react-dom': 19.2.3(@types/react@19.2.17) - - '@radix-ui/react-use-callback-ref@1.0.0(react@19.2.7)': - dependencies: - '@babel/runtime': 7.29.7 - react: 19.2.7 - - '@radix-ui/react-use-callback-ref@1.1.0(@types/react@19.2.17)(react@19.2.7)': - dependencies: - react: 19.2.7 - optionalDependencies: - '@types/react': 19.2.17 - - '@radix-ui/react-use-callback-ref@1.1.4(@types/react@19.2.17)(react@19.2.7)': - dependencies: - react: 19.2.7 - optionalDependencies: - '@types/react': 19.2.17 - - '@radix-ui/react-use-controllable-state@1.0.0(react@19.2.7)': - dependencies: - '@babel/runtime': 7.29.7 - '@radix-ui/react-use-callback-ref': 1.0.0(react@19.2.7) - react: 19.2.7 - - '@radix-ui/react-use-controllable-state@1.1.0(@types/react@19.2.17)(react@19.2.7)': - dependencies: - '@radix-ui/react-use-callback-ref': 1.1.0(@types/react@19.2.17)(react@19.2.7) - react: 19.2.7 - optionalDependencies: - '@types/react': 19.2.17 - - '@radix-ui/react-use-controllable-state@1.2.6(@types/react@19.2.17)(react@19.2.7)': - dependencies: - '@radix-ui/primitive': 1.1.7 - '@radix-ui/react-use-effect-event': 0.0.5(@types/react@19.2.17)(react@19.2.7) - '@radix-ui/react-use-layout-effect': 1.1.4(@types/react@19.2.17)(react@19.2.7) - react: 19.2.7 - optionalDependencies: - '@types/react': 19.2.17 - - '@radix-ui/react-use-effect-event@0.0.5(@types/react@19.2.17)(react@19.2.7)': - dependencies: - '@radix-ui/react-use-layout-effect': 1.1.4(@types/react@19.2.17)(react@19.2.7) - react: 19.2.7 - optionalDependencies: - '@types/react': 19.2.17 - - '@radix-ui/react-use-escape-keydown@1.1.0(@types/react@19.2.17)(react@19.2.7)': - dependencies: - '@radix-ui/react-use-callback-ref': 1.1.0(@types/react@19.2.17)(react@19.2.7) - react: 19.2.7 - optionalDependencies: - '@types/react': 19.2.17 - - '@radix-ui/react-use-escape-keydown@1.1.5(@types/react@19.2.17)(react@19.2.7)': - dependencies: - '@radix-ui/react-use-callback-ref': 1.1.4(@types/react@19.2.17)(react@19.2.7) - react: 19.2.7 - optionalDependencies: - '@types/react': 19.2.17 - - '@radix-ui/react-use-is-hydrated@0.1.3(@types/react@19.2.17)(react@19.2.7)': - dependencies: - react: 19.2.7 - optionalDependencies: - '@types/react': 19.2.17 - - '@radix-ui/react-use-layout-effect@1.0.0(react@19.2.7)': - dependencies: - '@babel/runtime': 7.29.7 - react: 19.2.7 - - '@radix-ui/react-use-layout-effect@1.1.0(@types/react@19.2.17)(react@19.2.7)': - dependencies: - react: 19.2.7 - optionalDependencies: - '@types/react': 19.2.17 - - '@radix-ui/react-use-layout-effect@1.1.4(@types/react@19.2.17)(react@19.2.7)': - dependencies: - react: 19.2.7 - optionalDependencies: - '@types/react': 19.2.17 - - '@radix-ui/react-use-previous@1.1.4(@types/react@19.2.17)(react@19.2.7)': - dependencies: - react: 19.2.7 - optionalDependencies: - '@types/react': 19.2.17 - - '@radix-ui/react-use-rect@1.1.0(@types/react@19.2.17)(react@19.2.7)': - dependencies: - '@radix-ui/rect': 1.1.0 - react: 19.2.7 - optionalDependencies: - '@types/react': 19.2.17 - - '@radix-ui/react-use-rect@1.1.4(@types/react@19.2.17)(react@19.2.7)': - dependencies: - '@radix-ui/rect': 1.1.3 - react: 19.2.7 - optionalDependencies: - '@types/react': 19.2.17 - - '@radix-ui/react-use-size@1.1.0(@types/react@19.2.17)(react@19.2.7)': - dependencies: - '@radix-ui/react-use-layout-effect': 1.1.0(@types/react@19.2.17)(react@19.2.7) - react: 19.2.7 - optionalDependencies: - '@types/react': 19.2.17 - - '@radix-ui/react-use-size@1.1.4(@types/react@19.2.17)(react@19.2.7)': - dependencies: - '@radix-ui/react-use-layout-effect': 1.1.4(@types/react@19.2.17)(react@19.2.7) - react: 19.2.7 - optionalDependencies: - '@types/react': 19.2.17 - - '@radix-ui/react-visually-hidden@1.2.11(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)': - dependencies: - '@radix-ui/react-primitive': 2.1.10(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) - react: 19.2.7 - react-dom: 19.2.7(react@19.2.7) - optionalDependencies: - '@types/react': 19.2.17 - '@types/react-dom': 19.2.3(@types/react@19.2.17) - - '@radix-ui/rect@1.1.0': {} - - '@radix-ui/rect@1.1.3': {} - - '@rc-component/async-validator@6.0.0': - dependencies: - '@babel/runtime': 7.29.7 - - '@rc-component/cascader@1.22.0(react-dom@19.2.7(react@19.2.7))(react@19.2.7)': - dependencies: - '@rc-component/select': 1.10.1(react-dom@19.2.7(react@19.2.7))(react@19.2.7) - '@rc-component/tree': 1.4.0(react-dom@19.2.7(react@19.2.7))(react@19.2.7) - '@rc-component/util': 1.12.0(react-dom@19.2.7(react@19.2.7))(react@19.2.7) - clsx: 2.1.1 - react: 19.2.7 - react-dom: 19.2.7(react@19.2.7) - - '@rc-component/checkbox@2.0.0(react-dom@19.2.7(react@19.2.7))(react@19.2.7)': - dependencies: - '@rc-component/util': 1.12.0(react-dom@19.2.7(react@19.2.7))(react@19.2.7) - clsx: 2.1.1 - react: 19.2.7 - react-dom: 19.2.7(react@19.2.7) - - '@rc-component/collapse@1.2.0(react-dom@19.2.7(react@19.2.7))(react@19.2.7)': - dependencies: - '@babel/runtime': 7.29.7 - '@rc-component/motion': 1.3.3(react-dom@19.2.7(react@19.2.7))(react@19.2.7) - '@rc-component/util': 1.12.0(react-dom@19.2.7(react@19.2.7))(react@19.2.7) - clsx: 2.1.1 - react: 19.2.7 - react-dom: 19.2.7(react@19.2.7) - - '@rc-component/color-picker@3.1.1(react-dom@19.2.7(react@19.2.7))(react@19.2.7)': - dependencies: - '@ant-design/fast-color': 3.0.1 - '@rc-component/util': 1.12.0(react-dom@19.2.7(react@19.2.7))(react@19.2.7) - clsx: 2.1.1 - react: 19.2.7 - react-dom: 19.2.7(react@19.2.7) - - '@rc-component/context@2.0.2(react-dom@19.2.7(react@19.2.7))(react@19.2.7)': - dependencies: - '@rc-component/util': 1.12.0(react-dom@19.2.7(react@19.2.7))(react@19.2.7) - react: 19.2.7 - react-dom: 19.2.7(react@19.2.7) - - '@rc-component/dialog@1.10.0(react-dom@19.2.7(react@19.2.7))(react@19.2.7)': - dependencies: - '@rc-component/motion': 1.3.3(react-dom@19.2.7(react@19.2.7))(react@19.2.7) - '@rc-component/portal': 2.2.1(react-dom@19.2.7(react@19.2.7))(react@19.2.7) - '@rc-component/util': 1.12.0(react-dom@19.2.7(react@19.2.7))(react@19.2.7) - clsx: 2.1.1 - react: 19.2.7 - react-dom: 19.2.7(react@19.2.7) - - '@rc-component/drawer@1.4.2(react-dom@19.2.7(react@19.2.7))(react@19.2.7)': - dependencies: - '@rc-component/motion': 1.3.3(react-dom@19.2.7(react@19.2.7))(react@19.2.7) - '@rc-component/portal': 2.2.1(react-dom@19.2.7(react@19.2.7))(react@19.2.7) - '@rc-component/util': 1.12.0(react-dom@19.2.7(react@19.2.7))(react@19.2.7) - clsx: 2.1.1 - react: 19.2.7 - react-dom: 19.2.7(react@19.2.7) - - '@rc-component/dropdown@1.0.3(react-dom@19.2.7(react@19.2.7))(react@19.2.7)': - dependencies: - '@rc-component/trigger': 3.10.1(react-dom@19.2.7(react@19.2.7))(react@19.2.7) - '@rc-component/util': 1.12.0(react-dom@19.2.7(react@19.2.7))(react@19.2.7) - clsx: 2.1.1 - react: 19.2.7 - react-dom: 19.2.7(react@19.2.7) - - '@rc-component/form@1.8.6(react-dom@19.2.7(react@19.2.7))(react@19.2.7)': - dependencies: - '@rc-component/async-validator': 6.0.0 - '@rc-component/util': 1.12.0(react-dom@19.2.7(react@19.2.7))(react@19.2.7) - clsx: 2.1.1 - react: 19.2.7 - react-dom: 19.2.7(react@19.2.7) - - '@rc-component/image@1.10.0(react-dom@19.2.7(react@19.2.7))(react@19.2.7)': - dependencies: - '@rc-component/motion': 1.3.3(react-dom@19.2.7(react@19.2.7))(react@19.2.7) - '@rc-component/portal': 2.2.1(react-dom@19.2.7(react@19.2.7))(react@19.2.7) - '@rc-component/util': 1.12.0(react-dom@19.2.7(react@19.2.7))(react@19.2.7) - clsx: 2.1.1 - react: 19.2.7 - react-dom: 19.2.7(react@19.2.7) - - '@rc-component/input-number@1.6.2(react-dom@19.2.7(react@19.2.7))(react@19.2.7)': - dependencies: - '@rc-component/mini-decimal': 1.1.4 - '@rc-component/util': 1.12.0(react-dom@19.2.7(react@19.2.7))(react@19.2.7) - clsx: 2.1.1 - react: 19.2.7 - react-dom: 19.2.7(react@19.2.7) - - '@rc-component/input@1.3.1(react-dom@19.2.7(react@19.2.7))(react@19.2.7)': - dependencies: - '@rc-component/resize-observer': 1.1.2(react-dom@19.2.7(react@19.2.7))(react@19.2.7) - '@rc-component/util': 1.12.0(react-dom@19.2.7(react@19.2.7))(react@19.2.7) - clsx: 2.1.1 - react: 19.2.7 - react-dom: 19.2.7(react@19.2.7) - - '@rc-component/listy@1.2.3(react-dom@19.2.7(react@19.2.7))(react@19.2.7)': - dependencies: - '@rc-component/motion': 1.3.3(react-dom@19.2.7(react@19.2.7))(react@19.2.7) - '@rc-component/portal': 2.2.1(react-dom@19.2.7(react@19.2.7))(react@19.2.7) - '@rc-component/resize-observer': 1.1.2(react-dom@19.2.7(react@19.2.7))(react@19.2.7) - '@rc-component/util': 1.12.0(react-dom@19.2.7(react@19.2.7))(react@19.2.7) - '@rc-component/virtual-list': 1.5.1(react-dom@19.2.7(react@19.2.7))(react@19.2.7) - clsx: 2.1.1 - react: 19.2.7 - react-dom: 19.2.7(react@19.2.7) - - '@rc-component/mentions@1.11.0(react-dom@19.2.7(react@19.2.7))(react@19.2.7)': - dependencies: - '@rc-component/input': 1.3.1(react-dom@19.2.7(react@19.2.7))(react@19.2.7) - '@rc-component/menu': 1.4.1(react-dom@19.2.7(react@19.2.7))(react@19.2.7) - '@rc-component/trigger': 3.10.1(react-dom@19.2.7(react@19.2.7))(react@19.2.7) - '@rc-component/util': 1.12.0(react-dom@19.2.7(react@19.2.7))(react@19.2.7) - clsx: 2.1.1 - react: 19.2.7 - react-dom: 19.2.7(react@19.2.7) - - '@rc-component/menu@1.4.1(react-dom@19.2.7(react@19.2.7))(react@19.2.7)': - dependencies: - '@rc-component/motion': 1.3.3(react-dom@19.2.7(react@19.2.7))(react@19.2.7) - '@rc-component/overflow': 1.0.1(react-dom@19.2.7(react@19.2.7))(react@19.2.7) - '@rc-component/trigger': 3.10.1(react-dom@19.2.7(react@19.2.7))(react@19.2.7) - '@rc-component/util': 1.12.0(react-dom@19.2.7(react@19.2.7))(react@19.2.7) - clsx: 2.1.1 - react: 19.2.7 - react-dom: 19.2.7(react@19.2.7) - - '@rc-component/mini-decimal@1.1.4': - dependencies: - '@babel/runtime': 7.29.7 - - '@rc-component/motion@1.3.3(react-dom@19.2.7(react@19.2.7))(react@19.2.7)': - dependencies: - '@rc-component/util': 1.12.0(react-dom@19.2.7(react@19.2.7))(react@19.2.7) - clsx: 2.1.1 - react: 19.2.7 - react-dom: 19.2.7(react@19.2.7) - - '@rc-component/mutate-observer@2.0.1(react-dom@19.2.7(react@19.2.7))(react@19.2.7)': - dependencies: - '@rc-component/util': 1.12.0(react-dom@19.2.7(react@19.2.7))(react@19.2.7) - react: 19.2.7 - react-dom: 19.2.7(react@19.2.7) - - '@rc-component/notification@2.0.7(react-dom@19.2.7(react@19.2.7))(react@19.2.7)': - dependencies: - '@rc-component/motion': 1.3.3(react-dom@19.2.7(react@19.2.7))(react@19.2.7) - '@rc-component/util': 1.12.0(react-dom@19.2.7(react@19.2.7))(react@19.2.7) - clsx: 2.1.1 - react: 19.2.7 - react-dom: 19.2.7(react@19.2.7) - - '@rc-component/overflow@1.0.1(react-dom@19.2.7(react@19.2.7))(react@19.2.7)': - dependencies: - '@babel/runtime': 7.29.7 - '@rc-component/resize-observer': 1.1.2(react-dom@19.2.7(react@19.2.7))(react@19.2.7) - '@rc-component/util': 1.12.0(react-dom@19.2.7(react@19.2.7))(react@19.2.7) - clsx: 2.1.1 - react: 19.2.7 - react-dom: 19.2.7(react@19.2.7) - - '@rc-component/pagination@1.4.0(react-dom@19.2.7(react@19.2.7))(react@19.2.7)': - dependencies: - '@rc-component/util': 1.12.0(react-dom@19.2.7(react@19.2.7))(react@19.2.7) - clsx: 2.1.1 - react: 19.2.7 - react-dom: 19.2.7(react@19.2.7) - - '@rc-component/picker@1.12.0(dayjs@1.11.21)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)': - dependencies: - '@rc-component/overflow': 1.0.1(react-dom@19.2.7(react@19.2.7))(react@19.2.7) - '@rc-component/resize-observer': 1.1.2(react-dom@19.2.7(react@19.2.7))(react@19.2.7) - '@rc-component/trigger': 3.10.1(react-dom@19.2.7(react@19.2.7))(react@19.2.7) - '@rc-component/util': 1.12.0(react-dom@19.2.7(react@19.2.7))(react@19.2.7) - clsx: 2.1.1 - react: 19.2.7 - react-dom: 19.2.7(react@19.2.7) - optionalDependencies: - dayjs: 1.11.21 - - '@rc-component/portal@1.1.0(react-dom@19.2.7(react@19.2.7))(react@19.2.7)': - dependencies: - '@babel/runtime': 7.29.7 - classnames: 2.5.1 - rc-util: 5.44.4(react-dom@19.2.7(react@19.2.7))(react@19.2.7) - react: 19.2.7 - react-dom: 19.2.7(react@19.2.7) - - '@rc-component/portal@2.2.1(react-dom@19.2.7(react@19.2.7))(react@19.2.7)': - dependencies: - '@rc-component/util': 1.12.0(react-dom@19.2.7(react@19.2.7))(react@19.2.7) - clsx: 2.1.1 - react: 19.2.7 - react-dom: 19.2.7(react@19.2.7) - - '@rc-component/progress@1.0.2(react-dom@19.2.7(react@19.2.7))(react@19.2.7)': - dependencies: - '@rc-component/util': 1.12.0(react-dom@19.2.7(react@19.2.7))(react@19.2.7) - clsx: 2.1.1 - react: 19.2.7 - react-dom: 19.2.7(react@19.2.7) - - '@rc-component/qrcode@2.0.0(react-dom@19.2.7(react@19.2.7))(react@19.2.7)': - dependencies: - '@babel/runtime': 7.29.7 - react: 19.2.7 - react-dom: 19.2.7(react@19.2.7) - - '@rc-component/rate@1.0.1(react-dom@19.2.7(react@19.2.7))(react@19.2.7)': - dependencies: - '@rc-component/util': 1.12.0(react-dom@19.2.7(react@19.2.7))(react@19.2.7) - clsx: 2.1.1 - react: 19.2.7 - react-dom: 19.2.7(react@19.2.7) - - '@rc-component/resize-observer@1.1.2(react-dom@19.2.7(react@19.2.7))(react@19.2.7)': - dependencies: - '@rc-component/util': 1.12.0(react-dom@19.2.7(react@19.2.7))(react@19.2.7) - react: 19.2.7 - react-dom: 19.2.7(react@19.2.7) - - '@rc-component/segmented@1.3.0(react-dom@19.2.7(react@19.2.7))(react@19.2.7)': - dependencies: - '@babel/runtime': 7.29.7 - '@rc-component/motion': 1.3.3(react-dom@19.2.7(react@19.2.7))(react@19.2.7) - '@rc-component/util': 1.12.0(react-dom@19.2.7(react@19.2.7))(react@19.2.7) - clsx: 2.1.1 - react: 19.2.7 - react-dom: 19.2.7(react@19.2.7) - - '@rc-component/select@1.10.1(react-dom@19.2.7(react@19.2.7))(react@19.2.7)': - dependencies: - '@rc-component/overflow': 1.0.1(react-dom@19.2.7(react@19.2.7))(react@19.2.7) - '@rc-component/trigger': 3.10.1(react-dom@19.2.7(react@19.2.7))(react@19.2.7) - '@rc-component/util': 1.12.0(react-dom@19.2.7(react@19.2.7))(react@19.2.7) - '@rc-component/virtual-list': 1.5.1(react-dom@19.2.7(react@19.2.7))(react@19.2.7) - clsx: 2.1.1 - react: 19.2.7 - react-dom: 19.2.7(react@19.2.7) - - '@rc-component/slider@1.1.1(react-dom@19.2.7(react@19.2.7))(react@19.2.7)': - dependencies: - '@rc-component/util': 1.12.0(react-dom@19.2.7(react@19.2.7))(react@19.2.7) - clsx: 2.1.1 - react: 19.2.7 - react-dom: 19.2.7(react@19.2.7) - - '@rc-component/steps@1.2.2(react-dom@19.2.7(react@19.2.7))(react@19.2.7)': - dependencies: - '@rc-component/util': 1.12.0(react-dom@19.2.7(react@19.2.7))(react@19.2.7) - clsx: 2.1.1 - react: 19.2.7 - react-dom: 19.2.7(react@19.2.7) - - '@rc-component/switch@1.0.3(react-dom@19.2.7(react@19.2.7))(react@19.2.7)': - dependencies: - '@rc-component/util': 1.12.0(react-dom@19.2.7(react@19.2.7))(react@19.2.7) - clsx: 2.1.1 - react: 19.2.7 - react-dom: 19.2.7(react@19.2.7) - - '@rc-component/table@1.11.1(react-dom@19.2.7(react@19.2.7))(react@19.2.7)': - dependencies: - '@rc-component/context': 2.0.2(react-dom@19.2.7(react@19.2.7))(react@19.2.7) - '@rc-component/resize-observer': 1.1.2(react-dom@19.2.7(react@19.2.7))(react@19.2.7) - '@rc-component/util': 1.12.0(react-dom@19.2.7(react@19.2.7))(react@19.2.7) - '@rc-component/virtual-list': 1.5.1(react-dom@19.2.7(react@19.2.7))(react@19.2.7) - clsx: 2.1.1 - react: 19.2.7 - react-dom: 19.2.7(react@19.2.7) - - '@rc-component/tabs@1.12.0(react-dom@19.2.7(react@19.2.7))(react@19.2.7)': - dependencies: - '@rc-component/dropdown': 1.0.3(react-dom@19.2.7(react@19.2.7))(react@19.2.7) - '@rc-component/menu': 1.4.1(react-dom@19.2.7(react@19.2.7))(react@19.2.7) - '@rc-component/motion': 1.3.3(react-dom@19.2.7(react@19.2.7))(react@19.2.7) - '@rc-component/resize-observer': 1.1.2(react-dom@19.2.7(react@19.2.7))(react@19.2.7) - '@rc-component/util': 1.12.0(react-dom@19.2.7(react@19.2.7))(react@19.2.7) - clsx: 2.1.1 - react: 19.2.7 - react-dom: 19.2.7(react@19.2.7) - - '@rc-component/tooltip@1.5.0(react-dom@19.2.7(react@19.2.7))(react@19.2.7)': - dependencies: - '@rc-component/trigger': 3.10.1(react-dom@19.2.7(react@19.2.7))(react@19.2.7) - '@rc-component/util': 1.12.0(react-dom@19.2.7(react@19.2.7))(react@19.2.7) - clsx: 2.1.1 - react: 19.2.7 - react-dom: 19.2.7(react@19.2.7) - - '@rc-component/tour@2.4.0(react-dom@19.2.7(react@19.2.7))(react@19.2.7)': - dependencies: - '@rc-component/portal': 2.2.1(react-dom@19.2.7(react@19.2.7))(react@19.2.7) - '@rc-component/trigger': 3.10.1(react-dom@19.2.7(react@19.2.7))(react@19.2.7) - '@rc-component/util': 1.12.0(react-dom@19.2.7(react@19.2.7))(react@19.2.7) - clsx: 2.1.1 - react: 19.2.7 - react-dom: 19.2.7(react@19.2.7) - - '@rc-component/tree-select@1.16.1(react-dom@19.2.7(react@19.2.7))(react@19.2.7)': - dependencies: - '@rc-component/select': 1.10.1(react-dom@19.2.7(react@19.2.7))(react@19.2.7) - '@rc-component/tree': 1.4.0(react-dom@19.2.7(react@19.2.7))(react@19.2.7) - '@rc-component/util': 1.12.0(react-dom@19.2.7(react@19.2.7))(react@19.2.7) - clsx: 2.1.1 - react: 19.2.7 - react-dom: 19.2.7(react@19.2.7) - - '@rc-component/tree@1.4.0(react-dom@19.2.7(react@19.2.7))(react@19.2.7)': - dependencies: - '@rc-component/motion': 1.3.3(react-dom@19.2.7(react@19.2.7))(react@19.2.7) - '@rc-component/util': 1.12.0(react-dom@19.2.7(react@19.2.7))(react@19.2.7) - '@rc-component/virtual-list': 1.5.1(react-dom@19.2.7(react@19.2.7))(react@19.2.7) - clsx: 2.1.1 - react: 19.2.7 - react-dom: 19.2.7(react@19.2.7) - - '@rc-component/trigger@2.3.1(react-dom@19.2.7(react@19.2.7))(react@19.2.7)': - dependencies: - '@babel/runtime': 7.29.7 - '@rc-component/portal': 1.1.0(react-dom@19.2.7(react@19.2.7))(react@19.2.7) - classnames: 2.5.1 - rc-motion: 2.9.5(react-dom@19.2.7(react@19.2.7))(react@19.2.7) - rc-resize-observer: 1.4.3(react-dom@19.2.7(react@19.2.7))(react@19.2.7) - rc-util: 5.44.4(react-dom@19.2.7(react@19.2.7))(react@19.2.7) - react: 19.2.7 - react-dom: 19.2.7(react@19.2.7) - - '@rc-component/trigger@3.10.1(react-dom@19.2.7(react@19.2.7))(react@19.2.7)': - dependencies: - '@rc-component/motion': 1.3.3(react-dom@19.2.7(react@19.2.7))(react@19.2.7) - '@rc-component/portal': 2.2.1(react-dom@19.2.7(react@19.2.7))(react@19.2.7) - '@rc-component/resize-observer': 1.1.2(react-dom@19.2.7(react@19.2.7))(react@19.2.7) - '@rc-component/util': 1.12.0(react-dom@19.2.7(react@19.2.7))(react@19.2.7) - clsx: 2.1.1 - react: 19.2.7 - react-dom: 19.2.7(react@19.2.7) - - '@rc-component/upload@1.1.1(react-dom@19.2.7(react@19.2.7))(react@19.2.7)': - dependencies: - '@rc-component/util': 1.12.0(react-dom@19.2.7(react@19.2.7))(react@19.2.7) - clsx: 2.1.1 - react: 19.2.7 - react-dom: 19.2.7(react@19.2.7) - - '@rc-component/util@1.12.0(react-dom@19.2.7(react@19.2.7))(react@19.2.7)': - dependencies: - is-mobile: 5.0.0 - react: 19.2.7 - react-dom: 19.2.7(react@19.2.7) - react-is: 19.2.8 - - '@rc-component/virtual-list@1.5.1(react-dom@19.2.7(react@19.2.7))(react@19.2.7)': - dependencies: - '@babel/runtime': 8.0.0 - '@rc-component/resize-observer': 1.1.2(react-dom@19.2.7(react@19.2.7))(react@19.2.7) - '@rc-component/util': 1.12.0(react-dom@19.2.7(react@19.2.7))(react@19.2.7) - clsx: 2.1.1 - react: 19.2.7 - react-dom: 19.2.7(react@19.2.7) - - '@react-three/drei@10.7.8(@react-three/fiber@9.7.0(@types/react@19.2.17)(immer@11.1.16)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)(three@0.185.1))(@types/react@19.2.17)(@types/three@0.185.4)(immer@11.1.16)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)(three@0.185.1)': - dependencies: - '@babel/runtime': 7.29.7 - '@mediapipe/tasks-vision': 0.10.17 - '@monogrid/gainmap-js': 3.4.0(three@0.185.1) - '@react-three/fiber': 9.7.0(@types/react@19.2.17)(immer@11.1.16)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)(three@0.185.1) - '@use-gesture/react': 10.3.1(react@19.2.7) - camera-controls: 3.1.2(three@0.185.1) - cross-env: 7.0.3 - detect-gpu: 5.0.70 - glsl-noise: 0.0.0 - hls.js: 1.7.0 - maath: 0.10.8(@types/three@0.185.4)(three@0.185.1) - meshline: 3.3.1(three@0.185.1) - react: 19.2.7 - stats-gl: 2.4.2(@types/three@0.185.4)(three@0.185.1) - stats.js: 0.17.0 - suspend-react: 0.1.3(react@19.2.7) - three: 0.185.1 - three-mesh-bvh: 0.8.3(three@0.185.1) - three-stdlib: 2.36.1(three@0.185.1) - troika-three-text: 0.52.5(three@0.185.1) - tunnel-rat: 0.1.2(@types/react@19.2.17)(immer@11.1.16)(react@19.2.7) - use-sync-external-store: 1.6.0(react@19.2.7) - utility-types: 3.11.0 - zustand: 5.0.15(@types/react@19.2.17)(immer@11.1.16)(react@19.2.7)(use-sync-external-store@1.6.0(react@19.2.7)) - optionalDependencies: - react-dom: 19.2.7(react@19.2.7) - transitivePeerDependencies: - - '@types/react' - - '@types/three' - - immer - - '@react-three/fiber@9.7.0(@types/react@19.2.17)(immer@11.1.16)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)(three@0.185.1)': - dependencies: - '@babel/runtime': 7.29.7 - '@types/webxr': 0.5.24 - base64-js: 1.5.1 - buffer: 6.0.3 - its-fine: 2.0.0(@types/react@19.2.17)(react@19.2.7) - react: 19.2.7 - react-use-measure: 2.1.7(react-dom@19.2.7(react@19.2.7))(react@19.2.7) - scheduler: 0.27.0 - suspend-react: 0.1.3(react@19.2.7) - three: 0.185.1 - use-sync-external-store: 1.6.0(react@19.2.7) - zustand: 5.0.15(@types/react@19.2.17)(immer@11.1.16)(react@19.2.7)(use-sync-external-store@1.6.0(react@19.2.7)) - optionalDependencies: - react-dom: 19.2.7(react@19.2.7) - transitivePeerDependencies: - - '@types/react' - - immer - - '@react-types/shared@3.36.1(react@19.2.7)': - dependencies: - react: 19.2.7 - - '@reduxjs/toolkit@2.12.0(react-redux@9.3.0(@types/react@19.2.17)(react@19.2.7)(redux@5.0.1))(react@19.2.7)': - dependencies: - '@standard-schema/spec': 1.1.0 - '@standard-schema/utils': 0.3.0 - immer: 11.1.16 - redux: 5.0.1 - redux-thunk: 3.1.0(redux@5.0.1) - reselect: 5.2.0 - optionalDependencies: - react: 19.2.7 - react-redux: 9.3.0(@types/react@19.2.17)(react@19.2.7)(redux@5.0.1) - - '@rolldown/binding-android-arm64@1.2.4': - optional: true - - '@rolldown/binding-darwin-arm64@1.2.4': - optional: true - - '@rolldown/binding-darwin-x64@1.2.4': - optional: true - - '@rolldown/binding-freebsd-x64@1.2.4': - optional: true - - '@rolldown/binding-linux-arm-gnueabihf@1.2.4': - optional: true - - '@rolldown/binding-linux-arm64-gnu@1.2.4': - optional: true - - '@rolldown/binding-linux-arm64-musl@1.2.4': - optional: true - - '@rolldown/binding-linux-ppc64-gnu@1.2.4': - optional: true - - '@rolldown/binding-linux-s390x-gnu@1.2.4': - optional: true - - '@rolldown/binding-linux-x64-gnu@1.2.4': - optional: true - - '@rolldown/binding-linux-x64-musl@1.2.4': - optional: true - - '@rolldown/binding-openharmony-arm64@1.2.4': - optional: true - - '@rolldown/binding-win32-arm64-msvc@1.2.4': - optional: true - - '@rolldown/binding-win32-x64-msvc@1.2.4': - optional: true - - '@rolldown/pluginutils@1.0.1': {} - - '@sec-ant/readable-stream@0.4.1': {} - - '@shikijs/core@3.23.0': - dependencies: - '@shikijs/types': 3.23.0 - '@shikijs/vscode-textmate': 10.0.2 - '@types/hast': 3.0.5 - hast-util-to-html: 9.0.5 - - '@shikijs/core@4.4.3': - dependencies: - '@shikijs/primitive': 4.4.3 - '@shikijs/types': 4.4.3 - '@shikijs/vscode-textmate': 10.0.2 - '@types/hast': 3.0.5 - hast-util-to-html: 9.0.5 - - '@shikijs/engine-javascript@3.23.0': - dependencies: - '@shikijs/types': 3.23.0 - '@shikijs/vscode-textmate': 10.0.2 - oniguruma-to-es: 4.3.6 - - '@shikijs/engine-javascript@4.4.3': - dependencies: - '@shikijs/types': 4.4.3 - '@shikijs/vscode-textmate': 10.0.2 - oniguruma-to-es: 4.3.6 - - '@shikijs/engine-oniguruma@3.23.0': - dependencies: - '@shikijs/types': 3.23.0 - '@shikijs/vscode-textmate': 10.0.2 - - '@shikijs/engine-oniguruma@4.4.3': - dependencies: - '@shikijs/types': 4.4.3 - '@shikijs/vscode-textmate': 10.0.2 - - '@shikijs/langs@3.23.0': - dependencies: - '@shikijs/types': 3.23.0 - - '@shikijs/langs@4.4.3': - dependencies: - '@shikijs/types': 4.4.3 - - '@shikijs/primitive@4.4.3': - dependencies: - '@shikijs/types': 4.4.3 - '@shikijs/vscode-textmate': 10.0.2 - '@types/hast': 3.0.5 - - '@shikijs/stream@4.4.3(react@19.2.7)': - dependencies: - '@shikijs/core': 4.4.3 - optionalDependencies: - react: 19.2.7 - - '@shikijs/themes@3.23.0': - dependencies: - '@shikijs/types': 3.23.0 - - '@shikijs/themes@4.4.3': - dependencies: - '@shikijs/types': 4.4.3 - - '@shikijs/transformers@4.4.3': - dependencies: - '@shikijs/core': 4.4.3 - '@shikijs/types': 4.4.3 - - '@shikijs/types@3.23.0': - dependencies: - '@shikijs/vscode-textmate': 10.0.2 - '@types/hast': 3.0.5 - - '@shikijs/types@4.4.3': - dependencies: - '@shikijs/vscode-textmate': 10.0.2 - '@types/hast': 3.0.5 - - '@shikijs/vscode-textmate@10.0.2': {} - - '@sindresorhus/merge-streams@4.0.0': {} - - '@splinetool/runtime@1.12.98': - dependencies: - on-change: 4.0.0 - semver-compare: 1.0.0 - - '@standard-schema/spec@1.1.0': {} - - '@standard-schema/utils@0.3.0': {} - - '@stitches/react@1.2.8(react@19.2.7)': - dependencies: - react: 19.2.7 - - '@streamdown/code@1.1.1(react@19.2.7)': - dependencies: - react: 19.2.7 - shiki: 3.23.0 - - '@swc/helpers@0.5.23': - dependencies: - tslib: 2.8.1 - - '@tailwindcss/node@4.3.3': - dependencies: - '@jridgewell/remapping': 2.3.5 - enhanced-resolve: 5.24.5 - jiti: 2.7.0 - lightningcss: 1.32.0 - magic-string: 0.30.21 - source-map-js: 1.2.1 - tailwindcss: 4.3.3 - - '@tailwindcss/oxide-android-arm64@4.3.3': - optional: true - - '@tailwindcss/oxide-darwin-arm64@4.3.3': - optional: true - - '@tailwindcss/oxide-darwin-x64@4.3.3': - optional: true - - '@tailwindcss/oxide-freebsd-x64@4.3.3': - optional: true - - '@tailwindcss/oxide-linux-arm-gnueabihf@4.3.3': - optional: true - - '@tailwindcss/oxide-linux-arm64-gnu@4.3.3': - optional: true - - '@tailwindcss/oxide-linux-arm64-musl@4.3.3': - optional: true - - '@tailwindcss/oxide-linux-x64-gnu@4.3.3': - optional: true - - '@tailwindcss/oxide-linux-x64-musl@4.3.3': - optional: true - - '@tailwindcss/oxide-wasm32-wasi@4.3.3': - optional: true - - '@tailwindcss/oxide-win32-arm64-msvc@4.3.3': - optional: true - - '@tailwindcss/oxide-win32-x64-msvc@4.3.3': - optional: true - - '@tailwindcss/oxide@4.3.3': - optionalDependencies: - '@tailwindcss/oxide-android-arm64': 4.3.3 - '@tailwindcss/oxide-darwin-arm64': 4.3.3 - '@tailwindcss/oxide-darwin-x64': 4.3.3 - '@tailwindcss/oxide-freebsd-x64': 4.3.3 - '@tailwindcss/oxide-linux-arm-gnueabihf': 4.3.3 - '@tailwindcss/oxide-linux-arm64-gnu': 4.3.3 - '@tailwindcss/oxide-linux-arm64-musl': 4.3.3 - '@tailwindcss/oxide-linux-x64-gnu': 4.3.3 - '@tailwindcss/oxide-linux-x64-musl': 4.3.3 - '@tailwindcss/oxide-wasm32-wasi': 4.3.3 - '@tailwindcss/oxide-win32-arm64-msvc': 4.3.3 - '@tailwindcss/oxide-win32-x64-msvc': 4.3.3 - - '@tailwindcss/postcss@4.3.3': - dependencies: - '@alloc/quick-lru': 5.2.0 - '@tailwindcss/node': 4.3.3 - '@tailwindcss/oxide': 4.3.3 - postcss: 8.5.26 - tailwindcss: 4.3.3 - - '@tanstack/query-core@5.101.4': {} - - '@tanstack/react-query@5.101.4(react@19.2.7)': - dependencies: - '@tanstack/query-core': 5.101.4 - react: 19.2.7 - - '@tanstack/react-virtual@3.14.9(react-dom@19.2.7(react@19.2.7))(react@19.2.7)': - dependencies: - '@tanstack/virtual-core': 3.17.7 - react: 19.2.7 - react-dom: 19.2.7(react@19.2.7) - - '@tanstack/virtual-core@3.17.7': {} - - '@tiptap/core@3.28.0(@tiptap/pm@3.28.0)': - dependencies: - '@tiptap/pm': 3.28.0 - - '@tiptap/extension-blockquote@3.28.0(@tiptap/core@3.28.0(@tiptap/pm@3.28.0))(@tiptap/pm@3.28.0)': - dependencies: - '@tiptap/core': 3.28.0(@tiptap/pm@3.28.0) - '@tiptap/pm': 3.28.0 - - '@tiptap/extension-bold@3.28.0(@tiptap/core@3.28.0(@tiptap/pm@3.28.0))': - dependencies: - '@tiptap/core': 3.28.0(@tiptap/pm@3.28.0) - - '@tiptap/extension-bubble-menu@3.28.0(@tiptap/core@3.28.0(@tiptap/pm@3.28.0))(@tiptap/pm@3.28.0)': - dependencies: - '@floating-ui/dom': 1.8.0 - '@tiptap/core': 3.28.0(@tiptap/pm@3.28.0) - '@tiptap/pm': 3.28.0 - optional: true - - '@tiptap/extension-bullet-list@3.28.0(@tiptap/extension-list@3.28.0(@tiptap/core@3.28.0(@tiptap/pm@3.28.0))(@tiptap/pm@3.28.0))': - dependencies: - '@tiptap/extension-list': 3.28.0(@tiptap/core@3.28.0(@tiptap/pm@3.28.0))(@tiptap/pm@3.28.0) - - '@tiptap/extension-character-count@3.28.0(@tiptap/extensions@3.28.0(@tiptap/core@3.28.0(@tiptap/pm@3.28.0))(@tiptap/pm@3.28.0))': - dependencies: - '@tiptap/extensions': 3.28.0(@tiptap/core@3.28.0(@tiptap/pm@3.28.0))(@tiptap/pm@3.28.0) - - '@tiptap/extension-code-block@3.28.0(@tiptap/core@3.28.0(@tiptap/pm@3.28.0))(@tiptap/pm@3.28.0)': - dependencies: - '@tiptap/core': 3.28.0(@tiptap/pm@3.28.0) - '@tiptap/pm': 3.28.0 - - '@tiptap/extension-code@3.28.0(@tiptap/core@3.28.0(@tiptap/pm@3.28.0))': - dependencies: - '@tiptap/core': 3.28.0(@tiptap/pm@3.28.0) - - '@tiptap/extension-color@3.28.0(@tiptap/extension-text-style@3.28.0(@tiptap/core@3.28.0(@tiptap/pm@3.28.0)))': - dependencies: - '@tiptap/extension-text-style': 3.28.0(@tiptap/core@3.28.0(@tiptap/pm@3.28.0)) - - '@tiptap/extension-document@3.28.0(@tiptap/core@3.28.0(@tiptap/pm@3.28.0))': - dependencies: - '@tiptap/core': 3.28.0(@tiptap/pm@3.28.0) - - '@tiptap/extension-dropcursor@3.28.0(@tiptap/extensions@3.28.0(@tiptap/core@3.28.0(@tiptap/pm@3.28.0))(@tiptap/pm@3.28.0))': - dependencies: - '@tiptap/extensions': 3.28.0(@tiptap/core@3.28.0(@tiptap/pm@3.28.0))(@tiptap/pm@3.28.0) - - '@tiptap/extension-floating-menu@3.28.0(@floating-ui/dom@1.8.0)(@tiptap/core@3.28.0(@tiptap/pm@3.28.0))(@tiptap/pm@3.28.0)': - dependencies: - '@floating-ui/dom': 1.8.0 - '@tiptap/core': 3.28.0(@tiptap/pm@3.28.0) - '@tiptap/pm': 3.28.0 - optional: true - - '@tiptap/extension-gapcursor@3.28.0(@tiptap/extensions@3.28.0(@tiptap/core@3.28.0(@tiptap/pm@3.28.0))(@tiptap/pm@3.28.0))': - dependencies: - '@tiptap/extensions': 3.28.0(@tiptap/core@3.28.0(@tiptap/pm@3.28.0))(@tiptap/pm@3.28.0) - - '@tiptap/extension-hard-break@3.28.0(@tiptap/core@3.28.0(@tiptap/pm@3.28.0))': - dependencies: - '@tiptap/core': 3.28.0(@tiptap/pm@3.28.0) - - '@tiptap/extension-heading@3.28.0(@tiptap/core@3.28.0(@tiptap/pm@3.28.0))': - dependencies: - '@tiptap/core': 3.28.0(@tiptap/pm@3.28.0) - - '@tiptap/extension-highlight@3.28.0(@tiptap/core@3.28.0(@tiptap/pm@3.28.0))': - dependencies: - '@tiptap/core': 3.28.0(@tiptap/pm@3.28.0) - - '@tiptap/extension-horizontal-rule@3.28.0(@tiptap/core@3.28.0(@tiptap/pm@3.28.0))(@tiptap/pm@3.28.0)': - dependencies: - '@tiptap/core': 3.28.0(@tiptap/pm@3.28.0) - '@tiptap/pm': 3.28.0 - - '@tiptap/extension-italic@3.28.0(@tiptap/core@3.28.0(@tiptap/pm@3.28.0))': - dependencies: - '@tiptap/core': 3.28.0(@tiptap/pm@3.28.0) - - '@tiptap/extension-link@3.28.0(@tiptap/core@3.28.0(@tiptap/pm@3.28.0))(@tiptap/pm@3.28.0)': - dependencies: - '@tiptap/core': 3.28.0(@tiptap/pm@3.28.0) - '@tiptap/pm': 3.28.0 - linkifyjs: 4.3.3 - - '@tiptap/extension-list-item@3.28.0(@tiptap/extension-list@3.28.0(@tiptap/core@3.28.0(@tiptap/pm@3.28.0))(@tiptap/pm@3.28.0))': - dependencies: - '@tiptap/extension-list': 3.28.0(@tiptap/core@3.28.0(@tiptap/pm@3.28.0))(@tiptap/pm@3.28.0) - - '@tiptap/extension-list-keymap@3.28.0(@tiptap/extension-list@3.28.0(@tiptap/core@3.28.0(@tiptap/pm@3.28.0))(@tiptap/pm@3.28.0))': - dependencies: - '@tiptap/extension-list': 3.28.0(@tiptap/core@3.28.0(@tiptap/pm@3.28.0))(@tiptap/pm@3.28.0) - - '@tiptap/extension-list@3.28.0(@tiptap/core@3.28.0(@tiptap/pm@3.28.0))(@tiptap/pm@3.28.0)': - dependencies: - '@tiptap/core': 3.28.0(@tiptap/pm@3.28.0) - '@tiptap/pm': 3.28.0 - - '@tiptap/extension-ordered-list@3.28.0(@tiptap/extension-list@3.28.0(@tiptap/core@3.28.0(@tiptap/pm@3.28.0))(@tiptap/pm@3.28.0))': - dependencies: - '@tiptap/extension-list': 3.28.0(@tiptap/core@3.28.0(@tiptap/pm@3.28.0))(@tiptap/pm@3.28.0) - - '@tiptap/extension-paragraph@3.28.0(@tiptap/core@3.28.0(@tiptap/pm@3.28.0))': - dependencies: - '@tiptap/core': 3.28.0(@tiptap/pm@3.28.0) - - '@tiptap/extension-placeholder@3.28.0(@tiptap/extensions@3.28.0(@tiptap/core@3.28.0(@tiptap/pm@3.28.0))(@tiptap/pm@3.28.0))': - dependencies: - '@tiptap/extensions': 3.28.0(@tiptap/core@3.28.0(@tiptap/pm@3.28.0))(@tiptap/pm@3.28.0) - - '@tiptap/extension-strike@3.28.0(@tiptap/core@3.28.0(@tiptap/pm@3.28.0))': - dependencies: - '@tiptap/core': 3.28.0(@tiptap/pm@3.28.0) - - '@tiptap/extension-text-align@3.28.0(@tiptap/core@3.28.0(@tiptap/pm@3.28.0))': - dependencies: - '@tiptap/core': 3.28.0(@tiptap/pm@3.28.0) - - '@tiptap/extension-text-style@3.28.0(@tiptap/core@3.28.0(@tiptap/pm@3.28.0))': - dependencies: - '@tiptap/core': 3.28.0(@tiptap/pm@3.28.0) - - '@tiptap/extension-text@3.28.0(@tiptap/core@3.28.0(@tiptap/pm@3.28.0))': - dependencies: - '@tiptap/core': 3.28.0(@tiptap/pm@3.28.0) - - '@tiptap/extension-underline@3.28.0(@tiptap/core@3.28.0(@tiptap/pm@3.28.0))': - dependencies: - '@tiptap/core': 3.28.0(@tiptap/pm@3.28.0) - - '@tiptap/extensions@3.28.0(@tiptap/core@3.28.0(@tiptap/pm@3.28.0))(@tiptap/pm@3.28.0)': - dependencies: - '@tiptap/core': 3.28.0(@tiptap/pm@3.28.0) - '@tiptap/pm': 3.28.0 - - '@tiptap/pm@3.28.0': - dependencies: - prosemirror-changeset: 2.4.1 - prosemirror-commands: 1.7.2 - prosemirror-dropcursor: 1.8.3 - prosemirror-gapcursor: 1.4.1 - prosemirror-history: 1.5.0 - prosemirror-inputrules: 1.5.1 - prosemirror-keymap: 1.2.3 - prosemirror-model: 1.25.11 - prosemirror-schema-list: 1.5.1 - prosemirror-state: 1.4.4 - prosemirror-tables: 1.8.5 - prosemirror-transform: 1.12.0 - prosemirror-view: 1.42.2 - - '@tiptap/react@3.28.0(@floating-ui/dom@1.8.0)(@tiptap/core@3.28.0(@tiptap/pm@3.28.0))(@tiptap/pm@3.28.0)(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)': - dependencies: - '@tiptap/core': 3.28.0(@tiptap/pm@3.28.0) - '@tiptap/pm': 3.28.0 - '@types/react': 19.2.17 - '@types/react-dom': 19.2.3(@types/react@19.2.17) - '@types/use-sync-external-store': 0.0.6 - fast-equals: 5.4.1 - react: 19.2.7 - react-dom: 19.2.7(react@19.2.7) - use-sync-external-store: 1.6.0(react@19.2.7) - optionalDependencies: - '@tiptap/extension-bubble-menu': 3.28.0(@tiptap/core@3.28.0(@tiptap/pm@3.28.0))(@tiptap/pm@3.28.0) - '@tiptap/extension-floating-menu': 3.28.0(@floating-ui/dom@1.8.0)(@tiptap/core@3.28.0(@tiptap/pm@3.28.0))(@tiptap/pm@3.28.0) - transitivePeerDependencies: - - '@floating-ui/dom' - - '@tiptap/starter-kit@3.28.0': - dependencies: - '@tiptap/core': 3.28.0(@tiptap/pm@3.28.0) - '@tiptap/extension-blockquote': 3.28.0(@tiptap/core@3.28.0(@tiptap/pm@3.28.0))(@tiptap/pm@3.28.0) - '@tiptap/extension-bold': 3.28.0(@tiptap/core@3.28.0(@tiptap/pm@3.28.0)) - '@tiptap/extension-bullet-list': 3.28.0(@tiptap/extension-list@3.28.0(@tiptap/core@3.28.0(@tiptap/pm@3.28.0))(@tiptap/pm@3.28.0)) - '@tiptap/extension-code': 3.28.0(@tiptap/core@3.28.0(@tiptap/pm@3.28.0)) - '@tiptap/extension-code-block': 3.28.0(@tiptap/core@3.28.0(@tiptap/pm@3.28.0))(@tiptap/pm@3.28.0) - '@tiptap/extension-document': 3.28.0(@tiptap/core@3.28.0(@tiptap/pm@3.28.0)) - '@tiptap/extension-dropcursor': 3.28.0(@tiptap/extensions@3.28.0(@tiptap/core@3.28.0(@tiptap/pm@3.28.0))(@tiptap/pm@3.28.0)) - '@tiptap/extension-gapcursor': 3.28.0(@tiptap/extensions@3.28.0(@tiptap/core@3.28.0(@tiptap/pm@3.28.0))(@tiptap/pm@3.28.0)) - '@tiptap/extension-hard-break': 3.28.0(@tiptap/core@3.28.0(@tiptap/pm@3.28.0)) - '@tiptap/extension-heading': 3.28.0(@tiptap/core@3.28.0(@tiptap/pm@3.28.0)) - '@tiptap/extension-horizontal-rule': 3.28.0(@tiptap/core@3.28.0(@tiptap/pm@3.28.0))(@tiptap/pm@3.28.0) - '@tiptap/extension-italic': 3.28.0(@tiptap/core@3.28.0(@tiptap/pm@3.28.0)) - '@tiptap/extension-link': 3.28.0(@tiptap/core@3.28.0(@tiptap/pm@3.28.0))(@tiptap/pm@3.28.0) - '@tiptap/extension-list': 3.28.0(@tiptap/core@3.28.0(@tiptap/pm@3.28.0))(@tiptap/pm@3.28.0) - '@tiptap/extension-list-item': 3.28.0(@tiptap/extension-list@3.28.0(@tiptap/core@3.28.0(@tiptap/pm@3.28.0))(@tiptap/pm@3.28.0)) - '@tiptap/extension-list-keymap': 3.28.0(@tiptap/extension-list@3.28.0(@tiptap/core@3.28.0(@tiptap/pm@3.28.0))(@tiptap/pm@3.28.0)) - '@tiptap/extension-ordered-list': 3.28.0(@tiptap/extension-list@3.28.0(@tiptap/core@3.28.0(@tiptap/pm@3.28.0))(@tiptap/pm@3.28.0)) - '@tiptap/extension-paragraph': 3.28.0(@tiptap/core@3.28.0(@tiptap/pm@3.28.0)) - '@tiptap/extension-strike': 3.28.0(@tiptap/core@3.28.0(@tiptap/pm@3.28.0)) - '@tiptap/extension-text': 3.28.0(@tiptap/core@3.28.0(@tiptap/pm@3.28.0)) - '@tiptap/extension-underline': 3.28.0(@tiptap/core@3.28.0(@tiptap/pm@3.28.0)) - '@tiptap/extensions': 3.28.0(@tiptap/core@3.28.0(@tiptap/pm@3.28.0))(@tiptap/pm@3.28.0) - '@tiptap/pm': 3.28.0 - - '@tldraw/driver@5.2.5(@floating-ui/dom@1.8.0)(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)': - dependencies: - '@tldraw/editor': 5.2.5(@floating-ui/dom@1.8.0)(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) - '@tldraw/utils': 5.2.5 - transitivePeerDependencies: - - '@floating-ui/dom' - - '@types/react' - - '@types/react-dom' - - react - - react-dom - - '@tldraw/editor@5.2.5(@floating-ui/dom@1.8.0)(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)': - dependencies: - '@tiptap/core': 3.28.0(@tiptap/pm@3.28.0) - '@tiptap/pm': 3.28.0 - '@tiptap/react': 3.28.0(@floating-ui/dom@1.8.0)(@tiptap/core@3.28.0(@tiptap/pm@3.28.0))(@tiptap/pm@3.28.0)(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) - '@tldraw/state': 5.2.5 - '@tldraw/state-react': 5.2.5(react-dom@19.2.7(react@19.2.7))(react@19.2.7) - '@tldraw/store': 5.2.5(react@19.2.7) - '@tldraw/tlschema': 5.2.5(react-dom@19.2.7(react@19.2.7))(react@19.2.7) - '@tldraw/utils': 5.2.5 - '@tldraw/validate': 5.2.5 - classnames: 2.5.1 - eventemitter3: 4.0.7 - idb: 7.1.1 - is-plain-object: 5.0.0 - rbush: 3.0.1 - react: 19.2.7 - react-dom: 19.2.7(react@19.2.7) - transitivePeerDependencies: - - '@floating-ui/dom' - - '@types/react' - - '@types/react-dom' - - '@tldraw/state-react@5.2.5(react-dom@19.2.7(react@19.2.7))(react@19.2.7)': - dependencies: - '@tldraw/state': 5.2.5 - '@tldraw/utils': 5.2.5 - react: 19.2.7 - react-dom: 19.2.7(react@19.2.7) - - '@tldraw/state@5.2.5': - dependencies: - '@tldraw/utils': 5.2.5 - - '@tldraw/store@5.2.5(react@19.2.7)': - dependencies: - '@tldraw/state': 5.2.5 - '@tldraw/utils': 5.2.5 - react: 19.2.7 - - '@tldraw/tlschema@5.2.5(react-dom@19.2.7(react@19.2.7))(react@19.2.7)': - dependencies: - '@tldraw/state': 5.2.5 - '@tldraw/store': 5.2.5(react@19.2.7) - '@tldraw/utils': 5.2.5 - '@tldraw/validate': 5.2.5 - react: 19.2.7 - react-dom: 19.2.7(react@19.2.7) - - '@tldraw/utils@5.2.5': - dependencies: - lodash.isequal: 4.5.0 - lodash.isequalwith: 4.4.0 - lodash.throttle: 4.1.1 - lodash.uniq: 4.5.0 - - '@tldraw/validate@5.2.5': - dependencies: - '@tldraw/utils': 5.2.5 - - '@ts-morph/common@0.27.0': - dependencies: - fast-glob: 3.3.3 - minimatch: 10.2.6 - path-browserify: 1.0.1 - - '@tweenjs/tween.js@23.1.3': {} - - '@types/d3-array@3.2.2': {} - - '@types/d3-axis@3.0.6': - dependencies: - '@types/d3-selection': 3.0.11 - - '@types/d3-brush@3.0.6': - dependencies: - '@types/d3-selection': 3.0.11 - - '@types/d3-chord@3.0.6': {} - - '@types/d3-color@3.1.3': {} - - '@types/d3-contour@3.0.6': - dependencies: - '@types/d3-array': 3.2.2 - '@types/geojson': 7946.0.16 - - '@types/d3-delaunay@6.0.4': {} - - '@types/d3-dispatch@3.0.7': {} - - '@types/d3-drag@3.0.7': - dependencies: - '@types/d3-selection': 3.0.11 - - '@types/d3-dsv@3.0.7': {} - - '@types/d3-ease@3.0.2': {} - - '@types/d3-fetch@3.0.7': - dependencies: - '@types/d3-dsv': 3.0.7 - - '@types/d3-force@3.0.10': {} - - '@types/d3-format@3.0.4': {} - - '@types/d3-geo@3.1.1': - dependencies: - '@types/geojson': 7946.0.16 - - '@types/d3-hierarchy@3.1.7': {} - - '@types/d3-interpolate@3.0.4': - dependencies: - '@types/d3-color': 3.1.3 - - '@types/d3-path@3.1.1': {} - - '@types/d3-polygon@3.0.2': {} - - '@types/d3-quadtree@3.0.6': {} - - '@types/d3-random@3.0.4': {} - - '@types/d3-scale-chromatic@3.1.0': {} - - '@types/d3-scale@4.0.9': - dependencies: - '@types/d3-time': 3.0.4 - - '@types/d3-selection@3.0.11': {} - - '@types/d3-shape@3.1.8': - dependencies: - '@types/d3-path': 3.1.1 - - '@types/d3-time-format@4.0.3': {} - - '@types/d3-time@3.0.4': {} - - '@types/d3-timer@3.0.2': {} - - '@types/d3-transition@3.0.9': - dependencies: - '@types/d3-selection': 3.0.11 - - '@types/d3-zoom@3.0.8': - dependencies: - '@types/d3-interpolate': 3.0.4 - '@types/d3-selection': 3.0.11 - - '@types/d3@7.4.3': - dependencies: - '@types/d3-array': 3.2.2 - '@types/d3-axis': 3.0.6 - '@types/d3-brush': 3.0.6 - '@types/d3-chord': 3.0.6 - '@types/d3-color': 3.1.3 - '@types/d3-contour': 3.0.6 - '@types/d3-delaunay': 6.0.4 - '@types/d3-dispatch': 3.0.7 - '@types/d3-drag': 3.0.7 - '@types/d3-dsv': 3.0.7 - '@types/d3-ease': 3.0.2 - '@types/d3-fetch': 3.0.7 - '@types/d3-force': 3.0.10 - '@types/d3-format': 3.0.4 - '@types/d3-geo': 3.1.1 - '@types/d3-hierarchy': 3.1.7 - '@types/d3-interpolate': 3.0.4 - '@types/d3-path': 3.1.1 - '@types/d3-polygon': 3.0.2 - '@types/d3-quadtree': 3.0.6 - '@types/d3-random': 3.0.4 - '@types/d3-scale': 4.0.9 - '@types/d3-scale-chromatic': 3.1.0 - '@types/d3-selection': 3.0.11 - '@types/d3-shape': 3.1.8 - '@types/d3-time': 3.0.4 - '@types/d3-time-format': 4.0.3 - '@types/d3-timer': 3.0.2 - '@types/d3-transition': 3.0.9 - '@types/d3-zoom': 3.0.8 - - '@types/debug@4.1.13': - dependencies: - '@types/ms': 2.1.0 - - '@types/draco3d@1.4.10': {} - - '@types/estree-jsx@1.0.5': - dependencies: - '@types/estree': 1.0.9 - - '@types/estree@1.0.9': {} - - '@types/file-saver@2.0.7': {} - - '@types/geojson@7946.0.16': {} - - '@types/hast@3.0.5': - dependencies: - '@types/unist': 3.0.3 - - '@types/js-cookie@3.0.6': {} - - '@types/katex@0.16.8': {} - - '@types/mdast@4.0.4': - dependencies: - '@types/unist': 3.0.3 - - '@types/mdx@2.0.14': {} - - '@types/ms@2.1.0': {} - - '@types/node@20.19.43': - dependencies: - undici-types: 6.21.0 - - '@types/offscreencanvas@2019.7.3': {} - - '@types/parse-json@4.0.2': {} - - '@types/prismjs@1.26.6': {} - - '@types/react-dom@19.2.3(@types/react@19.2.17)': - dependencies: - '@types/react': 19.2.17 - - '@types/react-reconciler@0.28.9(@types/react@19.2.17)': - dependencies: - '@types/react': 19.2.17 - - '@types/react@19.2.17': - dependencies: - csstype: 3.2.3 - - '@types/stats.js@0.17.4': {} - - '@types/three@0.185.4': - dependencies: - '@dimforge/rapier3d-compat': 0.12.0 - '@tweenjs/tween.js': 23.1.3 - '@types/stats.js': 0.17.4 - '@types/webxr': 0.5.24 - fflate: 0.8.3 - meshoptimizer: 1.1.1 - - '@types/trusted-types@2.0.7': {} - - '@types/unist@2.0.11': {} - - '@types/unist@3.0.3': {} - - '@types/use-sync-external-store@0.0.6': {} - - '@types/validate-npm-package-name@4.0.2': {} - - '@types/webxr@0.5.24': {} - - '@typescript/typescript-aix-ppc64@7.0.2': - optional: true - - '@typescript/typescript-darwin-arm64@7.0.2': - optional: true - - '@typescript/typescript-darwin-x64@7.0.2': - optional: true - - '@typescript/typescript-freebsd-arm64@7.0.2': - optional: true - - '@typescript/typescript-freebsd-x64@7.0.2': - optional: true - - '@typescript/typescript-linux-arm64@7.0.2': - optional: true - - '@typescript/typescript-linux-arm@7.0.2': - optional: true - - '@typescript/typescript-linux-loong64@7.0.2': - optional: true - - '@typescript/typescript-linux-mips64el@7.0.2': - optional: true - - '@typescript/typescript-linux-ppc64@7.0.2': - optional: true - - '@typescript/typescript-linux-riscv64@7.0.2': - optional: true - - '@typescript/typescript-linux-s390x@7.0.2': - optional: true - - '@typescript/typescript-linux-x64@7.0.2': - optional: true - - '@typescript/typescript-netbsd-arm64@7.0.2': - optional: true - - '@typescript/typescript-netbsd-x64@7.0.2': - optional: true - - '@typescript/typescript-openbsd-arm64@7.0.2': - optional: true - - '@typescript/typescript-openbsd-x64@7.0.2': - optional: true - - '@typescript/typescript-sunos-x64@7.0.2': - optional: true - - '@typescript/typescript-win32-arm64@7.0.2': - optional: true - - '@typescript/typescript-win32-x64@7.0.2': - optional: true - - '@uiw/codemirror-extensions-basic-setup@4.25.11(@codemirror/autocomplete@6.20.3)(@codemirror/commands@6.10.4)(@codemirror/language@6.12.4)(@codemirror/lint@6.9.7)(@codemirror/search@6.7.1)(@codemirror/state@6.7.1)(@codemirror/view@6.43.8)': - dependencies: - '@codemirror/autocomplete': 6.20.3 - '@codemirror/commands': 6.10.4 - '@codemirror/language': 6.12.4 - '@codemirror/lint': 6.9.7 - '@codemirror/search': 6.7.1 - '@codemirror/state': 6.7.1 - '@codemirror/view': 6.43.8 - - '@uiw/react-codemirror@4.25.11(@babel/runtime@8.0.0)(@codemirror/autocomplete@6.20.3)(@codemirror/language@6.12.4)(@codemirror/lint@6.9.7)(@codemirror/search@6.7.1)(@codemirror/state@6.7.1)(@codemirror/theme-one-dark@6.1.3)(@codemirror/view@6.43.8)(codemirror@6.0.2)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)': - dependencies: - '@babel/runtime': 8.0.0 - '@codemirror/commands': 6.10.4 - '@codemirror/state': 6.7.1 - '@codemirror/theme-one-dark': 6.1.3 - '@codemirror/view': 6.43.8 - '@uiw/codemirror-extensions-basic-setup': 4.25.11(@codemirror/autocomplete@6.20.3)(@codemirror/commands@6.10.4)(@codemirror/language@6.12.4)(@codemirror/lint@6.9.7)(@codemirror/search@6.7.1)(@codemirror/state@6.7.1)(@codemirror/view@6.43.8) - codemirror: 6.0.2 - react: 19.2.7 - react-dom: 19.2.7(react@19.2.7) - transitivePeerDependencies: - - '@codemirror/autocomplete' - - '@codemirror/language' - - '@codemirror/lint' - - '@codemirror/search' - - '@ungap/structured-clone@1.3.3': {} - - '@upsetjs/venn.js@2.0.0': - optionalDependencies: - d3-selection: 3.0.0 - d3-transition: 3.0.1(d3-selection@3.0.0) - - '@use-gesture/core@10.3.1': {} - - '@use-gesture/react@10.3.1(react@19.2.7)': - dependencies: - '@use-gesture/core': 10.3.1 - react: 19.2.7 - - '@vidstack/react@1.15.6(@types/react@19.2.17)(react@19.2.7)': - dependencies: - '@floating-ui/dom': 1.8.0 - '@types/react': 19.2.17 - media-captions: 1.0.4 - react: 19.2.7 - - '@vitejs/plugin-react@6.0.5(vite@8.2.1(@types/node@20.19.43)(jiti@2.7.0))': - dependencies: - '@rolldown/pluginutils': 1.0.1 - vite: 8.2.1(@types/node@20.19.43)(jiti@2.7.0) - - '@xmldom/xmldom@0.8.14': {} - - accepts@2.0.0: - dependencies: - mime-types: 3.0.2 - negotiator: 1.0.0 - - acorn-jsx@5.3.2(acorn@8.18.0): - dependencies: - acorn: 8.18.0 - - acorn@8.18.0: {} - - agent-base@6.0.2: - dependencies: - debug: 4.4.3 - transitivePeerDependencies: - - supports-color - - ahooks@3.9.7(react-dom@19.2.7(react@19.2.7))(react@19.2.7): - dependencies: - '@babel/runtime': 7.29.7 - '@types/js-cookie': 3.0.6 - dayjs: 1.11.21 - intersection-observer: 0.12.2 - js-cookie: 3.0.8 - lodash: 4.18.1 - react: 19.2.7 - react-dom: 19.2.7(react@19.2.7) - react-fast-compare: 3.2.2 - resize-observer-polyfill: 1.5.1 - screenfull: 5.2.0 - tslib: 2.8.1 - - ajv-formats@2.1.1(ajv@8.20.0): - optionalDependencies: - ajv: 8.20.0 - - ajv-formats@3.0.1(ajv@8.20.0): - optionalDependencies: - ajv: 8.20.0 - - ajv@8.20.0: - dependencies: - fast-deep-equal: 3.1.3 - fast-uri: 3.1.5 - json-schema-traverse: 1.0.0 - require-from-string: 2.0.2 - - ansi-colors@4.1.3: {} - - ansi-regex@5.0.1: {} - - ansi-regex@6.3.0: {} - - antd-style@4.1.0(@types/react@19.2.17)(antd@6.6.0(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(react-dom@19.2.7(react@19.2.7))(react@19.2.7): - dependencies: - '@ant-design/cssinjs': 2.1.2(react-dom@19.2.7(react@19.2.7))(react@19.2.7) - '@babel/runtime': 7.29.7 - '@emotion/cache': 11.14.0 - '@emotion/css': 11.13.5 - '@emotion/react': 11.14.0(@types/react@19.2.17)(react@19.2.7) - '@emotion/serialize': 1.3.3 - '@emotion/utils': 1.4.2 - antd: 6.6.0(react-dom@19.2.7(react@19.2.7))(react@19.2.7) - react: 19.2.7 - use-merge-value: 1.2.0(react@19.2.7) - transitivePeerDependencies: - - '@types/react' - - react-dom - - supports-color - - antd@6.6.0(react-dom@19.2.7(react@19.2.7))(react@19.2.7): - dependencies: - '@ant-design/colors': 8.0.1 - '@ant-design/cssinjs': 2.1.2(react-dom@19.2.7(react@19.2.7))(react@19.2.7) - '@ant-design/cssinjs-utils': 2.1.2(react-dom@19.2.7(react@19.2.7))(react@19.2.7) - '@ant-design/fast-color': 3.0.1 - '@ant-design/icons': 6.3.2(react-dom@19.2.7(react@19.2.7))(react@19.2.7) - '@ant-design/react-slick': 2.0.0(react-dom@19.2.7(react@19.2.7))(react@19.2.7) - '@babel/runtime': 7.29.7 - '@rc-component/cascader': 1.22.0(react-dom@19.2.7(react@19.2.7))(react@19.2.7) - '@rc-component/checkbox': 2.0.0(react-dom@19.2.7(react@19.2.7))(react@19.2.7) - '@rc-component/collapse': 1.2.0(react-dom@19.2.7(react@19.2.7))(react@19.2.7) - '@rc-component/color-picker': 3.1.1(react-dom@19.2.7(react@19.2.7))(react@19.2.7) - '@rc-component/dialog': 1.10.0(react-dom@19.2.7(react@19.2.7))(react@19.2.7) - '@rc-component/drawer': 1.4.2(react-dom@19.2.7(react@19.2.7))(react@19.2.7) - '@rc-component/dropdown': 1.0.3(react-dom@19.2.7(react@19.2.7))(react@19.2.7) - '@rc-component/form': 1.8.6(react-dom@19.2.7(react@19.2.7))(react@19.2.7) - '@rc-component/image': 1.10.0(react-dom@19.2.7(react@19.2.7))(react@19.2.7) - '@rc-component/input': 1.3.1(react-dom@19.2.7(react@19.2.7))(react@19.2.7) - '@rc-component/input-number': 1.6.2(react-dom@19.2.7(react@19.2.7))(react@19.2.7) - '@rc-component/listy': 1.2.3(react-dom@19.2.7(react@19.2.7))(react@19.2.7) - '@rc-component/mentions': 1.11.0(react-dom@19.2.7(react@19.2.7))(react@19.2.7) - '@rc-component/menu': 1.4.1(react-dom@19.2.7(react@19.2.7))(react@19.2.7) - '@rc-component/motion': 1.3.3(react-dom@19.2.7(react@19.2.7))(react@19.2.7) - '@rc-component/mutate-observer': 2.0.1(react-dom@19.2.7(react@19.2.7))(react@19.2.7) - '@rc-component/notification': 2.0.7(react-dom@19.2.7(react@19.2.7))(react@19.2.7) - '@rc-component/pagination': 1.4.0(react-dom@19.2.7(react@19.2.7))(react@19.2.7) - '@rc-component/picker': 1.12.0(dayjs@1.11.21)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) - '@rc-component/progress': 1.0.2(react-dom@19.2.7(react@19.2.7))(react@19.2.7) - '@rc-component/qrcode': 2.0.0(react-dom@19.2.7(react@19.2.7))(react@19.2.7) - '@rc-component/rate': 1.0.1(react-dom@19.2.7(react@19.2.7))(react@19.2.7) - '@rc-component/resize-observer': 1.1.2(react-dom@19.2.7(react@19.2.7))(react@19.2.7) - '@rc-component/segmented': 1.3.0(react-dom@19.2.7(react@19.2.7))(react@19.2.7) - '@rc-component/select': 1.10.1(react-dom@19.2.7(react@19.2.7))(react@19.2.7) - '@rc-component/slider': 1.1.1(react-dom@19.2.7(react@19.2.7))(react@19.2.7) - '@rc-component/steps': 1.2.2(react-dom@19.2.7(react@19.2.7))(react@19.2.7) - '@rc-component/switch': 1.0.3(react-dom@19.2.7(react@19.2.7))(react@19.2.7) - '@rc-component/table': 1.11.1(react-dom@19.2.7(react@19.2.7))(react@19.2.7) - '@rc-component/tabs': 1.12.0(react-dom@19.2.7(react@19.2.7))(react@19.2.7) - '@rc-component/tooltip': 1.5.0(react-dom@19.2.7(react@19.2.7))(react@19.2.7) - '@rc-component/tour': 2.4.0(react-dom@19.2.7(react@19.2.7))(react@19.2.7) - '@rc-component/tree': 1.4.0(react-dom@19.2.7(react@19.2.7))(react@19.2.7) - '@rc-component/tree-select': 1.16.1(react-dom@19.2.7(react@19.2.7))(react@19.2.7) - '@rc-component/trigger': 3.10.1(react-dom@19.2.7(react@19.2.7))(react@19.2.7) - '@rc-component/upload': 1.1.1(react-dom@19.2.7(react@19.2.7))(react@19.2.7) - '@rc-component/util': 1.12.0(react-dom@19.2.7(react@19.2.7))(react@19.2.7) - clsx: 2.1.1 - dayjs: 1.11.21 - react: 19.2.7 - react-dom: 19.2.7(react@19.2.7) - scroll-into-view-if-needed: 3.1.0 - throttle-debounce: 5.0.2 - transitivePeerDependencies: - - date-fns - - luxon - - moment - - anymatch@3.1.3: - dependencies: - normalize-path: 3.0.0 - picomatch: 2.3.2 - - argparse@1.0.10: - dependencies: - sprintf-js: 1.0.3 - - argparse@2.0.1: {} - - aria-hidden@1.2.6: - dependencies: - tslib: 2.8.1 - - assign-symbols@1.0.0: {} - - ast-types@0.16.1: - dependencies: - tslib: 2.8.1 - - astring@1.9.0: {} - - asynckit@0.4.0: {} - - atomically@1.7.0: {} - - attr-accept@2.2.2: {} - - axios@1.19.0: - dependencies: - follow-redirects: 1.16.0 - form-data: 4.0.6 - https-proxy-agent: 5.0.1 - proxy-from-env: 2.1.0 - transitivePeerDependencies: - - debug - - supports-color - - babel-plugin-macros@3.1.0: - dependencies: - '@babel/runtime': 7.29.7 - cosmiconfig: 7.0.0 - resolve: 1.22.12 - - bail@2.0.2: {} - - balanced-match@4.0.4: {} - - base64-js@1.5.1: {} - - baseline-browser-mapping@2.11.14: {} - - beautiful-mermaid@1.1.3: - dependencies: - elkjs: 0.11.1 - entities: 7.0.1 - - bidi-js@1.0.3: - dependencies: - require-from-string: 2.0.2 - - binary-extensions@2.3.0: {} - - bluebird@3.4.7: {} - - body-parser@2.3.0: - dependencies: - bytes: 3.1.2 - content-type: 2.1.0 - debug: 4.4.3 - http-errors: 2.0.1 - iconv-lite: 0.7.3 - on-finished: 2.4.1 - qs: 6.15.3 - raw-body: 3.0.2 - type-is: 2.1.0 - transitivePeerDependencies: - - supports-color - - brace-expansion@5.0.9: - dependencies: - balanced-match: 4.0.4 - - braces@3.0.3: - dependencies: - fill-range: 7.1.1 - - browser-fs-access@0.29.1: {} - - browserslist@4.28.8: - dependencies: - baseline-browser-mapping: 2.11.14 - caniuse-lite: 1.0.30001809 - electron-to-chromium: 1.5.406 - node-releases: 2.0.53 - update-browserslist-db: 1.3.1(browserslist@4.28.8) - - buffer@6.0.3: - dependencies: - base64-js: 1.5.1 - ieee754: 1.2.1 - - bundle-name@4.1.0: - dependencies: - run-applescript: 7.1.0 - - bytes@3.1.2: {} - - call-bind-apply-helpers@1.0.2: - dependencies: - es-errors: 1.3.0 - function-bind: 1.1.2 - - call-bound@1.0.4: - dependencies: - call-bind-apply-helpers: 1.0.2 - get-intrinsic: 1.3.0 - - callsites@3.1.0: {} - - camera-controls@3.1.2(three@0.185.1): - dependencies: - three: 0.185.1 - - caniuse-lite@1.0.30001809: {} - - canvas-roundrect-polyfill@0.0.1: {} - - ccount@2.0.1: {} - - chalk@5.6.2: {} - - character-entities-html4@2.1.0: {} - - character-entities-legacy@3.0.0: {} - - character-entities@2.0.2: {} - - character-reference-invalid@2.0.1: {} - - chevrotain-allstar@0.3.1(chevrotain@11.0.3): - dependencies: - chevrotain: 11.0.3 - lodash-es: 4.18.1 - - chevrotain@11.0.3: - dependencies: - '@chevrotain/cst-dts-gen': 11.0.3 - '@chevrotain/gast': 11.0.3 - '@chevrotain/regexp-to-ast': 11.0.3 - '@chevrotain/types': 11.0.3 - '@chevrotain/utils': 11.0.3 - lodash-es: 4.17.21 - - chokidar@3.6.0: - dependencies: - anymatch: 3.1.3 - braces: 3.0.3 - glob-parent: 5.1.2 - is-binary-path: 2.1.0 - is-glob: 4.0.3 - normalize-path: 3.0.0 - readdirp: 3.6.0 - optionalDependencies: - fsevents: 2.3.3 - - chroma-js@3.2.0: {} - - class-variance-authority@0.7.1: - dependencies: - clsx: 2.1.1 - - classnames@2.5.1: {} - - cli-cursor@5.0.0: - dependencies: - restore-cursor: 5.1.0 - - cli-spinners@2.9.2: {} - - client-only@0.0.1: {} - - clsx@1.1.1: {} - - clsx@2.1.1: {} - - code-block-writer@13.0.3: {} - - codemirror@6.0.2: - dependencies: - '@codemirror/autocomplete': 6.20.3 - '@codemirror/commands': 6.10.4 - '@codemirror/language': 6.12.4 - '@codemirror/lint': 6.9.7 - '@codemirror/search': 6.7.1 - '@codemirror/state': 6.7.1 - '@codemirror/view': 6.43.8 - - collapse-white-space@2.1.0: {} - - colord@2.9.5: {} - - combined-stream@1.0.8: - dependencies: - delayed-stream: 1.0.0 - - comma-separated-tokens@2.0.3: {} - - commander@11.1.0: {} - - commander@14.0.3: {} - - commander@7.2.0: {} - - commander@8.3.0: {} - - compute-scroll-into-view@3.1.1: {} - - conf@10.2.0: - dependencies: - ajv: 8.20.0 - ajv-formats: 2.1.1(ajv@8.20.0) - atomically: 1.7.0 - debounce-fn: 4.0.0 - dot-prop: 6.0.1 - env-paths: 2.2.1 - json-schema-typed: 7.0.3 - onetime: 5.1.2 - pkg-up: 3.1.0 - semver: 7.8.5 - - content-disposition@1.1.0: {} - - content-type@1.0.5: {} - - content-type@2.1.0: {} - - convert-source-map@1.9.0: {} - - convert-source-map@2.0.0: {} - - cookie-es@3.1.1: {} - - cookie-signature@1.2.2: {} - - cookie@0.7.2: {} - - copy-to-clipboard@4.0.2: {} - - core-util-is@1.0.3: {} - - cors@2.8.6: - dependencies: - object-assign: 4.1.1 - vary: 1.1.2 - - cose-base@1.0.3: - dependencies: - layout-base: 1.0.2 - - cose-base@2.2.0: - dependencies: - layout-base: 2.0.1 - - cosmiconfig@7.0.0: - dependencies: - '@types/parse-json': 4.0.2 - import-fresh: 3.3.1 - parse-json: 5.2.0 - path-type: 4.0.0 - yaml: 1.10.3 - - cosmiconfig@9.0.2(typescript@7.0.2): - dependencies: - env-paths: 2.2.1 - import-fresh: 3.3.1 - js-yaml: 4.3.1 - parse-json: 5.2.0 - optionalDependencies: - typescript: 7.0.2 - - crc-32@0.3.0: {} - - crelt@1.0.7: {} - - cross-env@7.0.3: - dependencies: - cross-spawn: 7.0.6 - - cross-spawn@7.0.6: - dependencies: - path-key: 3.1.1 - shebang-command: 2.0.0 - which: 2.0.2 - - cssesc@3.0.0: {} - - csstype@3.2.3: {} - - cytoscape-cose-bilkent@4.1.0(cytoscape@3.34.1): - dependencies: - cose-base: 1.0.3 - cytoscape: 3.34.1 - - cytoscape-fcose@2.2.0(cytoscape@3.34.1): - dependencies: - cose-base: 2.2.0 - cytoscape: 3.34.1 - - cytoscape@3.34.1: {} - - d3-array@2.12.1: - dependencies: - internmap: 1.0.1 - - d3-array@3.2.4: - dependencies: - internmap: 2.0.3 - - d3-axis@3.0.0: {} - - d3-brush@3.0.0: - dependencies: - d3-dispatch: 3.0.1 - d3-drag: 3.0.0 - d3-interpolate: 3.0.1 - d3-selection: 3.0.0 - d3-transition: 3.0.1(d3-selection@3.0.0) - - d3-chord@3.0.1: - dependencies: - d3-path: 3.1.0 - - d3-color@3.1.0: {} - - d3-contour@4.0.2: - dependencies: - d3-array: 3.2.4 - - d3-delaunay@6.0.4: - dependencies: - delaunator: 5.1.0 - - d3-dispatch@3.0.1: {} - - d3-drag@3.0.0: - dependencies: - d3-dispatch: 3.0.1 - d3-selection: 3.0.0 - - d3-dsv@3.0.1: - dependencies: - commander: 7.2.0 - iconv-lite: 0.6.3 - rw: 1.3.3 - - d3-ease@3.0.1: {} - - d3-fetch@3.0.1: - dependencies: - d3-dsv: 3.0.1 - - d3-force@3.0.0: - dependencies: - d3-dispatch: 3.0.1 - d3-quadtree: 3.0.1 - d3-timer: 3.0.1 - - d3-format@3.1.2: {} - - d3-geo@3.1.1: - dependencies: - d3-array: 3.2.4 - - d3-hierarchy@3.1.2: {} - - d3-interpolate@3.0.1: - dependencies: - d3-color: 3.1.0 - - d3-path@1.0.9: {} - - d3-path@3.1.0: {} - - d3-polygon@3.0.1: {} - - d3-quadtree@3.0.1: {} - - d3-random@3.0.1: {} - - d3-sankey@0.12.3: - dependencies: - d3-array: 2.12.1 - d3-shape: 1.3.7 - - d3-scale-chromatic@3.1.0: - dependencies: - d3-color: 3.1.0 - d3-interpolate: 3.0.1 - - d3-scale@4.0.2: - dependencies: - d3-array: 3.2.4 - d3-format: 3.1.2 - d3-interpolate: 3.0.1 - d3-time: 3.1.0 - d3-time-format: 4.1.0 - - d3-selection@3.0.0: {} - - d3-shape@1.3.7: - dependencies: - d3-path: 1.0.9 - - d3-shape@3.2.0: - dependencies: - d3-path: 3.1.0 - - d3-time-format@4.1.0: - dependencies: - d3-time: 3.1.0 - - d3-time@3.1.0: - dependencies: - d3-array: 3.2.4 - - d3-timer@3.0.1: {} - - d3-transition@3.0.1(d3-selection@3.0.0): - dependencies: - d3-color: 3.1.0 - d3-dispatch: 3.0.1 - d3-ease: 3.0.1 - d3-interpolate: 3.0.1 - d3-selection: 3.0.0 - d3-timer: 3.0.1 - - d3-zoom@3.0.0: - dependencies: - d3-dispatch: 3.0.1 - d3-drag: 3.0.0 - d3-interpolate: 3.0.1 - d3-selection: 3.0.0 - d3-transition: 3.0.1(d3-selection@3.0.0) - - d3@7.9.0: - dependencies: - d3-array: 3.2.4 - d3-axis: 3.0.0 - d3-brush: 3.0.0 - d3-chord: 3.0.1 - d3-color: 3.1.0 - d3-contour: 4.0.2 - d3-delaunay: 6.0.4 - d3-dispatch: 3.0.1 - d3-drag: 3.0.0 - d3-dsv: 3.0.1 - d3-ease: 3.0.1 - d3-fetch: 3.0.1 - d3-force: 3.0.0 - d3-format: 3.1.2 - d3-geo: 3.1.1 - d3-hierarchy: 3.1.2 - d3-interpolate: 3.0.1 - d3-path: 3.1.0 - d3-polygon: 3.0.1 - d3-quadtree: 3.0.1 - d3-random: 3.0.1 - d3-scale: 4.0.2 - d3-scale-chromatic: 3.1.0 - d3-selection: 3.0.0 - d3-shape: 3.2.0 - d3-time: 3.1.0 - d3-time-format: 4.1.0 - d3-timer: 3.0.1 - d3-transition: 3.0.1(d3-selection@3.0.0) - d3-zoom: 3.0.0 - - dagre-d3-es@7.0.14: - dependencies: - d3: 7.9.0 - lodash-es: 4.18.1 - - dayjs@1.11.21: {} - - debounce-fn@4.0.0: - dependencies: - mimic-fn: 3.1.0 - - debug@4.4.3: - dependencies: - ms: 2.1.3 - - decimal.js-light@2.5.1: {} - - decode-named-character-reference@1.3.0: - dependencies: - character-entities: 2.0.2 - - decode-uri-component@0.5.0: {} - - dedent@1.7.2(babel-plugin-macros@3.1.0): - optionalDependencies: - babel-plugin-macros: 3.1.0 - - deepmerge@4.3.1: {} - - default-browser-id@5.0.1: {} - - default-browser@5.5.0: - dependencies: - bundle-name: 4.1.0 - default-browser-id: 5.0.1 - - define-lazy-prop@2.0.0: {} - - define-lazy-prop@3.0.0: {} - - delaunator@5.1.0: - dependencies: - robust-predicates: 3.0.3 - - delayed-stream@1.0.0: {} - - depd@2.0.0: {} - - dequal@2.0.3: {} - - detect-gpu@5.0.70: - dependencies: - webgl-constants: 1.1.1 - - detect-libc@2.1.2: {} - - detect-node-es@1.1.0: {} - - devlop@1.1.0: - dependencies: - dequal: 2.0.3 - - diff@8.0.4: {} - - diff@9.0.0: {} - - dingbat-to-unicode@1.0.1: {} - - dompurify@3.4.13: - optionalDependencies: - '@types/trusted-types': 2.0.7 - - dot-prop@6.0.1: - dependencies: - is-obj: 2.0.0 - - dotenv@17.4.2: {} - - draco3d@1.5.7: {} - - duck@0.1.12: - dependencies: - underscore: 1.13.8 - - dunder-proto@1.0.1: - dependencies: - call-bind-apply-helpers: 1.0.2 - es-errors: 1.3.0 - gopd: 1.2.0 - - ee-first@1.1.1: {} - - electron-to-chromium@1.5.406: {} - - elkjs@0.11.1: {} - - emoji-mart@5.6.0: {} - - emoji-regex@10.6.0: {} - - encodeurl@2.0.0: {} - - enhanced-resolve@5.24.5: - dependencies: - graceful-fs: 4.2.11 - tapable: 2.3.3 - - enquirer@2.4.1: - dependencies: - ansi-colors: 4.1.3 - strip-ansi: 6.0.1 - - entities@6.0.1: {} - - entities@7.0.1: {} - - env-paths@2.2.1: {} - - error-ex@1.3.4: - dependencies: - is-arrayish: 0.2.1 - - es-define-property@1.0.1: {} - - es-errors@1.3.0: {} - - es-object-atoms@1.1.2: - dependencies: - es-errors: 1.3.0 - - es-set-tostringtag@2.1.0: - dependencies: - es-errors: 1.3.0 - get-intrinsic: 1.3.0 - has-tostringtag: 1.0.2 - hasown: 2.0.4 - - es-toolkit@1.50.0: {} - - es6-promise-pool@2.5.0: {} - - esast-util-from-estree@2.0.0: - dependencies: - '@types/estree-jsx': 1.0.5 - devlop: 1.1.0 - estree-util-visit: 2.0.0 - unist-util-position-from-estree: 2.0.0 - - esast-util-from-js@2.0.1: - dependencies: - '@types/estree-jsx': 1.0.5 - acorn: 8.18.0 - esast-util-from-estree: 2.0.0 - vfile-message: 4.0.3 - - escalade@3.2.0: {} - - escape-html@1.0.3: {} - - escape-string-regexp@4.0.0: {} - - escape-string-regexp@5.0.0: {} - - esprima@4.0.1: {} - - estree-util-attach-comments@3.0.0: - dependencies: - '@types/estree': 1.0.9 - - estree-util-build-jsx@3.0.1: - dependencies: - '@types/estree-jsx': 1.0.5 - devlop: 1.1.0 - estree-util-is-identifier-name: 3.0.0 - estree-walker: 3.0.3 - - estree-util-is-identifier-name@3.0.0: {} - - estree-util-scope@1.0.0: - dependencies: - '@types/estree': 1.0.9 - devlop: 1.1.0 - - estree-util-to-js@2.0.0: - dependencies: - '@types/estree-jsx': 1.0.5 - astring: 1.9.0 - source-map: 0.7.0 - - estree-util-visit@2.0.0: - dependencies: - '@types/estree-jsx': 1.0.5 - '@types/unist': 3.0.3 - - estree-walker@3.0.3: - dependencies: - '@types/estree': 1.0.9 - - etag@1.8.1: {} - - eventemitter3@4.0.7: {} - - eventemitter3@5.0.4: {} - - eventsource-parser@3.1.1: {} - - eventsource@3.0.7: - dependencies: - eventsource-parser: 3.1.1 - - execa@5.1.1: - dependencies: - cross-spawn: 7.0.6 - get-stream: 6.0.1 - human-signals: 2.1.0 - is-stream: 2.0.1 - merge-stream: 2.0.0 - npm-run-path: 4.0.1 - onetime: 5.1.2 - signal-exit: 3.0.7 - strip-final-newline: 2.0.0 - - execa@9.6.1: - dependencies: - '@sindresorhus/merge-streams': 4.0.0 - cross-spawn: 7.0.6 - figures: 6.1.0 - get-stream: 9.0.1 - human-signals: 8.0.1 - is-plain-obj: 4.1.0 - is-stream: 4.0.1 - npm-run-path: 6.0.0 - pretty-ms: 9.3.0 - signal-exit: 4.1.0 - strip-final-newline: 4.0.0 - yoctocolors: 2.2.0 - - express-rate-limit@8.6.2(express@5.2.1): - dependencies: - debug: 4.4.3 - express: 5.2.1 - ip-address: 10.5.0 - transitivePeerDependencies: - - supports-color - - express@5.2.1: - dependencies: - accepts: 2.0.0 - body-parser: 2.3.0 - content-disposition: 1.1.0 - content-type: 1.0.5 - cookie: 0.7.2 - cookie-signature: 1.2.2 - debug: 4.4.3 - depd: 2.0.0 - encodeurl: 2.0.0 - escape-html: 1.0.3 - etag: 1.8.1 - finalhandler: 2.1.1 - fresh: 2.0.0 - http-errors: 2.0.1 - merge-descriptors: 2.0.0 - mime-types: 3.0.2 - on-finished: 2.4.1 - once: 1.4.0 - parseurl: 1.3.3 - proxy-addr: 2.0.7 - qs: 6.15.3 - range-parser: 1.3.0 - router: 2.2.0 - send: 1.2.1 - serve-static: 2.2.1 - statuses: 2.0.2 - type-is: 2.1.0 - vary: 1.1.2 - transitivePeerDependencies: - - supports-color - - extend-shallow@2.0.1: - dependencies: - is-extendable: 0.1.1 - - extend-shallow@3.0.2: - dependencies: - assign-symbols: 1.0.0 - is-extendable: 1.0.1 - - extend@3.0.2: {} - - fast-deep-equal@3.1.3: {} - - fast-equals@5.4.1: {} - - fast-glob@3.3.3: - dependencies: - '@nodelib/fs.stat': 2.0.5 - '@nodelib/fs.walk': 1.2.8 - glob-parent: 5.1.2 - merge2: 1.4.1 - micromatch: 4.0.8 - - fast-uri@3.1.5: {} - - fastq@1.20.1: - dependencies: - reusify: 1.1.0 - - fault@1.0.4: - dependencies: - format: 0.2.2 - - fdir@6.5.0(picomatch@4.0.5): - optionalDependencies: - picomatch: 4.0.5 - - fflate@0.6.11: {} - - fflate@0.8.3: {} - - figures@6.1.0: - dependencies: - is-unicode-supported: 2.1.0 - - file-saver@2.0.5: {} - - file-selector@0.4.0: - dependencies: - tslib: 2.8.1 - - fill-range@7.1.1: - dependencies: - to-regex-range: 5.0.1 - - filter-obj@5.1.0: {} - - finalhandler@2.1.1: - dependencies: - debug: 4.4.3 - encodeurl: 2.0.0 - escape-html: 1.0.3 - on-finished: 2.4.1 - parseurl: 1.3.3 - statuses: 2.0.2 - transitivePeerDependencies: - - supports-color - - find-root@1.1.0: {} - - find-up@3.0.0: - dependencies: - locate-path: 3.0.0 - - follow-redirects@1.16.0: {} - - for-in@1.0.2: {} - - form-data@4.0.6: - dependencies: - asynckit: 0.4.0 - combined-stream: 1.0.8 - es-set-tostringtag: 2.1.0 - hasown: 2.0.4 - mime-types: 2.1.35 - - format@0.2.2: {} - - forwarded@0.2.0: {} - - fractional-indexing@3.2.0: {} - - framer-motion@12.43.0(@emotion/is-prop-valid@1.4.0)(react-dom@19.2.7(react@19.2.7))(react@19.2.7): - dependencies: - motion-dom: 12.43.0 - motion-utils: 12.39.0 - tslib: 2.8.1 - optionalDependencies: - '@emotion/is-prop-valid': 1.4.0 - react: 19.2.7 - react-dom: 19.2.7(react@19.2.7) - - fresh@2.0.0: {} - - fs-extra@11.4.0: - dependencies: - graceful-fs: 4.2.11 - jsonfile: 6.2.1 - universalify: 2.0.1 - - fsevents@2.3.3: - optional: true - - function-bind@1.1.2: {} - - fuzzy@0.1.3: {} - - fuzzysort@3.1.0: {} - - gensync@1.0.0-beta.2: {} - - get-east-asian-width@1.6.0: {} - - get-intrinsic@1.3.0: - dependencies: - call-bind-apply-helpers: 1.0.2 - es-define-property: 1.0.1 - es-errors: 1.3.0 - es-object-atoms: 1.1.2 - function-bind: 1.1.2 - get-proto: 1.0.1 - gopd: 1.2.0 - has-symbols: 1.1.0 - hasown: 2.0.4 - math-intrinsics: 1.1.0 - - get-nonce@1.0.1: {} - - get-own-enumerable-keys@1.0.0: {} - - get-proto@1.0.1: - dependencies: - dunder-proto: 1.0.1 - es-object-atoms: 1.1.2 - - get-stream@6.0.1: {} - - get-stream@9.0.1: - dependencies: - '@sec-ant/readable-stream': 0.4.1 - is-stream: 4.0.1 - - get-value@2.0.6: {} - - giscus@1.6.0: - dependencies: - lit: 3.3.3 - - glob-parent@5.1.2: - dependencies: - is-glob: 4.0.3 - - glsl-noise@0.0.0: {} - - glur@1.1.2: {} - - gopd@1.2.0: {} - - graceful-fs@4.2.11: {} - - hachure-fill@0.5.2: {} - - has-symbols@1.1.0: {} - - has-tostringtag@1.0.2: - dependencies: - has-symbols: 1.1.0 - - hasown@2.0.4: - dependencies: - function-bind: 1.1.2 - - hast-util-from-dom@5.0.1: - dependencies: - '@types/hast': 3.0.5 - hastscript: 9.0.1 - web-namespaces: 2.0.1 - - hast-util-from-html-isomorphic@2.0.0: - dependencies: - '@types/hast': 3.0.5 - hast-util-from-dom: 5.0.1 - hast-util-from-html: 2.0.3 - unist-util-remove-position: 5.0.0 - - hast-util-from-html@2.0.3: - dependencies: - '@types/hast': 3.0.5 - devlop: 1.1.0 - hast-util-from-parse5: 8.0.3 - parse5: 7.3.0 - vfile: 6.0.3 - vfile-message: 4.0.3 - - hast-util-from-parse5@8.0.3: - dependencies: - '@types/hast': 3.0.5 - '@types/unist': 3.0.3 - devlop: 1.1.0 - hastscript: 9.0.1 - property-information: 7.2.0 - vfile: 6.0.3 - vfile-location: 5.0.3 - web-namespaces: 2.0.1 - - hast-util-is-element@3.0.0: - dependencies: - '@types/hast': 3.0.5 - - hast-util-parse-selector@4.0.0: - dependencies: - '@types/hast': 3.0.5 - - hast-util-raw@9.1.0: - dependencies: - '@types/hast': 3.0.5 - '@types/unist': 3.0.3 - '@ungap/structured-clone': 1.3.3 - hast-util-from-parse5: 8.0.3 - hast-util-to-parse5: 8.0.1 - html-void-elements: 3.0.0 - mdast-util-to-hast: 13.2.1 - parse5: 7.3.0 - unist-util-position: 5.0.0 - unist-util-visit: 5.1.0 - vfile: 6.0.3 - web-namespaces: 2.0.1 - zwitch: 2.0.4 - - hast-util-sanitize@5.0.2: - dependencies: - '@types/hast': 3.0.5 - '@ungap/structured-clone': 1.3.3 - unist-util-position: 5.0.0 - - hast-util-to-estree@3.1.3: - dependencies: - '@types/estree': 1.0.9 - '@types/estree-jsx': 1.0.5 - '@types/hast': 3.0.5 - comma-separated-tokens: 2.0.3 - devlop: 1.1.0 - estree-util-attach-comments: 3.0.0 - estree-util-is-identifier-name: 3.0.0 - hast-util-whitespace: 3.0.0 - mdast-util-mdx-expression: 2.0.1 - mdast-util-mdx-jsx: 3.2.0 - mdast-util-mdxjs-esm: 2.0.1 - property-information: 7.2.0 - space-separated-tokens: 2.0.2 - style-to-js: 1.1.21 - unist-util-position: 5.0.0 - zwitch: 2.0.4 - transitivePeerDependencies: - - supports-color - - hast-util-to-html@9.0.5: - dependencies: - '@types/hast': 3.0.5 - '@types/unist': 3.0.3 - ccount: 2.0.1 - comma-separated-tokens: 2.0.3 - hast-util-whitespace: 3.0.0 - html-void-elements: 3.0.0 - mdast-util-to-hast: 13.2.1 - property-information: 7.2.0 - space-separated-tokens: 2.0.2 - stringify-entities: 4.0.4 - zwitch: 2.0.4 - - hast-util-to-jsx-runtime@2.3.6: - dependencies: - '@types/estree': 1.0.9 - '@types/hast': 3.0.5 - '@types/unist': 3.0.3 - comma-separated-tokens: 2.0.3 - devlop: 1.1.0 - estree-util-is-identifier-name: 3.0.0 - hast-util-whitespace: 3.0.0 - mdast-util-mdx-expression: 2.0.1 - mdast-util-mdx-jsx: 3.2.0 - mdast-util-mdxjs-esm: 2.0.1 - property-information: 7.2.0 - space-separated-tokens: 2.0.2 - style-to-js: 1.1.21 - unist-util-position: 5.0.0 - vfile-message: 4.0.3 - transitivePeerDependencies: - - supports-color - - hast-util-to-parse5@8.0.1: - dependencies: - '@types/hast': 3.0.5 - comma-separated-tokens: 2.0.3 - devlop: 1.1.0 - property-information: 7.2.0 - space-separated-tokens: 2.0.2 - web-namespaces: 2.0.1 - zwitch: 2.0.4 - - hast-util-to-text@4.0.2: - dependencies: - '@types/hast': 3.0.5 - '@types/unist': 3.0.3 - hast-util-is-element: 3.0.0 - unist-util-find-after: 5.0.0 - - hast-util-whitespace@3.0.0: - dependencies: - '@types/hast': 3.0.5 - - hastscript@9.0.1: - dependencies: - '@types/hast': 3.0.5 - comma-separated-tokens: 2.0.3 - hast-util-parse-selector: 4.0.0 - property-information: 7.2.0 - space-separated-tokens: 2.0.2 - - highlight.js@10.7.3: {} - - highlightjs-vue@1.0.0: {} - - hls.js@1.7.0: {} - - hoist-non-react-statics@3.3.2: - dependencies: - react-is: 16.7.0 - - hono@4.13.2: {} - - html-url-attributes@3.0.1: {} - - html-void-elements@3.0.0: {} - - http-errors@2.0.1: - dependencies: - depd: 2.0.0 - inherits: 2.0.4 - setprototypeof: 1.2.0 - statuses: 2.0.2 - toidentifier: 1.0.1 - - https-proxy-agent@5.0.1: - dependencies: - agent-base: 6.0.2 - debug: 4.4.3 - transitivePeerDependencies: - - supports-color - - human-signals@2.1.0: {} - - human-signals@8.0.1: {} - - iconv-lite@0.6.3: - dependencies: - safer-buffer: 2.1.2 - - iconv-lite@0.7.3: - dependencies: - safer-buffer: 2.1.2 - - idb@7.1.1: {} - - idb@8.0.3: {} - - ieee754@1.2.1: {} - - ignore@5.3.2: {} - - image-blob-reduce@3.0.1: - dependencies: - pica: 7.1.1 - - immediate@3.0.6: {} - - immer@11.1.16: {} - - immutable@4.3.9: {} - - import-fresh@3.3.1: - dependencies: - parent-module: 1.0.1 - resolve-from: 4.0.0 - - import-meta-resolve@4.2.0: {} - - inherits@2.0.4: {} - - inline-style-parser@0.2.7: {} - - internmap@1.0.1: {} - - internmap@2.0.3: {} - - intersection-observer@0.12.2: {} - - ip-address@10.5.0: {} - - ipaddr.js@1.9.1: {} - - is-alphabetical@2.0.1: {} - - is-alphanumerical@2.0.1: - dependencies: - is-alphabetical: 2.0.1 - is-decimal: 2.0.1 - - is-arrayish@0.2.1: {} - - is-binary-path@2.1.0: - dependencies: - binary-extensions: 2.3.0 - - is-core-module@2.16.2: - dependencies: - hasown: 2.0.4 - - is-decimal@2.0.1: {} - - is-docker@2.2.1: {} - - is-docker@3.0.0: {} - - is-extendable@0.1.1: {} - - is-extendable@1.0.1: - dependencies: - is-plain-object: 2.0.4 - - is-extglob@2.1.1: {} - - is-glob@4.0.3: - dependencies: - is-extglob: 2.1.1 - - is-hexadecimal@2.0.1: {} - - is-in-ssh@1.0.0: {} - - is-inside-container@1.0.0: - dependencies: - is-docker: 3.0.0 - - is-interactive@2.0.0: {} - - is-mobile@5.0.0: {} - - is-number@7.0.0: {} - - is-obj@2.0.0: {} - - is-obj@3.0.0: {} - - is-plain-obj@4.1.0: {} - - is-plain-object@2.0.4: - dependencies: - isobject: 3.0.1 - - is-plain-object@5.0.0: {} - - is-promise@2.2.2: {} - - is-promise@4.0.0: {} - - is-regexp@3.1.0: {} - - is-stream@2.0.1: {} - - is-stream@4.0.1: {} - - is-unicode-supported@1.3.0: {} - - is-unicode-supported@2.1.0: {} - - is-wsl@2.2.0: - dependencies: - is-docker: 2.2.1 - - is-wsl@3.1.1: - dependencies: - is-inside-container: 1.0.0 - - isarray@1.0.0: {} - - isexe@2.0.0: {} - - isexe@3.1.5: {} - - isobject@3.0.1: {} - - its-fine@2.0.0(@types/react@19.2.17)(react@19.2.7): - dependencies: - '@types/react-reconciler': 0.28.9(@types/react@19.2.17) - react: 19.2.7 - transitivePeerDependencies: - - '@types/react' - - jiti@2.7.0: {} - - jose@6.2.8: {} - - jotai-scope@0.7.2(jotai@2.11.0(@types/react@19.2.17)(react@19.2.7))(react@19.2.7): - dependencies: - jotai: 2.11.0(@types/react@19.2.17)(react@19.2.7) - react: 19.2.7 - - jotai@2.11.0(@types/react@19.2.17)(react@19.2.7): - optionalDependencies: - '@types/react': 19.2.17 - react: 19.2.7 - - js-cookie@3.0.8: {} - - js-tokens@4.0.0: {} - - js-yaml@4.3.1: - dependencies: - argparse: 2.0.1 - - jsesc@3.1.0: {} - - json-canonicalize@2.0.0: {} - - json-parse-even-better-errors@2.3.1: {} - - json-schema-traverse@1.0.0: {} - - json-schema-typed@7.0.3: {} - - json-schema-typed@8.0.2: {} - - json2mq@0.2.0: - dependencies: - string-convert: 0.2.1 - - json5@2.2.3: {} - - jsonfile@6.2.1: - dependencies: - universalify: 2.0.1 - optionalDependencies: - graceful-fs: 4.2.11 - - jszip@3.10.1: - dependencies: - lie: 3.3.0 - pako: 1.0.11 - readable-stream: 2.3.8 - setimmediate: 1.0.5 - - katex@0.16.47: - dependencies: - commander: 8.3.0 - - katex@0.18.4: - dependencies: - commander: 8.3.0 - - khroma@2.1.0: {} - - kleur@3.0.3: {} - - kleur@4.1.5: {} - - langium@3.3.1: - dependencies: - chevrotain: 11.0.3 - chevrotain-allstar: 0.3.1(chevrotain@11.0.3) - vscode-languageserver: 9.0.1 - vscode-languageserver-textdocument: 1.0.12 - vscode-uri: 3.0.8 - - layout-base@1.0.2: {} - - layout-base@2.0.1: {} - - leafer-ui@2.2.9: - dependencies: - '@leafer-in/interface': 2.2.9(@leafer-ui/interface@2.2.9)(@leafer/interface@2.2.9) - '@leafer-ui/core': 2.2.9 - '@leafer-ui/draw': 2.2.9 - '@leafer-ui/interaction-web': 2.2.9 - '@leafer-ui/interface': 2.2.9 - '@leafer-ui/partner': 2.2.9 - '@leafer-ui/web': 2.2.9 - '@leafer/core': 2.2.9 - '@leafer/interface': 2.2.9 - '@leafer/partner': 2.2.9 - - leva@0.10.1(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7): - dependencies: - '@radix-ui/react-portal': 1.1.17(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) - '@radix-ui/react-tooltip': 1.2.16(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) - '@stitches/react': 1.2.8(react@19.2.7) - '@use-gesture/react': 10.3.1(react@19.2.7) - colord: 2.9.5 - dequal: 2.0.3 - merge-value: 1.0.0 - react: 19.2.7 - react-colorful: 5.8.0(react-dom@19.2.7(react@19.2.7))(react@19.2.7) - react-dom: 19.2.7(react@19.2.7) - react-dropzone: 12.0.0(react@19.2.7) - v8n: 1.5.1 - zustand: 3.6.9(react@19.2.7) - transitivePeerDependencies: - - '@types/react' - - '@types/react-dom' - - lie@3.1.1: - dependencies: - immediate: 3.0.6 - - lie@3.3.0: - dependencies: - immediate: 3.0.6 - - lightningcss-android-arm64@1.32.0: - optional: true - - lightningcss-android-arm64@1.33.0: - optional: true - - lightningcss-darwin-arm64@1.32.0: - optional: true - - lightningcss-darwin-arm64@1.33.0: - optional: true - - lightningcss-darwin-x64@1.32.0: - optional: true - - lightningcss-darwin-x64@1.33.0: - optional: true - - lightningcss-freebsd-x64@1.32.0: - optional: true - - lightningcss-freebsd-x64@1.33.0: - optional: true - - lightningcss-linux-arm-gnueabihf@1.32.0: - optional: true - - lightningcss-linux-arm-gnueabihf@1.33.0: - optional: true - - lightningcss-linux-arm64-gnu@1.32.0: - optional: true - - lightningcss-linux-arm64-gnu@1.33.0: - optional: true - - lightningcss-linux-arm64-musl@1.32.0: - optional: true - - lightningcss-linux-arm64-musl@1.33.0: - optional: true - - lightningcss-linux-x64-gnu@1.32.0: - optional: true - - lightningcss-linux-x64-gnu@1.33.0: - optional: true - - lightningcss-linux-x64-musl@1.32.0: - optional: true - - lightningcss-linux-x64-musl@1.33.0: - optional: true - - lightningcss-win32-arm64-msvc@1.32.0: - optional: true - - lightningcss-win32-arm64-msvc@1.33.0: - optional: true - - lightningcss-win32-x64-msvc@1.32.0: - optional: true - - lightningcss-win32-x64-msvc@1.33.0: - optional: true - - lightningcss@1.32.0: - dependencies: - detect-libc: 2.1.2 - 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 - - lightningcss@1.33.0: - dependencies: - detect-libc: 2.1.2 - optionalDependencies: - lightningcss-android-arm64: 1.33.0 - lightningcss-darwin-arm64: 1.33.0 - lightningcss-darwin-x64: 1.33.0 - lightningcss-freebsd-x64: 1.33.0 - lightningcss-linux-arm-gnueabihf: 1.33.0 - lightningcss-linux-arm64-gnu: 1.33.0 - lightningcss-linux-arm64-musl: 1.33.0 - lightningcss-linux-x64-gnu: 1.33.0 - lightningcss-linux-x64-musl: 1.33.0 - lightningcss-win32-arm64-msvc: 1.33.0 - lightningcss-win32-x64-msvc: 1.33.0 - - lines-and-columns@1.2.4: {} - - linkifyjs@4.3.3: {} - - lit-element@4.2.2: - dependencies: - '@lit-labs/ssr-dom-shim': 1.6.0 - '@lit/reactive-element': 2.1.2 - lit-html: 3.3.3 - - lit-html@3.3.3: - dependencies: - '@types/trusted-types': 2.0.7 - - lit@3.3.3: - dependencies: - '@lit/reactive-element': 2.1.2 - lit-element: 4.2.2 - lit-html: 3.3.3 - - localforage@1.10.0: - dependencies: - lie: 3.1.1 - - locate-path@3.0.0: - dependencies: - p-locate: 3.0.0 - path-exists: 3.0.0 - - lodash-es@4.17.21: {} - - lodash-es@4.18.1: {} - - lodash.debounce@4.0.8: {} - - lodash.isequal@4.5.0: {} - - lodash.isequalwith@4.4.0: {} - - lodash.throttle@4.1.1: {} - - lodash.uniq@4.5.0: {} - - lodash@4.18.1: {} - - log-symbols@6.0.0: - dependencies: - chalk: 5.6.2 - is-unicode-supported: 1.3.0 - - longest-streak@3.1.0: {} - - loose-envify@1.4.0: - dependencies: - js-tokens: 4.0.0 - - lop@0.4.2: - dependencies: - duck: 0.1.12 - option: 0.2.4 - underscore: 1.13.8 - - lowlight@1.20.0: - dependencies: - fault: 1.0.4 - highlight.js: 10.7.3 - - lru-cache@5.1.1: - dependencies: - yallist: 3.1.1 - - lru_map@0.4.1: {} - - lucide-react@0.469.0(react@19.2.7): - dependencies: - react: 19.2.7 - - lucide-react@0.562.0(react@19.2.7): - dependencies: - react: 19.2.7 - - lucide-react@1.31.0(react@19.2.7): - dependencies: - react: 19.2.7 - - lz-string@1.5.0: {} - - maath@0.10.8(@types/three@0.185.4)(three@0.185.1): - dependencies: - '@types/three': 0.185.4 - three: 0.185.1 - - magic-string@0.30.21: - dependencies: - '@jridgewell/sourcemap-codec': 1.5.5 - - mammoth@1.12.1: - dependencies: - '@xmldom/xmldom': 0.8.14 - argparse: 1.0.10 - base64-js: 1.5.1 - bluebird: 3.4.7 - dingbat-to-unicode: 1.0.1 - jszip: 3.10.1 - lop: 0.4.2 - path-is-absolute: 1.0.1 - underscore: 1.13.8 - xmlbuilder: 10.1.1 - - markdown-extensions@2.0.0: {} - - markdown-table@3.0.4: {} - - marked@16.4.2: {} - - marked@17.0.6: {} - - marked@18.0.10: {} - - math-intrinsics@1.1.0: {} - - mdast-util-find-and-replace@3.0.2: - dependencies: - '@types/mdast': 4.0.4 - escape-string-regexp: 5.0.0 - unist-util-is: 6.0.1 - unist-util-visit-parents: 6.0.2 - - mdast-util-from-markdown@2.0.3: - dependencies: - '@types/mdast': 4.0.4 - '@types/unist': 3.0.3 - decode-named-character-reference: 1.3.0 - devlop: 1.1.0 - mdast-util-to-string: 4.0.0 - micromark: 4.0.2 - micromark-util-decode-numeric-character-reference: 2.0.2 - micromark-util-decode-string: 2.0.1 - micromark-util-normalize-identifier: 2.0.1 - micromark-util-symbol: 2.0.1 - micromark-util-types: 2.0.2 - unist-util-stringify-position: 4.0.0 - transitivePeerDependencies: - - supports-color - - mdast-util-gfm-autolink-literal@2.0.1: - dependencies: - '@types/mdast': 4.0.4 - ccount: 2.0.1 - devlop: 1.1.0 - mdast-util-find-and-replace: 3.0.2 - micromark-util-character: 2.1.1 - - mdast-util-gfm-footnote@2.1.0: - dependencies: - '@types/mdast': 4.0.4 - devlop: 1.1.0 - mdast-util-from-markdown: 2.0.3 - mdast-util-to-markdown: 2.1.2 - micromark-util-normalize-identifier: 2.0.1 - transitivePeerDependencies: - - supports-color - - mdast-util-gfm-strikethrough@2.0.0: - dependencies: - '@types/mdast': 4.0.4 - mdast-util-from-markdown: 2.0.3 - mdast-util-to-markdown: 2.1.2 - transitivePeerDependencies: - - supports-color - - mdast-util-gfm-table@2.0.0: - dependencies: - '@types/mdast': 4.0.4 - devlop: 1.1.0 - markdown-table: 3.0.4 - mdast-util-from-markdown: 2.0.3 - mdast-util-to-markdown: 2.1.2 - transitivePeerDependencies: - - supports-color - - mdast-util-gfm-task-list-item@2.0.0: - dependencies: - '@types/mdast': 4.0.4 - devlop: 1.1.0 - mdast-util-from-markdown: 2.0.3 - mdast-util-to-markdown: 2.1.2 - transitivePeerDependencies: - - supports-color - - mdast-util-gfm@3.1.0: - dependencies: - mdast-util-from-markdown: 2.0.3 - mdast-util-gfm-autolink-literal: 2.0.1 - mdast-util-gfm-footnote: 2.1.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.1.2 - transitivePeerDependencies: - - supports-color - - mdast-util-math@3.0.0: - dependencies: - '@types/hast': 3.0.5 - '@types/mdast': 4.0.4 - devlop: 1.1.0 - longest-streak: 3.1.0 - mdast-util-from-markdown: 2.0.3 - mdast-util-to-markdown: 2.1.2 - unist-util-remove-position: 5.0.0 - transitivePeerDependencies: - - supports-color - - mdast-util-mdx-expression@2.0.1: - dependencies: - '@types/estree-jsx': 1.0.5 - '@types/hast': 3.0.5 - '@types/mdast': 4.0.4 - devlop: 1.1.0 - mdast-util-from-markdown: 2.0.3 - mdast-util-to-markdown: 2.1.2 - transitivePeerDependencies: - - supports-color - - mdast-util-mdx-jsx@3.2.0: - dependencies: - '@types/estree-jsx': 1.0.5 - '@types/hast': 3.0.5 - '@types/mdast': 4.0.4 - '@types/unist': 3.0.3 - ccount: 2.0.1 - devlop: 1.1.0 - mdast-util-from-markdown: 2.0.3 - mdast-util-to-markdown: 2.1.2 - parse-entities: 4.0.2 - stringify-entities: 4.0.4 - unist-util-stringify-position: 4.0.0 - vfile-message: 4.0.3 - transitivePeerDependencies: - - supports-color - - mdast-util-mdx@3.0.0: - dependencies: - mdast-util-from-markdown: 2.0.3 - mdast-util-mdx-expression: 2.0.1 - mdast-util-mdx-jsx: 3.2.0 - mdast-util-mdxjs-esm: 2.0.1 - mdast-util-to-markdown: 2.1.2 - transitivePeerDependencies: - - supports-color - - mdast-util-mdxjs-esm@2.0.1: - dependencies: - '@types/estree-jsx': 1.0.5 - '@types/hast': 3.0.5 - '@types/mdast': 4.0.4 - devlop: 1.1.0 - mdast-util-from-markdown: 2.0.3 - mdast-util-to-markdown: 2.1.2 - transitivePeerDependencies: - - supports-color - - mdast-util-newline-to-break@2.0.0: - dependencies: - '@types/mdast': 4.0.4 - mdast-util-find-and-replace: 3.0.2 - - mdast-util-phrasing@4.1.0: - dependencies: - '@types/mdast': 4.0.4 - unist-util-is: 6.0.1 - - mdast-util-to-hast@13.2.1: - dependencies: - '@types/hast': 3.0.5 - '@types/mdast': 4.0.4 - '@ungap/structured-clone': 1.3.3 - devlop: 1.1.0 - micromark-util-sanitize-uri: 2.0.1 - trim-lines: 3.0.1 - unist-util-position: 5.0.0 - unist-util-visit: 5.1.0 - vfile: 6.0.3 - - mdast-util-to-markdown-cjk-friendly@1.0.0(@types/mdast@4.0.4)(micromark-util-types@2.0.2): - dependencies: - mdast-util-to-markdown: 2.1.2 - micromark-extension-cjk-friendly-util: 3.0.1(micromark-util-types@2.0.2) - micromark-util-symbol: 2.0.1 - optionalDependencies: - '@types/mdast': 4.0.4 - transitivePeerDependencies: - - micromark-util-types - - mdast-util-to-markdown@2.1.2: - dependencies: - '@types/mdast': 4.0.4 - '@types/unist': 3.0.3 - longest-streak: 3.1.0 - mdast-util-phrasing: 4.1.0 - mdast-util-to-string: 4.0.0 - micromark-util-classify-character: 2.0.1 - micromark-util-decode-string: 2.0.1 - unist-util-visit: 5.1.0 - zwitch: 2.0.4 - - mdast-util-to-string@4.0.0: - dependencies: - '@types/mdast': 4.0.4 - - media-captions@1.0.4: {} - - media-typer@1.1.1: {} - - merge-descriptors@2.0.0: {} - - merge-stream@2.0.0: {} - - merge-value@1.0.0: - dependencies: - get-value: 2.0.6 - is-extendable: 1.0.1 - mixin-deep: 1.3.2 - set-value: 2.0.1 - - merge2@1.4.1: {} - - mermaid@11.16.1: - dependencies: - '@braintree/sanitize-url': 7.1.2 - '@iconify/utils': 3.1.4 - '@mermaid-js/parser': 1.2.0 - '@types/d3': 7.4.3 - '@upsetjs/venn.js': 2.0.0 - cytoscape: 3.34.1 - cytoscape-cose-bilkent: 4.1.0(cytoscape@3.34.1) - cytoscape-fcose: 2.2.0(cytoscape@3.34.1) - d3: 7.9.0 - d3-sankey: 0.12.3 - dagre-d3-es: 7.0.14 - dayjs: 1.11.21 - dompurify: 3.4.13 - es-toolkit: 1.50.0 - katex: 0.16.47 - khroma: 2.1.0 - marked: 16.4.2 - roughjs: 4.6.6 - stylis: 4.4.0 - ts-dedent: 2.3.0 - uuid: 14.0.1 - - meshline@3.3.1(three@0.185.1): - dependencies: - three: 0.185.1 - - meshoptimizer@1.1.1: {} - - micromark-core-commonmark@2.0.3: - dependencies: - decode-named-character-reference: 1.3.0 - devlop: 1.1.0 - micromark-factory-destination: 2.0.1 - micromark-factory-label: 2.0.1 - micromark-factory-space: 2.0.1 - micromark-factory-title: 2.0.1 - micromark-factory-whitespace: 2.0.1 - micromark-util-character: 2.1.1 - micromark-util-chunked: 2.0.1 - micromark-util-classify-character: 2.0.1 - micromark-util-html-tag-name: 2.0.1 - micromark-util-normalize-identifier: 2.0.1 - micromark-util-resolve-all: 2.0.1 - micromark-util-subtokenize: 2.1.0 - micromark-util-symbol: 2.0.1 - micromark-util-types: 2.0.2 - - micromark-extension-cjk-friendly-util@3.0.1(micromark-util-types@2.0.2): - dependencies: - get-east-asian-width: 1.6.0 - micromark-util-character: 2.1.1 - micromark-util-symbol: 2.0.1 - optionalDependencies: - micromark-util-types: 2.0.2 - - micromark-extension-cjk-friendly@2.0.1(micromark-util-types@2.0.2)(micromark@4.0.2): - dependencies: - devlop: 1.1.0 - micromark: 4.0.2 - micromark-extension-cjk-friendly-util: 3.0.1(micromark-util-types@2.0.2) - micromark-util-chunked: 2.0.1 - micromark-util-resolve-all: 2.0.1 - micromark-util-symbol: 2.0.1 - optionalDependencies: - micromark-util-types: 2.0.2 - - micromark-extension-gfm-autolink-literal@2.1.0: - dependencies: - micromark-util-character: 2.1.1 - micromark-util-sanitize-uri: 2.0.1 - micromark-util-symbol: 2.0.1 - micromark-util-types: 2.0.2 - - micromark-extension-gfm-footnote@2.1.0: - dependencies: - devlop: 1.1.0 - micromark-core-commonmark: 2.0.3 - micromark-factory-space: 2.0.1 - micromark-util-character: 2.1.1 - micromark-util-normalize-identifier: 2.0.1 - micromark-util-sanitize-uri: 2.0.1 - micromark-util-symbol: 2.0.1 - micromark-util-types: 2.0.2 - - micromark-extension-gfm-strikethrough@2.1.0: - dependencies: - devlop: 1.1.0 - micromark-util-chunked: 2.0.1 - micromark-util-classify-character: 2.0.1 - micromark-util-resolve-all: 2.0.1 - micromark-util-symbol: 2.0.1 - micromark-util-types: 2.0.2 - - micromark-extension-gfm-table@2.1.1: - dependencies: - devlop: 1.1.0 - micromark-factory-space: 2.0.1 - micromark-util-character: 2.1.1 - micromark-util-symbol: 2.0.1 - micromark-util-types: 2.0.2 - - micromark-extension-gfm-tagfilter@2.0.0: - dependencies: - micromark-util-types: 2.0.2 - - micromark-extension-gfm-task-list-item@2.1.0: - dependencies: - devlop: 1.1.0 - micromark-factory-space: 2.0.1 - micromark-util-character: 2.1.1 - micromark-util-symbol: 2.0.1 - micromark-util-types: 2.0.2 - - micromark-extension-gfm@3.0.0: - dependencies: - micromark-extension-gfm-autolink-literal: 2.1.0 - micromark-extension-gfm-footnote: 2.1.0 - micromark-extension-gfm-strikethrough: 2.1.0 - micromark-extension-gfm-table: 2.1.1 - micromark-extension-gfm-tagfilter: 2.0.0 - micromark-extension-gfm-task-list-item: 2.1.0 - micromark-util-combine-extensions: 2.0.1 - micromark-util-types: 2.0.2 - - micromark-extension-math@3.1.0: - dependencies: - '@types/katex': 0.16.8 - devlop: 1.1.0 - katex: 0.16.47 - micromark-factory-space: 2.0.1 - micromark-util-character: 2.1.1 - micromark-util-symbol: 2.0.1 - micromark-util-types: 2.0.2 - - micromark-extension-mdx-expression@3.0.0: - dependencies: - '@types/estree': 1.0.9 - devlop: 1.1.0 - micromark-factory-mdx-expression: 2.0.0 - micromark-factory-space: 2.0.1 - micromark-util-character: 2.1.1 - micromark-util-events-to-acorn: 2.0.3 - micromark-util-symbol: 2.0.1 - micromark-util-types: 2.0.2 - - micromark-extension-mdx-jsx@3.0.2: - dependencies: - '@types/estree': 1.0.9 - devlop: 1.1.0 - estree-util-is-identifier-name: 3.0.0 - micromark-factory-mdx-expression: 2.0.0 - micromark-factory-space: 2.0.1 - micromark-util-character: 2.1.1 - micromark-util-events-to-acorn: 2.0.3 - micromark-util-symbol: 2.0.1 - micromark-util-types: 2.0.2 - vfile-message: 4.0.3 - - micromark-extension-mdx-md@2.0.0: - dependencies: - micromark-util-types: 2.0.2 - - micromark-extension-mdxjs-esm@3.0.0: - dependencies: - '@types/estree': 1.0.9 - devlop: 1.1.0 - micromark-core-commonmark: 2.0.3 - micromark-util-character: 2.1.1 - micromark-util-events-to-acorn: 2.0.3 - micromark-util-symbol: 2.0.1 - micromark-util-types: 2.0.2 - unist-util-position-from-estree: 2.0.0 - vfile-message: 4.0.3 - - micromark-extension-mdxjs@3.0.0: - dependencies: - acorn: 8.18.0 - acorn-jsx: 5.3.2(acorn@8.18.0) - micromark-extension-mdx-expression: 3.0.0 - micromark-extension-mdx-jsx: 3.0.2 - micromark-extension-mdx-md: 2.0.0 - micromark-extension-mdxjs-esm: 3.0.0 - micromark-util-combine-extensions: 2.0.1 - micromark-util-types: 2.0.2 - - micromark-factory-destination@2.0.1: - dependencies: - micromark-util-character: 2.1.1 - micromark-util-symbol: 2.0.1 - micromark-util-types: 2.0.2 - - micromark-factory-label@2.0.1: - dependencies: - devlop: 1.1.0 - micromark-util-character: 2.1.1 - micromark-util-symbol: 2.0.1 - micromark-util-types: 2.0.2 - - micromark-factory-mdx-expression@2.0.0: - dependencies: - '@types/estree': 1.0.9 - devlop: 1.1.0 - micromark-util-character: 2.1.1 - micromark-util-events-to-acorn: 2.0.3 - micromark-util-symbol: 2.0.1 - micromark-util-types: 2.0.2 - unist-util-position-from-estree: 1.1.2 - vfile-message: 4.0.3 - - micromark-factory-space@2.0.1: - dependencies: - micromark-util-character: 2.1.1 - micromark-util-types: 2.0.2 - - micromark-factory-title@2.0.1: - dependencies: - micromark-factory-space: 2.0.1 - micromark-util-character: 2.1.1 - micromark-util-symbol: 2.0.1 - micromark-util-types: 2.0.2 - - micromark-factory-whitespace@2.0.1: - dependencies: - micromark-factory-space: 2.0.1 - micromark-util-character: 2.1.1 - micromark-util-symbol: 2.0.1 - micromark-util-types: 2.0.2 - - micromark-util-character@2.1.1: - dependencies: - micromark-util-symbol: 2.0.1 - micromark-util-types: 2.0.2 - - micromark-util-chunked@2.0.1: - dependencies: - micromark-util-symbol: 2.0.1 - - micromark-util-classify-character@2.0.1: - dependencies: - micromark-util-character: 2.1.1 - micromark-util-symbol: 2.0.1 - micromark-util-types: 2.0.2 - - micromark-util-combine-extensions@2.0.1: - dependencies: - micromark-util-chunked: 2.0.1 - micromark-util-types: 2.0.2 - - micromark-util-decode-numeric-character-reference@2.0.2: - dependencies: - micromark-util-symbol: 2.0.1 - - micromark-util-decode-string@2.0.1: - dependencies: - decode-named-character-reference: 1.3.0 - micromark-util-character: 2.1.1 - micromark-util-decode-numeric-character-reference: 2.0.2 - micromark-util-symbol: 2.0.1 - - micromark-util-encode@2.0.1: {} - - micromark-util-events-to-acorn@2.0.3: - dependencies: - '@types/estree': 1.0.9 - '@types/unist': 3.0.3 - devlop: 1.1.0 - estree-util-visit: 2.0.0 - micromark-util-symbol: 2.0.1 - micromark-util-types: 2.0.2 - vfile-message: 4.0.3 - - micromark-util-html-tag-name@2.0.1: {} - - micromark-util-normalize-identifier@2.0.1: - dependencies: - micromark-util-symbol: 2.0.1 - - micromark-util-resolve-all@2.0.1: - dependencies: - micromark-util-types: 2.0.2 - - micromark-util-sanitize-uri@2.0.1: - dependencies: - micromark-util-character: 2.1.1 - micromark-util-encode: 2.0.1 - micromark-util-symbol: 2.0.1 - - micromark-util-subtokenize@2.1.0: - dependencies: - devlop: 1.1.0 - micromark-util-chunked: 2.0.1 - micromark-util-symbol: 2.0.1 - micromark-util-types: 2.0.2 - - micromark-util-symbol@2.0.1: {} - - micromark-util-types@2.0.2: {} - - micromark@4.0.2: - dependencies: - '@types/debug': 4.1.13 - debug: 4.4.3 - decode-named-character-reference: 1.3.0 - devlop: 1.1.0 - micromark-core-commonmark: 2.0.3 - micromark-factory-space: 2.0.1 - micromark-util-character: 2.1.1 - micromark-util-chunked: 2.0.1 - micromark-util-combine-extensions: 2.0.1 - micromark-util-decode-numeric-character-reference: 2.0.2 - micromark-util-encode: 2.0.1 - micromark-util-normalize-identifier: 2.0.1 - micromark-util-resolve-all: 2.0.1 - micromark-util-sanitize-uri: 2.0.1 - micromark-util-subtokenize: 2.1.0 - micromark-util-symbol: 2.0.1 - micromark-util-types: 2.0.2 - transitivePeerDependencies: - - supports-color - - micromatch@4.0.8: - dependencies: - braces: 3.0.3 - picomatch: 2.3.2 - - mime-db@1.52.0: {} - - mime-db@1.54.0: {} - - mime-types@2.1.35: - dependencies: - mime-db: 1.52.0 - - mime-types@3.0.2: - dependencies: - mime-db: 1.54.0 - - mimic-fn@2.1.0: {} - - mimic-fn@3.1.0: {} - - mimic-function@5.0.1: {} - - minimatch@10.2.6: - dependencies: - brace-expansion: 5.0.9 - - minimist@1.2.8: {} - - mixin-deep@1.3.2: - dependencies: - for-in: 1.0.2 - is-extendable: 1.0.1 - - motion-dom@12.43.0: - dependencies: - motion-utils: 12.39.0 - - motion-utils@12.39.0: {} - - motion@12.43.0(@emotion/is-prop-valid@1.4.0)(react-dom@19.2.7(react@19.2.7))(react@19.2.7): - dependencies: - framer-motion: 12.43.0(@emotion/is-prop-valid@1.4.0)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) - tslib: 2.8.1 - optionalDependencies: - '@emotion/is-prop-valid': 1.4.0 - react: 19.2.7 - react-dom: 19.2.7(react@19.2.7) - - ms@2.1.3: {} - - multimath@2.0.0: - dependencies: - glur: 1.1.2 - object-assign: 4.1.1 - - nanoid@3.3.18: {} - - nanoid@3.3.3: {} - - nanoid@4.0.2: {} - - nanoid@6.0.1: {} - - negotiator@1.0.0: {} - - node-releases@2.0.53: {} - - normalize-path@3.0.0: {} - - npm-run-path@4.0.1: - dependencies: - path-key: 3.1.1 - - npm-run-path@6.0.0: - dependencies: - path-key: 4.0.0 - unicorn-magic: 0.3.0 - - numeral@2.0.6: {} - - object-assign@4.1.1: {} - - object-inspect@1.13.4: {} - - object-treeify@1.1.33: {} - - on-change@4.0.0: {} - - on-finished@2.4.1: - dependencies: - ee-first: 1.1.1 - - once@1.4.0: - dependencies: - wrappy: 1.0.2 - - onetime@5.1.2: - dependencies: - mimic-fn: 2.1.0 - - onetime@7.0.0: - dependencies: - mimic-function: 5.0.1 - - oniguruma-parser@0.12.2: {} - - oniguruma-to-es@4.3.6: - dependencies: - oniguruma-parser: 0.12.2 - regex: 6.1.0 - regex-recursion: 6.0.2 - - open-color@1.9.1: {} - - open@11.0.1: - dependencies: - default-browser: 5.5.0 - define-lazy-prop: 3.0.0 - is-in-ssh: 1.0.0 - is-inside-container: 1.0.0 - powershell-utils: 0.2.0 - wsl-utils: 1.0.0 - - open@8.4.2: - dependencies: - define-lazy-prop: 2.0.0 - is-docker: 2.2.1 - is-wsl: 2.2.0 - - option@0.2.4: {} - - ora@8.2.0: - dependencies: - chalk: 5.6.2 - cli-cursor: 5.0.0 - cli-spinners: 2.9.2 - is-interactive: 2.0.0 - is-unicode-supported: 2.1.0 - log-symbols: 6.0.0 - stdin-discarder: 0.2.2 - string-width: 7.2.0 - strip-ansi: 7.2.0 - - orderedmap@2.1.1: {} - - p-limit@2.3.0: - dependencies: - p-try: 2.2.0 - - p-locate@3.0.0: - dependencies: - p-limit: 2.3.0 - - p-try@2.2.0: {} - - package-manager-detector@1.8.0: {} - - pako@1.0.11: {} - - pako@2.0.3: {} - - parent-module@1.0.1: - dependencies: - callsites: 3.1.0 - - parse-entities@4.0.2: - dependencies: - '@types/unist': 2.0.11 - character-entities-legacy: 3.0.0 - character-reference-invalid: 2.0.1 - decode-named-character-reference: 1.3.0 - is-alphanumerical: 2.0.1 - is-decimal: 2.0.1 - is-hexadecimal: 2.0.1 - - parse-json@5.2.0: - dependencies: - '@babel/code-frame': 7.29.7 - error-ex: 1.3.4 - json-parse-even-better-errors: 2.3.1 - lines-and-columns: 1.2.4 - - parse-ms@4.0.0: {} - - parse5@7.3.0: - dependencies: - entities: 6.0.1 - - parseurl@1.3.3: {} - - path-browserify@1.0.1: {} - - path-data-parser@0.1.0: {} - - path-exists@3.0.0: {} - - path-is-absolute@1.0.1: {} - - path-key@3.1.1: {} - - path-key@4.0.0: {} - - path-parse@1.0.7: {} - - path-to-regexp@8.4.2: {} - - path-type@4.0.0: {} - - perfect-freehand@1.2.0: {} - - pica@7.1.1: - dependencies: - glur: 1.1.2 - inherits: 2.0.4 - multimath: 2.0.0 - object-assign: 4.1.1 - webworkify: 1.5.0 - - picocolors@1.1.1: {} - - picomatch@2.3.2: {} - - picomatch@4.0.5: {} - - pkce-challenge@5.0.1: {} - - pkg-up@3.1.0: - dependencies: - find-up: 3.0.0 - - png-chunk-text@1.0.0: {} - - png-chunks-encode@1.0.0: - dependencies: - crc-32: 0.3.0 - sliced: 1.0.1 - - png-chunks-extract@1.0.0: - dependencies: - crc-32: 0.3.0 - - points-on-curve@0.2.0: {} - - points-on-curve@1.0.1: {} - - points-on-path@0.2.1: - dependencies: - path-data-parser: 0.1.0 - points-on-curve: 0.2.0 - - polished@4.3.1: - dependencies: - '@babel/runtime': 7.29.7 - - postcss-selector-parser@7.1.5: - dependencies: - cssesc: 3.0.0 - util-deprecate: 1.0.2 - - postcss@8.5.26: - dependencies: - nanoid: 3.3.18 - picocolors: 1.1.1 - source-map-js: 1.2.1 - - potpack@1.0.2: {} - - powershell-utils@0.1.0: {} - - powershell-utils@0.2.0: {} - - prettier@3.9.6: {} - - pretty-ms@9.3.0: - dependencies: - parse-ms: 4.0.0 - - prismjs@1.30.0: {} - - process-nextick-args@2.0.1: {} - - promise-worker-transferable@1.0.4: - dependencies: - is-promise: 2.2.2 - lie: 3.3.0 - - prompts@2.4.2: - dependencies: - kleur: 3.0.3 - sisteransi: 1.0.5 - - prop-types@15.8.1: - dependencies: - loose-envify: 1.4.0 - object-assign: 4.1.1 - react-is: 16.13.1 - - property-information@7.2.0: {} - - prosemirror-changeset@2.4.1: - dependencies: - prosemirror-transform: 1.12.0 - - prosemirror-commands@1.7.2: - dependencies: - prosemirror-model: 1.25.11 - prosemirror-state: 1.4.4 - prosemirror-transform: 1.12.0 - - prosemirror-dropcursor@1.8.3: - dependencies: - prosemirror-state: 1.4.4 - prosemirror-transform: 1.12.0 - prosemirror-view: 1.42.2 - - prosemirror-gapcursor@1.4.1: - dependencies: - prosemirror-keymap: 1.2.3 - prosemirror-model: 1.25.11 - prosemirror-state: 1.4.4 - prosemirror-view: 1.42.2 - - prosemirror-history@1.5.0: - dependencies: - prosemirror-state: 1.4.4 - prosemirror-transform: 1.12.0 - prosemirror-view: 1.42.2 - rope-sequence: 1.3.4 - - prosemirror-inputrules@1.5.1: - dependencies: - prosemirror-state: 1.4.4 - prosemirror-transform: 1.12.0 - - prosemirror-keymap@1.2.3: - dependencies: - prosemirror-state: 1.4.4 - w3c-keyname: 2.2.8 - - prosemirror-model@1.25.11: - dependencies: - orderedmap: 2.1.1 - - prosemirror-schema-list@1.5.1: - dependencies: - prosemirror-model: 1.25.11 - prosemirror-state: 1.4.4 - prosemirror-transform: 1.12.0 - - prosemirror-state@1.4.4: - dependencies: - prosemirror-model: 1.25.11 - prosemirror-transform: 1.12.0 - prosemirror-view: 1.42.2 - - prosemirror-tables@1.8.5: - dependencies: - prosemirror-keymap: 1.2.3 - prosemirror-model: 1.25.11 - prosemirror-state: 1.4.4 - prosemirror-transform: 1.12.0 - prosemirror-view: 1.42.2 - - prosemirror-transform@1.12.0: - dependencies: - prosemirror-model: 1.25.11 - - prosemirror-view@1.42.2: - dependencies: - prosemirror-model: 1.25.11 - prosemirror-state: 1.4.4 - prosemirror-transform: 1.12.0 - - proxy-addr@2.0.7: - dependencies: - forwarded: 0.2.0 - ipaddr.js: 1.9.1 - - proxy-from-env@2.1.0: {} - - pwacompat@2.0.17: {} - - qs@6.15.3: - dependencies: - es-define-property: 1.0.1 - side-channel: 1.1.1 - - query-string@9.5.0: - dependencies: - decode-uri-component: 0.5.0 - filter-obj: 5.1.0 - split-on-first: 3.0.0 - - queue-microtask@1.2.3: {} - - quickselect@2.0.0: {} - - radix-ui@1.6.7(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7): - dependencies: - '@radix-ui/primitive': 1.1.7 - '@radix-ui/react-accessible-icon': 1.1.15(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) - '@radix-ui/react-accordion': 1.2.20(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) - '@radix-ui/react-alert-dialog': 1.1.23(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) - '@radix-ui/react-arrow': 1.1.15(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) - '@radix-ui/react-aspect-ratio': 1.1.15(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) - '@radix-ui/react-avatar': 1.2.6(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) - '@radix-ui/react-checkbox': 1.3.11(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) - '@radix-ui/react-collapsible': 1.1.20(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) - '@radix-ui/react-collection': 1.1.15(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) - '@radix-ui/react-compose-refs': 1.1.5(@types/react@19.2.17)(react@19.2.7) - '@radix-ui/react-context': 1.2.2(@types/react@19.2.17)(react@19.2.7) - '@radix-ui/react-context-menu': 2.3.7(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) - '@radix-ui/react-dialog': 1.1.23(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) - '@radix-ui/react-direction': 1.1.4(@types/react@19.2.17)(react@19.2.7) - '@radix-ui/react-dismissable-layer': 1.1.19(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) - '@radix-ui/react-dropdown-menu': 2.1.24(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) - '@radix-ui/react-focus-guards': 1.1.6(@types/react@19.2.17)(react@19.2.7) - '@radix-ui/react-focus-scope': 1.1.16(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) - '@radix-ui/react-form': 0.1.16(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) - '@radix-ui/react-hover-card': 1.1.23(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) - '@radix-ui/react-label': 2.1.15(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) - '@radix-ui/react-menu': 2.1.24(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) - '@radix-ui/react-menubar': 1.1.24(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) - '@radix-ui/react-navigation-menu': 1.2.22(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) - '@radix-ui/react-one-time-password-field': 0.1.16(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) - '@radix-ui/react-password-toggle-field': 0.1.11(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) - '@radix-ui/react-popover': 1.1.23(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) - '@radix-ui/react-popper': 1.3.7(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) - '@radix-ui/react-portal': 1.1.17(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) - '@radix-ui/react-presence': 1.1.10(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) - '@radix-ui/react-primitive': 2.1.10(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) - '@radix-ui/react-progress': 1.1.16(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) - '@radix-ui/react-radio-group': 1.4.7(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) - '@radix-ui/react-roving-focus': 1.1.19(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) - '@radix-ui/react-scroll-area': 1.2.18(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) - '@radix-ui/react-select': 2.3.7(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) - '@radix-ui/react-separator': 1.1.15(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) - '@radix-ui/react-slider': 1.4.7(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) - '@radix-ui/react-slot': 1.3.3(@types/react@19.2.17)(react@19.2.7) - '@radix-ui/react-switch': 1.3.7(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) - '@radix-ui/react-tabs': 1.1.21(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) - '@radix-ui/react-toast': 1.2.23(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) - '@radix-ui/react-toggle': 1.1.18(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) - '@radix-ui/react-toggle-group': 1.1.19(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) - '@radix-ui/react-toolbar': 1.1.19(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) - '@radix-ui/react-tooltip': 1.2.16(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) - '@radix-ui/react-use-callback-ref': 1.1.4(@types/react@19.2.17)(react@19.2.7) - '@radix-ui/react-use-controllable-state': 1.2.6(@types/react@19.2.17)(react@19.2.7) - '@radix-ui/react-use-effect-event': 0.0.5(@types/react@19.2.17)(react@19.2.7) - '@radix-ui/react-use-escape-keydown': 1.1.5(@types/react@19.2.17)(react@19.2.7) - '@radix-ui/react-use-is-hydrated': 0.1.3(@types/react@19.2.17)(react@19.2.7) - '@radix-ui/react-use-layout-effect': 1.1.4(@types/react@19.2.17)(react@19.2.7) - '@radix-ui/react-use-size': 1.1.4(@types/react@19.2.17)(react@19.2.7) - '@radix-ui/react-visually-hidden': 1.2.11(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) - react: 19.2.7 - react-dom: 19.2.7(react@19.2.7) - optionalDependencies: - '@types/react': 19.2.17 - '@types/react-dom': 19.2.3(@types/react@19.2.17) - - range-parser@1.3.0: {} - - raw-body@3.0.2: - dependencies: - bytes: 3.1.2 - http-errors: 2.0.1 - iconv-lite: 0.7.3 - unpipe: 1.0.0 - - rbush@3.0.1: - dependencies: - quickselect: 2.0.0 - - rc-collapse@4.0.0(react-dom@19.2.7(react@19.2.7))(react@19.2.7): - dependencies: - '@babel/runtime': 7.29.7 - classnames: 2.5.1 - rc-motion: 2.9.5(react-dom@19.2.7(react@19.2.7))(react@19.2.7) - rc-util: 5.44.4(react-dom@19.2.7(react@19.2.7))(react@19.2.7) - react: 19.2.7 - react-dom: 19.2.7(react@19.2.7) - - rc-dialog@9.6.0(react-dom@19.2.7(react@19.2.7))(react@19.2.7): - dependencies: - '@babel/runtime': 7.29.7 - '@rc-component/portal': 1.1.0(react-dom@19.2.7(react@19.2.7))(react@19.2.7) - classnames: 2.5.1 - rc-motion: 2.9.5(react-dom@19.2.7(react@19.2.7))(react@19.2.7) - rc-util: 5.44.4(react-dom@19.2.7(react@19.2.7))(react@19.2.7) - react: 19.2.7 - react-dom: 19.2.7(react@19.2.7) - - rc-footer@0.6.8(react-dom@19.2.7(react@19.2.7))(react@19.2.7): - dependencies: - '@babel/runtime': 7.29.7 - classnames: 2.5.1 - react: 19.2.7 - react-dom: 19.2.7(react@19.2.7) - - rc-image@7.12.0(react-dom@19.2.7(react@19.2.7))(react@19.2.7): - dependencies: - '@babel/runtime': 7.29.7 - '@rc-component/portal': 1.1.0(react-dom@19.2.7(react@19.2.7))(react@19.2.7) - classnames: 2.5.1 - rc-dialog: 9.6.0(react-dom@19.2.7(react@19.2.7))(react@19.2.7) - rc-motion: 2.9.5(react-dom@19.2.7(react@19.2.7))(react@19.2.7) - rc-util: 5.44.4(react-dom@19.2.7(react@19.2.7))(react@19.2.7) - react: 19.2.7 - react-dom: 19.2.7(react@19.2.7) - - rc-input-number@9.5.0(react-dom@19.2.7(react@19.2.7))(react@19.2.7): - dependencies: - '@babel/runtime': 7.29.7 - '@rc-component/mini-decimal': 1.1.4 - classnames: 2.5.1 - rc-input: 1.8.0(react-dom@19.2.7(react@19.2.7))(react@19.2.7) - rc-util: 5.44.4(react-dom@19.2.7(react@19.2.7))(react@19.2.7) - react: 19.2.7 - react-dom: 19.2.7(react@19.2.7) - - rc-input@1.8.0(react-dom@19.2.7(react@19.2.7))(react@19.2.7): - dependencies: - '@babel/runtime': 7.29.7 - classnames: 2.5.1 - rc-util: 5.44.4(react-dom@19.2.7(react@19.2.7))(react@19.2.7) - react: 19.2.7 - react-dom: 19.2.7(react@19.2.7) - - rc-menu@9.16.1(react-dom@19.2.7(react@19.2.7))(react@19.2.7): - dependencies: - '@babel/runtime': 7.29.7 - '@rc-component/trigger': 2.3.1(react-dom@19.2.7(react@19.2.7))(react@19.2.7) - classnames: 2.5.1 - rc-motion: 2.9.5(react-dom@19.2.7(react@19.2.7))(react@19.2.7) - rc-overflow: 1.5.0(react-dom@19.2.7(react@19.2.7))(react@19.2.7) - rc-util: 5.44.4(react-dom@19.2.7(react@19.2.7))(react@19.2.7) - react: 19.2.7 - react-dom: 19.2.7(react@19.2.7) - - rc-motion@2.9.5(react-dom@19.2.7(react@19.2.7))(react@19.2.7): - dependencies: - '@babel/runtime': 7.29.7 - classnames: 2.5.1 - rc-util: 5.44.4(react-dom@19.2.7(react@19.2.7))(react@19.2.7) - react: 19.2.7 - react-dom: 19.2.7(react@19.2.7) - - rc-overflow@1.5.0(react-dom@19.2.7(react@19.2.7))(react@19.2.7): - dependencies: - '@babel/runtime': 7.29.7 - classnames: 2.5.1 - rc-resize-observer: 1.4.3(react-dom@19.2.7(react@19.2.7))(react@19.2.7) - rc-util: 5.44.4(react-dom@19.2.7(react@19.2.7))(react@19.2.7) - react: 19.2.7 - react-dom: 19.2.7(react@19.2.7) - - rc-resize-observer@1.4.3(react-dom@19.2.7(react@19.2.7))(react@19.2.7): - dependencies: - '@babel/runtime': 7.29.7 - classnames: 2.5.1 - rc-util: 5.44.4(react-dom@19.2.7(react@19.2.7))(react@19.2.7) - react: 19.2.7 - react-dom: 19.2.7(react@19.2.7) - resize-observer-polyfill: 1.5.1 - - rc-util@5.44.4(react-dom@19.2.7(react@19.2.7))(react@19.2.7): - dependencies: - '@babel/runtime': 7.29.7 - react: 19.2.7 - react-dom: 19.2.7(react@19.2.7) - react-is: 18.2.0 - - re-resizable@6.11.2(react-dom@19.2.7(react@19.2.7))(react@19.2.7): - dependencies: - react: 19.2.7 - react-dom: 19.2.7(react@19.2.7) - - react-aria-components@1.21.1(react-dom@19.2.7(react@19.2.7))(react@19.2.7): - dependencies: - '@internationalized/date': 3.12.4 - '@internationalized/string': 3.2.10 - '@react-types/shared': 3.36.1(react@19.2.7) - '@swc/helpers': 0.5.23 - client-only: 0.0.1 - react: 19.2.7 - react-aria: 3.52.1(react-dom@19.2.7(react@19.2.7))(react@19.2.7) - react-dom: 19.2.7(react@19.2.7) - react-stately: 3.50.0(react@19.2.7) - - react-aria@3.52.1(react-dom@19.2.7(react@19.2.7))(react@19.2.7): - dependencies: - '@internationalized/date': 3.12.4 - '@internationalized/number': 3.6.8 - '@internationalized/string': 3.2.10 - '@react-types/shared': 3.36.1(react@19.2.7) - '@swc/helpers': 0.5.23 - aria-hidden: 1.2.6 - clsx: 2.1.1 - react: 19.2.7 - react-dom: 19.2.7(react@19.2.7) - react-stately: 3.50.0(react@19.2.7) - use-sync-external-store: 1.6.0(react@19.2.7) - - react-avatar-editor@15.1.0(react-dom@19.2.7(react@19.2.7))(react@19.2.7): - dependencies: - react: 19.2.7 - react-dom: 19.2.7(react@19.2.7) - - react-colorful@5.8.0(react-dom@19.2.7(react@19.2.7))(react@19.2.7): - dependencies: - react: 19.2.7 - react-dom: 19.2.7(react@19.2.7) - - react-dom@19.2.7(react@19.2.7): - dependencies: - react: 19.2.7 - scheduler: 0.27.0 - - react-draggable@4.7.1(react-dom@19.2.7(react@19.2.7))(react@19.2.7): - dependencies: - clsx: 2.1.1 - prop-types: 15.8.1 - react: 19.2.7 - react-dom: 19.2.7(react@19.2.7) - - react-dropzone@12.0.0(react@19.2.7): - dependencies: - attr-accept: 2.2.2 - file-selector: 0.4.0 - prop-types: 15.8.1 - react: 19.2.7 - - react-error-boundary@6.1.3(@types/react@19.2.17)(react@19.2.7): - dependencies: - react: 19.2.7 - optionalDependencies: - '@types/react': 19.2.17 - - react-fast-compare@3.2.2: {} - - react-hotkeys-hook@5.3.3(react-dom@19.2.7(react@19.2.7))(react@19.2.7): - dependencies: - react: 19.2.7 - react-dom: 19.2.7(react@19.2.7) - - react-is@16.13.1: {} - - react-is@16.7.0: {} - - react-is@18.2.0: {} - - react-is@19.2.8: {} - - react-markdown@10.1.0(@types/react@19.2.17)(react@19.2.7): - dependencies: - '@types/hast': 3.0.5 - '@types/mdast': 4.0.4 - '@types/react': 19.2.17 - devlop: 1.1.0 - hast-util-to-jsx-runtime: 2.3.6 - html-url-attributes: 3.0.1 - mdast-util-to-hast: 13.2.1 - react: 19.2.7 - remark-parse: 11.0.0 - remark-rehype: 11.1.2 - unified: 11.0.5 - unist-util-visit: 5.1.0 - vfile: 6.0.3 - transitivePeerDependencies: - - supports-color - - react-merge-refs@3.0.2(react@19.2.7): - optionalDependencies: - react: 19.2.7 - - react-redux@9.3.0(@types/react@19.2.17)(react@19.2.7)(redux@5.0.1): - dependencies: - '@types/use-sync-external-store': 0.0.6 - react: 19.2.7 - use-sync-external-store: 1.6.0(react@19.2.7) - optionalDependencies: - '@types/react': 19.2.17 - redux: 5.0.1 - - react-remove-scroll-bar@2.3.8(@types/react@19.2.17)(react@19.2.7): - dependencies: - react: 19.2.7 - react-style-singleton: 2.2.3(@types/react@19.2.17)(react@19.2.7) - tslib: 2.8.1 - optionalDependencies: - '@types/react': 19.2.17 - - react-remove-scroll@2.7.2(@types/react@19.2.17)(react@19.2.7): - dependencies: - react: 19.2.7 - react-remove-scroll-bar: 2.3.8(@types/react@19.2.17)(react@19.2.7) - react-style-singleton: 2.2.3(@types/react@19.2.17)(react@19.2.7) - tslib: 2.8.1 - use-callback-ref: 1.3.3(@types/react@19.2.17)(react@19.2.7) - use-sidecar: 1.1.3(@types/react@19.2.17)(react@19.2.7) - optionalDependencies: - '@types/react': 19.2.17 - - react-rnd@10.5.3(react-dom@19.2.7(react@19.2.7))(react@19.2.7): - dependencies: - re-resizable: 6.11.2(react-dom@19.2.7(react@19.2.7))(react@19.2.7) - react: 19.2.7 - react-dom: 19.2.7(react@19.2.7) - react-draggable: 4.7.1(react-dom@19.2.7(react@19.2.7))(react@19.2.7) - tslib: 2.6.2 - - react-router@8.3.0(react-dom@19.2.7(react@19.2.7))(react@19.2.7): - dependencies: - cookie-es: 3.1.1 - react: 19.2.7 - optionalDependencies: - react-dom: 19.2.7(react@19.2.7) - - react-stately@3.50.0(react@19.2.7): - dependencies: - '@internationalized/date': 3.12.4 - '@internationalized/number': 3.6.8 - '@internationalized/string': 3.2.10 - '@react-types/shared': 3.36.1(react@19.2.7) - '@swc/helpers': 0.5.23 - react: 19.2.7 - use-sync-external-store: 1.6.0(react@19.2.7) - - react-style-singleton@2.2.3(@types/react@19.2.17)(react@19.2.7): - dependencies: - get-nonce: 1.0.1 - react: 19.2.7 - tslib: 2.8.1 - optionalDependencies: - '@types/react': 19.2.17 - - react-syntax-highlighter@16.1.1(react@19.2.7): - dependencies: - '@babel/runtime': 7.29.7 - highlight.js: 10.7.3 - highlightjs-vue: 1.0.0 - lowlight: 1.20.0 - prismjs: 1.30.0 - react: 19.2.7 - refractor: 5.0.0 - - react-use-measure@2.1.7(react-dom@19.2.7(react@19.2.7))(react@19.2.7): - dependencies: - react: 19.2.7 - optionalDependencies: - react-dom: 19.2.7(react@19.2.7) - - react-zoom-pan-pinch@4.0.4(react-dom@19.2.7(react@19.2.7))(react@19.2.7): - dependencies: - react: 19.2.7 - react-dom: 19.2.7(react@19.2.7) - - react@19.2.7: {} - - readable-stream@2.3.8: - dependencies: - core-util-is: 1.0.3 - inherits: 2.0.4 - isarray: 1.0.0 - process-nextick-args: 2.0.1 - safe-buffer: 5.1.2 - string_decoder: 1.1.1 - util-deprecate: 1.0.2 - - readdirp@3.6.0: - dependencies: - picomatch: 2.3.2 - - recast@0.23.21: - dependencies: - ast-types: 0.16.1 - esprima: 4.0.1 - source-map: 0.6.1 - tiny-invariant: 1.3.3 - tslib: 2.8.1 - - recharts@3.10.1(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react-is@19.2.8)(react@19.2.7)(redux@5.0.1): - dependencies: - '@reduxjs/toolkit': 2.12.0(react-redux@9.3.0(@types/react@19.2.17)(react@19.2.7)(redux@5.0.1))(react@19.2.7) - clsx: 2.1.1 - decimal.js-light: 2.5.1 - es-toolkit: 1.50.0 - eventemitter3: 5.0.4 - immer: 11.1.16 - react: 19.2.7 - react-dom: 19.2.7(react@19.2.7) - react-is: 19.2.8 - react-redux: 9.3.0(@types/react@19.2.17)(react@19.2.7)(redux@5.0.1) - reselect: 5.2.0 - tiny-invariant: 1.3.3 - use-sync-external-store: 1.6.0(react@19.2.7) - victory-vendor: 37.3.6 - transitivePeerDependencies: - - '@types/react' - - redux - - recma-build-jsx@1.0.0: - dependencies: - '@types/estree': 1.0.9 - estree-util-build-jsx: 3.0.1 - vfile: 6.0.3 - - recma-jsx@1.0.1(acorn@8.18.0): - dependencies: - acorn: 8.18.0 - acorn-jsx: 5.3.2(acorn@8.18.0) - estree-util-to-js: 2.0.0 - recma-parse: 1.0.0 - recma-stringify: 1.0.0 - unified: 11.0.5 - - recma-parse@1.0.0: - dependencies: - '@types/estree': 1.0.9 - esast-util-from-js: 2.0.1 - unified: 11.0.5 - vfile: 6.0.3 - - recma-stringify@1.0.0: - dependencies: - '@types/estree': 1.0.9 - estree-util-to-js: 2.0.0 - unified: 11.0.5 - vfile: 6.0.3 - - redux-thunk@3.1.0(redux@5.0.1): - dependencies: - redux: 5.0.1 - - redux@5.0.1: {} - - refractor@5.0.0: - dependencies: - '@types/hast': 3.0.5 - '@types/prismjs': 1.26.6 - hastscript: 9.0.1 - parse-entities: 4.0.2 - - regex-recursion@6.0.2: - dependencies: - regex-utilities: 2.3.0 - - regex-utilities@2.3.0: {} - - regex@6.1.0: - dependencies: - regex-utilities: 2.3.0 - - rehype-github-alerts@4.2.0: - dependencies: - '@primer/octicons': 19.33.0 - hast-util-from-html: 2.0.3 - hast-util-is-element: 3.0.0 - unist-util-visit: 5.1.0 - - rehype-harden@1.1.8: - dependencies: - unist-util-visit: 5.1.0 - - rehype-katex@7.0.1: - dependencies: - '@types/hast': 3.0.5 - '@types/katex': 0.16.8 - hast-util-from-html-isomorphic: 2.0.0 - hast-util-to-text: 4.0.2 - katex: 0.16.47 - unist-util-visit-parents: 6.0.2 - vfile: 6.0.3 - - rehype-raw@7.0.0: - dependencies: - '@types/hast': 3.0.5 - hast-util-raw: 9.1.0 - vfile: 6.0.3 - - rehype-recma@1.0.0: - dependencies: - '@types/estree': 1.0.9 - '@types/hast': 3.0.5 - hast-util-to-estree: 3.1.3 - transitivePeerDependencies: - - supports-color - - rehype-sanitize@6.0.0: - dependencies: - '@types/hast': 3.0.5 - hast-util-sanitize: 5.0.2 - - remark-breaks@4.0.0: - dependencies: - '@types/mdast': 4.0.4 - mdast-util-newline-to-break: 2.0.0 - unified: 11.0.5 - - remark-cjk-friendly@2.3.1(@types/mdast@4.0.4)(micromark-util-types@2.0.2)(micromark@4.0.2)(unified@11.0.5): - dependencies: - mdast-util-to-markdown-cjk-friendly: 1.0.0(@types/mdast@4.0.4)(micromark-util-types@2.0.2) - micromark-extension-cjk-friendly: 2.0.1(micromark-util-types@2.0.2)(micromark@4.0.2) - unified: 11.0.5 - optionalDependencies: - '@types/mdast': 4.0.4 - transitivePeerDependencies: - - micromark - - micromark-util-types - - remark-gfm@4.0.1: - dependencies: - '@types/mdast': 4.0.4 - mdast-util-gfm: 3.1.0 - micromark-extension-gfm: 3.0.0 - remark-parse: 11.0.0 - remark-stringify: 11.0.0 - unified: 11.0.5 - transitivePeerDependencies: - - supports-color - - remark-github@12.0.0: - dependencies: - '@types/mdast': 4.0.4 - mdast-util-find-and-replace: 3.0.2 - mdast-util-to-string: 4.0.0 - to-vfile: 8.0.0 - unist-util-visit: 5.1.0 - vfile: 6.0.3 - - remark-math@6.0.0: - dependencies: - '@types/mdast': 4.0.4 - mdast-util-math: 3.0.0 - micromark-extension-math: 3.1.0 - unified: 11.0.5 - transitivePeerDependencies: - - supports-color - - remark-mdx@3.0.0: - dependencies: - mdast-util-mdx: 3.0.0 - micromark-extension-mdxjs: 3.0.0 - transitivePeerDependencies: - - supports-color - - remark-parse@11.0.0: - dependencies: - '@types/mdast': 4.0.4 - mdast-util-from-markdown: 2.0.3 - micromark-util-types: 2.0.2 - unified: 11.0.5 - transitivePeerDependencies: - - supports-color - - remark-rehype@11.1.2: - dependencies: - '@types/hast': 3.0.5 - '@types/mdast': 4.0.4 - mdast-util-to-hast: 13.2.1 - unified: 11.0.5 - vfile: 6.0.3 - - remark-stringify@11.0.0: - dependencies: - '@types/mdast': 4.0.4 - mdast-util-to-markdown: 2.1.2 - unified: 11.0.5 - - remend@1.3.0: {} - - require-from-string@2.0.2: {} - - reselect@5.2.0: {} - - resize-observer-polyfill@1.5.1: {} - - resolve-from@4.0.0: {} - - resolve@1.22.12: - dependencies: - es-errors: 1.3.0 - is-core-module: 2.16.2 - path-parse: 1.0.7 - supports-preserve-symlinks-flag: 1.0.0 - - restore-cursor@5.1.0: - dependencies: - onetime: 7.0.0 - signal-exit: 4.1.0 - - reusify@1.1.0: {} - - robust-predicates@3.0.3: {} - - rolldown@1.2.4: - dependencies: - '@oxc-project/types': 0.144.0 - '@rolldown/pluginutils': 1.0.1 - optionalDependencies: - '@rolldown/binding-android-arm64': 1.2.4 - '@rolldown/binding-darwin-arm64': 1.2.4 - '@rolldown/binding-darwin-x64': 1.2.4 - '@rolldown/binding-freebsd-x64': 1.2.4 - '@rolldown/binding-linux-arm-gnueabihf': 1.2.4 - '@rolldown/binding-linux-arm64-gnu': 1.2.4 - '@rolldown/binding-linux-arm64-musl': 1.2.4 - '@rolldown/binding-linux-ppc64-gnu': 1.2.4 - '@rolldown/binding-linux-s390x-gnu': 1.2.4 - '@rolldown/binding-linux-x64-gnu': 1.2.4 - '@rolldown/binding-linux-x64-musl': 1.2.4 - '@rolldown/binding-openharmony-arm64': 1.2.4 - '@rolldown/binding-win32-arm64-msvc': 1.2.4 - '@rolldown/binding-win32-x64-msvc': 1.2.4 - - rope-sequence@1.3.4: {} - - roughjs@4.6.4: - dependencies: - hachure-fill: 0.5.2 - path-data-parser: 0.1.0 - points-on-curve: 0.2.0 - points-on-path: 0.2.1 - - 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 - - router@2.2.0: - dependencies: - debug: 4.4.3 - depd: 2.0.0 - is-promise: 4.0.0 - parseurl: 1.3.3 - path-to-regexp: 8.4.2 - transitivePeerDependencies: - - supports-color - - run-applescript@7.1.0: {} - - run-parallel@1.2.0: - dependencies: - queue-microtask: 1.2.3 - - rw@1.3.3: {} - - safe-buffer@5.1.2: {} - - safer-buffer@2.1.2: {} - - sass@1.51.0: - dependencies: - chokidar: 3.6.0 - immutable: 4.3.9 - source-map-js: 1.2.1 - - scheduler@0.27.0: {} - - screenfull@5.2.0: {} - - scroll-into-view-if-needed@3.1.0: - dependencies: - compute-scroll-into-view: 3.1.1 - - semver-compare@1.0.0: {} - - semver@6.3.1: {} - - semver@7.8.5: {} - - send@1.2.1: - dependencies: - debug: 4.4.3 - encodeurl: 2.0.0 - escape-html: 1.0.3 - etag: 1.8.1 - fresh: 2.0.0 - http-errors: 2.0.1 - mime-types: 3.0.2 - ms: 2.1.3 - on-finished: 2.4.1 - range-parser: 1.3.0 - statuses: 2.0.2 - transitivePeerDependencies: - - supports-color - - serve-static@2.2.1: - dependencies: - encodeurl: 2.0.0 - escape-html: 1.0.3 - parseurl: 1.3.3 - send: 1.2.1 - transitivePeerDependencies: - - supports-color - - set-value@2.0.1: - dependencies: - extend-shallow: 2.0.1 - is-extendable: 0.1.1 - is-plain-object: 2.0.4 - split-string: 3.1.0 - - setimmediate@1.0.5: {} - - setprototypeof@1.2.0: {} - - shadcn@4.18.0(babel-plugin-macros@3.1.0)(typescript@7.0.2): - dependencies: - '@babel/core': 7.29.7 - '@babel/parser': 7.29.8 - '@babel/plugin-transform-typescript': 7.29.7(@babel/core@7.29.7) - '@babel/preset-typescript': 7.29.7(@babel/core@7.29.7) - '@dotenvx/dotenvx': 1.75.1 - '@modelcontextprotocol/sdk': 1.30.0(zod@3.25.76) - '@types/validate-npm-package-name': 4.0.2 - browserslist: 4.28.8 - commander: 14.0.3 - cosmiconfig: 9.0.2(typescript@7.0.2) - dedent: 1.7.2(babel-plugin-macros@3.1.0) - deepmerge: 4.3.1 - diff: 8.0.4 - execa: 9.6.1 - fast-glob: 3.3.3 - fs-extra: 11.4.0 - fuzzysort: 3.1.0 - kleur: 4.1.5 - open: 11.0.1 - ora: 8.2.0 - postcss: 8.5.26 - postcss-selector-parser: 7.1.5 - prompts: 2.4.2 - recast: 0.23.21 - socks: 2.8.9 - stringify-object: 5.0.0 - tailwind-merge: 3.6.0 - ts-morph: 26.0.0 - tsconfig-paths: 4.2.0 - undici: 7.29.0 - validate-npm-package-name: 7.0.2 - zod: 3.25.76 - zod-to-json-schema: 3.25.2(zod@3.25.76) - transitivePeerDependencies: - - '@cfworker/json-schema' - - babel-plugin-macros - - supports-color - - typescript - - shebang-command@2.0.0: - dependencies: - shebang-regex: 3.0.0 - - shebang-regex@3.0.0: {} - - shiki@3.23.0: - dependencies: - '@shikijs/core': 3.23.0 - '@shikijs/engine-javascript': 3.23.0 - '@shikijs/engine-oniguruma': 3.23.0 - '@shikijs/langs': 3.23.0 - '@shikijs/themes': 3.23.0 - '@shikijs/types': 3.23.0 - '@shikijs/vscode-textmate': 10.0.2 - '@types/hast': 3.0.5 - - shiki@4.4.3: - dependencies: - '@shikijs/core': 4.4.3 - '@shikijs/engine-javascript': 4.4.3 - '@shikijs/engine-oniguruma': 4.4.3 - '@shikijs/langs': 4.4.3 - '@shikijs/themes': 4.4.3 - '@shikijs/types': 4.4.3 - '@shikijs/vscode-textmate': 10.0.2 - '@types/hast': 3.0.5 - - side-channel-list@1.0.1: - dependencies: - es-errors: 1.3.0 - object-inspect: 1.13.4 - - side-channel-map@1.0.1: - dependencies: - call-bound: 1.0.4 - es-errors: 1.3.0 - get-intrinsic: 1.3.0 - object-inspect: 1.13.4 - - side-channel-weakmap@1.0.2: - dependencies: - call-bound: 1.0.4 - es-errors: 1.3.0 - get-intrinsic: 1.3.0 - object-inspect: 1.13.4 - side-channel-map: 1.0.1 - - side-channel@1.1.1: - dependencies: - es-errors: 1.3.0 - object-inspect: 1.13.4 - side-channel-list: 1.0.1 - side-channel-map: 1.0.1 - side-channel-weakmap: 1.0.2 - - signal-exit@3.0.7: {} - - signal-exit@4.1.0: {} - - sisteransi@1.0.5: {} - - sliced@1.0.1: {} - - smart-buffer@4.2.0: {} - - socks@2.8.9: - dependencies: - ip-address: 10.5.0 - smart-buffer: 4.2.0 - - source-map-js@1.2.1: {} - - source-map@0.5.7: {} - - source-map@0.6.1: {} - - source-map@0.7.0: {} - - space-separated-tokens@2.0.2: {} - - split-on-first@3.0.0: {} - - split-string@3.1.0: - dependencies: - extend-shallow: 3.0.2 - - sprintf-js@1.0.3: {} - - stats-gl@2.4.2(@types/three@0.185.4)(three@0.185.1): - dependencies: - '@types/three': 0.185.4 - three: 0.185.1 - - stats.js@0.17.0: {} - - statuses@2.0.2: {} - - stdin-discarder@0.2.2: {} - - streamdown@2.5.0(react-dom@19.2.7(react@19.2.7))(react@19.2.7): - dependencies: - clsx: 2.1.1 - hast-util-to-jsx-runtime: 2.3.6 - html-url-attributes: 3.0.1 - marked: 17.0.6 - mermaid: 11.16.1 - react: 19.2.7 - react-dom: 19.2.7(react@19.2.7) - 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.6.0 - unified: 11.0.5 - unist-util-visit: 5.1.0 - unist-util-visit-parents: 6.0.2 - transitivePeerDependencies: - - supports-color - - string-convert@0.2.1: {} - - string-width@7.2.0: - dependencies: - emoji-regex: 10.6.0 - get-east-asian-width: 1.6.0 - strip-ansi: 7.2.0 - - string_decoder@1.1.1: - dependencies: - safe-buffer: 5.1.2 - - stringify-entities@4.0.4: - dependencies: - character-entities-html4: 2.1.0 - character-entities-legacy: 3.0.0 - - stringify-object@5.0.0: - dependencies: - get-own-enumerable-keys: 1.0.0 - is-obj: 3.0.0 - is-regexp: 3.1.0 - - strip-ansi@6.0.1: - dependencies: - ansi-regex: 5.0.1 - - strip-ansi@7.2.0: - dependencies: - ansi-regex: 6.3.0 - - strip-bom@3.0.0: {} - - strip-final-newline@2.0.0: {} - - strip-final-newline@4.0.0: {} - - style-mod@4.1.3: {} - - style-to-js@1.1.21: - dependencies: - style-to-object: 1.0.14 - - style-to-object@1.0.14: - dependencies: - inline-style-parser: 0.2.7 - - stylis@4.2.0: {} - - stylis@4.4.0: {} - - supports-preserve-symlinks-flag@1.0.0: {} - - suspend-react@0.1.3(react@19.2.7): - dependencies: - react: 19.2.7 - - swr@2.5.1(react@19.2.7): - dependencies: - dequal: 2.0.3 - react: 19.2.7 - use-sync-external-store: 1.6.0(react@19.2.7) - - systeminformation@5.33.1: {} - - tabbable@6.5.0: {} - - tailwind-merge@3.6.0: {} - - tailwindcss@4.3.3: {} - - tapable@2.3.3: {} - - three-mesh-bvh@0.8.3(three@0.185.1): - dependencies: - three: 0.185.1 - - three-stdlib@2.36.1(three@0.185.1): - dependencies: - '@types/draco3d': 1.4.10 - '@types/offscreencanvas': 2019.7.3 - '@types/webxr': 0.5.24 - draco3d: 1.5.7 - fflate: 0.6.11 - potpack: 1.0.2 - three: 0.185.1 - - three@0.185.1: {} - - throttle-debounce@5.0.2: {} - - tiny-invariant@1.3.3: {} - - tinyexec@1.3.0: {} - - tinyglobby@0.2.17: - dependencies: - fdir: 6.5.0(picomatch@4.0.5) - picomatch: 4.0.5 - - tldraw@5.2.5(@floating-ui/dom@1.8.0)(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7): - dependencies: - '@tiptap/core': 3.28.0(@tiptap/pm@3.28.0) - '@tiptap/extension-code': 3.28.0(@tiptap/core@3.28.0(@tiptap/pm@3.28.0)) - '@tiptap/extension-highlight': 3.28.0(@tiptap/core@3.28.0(@tiptap/pm@3.28.0)) - '@tiptap/extension-list': 3.28.0(@tiptap/core@3.28.0(@tiptap/pm@3.28.0))(@tiptap/pm@3.28.0) - '@tiptap/pm': 3.28.0 - '@tiptap/react': 3.28.0(@floating-ui/dom@1.8.0)(@tiptap/core@3.28.0(@tiptap/pm@3.28.0))(@tiptap/pm@3.28.0)(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) - '@tiptap/starter-kit': 3.28.0 - '@tldraw/driver': 5.2.5(@floating-ui/dom@1.8.0)(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) - '@tldraw/editor': 5.2.5(@floating-ui/dom@1.8.0)(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) - '@tldraw/store': 5.2.5(react@19.2.7) - classnames: 2.5.1 - idb: 7.1.1 - lz-string: 1.5.0 - radix-ui: 1.6.7(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) - react: 19.2.7 - react-dom: 19.2.7(react@19.2.7) - transitivePeerDependencies: - - '@floating-ui/dom' - - '@types/react' - - '@types/react-dom' - - to-regex-range@5.0.1: - dependencies: - is-number: 7.0.0 - - to-vfile@8.0.0: - dependencies: - vfile: 6.0.3 - - toidentifier@1.0.1: {} - - trim-lines@3.0.1: {} - - troika-three-text@0.52.5(three@0.185.1): - dependencies: - bidi-js: 1.0.3 - three: 0.185.1 - troika-three-utils: 0.52.5(three@0.185.1) - troika-worker-utils: 0.52.0 - webgl-sdf-generator: 1.1.1 - - troika-three-utils@0.52.5(three@0.185.1): - dependencies: - three: 0.185.1 - - troika-worker-utils@0.52.0: {} - - trough@2.2.0: {} - - ts-dedent@2.3.0: {} - - ts-md5@2.0.1: {} - - ts-morph@26.0.0: - dependencies: - '@ts-morph/common': 0.27.0 - code-block-writer: 13.0.3 - - tsconfig-paths@4.2.0: - dependencies: - json5: 2.2.3 - minimist: 1.2.8 - strip-bom: 3.0.0 - - tslib@2.6.2: {} - - tslib@2.8.1: {} - - tunnel-rat@0.1.2(@types/react@19.2.17)(immer@11.1.16)(react@19.2.7): - dependencies: - zustand: 4.5.7(@types/react@19.2.17)(immer@11.1.16)(react@19.2.7) - transitivePeerDependencies: - - '@types/react' - - immer - - react - - tw-animate-css@1.4.0: {} - - type-is@2.1.0: - dependencies: - content-type: 2.1.0 - media-typer: 1.1.1 - mime-types: 3.0.2 - - typescript@7.0.2: - optionalDependencies: - '@typescript/typescript-aix-ppc64': 7.0.2 - '@typescript/typescript-darwin-arm64': 7.0.2 - '@typescript/typescript-darwin-x64': 7.0.2 - '@typescript/typescript-freebsd-arm64': 7.0.2 - '@typescript/typescript-freebsd-x64': 7.0.2 - '@typescript/typescript-linux-arm': 7.0.2 - '@typescript/typescript-linux-arm64': 7.0.2 - '@typescript/typescript-linux-loong64': 7.0.2 - '@typescript/typescript-linux-mips64el': 7.0.2 - '@typescript/typescript-linux-ppc64': 7.0.2 - '@typescript/typescript-linux-riscv64': 7.0.2 - '@typescript/typescript-linux-s390x': 7.0.2 - '@typescript/typescript-linux-x64': 7.0.2 - '@typescript/typescript-netbsd-arm64': 7.0.2 - '@typescript/typescript-netbsd-x64': 7.0.2 - '@typescript/typescript-openbsd-arm64': 7.0.2 - '@typescript/typescript-openbsd-x64': 7.0.2 - '@typescript/typescript-sunos-x64': 7.0.2 - '@typescript/typescript-win32-arm64': 7.0.2 - '@typescript/typescript-win32-x64': 7.0.2 - - underscore@1.13.8: {} - - undici-types@6.21.0: {} - - undici@7.29.0: {} - - unicorn-magic@0.3.0: {} - - unified@11.0.5: - dependencies: - '@types/unist': 3.0.3 - bail: 2.0.2 - devlop: 1.1.0 - extend: 3.0.2 - is-plain-obj: 4.1.0 - trough: 2.2.0 - vfile: 6.0.3 - - unist-util-find-after@5.0.0: - dependencies: - '@types/unist': 3.0.3 - unist-util-is: 6.0.1 - - unist-util-is@6.0.1: - dependencies: - '@types/unist': 3.0.3 - - unist-util-position-from-estree@1.1.2: - dependencies: - '@types/unist': 2.0.11 - - unist-util-position-from-estree@2.0.0: - dependencies: - '@types/unist': 3.0.3 - - unist-util-position@5.0.0: - dependencies: - '@types/unist': 3.0.3 - - unist-util-remove-position@5.0.0: - dependencies: - '@types/unist': 3.0.3 - unist-util-visit: 5.1.0 - - unist-util-stringify-position@4.0.0: - dependencies: - '@types/unist': 3.0.3 - - unist-util-visit-parents@6.0.2: - dependencies: - '@types/unist': 3.0.3 - unist-util-is: 6.0.1 - - unist-util-visit@5.1.0: - dependencies: - '@types/unist': 3.0.3 - unist-util-is: 6.0.1 - unist-util-visit-parents: 6.0.2 - - universalify@2.0.1: {} - - unpipe@1.0.0: {} - - update-browserslist-db@1.3.1(browserslist@4.28.8): - dependencies: - browserslist: 4.28.8 - escalade: 3.2.0 - picocolors: 1.1.1 - - url-join@5.0.0: {} - - use-callback-ref@1.3.3(@types/react@19.2.17)(react@19.2.7): - dependencies: - react: 19.2.7 - tslib: 2.8.1 - optionalDependencies: - '@types/react': 19.2.17 - - use-merge-value@1.2.0(react@19.2.7): - dependencies: - react: 19.2.7 - - use-sidecar@1.1.3(@types/react@19.2.17)(react@19.2.7): - dependencies: - detect-node-es: 1.1.0 - react: 19.2.7 - tslib: 2.8.1 - optionalDependencies: - '@types/react': 19.2.17 - - use-sync-external-store@1.6.0(react@19.2.7): - dependencies: - react: 19.2.7 - - util-deprecate@1.0.2: {} - - utility-types@3.11.0: {} - - uuid@14.0.1: {} - - v8n@1.5.1: {} - - validate-npm-package-name@7.0.2: {} - - vary@1.1.2: {} - - vfile-location@5.0.3: - dependencies: - '@types/unist': 3.0.3 - vfile: 6.0.3 - - vfile-message@4.0.3: - dependencies: - '@types/unist': 3.0.3 - unist-util-stringify-position: 4.0.0 - - vfile@6.0.3: - dependencies: - '@types/unist': 3.0.3 - vfile-message: 4.0.3 - - victory-vendor@37.3.6: - dependencies: - '@types/d3-array': 3.2.2 - '@types/d3-ease': 3.0.2 - '@types/d3-interpolate': 3.0.4 - '@types/d3-scale': 4.0.9 - '@types/d3-shape': 3.1.8 - '@types/d3-time': 3.0.4 - '@types/d3-timer': 3.0.2 - d3-array: 3.2.4 - d3-ease: 3.0.1 - d3-interpolate: 3.0.1 - d3-scale: 4.0.2 - d3-shape: 3.2.0 - d3-time: 3.1.0 - d3-timer: 3.0.1 - - virtua@0.49.3(react-dom@19.2.7(react@19.2.7))(react@19.2.7): - optionalDependencies: - react: 19.2.7 - react-dom: 19.2.7(react@19.2.7) - - vite@8.2.1(@types/node@20.19.43)(jiti@2.7.0): - dependencies: - lightningcss: 1.33.0 - picomatch: 4.0.5 - postcss: 8.5.26 - rolldown: 1.2.4 - tinyglobby: 0.2.17 - optionalDependencies: - '@types/node': 20.19.43 - fsevents: 2.3.3 - jiti: 2.7.0 - - vscode-jsonrpc@8.2.0: {} - - vscode-languageserver-protocol@3.17.5: - dependencies: - vscode-jsonrpc: 8.2.0 - vscode-languageserver-types: 3.17.5 - - vscode-languageserver-textdocument@1.0.12: {} - - vscode-languageserver-types@3.17.5: {} - - vscode-languageserver@9.0.1: - dependencies: - vscode-languageserver-protocol: 3.17.5 - - vscode-uri@3.0.8: {} - - w3c-keyname@2.2.8: {} - - web-namespaces@2.0.1: {} - - webgl-constants@1.1.1: {} - - webgl-sdf-generator@1.1.1: {} - - webworkify@1.5.0: {} - - which@2.0.2: - dependencies: - isexe: 2.0.0 - - which@4.0.0: - dependencies: - isexe: 3.1.5 - - wrappy@1.0.2: {} - - wsl-utils@1.0.0: - dependencies: - is-wsl: 3.1.1 - powershell-utils: 0.1.0 - - xmlbuilder@10.1.1: {} - - yallist@3.1.1: {} - - yaml@1.10.3: {} - - yocto-spinner@1.2.2: - dependencies: - yoctocolors: 2.2.0 - - yoctocolors@2.2.0: {} - - zod-to-json-schema@3.25.2(zod@3.25.76): - dependencies: - zod: 3.25.76 - - zod@3.25.76: {} - - zustand@3.6.9(react@19.2.7): - optionalDependencies: - react: 19.2.7 - - zustand@4.5.7(@types/react@19.2.17)(immer@11.1.16)(react@19.2.7): - dependencies: - use-sync-external-store: 1.6.0(react@19.2.7) - optionalDependencies: - '@types/react': 19.2.17 - immer: 11.1.16 - react: 19.2.7 - - zustand@5.0.15(@types/react@19.2.17)(immer@11.1.16)(react@19.2.7)(use-sync-external-store@1.6.0(react@19.2.7)): - optionalDependencies: - '@types/react': 19.2.17 - immer: 11.1.16 - react: 19.2.7 - use-sync-external-store: 1.6.0(react@19.2.7) - - zwitch@2.0.4: {} diff --git a/web/src/components/assets/asset-library-picker-modal.tsx b/web/src/components/assets/asset-library-picker-modal.tsx index acde4eff2..dc4a894b4 100644 --- a/web/src/components/assets/asset-library-picker-modal.tsx +++ b/web/src/components/assets/asset-library-picker-modal.tsx @@ -1,5 +1,6 @@ -import { App, Button, Dropdown, Modal, Popconfirm } from "antd"; +import { App, Button, Dropdown, Popconfirm } from "antd"; import type { MenuProps } from "antd"; +import { AppModal } from "@/components/ui/product/app-modal"; import { Check, ChevronDown, FileText, FolderOpen, HardDrive, Image as ImageIcon, LoaderCircle, Music2, Puzzle, RotateCcw, Search, Trash2, Upload, UserRound, Video } from "lucide-react"; import { useEffect, useMemo, useRef, useState, type ReactNode } from "react"; import { useQuery } from "@tanstack/react-query"; @@ -12,7 +13,7 @@ import { PaginationBar } from "@/components/layout/workspace-page"; import { cn } from "@/lib/utils"; import type { ExternalAssetPickerReference } from "@/lib/plugins/plugin-types"; import { flushAssetStorePersistence, useAssetStore, type Asset } from "@/stores/use-asset-store"; -import { deleteAssetWithRemoteSync, loadAssetLibraryPage, saveRemoteUserDataNow } from "@/services/user-data-sync"; +import { deleteAssetWithRemoteSync, loadAssetLibraryPage, localSavedRemotePendingMessage, saveRemoteUserDataNow } from "@/services/user-data-sync"; export type AssetPickerMediaKind = "image" | "video" | "audio" | "text"; @@ -271,8 +272,8 @@ export function AssetLibraryPickerModal({ setSelected(new Set()); message.success(`已还原 ${archivedSelectedIds.length} 个素材至素材库`); setCategory("all"); - } catch { - message.warning("已在本地还原,稍后自动同步至云端"); + } catch (error) { + message.warning(localSavedRemotePendingMessage("已在本地还原", error)); } finally { setWorking(false); if (remoteEnabled) void remoteQuery.refetch(); @@ -380,7 +381,7 @@ export function AssetLibraryPickerModal({ const uploading = uploadingCount > 0; return ( -
@@ -559,7 +560,7 @@ export function AssetLibraryPickerModal({
-
+ ); } diff --git a/web/src/components/canvas/art-critique/ai-art-critique-modal.tsx b/web/src/components/canvas/art-critique/ai-art-critique-modal.tsx index dd0e09d59..5049ea75e 100644 --- a/web/src/components/canvas/art-critique/ai-art-critique-modal.tsx +++ b/web/src/components/canvas/art-critique/ai-art-critique-modal.tsx @@ -1,4 +1,5 @@ -import { Button, Modal, Tag } from "antd"; +import { Button, Tag } from "antd"; +import { AppModal } from "@/components/ui/product/app-modal"; import { EmptyState } from "@/components/ui/product/empty-state"; import { ArrowLeft, ArrowRight, CheckCircle2, ChevronDown, ChevronRight, Copy, FileText, Image as ImageIcon, LoaderCircle, RefreshCw, Sparkles, Target, X } from "lucide-react"; import { useEffect, useMemo, useRef, useState } from "react"; @@ -256,7 +257,7 @@ export function AiArtCritiqueModal({ node, upstreamNodes, open, onClose, onUpdat const modelInlineLabel = selectedCritiqueModel || visibleState.report?.modelLabel ? `${modelStatusLabel} · ${displayedModelLabel}` : "请到设置中选择支持图片理解的文本模型"; return ( -
@@ -465,7 +466,7 @@ export function AiArtCritiqueModal({ node, upstreamNodes, open, onClose, onUpdat
-
+ ); } diff --git a/web/src/components/canvas/canvas-agent-chat-ui.tsx b/web/src/components/canvas/canvas-agent-chat-ui.tsx index 85422a4c1..414df050d 100644 --- a/web/src/components/canvas/canvas-agent-chat-ui.tsx +++ b/web/src/components/canvas/canvas-agent-chat-ui.tsx @@ -340,7 +340,7 @@ export function AgentChatComposer({ }; const applySlashSkill = (skill: Skill) => { - const token = `@[skill:${skill.skill_id}] `; + const token = `@[skill:${skill.skillId}] `; const next = slash ? `${prompt.slice(0, slash.start)}${token}${prompt.slice(slash.start + slash.query.length)}` : prompt ? `${prompt.replace(/\s+$/u, "")} ${token}` : token; setSlash(null); setSlashIndex(0); @@ -463,7 +463,7 @@ export function AgentChatComposer({ > {availableSlashSkills.map((skill, index) => ( ))} diff --git a/web/src/components/canvas/canvas-assistant-online-tools.ts b/web/src/components/canvas/canvas-assistant-online-tools.ts new file mode 100644 index 000000000..ad5131bfe --- /dev/null +++ b/web/src/components/canvas/canvas-assistant-online-tools.ts @@ -0,0 +1,573 @@ +import { nanoid } from "nanoid"; +import { type ResponseInputMessage, type ResponseToolCall } from "@/services/api/image"; +import { runBackendToolGenerationTask } from "@/services/api/generation-task"; +import { imageToDataUrl } from "@/services/image-storage"; +import { normalizeModelOptionValue, resolveModelRequestConfig, selectableModelsByCapability, type AiConfig } from "@/stores/use-config-store"; +import { NODE_DEFAULT_SIZE } from "@/constant/canvas"; +import { CanvasNodeType, type CanvasAssistantMessage, type CanvasAssistantReference, type CanvasAssistantSession, type CanvasNodeData } from "@/types/canvas"; +import { previewCanvasAgentOps, type CanvasAgentOp, type CanvasAgentOperationImpact, type CanvasAgentSnapshot } from "@/lib/canvas/canvas-agent-ops"; +import { isWritableToolCall, buildCanvasOnlineAgentSystemContent } from "@/lib/canvas/canvas-agent-protocol"; +import { CANVAS_ONLINE_AGENT_TOOLS } from "@/lib/canvas/canvas-agent-tools"; +import { buildOrderedCanvasResourceReferences, canvasResourceMentionToken } from "@/lib/canvas/canvas-resource-references"; +import { buildCanvasWorkflowOps, looksLikeWorkflowRequest, type CanvasWorkflowInput } from "@/lib/canvas/canvas-agent-workflow"; +import type { Skill } from "@/services/api/skills"; + +export type OnlineToolResult = { ok: true; message: string; data?: unknown } | { ok: false; message: string }; + +export function objectDetail(value: unknown) { + return value && typeof value === "object" ? (value as Record) : {}; +} + +export function describeCanvasSnapshot(snapshot: CanvasAgentSnapshot) { + const counts = snapshot.nodes.reduce>((acc, node) => { + acc[node.type] = (acc[node.type] || 0) + 1; + return acc; + }, {}); + return `当前画布有 ${snapshot.nodes.length} 个节点、${snapshot.connections.length} 条连线。背板 ${counts[CanvasNodeType.Frame] || 0} 个,文本 ${counts[CanvasNodeType.Text] || 0} 个,绘图 ${counts[CanvasNodeType.Drawing] || 0} 个,分镜脚本 ${counts[CanvasNodeType.Script] || 0} 个,技能 ${counts[CanvasNodeType.Skill] || 0} 个,图片 ${counts[CanvasNodeType.Image] || 0} 个,生成配置 ${counts[CanvasNodeType.Config] || 0} 个,视频 ${counts[CanvasNodeType.Video] || 0} 个,音频 ${counts[CanvasNodeType.Audio] || 0} 个。`; +} + +export function parseToolArguments(value: string) { + try { + const parsed = JSON.parse(value || "{}"); + if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) throw new Error("工具参数必须是 JSON 对象"); + return parsed as Record; + } catch { + throw new Error("工具参数不是合法 JSON 对象"); + } +} + +/** + * 把在线 Agent 的工具调用转换为可审计的画布操作,不在这里直接修改 store。 + * 只读工具不会进入该函数;写操作先由上层确认,并以 snapshot 生成的 revision/hash 做并发保护,执行失败时保留原始工具消息和错误。 + */ + +export function onlineToolToOps(name: string, input: Record, snapshot: CanvasAgentSnapshot, config: AiConfig): CanvasAgentOp[] { + if (name === "canvas_apply_ops") return requireOps(input.ops); + if (name === "canvas_create_workflow") return buildCanvasWorkflowOps(input as unknown as CanvasWorkflowInput, snapshot, config); + if (name === "canvas_create_node") { + const nodeType = requireNodeType(input.nodeType); + const x = numberOr(input.x, nextCanvasX(snapshot)); + const y = numberOr(input.y, 0); + return [{ type: "add_node", nodeType, title: stringOptional(input.title), position: { x, y }, width: numberOptional(input.width), height: numberOptional(input.height), metadata: recordOptional(input.metadata) as CanvasNodeData["metadata"] }]; + } + if (name === "canvas_create_text_node") return [textNodeOp(input, numberOr(input.x, nextCanvasX(snapshot)), numberOr(input.y, 0))]; + if (name === "canvas_create_text_nodes") { + const items = requireRecordArray(input.items, "items"); + const textBatch = items.map((item) => `${String(item.title || "")} ${String(item.text || "")}`).join(" "); + if (looksLikeWorkflowRequest(textBatch)) throw new Error("检测到流水线/工作流意图,请使用 canvas_create_workflow 创建真实类型节点和连线。" ); + const x = numberOr(input.x, nextCanvasX(snapshot)); + const y = numberOr(input.y, 0); + const gap = numberOr(input.gap, 40); + const direction = input.direction === "row" ? "row" : "column"; + return items.map((item, index) => + textNodeOp( + { ...item, text: requireString(item.text, "text") }, + numberOr(item.x, direction === "row" ? x + index * (NODE_DEFAULT_SIZE[CanvasNodeType.Text].width + gap) : x), + numberOr(item.y, direction === "row" ? y : y + index * (NODE_DEFAULT_SIZE[CanvasNodeType.Text].height + gap)), + ), + ); + } + if (name === "canvas_create_image_prompt_flow") return generationFlowOps({ ...input, mode: "image" }, snapshot, config); + if (name === "canvas_create_generation_flow") return generationFlowOps(input, snapshot, config); + if (name === "canvas_generate_text") return generationFlowOps({ ...input, mode: "text", autoRun: true }, snapshot, config); + if (name === "canvas_generate_image") return generationFlowOps({ ...input, mode: "image", autoRun: true }, snapshot, config); + if (name === "canvas_generate_video") return generationFlowOps({ ...input, mode: "video", autoRun: true }, snapshot, config); + if (name === "canvas_generate_audio") return generationFlowOps({ ...input, mode: "audio", autoRun: true }, snapshot, config); + if (name === "canvas_update_node") return [{ type: "update_node", id: requireString(input.id, "id"), patch: recordOptional(input.patch) as Partial | undefined, metadata: recordOptional(input.metadata) as CanvasNodeData["metadata"] }]; + if (name === "canvas_update_node_text") + return [{ type: "update_node", id: requireString(input.id, "id"), patch: stringOptional(input.title) ? { title: stringOptional(input.title) } : undefined, metadata: { content: requireString(input.text, "text"), status: "success" } }]; + if (name === "canvas_move_nodes") { + return requireRecordArray(input.items, "items").map((item) => { + const id = requireString(item.id, "id"); + const current = snapshot.nodes.find((node) => node.id === id); + return { type: "update_node", id, patch: { position: { x: numberOr(item.x, (current?.position.x || 0) + numberOr(item.dx, 0)), y: numberOr(item.y, (current?.position.y || 0) + numberOr(item.dy, 0)) } } }; + }); + } + if (name === "canvas_resize_node") + return [ + { + type: "update_node", + id: requireString(input.id, "id"), + patch: { width: requireNumber(input.width, "width"), height: requireNumber(input.height, "height") }, + metadata: typeof input.freeResize === "boolean" ? { freeResize: input.freeResize } : undefined, + }, + ]; + if (name === "canvas_delete_nodes") return [{ type: "delete_node", ids: requireStringArray(input.ids, "ids") }]; + if (name === "canvas_connect_nodes") + return requireRecordArray(input.connections, "connections").map((connection) => ({ type: "connect_nodes", fromNodeId: requireString(connection.fromNodeId, "fromNodeId"), toNodeId: requireString(connection.toNodeId, "toNodeId") })); + if (name === "canvas_select_nodes") return [{ type: "select_nodes", ids: requireStringArray(input.ids, "ids") }]; + if (name === "canvas_set_viewport") return [{ type: "set_viewport", viewport: requireViewport(input.viewport) }]; + if (name === "canvas_run_generation") return [runGenerationOp(requireString(input.nodeId, "nodeId"), generationMode(input.mode), stringOptional(input.prompt), input.retry === true)]; + throw new Error(`不支持的工具:${name}`); +} + +function generationFlowOps(input: Record, snapshot: CanvasAgentSnapshot, config: AiConfig): CanvasAgentOp[] { + const mode = generationMode(input.mode); + const prompt = requireString(input.prompt, "prompt"); + const x = numberOr(input.x, nextCanvasX(snapshot)); + const y = numberOr(input.y, 0); + const textId = `text-${nanoid()}`; + const targetId = `${mode}-${nanoid()}`; + const referenceNodeIds = Array.isArray(input.referenceNodeIds) ? input.referenceNodeIds.filter((id): id is string => typeof id === "string") : []; + const promptNode: CanvasNodeData = { + id: textId, + type: CanvasNodeType.Text, + title: stringOptional(input.title) || "提示词", + position: { x, y }, + width: NODE_DEFAULT_SIZE[CanvasNodeType.Text].width, + height: NODE_DEFAULT_SIZE[CanvasNodeType.Text].height, + metadata: { content: prompt }, + }; + const referenceNodes = referenceNodeIds.flatMap((id) => { + const node = snapshot.nodes.find((candidate) => candidate.id === id); + return node ? [node] : []; + }); + const tokens = buildOrderedCanvasResourceReferences([promptNode, ...referenceNodes]).map(canvasResourceMentionToken); + return [ + textNodeOp({ id: textId, text: prompt, title: stringOptional(input.title) || "提示词" }, x, y), + generationTargetNodeOp(targetId, { ...input, prompt: tokens.join("\n") }, x + NODE_DEFAULT_SIZE[CanvasNodeType.Text].width + 80, y, config), + { type: "connect_nodes", fromNodeId: textId, toNodeId: targetId }, + ...referenceNodeIds.map((fromNodeId) => ({ type: "connect_nodes" as const, fromNodeId, toNodeId: targetId })), + { type: "select_nodes", ids: [targetId] }, + ...(input.autoRun ? [runGenerationOp(targetId, mode, tokens.join("\n"))] : []), + ]; +} + +function textNodeOp(input: Record, x: number, y: number): CanvasAgentOp { + return { + type: "add_node", + id: stringOptional(input.id), + nodeType: CanvasNodeType.Text, + title: stringOptional(input.title), + position: { x, y }, + width: numberOptional(input.width), + height: numberOptional(input.height), + metadata: { content: stringOptional(input.text), status: "success", fontSize: 14 }, + }; +} + +function generationTargetNodeOp(id: string, input: Record, x: number, y: number, config: AiConfig): CanvasAgentOp { + const mode = generationMode(input.mode); + const prompt = stringOptional(input.prompt); + const nodeType = generationNodeType(mode); + return { + type: "add_node", + id, + nodeType, + title: stringOptional(input.title) || generationTitle(mode), + position: { x, y }, + width: numberOptional(input.width), + height: numberOptional(input.height), + metadata: cleanRecord({ + content: "", + fontSize: nodeType === CanvasNodeType.Text ? 14 : undefined, + generationMode: mode, + composerContent: prompt, + prompt, + status: "idle", + model: resolveGenerationModel(config, mode, stringOptional(input.model)), + size: stringOptional(input.size) || config.size, + quality: stringOptional(input.quality) || config.quality, + transparentBackground: stringOptional(input.transparentBackground) || config.transparentBackground, + count: numberOptional(input.count) ?? generationCount(mode === "image" ? config.canvasImageCount || config.count : config.count), + seconds: stringOptional(input.seconds) || config.videoSeconds, + vquality: stringOptional(input.vquality) || config.vquality, + generateAudio: stringOptional(input.generateAudio) || config.videoGenerateAudio, + watermark: stringOptional(input.watermark) || config.videoWatermark, + audioVoice: stringOptional(input.audioVoice) || config.audioVoice, + audioFormat: stringOptional(input.audioFormat) || config.audioFormat, + audioSpeed: stringOptional(input.audioSpeed) || config.audioSpeed, + audioInstructions: stringOptional(input.audioInstructions) || config.audioInstructions, + }) as CanvasNodeData["metadata"], + }; +} + +function generationNodeType(mode: "text" | "image" | "video" | "audio") { + if (mode === "text") return CanvasNodeType.Text; + if (mode === "video") return CanvasNodeType.Video; + if (mode === "audio") return CanvasNodeType.Audio; + return CanvasNodeType.Image; +} + +function runGenerationOp(nodeId: string, mode: "text" | "image" | "video" | "audio", prompt?: string, retry?: boolean): CanvasAgentOp { + return { type: "run_generation", nodeId, mode, prompt, ...(retry ? { retry: true } : {}) }; +} + +export function toolCallsFromDetail(detail: Record): ResponseToolCall[] { + return Array.isArray(detail.toolCalls) ? (detail.toolCalls.filter(isResponseToolCall) as ResponseToolCall[]) : []; +} + +function isResponseToolCall(value: unknown): value is ResponseToolCall { + const item = objectDetail(value); + const fn = objectDetail(item.function); + return typeof item.id === "string" && item.type === "function" && typeof fn.name === "string" && typeof fn.arguments === "string"; +} + +export function toolCallToResponseInput(call: ResponseToolCall): ResponseInputMessage { + return { type: "function_call", call_id: call.id, name: call.function.name, arguments: call.function.arguments, ...(call.thoughtSignature ? { thoughtSignature: call.thoughtSignature } : {}) }; +} + +// 上游兼容兜底:DeepSeek 等 OpenAI 兼容渠道的思考/推理模式不允许 +// tool_choice=required(后端归类文案见 providerPayloadErrorCategory)。 +// required 只用于对话首步把智能体拖入工具循环;遇到该类拒绝时降级为 auto +// 重试一次——auto 在思考模式下可用,对话与工具调用均不受影响。 +// 语义与 image.ts 对非 auto tool_choice 的兼容降级策略一致。 +const THINKING_MODE_TOOL_CHOICE_REJECTION = "不支持强制工具调用"; + +export async function requestOnlineAgentModel(config: AiConfig, messages: ResponseInputMessage[], toolChoice: "auto" | "required", prompt: string, onDelta: (text: string) => void) { + const submit = (choice: "auto" | "required") => runBackendToolGenerationTask({ prompt, config, messages, tools: CANVAS_ONLINE_AGENT_TOOLS, toolChoice: choice, onDelta }); + try { + return await submit(toolChoice); + } catch (error) { + if (toolChoice !== "required" || !(error instanceof Error) || !error.message.includes(THINKING_MODE_TOOL_CHOICE_REJECTION)) { + throw error; + } + return submit("auto"); + } +} + +export function summarizeToolCalls(calls: ResponseToolCall[]) { + return calls.map((call) => toolCallLabel(call.function.name)).join(",") || "工具调用"; +} + +export function previewOnlineToolCalls(calls: ResponseToolCall[], snapshot: CanvasAgentSnapshot, config: AiConfig): CanvasAgentOperationImpact { + const ops: CanvasAgentOp[] = []; + let deferredCinematicCount = 0; + calls.filter(isWritableToolCall).forEach((call) => { + if (call.function.name === "canvas_create_cinematic_session") { + deferredCinematicCount += 1; + return; + } + try { + ops.push(...onlineToolToOps(call.function.name, parseToolArguments(call.function.arguments), snapshot, config)); + } catch { + // 参数错误会在真正执行时显式失败;预览阶段只展示可确定的影响。 + } + }); + const impact = previewCanvasAgentOps(ops, snapshot); + if (!deferredCinematicCount) return impact; + return { + ...impact, + operationCount: impact.operationCount + deferredCinematicCount, + items: [...impact.items, "启动影视 Agent,会话完成后将剧本、分镜和生成节点写回当前画布"].slice(0, 8), + warning: [impact.warning, "影视 Agent 的具体写回范围将在后端完成拆解后确定。"].filter(Boolean).join(" "), + }; +} + +function toolCallLabel(name: string) { + if (name === "canvas_list_skills") return "列出技能"; + if (name === "canvas_get_skill") return "读取技能入口"; + if (name === "canvas_list_skill_files") return "列出技能文件"; + if (name === "canvas_read_skill_file") return "读取技能文件"; + if (name === "canvas_search_skill_files") return "搜索技能文件"; + if (name === "canvas_apply_ops") return "画布操作"; + if (name === "canvas_get_state") return "读取画布"; + if (name === "canvas_get_context") return "读取上下文"; + if (name === "canvas_find_nodes") return "检索节点"; + if (name === "canvas_get_node") return "读取节点"; + if (name === "canvas_get_connection") return "读取连线"; + if (name === "canvas_get_generation_tasks") return "读取生成任务"; + if (name === "canvas_get_resources") return "读取资源"; + if (name === "canvas_validate_ops") return "校验操作"; + if (name === "canvas_get_selection") return "读取选区"; + if (name === "canvas_export_snapshot") return "导出快照"; + if (name === "canvas_create_cinematic_session") return "创建影视项目"; + if (name === "canvas_create_workflow") return "创建工作流"; + if (name === "canvas_create_node") return "创建节点"; + if (name === "canvas_create_text_node") return "创建文本"; + if (name === "canvas_create_text_nodes") return "批量创建文本"; + if (name === "canvas_create_image_prompt_flow") return "创建生图流程"; + if (name === "canvas_create_generation_flow") return "创建生成流程"; + if (name === "canvas_generate_text") return "生成文本"; + if (name === "canvas_generate_image") return "生成图片"; + if (name === "canvas_generate_video") return "生成视频"; + if (name === "canvas_generate_audio") return "生成音频"; + if (name === "canvas_update_node") return "更新节点"; + if (name === "canvas_update_node_text") return "更新文本"; + if (name === "canvas_move_nodes") return "移动节点"; + if (name === "canvas_resize_node") return "调整节点尺寸"; + if (name === "canvas_delete_nodes") return "删除节点"; + if (name === "canvas_connect_nodes") return "连接节点"; + if (name === "canvas_select_nodes") return "选择节点"; + if (name === "canvas_set_viewport") return "调整视口"; + if (name === "canvas_run_generation") return "触发生成"; + return name; +} + +export function toolResultText(result: OnlineToolResult) { + return result.message; +} + +function requireStringArray(value: unknown, field: string): string[] { + if (!Array.isArray(value)) throw new Error(`${field} 必须是字符串数组`); + if (!value.every((item) => typeof item === "string" && Boolean(item))) throw new Error(`${field} 必须只包含非空字符串`); + return value as string[]; +} + +export function requireOps(value: unknown): CanvasAgentOp[] { + if (!Array.isArray(value)) throw new Error("ops 必须是数组"); + return value.map(toCanvasAgentOp); +} + +function toCanvasAgentOp(value: unknown): CanvasAgentOp { + const item = objectDetail(value); + const type = item.type; + if (type === "add_node") { + return { + type, + id: stringOptional(item.id), + nodeType: item.nodeType ? requireNodeType(item.nodeType) : undefined, + title: stringOptional(item.title), + position: recordOptional(item.position) ? { x: requireNumber(objectDetail(item.position).x, "position.x"), y: requireNumber(objectDetail(item.position).y, "position.y") } : undefined, + x: numberOptional(item.x), + y: numberOptional(item.y), + width: numberOptional(item.width), + height: numberOptional(item.height), + metadata: recordOptional(item.metadata) as CanvasNodeData["metadata"], + }; + } + if (type === "update_node") return { type, id: requireString(item.id, "id"), patch: recordOptional(item.patch) as Partial | undefined, metadata: recordOptional(item.metadata) as CanvasNodeData["metadata"] }; + if (type === "delete_node") return { type, id: stringOptional(item.id), ids: Array.isArray(item.ids) ? requireStringArray(item.ids, "ids") : undefined }; + if (type === "delete_connections") return { type, id: stringOptional(item.id), ids: Array.isArray(item.ids) ? requireStringArray(item.ids, "ids") : undefined, all: typeof item.all === "boolean" ? item.all : undefined }; + if (type === "connect_nodes") return { type, id: stringOptional(item.id), fromNodeId: requireString(item.fromNodeId, "fromNodeId"), toNodeId: requireString(item.toNodeId, "toNodeId") }; + if (type === "set_viewport") return { type, viewport: requireViewport(item.viewport) }; + if (type === "select_nodes") return { type, ids: requireStringArray(item.ids, "ids") }; + if (type === "run_generation") return { type, nodeId: requireString(item.nodeId, "nodeId"), mode: generationMode(item.mode), prompt: stringOptional(item.prompt), ...(item.retry === true ? { retry: true } : {}) }; + throw new Error("不支持的画布操作类型"); +} + +function requireRecordArray(value: unknown, field: string): Record[] { + if (!Array.isArray(value)) throw new Error(`${field} 必须是数组`); + return value.map((item) => { + const record = objectDetail(item); + if (!Object.keys(record).length) throw new Error(`${field} 必须只包含对象`); + return record; + }); +} + +export function requireString(value: unknown, field: string) { + if (typeof value !== "string" || !value) throw new Error(`${field} 必须是非空字符串`); + return value; +} + +function requireNumber(value: unknown, field: string) { + if (typeof value !== "number" || !Number.isFinite(value)) throw new Error(`${field} 必须是数字`); + return value; +} + +function requireNodeType(value: unknown): CanvasNodeType { + if (Object.values(CanvasNodeType).includes(value as CanvasNodeType)) return value as CanvasNodeType; + throw new Error("节点类型必须是 text、image、config、video 或 audio"); +} + +function requireViewport(value: unknown) { + const item = objectDetail(value); + return { x: requireNumber(item.x, "viewport.x"), y: requireNumber(item.y, "viewport.y"), k: requireNumber(item.k, "viewport.k") }; +} + +function recordOptional(value: unknown) { + return value && typeof value === "object" && !Array.isArray(value) ? (value as Record) : undefined; +} + +function stringOptional(value: unknown) { + return typeof value === "string" ? value : ""; +} + +function numberOptional(value: unknown) { + return typeof value === "number" && Number.isFinite(value) ? value : undefined; +} + +function numberOr(value: unknown, fallback: number) { + return numberOptional(value) ?? fallback; +} + +function nextCanvasX(snapshot: CanvasAgentSnapshot) { + return snapshot.nodes.length ? Math.max(...snapshot.nodes.map((node) => node.position.x + node.width)) + 80 : 0; +} + +function generationMode(value: unknown): "text" | "image" | "video" | "audio" { + return value === "text" || value === "video" || value === "audio" ? value : "image"; +} + +function generationTitle(mode: "text" | "image" | "video" | "audio") { + if (mode === "text") return "文本生成"; + if (mode === "video") return "视频生成"; + if (mode === "audio") return "音频生成"; + return "图片生成"; +} + +function defaultGenerationModel(config: AiConfig, mode: "text" | "image" | "video" | "audio") { + if (mode === "image") return config.imageModel || config.model; + if (mode === "video") return config.videoModel || config.model; + if (mode === "audio") return config.audioModel || config.model; + return config.textModel || config.model; +} + +function resolveGenerationModel(config: AiConfig, mode: "text" | "image" | "video" | "audio", model?: string) { + const normalized = normalizeModelOptionValue(model, config.channels); + return normalized && selectableModelsByCapability(config, mode).includes(normalized) ? normalized : defaultGenerationModel(config, mode); +} + +function generationCount(value: string) { + return Math.max(1, Math.min(15, Math.floor(Math.abs(Number(value)) || 1))); +} + +function cleanRecord(value: Record) { + return Object.fromEntries(Object.entries(value).filter(([, item]) => item !== undefined && item !== "")); +} + +export function snapshotSignature(snapshot: CanvasAgentSnapshot) { + return JSON.stringify({ nodes: snapshot.nodes, connections: snapshot.connections, selectedNodeIds: snapshot.selectedNodeIds, viewport: snapshot.viewport }); +} + +export function explainNoop(ops: CanvasAgentOp[], snapshot: CanvasAgentSnapshot) { + if (!ops.length) return "模型没有返回可执行的画布操作。"; + const nodeIds = new Set(snapshot.nodes.map((node) => node.id)); + const connectionIds = new Set(snapshot.connections.map((conn) => conn.id)); + const deleteConnectionOps = ops.filter((op): op is Extract => op.type === "delete_connections"); + const connectOps = ops.filter((op): op is Extract => op.type === "connect_nodes"); + const deleteNodeOps = ops.filter((op): op is Extract => op.type === "delete_node"); + const updateOps = ops.filter((op): op is Extract => op.type === "update_node"); + const selectOps = ops.filter((op): op is Extract => op.type === "select_nodes"); + const generationOps = ops.filter((op): op is Extract => op.type === "run_generation"); + if (deleteConnectionOps.length && !snapshot.connections.length) return "画布当前没有连线可删除。"; + if (deleteConnectionOps.length && deleteConnectionOps.every((op) => !op.all && [...(op.ids || []), ...(op.id ? [op.id] : [])].every((id) => !connectionIds.has(id)))) return "没有找到要删除的连线。"; + if (connectOps.length && connectOps.every((op) => snapshot.connections.some((conn) => conn.fromNodeId === op.fromNodeId && conn.toNodeId === op.toNodeId))) return "这些节点已经存在对应连线,无需重复连接。"; + if (connectOps.length && connectOps.every((op) => !nodeIds.has(op.fromNodeId) || !nodeIds.has(op.toNodeId))) return "没有找到要连接的节点。"; + if (deleteNodeOps.length && deleteNodeOps.every((op) => op.nodeType === CanvasNodeType.Config) && !snapshot.nodes.some((node) => node.type === CanvasNodeType.Config)) return "画布当前没有生成配置节点可删除。"; + if (deleteNodeOps.length && deleteNodeOps.every((op) => [...(op.ids || []), ...(op.id ? [op.id] : [])].every((id) => !nodeIds.has(id)))) return "没有找到要删除的节点。"; + if (updateOps.length && updateOps.every((op) => !nodeIds.has(op.id))) return "没有找到要更新的节点。"; + if (selectOps.length && selectOps.every((op) => !(op.ids || []).some((id) => nodeIds.has(id)))) return "没有找到要选择的节点。"; + if (generationOps.length && generationOps.every((op) => !nodeIds.has(op.nodeId))) return "没有找到要触发生成的节点。"; + if (ops.every((op) => op.type === "set_viewport")) return "视图已经是目标状态。"; + if (selectOps.length && selectOps.every((op) => JSON.stringify(op.ids || []) === JSON.stringify(snapshot.selectedNodeIds))) return "选区已经是目标状态。"; + return "工具已执行,但画布状态没有变化;请在日志 tab 查看工具参数和执行前后状态。"; +} + +export function nodeToReference(node: CanvasNodeData): CanvasAssistantReference | null { + if (node.type === CanvasNodeType.Image && (node.metadata?.content || node.metadata?.storageKey)) { + return { id: node.id, type: node.type, title: node.title, dataUrl: node.metadata.content, storageKey: node.metadata.storageKey }; + } + if (node.type === CanvasNodeType.Text && node.metadata?.content) { + return { id: node.id, type: node.type, title: node.title, text: node.metadata.content }; + } + if (node.type === CanvasNodeType.Skill && node.metadata?.skillSnapshot) { + return { id: node.id, type: node.type, title: node.title, text: [node.metadata.skillSnapshot.name, node.metadata.skillSnapshot.template, node.metadata.skillSnapshot.outputContract].filter(Boolean).join("\n\n") }; + } + return null; +} + +export function buildAssistantReferences(nodes: CanvasNodeData[], selectedNodeIds: Set) { + const nodeById = new Map(nodes.map((node) => [node.id, node])); + return Array.from(selectedNodeIds) + .map((id) => nodeById.get(id)) + .filter((node): node is CanvasNodeData => Boolean(node)) + .map(nodeToReference) + .filter((item): item is CanvasAssistantReference => Boolean(item)); +} + +export async function buildToolAgentMessages(snapshot: CanvasAgentSnapshot, history: CanvasAssistantMessage[], userMessage: CanvasAssistantMessage, skills: Skill[] = []): Promise { + const refs = userMessage.references || []; + const skillCatalog = skills + .filter((skill) => skill.isAdded) + .slice(0, 40) + .map((skill) => `- ${skill.skillName}(${skill.skillId},v${skill.version || "1"},${skill.fileCount || 1} 文件):${skill.description}`) + .join("\n"); + const systemContent = buildCanvasOnlineAgentSystemContent(skillCatalog); + return [ + { role: "system", content: systemContent }, + ...history + .filter((message) => message.role === "user" || message.role === "assistant" || message.role === "system") + .slice(-8) + .map((message): ResponseInputMessage => ({ role: message.role as "system" | "user" | "assistant", content: message.text })), + { + role: "user", + content: [ + ...refs.flatMap((item) => (item.text ? [{ type: "text" as const, text: `选中节点 ${item.title}:${item.text}` }] : [])), + { type: "text", text: `当前画布:${JSON.stringify(compactSnapshot(snapshot))}\n\n用户需求:${userMessage.text}` }, + ...(await Promise.all(refs.filter((item) => item.dataUrl).map(async (item) => ({ type: "image_url" as const, image_url: { url: await imageToDataUrl(item) } })))), + ], + }, + ]; +} + +export function compactSnapshot(snapshot: CanvasAgentSnapshot) { + return { + title: snapshot.title, + viewport: snapshot.viewport, + selectedNodeIds: snapshot.selectedNodeIds, + nodes: snapshot.nodes.map((node) => ({ + id: node.id, + type: node.type, + title: node.title, + position: node.position, + width: node.width, + height: node.height, + metadata: compactMetadata(node.metadata || {}), + })), + connections: snapshot.connections, + }; +} + +function compactMetadata(metadata: CanvasNodeData["metadata"]) { + return { + content: String(metadata?.content || "").slice(0, 500), + prompt: String(metadata?.prompt || metadata?.composerContent || "").slice(0, 500), + status: metadata?.status, + skillName: metadata?.skillSnapshot?.name, + skillVersion: metadata?.skillSnapshot?.version, + generationMode: metadata?.generationMode, + model: metadata?.model, + size: metadata?.size, + assetTags: metadata?.assetTags, + workflowKind: metadata?.workflowKind, + workflowTitle: metadata?.workflowTitle, + workflowDescription: metadata?.workflowDescription, + characterName: metadata?.characterName, + characterAssetId: metadata?.characterAssetId, + characterVersionId: metadata?.characterVersionId, + chapterId: metadata?.chapterId, + chapterTitle: metadata?.chapterTitle, + shotIndex: metadata?.shotIndex, + }; +} + +export function backendAgentProviderConfig(config: ReturnType) { + return { + channelId: config.channelId, + apiFormat: config.apiFormat, + interfaceType: config.interfaceType, + baseUrl: config.baseUrl, + allowLocalChannel: config.allowLocalChannel === true, + apiKey: config.apiKey, + secretKey: config.secretKey, + model: config.model, + size: config.size, + quality: config.quality, + transparentBackground: config.transparentBackground, + count: config.count, + videoSeconds: config.videoSeconds, + vquality: config.vquality, + videoGenerateAudio: config.videoGenerateAudio, + videoWatermark: config.videoWatermark, + audioVoice: config.audioVoice, + audioFormat: config.audioFormat, + audioSpeed: config.audioSpeed, + audioInstructions: config.audioInstructions, + systemPrompt: config.systemPrompt, + }; +} + +export function cinematicSessionMessageId(backendSessionId: string) { + return `cinematic-session:${backendSessionId}`; +} + +export function upsertAssistantMessage(messages: CanvasAssistantMessage[], message: CanvasAssistantMessage) { + const exists = messages.some((item) => item.id === message.id); + return exists ? messages.map((item) => (item.id === message.id ? { ...item, ...message } : item)) : [...messages, message]; +} + +export function createSession(): CanvasAssistantSession { + const now = new Date().toISOString(); + return { id: nanoid(), title: "新对话", messages: [], createdAt: now, updatedAt: now }; +} diff --git a/web/src/components/canvas/canvas-assistant-panel-views.tsx b/web/src/components/canvas/canvas-assistant-panel-views.tsx new file mode 100644 index 000000000..00db53384 --- /dev/null +++ b/web/src/components/canvas/canvas-assistant-panel-views.tsx @@ -0,0 +1,251 @@ +import { Button, Select } from "antd"; +import { Tooltip } from "@/components/ui/base/tooltip"; +import { useMemo, useState } from "react"; +import { AtSign, Cpu, Trash2, X } from "lucide-react"; + +import { modelDisplayName, modelIcon, resolveModelChannel, selectableModelsByCapability, type AiConfig } from "@/stores/use-config-store"; +import { canvasThemes } from "@/lib/canvas-theme"; +import { AgentImagePreview, type CanvasAgentChatMessage } from "./canvas-agent-chat-ui"; +import { ModelLogo } from "@/components/model-logo"; +import { useResolvedCanvasResourceReferences } from "./use-resolved-canvas-resource-references"; +import { imageReferenceLabel } from "@/lib/image-reference-prompt"; +import { type CanvasResourceReference } from "@/lib/canvas/canvas-resource-references"; +import { CanvasNodeType, type CanvasAssistantMessage, type CanvasAssistantReference, type CanvasAssistantSession } from "@/types/canvas"; +import { useThemeStore } from "@/stores/use-theme-store"; + +export function AgentTextModelPicker({ config, value, onChange }: { config: AiConfig; value: string; onChange: (model: string) => void }) { + const options = useMemo(() => Array.from(new Set([value, ...selectableModelsByCapability(config, "text")].filter(Boolean))), [config, value]); + const current = value || ""; + return ( +
event.stopPropagation()} onPointerDown={(event) => event.stopPropagation()}> + + size="small" + variant="borderless" + value={current || undefined} + className="agent-text-model-select w-full" + popupMatchSelectWidth={288} + options={options.map((model) => ({ value: model, label: agentModelLabel(config, model) }))} + notFoundContent={暂无文本模型} + optionRender={(option) => { + const model = String(option.value); + return ( + + + {modelDisplayName(config, model)} + {agentModelSource(config, model) ? {agentModelSource(config, model)} : null} + + ); + }} + labelRender={() => ( + + + {current ? modelDisplayName(config, current) : "选择文本模型"} + {current && agentModelSource(config, current) ? {agentModelSource(config, current)} : null} + + )} + onChange={onChange} + aria-label="选择 Agent 文本模型" + title={current ? agentModelLabel(config, current) : "选择文本模型"} + /> +
+ ); +} + +export function agentModelSource(config: AiConfig, model: string) { + const channel = resolveModelChannel(config, model); + return channel.scope === "system" ? "" : channel.name; +} + +export function agentModelLabel(config: AiConfig, model: string) { + const source = agentModelSource(config, model); + return source ? `${modelDisplayName(config, model)} · ${source}` : modelDisplayName(config, model); +} + +export function AgentModelIcon({ config, model }: { config: AiConfig; model: string }) { + const icon = modelIcon(config, model); + return icon ? : ; +} + +export function AssistantHistory({ sessions, activeSession, onOpen, onDelete }: { sessions: CanvasAssistantSession[]; activeSession: CanvasAssistantSession | null; onOpen: (id: string) => void; onDelete: (id: string) => void }) { + const theme = canvasThemes[useThemeStore((state) => state.theme)]; + + return ( +
+
+ {sessions.length ? `${sessions.length} 条历史` : "暂无历史"} +
+ {sessions.map((session) => ( +
+
+
+
+ {session.id === activeSession?.id ? ( + + 当前 + + ) : null} +
{session.title}
+
+
{sessionPreview(session)}
+
+
+ {formatSessionTime(session.updatedAt || session.createdAt)} + + +
+
+
+ ))} + {!sessions.length ? ( +
+ 网站 Agent 的对话记录会显示在这里 +
+ ) : null} +
+ ); +} + +export function MessageReferences({ message }: { message: CanvasAssistantMessage }) { + return ( +
+ {message.references?.map((item, index, references) => ( + + ))} +
+ ); +} + +export function promptAlreadyHasMention(prompt: string, label: string) { + const token = `@${label}`; + let from = 0; + while (from <= prompt.length) { + const index = prompt.indexOf(token, from); + if (index < 0) return false; + const next = prompt[index + token.length]; + if (!next || /\s|[,.!?;:,。!?;:、)\]}】)]/.test(next)) return true; + from = index + 1; + } + return false; +} + +export function AssistantReferenceChip({ item, label, previewUrl, onRemove, onInsertMention }: { item: CanvasAssistantReference; label?: string; previewUrl?: string; onRemove?: () => void; onInsertMention?: (label: string) => void }) { + const theme = canvasThemes[useThemeStore((state) => state.theme)]; + const [previewOpen, setPreviewOpen] = useState(false); + const mentionReference = useMemo(() => assistantReferencesToMentionReferences([item]), [item]); + const resolvedReference = useResolvedCanvasResourceReferences(mentionReference)[0]; + const text = (item.text || item.title).replace(/\s+/g, " ").trim().slice(0, 1) || "文"; + const imageUrl = previewUrl || resolvedReference?.previewUrl || item.dataUrl; + const hasImage = Boolean(imageUrl || item.storageKey); + const chipStyle = { background: theme.spatial.surface, color: theme.node.text }; + const actionStyle = { background: theme.toolbar.panel, borderColor: theme.node.stroke, color: theme.node.text }; + + if (hasImage) { + return ( + <> + + + {label && onInsertMention ? ( + + ) : label ? ( + + + {label} + + ) : null} + {onRemove ? ( + + ) : null} + + {previewOpen && imageUrl ? setPreviewOpen(false)} /> : null} + + ); + } + + return ( + + + {text} + + {item.title} + {onRemove ? ( + + ) : null} + + ); +} + +export function assistantReferencesToMentionReferences(references: CanvasAssistantReference[]): CanvasResourceReference[] { + return references.flatMap((item, index): CanvasResourceReference[] => { + if (item.dataUrl || item.storageKey) { + const label = assistantImageReferenceLabel(references, index) || item.title; + return [{ id: item.id, nodeId: item.id, kind: "image", label, title: item.title, previewUrl: item.dataUrl, storageKey: item.storageKey, active: true }]; + } + if (item.text) { + const label = item.type === CanvasNodeType.Skill ? `技能${index + 1}` : `文本${index + 1}`; + return [{ id: item.id, nodeId: item.id, kind: item.type === CanvasNodeType.Skill ? "skill" : "text", label, title: item.title, text: item.text, active: true }]; + } + return []; + }); +} + +export function assistantImageReferenceLabel(references: CanvasAssistantReference[], index: number) { + if (!references[index]?.dataUrl && !references[index]?.storageKey) return undefined; + const imageIndex = references.slice(0, index + 1).filter((item) => item.dataUrl || item.storageKey).length - 1; + return imageIndex >= 0 ? imageReferenceLabel(imageIndex) : undefined; +} + +export function assistantMessageToChatMessage(message: CanvasAssistantMessage): CanvasAgentChatMessage { + return { id: message.id, role: message.role, title: message.title, text: message.text, meta: message.meta, detail: message.detail }; +} + +export function formatSessionTime(value?: string) { + return value ? new Date(value).toLocaleString() : ""; +} + +export function sessionPreview(session: CanvasAssistantSession) { + return session.messages.at(-1)?.text || `${session.messages.length} 条消息`; +} + diff --git a/web/src/components/canvas/canvas-assistant-panel.tsx b/web/src/components/canvas/canvas-assistant-panel.tsx index 2b26a9b22..e1c68fe8e 100644 --- a/web/src/components/canvas/canvas-assistant-panel.tsx +++ b/web/src/components/canvas/canvas-assistant-panel.tsx @@ -1,251 +1,48 @@ -import { Button, Modal, Segmented, Select } from "antd"; +import { Button, Modal } from "antd"; import { Tooltip } from "@/components/ui/base/tooltip"; import { useEffect, useMemo, useRef, useState } from "react"; -import copyToClipboard from "copy-to-clipboard"; -import { Copy, Cpu, Settings2, Trash2, X, AtSign } from "lucide-react"; +import { X } from "lucide-react"; import { motion } from "motion/react"; -import { modelDisplayName, modelIcon, normalizeModelOptionValue, resolveModelChannel, resolveModelRequestConfig, selectableModelsByCapability, useConfigStore, useEffectiveConfig, type AiConfig } from "@/stores/use-config-store"; +import { resolveModelRequestConfig, useConfigStore, useEffectiveConfig, type AiConfig } from "@/stores/use-config-store"; import { canvasThemes } from "@/lib/canvas-theme"; import { nanoid } from "nanoid"; -import { type ResponseFunctionTool, type ResponseInputMessage, type ResponseToolCall } from "@/services/api/image"; -import { runBackendToolGenerationTask } from "@/services/api/generation-task"; -import { imageToDataUrl } from "@/services/image-storage"; -import { isCanvasGenerationDurableAckError, persistCanvasCinematicSessionContinuationEffect } from "@/services/canvas-generation-consumer"; +import { type ResponseInputMessage, type ResponseToolCall } from "@/services/api/image"; import { consumeGenerationTaskAgent } from "@/services/project-asset-sync"; import { applyGenerationConsumerEffect, generationEffectApplied } from "@/services/generation-consumer-dedupe"; import { activeGenerationConsumerController } from "@/services/generation-consumer-lifecycle"; import { useAssetStore } from "@/stores/use-asset-store"; import { useThemeStore } from "@/stores/use-theme-store"; import { useUserStore } from "@/stores/use-user-store"; -import { imageReferenceLabel } from "@/lib/image-reference-prompt"; import { navigateToSettings } from "@/lib/settings-navigation"; import { cinematicAgentSessionOpsJson, createCinematicAgentSession, isAgentSessionPollingAbort, resumeCinematicAgentSession } from "@/lib/canvas/canvas-agent-session"; import { summarizeCanvasContext } from "@/lib/canvas/canvas-context-summary"; -import { buildOrderedCanvasResourceReferences, canvasResourceMentionToken, type CanvasResourceReference } from "@/lib/canvas/canvas-resource-references"; -import { AgentChatComposer, AgentChatMessage, AgentImagePreview, AgentWorkingMessage, type CanvasAgentChatMessage, type CanvasAgentMode } from "./canvas-agent-chat-ui"; +import { AgentChatComposer, AgentChatMessage, AgentWorkingMessage, type CanvasAgentMode } from "./canvas-agent-chat-ui"; import { VoiceRecordingButton } from "@/components/conversation/voice-recording-button"; -import { ModelLogo } from "@/components/model-logo"; import { AgentChatEmptyState, AgentPanelChrome } from "./canvas-agent-panel-chrome"; import { CanvasLocalAgentPanel } from "./canvas-local-agent-panel"; import { useResolvedCanvasResourceReferences } from "./use-resolved-canvas-resource-references"; -import { NODE_DEFAULT_SIZE } from "@/constant/canvas"; -import { CanvasNodeType, type CanvasAssistantMessage, type CanvasAssistantPendingBackendSession, type CanvasAssistantReference, type CanvasAssistantSession, type CanvasNodeData } from "@/types/canvas"; +import { type CanvasAssistantMessage, type CanvasAssistantPendingBackendSession, type CanvasAssistantReference, type CanvasAssistantSession, type CanvasNodeData } from "@/types/canvas"; import { useCanvasAgentStore } from "@/stores/canvas/use-canvas-agent-store"; -import { canvasAgentPostconditionMessage, previewCanvasAgentOps, summarizeCanvasAgentOps, verifyCanvasAgentOps, type CanvasAgentOp, type CanvasAgentOperationImpact, type CanvasAgentSnapshot } from "@/lib/canvas/canvas-agent-ops"; +import { canvasAgentPostconditionMessage, summarizeCanvasAgentOps, verifyCanvasAgentOps, type CanvasAgentOp, type CanvasAgentSnapshot } from "@/lib/canvas/canvas-agent-ops"; import { buildCanvasAgentContext, findCanvasAgentNodes, getCanvasAgentConnection, getCanvasAgentGenerationTasks, getCanvasAgentNode, getCanvasAgentResources, validateCanvasAgentOps } from "@/lib/canvas/canvas-agent-context"; import { resolveStoryboardGenerationContext } from "@/lib/canvas/canvas-storyboard-context"; -import { buildCanvasWorkflowOps, looksLikeWorkflowRequest, type CanvasWorkflowInput } from "@/lib/canvas/canvas-agent-workflow"; +import { isWritableToolCall } from "@/lib/canvas/canvas-agent-protocol"; import { listAddedSkills, type Skill } from "@/services/api/skills"; -import { buildSkillMentionReferences, SKILL_RUNTIME_AGENT_GUIDANCE, skillRuntime } from "@/services/skill-runtime"; +import { buildSkillMentionReferences, skillRuntime } from "@/services/skill-runtime"; +import { handleCinematicContinuationFailure, canvasCinematicContinuationEntryAdapters, type CinematicContinuationFailureDisposition, type CinematicContinuationLiveSessionState } from "./canvas-cinematic-continuation"; +import { AgentTextModelPicker, AssistantHistory, AssistantReferenceChip, MessageReferences, assistantImageReferenceLabel, assistantMessageToChatMessage, assistantReferencesToMentionReferences, promptAlreadyHasMention } from "./canvas-assistant-panel-views"; +import { backendAgentProviderConfig, buildAssistantReferences, buildToolAgentMessages, cinematicSessionMessageId, compactSnapshot, createSession, describeCanvasSnapshot, explainNoop, nodeToReference, objectDetail, onlineToolToOps, parseToolArguments, previewOnlineToolCalls, requestOnlineAgentModel, requireOps, requireString, snapshotSignature, summarizeToolCalls, toolCallToResponseInput, toolCallsFromDetail, toolResultText, upsertAssistantMessage, type OnlineToolResult } from "./canvas-assistant-online-tools"; export const CANVAS_AGENT_PANEL_MOTION_MS = 500; const PANEL_MOTION_SECONDS = CANVAS_AGENT_PANEL_MOTION_MS / 1000; const ONLINE_AGENT_MAX_STEPS = 8; -const ONLINE_AGENT_PROMPT = - `你是当前创作工作台内置的在线画布助手。首轮必须先调用 canvas_get_context;涉及已有节点时用 canvas_find_nodes 获取真实 id,涉及媒体参考时用 canvas_get_resources。流水线、工作流、管线、节点图或用户要求连线时,必须使用 canvas_create_workflow:把需求拆成有语义的节点类型、真实内容/提示词、边和布局,禁止把业务阶段退化成几个空文本卡片;工具会自动分配 id、布局并建立连线。复杂写操作先 canvas_validate_ops,再执行 canvas_apply_ops。任何写入后都必须检查工具返回的真实节点类型、connectionCount、overlapWarnings 和 verification;没有真实连线时绝不能说已连线,没有生成资源时绝不能说已完成。不要输出 JSON ops、不要猜 id、不要把未就绪资源当作可用素材、不要编造执行结果。需要用户选择时,给出可点击的短选项,不要只让用户输入 1、2、3。${SKILL_RUNTIME_AGENT_GUIDANCE}`; -const JSON_RECORD_SCHEMA = { type: "object", additionalProperties: true }; -const POSITION_SCHEMA = { type: "object", properties: { x: { type: "number" }, y: { type: "number" } }, required: ["x", "y"], additionalProperties: false }; -const VIEWPORT_SCHEMA = { type: "object", properties: { x: { type: "number" }, y: { type: "number" }, k: { type: "number" } }, required: ["x", "y", "k"], additionalProperties: false }; -const NODE_TYPE_SCHEMA = { type: "string", enum: ["image", "text", "skill", "video", "audio"] }; -const WORKFLOW_NODE_KIND_SCHEMA = { type: "string", enum: ["text", "script", "image", "video", "audio", "character_cards", "character_three_view", "storyboard_video"] }; -const GENERATION_MODE_SCHEMA = { type: "string", enum: ["text", "image", "video", "audio"] }; -const GENERATION_OPTION_PROPERTIES = { - model: { type: "string" }, - size: { type: "string" }, - quality: { type: "string" }, - transparentBackground: { type: "string", enum: ["true", "false"] }, - count: { type: "number" }, - seconds: { type: "string" }, - vquality: { type: "string" }, - generateAudio: { type: "string" }, - watermark: { type: "string" }, - audioVoice: { type: "string" }, - audioFormat: { type: "string" }, - audioSpeed: { type: "string" }, - audioInstructions: { type: "string" }, -}; -const CANVAS_OP_SCHEMA = { - type: "object", - properties: { - type: { type: "string", enum: ["add_node", "update_node", "delete_node", "delete_connections", "connect_nodes", "set_viewport", "select_nodes", "run_generation"] }, - id: { type: "string" }, - ids: { type: "array", items: { type: "string" } }, - nodeType: NODE_TYPE_SCHEMA, - title: { type: "string" }, - x: { type: "number" }, - y: { type: "number" }, - width: { type: "number" }, - height: { type: "number" }, - position: POSITION_SCHEMA, - metadata: JSON_RECORD_SCHEMA, - patch: JSON_RECORD_SCHEMA, - all: { type: "boolean" }, - fromNodeId: { type: "string" }, - toNodeId: { type: "string" }, - viewport: VIEWPORT_SCHEMA, - nodeId: { type: "string" }, - mode: GENERATION_MODE_SCHEMA, - prompt: { type: "string" }, - retry: { type: "boolean" }, - }, - required: ["type"], - additionalProperties: false, -}; -const ONLINE_READ_TOOLS = new Set([...skillRuntime.agentToolNames("onlineAgent"), "canvas_get_state", "canvas_get_context", "canvas_find_nodes", "canvas_get_node", "canvas_get_connection", "canvas_get_generation_tasks", "canvas_get_resources", "canvas_validate_ops", "canvas_get_selection", "canvas_export_snapshot"]); -function toolDefinition(name: string, description: string, properties: Record, required: string[] = [], strict = false): ResponseFunctionTool { - return { type: "function", function: { name, description, parameters: { type: "object", properties, required, additionalProperties: false }, strict } }; -} - -function generationToolDefinition(name: string, description: string, mode?: "text" | "image" | "video" | "audio") { - return toolDefinition( - name, - description, - { - prompt: { type: "string" }, - title: { type: "string" }, - x: { type: "number" }, - y: { type: "number" }, - referenceNodeIds: { type: "array", items: { type: "string" } }, - ...(mode ? {} : { mode: GENERATION_MODE_SCHEMA }), - autoRun: { type: "boolean" }, - ...GENERATION_OPTION_PROPERTIES, - }, - ["prompt"], - ); -} - -const ONLINE_AGENT_TOOLS: ResponseFunctionTool[] = [ - ...skillRuntime.agentTools("onlineAgent"), - toolDefinition("canvas_get_state", "读取当前网页画布的节点、连线、选区和视口。", {}), - toolDefinition("canvas_get_context", "读取语义化画布上下文、真实节点 id、连接关系、资源就绪状态和状态哈希。", {}), - toolDefinition("canvas_find_nodes", "按标题、内容、提示词、类型、状态或资产检索真实节点。", { query: { type: "string" }, ids: { type: "array", items: { type: "string" } }, types: { type: "array", items: { type: "string" } }, statuses: { type: "array", items: { type: "string" } }, resourceOnly: { type: "boolean" }, limit: { type: "number" } }), - toolDefinition("canvas_get_node", "按真实节点 id 精确读取单个节点、资源状态和关联连线。", { id: { type: "string" } }, ["id"]), - toolDefinition("canvas_get_connection", "按真实连线 id 精确读取端点节点和 handle 信息。", { id: { type: "string" } }, ["id"]), - toolDefinition("canvas_get_generation_tasks", "读取当前画布绑定的生成任务观察状态,不主动轮询上游。", { status: { type: "string" }, nodeIds: { type: "array", items: { type: "string" } }, limit: { type: "number" } }), - toolDefinition("canvas_get_resources", "读取画布媒体资源引用、类型、尺寸、大小、时长和就绪状态,不返回媒体 URL。", { nodeIds: { type: "array", items: { type: "string" } }, status: { type: "string" }, limit: { type: "number" } }), - toolDefinition("canvas_validate_ops", "在写入前校验节点 id、连接关系和批量操作参数。", { ops: { type: "array", items: CANVAS_OP_SCHEMA } }, ["ops"]), - toolDefinition("canvas_get_selection", "读取当前网页画布选中的节点。", {}), - toolDefinition("canvas_export_snapshot", "导出当前画布快照,用于理解布局。", {}), - toolDefinition( - "canvas_apply_ops", - "批量操作当前网页画布。复杂写操作应先 canvas_validate_ops;可传 canvas_get_context 返回的 expectedStateHash 防止基于过期状态写入。", - { ops: { type: "array", items: CANVAS_OP_SCHEMA }, expectedRevision: { type: "number" }, expectedStateHash: { type: "string" } }, - ["ops"], - false, - ), - toolDefinition( - "canvas_create_workflow", - "创建语义化工作流/流水线:节点使用真实的文本、脚本、图片、视频或音频类型;character_cards=角色拆分图片卡片,character_three_view=角色三视图,storyboard_video=分镜剧情视频。工具会自动生成唯一 id、按节点实际尺寸布局、创建 edges/referenceRefs/referenceNodeIds 连线、选择新节点并复核重叠。媒体节点必须提供有意义的 prompt 或 content;已有素材先 canvas_find_nodes/canvas_get_resources,再把真实 node id 放入 referenceNodeIds。不要用 canvas_create_text_nodes 代替工作流。", - { - title: { type: "string" }, - description: { type: "string" }, - nodes: { - type: "array", - minItems: 1, - items: { - type: "object", - properties: { - ref: { type: "string" }, - kind: WORKFLOW_NODE_KIND_SCHEMA, - title: { type: "string" }, - content: { type: "string" }, - prompt: { type: "string" }, - description: { type: "string" }, - referenceRefs: { type: "array", items: { type: "string" } }, - referenceNodeIds: { type: "array", items: { type: "string" } }, - runGeneration: { type: "boolean" }, - width: { type: "number" }, - height: { type: "number" }, - }, - required: ["ref", "kind", "title"], - additionalProperties: false, - }, - }, - edges: { type: "array", items: { type: "object", properties: { from: { type: "string" }, to: { type: "string" } }, required: ["from", "to"], additionalProperties: false } }, - direction: { type: "string", enum: ["horizontal", "vertical"] }, - start: POSITION_SCHEMA, - gap: { type: "number" }, - autoRun: { type: "boolean" }, - }, - ["nodes"], - ), - toolDefinition( - "canvas_create_node", - "创建任意类型节点:text、image、video、audio。适合创建文本、媒体占位或自定义 metadata 节点。", - { nodeType: NODE_TYPE_SCHEMA, title: { type: "string" }, x: { type: "number" }, y: { type: "number" }, width: { type: "number" }, height: { type: "number" }, metadata: JSON_RECORD_SCHEMA }, - ["nodeType"], - ), - toolDefinition("canvas_create_text_node", "在当前画布创建单个文本节点。", { text: { type: "string" }, x: { type: "number" }, y: { type: "number" }, title: { type: "string" }, width: { type: "number" }, height: { type: "number" } }), - toolDefinition( - "canvas_create_text_nodes", - "批量创建文本节点,适合生成标题、段落、脚本、说明等内容块。", - { - items: { - type: "array", - minItems: 1, - items: { - type: "object", - properties: { text: { type: "string" }, title: { type: "string" }, x: { type: "number" }, y: { type: "number" }, width: { type: "number" }, height: { type: "number" } }, - required: ["text"], - additionalProperties: false, - }, - }, - x: { type: "number" }, - y: { type: "number" }, - gap: { type: "number" }, - direction: { type: "string", enum: ["row", "column"] }, - }, - ["items"], - ), - toolDefinition("canvas_create_cinematic_session", "把自然语言创作指令提交给后端影视 Agent 会话,后端拆解为剧本、场景、分镜、镜头和成片节点,并返回可写回画布的操作。", { prompt: { type: "string" } }, ["prompt"]), - toolDefinition( - "canvas_create_image_prompt_flow", - "创建提示词文本节点和图片目标节点并自动连线,可选择立即触发生图。", - { prompt: { type: "string" }, x: { type: "number" }, y: { type: "number" }, autoRun: { type: "boolean" }, ...GENERATION_OPTION_PROPERTIES }, - ["prompt"], - ), - generationToolDefinition("canvas_create_generation_flow", "创建通用生成流程:提示词文本节点、对应类型的生成目标节点和参考节点连线,可用于文案、生图、视频或音频。"), - generationToolDefinition("canvas_generate_text", "创建通用文本生成流程并立即触发生成。", "text"), - generationToolDefinition("canvas_generate_image", "创建通用图片生成流程并立即触发生成。", "image"), - generationToolDefinition("canvas_generate_video", "创建通用视频生成流程并立即触发生成。", "video"), - generationToolDefinition("canvas_generate_audio", "创建通用音频生成流程并立即触发生成。", "audio"), - toolDefinition("canvas_update_node", "更新节点基础字段或 metadata。", { id: { type: "string" }, patch: JSON_RECORD_SCHEMA, metadata: JSON_RECORD_SCHEMA }, ["id"]), - toolDefinition("canvas_update_node_text", "更新文本节点内容和标题。", { id: { type: "string" }, text: { type: "string" }, title: { type: "string" } }, ["id", "text"]), - toolDefinition( - "canvas_move_nodes", - "移动一个或多个节点,支持绝对坐标或 dx/dy 偏移。", - { - items: { - type: "array", - minItems: 1, - items: { type: "object", properties: { id: { type: "string" }, x: { type: "number" }, y: { type: "number" }, dx: { type: "number" }, dy: { type: "number" } }, required: ["id"], additionalProperties: false }, - }, - }, - ["items"], - ), - toolDefinition("canvas_resize_node", "调整节点尺寸。", { id: { type: "string" }, width: { type: "number" }, height: { type: "number" }, freeResize: { type: "boolean" } }, ["id", "width", "height"]), - toolDefinition("canvas_delete_nodes", "删除指定节点及相关连线。", { ids: { type: "array", items: { type: "string" }, minItems: 1 } }, ["ids"]), - toolDefinition( - "canvas_connect_nodes", - "批量连接节点。", - { connections: { type: "array", minItems: 1, items: { type: "object", properties: { fromNodeId: { type: "string" }, toNodeId: { type: "string" } }, required: ["fromNodeId", "toNodeId"], additionalProperties: false } } }, - ["connections"], - ), - toolDefinition("canvas_select_nodes", "设置当前选中节点。", { ids: { type: "array", items: { type: "string" } } }, ["ids"]), - toolDefinition("canvas_set_viewport", "调整画布视口。", { viewport: VIEWPORT_SCHEMA }, ["viewport"]), - toolDefinition("canvas_run_generation", "触发指定节点生成;对已有生成任务明确重试时传 retry=true。", { nodeId: { type: "string" }, mode: GENERATION_MODE_SCHEMA, prompt: { type: "string" }, retry: { type: "boolean" } }, ["nodeId"]), -]; type OnlineAgentTab = "chat" | "history"; -type OnlineAgentLog = { id: string; time: string; title: string; data?: unknown }; -type OnlineAgentLogContext = { model: string; running: boolean; confirmTools: boolean; messages: number; nodes: number; connections: number }; type OnlineLoopContext = { step: number }; -type OnlineToolResult = { ok: true; message: string; data?: unknown } | { ok: false; message: string }; type OnlineExecutedToolCall = { toolCallId: string; name: string; result: OnlineToolResult }; type PendingOnlineToolContext = { messages: ResponseInputMessage[]; toolCalls: ResponseToolCall[]; assistantId: string; step: number }; - type CanvasAssistantPanelProps = { nodes: CanvasNodeData[]; selectedNodeIds: Set; @@ -270,73 +67,8 @@ type CanvasAssistantPanelProps = { resizing?: boolean; }; -export type CinematicContinuationFailureDisposition = "abort" | "durable-ack" | "provider-failed"; - -type CinematicContinuationLiveSessionState = { sessions: CanvasAssistantSession[]; activeChatId: string | null }; - -type CanvasCinematicContinuationBoundaryInput = { - projectId: string; - effectKey?: string; - signal?: AbortSignal; - readSnapshot: () => CanvasAgentSnapshot; - executeOps: () => Promise; - completeSession: (effectKey?: string) => CanvasAssistantSession[]; - readLiveSessionState: () => CinematicContinuationLiveSessionState; - restoreLiveSessions: (sessions: CanvasAssistantSession[], activeChatId: string | null) => void; - restoreLiveSnapshot: (state: Pick) => void; - failProvider: (error: unknown) => void; - onFailureDisposition?: (disposition: CinematicContinuationFailureDisposition, error: unknown) => void; - persistContinuation?: typeof persistCanvasCinematicSessionContinuationEffect; -}; - -export function handleCinematicContinuationFailure(error: unknown, failProvider: (error: unknown) => void): CinematicContinuationFailureDisposition { - if (isAgentSessionPollingAbort(error)) return "abort"; - if (isCanvasGenerationDurableAckError(error)) return "durable-ack"; - failProvider(error); - return "provider-failed"; -} - -export async function runCanvasCinematicContinuationBoundary(input: CanvasCinematicContinuationBoundaryInput) { - const previousSnapshot = input.readSnapshot(); - const previousSessionState = input.readLiveSessionState(); - try { - if (input.signal?.aborted) throw new DOMException("The operation was aborted", "AbortError"); - const result = await input.executeOps(); - const attemptedSnapshot = input.readSnapshot(); - input.completeSession(input.effectKey); - const attemptedSessionState = input.readLiveSessionState(); - if (input.effectKey) { - await (input.persistContinuation ?? persistCanvasCinematicSessionContinuationEffect)({ - projectId: input.projectId, - effectKey: input.effectKey, - previousNodes: previousSnapshot.nodes, - nodes: attemptedSnapshot.nodes, - previousConnections: previousSnapshot.connections, - connections: attemptedSnapshot.connections, - previousChatSessions: previousSessionState.sessions, - chatSessions: attemptedSessionState.sessions, - previousActiveChatId: previousSessionState.activeChatId, - activeChatId: attemptedSessionState.activeChatId, - signal: input.signal, - readLiveSessionState: input.readLiveSessionState, - restoreLiveSessions: input.restoreLiveSessions, - restoreLiveSnapshot: input.restoreLiveSnapshot, - }); - } - return result; - } catch (error) { - const disposition = handleCinematicContinuationFailure(error, input.failProvider); - input.onFailureDisposition?.(disposition, error); - throw error; - } -} - -export const canvasCinematicContinuationEntryAdapters = { - "online-tool": runCanvasCinematicContinuationBoundary, - "submit-cinematic": runCanvasCinematicContinuationBoundary, - "resume-cinematic": runCanvasCinematicContinuationBoundary, -} as const; - +export { handleCinematicContinuationFailure, runCanvasCinematicContinuationBoundary, canvasCinematicContinuationEntryAdapters } from "./canvas-cinematic-continuation"; +export type { CinematicContinuationFailureDisposition } from "./canvas-cinematic-continuation"; export function CanvasAssistantPanel({ nodes, selectedNodeIds, @@ -373,7 +105,6 @@ export function CanvasAssistantPanel({ const [cinematicEntryActive, setCinematicEntryActive] = useState(cinematicEntry); const [isRunning, setIsRunning] = useState(false); const [deleteChatIds, setDeleteChatIds] = useState([]); - const [onlineLogs, setOnlineLogs] = useState([]); const [composerSkills, setComposerSkills] = useState([]); const [attachedReferenceIds, setAttachedReferenceIds] = useState>(() => new Set()); const [localSessions, setLocalSessions] = useState(() => (sessions.length ? sessions : [createSession()])); @@ -511,7 +242,6 @@ export function CanvasAssistantPanel({ updatedAt: new Date().toISOString(), })); }; - const addOnlineLog = (title: string, data?: unknown) => setOnlineLogs((prev) => [{ id: nanoid(), time: new Date().toLocaleTimeString(), title, data }, ...prev].slice(0, 80)); const upsertMessage = (sessionId: string, message: CanvasAssistantMessage) => { updateSession(sessionId, (session) => { @@ -615,7 +345,6 @@ export function CanvasAssistantPanel({ cinematicSessionControllersRef.current.delete(requestKey); cinematicSessionControllersRef.current.set(backendSessionId, controller); setPendingCinematicSession(sessionId, backendSessionId); - addOnlineLog("后端影视 Agent 会话已创建", { backendSessionId }); onCreated?.(backendSessionId); }, }, @@ -681,7 +410,6 @@ export function CanvasAssistantPanel({ const userMessage: CanvasAssistantMessage = { id: nanoid(), role: "user", text, references: refs }; const assistantId = nanoid(); appendMessage(session.id, userMessage); - addOnlineLog("发送请求", { text, selectedNodeIds: snapshotRef.current.selectedNodeIds, nodeCount: snapshotRef.current.nodes.length, connectionCount: snapshotRef.current.connections.length }); setPrompt(""); setIsRunning(true); void runOnlineAgentStep(session.id, assistantId, history, userMessage, { step: 1 }); @@ -692,13 +420,11 @@ export function CanvasAssistantPanel({ try { setIsRunning(true); const messages = await buildToolAgentMessages(snapshotRef.current, history, userMessage, composerSkills); - addOnlineLog(`Agent Tool Loop ${loop.step} 开始`, { toolChoice: "required" }); let streamed = ""; const result = await requestOnlineAgentModel({ ...requestConfig, systemPrompt: "" }, messages, "required", userMessage.text, (text) => { streamed = text; if (text.trim()) upsertMessage(sessionId, { id: assistantId, role: "assistant", text }); }); - addOnlineLog("模型工具回复", result); if (result.toolCalls.length) { const writableCalls = result.toolCalls.filter(isWritableToolCall); if (confirmTools && writableCalls.length) { @@ -713,18 +439,15 @@ export function CanvasAssistantPanel({ detail: { status: "pending", step: loop.step, toolCalls: result.toolCalls, impact: previewOnlineToolCalls(result.toolCalls, snapshotRef.current, effectiveConfig) }, }; appendMessage(sessionId, toolMessage); - addOnlineLog("等待用户确认", result.toolCalls); return; } await continueOnlineToolLoop(sessionId, assistantId, messages, result, loop.step); } else { if (!result.content.trim()) throw new Error("模型没有返回工具调用,画布操作未执行。"); upsertMessage(sessionId, { id: assistantId, role: "assistant", text: result.content || streamed || "没有返回内容。" }); - addOnlineLog(`Agent Tool Loop ${loop.step} 结束`, { reply: result.content }); } } catch (error) { if (isAgentSessionPollingAbort(error)) return; - addOnlineLog("请求失败", error instanceof Error ? error.message : error); appendMessage(sessionId, { id: nanoid(), role: "error", title: "操作失败", text: error instanceof Error ? error.message : "操作失败" }); } finally { setIsRunning(false); @@ -733,7 +456,6 @@ export function CanvasAssistantPanel({ const continueOnlineToolLoop = async (sessionId: string, assistantId: string, messages: ResponseInputMessage[], result: { content: string; toolCalls: ResponseToolCall[] }, step: number) => { const toolResults = await executeOnlineToolCalls(sessionId, result.toolCalls); - addOnlineLog("工具执行结果", toolResults); appendMessage(sessionId, { id: nanoid(), role: "tool", @@ -748,7 +470,6 @@ export function CanvasAssistantPanel({ const nextMessages: ResponseInputMessage[] = [...messages, ...toolCalls.map(toolCallToResponseInput), ...toolResults.map((item) => ({ role: "tool" as const, tool_call_id: item.toolCallId, content: JSON.stringify(item.result) }))]; if (step >= ONLINE_AGENT_MAX_STEPS) { upsertMessage(sessionId, { id: assistantId, role: "assistant", text: toolResults.map((item) => toolResultText(item.result)).join("\n") || "工具已执行。" }); - addOnlineLog("Agent Tool Loop 达到步数上限", { maxSteps: ONLINE_AGENT_MAX_STEPS }); return; } const requestConfig = { ...effectiveConfig, model: effectiveConfig.textModel || effectiveConfig.model }; @@ -757,7 +478,6 @@ export function CanvasAssistantPanel({ streamed = text; if (text.trim()) upsertMessage(sessionId, { id: assistantId, role: "assistant", text }); }); - addOnlineLog(`Agent Tool Loop ${step + 1} 回复`, next); if (next.toolCalls.length) { const writableCalls = next.toolCalls.filter(isWritableToolCall); if (confirmTools && writableCalls.length) { @@ -771,7 +491,6 @@ export function CanvasAssistantPanel({ text: summarizeToolCalls(next.toolCalls), detail: { status: "pending", step: step + 1, toolCalls: next.toolCalls, impact: previewOnlineToolCalls(next.toolCalls, snapshotRef.current, effectiveConfig) }, }); - addOnlineLog("等待用户确认", next.toolCalls); return; } await continueOnlineToolLoop(sessionId, assistantId, nextMessages, next, step + 1); @@ -893,7 +612,6 @@ export function CanvasAssistantPanel({ const toolCalls = pendingContext?.toolCalls || toolCallsFromDetail(detail); const previousMessages = pendingContext?.messages || []; const session = safeSessions.find((session) => session.messages.some((item) => item.id === messageId)); - addOnlineLog("批准工具", { messageId, toolCalls }); const assistantId = pendingContext?.assistantId || ""; if (!session) return; if (!toolCalls.length || !previousMessages.length || !assistantId) { @@ -903,13 +621,11 @@ export function CanvasAssistantPanel({ try { setIsRunning(true); const results = await executeOnlineToolCalls(session.id, toolCalls); - addOnlineLog("工具执行结果", results); upsertMessage(session.id, { id: messageId, role: "tool", title: "工具执行完成", text: results.map((item) => toolResultText(item.result)).join("\n"), detail: { ...detail, results, status: "completed" } }); pendingToolContextRef.current.delete(messageId); await continueOnlineToolLoopAfterResults(session.id, assistantId, previousMessages, toolCalls, results, pendingContext?.step || Number(detail.step) || 1); } catch (error) { if (isAgentSessionPollingAbort(error)) return; - addOnlineLog("工具续跑失败", error instanceof Error ? error.message : error); appendMessage(session.id, { id: nanoid(), role: "error", title: "操作失败", text: error instanceof Error ? error.message : "操作失败" }); } finally { setIsRunning(false); @@ -918,7 +634,6 @@ export function CanvasAssistantPanel({ const rejectOnlineTool = (messageId: string) => { const session = safeSessions.find((session) => session.messages.some((item) => item.id === messageId)); - addOnlineLog("拒绝工具", { messageId }); pendingToolContextRef.current.delete(messageId); if (session) upsertMessage(session.id, { id: messageId, role: "tool", title: "已拒绝执行", text: "工具调用已取消", detail: { ...objectDetail(session.messages.find((item) => item.id === messageId)?.detail), status: "rejected" } }); }; @@ -1015,7 +730,6 @@ export function CanvasAssistantPanel({ const controller = new AbortController(); cinematicSessionControllersRef.current.set(pending.id, controller); setIsRunning(true); - addOnlineLog("恢复后端影视 Agent 会话", { backendSessionId: pending.id }); let continuationFailureDisposition: CinematicContinuationFailureDisposition | undefined; try { const detail = await resumeCinematicAgentSession(pending.id, { signal: controller.signal }); @@ -1035,14 +749,11 @@ export function CanvasAssistantPanel({ restoreLiveSnapshot: restoreCinematicSnapshot, failProvider: (failure) => { failCinematicSession(sessionId, pending.id, failure); - addOnlineLog("后端影视 Agent 会话恢复失败", failure instanceof Error ? failure.message : failure); }, onFailureDisposition: (disposition, error) => { continuationFailureDisposition = disposition; - if (disposition === "durable-ack") addOnlineLog("后端影视 Agent 会话持久化失败,保留待恢复状态", error instanceof Error ? error.message : error); }, }); - addOnlineLog("后端影视 Agent 会话恢复完成", { backendSessionId: pending.id }); }; if (continuationTask) { await consumeGenerationTaskAgent(continuationTask, pending.id, applyContinuation, { signal: controller.signal }); @@ -1053,9 +764,7 @@ export function CanvasAssistantPanel({ if (continuationFailureDisposition) return; const disposition = handleCinematicContinuationFailure(error, (failure) => { failCinematicSession(sessionId, pending.id, failure); - addOnlineLog("后端影视 Agent 会话恢复失败", failure instanceof Error ? failure.message : failure); }); - if (disposition === "durable-ack") addOnlineLog("后端影视 Agent 会话持久化失败,保留待恢复状态", error instanceof Error ? error.message : error); } finally { if (cinematicSessionControllersRef.current.get(pending.id) === controller) cinematicSessionControllersRef.current.delete(pending.id); if (cinematicSessionControllersRef.current.size === 0) setIsRunning(false); @@ -1236,896 +945,3 @@ export function CanvasAssistantPanel({ ); } - -function AgentTextModelPicker({ config, value, onChange }: { config: AiConfig; value: string; onChange: (model: string) => void }) { - const options = useMemo(() => Array.from(new Set([value, ...selectableModelsByCapability(config, "text")].filter(Boolean))), [config, value]); - const current = value || ""; - return ( -
event.stopPropagation()} onPointerDown={(event) => event.stopPropagation()}> - - size="small" - variant="borderless" - value={current || undefined} - className="agent-text-model-select w-full" - popupMatchSelectWidth={288} - options={options.map((model) => ({ value: model, label: agentModelLabel(config, model) }))} - notFoundContent={暂无文本模型} - optionRender={(option) => { - const model = String(option.value); - return ( - - - {modelDisplayName(config, model)} - {agentModelSource(config, model) ? {agentModelSource(config, model)} : null} - - ); - }} - labelRender={() => ( - - - {current ? modelDisplayName(config, current) : "选择文本模型"} - {current && agentModelSource(config, current) ? {agentModelSource(config, current)} : null} - - )} - onChange={onChange} - aria-label="选择 Agent 文本模型" - title={current ? agentModelLabel(config, current) : "选择文本模型"} - /> -
- ); -} - -function agentModelSource(config: AiConfig, model: string) { - const channel = resolveModelChannel(config, model); - return channel.scope === "system" ? "" : channel.name; -} - -function agentModelLabel(config: AiConfig, model: string) { - const source = agentModelSource(config, model); - return source ? `${modelDisplayName(config, model)} · ${source}` : modelDisplayName(config, model); -} - -function AgentModelIcon({ config, model }: { config: AiConfig; model: string }) { - const icon = modelIcon(config, model); - return icon ? : ; -} - -function AssistantHistory({ sessions, activeSession, onOpen, onDelete }: { sessions: CanvasAssistantSession[]; activeSession: CanvasAssistantSession | null; onOpen: (id: string) => void; onDelete: (id: string) => void }) { - const theme = canvasThemes[useThemeStore((state) => state.theme)]; - - return ( -
-
- {sessions.length ? `${sessions.length} 条历史` : "暂无历史"} -
- {sessions.map((session) => ( -
-
-
-
- {session.id === activeSession?.id ? ( - - 当前 - - ) : null} -
{session.title}
-
-
{sessionPreview(session)}
-
-
- {formatSessionTime(session.updatedAt || session.createdAt)} - - -
-
-
- ))} - {!sessions.length ? ( -
- 网站 Agent 的对话记录会显示在这里 -
- ) : null} -
- ); -} - -function OnlineAgentSetupView({ theme, activeModel, onOpenConfig }: { theme: (typeof canvasThemes)[keyof typeof canvasThemes]; activeModel: string; onOpenConfig: () => void }) { - return ( -
-
-
-
连接配置
-
- 网站 Agent 直接使用当前网页配置的文本模型和 API。 -
-
-
-
-
-
文本模型
-
- {activeModel || "未配置模型"} -
-
- -
-
-
-
- ); -} - -function OnlineAgentLogView({ logs, theme, context, onClear }: { logs: OnlineAgentLog[]; theme: (typeof canvasThemes)[keyof typeof canvasThemes]; context: OnlineAgentLogContext; onClear: () => void }) { - const [mode, setMode] = useState<"text" | "json">("text"); - const textareaRef = useRef(null); - const content = mode === "text" ? formatOnlineLogText(logs, context) : formatOnlineLogJson(logs, context); - const lastError = [...logs].reverse().find((item) => /错误|失败|error/i.test(`${item.title}\n${stringifyLog(item.data)}`)); - const copy = async (value = content) => { - if (await copyToClipboard(value)) return; - textareaRef.current?.focus(); - textareaRef.current?.select(); - }; - return ( -
-
- setMode(value as "text" | "json")} - options={[ - { label: "排查日志", value: "text" }, - { label: "原始 JSON", value: "json" }, - ]} - /> -
- - {logs.length} 条 - - - - -
-
-