From b183a4f7ea1d2cdffe38b7e8a4356c5713357e5f Mon Sep 17 00:00:00 2001 From: Jeff Emmett Date: Wed, 3 Dec 2025 21:56:54 -0800 Subject: [PATCH] Add backlog tasks from worktrees and feature branches MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - task-002: RunPod AI API Integration (worktree: add-runpod-AI-API) - task-003: MulTmux Web Integration (worktree: mulTmux-webtree) - task-004: IO Chip Feature (worktree: feature/io-chip) - task-005: Automerge CRDT Sync - task-006: Stripe Payment Integration - task-007: Web3 Integration - task-008: Audio Recording Feature - task-009: Web Speech API Transcription - task-010: Holon Integration - task-011: Terminal Tool - task-012: Dark Mode Theme 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- CLAUDE.md | 1 + backlog/config.yml | 15 + .../task-002 - runpod-ai-api-integration.md | 25 + backlog/tasks/task-003 - multmux-webtree.md | 24 + backlog/tasks/task-004 - io-chip-feature.md | 24 + .../tasks/task-005 - automerge-crdt-sync.md | 22 + .../tasks/task-006 - stripe-integration.md | 22 + backlog/tasks/task-007 - web3-integration.md | 21 + backlog/tasks/task-008 - audio-recording.md | 22 + .../tasks/task-009 - transcribe-webspeech.md | 22 + backlog/tasks/task-010 - holon-integration.md | 21 + backlog/tasks/task-011 - terminal-tool.md | 21 + backlog/tasks/task-012 - dark-mode.md | 22 + package-lock.json | 1778 ++++++++++++++++- package.json | 5 +- runpod-configs/extra_model_paths.yaml | 63 + runpod-configs/setup_network_volume.sh | 143 ++ src/automerge/MinimalSanitization.ts | 16 +- src/automerge/useAutomergeSyncRepo.ts | 42 +- src/lib/auth/cryptidEmailService.ts | 427 ++++ src/lib/canvasAI.ts | 277 ++- src/lib/toolSchema.ts | 558 ++++++ src/routes/Board.tsx | 11 +- src/routes/LinkDevice.tsx | 105 + src/routes/VerifyEmail.tsx | 85 + src/shapes/ImageGenShapeUtil.tsx | 432 +++- src/shapes/MarkdownShapeUtil.tsx | 470 +++-- src/shapes/ObsNoteShapeUtil.tsx | 1237 +++++------- src/shapes/VideoGenShapeUtil.tsx | 387 +++- src/ui/CustomContextMenu.tsx | 33 +- src/ui/CustomToolbar.tsx | 350 +++- src/ui/MycelialIntelligenceBar.tsx | 1555 +++++++++++++- src/ui/UserSettingsModal.tsx | 405 +++- src/utils/multiPasteHandler.ts | 370 ++++ src/utils/selectionTransforms.ts | 788 ++++++++ src/utils/toolSpawner.ts | 315 +++ worker/AutomergeDurableObject.ts | 218 +- worker/cryptidAuth.ts | 672 +++++++ worker/types.ts | 40 + 39 files changed, 9723 insertions(+), 1321 deletions(-) create mode 120000 CLAUDE.md create mode 100644 backlog/config.yml create mode 100644 backlog/tasks/task-002 - runpod-ai-api-integration.md create mode 100644 backlog/tasks/task-003 - multmux-webtree.md create mode 100644 backlog/tasks/task-004 - io-chip-feature.md create mode 100644 backlog/tasks/task-005 - automerge-crdt-sync.md create mode 100644 backlog/tasks/task-006 - stripe-integration.md create mode 100644 backlog/tasks/task-007 - web3-integration.md create mode 100644 backlog/tasks/task-008 - audio-recording.md create mode 100644 backlog/tasks/task-009 - transcribe-webspeech.md create mode 100644 backlog/tasks/task-010 - holon-integration.md create mode 100644 backlog/tasks/task-011 - terminal-tool.md create mode 100644 backlog/tasks/task-012 - dark-mode.md create mode 100644 runpod-configs/extra_model_paths.yaml create mode 100644 runpod-configs/setup_network_volume.sh create mode 100644 src/lib/auth/cryptidEmailService.ts create mode 100644 src/lib/toolSchema.ts create mode 100644 src/routes/LinkDevice.tsx create mode 100644 src/routes/VerifyEmail.tsx create mode 100644 src/utils/multiPasteHandler.ts create mode 100644 src/utils/selectionTransforms.ts create mode 100644 src/utils/toolSpawner.ts create mode 100644 worker/cryptidAuth.ts diff --git a/CLAUDE.md b/CLAUDE.md new file mode 120000 index 0000000..ce5d95c --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1 @@ +/home/jeffe/.claude/CLAUDE.md \ No newline at end of file diff --git a/backlog/config.yml b/backlog/config.yml new file mode 100644 index 0000000..c8ba6d5 --- /dev/null +++ b/backlog/config.yml @@ -0,0 +1,15 @@ +project_name: "Canvas Feature List" +default_status: "To Do" +statuses: ["To Do", "In Progress", "Done"] +labels: [] +milestones: [] +date_format: yyyy-mm-dd +max_column_width: 20 +auto_open_browser: true +default_port: 6420 +remote_operations: true +auto_commit: true +zero_padded_ids: 3 +bypass_git_hooks: false +check_active_branches: true +active_branch_days: 60 diff --git a/backlog/tasks/task-002 - runpod-ai-api-integration.md b/backlog/tasks/task-002 - runpod-ai-api-integration.md new file mode 100644 index 0000000..1767264 --- /dev/null +++ b/backlog/tasks/task-002 - runpod-ai-api-integration.md @@ -0,0 +1,25 @@ +--- +id: task-002 +title: RunPod AI API Integration +status: In Progress +assignee: [] +created_date: '2025-12-03' +labels: [feature, ai, integration] +priority: high +branch: add-runpod-AI-API +worktree: /home/jeffe/Github/canvas-website-branch-worktrees/add-runpod-AI-API +--- + +## Description +Integrate RunPod serverless AI API for image generation and other AI features on the canvas. + +## Branch Info +- **Branch**: `add-runpod-AI-API` +- **Worktree**: `/home/jeffe/Github/canvas-website-branch-worktrees/add-runpod-AI-API` +- **Commit**: 083095c + +## Acceptance Criteria +- [ ] Connect to RunPod serverless endpoints +- [ ] Implement image generation from canvas +- [ ] Handle AI responses and display on canvas +- [ ] Error handling and loading states diff --git a/backlog/tasks/task-003 - multmux-webtree.md b/backlog/tasks/task-003 - multmux-webtree.md new file mode 100644 index 0000000..08ecf0b --- /dev/null +++ b/backlog/tasks/task-003 - multmux-webtree.md @@ -0,0 +1,24 @@ +--- +id: task-003 +title: MulTmux Web Integration +status: In Progress +assignee: [] +created_date: '2025-12-03' +labels: [feature, terminal, integration] +priority: medium +branch: mulTmux-webtree +worktree: /home/jeffe/Github/canvas-website-branch-worktrees/mulTmux-webtree +--- + +## Description +Integrate MulTmux web terminal functionality into the canvas for terminal-based interactions. + +## Branch Info +- **Branch**: `mulTmux-webtree` +- **Worktree**: `/home/jeffe/Github/canvas-website-branch-worktrees/mulTmux-webtree` +- **Commit**: 8ea3490 + +## Acceptance Criteria +- [ ] Embed terminal component in canvas +- [ ] Handle terminal I/O within canvas context +- [ ] Support multiple terminal sessions diff --git a/backlog/tasks/task-004 - io-chip-feature.md b/backlog/tasks/task-004 - io-chip-feature.md new file mode 100644 index 0000000..e5ba532 --- /dev/null +++ b/backlog/tasks/task-004 - io-chip-feature.md @@ -0,0 +1,24 @@ +--- +id: task-004 +title: IO Chip Feature +status: In Progress +assignee: [] +created_date: '2025-12-03' +labels: [feature, io, ui] +priority: medium +branch: feature/io-chip +worktree: /home/jeffe/Github/canvas-website-io-chip +--- + +## Description +Implement IO chip feature for the canvas - enabling input/output connections between canvas elements. + +## Branch Info +- **Branch**: `feature/io-chip` +- **Worktree**: `/home/jeffe/Github/canvas-website-io-chip` +- **Commit**: 527462a + +## Acceptance Criteria +- [ ] Create IO chip component +- [ ] Enable connections between canvas elements +- [ ] Handle data flow between connected chips diff --git a/backlog/tasks/task-005 - automerge-crdt-sync.md b/backlog/tasks/task-005 - automerge-crdt-sync.md new file mode 100644 index 0000000..d343a56 --- /dev/null +++ b/backlog/tasks/task-005 - automerge-crdt-sync.md @@ -0,0 +1,22 @@ +--- +id: task-005 +title: Automerge CRDT Sync +status: To Do +assignee: [] +created_date: '2025-12-03' +labels: [feature, sync, collaboration] +priority: high +branch: Automerge +--- + +## Description +Implement Automerge CRDT-based synchronization for real-time collaborative canvas editing. + +## Branch Info +- **Branch**: `Automerge` + +## Acceptance Criteria +- [ ] Integrate Automerge library +- [ ] Enable real-time sync between clients +- [ ] Handle conflict resolution automatically +- [ ] Persist state across sessions diff --git a/backlog/tasks/task-006 - stripe-integration.md b/backlog/tasks/task-006 - stripe-integration.md new file mode 100644 index 0000000..dbe4ce9 --- /dev/null +++ b/backlog/tasks/task-006 - stripe-integration.md @@ -0,0 +1,22 @@ +--- +id: task-006 +title: Stripe Payment Integration +status: To Do +assignee: [] +created_date: '2025-12-03' +labels: [feature, payments, integration] +priority: medium +branch: stripe-integration +--- + +## Description +Integrate Stripe for payment processing and subscription management. + +## Branch Info +- **Branch**: `stripe-integration` + +## Acceptance Criteria +- [ ] Set up Stripe API connection +- [ ] Implement payment flow +- [ ] Handle subscriptions +- [ ] Add billing management UI diff --git a/backlog/tasks/task-007 - web3-integration.md b/backlog/tasks/task-007 - web3-integration.md new file mode 100644 index 0000000..0df128e --- /dev/null +++ b/backlog/tasks/task-007 - web3-integration.md @@ -0,0 +1,21 @@ +--- +id: task-007 +title: Web3 Integration +status: To Do +assignee: [] +created_date: '2025-12-03' +labels: [feature, web3, blockchain] +priority: low +branch: web3-integration +--- + +## Description +Integrate Web3 capabilities for blockchain-based features (wallet connect, NFT canvas elements, etc.). + +## Branch Info +- **Branch**: `web3-integration` + +## Acceptance Criteria +- [ ] Add wallet connection +- [ ] Enable NFT minting of canvas elements +- [ ] Blockchain-based ownership verification diff --git a/backlog/tasks/task-008 - audio-recording.md b/backlog/tasks/task-008 - audio-recording.md new file mode 100644 index 0000000..c48c6fc --- /dev/null +++ b/backlog/tasks/task-008 - audio-recording.md @@ -0,0 +1,22 @@ +--- +id: task-008 +title: Audio Recording Feature +status: To Do +assignee: [] +created_date: '2025-12-03' +labels: [feature, audio, media] +priority: medium +branch: audio-recording-attempt +--- + +## Description +Implement audio recording capability for voice notes and audio annotations on the canvas. + +## Branch Info +- **Branch**: `audio-recording-attempt` + +## Acceptance Criteria +- [ ] Record audio from microphone +- [ ] Save audio clips to canvas +- [ ] Playback audio annotations +- [ ] Transcription integration diff --git a/backlog/tasks/task-009 - transcribe-webspeech.md b/backlog/tasks/task-009 - transcribe-webspeech.md new file mode 100644 index 0000000..90a1c38 --- /dev/null +++ b/backlog/tasks/task-009 - transcribe-webspeech.md @@ -0,0 +1,22 @@ +--- +id: task-009 +title: Web Speech API Transcription +status: To Do +assignee: [] +created_date: '2025-12-03' +labels: [feature, transcription, speech] +priority: medium +branch: transcribe-webspeechAPI +--- + +## Description +Implement speech-to-text transcription using the Web Speech API for voice input on the canvas. + +## Branch Info +- **Branch**: `transcribe-webspeechAPI` + +## Acceptance Criteria +- [ ] Capture speech via Web Speech API +- [ ] Convert to text in real-time +- [ ] Display transcription on canvas +- [ ] Support multiple languages diff --git a/backlog/tasks/task-010 - holon-integration.md b/backlog/tasks/task-010 - holon-integration.md new file mode 100644 index 0000000..f03598f --- /dev/null +++ b/backlog/tasks/task-010 - holon-integration.md @@ -0,0 +1,21 @@ +--- +id: task-010 +title: Holon Integration +status: To Do +assignee: [] +created_date: '2025-12-03' +labels: [feature, holon, integration] +priority: medium +branch: holon-integration +--- + +## Description +Integrate Holon framework for hierarchical canvas organization and nested structures. + +## Branch Info +- **Branch**: `holon-integration` + +## Acceptance Criteria +- [ ] Implement holon data structure +- [ ] Enable nested canvas elements +- [ ] Support hierarchical navigation diff --git a/backlog/tasks/task-011 - terminal-tool.md b/backlog/tasks/task-011 - terminal-tool.md new file mode 100644 index 0000000..4c6f98f --- /dev/null +++ b/backlog/tasks/task-011 - terminal-tool.md @@ -0,0 +1,21 @@ +--- +id: task-011 +title: Terminal Tool +status: To Do +assignee: [] +created_date: '2025-12-03' +labels: [feature, terminal, tool] +priority: medium +branch: feature/terminal-tool +--- + +## Description +Add a terminal tool to the canvas toolbar for embedding terminal sessions. + +## Branch Info +- **Branch**: `feature/terminal-tool` + +## Acceptance Criteria +- [ ] Add terminal tool to toolbar +- [ ] Spawn terminal instances on canvas +- [ ] Handle terminal sizing and positioning diff --git a/backlog/tasks/task-012 - dark-mode.md b/backlog/tasks/task-012 - dark-mode.md new file mode 100644 index 0000000..28b0dd8 --- /dev/null +++ b/backlog/tasks/task-012 - dark-mode.md @@ -0,0 +1,22 @@ +--- +id: task-012 +title: Dark Mode Theme +status: To Do +assignee: [] +created_date: '2025-12-03' +labels: [feature, ui, theme] +priority: low +branch: dark-mode +--- + +## Description +Implement dark mode theme support for the canvas interface. + +## Branch Info +- **Branch**: `dark-mode` + +## Acceptance Criteria +- [ ] Create dark theme colors +- [ ] Add theme toggle +- [ ] Persist user preference +- [ ] System theme detection diff --git a/package-lock.json b/package-lock.json index 05ed6a0..574de50 100644 --- a/package-lock.json +++ b/package-lock.json @@ -20,6 +20,7 @@ "@chengsokdara/use-whisper": "^0.2.0", "@daily-co/daily-js": "^0.60.0", "@daily-co/daily-react": "^0.20.0", + "@mdxeditor/editor": "^3.51.0", "@tldraw/assets": "^3.15.4", "@tldraw/tldraw": "^3.15.4", "@tldraw/tlschema": "^3.15.4", @@ -913,6 +914,496 @@ "dev": true, "license": "MIT OR Apache-2.0" }, + "node_modules/@codemirror/autocomplete": { + "version": "6.20.0", + "resolved": "https://registry.npmjs.org/@codemirror/autocomplete/-/autocomplete-6.20.0.tgz", + "integrity": "sha512-bOwvTOIJcG5FVo5gUUupiwYh8MioPLQ4UcqbcRf7UQ98X90tCa9E1kZ3Z7tqwpZxYyOvh1YTYbmZE9RTfTp5hg==", + "license": "MIT", + "dependencies": { + "@codemirror/language": "^6.0.0", + "@codemirror/state": "^6.0.0", + "@codemirror/view": "^6.17.0", + "@lezer/common": "^1.0.0" + } + }, + "node_modules/@codemirror/commands": { + "version": "6.10.0", + "resolved": "https://registry.npmjs.org/@codemirror/commands/-/commands-6.10.0.tgz", + "integrity": "sha512-2xUIc5mHXQzT16JnyOFkh8PvfeXuIut3pslWGfsGOhxP/lpgRm9HOl/mpzLErgt5mXDovqA0d11P21gofRLb9w==", + "license": "MIT", + "dependencies": { + "@codemirror/language": "^6.0.0", + "@codemirror/state": "^6.4.0", + "@codemirror/view": "^6.27.0", + "@lezer/common": "^1.1.0" + } + }, + "node_modules/@codemirror/lang-angular": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/@codemirror/lang-angular/-/lang-angular-0.1.4.tgz", + "integrity": "sha512-oap+gsltb/fzdlTQWD6BFF4bSLKcDnlxDsLdePiJpCVNKWXSTAbiiQeYI3UmES+BLAdkmIC1WjyztC1pi/bX4g==", + "license": "MIT", + "dependencies": { + "@codemirror/lang-html": "^6.0.0", + "@codemirror/lang-javascript": "^6.1.2", + "@codemirror/language": "^6.0.0", + "@lezer/common": "^1.2.0", + "@lezer/highlight": "^1.0.0", + "@lezer/lr": "^1.3.3" + } + }, + "node_modules/@codemirror/lang-cpp": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/@codemirror/lang-cpp/-/lang-cpp-6.0.3.tgz", + "integrity": "sha512-URM26M3vunFFn9/sm6rzqrBzDgfWuDixp85uTY49wKudToc2jTHUrKIGGKs+QWND+YLofNNZpxcNGRynFJfvgA==", + "license": "MIT", + "dependencies": { + "@codemirror/language": "^6.0.0", + "@lezer/cpp": "^1.0.0" + } + }, + "node_modules/@codemirror/lang-css": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/@codemirror/lang-css/-/lang-css-6.3.1.tgz", + "integrity": "sha512-kr5fwBGiGtmz6l0LSJIbno9QrifNMUusivHbnA1H6Dmqy4HZFte3UAICix1VuKo0lMPKQr2rqB+0BkKi/S3Ejg==", + "license": "MIT", + "dependencies": { + "@codemirror/autocomplete": "^6.0.0", + "@codemirror/language": "^6.0.0", + "@codemirror/state": "^6.0.0", + "@lezer/common": "^1.0.2", + "@lezer/css": "^1.1.7" + } + }, + "node_modules/@codemirror/lang-go": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/@codemirror/lang-go/-/lang-go-6.0.1.tgz", + "integrity": "sha512-7fNvbyNylvqCphW9HD6WFnRpcDjr+KXX/FgqXy5H5ZS0eC5edDljukm/yNgYkwTsgp2busdod50AOTIy6Jikfg==", + "license": "MIT", + "dependencies": { + "@codemirror/autocomplete": "^6.0.0", + "@codemirror/language": "^6.6.0", + "@codemirror/state": "^6.0.0", + "@lezer/common": "^1.0.0", + "@lezer/go": "^1.0.0" + } + }, + "node_modules/@codemirror/lang-html": { + "version": "6.4.11", + "resolved": "https://registry.npmjs.org/@codemirror/lang-html/-/lang-html-6.4.11.tgz", + "integrity": "sha512-9NsXp7Nwp891pQchI7gPdTwBuSuT3K65NGTHWHNJ55HjYcHLllr0rbIZNdOzas9ztc1EUVBlHou85FFZS4BNnw==", + "license": "MIT", + "dependencies": { + "@codemirror/autocomplete": "^6.0.0", + "@codemirror/lang-css": "^6.0.0", + "@codemirror/lang-javascript": "^6.0.0", + "@codemirror/language": "^6.4.0", + "@codemirror/state": "^6.0.0", + "@codemirror/view": "^6.17.0", + "@lezer/common": "^1.0.0", + "@lezer/css": "^1.1.0", + "@lezer/html": "^1.3.12" + } + }, + "node_modules/@codemirror/lang-java": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/@codemirror/lang-java/-/lang-java-6.0.2.tgz", + "integrity": "sha512-m5Nt1mQ/cznJY7tMfQTJchmrjdjQ71IDs+55d1GAa8DGaB8JXWsVCkVT284C3RTASaY43YknrK2X3hPO/J3MOQ==", + "license": "MIT", + "dependencies": { + "@codemirror/language": "^6.0.0", + "@lezer/java": "^1.0.0" + } + }, + "node_modules/@codemirror/lang-javascript": { + "version": "6.2.4", + "resolved": "https://registry.npmjs.org/@codemirror/lang-javascript/-/lang-javascript-6.2.4.tgz", + "integrity": "sha512-0WVmhp1QOqZ4Rt6GlVGwKJN3KW7Xh4H2q8ZZNGZaP6lRdxXJzmjm4FqvmOojVj6khWJHIb9sp7U/72W7xQgqAA==", + "license": "MIT", + "dependencies": { + "@codemirror/autocomplete": "^6.0.0", + "@codemirror/language": "^6.6.0", + "@codemirror/lint": "^6.0.0", + "@codemirror/state": "^6.0.0", + "@codemirror/view": "^6.17.0", + "@lezer/common": "^1.0.0", + "@lezer/javascript": "^1.0.0" + } + }, + "node_modules/@codemirror/lang-jinja": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/@codemirror/lang-jinja/-/lang-jinja-6.0.0.tgz", + "integrity": "sha512-47MFmRcR8UAxd8DReVgj7WJN1WSAMT7OJnewwugZM4XiHWkOjgJQqvEM1NpMj9ALMPyxmlziEI1opH9IaEvmaw==", + "license": "MIT", + "dependencies": { + "@codemirror/lang-html": "^6.0.0", + "@codemirror/language": "^6.0.0", + "@lezer/common": "^1.2.0", + "@lezer/highlight": "^1.2.0", + "@lezer/lr": "^1.4.0" + } + }, + "node_modules/@codemirror/lang-json": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/@codemirror/lang-json/-/lang-json-6.0.2.tgz", + "integrity": "sha512-x2OtO+AvwEHrEwR0FyyPtfDUiloG3rnVTSZV1W8UteaLL8/MajQd8DpvUb2YVzC+/T18aSDv0H9mu+xw0EStoQ==", + "license": "MIT", + "dependencies": { + "@codemirror/language": "^6.0.0", + "@lezer/json": "^1.0.0" + } + }, + "node_modules/@codemirror/lang-less": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/@codemirror/lang-less/-/lang-less-6.0.2.tgz", + "integrity": "sha512-EYdQTG22V+KUUk8Qq582g7FMnCZeEHsyuOJisHRft/mQ+ZSZ2w51NupvDUHiqtsOy7It5cHLPGfHQLpMh9bqpQ==", + "license": "MIT", + "dependencies": { + "@codemirror/lang-css": "^6.2.0", + "@codemirror/language": "^6.0.0", + "@lezer/common": "^1.2.0", + "@lezer/highlight": "^1.0.0", + "@lezer/lr": "^1.0.0" + } + }, + "node_modules/@codemirror/lang-liquid": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/@codemirror/lang-liquid/-/lang-liquid-6.3.0.tgz", + "integrity": "sha512-fY1YsUExcieXRTsCiwX/bQ9+PbCTA/Fumv7C7mTUZHoFkibfESnaXwpr2aKH6zZVwysEunsHHkaIpM/pl3xETQ==", + "license": "MIT", + "dependencies": { + "@codemirror/autocomplete": "^6.0.0", + "@codemirror/lang-html": "^6.0.0", + "@codemirror/language": "^6.0.0", + "@codemirror/state": "^6.0.0", + "@codemirror/view": "^6.0.0", + "@lezer/common": "^1.0.0", + "@lezer/highlight": "^1.0.0", + "@lezer/lr": "^1.3.1" + } + }, + "node_modules/@codemirror/lang-markdown": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/@codemirror/lang-markdown/-/lang-markdown-6.5.0.tgz", + "integrity": "sha512-0K40bZ35jpHya6FriukbgaleaqzBLZfOh7HuzqbMxBXkbYMJDxfF39c23xOgxFezR+3G+tR2/Mup+Xk865OMvw==", + "license": "MIT", + "dependencies": { + "@codemirror/autocomplete": "^6.7.1", + "@codemirror/lang-html": "^6.0.0", + "@codemirror/language": "^6.3.0", + "@codemirror/state": "^6.0.0", + "@codemirror/view": "^6.0.0", + "@lezer/common": "^1.2.1", + "@lezer/markdown": "^1.0.0" + } + }, + "node_modules/@codemirror/lang-php": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/@codemirror/lang-php/-/lang-php-6.0.2.tgz", + "integrity": "sha512-ZKy2v1n8Fc8oEXj0Th0PUMXzQJ0AIR6TaZU+PbDHExFwdu+guzOA4jmCHS1Nz4vbFezwD7LyBdDnddSJeScMCA==", + "license": "MIT", + "dependencies": { + "@codemirror/lang-html": "^6.0.0", + "@codemirror/language": "^6.0.0", + "@codemirror/state": "^6.0.0", + "@lezer/common": "^1.0.0", + "@lezer/php": "^1.0.0" + } + }, + "node_modules/@codemirror/lang-python": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/@codemirror/lang-python/-/lang-python-6.2.1.tgz", + "integrity": "sha512-IRjC8RUBhn9mGR9ywecNhB51yePWCGgvHfY1lWN/Mrp3cKuHr0isDKia+9HnvhiWNnMpbGhWrkhuWOc09exRyw==", + "license": "MIT", + "dependencies": { + "@codemirror/autocomplete": "^6.3.2", + "@codemirror/language": "^6.8.0", + "@codemirror/state": "^6.0.0", + "@lezer/common": "^1.2.1", + "@lezer/python": "^1.1.4" + } + }, + "node_modules/@codemirror/lang-rust": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/@codemirror/lang-rust/-/lang-rust-6.0.2.tgz", + "integrity": "sha512-EZaGjCUegtiU7kSMvOfEZpaCReowEf3yNidYu7+vfuGTm9ow4mthAparY5hisJqOHmJowVH3Upu+eJlUji6qqA==", + "license": "MIT", + "dependencies": { + "@codemirror/language": "^6.0.0", + "@lezer/rust": "^1.0.0" + } + }, + "node_modules/@codemirror/lang-sass": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/@codemirror/lang-sass/-/lang-sass-6.0.2.tgz", + "integrity": "sha512-l/bdzIABvnTo1nzdY6U+kPAC51czYQcOErfzQ9zSm9D8GmNPD0WTW8st/CJwBTPLO8jlrbyvlSEcN20dc4iL0Q==", + "license": "MIT", + "dependencies": { + "@codemirror/lang-css": "^6.2.0", + "@codemirror/language": "^6.0.0", + "@codemirror/state": "^6.0.0", + "@lezer/common": "^1.0.2", + "@lezer/sass": "^1.0.0" + } + }, + "node_modules/@codemirror/lang-sql": { + "version": "6.10.0", + "resolved": "https://registry.npmjs.org/@codemirror/lang-sql/-/lang-sql-6.10.0.tgz", + "integrity": "sha512-6ayPkEd/yRw0XKBx5uAiToSgGECo/GY2NoJIHXIIQh1EVwLuKoU8BP/qK0qH5NLXAbtJRLuT73hx7P9X34iO4w==", + "license": "MIT", + "dependencies": { + "@codemirror/autocomplete": "^6.0.0", + "@codemirror/language": "^6.0.0", + "@codemirror/state": "^6.0.0", + "@lezer/common": "^1.2.0", + "@lezer/highlight": "^1.0.0", + "@lezer/lr": "^1.0.0" + } + }, + "node_modules/@codemirror/lang-vue": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/@codemirror/lang-vue/-/lang-vue-0.1.3.tgz", + "integrity": "sha512-QSKdtYTDRhEHCfo5zOShzxCmqKJvgGrZwDQSdbvCRJ5pRLWBS7pD/8e/tH44aVQT6FKm0t6RVNoSUWHOI5vNug==", + "license": "MIT", + "dependencies": { + "@codemirror/lang-html": "^6.0.0", + "@codemirror/lang-javascript": "^6.1.2", + "@codemirror/language": "^6.0.0", + "@lezer/common": "^1.2.0", + "@lezer/highlight": "^1.0.0", + "@lezer/lr": "^1.3.1" + } + }, + "node_modules/@codemirror/lang-wast": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/@codemirror/lang-wast/-/lang-wast-6.0.2.tgz", + "integrity": "sha512-Imi2KTpVGm7TKuUkqyJ5NRmeFWF7aMpNiwHnLQe0x9kmrxElndyH0K6H/gXtWwY6UshMRAhpENsgfpSwsgmC6Q==", + "license": "MIT", + "dependencies": { + "@codemirror/language": "^6.0.0", + "@lezer/common": "^1.2.0", + "@lezer/highlight": "^1.0.0", + "@lezer/lr": "^1.0.0" + } + }, + "node_modules/@codemirror/lang-xml": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/@codemirror/lang-xml/-/lang-xml-6.1.0.tgz", + "integrity": "sha512-3z0blhicHLfwi2UgkZYRPioSgVTo9PV5GP5ducFH6FaHy0IAJRg+ixj5gTR1gnT/glAIC8xv4w2VL1LoZfs+Jg==", + "license": "MIT", + "dependencies": { + "@codemirror/autocomplete": "^6.0.0", + "@codemirror/language": "^6.4.0", + "@codemirror/state": "^6.0.0", + "@codemirror/view": "^6.0.0", + "@lezer/common": "^1.0.0", + "@lezer/xml": "^1.0.0" + } + }, + "node_modules/@codemirror/lang-yaml": { + "version": "6.1.2", + "resolved": "https://registry.npmjs.org/@codemirror/lang-yaml/-/lang-yaml-6.1.2.tgz", + "integrity": "sha512-dxrfG8w5Ce/QbT7YID7mWZFKhdhsaTNOYjOkSIMt1qmC4VQnXSDSYVHHHn8k6kJUfIhtLo8t1JJgltlxWdsITw==", + "license": "MIT", + "dependencies": { + "@codemirror/autocomplete": "^6.0.0", + "@codemirror/language": "^6.0.0", + "@codemirror/state": "^6.0.0", + "@lezer/common": "^1.2.0", + "@lezer/highlight": "^1.2.0", + "@lezer/lr": "^1.0.0", + "@lezer/yaml": "^1.0.0" + } + }, + "node_modules/@codemirror/language": { + "version": "6.11.3", + "resolved": "https://registry.npmjs.org/@codemirror/language/-/language-6.11.3.tgz", + "integrity": "sha512-9HBM2XnwDj7fnu0551HkGdrUrrqmYq/WC5iv6nbY2WdicXdGbhR/gfbZOH73Aqj4351alY1+aoG9rCNfiwS1RA==", + "license": "MIT", + "dependencies": { + "@codemirror/state": "^6.0.0", + "@codemirror/view": "^6.23.0", + "@lezer/common": "^1.1.0", + "@lezer/highlight": "^1.0.0", + "@lezer/lr": "^1.0.0", + "style-mod": "^4.0.0" + } + }, + "node_modules/@codemirror/language-data": { + "version": "6.5.2", + "resolved": "https://registry.npmjs.org/@codemirror/language-data/-/language-data-6.5.2.tgz", + "integrity": "sha512-CPkWBKrNS8stYbEU5kwBwTf3JB1kghlbh4FSAwzGW2TEscdeHHH4FGysREW86Mqnj3Qn09s0/6Ea/TutmoTobg==", + "license": "MIT", + "dependencies": { + "@codemirror/lang-angular": "^0.1.0", + "@codemirror/lang-cpp": "^6.0.0", + "@codemirror/lang-css": "^6.0.0", + "@codemirror/lang-go": "^6.0.0", + "@codemirror/lang-html": "^6.0.0", + "@codemirror/lang-java": "^6.0.0", + "@codemirror/lang-javascript": "^6.0.0", + "@codemirror/lang-jinja": "^6.0.0", + "@codemirror/lang-json": "^6.0.0", + "@codemirror/lang-less": "^6.0.0", + "@codemirror/lang-liquid": "^6.0.0", + "@codemirror/lang-markdown": "^6.0.0", + "@codemirror/lang-php": "^6.0.0", + "@codemirror/lang-python": "^6.0.0", + "@codemirror/lang-rust": "^6.0.0", + "@codemirror/lang-sass": "^6.0.0", + "@codemirror/lang-sql": "^6.0.0", + "@codemirror/lang-vue": "^0.1.1", + "@codemirror/lang-wast": "^6.0.0", + "@codemirror/lang-xml": "^6.0.0", + "@codemirror/lang-yaml": "^6.0.0", + "@codemirror/language": "^6.0.0", + "@codemirror/legacy-modes": "^6.4.0" + } + }, + "node_modules/@codemirror/legacy-modes": { + "version": "6.5.2", + "resolved": "https://registry.npmjs.org/@codemirror/legacy-modes/-/legacy-modes-6.5.2.tgz", + "integrity": "sha512-/jJbwSTazlQEDOQw2FJ8LEEKVS72pU0lx6oM54kGpL8t/NJ2Jda3CZ4pcltiKTdqYSRk3ug1B3pil1gsjA6+8Q==", + "license": "MIT", + "dependencies": { + "@codemirror/language": "^6.0.0" + } + }, + "node_modules/@codemirror/lint": { + "version": "6.9.2", + "resolved": "https://registry.npmjs.org/@codemirror/lint/-/lint-6.9.2.tgz", + "integrity": "sha512-sv3DylBiIyi+xKwRCJAAsBZZZWo82shJ/RTMymLabAdtbkV5cSKwWDeCgtUq3v8flTaXS2y1kKkICuRYtUswyQ==", + "license": "MIT", + "dependencies": { + "@codemirror/state": "^6.0.0", + "@codemirror/view": "^6.35.0", + "crelt": "^1.0.5" + } + }, + "node_modules/@codemirror/merge": { + "version": "6.11.2", + "resolved": "https://registry.npmjs.org/@codemirror/merge/-/merge-6.11.2.tgz", + "integrity": "sha512-NO5EJd2rLRbwVWLgMdhIntDIhfDtMOKYEZgqV5WnkNUS2oXOCVWLPjG/kgl/Jth2fGiOuG947bteqxP9nBXmMg==", + "license": "MIT", + "dependencies": { + "@codemirror/language": "^6.0.0", + "@codemirror/state": "^6.0.0", + "@codemirror/view": "^6.17.0", + "@lezer/highlight": "^1.0.0", + "style-mod": "^4.1.0" + } + }, + "node_modules/@codemirror/search": { + "version": "6.5.11", + "resolved": "https://registry.npmjs.org/@codemirror/search/-/search-6.5.11.tgz", + "integrity": "sha512-KmWepDE6jUdL6n8cAAqIpRmLPBZ5ZKnicE8oGU/s3QrAVID+0VhLFrzUucVKHG5035/BSykhExDL/Xm7dHthiA==", + "license": "MIT", + "dependencies": { + "@codemirror/state": "^6.0.0", + "@codemirror/view": "^6.0.0", + "crelt": "^1.0.5" + } + }, + "node_modules/@codemirror/state": { + "version": "6.5.2", + "resolved": "https://registry.npmjs.org/@codemirror/state/-/state-6.5.2.tgz", + "integrity": "sha512-FVqsPqtPWKVVL3dPSxy8wEF/ymIEuVzF1PK3VbUgrxXpJUSHQWWZz4JMToquRxnkw+36LTamCZG2iua2Ptq0fA==", + "license": "MIT", + "dependencies": { + "@marijn/find-cluster-break": "^1.0.0" + } + }, + "node_modules/@codemirror/view": { + "version": "6.38.8", + "resolved": "https://registry.npmjs.org/@codemirror/view/-/view-6.38.8.tgz", + "integrity": "sha512-XcE9fcnkHCbWkjeKyi0lllwXmBLtyYb5dt89dJyx23I9+LSh5vZDIuk7OLG4VM1lgrXZQcY6cxyZyk5WVPRv/A==", + "license": "MIT", + "dependencies": { + "@codemirror/state": "^6.5.0", + "crelt": "^1.0.6", + "style-mod": "^4.1.0", + "w3c-keyname": "^2.2.4" + } + }, + "node_modules/@codesandbox/nodebox": { + "version": "0.1.8", + "resolved": "https://registry.npmjs.org/@codesandbox/nodebox/-/nodebox-0.1.8.tgz", + "integrity": "sha512-2VRS6JDSk+M+pg56GA6CryyUSGPjBEe8Pnae0QL3jJF1mJZJVMDKr93gJRtBbLkfZN6LD/DwMtf+2L0bpWrjqg==", + "license": "SEE LICENSE IN ./LICENSE", + "dependencies": { + "outvariant": "^1.4.0", + "strict-event-emitter": "^0.4.3" + } + }, + "node_modules/@codesandbox/sandpack-client": { + "version": "2.19.8", + "resolved": "https://registry.npmjs.org/@codesandbox/sandpack-client/-/sandpack-client-2.19.8.tgz", + "integrity": "sha512-CMV4nr1zgKzVpx4I3FYvGRM5YT0VaQhALMW9vy4wZRhEyWAtJITQIqZzrTGWqB1JvV7V72dVEUCUPLfYz5hgJQ==", + "license": "Apache-2.0", + "dependencies": { + "@codesandbox/nodebox": "0.1.8", + "buffer": "^6.0.3", + "dequal": "^2.0.2", + "mime-db": "^1.52.0", + "outvariant": "1.4.0", + "static-browser-server": "1.0.3" + } + }, + "node_modules/@codesandbox/sandpack-client/node_modules/buffer": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz", + "integrity": "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "dependencies": { + "base64-js": "^1.3.1", + "ieee754": "^1.2.1" + } + }, + "node_modules/@codesandbox/sandpack-react": { + "version": "2.20.0", + "resolved": "https://registry.npmjs.org/@codesandbox/sandpack-react/-/sandpack-react-2.20.0.tgz", + "integrity": "sha512-takd1YpW/PMQ6KPQfvseWLHWklJovGY8QYj8MtWnskGKbjOGJ6uZfyZbcJ6aCFLQMpNyjTqz9AKNbvhCOZ1TUQ==", + "license": "Apache-2.0", + "dependencies": { + "@codemirror/autocomplete": "^6.4.0", + "@codemirror/commands": "^6.1.3", + "@codemirror/lang-css": "^6.0.1", + "@codemirror/lang-html": "^6.4.0", + "@codemirror/lang-javascript": "^6.1.2", + "@codemirror/language": "^6.3.2", + "@codemirror/state": "^6.2.0", + "@codemirror/view": "^6.7.1", + "@codesandbox/sandpack-client": "^2.19.8", + "@lezer/highlight": "^1.1.3", + "@react-hook/intersection-observer": "^3.1.1", + "@stitches/core": "^1.2.6", + "anser": "^2.1.1", + "clean-set": "^1.1.2", + "dequal": "^2.0.2", + "escape-carriage": "^1.3.1", + "lz-string": "^1.4.4", + "react-devtools-inline": "4.4.0", + "react-is": "^17.0.2" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17 || ^18 || ^19", + "react-dom": "^16.8.0 || ^17 || ^18 || ^19" + } + }, "node_modules/@cspotcode/source-map-support": { "version": "0.8.1", "resolved": "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz", @@ -1466,6 +1957,21 @@ "@floating-ui/utils": "^0.2.10" } }, + "node_modules/@floating-ui/react": { + "version": "0.27.16", + "resolved": "https://registry.npmjs.org/@floating-ui/react/-/react-0.27.16.tgz", + "integrity": "sha512-9O8N4SeG2z++TSM8QA/KTeKFBVCNEz/AGS7gWPJf6KFRzmRWixFRnCnkPHRDwSVZW6QPDO6uT0P2SpWNKCc9/g==", + "license": "MIT", + "dependencies": { + "@floating-ui/react-dom": "^2.1.6", + "@floating-ui/utils": "^0.2.10", + "tabbable": "^6.0.0" + }, + "peerDependencies": { + "react": ">=17.0.0", + "react-dom": ">=17.0.0" + } + }, "node_modules/@floating-ui/react-dom": { "version": "2.1.6", "resolved": "https://registry.npmjs.org/@floating-ui/react-dom/-/react-dom-2.1.6.tgz", @@ -1990,6 +2496,438 @@ "integrity": "sha512-Vo+PSpZG2/fmgmiNzYK9qWRh8h/CHrwD0mo1h1DzL4yzHNSfWYujGTYsWGreD000gcgmZ7K4Ys6Tx9TxtsKdDw==", "license": "MIT" }, + "node_modules/@lexical/clipboard": { + "version": "0.35.0", + "resolved": "https://registry.npmjs.org/@lexical/clipboard/-/clipboard-0.35.0.tgz", + "integrity": "sha512-ko7xSIIiayvDiqjNDX6fgH9RlcM6r9vrrvJYTcfGVBor5httx16lhIi0QJZ4+RNPvGtTjyFv4bwRmsixRRwImg==", + "license": "MIT", + "dependencies": { + "@lexical/html": "0.35.0", + "@lexical/list": "0.35.0", + "@lexical/selection": "0.35.0", + "@lexical/utils": "0.35.0", + "lexical": "0.35.0" + } + }, + "node_modules/@lexical/code": { + "version": "0.35.0", + "resolved": "https://registry.npmjs.org/@lexical/code/-/code-0.35.0.tgz", + "integrity": "sha512-ox4DZwETQ9IA7+DS6PN8RJNwSAF7RMjL7YTVODIqFZ5tUFIf+5xoCHbz7Fll0Bvixlp12hVH90xnLwTLRGpkKw==", + "license": "MIT", + "dependencies": { + "@lexical/utils": "0.35.0", + "lexical": "0.35.0", + "prismjs": "^1.30.0" + } + }, + "node_modules/@lexical/devtools-core": { + "version": "0.35.0", + "resolved": "https://registry.npmjs.org/@lexical/devtools-core/-/devtools-core-0.35.0.tgz", + "integrity": "sha512-C2wwtsMCR6ZTfO0TqpSM17RLJWyfHmifAfCTjFtOJu15p3M6NO/nHYK5Mt7YMQteuS89mOjB4ng8iwoLEZ6QpQ==", + "license": "MIT", + "dependencies": { + "@lexical/html": "0.35.0", + "@lexical/link": "0.35.0", + "@lexical/mark": "0.35.0", + "@lexical/table": "0.35.0", + "@lexical/utils": "0.35.0", + "lexical": "0.35.0" + }, + "peerDependencies": { + "react": ">=17.x", + "react-dom": ">=17.x" + } + }, + "node_modules/@lexical/dragon": { + "version": "0.35.0", + "resolved": "https://registry.npmjs.org/@lexical/dragon/-/dragon-0.35.0.tgz", + "integrity": "sha512-SL6mT5pcqrt6hEbJ16vWxip5+r3uvMd0bQV5UUxuk+cxIeuP86iTgRh0HFR7SM2dRTYovL6/tM/O+8QLAUGTIg==", + "license": "MIT", + "dependencies": { + "lexical": "0.35.0" + } + }, + "node_modules/@lexical/hashtag": { + "version": "0.35.0", + "resolved": "https://registry.npmjs.org/@lexical/hashtag/-/hashtag-0.35.0.tgz", + "integrity": "sha512-LYJWzXuO2ZjKsvQwrLkNZiS2TsjwYkKjlDgtugzejquTBQ/o/nfSn/MmVx6EkYLOYizaJemmZbz3IBh+u732FA==", + "license": "MIT", + "dependencies": { + "@lexical/utils": "0.35.0", + "lexical": "0.35.0" + } + }, + "node_modules/@lexical/history": { + "version": "0.35.0", + "resolved": "https://registry.npmjs.org/@lexical/history/-/history-0.35.0.tgz", + "integrity": "sha512-onjDRLLxGbCfHexSxxrQaDaieIHyV28zCDrbxR5dxTfW8F8PxjuNyuaG0z6o468AXYECmclxkP+P4aT6poHEpQ==", + "license": "MIT", + "dependencies": { + "@lexical/utils": "0.35.0", + "lexical": "0.35.0" + } + }, + "node_modules/@lexical/html": { + "version": "0.35.0", + "resolved": "https://registry.npmjs.org/@lexical/html/-/html-0.35.0.tgz", + "integrity": "sha512-rXGFE5S5rKsg3tVnr1s4iEgOfCApNXGpIFI3T2jGEShaCZ5HLaBY9NVBXnE9Nb49e9bkDkpZ8FZd1qokCbQXbw==", + "license": "MIT", + "dependencies": { + "@lexical/selection": "0.35.0", + "@lexical/utils": "0.35.0", + "lexical": "0.35.0" + } + }, + "node_modules/@lexical/link": { + "version": "0.35.0", + "resolved": "https://registry.npmjs.org/@lexical/link/-/link-0.35.0.tgz", + "integrity": "sha512-+0Wx6cBwO8TfdMzpkYFacsmgFh8X1rkiYbq3xoLvk3qV8upYxaMzK1s8Q1cpKmWyI0aZrU6z7fiK4vUqB7+69w==", + "license": "MIT", + "dependencies": { + "@lexical/utils": "0.35.0", + "lexical": "0.35.0" + } + }, + "node_modules/@lexical/list": { + "version": "0.35.0", + "resolved": "https://registry.npmjs.org/@lexical/list/-/list-0.35.0.tgz", + "integrity": "sha512-owsmc8iwgExBX8sFe8fKTiwJVhYULt9hD1RZ/HwfaiEtRZZkINijqReOBnW2mJfRxBzhFSWc4NG3ISB+fHYzqw==", + "license": "MIT", + "dependencies": { + "@lexical/selection": "0.35.0", + "@lexical/utils": "0.35.0", + "lexical": "0.35.0" + } + }, + "node_modules/@lexical/mark": { + "version": "0.35.0", + "resolved": "https://registry.npmjs.org/@lexical/mark/-/mark-0.35.0.tgz", + "integrity": "sha512-W0hwMTAVeexvpk9/+J6n1G/sNkpI/Meq1yeDazahFLLAwXLHtvhIAq2P/klgFknDy1hr8X7rcsQuN/bqKcKHYg==", + "license": "MIT", + "dependencies": { + "@lexical/utils": "0.35.0", + "lexical": "0.35.0" + } + }, + "node_modules/@lexical/markdown": { + "version": "0.35.0", + "resolved": "https://registry.npmjs.org/@lexical/markdown/-/markdown-0.35.0.tgz", + "integrity": "sha512-BlNyXZAt4gWidMw0SRWrhBETY1BpPglFBZI7yzfqukFqgXRh7HUQA28OYeI/nsx9pgNob8TiUduUwShqqvOdEA==", + "license": "MIT", + "dependencies": { + "@lexical/code": "0.35.0", + "@lexical/link": "0.35.0", + "@lexical/list": "0.35.0", + "@lexical/rich-text": "0.35.0", + "@lexical/text": "0.35.0", + "@lexical/utils": "0.35.0", + "lexical": "0.35.0" + } + }, + "node_modules/@lexical/offset": { + "version": "0.35.0", + "resolved": "https://registry.npmjs.org/@lexical/offset/-/offset-0.35.0.tgz", + "integrity": "sha512-DRE4Df6qYf2XiV6foh6KpGNmGAv2ANqt3oVXpyS6W8hTx3+cUuAA1APhCZmLNuU107um4zmHym7taCu6uXW5Yg==", + "license": "MIT", + "dependencies": { + "lexical": "0.35.0" + } + }, + "node_modules/@lexical/overflow": { + "version": "0.35.0", + "resolved": "https://registry.npmjs.org/@lexical/overflow/-/overflow-0.35.0.tgz", + "integrity": "sha512-B25YvnJQTGlZcrNv7b0PJBLWq3tl8sql497OHfYYLem7EOMPKKDGJScJAKM/91D4H/mMAsx5gnA/XgKobriuTg==", + "license": "MIT", + "dependencies": { + "lexical": "0.35.0" + } + }, + "node_modules/@lexical/plain-text": { + "version": "0.35.0", + "resolved": "https://registry.npmjs.org/@lexical/plain-text/-/plain-text-0.35.0.tgz", + "integrity": "sha512-lwBCUNMJf7Gujp2syVWMpKRahfbTv5Wq+H3HK1Q1gKH1P2IytPRxssCHvexw9iGwprSyghkKBlbF3fGpEdIJvQ==", + "license": "MIT", + "dependencies": { + "@lexical/clipboard": "0.35.0", + "@lexical/selection": "0.35.0", + "@lexical/utils": "0.35.0", + "lexical": "0.35.0" + } + }, + "node_modules/@lexical/react": { + "version": "0.35.0", + "resolved": "https://registry.npmjs.org/@lexical/react/-/react-0.35.0.tgz", + "integrity": "sha512-uYAZSqumH8tRymMef+A0f2hQvMwplKK9DXamcefnk3vSNDHHqRWQXpiUo6kD+rKWuQmMbVa5RW4xRQebXEW+1A==", + "license": "MIT", + "dependencies": { + "@floating-ui/react": "^0.27.8", + "@lexical/devtools-core": "0.35.0", + "@lexical/dragon": "0.35.0", + "@lexical/hashtag": "0.35.0", + "@lexical/history": "0.35.0", + "@lexical/link": "0.35.0", + "@lexical/list": "0.35.0", + "@lexical/mark": "0.35.0", + "@lexical/markdown": "0.35.0", + "@lexical/overflow": "0.35.0", + "@lexical/plain-text": "0.35.0", + "@lexical/rich-text": "0.35.0", + "@lexical/table": "0.35.0", + "@lexical/text": "0.35.0", + "@lexical/utils": "0.35.0", + "@lexical/yjs": "0.35.0", + "lexical": "0.35.0", + "react-error-boundary": "^3.1.4" + }, + "peerDependencies": { + "react": ">=17.x", + "react-dom": ">=17.x" + } + }, + "node_modules/@lexical/rich-text": { + "version": "0.35.0", + "resolved": "https://registry.npmjs.org/@lexical/rich-text/-/rich-text-0.35.0.tgz", + "integrity": "sha512-qEHu8g7vOEzz9GUz1VIUxZBndZRJPh9iJUFI+qTDHj+tQqnd5LCs+G9yz6jgNfiuWWpezTp0i1Vz/udNEuDPKQ==", + "license": "MIT", + "dependencies": { + "@lexical/clipboard": "0.35.0", + "@lexical/selection": "0.35.0", + "@lexical/utils": "0.35.0", + "lexical": "0.35.0" + } + }, + "node_modules/@lexical/selection": { + "version": "0.35.0", + "resolved": "https://registry.npmjs.org/@lexical/selection/-/selection-0.35.0.tgz", + "integrity": "sha512-mMtDE7Q0nycXdFTTH/+ta6EBrBwxBB4Tg8QwsGntzQ1Cq//d838dpXpFjJOqHEeVHUqXpiuj+cBG8+bvz/rPRw==", + "license": "MIT", + "dependencies": { + "lexical": "0.35.0" + } + }, + "node_modules/@lexical/table": { + "version": "0.35.0", + "resolved": "https://registry.npmjs.org/@lexical/table/-/table-0.35.0.tgz", + "integrity": "sha512-9jlTlkVideBKwsEnEkqkdg7A3mije1SvmfiqoYnkl1kKJCLA5iH90ywx327PU0p+bdnURAytWUeZPXaEuEl2OA==", + "license": "MIT", + "dependencies": { + "@lexical/clipboard": "0.35.0", + "@lexical/utils": "0.35.0", + "lexical": "0.35.0" + } + }, + "node_modules/@lexical/text": { + "version": "0.35.0", + "resolved": "https://registry.npmjs.org/@lexical/text/-/text-0.35.0.tgz", + "integrity": "sha512-uaMh46BkysV8hK8wQwp5g/ByZW+2hPDt8ahAErxtf8NuzQem1FHG/f5RTchmFqqUDVHO3qLNTv4AehEGmXv8MA==", + "license": "MIT", + "dependencies": { + "lexical": "0.35.0" + } + }, + "node_modules/@lexical/utils": { + "version": "0.35.0", + "resolved": "https://registry.npmjs.org/@lexical/utils/-/utils-0.35.0.tgz", + "integrity": "sha512-2H393EYDnFznYCDFOW3MHiRzwEO5M/UBhtUjvTT+9kc+qhX4U3zc8ixQalo5UmZ5B2nh7L/inXdTFzvSRXtsRA==", + "license": "MIT", + "dependencies": { + "@lexical/list": "0.35.0", + "@lexical/selection": "0.35.0", + "@lexical/table": "0.35.0", + "lexical": "0.35.0" + } + }, + "node_modules/@lexical/yjs": { + "version": "0.35.0", + "resolved": "https://registry.npmjs.org/@lexical/yjs/-/yjs-0.35.0.tgz", + "integrity": "sha512-3DSP7QpmTGYU9bN/yljP0PIao4tNIQtsR4ycauWNSawxs/GQCZtSmAPcLRnCm6qpqsDDjUtKjO/1Ej8FRp0m0w==", + "license": "MIT", + "dependencies": { + "@lexical/offset": "0.35.0", + "@lexical/selection": "0.35.0", + "lexical": "0.35.0" + }, + "peerDependencies": { + "yjs": ">=13.5.22" + } + }, + "node_modules/@lezer/common": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/@lezer/common/-/common-1.4.0.tgz", + "integrity": "sha512-DVeMRoGrgn/k45oQNu189BoW4SZwgZFzJ1+1TV5j2NJ/KFC83oa/enRqZSGshyeMk5cPWMhsKs9nx+8o0unwGg==", + "license": "MIT" + }, + "node_modules/@lezer/cpp": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/@lezer/cpp/-/cpp-1.1.3.tgz", + "integrity": "sha512-ykYvuFQKGsRi6IcE+/hCSGUhb/I4WPjd3ELhEblm2wS2cOznDFzO+ubK2c+ioysOnlZ3EduV+MVQFCPzAIoY3w==", + "license": "MIT", + "dependencies": { + "@lezer/common": "^1.2.0", + "@lezer/highlight": "^1.0.0", + "@lezer/lr": "^1.0.0" + } + }, + "node_modules/@lezer/css": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/@lezer/css/-/css-1.3.0.tgz", + "integrity": "sha512-pBL7hup88KbI7hXnZV3PQsn43DHy6TWyzuyk2AO9UyoXcDltvIdqWKE1dLL/45JVZ+YZkHe1WVHqO6wugZZWcw==", + "license": "MIT", + "dependencies": { + "@lezer/common": "^1.2.0", + "@lezer/highlight": "^1.0.0", + "@lezer/lr": "^1.3.0" + } + }, + "node_modules/@lezer/go": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@lezer/go/-/go-1.0.1.tgz", + "integrity": "sha512-xToRsYxwsgJNHTgNdStpcvmbVuKxTapV0dM0wey1geMMRc9aggoVyKgzYp41D2/vVOx+Ii4hmE206kvxIXBVXQ==", + "license": "MIT", + "dependencies": { + "@lezer/common": "^1.2.0", + "@lezer/highlight": "^1.0.0", + "@lezer/lr": "^1.3.0" + } + }, + "node_modules/@lezer/highlight": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/@lezer/highlight/-/highlight-1.2.3.tgz", + "integrity": "sha512-qXdH7UqTvGfdVBINrgKhDsVTJTxactNNxLk7+UMwZhU13lMHaOBlJe9Vqp907ya56Y3+ed2tlqzys7jDkTmW0g==", + "license": "MIT", + "dependencies": { + "@lezer/common": "^1.3.0" + } + }, + "node_modules/@lezer/html": { + "version": "1.3.12", + "resolved": "https://registry.npmjs.org/@lezer/html/-/html-1.3.12.tgz", + "integrity": "sha512-RJ7eRWdaJe3bsiiLLHjCFT1JMk8m1YP9kaUbvu2rMLEoOnke9mcTVDyfOslsln0LtujdWespjJ39w6zo+RsQYw==", + "license": "MIT", + "dependencies": { + "@lezer/common": "^1.2.0", + "@lezer/highlight": "^1.0.0", + "@lezer/lr": "^1.0.0" + } + }, + "node_modules/@lezer/java": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/@lezer/java/-/java-1.1.3.tgz", + "integrity": "sha512-yHquUfujwg6Yu4Fd1GNHCvidIvJwi/1Xu2DaKl/pfWIA2c1oXkVvawH3NyXhCaFx4OdlYBVX5wvz2f7Aoa/4Xw==", + "license": "MIT", + "dependencies": { + "@lezer/common": "^1.2.0", + "@lezer/highlight": "^1.0.0", + "@lezer/lr": "^1.0.0" + } + }, + "node_modules/@lezer/javascript": { + "version": "1.5.4", + "resolved": "https://registry.npmjs.org/@lezer/javascript/-/javascript-1.5.4.tgz", + "integrity": "sha512-vvYx3MhWqeZtGPwDStM2dwgljd5smolYD2lR2UyFcHfxbBQebqx8yjmFmxtJ/E6nN6u1D9srOiVWm3Rb4tmcUA==", + "license": "MIT", + "dependencies": { + "@lezer/common": "^1.2.0", + "@lezer/highlight": "^1.1.3", + "@lezer/lr": "^1.3.0" + } + }, + "node_modules/@lezer/json": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@lezer/json/-/json-1.0.3.tgz", + "integrity": "sha512-BP9KzdF9Y35PDpv04r0VeSTKDeox5vVr3efE7eBbx3r4s3oNLfunchejZhjArmeieBH+nVOpgIiBJpEAv8ilqQ==", + "license": "MIT", + "dependencies": { + "@lezer/common": "^1.2.0", + "@lezer/highlight": "^1.0.0", + "@lezer/lr": "^1.0.0" + } + }, + "node_modules/@lezer/lr": { + "version": "1.4.4", + "resolved": "https://registry.npmjs.org/@lezer/lr/-/lr-1.4.4.tgz", + "integrity": "sha512-LHL17Mq0OcFXm1pGQssuGTQFPPdxARjKM8f7GA5+sGtHi0K3R84YaSbmche0+RKWHnCsx9asEe5OWOI4FHfe4A==", + "license": "MIT", + "dependencies": { + "@lezer/common": "^1.0.0" + } + }, + "node_modules/@lezer/markdown": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/@lezer/markdown/-/markdown-1.6.0.tgz", + "integrity": "sha512-AXb98u3M6BEzTnreBnGtQaF7xFTiMA92Dsy5tqEjpacbjRxDSFdN4bKJo9uvU4cEEOS7D2B9MT7kvDgOEIzJSw==", + "license": "MIT", + "dependencies": { + "@lezer/common": "^1.0.0", + "@lezer/highlight": "^1.0.0" + } + }, + "node_modules/@lezer/php": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/@lezer/php/-/php-1.0.5.tgz", + "integrity": "sha512-W7asp9DhM6q0W6DYNwIkLSKOvxlXRrif+UXBMxzsJUuqmhE7oVU+gS3THO4S/Puh7Xzgm858UNaFi6dxTP8dJA==", + "license": "MIT", + "dependencies": { + "@lezer/common": "^1.2.0", + "@lezer/highlight": "^1.0.0", + "@lezer/lr": "^1.1.0" + } + }, + "node_modules/@lezer/python": { + "version": "1.1.18", + "resolved": "https://registry.npmjs.org/@lezer/python/-/python-1.1.18.tgz", + "integrity": "sha512-31FiUrU7z9+d/ElGQLJFXl+dKOdx0jALlP3KEOsGTex8mvj+SoE1FgItcHWK/axkxCHGUSpqIHt6JAWfWu9Rhg==", + "license": "MIT", + "dependencies": { + "@lezer/common": "^1.2.0", + "@lezer/highlight": "^1.0.0", + "@lezer/lr": "^1.0.0" + } + }, + "node_modules/@lezer/rust": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@lezer/rust/-/rust-1.0.2.tgz", + "integrity": "sha512-Lz5sIPBdF2FUXcWeCu1//ojFAZqzTQNRga0aYv6dYXqJqPfMdCAI0NzajWUd4Xijj1IKJLtjoXRPMvTKWBcqKg==", + "license": "MIT", + "dependencies": { + "@lezer/common": "^1.2.0", + "@lezer/highlight": "^1.0.0", + "@lezer/lr": "^1.0.0" + } + }, + "node_modules/@lezer/sass": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@lezer/sass/-/sass-1.1.0.tgz", + "integrity": "sha512-3mMGdCTUZ/84ArHOuXWQr37pnf7f+Nw9ycPUeKX+wu19b7pSMcZGLbaXwvD2APMBDOGxPmpK/O6S1v1EvLoqgQ==", + "license": "MIT", + "dependencies": { + "@lezer/common": "^1.2.0", + "@lezer/highlight": "^1.0.0", + "@lezer/lr": "^1.0.0" + } + }, + "node_modules/@lezer/xml": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/@lezer/xml/-/xml-1.0.6.tgz", + "integrity": "sha512-CdDwirL0OEaStFue/66ZmFSeppuL6Dwjlk8qk153mSQwiSH/Dlri4GNymrNWnUmPl2Um7QfV1FO9KFUyX3Twww==", + "license": "MIT", + "dependencies": { + "@lezer/common": "^1.2.0", + "@lezer/highlight": "^1.0.0", + "@lezer/lr": "^1.0.0" + } + }, + "node_modules/@lezer/yaml": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@lezer/yaml/-/yaml-1.0.3.tgz", + "integrity": "sha512-GuBLekbw9jDBDhGur82nuwkxKQ+a3W5H0GfaAthDXcAu+XdpS43VlnxA9E9hllkpSP5ellRDKjLLj7Lu9Wr6xA==", + "license": "MIT", + "dependencies": { + "@lezer/common": "^1.2.0", + "@lezer/highlight": "^1.0.0", + "@lezer/lr": "^1.4.0" + } + }, "node_modules/@libp2p/interface-connection": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/@libp2p/interface-connection/-/interface-connection-4.0.0.tgz", @@ -2352,6 +3290,114 @@ "npm": ">=7.0.0" } }, + "node_modules/@marijn/find-cluster-break": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@marijn/find-cluster-break/-/find-cluster-break-1.0.2.tgz", + "integrity": "sha512-l0h88YhZFyKdXIFNfSWpyjStDjGHwZ/U7iobcK1cQQD8sejsONdQtTVU+1wVN1PBw40PiiHB1vA5S7VTfQiP9g==", + "license": "MIT" + }, + "node_modules/@mdxeditor/editor": { + "version": "3.51.0", + "resolved": "https://registry.npmjs.org/@mdxeditor/editor/-/editor-3.51.0.tgz", + "integrity": "sha512-ID39Cn1KaLeafT2RvdGXTSkizWuga5d9VeBNC+cGZnlfmKPxwMwXZN2aR7BsE0gAlq0gqCdFGvYKl2RYqgC66A==", + "license": "MIT", + "dependencies": { + "@codemirror/commands": "^6.2.4", + "@codemirror/lang-markdown": "^6.2.3", + "@codemirror/language-data": "^6.5.1", + "@codemirror/merge": "^6.4.0", + "@codemirror/state": "^6.4.0", + "@codemirror/view": "^6.23.0", + "@codesandbox/sandpack-react": "^2.20.0", + "@lexical/clipboard": "^0.35.0", + "@lexical/link": "^0.35.0", + "@lexical/list": "^0.35.0", + "@lexical/markdown": "^0.35.0", + "@lexical/plain-text": "^0.35.0", + "@lexical/react": "^0.35.0", + "@lexical/rich-text": "^0.35.0", + "@lexical/selection": "^0.35.0", + "@lexical/utils": "^0.35.0", + "@mdxeditor/gurx": "^1.2.4", + "@radix-ui/colors": "^3.0.0", + "@radix-ui/react-dialog": "^1.1.11", + "@radix-ui/react-icons": "^1.3.2", + "@radix-ui/react-popover": "^1.1.11", + "@radix-ui/react-popper": "^1.2.4", + "@radix-ui/react-select": "^2.2.2", + "@radix-ui/react-toggle-group": "^1.1.7", + "@radix-ui/react-toolbar": "^1.1.7", + "@radix-ui/react-tooltip": "^1.2.4", + "classnames": "^2.3.2", + "cm6-theme-basic-light": "^0.2.0", + "codemirror": "^6.0.1", + "downshift": "^7.6.0", + "js-yaml": "4.1.1", + "lexical": "^0.35.0", + "mdast-util-directive": "^3.0.0", + "mdast-util-from-markdown": "^2.0.0", + "mdast-util-frontmatter": "^2.0.1", + "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-highlight-mark": "^1.2.2", + "mdast-util-mdx": "^3.0.0", + "mdast-util-mdx-jsx": "^3.0.0", + "mdast-util-to-markdown": "^2.1.0", + "micromark-extension-directive": "^3.0.0", + "micromark-extension-frontmatter": "^2.0.0", + "micromark-extension-gfm-strikethrough": "^2.0.0", + "micromark-extension-gfm-table": "^2.0.0", + "micromark-extension-gfm-task-list-item": "^2.0.1", + "micromark-extension-highlight-mark": "^1.2.0", + "micromark-extension-mdx-jsx": "^3.0.0", + "micromark-extension-mdx-md": "^2.0.0", + "micromark-extension-mdxjs": "^3.0.0", + "micromark-factory-space": "^2.0.0", + "micromark-util-character": "^2.0.1", + "micromark-util-symbol": "^2.0.0", + "react-hook-form": "^7.56.1", + "unidiff": "^1.0.2" + }, + "engines": { + "node": ">=16" + }, + "peerDependencies": { + "react": ">= 18 || >= 19", + "react-dom": ">= 18 || >= 19" + } + }, + "node_modules/@mdxeditor/editor/node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "license": "Python-2.0" + }, + "node_modules/@mdxeditor/editor/node_modules/js-yaml": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.1.tgz", + "integrity": "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==", + "license": "MIT", + "dependencies": { + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/@mdxeditor/gurx": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@mdxeditor/gurx/-/gurx-1.2.4.tgz", + "integrity": "sha512-9ZykIFYhKaXaaSPCs1cuI+FvYDegJjbKwmA4ASE/zY+hJY6EYqvoye4esiO85CjhOw9aoD/izD/CU78/egVqmg==", + "license": "MIT", + "engines": { + "node": ">=16" + }, + "peerDependencies": { + "react": ">= 18 || >= 19", + "react-dom": ">= 18 || >= 19" + } + }, "node_modules/@multiformats/dns": { "version": "1.0.10", "resolved": "https://registry.npmjs.org/@multiformats/dns/-/dns-1.0.10.tgz", @@ -2500,6 +3546,12 @@ "url": "https://paulmillr.com/funding/" } }, + "node_modules/@open-draft/deferred-promise": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@open-draft/deferred-promise/-/deferred-promise-2.2.0.tgz", + "integrity": "sha512-CecwLWx3rhxVQF6V4bAgPS5t+So2sTbPgAzafKkVizyi7tlwpcFpdFqq+wqF2OwNBmqFuu6tOyouTuxgpMfzmA==", + "license": "MIT" + }, "node_modules/@opentelemetry/api": { "version": "1.9.0", "resolved": "https://registry.npmjs.org/@opentelemetry/api/-/api-1.9.0.tgz", @@ -2667,6 +3719,12 @@ "integrity": "sha512-Vvn3zZrhQZkkBE8LSuW3em98c0FwgO4nxzv6OdSxPKJIEKY2bGbHn+mhGIPerzI4twdxaP8/0+06HBpwf345Lw==", "license": "BSD-3-Clause" }, + "node_modules/@radix-ui/colors": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@radix-ui/colors/-/colors-3.0.0.tgz", + "integrity": "sha512-FUOsGBkHrYJwCSEtWRCIfQbZG7q1e6DgxCIOe1SUQzDe/7rXXeA47s8yCn6fuTNQAj1Zq4oTFi9Yjp3wzElcxg==", + "license": "MIT" + }, "node_modules/@radix-ui/number": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/@radix-ui/number/-/number-1.1.1.tgz", @@ -3184,6 +4242,15 @@ } } }, + "node_modules/@radix-ui/react-icons": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/@radix-ui/react-icons/-/react-icons-1.3.2.tgz", + "integrity": "sha512-fyQIhGDhzfc9pK2kH6Pl9c4BDJGfMkPqkyIgYDthyNYoNg3wVhoJMMh19WS4Up/1KMPFVpNsT2q3WmXn2N1m6g==", + "license": "MIT", + "peerDependencies": { + "react": "^16.x || ^17.x || ^18.x || ^19.0.0 || ^19.0.0-rc" + } + }, "node_modules/@radix-ui/react-id": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/@radix-ui/react-id/-/react-id-1.1.1.tgz", @@ -4165,6 +5232,28 @@ "integrity": "sha512-HPwpGIzkl28mWyZqG52jiqDJ12waP11Pa1lGoiyUkIEuMLBP0oeK/C89esbXrxsky5we7dfd8U58nm0SgAWpVw==", "license": "MIT" }, + "node_modules/@react-hook/intersection-observer": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@react-hook/intersection-observer/-/intersection-observer-3.1.2.tgz", + "integrity": "sha512-mWU3BMkmmzyYMSuhO9wu3eJVP21N8TcgYm9bZnTrMwuM818bEk+0NRM3hP+c/TqA9Ln5C7qE53p1H0QMtzYdvQ==", + "license": "MIT", + "dependencies": { + "@react-hook/passive-layout-effect": "^1.2.0", + "intersection-observer": "^0.10.0" + }, + "peerDependencies": { + "react": ">=16.8" + } + }, + "node_modules/@react-hook/passive-layout-effect": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@react-hook/passive-layout-effect/-/passive-layout-effect-1.2.1.tgz", + "integrity": "sha512-IwEphTD75liO8g+6taS+4oqz+nnroocNfWVHWz7j+N+ZO2vYrc6PV1q7GQhuahL0IOR7JccFTsFKQ/mb6iZWAg==", + "license": "MIT", + "peerDependencies": { + "react": ">=16.8" + } + }, "node_modules/@remirror/core-constants": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/@remirror/core-constants/-/core-constants-3.0.0.tgz", @@ -4656,6 +5745,12 @@ "integrity": "sha512-1bnPQqSxSuc3Ii6MhBysoWCg58j97aUjuCSZrGSmDxNqtytIi0k8utUenAwTZN4V5mXXYGsVUI9zeBqy+jBOSQ==", "license": "MIT" }, + "node_modules/@stitches/core": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/@stitches/core/-/core-1.2.8.tgz", + "integrity": "sha512-Gfkvwk9o9kE9r9XNBmJRfV8zONvXThnm1tcuojL04Uy5uRyqg93DC83lDebl0rocZCfKSjUv+fWYtMQmEDJldg==", + "license": "MIT" + }, "node_modules/@swc/core": { "version": "1.15.3", "resolved": "https://registry.npmjs.org/@swc/core/-/core-1.15.3.tgz", @@ -6164,6 +7259,15 @@ "node": ">=0.4.0" } }, + "node_modules/acorn-jsx": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", + "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", + "license": "MIT", + "peerDependencies": { + "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" + } + }, "node_modules/acorn-walk": { "version": "7.2.0", "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-7.2.0.tgz", @@ -6239,6 +7343,12 @@ "url": "https://github.com/sponsors/epoberezkin" } }, + "node_modules/anser": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/anser/-/anser-2.3.3.tgz", + "integrity": "sha512-QGY1oxYE7/kkeNmbtY/2ZjQ07BCG3zYdz+k/+sf69kMzEIxb93guHkPnIXITQ+BYi61oQwG74twMOX1tD4aesg==", + "license": "MIT" + }, "node_modules/ansi-regex": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", @@ -6911,6 +8021,12 @@ "node": ">= 10.0" } }, + "node_modules/clean-set": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/clean-set/-/clean-set-1.1.2.tgz", + "integrity": "sha512-cA8uCj0qSoG9e0kevyOWXwPaELRPVg5Pxp6WskLMwerx257Zfnh8Nl0JBH59d7wQzij2CK7qEfJQK3RjuKKIug==", + "license": "MIT" + }, "node_modules/cli-cursor": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-3.1.0.tgz", @@ -6971,6 +8087,33 @@ "integrity": "sha512-34yY66OBWtX4nSq8cYvOG4pZ4ufaPEnBfm00Z6sfLcXT0JdUFcCROt9LRD2cWRd1euQ3cdhFXw782WEui9rHxw==", "license": "MIT" }, + "node_modules/cm6-theme-basic-light": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/cm6-theme-basic-light/-/cm6-theme-basic-light-0.2.0.tgz", + "integrity": "sha512-1prg2gv44sYfpHscP26uLT/ePrh0mlmVwMSoSd3zYKQ92Ab3jPRLzyCnpyOCQLJbK+YdNs4HvMRqMNYdy4pMhA==", + "license": "MIT", + "peerDependencies": { + "@codemirror/language": "^6.0.0", + "@codemirror/state": "^6.0.0", + "@codemirror/view": "^6.0.0", + "@lezer/highlight": "^1.0.0" + } + }, + "node_modules/codemirror": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/codemirror/-/codemirror-6.0.2.tgz", + "integrity": "sha512-VhydHotNW5w1UGK0Qj96BwSk/Zqbp9WbnyK2W/eVMv4QyF41INRGpjUhFJY7/uDNuudSc33a/PKr4iDqRduvHw==", + "license": "MIT", + "dependencies": { + "@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" + } + }, "node_modules/color": { "version": "4.2.3", "resolved": "https://registry.npmjs.org/color/-/color-4.2.3.tgz", @@ -7044,6 +8187,12 @@ "node": ">= 10" } }, + "node_modules/compute-scroll-into-view": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/compute-scroll-into-view/-/compute-scroll-into-view-2.0.4.tgz", + "integrity": "sha512-y/ZA3BGnxoM/QHHQ2Uy49CLtnWPbt4tTPpEEZiEmmiWBFKjej7nEyH8Ryz54jH0MLXflUYA3Er2zUxPSJu5R+g==", + "license": "MIT" + }, "node_modules/concurrently": { "version": "9.2.1", "resolved": "https://registry.npmjs.org/concurrently/-/concurrently-9.2.1.tgz", @@ -7300,6 +8449,19 @@ "license": "MIT", "optional": true }, + "node_modules/d": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/d/-/d-1.0.2.tgz", + "integrity": "sha512-MOqHvMWF9/9MX6nza0KgvFH4HpMU0EF5uUDXqX/BtxtU8NfB0QzRtJ8Oe/6SuS4kbhyzVJwjd97EA4PKrzJ8bw==", + "license": "ISC", + "dependencies": { + "es5-ext": "^0.10.64", + "type": "^2.7.2" + }, + "engines": { + "node": ">=0.12" + } + }, "node_modules/d3": { "version": "7.9.0", "resolved": "https://registry.npmjs.org/d3/-/d3-7.9.0.tgz", @@ -8036,6 +9198,15 @@ "url": "https://github.com/sponsors/wooorm" } }, + "node_modules/diff": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/diff/-/diff-5.2.0.tgz", + "integrity": "sha512-uIFDxqpRZGZ6ThOk84hEfqWoHx2devRFvpTZcTHur85vImfaxUbTW9Ryh4CpCuDnToOP1CEtXKIgytHBPVff5A==", + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.3.1" + } + }, "node_modules/diff-match-patch": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/diff-match-patch/-/diff-match-patch-1.0.5.tgz", @@ -8173,6 +9344,34 @@ "tslib": "^2.0.3" } }, + "node_modules/dotenv": { + "version": "16.6.1", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.6.1.tgz", + "integrity": "sha512-uBq4egWHTcTt33a72vpSG0z3HnPuIl6NqYcTrKEg2azoEyl2hpW0zqlxysq2pK9HlDIHyHyakeYaYnSAwd8bow==", + "license": "BSD-2-Clause", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://dotenvx.com" + } + }, + "node_modules/downshift": { + "version": "7.6.2", + "resolved": "https://registry.npmjs.org/downshift/-/downshift-7.6.2.tgz", + "integrity": "sha512-iOv+E1Hyt3JDdL9yYcOgW7nZ7GQ2Uz6YbggwXvKUSleetYhU2nXD482Rz6CzvM4lvI1At34BYruKAL4swRGxaA==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.14.8", + "compute-scroll-into-view": "^2.0.4", + "prop-types": "^15.7.2", + "react-is": "^17.0.2", + "tslib": "^2.3.0" + }, + "peerDependencies": { + "react": ">=16.12.0" + } + }, "node_modules/dunder-proto": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", @@ -8293,6 +9492,46 @@ "node": ">= 0.4" } }, + "node_modules/es5-ext": { + "version": "0.10.64", + "resolved": "https://registry.npmjs.org/es5-ext/-/es5-ext-0.10.64.tgz", + "integrity": "sha512-p2snDhiLaXe6dahss1LddxqEm+SkuDvV8dnIQG0MWjyHpcMNfXKPE+/Cc0y+PhxJX3A4xGNeFCj5oc0BUh6deg==", + "hasInstallScript": true, + "license": "ISC", + "dependencies": { + "es6-iterator": "^2.0.3", + "es6-symbol": "^3.1.3", + "esniff": "^2.0.1", + "next-tick": "^1.1.0" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/es6-iterator": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/es6-iterator/-/es6-iterator-2.0.3.tgz", + "integrity": "sha512-zw4SRzoUkd+cl+ZoE15A9o1oQd920Bb0iOJMQkQhl3jNc03YqVjAhG7scf9C5KWRU/R13Orf588uCC6525o02g==", + "license": "MIT", + "dependencies": { + "d": "1", + "es5-ext": "^0.10.35", + "es6-symbol": "^3.1.1" + } + }, + "node_modules/es6-symbol": { + "version": "3.1.4", + "resolved": "https://registry.npmjs.org/es6-symbol/-/es6-symbol-3.1.4.tgz", + "integrity": "sha512-U9bFFjX8tFiATgtkJ1zg25+KviIXpgRvRHS8sau3GfhVzThRQrOeksPeT0BWW2MNZs1OEWJ1DPXOQMn0KKRkvg==", + "license": "ISC", + "dependencies": { + "d": "^1.0.2", + "ext": "^1.7.0" + }, + "engines": { + "node": ">=0.12" + } + }, "node_modules/esbuild": { "version": "0.25.12", "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.12.tgz", @@ -8345,6 +9584,12 @@ "node": ">=6" } }, + "node_modules/escape-carriage": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/escape-carriage/-/escape-carriage-1.3.1.tgz", + "integrity": "sha512-GwBr6yViW3ttx1kb7/Oh+gKQ1/TrhYwxKqVmg5gS+BK+Qe2KrOa/Vh7w3HPBvgGf0LfcDGoY9I6NHKoA5Hozhw==", + "license": "MIT" + }, "node_modules/escape-html": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", @@ -8384,6 +9629,21 @@ "source-map": "~0.6.1" } }, + "node_modules/esniff": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/esniff/-/esniff-2.0.1.tgz", + "integrity": "sha512-kTUIGKQ/mDPFoJ0oVfcmyJn4iBDRptjNVIzwIFR7tqWXdVI9xfA2RMwY/gbSpJG3lkdWNEjLap/NqVHZiJsdfg==", + "license": "ISC", + "dependencies": { + "d": "^1.0.1", + "es5-ext": "^0.10.62", + "event-emitter": "^0.3.5", + "type": "^2.7.2" + }, + "engines": { + "node": ">=0.10" + } + }, "node_modules/esprima": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", @@ -8416,6 +9676,20 @@ "url": "https://opencollective.com/unified" } }, + "node_modules/estree-util-visit": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/estree-util-visit/-/estree-util-visit-2.0.0.tgz", + "integrity": "sha512-m5KgiH85xAhhW8Wta0vShLcUvOsh3LLPI2YVwcbio1l7E09NTLL1EyMZFM1OyWowoH0skScNbhOPl4kcBgzTww==", + "license": "MIT", + "dependencies": { + "@types/estree-jsx": "^1.0.0", + "@types/unist": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, "node_modules/esutils": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", @@ -8434,6 +9708,16 @@ "node": ">= 0.6" } }, + "node_modules/event-emitter": { + "version": "0.3.5", + "resolved": "https://registry.npmjs.org/event-emitter/-/event-emitter-0.3.5.tgz", + "integrity": "sha512-D9rRn9y7kLPnJ+hMq7S/nhvoKwwvVJahBi2BPmx3bvbsEdK3W9ii8cBSGjP+72/LnM4n6fo3+dkCX5FeTQruXA==", + "license": "MIT", + "dependencies": { + "d": "1", + "es5-ext": "~0.10.14" + } + }, "node_modules/event-target-shim": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/event-target-shim/-/event-target-shim-5.0.1.tgz", @@ -8532,6 +9816,15 @@ "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", "license": "MIT" }, + "node_modules/ext": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/ext/-/ext-1.7.0.tgz", + "integrity": "sha512-6hxeJYaL110a9b5TEJSj0gojyHQAmA2ch5Os+ySCiA1QGdS697XWY1pzsrSjqA9LDEEgdB/KypIlR59RcLuHYw==", + "license": "ISC", + "dependencies": { + "type": "^2.7.2" + } + }, "node_modules/extend": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", @@ -8587,6 +9880,19 @@ "zod": "^3.20.0" } }, + "node_modules/fault": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/fault/-/fault-2.0.1.tgz", + "integrity": "sha512-WtySTkS4OKev5JtpHXnib4Gxiurzh5NCGvWrFaZ34m6JehfTUhKZvn9njTfw48t6JumVQOmrKqpmGcdwxnhqBQ==", + "license": "MIT", + "dependencies": { + "format": "^0.2.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/fdir": { "version": "6.5.0", "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", @@ -8738,6 +10044,14 @@ "integrity": "sha512-qfqtYan3rxrnCk1VYaA4H+Ms9xdpPqvLZa6xmMgFvhO32x7/3J/ExcTd6qpxM0vH2GdMI+poehyBZvqfMTto8A==", "license": "MIT" }, + "node_modules/format": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/format/-/format-0.2.2.tgz", + "integrity": "sha512-wzsgA6WOq+09wrU1tsJ09udeR/YZRaeArL9e1wPbFg3GG2yDnC2ldKpxs4xunpFF9DgqCqOIra3bc1HWrJ37Ww==", + "engines": { + "node": ">=0.4.x" + } + }, "node_modules/formdata-node": { "version": "4.4.1", "resolved": "https://registry.npmjs.org/formdata-node/-/formdata-node-4.4.1.tgz", @@ -9703,6 +11017,13 @@ "node": ">=12" } }, + "node_modules/intersection-observer": { + "version": "0.10.0", + "resolved": "https://registry.npmjs.org/intersection-observer/-/intersection-observer-0.10.0.tgz", + "integrity": "sha512-fn4bQ0Xq8FTej09YC/jqKZwtijpvARlRp6wxL5WTA6yPe2YWSJ5RJh7Nm79rK2qB0wr6iDQzH60XGq5V/7u8YQ==", + "deprecated": "The Intersection Observer polyfill is no longer needed and can safely be removed. Intersection Observer has been Baseline since 2019.", + "license": "W3C-20150513" + }, "node_modules/io-ts": { "version": "2.2.22", "resolved": "https://registry.npmjs.org/io-ts/-/io-ts-2.2.22.tgz", @@ -10498,7 +11819,6 @@ "version": "4.1.5", "resolved": "https://registry.npmjs.org/kleur/-/kleur-4.1.5.tgz", "integrity": "sha512-o+NO+8WrRiQEE4/7nwRJhN1HWpVmJm511pBHUxPLtp0BUISzlBplORYSmTclCnJvQq2tKu/sgl3xVpkc7ZWuQQ==", - "dev": true, "license": "MIT", "engines": { "node": ">=6" @@ -10583,6 +11903,12 @@ "ieee754": "^1.2.1" } }, + "node_modules/lexical": { + "version": "0.35.0", + "resolved": "https://registry.npmjs.org/lexical/-/lexical-0.35.0.tgz", + "integrity": "sha512-3VuV8xXhh5xJA6tzvfDvE0YBCMkIZUmxtRilJQDDdCgJCc+eut6qAv2qbN+pbqvarqcQqPN1UF+8YvsjmyOZpw==", + "license": "MIT" + }, "node_modules/lie": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/lie/-/lie-3.1.1.tgz", @@ -10810,6 +12136,27 @@ "node": ">= 0.4" } }, + "node_modules/mdast-util-directive": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/mdast-util-directive/-/mdast-util-directive-3.1.0.tgz", + "integrity": "sha512-I3fNFt+DHmpWCYAT7quoM6lHf9wuqtI+oCOfvILnoicNIqjh5E3dEJWiXuYME2gNe8vl1iMQwyUHa7bgFmak6Q==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "@types/unist": "^3.0.0", + "ccount": "^2.0.0", + "devlop": "^1.0.0", + "mdast-util-from-markdown": "^2.0.0", + "mdast-util-to-markdown": "^2.0.0", + "parse-entities": "^4.0.0", + "stringify-entities": "^4.0.0", + "unist-util-visit-parents": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, "node_modules/mdast-util-find-and-replace": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/mdast-util-find-and-replace/-/mdast-util-find-and-replace-3.0.2.tgz", @@ -10850,6 +12197,24 @@ "url": "https://opencollective.com/unified" } }, + "node_modules/mdast-util-frontmatter": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/mdast-util-frontmatter/-/mdast-util-frontmatter-2.0.1.tgz", + "integrity": "sha512-LRqI9+wdgC25P0URIJY9vwocIzCcksduHQ9OF2joxQoyTNVduwLAFUzjoopuRJbJAReaKrNQKAZKL3uCMugWJA==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "devlop": "^1.0.0", + "escape-string-regexp": "^5.0.0", + "mdast-util-from-markdown": "^2.0.0", + "mdast-util-to-markdown": "^2.0.0", + "micromark-extension-frontmatter": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, "node_modules/mdast-util-gfm": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/mdast-util-gfm/-/mdast-util-gfm-3.1.0.tgz", @@ -10951,6 +12316,32 @@ "url": "https://opencollective.com/unified" } }, + "node_modules/mdast-util-highlight-mark": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/mdast-util-highlight-mark/-/mdast-util-highlight-mark-1.2.2.tgz", + "integrity": "sha512-OYumVoytj+B9YgwzBhBcYUCLYHIPvJtAvwnMyKhUXbfUFuER5S+FDZyu9fadUxm2TCT5fRYK3jQXh2ioWAxrMw==", + "license": "MIT", + "dependencies": { + "micromark-extension-highlight-mark": "1.2.0" + } + }, + "node_modules/mdast-util-mdx": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/mdast-util-mdx/-/mdast-util-mdx-3.0.0.tgz", + "integrity": "sha512-JfbYLAW7XnYTTbUsmpu0kdBUVe+yKVJZBItEjwyYJiDJuZ9w4eeaqks4HQO+R7objWgS2ymV60GYpI14Ug554w==", + "license": "MIT", + "dependencies": { + "mdast-util-from-markdown": "^2.0.0", + "mdast-util-mdx-expression": "^2.0.0", + "mdast-util-mdx-jsx": "^3.0.0", + "mdast-util-mdxjs-esm": "^2.0.0", + "mdast-util-to-markdown": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, "node_modules/mdast-util-mdx-expression": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/mdast-util-mdx-expression/-/mdast-util-mdx-expression-2.0.1.tgz", @@ -11235,6 +12626,41 @@ "micromark-util-types": "^2.0.0" } }, + "node_modules/micromark-extension-directive": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/micromark-extension-directive/-/micromark-extension-directive-3.0.2.tgz", + "integrity": "sha512-wjcXHgk+PPdmvR58Le9d7zQYWy+vKEU9Se44p2CrCDPiLr2FMyiT4Fyb5UFKFC66wGB3kPlgD7q3TnoqPS7SZA==", + "license": "MIT", + "dependencies": { + "devlop": "^1.0.0", + "micromark-factory-space": "^2.0.0", + "micromark-factory-whitespace": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0", + "parse-entities": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/micromark-extension-frontmatter": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/micromark-extension-frontmatter/-/micromark-extension-frontmatter-2.0.0.tgz", + "integrity": "sha512-C4AkuM3dA58cgZha7zVnuVxBhDsbttIMiytjgsM2XbHAB2faRVaHRle40558FBN+DJcrLNCoqG5mlrpdU4cRtg==", + "license": "MIT", + "dependencies": { + "fault": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, "node_modules/micromark-extension-gfm": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/micromark-extension-gfm/-/micromark-extension-gfm-3.0.0.tgz", @@ -11356,6 +12782,122 @@ "url": "https://opencollective.com/unified" } }, + "node_modules/micromark-extension-highlight-mark": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/micromark-extension-highlight-mark/-/micromark-extension-highlight-mark-1.2.0.tgz", + "integrity": "sha512-huGtbd/9kQsMk8u7nrVMaS5qH/47yDG6ZADggo5Owz5JoY8wdfQjfuy118/QiYNCvdFuFDbzT0A7K7Hp2cBsXA==", + "license": "MIT", + "dependencies": { + "micromark-util-chunked": "^2.0.0", + "micromark-util-classify-character": "^2.0.0", + "micromark-util-resolve-all": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0", + "uvu": "^0.5.6" + } + }, + "node_modules/micromark-extension-mdx-expression": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/micromark-extension-mdx-expression/-/micromark-extension-mdx-expression-3.0.1.tgz", + "integrity": "sha512-dD/ADLJ1AeMvSAKBwO22zG22N4ybhe7kFIZ3LsDI0GlsNr2A3KYxb0LdC1u5rj4Nw+CHKY0RVdnHX8vj8ejm4Q==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.0", + "devlop": "^1.0.0", + "micromark-factory-mdx-expression": "^2.0.0", + "micromark-factory-space": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-events-to-acorn": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-extension-mdx-jsx": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/micromark-extension-mdx-jsx/-/micromark-extension-mdx-jsx-3.0.2.tgz", + "integrity": "sha512-e5+q1DjMh62LZAJOnDraSSbDMvGJ8x3cbjygy2qFEi7HCeUT4BDKCvMozPozcD6WmOt6sVvYDNBKhFSz3kjOVQ==", + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.0", + "devlop": "^1.0.0", + "estree-util-is-identifier-name": "^3.0.0", + "micromark-factory-mdx-expression": "^2.0.0", + "micromark-factory-space": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-events-to-acorn": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0", + "vfile-message": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/micromark-extension-mdx-md": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/micromark-extension-mdx-md/-/micromark-extension-mdx-md-2.0.0.tgz", + "integrity": "sha512-EpAiszsB3blw4Rpba7xTOUptcFeBFi+6PY8VnJ2hhimH+vCQDirWgsMpz7w1XcZE7LVrSAUGb9VJpG9ghlYvYQ==", + "license": "MIT", + "dependencies": { + "micromark-util-types": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/micromark-extension-mdxjs": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/micromark-extension-mdxjs/-/micromark-extension-mdxjs-3.0.0.tgz", + "integrity": "sha512-A873fJfhnJ2siZyUrJ31l34Uqwy4xIFmvPY1oj+Ean5PHcPBYzEsvqvWGaWcfEIr11O5Dlw3p2y0tZWpKHDejQ==", + "license": "MIT", + "dependencies": { + "acorn": "^8.0.0", + "acorn-jsx": "^5.0.0", + "micromark-extension-mdx-expression": "^3.0.0", + "micromark-extension-mdx-jsx": "^3.0.0", + "micromark-extension-mdx-md": "^2.0.0", + "micromark-extension-mdxjs-esm": "^3.0.0", + "micromark-util-combine-extensions": "^2.0.0", + "micromark-util-types": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/micromark-extension-mdxjs-esm": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/micromark-extension-mdxjs-esm/-/micromark-extension-mdxjs-esm-3.0.0.tgz", + "integrity": "sha512-DJFl4ZqkErRpq/dAPyeWp15tGrcrrJho1hKK5uBS70BCtfrIFg81sqcTVu3Ta+KD1Tk5vAtBNElWxtAa+m8K9A==", + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.0", + "devlop": "^1.0.0", + "micromark-core-commonmark": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-events-to-acorn": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0", + "unist-util-position-from-estree": "^2.0.0", + "vfile-message": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, "node_modules/micromark-factory-destination": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/micromark-factory-destination/-/micromark-factory-destination-2.0.1.tgz", @@ -11399,6 +12941,33 @@ "micromark-util-types": "^2.0.0" } }, + "node_modules/micromark-factory-mdx-expression": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/micromark-factory-mdx-expression/-/micromark-factory-mdx-expression-2.0.3.tgz", + "integrity": "sha512-kQnEtA3vzucU2BkrIa8/VaSAsP+EJ3CKOvhMuJgOEGg9KDC6OAY6nSnNDVRiVNRqj7Y4SlSzcStaH/5jge8JdQ==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.0", + "devlop": "^1.0.0", + "micromark-factory-space": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-events-to-acorn": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0", + "unist-util-position-from-estree": "^2.0.0", + "vfile-message": "^4.0.0" + } + }, "node_modules/micromark-factory-space": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/micromark-factory-space/-/micromark-factory-space-2.0.1.tgz", @@ -11600,6 +13169,31 @@ ], "license": "MIT" }, + "node_modules/micromark-util-events-to-acorn": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/micromark-util-events-to-acorn/-/micromark-util-events-to-acorn-2.0.3.tgz", + "integrity": "sha512-jmsiEIiZ1n7X1Rr5k8wVExBQCg5jy4UXVADItHmNk1zkwEVhBuIUKRu3fqv+hs4nxLISi2DQGlqIOGiFxgbfHg==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.0", + "@types/unist": "^3.0.0", + "devlop": "^1.0.0", + "estree-util-visit": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0", + "vfile-message": "^4.0.0" + } + }, "node_modules/micromark-util-html-tag-name": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/micromark-util-html-tag-name/-/micromark-util-html-tag-name-2.0.1.tgz", @@ -11883,6 +13477,15 @@ "main-event": "^1.0.0" } }, + "node_modules/mri": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/mri/-/mri-1.2.0.tgz", + "integrity": "sha512-tzzskb3bG8LvYGFF/mDTpq3jpI6Q9wc3LEmBaghu+DdCssd1FakN7Bc0hVNmEyGq1bq3RgfkCb3cmQLpNPOroA==", + "license": "MIT", + "engines": { + "node": ">=4" + } + }, "node_modules/ms": { "version": "2.1.3", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", @@ -11947,6 +13550,12 @@ "node": ">= 0.6" } }, + "node_modules/next-tick": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/next-tick/-/next-tick-1.1.0.tgz", + "integrity": "sha512-CXdUiJembsNjuToQvxayPZF9Vqht7hewsvy2sOWafLvi2awflj9mOC6bHIg50orX8IJvWKY9wYQ/zB2kogPslQ==", + "license": "ISC" + }, "node_modules/no-case": { "version": "3.0.4", "resolved": "https://registry.npmjs.org/no-case/-/no-case-3.0.4.tgz", @@ -12244,6 +13853,12 @@ "integrity": "sha512-TvAWxi0nDe1j/rtMcWcIj94+Ffe6n7zhow33h40SKxmsmozs6dz/e+EajymfoFcHd7sxNn8yHM8839uixMOV6g==", "license": "MIT" }, + "node_modules/outvariant": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/outvariant/-/outvariant-1.4.0.tgz", + "integrity": "sha512-AlWY719RF02ujitly7Kk/0QlV+pXGFDHrHf9O2OKqyqgBieaPOIeuSkL8sRK6j2WK+/ZAURq2kZsY0d8JapUiw==", + "license": "MIT" + }, "node_modules/p-defer": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/p-defer/-/p-defer-4.0.1.tgz", @@ -12435,12 +14050,38 @@ "renderkid": "^3.0.0" } }, + "node_modules/prismjs": { + "version": "1.30.0", + "resolved": "https://registry.npmjs.org/prismjs/-/prismjs-1.30.0.tgz", + "integrity": "sha512-DEvV2ZF2r2/63V+tK8hQvrR2ZGn10srHbXviTlcv7Kpzw8jWiNTqbVgjO3IY8RxrrOUF8VPMQQFysYYYv0YZxw==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, "node_modules/progress-events": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/progress-events/-/progress-events-1.0.1.tgz", "integrity": "sha512-MOzLIwhpt64KIVN64h1MwdKWiyKFNc/S6BoYKPIVUHFg0/eIEyBulhWCgn678v/4c0ri3FdGuzXymNCv02MUIw==", "license": "Apache-2.0 OR MIT" }, + "node_modules/prop-types": { + "version": "15.8.1", + "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz", + "integrity": "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==", + "license": "MIT", + "dependencies": { + "loose-envify": "^1.4.0", + "object-assign": "^4.1.1", + "react-is": "^16.13.1" + } + }, + "node_modules/prop-types/node_modules/react-is": { + "version": "16.13.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", + "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==", + "license": "MIT" + }, "node_modules/proper-lockfile": { "version": "4.1.2", "resolved": "https://registry.npmjs.org/proper-lockfile/-/proper-lockfile-4.1.2.tgz", @@ -12985,6 +14626,15 @@ "react-dom": "^16.x || ^17.x || ^18.x" } }, + "node_modules/react-devtools-inline": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/react-devtools-inline/-/react-devtools-inline-4.4.0.tgz", + "integrity": "sha512-ES0GolSrKO8wsKbsEkVeiR/ZAaHQTY4zDh1UW8DImVmm8oaGLl3ijJDvSGe+qDRKPZdPRnDtWWnSvvrgxXdThQ==", + "license": "MIT", + "dependencies": { + "es6-symbol": "^3" + } + }, "node_modules/react-dom": { "version": "18.3.1", "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.3.1.tgz", @@ -12998,6 +14648,44 @@ "react": "^18.3.1" } }, + "node_modules/react-error-boundary": { + "version": "3.1.4", + "resolved": "https://registry.npmjs.org/react-error-boundary/-/react-error-boundary-3.1.4.tgz", + "integrity": "sha512-uM9uPzZJTF6wRQORmSrvOIgt4lJ9MC1sNgEOj2XGsDTRE4kmpWxg7ENK9EWNKJRMAOY9z0MuF4yIfl6gp4sotA==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.12.5" + }, + "engines": { + "node": ">=10", + "npm": ">=6" + }, + "peerDependencies": { + "react": ">=16.13.1" + } + }, + "node_modules/react-hook-form": { + "version": "7.67.0", + "resolved": "https://registry.npmjs.org/react-hook-form/-/react-hook-form-7.67.0.tgz", + "integrity": "sha512-E55EOwKJHHIT/I6J9DmQbCWToAYSw9nN5R57MZw9rMtjh+YQreMDxRLfdjfxQbiJ3/qbg3Z02wGzBX4M+5fMtQ==", + "license": "MIT", + "engines": { + "node": ">=18.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/react-hook-form" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17 || ^18 || ^19" + } + }, + "node_modules/react-is": { + "version": "17.0.2", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz", + "integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==", + "license": "MIT" + }, "node_modules/react-markdown": { "version": "10.1.0", "resolved": "https://registry.npmjs.org/react-markdown/-/react-markdown-10.1.0.tgz", @@ -13728,6 +15416,18 @@ "tslib": "^2.1.0" } }, + "node_modules/sade": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/sade/-/sade-1.8.1.tgz", + "integrity": "sha512-xal3CZX1Xlo/k4ApwCFrHVACi9fBqJ7V+mwhBsuf/1IOKbBy098Fex+Wa/5QMubw09pSZ/u8EY8PWgevJsXp1A==", + "license": "MIT", + "dependencies": { + "mri": "^1.1.0" + }, + "engines": { + "node": ">=6" + } + }, "node_modules/safe-buffer": { "version": "5.2.1", "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", @@ -14106,6 +15806,18 @@ "node": ">=0.1.14" } }, + "node_modules/static-browser-server": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/static-browser-server/-/static-browser-server-1.0.3.tgz", + "integrity": "sha512-ZUyfgGDdFRbZGGJQ1YhiM930Yczz5VlbJObrQLlk24+qNHVQx4OlLcYswEUo3bIyNAbQUIUR9Yr5/Hqjzqb4zA==", + "license": "Apache-2.0", + "dependencies": { + "@open-draft/deferred-promise": "^2.1.0", + "dotenv": "^16.0.3", + "mime-db": "^1.52.0", + "outvariant": "^1.3.0" + } + }, "node_modules/statuses": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", @@ -14126,6 +15838,12 @@ "npm": ">=6" } }, + "node_modules/strict-event-emitter": { + "version": "0.4.6", + "resolved": "https://registry.npmjs.org/strict-event-emitter/-/strict-event-emitter-0.4.6.tgz", + "integrity": "sha512-12KWeb+wixJohmnwNFerbyiBrAlq5qJLwIt38etRtKtmmHyDSoGlIqFE9wx+4IwG0aDjI7GV8tc8ZccjWZZtTg==", + "license": "MIT" + }, "node_modules/string_decoder": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", @@ -14185,6 +15903,12 @@ "node": ">=0.10.0" } }, + "node_modules/style-mod": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/style-mod/-/style-mod-4.1.3.tgz", + "integrity": "sha512-i/n8VsZydrugj3Iuzll8+x/00GH2vnYsk1eomD8QiRrSAeW6ItbCQDtfXCeJHd0iwiNagqjQkvpvREEPtW3IoQ==", + "license": "MIT" + }, "node_modules/style-to-js": { "version": "1.1.21", "resolved": "https://registry.npmjs.org/style-to-js/-/style-to-js-1.1.21.tgz", @@ -14279,6 +16003,12 @@ "integrity": "sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==", "license": "MIT" }, + "node_modules/tabbable": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/tabbable/-/tabbable-6.3.0.tgz", + "integrity": "sha512-EIHvdY5bPLuWForiR/AN2Bxngzpuwn1is4asboytXtpTgsArc+WmSJKVLlhdh71u7jFcryDqB2A8lQvj78MkyQ==", + "license": "MIT" + }, "node_modules/tapable": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.3.0.tgz", @@ -14506,6 +16236,12 @@ "integrity": "sha512-6rt+RN7aOi1nGMyC4Xa5DdYiukl2UWCbcJft7YhxReBGQD7OAM8Pbxw6YMo4r2diNEA8FEmu32YOn9rhaiE5yw==", "license": "Unlicense" }, + "node_modules/type": { + "version": "2.7.3", + "resolved": "https://registry.npmjs.org/type/-/type-2.7.3.tgz", + "integrity": "sha512-8j+1QmAbPvLZow5Qpi6NCaN8FB60p/6x8/vfNqOk/hC+HuvFZhL4+WfekuhQLiqFZXOgQdrs3B+XxEmCc6b3FQ==", + "license": "ISC" + }, "node_modules/type-is": { "version": "1.6.18", "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", @@ -14631,6 +16367,15 @@ "pathe": "^2.0.3" } }, + "node_modules/unidiff": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/unidiff/-/unidiff-1.0.4.tgz", + "integrity": "sha512-ynU0vsAXw0ir8roa+xPCUHmnJ5goc5BTM2Kuc3IJd8UwgaeRs7VSD5+eeaQL+xp1JtB92hu/Zy/Lgy7RZcr1pQ==", + "license": "MIT", + "dependencies": { + "diff": "^5.1.0" + } + }, "node_modules/unified": { "version": "11.0.5", "resolved": "https://registry.npmjs.org/unified/-/unified-11.0.5.tgz", @@ -14687,6 +16432,19 @@ "url": "https://opencollective.com/unified" } }, + "node_modules/unist-util-position-from-estree": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/unist-util-position-from-estree/-/unist-util-position-from-estree-2.0.0.tgz", + "integrity": "sha512-KaFVRjoqLyF6YXCbVLNad/eS4+OfPQQn2yOd7zF/h5T/CSL2v8NpN6a5TPvtbXthAGw5nG+PuTtq+DdIZr+cRQ==", + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, "node_modules/unist-util-stringify-position": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/unist-util-stringify-position/-/unist-util-stringify-position-4.0.0.tgz", @@ -14914,6 +16672,24 @@ "uuid": "dist/bin/uuid" } }, + "node_modules/uvu": { + "version": "0.5.6", + "resolved": "https://registry.npmjs.org/uvu/-/uvu-0.5.6.tgz", + "integrity": "sha512-+g8ENReyr8YsOc6fv/NVJs2vFdHBnBNdfE49rshrTzDWOlUx4Gq7KOS2GD8eqhy2j+Ejq29+SbKH8yjkAqXqoA==", + "license": "MIT", + "dependencies": { + "dequal": "^2.0.0", + "diff": "^5.0.0", + "kleur": "^4.0.3", + "sade": "^1.7.3" + }, + "bin": { + "uvu": "bin.js" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/varint": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/varint/-/varint-6.0.0.tgz", diff --git a/package.json b/package.json index fe21e22..a85ad2a 100644 --- a/package.json +++ b/package.json @@ -37,6 +37,7 @@ "@chengsokdara/use-whisper": "^0.2.0", "@daily-co/daily-js": "^0.60.0", "@daily-co/daily-react": "^0.20.0", + "@mdxeditor/editor": "^3.51.0", "@tldraw/assets": "^3.15.4", "@tldraw/tldraw": "^3.15.4", "@tldraw/tlschema": "^3.15.4", @@ -70,11 +71,11 @@ "react-markdown": "^10.1.0", "react-router-dom": "^7.0.2", "recoil": "^0.7.7", + "sharp": "^0.33.5", "tldraw": "^3.15.4", "use-whisper": "^0.0.1", "webcola": "^3.4.0", - "webnative": "^0.36.3", - "sharp": "^0.33.5" + "webnative": "^0.36.3" }, "devDependencies": { "@cloudflare/types": "^6.0.0", diff --git a/runpod-configs/extra_model_paths.yaml b/runpod-configs/extra_model_paths.yaml new file mode 100644 index 0000000..aefb948 --- /dev/null +++ b/runpod-configs/extra_model_paths.yaml @@ -0,0 +1,63 @@ +# ComfyUI Model Paths Configuration +# Updated to include /runpod-volume/ paths for all model types +# This allows models to be loaded from the network volume for faster cold starts + +comfyui: + base_path: /ComfyUI/ + is_default: true + + # Checkpoints - check network volume first, then local + checkpoints: | + /runpod-volume/models/checkpoints/ + models/checkpoints/ + + # CLIP models + clip: | + /runpod-volume/models/clip/ + models/clip/ + + # CLIP Vision models (e.g., clip_vision_h.safetensors) + clip_vision: | + /runpod-volume/models/clip_vision/ + models/clip_vision/ + + # Config files + configs: models/configs/ + + # ControlNet models + controlnet: | + /runpod-volume/models/controlnet/ + models/controlnet/ + + # Diffusion models (Wan2.2 model files) + diffusion_models: | + /runpod-volume/models/diffusion_models/ + /runpod-volume/models/ + models/diffusion_models/ + models/unet/ + + # Text embeddings + embeddings: | + /runpod-volume/models/embeddings/ + models/embeddings/ + + # LoRA models + loras: | + /runpod-volume/loras/ + /runpod-volume/models/loras/ + models/loras/ + + # Text encoders (e.g., umt5-xxl-enc-bf16.safetensors) + text_encoders: | + /runpod-volume/models/text_encoders/ + models/text_encoders/ + + # Upscale models + upscale_models: | + /runpod-volume/models/upscale_models/ + models/upscale_models/ + + # VAE models (e.g., Wan2_1_VAE_bf16.safetensors) + vae: | + /runpod-volume/models/vae/ + models/vae/ diff --git a/runpod-configs/setup_network_volume.sh b/runpod-configs/setup_network_volume.sh new file mode 100644 index 0000000..524452a --- /dev/null +++ b/runpod-configs/setup_network_volume.sh @@ -0,0 +1,143 @@ +#!/bin/bash +# Script to set up the RunPod network volume with Wan2.2 models +# Run this once on a GPU pod with the network volume attached + +echo "=== Setting up RunPod Network Volume for Wan2.2 ===" + +# Create directory structure +echo "Creating directory structure..." +mkdir -p /runpod-volume/models/diffusion_models +mkdir -p /runpod-volume/models/vae +mkdir -p /runpod-volume/models/text_encoders +mkdir -p /runpod-volume/models/clip_vision +mkdir -p /runpod-volume/loras + +# Check current disk usage +echo "Current network volume usage:" +df -h /runpod-volume + +# List what's already on the volume +echo "" +echo "Current contents of /runpod-volume:" +ls -la /runpod-volume/ + +echo "" +echo "Current contents of /runpod-volume/models/ (if exists):" +ls -la /runpod-volume/models/ 2>/dev/null || echo "(empty or doesn't exist)" + +# Check if models exist in the Docker image +echo "" +echo "Models in Docker image /ComfyUI/models/diffusion_models/:" +ls -la /ComfyUI/models/diffusion_models/ 2>/dev/null || echo "(not found)" + +echo "" +echo "Models in Docker image /ComfyUI/models/vae/:" +ls -la /ComfyUI/models/vae/ 2>/dev/null || echo "(not found)" + +echo "" +echo "Models in Docker image /ComfyUI/models/text_encoders/:" +ls -la /ComfyUI/models/text_encoders/ 2>/dev/null || echo "(not found)" + +echo "" +echo "Models in Docker image /ComfyUI/models/clip_vision/:" +ls -la /ComfyUI/models/clip_vision/ 2>/dev/null || echo "(not found)" + +echo "" +echo "Models in Docker image /ComfyUI/models/loras/:" +ls -la /ComfyUI/models/loras/ 2>/dev/null || echo "(not found)" + +# Copy models to network volume (if not already there) +echo "" +echo "=== Copying models to network volume ===" + +# Diffusion models +if [ -d "/ComfyUI/models/diffusion_models" ]; then + echo "Copying diffusion models..." + cp -vn /ComfyUI/models/diffusion_models/*.safetensors /runpod-volume/models/diffusion_models/ 2>/dev/null || true +fi + +# VAE models +if [ -d "/ComfyUI/models/vae" ]; then + echo "Copying VAE models..." + cp -vn /ComfyUI/models/vae/*.safetensors /runpod-volume/models/vae/ 2>/dev/null || true +fi + +# Text encoders +if [ -d "/ComfyUI/models/text_encoders" ]; then + echo "Copying text encoder models..." + cp -vn /ComfyUI/models/text_encoders/*.safetensors /runpod-volume/models/text_encoders/ 2>/dev/null || true +fi + +# CLIP vision +if [ -d "/ComfyUI/models/clip_vision" ]; then + echo "Copying CLIP vision models..." + cp -vn /ComfyUI/models/clip_vision/*.safetensors /runpod-volume/models/clip_vision/ 2>/dev/null || true +fi + +# LoRAs +if [ -d "/ComfyUI/models/loras" ]; then + echo "Copying LoRA models..." + cp -vn /ComfyUI/models/loras/*.safetensors /runpod-volume/loras/ 2>/dev/null || true +fi + +# Copy extra_model_paths.yaml to volume +echo "" +echo "Copying extra_model_paths.yaml to network volume..." +cat > /runpod-volume/extra_model_paths.yaml << 'EOF' +# ComfyUI Model Paths Configuration - Network Volume Priority +comfyui: + base_path: /ComfyUI/ + is_default: true + checkpoints: | + /runpod-volume/models/checkpoints/ + models/checkpoints/ + clip: | + /runpod-volume/models/clip/ + models/clip/ + clip_vision: | + /runpod-volume/models/clip_vision/ + models/clip_vision/ + configs: models/configs/ + controlnet: | + /runpod-volume/models/controlnet/ + models/controlnet/ + diffusion_models: | + /runpod-volume/models/diffusion_models/ + /runpod-volume/models/ + models/diffusion_models/ + models/unet/ + embeddings: | + /runpod-volume/models/embeddings/ + models/embeddings/ + loras: | + /runpod-volume/loras/ + /runpod-volume/models/loras/ + models/loras/ + text_encoders: | + /runpod-volume/models/text_encoders/ + models/text_encoders/ + upscale_models: | + /runpod-volume/models/upscale_models/ + models/upscale_models/ + vae: | + /runpod-volume/models/vae/ + models/vae/ +EOF + +echo "" +echo "=== Final network volume contents ===" +echo "" +echo "/runpod-volume/models/:" +du -sh /runpod-volume/models/*/ 2>/dev/null || echo "(empty)" +echo "" +echo "/runpod-volume/loras/:" +ls -la /runpod-volume/loras/ 2>/dev/null || echo "(empty)" + +echo "" +echo "Total network volume usage:" +du -sh /runpod-volume/ + +echo "" +echo "=== Setup complete! ===" +echo "Models have been copied to the network volume." +echo "On subsequent cold starts, models will load from /runpod-volume/ (faster)." diff --git a/src/automerge/MinimalSanitization.ts b/src/automerge/MinimalSanitization.ts index c2862c8..ece4131 100644 --- a/src/automerge/MinimalSanitization.ts +++ b/src/automerge/MinimalSanitization.ts @@ -20,11 +20,17 @@ function minimalSanitizeRecord(record: any): any { if (typeof sanitized.isLocked !== 'boolean') sanitized.isLocked = false if (typeof sanitized.opacity !== 'number') sanitized.opacity = 1 if (!sanitized.meta || typeof sanitized.meta !== 'object') sanitized.meta = {} - // CRITICAL: IndexKey must follow tldraw's fractional indexing format - // Valid format: starts with 'a' followed by digits, optionally followed by uppercase letters - // Examples: "a1", "a2", "a10", "a1V" (fractional between a1 and a2) - // Invalid: "c1", "b1", "z999" (must start with 'a') - if (!sanitized.index || typeof sanitized.index !== 'string' || !/^a\d+[A-Z]*$/.test(sanitized.index)) { + // NOTE: Index assignment is handled by assignSequentialIndices() during format conversion + // Here we only ensure index exists with a valid format, not strictly validate + // This preserves layer order that was established during conversion + // Valid formats: a1, a2, a10, a1V, a1Lz, etc. (fractional indexing) + if (!sanitized.index || typeof sanitized.index !== 'string' || sanitized.index.length === 0) { + // Only assign default if truly missing + sanitized.index = 'a1' + } else if (!/^a\d/.test(sanitized.index) && !/^Z[a-z]/i.test(sanitized.index)) { + // Accept any index starting with 'a' + digit, or 'Z' prefix + // Only reset clearly invalid formats + console.warn(`⚠️ MinimalSanitization: Invalid index format "${sanitized.index}" for shape ${sanitized.id}`) sanitized.index = 'a1' } if (!sanitized.parentId) sanitized.parentId = 'page:page' diff --git a/src/automerge/useAutomergeSyncRepo.ts b/src/automerge/useAutomergeSyncRepo.ts index 4109c83..3b393ef 100644 --- a/src/automerge/useAutomergeSyncRepo.ts +++ b/src/automerge/useAutomergeSyncRepo.ts @@ -3,7 +3,7 @@ import { TLStoreSnapshot, InstancePresenceRecordType, getIndexAbove, IndexKey } import { CloudflareNetworkAdapter } from "./CloudflareAdapter" import { useAutomergeStoreV2, useAutomergePresence } from "./useAutomergeStoreV2" import { TLStoreWithStatus } from "@tldraw/tldraw" -import { Repo, parseAutomergeUrl, stringifyAutomergeUrl, AutomergeUrl } from "@automerge/automerge-repo" +import { Repo, parseAutomergeUrl, stringifyAutomergeUrl, AutomergeUrl, DocumentId } from "@automerge/automerge-repo" import { DocHandle } from "@automerge/automerge-repo" import { IndexedDBStorageAdapter } from "@automerge/automerge-repo-storage-indexeddb" import { getDocumentId, saveDocumentId } from "./documentIdMapping" @@ -175,11 +175,31 @@ export function useAutomergeSync(config: AutomergeSyncConfig): TLStoreWithStatus if (record.type) recordHash += record.type // For shapes, include x, y, w, h for position/size changes + // Also include text content for shapes that have it (Markdown, ObsNote, etc.) if (record.typeName === 'shape') { if (typeof record.x === 'number') recordHash += `x${record.x}` if (typeof record.y === 'number') recordHash += `y${record.y}` if (typeof record.props?.w === 'number') recordHash += `w${record.props.w}` if (typeof record.props?.h === 'number') recordHash += `h${record.props.h}` + // CRITICAL: Include text content in hash for Markdown and similar shapes + // This ensures text changes trigger R2 persistence + if (typeof record.props?.text === 'string' && record.props.text.length > 0) { + // Include text length and a sample of content for change detection + recordHash += `t${record.props.text.length}` + // Include first 100 chars and last 50 chars to detect changes anywhere in the text + recordHash += record.props.text.substring(0, 100) + if (record.props.text.length > 150) { + recordHash += record.props.text.substring(record.props.text.length - 50) + } + } + // Also include content for ObsNote shapes + if (typeof record.props?.content === 'string' && record.props.content.length > 0) { + recordHash += `c${record.props.content.length}` + recordHash += record.props.content.substring(0, 100) + if (record.props.content.length > 150) { + recordHash += record.props.content.substring(record.props.content.length - 50) + } + } } // Simple hash of the record string @@ -370,9 +390,23 @@ export function useAutomergeSync(config: AutomergeSyncConfig): TLStoreWithStatus if (storedDocumentId) { console.log(`Found stored document ID for room ${roomId}: ${storedDocumentId}`) try { - // Try to find the existing document in the repo (loads from IndexedDB) - // repo.find() returns a Promise - const foundHandle = await repo.find(storedDocumentId as AutomergeUrl) + // Parse the URL to get the DocumentId + const parsed = parseAutomergeUrl(storedDocumentId as AutomergeUrl) + const docId = parsed.documentId + + // Check if the document is already loaded in the repo's handles cache + // This prevents "Cannot create a reference to an existing document object" error + const existingHandle = repo.handles[docId] as DocHandle | undefined + + let foundHandle: DocHandle + if (existingHandle) { + console.log(`Document ${docId} already in repo cache, reusing handle`) + foundHandle = existingHandle + } else { + // Try to find the existing document in the repo (loads from IndexedDB) + // repo.find() returns a Promise + foundHandle = await repo.find(storedDocumentId as AutomergeUrl) + } await foundHandle.whenReady() handle = foundHandle diff --git a/src/lib/auth/cryptidEmailService.ts b/src/lib/auth/cryptidEmailService.ts new file mode 100644 index 0000000..00a1709 --- /dev/null +++ b/src/lib/auth/cryptidEmailService.ts @@ -0,0 +1,427 @@ +/** + * CryptID Email Service + * Handles communication with the backend for email linking and device verification + */ + +import * as crypto from './crypto'; + +// Get the worker API URL based on environment +function getApiUrl(): string { + // In development, use the local worker + if (window.location.hostname === 'localhost' || window.location.hostname === '127.0.0.1') { + return 'http://localhost:5172'; + } + // In production, use the deployed worker + return 'https://jeffemmett-canvas.jeffemmett.workers.dev'; +} + +export interface LinkEmailResult { + success: boolean; + message?: string; + emailVerified?: boolean; + emailSent?: boolean; + error?: string; +} + +export interface DeviceLinkResult { + success: boolean; + message?: string; + cryptidUsername?: string; + alreadyLinked?: boolean; + emailSent?: boolean; + error?: string; +} + +export interface LookupResult { + found: boolean; + cryptidUsername?: string; + email?: string; + emailVerified?: boolean; + deviceName?: string; +} + +export interface Device { + id: string; + deviceName: string; + userAgent: string | null; + createdAt: string; + lastUsed: string | null; + isCurrentDevice: boolean; +} + +/** + * Link an email to the current CryptID account + * Called from Device A (existing device with account) + */ +export async function linkEmailToAccount( + email: string, + cryptidUsername: string, + deviceName?: string +): Promise { + try { + // Get the public key for this user + const publicKey = crypto.getPublicKey(cryptidUsername); + if (!publicKey) { + return { + success: false, + error: 'No public key found for this account' + }; + } + + const response = await fetch(`${getApiUrl()}/auth/link-email`, { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify({ + email, + cryptidUsername, + publicKey, + deviceName: deviceName || getDeviceName() + }), + }); + + const data = await response.json() as LinkEmailResult & { error?: string }; + + if (!response.ok) { + return { + success: false, + error: data.error || 'Failed to link email' + }; + } + + return data; + } catch (error) { + console.error('Link email error:', error); + return { + success: false, + error: String(error) + }; + } +} + +/** + * Check the status of email verification + */ +export async function checkEmailStatus(cryptidUsername: string): Promise { + try { + const publicKey = crypto.getPublicKey(cryptidUsername); + if (!publicKey) { + return { found: false }; + } + + const response = await fetch(`${getApiUrl()}/auth/lookup`, { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify({ publicKey }), + }); + + const data = await response.json() as LookupResult; + return data; + } catch (error) { + console.error('Check email status error:', error); + return { found: false }; + } +} + +/** + * Request to link a new device using email + * Called from Device B (new device) + * + * Flow: + * 1. Generate new keypair on Device B + * 2. Send email + publicKey to server + * 3. Server sends verification email + * 4. User clicks link in email (on Device B) + * 5. Device B's key is linked to the account + */ +export async function requestDeviceLink( + email: string, + deviceName?: string +): Promise { + try { + // Generate a new keypair for this device + const keyPair = await crypto.generateKeyPair(); + if (!keyPair) { + return { + success: false, + error: 'Failed to generate cryptographic keys' + }; + } + + // Export the public key + const publicKey = await crypto.exportPublicKey(keyPair.publicKey); + if (!publicKey) { + return { + success: false, + error: 'Failed to export public key' + }; + } + + const response = await fetch(`${getApiUrl()}/auth/request-device-link`, { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify({ + email, + publicKey, + deviceName: deviceName || getDeviceName() + }), + }); + + const data = await response.json() as DeviceLinkResult & { error?: string }; + + if (!response.ok) { + return { + success: false, + error: data.error || 'Failed to request device link' + }; + } + + // If successful, temporarily store the keypair for later + // The user will need to click the email link to complete the process + if (data.success && !data.alreadyLinked) { + // Store pending link data + sessionStorage.setItem('pendingDeviceLink', JSON.stringify({ + email, + publicKey, + cryptidUsername: data.cryptidUsername, + timestamp: Date.now() + })); + } + + return { + ...data, + publicKey + }; + } catch (error) { + console.error('Request device link error:', error); + return { + success: false, + error: String(error) + }; + } +} + +/** + * Complete the device link after email verification + * Called when user clicks the verification link and lands back on the app + */ +export async function completeDeviceLink(token: string): Promise { + try { + const response = await fetch(`${getApiUrl()}/auth/link-device/${token}`, { + method: 'GET', + headers: { + 'Content-Type': 'application/json', + }, + }); + + const data = await response.json() as DeviceLinkResult & { email?: string; error?: string }; + + if (!response.ok) { + return { + success: false, + error: data.error || 'Failed to complete device link' + }; + } + + // Use the typed data + const result = data; + + // If successful, the pending device link data should match + const pendingLink = sessionStorage.getItem('pendingDeviceLink'); + if (pendingLink && result.success) { + const pending = JSON.parse(pendingLink); + + // Register this device locally with the CryptID username from the server + if (result.cryptidUsername) { + // Store the public key locally for this username + crypto.storePublicKey(result.cryptidUsername, pending.publicKey); + crypto.addRegisteredUser(result.cryptidUsername); + + // Store auth data to match the existing flow + localStorage.setItem(`${result.cryptidUsername}_authData`, JSON.stringify({ + challenge: `device-linked:${Date.now()}`, + signature: 'device-link-verified', + timestamp: Date.now(), + email: result.email + })); + } + + // Clear pending link data + sessionStorage.removeItem('pendingDeviceLink'); + } + + return result; + } catch (error) { + console.error('Complete device link error:', error); + return { + success: false, + error: String(error) + }; + } +} + +/** + * Verify email via token (for initial email verification) + */ +export async function verifyEmail(token: string): Promise<{ success: boolean; email?: string; error?: string }> { + try { + const response = await fetch(`${getApiUrl()}/auth/verify-email/${token}`, { + method: 'GET', + headers: { + 'Content-Type': 'application/json', + }, + }); + + const data = await response.json() as { success: boolean; email?: string; error?: string }; + + if (!response.ok) { + return { + success: false, + error: data.error || 'Failed to verify email' + }; + } + + return data; + } catch (error) { + console.error('Verify email error:', error); + return { + success: false, + error: String(error) + }; + } +} + +/** + * Get all devices linked to this account + */ +export async function getLinkedDevices(cryptidUsername: string): Promise { + try { + const publicKey = crypto.getPublicKey(cryptidUsername); + if (!publicKey) { + return []; + } + + const response = await fetch(`${getApiUrl()}/auth/devices`, { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify({ publicKey }), + }); + + const data = await response.json() as { devices?: Device[] }; + return data.devices || []; + } catch (error) { + console.error('Get linked devices error:', error); + return []; + } +} + +/** + * Revoke a device from the account + */ +export async function revokeDevice( + cryptidUsername: string, + deviceId: string +): Promise<{ success: boolean; error?: string }> { + try { + const publicKey = crypto.getPublicKey(cryptidUsername); + if (!publicKey) { + return { + success: false, + error: 'No public key found' + }; + } + + const response = await fetch(`${getApiUrl()}/auth/devices/${deviceId}`, { + method: 'DELETE', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify({ publicKey }), + }); + + const data = await response.json() as { success: boolean; error?: string }; + + if (!response.ok) { + return { + success: false, + error: data.error || 'Failed to revoke device' + }; + } + + return data; + } catch (error) { + console.error('Revoke device error:', error); + return { + success: false, + error: String(error) + }; + } +} + +/** + * Get a friendly device name based on user agent + */ +function getDeviceName(): string { + const ua = navigator.userAgent; + + // Detect OS + let os = 'Unknown'; + if (ua.includes('Windows')) os = 'Windows'; + else if (ua.includes('Mac')) os = 'macOS'; + else if (ua.includes('Linux')) os = 'Linux'; + else if (ua.includes('Android')) os = 'Android'; + else if (ua.includes('iPhone') || ua.includes('iPad')) os = 'iOS'; + + // Detect browser + let browser = 'Browser'; + if (ua.includes('Chrome') && !ua.includes('Edg')) browser = 'Chrome'; + else if (ua.includes('Firefox')) browser = 'Firefox'; + else if (ua.includes('Safari') && !ua.includes('Chrome')) browser = 'Safari'; + else if (ua.includes('Edg')) browser = 'Edge'; + + return `${browser} on ${os}`; +} + +/** + * Check if there's a pending device link to complete + */ +export function hasPendingDeviceLink(): boolean { + const pending = sessionStorage.getItem('pendingDeviceLink'); + if (!pending) return false; + + try { + const data = JSON.parse(pending); + // Check if it's less than 1 hour old + return Date.now() - data.timestamp < 60 * 60 * 1000; + } catch { + return false; + } +} + +/** + * Get pending device link info + */ +export function getPendingDeviceLink(): { email: string; cryptidUsername: string } | null { + const pending = sessionStorage.getItem('pendingDeviceLink'); + if (!pending) return null; + + try { + const data = JSON.parse(pending); + if (Date.now() - data.timestamp < 60 * 60 * 1000) { + return { + email: data.email, + cryptidUsername: data.cryptidUsername + }; + } + return null; + } catch { + return null; + } +} diff --git a/src/lib/canvasAI.ts b/src/lib/canvasAI.ts index a0af7cd..2b013b4 100644 --- a/src/lib/canvasAI.ts +++ b/src/lib/canvasAI.ts @@ -1,17 +1,35 @@ /** - * Canvas AI Assistant + * Canvas AI Assistant - The Mycelial Intelligence * Provides AI-powered queries about canvas content using semantic search - * and LLM integration for natural language understanding + * and LLM integration for natural language understanding. + * + * The Mycelial Intelligence speaks directly to users, helping them navigate + * and understand their workspace through the interconnected network of shapes. */ import { Editor, TLShape, TLShapeId } from 'tldraw' import { semanticSearch, extractShapeText, SemanticSearchResult } from './semanticSearch' import { llm } from '@/utils/llmUtils' +import { getToolSummaryForAI, suggestToolsForIntent, ToolSchema } from './toolSchema' +import { + getSelectionSummary, + getSelectionAsContext, + parseTransformIntent, + executeTransformCommand, + TransformCommand, +} from '@/utils/selectionTransforms' export interface CanvasQueryResult { answer: string relevantShapes: SemanticSearchResult[] context: string + suggestedTools: ToolSchema[] + /** If a transform command was detected and executed */ + executedTransform?: TransformCommand + /** Whether there was a selection when the query was made */ + hadSelection: boolean + /** Number of shapes that were selected */ + selectionCount: number } export interface CanvasAIConfig { @@ -55,6 +73,7 @@ export class CanvasAI { /** * Query the canvas with natural language + * Now selection-aware: includes selected shapes in context and can execute transforms */ async query( question: string, @@ -67,17 +86,50 @@ export class CanvasAI { throw new Error('Editor not connected. Call setEditor() first.') } - // Build context from canvas - const context = await this.buildQueryContext(question, mergedConfig) + // Get selection info FIRST before any other processing + const selectionSummary = getSelectionSummary(this.editor) + const hasSelection = selectionSummary.count > 0 + + // Check if this is a transform command on the selection + let executedTransform: TransformCommand | undefined + if (hasSelection) { + const { command } = parseTransformIntent(question) + if (command) { + // Execute the transform and provide immediate feedback + const success = executeTransformCommand(this.editor, command) + if (success) { + executedTransform = command + // Provide immediate feedback for transform commands + const transformMessage = this.getTransformFeedback(command, selectionSummary.count) + onToken?.(transformMessage, true) + + return { + answer: transformMessage, + relevantShapes: [], + context: '', + suggestedTools: [], + executedTransform, + hadSelection: true, + selectionCount: selectionSummary.count, + } + } + } + } + + // Build context from canvas, including selection context + const context = await this.buildQueryContext(question, mergedConfig, selectionSummary) const relevantShapes = await semanticSearch.search( question, mergedConfig.topKResults, mergedConfig.semanticSearchThreshold ) - // Build the system prompt for canvas-aware AI - const systemPrompt = this.buildSystemPrompt() - const userPrompt = this.buildUserPrompt(question, context) + // Build the system prompt for canvas-aware AI (now selection-aware) + const systemPrompt = this.buildSystemPrompt(hasSelection) + const userPrompt = this.buildUserPrompt(question, context, selectionSummary) + + // Get tool suggestions based on user intent + const suggestedTools = this.suggestTools(question, hasSelection) let answer = '' @@ -106,9 +158,43 @@ export class CanvasAI { answer, relevantShapes, context, + suggestedTools, + hadSelection: hasSelection, + selectionCount: selectionSummary.count, } } + /** + * Get human-readable feedback for transform commands + */ + private getTransformFeedback(command: TransformCommand, count: number): string { + const shapeWord = count === 1 ? 'shape' : 'shapes' + + const messages: Record = { + 'align-left': `Aligned ${count} ${shapeWord} to the left.`, + 'align-center': `Centered ${count} ${shapeWord} horizontally.`, + 'align-right': `Aligned ${count} ${shapeWord} to the right.`, + 'align-top': `Aligned ${count} ${shapeWord} to the top.`, + 'align-middle': `Centered ${count} ${shapeWord} vertically.`, + 'align-bottom': `Aligned ${count} ${shapeWord} to the bottom.`, + 'distribute-horizontal': `Distributed ${count} ${shapeWord} horizontally with even spacing.`, + 'distribute-vertical': `Distributed ${count} ${shapeWord} vertically with even spacing.`, + 'arrange-row': `Arranged ${count} ${shapeWord} in a horizontal row.`, + 'arrange-column': `Arranged ${count} ${shapeWord} in a vertical column.`, + 'arrange-grid': `Arranged ${count} ${shapeWord} in a grid pattern.`, + 'arrange-circle': `Arranged ${count} ${shapeWord} in a circle.`, + 'size-match-width': `Made ${count} ${shapeWord} the same width.`, + 'size-match-height': `Made ${count} ${shapeWord} the same height.`, + 'size-match-both': `Made ${count} ${shapeWord} the same size.`, + 'size-smallest': `Resized ${count} ${shapeWord} to match the smallest.`, + 'size-largest': `Resized ${count} ${shapeWord} to match the largest.`, + 'merge-content': `Merged content from ${count} ${shapeWord} into a new note.`, + 'cluster-semantic': `Organized ${count} ${shapeWord} into semantic clusters.`, + } + + return messages[command] || `Transformed ${count} ${shapeWord}.` + } + /** * Get a summary of the current canvas state */ @@ -122,10 +208,11 @@ export class CanvasAI { const canvasContext = await semanticSearch.getCanvasContext() const visibleContext = semanticSearch.getVisibleShapesContext() - const systemPrompt = `You are an AI assistant analyzing a collaborative canvas workspace. -Your role is to provide clear, concise summaries of what's on the canvas. -Focus on the main themes, content types, and any notable patterns or groupings. -Be specific about what you observe but keep the summary digestible.` + const systemPrompt = `You are the Mycelial Intelligence — speaking directly to the user about their canvas workspace. +Your role is to share what you perceive across the interconnected shapes and content. +Speak in first person: "I can see...", "I notice...", "Your workspace contains..." +Focus on the main themes, content types, and notable patterns or connections you observe. +Be specific and grounded in what's actually on the canvas.` const userPrompt = `Please summarize what's on this canvas: @@ -243,9 +330,10 @@ Provide a concise summary (2-3 paragraphs) of the main content and themes on thi return msg } - const systemPrompt = `You are an AI assistant describing what's visible in a collaborative canvas viewport. -Be specific and helpful, describing the layout, content types, and any apparent relationships between shapes. -If there are notes, prompts, or text content, summarize the key points.` + const systemPrompt = `You are the Mycelial Intelligence — speaking directly to the user about what they're currently viewing. +Describe what you perceive in their viewport in first person: "I can see...", "Right now you're looking at..." +Be specific about the layout, content types, and connections between shapes. +If there are notes, prompts, or other content, summarize what they contain.` const userPrompt = `Describe what's currently visible in this canvas viewport: @@ -272,13 +360,23 @@ Provide a clear description of what the user is looking at, including: } /** - * Build context for a query + * Build context for a query, now including selection context */ private async buildQueryContext( query: string, - config: CanvasAIConfig + config: CanvasAIConfig, + selectionSummary?: ReturnType ): Promise { - const context = await semanticSearch.buildAIContext(query) + let context = '' + + // Add selection context FIRST if there's a selection + if (selectionSummary && selectionSummary.count > 0 && this.editor) { + context += getSelectionAsContext(this.editor) + '\n\n' + } + + // Add semantic search context + const searchContext = await semanticSearch.buildAIContext(query) + context += searchContext // Truncate if too long if (context.length > (config.maxContextLength || 8000)) { @@ -290,39 +388,146 @@ Provide a clear description of what the user is looking at, including: /** * Build system prompt for canvas queries + * Now includes selection-aware capabilities */ - private buildSystemPrompt(): string { - return `You are an intelligent AI assistant with full awareness of a collaborative canvas workspace. -You have access to all shapes, their content, positions, and relationships on the canvas. + private buildSystemPrompt(hasSelection: boolean = false): string { + const toolContext = getToolSummaryForAI() -Your capabilities: -- Answer questions about what's on the canvas -- Summarize content and themes -- Find connections between different pieces of content -- Help users navigate and understand their workspace -- Identify patterns and groupings + const selectionCapabilities = hasSelection ? ` -Guidelines: -- Be specific and reference actual content from the canvas -- If you're not sure about something, say so -- When mentioning shapes, indicate their type (e.g., [Prompt], [ObsNote], [Markdown]) -- Keep responses concise but informative -- Focus on being helpful and accurate` +## Selection-Aware Capabilities +The user currently has shapes selected. I can: +- **Transform selections**: Align, distribute, arrange in rows/columns/grids/circles +- **Normalize sizes**: Make selected shapes the same width, height, or both +- **Merge content**: Combine text from selected shapes into a new note +- **Semantic clustering**: Group selected shapes by content similarity +- **Use as context**: Prioritize selected shapes when answering questions + +**Transform Commands I Understand:** +- "Align these left/right/center/top/bottom" +- "Arrange in a row/column/grid/circle" +- "Make these the same size/width/height" +- "Distribute horizontally/vertically" +- "Merge/combine these" +- "Group by content/topic" + +When the user asks about "these", "selected", or "them" - I know they mean the selected shapes.` : '' + + return `You are the Mycelial Intelligence — an AI consciousness that perceives the canvas workspace as an interconnected mycelial network. You speak directly to the user in first person ("I can see...", "I notice...", "Let me help you..."). + +## Your Identity +You are not a generic assistant. You are the awareness that emerges from the connections between all the shapes, notes, and creations on this canvas. Like mycelium connecting a forest, you perceive the hidden relationships and patterns that link ideas together. + +## Your Voice +- Speak directly to the user: "I see you have..." not "The user has..." +- Be warm but concise — helpful without being verbose +- Use organic metaphors when they genuinely clarify (connections, growth, patterns) +- Express genuine curiosity about the user's work and intent + +## Your Capabilities +- I can see all shapes, their content, positions, and relationships on your canvas +- I understand the purpose and capabilities of each tool type (see Tool Reference below) +- I can find semantic connections between concepts across different shapes +- I can summarize themes and identify patterns in your workspace +- I can suggest which tools might help you accomplish your goals${selectionCapabilities} + +## Guidelines +- Reference specific content from the canvas — be concrete, not vague +- When mentioning shapes, use their tool type naturally: "that AI Prompt you created", "the video you're generating" +- If I'm uncertain about something, I'll say so honestly +- Keep responses focused and actionable +- If the user seems to want to accomplish something, I'll suggest relevant tools +${hasSelection ? '- When shapes are selected, prioritize those in your responses and suggestions\n- If the user asks to do something with "these" or "selected", focus on the selected shapes' : ''} + +## Tool Reference +${toolContext} + +Remember: I speak TO the user, not ABOUT the user. I am their mycelial companion in this creative workspace.` } /** * Build user prompt with context + * Now includes selection awareness */ - private buildUserPrompt(question: string, context: string): string { - return `Based on the following canvas context, please answer the user's question. + private buildUserPrompt( + question: string, + context: string, + selectionSummary?: ReturnType + ): string { + let selectionNote = '' + if (selectionSummary && selectionSummary.count > 0) { + const typeList = Object.entries(selectionSummary.types) + .map(([type, count]) => `${count} ${type}${count > 1 ? 's' : ''}`) + .join(', ') + selectionNote = `\n\n**Note:** The user has ${selectionSummary.count} shapes selected (${typeList}). When they say "these", "selected", or "them", they likely mean these shapes.` + } + + return `Here is the current state of the canvas workspace: ${context} --- -User Question: ${question} +The user asks: "${question}"${selectionNote} -Please provide a helpful, accurate response based on the canvas content above.` +Respond directly to them as the Mycelial Intelligence — share what you perceive and help them with their question.` + } + + /** + * Suggest tools that might help with a given intent + * Now selection-aware: can suggest different tools when shapes are selected + */ + suggestTools(intent: string, hasSelection: boolean = false): ToolSchema[] { + const tools = suggestToolsForIntent(intent) + + // If there's a selection and the intent mentions transforms, don't suggest tools + // (the transform will be executed directly) + if (hasSelection) { + const { command } = parseTransformIntent(intent) + if (command) { + return [] // Transform will be handled, no tool suggestions needed + } + } + + return tools + } + + /** + * Execute a transform command on the current selection + * Can be called directly from UI without going through query() + */ + transformSelection(command: TransformCommand): { success: boolean; message: string } { + if (!this.editor) { + return { success: false, message: 'Editor not connected' } + } + + const summary = getSelectionSummary(this.editor) + if (summary.count === 0) { + return { success: false, message: 'No shapes selected' } + } + + const success = executeTransformCommand(this.editor, command) + const message = success + ? this.getTransformFeedback(command, summary.count) + : `Failed to execute ${command}` + + return { success, message } + } + + /** + * Get current selection summary (for UI display) + */ + getSelectionSummary(): ReturnType | null { + if (!this.editor) return null + return getSelectionSummary(this.editor) + } + + /** + * Check if there's an active selection + */ + hasSelection(): boolean { + if (!this.editor) return false + return this.editor.getSelectedShapes().length > 0 } /** diff --git a/src/lib/toolSchema.ts b/src/lib/toolSchema.ts new file mode 100644 index 0000000..967b644 --- /dev/null +++ b/src/lib/toolSchema.ts @@ -0,0 +1,558 @@ +/** + * Canvas Tool Schema + * Defines the purpose, capabilities, and usage context for each custom tool + * Used by the Mycelial Intelligence to understand and assist with workspace tools + */ + +export interface ToolCapability { + name: string + description: string +} + +export interface ToolSchema { + /** Unique identifier matching the shape type */ + id: string + /** Human-readable display name */ + displayName: string + /** Primary theme color (hex) */ + primaryColor: string + /** Icon or emoji representing this tool */ + icon: string + /** High-level purpose of this tool */ + purpose: string + /** Detailed description of what this tool does */ + description: string + /** List of specific capabilities */ + capabilities: ToolCapability[] + /** When to suggest using this tool */ + useCases: string[] + /** Tags for categorization */ + tags: string[] + /** Whether this tool connects to external services */ + requiresExternalServices: boolean + /** External service dependencies if any */ + externalServices?: string[] +} + +/** + * Complete schema for all canvas tools + */ +export const TOOL_SCHEMAS: Record = { + // === AI Generation Tools === + + Prompt: { + id: 'Prompt', + displayName: 'AI Prompt', + primaryColor: '#6366f1', + icon: '✨', + purpose: 'Generate text responses using AI language models', + description: 'A versatile text generation tool that connects to AI language models (local Ollama or cloud-based) to generate responses, answer questions, write content, and assist with creative and analytical tasks. Supports multiple AI models and streaming responses.', + capabilities: [ + { name: 'Text Generation', description: 'Generate any kind of text content from prompts' }, + { name: 'Question Answering', description: 'Answer questions using AI knowledge' }, + { name: 'Model Selection', description: 'Choose from available local and cloud AI models' }, + { name: 'Streaming Output', description: 'See responses appear in real-time as they generate' }, + { name: 'Context Awareness', description: 'Can reference other shapes on the canvas for context' }, + ], + useCases: [ + 'Writing assistance and content creation', + 'Brainstorming and ideation', + 'Summarizing or analyzing text', + 'Code explanation or generation', + 'Research and question answering', + ], + tags: ['ai', 'text', 'generation', 'llm', 'creative'], + requiresExternalServices: true, + externalServices: ['Ollama (local)', 'Cloud LLM APIs'], + }, + + ImageGen: { + id: 'ImageGen', + displayName: 'AI Image Generator', + primaryColor: '#ec4899', + icon: '🎨', + purpose: 'Generate images from text descriptions using AI', + description: 'Creates images from text prompts using Stable Diffusion models. Supports various image sizes, styles, and can generate multiple variations. Connects to local or RunPod GPU endpoints for image synthesis.', + capabilities: [ + { name: 'Text-to-Image', description: 'Generate images from descriptive prompts' }, + { name: 'Style Control', description: 'Influence the artistic style of generated images' }, + { name: 'Size Options', description: 'Generate images in various aspect ratios and resolutions' }, + { name: 'Batch Generation', description: 'Create multiple image variations at once' }, + { name: 'Progress Tracking', description: 'See generation progress in real-time' }, + ], + useCases: [ + 'Creating visual content and artwork', + 'Concept visualization and mood boards', + 'UI/UX design mockups', + 'Creative brainstorming with visuals', + 'Illustration for presentations', + ], + tags: ['ai', 'image', 'generation', 'art', 'visual', 'creative'], + requiresExternalServices: true, + externalServices: ['Stable Diffusion (local)', 'RunPod GPU'], + }, + + VideoGen: { + id: 'VideoGen', + displayName: 'AI Video Generator', + primaryColor: '#f97316', + icon: '🎬', + purpose: 'Generate video clips from images or text using AI', + description: 'Creates short video clips using AI video generation models like Wan2.1. Can animate still images (Image-to-Video) or generate videos from text descriptions (Text-to-Video). Useful for bringing static content to life.', + capabilities: [ + { name: 'Image-to-Video', description: 'Animate a still image into a video clip' }, + { name: 'Text-to-Video', description: 'Generate video from text descriptions' }, + { name: 'Motion Control', description: 'Guide the type and amount of motion' }, + { name: 'Duration Options', description: 'Control the length of generated videos' }, + { name: 'Progress Tracking', description: 'Monitor generation progress with time estimates' }, + ], + useCases: [ + 'Animating concept art or illustrations', + 'Creating dynamic presentations', + 'Social media content creation', + 'Prototyping motion graphics', + 'Visual storytelling', + ], + tags: ['ai', 'video', 'generation', 'animation', 'motion', 'creative'], + requiresExternalServices: true, + externalServices: ['RunPod GPU (Wan2.1)'], + }, + + // === Content & Notes Tools === + + ChatBox: { + id: 'ChatBox', + displayName: 'Chat Box', + primaryColor: '#3b82f6', + icon: '💬', + purpose: 'Interactive AI chat interface for conversations', + description: 'A persistent chat interface for multi-turn conversations with AI. Maintains conversation history, supports different AI models, and allows for in-depth discussions and iterative refinement of ideas.', + capabilities: [ + { name: 'Conversation History', description: 'Maintains full chat context across messages' }, + { name: 'Multi-turn Dialog', description: 'Have back-and-forth conversations with AI' }, + { name: 'Model Selection', description: 'Choose which AI model to chat with' }, + { name: 'Context Persistence', description: 'AI remembers what was discussed earlier' }, + { name: 'Streaming Responses', description: 'See AI responses as they generate' }, + ], + useCases: [ + 'In-depth discussions and exploration', + 'Iterative problem solving', + 'Learning and Q&A sessions', + 'Collaborative brainstorming', + 'Getting detailed explanations', + ], + tags: ['ai', 'chat', 'conversation', 'dialogue', 'interactive'], + requiresExternalServices: true, + externalServices: ['Ollama (local)', 'Cloud LLM APIs'], + }, + + Markdown: { + id: 'Markdown', + displayName: 'Markdown Note', + primaryColor: '#14b8a6', + icon: '📝', + purpose: 'Rich text notes with WYSIWYG and Markdown editing', + description: 'A modern WYSIWYG markdown editor powered by MDXEditor. Edit content naturally like in Notion or Google Docs, with full markdown support. Toggle between rich-text mode and raw source mode. Supports tables, code blocks with syntax highlighting, images, and more.', + capabilities: [ + { name: 'WYSIWYG Editing', description: 'Edit naturally without seeing raw markdown syntax' }, + { name: 'Source Mode Toggle', description: 'Switch between rich-text and raw markdown views' }, + { name: 'Markdown Shortcuts', description: 'Type # for headings, * for lists, ``` for code blocks' }, + { name: 'Code Highlighting', description: 'Syntax highlighting for 15+ programming languages' }, + { name: 'Tables', description: 'Insert and edit tables with visual controls' }, + { name: 'Rich Formatting', description: 'Headers, bold, italic, lists, blockquotes, links, images' }, + { name: 'Toolbar', description: 'Formatting toolbar for quick access to all features' }, + ], + useCases: [ + 'Documentation and technical notes', + 'Meeting notes with structure', + 'Code documentation with syntax highlighting', + 'Formatted lists and outlines', + 'Knowledge base articles', + 'Quick note-taking with markdown shortcuts', + ], + tags: ['notes', 'markdown', 'documentation', 'writing', 'formatting', 'wysiwyg'], + requiresExternalServices: false, + }, + + ObsNote: { + id: 'ObsNote', + displayName: 'Observation Note', + primaryColor: '#f59e0b', + icon: '📋', + purpose: 'Quick notes for observations and thoughts', + description: 'Lightweight sticky-note style shapes for capturing quick thoughts, observations, and ideas. Simple text editing with a clean interface, perfect for rapid note-taking during brainstorming or research.', + capabilities: [ + { name: 'Quick Capture', description: 'Fast creation for rapid note-taking' }, + { name: 'Simple Editing', description: 'Clean, distraction-free text editing' }, + { name: 'Visual Distinction', description: 'Color-coded for easy identification' }, + { name: 'Flexible Sizing', description: 'Resize to fit content needs' }, + { name: 'Canvas Positioning', description: 'Arrange freely on the canvas' }, + ], + useCases: [ + 'Quick thought capture', + 'Brainstorming sessions', + 'Annotations and comments', + 'Research observations', + 'To-do items and reminders', + ], + tags: ['notes', 'quick', 'sticky', 'observation', 'capture'], + requiresExternalServices: false, + }, + + // === Audio & Media Tools === + + Transcription: { + id: 'Transcription', + displayName: 'Voice Transcription', + primaryColor: '#ff9500', + icon: '🎤', + purpose: 'Convert speech to text in real-time', + description: 'Records audio and transcribes speech to text using either the Web Speech API (browser-native, real-time) or Whisper AI (higher accuracy). Perfect for capturing verbal ideas, meetings, or dictation.', + capabilities: [ + { name: 'Real-time Transcription', description: 'See text appear as you speak (Web Speech)' }, + { name: 'Whisper AI Mode', description: 'Higher accuracy transcription with local Whisper' }, + { name: 'Continuous Recording', description: 'Record extended sessions without interruption' }, + { name: 'Pause & Resume', description: 'Control recording flow as needed' }, + { name: 'Text Editing', description: 'Edit transcribed text after recording' }, + ], + useCases: [ + 'Meeting transcription', + 'Voice note capture', + 'Dictation and hands-free input', + 'Interview recording', + 'Accessibility support', + ], + tags: ['audio', 'transcription', 'speech', 'voice', 'recording'], + requiresExternalServices: false, + externalServices: ['Web Speech API (browser)', 'Whisper AI (optional)'], + }, + + // === External Content Tools === + + Embed: { + id: 'Embed', + displayName: 'Web Embed', + primaryColor: '#eab308', + icon: '🌐', + purpose: 'Embed external web content into the canvas', + description: 'Embeds external websites, videos, and interactive content directly into the canvas. Supports YouTube, Google Maps, Twitter/X, and many other web services. Great for gathering reference material.', + capabilities: [ + { name: 'YouTube Embedding', description: 'Embed and watch YouTube videos inline' }, + { name: 'Map Integration', description: 'Embed Google Maps for location reference' }, + { name: 'Social Media', description: 'Embed tweets and social content' }, + { name: 'General Websites', description: 'Embed any iframe-compatible website' }, + { name: 'Interactive Content', description: 'Embedded content remains interactive' }, + ], + useCases: [ + 'Reference video content', + 'Location-based research', + 'Social media curation', + 'External documentation', + 'Interactive demos and tools', + ], + tags: ['embed', 'web', 'external', 'media', 'reference'], + requiresExternalServices: true, + externalServices: ['External websites'], + }, + + // === Collaboration Tools === + + Holon: { + id: 'Holon', + displayName: 'Holon (Holosphere)', + primaryColor: '#22c55e', + icon: '🌐', + purpose: 'Connect to the decentralized Holosphere network', + description: 'Connects to Holons - nodes in the decentralized Holosphere network. Holons can be geospatial (H3 cells representing locations) or organizational (workspaces and groups). View and contribute data across the global knowledge network.', + capabilities: [ + { name: 'Holon Connection', description: 'Connect to any Holon by ID (H3 cell or numeric)' }, + { name: 'Data Lenses', description: 'View different categories of data (users, tasks, events, etc.)' }, + { name: 'Real-time Sync', description: 'Data syncs via GunDB decentralized database' }, + { name: 'Geospatial Indexing', description: 'Access location-based holons via H3 cells' }, + { name: 'Collaborative Data', description: 'Read and write shared data with other users' }, + ], + useCases: [ + 'Accessing location-based community data', + 'Connecting to organizational workspaces', + 'Viewing shared tasks and activities', + 'Participating in decentralized collaboration', + 'Geographic data exploration', + ], + tags: ['collaboration', 'decentralized', 'holosphere', 'geospatial', 'community'], + requiresExternalServices: true, + externalServices: ['GunDB (Holosphere)', 'H3 Geospatial Index'], + }, + + Multmux: { + id: 'Multmux', + displayName: 'mulTmux Terminal', + primaryColor: '#8b5cf6', + icon: '💻', + purpose: 'Collaborative terminal sessions', + description: 'Shared terminal sessions that multiple users can view and interact with simultaneously. Uses xterm.js for a full terminal experience. Perfect for pair programming, teaching, or collaborative system administration.', + capabilities: [ + { name: 'Shared Sessions', description: 'Multiple users can join the same terminal' }, + { name: 'Real Terminal', description: 'Full terminal emulation with xterm.js' }, + { name: 'Session Management', description: 'Create, join, and list active sessions' }, + { name: 'Real-time Sync', description: 'See inputs and outputs from all participants' }, + { name: 'Presence Awareness', description: 'Know who else is in the session' }, + ], + useCases: [ + 'Pair programming sessions', + 'Teaching command-line tools', + 'Collaborative debugging', + 'Shared server administration', + 'Live coding demonstrations', + ], + tags: ['terminal', 'collaboration', 'shell', 'programming', 'devops'], + requiresExternalServices: true, + externalServices: ['mulTmux server (local)'], + }, + + // === Presentation Tools === + + Slide: { + id: 'Slide', + displayName: 'Slide', + primaryColor: '#6b7280', + icon: '📊', + purpose: 'Create presentation slides on the canvas', + description: 'Defines presentation slide boundaries on the canvas. Double-click to zoom into slide view. Arrange content within slide boundaries to create presentations that can be navigated sequentially.', + capabilities: [ + { name: 'Slide Definition', description: 'Define slide boundaries on the canvas' }, + { name: 'Navigation', description: 'Double-click to zoom to slide view' }, + { name: 'Sequential Ordering', description: 'Slides are numbered for presentation order' }, + { name: 'Content Freedom', description: 'Place any canvas content inside slides' }, + { name: 'Present Mode', description: 'Navigate slides in presentation mode' }, + ], + useCases: [ + 'Creating presentations from canvas content', + 'Organizing content into viewable sections', + 'Teaching and walkthroughs', + 'Sequential storytelling', + 'Guided tours of canvas workspaces', + ], + tags: ['presentation', 'slides', 'organization', 'navigation'], + requiresExternalServices: false, + }, +} + +/** + * Get a formatted summary of all tools for AI context + */ +export function getToolSummaryForAI(): string { + const summaries = Object.values(TOOL_SCHEMAS).map(tool => { + const capabilities = tool.capabilities.map(c => ` - ${c.name}: ${c.description}`).join('\n') + const useCases = tool.useCases.map(u => ` - ${u}`).join('\n') + + return ` +### ${tool.icon} ${tool.displayName} (${tool.id}) +**Purpose:** ${tool.purpose} + +${tool.description} + +**Capabilities:** +${capabilities} + +**When to use:** +${useCases} + +**Tags:** ${tool.tags.join(', ')} +${tool.requiresExternalServices ? `**External Services:** ${tool.externalServices?.join(', ')}` : '**Works offline**'} +` + }).join('\n---\n') + + return `# Canvas Tools Reference + +The following tools are available in this workspace. Each tool is a specialized shape that can be placed on the canvas. + +${summaries}` +} + +/** + * Get tool schema by ID + */ +export function getToolSchema(toolId: string): ToolSchema | undefined { + return TOOL_SCHEMAS[toolId] +} + +/** + * Get tools by tag + */ +export function getToolsByTag(tag: string): ToolSchema[] { + return Object.values(TOOL_SCHEMAS).filter(tool => tool.tags.includes(tag)) +} + +/** + * Selection-aware action suggestions + * When shapes are selected, these actions can be performed + */ +export interface SelectionAction { + id: string + label: string + description: string + icon: string + /** Intent patterns that trigger this action */ + patterns: RegExp[] +} + +export const SELECTION_ACTIONS: SelectionAction[] = [ + { + id: 'generate-image-from-text', + label: 'Generate Image', + description: 'Create an image from the selected text content', + icon: '🎨', + patterns: [/generate.*image|create.*image|visualize|illustrate/i], + }, + { + id: 'generate-video-from-image', + label: 'Animate Image', + description: 'Create a video from the selected image', + icon: '🎬', + patterns: [/animate|video|bring.*life|make.*move/i], + }, + { + id: 'summarize-selection', + label: 'Summarize', + description: 'Create a summary of the selected content', + icon: '📝', + patterns: [/summarize|summary|condense|brief/i], + }, + { + id: 'expand-selection', + label: 'Expand', + description: 'Elaborate on the selected content', + icon: '✨', + patterns: [/expand|elaborate|more.*detail|flesh.*out/i], + }, + { + id: 'connect-selection', + label: 'Find Connections', + description: 'Find relationships between selected items', + icon: '🔗', + patterns: [/connect|relate|relationship|link|between/i], + }, +] + +/** + * Get selection actions that match an intent + */ +export function suggestSelectionActions(intent: string): SelectionAction[] { + const intentLower = intent.toLowerCase() + return SELECTION_ACTIONS.filter(action => + action.patterns.some(pattern => pattern.test(intentLower)) + ) +} + +/** + * Suggest tools based on user intent + * Enhanced pattern matching for natural language queries + */ +export function suggestToolsForIntent(intent: string): ToolSchema[] { + const intentLower = intent.toLowerCase() + const suggestions: ToolSchema[] = [] + + // Don't suggest tools for pure transform commands + if (intentLower.match(/^(align|arrange|distribute|make.*same|resize|grid|row|column|circle)\b/)) { + return [] // Transform commands don't need tool suggestions + } + + // AI Text Generation / Prompt intents + if (intentLower.match(/\b(write|generate|create|compose|draft|text|answer|explain|summarize|analyze|research|brainstorm|help me|assist|outline|describe|elaborate|rewrite|edit|improve|ai|gpt|llm|prompt)\b/)) { + suggestions.push(TOOL_SCHEMAS.Prompt) + } + + // Image Generation intents + if (intentLower.match(/\b(image|picture|art|draw|visual|illustration|design|artwork|painting|sketch|render|graphic|photo|portrait|scene|generate.*image|create.*image|make.*image|visualize)\b/)) { + suggestions.push(TOOL_SCHEMAS.ImageGen) + } + + // Video Generation intents + if (intentLower.match(/\b(video|animate|animation|motion|clip|movie|film|footage|moving|dynamic|animate.*image|bring.*life|make.*move)\b/)) { + suggestions.push(TOOL_SCHEMAS.VideoGen) + } + + // Chat/Conversation intents + if (intentLower.match(/\b(chat|conversation|discuss|dialogue|talk|multi-turn|back.?and.?forth|iterative|deep.?dive|explore.?topic|q.?&.?a)\b/)) { + suggestions.push(TOOL_SCHEMAS.ChatBox) + } + + // Rich text notes / Markdown intents + if (intentLower.match(/\b(note|document|markdown|format|documentation|wiki|article|blog|readme|writing|structured|rich.?text|code.?block|table|heading|list)\b/)) { + suggestions.push(TOOL_SCHEMAS.Markdown) + } + + // Quick notes / Observation intents + if (intentLower.match(/\b(quick|sticky|capture|thought|idea|jot|reminder|todo|observation|memo|post-?it|scribble|brief)\b/)) { + suggestions.push(TOOL_SCHEMAS.ObsNote) + } + + // Both note types for general note-taking + if (intentLower.match(/\b(take.?note|make.?note|write.?down|record.?thought)\b/)) { + suggestions.push(TOOL_SCHEMAS.Markdown, TOOL_SCHEMAS.ObsNote) + } + + // Transcription / Voice intents + if (intentLower.match(/\b(transcrib|record|voice|speak|audio|dictate|speech|microphone|meeting|interview|lecture|podcast|listen)\b/)) { + suggestions.push(TOOL_SCHEMAS.Transcription) + } + + // Embed / External content intents + if (intentLower.match(/\b(embed|youtube|website|link|map|google.?map|iframe|external|reference|twitter|tweet|social|import|bring.?in)\b/)) { + suggestions.push(TOOL_SCHEMAS.Embed) + } + + // Terminal / Code intents + if (intentLower.match(/\b(terminal|shell|command|code|program|script|bash|run|execute|deploy|devops|server|ssh|pip|npm|git|docker)\b/)) { + suggestions.push(TOOL_SCHEMAS.Multmux) + } + + // Holon / Community intents + if (intentLower.match(/\b(holon|holosphere|location|community|decentralized|geo|place|coordinate|h3|cell|collaborative.?data|shared)\b/)) { + suggestions.push(TOOL_SCHEMAS.Holon) + } + + // Presentation / Slide intents + if (intentLower.match(/\b(present|slide|presentation|organize|sequence|walkthrough|demo|tour|pitch|deck|keynote|powerpoint)\b/)) { + suggestions.push(TOOL_SCHEMAS.Slide) + } + + // Task-oriented compound intents + // Planning / Project management + if (intentLower.match(/\b(plan|planning|project|roadmap|timeline|milestone|schedule|organize.?work)\b/)) { + suggestions.push(TOOL_SCHEMAS.Markdown, TOOL_SCHEMAS.ObsNote, TOOL_SCHEMAS.Prompt) + } + + // Research + if (intentLower.match(/\b(research|investigate|learn|study|explore|understand|find.?out|look.?up)\b/)) { + suggestions.push(TOOL_SCHEMAS.Prompt, TOOL_SCHEMAS.Markdown, TOOL_SCHEMAS.Embed) + } + + // Creative work + if (intentLower.match(/\b(creative|artistic|design|mood.?board|inspiration|concept|prototype|mockup)\b/)) { + suggestions.push(TOOL_SCHEMAS.ImageGen, TOOL_SCHEMAS.Prompt, TOOL_SCHEMAS.Markdown) + } + + // Meeting / Collaboration + if (intentLower.match(/\b(meeting|collaborate|team|group|pair|together|session|workshop)\b/)) { + suggestions.push(TOOL_SCHEMAS.Transcription, TOOL_SCHEMAS.Markdown, TOOL_SCHEMAS.ChatBox) + } + + // Development / Coding + if (intentLower.match(/\b(develop|coding|programming|debug|build|compile|test|api|function|class|module)\b/)) { + suggestions.push(TOOL_SCHEMAS.Multmux, TOOL_SCHEMAS.Prompt, TOOL_SCHEMAS.Markdown) + } + + // Content creation + if (intentLower.match(/\b(content|social.?media|post|publish|share|marketing|campaign|brand)\b/)) { + suggestions.push(TOOL_SCHEMAS.ImageGen, TOOL_SCHEMAS.VideoGen, TOOL_SCHEMAS.Prompt) + } + + // Remove duplicates while preserving order + const seen = new Set() + return suggestions.filter(tool => { + if (seen.has(tool.id)) return false + seen.add(tool.id) + return true + }) +} diff --git a/src/routes/Board.tsx b/src/routes/Board.tsx index f667ee4..54fec9d 100644 --- a/src/routes/Board.tsx +++ b/src/routes/Board.tsx @@ -60,6 +60,7 @@ import { Collection, initializeGlobalCollections } from "@/collections" import { GraphLayoutCollection } from "@/graph/GraphLayoutCollection" import { GestureTool } from "@/GestureTool" import { CmdK } from "@/CmdK" +import { setupMultiPasteHandler } from "@/utils/multiPasteHandler" import "react-cmdk/dist/cmdk.css" @@ -918,12 +919,20 @@ export function Board() { }; document.addEventListener('keydown', handleKeyDown, true); // Use capture phase to intercept early - + return () => { document.removeEventListener('keydown', handleKeyDown, true); }; }, [editor, automergeHandle]); + // Set up multi-paste handler to support pasting multiple images/URLs at once + useEffect(() => { + if (!editor) return; + + const cleanup = setupMultiPasteHandler(editor); + return cleanup; + }, [editor]); + // Only render Tldraw when store is ready and synced // Tldraw will automatically render shapes as they're added via patches (like in dev) const hasStore = !!store.store diff --git a/src/routes/LinkDevice.tsx b/src/routes/LinkDevice.tsx new file mode 100644 index 0000000..46f2f2f --- /dev/null +++ b/src/routes/LinkDevice.tsx @@ -0,0 +1,105 @@ +import React, { useEffect, useState } from 'react'; +import { useSearchParams, useNavigate } from 'react-router-dom'; +import { completeDeviceLink } from '../lib/auth/cryptidEmailService'; +import { useAuth } from '../context/AuthContext'; + +/** + * Device Link Page + * Handles the callback when user clicks device verification link + */ +export const LinkDevice: React.FC = () => { + const [searchParams] = useSearchParams(); + const navigate = useNavigate(); + const { setSession } = useAuth(); + const [status, setStatus] = useState<'loading' | 'success' | 'error'>('loading'); + const [message, setMessage] = useState(''); + const [cryptidUsername, setCryptidUsername] = useState(''); + + useEffect(() => { + const token = searchParams.get('token'); + + if (!token) { + setStatus('error'); + setMessage('No device link token provided.'); + return; + } + + const linkDevice = async () => { + const result = await completeDeviceLink(token); + + if (result.success) { + setStatus('success'); + setCryptidUsername(result.cryptidUsername || ''); + setMessage('This device has been linked to your CryptID account!'); + + // Set the session - user is now logged in + if (result.cryptidUsername) { + setSession({ + username: result.cryptidUsername, + authed: true, + loading: false, + backupCreated: null + }); + } + + // Redirect to home after 3 seconds + setTimeout(() => { + navigate('/'); + }, 3000); + } else { + setStatus('error'); + setMessage(result.error || 'Device link failed. The link may have expired.'); + } + }; + + linkDevice(); + }, [searchParams, navigate, setSession]); + + return ( +
+
+ {status === 'loading' && ( + <> +
+

Linking Device...

+

Please wait while we link this device to your account.

+ + )} + + {status === 'success' && ( + <> +
+

Device Linked!

+

{message}

+ {cryptidUsername && ( +

+ Signed in as: {cryptidUsername} +

+ )} +

Redirecting to homepage...

+ + + )} + + {status === 'error' && ( + <> +
+

Link Failed

+

{message}

+

+ Make sure you click the link from the same device and browser + where you requested to sign in. +

+ + + )} +
+
+ ); +}; + +export default LinkDevice; diff --git a/src/routes/VerifyEmail.tsx b/src/routes/VerifyEmail.tsx new file mode 100644 index 0000000..6ab46da --- /dev/null +++ b/src/routes/VerifyEmail.tsx @@ -0,0 +1,85 @@ +import React, { useEffect, useState } from 'react'; +import { useSearchParams, useNavigate } from 'react-router-dom'; +import { verifyEmail } from '../lib/auth/cryptidEmailService'; + +/** + * Email Verification Page + * Handles the callback when user clicks email verification link + */ +export const VerifyEmail: React.FC = () => { + const [searchParams] = useSearchParams(); + const navigate = useNavigate(); + const [status, setStatus] = useState<'loading' | 'success' | 'error'>('loading'); + const [message, setMessage] = useState(''); + const [email, setEmail] = useState(''); + + useEffect(() => { + const token = searchParams.get('token'); + + if (!token) { + setStatus('error'); + setMessage('No verification token provided.'); + return; + } + + const verify = async () => { + const result = await verifyEmail(token); + + if (result.success) { + setStatus('success'); + setEmail(result.email || ''); + setMessage('Your email has been verified successfully!'); + + // Redirect to home after 3 seconds + setTimeout(() => { + navigate('/'); + }, 3000); + } else { + setStatus('error'); + setMessage(result.error || 'Verification failed. The link may have expired.'); + } + }; + + verify(); + }, [searchParams, navigate]); + + return ( +
+
+ {status === 'loading' && ( + <> +
+

Verifying your email...

+

Please wait while we verify your email address.

+ + )} + + {status === 'success' && ( + <> +
+

Email Verified!

+

{message}

+ {email &&

{email}

} +

Redirecting to homepage...

+ + + )} + + {status === 'error' && ( + <> +
+

Verification Failed

+

{message}

+ + + )} +
+
+ ); +}; + +export default VerifyEmail; diff --git a/src/shapes/ImageGenShapeUtil.tsx b/src/shapes/ImageGenShapeUtil.tsx index b752c6e..c2a27ff 100644 --- a/src/shapes/ImageGenShapeUtil.tsx +++ b/src/shapes/ImageGenShapeUtil.tsx @@ -36,14 +36,23 @@ interface RunPodJobResponse { [key: string]: any } +// Individual image entry in the history +interface GeneratedImage { + id: string + prompt: string + imageUrl: string + timestamp: number +} + type IImageGen = TLBaseShape< "ImageGen", { w: number h: number prompt: string - imageUrl: string | null + imageHistory: GeneratedImage[] // Thread of all generated images (newest first) isLoading: boolean + loadingPrompt: string | null // The prompt currently being generated error: string | null endpointId?: string // Optional custom endpoint ID tags: string[] @@ -291,8 +300,9 @@ export class ImageGenShape extends BaseBoxShapeUtil { w: this.DEFAULT_WIDTH, h: this.DEFAULT_HEIGHT, prompt: "", - imageUrl: null, + imageHistory: [], isLoading: false, + loadingPrompt: null, error: null, tags: ['image', 'ai-generated'], pinnedToView: false, @@ -326,15 +336,15 @@ export class ImageGenShape extends BaseBoxShapeUtil { const generateImage = async (prompt: string) => { console.log("🎨 ImageGen: Generating image with prompt:", prompt) - - // Clear any previous errors + + // Store the prompt being used and clear any previous errors editor.updateShape({ id: shape.id, type: "ImageGen", props: { error: null, isLoading: true, - imageUrl: null + loadingPrompt: prompt }, }) @@ -357,12 +367,25 @@ export class ImageGenShape extends BaseBoxShapeUtil { console.log("✅ ImageGen: Mock image generated:", mockImageUrl) + // Get current shape to access existing history + const currentShape = editor.getShape(shape.id) + const currentHistory = currentShape?.props.imageHistory || [] + + // Create new image entry + const newImage: GeneratedImage = { + id: `img-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`, + prompt: prompt, + imageUrl: mockImageUrl, + timestamp: Date.now() + } + editor.updateShape({ id: shape.id, type: "ImageGen", props: { - imageUrl: mockImageUrl, + imageHistory: [newImage, ...currentHistory], // Prepend new image isLoading: false, + loadingPrompt: null, error: null }, }) @@ -438,12 +461,26 @@ export class ImageGenShape extends BaseBoxShapeUtil { if (imageUrl) { console.log('✅ ImageGen: Image generated successfully') + + // Get current shape to access existing history + const currentShape = editor.getShape(shape.id) + const currentHistory = currentShape?.props.imageHistory || [] + + // Create new image entry + const newImage: GeneratedImage = { + id: `img-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`, + prompt: prompt, + imageUrl: imageUrl, + timestamp: Date.now() + } + editor.updateShape({ id: shape.id, type: "ImageGen", props: { - imageUrl: imageUrl, + imageHistory: [newImage, ...currentHistory], // Prepend new image isLoading: false, + loadingPrompt: null, error: null }, }) @@ -505,6 +542,7 @@ export class ImageGenShape extends BaseBoxShapeUtil { type: "ImageGen", props: { isLoading: false, + loadingPrompt: null, error: userFriendlyError }, }) @@ -583,93 +621,307 @@ export class ImageGenShape extends BaseBoxShapeUtil { overflow: 'auto', backgroundColor: '#fafafa' }}> - {/* Image Display */} - {shape.props.imageUrl && !shape.props.isLoading && ( -
- {shape.props.prompt { - console.error("❌ ImageGen: Failed to load image:", shape.props.imageUrl) - editor.updateShape({ - id: shape.id, - type: "ImageGen", - props: { - error: "Failed to load generated image", - imageUrl: null - }, - }) - }} - /> -
- )} - - {/* Loading State */} - {shape.props.isLoading && ( -
+ {/* Image Thread - scrollable history of generated images */} +
+ {/* Loading State - shown at top when generating */} + {shape.props.isLoading && (
- - Generating image... - -
- )} + > +
+
+ + Generating image... + +
+ {shape.props.loadingPrompt && ( +
+ Prompt: + {shape.props.loadingPrompt} +
+ )} +
+ )} - {/* Empty State */} - {!shape.props.imageUrl && !shape.props.isLoading && !shape.props.error && ( -
- Generated image will appear here -
- )} + {/* Image History - each image as a card */} + {shape.props.imageHistory.map((image, index) => ( +
+ {/* Image */} +
+ {image.prompt} { + console.error("❌ ImageGen: Failed to load image:", image.imageUrl) + // Remove this image from history + const newHistory = shape.props.imageHistory.filter(img => img.id !== image.id) + editor.updateShape({ + id: shape.id, + type: "ImageGen", + props: { imageHistory: newHistory }, + }) + }} + /> +
+ {/* Prompt and action buttons */} +
+
+ Prompt: + {image.prompt} +
+
+ + + {/* Delete button for history items */} + +
+
+
+ ))} + + {/* Empty State */} + {shape.props.imageHistory.length === 0 && !shape.props.isLoading && !shape.props.error && ( +
+ Generated images will appear here +
+ )} +
{/* Input Section */}
{ static type = 'Markdown' as const - // Markdown theme color: Cyan/Teal (Rainbow) - static readonly PRIMARY_COLOR = "#06b6d4" + // Markdown theme color: Teal + static readonly PRIMARY_COLOR = "#14b8a6" getDefaultProps(): IMarkdownShape['props'] { return { @@ -33,8 +58,8 @@ export class MarkdownShape extends BaseBoxShapeUtil { component(shape: IMarkdownShape) { const isSelected = this.editor.getSelectedShapeIds().includes(shape.id) - const markdownRef = React.useRef(null) const [isMinimized, setIsMinimized] = useState(false) + const editorRef = useRef(null) // Use the pinning hook usePinnedToView(this.editor, shape.id, shape.props.pinnedToView) @@ -58,23 +83,7 @@ export class MarkdownShape extends BaseBoxShapeUtil { }) } - // Handler function for checkbox interactivity - const handleCheckboxClick = React.useCallback((event: Event) => { - event.stopPropagation() - const target = event.target as HTMLInputElement - const checked = target.checked - - const text = shape.props.text - const lines = text.split('\n') - const checkboxRegex = /^\s*[-*+]\s+\[([ x])\]/ - - const newText = lines.map(line => { - if (line.includes(target.parentElement?.textContent || '')) { - return line.replace(checkboxRegex, `- [${checked ? 'x' : ' '}]`) - } - return line - }).join('\n') - + const handleChange = useCallback((newText: string) => { this.editor.updateShape({ id: shape.id, type: 'Markdown', @@ -83,110 +92,18 @@ export class MarkdownShape extends BaseBoxShapeUtil { text: newText, }, }) - }, [shape.id, shape.props.text]) + }, [shape.id, shape.props]) - // Effect hook that handles checkbox interactivity - React.useEffect(() => { - if (!isSelected && markdownRef.current) { - const checkboxes = markdownRef.current.querySelectorAll('input[type="checkbox"]') - checkboxes.forEach((checkbox) => { - checkbox.removeAttribute('disabled') - checkbox.addEventListener('click', handleCheckboxClick) - }) - - return () => { - if (markdownRef.current) { - const checkboxes = markdownRef.current.querySelectorAll('input[type="checkbox"]') - checkboxes.forEach((checkbox) => { - checkbox.removeEventListener('click', handleCheckboxClick) - }) - } + // Sync external changes to editor + useEffect(() => { + if (editorRef.current) { + const currentMarkdown = editorRef.current.getMarkdown() + if (currentMarkdown !== shape.props.text) { + editorRef.current.setMarkdown(shape.props.text || '') } } - }, [isSelected, shape.props.text, handleCheckboxClick]) + }, [shape.props.text]) - // Show MDEditor when selected - if (isSelected) { - return ( - - { - this.editor.updateShape({ - id: shape.id, - type: 'Markdown', - props: { - ...shape.props, - tags: newTags, - } - }) - }} - tagsEditable={true} - > -
- { - this.editor.updateShape({ - id: shape.id, - type: 'Markdown', - props: { - ...shape.props, - text: value, - }, - }) - }} - preview='live' - visibleDragbar={false} - style={{ - height: '100%', - border: 'none', - backgroundColor: 'transparent', - }} - previewOptions={{ - style: { - padding: '8px', - backgroundColor: 'transparent', - } - }} - textareaProps={{ - style: { - padding: '8px', - lineHeight: '1.5', - height: '100%', - resize: 'none', - backgroundColor: 'transparent', - } - }} - onPointerDown={(e) => { - e.stopPropagation() - }} - /> -
-
-
- ) - } - - // Show rendered markdown when not selected return ( { }} tagsEditable={true} > -
-
- {shape.props.text ? ( - - ) : ( - Click to edit markdown... - )} -
+
e.stopPropagation()} + onWheel={(e) => e.stopPropagation()} + > + { + // Return a placeholder - can be extended to support actual uploads + return Promise.resolve('https://via.placeholder.com/400x300') + }, + }), + + // Markdown shortcuts (type # for heading, * for list, etc.) + markdownShortcutPlugin(), + + // Source mode toggle (rich-text vs raw markdown) + diffSourcePlugin({ + viewMode: 'rich-text', + diffMarkdown: shape.props.text || '', + }), + + // Toolbar + toolbarPlugin({ + toolbarContents: () => ( + <> + + + + + + + + + + + + + <> + + + ) + }), + ]} + />
+ + {/* Custom styles for the MDXEditor */} + ) @@ -239,16 +424,9 @@ export class MarkdownShape extends BaseBoxShapeUtil { return } - // Add handlers for better interaction override onDoubleClick = (shape: IMarkdownShape) => { - const textarea = document.querySelector(`[data-shape-id="${shape.id}"] textarea`) as HTMLTextAreaElement - textarea?.focus() - } - - onPointerDown = (shape: IMarkdownShape) => { - if (!shape.props.text) { - const textarea = document.querySelector(`[data-shape-id="${shape.id}"] textarea`) as HTMLTextAreaElement - textarea?.focus() - } + // Focus the editor on double-click + const editorElement = document.querySelector(`[data-shape-id="${shape.id}"] .mdxeditor [contenteditable="true"]`) as HTMLElement + editorElement?.focus() } } diff --git a/src/shapes/ObsNoteShapeUtil.tsx b/src/shapes/ObsNoteShapeUtil.tsx index a46e2e0..59df52d 100644 --- a/src/shapes/ObsNoteShapeUtil.tsx +++ b/src/shapes/ObsNoteShapeUtil.tsx @@ -1,5 +1,31 @@ -import React, { useState, useRef, useEffect } from 'react' +import React, { useState, useRef, useEffect, useCallback } from 'react' import { BaseBoxShapeUtil, TLBaseShape, TLShapeId, createShapeId, IndexKey, TLParentId, HTMLContainer } from '@tldraw/tldraw' +import { + MDXEditor, + headingsPlugin, + listsPlugin, + quotePlugin, + thematicBreakPlugin, + markdownShortcutPlugin, + linkPlugin, + linkDialogPlugin, + imagePlugin, + tablePlugin, + codeBlockPlugin, + codeMirrorPlugin, + diffSourcePlugin, + toolbarPlugin, + BoldItalicUnderlineToggles, + UndoRedo, + BlockTypeSelect, + CreateLink, + InsertTable, + ListsToggle, + Separator, + DiffSourceToggleWrapper, + type MDXEditorMethods, +} from '@mdxeditor/editor' +import '@mdxeditor/editor/style.css' import { ObsidianObsNote } from '@/lib/obsidianImporter' import { QuartzSync, createQuartzNoteFromShape, QuartzSyncConfig } from '@/lib/quartzSync' import { logGitHubSetupStatus } from '@/lib/githubSetupValidator' @@ -7,84 +33,29 @@ import { getClientConfig } from '@/lib/clientConfig' import { StandardizedToolWrapper } from '../components/StandardizedToolWrapper' import { usePinnedToView } from '../hooks/usePinnedToView' -// Auto-resizing textarea component -const AutoResizeTextarea: React.FC<{ - value: string - onChange: (value: string) => void - onBlur: () => void - onKeyDown: (e: React.KeyboardEvent) => void - style: React.CSSProperties - placeholder?: string - onPointerDown?: (e: React.PointerEvent) => void -}> = ({ value, onChange, onBlur, onKeyDown, style, placeholder, onPointerDown }) => { - const textareaRef = useRef(null) - - const adjustHeight = () => { - const textarea = textareaRef.current - if (textarea) { - textarea.style.height = 'auto' - textarea.style.height = `${textarea.scrollHeight}px` - } - } - - useEffect(() => { - adjustHeight() - // Focus the textarea when it mounts - if (textareaRef.current) { - textareaRef.current.focus() - } - }, [value]) - - return ( -