dylanebert commited on
Commit
bc7e9cd
·
1 Parent(s): 12673bf
This view is limited to 50 files because it contains too many changes.   See raw diff
Files changed (50) hide show
  1. agents.md +155 -0
  2. bun.lock +214 -39
  3. eslint.config.js +4 -0
  4. layers/structure.md +1 -2
  5. llms.txt +0 -1140
  6. package.json +7 -4
  7. src/App.svelte +4 -0
  8. src/lib/components/Editor.svelte +32 -12
  9. src/lib/components/chat/ChatPanel.svelte +251 -653
  10. src/lib/components/chat/ExampleMessages.svelte +168 -172
  11. src/lib/components/chat/InProgressBlock.svelte +0 -474
  12. src/lib/components/chat/MarkdownRenderer.svelte +0 -202
  13. src/lib/components/chat/Message.svelte +76 -0
  14. src/lib/components/chat/MessageContent.svelte +74 -0
  15. src/lib/components/chat/MessageInput.svelte +294 -0
  16. src/lib/components/chat/MessageList.svelte +169 -0
  17. src/lib/components/chat/MessageSegment.svelte +0 -70
  18. src/lib/components/chat/ReasoningBlock.svelte +0 -293
  19. src/lib/components/chat/StreamingIndicator.svelte +0 -157
  20. src/lib/components/chat/StreamingText.svelte +0 -146
  21. src/lib/components/chat/TextRenderer.svelte +68 -0
  22. src/lib/components/chat/ToolInvocation.svelte +0 -520
  23. src/lib/components/chat/context.md +22 -16
  24. src/lib/components/chat/segments/TodoSegment.svelte +128 -0
  25. src/lib/components/chat/segments/ToolBlock.svelte +236 -0
  26. src/lib/components/editor/CodeEditor.svelte +3 -9
  27. src/lib/components/game/GameCanvas.svelte +8 -10
  28. src/lib/components/layout/AppHeader.svelte +5 -5
  29. src/lib/controllers/animation-controller.ts +190 -0
  30. src/lib/controllers/chat-controller.ts +116 -0
  31. src/lib/controllers/context.md +34 -0
  32. src/lib/models/chat-data.ts +89 -0
  33. src/lib/models/context.md +28 -0
  34. src/lib/models/segment-view.ts +188 -0
  35. src/lib/server/api.ts +30 -10
  36. src/lib/server/console-buffer.ts +14 -5
  37. src/lib/server/context.md +13 -12
  38. src/lib/server/documentation.ts +1 -1
  39. src/lib/server/langgraph-agent.ts +91 -105
  40. src/lib/server/mcp-client.ts +713 -0
  41. src/lib/server/task-tracker.ts +1 -1
  42. src/lib/server/tools.ts +30 -305
  43. src/lib/services/agent.ts +12 -75
  44. src/lib/services/content-manager.ts +248 -0
  45. src/lib/services/context.md +21 -31
  46. src/lib/services/game-engine.ts +129 -79
  47. src/lib/services/html-document-parser.ts +56 -0
  48. src/lib/services/html-parser.ts +0 -38
  49. src/lib/services/message-handler.ts +168 -131
  50. src/lib/services/segment-formatter.ts +185 -0
agents.md ADDED
@@ -0,0 +1,155 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ VibeGame is a 3D game engine with declarative XML syntax and ECS architecture. This file provides essential context for AI agents working with the engine.
2
+
3
+ ## Core Concepts
4
+
5
+ **ECS Architecture**: Entity-Component-System pattern where entities are IDs, components are data containers, and systems contain logic.
6
+
7
+ **Declarative XML**: Game entities defined in HTML-like syntax within `<world>` tags.
8
+
9
+ **Auto-Creation**: Engine automatically creates player, camera, and lighting if not explicitly defined.
10
+
11
+ ## Essential Syntax
12
+
13
+ ```xml
14
+ <world canvas="#game-canvas" sky="#87ceeb">
15
+ <!-- Ground (REQUIRED to prevent player falling) -->
16
+ <static-part pos="0 -0.5 0" shape="box" size="20 1 20" color="#90ee90"></static-part>
17
+
18
+ <!-- Physics objects -->
19
+ <dynamic-part pos="0 5 0" shape="sphere" size="1" color="#ff0000"></dynamic-part>
20
+ <kinematic-part pos="5 2 0" shape="box" size="3 0.5 3" color="#0000ff">
21
+ <tween target="body.pos-y" from="2" to="5" duration="3" loop="ping-pong"></tween>
22
+ </kinematic-part>
23
+ </world>
24
+ ```
25
+
26
+ ## Key Recipes
27
+
28
+ - `<static-part>` - Immovable objects (grounds, walls, platforms)
29
+ - `<dynamic-part>` - Gravity-affected objects (balls, crates, debris)
30
+ - `<kinematic-part>` - Script-controlled physics (moving platforms, doors)
31
+ - `<player>` - Player character (auto-created if missing)
32
+ - `<camera>` - Orbital camera (auto-created if missing)
33
+ - `<entity>` - Base entity with any components via attributes
34
+
35
+ ## Critical Physics Rule
36
+
37
+ ⚠️ **Physics bodies override transform positions!** Always set position on the body, not the transform, for physics entities.
38
+
39
+ ```xml
40
+ <!-- ✅ BEST: Use recipe with pos shorthand -->
41
+ <dynamic-part pos="0 5 0" shape="sphere" size="1"></dynamic-part>
42
+
43
+ <!-- ❌ WRONG: Transform position ignored if body exists -->
44
+ <entity transform="pos: 0 5 0" body collider></entity>
45
+ ```
46
+
47
+ ## Component System
48
+
49
+ Components declared as bare attributes (defaults) or with values:
50
+
51
+ ```xml
52
+ <!-- Bare attributes use defaults -->
53
+ <entity transform body collider renderer></entity>
54
+
55
+ <!-- Override specific properties -->
56
+ <entity transform="pos: 0 5 0" body="type: dynamic; mass: 10" collider renderer></entity>
57
+ ```
58
+
59
+ Shorthands automatically expand to matching component properties:
60
+
61
+ - `pos="x y z"` → applies to transform.pos* AND body.pos*
62
+ - `color="#ff0000"` → applies to renderer.color
63
+ - `size="2"` → broadcasts to sizeX, sizeY, sizeZ
64
+
65
+ ## TypeScript API
66
+
67
+ ```typescript
68
+ import * as GAME from "vibegame";
69
+
70
+ // Component definition
71
+ const Health = GAME.defineComponent({
72
+ current: GAME.Types.f32,
73
+ max: GAME.Types.f32,
74
+ });
75
+
76
+ // System with query
77
+ const healthQuery = GAME.defineQuery([Health]);
78
+ const HealthSystem: GAME.System = {
79
+ update: (state) => {
80
+ const entities = healthQuery(state.world);
81
+ for (const entity of entities) {
82
+ Health.current[entity] -= 1 * state.time.deltaTime;
83
+ }
84
+ },
85
+ };
86
+
87
+ // Plugin registration
88
+ GAME.withComponent("health", Health).withSystem(HealthSystem).run();
89
+ ```
90
+
91
+ ## Available Features
92
+
93
+ ✅ **Core Systems**: Physics (Rapier 3D), rendering (Three.js), input (keyboard/mouse/gamepad), tweening, transforms
94
+
95
+ ✅ **Game Elements**: Player controller, orbital camera, collision detection, respawn system, post-processing effects
96
+
97
+ ❌ **Not Built-In**: Audio, multiplayer, save/load, inventory, AI/pathfinding, particles, custom shaders
98
+
99
+ ## Development Commands
100
+
101
+ ```bash
102
+ # Project creation
103
+ npm create vibegame@latest my-game
104
+ cd my-game
105
+
106
+ # Development
107
+ bun dev # Start dev server
108
+ bun run build # Production build
109
+ bun run check # TypeScript validation
110
+ bun run lint --fix # ESLint analysis
111
+ bun test # Run tests
112
+ ```
113
+
114
+ ## Getting More Information
115
+
116
+ **Comprehensive Documentation**: Use Context7 to fetch detailed documentation with examples:
117
+
118
+ ```typescript
119
+ // For AI agents with Context7 access:
120
+ // Use mcp__context7__resolve-library-id to find "vibegame"
121
+ // Then use mcp__context7__get-library-docs with the resolved ID
122
+ // This provides the full 2000+ line documentation with detailed examples
123
+ ```
124
+
125
+ **Quick References**:
126
+
127
+ - Shapes: `box`, `sphere`, `cylinder`, `capsule`
128
+ - Physics Types: `static` (fixed), `dynamic` (gravity), `kinematic` (scripted)
129
+ - Easing: `linear`, `sine-in-out`, `quad-out`, `bounce-in`, etc.
130
+ - Loop Modes: `once`, `loop`, `ping-pong`
131
+
132
+ ## Common Patterns
133
+
134
+ **Basic Platformer**: Ground + static platforms + player (auto-created)
135
+ **Physics Playground**: Ground + walls + dynamic objects with collision
136
+ **Moving Platforms**: Kinematic bodies + position tweening
137
+ **Collectibles**: Kinematic objects + rotation tweening + collision detection
138
+
139
+ ## Best Practices for AI Development
140
+
141
+ 1. **Always include ground** - Player falls without platforms
142
+ 2. **Use recipes over raw entities** - Cleaner and more reliable
143
+ 3. **Leverage auto-creation** - Engine handles player/camera/lighting defaults
144
+ 4. **Physics position priority** - Set positions on bodies, not transforms
145
+ 5. **Query Context7 for details** - This file is overview only, get specifics via Context7
146
+ 6. **Test incrementally** - Start simple, add complexity progressively
147
+
148
+ ## Architecture Notes
149
+
150
+ - **Plugin System**: Bevy-inspired modular architecture
151
+ - **Update Phases**: SetupBatch → FixedBatch → DrawBatch
152
+ - **Context Management**: Use /clear frequently in Claude Code sessions
153
+ - **Parallel Operations**: Invoke multiple tools simultaneously for efficiency
154
+
155
+ This context enables basic VibeGame development. For detailed API references, extensive examples, or advanced features, fetch comprehensive documentation via Context7.
bun.lock CHANGED
@@ -8,13 +8,16 @@
8
  "@huggingface/inference": "^4.8.0",
9
  "@langchain/core": "^0.3.75",
10
  "@langchain/langgraph": "^0.4.9",
 
 
 
11
  "@types/marked": "^6.0.0",
12
  "@types/node": "^24.3.3",
13
  "gsap": "^3.13.0",
14
  "marked": "^16.2.1",
15
  "monaco-editor": "^0.50.0",
16
  "svelte-splitpanes": "^8.0.5",
17
- "vibegame": "^0.1.6",
18
  "zod": "^4.1.8",
19
  },
20
  "devDependencies": {
@@ -124,6 +127,8 @@
124
 
125
  "@humanwhocodes/retry": ["@humanwhocodes/[email protected]", "", {}, "sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ=="],
126
 
 
 
127
  "@jridgewell/gen-mapping": ["@jridgewell/[email protected]", "", { "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.0", "@jridgewell/trace-mapping": "^0.3.24" } }, "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA=="],
128
 
129
  "@jridgewell/resolve-uri": ["@jridgewell/[email protected]", "", {}, "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw=="],
@@ -132,13 +137,19 @@
132
 
133
  "@jridgewell/trace-mapping": ["@jridgewell/[email protected]", "", { "dependencies": { "@jridgewell/resolve-uri": "^3.1.0", "@jridgewell/sourcemap-codec": "^1.4.14" } }, "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw=="],
134
 
135
- "@langchain/core": ["@langchain/[email protected].75", "", { "dependencies": { "@cfworker/json-schema": "^4.0.2", "ansi-styles": "^5.0.0", "camelcase": "6", "decamelize": "1.2.0", "js-tiktoken": "^1.0.12", "langsmith": "^0.3.67", "mustache": "^4.2.0", "p-queue": "^6.6.2", "p-retry": "4", "uuid": "^10.0.0", "zod": "^3.25.32", "zod-to-json-schema": "^3.22.3" } }, "sha512-kTyBS0DTeD0JYa9YH5lg6UdDbHmvplk3t9PCjP5jDQZCK5kPe2aDFToqdiCaLzZg8RzzM+clXLVyJtPTE8bZ2Q=="],
136
 
137
  "@langchain/langgraph": ["@langchain/[email protected]", "", { "dependencies": { "@langchain/langgraph-checkpoint": "^0.1.1", "@langchain/langgraph-sdk": "~0.1.0", "uuid": "^10.0.0", "zod": "^3.25.32" }, "peerDependencies": { "@langchain/core": ">=0.3.58 < 0.4.0", "zod-to-json-schema": "^3.x" }, "optionalPeers": ["zod-to-json-schema"] }, "sha512-+rcdTGi4Ium4X/VtIX3Zw4RhxEkYWpwUyz806V6rffjHOAMamg6/WZDxpJbrP33RV/wJG1GH12Z29oX3Pqq3Aw=="],
138
 
139
  "@langchain/langgraph-checkpoint": ["@langchain/[email protected]", "", { "dependencies": { "uuid": "^10.0.0" }, "peerDependencies": { "@langchain/core": ">=0.2.31 <0.4.0 || ^1.0.0-alpha" } }, "sha512-h2bP0RUikQZu0Um1ZUPErQLXyhzroJqKRbRcxYRTAh49oNlsfeq4A3K4YEDRbGGuyPZI/Jiqwhks1wZwY73AZw=="],
140
 
141
- "@langchain/langgraph-sdk": ["@langchain/[email protected].3", "", { "dependencies": { "@types/json-schema": "^7.0.15", "p-queue": "^6.6.2", "p-retry": "4", "uuid": "^9.0.0" }, "peerDependencies": { "@langchain/core": ">=0.2.31 <0.4.0", "react": "^18 || ^19", "react-dom": "^18 || ^19" }, "optionalPeers": ["@langchain/core", "react", "react-dom"] }, "sha512-muZJ+D9A7gL0/QSvsjL6LMwgvN58euO2+MwSXq5Nb40ZgnjVWWtUYmjdVLno4IpgAXWXOQi34i2XtLbTwbyXvg=="],
 
 
 
 
 
 
142
 
143
  "@nodelib/fs.scandir": ["@nodelib/[email protected]", "", { "dependencies": { "@nodelib/fs.stat": "2.0.5", "run-parallel": "^1.1.9" } }, "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g=="],
144
 
@@ -146,49 +157,51 @@
146
 
147
  "@nodelib/fs.walk": ["@nodelib/[email protected]", "", { "dependencies": { "@nodelib/fs.scandir": "2.1.5", "fastq": "^1.6.0" } }, "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg=="],
148
 
 
 
149
  "@pkgr/core": ["@pkgr/[email protected]", "", {}, "sha512-QNqXyfVS2wm9hweSYD2O7F0G06uurj9kZ96TRQE5Y9hU7+tgdZwIkbAKc5Ocy1HxEY2kuDQa6cQ1WRs/O5LFKA=="],
150
 
151
- "@rollup/rollup-android-arm-eabi": ["@rollup/[email protected].1", "", { "os": "android", "cpu": "arm" }, "sha512-HJXwzoZN4eYTdD8bVV22DN8gsPCAj3V20NHKOs8ezfXanGpmVPR7kalUHd+Y31IJp9stdB87VKPFbsGY3H/2ag=="],
152
 
153
- "@rollup/rollup-android-arm64": ["@rollup/[email protected].1", "", { "os": "android", "cpu": "arm64" }, "sha512-PZlsJVcjHfcH53mOImyt3bc97Ep3FJDXRpk9sMdGX0qgLmY0EIWxCag6EigerGhLVuL8lDVYNnSo8qnTElO4xw=="],
154
 
155
- "@rollup/rollup-darwin-arm64": ["@rollup/[email protected].1", "", { "os": "darwin", "cpu": "arm64" }, "sha512-xc6i2AuWh++oGi4ylOFPmzJOEeAa2lJeGUGb4MudOtgfyyjr4UPNK+eEWTPLvmPJIY/pgw6ssFIox23SyrkkJw=="],
156
 
157
- "@rollup/rollup-darwin-x64": ["@rollup/[email protected].1", "", { "os": "darwin", "cpu": "x64" }, "sha512-2ofU89lEpDYhdLAbRdeyz/kX3Y2lpYc6ShRnDjY35bZhd2ipuDMDi6ZTQ9NIag94K28nFMofdnKeHR7BT0CATw=="],
158
 
159
- "@rollup/rollup-freebsd-arm64": ["@rollup/[email protected].1", "", { "os": "freebsd", "cpu": "arm64" }, "sha512-wOsE6H2u6PxsHY/BeFHA4VGQN3KUJFZp7QJBmDYI983fgxq5Th8FDkVuERb2l9vDMs1D5XhOrhBrnqcEY6l8ZA=="],
160
 
161
- "@rollup/rollup-freebsd-x64": ["@rollup/[email protected].1", "", { "os": "freebsd", "cpu": "x64" }, "sha512-A/xeqaHTlKbQggxCqispFAcNjycpUEHP52mwMQZUNqDUJFFYtPHCXS1VAG29uMlDzIVr+i00tSFWFLivMcoIBQ=="],
162
 
163
- "@rollup/rollup-linux-arm-gnueabihf": ["@rollup/[email protected].1", "", { "os": "linux", "cpu": "arm" }, "sha512-54v4okehwl5TaSIkpp97rAHGp7t3ghinRd/vyC1iXqXMfjYUTm7TfYmCzXDoHUPTTf36L8pr0E7YsD3CfB3ZDg=="],
164
 
165
- "@rollup/rollup-linux-arm-musleabihf": ["@rollup/[email protected].1", "", { "os": "linux", "cpu": "arm" }, "sha512-p/LaFyajPN/0PUHjv8TNyxLiA7RwmDoVY3flXHPSzqrGcIp/c2FjwPPP5++u87DGHtw+5kSH5bCJz0mvXngYxw=="],
166
 
167
- "@rollup/rollup-linux-arm64-gnu": ["@rollup/[email protected].1", "", { "os": "linux", "cpu": "arm64" }, "sha512-2AbMhFFkTo6Ptna1zO7kAXXDLi7H9fGTbVaIq2AAYO7yzcAsuTNWPHhb2aTA6GPiP+JXh85Y8CiS54iZoj4opw=="],
168
 
169
- "@rollup/rollup-linux-arm64-musl": ["@rollup/[email protected].1", "", { "os": "linux", "cpu": "arm64" }, "sha512-Cgef+5aZwuvesQNw9eX7g19FfKX5/pQRIyhoXLCiBOrWopjo7ycfB292TX9MDcDijiuIJlx1IzJz3IoCPfqs9w=="],
170
 
171
- "@rollup/rollup-linux-loongarch64-gnu": ["@rollup/rollup-linux-loongarch64[email protected].1", "", { "os": "linux", "cpu": "none" }, "sha512-RPhTwWMzpYYrHrJAS7CmpdtHNKtt2Ueo+BlLBjfZEhYBhK00OsEqM08/7f+eohiF6poe0YRDDd8nAvwtE/Y62Q=="],
172
 
173
- "@rollup/rollup-linux-ppc64-gnu": ["@rollup/[email protected].1", "", { "os": "linux", "cpu": "ppc64" }, "sha512-eSGMVQw9iekut62O7eBdbiccRguuDgiPMsw++BVUg+1K7WjZXHOg/YOT9SWMzPZA+w98G+Fa1VqJgHZOHHnY0Q=="],
174
 
175
- "@rollup/rollup-linux-riscv64-gnu": ["@rollup/[email protected].1", "", { "os": "linux", "cpu": "none" }, "sha512-S208ojx8a4ciIPrLgazF6AgdcNJzQE4+S9rsmOmDJkusvctii+ZvEuIC4v/xFqzbuP8yDjn73oBlNDgF6YGSXQ=="],
176
 
177
- "@rollup/rollup-linux-riscv64-musl": ["@rollup/[email protected].1", "", { "os": "linux", "cpu": "none" }, "sha512-3Ag8Ls1ggqkGUvSZWYcdgFwriy2lWo+0QlYgEFra/5JGtAd6C5Hw59oojx1DeqcA2Wds2ayRgvJ4qxVTzCHgzg=="],
178
 
179
- "@rollup/rollup-linux-s390x-gnu": ["@rollup/[email protected].1", "", { "os": "linux", "cpu": "s390x" }, "sha512-t9YrKfaxCYe7l7ldFERE1BRg/4TATxIg+YieHQ966jwvo7ddHJxPj9cNFWLAzhkVsbBvNA4qTbPVNsZKBO4NSg=="],
180
 
181
- "@rollup/rollup-linux-x64-gnu": ["@rollup/[email protected].1", "", { "os": "linux", "cpu": "x64" }, "sha512-MCgtFB2+SVNuQmmjHf+wfI4CMxy3Tk8XjA5Z//A0AKD7QXUYFMQcns91K6dEHBvZPCnhJSyDWLApk40Iq/H3tA=="],
182
 
183
- "@rollup/rollup-linux-x64-musl": ["@rollup/[email protected].1", "", { "os": "linux", "cpu": "x64" }, "sha512-nEvqG+0jeRmqaUMuwzlfMKwcIVffy/9KGbAGyoa26iu6eSngAYQ512bMXuqqPrlTyfqdlB9FVINs93j534UJrg=="],
184
 
185
- "@rollup/rollup-openharmony-arm64": ["@rollup/[email protected].1", "", { "os": "none", "cpu": "arm64" }, "sha512-RDsLm+phmT3MJd9SNxA9MNuEAO/J2fhW8GXk62G/B4G7sLVumNFbRwDL6v5NrESb48k+QMqdGbHgEtfU0LCpbA=="],
186
 
187
- "@rollup/rollup-win32-arm64-msvc": ["@rollup/[email protected].1", "", { "os": "win32", "cpu": "arm64" }, "sha512-hpZB/TImk2FlAFAIsoElM3tLzq57uxnGYwplg6WDyAxbYczSi8O2eQ+H2Lx74504rwKtZ3N2g4bCUkiamzS6TQ=="],
188
 
189
- "@rollup/rollup-win32-ia32-msvc": ["@rollup/[email protected].1", "", { "os": "win32", "cpu": "ia32" }, "sha512-SXjv8JlbzKM0fTJidX4eVsH+Wmnp0/WcD8gJxIZyR6Gay5Qcsmdbi9zVtnbkGPG8v2vMR1AD06lGWy5FLMcG7A=="],
190
 
191
- "@rollup/rollup-win32-x64-msvc": ["@rollup/[email protected].1", "", { "os": "win32", "cpu": "x64" }, "sha512-StxAO/8ts62KZVRAm4JZYq9+NqNsV7RvimNK+YM7ry//zebEH6meuugqW/P5OFUCjyQgui+9fUxT6d5NShvMvA=="],
192
 
193
  "@rtsao/scc": ["@rtsao/[email protected]", "", {}, "sha512-zt6OdqaDoOnJ1ZYsCYGt9YmWzDXl4vQdKTyJev62gFhRGKdx7mcT54V9KIjg+d2wi9EXsPvAPKe7i7WjfVWB8g=="],
194
 
@@ -206,7 +219,7 @@
206
 
207
  "@types/marked": ["@types/[email protected]", "", { "dependencies": { "marked": "*" } }, "sha512-jmjpa4BwUsmhxcfsgUit/7A9KbrC48Q0q8KvnY107ogcjGgTFDlIL3RpihNpx2Mu1hM4mdFQjoVc4O6JoGKHsA=="],
208
 
209
- "@types/node": ["@types/node@24.4.0", "", { "dependencies": { "undici-types": "~7.11.0" } }, "sha512-gUuVEAK4/u6F9wRLznPUU4WGUacSEBDPoC2TrBkw3GAnOLHBL45QdfHOXp1kJ4ypBGLxTOB+t7NJLpKoC3gznQ=="],
210
 
211
  "@types/pug": ["@types/[email protected]", "", {}, "sha512-Sk/uYFOBAB7mb74XcpizmH0KOR2Pv3D2Hmrh1Dmy5BmK3MpdSa5kqZcg6EKBdklU0bFXX9gCfzvpnyUehrPIuA=="],
212
 
@@ -218,25 +231,27 @@
218
 
219
  "@types/ws": ["@types/[email protected]", "", { "dependencies": { "@types/node": "*" } }, "sha512-ThVF6DCVhA8kUGy+aazFQ4kXQ7E1Ty7A3ypFOe0IcJV8O/M511G99AW24irKrW56Wt44yG9+ij8FaqoBGkuBXg=="],
220
 
221
- "@typescript-eslint/eslint-plugin": ["@typescript-eslint/eslint-plugin@8.43.0", "", { "dependencies": { "@eslint-community/regexpp": "^4.10.0", "@typescript-eslint/scope-manager": "8.43.0", "@typescript-eslint/type-utils": "8.43.0", "@typescript-eslint/utils": "8.43.0", "@typescript-eslint/visitor-keys": "8.43.0", "graphemer": "^1.4.0", "ignore": "^7.0.0", "natural-compare": "^1.4.0", "ts-api-utils": "^2.1.0" }, "peerDependencies": { "@typescript-eslint/parser": "^8.43.0", "eslint": "^8.57.0 || ^9.0.0", "typescript": ">=4.8.4 <6.0.0" } }, "sha512-8tg+gt7ENL7KewsKMKDHXR1vm8tt9eMxjJBYINf6swonlWgkYn5NwyIgXpbbDxTNU5DgpDFfj95prcTq2clIQQ=="],
 
 
222
 
223
- "@typescript-eslint/parser": ["@typescript-eslint/parser@8.43.0", "", { "dependencies": { "@typescript-eslint/scope-manager": "8.43.0", "@typescript-eslint/types": "8.43.0", "@typescript-eslint/typescript-estree": "8.43.0", "@typescript-eslint/visitor-keys": "8.43.0", "debug": "^4.3.4" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0", "typescript": ">=4.8.4 <6.0.0" } }, "sha512-B7RIQiTsCBBmY+yW4+ILd6mF5h1FUwJsVvpqkrgpszYifetQ2Ke+Z4u6aZh0CblkUGIdR59iYVyXqqZGkZ3aBw=="],
224
 
225
- "@typescript-eslint/project-service": ["@typescript-eslint/project-service@8.43.0", "", { "dependencies": { "@typescript-eslint/tsconfig-utils": "^8.43.0", "@typescript-eslint/types": "^8.43.0", "debug": "^4.3.4" }, "peerDependencies": { "typescript": ">=4.8.4 <6.0.0" } }, "sha512-htB/+D/BIGoNTQYffZw4uM4NzzuolCoaA/BusuSIcC8YjmBYQioew5VUZAYdAETPjeed0hqCaW7EHg+Robq8uw=="],
226
 
227
- "@typescript-eslint/scope-manager": ["@typescript-eslint/scope-manager@8.43.0", "", { "dependencies": { "@typescript-eslint/types": "8.43.0", "@typescript-eslint/visitor-keys": "8.43.0" } }, "sha512-daSWlQ87ZhsjrbMLvpuuMAt3y4ba57AuvadcR7f3nl8eS3BjRc8L9VLxFLk92RL5xdXOg6IQ+qKjjqNEimGuAg=="],
228
 
229
- "@typescript-eslint/tsconfig-utils": ["@typescript-eslint/tsconfig-utils@8.43.0", "", { "peerDependencies": { "typescript": ">=4.8.4 <6.0.0" } }, "sha512-ALC2prjZcj2YqqL5X/bwWQmHA2em6/94GcbB/KKu5SX3EBDOsqztmmX1kMkvAJHzxk7TazKzJfFiEIagNV3qEA=="],
230
 
231
- "@typescript-eslint/type-utils": ["@typescript-eslint/[email protected]", "", { "dependencies": { "@typescript-eslint/types": "8.43.0", "@typescript-eslint/typescript-estree": "8.43.0", "@typescript-eslint/utils": "8.43.0", "debug": "^4.3.4", "ts-api-utils": "^2.1.0" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0", "typescript": ">=4.8.4 <6.0.0" } }, "sha512-qaH1uLBpBuBBuRf8c1mLJ6swOfzCXryhKND04Igr4pckzSEW9JX5Aw9AgW00kwfjWJF0kk0ps9ExKTfvXfw4Qg=="],
232
 
233
- "@typescript-eslint/types": ["@typescript-eslint/types@8.43.0", "", {}, "sha512-vQ2FZaxJpydjSZJKiSW/LJsabFFvV7KgLC5DiLhkBcykhQj8iK9BOaDmQt74nnKdLvceM5xmhaTF+pLekrxEkw=="],
234
 
235
- "@typescript-eslint/typescript-estree": ["@typescript-eslint/typescript-estree@8.43.0", "", { "dependencies": { "@typescript-eslint/project-service": "8.43.0", "@typescript-eslint/tsconfig-utils": "8.43.0", "@typescript-eslint/types": "8.43.0", "@typescript-eslint/visitor-keys": "8.43.0", "debug": "^4.3.4", "fast-glob": "^3.3.2", "is-glob": "^4.0.3", "minimatch": "^9.0.4", "semver": "^7.6.0", "ts-api-utils": "^2.1.0" }, "peerDependencies": { "typescript": ">=4.8.4 <6.0.0" } }, "sha512-7Vv6zlAhPb+cvEpP06WXXy/ZByph9iL6BQRBDj4kmBsW98AqEeQHlj/13X+sZOrKSo9/rNKH4Ul4f6EICREFdw=="],
236
 
237
- "@typescript-eslint/utils": ["@typescript-eslint/utils@8.43.0", "", { "dependencies": { "@eslint-community/eslint-utils": "^4.7.0", "@typescript-eslint/scope-manager": "8.43.0", "@typescript-eslint/types": "8.43.0", "@typescript-eslint/typescript-estree": "8.43.0" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0", "typescript": ">=4.8.4 <6.0.0" } }, "sha512-S1/tEmkUeeswxd0GGcnwuVQPFWo8NzZTOMxCvw8BX7OMxnNae+i8Tm7REQen/SwUIPoPqfKn7EaZ+YLpiB3k9g=="],
238
 
239
- "@typescript-eslint/visitor-keys": ["@typescript-eslint/visitor-keys@8.43.0", "", { "dependencies": { "@typescript-eslint/types": "8.43.0", "eslint-visitor-keys": "^4.2.1" } }, "sha512-T+S1KqRD4sg/bHfLwrpF/K3gQLBM1n7Rp7OjjikjTEssI2YJzQpi5WXoynOaQ93ERIuq3O8RBTOUYDKszUCEHw=="],
240
 
241
  "acorn": ["[email protected]", "", { "bin": { "acorn": "bin/acorn" } }, "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg=="],
242
 
@@ -280,6 +295,8 @@
280
 
281
  "bitecs": ["[email protected]", "", {}, "sha512-wAylY4pNfX8IeIH5phtwt1lUNtHKrkoSNrArI7Ris2Y4nEQWFIVvXdgAuqprEg9bq8Wolmlj0gVfeG6MFmtI2Q=="],
282
 
 
 
283
  "brace-expansion": ["[email protected]", "", { "dependencies": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" } }, "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg=="],
284
 
285
  "braces": ["[email protected]", "", { "dependencies": { "fill-range": "^7.1.1" } }, "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA=="],
@@ -288,6 +305,8 @@
288
 
289
  "bun-types": ["[email protected]", "", { "dependencies": { "@types/node": "*" }, "peerDependencies": { "@types/react": "^19" } }, "sha512-hwaAu8tct/Zn6Zft4U9BsZcXkYomzpHJX28ofvx7k0Zz2HNz54n1n+tDgxoWFGB4PcFvJXJQloPhaV2eP3Q6EA=="],
290
 
 
 
291
  "call-bind": ["[email protected]", "", { "dependencies": { "call-bind-apply-helpers": "^1.0.0", "es-define-property": "^1.0.0", "get-intrinsic": "^1.2.4", "set-function-length": "^1.2.2" } }, "sha512-oKlSFMcMwpUg2ednkhQ454wfWiU/ul3CkJe/PEHcTKuiX6RpbehUiFMXu13HalGZxfUwCQzZG747YXBn1im9ww=="],
292
 
293
  "call-bind-apply-helpers": ["[email protected]", "", { "dependencies": { "es-errors": "^1.3.0", "function-bind": "^1.1.2" } }, "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ=="],
@@ -314,6 +333,16 @@
314
 
315
  "console-table-printer": ["[email protected]", "", { "dependencies": { "simple-wcswidth": "^1.0.1" } }, "sha512-MCBl5HNVaFuuHW6FGbL/4fB7N/ormCy+tQ+sxTrF6QtSbSNETvPuOVbkJBhzDgYhvjWGrTma4eYJa37ZuoQsPw=="],
316
 
 
 
 
 
 
 
 
 
 
 
317
  "cross-spawn": ["[email protected]", "", { "dependencies": { "path-key": "^3.1.0", "shebang-command": "^2.0.0", "which": "^2.0.1" } }, "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA=="],
318
 
319
  "css-tree": ["[email protected]", "", { "dependencies": { "mdn-data": "2.0.30", "source-map-js": "^1.0.1" } }, "sha512-6Fv1DV/TYw//QF5IzQdqsNDjx/wc8TrMBZsqjL9eW01tWb7R7k/mq+/VXfJCl7SoD5emsJop9cOByJZfs8hYIw=="],
@@ -338,14 +367,22 @@
338
 
339
  "define-properties": ["[email protected]", "", { "dependencies": { "define-data-property": "^1.0.1", "has-property-descriptors": "^1.0.0", "object-keys": "^1.1.1" } }, "sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg=="],
340
 
 
 
341
  "detect-indent": ["[email protected]", "", {}, "sha512-reYkTUJAZb9gUuZ2RvVCNhVHdg62RHnJ7WJl8ftMi4diZ6NWlciOzQN88pUhSELEwflJht4oQDv0F0BMlwaYtA=="],
342
 
343
  "doctrine": ["[email protected]", "", { "dependencies": { "esutils": "^2.0.2" } }, "sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw=="],
344
 
345
  "dunder-proto": ["[email protected]", "", { "dependencies": { "call-bind-apply-helpers": "^1.0.1", "es-errors": "^1.3.0", "gopd": "^1.2.0" } }, "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A=="],
346
 
 
 
 
 
347
  "emoji-regex": ["[email protected]", "", {}, "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A=="],
348
 
 
 
349
  "es-abstract": ["[email protected]", "", { "dependencies": { "array-buffer-byte-length": "^1.0.2", "arraybuffer.prototype.slice": "^1.0.4", "available-typed-arrays": "^1.0.7", "call-bind": "^1.0.8", "call-bound": "^1.0.4", "data-view-buffer": "^1.0.2", "data-view-byte-length": "^1.0.2", "data-view-byte-offset": "^1.0.1", "es-define-property": "^1.0.1", "es-errors": "^1.3.0", "es-object-atoms": "^1.1.1", "es-set-tostringtag": "^2.1.0", "es-to-primitive": "^1.3.0", "function.prototype.name": "^1.1.8", "get-intrinsic": "^1.3.0", "get-proto": "^1.0.1", "get-symbol-description": "^1.1.0", "globalthis": "^1.0.4", "gopd": "^1.2.0", "has-property-descriptors": "^1.0.2", "has-proto": "^1.2.0", "has-symbols": "^1.1.0", "hasown": "^2.0.2", "internal-slot": "^1.1.0", "is-array-buffer": "^3.0.5", "is-callable": "^1.2.7", "is-data-view": "^1.0.2", "is-negative-zero": "^2.0.3", "is-regex": "^1.2.1", "is-set": "^2.0.3", "is-shared-array-buffer": "^1.0.4", "is-string": "^1.1.1", "is-typed-array": "^1.1.15", "is-weakref": "^1.1.1", "math-intrinsics": "^1.1.0", "object-inspect": "^1.13.4", "object-keys": "^1.1.1", "object.assign": "^4.1.7", "own-keys": "^1.0.1", "regexp.prototype.flags": "^1.5.4", "safe-array-concat": "^1.1.3", "safe-push-apply": "^1.0.0", "safe-regex-test": "^1.1.0", "set-proto": "^1.0.0", "stop-iteration-iterator": "^1.1.0", "string.prototype.trim": "^1.2.10", "string.prototype.trimend": "^1.0.9", "string.prototype.trimstart": "^1.0.8", "typed-array-buffer": "^1.0.3", "typed-array-byte-length": "^1.0.3", "typed-array-byte-offset": "^1.0.4", "typed-array-length": "^1.0.7", "unbox-primitive": "^1.1.0", "which-typed-array": "^1.1.19" } }, "sha512-WSzPgsdLtTcQwm4CROfS5ju2Wa1QQcVeT37jFjYzdFz1r9ahadC8B8/a4qxJxM+09F18iumCdRmlr96ZYkQvEg=="],
350
 
351
  "es-define-property": ["[email protected]", "", {}, "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g=="],
@@ -364,6 +401,8 @@
364
 
365
  "esbuild": ["[email protected]", "", { "optionalDependencies": { "@esbuild/aix-ppc64": "0.21.5", "@esbuild/android-arm": "0.21.5", "@esbuild/android-arm64": "0.21.5", "@esbuild/android-x64": "0.21.5", "@esbuild/darwin-arm64": "0.21.5", "@esbuild/darwin-x64": "0.21.5", "@esbuild/freebsd-arm64": "0.21.5", "@esbuild/freebsd-x64": "0.21.5", "@esbuild/linux-arm": "0.21.5", "@esbuild/linux-arm64": "0.21.5", "@esbuild/linux-ia32": "0.21.5", "@esbuild/linux-loong64": "0.21.5", "@esbuild/linux-mips64el": "0.21.5", "@esbuild/linux-ppc64": "0.21.5", "@esbuild/linux-riscv64": "0.21.5", "@esbuild/linux-s390x": "0.21.5", "@esbuild/linux-x64": "0.21.5", "@esbuild/netbsd-x64": "0.21.5", "@esbuild/openbsd-x64": "0.21.5", "@esbuild/sunos-x64": "0.21.5", "@esbuild/win32-arm64": "0.21.5", "@esbuild/win32-ia32": "0.21.5", "@esbuild/win32-x64": "0.21.5" }, "bin": { "esbuild": "bin/esbuild" } }, "sha512-mg3OPMV4hXywwpoDxu3Qda5xCKQi+vCTZq8S9J/EpkhB2HzKXq4SNFZE3+NK93JYxc8VMSep+lOUSC/RVKaBqw=="],
366
 
 
 
367
  "escape-string-regexp": ["[email protected]", "", {}, "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA=="],
368
 
369
  "eslint": ["[email protected]", "", { "dependencies": { "@eslint-community/eslint-utils": "^4.8.0", "@eslint-community/regexpp": "^4.12.1", "@eslint/config-array": "^0.21.0", "@eslint/config-helpers": "^0.3.1", "@eslint/core": "^0.15.2", "@eslint/eslintrc": "^3.3.1", "@eslint/js": "9.35.0", "@eslint/plugin-kit": "^0.3.5", "@humanfs/node": "^0.16.6", "@humanwhocodes/module-importer": "^1.0.1", "@humanwhocodes/retry": "^0.4.2", "@types/estree": "^1.0.6", "@types/json-schema": "^7.0.15", "ajv": "^6.12.4", "chalk": "^4.0.0", "cross-spawn": "^7.0.6", "debug": "^4.3.2", "escape-string-regexp": "^4.0.0", "eslint-scope": "^8.4.0", "eslint-visitor-keys": "^4.2.1", "espree": "^10.4.0", "esquery": "^1.5.0", "esutils": "^2.0.2", "fast-deep-equal": "^3.1.3", "file-entry-cache": "^8.0.0", "find-up": "^5.0.0", "glob-parent": "^6.0.2", "ignore": "^5.2.0", "imurmurhash": "^0.1.4", "is-glob": "^4.0.0", "json-stable-stringify-without-jsonify": "^1.0.1", "lodash.merge": "^4.6.2", "minimatch": "^3.1.2", "natural-compare": "^1.4.0", "optionator": "^0.9.3" }, "peerDependencies": { "jiti": "*" }, "optionalPeers": ["jiti"], "bin": { "eslint": "bin/eslint.js" } }, "sha512-QePbBFMJFjgmlE+cXAlbHZbHpdFVS2E/6vzCy7aKlebddvl1vadiC4JFV5u/wqTkNUwEV8WrQi257jf5f06hrg=="],
@@ -398,8 +437,20 @@
398
 
399
  "esutils": ["[email protected]", "", {}, "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g=="],
400
 
 
 
401
  "eventemitter3": ["[email protected]", "", {}, "sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw=="],
402
 
 
 
 
 
 
 
 
 
 
 
403
  "fast-deep-equal": ["[email protected]", "", {}, "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q=="],
404
 
405
  "fast-diff": ["[email protected]", "", {}, "sha512-VxPP4NqbUjj6MaAOafWeUn2cXWLcCtljklUtZf0Ind4XQ+QPtmA0b18zZy0jIQx+ExRVCR/ZQpBmik5lXshNsw=="],
@@ -416,6 +467,8 @@
416
 
417
  "fill-range": ["[email protected]", "", { "dependencies": { "to-regex-range": "^5.0.1" } }, "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg=="],
418
 
 
 
419
  "find-up": ["[email protected]", "", { "dependencies": { "locate-path": "^6.0.0", "path-exists": "^4.0.0" } }, "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng=="],
420
 
421
  "flat-cache": ["[email protected]", "", { "dependencies": { "flatted": "^3.2.9", "keyv": "^4.5.4" } }, "sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw=="],
@@ -424,6 +477,12 @@
424
 
425
  "for-each": ["[email protected]", "", { "dependencies": { "is-callable": "^1.2.7" } }, "sha512-dKx12eRCVIzqCxFGplyFKJMPvLEWgmNtUrpTiJIR5u97zEhRG8ySrtboPHZXx7daLxQVrl643cTzbab2tkQjxg=="],
426
 
 
 
 
 
 
 
427
  "fs.realpath": ["[email protected]", "", {}, "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw=="],
428
 
429
  "fsevents": ["[email protected]", "", { "os": "darwin" }, "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw=="],
@@ -440,7 +499,7 @@
440
 
441
  "get-symbol-description": ["[email protected]", "", { "dependencies": { "call-bound": "^1.0.3", "es-errors": "^1.3.0", "get-intrinsic": "^1.2.6" } }, "sha512-w9UMqWwJxHNOvoNzSJ2oPF5wvYcvP7jUvYzhp67yEhTi17ZDBBC1z9pTdGuzjD+EFIqLSYRweZjqfiPzQ06Ebg=="],
442
 
443
- "glob": ["glob@7.2.3", "", { "dependencies": { "fs.realpath": "^1.0.0", "inflight": "^1.0.4", "inherits": "2", "minimatch": "^3.1.1", "once": "^1.3.0", "path-is-absolute": "^1.0.0" } }, "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q=="],
444
 
445
  "glob-parent": ["[email protected]", "", { "dependencies": { "is-glob": "^4.0.3" } }, "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A=="],
446
 
@@ -470,6 +529,10 @@
470
 
471
  "hasown": ["[email protected]", "", { "dependencies": { "function-bind": "^1.1.2" } }, "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ=="],
472
 
 
 
 
 
473
  "ignore": ["[email protected]", "", {}, "sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg=="],
474
 
475
  "import-fresh": ["[email protected]", "", { "dependencies": { "parent-module": "^1.0.0", "resolve-from": "^4.0.0" } }, "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ=="],
@@ -482,6 +545,8 @@
482
 
483
  "internal-slot": ["[email protected]", "", { "dependencies": { "es-errors": "^1.3.0", "hasown": "^2.0.2", "side-channel": "^1.1.0" } }, "sha512-4gd7VpWNQNB4UKKCFFVcp1AVv+FMOgs9NKzjHKusc8jTMhd5eL1NqQqOpE0KzMds804/yHlglp3uxgluOqAPLw=="],
484
 
 
 
485
  "is-array-buffer": ["[email protected]", "", { "dependencies": { "call-bind": "^1.0.8", "call-bound": "^1.0.3", "get-intrinsic": "^1.2.6" } }, "sha512-DDfANUiiG2wC1qawP66qlTugJeL5HyzMpfr8lLK+jMQirGzNod0B12cFB/9q838Ru27sBwfw78/rdoU7RERz6A=="],
486
 
487
  "is-async-function": ["[email protected]", "", { "dependencies": { "async-function": "^1.0.0", "call-bound": "^1.0.3", "get-proto": "^1.0.1", "has-tostringtag": "^1.0.2", "safe-regex-test": "^1.1.0" } }, "sha512-9dgM/cZBnNvjzaMYHVoxxfPj2QXt22Ev7SuuPrs+xav0ukGB0S6d4ydZdEiM48kLx5kDV+QBPrpVnFyefL8kkQ=="],
@@ -518,6 +583,8 @@
518
 
519
  "is-number-object": ["[email protected]", "", { "dependencies": { "call-bound": "^1.0.3", "has-tostringtag": "^1.0.2" } }, "sha512-lZhclumE1G6VYD8VHe35wFaIif+CTy5SJIi5+3y4psDgWu4wPDoBhF8NxUOinEc7pHgiTsT6MaBb92rKhhD+Xw=="],
520
 
 
 
521
  "is-reference": ["[email protected]", "", { "dependencies": { "@types/estree": "^1.0.6" } }, "sha512-ixkJoqQvAP88E6wLydLGGqCJsrFUnqoH6HnaczB8XmDH1oaWU+xxdptvikTgaEhtZ53Ky6YXiBuUI2WXLMCwjw=="],
522
 
523
  "is-regex": ["[email protected]", "", { "dependencies": { "call-bound": "^1.0.2", "gopd": "^1.2.0", "has-tostringtag": "^1.0.2", "hasown": "^2.0.2" } }, "sha512-MjYsKHO5O7mCsmRGxWcLWheFqN9DJ/2TmngvjKXihe6efViPqc274+Fx/4fYj/r03+ESvBdTXK0V6tA3rgez1g=="],
@@ -542,6 +609,8 @@
542
 
543
  "isexe": ["[email protected]", "", {}, "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw=="],
544
 
 
 
545
  "js-tiktoken": ["[email protected]", "", { "dependencies": { "base64-js": "^1.5.1" } }, "sha512-biOj/6M5qdgx5TKjDnFT1ymSpM5tbd3ylwDtrQvFQSu0Z7bBYko2dF+W/aUkXUPuk6IVpRxk/3Q2sHOzGlS36g=="],
546
 
547
  "js-yaml": ["[email protected]", "", { "dependencies": { "argparse": "^2.0.1" }, "bin": { "js-yaml": "bin/js-yaml.js" } }, "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA=="],
@@ -568,6 +637,8 @@
568
 
569
  "lodash.merge": ["[email protected]", "", {}, "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ=="],
570
 
 
 
571
  "magic-string": ["[email protected]", "", { "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.5" } }, "sha512-2N21sPY9Ws53PZvsEpVtNuSW+ScYbQdp4b9qUaL+9QkHUrGFKo56Lg9Emg5s9V/qrtNBmiR01sYhUOwu3H+VOw=="],
572
 
573
  "marked": ["[email protected]", "", { "bin": { "marked": "bin/marked.js" } }, "sha512-K3UxuKu6l6bmA5FUwYho8CfJBlsUWAooKtdGgMcERSpF7gcBUrCGsLH7wDaaNOzwq18JzSUDyoEb/YsrqMac3w=="],
@@ -576,16 +647,26 @@
576
 
577
  "mdn-data": ["[email protected]", "", {}, "sha512-GaqWWShW4kv/G9IEucWScBx9G1/vsFZZJUO+tD26M8J8z3Kw5RDQjaoZe03YAClgeS/SWPOcb4nkFBTEi5DUEA=="],
578
 
 
 
 
 
579
  "merge2": ["[email protected]", "", {}, "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg=="],
580
 
581
  "micromatch": ["[email protected]", "", { "dependencies": { "braces": "^3.0.3", "picomatch": "^2.3.1" } }, "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA=="],
582
 
 
 
 
 
583
  "min-indent": ["[email protected]", "", {}, "sha512-I9jwMn07Sy/IwOj3zVkVik2JTvgpaykDZEigL6Rx6N9LbMywwUSMtxET+7lVoDLLd3O3IXwJwvuuns8UB/HeAg=="],
584
 
585
  "minimatch": ["[email protected]", "", { "dependencies": { "brace-expansion": "^1.1.7" } }, "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw=="],
586
 
587
  "minimist": ["[email protected]", "", {}, "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA=="],
588
 
 
 
589
  "mkdirp": ["[email protected]", "", { "dependencies": { "minimist": "^1.2.6" }, "bin": { "mkdirp": "bin/cmd.js" } }, "sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw=="],
590
 
591
  "monaco-editor": ["[email protected]", "", {}, "sha512-8CclLCmrRRh+sul7C08BmPBP3P8wVWfBHomsTcndxg5NRCEPfu/mc2AGU8k37ajjDVXcXFc12ORAMUkmk+lkFA=="],
@@ -600,8 +681,12 @@
600
 
601
  "natural-compare": ["[email protected]", "", {}, "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw=="],
602
 
 
 
603
  "normalize-path": ["[email protected]", "", {}, "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA=="],
604
 
 
 
605
  "object-inspect": ["[email protected]", "", {}, "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew=="],
606
 
607
  "object-keys": ["[email protected]", "", {}, "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA=="],
@@ -614,6 +699,8 @@
614
 
615
  "object.values": ["[email protected]", "", { "dependencies": { "call-bind": "^1.0.8", "call-bound": "^1.0.3", "define-properties": "^1.2.1", "es-object-atoms": "^1.0.0" } }, "sha512-gXah6aZrcUxjWg2zR2MwouP2eHlCBzdV4pygudehaKXSGW4v2AsRQUK+lwwXhii6KFZcunEnmSUoYp5CXibxtA=="],
616
 
 
 
617
  "once": ["[email protected]", "", { "dependencies": { "wrappy": "1" } }, "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w=="],
618
 
619
  "optionator": ["[email protected]", "", { "dependencies": { "deep-is": "^0.1.3", "fast-levenshtein": "^2.0.6", "levn": "^0.4.1", "prelude-ls": "^1.2.1", "type-check": "^0.4.0", "word-wrap": "^1.2.5" } }, "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g=="],
@@ -632,8 +719,12 @@
632
 
633
  "p-timeout": ["[email protected]", "", { "dependencies": { "p-finally": "^1.0.0" } }, "sha512-rhIwUycgwwKcP9yTOOFK/AKsAopjjCakVqLHePO3CC6Mir1Z99xT+R63jZxAT5lFZLa2inS5h+ZS2GvR99/FBg=="],
634
 
 
 
635
  "parent-module": ["[email protected]", "", { "dependencies": { "callsites": "^3.0.0" } }, "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g=="],
636
 
 
 
637
  "path-exists": ["[email protected]", "", {}, "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w=="],
638
 
639
  "path-is-absolute": ["[email protected]", "", {}, "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg=="],
@@ -642,12 +733,18 @@
642
 
643
  "path-parse": ["[email protected]", "", {}, "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw=="],
644
 
 
 
 
 
645
  "periscopic": ["[email protected]", "", { "dependencies": { "@types/estree": "^1.0.0", "estree-walker": "^3.0.0", "is-reference": "^3.0.0" } }, "sha512-vKiQ8RRtkl9P+r/+oefh25C3fhybptkHKCZSPlcXiJux2tJF55GnEj3BVn4A5gKfq9NWWXXrxkHBwVPUfH0opw=="],
646
 
647
  "picocolors": ["[email protected]", "", {}, "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA=="],
648
 
649
  "picomatch": ["[email protected]", "", {}, "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA=="],
650
 
 
 
651
  "possible-typed-array-names": ["[email protected]", "", {}, "sha512-/+5VFTchJDoVj3bhoqi6UeymcD00DAwb1nJwamzPvHEszJ4FpF6SNNbUbOS8yI56qHzdV8eK0qEfOSiodkTdxg=="],
652
 
653
  "postcss": ["[email protected]", "", { "dependencies": { "nanoid": "^3.3.11", "picocolors": "^1.1.1", "source-map-js": "^1.2.1" } }, "sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg=="],
@@ -660,10 +757,18 @@
660
 
661
  "prettier-linter-helpers": ["[email protected]", "", { "dependencies": { "fast-diff": "^1.1.2" } }, "sha512-GbK2cP9nraSSUF9N2XwUwqfzlAFlMNYYl+ShE/V+H8a9uNl/oUqB1w2EL54Jh0OlyRSd8RfWYJ3coVS4TROP2w=="],
662
 
 
 
663
  "punycode": ["[email protected]", "", {}, "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg=="],
664
 
 
 
665
  "queue-microtask": ["[email protected]", "", {}, "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A=="],
666
 
 
 
 
 
667
  "readdirp": ["[email protected]", "", { "dependencies": { "picomatch": "^2.2.1" } }, "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA=="],
668
 
669
  "reflect.getprototypeof": ["[email protected]", "", { "dependencies": { "call-bind": "^1.0.8", "define-properties": "^1.2.1", "es-abstract": "^1.23.9", "es-errors": "^1.3.0", "es-object-atoms": "^1.0.0", "get-intrinsic": "^1.2.7", "get-proto": "^1.0.1", "which-builtin-type": "^1.2.1" } }, "sha512-00o4I+DVrefhv+nX0ulyi3biSHCPDe+yLv5o/p6d/UVlirijB8E16FtfwSAi4g3tcqrQ4lRAqQSoFEZJehYEcw=="],
@@ -680,7 +785,9 @@
680
 
681
  "rimraf": ["[email protected]", "", { "dependencies": { "glob": "^7.1.3" }, "bin": { "rimraf": "./bin.js" } }, "sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w=="],
682
 
683
- "rollup": ["[email protected].1", "", { "dependencies": { "@types/estree": "1.0.8" }, "optionalDependencies": { "@rollup/rollup-android-arm-eabi": "4.50.1", "@rollup/rollup-android-arm64": "4.50.1", "@rollup/rollup-darwin-arm64": "4.50.1", "@rollup/rollup-darwin-x64": "4.50.1", "@rollup/rollup-freebsd-arm64": "4.50.1", "@rollup/rollup-freebsd-x64": "4.50.1", "@rollup/rollup-linux-arm-gnueabihf": "4.50.1", "@rollup/rollup-linux-arm-musleabihf": "4.50.1", "@rollup/rollup-linux-arm64-gnu": "4.50.1", "@rollup/rollup-linux-arm64-musl": "4.50.1", "@rollup/rollup-linux-loongarch64-gnu": "4.50.1", "@rollup/rollup-linux-ppc64-gnu": "4.50.1", "@rollup/rollup-linux-riscv64-gnu": "4.50.1", "@rollup/rollup-linux-riscv64-musl": "4.50.1", "@rollup/rollup-linux-s390x-gnu": "4.50.1", "@rollup/rollup-linux-x64-gnu": "4.50.1", "@rollup/rollup-linux-x64-musl": "4.50.1", "@rollup/rollup-openharmony-arm64": "4.50.1", "@rollup/rollup-win32-arm64-msvc": "4.50.1", "@rollup/rollup-win32-ia32-msvc": "4.50.1", "@rollup/rollup-win32-x64-msvc": "4.50.1", "fsevents": "~2.3.2" }, "bin": { "rollup": "dist/bin/rollup" } }, "sha512-78E9voJHwnXQMiQdiqswVLZwJIzdBKJ1GdI5Zx6XwoFKUIk09/sSrr+05QFzvYb8q6Y9pPV45zzDuYa3907TZA=="],
 
 
684
 
685
  "run-parallel": ["[email protected]", "", { "dependencies": { "queue-microtask": "^1.2.2" } }, "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA=="],
686
 
@@ -688,20 +795,30 @@
688
 
689
  "safe-array-concat": ["[email protected]", "", { "dependencies": { "call-bind": "^1.0.8", "call-bound": "^1.0.2", "get-intrinsic": "^1.2.6", "has-symbols": "^1.1.0", "isarray": "^2.0.5" } }, "sha512-AURm5f0jYEOydBj7VQlVvDrjeFgthDdEF5H1dP+6mNpoXOMo1quQqJ4wvJDyRZ9+pO3kGWoOdmV08cSv2aJV6Q=="],
690
 
 
 
691
  "safe-push-apply": ["[email protected]", "", { "dependencies": { "es-errors": "^1.3.0", "isarray": "^2.0.5" } }, "sha512-iKE9w/Z7xCzUMIZqdBsp6pEQvwuEebH4vdpjcDWnyzaI6yl6O9FHvVpmGelvEHNsoY6wGblkxR6Zty/h00WiSA=="],
692
 
693
  "safe-regex-test": ["[email protected]", "", { "dependencies": { "call-bound": "^1.0.2", "es-errors": "^1.3.0", "is-regex": "^1.2.1" } }, "sha512-x/+Cz4YrimQxQccJf5mKEbIa1NzeCRNI5Ecl/ekmlYaampdNLPalVyIcCZNNH3MvmqBugV5TMYZXv0ljslUlaw=="],
694
 
 
 
695
  "sander": ["[email protected]", "", { "dependencies": { "es6-promise": "^3.1.2", "graceful-fs": "^4.1.3", "mkdirp": "^0.5.1", "rimraf": "^2.5.2" } }, "sha512-3lVqBir7WuKDHGrKRDn/1Ye3kwpXaDOMsiRP1wd6wpZW56gJhsbp5RqQpA6JG/P+pkXizygnr1dKR8vzWaVsfA=="],
696
 
697
  "semver": ["[email protected]", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA=="],
698
 
 
 
 
 
699
  "set-function-length": ["[email protected]", "", { "dependencies": { "define-data-property": "^1.1.4", "es-errors": "^1.3.0", "function-bind": "^1.1.2", "get-intrinsic": "^1.2.4", "gopd": "^1.0.1", "has-property-descriptors": "^1.0.2" } }, "sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg=="],
700
 
701
  "set-function-name": ["[email protected]", "", { "dependencies": { "define-data-property": "^1.1.4", "es-errors": "^1.3.0", "functions-have-names": "^1.2.3", "has-property-descriptors": "^1.0.2" } }, "sha512-7PGFlmtwsEADb0WYyvCMa1t+yke6daIG4Wirafur5kcf+MhUnPms1UeR0CKQdTZD81yESwMHbtn+TR+dMviakQ=="],
702
 
703
  "set-proto": ["[email protected]", "", { "dependencies": { "dunder-proto": "^1.0.1", "es-errors": "^1.3.0", "es-object-atoms": "^1.0.0" } }, "sha512-RJRdvCo6IAnPdsvP/7m6bsQqNnn1FCBX5ZNtFL98MmFF/4xAIJTIg1YbHW5DC2W5SKZanrC6i4HsJqlajw/dZw=="],
704
 
 
 
705
  "shebang-command": ["[email protected]", "", { "dependencies": { "shebang-regex": "^3.0.0" } }, "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA=="],
706
 
707
  "shebang-regex": ["[email protected]", "", {}, "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A=="],
@@ -714,16 +831,22 @@
714
 
715
  "side-channel-weakmap": ["[email protected]", "", { "dependencies": { "call-bound": "^1.0.2", "es-errors": "^1.3.0", "get-intrinsic": "^1.2.5", "object-inspect": "^1.13.3", "side-channel-map": "^1.0.1" } }, "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A=="],
716
 
 
 
717
  "simple-wcswidth": ["[email protected]", "", {}, "sha512-j7piyCjAeTDSjzTSQ7DokZtMNwNlEAyxqSZeCS+CXH7fJ4jx3FuJ/mTW3mE+6JLs4VJBbcll0Kjn+KXI5t21Iw=="],
718
 
719
  "sorcery": ["[email protected]", "", { "dependencies": { "@jridgewell/sourcemap-codec": "^1.4.14", "buffer-crc32": "^1.0.0", "minimist": "^1.2.0", "sander": "^0.5.0" }, "bin": { "sorcery": "bin/sorcery" } }, "sha512-o7npfeJE6wi6J9l0/5LKshFzZ2rMatRiCDwYeDQaOzqdzRJwALhX7mk/A/ecg6wjMu7wdZbmXfD2S/vpOg0bdQ=="],
720
 
721
  "source-map-js": ["[email protected]", "", {}, "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA=="],
722
 
 
 
723
  "stop-iteration-iterator": ["[email protected]", "", { "dependencies": { "es-errors": "^1.3.0", "internal-slot": "^1.1.0" } }, "sha512-eLoXW/DHyl62zxY4SCaIgnRhuMr6ri4juEYARS8E6sCEqzKpOiE521Ucofdx+KnDZl5xmvGYaaKCk5FEOxJCoQ=="],
724
 
725
  "string-width": ["[email protected]", "", { "dependencies": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", "strip-ansi": "^6.0.1" } }, "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g=="],
726
 
 
 
727
  "string.prototype.trim": ["[email protected]", "", { "dependencies": { "call-bind": "^1.0.8", "call-bound": "^1.0.2", "define-data-property": "^1.1.4", "define-properties": "^1.2.1", "es-abstract": "^1.23.5", "es-object-atoms": "^1.0.0", "has-property-descriptors": "^1.0.2" } }, "sha512-Rs66F0P/1kedk5lyYyH9uBzuiI/kNRmwJAR9quK6VOtIpZ2G+hMZd+HQbbv25MgCA6gEffoMZYxlTod4WcdrKA=="],
728
 
729
  "string.prototype.trimend": ["[email protected]", "", { "dependencies": { "call-bind": "^1.0.8", "call-bound": "^1.0.2", "define-properties": "^1.2.1", "es-object-atoms": "^1.0.0" } }, "sha512-G7Ok5C6E/j4SGfyLCloXTrngQIQU3PWtXGst3yM7Bea9FRURf1S42ZHlZZtsNque2FN2PoUhfZXYLNWwEr4dLQ=="],
@@ -732,6 +855,8 @@
732
 
733
  "strip-ansi": ["[email protected]", "", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="],
734
 
 
 
735
  "strip-bom": ["[email protected]", "", {}, "sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA=="],
736
 
737
  "strip-indent": ["[email protected]", "", { "dependencies": { "min-indent": "^1.0.0" } }, "sha512-laJTa3Jb+VQpaC6DseHhF7dXVqHTfJPCRDaEbid/drOhgitgYku/letMUqOXFoWV0zIIUbjpdH2t+tYj4bQMRQ=="],
@@ -758,12 +883,16 @@
758
 
759
  "to-regex-range": ["[email protected]", "", { "dependencies": { "is-number": "^7.0.0" } }, "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ=="],
760
 
 
 
761
  "ts-api-utils": ["[email protected]", "", { "peerDependencies": { "typescript": ">=4.8.4" } }, "sha512-CUgTZL1irw8u29bzrOD/nH85jqyc74D6SshFgujOIA7osm2Rz7dYH77agkx7H4FBNxDq7Cjf+IjaX/8zwFW+ZQ=="],
762
 
763
  "tsconfig-paths": ["[email protected]", "", { "dependencies": { "@types/json5": "^0.0.29", "json5": "^1.0.2", "minimist": "^1.2.6", "strip-bom": "^3.0.0" } }, "sha512-2Ac2RgzDe/cn48GvOe3M+o82pEFewD3UPbyoUHHdKasHwJKjds4fLXWf/Ux5kATBKN20oaFGu+jbElp1pos0mg=="],
764
 
765
  "type-check": ["[email protected]", "", { "dependencies": { "prelude-ls": "^1.2.1" } }, "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew=="],
766
 
 
 
767
  "typed-array-buffer": ["[email protected]", "", { "dependencies": { "call-bound": "^1.0.3", "es-errors": "^1.3.0", "is-typed-array": "^1.1.14" } }, "sha512-nAYYwfY3qnzX30IkA6AQZjVbtK6duGontcQm1WSG1MD94YLqK0515GNApXkoxKOWMusVssAHWLh9SeaoefYFGw=="],
768
 
769
  "typed-array-byte-length": ["[email protected]", "", { "dependencies": { "call-bind": "^1.0.8", "for-each": "^0.3.3", "gopd": "^1.2.0", "has-proto": "^1.2.0", "is-typed-array": "^1.1.14" } }, "sha512-BaXgOuIxz8n8pIq3e7Atg/7s+DpiYrxn4vdot3w9KbnBhcRQq6o3xemQdIfynqSeXeDrF32x+WvfzmOjPiY9lg=="],
@@ -776,13 +905,17 @@
776
 
777
  "unbox-primitive": ["[email protected]", "", { "dependencies": { "call-bound": "^1.0.3", "has-bigints": "^1.0.2", "has-symbols": "^1.1.0", "which-boxed-primitive": "^1.1.1" } }, "sha512-nWJ91DjeOkej/TA8pXQ3myruKpKEYgqvpw9lz4OPHj/NWFNluYrjbz9j01CJ8yKQd2g4jFoOkINCTW2I5LEEyw=="],
778
 
779
- "undici-types": ["undici-types@7.11.0", "", {}, "sha512-kt1ZriHTi7MU+Z/r9DOdAI3ONdaR3M3csEaRc6ewa4f4dTvX4cQCbJ4NkEn0ohE4hHtq85+PhPSTY+pO/1PwgA=="],
 
 
780
 
781
  "uri-js": ["[email protected]", "", { "dependencies": { "punycode": "^2.1.0" } }, "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg=="],
782
 
783
  "uuid": ["[email protected]", "", { "bin": { "uuid": "dist/bin/uuid" } }, "sha512-8XkAphELsDnEGrDxUOHB3RGvXz6TeuYSGEZBOjtTtPm2lwhGBjLgOzLHB63IUWfBpNucQjND6d3AOudO+H3RWQ=="],
784
 
785
- "vibegame": ["vibegame@0.1.6", "", { "dependencies": { "@dimforge/rapier3d-compat": "^0.18.2", "gsap": "^3.13.0", "postprocessing": "^6.37.8", "zod": "^4.1.5" }, "peerDependencies": { "bitecs": ">=0.3.40", "three": ">=0.170.0" } }, "sha512-xls8UcndcU2jU4YlCOUR3iDQCQozHtvpuspwHrOJQiRmdozFh534WcqA03RIh0MlWpyCqNy2CWH0OtKEyAyVYg=="],
 
 
786
 
787
  "vite": ["[email protected]", "", { "dependencies": { "esbuild": "^0.21.3", "postcss": "^8.4.43", "rollup": "^4.20.0" }, "optionalDependencies": { "fsevents": "~2.3.3" }, "peerDependencies": { "@types/node": "^18.0.0 || >=20.0.0", "less": "*", "lightningcss": "^1.21.0", "sass": "*", "sass-embedded": "*", "stylus": "*", "sugarss": "*", "terser": "^5.4.0" }, "optionalPeers": ["@types/node", "less", "lightningcss", "sass", "sass-embedded", "stylus", "sugarss", "terser"], "bin": { "vite": "bin/vite.js" } }, "sha512-j3lYzGC3P+B5Yfy/pfKNgVEg4+UtcIJcVRt2cDjIOmhLourAqPqf8P7acgxeiSgUB7E3p2P8/3gNIgDLpwzs4g=="],
788
 
@@ -800,6 +933,10 @@
800
 
801
  "word-wrap": ["[email protected]", "", {}, "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA=="],
802
 
 
 
 
 
803
  "wrappy": ["[email protected]", "", {}, "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ=="],
804
 
805
  "ws": ["[email protected]", "", { "peerDependencies": { "bufferutil": "^4.0.1", "utf-8-validate": ">=5.0.2" }, "optionalPeers": ["bufferutil", "utf-8-validate"] }, "sha512-PEIGCY5tSlUt50cqyMXfCzX+oOPqN0vuGqWzbcJ2xvnkzkq46oOpz7dQaTDBdfICb4N14+GARUDw2XV2N4tvzg=="],
@@ -814,16 +951,30 @@
814
 
815
  "@eslint/eslintrc/ignore": ["[email protected]", "", {}, "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g=="],
816
 
 
 
 
 
817
  "@langchain/core/zod": ["[email protected]", "", {}, "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ=="],
818
 
819
  "@langchain/langgraph/zod": ["[email protected]", "", {}, "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ=="],
820
 
821
  "@langchain/langgraph-sdk/uuid": ["[email protected]", "", { "bin": { "uuid": "dist/bin/uuid" } }, "sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA=="],
822
 
 
 
 
 
 
 
 
 
823
  "@typescript-eslint/typescript-estree/minimatch": ["[email protected]", "", { "dependencies": { "brace-expansion": "^2.0.1" } }, "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow=="],
824
 
825
  "@typescript-eslint/typescript-estree/semver": ["[email protected]", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA=="],
826
 
 
 
827
  "chalk/ansi-styles": ["[email protected]", "", { "dependencies": { "color-convert": "^2.0.1" } }, "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg=="],
828
 
829
  "chokidar/glob-parent": ["[email protected]", "", { "dependencies": { "is-glob": "^4.0.1" } }, "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow=="],
@@ -838,8 +989,32 @@
838
 
839
  "fast-glob/glob-parent": ["[email protected]", "", { "dependencies": { "is-glob": "^4.0.1" } }, "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow=="],
840
 
 
 
841
  "langsmith/semver": ["[email protected]", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA=="],
842
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
843
  "@typescript-eslint/typescript-estree/minimatch/brace-expansion": ["[email protected]", "", { "dependencies": { "balanced-match": "^1.0.0" } }, "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ=="],
 
 
 
 
 
 
844
  }
845
  }
 
8
  "@huggingface/inference": "^4.8.0",
9
  "@langchain/core": "^0.3.75",
10
  "@langchain/langgraph": "^0.4.9",
11
+ "@langchain/mcp-adapters": "^0.6.0",
12
+ "@modelcontextprotocol/sdk": "^0.6.0",
13
+ "@modelcontextprotocol/server-filesystem": "^0.6.2",
14
  "@types/marked": "^6.0.0",
15
  "@types/node": "^24.3.3",
16
  "gsap": "^3.13.0",
17
  "marked": "^16.2.1",
18
  "monaco-editor": "^0.50.0",
19
  "svelte-splitpanes": "^8.0.5",
20
+ "vibegame": "^0.1.7",
21
  "zod": "^4.1.8",
22
  },
23
  "devDependencies": {
 
127
 
128
  "@humanwhocodes/retry": ["@humanwhocodes/[email protected]", "", {}, "sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ=="],
129
 
130
+ "@isaacs/cliui": ["@isaacs/[email protected]", "", { "dependencies": { "string-width": "^5.1.2", "string-width-cjs": "npm:string-width@^4.2.0", "strip-ansi": "^7.0.1", "strip-ansi-cjs": "npm:strip-ansi@^6.0.1", "wrap-ansi": "^8.1.0", "wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0" } }, "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA=="],
131
+
132
  "@jridgewell/gen-mapping": ["@jridgewell/[email protected]", "", { "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.0", "@jridgewell/trace-mapping": "^0.3.24" } }, "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA=="],
133
 
134
  "@jridgewell/resolve-uri": ["@jridgewell/[email protected]", "", {}, "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw=="],
 
137
 
138
  "@jridgewell/trace-mapping": ["@jridgewell/[email protected]", "", { "dependencies": { "@jridgewell/resolve-uri": "^3.1.0", "@jridgewell/sourcemap-codec": "^1.4.14" } }, "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw=="],
139
 
140
+ "@langchain/core": ["@langchain/[email protected].76", "", { "dependencies": { "@cfworker/json-schema": "^4.0.2", "ansi-styles": "^5.0.0", "camelcase": "6", "decamelize": "1.2.0", "js-tiktoken": "^1.0.12", "langsmith": "^0.3.67", "mustache": "^4.2.0", "p-queue": "^6.6.2", "p-retry": "4", "uuid": "^10.0.0", "zod": "^3.25.32", "zod-to-json-schema": "^3.22.3" } }, "sha512-jYQ9Djbmp/iQNzZOtXc+/WACsBKV2Bha83+p7TddN+aC7Sb7jTRENoNWypnIFCd5EaEn1XVQazuXlaWZoxkA+A=="],
141
 
142
  "@langchain/langgraph": ["@langchain/[email protected]", "", { "dependencies": { "@langchain/langgraph-checkpoint": "^0.1.1", "@langchain/langgraph-sdk": "~0.1.0", "uuid": "^10.0.0", "zod": "^3.25.32" }, "peerDependencies": { "@langchain/core": ">=0.3.58 < 0.4.0", "zod-to-json-schema": "^3.x" }, "optionalPeers": ["zod-to-json-schema"] }, "sha512-+rcdTGi4Ium4X/VtIX3Zw4RhxEkYWpwUyz806V6rffjHOAMamg6/WZDxpJbrP33RV/wJG1GH12Z29oX3Pqq3Aw=="],
143
 
144
  "@langchain/langgraph-checkpoint": ["@langchain/[email protected]", "", { "dependencies": { "uuid": "^10.0.0" }, "peerDependencies": { "@langchain/core": ">=0.2.31 <0.4.0 || ^1.0.0-alpha" } }, "sha512-h2bP0RUikQZu0Um1ZUPErQLXyhzroJqKRbRcxYRTAh49oNlsfeq4A3K4YEDRbGGuyPZI/Jiqwhks1wZwY73AZw=="],
145
 
146
+ "@langchain/langgraph-sdk": ["@langchain/[email protected].4", "", { "dependencies": { "@types/json-schema": "^7.0.15", "p-queue": "^6.6.2", "p-retry": "4", "uuid": "^9.0.0" }, "peerDependencies": { "@langchain/core": ">=0.2.31 <0.4.0 || ^1.0.0-alpha", "react": "^18 || ^19", "react-dom": "^18 || ^19" }, "optionalPeers": ["@langchain/core", "react", "react-dom"] }, "sha512-pITGjh6ayNLgtJ8Ant2lyFZ/o94ePlrH8zNgMLeiDqdRDjoqSehW5k3SZucpG4n9U12qR/WtPXDzbyHa93ISbA=="],
147
+
148
+ "@langchain/mcp-adapters": ["@langchain/[email protected]", "", { "dependencies": { "@modelcontextprotocol/sdk": "^1.12.1", "debug": "^4.4.0", "zod": "^3.24.2" }, "optionalDependencies": { "extended-eventsource": "^1.x" }, "peerDependencies": { "@langchain/core": "^0.3.66" } }, "sha512-NHQNH9NciLhxlCnL/4HDebiYT3UQvpBfF5KPlIi/uSXn8te/bYjPV64gUyAloNNo+fjj4qDvKP1/nHj0r7fKFw=="],
149
+
150
+ "@modelcontextprotocol/sdk": ["@modelcontextprotocol/[email protected]", "", { "dependencies": { "content-type": "^1.0.5", "raw-body": "^3.0.0", "zod": "^3.23.8" } }, "sha512-OkVXMix3EIbB5Z6yife2XTrSlOnVvCLR1Kg91I4pYFEsV9RbnoyQVScXCuVhGaZHOnTZgso8lMQN1Po2TadGKQ=="],
151
+
152
+ "@modelcontextprotocol/server-filesystem": ["@modelcontextprotocol/[email protected]", "", { "dependencies": { "@modelcontextprotocol/sdk": "1.0.1", "glob": "^10.3.10", "zod-to-json-schema": "^3.23.5" }, "bin": { "mcp-server-filesystem": "dist/index.js" } }, "sha512-qBrhLY524WEFmIg+s2O6bPIFBK8Dy0l20yjQ0reYN1moWYNy28kNyYgWVgTiSj4QvpMq2LFZs6foDHrG1Kgt2w=="],
153
 
154
  "@nodelib/fs.scandir": ["@nodelib/[email protected]", "", { "dependencies": { "@nodelib/fs.stat": "2.0.5", "run-parallel": "^1.1.9" } }, "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g=="],
155
 
 
157
 
158
  "@nodelib/fs.walk": ["@nodelib/[email protected]", "", { "dependencies": { "@nodelib/fs.scandir": "2.1.5", "fastq": "^1.6.0" } }, "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg=="],
159
 
160
+ "@pkgjs/parseargs": ["@pkgjs/[email protected]", "", {}, "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg=="],
161
+
162
  "@pkgr/core": ["@pkgr/[email protected]", "", {}, "sha512-QNqXyfVS2wm9hweSYD2O7F0G06uurj9kZ96TRQE5Y9hU7+tgdZwIkbAKc5Ocy1HxEY2kuDQa6cQ1WRs/O5LFKA=="],
163
 
164
+ "@rollup/rollup-android-arm-eabi": ["@rollup/[email protected].2", "", { "os": "android", "cpu": "arm" }, "sha512-uLN8NAiFVIRKX9ZQha8wy6UUs06UNSZ32xj6giK/rmMXAgKahwExvK6SsmgU5/brh4w/nSgj8e0k3c1HBQpa0A=="],
165
 
166
+ "@rollup/rollup-android-arm64": ["@rollup/[email protected].2", "", { "os": "android", "cpu": "arm64" }, "sha512-oEouqQk2/zxxj22PNcGSskya+3kV0ZKH+nQxuCCOGJ4oTXBdNTbv+f/E3c74cNLeMO1S5wVWacSws10TTSB77g=="],
167
 
168
+ "@rollup/rollup-darwin-arm64": ["@rollup/[email protected].2", "", { "os": "darwin", "cpu": "arm64" }, "sha512-OZuTVTpj3CDSIxmPgGH8en/XtirV5nfljHZ3wrNwvgkT5DQLhIKAeuFSiwtbMto6oVexV0k1F1zqURPKf5rI1Q=="],
169
 
170
+ "@rollup/rollup-darwin-x64": ["@rollup/[email protected].2", "", { "os": "darwin", "cpu": "x64" }, "sha512-Wa/Wn8RFkIkr1vy1k1PB//VYhLnlnn5eaJkfTQKivirOvzu5uVd2It01ukeQstMursuz7S1bU+8WW+1UPXpa8A=="],
171
 
172
+ "@rollup/rollup-freebsd-arm64": ["@rollup/[email protected].2", "", { "os": "freebsd", "cpu": "arm64" }, "sha512-QkzxvH3kYN9J1w7D1A+yIMdI1pPekD+pWx7G5rXgnIlQ1TVYVC6hLl7SOV9pi5q9uIDF9AuIGkuzcbF7+fAhow=="],
173
 
174
+ "@rollup/rollup-freebsd-x64": ["@rollup/[email protected].2", "", { "os": "freebsd", "cpu": "x64" }, "sha512-dkYXB0c2XAS3a3jmyDkX4Jk0m7gWLFzq1C3qUnJJ38AyxIF5G/dyS4N9B30nvFseCfgtCEdbYFhk0ChoCGxPog=="],
175
 
176
+ "@rollup/rollup-linux-arm-gnueabihf": ["@rollup/[email protected].2", "", { "os": "linux", "cpu": "arm" }, "sha512-9VlPY/BN3AgbukfVHAB8zNFWB/lKEuvzRo1NKev0Po8sYFKx0i+AQlCYftgEjcL43F2h9Ui1ZSdVBc4En/sP2w=="],
177
 
178
+ "@rollup/rollup-linux-arm-musleabihf": ["@rollup/[email protected].2", "", { "os": "linux", "cpu": "arm" }, "sha512-+GdKWOvsifaYNlIVf07QYan1J5F141+vGm5/Y8b9uCZnG/nxoGqgCmR24mv0koIWWuqvFYnbURRqw1lv7IBINw=="],
179
 
180
+ "@rollup/rollup-linux-arm64-gnu": ["@rollup/[email protected].2", "", { "os": "linux", "cpu": "arm64" }, "sha512-df0Eou14ojtUdLQdPFnymEQteENwSJAdLf5KCDrmZNsy1c3YaCNaJvYsEUHnrg+/DLBH612/R0xd3dD03uz2dg=="],
181
 
182
+ "@rollup/rollup-linux-arm64-musl": ["@rollup/[email protected].2", "", { "os": "linux", "cpu": "arm64" }, "sha512-iPeouV0UIDtz8j1YFR4OJ/zf7evjauqv7jQ/EFs0ClIyL+by++hiaDAfFipjOgyz6y6xbDvJuiU4HwpVMpRFDQ=="],
183
 
184
+ "@rollup/rollup-linux-loong64-gnu": ["@rollup/rollup-linux-loong64[email protected].2", "", { "os": "linux", "cpu": "none" }, "sha512-OL6KaNvBopLlj5fTa5D5bau4W82f+1TyTZRr2BdnfsrnQnmdxh4okMxR2DcDkJuh4KeoQZVuvHvzuD/lyLn2Kw=="],
185
 
186
+ "@rollup/rollup-linux-ppc64-gnu": ["@rollup/[email protected].2", "", { "os": "linux", "cpu": "ppc64" }, "sha512-I21VJl1w6z/K5OTRl6aS9DDsqezEZ/yKpbqlvfHbW0CEF5IL8ATBMuUx6/mp683rKTK8thjs/0BaNrZLXetLag=="],
187
 
188
+ "@rollup/rollup-linux-riscv64-gnu": ["@rollup/[email protected].2", "", { "os": "linux", "cpu": "none" }, "sha512-Hq6aQJT/qFFHrYMjS20nV+9SKrXL2lvFBENZoKfoTH2kKDOJqff5OSJr4x72ZaG/uUn+XmBnGhfr4lwMRrmqCQ=="],
189
 
190
+ "@rollup/rollup-linux-riscv64-musl": ["@rollup/[email protected].2", "", { "os": "linux", "cpu": "none" }, "sha512-82rBSEXRv5qtKyr0xZ/YMF531oj2AIpLZkeNYxmKNN6I2sVE9PGegN99tYDLK2fYHJITL1P2Lgb4ZXnv0PjQvw=="],
191
 
192
+ "@rollup/rollup-linux-s390x-gnu": ["@rollup/[email protected].2", "", { "os": "linux", "cpu": "s390x" }, "sha512-4Q3S3Hy7pC6uaRo9gtXUTJ+EKo9AKs3BXKc2jYypEcMQ49gDPFU2P1ariX9SEtBzE5egIX6fSUmbmGazwBVF9w=="],
193
 
194
+ "@rollup/rollup-linux-x64-gnu": ["@rollup/[email protected].2", "", { "os": "linux", "cpu": "x64" }, "sha512-9Jie/At6qk70dNIcopcL4p+1UirusEtznpNtcq/u/C5cC4HBX7qSGsYIcG6bdxj15EYWhHiu02YvmdPzylIZlA=="],
195
 
196
+ "@rollup/rollup-linux-x64-musl": ["@rollup/[email protected].2", "", { "os": "linux", "cpu": "x64" }, "sha512-HPNJwxPL3EmhzeAnsWQCM3DcoqOz3/IC6de9rWfGR8ZCuEHETi9km66bH/wG3YH0V3nyzyFEGUZeL5PKyy4xvw=="],
197
 
198
+ "@rollup/rollup-openharmony-arm64": ["@rollup/[email protected].2", "", { "os": "none", "cpu": "arm64" }, "sha512-nMKvq6FRHSzYfKLHZ+cChowlEkR2lj/V0jYj9JnGUVPL2/mIeFGmVM2mLaFeNa5Jev7W7TovXqXIG2d39y1KYA=="],
199
 
200
+ "@rollup/rollup-win32-arm64-msvc": ["@rollup/[email protected].2", "", { "os": "win32", "cpu": "arm64" }, "sha512-eFUvvnTYEKeTyHEijQKz81bLrUQOXKZqECeiWH6tb8eXXbZk+CXSG2aFrig2BQ/pjiVRj36zysjgILkqarS2YA=="],
201
 
202
+ "@rollup/rollup-win32-ia32-msvc": ["@rollup/[email protected].2", "", { "os": "win32", "cpu": "ia32" }, "sha512-cBaWmXqyfRhH8zmUxK3d3sAhEWLrtMjWBRwdMMHJIXSjvjLKvv49adxiEz+FJ8AP90apSDDBx2Tyd/WylV6ikA=="],
203
 
204
+ "@rollup/rollup-win32-x64-msvc": ["@rollup/[email protected].2", "", { "os": "win32", "cpu": "x64" }, "sha512-APwKy6YUhvZaEoHyM+9xqmTpviEI+9eL7LoCH+aLcvWYHJ663qG5zx7WzWZY+a9qkg5JtzcMyJ9z0WtQBMDmgA=="],
205
 
206
  "@rtsao/scc": ["@rtsao/[email protected]", "", {}, "sha512-zt6OdqaDoOnJ1ZYsCYGt9YmWzDXl4vQdKTyJev62gFhRGKdx7mcT54V9KIjg+d2wi9EXsPvAPKe7i7WjfVWB8g=="],
207
 
 
219
 
220
  "@types/marked": ["@types/[email protected]", "", { "dependencies": { "marked": "*" } }, "sha512-jmjpa4BwUsmhxcfsgUit/7A9KbrC48Q0q8KvnY107ogcjGgTFDlIL3RpihNpx2Mu1hM4mdFQjoVc4O6JoGKHsA=="],
221
 
222
+ "@types/node": ["@types/node@24.5.0", "", { "dependencies": { "undici-types": "~7.12.0" } }, "sha512-y1dMvuvJspJiPSDZUQ+WMBvF7dpnEqN4x9DDC9ie5Fs/HUZJA3wFp7EhHoVaKX/iI0cRoECV8X2jL8zi0xrHCg=="],
223
 
224
  "@types/pug": ["@types/[email protected]", "", {}, "sha512-Sk/uYFOBAB7mb74XcpizmH0KOR2Pv3D2Hmrh1Dmy5BmK3MpdSa5kqZcg6EKBdklU0bFXX9gCfzvpnyUehrPIuA=="],
225
 
 
231
 
232
  "@types/ws": ["@types/[email protected]", "", { "dependencies": { "@types/node": "*" } }, "sha512-ThVF6DCVhA8kUGy+aazFQ4kXQ7E1Ty7A3ypFOe0IcJV8O/M511G99AW24irKrW56Wt44yG9+ij8FaqoBGkuBXg=="],
233
 
234
+ "@typescript-eslint/eslint-plugin": ["@typescript-eslint/eslint-plugin@8.44.0", "", { "dependencies": { "@eslint-community/regexpp": "^4.10.0", "@typescript-eslint/scope-manager": "8.44.0", "@typescript-eslint/type-utils": "8.44.0", "@typescript-eslint/utils": "8.44.0", "@typescript-eslint/visitor-keys": "8.44.0", "graphemer": "^1.4.0", "ignore": "^7.0.0", "natural-compare": "^1.4.0", "ts-api-utils": "^2.1.0" }, "peerDependencies": { "@typescript-eslint/parser": "^8.44.0", "eslint": "^8.57.0 || ^9.0.0", "typescript": ">=4.8.4 <6.0.0" } }, "sha512-EGDAOGX+uwwekcS0iyxVDmRV9HX6FLSM5kzrAToLTsr9OWCIKG/y3lQheCq18yZ5Xh78rRKJiEpP0ZaCs4ryOQ=="],
235
+
236
+ "@typescript-eslint/parser": ["@typescript-eslint/[email protected]", "", { "dependencies": { "@typescript-eslint/scope-manager": "8.44.0", "@typescript-eslint/types": "8.44.0", "@typescript-eslint/typescript-estree": "8.44.0", "@typescript-eslint/visitor-keys": "8.44.0", "debug": "^4.3.4" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0", "typescript": ">=4.8.4 <6.0.0" } }, "sha512-VGMpFQGUQWYT9LfnPcX8ouFojyrZ/2w3K5BucvxL/spdNehccKhB4jUyB1yBCXpr2XFm0jkECxgrpXBW2ipoAw=="],
237
 
238
+ "@typescript-eslint/project-service": ["@typescript-eslint/project-service@8.44.0", "", { "dependencies": { "@typescript-eslint/tsconfig-utils": "^8.44.0", "@typescript-eslint/types": "^8.44.0", "debug": "^4.3.4" }, "peerDependencies": { "typescript": ">=4.8.4 <6.0.0" } }, "sha512-ZeaGNraRsq10GuEohKTo4295Z/SuGcSq2LzfGlqiuEvfArzo/VRrT0ZaJsVPuKZ55lVbNk8U6FcL+ZMH8CoyVA=="],
239
 
240
+ "@typescript-eslint/scope-manager": ["@typescript-eslint/scope-manager@8.44.0", "", { "dependencies": { "@typescript-eslint/types": "8.44.0", "@typescript-eslint/visitor-keys": "8.44.0" } }, "sha512-87Jv3E+al8wpD+rIdVJm/ItDBe/Im09zXIjFoipOjr5gHUhJmTzfFLuTJ/nPTMc2Srsroy4IBXwcTCHyRR7KzA=="],
241
 
242
+ "@typescript-eslint/tsconfig-utils": ["@typescript-eslint/tsconfig-utils@8.44.0", "", { "peerDependencies": { "typescript": ">=4.8.4 <6.0.0" } }, "sha512-x5Y0+AuEPqAInc6yd0n5DAcvtoQ/vyaGwuX5HE9n6qAefk1GaedqrLQF8kQGylLUb9pnZyLf+iEiL9fr8APDtQ=="],
243
 
244
+ "@typescript-eslint/type-utils": ["@typescript-eslint/type-utils@8.44.0", "", { "dependencies": { "@typescript-eslint/types": "8.44.0", "@typescript-eslint/typescript-estree": "8.44.0", "@typescript-eslint/utils": "8.44.0", "debug": "^4.3.4", "ts-api-utils": "^2.1.0" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0", "typescript": ">=4.8.4 <6.0.0" } }, "sha512-9cwsoSxJ8Sak67Be/hD2RNt/fsqmWnNE1iHohG8lxqLSNY8xNfyY7wloo5zpW3Nu9hxVgURevqfcH6vvKCt6yg=="],
245
 
246
+ "@typescript-eslint/types": ["@typescript-eslint/types@8.44.0", "", {}, "sha512-ZSl2efn44VsYM0MfDQe68RKzBz75NPgLQXuGypmym6QVOWL5kegTZuZ02xRAT9T+onqvM6T8CdQk0OwYMB6ZvA=="],
247
 
248
+ "@typescript-eslint/typescript-estree": ["@typescript-eslint/[email protected]", "", { "dependencies": { "@typescript-eslint/project-service": "8.44.0", "@typescript-eslint/tsconfig-utils": "8.44.0", "@typescript-eslint/types": "8.44.0", "@typescript-eslint/visitor-keys": "8.44.0", "debug": "^4.3.4", "fast-glob": "^3.3.2", "is-glob": "^4.0.3", "minimatch": "^9.0.4", "semver": "^7.6.0", "ts-api-utils": "^2.1.0" }, "peerDependencies": { "typescript": ">=4.8.4 <6.0.0" } }, "sha512-lqNj6SgnGcQZwL4/SBJ3xdPEfcBuhCG8zdcwCPgYcmiPLgokiNDKlbPzCwEwu7m279J/lBYWtDYL+87OEfn8Jw=="],
249
 
250
+ "@typescript-eslint/utils": ["@typescript-eslint/utils@8.44.0", "", { "dependencies": { "@eslint-community/eslint-utils": "^4.7.0", "@typescript-eslint/scope-manager": "8.44.0", "@typescript-eslint/types": "8.44.0", "@typescript-eslint/typescript-estree": "8.44.0" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0", "typescript": ">=4.8.4 <6.0.0" } }, "sha512-nktOlVcg3ALo0mYlV+L7sWUD58KG4CMj1rb2HUVOO4aL3K/6wcD+NERqd0rrA5Vg06b42YhF6cFxeixsp9Riqg=="],
251
 
252
+ "@typescript-eslint/visitor-keys": ["@typescript-eslint/visitor-keys@8.44.0", "", { "dependencies": { "@typescript-eslint/types": "8.44.0", "eslint-visitor-keys": "^4.2.1" } }, "sha512-zaz9u8EJ4GBmnehlrpoKvj/E3dNbuQ7q0ucyZImm3cLqJ8INTc970B1qEqDX/Rzq65r3TvVTN7kHWPBoyW7DWw=="],
253
 
254
+ "accepts": ["accepts@2.0.0", "", { "dependencies": { "mime-types": "^3.0.0", "negotiator": "^1.0.0" } }, "sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng=="],
255
 
256
  "acorn": ["[email protected]", "", { "bin": { "acorn": "bin/acorn" } }, "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg=="],
257
 
 
295
 
296
  "bitecs": ["[email protected]", "", {}, "sha512-wAylY4pNfX8IeIH5phtwt1lUNtHKrkoSNrArI7Ris2Y4nEQWFIVvXdgAuqprEg9bq8Wolmlj0gVfeG6MFmtI2Q=="],
297
 
298
+ "body-parser": ["[email protected]", "", { "dependencies": { "bytes": "^3.1.2", "content-type": "^1.0.5", "debug": "^4.4.0", "http-errors": "^2.0.0", "iconv-lite": "^0.6.3", "on-finished": "^2.4.1", "qs": "^6.14.0", "raw-body": "^3.0.0", "type-is": "^2.0.0" } }, "sha512-02qvAaxv8tp7fBa/mw1ga98OGm+eCbqzJOKoRt70sLmfEEi+jyBYVTDGfCL/k06/4EMk/z01gCe7HoCH/f2LTg=="],
299
+
300
  "brace-expansion": ["[email protected]", "", { "dependencies": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" } }, "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg=="],
301
 
302
  "braces": ["[email protected]", "", { "dependencies": { "fill-range": "^7.1.1" } }, "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA=="],
 
305
 
306
  "bun-types": ["[email protected]", "", { "dependencies": { "@types/node": "*" }, "peerDependencies": { "@types/react": "^19" } }, "sha512-hwaAu8tct/Zn6Zft4U9BsZcXkYomzpHJX28ofvx7k0Zz2HNz54n1n+tDgxoWFGB4PcFvJXJQloPhaV2eP3Q6EA=="],
307
 
308
+ "bytes": ["[email protected]", "", {}, "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg=="],
309
+
310
  "call-bind": ["[email protected]", "", { "dependencies": { "call-bind-apply-helpers": "^1.0.0", "es-define-property": "^1.0.0", "get-intrinsic": "^1.2.4", "set-function-length": "^1.2.2" } }, "sha512-oKlSFMcMwpUg2ednkhQ454wfWiU/ul3CkJe/PEHcTKuiX6RpbehUiFMXu13HalGZxfUwCQzZG747YXBn1im9ww=="],
311
 
312
  "call-bind-apply-helpers": ["[email protected]", "", { "dependencies": { "es-errors": "^1.3.0", "function-bind": "^1.1.2" } }, "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ=="],
 
333
 
334
  "console-table-printer": ["[email protected]", "", { "dependencies": { "simple-wcswidth": "^1.0.1" } }, "sha512-MCBl5HNVaFuuHW6FGbL/4fB7N/ormCy+tQ+sxTrF6QtSbSNETvPuOVbkJBhzDgYhvjWGrTma4eYJa37ZuoQsPw=="],
335
 
336
+ "content-disposition": ["[email protected]", "", { "dependencies": { "safe-buffer": "5.2.1" } }, "sha512-Au9nRL8VNUut/XSzbQA38+M78dzP4D+eqg3gfJHMIHHYa3bg067xj1KxMUWj+VULbiZMowKngFFbKczUrNJ1mg=="],
337
+
338
+ "content-type": ["[email protected]", "", {}, "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA=="],
339
+
340
+ "cookie": ["[email protected]", "", {}, "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w=="],
341
+
342
+ "cookie-signature": ["[email protected]", "", {}, "sha512-D76uU73ulSXrD1UXF4KE2TMxVVwhsnCgfAyTg9k8P6KGZjlXKrOLe4dJQKI3Bxi5wjesZoFXJWElNWBjPZMbhg=="],
343
+
344
+ "cors": ["[email protected]", "", { "dependencies": { "object-assign": "^4", "vary": "^1" } }, "sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g=="],
345
+
346
  "cross-spawn": ["[email protected]", "", { "dependencies": { "path-key": "^3.1.0", "shebang-command": "^2.0.0", "which": "^2.0.1" } }, "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA=="],
347
 
348
  "css-tree": ["[email protected]", "", { "dependencies": { "mdn-data": "2.0.30", "source-map-js": "^1.0.1" } }, "sha512-6Fv1DV/TYw//QF5IzQdqsNDjx/wc8TrMBZsqjL9eW01tWb7R7k/mq+/VXfJCl7SoD5emsJop9cOByJZfs8hYIw=="],
 
367
 
368
  "define-properties": ["[email protected]", "", { "dependencies": { "define-data-property": "^1.0.1", "has-property-descriptors": "^1.0.0", "object-keys": "^1.1.1" } }, "sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg=="],
369
 
370
+ "depd": ["[email protected]", "", {}, "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw=="],
371
+
372
  "detect-indent": ["[email protected]", "", {}, "sha512-reYkTUJAZb9gUuZ2RvVCNhVHdg62RHnJ7WJl8ftMi4diZ6NWlciOzQN88pUhSELEwflJht4oQDv0F0BMlwaYtA=="],
373
 
374
  "doctrine": ["[email protected]", "", { "dependencies": { "esutils": "^2.0.2" } }, "sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw=="],
375
 
376
  "dunder-proto": ["[email protected]", "", { "dependencies": { "call-bind-apply-helpers": "^1.0.1", "es-errors": "^1.3.0", "gopd": "^1.2.0" } }, "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A=="],
377
 
378
+ "eastasianwidth": ["[email protected]", "", {}, "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA=="],
379
+
380
+ "ee-first": ["[email protected]", "", {}, "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow=="],
381
+
382
  "emoji-regex": ["[email protected]", "", {}, "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A=="],
383
 
384
+ "encodeurl": ["[email protected]", "", {}, "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg=="],
385
+
386
  "es-abstract": ["[email protected]", "", { "dependencies": { "array-buffer-byte-length": "^1.0.2", "arraybuffer.prototype.slice": "^1.0.4", "available-typed-arrays": "^1.0.7", "call-bind": "^1.0.8", "call-bound": "^1.0.4", "data-view-buffer": "^1.0.2", "data-view-byte-length": "^1.0.2", "data-view-byte-offset": "^1.0.1", "es-define-property": "^1.0.1", "es-errors": "^1.3.0", "es-object-atoms": "^1.1.1", "es-set-tostringtag": "^2.1.0", "es-to-primitive": "^1.3.0", "function.prototype.name": "^1.1.8", "get-intrinsic": "^1.3.0", "get-proto": "^1.0.1", "get-symbol-description": "^1.1.0", "globalthis": "^1.0.4", "gopd": "^1.2.0", "has-property-descriptors": "^1.0.2", "has-proto": "^1.2.0", "has-symbols": "^1.1.0", "hasown": "^2.0.2", "internal-slot": "^1.1.0", "is-array-buffer": "^3.0.5", "is-callable": "^1.2.7", "is-data-view": "^1.0.2", "is-negative-zero": "^2.0.3", "is-regex": "^1.2.1", "is-set": "^2.0.3", "is-shared-array-buffer": "^1.0.4", "is-string": "^1.1.1", "is-typed-array": "^1.1.15", "is-weakref": "^1.1.1", "math-intrinsics": "^1.1.0", "object-inspect": "^1.13.4", "object-keys": "^1.1.1", "object.assign": "^4.1.7", "own-keys": "^1.0.1", "regexp.prototype.flags": "^1.5.4", "safe-array-concat": "^1.1.3", "safe-push-apply": "^1.0.0", "safe-regex-test": "^1.1.0", "set-proto": "^1.0.0", "stop-iteration-iterator": "^1.1.0", "string.prototype.trim": "^1.2.10", "string.prototype.trimend": "^1.0.9", "string.prototype.trimstart": "^1.0.8", "typed-array-buffer": "^1.0.3", "typed-array-byte-length": "^1.0.3", "typed-array-byte-offset": "^1.0.4", "typed-array-length": "^1.0.7", "unbox-primitive": "^1.1.0", "which-typed-array": "^1.1.19" } }, "sha512-WSzPgsdLtTcQwm4CROfS5ju2Wa1QQcVeT37jFjYzdFz1r9ahadC8B8/a4qxJxM+09F18iumCdRmlr96ZYkQvEg=="],
387
 
388
  "es-define-property": ["[email protected]", "", {}, "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g=="],
 
401
 
402
  "esbuild": ["[email protected]", "", { "optionalDependencies": { "@esbuild/aix-ppc64": "0.21.5", "@esbuild/android-arm": "0.21.5", "@esbuild/android-arm64": "0.21.5", "@esbuild/android-x64": "0.21.5", "@esbuild/darwin-arm64": "0.21.5", "@esbuild/darwin-x64": "0.21.5", "@esbuild/freebsd-arm64": "0.21.5", "@esbuild/freebsd-x64": "0.21.5", "@esbuild/linux-arm": "0.21.5", "@esbuild/linux-arm64": "0.21.5", "@esbuild/linux-ia32": "0.21.5", "@esbuild/linux-loong64": "0.21.5", "@esbuild/linux-mips64el": "0.21.5", "@esbuild/linux-ppc64": "0.21.5", "@esbuild/linux-riscv64": "0.21.5", "@esbuild/linux-s390x": "0.21.5", "@esbuild/linux-x64": "0.21.5", "@esbuild/netbsd-x64": "0.21.5", "@esbuild/openbsd-x64": "0.21.5", "@esbuild/sunos-x64": "0.21.5", "@esbuild/win32-arm64": "0.21.5", "@esbuild/win32-ia32": "0.21.5", "@esbuild/win32-x64": "0.21.5" }, "bin": { "esbuild": "bin/esbuild" } }, "sha512-mg3OPMV4hXywwpoDxu3Qda5xCKQi+vCTZq8S9J/EpkhB2HzKXq4SNFZE3+NK93JYxc8VMSep+lOUSC/RVKaBqw=="],
403
 
404
+ "escape-html": ["[email protected]", "", {}, "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow=="],
405
+
406
  "escape-string-regexp": ["[email protected]", "", {}, "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA=="],
407
 
408
  "eslint": ["[email protected]", "", { "dependencies": { "@eslint-community/eslint-utils": "^4.8.0", "@eslint-community/regexpp": "^4.12.1", "@eslint/config-array": "^0.21.0", "@eslint/config-helpers": "^0.3.1", "@eslint/core": "^0.15.2", "@eslint/eslintrc": "^3.3.1", "@eslint/js": "9.35.0", "@eslint/plugin-kit": "^0.3.5", "@humanfs/node": "^0.16.6", "@humanwhocodes/module-importer": "^1.0.1", "@humanwhocodes/retry": "^0.4.2", "@types/estree": "^1.0.6", "@types/json-schema": "^7.0.15", "ajv": "^6.12.4", "chalk": "^4.0.0", "cross-spawn": "^7.0.6", "debug": "^4.3.2", "escape-string-regexp": "^4.0.0", "eslint-scope": "^8.4.0", "eslint-visitor-keys": "^4.2.1", "espree": "^10.4.0", "esquery": "^1.5.0", "esutils": "^2.0.2", "fast-deep-equal": "^3.1.3", "file-entry-cache": "^8.0.0", "find-up": "^5.0.0", "glob-parent": "^6.0.2", "ignore": "^5.2.0", "imurmurhash": "^0.1.4", "is-glob": "^4.0.0", "json-stable-stringify-without-jsonify": "^1.0.1", "lodash.merge": "^4.6.2", "minimatch": "^3.1.2", "natural-compare": "^1.4.0", "optionator": "^0.9.3" }, "peerDependencies": { "jiti": "*" }, "optionalPeers": ["jiti"], "bin": { "eslint": "bin/eslint.js" } }, "sha512-QePbBFMJFjgmlE+cXAlbHZbHpdFVS2E/6vzCy7aKlebddvl1vadiC4JFV5u/wqTkNUwEV8WrQi257jf5f06hrg=="],
 
437
 
438
  "esutils": ["[email protected]", "", {}, "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g=="],
439
 
440
+ "etag": ["[email protected]", "", {}, "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg=="],
441
+
442
  "eventemitter3": ["[email protected]", "", {}, "sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw=="],
443
 
444
+ "eventsource": ["[email protected]", "", { "dependencies": { "eventsource-parser": "^3.0.1" } }, "sha512-CRT1WTyuQoD771GW56XEZFQ/ZoSfWid1alKGDYMmkt2yl8UXrVR4pspqWNEcqKvVIzg6PAltWjxcSSPrboA4iA=="],
445
+
446
+ "eventsource-parser": ["[email protected]", "", {}, "sha512-Vo1ab+QXPzZ4tCa8SwIHJFaSzy4R6SHf7BY79rFBDf0idraZWAkYrDjDj8uWaSm3S2TK+hJ7/t1CEmZ7jXw+pg=="],
447
+
448
+ "express": ["[email protected]", "", { "dependencies": { "accepts": "^2.0.0", "body-parser": "^2.2.0", "content-disposition": "^1.0.0", "content-type": "^1.0.5", "cookie": "^0.7.1", "cookie-signature": "^1.2.1", "debug": "^4.4.0", "encodeurl": "^2.0.0", "escape-html": "^1.0.3", "etag": "^1.8.1", "finalhandler": "^2.1.0", "fresh": "^2.0.0", "http-errors": "^2.0.0", "merge-descriptors": "^2.0.0", "mime-types": "^3.0.0", "on-finished": "^2.4.1", "once": "^1.4.0", "parseurl": "^1.3.3", "proxy-addr": "^2.0.7", "qs": "^6.14.0", "range-parser": "^1.2.1", "router": "^2.2.0", "send": "^1.1.0", "serve-static": "^2.2.0", "statuses": "^2.0.1", "type-is": "^2.0.1", "vary": "^1.1.2" } }, "sha512-DT9ck5YIRU+8GYzzU5kT3eHGA5iL+1Zd0EutOmTE9Dtk+Tvuzd23VBU+ec7HPNSTxXYO55gPV/hq4pSBJDjFpA=="],
449
+
450
+ "express-rate-limit": ["[email protected]", "", { "peerDependencies": { "express": ">= 4.11" } }, "sha512-7iN8iPMDzOMHPUYllBEsQdWVB6fPDMPqwjBaFrgr4Jgr/+okjvzAy+UHlYYL/Vs0OsOrMkwS6PJDkFlJwoxUnw=="],
451
+
452
+ "extended-eventsource": ["[email protected]", "", {}, "sha512-s8rtvZuYcKBpzytHb5g95cHbZ1J99WeMnV18oKc5wKoxkHzlzpPc/bNAm7Da2Db0BDw0CAu1z3LpH+7UsyzIpw=="],
453
+
454
  "fast-deep-equal": ["[email protected]", "", {}, "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q=="],
455
 
456
  "fast-diff": ["[email protected]", "", {}, "sha512-VxPP4NqbUjj6MaAOafWeUn2cXWLcCtljklUtZf0Ind4XQ+QPtmA0b18zZy0jIQx+ExRVCR/ZQpBmik5lXshNsw=="],
 
467
 
468
  "fill-range": ["[email protected]", "", { "dependencies": { "to-regex-range": "^5.0.1" } }, "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg=="],
469
 
470
+ "finalhandler": ["[email protected]", "", { "dependencies": { "debug": "^4.4.0", "encodeurl": "^2.0.0", "escape-html": "^1.0.3", "on-finished": "^2.4.1", "parseurl": "^1.3.3", "statuses": "^2.0.1" } }, "sha512-/t88Ty3d5JWQbWYgaOGCCYfXRwV1+be02WqYYlL6h0lEiUAMPM8o8qKGO01YIkOHzka2up08wvgYD0mDiI+q3Q=="],
471
+
472
  "find-up": ["[email protected]", "", { "dependencies": { "locate-path": "^6.0.0", "path-exists": "^4.0.0" } }, "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng=="],
473
 
474
  "flat-cache": ["[email protected]", "", { "dependencies": { "flatted": "^3.2.9", "keyv": "^4.5.4" } }, "sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw=="],
 
477
 
478
  "for-each": ["[email protected]", "", { "dependencies": { "is-callable": "^1.2.7" } }, "sha512-dKx12eRCVIzqCxFGplyFKJMPvLEWgmNtUrpTiJIR5u97zEhRG8ySrtboPHZXx7daLxQVrl643cTzbab2tkQjxg=="],
479
 
480
+ "foreground-child": ["[email protected]", "", { "dependencies": { "cross-spawn": "^7.0.6", "signal-exit": "^4.0.1" } }, "sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw=="],
481
+
482
+ "forwarded": ["[email protected]", "", {}, "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow=="],
483
+
484
+ "fresh": ["[email protected]", "", {}, "sha512-Rx/WycZ60HOaqLKAi6cHRKKI7zxWbJ31MhntmtwMoaTeF7XFH9hhBp8vITaMidfljRQ6eYWCKkaTK+ykVJHP2A=="],
485
+
486
  "fs.realpath": ["[email protected]", "", {}, "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw=="],
487
 
488
  "fsevents": ["[email protected]", "", { "os": "darwin" }, "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw=="],
 
499
 
500
  "get-symbol-description": ["[email protected]", "", { "dependencies": { "call-bound": "^1.0.3", "es-errors": "^1.3.0", "get-intrinsic": "^1.2.6" } }, "sha512-w9UMqWwJxHNOvoNzSJ2oPF5wvYcvP7jUvYzhp67yEhTi17ZDBBC1z9pTdGuzjD+EFIqLSYRweZjqfiPzQ06Ebg=="],
501
 
502
+ "glob": ["glob@10.4.5", "", { "dependencies": { "foreground-child": "^3.1.0", "jackspeak": "^3.1.2", "minimatch": "^9.0.4", "minipass": "^7.1.2", "package-json-from-dist": "^1.0.0", "path-scurry": "^1.11.1" }, "bin": { "glob": "dist/esm/bin.mjs" } }, "sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg=="],
503
 
504
  "glob-parent": ["[email protected]", "", { "dependencies": { "is-glob": "^4.0.3" } }, "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A=="],
505
 
 
529
 
530
  "hasown": ["[email protected]", "", { "dependencies": { "function-bind": "^1.1.2" } }, "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ=="],
531
 
532
+ "http-errors": ["[email protected]", "", { "dependencies": { "depd": "2.0.0", "inherits": "2.0.4", "setprototypeof": "1.2.0", "statuses": "2.0.1", "toidentifier": "1.0.1" } }, "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ=="],
533
+
534
+ "iconv-lite": ["[email protected]", "", { "dependencies": { "safer-buffer": ">= 2.1.2 < 3.0.0" } }, "sha512-cf6L2Ds3h57VVmkZe+Pn+5APsT7FpqJtEhhieDCvrE2MK5Qk9MyffgQyuxQTm6BChfeZNtcOLHp9IcWRVcIcBQ=="],
535
+
536
  "ignore": ["[email protected]", "", {}, "sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg=="],
537
 
538
  "import-fresh": ["[email protected]", "", { "dependencies": { "parent-module": "^1.0.0", "resolve-from": "^4.0.0" } }, "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ=="],
 
545
 
546
  "internal-slot": ["[email protected]", "", { "dependencies": { "es-errors": "^1.3.0", "hasown": "^2.0.2", "side-channel": "^1.1.0" } }, "sha512-4gd7VpWNQNB4UKKCFFVcp1AVv+FMOgs9NKzjHKusc8jTMhd5eL1NqQqOpE0KzMds804/yHlglp3uxgluOqAPLw=="],
547
 
548
+ "ipaddr.js": ["[email protected]", "", {}, "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g=="],
549
+
550
  "is-array-buffer": ["[email protected]", "", { "dependencies": { "call-bind": "^1.0.8", "call-bound": "^1.0.3", "get-intrinsic": "^1.2.6" } }, "sha512-DDfANUiiG2wC1qawP66qlTugJeL5HyzMpfr8lLK+jMQirGzNod0B12cFB/9q838Ru27sBwfw78/rdoU7RERz6A=="],
551
 
552
  "is-async-function": ["[email protected]", "", { "dependencies": { "async-function": "^1.0.0", "call-bound": "^1.0.3", "get-proto": "^1.0.1", "has-tostringtag": "^1.0.2", "safe-regex-test": "^1.1.0" } }, "sha512-9dgM/cZBnNvjzaMYHVoxxfPj2QXt22Ev7SuuPrs+xav0ukGB0S6d4ydZdEiM48kLx5kDV+QBPrpVnFyefL8kkQ=="],
 
583
 
584
  "is-number-object": ["[email protected]", "", { "dependencies": { "call-bound": "^1.0.3", "has-tostringtag": "^1.0.2" } }, "sha512-lZhclumE1G6VYD8VHe35wFaIif+CTy5SJIi5+3y4psDgWu4wPDoBhF8NxUOinEc7pHgiTsT6MaBb92rKhhD+Xw=="],
585
 
586
+ "is-promise": ["[email protected]", "", {}, "sha512-hvpoI6korhJMnej285dSg6nu1+e6uxs7zG3BYAm5byqDsgJNWwxzM6z6iZiAgQR4TJ30JmBTOwqZUw3WlyH3AQ=="],
587
+
588
  "is-reference": ["[email protected]", "", { "dependencies": { "@types/estree": "^1.0.6" } }, "sha512-ixkJoqQvAP88E6wLydLGGqCJsrFUnqoH6HnaczB8XmDH1oaWU+xxdptvikTgaEhtZ53Ky6YXiBuUI2WXLMCwjw=="],
589
 
590
  "is-regex": ["[email protected]", "", { "dependencies": { "call-bound": "^1.0.2", "gopd": "^1.2.0", "has-tostringtag": "^1.0.2", "hasown": "^2.0.2" } }, "sha512-MjYsKHO5O7mCsmRGxWcLWheFqN9DJ/2TmngvjKXihe6efViPqc274+Fx/4fYj/r03+ESvBdTXK0V6tA3rgez1g=="],
 
609
 
610
  "isexe": ["[email protected]", "", {}, "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw=="],
611
 
612
+ "jackspeak": ["[email protected]", "", { "dependencies": { "@isaacs/cliui": "^8.0.2" }, "optionalDependencies": { "@pkgjs/parseargs": "^0.11.0" } }, "sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw=="],
613
+
614
  "js-tiktoken": ["[email protected]", "", { "dependencies": { "base64-js": "^1.5.1" } }, "sha512-biOj/6M5qdgx5TKjDnFT1ymSpM5tbd3ylwDtrQvFQSu0Z7bBYko2dF+W/aUkXUPuk6IVpRxk/3Q2sHOzGlS36g=="],
615
 
616
  "js-yaml": ["[email protected]", "", { "dependencies": { "argparse": "^2.0.1" }, "bin": { "js-yaml": "bin/js-yaml.js" } }, "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA=="],
 
637
 
638
  "lodash.merge": ["[email protected]", "", {}, "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ=="],
639
 
640
+ "lru-cache": ["[email protected]", "", {}, "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ=="],
641
+
642
  "magic-string": ["[email protected]", "", { "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.5" } }, "sha512-2N21sPY9Ws53PZvsEpVtNuSW+ScYbQdp4b9qUaL+9QkHUrGFKo56Lg9Emg5s9V/qrtNBmiR01sYhUOwu3H+VOw=="],
643
 
644
  "marked": ["[email protected]", "", { "bin": { "marked": "bin/marked.js" } }, "sha512-K3UxuKu6l6bmA5FUwYho8CfJBlsUWAooKtdGgMcERSpF7gcBUrCGsLH7wDaaNOzwq18JzSUDyoEb/YsrqMac3w=="],
 
647
 
648
  "mdn-data": ["[email protected]", "", {}, "sha512-GaqWWShW4kv/G9IEucWScBx9G1/vsFZZJUO+tD26M8J8z3Kw5RDQjaoZe03YAClgeS/SWPOcb4nkFBTEi5DUEA=="],
649
 
650
+ "media-typer": ["[email protected]", "", {}, "sha512-aisnrDP4GNe06UcKFnV5bfMNPBUw4jsLGaWwWfnH3v02GnBuXX2MCVn5RbrWo0j3pczUilYblq7fQ7Nw2t5XKw=="],
651
+
652
+ "merge-descriptors": ["[email protected]", "", {}, "sha512-Snk314V5ayFLhp3fkUREub6WtjBfPdCPY1Ln8/8munuLuiYhsABgBVWsozAG+MWMbVEvcdcpbi9R7ww22l9Q3g=="],
653
+
654
  "merge2": ["[email protected]", "", {}, "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg=="],
655
 
656
  "micromatch": ["[email protected]", "", { "dependencies": { "braces": "^3.0.3", "picomatch": "^2.3.1" } }, "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA=="],
657
 
658
+ "mime-db": ["[email protected]", "", {}, "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ=="],
659
+
660
+ "mime-types": ["[email protected]", "", { "dependencies": { "mime-db": "^1.54.0" } }, "sha512-xRc4oEhT6eaBpU1XF7AjpOFD+xQmXNB5OVKwp4tqCuBpHLS/ZbBDrc07mYTDqVMg6PfxUjjNp85O6Cd2Z/5HWA=="],
661
+
662
  "min-indent": ["[email protected]", "", {}, "sha512-I9jwMn07Sy/IwOj3zVkVik2JTvgpaykDZEigL6Rx6N9LbMywwUSMtxET+7lVoDLLd3O3IXwJwvuuns8UB/HeAg=="],
663
 
664
  "minimatch": ["[email protected]", "", { "dependencies": { "brace-expansion": "^1.1.7" } }, "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw=="],
665
 
666
  "minimist": ["[email protected]", "", {}, "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA=="],
667
 
668
+ "minipass": ["[email protected]", "", {}, "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw=="],
669
+
670
  "mkdirp": ["[email protected]", "", { "dependencies": { "minimist": "^1.2.6" }, "bin": { "mkdirp": "bin/cmd.js" } }, "sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw=="],
671
 
672
  "monaco-editor": ["[email protected]", "", {}, "sha512-8CclLCmrRRh+sul7C08BmPBP3P8wVWfBHomsTcndxg5NRCEPfu/mc2AGU8k37ajjDVXcXFc12ORAMUkmk+lkFA=="],
 
681
 
682
  "natural-compare": ["[email protected]", "", {}, "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw=="],
683
 
684
+ "negotiator": ["[email protected]", "", {}, "sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg=="],
685
+
686
  "normalize-path": ["[email protected]", "", {}, "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA=="],
687
 
688
+ "object-assign": ["[email protected]", "", {}, "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg=="],
689
+
690
  "object-inspect": ["[email protected]", "", {}, "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew=="],
691
 
692
  "object-keys": ["[email protected]", "", {}, "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA=="],
 
699
 
700
  "object.values": ["[email protected]", "", { "dependencies": { "call-bind": "^1.0.8", "call-bound": "^1.0.3", "define-properties": "^1.2.1", "es-object-atoms": "^1.0.0" } }, "sha512-gXah6aZrcUxjWg2zR2MwouP2eHlCBzdV4pygudehaKXSGW4v2AsRQUK+lwwXhii6KFZcunEnmSUoYp5CXibxtA=="],
701
 
702
+ "on-finished": ["[email protected]", "", { "dependencies": { "ee-first": "1.1.1" } }, "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg=="],
703
+
704
  "once": ["[email protected]", "", { "dependencies": { "wrappy": "1" } }, "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w=="],
705
 
706
  "optionator": ["[email protected]", "", { "dependencies": { "deep-is": "^0.1.3", "fast-levenshtein": "^2.0.6", "levn": "^0.4.1", "prelude-ls": "^1.2.1", "type-check": "^0.4.0", "word-wrap": "^1.2.5" } }, "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g=="],
 
719
 
720
  "p-timeout": ["[email protected]", "", { "dependencies": { "p-finally": "^1.0.0" } }, "sha512-rhIwUycgwwKcP9yTOOFK/AKsAopjjCakVqLHePO3CC6Mir1Z99xT+R63jZxAT5lFZLa2inS5h+ZS2GvR99/FBg=="],
721
 
722
+ "package-json-from-dist": ["[email protected]", "", {}, "sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw=="],
723
+
724
  "parent-module": ["[email protected]", "", { "dependencies": { "callsites": "^3.0.0" } }, "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g=="],
725
 
726
+ "parseurl": ["[email protected]", "", {}, "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ=="],
727
+
728
  "path-exists": ["[email protected]", "", {}, "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w=="],
729
 
730
  "path-is-absolute": ["[email protected]", "", {}, "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg=="],
 
733
 
734
  "path-parse": ["[email protected]", "", {}, "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw=="],
735
 
736
+ "path-scurry": ["[email protected]", "", { "dependencies": { "lru-cache": "^10.2.0", "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0" } }, "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA=="],
737
+
738
+ "path-to-regexp": ["[email protected]", "", {}, "sha512-7jdwVIRtsP8MYpdXSwOS0YdD0Du+qOoF/AEPIt88PcCFrZCzx41oxku1jD88hZBwbNUIEfpqvuhjFaMAqMTWnA=="],
739
+
740
  "periscopic": ["[email protected]", "", { "dependencies": { "@types/estree": "^1.0.0", "estree-walker": "^3.0.0", "is-reference": "^3.0.0" } }, "sha512-vKiQ8RRtkl9P+r/+oefh25C3fhybptkHKCZSPlcXiJux2tJF55GnEj3BVn4A5gKfq9NWWXXrxkHBwVPUfH0opw=="],
741
 
742
  "picocolors": ["[email protected]", "", {}, "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA=="],
743
 
744
  "picomatch": ["[email protected]", "", {}, "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA=="],
745
 
746
+ "pkce-challenge": ["[email protected]", "", {}, "sha512-ueGLflrrnvwB3xuo/uGob5pd5FN7l0MsLf0Z87o/UQmRtwjvfylfc9MurIxRAWywCYTgrvpXBcqjV4OfCYGCIQ=="],
747
+
748
  "possible-typed-array-names": ["[email protected]", "", {}, "sha512-/+5VFTchJDoVj3bhoqi6UeymcD00DAwb1nJwamzPvHEszJ4FpF6SNNbUbOS8yI56qHzdV8eK0qEfOSiodkTdxg=="],
749
 
750
  "postcss": ["[email protected]", "", { "dependencies": { "nanoid": "^3.3.11", "picocolors": "^1.1.1", "source-map-js": "^1.2.1" } }, "sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg=="],
 
757
 
758
  "prettier-linter-helpers": ["[email protected]", "", { "dependencies": { "fast-diff": "^1.1.2" } }, "sha512-GbK2cP9nraSSUF9N2XwUwqfzlAFlMNYYl+ShE/V+H8a9uNl/oUqB1w2EL54Jh0OlyRSd8RfWYJ3coVS4TROP2w=="],
759
 
760
+ "proxy-addr": ["[email protected]", "", { "dependencies": { "forwarded": "0.2.0", "ipaddr.js": "1.9.1" } }, "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg=="],
761
+
762
  "punycode": ["[email protected]", "", {}, "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg=="],
763
 
764
+ "qs": ["[email protected]", "", { "dependencies": { "side-channel": "^1.1.0" } }, "sha512-YWWTjgABSKcvs/nWBi9PycY/JiPJqOD4JA6o9Sej2AtvSGarXxKC3OQSk4pAarbdQlKAh5D4FCQkJNkW+GAn3w=="],
765
+
766
  "queue-microtask": ["[email protected]", "", {}, "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A=="],
767
 
768
+ "range-parser": ["[email protected]", "", {}, "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg=="],
769
+
770
+ "raw-body": ["[email protected]", "", { "dependencies": { "bytes": "3.1.2", "http-errors": "2.0.0", "iconv-lite": "0.7.0", "unpipe": "1.0.0" } }, "sha512-9G8cA+tuMS75+6G/TzW8OtLzmBDMo8p1JRxN5AZ+LAp8uxGA8V8GZm4GQ4/N5QNQEnLmg6SS7wyuSmbKepiKqA=="],
771
+
772
  "readdirp": ["[email protected]", "", { "dependencies": { "picomatch": "^2.2.1" } }, "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA=="],
773
 
774
  "reflect.getprototypeof": ["[email protected]", "", { "dependencies": { "call-bind": "^1.0.8", "define-properties": "^1.2.1", "es-abstract": "^1.23.9", "es-errors": "^1.3.0", "es-object-atoms": "^1.0.0", "get-intrinsic": "^1.2.7", "get-proto": "^1.0.1", "which-builtin-type": "^1.2.1" } }, "sha512-00o4I+DVrefhv+nX0ulyi3biSHCPDe+yLv5o/p6d/UVlirijB8E16FtfwSAi4g3tcqrQ4lRAqQSoFEZJehYEcw=="],
 
785
 
786
  "rimraf": ["[email protected]", "", { "dependencies": { "glob": "^7.1.3" }, "bin": { "rimraf": "./bin.js" } }, "sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w=="],
787
 
788
+ "rollup": ["[email protected].2", "", { "dependencies": { "@types/estree": "1.0.8" }, "optionalDependencies": { "@rollup/rollup-android-arm-eabi": "4.50.2", "@rollup/rollup-android-arm64": "4.50.2", "@rollup/rollup-darwin-arm64": "4.50.2", "@rollup/rollup-darwin-x64": "4.50.2", "@rollup/rollup-freebsd-arm64": "4.50.2", "@rollup/rollup-freebsd-x64": "4.50.2", "@rollup/rollup-linux-arm-gnueabihf": "4.50.2", "@rollup/rollup-linux-arm-musleabihf": "4.50.2", "@rollup/rollup-linux-arm64-gnu": "4.50.2", "@rollup/rollup-linux-arm64-musl": "4.50.2", "@rollup/rollup-linux-loong64-gnu": "4.50.2", "@rollup/rollup-linux-ppc64-gnu": "4.50.2", "@rollup/rollup-linux-riscv64-gnu": "4.50.2", "@rollup/rollup-linux-riscv64-musl": "4.50.2", "@rollup/rollup-linux-s390x-gnu": "4.50.2", "@rollup/rollup-linux-x64-gnu": "4.50.2", "@rollup/rollup-linux-x64-musl": "4.50.2", "@rollup/rollup-openharmony-arm64": "4.50.2", "@rollup/rollup-win32-arm64-msvc": "4.50.2", "@rollup/rollup-win32-ia32-msvc": "4.50.2", "@rollup/rollup-win32-x64-msvc": "4.50.2", "fsevents": "~2.3.2" }, "bin": { "rollup": "dist/bin/rollup" } }, "sha512-BgLRGy7tNS9H66aIMASq1qSYbAAJV6Z6WR4QYTvj5FgF15rZ/ympT1uixHXwzbZUBDbkvqUI1KR0fH1FhMaQ9w=="],
789
+
790
+ "router": ["[email protected]", "", { "dependencies": { "debug": "^4.4.0", "depd": "^2.0.0", "is-promise": "^4.0.0", "parseurl": "^1.3.3", "path-to-regexp": "^8.0.0" } }, "sha512-nLTrUKm2UyiL7rlhapu/Zl45FwNgkZGaCpZbIHajDYgwlJCOzLSk+cIPAnsEqV955GjILJnKbdQC1nVPz+gAYQ=="],
791
 
792
  "run-parallel": ["[email protected]", "", { "dependencies": { "queue-microtask": "^1.2.2" } }, "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA=="],
793
 
 
795
 
796
  "safe-array-concat": ["[email protected]", "", { "dependencies": { "call-bind": "^1.0.8", "call-bound": "^1.0.2", "get-intrinsic": "^1.2.6", "has-symbols": "^1.1.0", "isarray": "^2.0.5" } }, "sha512-AURm5f0jYEOydBj7VQlVvDrjeFgthDdEF5H1dP+6mNpoXOMo1quQqJ4wvJDyRZ9+pO3kGWoOdmV08cSv2aJV6Q=="],
797
 
798
+ "safe-buffer": ["[email protected]", "", {}, "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ=="],
799
+
800
  "safe-push-apply": ["[email protected]", "", { "dependencies": { "es-errors": "^1.3.0", "isarray": "^2.0.5" } }, "sha512-iKE9w/Z7xCzUMIZqdBsp6pEQvwuEebH4vdpjcDWnyzaI6yl6O9FHvVpmGelvEHNsoY6wGblkxR6Zty/h00WiSA=="],
801
 
802
  "safe-regex-test": ["[email protected]", "", { "dependencies": { "call-bound": "^1.0.2", "es-errors": "^1.3.0", "is-regex": "^1.2.1" } }, "sha512-x/+Cz4YrimQxQccJf5mKEbIa1NzeCRNI5Ecl/ekmlYaampdNLPalVyIcCZNNH3MvmqBugV5TMYZXv0ljslUlaw=="],
803
 
804
+ "safer-buffer": ["[email protected]", "", {}, "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg=="],
805
+
806
  "sander": ["[email protected]", "", { "dependencies": { "es6-promise": "^3.1.2", "graceful-fs": "^4.1.3", "mkdirp": "^0.5.1", "rimraf": "^2.5.2" } }, "sha512-3lVqBir7WuKDHGrKRDn/1Ye3kwpXaDOMsiRP1wd6wpZW56gJhsbp5RqQpA6JG/P+pkXizygnr1dKR8vzWaVsfA=="],
807
 
808
  "semver": ["[email protected]", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA=="],
809
 
810
+ "send": ["[email protected]", "", { "dependencies": { "debug": "^4.3.5", "encodeurl": "^2.0.0", "escape-html": "^1.0.3", "etag": "^1.8.1", "fresh": "^2.0.0", "http-errors": "^2.0.0", "mime-types": "^3.0.1", "ms": "^2.1.3", "on-finished": "^2.4.1", "range-parser": "^1.2.1", "statuses": "^2.0.1" } }, "sha512-uaW0WwXKpL9blXE2o0bRhoL2EGXIrZxQ2ZQ4mgcfoBxdFmQold+qWsD2jLrfZ0trjKL6vOw0j//eAwcALFjKSw=="],
811
+
812
+ "serve-static": ["[email protected]", "", { "dependencies": { "encodeurl": "^2.0.0", "escape-html": "^1.0.3", "parseurl": "^1.3.3", "send": "^1.2.0" } }, "sha512-61g9pCh0Vnh7IutZjtLGGpTA355+OPn2TyDv/6ivP2h/AdAVX9azsoxmg2/M6nZeQZNYBEwIcsne1mJd9oQItQ=="],
813
+
814
  "set-function-length": ["[email protected]", "", { "dependencies": { "define-data-property": "^1.1.4", "es-errors": "^1.3.0", "function-bind": "^1.1.2", "get-intrinsic": "^1.2.4", "gopd": "^1.0.1", "has-property-descriptors": "^1.0.2" } }, "sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg=="],
815
 
816
  "set-function-name": ["[email protected]", "", { "dependencies": { "define-data-property": "^1.1.4", "es-errors": "^1.3.0", "functions-have-names": "^1.2.3", "has-property-descriptors": "^1.0.2" } }, "sha512-7PGFlmtwsEADb0WYyvCMa1t+yke6daIG4Wirafur5kcf+MhUnPms1UeR0CKQdTZD81yESwMHbtn+TR+dMviakQ=="],
817
 
818
  "set-proto": ["[email protected]", "", { "dependencies": { "dunder-proto": "^1.0.1", "es-errors": "^1.3.0", "es-object-atoms": "^1.0.0" } }, "sha512-RJRdvCo6IAnPdsvP/7m6bsQqNnn1FCBX5ZNtFL98MmFF/4xAIJTIg1YbHW5DC2W5SKZanrC6i4HsJqlajw/dZw=="],
819
 
820
+ "setprototypeof": ["[email protected]", "", {}, "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw=="],
821
+
822
  "shebang-command": ["[email protected]", "", { "dependencies": { "shebang-regex": "^3.0.0" } }, "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA=="],
823
 
824
  "shebang-regex": ["[email protected]", "", {}, "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A=="],
 
831
 
832
  "side-channel-weakmap": ["[email protected]", "", { "dependencies": { "call-bound": "^1.0.2", "es-errors": "^1.3.0", "get-intrinsic": "^1.2.5", "object-inspect": "^1.13.3", "side-channel-map": "^1.0.1" } }, "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A=="],
833
 
834
+ "signal-exit": ["[email protected]", "", {}, "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw=="],
835
+
836
  "simple-wcswidth": ["[email protected]", "", {}, "sha512-j7piyCjAeTDSjzTSQ7DokZtMNwNlEAyxqSZeCS+CXH7fJ4jx3FuJ/mTW3mE+6JLs4VJBbcll0Kjn+KXI5t21Iw=="],
837
 
838
  "sorcery": ["[email protected]", "", { "dependencies": { "@jridgewell/sourcemap-codec": "^1.4.14", "buffer-crc32": "^1.0.0", "minimist": "^1.2.0", "sander": "^0.5.0" }, "bin": { "sorcery": "bin/sorcery" } }, "sha512-o7npfeJE6wi6J9l0/5LKshFzZ2rMatRiCDwYeDQaOzqdzRJwALhX7mk/A/ecg6wjMu7wdZbmXfD2S/vpOg0bdQ=="],
839
 
840
  "source-map-js": ["[email protected]", "", {}, "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA=="],
841
 
842
+ "statuses": ["[email protected]", "", {}, "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ=="],
843
+
844
  "stop-iteration-iterator": ["[email protected]", "", { "dependencies": { "es-errors": "^1.3.0", "internal-slot": "^1.1.0" } }, "sha512-eLoXW/DHyl62zxY4SCaIgnRhuMr6ri4juEYARS8E6sCEqzKpOiE521Ucofdx+KnDZl5xmvGYaaKCk5FEOxJCoQ=="],
845
 
846
  "string-width": ["[email protected]", "", { "dependencies": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", "strip-ansi": "^6.0.1" } }, "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g=="],
847
 
848
+ "string-width-cjs": ["[email protected]", "", { "dependencies": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", "strip-ansi": "^6.0.1" } }, "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g=="],
849
+
850
  "string.prototype.trim": ["[email protected]", "", { "dependencies": { "call-bind": "^1.0.8", "call-bound": "^1.0.2", "define-data-property": "^1.1.4", "define-properties": "^1.2.1", "es-abstract": "^1.23.5", "es-object-atoms": "^1.0.0", "has-property-descriptors": "^1.0.2" } }, "sha512-Rs66F0P/1kedk5lyYyH9uBzuiI/kNRmwJAR9quK6VOtIpZ2G+hMZd+HQbbv25MgCA6gEffoMZYxlTod4WcdrKA=="],
851
 
852
  "string.prototype.trimend": ["[email protected]", "", { "dependencies": { "call-bind": "^1.0.8", "call-bound": "^1.0.2", "define-properties": "^1.2.1", "es-object-atoms": "^1.0.0" } }, "sha512-G7Ok5C6E/j4SGfyLCloXTrngQIQU3PWtXGst3yM7Bea9FRURf1S42ZHlZZtsNque2FN2PoUhfZXYLNWwEr4dLQ=="],
 
855
 
856
  "strip-ansi": ["[email protected]", "", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="],
857
 
858
+ "strip-ansi-cjs": ["[email protected]", "", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="],
859
+
860
  "strip-bom": ["[email protected]", "", {}, "sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA=="],
861
 
862
  "strip-indent": ["[email protected]", "", { "dependencies": { "min-indent": "^1.0.0" } }, "sha512-laJTa3Jb+VQpaC6DseHhF7dXVqHTfJPCRDaEbid/drOhgitgYku/letMUqOXFoWV0zIIUbjpdH2t+tYj4bQMRQ=="],
 
883
 
884
  "to-regex-range": ["[email protected]", "", { "dependencies": { "is-number": "^7.0.0" } }, "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ=="],
885
 
886
+ "toidentifier": ["[email protected]", "", {}, "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA=="],
887
+
888
  "ts-api-utils": ["[email protected]", "", { "peerDependencies": { "typescript": ">=4.8.4" } }, "sha512-CUgTZL1irw8u29bzrOD/nH85jqyc74D6SshFgujOIA7osm2Rz7dYH77agkx7H4FBNxDq7Cjf+IjaX/8zwFW+ZQ=="],
889
 
890
  "tsconfig-paths": ["[email protected]", "", { "dependencies": { "@types/json5": "^0.0.29", "json5": "^1.0.2", "minimist": "^1.2.6", "strip-bom": "^3.0.0" } }, "sha512-2Ac2RgzDe/cn48GvOe3M+o82pEFewD3UPbyoUHHdKasHwJKjds4fLXWf/Ux5kATBKN20oaFGu+jbElp1pos0mg=="],
891
 
892
  "type-check": ["[email protected]", "", { "dependencies": { "prelude-ls": "^1.2.1" } }, "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew=="],
893
 
894
+ "type-is": ["[email protected]", "", { "dependencies": { "content-type": "^1.0.5", "media-typer": "^1.1.0", "mime-types": "^3.0.0" } }, "sha512-OZs6gsjF4vMp32qrCbiVSkrFmXtG/AZhY3t0iAMrMBiAZyV9oALtXO8hsrHbMXF9x6L3grlFuwW2oAz7cav+Gw=="],
895
+
896
  "typed-array-buffer": ["[email protected]", "", { "dependencies": { "call-bound": "^1.0.3", "es-errors": "^1.3.0", "is-typed-array": "^1.1.14" } }, "sha512-nAYYwfY3qnzX30IkA6AQZjVbtK6duGontcQm1WSG1MD94YLqK0515GNApXkoxKOWMusVssAHWLh9SeaoefYFGw=="],
897
 
898
  "typed-array-byte-length": ["[email protected]", "", { "dependencies": { "call-bind": "^1.0.8", "for-each": "^0.3.3", "gopd": "^1.2.0", "has-proto": "^1.2.0", "is-typed-array": "^1.1.14" } }, "sha512-BaXgOuIxz8n8pIq3e7Atg/7s+DpiYrxn4vdot3w9KbnBhcRQq6o3xemQdIfynqSeXeDrF32x+WvfzmOjPiY9lg=="],
 
905
 
906
  "unbox-primitive": ["[email protected]", "", { "dependencies": { "call-bound": "^1.0.3", "has-bigints": "^1.0.2", "has-symbols": "^1.1.0", "which-boxed-primitive": "^1.1.1" } }, "sha512-nWJ91DjeOkej/TA8pXQ3myruKpKEYgqvpw9lz4OPHj/NWFNluYrjbz9j01CJ8yKQd2g4jFoOkINCTW2I5LEEyw=="],
907
 
908
+ "undici-types": ["undici-types@7.12.0", "", {}, "sha512-goOacqME2GYyOZZfb5Lgtu+1IDmAlAEu5xnD3+xTzS10hT0vzpf0SPjkXwAw9Jm+4n/mQGDP3LO8CPbYROeBfQ=="],
909
+
910
+ "unpipe": ["[email protected]", "", {}, "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ=="],
911
 
912
  "uri-js": ["[email protected]", "", { "dependencies": { "punycode": "^2.1.0" } }, "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg=="],
913
 
914
  "uuid": ["[email protected]", "", { "bin": { "uuid": "dist/bin/uuid" } }, "sha512-8XkAphELsDnEGrDxUOHB3RGvXz6TeuYSGEZBOjtTtPm2lwhGBjLgOzLHB63IUWfBpNucQjND6d3AOudO+H3RWQ=="],
915
 
916
+ "vary": ["vary@1.1.2", "", {}, "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg=="],
917
+
918
+ "vibegame": ["[email protected]", "", { "dependencies": { "@dimforge/rapier3d-compat": "^0.18.2", "gsap": "^3.13.0", "postprocessing": "^6.37.8", "zod": "^4.1.5" }, "peerDependencies": { "bitecs": ">=0.3.40", "three": ">=0.170.0" } }, "sha512-KFzNGi+EnlEWt4R3QKPN5jVJhkgBgMg443Qq3muzSjfrp+GJ1fsHIB8ovYylRFHg8+9P3kSlWmiQ6PlDiZ8msQ=="],
919
 
920
  "vite": ["[email protected]", "", { "dependencies": { "esbuild": "^0.21.3", "postcss": "^8.4.43", "rollup": "^4.20.0" }, "optionalDependencies": { "fsevents": "~2.3.3" }, "peerDependencies": { "@types/node": "^18.0.0 || >=20.0.0", "less": "*", "lightningcss": "^1.21.0", "sass": "*", "sass-embedded": "*", "stylus": "*", "sugarss": "*", "terser": "^5.4.0" }, "optionalPeers": ["@types/node", "less", "lightningcss", "sass", "sass-embedded", "stylus", "sugarss", "terser"], "bin": { "vite": "bin/vite.js" } }, "sha512-j3lYzGC3P+B5Yfy/pfKNgVEg4+UtcIJcVRt2cDjIOmhLourAqPqf8P7acgxeiSgUB7E3p2P8/3gNIgDLpwzs4g=="],
921
 
 
933
 
934
  "word-wrap": ["[email protected]", "", {}, "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA=="],
935
 
936
+ "wrap-ansi": ["[email protected]", "", { "dependencies": { "ansi-styles": "^6.1.0", "string-width": "^5.0.1", "strip-ansi": "^7.0.1" } }, "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ=="],
937
+
938
+ "wrap-ansi-cjs": ["[email protected]", "", { "dependencies": { "ansi-styles": "^4.0.0", "string-width": "^4.1.0", "strip-ansi": "^6.0.0" } }, "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q=="],
939
+
940
  "wrappy": ["[email protected]", "", {}, "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ=="],
941
 
942
  "ws": ["[email protected]", "", { "peerDependencies": { "bufferutil": "^4.0.1", "utf-8-validate": ">=5.0.2" }, "optionalPeers": ["bufferutil", "utf-8-validate"] }, "sha512-PEIGCY5tSlUt50cqyMXfCzX+oOPqN0vuGqWzbcJ2xvnkzkq46oOpz7dQaTDBdfICb4N14+GARUDw2XV2N4tvzg=="],
 
951
 
952
  "@eslint/eslintrc/ignore": ["[email protected]", "", {}, "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g=="],
953
 
954
+ "@isaacs/cliui/string-width": ["[email protected]", "", { "dependencies": { "eastasianwidth": "^0.2.0", "emoji-regex": "^9.2.2", "strip-ansi": "^7.0.1" } }, "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA=="],
955
+
956
+ "@isaacs/cliui/strip-ansi": ["[email protected]", "", { "dependencies": { "ansi-regex": "^6.0.1" } }, "sha512-gmBGslpoQJtgnMAvOVqGZpEz9dyoKTCzy2nfz/n8aIFhN/jCE/rCmcxabB6jOOHV+0WNnylOxaxBQPSvcWklhA=="],
957
+
958
  "@langchain/core/zod": ["[email protected]", "", {}, "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ=="],
959
 
960
  "@langchain/langgraph/zod": ["[email protected]", "", {}, "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ=="],
961
 
962
  "@langchain/langgraph-sdk/uuid": ["[email protected]", "", { "bin": { "uuid": "dist/bin/uuid" } }, "sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA=="],
963
 
964
+ "@langchain/mcp-adapters/@modelcontextprotocol/sdk": ["@modelcontextprotocol/[email protected]", "", { "dependencies": { "ajv": "^6.12.6", "content-type": "^1.0.5", "cors": "^2.8.5", "cross-spawn": "^7.0.5", "eventsource": "^3.0.2", "eventsource-parser": "^3.0.0", "express": "^5.0.1", "express-rate-limit": "^7.5.0", "pkce-challenge": "^5.0.0", "raw-body": "^3.0.0", "zod": "^3.23.8", "zod-to-json-schema": "^3.24.1" } }, "sha512-JvKyB6YwS3quM+88JPR0axeRgvdDu3Pv6mdZUy+w4qVkCzGgumb9bXG/TmtDRQv+671yaofVfXSQmFLlWU5qPQ=="],
965
+
966
+ "@langchain/mcp-adapters/zod": ["[email protected]", "", {}, "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ=="],
967
+
968
+ "@modelcontextprotocol/sdk/zod": ["[email protected]", "", {}, "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ=="],
969
+
970
+ "@modelcontextprotocol/server-filesystem/@modelcontextprotocol/sdk": ["@modelcontextprotocol/[email protected]", "", { "dependencies": { "content-type": "^1.0.5", "raw-body": "^3.0.0", "zod": "^3.23.8" } }, "sha512-slLdFaxQJ9AlRg+hw28iiTtGvShAOgOKXcD0F91nUcRYiOMuS9ZBYjcdNZRXW9G5JQ511GRTdUy1zQVZDpJ+4w=="],
971
+
972
  "@typescript-eslint/typescript-estree/minimatch": ["[email protected]", "", { "dependencies": { "brace-expansion": "^2.0.1" } }, "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow=="],
973
 
974
  "@typescript-eslint/typescript-estree/semver": ["[email protected]", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA=="],
975
 
976
+ "body-parser/iconv-lite": ["[email protected]", "", { "dependencies": { "safer-buffer": ">= 2.1.2 < 3.0.0" } }, "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw=="],
977
+
978
  "chalk/ansi-styles": ["[email protected]", "", { "dependencies": { "color-convert": "^2.0.1" } }, "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg=="],
979
 
980
  "chokidar/glob-parent": ["[email protected]", "", { "dependencies": { "is-glob": "^4.0.1" } }, "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow=="],
 
989
 
990
  "fast-glob/glob-parent": ["[email protected]", "", { "dependencies": { "is-glob": "^4.0.1" } }, "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow=="],
991
 
992
+ "glob/minimatch": ["[email protected]", "", { "dependencies": { "brace-expansion": "^2.0.1" } }, "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow=="],
993
+
994
  "langsmith/semver": ["[email protected]", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA=="],
995
 
996
+ "rimraf/glob": ["[email protected]", "", { "dependencies": { "fs.realpath": "^1.0.0", "inflight": "^1.0.4", "inherits": "2", "minimatch": "^3.1.1", "once": "^1.3.0", "path-is-absolute": "^1.0.0" } }, "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q=="],
997
+
998
+ "wrap-ansi/ansi-styles": ["[email protected]", "", {}, "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg=="],
999
+
1000
+ "wrap-ansi/string-width": ["[email protected]", "", { "dependencies": { "eastasianwidth": "^0.2.0", "emoji-regex": "^9.2.2", "strip-ansi": "^7.0.1" } }, "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA=="],
1001
+
1002
+ "wrap-ansi/strip-ansi": ["[email protected]", "", { "dependencies": { "ansi-regex": "^6.0.1" } }, "sha512-gmBGslpoQJtgnMAvOVqGZpEz9dyoKTCzy2nfz/n8aIFhN/jCE/rCmcxabB6jOOHV+0WNnylOxaxBQPSvcWklhA=="],
1003
+
1004
+ "wrap-ansi-cjs/ansi-styles": ["[email protected]", "", { "dependencies": { "color-convert": "^2.0.1" } }, "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg=="],
1005
+
1006
+ "@isaacs/cliui/string-width/emoji-regex": ["[email protected]", "", {}, "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg=="],
1007
+
1008
+ "@isaacs/cliui/strip-ansi/ansi-regex": ["[email protected]", "", {}, "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg=="],
1009
+
1010
+ "@modelcontextprotocol/server-filesystem/@modelcontextprotocol/sdk/zod": ["[email protected]", "", {}, "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ=="],
1011
+
1012
  "@typescript-eslint/typescript-estree/minimatch/brace-expansion": ["[email protected]", "", { "dependencies": { "balanced-match": "^1.0.0" } }, "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ=="],
1013
+
1014
+ "glob/minimatch/brace-expansion": ["[email protected]", "", { "dependencies": { "balanced-match": "^1.0.0" } }, "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ=="],
1015
+
1016
+ "wrap-ansi/string-width/emoji-regex": ["[email protected]", "", {}, "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg=="],
1017
+
1018
+ "wrap-ansi/strip-ansi/ansi-regex": ["[email protected]", "", {}, "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg=="],
1019
  }
1020
  }
eslint.config.js CHANGED
@@ -37,6 +37,10 @@ export default [
37
  localStorage: "readonly",
38
  AbortController: "readonly",
39
  AbortSignal: "readonly",
 
 
 
 
40
  },
41
  },
42
  plugins: {
 
37
  localStorage: "readonly",
38
  AbortController: "readonly",
39
  AbortSignal: "readonly",
40
+ DOMParser: "readonly",
41
+ Document: "readonly",
42
+ Element: "readonly",
43
+ HTMLCollectionOf: "readonly",
44
  },
45
  },
46
  plugins: {
layers/structure.md CHANGED
@@ -25,7 +25,6 @@ AI-assisted iterative game development environment with real-time feedback
25
  - `bun run lint` - ESLint code quality check
26
  - `bun run format` - Auto-format with Prettier
27
  - `bun run validate` - Complete validation (format, lint, type check)
28
- - `bun run update` - Update llms.txt from VibeGame
29
 
30
  ## Layout
31
 
@@ -55,7 +54,7 @@ vibegame/
55
  ├── tsconfig.json # TypeScript configuration
56
  ├── vite.config.ts # Vite + Svelte + VibeGame config
57
  ├── bun.lock # Dependency lock file
58
- ├── llms.txt # VibeGame documentation
59
  └── README.md
60
  ```
61
 
 
25
  - `bun run lint` - ESLint code quality check
26
  - `bun run format` - Auto-format with Prettier
27
  - `bun run validate` - Complete validation (format, lint, type check)
 
28
 
29
  ## Layout
30
 
 
54
  ├── tsconfig.json # TypeScript configuration
55
  ├── vite.config.ts # Vite + Svelte + VibeGame config
56
  ├── bun.lock # Dependency lock file
57
+ ├── agents.md # VibeGame documentation
58
  └── README.md
59
  ```
60
 
llms.txt DELETED
@@ -1,1140 +0,0 @@
1
- # VibeGame
2
-
3
- A 3D game engine with declarative XML syntax and ECS architecture. Start playing immediately with automatic player, camera, and lighting - just add a ground to prevent falling.
4
-
5
- ## Instant Playable Game
6
-
7
- ```html
8
- <script src="https://cdn.jsdelivr.net/npm/vibegame@latest/dist/cdn/vibegame.standalone.iife.js"></script>
9
-
10
- <world canvas="#game-canvas" sky="#87ceeb">
11
- <!-- Ground (REQUIRED to prevent player falling) -->
12
- <static-part pos="0 -0.5 0" shape="box" size="20 1 20" color="#90ee90"></static-part>
13
- </world>
14
-
15
- <canvas id="game-canvas"></canvas>
16
- <script>
17
- GAME.run();
18
- </script>
19
- ```
20
-
21
- This creates a complete game with:
22
- - ✅ Player character (auto-created)
23
- - ✅ Orbital camera (auto-created)
24
- - ✅ Directional + ambient lighting (auto-created)
25
- - ✅ Ground platform (you provide this)
26
- - ✅ WASD movement, mouse camera, space to jump
27
-
28
- ## Development Setup
29
-
30
- After installation with `npm create vibegame@latest my-game`:
31
-
32
- ```bash
33
- cd my-game
34
- bun dev # Start dev server with hot reload
35
- ```
36
-
37
- ### Project Structure
38
- - **TypeScript** - Full TypeScript support with strict type checking
39
- - **src/main.ts** - Entry point for your game
40
- - **index.html** - HTML template with canvas element
41
- - **vite.config.ts** - Build configuration
42
-
43
- ### Commands
44
- - `bun dev` - Development server with hot reload
45
- - `bun run build` - Production build
46
- - `bun run preview` - Preview production build
47
- - `bun run check` - TypeScript type checking
48
- - `bun run lint` - Lint code with ESLint
49
- - `bun run format` - Format code with Prettier
50
- - `bun run update` - Update llms.txt after upgrading vibegame
51
-
52
- ## Physics Objects
53
-
54
- ```xml
55
- <world canvas="#game-canvas">
56
- <!-- 1. Static: Never moves (grounds, walls, platforms) -->
57
- <static-part pos="0 -0.5 0" shape="box" size="20 1 20" color="#808080"></static-part>
58
-
59
- <!-- 2. Dynamic: Falls with gravity (balls, crates, debris) -->
60
- <dynamic-part pos="0 5 0" shape="sphere" size="1" color="#ff0000"></dynamic-part>
61
-
62
- <!-- 3. Kinematic: Script-controlled movement (moving platforms, doors) -->
63
- <kinematic-part pos="5 2 0" shape="box" size="3 0.5 3" color="#0000ff">
64
- <!-- Animate the platform up and down -->
65
- <tween target="body.pos-y" from="2" to="5" duration="3" loop="ping-pong"></tween>
66
- </kinematic-part>
67
- </world>
68
- ```
69
-
70
- ## CRITICAL: Physics Position vs Transform Position
71
-
72
- <warning>
73
- ⚠️ **Physics bodies override transform positions!**
74
- Always set position on the body, not the transform, for physics entities.
75
- </warning>
76
-
77
- ```xml
78
- <!-- ✅ BEST: Use recipe with pos shorthand -->
79
- <dynamic-part pos="0 5 0" shape="sphere" size="1"></dynamic-part>
80
-
81
- <!-- ❌ WRONG: Transform position ignored if body exists -->
82
- <entity transform="pos: 0 5 0" body collider></entity> <!-- Falls to 0,0,0! -->
83
-
84
- <!-- ✅ CORRECT: Set body position explicitly (if using raw entity) -->
85
- <entity transform body="pos: 0 5 0" collider></entity>
86
- ```
87
-
88
- ## ECS Architecture Explained
89
-
90
- Unlike traditional game engines with GameObjects, VibeGame uses Entity-Component-System:
91
-
92
- - **Entities**: Just numbers (IDs), no data or behavior
93
- - **Components**: Pure data containers (position, health, color)
94
- - **Systems**: Functions that process entities with specific components
95
-
96
- ```typescript
97
- // Component = Data only
98
- const Health = GAME.defineComponent({
99
- current: GAME.Types.f32,
100
- max: GAME.Types.f32
101
- });
102
-
103
- // System = Logic only
104
- const healthQuery = GAME.defineQuery([Health]);
105
- const DamageSystem: GAME.System = {
106
- update: (state) => {
107
- const entities = healthQuery(state.world);
108
- for (const entity of entities) {
109
- Health.current[entity] -= 1 * state.time.deltaTime;
110
- if (Health.current[entity] <= 0) {
111
- state.destroyEntity(entity);
112
- }
113
- }
114
- }
115
- };
116
- ```
117
-
118
- ## What's Auto-Created (Game Engine Defaults)
119
-
120
- The engine automatically creates these if missing:
121
- 1. **Player** - Character with physics, controls, and respawn (at 0, 1, 0)
122
- 2. **Camera** - Orbital camera following the player
123
- 3. **Lighting** - Ambient + directional light with shadows
124
-
125
- You only need to provide:
126
- - **Ground/platforms** - Or the player falls forever
127
- - **Game objects** - Whatever makes your game unique
128
-
129
- ### Override Auto-Creation (When Needed)
130
-
131
- While auto-creation is recommended, you can manually create these for customization:
132
-
133
- ```xml
134
- <world canvas="#game-canvas">
135
- <static-part pos="0 -0.5 0" shape="box" size="20 1 20" color="#90ee90"></static-part>
136
-
137
- <!-- Custom player spawn position and properties -->
138
- <player pos="0 10 0" speed="8" jump-height="3"></player>
139
-
140
- <!-- Custom camera settings -->
141
- <camera orbit-camera="distance: 10; target-pitch: 0.5"></camera>
142
-
143
- <!-- Custom lighting (or use <light> for both ambient + directional) -->
144
- <ambient-light sky-color="#ff6b6b" ground-color="#4ecdc4" intensity="0.8"></ambient-light>
145
- <directional-light color="#ffffff" intensity="0.5" direction="-1 -2 -1"></directional-light>
146
- </world>
147
- ```
148
-
149
- **Best Practice**: Use auto-creation unless you specifically need custom positions, properties, or multiple instances. The defaults are well-tuned for most games.
150
-
151
- ## Post-Processing Effects
152
-
153
- ```xml
154
- <!-- Bloom effect for glow -->
155
- <camera bloom="intensity: 2; luminance-threshold: 0.8"></camera>
156
-
157
- <!-- Retro dithering (reduces color palette) -->
158
- <camera dithering="color-bits: 3; scale: 2; noise: 1"></camera>
159
-
160
- <!-- Tonemapping for HDR-like visuals -->
161
- <camera tonemapping="mode: aces-filmic"></camera>
162
-
163
- <!-- Combined cinematic style -->
164
- <camera bloom="intensity: 1.5" tonemapping="mode: aces-filmic"></camera>
165
- ```
166
-
167
- ## Common Game Patterns
168
-
169
- ### Basic Platformer
170
- ```xml
171
- <world canvas="#game-canvas">
172
- <!-- Ground -->
173
- <static-part pos="0 -0.5 0" shape="box" size="50 1 50" color="#90ee90"></static-part>
174
-
175
- <!-- Platforms at different heights -->
176
- <static-part pos="-5 2 0" shape="box" size="3 0.5 3" color="#808080"></static-part>
177
- <static-part pos="0 4 0" shape="box" size="3 0.5 3" color="#808080"></static-part>
178
- <static-part pos="5 6 0" shape="box" size="3 0.5 3" color="#808080"></static-part>
179
-
180
- <!-- Moving platform -->
181
- <kinematic-part pos="0 3 5" shape="box" size="4 0.5 4" color="#4169e1">
182
- <tween target="body.pos-x" from="-10" to="10" duration="5" loop="ping-pong"></tween>
183
- </kinematic-part>
184
-
185
- <!-- Goal area -->
186
- <static-part pos="10 8 0" shape="box" size="5 0.5 5" color="#ffd700"></static-part>
187
- </world>
188
- ```
189
-
190
- ### Collectible Coins (Collision-based)
191
- ```xml
192
- <world canvas="#game-canvas">
193
- <static-part pos="0 -0.5 0" shape="box" size="20 1 20" color="#90ee90"></static-part>
194
-
195
- <!-- Spinning coins -->
196
- <kinematic-part pos="2 1 0" shape="cylinder" size="0.5 0.1 0.5" color="#ffd700">
197
- <tween target="body.euler-y" from="0" to="360" duration="2" loop="loop"></tween>
198
- </kinematic-part>
199
-
200
- <kinematic-part pos="-2 1 0" shape="cylinder" size="0.5 0.1 0.5" color="#ffd700">
201
- <tween target="body.euler-y" from="0" to="360" duration="2" loop="loop"></tween>
202
- </kinematic-part>
203
- </world>
204
- ```
205
-
206
- ### Physics Playground
207
- ```xml
208
- <world canvas="#game-canvas">
209
- <!-- Ground -->
210
- <static-part pos="0 -0.5 0" shape="box" size="30 1 30" color="#90ee90"></static-part>
211
-
212
- <!-- Walls -->
213
- <static-part pos="15 5 0" shape="box" size="1 10 30" color="#808080"></static-part>
214
- <static-part pos="-15 5 0" shape="box" size="1 10 30" color="#808080"></static-part>
215
- <static-part pos="0 5 15" shape="box" size="30 10 1" color="#808080"></static-part>
216
- <static-part pos="0 5 -15" shape="box" size="30 10 1" color="#808080"></static-part>
217
-
218
- <!-- Spawn balls at different positions -->
219
- <dynamic-part pos="-5 10 0" shape="sphere" size="1" color="#ff0000"></dynamic-part>
220
- <dynamic-part pos="0 12 0" shape="sphere" size="1.5" color="#00ff00"></dynamic-part>
221
- <dynamic-part pos="5 8 0" shape="sphere" size="0.8" color="#0000ff"></dynamic-part>
222
-
223
- <!-- Bouncy ball (high restitution) -->
224
- <dynamic-part pos="0 15 5" shape="sphere" size="1" color="#ff00ff"
225
- collider="restitution: 0.9"></dynamic-part>
226
- </world>
227
- ```
228
-
229
- ## Recipe Reference
230
-
231
- | Recipe | Purpose | Key Attributes | Common Use |
232
- |--------|---------|---------------|------------|
233
- | `<static-part>` | Immovable objects | `pos`, `shape`, `size`, `color` | Grounds, walls, platforms |
234
- | `<dynamic-part>` | Gravity-affected objects | `pos`, `shape`, `size`, `color`, `mass` | Balls, crates, falling objects |
235
- | `<kinematic-part>` | Script-controlled physics | `pos`, `shape`, `size`, `color` | Moving platforms, doors |
236
- | `<player>` | Player character | `pos`, `speed`, `jump-height` | Main character (auto-created) |
237
- | `<entity>` | Base entity | Any components via attributes | Custom entities |
238
-
239
- ### Shape Options
240
- - `box` - Rectangular solid (default)
241
- - `sphere` - Ball shape
242
- - `cylinder` - Cylindrical shape
243
- - `capsule` - Pill shape (good for characters)
244
-
245
- ### Size Attribute
246
- - Box: `size="width height depth"` or `size="2 1 2"`
247
- - Sphere: `size="diameter"` or `size="1"`
248
- - Cylinder: `size="diameter height"` or `size="1 2"`
249
- - Broadcast: `size="2"` becomes `size="2 2 2"`
250
-
251
- ## How Recipes and Shorthands Work
252
-
253
- ### Everything is an Entity
254
- Every XML tag creates an entity. Recipes like `<static-part>` are just shortcuts for `<entity>` with preset components.
255
-
256
- ```xml
257
- <!-- These are equivalent: -->
258
- <static-part pos="0 0 0" color="#ff0000"></static-part>
259
-
260
- <entity
261
- transform
262
- body="type: fixed"
263
- collider
264
- renderer="color: 0xff0000"
265
- pos="0 0 0"></entity>
266
- ```
267
-
268
- ### Component Attributes
269
- Components are declared using bare attributes (no value means "use defaults"):
270
-
271
- ```xml
272
- <!-- Bare attributes declare components with default values -->
273
- <entity transform body collider renderer></entity>
274
-
275
- <!-- Add properties to override defaults -->
276
- <entity transform="pos-x: 5; pos-y: 2; pos-z: -3; scale: 2"></entity>
277
-
278
- <!-- Mix bare and valued attributes -->
279
- <entity transform="pos: 0 5 0" body="type: dynamic; mass: 10" collider renderer></entity>
280
-
281
- <!-- Property groups -->
282
- <entity transform="pos: 5 2 -3; scale: 2 2 2"></entity>
283
- ```
284
-
285
- **Important**: Bare attributes like `transform` mean "include this component with default values", NOT "empty" or "disabled".
286
-
287
- ### Automatic Shorthand Expansion
288
- Shorthands expand to ANY component with matching properties:
289
-
290
- ```xml
291
- <!-- "pos" shorthand applies to components with posX, posY, posZ -->
292
- <entity transform body pos="0 5 0"></entity>
293
- <!-- Both transform AND body get pos values -->
294
-
295
- <!-- "color" shorthand applies to renderer.color -->
296
- <entity renderer color="#ff0000"></entity>
297
-
298
- <!-- "size" shorthand (broadcasts single value) -->
299
- <entity collider size="2"></entity>
300
- <!-- Expands to: sizeX: 2, sizeY: 2, sizeZ: 2 -->
301
-
302
- <!-- Multiple shorthands together -->
303
- <entity transform body collider renderer pos="0 5 0" size="1" color="#ff0000"></entity>
304
- ```
305
-
306
- ### Recipe Internals
307
- Recipes are registered component bundles with defaults:
308
-
309
- ```xml
310
- <!-- What <dynamic-part> actually is: -->
311
- <entity
312
- transform
313
- body="type: dynamic" <!-- Override -->
314
- collider
315
- renderer
316
- respawn
317
- ></entity>
318
-
319
- <!-- So this: -->
320
- <dynamic-part pos="0 5 0" color="#ff0000"></dynamic-part>
321
-
322
- <!-- Is really: -->
323
- <entity
324
- transform="pos: 0 5 0"
325
- body="type: dynamic; pos: 0 5 0" <!-- pos applies to body too! -->
326
- collider
327
- renderer="color: 0xff0000"
328
- respawn
329
- ></entity>
330
- ```
331
-
332
- ### Common Pitfall: Component Requirements
333
- ```xml
334
- <!-- ❌ BAD: Missing required components -->
335
- <entity pos="0 5 0"></entity> <!-- No transform component! -->
336
-
337
- <!-- ✅ GOOD: Explicit components -->
338
- <entity transform="pos: 0 5 0"></entity>
339
-
340
- <!-- ✅ BEST: Use recipe with built-in components -->
341
- <static-part pos="0 5 0"></static-part>
342
- ```
343
-
344
- ### Best Practices Summary
345
- 1. **Use recipes** (`<static-part>`, `<dynamic-part>`, etc.) instead of raw `<entity>` tags
346
- 2. **Use shorthands** (`pos`, `size`, `color`) for cleaner code
347
- 3. **Override only what you need** - recipes have good defaults
348
- 4. **Mix recipes with custom components** - e.g., `<dynamic-part health="max: 100">`
349
-
350
- ## Currently Supported Features
351
-
352
- ### ✅ What Works Well
353
- - **Basic platforming** - Jump puzzles, obstacle courses
354
- - **Physics interactions** - Balls, dominoes, stacking
355
- - **Moving platforms** - Via kinematic bodies + tweening
356
- - **Collectibles** - Using collision detection in systems
357
- - **Third-person character control** - WASD + mouse camera
358
- - **Gamepad support** - Xbox/PlayStation controllers
359
- - **Visual effects** - Tweening colors, positions, rotations
360
- - **Post-processing** - Bloom, dithering, and tonemapping effects for visual styling
361
-
362
- ### Example Prompts That Work
363
- - "Create a platformer with moving platforms and collectible coins"
364
- - "Make bouncing balls that collide with walls"
365
- - "Build an obstacle course with rotating platforms"
366
- - "Add falling crates that stack up"
367
- - "Create a simple parkour level"
368
-
369
- ## Features Not Yet Built-In
370
-
371
- ### ❌ Engine Features Not Available
372
- - **Multiplayer/Networking** - No server sync
373
- - **Sound/Audio** - No audio system yet
374
- - **Save/Load** - No persistence system
375
- - **Inventory** - No item management
376
- - **Dialog/NPCs** - No conversation system
377
- - **AI/Pathfinding** - No enemy AI
378
- - **Particles** - No particle effects
379
- - **Custom shaders** - Fixed rendering pipeline
380
- - **Terrain** - Use box platforms instead
381
-
382
- ### ✅ Available Through Web Platform
383
- - **UI/HUD** - Use standard HTML/CSS overlays on the canvas
384
- - **Animations** - GSAP is included for advanced UI animations
385
- - **Score display** → HTML elements positioned over canvas
386
- - **Menus** → Standard web UI (divs, buttons, etc.)
387
-
388
- ### Recommended Approaches
389
- - **UI** → Position HTML elements over the canvas with CSS
390
- - **Animations** → Use GSAP for smooth UI transitions
391
- - **Level progression** → Reload with different XML or hide/show worlds
392
- - **Enemy behavior** → Tweened movement patterns
393
- - **Interactions** → Collision detection in custom systems
394
-
395
- ## Common Mistakes to Avoid
396
-
397
- ### ❌ Forgetting the Ground
398
- ```xml
399
- <!-- BAD: No ground, player falls forever -->
400
- <world canvas="#game-canvas">
401
- <dynamic-part pos="0 5 0" shape="sphere"></dynamic-part>
402
- </world>
403
- ```
404
-
405
- ### ❌ Setting Transform Position on Physics Objects
406
- ```xml
407
- <!-- BAD: Transform position ignored -->
408
- <entity transform="pos: 0 5 0" body collider></entity>
409
-
410
- <!-- GOOD: Set body position (raw entity) -->
411
- <entity transform body="pos: 0 5 0" collider></entity>
412
-
413
- <!-- BEST: Use recipes with pos shorthand -->
414
- <dynamic-part pos="0 5 0" shape="sphere"></dynamic-part>
415
- ```
416
-
417
- ### ❌ Missing World Tag
418
- ```xml
419
- <!-- BAD: Entities outside world tag -->
420
- <static-part pos="0 0 0" shape="box"></static-part>
421
-
422
- <!-- GOOD: Everything inside world -->
423
- <world canvas="#game-canvas">
424
- <static-part pos="0 0 0" shape="box"></static-part>
425
- </world>
426
- ```
427
-
428
- ### ❌ Wrong Physics Type
429
- ```xml
430
- <!-- BAD: Dynamic platform (falls with gravity) -->
431
- <dynamic-part pos="0 3 0" shape="box">
432
- <tween target="body.pos-x" from="-5" to="5"></tween>
433
- </dynamic-part>
434
-
435
- <!-- GOOD: Kinematic for controlled movement -->
436
- <kinematic-part pos="0 3 0" shape="box">
437
- <tween target="body.pos-x" from="-5" to="5"></tween>
438
- </kinematic-part>
439
- ```
440
-
441
- ## Custom Components and Systems
442
-
443
- ### Creating a Health System
444
- ```typescript
445
- import * as GAME from 'vibegame';
446
-
447
- // Define the component
448
- const Health = GAME.defineComponent({
449
- current: GAME.Types.f32,
450
- max: GAME.Types.f32
451
- });
452
-
453
- // Create the system
454
- const HealthSystem: GAME.System = {
455
- update: (state) => {
456
- const entities = GAME.defineQuery([Health])(state.world);
457
- for (const entity of entities) {
458
- // Regenerate health over time
459
- if (Health.current[entity] < Health.max[entity]) {
460
- Health.current[entity] += 5 * state.time.deltaTime;
461
- }
462
- }
463
- }
464
- };
465
-
466
- // Bundle as plugin
467
- const HealthPlugin: GAME.Plugin = {
468
- components: { Health },
469
- systems: [HealthSystem],
470
- config: {
471
- defaults: {
472
- "health": { current: 100, max: 100 }
473
- }
474
- }
475
- };
476
-
477
- // Use in game
478
- GAME.withPlugin(HealthPlugin).run();
479
- ```
480
-
481
- ### Using in XML
482
- ```xml
483
- <world canvas="#game-canvas">
484
- <!-- Add health to a dynamic entity (best practice: use recipes) -->
485
- <dynamic-part pos="0 2 0" shape="sphere" color="#ff0000"
486
- health="current: 50; max: 100"></dynamic-part>
487
- </world>
488
- ```
489
-
490
- ## State API Reference
491
-
492
- Available in all systems via the `state` parameter:
493
-
494
- ### Entity Management
495
- - `createEntity(): number` - Create new entity
496
- - `destroyEntity(entity: number)` - Remove entity
497
- - `query(...Components): number[]` - Find entities with components
498
-
499
- ### Component Operations
500
- - `addComponent(entity, Component, data?)` - Add component
501
- - `removeComponent(entity, Component)` - Remove component
502
- - `hasComponent(entity, Component): boolean` - Check component
503
- - `getComponent(name: string): Component | null` - Get by name
504
-
505
- ### Time
506
- - `time.delta: number` - Frame time in seconds
507
- - `time.elapsed: number` - Total time in seconds
508
- - `time.fixed: number` - Fixed timestep (1/50)
509
-
510
- ### Physics Helpers
511
- - `addComponent(entity, ApplyImpulse, {x, y, z})` - One-time push
512
- - `addComponent(entity, ApplyForce, {x, y, z})` - Continuous force
513
- - `addComponent(entity, KinematicMove, {x, y, z})` - Move kinematic
514
-
515
- ## Plugin System
516
-
517
- ### Using Specific Plugins
518
- ```typescript
519
- import * as GAME from 'vibegame';
520
-
521
- // Start with no plugins
522
- GAME
523
- .withoutDefaultPlugins()
524
- .withPlugin(TransformsPlugin) // Just transforms
525
- .withPlugin(RenderingPlugin) // Add rendering
526
- .withPlugin(PhysicsPlugin) // Add physics
527
- .run();
528
- ```
529
-
530
- ### Default Plugin Bundle
531
- - **RecipesPlugin** - XML parsing and entity creation
532
- - **TransformsPlugin** - Position, rotation, scale, hierarchy
533
- - **RenderingPlugin** - Three.js meshes, lights, camera
534
- - **PhysicsPlugin** - Rapier physics simulation
535
- - **InputPlugin** - Keyboard, mouse, gamepad input
536
- - **OrbitCameraPlugin** - Third-person camera
537
- - **PlayerPlugin** - Character controller
538
- - **TweenPlugin** - Animation system
539
- - **RespawnPlugin** - Fall detection and reset
540
- - **StartupPlugin** - Auto-create player/camera/lights
541
-
542
- ## Module Documentation
543
-
544
- ### Core
545
- Math utilities for interpolation and 3D transformations.
546
-
547
- ### Animation
548
- Procedural character animation with body parts that respond to movement states.
549
-
550
- ### Input
551
- Focus-aware input handling for mouse, keyboard, and gamepad with buffered actions. Keyboard input only responds when canvas has focus.
552
-
553
- ### Orbit Camera
554
- Orbital camera controller for third-person views and smooth target following.
555
-
556
- ### Physics
557
- 3D physics simulation with Rapier including rigid bodies, collisions, and character controllers.
558
-
559
- ### Player
560
- Complete player character controller with physics movement and jumping.
561
-
562
- ### Recipes
563
- Foundation for declarative XML entity creation with parent-child hierarchies and attribute shorthands.
564
-
565
- ### Rendering
566
- Three.js rendering pipeline with meshes, lights, cameras, and post-processing effects.
567
-
568
- ### Respawn
569
- Automatic respawn system that resets entities when falling below Y=-100.
570
-
571
- ### Startup
572
- Auto-creates player, camera, and lighting entities at startup if missing.
573
-
574
- ### Transforms
575
- 3D transforms with position, rotation, scale, and parent-child hierarchies.
576
-
577
- ### Tweening
578
- Animates component properties with easing functions and loop modes.
579
-
580
- ## Plugin Reference
581
-
582
- ### Core
583
-
584
- ### Functions
585
-
586
- #### lerp(a, b, t): number
587
- Linear interpolation
588
-
589
- #### slerp(fromX, fromY, fromZ, fromW, toX, toY, toZ, toW, t): Quaternion
590
- Quaternion spherical interpolation
591
-
592
- ### Animation
593
-
594
- ### Components
595
-
596
- #### AnimatedCharacter
597
- - headEntity: eid
598
- - torsoEntity: eid
599
- - leftArmEntity: eid
600
- - rightArmEntity: eid
601
- - leftLegEntity: eid
602
- - rightLegEntity: eid
603
- - phase: f32 - Walk cycle phase (0-1)
604
- - jumpTime: f32
605
- - fallTime: f32
606
- - animationState: ui8 - 0=IDLE, 1=WALKING, 2=JUMPING, 3=FALLING, 4=LANDING
607
- - stateTransition: f32
608
-
609
- #### HasAnimator
610
- Tag component (no properties)
611
-
612
- ### Systems
613
-
614
- #### AnimatedCharacterInitializationSystem
615
- - Group: setup
616
- - Creates body part entities for AnimatedCharacter components
617
-
618
- #### AnimatedCharacterUpdateSystem
619
- - Group: simulation
620
- - Updates character animation based on movement and physics state
621
-
622
- ### Input
623
-
624
- ### Components
625
-
626
- #### InputState
627
- - moveX: f32 - Horizontal axis (-1 left, 1 right)
628
- - moveY: f32 - Forward/backward (-1 back, 1 forward)
629
- - moveZ: f32 - Vertical axis (-1 down, 1 up)
630
- - lookX: f32 - Mouse delta X
631
- - lookY: f32 - Mouse delta Y
632
- - scrollDelta: f32 - Mouse wheel delta
633
- - jump: ui8 - Jump available (0/1)
634
- - primaryAction: ui8 - Primary action (0/1)
635
- - secondaryAction: ui8 - Secondary action (0/1)
636
- - leftMouse: ui8 - Left button (0/1)
637
- - rightMouse: ui8 - Right button (0/1)
638
- - middleMouse: ui8 - Middle button (0/1)
639
- - jumpBufferTime: f32
640
- - primaryBufferTime: f32
641
- - secondaryBufferTime: f32
642
-
643
- ### Systems
644
-
645
- #### InputSystem
646
- - Group: simulation
647
- - Updates InputState components with current input data
648
-
649
- ### Functions
650
-
651
- #### setTargetCanvas(canvas: HTMLCanvasElement | null): void
652
- Registers canvas for focus-based keyboard input
653
-
654
- #### consumeJump(): boolean
655
- Consumes buffered jump input
656
-
657
- #### consumePrimary(): boolean
658
- Consumes buffered primary action
659
-
660
- #### consumeSecondary(): boolean
661
- Consumes buffered secondary action
662
-
663
- #### handleMouseMove(event: MouseEvent): void
664
- Processes mouse movement
665
-
666
- #### handleMouseDown(event: MouseEvent): void
667
- Processes mouse button press
668
-
669
- #### handleMouseUp(event: MouseEvent): void
670
- Processes mouse button release
671
-
672
- #### handleWheel(event: WheelEvent): void
673
- Processes mouse wheel
674
-
675
- ### Constants
676
-
677
- #### INPUT_CONFIG
678
- Default input mappings and sensitivity settings
679
-
680
- ### Orbit Camera
681
-
682
- ### Components
683
-
684
- #### OrbitCamera
685
- - target: eid (0) - Target entity ID
686
- - current-yaw: f32 (π) - Current horizontal angle
687
- - current-pitch: f32 (π/6) - Current vertical angle
688
- - current-distance: f32 (4) - Current distance
689
- - target-yaw: f32 (π) - Target horizontal angle
690
- - target-pitch: f32 (π/6) - Target vertical angle
691
- - target-distance: f32 (4) - Target distance
692
- - min-distance: f32 (1)
693
- - max-distance: f32 (25)
694
- - min-pitch: f32 (0)
695
- - max-pitch: f32 (π/2)
696
- - smoothness: f32 (0.5) - Interpolation speed
697
- - offset-x: f32 (0)
698
- - offset-y: f32 (1.25)
699
- - offset-z: f32 (0)
700
-
701
- ### Systems
702
-
703
- #### OrbitCameraSystem
704
- - Group: draw
705
- - Updates camera position and rotation around target
706
-
707
- ### Recipes
708
-
709
- #### camera
710
- - Creates orbital camera with default settings
711
- - Components: orbit-camera, transform, world-transform, main-camera
712
-
713
- ### Physics
714
-
715
- ### Constants
716
-
717
- - DEFAULT_GRAVITY: -60
718
-
719
- ### Enums
720
-
721
- #### BodyType
722
- - Dynamic = 0 - Affected by forces
723
- - Fixed = 1 - Immovable static
724
- - KinematicPositionBased = 2 - Script position
725
- - KinematicVelocityBased = 3 - Script velocity
726
-
727
- #### ColliderShape
728
- - Box = 0
729
- - Sphere = 1
730
- - Capsule = 2
731
-
732
- ### Components
733
-
734
- #### PhysicsWorld
735
- - gravityX: f32 (0)
736
- - gravityY: f32 (-60)
737
- - gravityZ: f32 (0)
738
-
739
- #### Body
740
- - type: ui8 - BodyType enum (Fixed)
741
- - mass: f32 (1)
742
- - linearDamping: f32 (0)
743
- - angularDamping: f32 (0)
744
- - gravityScale: f32 (1)
745
- - ccd: ui8 (0)
746
- - lockRotX: ui8 (0)
747
- - lockRotY: ui8 (0)
748
- - lockRotZ: ui8 (0)
749
- - posX, posY, posZ: f32
750
- - rotX, rotY, rotZ, rotW: f32 (rotW=1)
751
- - eulerX, eulerY, eulerZ: f32
752
- - velX, velY, velZ: f32
753
- - rotVelX, rotVelY, rotVelZ: f32
754
-
755
- #### Collider
756
- - shape: ui8 - ColliderShape enum (Box)
757
- - sizeX, sizeY, sizeZ: f32 (1)
758
- - radius: f32 (0.5)
759
- - height: f32 (1)
760
- - friction: f32 (0.5)
761
- - restitution: f32 (0)
762
- - density: f32 (1)
763
- - isSensor: ui8 (0)
764
- - membershipGroups: ui16 (0xffff)
765
- - filterGroups: ui16 (0xffff)
766
- - posOffsetX, posOffsetY, posOffsetZ: f32
767
- - rotOffsetX, rotOffsetY, rotOffsetZ, rotOffsetW: f32 (rotOffsetW=1)
768
-
769
- #### CharacterController
770
- - offset: f32 (0.08)
771
- - maxSlope: f32 (45°)
772
- - maxSlide: f32 (30°)
773
- - snapDist: f32 (0.5)
774
- - autoStep: ui8 (1)
775
- - maxStepHeight: f32 (0.3)
776
- - minStepWidth: f32 (0.05)
777
- - upX, upY, upZ: f32 (upY=1)
778
- - moveX, moveY, moveZ: f32
779
- - grounded: ui8
780
-
781
- #### CharacterMovement
782
- - desiredVelX, desiredVelY, desiredVelZ: f32
783
- - velocityY: f32
784
- - actualMoveX, actualMoveY, actualMoveZ: f32
785
-
786
- #### InterpolatedTransform
787
- - prevPosX, prevPosY, prevPosZ: f32
788
- - prevRotX, prevRotY, prevRotZ, prevRotW: f32
789
- - posX, posY, posZ: f32
790
- - rotX, rotY, rotZ, rotW: f32
791
-
792
- #### Force/Impulse Components
793
- - ApplyForce: x, y, z (f32)
794
- - ApplyTorque: x, y, z (f32)
795
- - ApplyImpulse: x, y, z (f32)
796
- - ApplyAngularImpulse: x, y, z (f32)
797
- - SetLinearVelocity: x, y, z (f32)
798
- - SetAngularVelocity: x, y, z (f32)
799
- - KinematicMove: x, y, z (f32)
800
- - KinematicRotate: x, y, z, w (f32)
801
-
802
- #### Collision Events
803
- - CollisionEvents: activeEvents (ui8)
804
- - TouchedEvent: other, handle1, handle2 (ui32)
805
- - TouchEndedEvent: other, handle1, handle2 (ui32)
806
-
807
- ### Systems
808
-
809
- - PhysicsWorldSystem - Initializes physics world
810
- - PhysicsInitializationSystem - Creates bodies and colliders
811
- - CharacterMovementSystem - Character controller movement
812
- - PhysicsCleanupSystem - Removes physics on entity destroy
813
- - CollisionEventCleanupSystem - Clears collision events
814
- - ApplyForcesSystem - Applies forces
815
- - ApplyTorquesSystem - Applies torques
816
- - ApplyImpulsesSystem - Applies impulses
817
- - ApplyAngularImpulsesSystem - Applies angular impulses
818
- - SetVelocitySystem - Sets velocities
819
- - TeleportationSystem - Instant position changes
820
- - KinematicMovementSystem - Kinematic movement
821
- - PhysicsStepSystem - Steps simulation
822
- - PhysicsRapierSyncSystem - Syncs Rapier to ECS
823
- - PhysicsInterpolationSystem - Interpolates for rendering
824
-
825
- ### Functions
826
-
827
- #### initializePhysics(): Promise<void>
828
- Initializes Rapier WASM physics engine
829
-
830
- ### Recipes
831
-
832
- - static-part - Immovable physics objects
833
- - dynamic-part - Gravity-affected objects
834
- - kinematic-part - Script-controlled objects
835
-
836
- ### Player
837
-
838
- ### Components
839
-
840
- #### Player
841
- - speed: f32 (5.3)
842
- - jumpHeight: f32 (2.3)
843
- - rotationSpeed: f32 (10)
844
- - canJump: ui8 (1)
845
- - isJumping: ui8 (0)
846
- - jumpCooldown: f32 (0)
847
- - lastGroundedTime: f32 (0)
848
- - jumpBufferTime: f32 (-10000)
849
- - cameraSensitivity: f32 (0.007)
850
- - cameraZoomSensitivity: f32 (1.5)
851
- - cameraEntity: eid (0)
852
-
853
- ### Systems
854
-
855
- #### PlayerMovementSystem
856
- - Group: fixed
857
- - Handles movement, rotation, and jumping from input
858
-
859
- #### PlayerGroundedSystem
860
- - Group: fixed
861
- - Tracks grounded state and jump availability
862
-
863
- #### PlayerCameraLinkingSystem
864
- - Group: simulation
865
- - Links player to orbit camera
866
-
867
- #### PlayerCameraControlSystem
868
- - Group: simulation
869
- - Camera control via mouse input
870
-
871
- ### Recipes
872
-
873
- #### player
874
- - Complete player setup with physics
875
- - Components: player, character-movement, transform, world-transform, body, collider, character-controller, input-state, respawn
876
-
877
- ### Functions
878
-
879
- #### processInput(moveForward, moveRight, cameraYaw): Vector3
880
- Converts input to world-space movement
881
-
882
- #### handleJump(entity, jumpPressed, currentTime): number
883
- Processes jump with buffering
884
-
885
- #### updateRotation(entity, inputVector, deltaTime, rotationData): Quaternion
886
- Smooth rotation towards movement
887
-
888
- ### Recipes
889
-
890
- ### Components
891
-
892
- #### Parent
893
- - entity: i32 - Parent entity ID
894
-
895
- ### Functions
896
-
897
- #### parseXMLToEntities(state, xmlContent): EntityCreationResult[]
898
- Converts XML elements to ECS entities with hierarchy
899
-
900
- #### createEntityFromRecipe(state, recipeName, attributes?): number
901
- Creates entity from recipe with attributes
902
-
903
- #### fromEuler(x, y, z): Quaternion
904
- Converts Euler angles (radians) to quaternion
905
-
906
- ### Types
907
-
908
- #### EntityCreationResult
909
- - entity: number - Entity ID
910
- - tagName: string - Recipe name
911
- - children: EntityCreationResult[]
912
-
913
- ### Recipes
914
-
915
- #### entity
916
- - Base recipe with no default components
917
-
918
- ### Property Formats
919
-
920
- - Single value: `transform="scale: 2"`
921
- - Vector3: `transform="pos: 0 5 -3"`
922
- - Broadcast: `transform="scale: 2"` → scale: 2 2 2
923
- - Euler angles: `transform="euler: 0 45 0"` (degrees)
924
- - Multiple: `transform="pos: 0 5 0; euler: 0 45 0"`
925
- - Shorthands: `pos="0 5 0"` → transform component
926
-
927
- ### Rendering
928
-
929
- ### Components
930
-
931
- #### Renderer
932
- - shape: ui8 - 0=box, 1=sphere, 2=cylinder, 3=plane
933
- - sizeX, sizeY, sizeZ: f32 (1)
934
- - color: ui32 (0xffffff)
935
- - visible: ui8 (1)
936
-
937
- #### RenderContext
938
- - clearColor: ui32 (0x000000)
939
- - hasCanvas: ui8
940
-
941
- #### MainCamera
942
- Tag component (no properties)
943
-
944
- #### Ambient
945
- - skyColor: ui32 (0x87ceeb)
946
- - groundColor: ui32 (0x4a4a4a)
947
- - intensity: f32 (0.6)
948
-
949
- #### Directional
950
- - color: ui32 (0xffffff)
951
- - intensity: f32 (1)
952
- - castShadow: ui8 (1)
953
- - shadowMapSize: ui32 (4096)
954
- - directionX: f32 (-1)
955
- - directionY: f32 (2)
956
- - directionZ: f32 (-1)
957
- - distance: f32 (30)
958
-
959
- #### Bloom
960
- - intensity: f32 (1.0) - Bloom intensity
961
- - luminanceThreshold: f32 (1.0) - Luminance threshold for bloom
962
- - luminanceSmoothing: f32 (0.03) - Smoothness of luminance threshold
963
- - mipmapBlur: ui8 (1) - Enable mipmap blur
964
- - radius: f32 (0.85) - Blur radius for mipmap blur
965
- - levels: ui8 (8) - Number of MIP levels for mipmap blur
966
-
967
- #### Dithering
968
- - colorBits: ui8 (4) - Bits per color channel (1-8)
969
- - intensity: f32 (1.0) - Effect intensity (0-1)
970
- - grayscale: ui8 (0) - Enable grayscale mode (0/1)
971
- - scale: f32 (1.0) - Pattern scale (higher = coarser dithering)
972
- - noise: f32 (1.0) - Noise threshold intensity
973
-
974
- ### Systems
975
-
976
- #### MeshInstanceSystem
977
- - Group: draw
978
- - Synchronizes transforms with Three.js meshes
979
-
980
- #### LightSyncSystem
981
- - Group: draw
982
- - Updates Three.js lights
983
-
984
- #### CameraSyncSystem
985
- - Group: draw
986
- - Updates Three.js camera and manages post-processing effects
987
-
988
- #### WebGLRenderSystem
989
- - Group: draw (last)
990
- - Renders scene through EffectComposer
991
-
992
- ### Functions
993
-
994
- #### setCanvasElement(entity, canvas): void
995
- Associates canvas with RenderContext
996
-
997
- ### Recipes
998
-
999
- - ambient-light - Ambient hemisphere lighting
1000
- - directional-light - Directional light with shadows
1001
- - light - Both ambient and directional
1002
-
1003
- ### Respawn
1004
-
1005
- ### Components
1006
-
1007
- #### Respawn
1008
- - posX, posY, posZ: f32 - Spawn position
1009
- - eulerX, eulerY, eulerZ: f32 - Spawn rotation (degrees)
1010
-
1011
- ### Systems
1012
-
1013
- #### RespawnSystem
1014
- - Group: simulation
1015
- - Resets entities when Y < -100
1016
-
1017
- ### Startup
1018
-
1019
- ### Systems
1020
-
1021
- #### LightingStartupSystem
1022
- - Group: setup
1023
- - Creates default lighting if none exists
1024
-
1025
- #### CameraStartupSystem
1026
- - Group: setup
1027
- - Creates main camera if none exists
1028
-
1029
- #### PlayerStartupSystem
1030
- - Group: setup
1031
- - Creates player entity if none exists
1032
-
1033
- #### PlayerCharacterSystem
1034
- - Group: setup
1035
- - Adds animated character to players
1036
-
1037
- ### Transforms
1038
-
1039
- ### Components
1040
-
1041
- #### Transform
1042
- - posX, posY, posZ: f32 (0)
1043
- - rotX, rotY, rotZ, rotW: f32 (rotW=1) - Quaternion
1044
- - eulerX, eulerY, eulerZ: f32 (0) - Degrees
1045
- - scaleX, scaleY, scaleZ: f32 (1)
1046
-
1047
- #### WorldTransform
1048
- - Same properties as Transform
1049
- - Auto-computed from hierarchy (read-only)
1050
-
1051
- ### Systems
1052
-
1053
- #### TransformHierarchySystem
1054
- - Group: simulation (last)
1055
- - Syncs euler/quaternion and computes world transforms
1056
-
1057
- ### Tweening
1058
-
1059
- ### Components
1060
-
1061
- #### Tween
1062
- - duration: f32 (1) - Seconds
1063
- - elapsed: f32
1064
- - easingIndex: ui8
1065
- - loopMode: ui8 - 0=Once, 1=Loop, 2=PingPong
1066
-
1067
- #### TweenValue
1068
- - source: ui32 - Tween entity
1069
- - target: ui32 - Target entity
1070
- - componentId: ui32
1071
- - fieldIndex: ui32
1072
- - from: f32
1073
- - to: f32
1074
- - value: f32 - Current value
1075
-
1076
- ### Systems
1077
-
1078
- #### TweenSystem
1079
- - Group: simulation
1080
- - Interpolates values with easing and auto-cleanup
1081
-
1082
- ### Functions
1083
-
1084
- #### createTween(state, entity, target, options): number | null
1085
- Animates component property
1086
-
1087
- ### Easing Functions
1088
-
1089
- - linear
1090
- - sine-in, sine-out, sine-in-out
1091
- - quad-in, quad-out, quad-in-out
1092
- - cubic-in, cubic-out, cubic-in-out
1093
- - quart-in, quart-out, quart-in-out
1094
- - expo-in, expo-out, expo-in-out
1095
- - circ-in, circ-out, circ-in-out
1096
- - back-in, back-out, back-in-out
1097
- - elastic-in, elastic-out, elastic-in-out
1098
- - bounce-in, bounce-out, bounce-in-out
1099
-
1100
- ### Loop Modes
1101
-
1102
- - once - Play once and destroy
1103
- - loop - Repeat indefinitely
1104
- - ping-pong - Alternate directions
1105
-
1106
- ### Shorthand Targets
1107
-
1108
- - rotation - body.eulerX/Y/Z
1109
- - at - body.posX/Y/Z
1110
- - scale - transform.scaleX/Y/Z
1111
-
1112
- ## API Reference (External Links)
1113
-
1114
- - [Core](https://dylanebert.github.io/vibegame/reference/core)
1115
- - [Animation](https://dylanebert.github.io/vibegame/reference/animation)
1116
- - [Input](https://dylanebert.github.io/vibegame/reference/input)
1117
- - [Orbit Camera](https://dylanebert.github.io/vibegame/reference/orbit-camera)
1118
- - [Physics](https://dylanebert.github.io/vibegame/reference/physics)
1119
- - [Player](https://dylanebert.github.io/vibegame/reference/player)
1120
- - [Recipes](https://dylanebert.github.io/vibegame/reference/recipes)
1121
- - [Rendering](https://dylanebert.github.io/vibegame/reference/rendering)
1122
- - [Respawn](https://dylanebert.github.io/vibegame/reference/respawn)
1123
- - [Startup](https://dylanebert.github.io/vibegame/reference/startup)
1124
- - [Transforms](https://dylanebert.github.io/vibegame/reference/transforms)
1125
- - [Tweening](https://dylanebert.github.io/vibegame/reference/tweening)
1126
-
1127
- ## Examples (External Links)
1128
-
1129
- - [Core](https://dylanebert.github.io/vibegame/examples/core)
1130
- - [Animation](https://dylanebert.github.io/vibegame/examples/animation)
1131
- - [Input](https://dylanebert.github.io/vibegame/examples/input)
1132
- - [Orbit Camera](https://dylanebert.github.io/vibegame/examples/orbit-camera)
1133
- - [Physics](https://dylanebert.github.io/vibegame/examples/physics)
1134
- - [Player](https://dylanebert.github.io/vibegame/examples/player)
1135
- - [Recipes](https://dylanebert.github.io/vibegame/examples/recipes)
1136
- - [Rendering](https://dylanebert.github.io/vibegame/examples/rendering)
1137
- - [Respawn](https://dylanebert.github.io/vibegame/examples/respawn)
1138
- - [Startup](https://dylanebert.github.io/vibegame/examples/startup)
1139
- - [Transforms](https://dylanebert.github.io/vibegame/examples/transforms)
1140
- - [Tweening](https://dylanebert.github.io/vibegame/examples/tweening)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
package.json CHANGED
@@ -9,13 +9,13 @@
9
  "check": "tsc --noEmit && svelte-check",
10
  "check:ts": "tsc --noEmit",
11
  "check:svelte": "svelte-check",
12
- "update": "cp node_modules/vibegame/llms.txt ./llms.txt && echo '✓ Updated llms.txt from VibeGame'",
13
- "postinstall": "test -f node_modules/vibegame/llms.txt && cp node_modules/vibegame/llms.txt ./llms.txt || true",
14
  "format": "prettier --write .",
15
  "format:check": "prettier --check .",
16
  "lint": "eslint .",
17
  "lint:fix": "eslint . --fix",
18
- "validate": "bun run format:check && bun run lint && bun run check"
19
  },
20
  "devDependencies": {
21
  "@eslint/js": "^9.33.0",
@@ -40,13 +40,16 @@
40
  "@huggingface/inference": "^4.8.0",
41
  "@langchain/core": "^0.3.75",
42
  "@langchain/langgraph": "^0.4.9",
 
 
 
43
  "@types/marked": "^6.0.0",
44
  "@types/node": "^24.3.3",
45
  "gsap": "^3.13.0",
46
  "marked": "^16.2.1",
47
  "monaco-editor": "^0.50.0",
48
  "svelte-splitpanes": "^8.0.5",
49
- "vibegame": "^0.1.6",
50
  "zod": "^4.1.8"
51
  }
52
  }
 
9
  "check": "tsc --noEmit && svelte-check",
10
  "check:ts": "tsc --noEmit",
11
  "check:svelte": "svelte-check",
12
+ "test": "bun test",
13
+ "test:watch": "bun test --watch",
14
  "format": "prettier --write .",
15
  "format:check": "prettier --check .",
16
  "lint": "eslint .",
17
  "lint:fix": "eslint . --fix",
18
+ "validate": "bun run format:check && bun run lint --fix && bun run check"
19
  },
20
  "devDependencies": {
21
  "@eslint/js": "^9.33.0",
 
40
  "@huggingface/inference": "^4.8.0",
41
  "@langchain/core": "^0.3.75",
42
  "@langchain/langgraph": "^0.4.9",
43
+ "@langchain/mcp-adapters": "^0.6.0",
44
+ "@modelcontextprotocol/sdk": "^0.6.0",
45
+ "@modelcontextprotocol/server-filesystem": "^0.6.2",
46
  "@types/marked": "^6.0.0",
47
  "@types/node": "^24.3.3",
48
  "gsap": "^3.13.0",
49
  "marked": "^16.2.1",
50
  "monaco-editor": "^0.50.0",
51
  "svelte-splitpanes": "^8.0.5",
52
+ "vibegame": "^0.1.7",
53
  "zod": "^4.1.8"
54
  }
55
  }
src/App.svelte CHANGED
@@ -2,6 +2,7 @@
2
  import { onMount, onDestroy } from 'svelte';
3
  import { registerShortcuts, shortcuts } from './lib/config/shortcuts';
4
  import { loadingStore } from './lib/stores/loading';
 
5
  import AppHeader from './lib/components/layout/AppHeader.svelte';
6
  import SplitView from './lib/components/layout/SplitView.svelte';
7
  import LoadingScreen from './lib/components/layout/LoadingScreen.svelte';
@@ -11,6 +12,9 @@
11
  onMount(() => {
12
  loadingStore.startLoading();
13
 
 
 
 
14
  unregisterShortcuts = registerShortcuts(shortcuts);
15
 
16
  setTimeout(() => {
 
2
  import { onMount, onDestroy } from 'svelte';
3
  import { registerShortcuts, shortcuts } from './lib/config/shortcuts';
4
  import { loadingStore } from './lib/stores/loading';
5
+ import { contentManager } from './lib/services/content-manager';
6
  import AppHeader from './lib/components/layout/AppHeader.svelte';
7
  import SplitView from './lib/components/layout/SplitView.svelte';
8
  import LoadingScreen from './lib/components/layout/LoadingScreen.svelte';
 
12
  onMount(() => {
13
  loadingStore.startLoading();
14
 
15
+ // Initialize ContentManager
16
+ contentManager.initialize();
17
+
18
  unregisterShortcuts = registerShortcuts(shortcuts);
19
 
20
  setTimeout(() => {
src/lib/components/Editor.svelte CHANGED
@@ -1,7 +1,8 @@
1
  <script lang="ts">
2
- import { onMount, onDestroy, createEventDispatcher } from "svelte";
3
  import * as monaco from "monaco-editor";
4
  import type { editor } from "monaco-editor";
 
5
 
6
  import editorWorker from "monaco-editor/esm/vs/editor/editor.worker?worker";
7
  import jsonWorker from "monaco-editor/esm/vs/language/json/json.worker?worker";
@@ -9,16 +10,14 @@
9
  import htmlWorker from "monaco-editor/esm/vs/language/html/html.worker?worker";
10
  import tsWorker from "monaco-editor/esm/vs/language/typescript/ts.worker?worker";
11
 
12
- export let value: string = "";
13
  export let language: string = "html";
14
  export let theme: string = "vs-dark";
15
  export let readOnly: boolean = false;
16
 
17
- const dispatch = createEventDispatcher();
18
-
19
  let editorContainer: HTMLDivElement;
20
  let editorInstance: editor.IStandaloneCodeEditor | null = null;
21
  let isInternalUpdate = false;
 
22
 
23
  (self as any).MonacoEnvironment = {
24
  getWorker: function (_: any, label: string) {
@@ -44,8 +43,10 @@
44
  };
45
 
46
  onMount(() => {
 
 
47
  editorInstance = monaco.editor.create(editorContainer, {
48
- value: value,
49
  language: language,
50
  theme: theme,
51
  minimap: {
@@ -74,10 +75,32 @@
74
  },
75
  });
76
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
77
  editorInstance.onDidChangeModelContent(() => {
78
  if (!isInternalUpdate) {
79
  const newValue = editorInstance?.getValue() || "";
80
- dispatch("change", newValue);
81
  }
82
  });
83
 
@@ -92,15 +115,12 @@
92
  });
93
 
94
  onDestroy(() => {
 
 
 
95
  editorInstance?.dispose();
96
  });
97
 
98
- $: if (editorInstance && value !== editorInstance.getValue()) {
99
- isInternalUpdate = true;
100
- editorInstance.setValue(value);
101
- isInternalUpdate = false;
102
- }
103
-
104
  $: if (editorInstance) {
105
  const model = editorInstance.getModel();
106
  if (model) {
 
1
  <script lang="ts">
2
+ import { onMount, onDestroy } from "svelte";
3
  import * as monaco from "monaco-editor";
4
  import type { editor } from "monaco-editor";
5
+ import { contentManager } from "../services/content-manager";
6
 
7
  import editorWorker from "monaco-editor/esm/vs/editor/editor.worker?worker";
8
  import jsonWorker from "monaco-editor/esm/vs/language/json/json.worker?worker";
 
10
  import htmlWorker from "monaco-editor/esm/vs/language/html/html.worker?worker";
11
  import tsWorker from "monaco-editor/esm/vs/language/typescript/ts.worker?worker";
12
 
 
13
  export let language: string = "html";
14
  export let theme: string = "vs-dark";
15
  export let readOnly: boolean = false;
16
 
 
 
17
  let editorContainer: HTMLDivElement;
18
  let editorInstance: editor.IStandaloneCodeEditor | null = null;
19
  let isInternalUpdate = false;
20
+ let contentUnsubscribe: (() => void) | null = null;
21
 
22
  (self as any).MonacoEnvironment = {
23
  getWorker: function (_: any, label: string) {
 
43
  };
44
 
45
  onMount(() => {
46
+ const currentContent = contentManager.getCurrentContent();
47
+
48
  editorInstance = monaco.editor.create(editorContainer, {
49
+ value: currentContent,
50
  language: language,
51
  theme: theme,
52
  minimap: {
 
75
  },
76
  });
77
 
78
+ // Subscribe to ContentManager changes
79
+ contentUnsubscribe = contentManager.subscribe((state) => {
80
+ if (editorInstance && state.content !== editorInstance.getValue()) {
81
+ isInternalUpdate = true;
82
+
83
+ // Preserve cursor position
84
+ const position = editorInstance.getPosition();
85
+ const scrollTop = editorInstance.getScrollTop();
86
+
87
+ editorInstance.setValue(state.content);
88
+
89
+ // Restore cursor position if possible
90
+ if (position) {
91
+ editorInstance.setPosition(position);
92
+ }
93
+ editorInstance.setScrollTop(scrollTop);
94
+
95
+ isInternalUpdate = false;
96
+ }
97
+ });
98
+
99
+ // Handle user changes
100
  editorInstance.onDidChangeModelContent(() => {
101
  if (!isInternalUpdate) {
102
  const newValue = editorInstance?.getValue() || "";
103
+ contentManager.updateFromUI(newValue);
104
  }
105
  });
106
 
 
115
  });
116
 
117
  onDestroy(() => {
118
+ if (contentUnsubscribe) {
119
+ contentUnsubscribe();
120
+ }
121
  editorInstance?.dispose();
122
  });
123
 
 
 
 
 
 
 
124
  $: if (editorInstance) {
125
  const model = editorInstance.getModel();
126
  if (model) {
src/lib/components/chat/ChatPanel.svelte CHANGED
@@ -1,678 +1,276 @@
1
  <script lang="ts">
2
- import { onMount, onDestroy, afterUpdate } from "svelte";
3
- import { fade } from "svelte/transition";
4
- import { agentStore, isConnected, isProcessing } from "../../stores/agent";
5
- import { authStore } from "../../services/auth";
6
- import { agentService } from "../../services/agent";
7
- import gsap from "gsap";
8
- import ReasoningBlock from "./ReasoningBlock.svelte";
9
- import MarkdownRenderer from "./MarkdownRenderer.svelte";
10
- import InProgressBlock from "./InProgressBlock.svelte";
11
- import MessageSegment from "./MessageSegment.svelte";
12
- import ExampleMessages from "./ExampleMessages.svelte";
13
-
14
- let inputValue = "";
15
- let messagesContainer: HTMLDivElement;
16
- let sendButton: HTMLButtonElement;
17
- let stopButton: HTMLButtonElement;
18
- let inputTextarea: HTMLTextAreaElement;
19
- let authPromptBtn: HTMLButtonElement;
20
- let clearButton: HTMLButtonElement;
21
-
22
- let hasConnected = false;
23
-
24
- onMount(() => {
25
- if ($authStore.isAuthenticated && !$authStore.loading) {
26
- agentService.connect();
27
- hasConnected = true;
28
- }
29
-
30
- if (authPromptBtn) {
31
- gsap.to(authPromptBtn, {
32
- boxShadow: '0 0 25px rgba(255, 210, 30, 0.3)',
33
- duration: 2.5,
34
- repeat: -1,
35
- yoyo: true,
36
- ease: 'sine.inOut'
37
- });
38
- }
39
- });
40
-
41
- onDestroy(() => {
42
- agentService.disconnect();
43
-
44
- if (scrollAnimation) {
45
- scrollAnimation.kill();
46
- }
47
-
48
- if (sendButton) gsap.killTweensOf(sendButton);
49
- if (stopButton) gsap.killTweensOf(stopButton);
50
- if (inputTextarea) gsap.killTweensOf(inputTextarea);
51
- if (authPromptBtn) gsap.killTweensOf(authPromptBtn);
52
- if (clearButton) gsap.killTweensOf(clearButton);
53
- if (messagesContainer) gsap.killTweensOf(messagesContainer);
54
- });
55
-
56
- $: if ($authStore.isAuthenticated && !$authStore.loading && !hasConnected) {
57
- agentService.connect();
58
- hasConnected = true;
59
- }
60
-
61
- $: if (inputTextarea && $isConnected && !$isProcessing && $agentStore.messages.length === 0) {
62
- gsap.to(inputTextarea, {
63
- borderColor: 'rgba(65, 105, 225, 0.4)',
64
- duration: 2,
65
- repeat: -1,
66
- yoyo: true,
67
- ease: 'sine.inOut'
68
- });
69
- } else if (inputTextarea) {
70
- gsap.killTweensOf(inputTextarea);
71
- gsap.set(inputTextarea, {
72
- borderColor: 'rgba(255, 255, 255, 0.1)'
73
- });
74
- }
75
-
76
- let autoScroll = true;
77
- let isUserScrolling = false;
78
- let scrollAnimation: gsap.core.Tween | null = null;
79
-
80
- afterUpdate(() => {
81
- if (messagesContainer && autoScroll) {
82
- setTimeout(() => smoothScrollToBottom(), 100);
83
- }
84
- });
85
-
86
- function smoothScrollToBottom() {
87
- if (!messagesContainer || isUserScrolling) return;
88
-
89
- if (scrollAnimation) {
90
- scrollAnimation.kill();
91
- }
92
-
93
- const targetScroll = messagesContainer.scrollHeight - messagesContainer.clientHeight;
94
- const currentScroll = messagesContainer.scrollTop;
95
- const distance = Math.abs(targetScroll - currentScroll);
96
- const duration = Math.min(0.5, distance / 1000);
97
-
98
- scrollAnimation = gsap.to(messagesContainer, {
99
- scrollTop: targetScroll,
100
- duration: duration,
101
- ease: "power2.out",
102
- onComplete: () => {
103
- scrollAnimation = null;
104
- }
105
- });
106
- }
107
-
108
- function handleScroll() {
109
- if (!messagesContainer) return;
110
-
111
- const isNearBottom =
112
- messagesContainer.scrollHeight - messagesContainer.scrollTop - messagesContainer.clientHeight < 100;
113
 
114
- autoScroll = isNearBottom;
 
 
 
115
 
116
- if (!autoScroll && !isUserScrolling) {
117
- isUserScrolling = true;
118
- } else if (autoScroll) {
119
- isUserScrolling = false;
120
- }
121
  }
122
 
123
- function scrollToBottom() {
124
- autoScroll = true;
125
- isUserScrolling = false;
126
- smoothScrollToBottom();
 
127
  }
 
128
 
129
- function handleSubmit() {
130
- if (inputValue.trim() && $authStore.isAuthenticated && $isConnected && !$isProcessing) {
131
- if (sendButton) {
132
- gsap.to(sendButton, {
133
- scale: 0.9,
134
- duration: 0.1,
135
- ease: "power2.in",
136
- onComplete: () => {
137
- gsap.to(sendButton, {
138
- scale: 1,
139
- duration: 0.2,
140
- ease: "elastic.out(1, 0.5)",
141
- });
142
- },
143
- });
144
- }
145
-
146
- agentService.sendMessage(inputValue.trim());
147
- inputValue = "";
148
- }
149
  }
 
150
 
151
- function handleStop() {
152
- if ($isProcessing) {
153
- if (stopButton) {
154
- gsap.to(stopButton, {
155
- scale: 0.9,
156
- duration: 0.1,
157
- ease: "power2.in",
158
- onComplete: () => {
159
- if (stopButton) {
160
- gsap.to(stopButton, {
161
- scale: 1,
162
- duration: 0.2,
163
- ease: "elastic.out(1, 0.5)",
164
- });
165
- }
166
- },
167
- });
168
- }
169
-
170
- agentService.stopConversation();
171
- }
172
- }
173
 
174
- function handleExampleMessage(message: string) {
175
- if ($authStore.isAuthenticated && $isConnected && !$isProcessing) {
176
- agentService.sendMessage(message);
177
- }
178
- }
179
 
180
- function handleClearConversation() {
181
- if (clearButton) {
182
- gsap.to(clearButton, {
183
- scale: 0.9,
184
- duration: 0.1,
185
- ease: "power2.in",
186
- onComplete: () => {
187
- gsap.to(clearButton, {
188
- scale: 1,
189
- duration: 0.2,
190
- ease: "elastic.out(1, 0.5)",
191
- });
192
- },
193
- });
194
- }
195
-
196
- agentStore.clearMessages();
197
- }
198
 
199
- function handleKeydown(event: KeyboardEvent) {
200
- if (event.key === "Enter" && !event.shiftKey) {
201
- event.preventDefault();
202
- handleSubmit();
203
- }
204
- }
205
 
206
- function handleButtonMouseEnter() {
207
- if (sendButton && !sendButton.disabled) {
208
- gsap.to(sendButton, {
209
- scale: 1.05,
210
- duration: 0.2,
211
- ease: "power2.out",
212
- });
213
- }
214
  }
 
215
 
216
- function handleButtonMouseLeave() {
217
- if (sendButton) {
218
- gsap.to(sendButton, {
219
- scale: 1,
220
- duration: 0.2,
221
- ease: "power2.out",
222
- });
223
- }
224
- }
225
 
226
- function handleButtonMouseDown() {
227
- if (sendButton && !sendButton.disabled) {
228
- gsap.to(sendButton, {
229
- scale: 0.95,
230
- duration: 0.1,
231
- ease: "power2.in",
232
- });
233
- }
234
- }
235
 
236
- function handleButtonMouseUp() {
237
- if (sendButton && !sendButton.disabled) {
238
- gsap.to(sendButton, {
239
- scale: 1.05,
240
- duration: 0.1,
241
- ease: "power2.out",
242
- });
243
- }
244
- }
245
-
246
- $: {
247
- if (sendButton && !$isProcessing) {
248
- if (!$authStore.isAuthenticated || !$isConnected || !inputValue.trim()) {
249
- gsap.to(sendButton, {
250
- opacity: 0.3,
251
- duration: 0.2,
252
- ease: "power2.out",
253
- });
254
- } else {
255
- gsap.to(sendButton, {
256
- opacity: 1,
257
- duration: 0.2,
258
- ease: "power2.out",
259
- });
260
- }
261
- }
262
- }
263
  </script>
264
 
265
  <div class="chat-panel">
266
- <div class="chat-header">
267
- <span class="chat-title">Chat</span>
268
- {#if $agentStore.messages.length > 0 && $authStore.isAuthenticated && !$isProcessing}
269
- <button
270
- bind:this={clearButton}
271
- class="clear-button"
272
- on:click={handleClearConversation}
273
- title="Clear conversation"
274
- transition:fade={{ duration: 200 }}
275
- >
276
- <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
277
- <path d="M3 6h18M8 6V4a2 2 0 012-2h4a2 2 0 012 2v2m3 0v14a2 2 0 01-2 2H7a2 2 0 01-2-2V6h14zM10 11v6M14 11v6" />
278
- </svg>
279
- <span>Clear</span>
280
- </button>
281
- {/if}
282
- </div>
283
-
284
- {#if isUserScrolling}
285
- <button
286
- class="scroll-to-bottom"
287
- on:click={scrollToBottom}
288
- title="Scroll to bottom"
289
- transition:fade={{ duration: 200 }}
290
- >
291
- <svg width="20" height="20" viewBox="0 0 20 20" fill="currentColor">
292
- <path d="M10 14L5 9L6.41 7.59L10 11.17L13.59 7.59L15 9L10 14Z" />
293
- </svg>
 
 
 
 
 
 
 
294
  </button>
 
 
 
 
 
 
 
 
 
 
 
295
  {/if}
296
-
297
- <div class="messages" bind:this={messagesContainer} on:scroll={handleScroll}>
298
- {#if !$authStore.isAuthenticated && !$authStore.loading}
299
- <div class="auth-prompt">
300
- <button bind:this={authPromptBtn} on:click={() => authStore.login()} class="auth-prompt-btn">
301
- Sign in with 🤗 Hugging Face
302
- </button>
303
- </div>
304
- {:else}
305
- {#if $agentStore.messages.length === 0 && $isConnected}
306
- <ExampleMessages onSendMessage={handleExampleMessage} />
307
- {/if}
308
-
309
- {#each $agentStore.messages as message (message.id)}
310
- <div class="message {message.role}">
311
- {#if message.reasoning && message.role === "assistant"}
312
- <ReasoningBlock reasoning={message.reasoning} />
313
- {/if}
314
- {#if message.segments && message.segments.length > 0}
315
- <div class="message-segments">
316
- {#each message.segments as segment (segment.id)}
317
- <MessageSegment {segment} />
318
- {/each}
319
- </div>
320
- {:else if message.content && message.content.trim()}
321
- <div class="message-content">
322
- {#if message.role === "assistant"}
323
- <MarkdownRenderer content={message.content.trim()} streaming={false} />
324
- {:else}
325
- {message.content.trim()}
326
- {/if}
327
- </div>
328
- {/if}
329
- </div>
330
- {/each}
331
-
332
- {#if $agentStore.streamingStatus !== "idle" && (!$agentStore.streamingContent || $agentStore.streamingStatus === "thinking")}
333
- <InProgressBlock
334
- status={$agentStore.streamingStatus === "completing" ? "completing" : $agentStore.streamingStatus}
335
- content={$agentStore.streamingContent}
336
- startTime={$agentStore.thinkingStartTime || Date.now()}
337
- isExpanded={false}
338
- />
339
- {/if}
340
-
341
- {#if $agentStore.error}
342
- <div class="error-message">
343
- Error: {$agentStore.error}
344
- </div>
345
- {/if}
346
- {/if}
347
- </div>
348
-
349
- <div class="input-area">
350
- <textarea
351
- bind:this={inputTextarea}
352
- bind:value={inputValue}
353
- on:keydown={handleKeydown}
354
- placeholder={!$authStore.isAuthenticated
355
- ? "Sign in to chat..."
356
- : $isConnected
357
- ? $isProcessing
358
- ? "Processing..."
359
- : "Type a message..."
360
- : "Connecting..."}
361
- disabled={!$authStore.isAuthenticated || !$isConnected || $isProcessing}
362
- rows="1"
363
- />
364
- {#if $isProcessing}
365
- <button
366
- bind:this={stopButton}
367
- on:click={handleStop}
368
- class="stop-btn"
369
- title="Stop conversation"
370
- >
371
-
372
- </button>
373
- {:else}
374
- <button
375
- bind:this={sendButton}
376
- on:click={handleSubmit}
377
- on:mouseenter={handleButtonMouseEnter}
378
- on:mouseleave={handleButtonMouseLeave}
379
- on:mousedown={handleButtonMouseDown}
380
- on:mouseup={handleButtonMouseUp}
381
- disabled={!$authStore.isAuthenticated || !$isConnected || !inputValue.trim()}
382
- class="send-btn"
383
- title="Send message"
384
- >
385
-
386
- </button>
387
- {/if}
388
- </div>
389
  </div>
390
 
391
  <style>
392
- .chat-panel {
393
- display: flex;
394
- flex-direction: column;
395
- height: 100%;
396
- width: 100%;
397
- background: rgba(20, 20, 20, 0.5);
398
- border-top: 1px solid rgba(255, 255, 255, 0.1);
399
- position: relative;
400
- }
401
-
402
- .chat-header {
403
- display: flex;
404
- align-items: center;
405
- justify-content: space-between;
406
- padding: 0.5rem 0.75rem;
407
- background: rgba(0, 0, 0, 0.3);
408
- border-bottom: 1px solid rgba(255, 255, 255, 0.1);
409
- min-height: 2.5rem;
410
- }
411
-
412
- .chat-title {
413
- font-size: 0.875rem;
414
- font-weight: 500;
415
- color: rgba(255, 255, 255, 0.7);
416
- }
417
-
418
- .clear-button {
419
- display: flex;
420
- align-items: center;
421
- gap: 0.25rem;
422
- padding: 0.25rem 0.5rem;
423
- background: transparent;
424
- border: 1px solid rgba(255, 255, 255, 0.2);
425
- border-radius: 4px;
426
- color: rgba(255, 255, 255, 0.6);
427
- font-size: 0.75rem;
428
- cursor: pointer;
429
- transition: all 0.2s ease;
430
- }
431
-
432
- .clear-button:hover {
433
- background: rgba(244, 67, 54, 0.1);
434
- border-color: rgba(244, 67, 54, 0.3);
435
- color: rgba(244, 67, 54, 0.9);
436
- }
437
-
438
- .clear-button:active {
439
- transform: scale(0.95);
440
- }
441
-
442
- .clear-button svg {
443
- width: 14px;
444
- height: 14px;
445
- }
446
-
447
- .scroll-to-bottom {
448
- position: absolute;
449
- bottom: 4.5rem;
450
- right: 1rem;
451
- width: 36px;
452
- height: 36px;
453
- border-radius: 50%;
454
- background: rgba(65, 105, 225, 0.2);
455
- border: 1px solid rgba(65, 105, 225, 0.3);
456
- color: rgba(65, 105, 225, 0.9);
457
- display: flex;
458
- align-items: center;
459
- justify-content: center;
460
- cursor: pointer;
461
- z-index: 10;
462
- transition: all 0.2s ease;
463
- box-shadow: 0 2px 8px rgba(0, 0, 0, 0.3);
464
- }
465
-
466
- .scroll-to-bottom:hover {
467
- background: rgba(65, 105, 225, 0.3);
468
- border-color: rgba(65, 105, 225, 0.5);
469
- transform: scale(1.1);
470
- }
471
-
472
- .scroll-to-bottom:active {
473
- transform: scale(0.95);
474
- }
475
-
476
- .messages {
477
- flex: 1;
478
- overflow-y: auto;
479
- padding: 0.5rem;
480
- display: flex;
481
- flex-direction: column;
482
- gap: 0.4rem;
483
- min-height: 0;
484
- }
485
-
486
- .message {
487
- padding: 0.4rem 0.6rem;
488
- border-radius: 3px;
489
- font-size: 0.875rem;
490
- animation: fadeIn 0.2s ease-out;
491
- }
492
-
493
- @keyframes fadeIn {
494
- from {
495
- opacity: 0;
496
- transform: translateY(5px);
497
- }
498
- to {
499
- opacity: 1;
500
- transform: translateY(0);
501
- }
502
- }
503
-
504
- .message.user {
505
- background: rgba(65, 105, 225, 0.08);
506
- border-left: 2px solid rgba(65, 105, 225, 0.6);
507
- margin-left: 1rem;
508
- }
509
-
510
- .message.assistant {
511
- background: rgba(255, 255, 255, 0.02);
512
- border-left: 2px solid rgba(255, 255, 255, 0.15);
513
- margin-right: 1rem;
514
- }
515
-
516
- .message-content {
517
- font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Monaco", "Menlo", monospace;
518
- margin: 0;
519
- display: block;
520
- }
521
-
522
- .message-segments {
523
- display: flex;
524
- flex-direction: column;
525
- }
526
-
527
- .message.user .message-content {
528
- color: rgba(255, 255, 255, 0.9);
529
- line-height: 1.5;
530
- white-space: pre-line;
531
- word-wrap: break-word;
532
- }
533
-
534
- .error-message {
535
- background: rgba(244, 67, 54, 0.1);
536
- border-left: 2px solid #f44336;
537
- border-radius: 3px;
538
- padding: 0.4rem 0.6rem;
539
- color: #ff9999;
540
- font-size: 0.875rem;
541
- animation: fadeIn 0.2s ease-out;
542
- }
543
-
544
- .input-area {
545
- display: flex;
546
- gap: 0.5rem;
547
- padding: 0.5rem;
548
- background: rgba(0, 0, 0, 0.2);
549
- border-top: 1px solid rgba(255, 255, 255, 0.1);
550
- }
551
-
552
- textarea {
553
- flex: 1;
554
- background: rgba(0, 0, 0, 0.3);
555
- color: rgba(255, 255, 255, 0.9);
556
- border: 1px solid rgba(255, 255, 255, 0.1);
557
- border-radius: 4px;
558
- padding: 0.4rem 0.6rem;
559
- resize: none;
560
- font-family: "Monaco", "Menlo", monospace;
561
- font-size: 0.875rem;
562
- line-height: 1.2;
563
- }
564
-
565
- textarea:focus {
566
- outline: none;
567
- border-color: rgba(65, 105, 225, 0.5);
568
- background: rgba(0, 0, 0, 0.5);
569
- }
570
-
571
- textarea:disabled {
572
- opacity: 0.5;
573
- cursor: not-allowed;
574
- }
575
-
576
- .send-btn {
577
- padding: 0.4rem 0.8rem;
578
- background: rgba(65, 105, 225, 0.2);
579
- color: #4169e1;
580
- border: 1px solid rgba(65, 105, 225, 0.3);
581
- border-radius: 4px;
582
- cursor: pointer;
583
- font-size: 1rem;
584
- transform-origin: center;
585
- will-change: transform, opacity;
586
- }
587
-
588
- .send-btn:hover:not(:disabled) {
589
- background: rgba(65, 105, 225, 0.3);
590
- border-color: rgba(65, 105, 225, 0.5);
591
- }
592
-
593
- .send-btn:disabled {
594
- cursor: not-allowed;
595
- }
596
-
597
- .stop-btn {
598
- padding: 0.4rem 0.8rem;
599
- background: rgba(244, 67, 54, 0.2);
600
- color: #f44336;
601
- border: 1px solid rgba(244, 67, 54, 0.3);
602
- border-radius: 4px;
603
- cursor: pointer;
604
- font-size: 1rem;
605
- transform-origin: center;
606
- will-change: transform;
607
- animation: pulse 1.5s ease-in-out infinite;
608
- }
609
-
610
- @keyframes pulse {
611
- 0%, 100% {
612
- opacity: 1;
613
- }
614
- 50% {
615
- opacity: 0.6;
616
- }
617
- }
618
-
619
- .stop-btn:hover {
620
- background: rgba(244, 67, 54, 0.3);
621
- border-color: rgba(244, 67, 54, 0.5);
622
- animation: none;
623
- }
624
-
625
- ::-webkit-scrollbar {
626
- width: 6px;
627
- }
628
-
629
- ::-webkit-scrollbar-track {
630
- background: transparent;
631
- }
632
-
633
- ::-webkit-scrollbar-thumb {
634
- background: rgba(255, 255, 255, 0.1);
635
- border-radius: 3px;
636
- }
637
-
638
- ::-webkit-scrollbar-thumb:hover {
639
- background: rgba(255, 255, 255, 0.2);
640
- }
641
-
642
- .auth-prompt {
643
- display: flex;
644
- flex-direction: column;
645
- align-items: center;
646
- justify-content: center;
647
- padding: 2rem;
648
- height: 100%;
649
- text-align: center;
650
- }
651
-
652
- .auth-prompt-btn {
653
- padding: 0.6rem 1.8rem;
654
- background: rgba(255, 210, 30, 0.08);
655
- color: rgba(255, 210, 30, 0.9);
656
- border: 1px solid rgba(255, 210, 30, 0.2);
657
- border-radius: 0.5rem;
658
- cursor: pointer;
659
- font-size: 0.875rem;
660
- font-weight: 600;
661
- transition: all 0.3s ease;
662
- backdrop-filter: blur(10px);
663
- position: relative;
664
- overflow: hidden;
665
- }
666
-
667
- .auth-prompt-btn:hover {
668
- transform: translateY(-2px);
669
- background: rgba(255, 210, 30, 0.12);
670
- color: rgba(255, 210, 30, 1);
671
- border-color: rgba(255, 210, 30, 0.3);
672
- }
673
-
674
- .auth-prompt-btn:active {
675
- transform: translateY(0);
676
- }
677
-
678
- </style>
 
1
  <script lang="ts">
2
+ import { onMount, onDestroy } from "svelte";
3
+ import { fade } from "svelte/transition";
4
+ import gsap from "gsap";
5
+ import MessageList from "./MessageList.svelte";
6
+ import MessageInput from "./MessageInput.svelte";
7
+ import { isConnected, isProcessing, messages, error } from "../../stores/chat-store";
8
+ import { chatController } from "../../controllers/chat-controller";
9
+ import { authStore } from "../../services/auth";
10
+ import { agentService } from "../../services/agent";
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
11
 
12
+ let inputValue = "";
13
+ let messageListRef: any;
14
+ let autoScroll = true;
15
+ let headerRef: HTMLDivElement;
16
 
17
+ onMount(() => {
18
+ if ($authStore.isAuthenticated && !$authStore.loading) {
19
+ agentService.connect();
 
 
20
  }
21
 
22
+ if (headerRef) {
23
+ gsap.fromTo(headerRef,
24
+ { opacity: 0, y: -10 },
25
+ { opacity: 1, y: 0, duration: 0.4, ease: "power2.out" }
26
+ );
27
  }
28
+ });
29
 
30
+ $: if ($authStore.isAuthenticated && !$authStore.loading) {
31
+ if (!$isConnected) {
32
+ agentService.connect();
33
+ } else {
34
+ agentService.reauthenticate();
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
35
  }
36
+ }
37
 
38
+ onDestroy(() => {
39
+ agentService.disconnect();
40
+ });
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
41
 
42
+ function handleSend(event: CustomEvent<string>) {
43
+ chatController.sendMessage(event.detail);
44
+ }
 
 
45
 
46
+ function handleStop() {
47
+ chatController.stopConversation();
48
+ }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
49
 
50
+ function handleClear() {
51
+ chatController.clearConversation();
52
+ }
 
 
 
53
 
54
+ function handleExampleMessage(message: string) {
55
+ if ($authStore.isAuthenticated && $isConnected && !$isProcessing) {
56
+ chatController.sendMessage(message);
 
 
 
 
 
57
  }
58
+ }
59
 
 
 
 
 
 
 
 
 
 
60
 
61
+ function handleScroll(event: CustomEvent<{ nearBottom: boolean }>) {
62
+ autoScroll = event.detail.nearBottom;
63
+ }
 
 
 
 
 
 
64
 
65
+ function scrollToBottom() {
66
+ messageListRef?.scrollToBottom();
67
+ }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
68
  </script>
69
 
70
  <div class="chat-panel">
71
+ <div class="chat-header" bind:this={headerRef}>
72
+ <h2>Chat</h2>
73
+ {#if $messages.length > 0 && $authStore.isAuthenticated && !$isProcessing}
74
+ <button
75
+ class="clear-button"
76
+ on:click={handleClear}
77
+ title="Clear conversation"
78
+ transition:fade={{ duration: 200 }}
79
+ >
80
+ <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
81
+ <path d="M3 6h18M8 6V4a2 2 0 012-2h4a2 2 0 012 2v2m3 0v14a2 2 0 01-2 2H7a2 2 0 01-2-2V6h14zM10 11v6M14 11v6" />
82
+ </svg>
83
+ <span>Clear</span>
84
+ </button>
85
+ {/if}
86
+ </div>
87
+
88
+ {#if !autoScroll}
89
+ <button
90
+ class="scroll-to-bottom"
91
+ on:click={scrollToBottom}
92
+ title="Scroll to bottom"
93
+ transition:fade={{ duration: 200 }}
94
+ >
95
+ <svg width="20" height="20" viewBox="0 0 20 20" fill="currentColor">
96
+ <path d="M10 14L5 9L6.41 7.59L10 11.17L13.59 7.59L15 9L10 14Z" />
97
+ </svg>
98
+ </button>
99
+ {/if}
100
+
101
+ <div class="messages-container">
102
+ {#if !$authStore.isAuthenticated && !$authStore.loading}
103
+ <div class="auth-prompt">
104
+ <button on:click={() => authStore.login()} class="auth-prompt-btn">
105
+ Sign in with 🤗 Hugging Face
106
  </button>
107
+ </div>
108
+ {:else}
109
+ <MessageList
110
+ bind:this={messageListRef}
111
+ messages={$messages}
112
+ error={$error}
113
+ {autoScroll}
114
+ isConnected={$isConnected}
115
+ onSendMessage={handleExampleMessage}
116
+ on:scroll={handleScroll}
117
+ />
118
  {/if}
119
+ </div>
120
+
121
+ <MessageInput
122
+ bind:value={inputValue}
123
+ placeholder={!$authStore.isAuthenticated
124
+ ? "Sign in to chat..."
125
+ : $isConnected
126
+ ? $isProcessing
127
+ ? "Processing..."
128
+ : "Type a message..."
129
+ : "Connecting..."}
130
+ disabled={!$authStore.isAuthenticated || !$isConnected || $isProcessing}
131
+ processing={$isProcessing}
132
+ on:send={handleSend}
133
+ on:stop={handleStop}
134
+ />
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
135
  </div>
136
 
137
  <style>
138
+ .chat-panel {
139
+ display: flex;
140
+ flex-direction: column;
141
+ height: 100%;
142
+ width: 100%;
143
+ background: rgba(8, 7, 6, 0.4);
144
+ border-top: 1px solid rgba(139, 115, 85, 0.06);
145
+ position: relative;
146
+ }
147
+
148
+ .chat-header {
149
+ display: flex;
150
+ justify-content: space-between;
151
+ align-items: center;
152
+ padding: 10px 16px;
153
+ background: rgba(15, 14, 12, 0.3);
154
+ border-bottom: 1px solid rgba(139, 115, 85, 0.06);
155
+ flex-shrink: 0;
156
+ }
157
+
158
+ .chat-header h2 {
159
+ margin: 0;
160
+ font-size: 11px;
161
+ font-weight: 500;
162
+ color: rgba(251, 248, 244, 0.5);
163
+ text-transform: uppercase;
164
+ letter-spacing: 0.5px;
165
+ }
166
+
167
+
168
+
169
+
170
+ .clear-button {
171
+ padding: 4px 10px;
172
+ background: rgba(139, 115, 85, 0.03);
173
+ color: rgba(251, 248, 244, 0.4);
174
+ border: 1px solid rgba(139, 115, 85, 0.08);
175
+ border-radius: 4px;
176
+ font-size: 10px;
177
+ font-weight: 500;
178
+ text-transform: uppercase;
179
+ letter-spacing: 0.3px;
180
+ cursor: pointer;
181
+ transition: all 0.2s cubic-bezier(0.4, 0, 0.2, 1);
182
+ display: flex;
183
+ align-items: center;
184
+ gap: 0.25rem;
185
+ }
186
+
187
+ .clear-button:hover {
188
+ background: rgba(139, 115, 85, 0.06);
189
+ color: rgba(251, 248, 244, 0.6);
190
+ border-color: rgba(139, 115, 85, 0.12);
191
+ }
192
+
193
+ .clear-button:active {
194
+ transform: scale(0.95);
195
+ }
196
+
197
+ .clear-button svg {
198
+ width: 14px;
199
+ height: 14px;
200
+ }
201
+
202
+ .scroll-to-bottom {
203
+ position: absolute;
204
+ bottom: 4.5rem;
205
+ right: 1rem;
206
+ width: 32px;
207
+ height: 32px;
208
+ border-radius: 4px;
209
+ background: rgba(124, 152, 133, 0.08);
210
+ border: 1px solid rgba(124, 152, 133, 0.15);
211
+ color: rgba(124, 152, 133, 0.9);
212
+ display: flex;
213
+ align-items: center;
214
+ justify-content: center;
215
+ cursor: pointer;
216
+ z-index: 10;
217
+ transition: all 0.2s ease;
218
+ box-shadow: 0 2px 8px rgba(0, 0, 0, 0.2);
219
+ }
220
+
221
+ .scroll-to-bottom:hover {
222
+ background: rgba(124, 152, 133, 0.15);
223
+ border-color: rgba(124, 152, 133, 0.3);
224
+ transform: translateY(-1px);
225
+ }
226
+
227
+ .scroll-to-bottom:active {
228
+ transform: translateY(0) scale(0.95);
229
+ }
230
+
231
+ .auth-prompt {
232
+ display: flex;
233
+ flex-direction: column;
234
+ align-items: center;
235
+ justify-content: center;
236
+ padding: 2rem;
237
+ height: 100%;
238
+ text-align: center;
239
+ }
240
+
241
+ .auth-prompt-btn {
242
+ padding: 0.6rem 1.8rem;
243
+ background: rgba(255, 210, 30, 0.08);
244
+ color: rgba(255, 210, 30, 0.9);
245
+ border: 1px solid rgba(255, 210, 30, 0.2);
246
+ border-radius: 0.5rem;
247
+ cursor: pointer;
248
+ font-size: 0.875rem;
249
+ font-weight: 600;
250
+ transition: all 0.3s ease;
251
+ backdrop-filter: blur(10px);
252
+ position: relative;
253
+ overflow: hidden;
254
+ }
255
+
256
+ .auth-prompt-btn:hover {
257
+ transform: translateY(-2px);
258
+ background: rgba(255, 210, 30, 0.12);
259
+ color: rgba(255, 210, 30, 1);
260
+ border-color: rgba(255, 210, 30, 0.3);
261
+ }
262
+
263
+ .auth-prompt-btn:active {
264
+ transform: translateY(0);
265
+ }
266
+
267
+ .messages-container {
268
+ flex: 1;
269
+ overflow-y: auto;
270
+ overflow-x: hidden;
271
+ display: flex;
272
+ flex-direction: column;
273
+ }
274
+
275
+
276
+ </style>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
src/lib/components/chat/ExampleMessages.svelte CHANGED
@@ -1,187 +1,183 @@
1
  <script lang="ts">
2
- import { onMount, onDestroy } from "svelte";
3
- import { fade } from "svelte/transition";
4
- import gsap from "gsap";
5
-
6
- export let onSendMessage: (message: string) => void;
7
-
8
- const examples = [
9
- { icon: "🏀", text: "add another ball" },
10
- { icon: "📝", text: "explain what the code does" },
11
- { icon: "🎮", text: "make an obby" },
12
- { icon: "⬆️", text: "increase the player jump height" }
13
- ];
14
-
15
- let exampleCards: HTMLButtonElement[] = [];
16
-
17
- onMount(() => {
18
- exampleCards.forEach((card, index) => {
19
- if (!card) return;
20
-
21
- gsap.fromTo(card, {
22
- opacity: 0,
23
- y: 15,
24
- scale: 0.97
25
- }, {
26
- opacity: 1,
27
- y: 0,
28
- scale: 1,
29
- duration: 0.2,
30
- delay: index * 0.02,
31
- ease: "power3.out"
32
- });
33
- });
34
  });
 
35
 
36
- onDestroy(() => {
37
- exampleCards.forEach((card) => {
38
- if (card) {
39
- gsap.killTweensOf(card);
40
- }
41
- });
42
  });
43
-
44
- function handleClick(text: string) {
45
- onSendMessage(text);
46
- }
47
-
48
- function handleMouseEnter(event: MouseEvent) {
49
- const card = event.currentTarget as HTMLButtonElement;
50
- gsap.to(card, {
51
- scale: 1.03,
52
- boxShadow: "0 6px 24px rgba(65, 105, 225, 0.25)",
53
- borderColor: "rgba(65, 105, 225, 0.5)",
54
- duration: 0.1,
55
- ease: "power3.out"
56
- });
57
- }
58
-
59
- function handleMouseLeave(event: MouseEvent) {
60
- const card = event.currentTarget as HTMLButtonElement;
61
- gsap.to(card, {
62
- scale: 1,
63
- boxShadow: "0 2px 10px rgba(0, 0, 0, 0.2)",
64
- borderColor: "rgba(255, 255, 255, 0.05)",
65
- duration: 0.12,
66
- ease: "power2.inOut"
67
- });
68
- }
69
-
70
- function handleMouseDown(event: MouseEvent) {
71
- const card = event.currentTarget as HTMLButtonElement;
72
- gsap.to(card, {
73
- scale: 0.97,
74
- duration: 0.05,
75
- ease: "power1.in"
76
- });
77
- }
78
-
79
- function handleMouseUp(event: MouseEvent) {
80
- const card = event.currentTarget as HTMLButtonElement;
81
- gsap.to(card, {
82
- scale: 1.03,
83
- duration: 0.08,
84
- ease: "back.out(1.7)"
85
- });
86
- }
 
87
  </script>
88
 
89
  <div class="examples-container" transition:fade={{ duration: 200 }}>
90
- <div class="examples-header">
91
- <span class="header-text">Try an example</span>
92
- </div>
93
- <div class="examples-grid">
94
- {#each examples as example, i}
95
- <button
96
- bind:this={exampleCards[i]}
97
- class="example-card"
98
- on:click={() => handleClick(example.text)}
99
- on:mouseenter={handleMouseEnter}
100
- on:mouseleave={handleMouseLeave}
101
- on:mousedown={handleMouseDown}
102
- on:mouseup={handleMouseUp}
103
- >
104
- <span class="example-icon">{example.icon}</span>
105
- <span class="example-text">{example.text}</span>
106
- </button>
107
- {/each}
108
- </div>
109
  </div>
110
 
111
  <style>
112
- .examples-container {
113
- display: flex;
114
- flex-direction: column;
115
- align-items: center;
116
- justify-content: center;
117
- padding: 2rem 1rem;
118
- height: 100%;
119
- min-height: 300px;
120
- gap: 1.5rem;
121
- }
122
-
123
- .examples-header {
124
- color: rgba(255, 255, 255, 0.3);
125
- font-size: 0.75rem;
126
- text-transform: uppercase;
127
- letter-spacing: 0.1em;
128
- font-weight: 500;
129
- }
130
-
131
- .header-text {
132
- animation: gentle-pulse 3s ease-in-out infinite;
 
 
 
 
 
133
  }
134
-
135
- @keyframes gentle-pulse {
136
- 0%, 100% {
137
- opacity: 0.5;
138
- }
139
- 50% {
140
- opacity: 0.8;
141
- }
142
  }
143
-
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
144
  .examples-grid {
145
- display: grid;
146
- grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
147
- gap: 0.75rem;
148
- width: 100%;
149
- max-width: 600px;
150
- }
151
-
152
- .example-card {
153
- display: flex;
154
- align-items: center;
155
- gap: 0.5rem;
156
- padding: 0.5rem 1rem;
157
- background: rgba(255, 255, 255, 0.02);
158
- border: 1px solid rgba(255, 255, 255, 0.05);
159
- border-radius: 6px;
160
- cursor: pointer;
161
- transition: all 0.2s ease;
162
- font-family: "Monaco", "Menlo", monospace;
163
- font-size: 0.875rem;
164
- color: rgba(255, 255, 255, 0.85);
165
- will-change: transform, box-shadow;
166
- box-shadow: 0 2px 10px rgba(0, 0, 0, 0.2);
167
- }
168
-
169
- .example-card:hover {
170
- background: rgba(65, 105, 225, 0.05);
171
- }
172
-
173
- .example-icon {
174
- font-size: 1rem;
175
- opacity: 0.8;
176
- }
177
-
178
- .example-text {
179
- flex: 1;
180
- }
181
-
182
- @media (max-width: 600px) {
183
- .examples-grid {
184
- grid-template-columns: 1fr;
185
- }
186
  }
 
187
  </style>
 
1
  <script lang="ts">
2
+ import { onMount, onDestroy } from "svelte";
3
+ import { fade } from "svelte/transition";
4
+ import gsap from "gsap";
5
+
6
+ export let onSendMessage: (message: string) => void;
7
+
8
+ const examples = [
9
+ { icon: "🏀", text: "add another ball" },
10
+ { icon: "📝", text: "explain what the code does" },
11
+ { icon: "🎮", text: "make an obby" },
12
+ { icon: "⬆️", text: "increase the player jump height" }
13
+ ];
14
+
15
+ let exampleCards: HTMLButtonElement[] = [];
16
+
17
+ onMount(() => {
18
+ exampleCards.forEach((card, index) => {
19
+ if (!card) return;
20
+
21
+ gsap.fromTo(card, {
22
+ opacity: 0,
23
+ y: 15,
24
+ scale: 0.97
25
+ }, {
26
+ opacity: 1,
27
+ y: 0,
28
+ scale: 1,
29
+ duration: 0.2,
30
+ delay: index * 0.02,
31
+ ease: "power3.out"
32
+ });
 
33
  });
34
+ });
35
 
36
+ onDestroy(() => {
37
+ exampleCards.forEach((card) => {
38
+ if (card) {
39
+ gsap.killTweensOf(card);
40
+ }
 
41
  });
42
+ });
43
+
44
+ function handleClick(text: string) {
45
+ onSendMessage(text);
46
+ }
47
+
48
+ function handleMouseEnter(event: MouseEvent) {
49
+ const card = event.currentTarget as HTMLButtonElement;
50
+ gsap.to(card, {
51
+ scale: 1.03,
52
+ boxShadow: "0 6px 24px rgba(65, 105, 225, 0.25)",
53
+ borderColor: "rgba(65, 105, 225, 0.5)",
54
+ duration: 0.08,
55
+ ease: "power2.out"
56
+ });
57
+ }
58
+
59
+ function handleMouseLeave(event: MouseEvent) {
60
+ const card = event.currentTarget as HTMLButtonElement;
61
+ gsap.to(card, {
62
+ scale: 1,
63
+ boxShadow: "0 2px 10px rgba(0, 0, 0, 0.2)",
64
+ borderColor: "rgba(255, 255, 255, 0.05)",
65
+ duration: 0.1,
66
+ ease: "power2.out"
67
+ });
68
+ }
69
+
70
+ function handleMouseDown(event: MouseEvent) {
71
+ const card = event.currentTarget as HTMLButtonElement;
72
+ gsap.to(card, {
73
+ scale: 0.97,
74
+ duration: 0.03,
75
+ ease: "power2.in"
76
+ });
77
+ }
78
+
79
+ function handleMouseUp(event: MouseEvent) {
80
+ const card = event.currentTarget as HTMLButtonElement;
81
+ gsap.to(card, {
82
+ scale: 1.03,
83
+ duration: 0.05,
84
+ ease: "back.out(1.5)"
85
+ });
86
+ }
87
  </script>
88
 
89
  <div class="examples-container" transition:fade={{ duration: 200 }}>
90
+ <div class="examples-header">
91
+ <span class="header-text">Try an example</span>
92
+ </div>
93
+ <div class="examples-grid">
94
+ {#each examples as example, i}
95
+ <button
96
+ bind:this={exampleCards[i]}
97
+ class="example-card"
98
+ on:click={() => handleClick(example.text)}
99
+ on:mouseenter={handleMouseEnter}
100
+ on:mouseleave={handleMouseLeave}
101
+ on:mousedown={handleMouseDown}
102
+ on:mouseup={handleMouseUp}
103
+ >
104
+ <span class="example-icon">{example.icon}</span>
105
+ <span class="example-text">{example.text}</span>
106
+ </button>
107
+ {/each}
108
+ </div>
109
  </div>
110
 
111
  <style>
112
+ .examples-container {
113
+ display: flex;
114
+ flex-direction: column;
115
+ align-items: center;
116
+ justify-content: center;
117
+ padding: 2rem 1rem;
118
+ height: 100%;
119
+ min-height: 300px;
120
+ gap: 1.5rem;
121
+ }
122
+
123
+ .examples-header {
124
+ color: rgba(255, 255, 255, 0.3);
125
+ font-size: 0.75rem;
126
+ text-transform: uppercase;
127
+ letter-spacing: 0.1em;
128
+ font-weight: 500;
129
+ }
130
+
131
+ .header-text {
132
+ animation: gentle-pulse 3s ease-in-out infinite;
133
+ }
134
+
135
+ @keyframes gentle-pulse {
136
+ 0%, 100% {
137
+ opacity: 0.5;
138
  }
139
+ 50% {
140
+ opacity: 0.8;
 
 
 
 
 
 
141
  }
142
+ }
143
+
144
+ .examples-grid {
145
+ display: grid;
146
+ grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
147
+ gap: 0.75rem;
148
+ width: 100%;
149
+ max-width: 600px;
150
+ }
151
+
152
+ .example-card {
153
+ display: flex;
154
+ align-items: center;
155
+ gap: 0.5rem;
156
+ padding: 0.5rem 1rem;
157
+ background: rgba(255, 255, 255, 0.02);
158
+ border: 1px solid rgba(255, 255, 255, 0.05);
159
+ border-radius: 6px;
160
+ cursor: pointer;
161
+ transition: none;
162
+ font-family: "Monaco", "Menlo", monospace;
163
+ font-size: 0.875rem;
164
+ color: rgba(255, 255, 255, 0.85);
165
+ will-change: transform, box-shadow;
166
+ box-shadow: 0 2px 10px rgba(0, 0, 0, 0.2);
167
+ }
168
+
169
+ .example-icon {
170
+ font-size: 1rem;
171
+ opacity: 0.8;
172
+ }
173
+
174
+ .example-text {
175
+ flex: 1;
176
+ }
177
+
178
+ @media (max-width: 600px) {
179
  .examples-grid {
180
+ grid-template-columns: 1fr;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
181
  }
182
+ }
183
  </style>
src/lib/components/chat/InProgressBlock.svelte DELETED
@@ -1,474 +0,0 @@
1
- <script lang="ts">
2
- import { onMount, onDestroy } from "svelte";
3
- import gsap from "gsap";
4
-
5
- export let status: "thinking" | "streaming" | "completing" = "thinking";
6
- export let content: string = "";
7
- export let startTime: number = Date.now();
8
- export let isExpanded: boolean = false;
9
- export let autoCollapse: boolean = true;
10
-
11
- let blockElement: HTMLDivElement;
12
- let contentElement: HTMLDivElement;
13
- let expandIcon: HTMLSpanElement;
14
- let statusElement: HTMLSpanElement;
15
- let progressBar: HTMLDivElement;
16
- let collapseTimeout: number | null = null;
17
- let prevStatus: string = status;
18
- let timeline: gsap.core.Timeline | null = null;
19
-
20
- $: duration = Date.now() - startTime;
21
- $: formattedDuration = formatDuration(duration);
22
-
23
- $: if (status !== prevStatus && statusElement) {
24
- animateStatusTransition(prevStatus, status);
25
- prevStatus = status;
26
- }
27
-
28
- function formatDuration(ms: number): string {
29
- if (ms < 1000) return `${ms}ms`;
30
- return `${(ms / 1000).toFixed(1)}s`;
31
- }
32
-
33
- function toggleExpanded() {
34
- isExpanded = !isExpanded;
35
- updateExpandState();
36
- }
37
-
38
- function updateExpandState() {
39
- if (!expandIcon || !contentElement) return;
40
-
41
- if (timeline) timeline.kill();
42
- timeline = gsap.timeline();
43
-
44
- if (isExpanded) {
45
- timeline
46
- .to(expandIcon, {
47
- rotation: 90,
48
- duration: 0.2,
49
- ease: "power2.out",
50
- })
51
- .set(contentElement, { display: "block" })
52
- .fromTo(
53
- contentElement,
54
- { opacity: 0, maxHeight: 0, y: -10 },
55
- { opacity: 1, maxHeight: 500, y: 0, duration: 0.3, ease: "power2.out" },
56
- "-=0.1",
57
- );
58
- } else {
59
- timeline
60
- .to(expandIcon, {
61
- rotation: 0,
62
- duration: 0.2,
63
- ease: "power2.in",
64
- })
65
- .to(
66
- contentElement,
67
- {
68
- opacity: 0,
69
- maxHeight: 0,
70
- y: -5,
71
- duration: 0.2,
72
- ease: "power2.in",
73
- onComplete: () => {
74
- gsap.set(contentElement, { display: "none" });
75
- },
76
- },
77
- "-=0.1",
78
- );
79
- }
80
- }
81
-
82
- function animateStatusTransition(_from: string, to: string) {
83
- if (!statusElement || !blockElement) return;
84
-
85
- const tl = gsap.timeline();
86
-
87
- tl.to(blockElement, {
88
- duration: 0.4,
89
- ease: "power2.inOut",
90
- onUpdate: function () {
91
- updateBlockStyle(to);
92
- },
93
- });
94
-
95
- tl.to(statusElement, {
96
- scale: 0.9,
97
- opacity: 0,
98
- duration: 0.15,
99
- ease: "power2.in",
100
- onComplete: () => {},
101
- }).to(statusElement, {
102
- scale: 1,
103
- opacity: 1,
104
- duration: 0.15,
105
- ease: "power2.out",
106
- });
107
-
108
- if (progressBar) {
109
- if (to === "streaming") {
110
- gsap.to(progressBar, {
111
- width: "60%",
112
- duration: 1,
113
- ease: "power2.out",
114
- });
115
- } else if (to === "completing") {
116
- gsap.to(progressBar, {
117
- width: "100%",
118
- duration: 0.5,
119
- ease: "power2.out",
120
- });
121
- }
122
- }
123
- }
124
-
125
- function updateBlockStyle(status: string) {
126
- if (!blockElement) return;
127
- blockElement.className = `in-progress-block ${status}`;
128
- }
129
-
130
- function getStatusIcon() {
131
- switch (status) {
132
- case "thinking":
133
- return "🤔";
134
- case "streaming":
135
- return "✍️";
136
- case "completing":
137
- return "✨";
138
- default:
139
- return "⚡";
140
- }
141
- }
142
-
143
- function getStatusText() {
144
- switch (status) {
145
- case "thinking":
146
- return "Thinking";
147
- case "streaming":
148
- return "Writing response";
149
- case "completing":
150
- return "Finalizing response";
151
- default:
152
- return "Processing";
153
- }
154
- }
155
-
156
- $: if (status === "completing" && autoCollapse && isExpanded) {
157
- if (collapseTimeout) clearTimeout(collapseTimeout);
158
- collapseTimeout = window.setTimeout(() => {
159
- isExpanded = false;
160
- updateExpandState();
161
- }, 500);
162
- }
163
-
164
- onMount(() => {
165
- if (blockElement) {
166
- gsap.fromTo(
167
- blockElement,
168
- { opacity: 0, y: -10, scale: 0.95 },
169
- { opacity: 1, y: 0, scale: 1, duration: 0.4, ease: "back.out(1.2)" },
170
- );
171
- }
172
-
173
- if (progressBar) {
174
- gsap.fromTo(
175
- progressBar,
176
- { width: "0%" },
177
- { width: "30%", duration: 0.5, ease: "power2.out" },
178
- );
179
- }
180
-
181
- const interval = setInterval(() => {
182
- duration = Date.now() - startTime;
183
- }, 100);
184
-
185
- return () => clearInterval(interval);
186
- });
187
-
188
- onDestroy(() => {
189
- if (collapseTimeout) clearTimeout(collapseTimeout);
190
- if (timeline) timeline.kill();
191
-
192
- if (expandIcon) gsap.killTweensOf(expandIcon);
193
- if (contentElement) gsap.killTweensOf(contentElement);
194
- if (statusElement) gsap.killTweensOf(statusElement);
195
- if (progressBar) gsap.killTweensOf(progressBar);
196
- if (blockElement) gsap.killTweensOf(blockElement);
197
- });
198
- </script>
199
-
200
- <div class="in-progress-block {status}" bind:this={blockElement}>
201
- <div class="progress-bar-track">
202
- <div bind:this={progressBar} class="progress-bar"></div>
203
- </div>
204
-
205
- <button class="progress-header" on:click={toggleExpanded} aria-expanded={isExpanded}>
206
- <span class="status-icon">
207
- {#if status === "streaming"}
208
- <span class="animated-icon">{getStatusIcon()}</span>
209
- {:else if status === "thinking"}
210
- <span class="spinning-icon">{getStatusIcon()}</span>
211
- {:else}
212
- {getStatusIcon()}
213
- {/if}
214
- </span>
215
-
216
- <span bind:this={statusElement} class="status-text">
217
- {getStatusText()}<span class="dots"></span>
218
- </span>
219
-
220
- <span class="progress-meta">
221
- <span class="duration">{formattedDuration}</span>
222
- {#if content}
223
- <span class="char-count">{content.length} chars</span>
224
- {/if}
225
- </span>
226
-
227
- <span bind:this={expandIcon} class="expand-icon">▶</span>
228
- </button>
229
-
230
- <div bind:this={contentElement} class="progress-content" style="display: none;">
231
- {#if content}
232
- <div class="content-wrapper">
233
- <div class="content-text">{content}</div>
234
- <span class="cursor">▊</span>
235
- </div>
236
- {:else}
237
- <div class="waiting-message">Waiting for response to begin...</div>
238
- {/if}
239
- </div>
240
- </div>
241
-
242
- <style>
243
- .in-progress-block {
244
- margin: 0.4rem 0;
245
- border-radius: 4px;
246
- overflow: hidden;
247
- border: 1px solid rgba(255, 210, 30, 0.2);
248
- background: rgba(255, 210, 30, 0.05);
249
- transition: all 0.2s ease;
250
- position: relative;
251
- }
252
-
253
- .in-progress-block.thinking {
254
- border-color: rgba(255, 210, 30, 0.3);
255
- background: rgba(255, 210, 30, 0.08);
256
- }
257
-
258
- .in-progress-block.streaming {
259
- border-color: rgba(65, 105, 225, 0.3);
260
- background: rgba(65, 105, 225, 0.08);
261
- }
262
-
263
- .in-progress-block.completing {
264
- border-color: rgba(0, 255, 0, 0.2);
265
- background: rgba(0, 255, 0, 0.05);
266
- }
267
-
268
- .progress-bar-track {
269
- position: absolute;
270
- top: 0;
271
- left: 0;
272
- right: 0;
273
- height: 2px;
274
- background: rgba(255, 255, 255, 0.05);
275
- overflow: hidden;
276
- }
277
-
278
- .progress-bar {
279
- height: 100%;
280
- background: linear-gradient(
281
- 90deg,
282
- rgba(255, 210, 30, 0.6) 0%,
283
- rgba(65, 105, 225, 0.6) 50%,
284
- rgba(0, 255, 0, 0.6) 100%
285
- );
286
- box-shadow: 0 0 10px rgba(65, 105, 225, 0.4);
287
- transition: width 0.3s ease;
288
- }
289
-
290
- .in-progress-block.thinking .progress-bar {
291
- background: rgba(255, 210, 30, 0.6);
292
- box-shadow: 0 0 10px rgba(255, 210, 30, 0.4);
293
- }
294
-
295
- .in-progress-block.streaming .progress-bar {
296
- background: rgba(65, 105, 225, 0.6);
297
- box-shadow: 0 0 10px rgba(65, 105, 225, 0.4);
298
- }
299
-
300
- .in-progress-block.completing .progress-bar {
301
- background: rgba(0, 255, 0, 0.6);
302
- box-shadow: 0 0 10px rgba(0, 255, 0, 0.4);
303
- }
304
-
305
- .progress-header {
306
- display: flex;
307
- align-items: center;
308
- gap: 0.5rem;
309
- width: 100%;
310
- padding: 0.5rem 0.75rem;
311
- background: transparent;
312
- border: none;
313
- color: inherit;
314
- font: inherit;
315
- text-align: left;
316
- cursor: pointer;
317
- transition: background 0.2s ease;
318
- }
319
-
320
- .progress-header:hover {
321
- background: rgba(255, 255, 255, 0.02);
322
- }
323
-
324
- .status-icon {
325
- font-size: 1rem;
326
- width: 1.5rem;
327
- text-align: center;
328
- }
329
-
330
- .spinning-icon {
331
- display: inline-block;
332
- animation: spin 1s linear infinite;
333
- }
334
-
335
- .animated-icon {
336
- display: inline-block;
337
- animation: bounce 1s ease-in-out infinite;
338
- }
339
-
340
- @keyframes spin {
341
- from {
342
- transform: rotate(0deg);
343
- }
344
- to {
345
- transform: rotate(360deg);
346
- }
347
- }
348
-
349
- @keyframes bounce {
350
- 0%,
351
- 100% {
352
- transform: translateY(0);
353
- }
354
- 50% {
355
- transform: translateY(-2px);
356
- }
357
- }
358
-
359
- .status-text {
360
- flex: 1;
361
- color: rgba(255, 255, 255, 0.9);
362
- font-size: 0.875rem;
363
- }
364
-
365
- .dots::after {
366
- content: "";
367
- animation: dots 1.5s steps(4, end) infinite;
368
- }
369
-
370
- @keyframes dots {
371
- 0%,
372
- 20% {
373
- content: "";
374
- }
375
- 40% {
376
- content: ".";
377
- }
378
- 60% {
379
- content: "..";
380
- }
381
- 80%,
382
- 100% {
383
- content: "...";
384
- }
385
- }
386
-
387
- .progress-meta {
388
- display: flex;
389
- gap: 1rem;
390
- align-items: center;
391
- color: rgba(255, 255, 255, 0.4);
392
- font-size: 0.75rem;
393
- font-family: "Monaco", "Menlo", monospace;
394
- }
395
-
396
- .duration {
397
- min-width: 3rem;
398
- }
399
-
400
- .char-count {
401
- opacity: 0.7;
402
- }
403
-
404
- .expand-icon {
405
- font-size: 0.7rem;
406
- color: rgba(255, 255, 255, 0.4);
407
- transition: transform 0.2s ease;
408
- transform-origin: center;
409
- }
410
-
411
- .progress-content {
412
- padding: 0.75rem;
413
- background: rgba(0, 0, 0, 0.2);
414
- border-top: 1px solid rgba(255, 255, 255, 0.05);
415
- max-height: 300px;
416
- overflow-y: auto;
417
- }
418
-
419
- .content-wrapper {
420
- font-family: "Monaco", "Menlo", monospace;
421
- font-size: 0.8rem;
422
- line-height: 1.5;
423
- color: rgba(255, 255, 255, 0.8);
424
- }
425
-
426
- .content-text {
427
- white-space: pre-wrap;
428
- word-wrap: break-word;
429
- display: inline;
430
- }
431
-
432
- .cursor {
433
- display: inline-block;
434
- animation: blink 1s infinite;
435
- color: rgba(65, 105, 225, 0.8);
436
- font-weight: bold;
437
- }
438
-
439
- @keyframes blink {
440
- 0%,
441
- 50% {
442
- opacity: 1;
443
- }
444
- 51%,
445
- 100% {
446
- opacity: 0;
447
- }
448
- }
449
-
450
- .waiting-message {
451
- text-align: center;
452
- color: rgba(255, 255, 255, 0.4);
453
- font-style: italic;
454
- font-size: 0.85rem;
455
- padding: 1rem;
456
- }
457
-
458
- ::-webkit-scrollbar {
459
- width: 6px;
460
- }
461
-
462
- ::-webkit-scrollbar-track {
463
- background: transparent;
464
- }
465
-
466
- ::-webkit-scrollbar-thumb {
467
- background: rgba(255, 255, 255, 0.1);
468
- border-radius: 3px;
469
- }
470
-
471
- ::-webkit-scrollbar-thumb:hover {
472
- background: rgba(255, 255, 255, 0.2);
473
- }
474
- </style>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
src/lib/components/chat/MarkdownRenderer.svelte DELETED
@@ -1,202 +0,0 @@
1
- <script lang="ts">
2
- import { marked } from "marked";
3
- import StreamingText from "./StreamingText.svelte";
4
-
5
- export let content: string;
6
- export let streaming: boolean = false;
7
-
8
- let htmlContent = "";
9
- let useStreamingText = false;
10
-
11
- const renderer = new marked.Renderer();
12
-
13
- renderer.code = ({ text, lang }) => {
14
- const language = lang || "text";
15
- return `<pre><code class="language-${language}">${escapeHtml(text)}</code></pre>`;
16
- };
17
-
18
- renderer.link = ({ href, title, tokens }) => {
19
- const firstToken = tokens?.[0];
20
- const text = (firstToken && 'text' in firstToken) ? firstToken.text : '';
21
- return `<a href="${href}" target="_blank" rel="noopener noreferrer" title="${title || text}">${text}</a>`;
22
- };
23
-
24
- function escapeHtml(text: string): string {
25
- const div = document.createElement("div");
26
- div.textContent = text;
27
- return div.innerHTML;
28
- }
29
-
30
- marked.setOptions({
31
- renderer,
32
- breaks: true,
33
- gfm: true,
34
- });
35
-
36
- $: {
37
- if (streaming && content) {
38
- useStreamingText = true;
39
- htmlContent = "";
40
- } else if (content) {
41
- useStreamingText = false;
42
- try {
43
- htmlContent = marked.parse(content) as string;
44
- } catch {
45
- htmlContent = escapeHtml(content);
46
- }
47
- }
48
- }
49
- </script>
50
-
51
- {#if useStreamingText}
52
- <div class="markdown-content">
53
- <StreamingText {content} {streaming} speed={120} />
54
- </div>
55
- {:else}
56
- <div class="markdown-content">
57
- {@html htmlContent}
58
- {#if streaming}
59
- <span class="cursor">▊</span>
60
- {/if}
61
- </div>
62
- {/if}
63
-
64
- <style>
65
- .markdown-content {
66
- color: rgba(255, 255, 255, 0.9);
67
- line-height: 1.6;
68
- word-wrap: break-word;
69
- }
70
-
71
- .markdown-content :global(p) {
72
- margin: 0 0 0.5rem 0;
73
- }
74
-
75
- .markdown-content :global(p:last-child) {
76
- margin-bottom: 0;
77
- }
78
-
79
- .markdown-content :global(h1),
80
- .markdown-content :global(h2),
81
- .markdown-content :global(h3),
82
- .markdown-content :global(h4),
83
- .markdown-content :global(h5),
84
- .markdown-content :global(h6) {
85
- margin: 0.75rem 0 0.5rem 0;
86
- font-weight: 600;
87
- color: rgba(255, 255, 255, 1);
88
- }
89
-
90
- .markdown-content :global(h1) {
91
- font-size: 1.25rem;
92
- }
93
-
94
- .markdown-content :global(h2) {
95
- font-size: 1.125rem;
96
- }
97
-
98
- .markdown-content :global(h3) {
99
- font-size: 1rem;
100
- }
101
-
102
- .markdown-content :global(pre) {
103
- background: rgba(0, 0, 0, 0.4);
104
- border: 1px solid rgba(255, 255, 255, 0.1);
105
- border-radius: 4px;
106
- padding: 0.75rem;
107
- margin: 0.5rem 0;
108
- overflow-x: auto;
109
- }
110
-
111
- .markdown-content :global(code) {
112
- font-family: "Monaco", "Menlo", "Consolas", monospace;
113
- font-size: 0.85rem;
114
- background: rgba(0, 0, 0, 0.3);
115
- padding: 0.125rem 0.25rem;
116
- border-radius: 3px;
117
- color: rgba(255, 210, 30, 0.9);
118
- }
119
-
120
- .markdown-content :global(pre code) {
121
- background: none;
122
- padding: 0;
123
- color: rgba(255, 255, 255, 0.9);
124
- }
125
-
126
- .markdown-content :global(ul),
127
- .markdown-content :global(ol) {
128
- margin: 0.5rem 0;
129
- padding-left: 1.5rem;
130
- }
131
-
132
- .markdown-content :global(li) {
133
- margin: 0.25rem 0;
134
- }
135
-
136
- .markdown-content :global(blockquote) {
137
- border-left: 3px solid rgba(255, 255, 255, 0.3);
138
- padding-left: 0.75rem;
139
- margin: 0.5rem 0;
140
- color: rgba(255, 255, 255, 0.7);
141
- font-style: italic;
142
- }
143
-
144
- .markdown-content :global(a) {
145
- color: rgba(65, 105, 225, 0.9);
146
- text-decoration: none;
147
- border-bottom: 1px solid transparent;
148
- transition: border-color 0.2s;
149
- }
150
-
151
- .markdown-content :global(a:hover) {
152
- border-bottom-color: rgba(65, 105, 225, 0.5);
153
- }
154
-
155
- .markdown-content :global(strong) {
156
- font-weight: 600;
157
- color: rgba(255, 255, 255, 1);
158
- }
159
-
160
- .markdown-content :global(em) {
161
- font-style: italic;
162
- }
163
-
164
- .markdown-content :global(hr) {
165
- border: none;
166
- border-top: 1px solid rgba(255, 255, 255, 0.1);
167
- margin: 1rem 0;
168
- }
169
-
170
- .markdown-content :global(table) {
171
- border-collapse: collapse;
172
- width: 100%;
173
- margin: 0.5rem 0;
174
- }
175
-
176
- .markdown-content :global(th),
177
- .markdown-content :global(td) {
178
- border: 1px solid rgba(255, 255, 255, 0.1);
179
- padding: 0.5rem;
180
- text-align: left;
181
- }
182
-
183
- .markdown-content :global(th) {
184
- background: rgba(0, 0, 0, 0.3);
185
- font-weight: 600;
186
- }
187
-
188
- .cursor {
189
- animation: blink 1s infinite;
190
- }
191
-
192
- @keyframes blink {
193
- 0%,
194
- 50% {
195
- opacity: 1;
196
- }
197
- 51%,
198
- 100% {
199
- opacity: 0;
200
- }
201
- }
202
- </style>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
src/lib/components/chat/Message.svelte ADDED
@@ -0,0 +1,76 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <script lang="ts">
2
+ import { onMount } from "svelte";
3
+ import gsap from "gsap";
4
+ import type { ChatMessage } from "../../models/chat-data";
5
+ import MessageContent from "./MessageContent.svelte";
6
+
7
+ export let message: ChatMessage;
8
+
9
+ let messageRef: HTMLDivElement;
10
+
11
+ onMount(() => {
12
+ if (messageRef) {
13
+ const isUser = message.role === 'user';
14
+
15
+ gsap.fromTo(messageRef,
16
+ {
17
+ opacity: 0,
18
+ y: 8,
19
+ x: isUser ? 10 : -10,
20
+ scale: 0.98
21
+ },
22
+ {
23
+ opacity: 1,
24
+ y: 0,
25
+ x: 0,
26
+ scale: 1,
27
+ duration: 0.25,
28
+ ease: "power2.out",
29
+ delay: 0.05
30
+ }
31
+ );
32
+
33
+ if (!isUser) {
34
+ gsap.fromTo(messageRef.querySelector('.message-content'),
35
+ { opacity: 0 },
36
+ { opacity: 1, duration: 0.3, delay: 0.1, ease: "power1.out" }
37
+ );
38
+ }
39
+ }
40
+ });
41
+ </script>
42
+
43
+ <div class="message {message.role}" bind:this={messageRef}>
44
+ <div class="message-content">
45
+ <MessageContent {message} />
46
+ </div>
47
+ </div>
48
+
49
+ <style>
50
+ .message {
51
+ padding: 0.375rem 0.5rem;
52
+ position: relative;
53
+ }
54
+
55
+ .message-content {
56
+ position: relative;
57
+ }
58
+
59
+ .message.user {
60
+ opacity: 0.9;
61
+ }
62
+
63
+ .message.user .message-content {
64
+ padding-left: 0.5rem;
65
+ border-left: 2px solid rgba(124, 152, 133, 0.3);
66
+ }
67
+
68
+ .message.assistant {
69
+ opacity: 1;
70
+ }
71
+
72
+ .message.assistant .message-content {
73
+ padding-left: 0.5rem;
74
+ border-left: 2px solid rgba(139, 115, 85, 0.08);
75
+ }
76
+ </style>
src/lib/components/chat/MessageContent.svelte ADDED
@@ -0,0 +1,74 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <script lang="ts">
2
+ import type { ChatMessage } from "../../models/chat-data";
3
+ import TextRenderer from "./TextRenderer.svelte";
4
+ import ToolBlock from "./segments/ToolBlock.svelte";
5
+ import TodoSegment from "./segments/TodoSegment.svelte";
6
+
7
+ export let message: ChatMessage;
8
+
9
+ $: hasSegments = message.segments && message.segments.length > 0;
10
+ $: isStreaming = message.streaming || (message.segments?.some(s => s.streaming));
11
+
12
+ // Normalize content for consistent rendering
13
+ $: normalizedContent = hasSegments ? null : (message.content || "");
14
+
15
+ // Filter segments for rendering
16
+ $: segmentsToRender = hasSegments ? message.segments!.filter((segment) => {
17
+ // Skip tool results that are paired with previous invocation (except todos)
18
+ if (segment.type === "tool-result") {
19
+ const isTodoTool = segment.toolName?.includes("task");
20
+ return isTodoTool;
21
+ }
22
+ return true;
23
+ }) : [];
24
+ </script>
25
+
26
+ <div class="message-content">
27
+ {#if normalizedContent !== null}
28
+ <!-- Fallback content rendering -->
29
+ <TextRenderer content={normalizedContent} streaming={isStreaming} />
30
+ {#if isStreaming}
31
+ <span class="streaming-indicator">●</span>
32
+ {/if}
33
+ {:else if hasSegments}
34
+ <!-- Segmented content rendering -->
35
+ {#each segmentsToRender as segment (segment.id)}
36
+ {#if segment.type === "text"}
37
+ <TextRenderer content={segment.content} streaming={segment.streaming || false} />
38
+ {:else if segment.type === "tool-invocation"}
39
+ <ToolBlock
40
+ invocation={segment}
41
+ result={message.segments && message.segments[message.segments.indexOf(segment) + 1]?.type === "tool-result"
42
+ ? message.segments[message.segments.indexOf(segment) + 1]
43
+ : null}
44
+ />
45
+ {:else if segment.type === "tool-result" && segment.toolName?.includes("task")}
46
+ <TodoSegment {segment} />
47
+ {/if}
48
+ {/each}
49
+ {/if}
50
+ </div>
51
+
52
+ <style>
53
+ .message-content {
54
+ display: flex;
55
+ flex-direction: column;
56
+ gap: 0.125rem;
57
+ }
58
+
59
+ .streaming-indicator {
60
+ display: inline-block;
61
+ color: rgba(65, 105, 225, 0.8);
62
+ animation: pulse 1.5s ease-in-out infinite;
63
+ margin-left: 0.25rem;
64
+ }
65
+
66
+ @keyframes pulse {
67
+ 0%, 100% {
68
+ opacity: 0.3;
69
+ }
70
+ 50% {
71
+ opacity: 1;
72
+ }
73
+ }
74
+ </style>
src/lib/components/chat/MessageInput.svelte ADDED
@@ -0,0 +1,294 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <script lang="ts">
2
+ import { createEventDispatcher, onMount } from "svelte";
3
+ import gsap from "gsap";
4
+
5
+ export let value: string = "";
6
+ export let placeholder: string = "Type a message...";
7
+ export let disabled: boolean = false;
8
+ export let processing: boolean = false;
9
+
10
+ const dispatch = createEventDispatcher();
11
+
12
+ let textareaRef: HTMLTextAreaElement;
13
+ let sendButtonRef: HTMLButtonElement;
14
+ let stopButtonRef: HTMLButtonElement;
15
+ let inputAreaRef: HTMLDivElement;
16
+
17
+ function handleSubmit() {
18
+ if (value.trim() && !disabled && !processing) {
19
+ if (sendButtonRef) {
20
+ gsap.to(sendButtonRef, {
21
+ scale: 0.9,
22
+ duration: 0.1,
23
+ yoyo: true,
24
+ repeat: 1,
25
+ ease: "power2.inOut",
26
+ onComplete: () => {
27
+ gsap.to(sendButtonRef, {
28
+ rotationY: 360,
29
+ duration: 0.6,
30
+ ease: "back.out(1.7)"
31
+ });
32
+ }
33
+ });
34
+ }
35
+
36
+ dispatch("send", value.trim());
37
+ value = "";
38
+
39
+ if (textareaRef) {
40
+ gsap.fromTo(textareaRef,
41
+ { scale: 1.02 },
42
+ { scale: 1, duration: 0.3, ease: "power2.out" }
43
+ );
44
+ }
45
+ }
46
+ }
47
+
48
+ function handleStop() {
49
+ if (processing) {
50
+ if (stopButtonRef) {
51
+ gsap.to(stopButtonRef, {
52
+ scale: 0.8,
53
+ duration: 0.15,
54
+ yoyo: true,
55
+ repeat: 1,
56
+ ease: "power2.inOut"
57
+ });
58
+ }
59
+ dispatch("stop");
60
+ }
61
+ }
62
+
63
+ onMount(() => {
64
+ if (inputAreaRef) {
65
+ gsap.fromTo(inputAreaRef,
66
+ { opacity: 0, y: 20 },
67
+ { opacity: 1, y: 0, duration: 0.4, ease: "power2.out" }
68
+ );
69
+ }
70
+ });
71
+
72
+ $: if (processing && stopButtonRef) {
73
+ gsap.to(stopButtonRef, {
74
+ scale: 1.05,
75
+ duration: 0.8,
76
+ yoyo: true,
77
+ repeat: -1,
78
+ ease: "power2.inOut"
79
+ });
80
+ }
81
+
82
+ $: if (!processing && stopButtonRef) {
83
+ gsap.killTweensOf(stopButtonRef);
84
+ gsap.set(stopButtonRef, { scale: 1 });
85
+ }
86
+
87
+ function handleKeydown(event: KeyboardEvent) {
88
+ if (event.key === "Enter" && !event.shiftKey) {
89
+ event.preventDefault();
90
+ handleSubmit();
91
+ }
92
+ }
93
+ </script>
94
+
95
+ <div class="input-area" bind:this={inputAreaRef}>
96
+ <div class="input-wrapper">
97
+ <textarea
98
+ bind:this={textareaRef}
99
+ bind:value
100
+ on:keydown={handleKeydown}
101
+ {placeholder}
102
+ {disabled}
103
+ rows="1"
104
+ />
105
+ <div class="input-glow"></div>
106
+ </div>
107
+ {#if processing}
108
+ <button
109
+ bind:this={stopButtonRef}
110
+ on:click={handleStop}
111
+ class="stop-btn"
112
+ title="Stop conversation"
113
+ >
114
+ <svg width="12" height="12" viewBox="0 0 12 12" fill="currentColor">
115
+ <rect x="2" y="2" width="8" height="8" rx="1"/>
116
+ </svg>
117
+ </button>
118
+ {:else}
119
+ <button
120
+ bind:this={sendButtonRef}
121
+ on:click={handleSubmit}
122
+ disabled={disabled || !value.trim()}
123
+ class="send-btn"
124
+ title="Send message"
125
+ >
126
+ <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
127
+ <path d="M22 2L11 13M22 2L15 22L11 13M22 2L2 9L11 13"/>
128
+ </svg>
129
+ </button>
130
+ {/if}
131
+ </div>
132
+
133
+ <style>
134
+ .input-area {
135
+ display: flex;
136
+ gap: 0.5rem;
137
+ padding: 12px 16px;
138
+ background: transparent;
139
+ border-top: 1px solid rgba(139, 115, 85, 0.06);
140
+ }
141
+
142
+ .input-wrapper {
143
+ flex: 1;
144
+ position: relative;
145
+ border-radius: 4px;
146
+ overflow: hidden;
147
+ }
148
+
149
+ .input-glow {
150
+ position: absolute;
151
+ top: 0;
152
+ left: 0;
153
+ right: 0;
154
+ bottom: 0;
155
+ background: linear-gradient(45deg, rgba(124, 152, 133, 0.05), rgba(139, 115, 85, 0.02));
156
+ opacity: 0;
157
+ transition: opacity 0.3s ease;
158
+ pointer-events: none;
159
+ border-radius: 4px;
160
+ }
161
+
162
+ textarea {
163
+ width: 100%;
164
+ background: rgba(139, 115, 85, 0.03);
165
+ color: rgba(251, 248, 244, 0.9);
166
+ border: 1px solid rgba(139, 115, 85, 0.08);
167
+ border-radius: 4px;
168
+ padding: 0.4rem 0.6rem;
169
+ resize: none;
170
+ font-family: "JetBrains Mono", "Monaco", "Menlo", monospace;
171
+ font-size: 11px;
172
+ line-height: 1.6;
173
+ transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
174
+ position: relative;
175
+ z-index: 1;
176
+ }
177
+
178
+ textarea:focus {
179
+ outline: none;
180
+ border-color: rgba(124, 152, 133, 0.2);
181
+ background: rgba(139, 115, 85, 0.06);
182
+ box-shadow: 0 0 0 1px rgba(124, 152, 133, 0.1);
183
+ }
184
+
185
+ textarea:focus + .input-glow {
186
+ opacity: 1;
187
+ }
188
+
189
+ textarea:disabled {
190
+ opacity: 0.5;
191
+ cursor: not-allowed;
192
+ }
193
+
194
+ .send-btn {
195
+ width: 32px;
196
+ height: 32px;
197
+ display: flex;
198
+ align-items: center;
199
+ justify-content: center;
200
+ background: rgba(124, 152, 133, 0.08);
201
+ color: rgba(124, 152, 133, 0.8);
202
+ border: 1px solid rgba(124, 152, 133, 0.15);
203
+ border-radius: 4px;
204
+ cursor: pointer;
205
+ transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
206
+ position: relative;
207
+ overflow: hidden;
208
+ }
209
+
210
+ .send-btn::before {
211
+ content: '';
212
+ position: absolute;
213
+ top: 0;
214
+ left: -100%;
215
+ width: 100%;
216
+ height: 100%;
217
+ background: linear-gradient(90deg, transparent, rgba(255, 255, 255, 0.2), transparent);
218
+ transition: left 0.5s ease;
219
+ }
220
+
221
+ .send-btn:hover:not(:disabled) {
222
+ background: rgba(124, 152, 133, 0.15);
223
+ border-color: rgba(124, 152, 133, 0.3);
224
+ transform: translateY(-1px);
225
+ box-shadow: 0 4px 12px rgba(124, 152, 133, 0.2);
226
+ color: rgba(124, 152, 133, 1);
227
+ }
228
+
229
+ .send-btn:hover:not(:disabled)::before {
230
+ left: 100%;
231
+ }
232
+
233
+ .send-btn:active:not(:disabled) {
234
+ transform: translateY(0) scale(0.98);
235
+ }
236
+
237
+ .send-btn:disabled {
238
+ opacity: 0.4;
239
+ cursor: not-allowed;
240
+ transform: none;
241
+ }
242
+
243
+ .stop-btn {
244
+ width: 32px;
245
+ height: 32px;
246
+ display: flex;
247
+ align-items: center;
248
+ justify-content: center;
249
+ background: rgba(184, 84, 80, 0.08);
250
+ color: rgba(184, 84, 80, 0.8);
251
+ border: 1px solid rgba(184, 84, 80, 0.15);
252
+ border-radius: 4px;
253
+ cursor: pointer;
254
+ transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
255
+ position: relative;
256
+ overflow: hidden;
257
+ }
258
+
259
+ .stop-btn::before {
260
+ content: '';
261
+ position: absolute;
262
+ top: 50%;
263
+ left: 50%;
264
+ width: 20px;
265
+ height: 20px;
266
+ border: 2px solid rgba(244, 67, 54, 0.3);
267
+ border-radius: 50%;
268
+ transform: translate(-50%, -50%);
269
+ animation: stop-pulse 1.5s ease-in-out infinite;
270
+ }
271
+
272
+ @keyframes stop-pulse {
273
+ 0%, 100% {
274
+ transform: translate(-50%, -50%) scale(1);
275
+ opacity: 1;
276
+ }
277
+ 50% {
278
+ transform: translate(-50%, -50%) scale(1.5);
279
+ opacity: 0.3;
280
+ }
281
+ }
282
+
283
+ .stop-btn:hover {
284
+ background: rgba(184, 84, 80, 0.15);
285
+ border-color: rgba(184, 84, 80, 0.3);
286
+ transform: translateY(-1px);
287
+ box-shadow: 0 4px 12px rgba(184, 84, 80, 0.2);
288
+ color: rgba(184, 84, 80, 1);
289
+ }
290
+
291
+ .stop-btn:active {
292
+ transform: translateY(0) scale(0.98);
293
+ }
294
+ </style>
src/lib/components/chat/MessageList.svelte ADDED
@@ -0,0 +1,169 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <script lang="ts">
2
+ import { createEventDispatcher, afterUpdate } from "svelte";
3
+ import gsap from "gsap";
4
+ import type { ChatMessage } from "../../models/chat-data";
5
+ import Message from "./Message.svelte";
6
+ import ExampleMessages from "./ExampleMessages.svelte";
7
+ import { typingIndicator } from "../../stores/chat-store";
8
+
9
+ export let messages: ReadonlyArray<ChatMessage> = [];
10
+ export let error: string | null = null;
11
+ export let autoScroll: boolean = true;
12
+ export let isConnected: boolean = false;
13
+ export let onSendMessage: ((message: string) => void) | undefined = undefined;
14
+
15
+ let typingIndicatorRef: HTMLDivElement;
16
+
17
+ const dispatch = createEventDispatcher();
18
+ let messagesDiv: HTMLDivElement;
19
+
20
+ function handleScroll() {
21
+ const isNearBottom =
22
+ messagesDiv.scrollHeight - messagesDiv.scrollTop - messagesDiv.clientHeight < 50;
23
+ dispatch("scroll", { nearBottom: isNearBottom });
24
+ }
25
+
26
+ export function scrollToBottom() {
27
+ messagesDiv.scrollTop = messagesDiv.scrollHeight;
28
+ }
29
+
30
+ afterUpdate(() => {
31
+ if (messages.length > 0 && autoScroll) {
32
+ scrollToBottom();
33
+ }
34
+ });
35
+
36
+ $: if ($typingIndicator && typingIndicatorRef) {
37
+ gsap.fromTo(typingIndicatorRef,
38
+ { opacity: 0, y: 10 },
39
+ { opacity: 1, y: 0, duration: 0.3, ease: "power2.out" }
40
+ );
41
+ }
42
+
43
+ $: if (!$typingIndicator && typingIndicatorRef) {
44
+ gsap.to(typingIndicatorRef, {
45
+ opacity: 0,
46
+ y: -5,
47
+ duration: 0.2,
48
+ ease: "power2.in"
49
+ });
50
+ }
51
+ </script>
52
+
53
+ <div class="messages" bind:this={messagesDiv} on:scroll={handleScroll}>
54
+ {#if messages.length === 0 && isConnected && onSendMessage}
55
+ <div class="welcome-section">
56
+ <ExampleMessages {onSendMessage} />
57
+ </div>
58
+ {/if}
59
+
60
+ {#each messages as message (message.id)}
61
+ <Message {message} />
62
+ {/each}
63
+
64
+ {#if $typingIndicator}
65
+ <div class="typing-indicator" bind:this={typingIndicatorRef}>
66
+ <div class="typing-dots">
67
+ <span class="dot"></span>
68
+ <span class="dot"></span>
69
+ <span class="dot"></span>
70
+ </div>
71
+ <span class="typing-text">AI is thinking...</span>
72
+ </div>
73
+ {/if}
74
+
75
+ {#if error}
76
+ <div class="error-message">Error: {error}</div>
77
+ {/if}
78
+ </div>
79
+
80
+ <style>
81
+ .messages {
82
+ flex: 1;
83
+ overflow-y: auto;
84
+ padding: 0.5rem;
85
+ display: flex;
86
+ flex-direction: column;
87
+ gap: 0.25rem;
88
+ min-height: 0;
89
+ position: relative;
90
+ }
91
+
92
+ .welcome-section {
93
+ position: absolute;
94
+ top: 50%;
95
+ left: 50%;
96
+ transform: translate(-50%, -50%);
97
+ width: 100%;
98
+ }
99
+
100
+ .typing-indicator {
101
+ display: flex;
102
+ align-items: center;
103
+ gap: 0.5rem;
104
+ padding: 0.375rem 0.5rem;
105
+ margin: 0.125rem 0;
106
+ border-left: 2px solid rgba(124, 152, 133, 0.2);
107
+ padding-left: 0.5rem;
108
+ }
109
+
110
+ .typing-dots {
111
+ display: flex;
112
+ gap: 0.25rem;
113
+ }
114
+
115
+ .dot {
116
+ width: 4px;
117
+ height: 4px;
118
+ border-radius: 50%;
119
+ background: rgba(124, 152, 133, 0.6);
120
+ animation: typing-bounce 1.4s ease-in-out infinite both;
121
+ }
122
+
123
+ .dot:nth-child(1) { animation-delay: -0.32s; }
124
+ .dot:nth-child(2) { animation-delay: -0.16s; }
125
+ .dot:nth-child(3) { animation-delay: 0s; }
126
+
127
+ @keyframes typing-bounce {
128
+ 0%, 80%, 100% {
129
+ transform: scale(0);
130
+ opacity: 0.5;
131
+ }
132
+ 40% {
133
+ transform: scale(1);
134
+ opacity: 1;
135
+ }
136
+ }
137
+
138
+ .typing-text {
139
+ font-size: 11px;
140
+ color: rgba(251, 248, 244, 0.3);
141
+ font-style: italic;
142
+ }
143
+
144
+ .error-message {
145
+ background: rgba(244, 67, 54, 0.1);
146
+ border-left: 2px solid #f44336;
147
+ border-radius: 3px;
148
+ padding: 0.4rem 0.6rem;
149
+ color: #ff9999;
150
+ font-size: 0.875rem;
151
+ }
152
+
153
+ ::-webkit-scrollbar {
154
+ width: 6px;
155
+ }
156
+
157
+ ::-webkit-scrollbar-track {
158
+ background: transparent;
159
+ }
160
+
161
+ ::-webkit-scrollbar-thumb {
162
+ background: rgba(255, 255, 255, 0.1);
163
+ border-radius: 3px;
164
+ }
165
+
166
+ ::-webkit-scrollbar-thumb:hover {
167
+ background: rgba(255, 255, 255, 0.2);
168
+ }
169
+ </style>
src/lib/components/chat/MessageSegment.svelte DELETED
@@ -1,70 +0,0 @@
1
- <script lang="ts">
2
- import type { MessageSegment } from "../../stores/agent";
3
- import MarkdownRenderer from "./MarkdownRenderer.svelte";
4
- import ToolInvocation from "./ToolInvocation.svelte";
5
-
6
- export let segment: MessageSegment;
7
- export let hideToolResult: boolean = false;
8
- </script>
9
-
10
- {#if segment.type === "text"}
11
- {#if segment.content.trim()}
12
- <div class="text-segment">
13
- <MarkdownRenderer content={segment.content} streaming={segment.streaming} />
14
- </div>
15
- {/if}
16
- {:else if segment.type === "tool-invocation"}
17
- <ToolInvocation {segment} />
18
- {:else if segment.type === "tool-result"}
19
- {#if !hideToolResult}
20
- <ToolInvocation {segment} />
21
- {/if}
22
- {:else if segment.type === "reasoning"}
23
- <div class="reasoning-segment">
24
- <details class="reasoning-details">
25
- <summary>Thinking...</summary>
26
- <div class="reasoning-content">
27
- {segment.content}
28
- </div>
29
- </details>
30
- </div>
31
- {/if}
32
-
33
- <style>
34
- .text-segment {
35
- margin: 0.25rem 0;
36
- }
37
-
38
- .reasoning-segment {
39
- margin: 0.25rem 0;
40
- padding: 0.4rem 0.6rem;
41
- background: rgba(255, 255, 255, 0.02);
42
- border-radius: 3px;
43
- border-left: 2px solid rgba(255, 255, 255, 0.1);
44
- }
45
-
46
- .reasoning-details {
47
- cursor: pointer;
48
- }
49
-
50
- .reasoning-details summary {
51
- color: rgba(255, 255, 255, 0.5);
52
- font-size: 0.8rem;
53
- user-select: none;
54
- }
55
-
56
- .reasoning-details summary:hover {
57
- color: rgba(255, 255, 255, 0.7);
58
- }
59
-
60
- .reasoning-content {
61
- margin-top: 0.5rem;
62
- padding: 0.5rem;
63
- background: rgba(0, 0, 0, 0.2);
64
- border-radius: 3px;
65
- color: rgba(255, 255, 255, 0.7);
66
- font-size: 0.825rem;
67
- font-family: "Monaco", "Menlo", monospace;
68
- white-space: pre-wrap;
69
- }
70
- </style>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
src/lib/components/chat/ReasoningBlock.svelte DELETED
@@ -1,293 +0,0 @@
1
- <script lang="ts">
2
- import { onMount, onDestroy } from "svelte";
3
- import gsap from "gsap";
4
-
5
- export let reasoning: string;
6
- export let isExpanded = false;
7
- export let autoCollapse = true;
8
- export let responseComplete = false;
9
-
10
- let iconElement: HTMLSpanElement;
11
- let contentElement: HTMLDivElement;
12
- let blockElement: HTMLDivElement;
13
- let collapseTimeout: number | null = null;
14
- let activeTimeline: gsap.core.Timeline | null = null;
15
-
16
- $: if (responseComplete && autoCollapse && isExpanded) {
17
- if (collapseTimeout) clearTimeout(collapseTimeout);
18
- collapseTimeout = window.setTimeout(() => {
19
- isExpanded = false;
20
- animateCollapse();
21
- }, 800);
22
- }
23
-
24
- function toggleExpanded() {
25
- isExpanded = !isExpanded;
26
-
27
- if (!iconElement || !contentElement) return;
28
-
29
- if (isExpanded) {
30
- animateExpand();
31
- } else {
32
- animateCollapse();
33
- }
34
- }
35
-
36
- function animateExpand() {
37
- if (!iconElement || !contentElement) return;
38
-
39
- if (activeTimeline) activeTimeline.kill();
40
-
41
- activeTimeline = gsap.timeline()
42
- .to(iconElement, {
43
- rotation: 180,
44
- duration: 0.15,
45
- ease: "power2.out"
46
- })
47
- .set(contentElement, { display: 'block' }, 0)
48
- .fromTo(contentElement, {
49
- opacity: 0,
50
- maxHeight: 0,
51
- y: -10
52
- }, {
53
- opacity: 1,
54
- maxHeight: 500,
55
- y: 0,
56
- duration: 0.2,
57
- ease: "power2.out"
58
- }, 0);
59
- }
60
-
61
- function animateCollapse() {
62
- if (!iconElement || !contentElement) return;
63
-
64
- if (activeTimeline) activeTimeline.kill();
65
-
66
- activeTimeline = gsap.timeline()
67
- .to(iconElement, {
68
- rotation: 0,
69
- duration: 0.1,
70
- ease: "power2.in"
71
- })
72
- .to(contentElement, {
73
- opacity: 0,
74
- maxHeight: 0,
75
- y: -5,
76
- duration: 0.1,
77
- ease: "power2.in",
78
- onComplete: () => {
79
- if (contentElement) {
80
- gsap.set(contentElement, { display: 'none' });
81
- }
82
- }
83
- }, 0)
84
- .to(blockElement, {
85
- scale: 0.98,
86
- duration: 0.1,
87
- ease: "power2.in"
88
- }, 0)
89
- .to(blockElement, {
90
- scale: 1,
91
- duration: 0.2,
92
- ease: "back.out(1.5)"
93
- });
94
- }
95
-
96
- onMount(() => {
97
- if (iconElement) {
98
- gsap.set(iconElement, {
99
- transformOrigin: "center",
100
- rotation: isExpanded ? 180 : 0
101
- });
102
- }
103
-
104
- if (contentElement) {
105
- gsap.set(contentElement, {
106
- display: isExpanded ? 'block' : 'none',
107
- opacity: isExpanded ? 1 : 0,
108
- maxHeight: isExpanded ? 500 : 0,
109
- y: isExpanded ? 0 : -10
110
- });
111
- }
112
-
113
- if (blockElement) {
114
- gsap.fromTo(blockElement,
115
- { opacity: 0, scale: 0.95, y: -5 },
116
- {
117
- opacity: 1,
118
- scale: 1,
119
- y: 0,
120
- duration: 0.3,
121
- ease: "power2.out"
122
- }
123
- );
124
- }
125
- });
126
-
127
- onDestroy(() => {
128
- if (collapseTimeout) clearTimeout(collapseTimeout);
129
- if (activeTimeline) activeTimeline.kill();
130
-
131
- if (iconElement) gsap.killTweensOf(iconElement);
132
- if (contentElement) gsap.killTweensOf(contentElement);
133
- if (blockElement) gsap.killTweensOf(blockElement);
134
- });
135
- </script>
136
-
137
- <div bind:this={blockElement} class="reasoning-block">
138
- <button
139
- class="reasoning-toggle"
140
- on:click={toggleExpanded}
141
- title={isExpanded ? "Hide AI thinking" : "Show AI thinking"}
142
- aria-expanded={isExpanded}
143
- aria-label="Toggle thinking visibility"
144
- >
145
- <div class="toggle-content">
146
- <span bind:this={iconElement} class="toggle-icon">
147
- <svg width="12" height="12" viewBox="0 0 12 12" fill="currentColor">
148
- <path d="M3 4.5L6 7.5L9 4.5" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" fill="none"/>
149
- </svg>
150
- </span>
151
- <span class="toggle-label">
152
- <span class="label-main">Thinking</span>
153
- <span class="label-hint">{isExpanded ? "Click to hide" : "Click to view"}</span>
154
- </span>
155
- </div>
156
- </button>
157
-
158
- <div
159
- bind:this={contentElement}
160
- class="reasoning-content"
161
- >
162
- <div class="content-inner">
163
- <pre class="reasoning-text">{reasoning}</pre>
164
- </div>
165
- </div>
166
- </div>
167
-
168
- <style>
169
- .reasoning-block {
170
- margin: 0.75rem 0;
171
- border-radius: 8px;
172
- background: linear-gradient(135deg, rgba(70, 70, 80, 0.15) 0%, rgba(60, 60, 70, 0.15) 100%);
173
- border: 1px solid rgba(120, 120, 140, 0.15);
174
- overflow: hidden;
175
- transition: border-color 0.2s ease, background 0.2s ease;
176
- }
177
-
178
- .reasoning-block:hover {
179
- border-color: rgba(140, 140, 160, 0.25);
180
- background: linear-gradient(135deg, rgba(75, 75, 85, 0.2) 0%, rgba(65, 65, 75, 0.2) 100%);
181
- }
182
-
183
- .reasoning-toggle {
184
- width: 100%;
185
- padding: 0.75rem 1rem;
186
- background: transparent;
187
- border: none;
188
- color: rgba(255, 255, 255, 0.7);
189
- cursor: pointer;
190
- font-family: inherit;
191
- text-align: left;
192
- transition: all 0.2s ease;
193
- }
194
-
195
- .reasoning-toggle:hover {
196
- background: rgba(255, 255, 255, 0.03);
197
- color: rgba(255, 255, 255, 0.9);
198
- }
199
-
200
- .reasoning-toggle:focus-visible {
201
- outline: 2px solid rgba(140, 140, 200, 0.5);
202
- outline-offset: -2px;
203
- }
204
-
205
- .toggle-content {
206
- display: flex;
207
- align-items: center;
208
- gap: 0.75rem;
209
- }
210
-
211
- .toggle-icon {
212
- display: flex;
213
- align-items: center;
214
- justify-content: center;
215
- width: 20px;
216
- height: 20px;
217
- border-radius: 4px;
218
- background: rgba(255, 255, 255, 0.05);
219
- color: rgba(255, 255, 255, 0.5);
220
- transition: background 0.2s ease, color 0.2s ease;
221
- }
222
-
223
- .reasoning-toggle:hover .toggle-icon {
224
- background: rgba(255, 255, 255, 0.08);
225
- color: rgba(255, 255, 255, 0.7);
226
- }
227
-
228
- .toggle-label {
229
- display: flex;
230
- flex-direction: column;
231
- gap: 0.125rem;
232
- }
233
-
234
- .label-main {
235
- font-size: 0.875rem;
236
- font-weight: 500;
237
- letter-spacing: 0.01em;
238
- }
239
-
240
- .label-hint {
241
- font-size: 0.7rem;
242
- color: rgba(255, 255, 255, 0.4);
243
- transition: color 0.2s ease;
244
- }
245
-
246
- .reasoning-toggle:hover .label-hint {
247
- color: rgba(255, 255, 255, 0.5);
248
- }
249
-
250
- .reasoning-content {
251
- border-top: 1px solid rgba(100, 100, 100, 0.15);
252
- background: rgba(30, 30, 35, 0.3);
253
- overflow: hidden;
254
- }
255
-
256
- .content-inner {
257
- padding: 0.75rem 1rem;
258
- }
259
-
260
- .reasoning-text {
261
- margin: 0;
262
- padding: 0.75rem;
263
- background: rgba(20, 20, 25, 0.4);
264
- border-radius: 6px;
265
- font-family: 'SF Mono', 'Monaco', 'Inconsolata', 'Fira Code', monospace;
266
- font-size: 0.75rem;
267
- line-height: 1.5;
268
- color: rgba(255, 255, 255, 0.6);
269
- white-space: pre-wrap;
270
- word-wrap: break-word;
271
- max-height: 350px;
272
- overflow-y: auto;
273
- }
274
-
275
- .reasoning-text::-webkit-scrollbar {
276
- width: 8px;
277
- }
278
-
279
- .reasoning-text::-webkit-scrollbar-track {
280
- background: rgba(30, 30, 35, 0.5);
281
- border-radius: 4px;
282
- }
283
-
284
- .reasoning-text::-webkit-scrollbar-thumb {
285
- background: rgba(100, 100, 110, 0.4);
286
- border-radius: 4px;
287
- transition: background 0.2s ease;
288
- }
289
-
290
- .reasoning-text::-webkit-scrollbar-thumb:hover {
291
- background: rgba(120, 120, 130, 0.5);
292
- }
293
- </style>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
src/lib/components/chat/StreamingIndicator.svelte DELETED
@@ -1,157 +0,0 @@
1
- <script lang="ts">
2
- import { onMount } from "svelte";
3
- import gsap from "gsap";
4
-
5
- export let status: "thinking" | "streaming" = "thinking";
6
- export let content: string = "";
7
- export let duration: number = 0;
8
-
9
- let indicatorElement: HTMLDivElement;
10
- let truncatedContent = "";
11
- const maxPreviewLength = 100;
12
-
13
- $: truncatedContent =
14
- content.length > maxPreviewLength ? content.slice(0, maxPreviewLength) + "..." : content;
15
-
16
- $: formattedDuration = duration > 0 ? `${(duration / 1000).toFixed(1)}s` : "";
17
-
18
- onMount(() => {
19
- if (indicatorElement) {
20
- gsap.fromTo(
21
- indicatorElement,
22
- { opacity: 0, y: -5 },
23
- { opacity: 1, y: 0, duration: 0.3, ease: "power2.out" },
24
- );
25
- }
26
- });
27
- </script>
28
-
29
- <div class="streaming-indicator" bind:this={indicatorElement}>
30
- <div class="indicator-header">
31
- <span class="status-icon">
32
- {#if status === "thinking"}
33
- <span class="thinking-icon">🤔</span>
34
- {:else}
35
- <span class="streaming-icon">✍️</span>
36
- {/if}
37
- </span>
38
- <span class="status-text">
39
- {#if status === "thinking"}
40
- Thinking<span class="dots"></span>
41
- {:else}
42
- Responding<span class="dots"></span>
43
- {/if}
44
- </span>
45
- {#if formattedDuration}
46
- <span class="duration">{formattedDuration}</span>
47
- {/if}
48
- </div>
49
-
50
- {#if truncatedContent}
51
- <div class="preview-content">
52
- {truncatedContent}
53
- </div>
54
- {/if}
55
- </div>
56
-
57
- <style>
58
- .streaming-indicator {
59
- background: rgba(65, 105, 225, 0.08);
60
- border: 1px solid rgba(65, 105, 225, 0.2);
61
- border-radius: 4px;
62
- padding: 0.5rem 0.75rem;
63
- margin: 0.25rem 0;
64
- font-size: 0.875rem;
65
- }
66
-
67
- .indicator-header {
68
- display: flex;
69
- align-items: center;
70
- gap: 0.5rem;
71
- color: rgba(255, 255, 255, 0.7);
72
- }
73
-
74
- .status-icon {
75
- display: inline-flex;
76
- align-items: center;
77
- justify-content: center;
78
- width: 1.2rem;
79
- height: 1.2rem;
80
- }
81
-
82
- .thinking-icon {
83
- animation: pulse 1.5s ease-in-out infinite;
84
- }
85
-
86
- .streaming-icon {
87
- animation: write 1s ease-in-out infinite;
88
- }
89
-
90
- @keyframes pulse {
91
- 0%,
92
- 100% {
93
- transform: scale(1);
94
- opacity: 0.8;
95
- }
96
- 50% {
97
- transform: scale(1.1);
98
- opacity: 1;
99
- }
100
- }
101
-
102
- @keyframes write {
103
- 0%,
104
- 100% {
105
- transform: translateX(0);
106
- }
107
- 25% {
108
- transform: translateX(-1px);
109
- }
110
- 75% {
111
- transform: translateX(1px);
112
- }
113
- }
114
-
115
- .status-text {
116
- flex: 1;
117
- }
118
-
119
- .dots::after {
120
- content: "";
121
- animation: dots 1.5s steps(4, end) infinite;
122
- }
123
-
124
- @keyframes dots {
125
- 0%,
126
- 20% {
127
- content: "";
128
- }
129
- 40% {
130
- content: ".";
131
- }
132
- 60% {
133
- content: "..";
134
- }
135
- 80%,
136
- 100% {
137
- content: "...";
138
- }
139
- }
140
-
141
- .duration {
142
- color: rgba(255, 255, 255, 0.4);
143
- font-size: 0.75rem;
144
- font-family: "Monaco", "Menlo", monospace;
145
- }
146
-
147
- .preview-content {
148
- margin-top: 0.5rem;
149
- padding-top: 0.5rem;
150
- border-top: 1px solid rgba(255, 255, 255, 0.05);
151
- color: rgba(255, 255, 255, 0.6);
152
- font-family: "Monaco", "Menlo", monospace;
153
- font-size: 0.8rem;
154
- line-height: 1.4;
155
- word-break: break-word;
156
- }
157
- </style>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
src/lib/components/chat/StreamingText.svelte DELETED
@@ -1,146 +0,0 @@
1
- <script lang="ts">
2
- import { onMount, afterUpdate } from "svelte";
3
- import gsap from "gsap";
4
-
5
- export let content: string = "";
6
- export let streaming: boolean = false;
7
- export let speed: number = 60;
8
- export let onComplete: (() => void) | undefined = undefined;
9
-
10
- let displayedContent = "";
11
- let cursorElement: HTMLSpanElement;
12
- let containerElement: HTMLDivElement;
13
- let animationTimeline: any = null;
14
- let buffer: string[] = [];
15
- let isProcessing = false;
16
- let lastProcessedLength = 0;
17
-
18
- $: if (streaming && content) {
19
- if (content.length > lastProcessedLength) {
20
- const newChars = content.slice(lastProcessedLength);
21
- if (newChars) {
22
- buffer.push(...newChars.split(''));
23
- lastProcessedLength = content.length;
24
- processBuffer();
25
- }
26
- }
27
- } else if (!streaming && content !== displayedContent) {
28
- displayedContent = content;
29
- lastProcessedLength = content.length;
30
- if (animationTimeline) {
31
- animationTimeline.kill();
32
- }
33
- hideCursor();
34
- }
35
-
36
- async function processBuffer() {
37
- if (isProcessing || buffer.length === 0) return;
38
- isProcessing = true;
39
-
40
- while (buffer.length > 0) {
41
- const chunkSize = Math.min(3, buffer.length);
42
- const chunk = buffer.splice(0, chunkSize).join('');
43
- displayedContent += chunk;
44
-
45
- if (buffer.length > 0) {
46
- await new Promise(resolve => setTimeout(resolve, 1000 / speed));
47
- }
48
- }
49
-
50
- isProcessing = false;
51
-
52
- if (!streaming && displayedContent === content) {
53
- hideCursor();
54
- onComplete?.();
55
- }
56
- }
57
-
58
- function showCursor() {
59
- if (cursorElement) {
60
- gsap.set(cursorElement, { display: "inline-block", opacity: 1 });
61
- gsap.to(cursorElement, {
62
- opacity: 0,
63
- duration: 0.5,
64
- repeat: -1,
65
- yoyo: true,
66
- ease: "steps(1)"
67
- });
68
- }
69
- }
70
-
71
- function hideCursor() {
72
- if (cursorElement) {
73
- gsap.killTweensOf(cursorElement);
74
- gsap.to(cursorElement, {
75
- opacity: 0,
76
- duration: 0.2,
77
- onComplete: () => {
78
- if (cursorElement) {
79
- gsap.set(cursorElement, { display: "none" });
80
- }
81
- }
82
- });
83
- }
84
- }
85
-
86
- onMount(() => {
87
- if (streaming) {
88
- showCursor();
89
- lastProcessedLength = 0;
90
- displayedContent = "";
91
-
92
- if (content) {
93
- buffer.push(...content.split(''));
94
- lastProcessedLength = content.length;
95
- processBuffer();
96
- }
97
-
98
- gsap.fromTo(containerElement,
99
- { opacity: 0, y: 5 },
100
- { opacity: 1, y: 0, duration: 0.3, ease: "power2.out" }
101
- );
102
- }
103
-
104
- return () => {
105
- if (animationTimeline) {
106
- animationTimeline.kill();
107
- }
108
- };
109
- });
110
-
111
- afterUpdate(() => {
112
- if (streaming && cursorElement) {
113
- showCursor();
114
- }
115
- });
116
- </script>
117
-
118
- <div class="streaming-text" bind:this={containerElement}>
119
- <span class="text-content">{displayedContent}</span>
120
- {#if streaming}
121
- <span bind:this={cursorElement} class="cursor">▊</span>
122
- {/if}
123
- </div>
124
-
125
- <style>
126
- .streaming-text {
127
- display: inline;
128
- word-wrap: break-word;
129
- white-space: pre-wrap;
130
- }
131
-
132
- .text-content {
133
- color: inherit;
134
- font-family: inherit;
135
- line-height: inherit;
136
- }
137
-
138
- .cursor {
139
- display: inline-block;
140
- color: rgba(65, 105, 225, 0.8);
141
- font-weight: bold;
142
- margin-left: 1px;
143
- animation: none;
144
- vertical-align: baseline;
145
- }
146
- </style>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
src/lib/components/chat/TextRenderer.svelte ADDED
@@ -0,0 +1,68 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <script lang="ts">
2
+ import { onMount } from "svelte";
3
+ import gsap from "gsap";
4
+
5
+ export let content: string;
6
+ export let streaming: boolean = false;
7
+
8
+ let textEl: HTMLDivElement;
9
+ let cursorEl: HTMLSpanElement;
10
+
11
+ onMount(() => {
12
+ if (textEl && !streaming) {
13
+ gsap.fromTo(textEl,
14
+ { opacity: 0, y: 3, scale: 0.99 },
15
+ { opacity: 1, y: 0, scale: 1, duration: 0.2, ease: "power2.out" }
16
+ );
17
+ }
18
+ });
19
+
20
+ $: if (streaming && cursorEl) {
21
+ gsap.to(cursorEl, {
22
+ opacity: 0.3,
23
+ duration: 0.6,
24
+ yoyo: true,
25
+ repeat: -1,
26
+ ease: "power2.inOut"
27
+ });
28
+ }
29
+
30
+ $: if (!streaming && cursorEl) {
31
+ gsap.killTweensOf(cursorEl);
32
+ gsap.to(cursorEl, {
33
+ opacity: 0,
34
+ duration: 0.3,
35
+ ease: "power2.out"
36
+ });
37
+ }
38
+ </script>
39
+
40
+ <div class="text-renderer" bind:this={textEl}>
41
+ <span class="content">
42
+ {content}{#if streaming}<span class="cursor" bind:this={cursorEl}>▊</span>{/if}
43
+ </span>
44
+ </div>
45
+
46
+ <style>
47
+ .text-renderer {
48
+ padding: 0.125rem 0;
49
+ line-height: 1.6;
50
+ white-space: pre-wrap;
51
+ word-wrap: break-word;
52
+ }
53
+
54
+ .content {
55
+ color: rgba(251, 248, 244, 0.9);
56
+ font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Monaco", "Menlo", monospace;
57
+ font-size: 0.875rem;
58
+ line-height: 1.6;
59
+ letter-spacing: 0.01em;
60
+ }
61
+
62
+ .cursor {
63
+ display: inline-block;
64
+ color: rgba(124, 152, 133, 0.7);
65
+ font-weight: 400;
66
+ margin-left: 1px;
67
+ }
68
+ </style>
src/lib/components/chat/ToolInvocation.svelte DELETED
@@ -1,520 +0,0 @@
1
- <script lang="ts">
2
- import { onMount, onDestroy } from "svelte";
3
- import gsap from "gsap";
4
- import type { MessageSegment } from "../../stores/agent";
5
-
6
- export let segment: MessageSegment;
7
-
8
- let element: HTMLDivElement;
9
- let expanded = false;
10
- let statusIcon: HTMLSpanElement;
11
- let progressRing: SVGCircleElement;
12
- let detailsElement: HTMLDivElement;
13
- let timeline: gsap.core.Timeline | null = null;
14
- let prevStatus = segment.toolStatus;
15
-
16
- const toolIcons: Record<string, string> = {
17
- read_editor: "📄",
18
- write_editor: "✏️",
19
- observe_console: "📟",
20
- default: "🔧",
21
- };
22
-
23
- const statusIcons: Record<string, string> = {
24
- pending: "⏳",
25
- running: "⚡",
26
- completed: "✅",
27
- error: "❌",
28
- };
29
-
30
- $: if (segment.toolStatus !== prevStatus && statusIcon) {
31
- animateStatusChange(prevStatus, segment.toolStatus);
32
- prevStatus = segment.toolStatus;
33
- }
34
-
35
- function toggle() {
36
- expanded = !expanded;
37
- animateToggle();
38
- }
39
-
40
- function animateToggle() {
41
- if (!detailsElement) return;
42
-
43
- if (timeline) timeline.kill();
44
- timeline = gsap.timeline();
45
-
46
- if (expanded) {
47
- timeline
48
- .set(detailsElement, { display: "block" })
49
- .fromTo(detailsElement,
50
- { opacity: 0, height: 0, y: -10 },
51
- { opacity: 1, height: "auto", y: 0, duration: 0.3, ease: "power2.out" }
52
- );
53
- } else {
54
- timeline
55
- .to(detailsElement,
56
- { opacity: 0, height: 0, y: -5, duration: 0.2, ease: "power2.in" }
57
- )
58
- .set(detailsElement, { display: "none" });
59
- }
60
- }
61
-
62
- function animateStatusChange(_from: string | undefined, to: string | undefined) {
63
- if (!statusIcon || !element) return;
64
-
65
- gsap.timeline()
66
- .to(statusIcon, {
67
- scale: 1.3,
68
- duration: 0.15,
69
- ease: "power2.in"
70
- })
71
- .to(statusIcon, {
72
- scale: 1,
73
- duration: 0.15,
74
- ease: "back.out(2)"
75
- });
76
-
77
- if (to === "completed") {
78
- gsap.to(element, {
79
- borderColor: "rgba(100, 200, 180, 0.3)",
80
- backgroundColor: "rgba(100, 200, 180, 0.05)",
81
- duration: 0.3,
82
- ease: "power2.out"
83
- });
84
- } else if (to === "error") {
85
- gsap.to(element, {
86
- borderColor: "rgba(255, 0, 0, 0.4)",
87
- backgroundColor: "rgba(255, 0, 0, 0.1)",
88
- duration: 0.3,
89
- ease: "power2.out"
90
- });
91
- }
92
-
93
- if (progressRing) {
94
- if (to === "running") {
95
- gsap.to(progressRing, {
96
- strokeDashoffset: 50,
97
- duration: 1,
98
- ease: "power2.inOut",
99
- repeat: -1,
100
- yoyo: true
101
- });
102
- } else if (to === "completed") {
103
- gsap.to(progressRing, {
104
- strokeDashoffset: 0,
105
- duration: 0.3,
106
- ease: "power2.out"
107
- });
108
- }
109
- }
110
- }
111
-
112
- function formatDuration(): string {
113
- if (!segment.startTime) return "";
114
- const duration = (segment.endTime || Date.now()) - segment.startTime;
115
- if (duration < 1000) {
116
- return `${duration}ms`;
117
- }
118
- return `${(duration / 1000).toFixed(1)}s`;
119
- }
120
-
121
- onMount(() => {
122
- gsap.fromTo(
123
- element,
124
- { opacity: 0, x: -20, scale: 0.95 },
125
- {
126
- opacity: 1,
127
- x: 0,
128
- scale: 1,
129
- duration: 0.4,
130
- ease: "back.out(1.5)"
131
- }
132
- );
133
-
134
- if (segment.toolStatus === "running" && progressRing) {
135
- gsap.to(progressRing, {
136
- strokeDashoffset: 50,
137
- duration: 1,
138
- ease: "power2.inOut",
139
- repeat: -1,
140
- yoyo: true
141
- });
142
- }
143
- });
144
-
145
- onDestroy(() => {
146
- if (timeline) timeline.kill();
147
- });
148
- </script>
149
-
150
- <div class="tool-invocation {segment.toolStatus}" bind:this={element}>
151
- <button class="tool-header" on:click={toggle} aria-expanded={expanded}>
152
- <div class="status-indicator">
153
- <svg class="progress-ring" width="24" height="24">
154
- <circle
155
- cx="12"
156
- cy="12"
157
- r="10"
158
- stroke="rgba(255, 255, 255, 0.1)"
159
- stroke-width="2"
160
- fill="none"
161
- />
162
- <circle
163
- bind:this={progressRing}
164
- cx="12"
165
- cy="12"
166
- r="10"
167
- stroke="currentColor"
168
- stroke-width="2"
169
- fill="none"
170
- stroke-dasharray="62.83"
171
- stroke-dashoffset="62.83"
172
- transform="rotate(-90 12 12)"
173
- />
174
- </svg>
175
- <span bind:this={statusIcon} class="status-icon">
176
- {#if segment.toolStatus === "running"}
177
- <span class="spinner">{statusIcons[segment.toolStatus || "pending"]}</span>
178
- {:else}
179
- {statusIcons[segment.toolStatus || "pending"]}
180
- {/if}
181
- </span>
182
- </div>
183
- <span class="tool-icon">{toolIcons[segment.toolName || "default"] || toolIcons.default}</span>
184
- <span class="tool-name">
185
- {#if segment.toolStatus === "running"}
186
- Calling {segment.toolName}...
187
- {:else if segment.toolStatus === "completed"}
188
- Called {segment.toolName}
189
- {:else if segment.toolStatus === "error"}
190
- Failed to call {segment.toolName}
191
- {:else}
192
- Preparing {segment.toolName}...
193
- {/if}
194
- </span>
195
- {#if segment.startTime}
196
- <span class="duration">{formatDuration()}</span>
197
- {/if}
198
- <span class="expand-icon" class:rotated={expanded}>▶</span>
199
- </button>
200
-
201
- <div bind:this={detailsElement} class="tool-details" style="display: none;">
202
- {#if segment.toolArgs && Object.keys(segment.toolArgs).length > 0}
203
- <div class="section-title">Parameters:</div>
204
- <div class="params">
205
- {#each Object.entries(segment.toolArgs) as [key, value]}
206
- <div class="param">
207
- <span class="param-key">{key}:</span>
208
- <span class="param-value">
209
- {#if typeof value === 'string' && value.length > 100}
210
- <pre>{value}</pre>
211
- {:else}
212
- {JSON.stringify(value)}
213
- {/if}
214
- </span>
215
- </div>
216
- {/each}
217
- </div>
218
- {/if}
219
- {#if segment.toolOutput || segment.toolResult}
220
- <div class="section-title">Result:</div>
221
- <div class="tool-output">
222
- <pre>{segment.toolOutput || segment.toolResult}</pre>
223
- </div>
224
- {/if}
225
- {#if segment.consoleOutput && segment.consoleOutput.length > 0}
226
- <div class="section-title">Console Output:</div>
227
- <div class="console-output">
228
- {#each segment.consoleOutput as line}
229
- <div class="console-line">{line}</div>
230
- {/each}
231
- </div>
232
- {/if}
233
- {#if segment.toolError}
234
- <div class="section-title">Error:</div>
235
- <div class="tool-error">
236
- {segment.toolError}
237
- </div>
238
- {/if}
239
- </div>
240
- </div>
241
-
242
- <style>
243
- .tool-invocation {
244
- margin: 0.5rem 0;
245
- margin-left: 1rem;
246
- border-radius: 4px;
247
- overflow: hidden;
248
- border: 1px solid rgba(65, 105, 225, 0.2);
249
- background: rgba(65, 105, 225, 0.05);
250
- transition: all 0.2s ease;
251
- }
252
-
253
- .tool-invocation.running {
254
- background: rgba(255, 210, 30, 0.08);
255
- border-color: rgba(255, 210, 30, 0.3);
256
- animation: pulse 2s ease-in-out infinite;
257
- }
258
-
259
- @keyframes pulse {
260
- 0%, 100% {
261
- opacity: 1;
262
- }
263
- 50% {
264
- opacity: 0.8;
265
- }
266
- }
267
-
268
- .tool-invocation.completed {
269
- background: rgba(100, 200, 180, 0.04);
270
- border-color: rgba(100, 200, 180, 0.15);
271
- }
272
-
273
- .tool-invocation.error {
274
- background: rgba(255, 0, 0, 0.08);
275
- border-color: rgba(255, 0, 0, 0.3);
276
- }
277
-
278
- .tool-header {
279
- display: flex;
280
- align-items: center;
281
- gap: 0.5rem;
282
- width: 100%;
283
- padding: 0.35rem 0.5rem;
284
- background: transparent;
285
- border: none;
286
- color: inherit;
287
- font: inherit;
288
- text-align: left;
289
- cursor: pointer;
290
- transition: background 0.2s ease;
291
- }
292
-
293
- .tool-header:hover {
294
- background: rgba(255, 255, 255, 0.02);
295
- }
296
-
297
- .status-indicator {
298
- position: relative;
299
- width: 24px;
300
- height: 24px;
301
- display: flex;
302
- align-items: center;
303
- justify-content: center;
304
- }
305
-
306
- .progress-ring {
307
- position: absolute;
308
- top: 0;
309
- left: 0;
310
- color: rgba(65, 105, 225, 0.6);
311
- }
312
-
313
- .tool-invocation.running .progress-ring {
314
- color: rgba(255, 210, 30, 0.8);
315
- }
316
-
317
- .tool-invocation.completed .progress-ring {
318
- color: rgba(100, 200, 180, 0.6);
319
- }
320
-
321
- .tool-invocation.error .progress-ring {
322
- color: rgba(255, 0, 0, 0.6);
323
- }
324
-
325
- .status-icon {
326
- font-size: 0.85rem;
327
- position: relative;
328
- z-index: 1;
329
- }
330
-
331
- .tool-icon {
332
- font-size: 0.95rem;
333
- }
334
-
335
- .tool-name {
336
- flex: 1;
337
- color: rgba(255, 255, 255, 0.85);
338
- font-size: 0.8rem;
339
- }
340
-
341
- .duration {
342
- color: rgba(255, 255, 255, 0.4);
343
- font-size: 0.7rem;
344
- font-family: "Monaco", "Menlo", monospace;
345
- }
346
-
347
- .expand-icon {
348
- font-size: 0.65rem;
349
- color: rgba(255, 255, 255, 0.4);
350
- transition: transform 0.2s ease;
351
- }
352
-
353
- .expand-icon.rotated {
354
- transform: rotate(90deg);
355
- }
356
-
357
- .tool-details {
358
- padding: 0.5rem;
359
- background: rgba(0, 0, 0, 0.2);
360
- border-top: 1px solid rgba(255, 255, 255, 0.05);
361
- }
362
-
363
- .section-title {
364
- color: rgba(255, 255, 255, 0.5);
365
- font-size: 0.7rem;
366
- font-weight: 600;
367
- margin-bottom: 0.25rem;
368
- text-transform: uppercase;
369
- letter-spacing: 0.5px;
370
- }
371
-
372
- .params {
373
- font-family: "Monaco", "Menlo", monospace;
374
- font-size: 0.75rem;
375
- }
376
-
377
- .param {
378
- display: block;
379
- margin: 0.4rem 0;
380
- }
381
-
382
- .param-key {
383
- color: rgba(255, 255, 255, 0.5);
384
- display: block;
385
- margin-bottom: 0.2rem;
386
- font-size: 0.7rem;
387
- }
388
-
389
- .param-value {
390
- color: rgba(255, 210, 30, 0.8);
391
- word-break: break-all;
392
- display: block;
393
- margin-left: 0.5rem;
394
- }
395
-
396
- .param-value pre {
397
- margin: 0;
398
- padding: 0.4rem;
399
- background: rgba(0, 0, 0, 0.3);
400
- border-radius: 3px;
401
- font-size: 0.7rem;
402
- overflow-x: auto;
403
- overflow-y: auto;
404
- max-height: 150px;
405
- }
406
-
407
- .param-value pre::-webkit-scrollbar {
408
- width: 8px;
409
- height: 8px;
410
- }
411
-
412
- .param-value pre::-webkit-scrollbar-track {
413
- background: rgba(0, 0, 0, 0.2);
414
- border-radius: 4px;
415
- }
416
-
417
- .param-value pre::-webkit-scrollbar-thumb {
418
- background: rgba(100, 200, 180, 0.3);
419
- border-radius: 4px;
420
- }
421
-
422
- .param-value pre::-webkit-scrollbar-thumb:hover {
423
- background: rgba(100, 200, 180, 0.5);
424
- }
425
-
426
- .spinner {
427
- display: inline-block;
428
- animation: spin 0.8s linear infinite;
429
- }
430
-
431
- @keyframes spin {
432
- from { transform: rotate(0deg); }
433
- to { transform: rotate(360deg); }
434
- }
435
-
436
- .tool-output {
437
- margin-top: 0.5rem;
438
- padding: 0.5rem;
439
- background: rgba(100, 200, 180, 0.05);
440
- border: 1px solid rgba(100, 200, 180, 0.1);
441
- border-radius: 3px;
442
- }
443
-
444
- .tool-output pre {
445
- margin: 0;
446
- font-family: "Monaco", "Menlo", monospace;
447
- font-size: 0.7rem;
448
- color: rgba(100, 200, 180, 0.9);
449
- white-space: pre-wrap;
450
- word-wrap: break-word;
451
- max-height: 200px;
452
- overflow-y: auto;
453
- }
454
-
455
- .tool-output pre::-webkit-scrollbar {
456
- width: 8px;
457
- height: 8px;
458
- }
459
-
460
- .tool-output pre::-webkit-scrollbar-track {
461
- background: rgba(0, 0, 0, 0.2);
462
- border-radius: 4px;
463
- }
464
-
465
- .tool-output pre::-webkit-scrollbar-thumb {
466
- background: rgba(100, 200, 180, 0.3);
467
- border-radius: 4px;
468
- }
469
-
470
- .tool-output pre::-webkit-scrollbar-thumb:hover {
471
- background: rgba(100, 200, 180, 0.5);
472
- }
473
-
474
- .console-output {
475
- margin-top: 0.5rem;
476
- background: rgba(0, 0, 0, 0.3);
477
- border-radius: 3px;
478
- padding: 0.4rem;
479
- font-family: "Monaco", "Menlo", monospace;
480
- font-size: 0.7rem;
481
- color: rgba(255, 255, 255, 0.8);
482
- overflow-x: auto;
483
- max-height: 200px;
484
- overflow-y: auto;
485
- }
486
-
487
- .console-output::-webkit-scrollbar {
488
- width: 8px;
489
- height: 8px;
490
- }
491
-
492
- .console-output::-webkit-scrollbar-track {
493
- background: rgba(0, 0, 0, 0.2);
494
- border-radius: 4px;
495
- }
496
-
497
- .console-output::-webkit-scrollbar-thumb {
498
- background: rgba(65, 105, 225, 0.3);
499
- border-radius: 4px;
500
- }
501
-
502
- .console-output::-webkit-scrollbar-thumb:hover {
503
- background: rgba(65, 105, 225, 0.5);
504
- }
505
-
506
- .console-line {
507
- margin: 0.1rem 0;
508
- }
509
-
510
- .tool-error {
511
- margin-top: 0.5rem;
512
- padding: 0.5rem;
513
- background: rgba(255, 0, 0, 0.08);
514
- border: 1px solid rgba(255, 0, 0, 0.2);
515
- border-radius: 3px;
516
- color: rgba(255, 100, 100, 0.9);
517
- font-size: 0.75rem;
518
- font-family: "Monaco", "Menlo", monospace;
519
- }
520
- </style>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
src/lib/components/chat/context.md CHANGED
@@ -1,24 +1,30 @@
1
  # Chat Context
2
 
3
- AI chat interface with real-time streaming and conversation control.
4
 
5
  ## Components
6
 
7
- - `ChatPanel.svelte` - Main chat UI with header bar, clear button, smooth scroll, stop button
8
- - `ExampleMessages.svelte` - Clickable prompt suggestions when chat is empty
9
- - `MessageSegment.svelte` - Renders text, tool invocations, and results
10
- - `StreamingText.svelte` - Optimized character streaming with state persistence
11
- - `ToolInvocation.svelte` - Tool execution and results display
12
- - `InProgressBlock.svelte` - Status indicator with progress bar
13
- - `ReasoningBlock.svelte` - Auto-collapsing thinking viewer
14
- - `MarkdownRenderer.svelte` - Markdown parser with configurable streaming speed
 
15
 
16
  ## Architecture
17
 
18
- - Segment-based messaging with unique IDs
19
- - StreamingText tracks processed content length to prevent duplication
20
- - Batch character processing (3 chars/cycle) at 120 chars/sec
21
- - Tool invocations displayed through dedicated segments
22
- - Clean separation between text and tool content
23
- - Abortable conversations via stop button during processing
24
- - All GSAP animations properly cleaned up on component destruction
 
 
 
 
 
 
1
  # Chat Context
2
 
3
+ AI chat interface with enhanced feedback and micro-interactions.
4
 
5
  ## Components
6
 
7
+ - `ChatPanel.svelte` - Main container with auth handling
8
+ - `MessageList.svelte` - Message container with typing indicators
9
+ - `Message.svelte` - Message wrapper with entrance animations
10
+ - `MessageContent.svelte` - Content normalizer and router
11
+ - `TextRenderer.svelte` - Text display with streaming cursors
12
+ - `MessageInput.svelte` - Input with enhanced button feedback
13
+ - `ExampleMessages.svelte` - Initial prompt suggestions
14
+ - `segments/ToolBlock.svelte` - Collapsible tool invocation/result
15
+ - `segments/TodoSegment.svelte` - Task list display
16
 
17
  ## Architecture
18
 
19
+ Clean MVC separation:
20
+
21
+ - **Model**: Enhanced chat store with feedback states
22
+ - **View**: Component hierarchy with GSAP animations
23
+ - **Controller**: WebSocket handling with state management
24
+
25
+ ## Design
26
+
27
+ - Consistent styling matches console/header color scheme
28
+ - GSAP micro-interactions provide constant user feedback
29
+ - Typing indicators and status management for engagement
30
+ - Single text renderer with streaming state animations
src/lib/components/chat/segments/TodoSegment.svelte ADDED
@@ -0,0 +1,128 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <script lang="ts">
2
+ import { onMount } from "svelte";
3
+ import gsap from "gsap";
4
+ import type { MessageSegment } from "../../../models/chat-data";
5
+ import type { TodoListView } from "../../../models/segment-view";
6
+ import { parseTodoList } from "../../../models/segment-view";
7
+
8
+ export let segment: MessageSegment;
9
+
10
+ let todoList: TodoListView | null = null;
11
+ let containerEl: HTMLDivElement;
12
+
13
+ $: {
14
+ const content = segment.toolOutput || segment.content;
15
+ todoList = parseTodoList(content);
16
+ }
17
+
18
+ onMount(() => {
19
+ if (containerEl) {
20
+ gsap.from(containerEl, {
21
+ opacity: 0,
22
+ y: 3,
23
+ duration: 0.2,
24
+ ease: "power2.out",
25
+ });
26
+ }
27
+ });
28
+ </script>
29
+
30
+ {#if todoList}
31
+ <div class="todo-segment" bind:this={containerEl}>
32
+ <div class="todo-header">
33
+ <span class="todo-title">Tasks</span>
34
+ <span class="todo-progress">
35
+ {todoList.completedCount}/{todoList.totalCount}
36
+ </span>
37
+ </div>
38
+
39
+ <div class="todo-list">
40
+ {#each todoList.tasks as task}
41
+ <div class="todo-task {task.status}">
42
+ <span class="task-emoji">{task.emoji}</span>
43
+ <span class="task-text">{task.description}</span>
44
+ </div>
45
+ {/each}
46
+ </div>
47
+ </div>
48
+ {:else}
49
+ <pre class="raw-output">{segment.toolOutput || segment.content}</pre>
50
+ {/if}
51
+
52
+ <style>
53
+ .todo-segment {
54
+ background: rgba(255, 255, 255, 0.01);
55
+ border: 1px solid rgba(255, 255, 255, 0.05);
56
+ border-radius: 4px;
57
+ overflow: hidden;
58
+ margin: 0.125rem 0;
59
+ }
60
+
61
+ .todo-header {
62
+ display: flex;
63
+ align-items: center;
64
+ justify-content: space-between;
65
+ padding: 0.375rem 0.5rem;
66
+ background: rgba(255, 255, 255, 0.02);
67
+ border-bottom: 1px solid rgba(255, 255, 255, 0.05);
68
+ }
69
+
70
+ .todo-title {
71
+ font-size: 0.8rem;
72
+ font-weight: 500;
73
+ color: rgba(255, 255, 255, 0.7);
74
+ }
75
+
76
+ .todo-progress {
77
+ font-size: 0.7rem;
78
+ color: rgba(255, 255, 255, 0.4);
79
+ background: rgba(255, 255, 255, 0.05);
80
+ padding: 0.125rem 0.375rem;
81
+ border-radius: 10px;
82
+ }
83
+
84
+ .todo-list {
85
+ padding: 0.375rem 0.5rem;
86
+ }
87
+
88
+ .todo-task {
89
+ display: flex;
90
+ align-items: center;
91
+ gap: 0.375rem;
92
+ padding: 0.25rem 0;
93
+ font-size: 0.75rem;
94
+ color: rgba(255, 255, 255, 0.7);
95
+ }
96
+
97
+ .task-emoji {
98
+ font-size: 0.9rem;
99
+ flex-shrink: 0;
100
+ }
101
+
102
+ .task-text {
103
+ flex: 1;
104
+ }
105
+
106
+ .todo-task.completed .task-text {
107
+ text-decoration: line-through;
108
+ opacity: 0.5;
109
+ }
110
+
111
+ .todo-task.in_progress {
112
+ color: rgba(255, 255, 255, 0.9);
113
+ }
114
+
115
+ .todo-task.in_progress .task-text {
116
+ font-weight: 500;
117
+ }
118
+
119
+ .raw-output {
120
+ margin: 0;
121
+ padding: 0.25rem;
122
+ white-space: pre-wrap;
123
+ word-wrap: break-word;
124
+ font-family: "Monaco", "Menlo", "Consolas", monospace;
125
+ font-size: 0.75rem;
126
+ color: rgba(255, 255, 255, 0.6);
127
+ }
128
+ </style>
src/lib/components/chat/segments/ToolBlock.svelte ADDED
@@ -0,0 +1,236 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <script lang="ts">
2
+ import { slide } from "svelte/transition";
3
+ import type { MessageSegment } from "../../../models/chat-data";
4
+
5
+ export let invocation: MessageSegment | null = null;
6
+ export let result: MessageSegment | null = null;
7
+
8
+ let isExpanded = false;
9
+
10
+ $: segment = result || invocation;
11
+ $: isRunning = segment?.streaming || segment?.toolStatus === "running";
12
+ $: isError = segment?.toolError || segment?.toolStatus === "error";
13
+ $: hasOutput = result?.toolOutput || result?.content;
14
+
15
+ // Auto-expand on error
16
+ $: if (isError && !isExpanded) {
17
+ isExpanded = true;
18
+ }
19
+
20
+ function toggleExpanded() {
21
+ isExpanded = !isExpanded;
22
+ }
23
+
24
+ function getToolName(): string {
25
+ const name = invocation?.toolName || result?.toolName || "Tool";
26
+ return name.replace(/_/g, " ");
27
+ }
28
+
29
+ function getStatusText(): string {
30
+ if (isRunning) return "Running...";
31
+ if (isError) return "Error";
32
+ if (result?.endTime && result?.startTime) {
33
+ const duration = result.endTime - result.startTime;
34
+ if (duration < 1000) return `${duration}ms`;
35
+ return `${(duration / 1000).toFixed(1)}s`;
36
+ }
37
+ return "";
38
+ }
39
+
40
+ function formatArgs(args: any): string {
41
+ if (!args) return "";
42
+ return JSON.stringify(args, null, 2);
43
+ }
44
+ </script>
45
+
46
+ <div class="tool-block" class:error={isError}>
47
+ <button
48
+ class="tool-header"
49
+ on:click={toggleExpanded}
50
+ class:expanded={isExpanded}
51
+ >
52
+ <span class="tool-name">{getToolName()}</span>
53
+
54
+ {#if isRunning}
55
+ <span class="status running">
56
+ <span class="pulse-dot">●</span>
57
+ {getStatusText()}
58
+ </span>
59
+ {:else if isError}
60
+ <span class="status error">❌ {getStatusText()}</span>
61
+ {:else if hasOutput}
62
+ <span class="status completed">{getStatusText()}</span>
63
+ {/if}
64
+
65
+ <span class="expand-icon" class:expanded={isExpanded}>▶</span>
66
+ </button>
67
+
68
+ {#if isExpanded}
69
+ <div class="tool-content" transition:slide={{ duration: 200 }}>
70
+ {#if invocation?.toolArgs}
71
+ <div class="section args">
72
+ <div class="label">Arguments</div>
73
+ <pre>{formatArgs(invocation.toolArgs)}</pre>
74
+ </div>
75
+ {/if}
76
+
77
+ {#if isRunning && result?.content}
78
+ <div class="section output streaming">
79
+ <div class="label">Output</div>
80
+ <pre>{result.content}<span class="cursor">▊</span></pre>
81
+ </div>
82
+ {:else if result?.toolError}
83
+ <div class="section error">
84
+ <div class="label">Error</div>
85
+ <pre>{result.toolError}</pre>
86
+ </div>
87
+ {:else if result?.toolOutput || result?.content}
88
+ <div class="section output">
89
+ <div class="label">Output</div>
90
+ <pre>{result.toolOutput || result.content}</pre>
91
+ </div>
92
+ {/if}
93
+ </div>
94
+ {/if}
95
+ </div>
96
+
97
+ <style>
98
+ .tool-block {
99
+ margin: 0.125rem 0;
100
+ border: 1px solid rgba(255, 255, 255, 0.05);
101
+ border-radius: 4px;
102
+ background: rgba(255, 255, 255, 0.01);
103
+ overflow: hidden;
104
+ }
105
+
106
+ .tool-block.error {
107
+ border-color: rgba(244, 67, 54, 0.2);
108
+ }
109
+
110
+ .tool-header {
111
+ display: flex;
112
+ align-items: center;
113
+ gap: 0.5rem;
114
+ padding: 0.375rem 0.5rem;
115
+ width: 100%;
116
+ background: transparent;
117
+ border: none;
118
+ color: inherit;
119
+ font: inherit;
120
+ text-align: left;
121
+ cursor: pointer;
122
+ transition: background 0.15s ease;
123
+ }
124
+
125
+ .tool-header:hover {
126
+ background: rgba(255, 255, 255, 0.02);
127
+ }
128
+
129
+ .tool-name {
130
+ flex: 1;
131
+ font-size: 0.8rem;
132
+ color: rgba(255, 255, 255, 0.7);
133
+ text-transform: capitalize;
134
+ }
135
+
136
+ .status {
137
+ font-size: 0.75rem;
138
+ padding: 0.125rem 0.375rem;
139
+ border-radius: 3px;
140
+ background: rgba(255, 255, 255, 0.05);
141
+ color: rgba(255, 255, 255, 0.6);
142
+ }
143
+
144
+ .status.running {
145
+ background: rgba(33, 150, 243, 0.1);
146
+ color: rgba(33, 150, 243, 0.9);
147
+ }
148
+
149
+ .status.error {
150
+ background: rgba(244, 67, 54, 0.1);
151
+ color: rgba(244, 67, 54, 0.9);
152
+ }
153
+
154
+ .status.completed {
155
+ color: rgba(255, 255, 255, 0.5);
156
+ }
157
+
158
+ .pulse-dot {
159
+ display: inline-block;
160
+ animation: pulse 1.5s ease-in-out infinite;
161
+ }
162
+
163
+ @keyframes pulse {
164
+ 0%, 100% {
165
+ opacity: 0.3;
166
+ }
167
+ 50% {
168
+ opacity: 1;
169
+ }
170
+ }
171
+
172
+ .expand-icon {
173
+ font-size: 0.6rem;
174
+ color: rgba(255, 255, 255, 0.3);
175
+ transition: transform 0.15s ease;
176
+ }
177
+
178
+ .expand-icon.expanded {
179
+ transform: rotate(90deg);
180
+ }
181
+
182
+ .tool-content {
183
+ border-top: 1px solid rgba(255, 255, 255, 0.05);
184
+ background: rgba(0, 0, 0, 0.1);
185
+ overflow: hidden;
186
+ }
187
+
188
+ .section {
189
+ padding: 0.5rem;
190
+ }
191
+
192
+ .section + .section {
193
+ border-top: 1px solid rgba(255, 255, 255, 0.03);
194
+ }
195
+
196
+ .label {
197
+ font-size: 0.7rem;
198
+ color: rgba(255, 255, 255, 0.4);
199
+ text-transform: uppercase;
200
+ letter-spacing: 0.05em;
201
+ margin-bottom: 0.25rem;
202
+ }
203
+
204
+ pre {
205
+ margin: 0;
206
+ font-family: "Monaco", "Menlo", "Consolas", monospace;
207
+ font-size: 0.75rem;
208
+ line-height: 1.4;
209
+ color: rgba(255, 255, 255, 0.8);
210
+ white-space: pre-wrap;
211
+ word-wrap: break-word;
212
+ }
213
+
214
+ .section.args pre {
215
+ color: rgba(255, 255, 255, 0.6);
216
+ }
217
+
218
+ .section.error pre {
219
+ color: rgba(244, 67, 54, 0.9);
220
+ }
221
+
222
+ .cursor {
223
+ display: inline-block;
224
+ color: rgba(65, 105, 225, 0.6);
225
+ animation: blink 1s infinite;
226
+ }
227
+
228
+ @keyframes blink {
229
+ 0%, 50% {
230
+ opacity: 1;
231
+ }
232
+ 51%, 100% {
233
+ opacity: 0;
234
+ }
235
+ }
236
+ </style>
src/lib/components/editor/CodeEditor.svelte CHANGED
@@ -1,18 +1,12 @@
1
  <script lang="ts">
2
- import { editorStore } from '../../stores/editor';
3
  import Editor from '../Editor.svelte';
4
-
5
- function handleChange(event: CustomEvent<string>) {
6
- editorStore.setContent(event.detail);
7
- }
8
  </script>
9
 
10
  <div class="editor-panel">
11
  <Editor
12
- value={$editorStore.content}
13
- language={$editorStore.language}
14
- theme={$editorStore.theme}
15
- on:change={handleChange}
16
  />
17
  </div>
18
 
 
1
  <script lang="ts">
2
+ import { contentManager } from '../../services/content-manager';
3
  import Editor from '../Editor.svelte';
 
 
 
 
4
  </script>
5
 
6
  <div class="editor-panel">
7
  <Editor
8
+ language={$contentManager.language}
9
+ theme={$contentManager.theme}
 
 
10
  />
11
  </div>
12
 
src/lib/components/game/GameCanvas.svelte CHANGED
@@ -2,9 +2,8 @@
2
  import { onMount, onDestroy } from 'svelte';
3
  import { uiStore } from '../../stores/ui';
4
  import { gameStore } from '../../stores/game';
5
- import { editorStore } from '../../stores/editor';
6
  import { gameEngine } from '../../services/game-engine';
7
- import { HTMLParser } from '../../services/html-parser';
8
  import GameError from './GameError.svelte';
9
 
10
  let reloadTimer: any;
@@ -12,8 +11,8 @@
12
  let isInitialized = false;
13
  let lastRestartTime = 0;
14
 
15
- $: if ($editorStore.content !== previousContent && $gameStore.isAutoRunning && isInitialized) {
16
- previousContent = $editorStore.content;
17
  clearTimeout(reloadTimer);
18
 
19
  const now = Date.now();
@@ -25,18 +24,17 @@
25
  }
26
  lastRestartTime = currentTime;
27
 
28
- const { world, scripts } = HTMLParser.extractGameContent($editorStore.content);
29
- await gameEngine.start(world, scripts);
30
  }, 1500);
31
  }
32
  }
33
 
34
  onMount(async () => {
35
- previousContent = $editorStore.content;
 
36
  setTimeout(async () => {
37
- if (!$gameStore.instance && $gameStore.isAutoRunning) {
38
- const { world, scripts } = HTMLParser.extractGameContent($editorStore.content);
39
- await gameEngine.start(world, scripts);
40
  }
41
  isInitialized = true;
42
  }, 400);
 
2
  import { onMount, onDestroy } from 'svelte';
3
  import { uiStore } from '../../stores/ui';
4
  import { gameStore } from '../../stores/game';
5
+ import { contentManager } from '../../services/content-manager';
6
  import { gameEngine } from '../../services/game-engine';
 
7
  import GameError from './GameError.svelte';
8
 
9
  let reloadTimer: any;
 
11
  let isInitialized = false;
12
  let lastRestartTime = 0;
13
 
14
+ $: if ($contentManager.content !== previousContent && $gameStore.isAutoRunning && isInitialized) {
15
+ previousContent = $contentManager.content;
16
  clearTimeout(reloadTimer);
17
 
18
  const now = Date.now();
 
24
  }
25
  lastRestartTime = currentTime;
26
 
27
+ await gameEngine.startFromDocument($contentManager.content);
 
28
  }, 1500);
29
  }
30
  }
31
 
32
  onMount(async () => {
33
+ previousContent = $contentManager.content;
34
+
35
  setTimeout(async () => {
36
+ if (!$gameStore.instance && $gameStore.isAutoRunning && $contentManager.content) {
37
+ await gameEngine.startFromDocument($contentManager.content);
 
38
  }
39
  isInitialized = true;
40
  }, 400);
src/lib/components/layout/AppHeader.svelte CHANGED
@@ -3,14 +3,14 @@
3
  import { uiStore } from '../../stores/ui';
4
  import { gameStore } from '../../stores/game';
5
  import { gameEngine } from '../../services/game-engine';
6
- import { editorStore } from '../../stores/editor';
7
- import { HTMLParser } from '../../services/html-parser';
8
  import LoginButton from '../auth/LoginButton.svelte';
9
  import gsap from 'gsap';
10
-
11
  async function restartGame() {
12
- const { world, scripts } = HTMLParser.extractGameContent($editorStore.content);
13
- await gameEngine.start(world, scripts);
 
14
  }
15
 
16
  function handleViewModeChange(mode: 'code' | 'preview') {
 
3
  import { uiStore } from '../../stores/ui';
4
  import { gameStore } from '../../stores/game';
5
  import { gameEngine } from '../../services/game-engine';
6
+ import { contentManager } from '../../services/content-manager';
 
7
  import LoginButton from '../auth/LoginButton.svelte';
8
  import gsap from 'gsap';
9
+
10
  async function restartGame() {
11
+ if ($contentManager.content) {
12
+ await gameEngine.startFromDocument($contentManager.content);
13
+ }
14
  }
15
 
16
  function handleViewModeChange(mode: 'code' | 'preview') {
src/lib/controllers/animation-controller.ts ADDED
@@ -0,0 +1,190 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import gsap from "gsap";
2
+
3
+ type AnimationTarget = HTMLElement | null;
4
+ type AnimationId = string;
5
+
6
+ interface AnimationConfig {
7
+ target: AnimationTarget;
8
+ animation: gsap.core.Tween | gsap.core.Timeline;
9
+ }
10
+
11
+ export class AnimationController {
12
+ private animations = new Map<AnimationId, AnimationConfig>();
13
+
14
+ register(id: AnimationId, target: AnimationTarget): void {
15
+ if (!target) return;
16
+ this.cleanup(id);
17
+ this.animations.set(id, { target, animation: null! });
18
+ }
19
+
20
+ cleanup(id: AnimationId): void {
21
+ const config = this.animations.get(id);
22
+ if (config?.animation) {
23
+ config.animation.kill();
24
+ }
25
+ if (config?.target) {
26
+ gsap.killTweensOf(config.target);
27
+ }
28
+ this.animations.delete(id);
29
+ }
30
+
31
+ cleanupAll(): void {
32
+ this.animations.forEach((_, id) => this.cleanup(id));
33
+ }
34
+
35
+ buttonPress(element: AnimationTarget): void {
36
+ if (!element) return;
37
+ gsap.to(element, {
38
+ scale: 0.9,
39
+ duration: 0.1,
40
+ ease: "power2.in",
41
+ onComplete: () => {
42
+ gsap.to(element, {
43
+ scale: 1,
44
+ duration: 0.2,
45
+ ease: "elastic.out(1, 0.5)",
46
+ });
47
+ },
48
+ });
49
+ }
50
+
51
+ buttonHover(element: AnimationTarget, entering: boolean): void {
52
+ if (!element) return;
53
+ gsap.to(element, {
54
+ scale: entering ? 1.05 : 1,
55
+ duration: 0.2,
56
+ ease: "power2.out",
57
+ });
58
+ }
59
+
60
+ fadeIn(element: AnimationTarget, duration = 0.3): void {
61
+ if (!element) return;
62
+ gsap.fromTo(
63
+ element,
64
+ { opacity: 0, y: 5 },
65
+ { opacity: 1, y: 0, duration, ease: "power2.out" },
66
+ );
67
+ }
68
+
69
+ fadeOut(element: AnimationTarget, duration = 0.2): Promise<void> {
70
+ return new Promise((resolve) => {
71
+ if (!element) {
72
+ resolve();
73
+ return;
74
+ }
75
+ gsap.to(element, {
76
+ opacity: 0,
77
+ duration,
78
+ ease: "power2.in",
79
+ onComplete: resolve,
80
+ });
81
+ });
82
+ }
83
+
84
+ pulseGlow(id: AnimationId, element: AnimationTarget, color: string): void {
85
+ if (!element) return;
86
+ const animation = gsap.to(element, {
87
+ boxShadow: `0 0 25px ${color}`,
88
+ duration: 2.5,
89
+ repeat: -1,
90
+ yoyo: true,
91
+ ease: "sine.inOut",
92
+ });
93
+ this.animations.set(id, { target: element, animation });
94
+ }
95
+
96
+ borderPulse(id: AnimationId, element: AnimationTarget, color: string): void {
97
+ if (!element) return;
98
+ const animation = gsap.to(element, {
99
+ borderColor: color,
100
+ duration: 2,
101
+ repeat: -1,
102
+ yoyo: true,
103
+ ease: "sine.inOut",
104
+ });
105
+ this.animations.set(id, { target: element, animation });
106
+ }
107
+
108
+ smoothScroll(
109
+ element: AnimationTarget,
110
+ targetScroll: number,
111
+ duration?: number,
112
+ ): Promise<void> {
113
+ return new Promise((resolve) => {
114
+ if (!element) {
115
+ resolve();
116
+ return;
117
+ }
118
+
119
+ const currentScroll = element.scrollTop;
120
+ const distance = Math.abs(targetScroll - currentScroll);
121
+ const calculatedDuration = duration ?? Math.min(0.5, distance / 1000);
122
+
123
+ gsap.to(element, {
124
+ scrollTop: targetScroll,
125
+ duration: calculatedDuration,
126
+ ease: "power2.out",
127
+ onComplete: resolve,
128
+ });
129
+ });
130
+ }
131
+
132
+ cursorBlink(id: AnimationId, element: AnimationTarget): void {
133
+ if (!element) return;
134
+ gsap.set(element, { display: "inline-block", opacity: 1 });
135
+ const animation = gsap.to(element, {
136
+ opacity: 0,
137
+ duration: 0.5,
138
+ repeat: -1,
139
+ yoyo: true,
140
+ ease: "steps(1)",
141
+ });
142
+ this.animations.set(id, { target: element, animation });
143
+ }
144
+
145
+ hideCursor(element: AnimationTarget): Promise<void> {
146
+ return new Promise((resolve) => {
147
+ if (!element) {
148
+ resolve();
149
+ return;
150
+ }
151
+ gsap.killTweensOf(element);
152
+ gsap.to(element, {
153
+ opacity: 0,
154
+ duration: 0.2,
155
+ onComplete: () => {
156
+ gsap.set(element, { display: "none" });
157
+ resolve();
158
+ },
159
+ });
160
+ });
161
+ }
162
+
163
+ slideDown(element: AnimationTarget, duration = 0.3): void {
164
+ if (!element) return;
165
+ gsap.from(element, {
166
+ height: 0,
167
+ opacity: 0,
168
+ duration,
169
+ ease: "power2.out",
170
+ });
171
+ }
172
+
173
+ slideUp(element: AnimationTarget, duration = 0.3): Promise<void> {
174
+ return new Promise((resolve) => {
175
+ if (!element) {
176
+ resolve();
177
+ return;
178
+ }
179
+ gsap.to(element, {
180
+ height: 0,
181
+ opacity: 0,
182
+ duration,
183
+ ease: "power2.in",
184
+ onComplete: resolve,
185
+ });
186
+ });
187
+ }
188
+ }
189
+
190
+ export const animationController = new AnimationController();
src/lib/controllers/chat-controller.ts ADDED
@@ -0,0 +1,116 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { get } from "svelte/store";
2
+ import { websocketService } from "../services/websocket";
3
+ import { chatStore } from "../stores/chat-store";
4
+ import { authStore } from "../services/auth";
5
+ import { type ChatMessage } from "../models/chat-data";
6
+
7
+ export class ChatController {
8
+ sendMessage(content: string): void {
9
+ const trimmedContent = content.trim();
10
+ if (!trimmedContent) return;
11
+
12
+ const state = get(chatStore);
13
+ if (!state.connected || state.processing) return;
14
+
15
+ const userMessage: ChatMessage = {
16
+ id: `user_${Date.now()}`,
17
+ role: "user",
18
+ content: trimmedContent,
19
+ timestamp: Date.now(),
20
+ };
21
+
22
+ chatStore.addMessage(userMessage);
23
+ chatStore.setError(null);
24
+
25
+ websocketService.send({
26
+ type: "chat",
27
+ payload: { content: trimmedContent },
28
+ timestamp: Date.now(),
29
+ });
30
+
31
+ chatStore.setProcessing(true);
32
+ }
33
+
34
+ stopConversation(): void {
35
+ const state = get(chatStore);
36
+ if (!state.connected) return;
37
+
38
+ websocketService.send({
39
+ type: "abort",
40
+ payload: {},
41
+ timestamp: Date.now(),
42
+ });
43
+
44
+ chatStore.setProcessing(false);
45
+ }
46
+
47
+ clearConversation(): void {
48
+ const state = get(chatStore);
49
+ if (!state.connected) return;
50
+
51
+ websocketService.send({
52
+ type: "clear_conversation",
53
+ payload: {},
54
+ timestamp: Date.now(),
55
+ });
56
+
57
+ chatStore.clearMessages();
58
+ }
59
+
60
+ handleConnectionChange(connected: boolean): void {
61
+ chatStore.setConnected(connected);
62
+
63
+ if (connected) {
64
+ this.authenticate();
65
+ this.syncEditor();
66
+ }
67
+ }
68
+
69
+ handleProcessingChange(processing: boolean): void {
70
+ chatStore.setProcessing(processing);
71
+ }
72
+
73
+ handleError(error: string | null): void {
74
+ chatStore.setError(error);
75
+ chatStore.setProcessing(false);
76
+ }
77
+
78
+ private authenticate(): void {
79
+ const token = authStore.getToken();
80
+ if (token) {
81
+ websocketService.send({
82
+ type: "auth",
83
+ payload: { token },
84
+ timestamp: Date.now(),
85
+ });
86
+ } else {
87
+ const authState = get(authStore);
88
+ if (!authState.isAuthenticated && !authState.loading) {
89
+ chatStore.setError(
90
+ "Authentication required. Please sign in with Hugging Face.",
91
+ );
92
+ chatStore.setConnected(false);
93
+ websocketService.disconnect();
94
+ }
95
+ }
96
+ }
97
+
98
+ private syncEditor(): void {
99
+ import("../services/content-manager").then(({ contentManager }) => {
100
+ const currentContent = contentManager.getCurrentContent();
101
+ if (currentContent) {
102
+ setTimeout(() => {
103
+ if (websocketService.isConnected()) {
104
+ websocketService.send({
105
+ type: "editor_sync",
106
+ payload: { content: currentContent },
107
+ timestamp: Date.now(),
108
+ });
109
+ }
110
+ }, 500);
111
+ }
112
+ });
113
+ }
114
+ }
115
+
116
+ export const chatController = new ChatController();
src/lib/controllers/context.md ADDED
@@ -0,0 +1,34 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Controllers
2
+
3
+ Business logic and orchestration layer
4
+
5
+ ## Purpose
6
+
7
+ - Handle all business logic and state mutations
8
+ - Coordinate between services and stores
9
+ - Centralize complex operations
10
+
11
+ ## Layout
12
+
13
+ ```
14
+ controllers/
15
+ ├── context.md # This file
16
+ ├── chat-controller.ts # Chat operations and state management
17
+ └── animation-controller.ts # Centralized GSAP animations
18
+ ```
19
+
20
+ ## Scope
21
+
22
+ - In-scope: Business logic, state orchestration, animations
23
+ - Out-of-scope: UI rendering, direct store access from components
24
+
25
+ ## Entrypoints
26
+
27
+ - `chatController` - All chat operations
28
+ - `animationController` - UI animations
29
+
30
+ ## Dependencies
31
+
32
+ - Stores for state updates
33
+ - Services for external operations
34
+ - GSAP for animations
src/lib/models/chat-data.ts ADDED
@@ -0,0 +1,89 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ export type MessageRole = "user" | "assistant" | "system";
2
+
3
+ export type MessageSegmentType =
4
+ | "text"
5
+ | "tool-invocation"
6
+ | "tool-result"
7
+ | "reasoning";
8
+
9
+ export type ToolStatus = "pending" | "running" | "completed" | "error";
10
+
11
+ export type StreamingStatus = "idle" | "thinking" | "streaming" | "completing";
12
+
13
+ export interface MessageSegment {
14
+ readonly id: string;
15
+ readonly type: MessageSegmentType;
16
+ readonly content: string;
17
+ readonly toolName?: string;
18
+ readonly toolArgs?: Record<string, unknown>;
19
+ readonly toolStatus?: ToolStatus;
20
+ readonly toolOutput?: string;
21
+ readonly toolResult?: string;
22
+ readonly toolError?: string;
23
+ readonly startTime?: number;
24
+ readonly endTime?: number;
25
+ readonly consoleOutput?: string[];
26
+ readonly expanded?: boolean;
27
+ readonly streaming?: boolean;
28
+ }
29
+
30
+ export interface ChatMessage {
31
+ readonly id: string;
32
+ readonly role: MessageRole;
33
+ readonly content: string;
34
+ readonly timestamp: number;
35
+ readonly streaming?: boolean;
36
+ readonly reasoning?: string;
37
+ readonly showReasoning?: boolean;
38
+ readonly segments?: ReadonlyArray<MessageSegment>;
39
+ }
40
+
41
+ export interface ChatState {
42
+ readonly messages: ReadonlyArray<ChatMessage>;
43
+ readonly connected: boolean;
44
+ readonly processing: boolean;
45
+ readonly error: string | null;
46
+ readonly streamingContent: string;
47
+ readonly streamingStatus: StreamingStatus;
48
+ readonly thinkingStartTime: number | null;
49
+ }
50
+
51
+ export function createMessage(
52
+ role: MessageRole,
53
+ content: string,
54
+ id?: string,
55
+ ): ChatMessage {
56
+ return {
57
+ id: id || `${role}_${Date.now()}`,
58
+ role,
59
+ content,
60
+ timestamp: Date.now(),
61
+ streaming: false,
62
+ };
63
+ }
64
+
65
+ export function createSegment(
66
+ type: MessageSegmentType,
67
+ content: string = "",
68
+ id?: string,
69
+ ): MessageSegment {
70
+ return {
71
+ id: id || `seg_${Date.now()}`,
72
+ type,
73
+ content,
74
+ streaming: type === "text",
75
+ startTime: Date.now(),
76
+ };
77
+ }
78
+
79
+ export function createInitialChatState(): ChatState {
80
+ return {
81
+ messages: [],
82
+ connected: false,
83
+ processing: false,
84
+ error: null,
85
+ streamingContent: "",
86
+ streamingStatus: "idle",
87
+ thinkingStartTime: null,
88
+ };
89
+ }
src/lib/models/context.md ADDED
@@ -0,0 +1,28 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Models
2
+
3
+ Pure data structures and type definitions
4
+
5
+ ## Purpose
6
+
7
+ Define data shapes, types, and factory functions for type safety
8
+
9
+ ## Layout
10
+
11
+ ```
12
+ models/
13
+ ├── context.md # This file
14
+ └── chat-data.ts # Chat types and factories
15
+ ```
16
+
17
+ ## Scope
18
+
19
+ - In-scope: Type definitions, interfaces, factory functions
20
+ - Out-of-scope: Business logic, state management
21
+
22
+ ## Entrypoints
23
+
24
+ Type exports and factory functions for data creation
25
+
26
+ ## Dependencies
27
+
28
+ Pure TypeScript types only
src/lib/models/segment-view.ts ADDED
@@ -0,0 +1,188 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import type { MessageSegment } from "./chat-data";
2
+
3
+ export interface SegmentView {
4
+ readonly segment: MessageSegment;
5
+ readonly isExpanded: boolean;
6
+ readonly displayTitle: string;
7
+ readonly displayStatus: string;
8
+ readonly statusColor: string;
9
+ readonly canExpand: boolean;
10
+ readonly formattedContent?: string;
11
+ readonly metadata?: SegmentMetadata;
12
+ }
13
+
14
+ export interface SegmentMetadata {
15
+ readonly toolType?: "todo" | "console" | "file" | "default";
16
+ readonly fileName?: string;
17
+ readonly lineCount?: number;
18
+ readonly errorMessage?: string;
19
+ readonly duration?: number;
20
+ }
21
+
22
+ export interface TodoListView {
23
+ readonly tasks: TodoTask[];
24
+ readonly completedCount: number;
25
+ readonly totalCount: number;
26
+ readonly lastUpdated: number;
27
+ }
28
+
29
+ export interface TodoTask {
30
+ readonly id: number;
31
+ readonly description: string;
32
+ readonly status: "pending" | "in_progress" | "completed";
33
+ readonly emoji: string;
34
+ }
35
+
36
+ export function createSegmentView(
37
+ segment: MessageSegment,
38
+ isExpanded: boolean = false,
39
+ ): SegmentView {
40
+ const statusColors: Record<string, string> = {
41
+ pending: "rgba(255, 193, 7, 0.8)",
42
+ running: "rgba(33, 150, 243, 0.8)",
43
+ completed: "rgba(76, 175, 80, 0.8)",
44
+ error: "rgba(244, 67, 54, 0.8)",
45
+ };
46
+
47
+ const displayTitle = getSegmentTitle(segment);
48
+ const displayStatus = getSegmentStatus(segment);
49
+ const statusColor =
50
+ statusColors[segment.toolStatus || "pending"] || "rgba(156, 163, 175, 0.8)";
51
+ const canExpand =
52
+ segment.type === "tool-invocation" || segment.type === "tool-result";
53
+
54
+ return {
55
+ segment,
56
+ isExpanded,
57
+ displayTitle,
58
+ displayStatus,
59
+ statusColor,
60
+ canExpand,
61
+ metadata: extractMetadata(segment),
62
+ };
63
+ }
64
+
65
+ function getSegmentTitle(segment: MessageSegment): string {
66
+ if (segment.type === "text") {
67
+ return "Text";
68
+ }
69
+
70
+ if (segment.type === "reasoning") {
71
+ return "Reasoning";
72
+ }
73
+
74
+ if (segment.toolName) {
75
+ const toolNames: Record<string, string> = {
76
+ plan_tasks: "📋 Plan Tasks",
77
+ update_task: "✏️ Update Task",
78
+ view_tasks: "👀 View Tasks",
79
+ observe_console: "📺 Console Output",
80
+ read_file: "📖 Read File",
81
+ write_file: "✍️ Write File",
82
+ edit_file: "✏️ Edit File",
83
+ };
84
+ return toolNames[segment.toolName] || `🔧 ${segment.toolName}`;
85
+ }
86
+
87
+ return "Tool";
88
+ }
89
+
90
+ function getSegmentStatus(segment: MessageSegment): string {
91
+ if (segment.streaming) {
92
+ return "streaming...";
93
+ }
94
+
95
+ if (segment.toolStatus) {
96
+ const statusLabels: Record<string, string> = {
97
+ pending: "Pending",
98
+ running: "Running",
99
+ completed: "Completed",
100
+ error: "Error",
101
+ };
102
+ return statusLabels[segment.toolStatus] || segment.toolStatus;
103
+ }
104
+
105
+ if (segment.endTime && segment.startTime) {
106
+ const duration = segment.endTime - segment.startTime;
107
+ if (duration < 1000) {
108
+ return `${duration}ms`;
109
+ }
110
+ return `${(duration / 1000).toFixed(1)}s`;
111
+ }
112
+
113
+ return "";
114
+ }
115
+
116
+ function extractMetadata(segment: MessageSegment): SegmentMetadata {
117
+ let toolType: SegmentMetadata["toolType"];
118
+ let fileName: string | undefined;
119
+ let errorMessage: string | undefined;
120
+ let duration: number | undefined;
121
+ let lineCount: number | undefined;
122
+
123
+ if (segment.toolName?.includes("task")) {
124
+ toolType = "todo";
125
+ } else if (segment.toolName === "observe_console") {
126
+ toolType = "console";
127
+ } else if (segment.toolName?.includes("file")) {
128
+ toolType = "file";
129
+ if (segment.toolArgs?.path) {
130
+ fileName = segment.toolArgs.path as string;
131
+ }
132
+ } else if (segment.toolName) {
133
+ toolType = "default";
134
+ }
135
+
136
+ if (segment.toolError) {
137
+ errorMessage = segment.toolError;
138
+ }
139
+
140
+ if (segment.endTime && segment.startTime) {
141
+ duration = segment.endTime - segment.startTime;
142
+ }
143
+
144
+ if (segment.toolOutput) {
145
+ lineCount = segment.toolOutput.split("\n").length;
146
+ }
147
+
148
+ return {
149
+ toolType,
150
+ fileName,
151
+ errorMessage,
152
+ duration,
153
+ lineCount,
154
+ };
155
+ }
156
+
157
+ export function parseTodoList(content: string): TodoListView | null {
158
+ const lines = content.split("\n");
159
+ const tasks: TodoTask[] = [];
160
+
161
+ for (const line of lines) {
162
+ const match = line.match(
163
+ /([⏳🔄✅])\s*\[(\d+)\]\s*(.+?)\s*\((pending|in_progress|completed)\)/u,
164
+ );
165
+ if (match) {
166
+ const [, emoji, id, description, status] = match;
167
+ tasks.push({
168
+ id: parseInt(id, 10),
169
+ description: description.trim(),
170
+ status: status as TodoTask["status"],
171
+ emoji,
172
+ });
173
+ }
174
+ }
175
+
176
+ if (tasks.length === 0) {
177
+ return null;
178
+ }
179
+
180
+ const completedCount = tasks.filter((t) => t.status === "completed").length;
181
+
182
+ return {
183
+ tasks,
184
+ completedCount,
185
+ totalCount: tasks.length,
186
+ lastUpdated: Date.now(),
187
+ };
188
+ }
src/lib/server/api.ts CHANGED
@@ -2,8 +2,9 @@ import type { IncomingMessage } from "http";
2
  import type { WebSocket } from "ws";
3
  import { LangGraphAgent } from "./langgraph-agent";
4
  import { HumanMessage, AIMessage, BaseMessage } from "@langchain/core/messages";
5
- import { updateEditorContent } from "./tools";
6
  import { consoleBuffer } from "./console-buffer";
 
 
7
 
8
  export interface WebSocketMessage {
9
  type:
@@ -19,7 +20,8 @@ export interface WebSocketMessage {
19
  | "editor_sync"
20
  | "tool_execution"
21
  | "console_sync"
22
- | "abort";
 
23
  payload: {
24
  content?: string;
25
  role?: string;
@@ -69,7 +71,8 @@ class WebSocketManager {
69
  }
70
  });
71
 
72
- ws.on("close", () => {
 
73
  this.connections.delete(ws);
74
  });
75
 
@@ -94,7 +97,10 @@ class WebSocketManager {
94
 
95
  this.sendMessage(ws, {
96
  type: "status",
97
- payload: { message: "Authenticated successfully" },
 
 
 
98
  timestamp: Date.now(),
99
  });
100
  } catch (error) {
@@ -109,12 +115,6 @@ class WebSocketManager {
109
  }
110
  break;
111
 
112
- case "editor_sync":
113
- if (message.payload.content) {
114
- updateEditorContent(message.payload.content);
115
- }
116
- break;
117
-
118
  case "console_sync":
119
  if (
120
  message.payload.id &&
@@ -142,6 +142,26 @@ class WebSocketManager {
142
  }
143
  break;
144
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
145
  case "chat":
146
  try {
147
  if (!connectionData?.agent) {
 
2
  import type { WebSocket } from "ws";
3
  import { LangGraphAgent } from "./langgraph-agent";
4
  import { HumanMessage, AIMessage, BaseMessage } from "@langchain/core/messages";
 
5
  import { consoleBuffer } from "./console-buffer";
6
+ import { mcpClientManager } from "./mcp-client";
7
+ import { virtualFileSystem } from "../services/virtual-fs";
8
 
9
  export interface WebSocketMessage {
10
  type:
 
20
  | "editor_sync"
21
  | "tool_execution"
22
  | "console_sync"
23
+ | "abort"
24
+ | "clear_conversation";
25
  payload: {
26
  content?: string;
27
  role?: string;
 
71
  }
72
  });
73
 
74
+ ws.on("close", async () => {
75
+ await mcpClientManager.cleanup();
76
  this.connections.delete(ws);
77
  });
78
 
 
97
 
98
  this.sendMessage(ws, {
99
  type: "status",
100
+ payload: {
101
+ message: "Authenticated successfully",
102
+ connected: true,
103
+ },
104
  timestamp: Date.now(),
105
  });
106
  } catch (error) {
 
115
  }
116
  break;
117
 
 
 
 
 
 
 
118
  case "console_sync":
119
  if (
120
  message.payload.id &&
 
142
  }
143
  break;
144
 
145
+ case "editor_sync":
146
+ if (message.payload.content) {
147
+ virtualFileSystem.updateGameContent(
148
+ message.payload.content as string,
149
+ );
150
+ }
151
+ break;
152
+
153
+ case "clear_conversation":
154
+ if (connectionData) {
155
+ connectionData.messages = [];
156
+
157
+ this.sendMessage(ws, {
158
+ type: "status",
159
+ payload: { message: "Conversation history cleared" },
160
+ timestamp: Date.now(),
161
+ });
162
+ }
163
+ break;
164
+
165
  case "chat":
166
  try {
167
  if (!connectionData?.agent) {
src/lib/server/console-buffer.ts CHANGED
@@ -55,22 +55,31 @@ export class ConsoleBuffer {
55
  lastError?: string;
56
  isReady: boolean;
57
  } {
58
- const recentMessages = this.getRecentMessages(Date.now() - 3000);
59
 
60
  const hasStartMessage = recentMessages.some(
61
  (msg) =>
62
  msg.message.includes("🎮 Starting game") ||
63
- msg.message.includes("Starting game"),
 
 
64
  );
65
 
66
  const hasSuccessMessage = recentMessages.some(
67
  (msg) =>
68
  msg.message.includes("✅ Game started") ||
69
- msg.message.includes("Game started!"),
 
 
70
  );
71
 
72
  const errorMessages = recentMessages.filter(
73
- (msg) => msg.type === "error" || msg.message.includes("❌ Error"),
 
 
 
 
 
74
  );
75
 
76
  const lastError =
@@ -83,7 +92,7 @@ export class ConsoleBuffer {
83
  hasStartMessage && !hasSuccessMessage && errorMessages.length === 0,
84
  hasError: errorMessages.length > 0,
85
  lastError,
86
- isReady: hasSuccessMessage,
87
  };
88
  }
89
  }
 
55
  lastError?: string;
56
  isReady: boolean;
57
  } {
58
+ const recentMessages = this.getRecentMessages(Date.now() - 5000);
59
 
60
  const hasStartMessage = recentMessages.some(
61
  (msg) =>
62
  msg.message.includes("🎮 Starting game") ||
63
+ msg.message.includes("Starting game") ||
64
+ msg.message.includes("loading") ||
65
+ msg.message.includes("Loading"),
66
  );
67
 
68
  const hasSuccessMessage = recentMessages.some(
69
  (msg) =>
70
  msg.message.includes("✅ Game started") ||
71
+ msg.message.includes("Game started!") ||
72
+ msg.message.includes("Game script loaded!") ||
73
+ msg.message.includes("successfully"),
74
  );
75
 
76
  const errorMessages = recentMessages.filter(
77
+ (msg) =>
78
+ msg.type === "error" ||
79
+ msg.message.includes("❌ Error") ||
80
+ msg.message.includes("Error:") ||
81
+ msg.message.includes("Failed") ||
82
+ msg.message.includes("failed"),
83
  );
84
 
85
  const lastError =
 
92
  hasStartMessage && !hasSuccessMessage && errorMessages.length === 0,
93
  hasError: errorMessages.length > 0,
94
  lastError,
95
+ isReady: hasSuccessMessage && errorMessages.length === 0,
96
  };
97
  }
98
  }
src/lib/server/context.md CHANGED
@@ -4,19 +4,20 @@ WebSocket server with LangGraph agent for AI-assisted game development.
4
 
5
  ## Key Components
6
 
7
- - **api.ts** - WebSocket message routing with abort handling
8
- - **langgraph-agent.ts** - LangGraph agent with task decomposition and token safety
9
- - **tools.ts** - Editor manipulation: read/write, search, incremental editing
10
- - **task-tracker.ts** - Task planning and progress tracking
11
- - **console-buffer.ts** - Console message storage
12
- - **documentation.ts** - VibeGame documentation loader
13
 
14
  ## Architecture
15
 
16
- LangGraph state machine with task-aware execution:
17
 
18
- - Task decomposition for complex operations
19
- - Token limit safety checks on tool arguments
 
 
20
  - Buffered streaming with segment handling
21
  - AbortController for canceling conversations
22
 
@@ -25,8 +26,8 @@ LangGraph state machine with task-aware execution:
25
  - `auth` - HF token authentication
26
  - `chat` - User messages
27
  - `abort` - Stop running conversation
28
- - `stream_start/token/end` - Legacy streaming
29
- - `segment_start/token/end` - Segment streaming
30
- - `editor_sync` - Sync editor content
31
  - `console_sync` - Forward console messages
32
  - `status` - Connection and processing state
 
4
 
5
  ## Key Components
6
 
7
+ - **api.ts** - WebSocket message routing with virtual file sync
8
+ - **langgraph-agent.ts** - LangGraph agent with MCP tools
9
+ - **mcp-client.ts** - MCP tools for virtual file operations and Context7 docs
10
+ - **tools.ts** - Console observation and task tracking
11
+ - **console-buffer.ts** - Console message buffering with game state analysis
 
12
 
13
  ## Architecture
14
 
15
+ LangGraph agent with bidirectional content synchronization:
16
 
17
+ - Virtual file system treats editor as single HTML file
18
+ - MCP tools operate directly on virtual file system
19
+ - Real-time bidirectional sync with ContentManager
20
+ - Context7 integration for external library documentation
21
  - Buffered streaming with segment handling
22
  - AbortController for canceling conversations
23
 
 
26
  - `auth` - HF token authentication
27
  - `chat` - User messages
28
  - `abort` - Stop running conversation
29
+ - `clear_conversation` - Clear conversation history
30
+ - `editor_sync` - Sync editor content to virtual file system
31
+ - `editor_update` - Update ContentManager from agent changes
32
  - `console_sync` - Forward console messages
33
  - `status` - Connection and processing state
src/lib/server/documentation.ts CHANGED
@@ -5,7 +5,7 @@ export class DocumentationService {
5
  private cache: string | null = null;
6
  private readonly docsPath: string;
7
 
8
- constructor(filename: string = "llms.txt") {
9
  this.docsPath = join(process.cwd(), filename);
10
  }
11
 
 
5
  private cache: string | null = null;
6
  private readonly docsPath: string;
7
 
8
+ constructor(filename: string = "agents.md") {
9
  this.docsPath = join(process.cwd(), filename);
10
  }
11
 
src/lib/server/langgraph-agent.ts CHANGED
@@ -6,20 +6,9 @@ import {
6
  BaseMessage,
7
  ToolMessage,
8
  } from "@langchain/core/messages";
9
- import {
10
- readEditorTool,
11
- readEditorLinesTool,
12
- searchEditorTool,
13
- editEditorTool,
14
- writeEditorTool,
15
- observeConsoleTool,
16
- setWebSocketConnection,
17
- } from "./tools";
18
- import {
19
- planTasksTool,
20
- updateTaskTool,
21
- viewTasksTool,
22
- } from "./task-tracker";
23
  import { documentationService } from "./documentation";
24
  import type { WebSocket } from "ws";
25
 
@@ -52,15 +41,11 @@ export class LangGraphAgent {
52
 
53
  if (ws) {
54
  this.ws = ws;
55
- setWebSocketConnection({
56
- send: (message: { type: string; payload: Record<string, unknown> }) => {
57
- if (this.ws && this.ws.readyState === this.ws.OPEN) {
58
- this.ws.send(JSON.stringify(message));
59
- }
60
- },
61
- });
62
  }
63
 
 
 
64
  const docs = await documentationService.load();
65
  this.documentation = docs || "";
66
  }
@@ -309,70 +294,85 @@ export class LangGraphAgent {
309
  }
310
 
311
  private buildSystemPrompt(): string {
312
- return `You are an expert VibeGame developer assistant that MUST use tools to complete tasks.
313
-
314
- CRITICAL INSTRUCTIONS:
315
- - You MUST use tools for ALL tasks. NEVER provide instructions without executing them.
316
- - You MUST respond using the EXACT format: TOOL: tool_name ARGS: {"param": "value"}
317
- - After using a tool, wait for the result before proceeding
318
- - For complex tasks, use plan_tasks FIRST to break down the work
319
- - Keep tool arguments concise - prefer multiple small edits over one large edit
320
-
321
- TASK DECOMPOSITION RULES:
322
- - For any task requiring 3+ changes, use plan_tasks FIRST
323
- - Break large code changes into smaller, focused edits
324
- - Each edit_editor call should modify ONE logical section (max ~20 lines)
325
- - Mark tasks as in_progress when starting, completed when done
326
-
327
- VIBEGAME CONTEXT:
328
- ${this.documentation}
329
 
330
- IMPORTANT:
331
- - The game auto-reloads on every change.
332
- - The GAME import is automatically provided by the framework.
333
- - The player is automatically created at [0, 0, 0] if not specified.
334
 
335
- AVAILABLE TOOLS:
 
 
 
 
336
 
337
- TASK MANAGEMENT:
338
- 1. plan_tasks - Break complex work into steps (USE FIRST for multi-step tasks!)
339
- Example: TOOL: plan_tasks ARGS: {"tasks": ["Find the player object", "Add jump ability", "Test the changes"]}
 
 
340
 
341
- 2. update_task - Mark task progress
342
- Example: TOOL: update_task ARGS: {"taskId": 1, "status": "in_progress"}
 
 
 
343
 
344
- 3. view_tasks - See current task list
345
- Example: TOOL: view_tasks ARGS: {}
 
 
346
 
347
- EDITOR TOOLS:
348
- 4. search_editor - Find text/patterns in code
349
- Example: TOOL: search_editor ARGS: {"query": "dynamic-part"}
350
 
351
- 5. read_editor - Read entire editor content
352
- Example: TOOL: read_editor ARGS: {}
353
 
354
- 6. read_editor_lines - Read specific lines (use after search_editor)
355
- Example: TOOL: read_editor_lines ARGS: {"startLine": 10, "endLine": 20}
 
 
356
 
357
- 7. edit_editor - Replace specific text (KEEP EDITS SMALL - max ~20 lines per call)
358
- Example: TOOL: edit_editor ARGS: {"oldText": "color='red'", "newText": "color='blue'"}
 
359
 
360
- 8. write_editor - Replace entire content (ONLY for new files or complete rewrites)
361
- Example: TOOL: write_editor ARGS: {"content": "<world>...</world>"}
 
 
362
 
363
- 9. observe_console - Check console for errors
364
- Example: TOOL: observe_console ARGS: {}
365
 
366
- WORKFLOW EXAMPLE:
367
- User: "Add jumping to the player"
368
- 1. TOOL: plan_tasks ARGS: {"tasks": ["Search for player code", "Add jump component", "Add jump controls", "Test jumping"]}
369
- 2. TOOL: update_task ARGS: {"taskId": 1, "status": "in_progress"}
370
- 3. TOOL: search_editor ARGS: {"query": "player"}
371
- 4. TOOL: edit_editor ARGS: {"oldText": "...", "newText": "..."}
372
- 5. TOOL: update_task ARGS: {"taskId": 1, "status": "completed"}
373
- (continue with remaining tasks...)
374
 
375
- IMPORTANT: You are an executor. Take action immediately using tools, don't explain what you would do.`;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
376
  }
377
 
378
  private async *streamModelResponse(
@@ -511,19 +511,24 @@ IMPORTANT: You are an executor. Take action immediately using tools, don't expla
511
  const argString = JSON.stringify(call.args);
512
  const estimatedTokens = argString.length / 4;
513
 
514
- if (estimatedTokens > 1000 && (call.name === "edit_editor" || call.name === "write_editor")) {
515
- console.warn(`Warning: Tool ${call.name} arguments are large (${estimatedTokens} estimated tokens)`);
 
 
 
 
 
516
 
517
  if (call.name === "edit_editor" && call.args.oldText) {
518
  const oldText = call.args.oldText as string;
519
 
520
- if (oldText.split('\n').length > 20) {
521
  results.push(
522
  new ToolMessage({
523
- content: `Error: The edit is too large (${oldText.split('\n').length} lines). Please break this into smaller edits of max 20 lines each. Use plan_tasks to organize multiple edits.`,
524
  tool_call_id: segmentId,
525
  name: call.name,
526
- })
527
  );
528
  continue;
529
  }
@@ -560,39 +565,17 @@ IMPORTANT: You are an executor. Take action immediately using tools, don't expla
560
  let result;
561
  let consoleOutput: string[] = [];
562
 
563
- if (call.name === "read_editor") {
564
- result = await readEditorTool.func("");
565
- } else if (call.name === "read_editor_lines") {
566
- result = await readEditorLinesTool.func(
567
- call.args as { startLine: number; endLine?: number },
568
- );
569
- } else if (call.name === "search_editor") {
570
- result = await searchEditorTool.func(
571
- call.args as {
572
- query: string;
573
- mode?: "text" | "regex";
574
- contextLines?: number;
575
- },
576
- );
577
- } else if (call.name === "edit_editor") {
578
- result = await editEditorTool.func(
579
- call.args as { oldText: string; newText: string },
580
- );
581
 
582
- const consoleMatch = result.match(/Console output:\n([\s\S]*?)$/);
583
- if (consoleMatch) {
584
- consoleOutput = consoleMatch[1]
585
- .split("\n")
586
- .filter((line) => line.trim());
587
- }
588
- } else if (call.name === "write_editor") {
589
- result = await writeEditorTool.func(call.args as { content: string });
590
 
591
  const consoleMatch = result.match(/Console output:\n([\s\S]*?)$/);
592
  if (consoleMatch) {
593
  consoleOutput = consoleMatch[1]
594
  .split("\n")
595
- .filter((line) => line.trim());
596
  }
597
  } else if (call.name === "observe_console") {
598
  result = await observeConsoleTool.func("");
@@ -600,7 +583,10 @@ IMPORTANT: You are an executor. Take action immediately using tools, don't expla
600
  result = await planTasksTool.func(call.args as { tasks: string[] });
601
  } else if (call.name === "update_task") {
602
  result = await updateTaskTool.func(
603
- call.args as { taskId: number; status: "pending" | "in_progress" | "completed" }
 
 
 
604
  );
605
  } else if (call.name === "view_tasks") {
606
  result = await viewTasksTool.func({});
 
6
  BaseMessage,
7
  ToolMessage,
8
  } from "@langchain/core/messages";
9
+ import { observeConsoleTool } from "./tools";
10
+ import { mcpClientManager, setMCPWebSocketConnection } from "./mcp-client";
11
+ import { planTasksTool, updateTaskTool, viewTasksTool } from "./task-tracker";
 
 
 
 
 
 
 
 
 
 
 
12
  import { documentationService } from "./documentation";
13
  import type { WebSocket } from "ws";
14
 
 
41
 
42
  if (ws) {
43
  this.ws = ws;
44
+ setMCPWebSocketConnection(ws);
 
 
 
 
 
 
45
  }
46
 
47
+ await mcpClientManager.initialize();
48
+
49
  const docs = await documentationService.load();
50
  this.documentation = docs || "";
51
  }
 
294
  }
295
 
296
  private buildSystemPrompt(): string {
297
+ return `You are an expert VibeGame developer assistant with MCP (Model Context Protocol) tools for direct code manipulation.
298
+
299
+ ## Core Principles
300
+ - ALWAYS use tools to complete tasks - never provide instructions without execution
301
+ - Use the EXACT format: TOOL: tool_name ARGS: {"param": "value"}
302
+ - Wait for tool results before proceeding to next step
303
+ - The game auto-reloads after each editor change
 
 
 
 
 
 
 
 
 
 
304
 
305
+ ## Tool Usage Strategy
 
 
 
306
 
307
+ ### 1. SEARCH FIRST
308
+ Always search before reading or editing to locate relevant code:
309
+ - search_editor: Find functions, components, or patterns by text/regex
310
+ - read_editor_lines: Examine specific sections after search
311
+ - read_editor: Only when you need the complete file context
312
 
313
+ ### 2. EDIT INCREMENTALLY
314
+ Keep edits small and focused:
315
+ - edit_editor: For targeted changes (max ~20 lines)
316
+ - write_editor: Only for complete file rewrites or new files
317
+ - Break large changes into multiple edit_editor calls
318
 
319
+ ### 3. TASK PLANNING
320
+ For complex work (3+ steps), use task management:
321
+ - plan_tasks: Decompose work into clear steps
322
+ - update_task: Track progress (in_progress → completed)
323
+ - view_tasks: Review current task list
324
 
325
+ ### 4. RESEARCH LIBRARIES
326
+ For external libraries/frameworks beyond VibeGame:
327
+ - resolve_library_id: Find Context7-compatible library ID first
328
+ - get_library_docs: Fetch current documentation with targeted search
329
 
330
+ ### 5. VERIFY CHANGES
331
+ - observe_console: Check for errors after edits
332
+ - Monitor game reload status in tool responses
333
 
334
+ ## MCP Tools Reference
 
335
 
336
+ ### Code Analysis
337
+ - search_editor: {"query": "text", "mode": "text|regex", "contextLines": 2}
338
+ - read_editor: {}
339
+ - read_editor_lines: {"startLine": 1, "endLine": 10}
340
 
341
+ ### Code Modification
342
+ - edit_editor: {"oldText": "exact text", "newText": "replacement"}
343
+ - write_editor: {"content": "complete file content"}
344
 
345
+ ### Task Management
346
+ - plan_tasks: {"tasks": ["step 1", "step 2", ...]}
347
+ - update_task: {"taskId": 1, "status": "in_progress|completed"}
348
+ - view_tasks: {}
349
 
 
 
350
 
351
+ ### Documentation & Research
352
+ - resolve_library_id: {"libraryName": "gsap"} - Find Context7-compatible library ID
353
+ - get_library_docs: {"context7CompatibleLibraryID": "/greensock/gsap", "tokens": 5000, "topic": "animations"} - Fetch up-to-date docs
 
 
 
 
 
354
 
355
+ ### Debugging
356
+ - observe_console: {}
357
+
358
+ ## VibeGame Context
359
+ ${this.documentation}
360
+
361
+ ## Example Workflows
362
+
363
+ ### VibeGame Edit:
364
+ User: "Change the ball color to blue"
365
+ 1. TOOL: search_editor ARGS: {"query": "ball"}
366
+ 2. TOOL: read_editor_lines ARGS: {"startLine": 10, "endLine": 15}
367
+ 3. TOOL: edit_editor ARGS: {"oldText": "color=\\"#ff4500\\"", "newText": "color=\\"#0000ff\\""}
368
+ 4. TOOL: observe_console ARGS: {}
369
+
370
+ ### External Library Research:
371
+ User: "How do I create GSAP animations?"
372
+ 1. TOOL: resolve_library_id ARGS: {"libraryName": "gsap"}
373
+ 2. TOOL: get_library_docs ARGS: {"context7CompatibleLibraryID": "/greensock/gsap", "topic": "animations"}
374
+
375
+ Remember: Execute immediately. Don't explain - just do.`;
376
  }
377
 
378
  private async *streamModelResponse(
 
511
  const argString = JSON.stringify(call.args);
512
  const estimatedTokens = argString.length / 4;
513
 
514
+ if (
515
+ estimatedTokens > 1000 &&
516
+ (call.name === "edit_editor" || call.name === "write_editor")
517
+ ) {
518
+ console.warn(
519
+ `Warning: Tool ${call.name} arguments are large (${estimatedTokens} estimated tokens)`,
520
+ );
521
 
522
  if (call.name === "edit_editor" && call.args.oldText) {
523
  const oldText = call.args.oldText as string;
524
 
525
+ if (oldText.split("\n").length > 20) {
526
  results.push(
527
  new ToolMessage({
528
+ content: `Error: The edit is too large (${oldText.split("\n").length} lines). Please break this into smaller edits of max 20 lines each. Use plan_tasks to organize multiple edits.`,
529
  tool_call_id: segmentId,
530
  name: call.name,
531
+ }),
532
  );
533
  continue;
534
  }
 
565
  let result;
566
  let consoleOutput: string[] = [];
567
 
568
+ const mcpTools = mcpClientManager.getTools();
569
+ const tool = mcpTools.find((t) => t.name === call.name);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
570
 
571
+ if (tool) {
572
+ result = await tool.func(call.args);
 
 
 
 
 
 
573
 
574
  const consoleMatch = result.match(/Console output:\n([\s\S]*?)$/);
575
  if (consoleMatch) {
576
  consoleOutput = consoleMatch[1]
577
  .split("\n")
578
+ .filter((line: string) => line.trim());
579
  }
580
  } else if (call.name === "observe_console") {
581
  result = await observeConsoleTool.func("");
 
583
  result = await planTasksTool.func(call.args as { tasks: string[] });
584
  } else if (call.name === "update_task") {
585
  result = await updateTaskTool.func(
586
+ call.args as {
587
+ taskId: number;
588
+ status: "pending" | "in_progress" | "completed";
589
+ },
590
  );
591
  } else if (call.name === "view_tasks") {
592
  result = await viewTasksTool.func({});
src/lib/server/mcp-client.ts ADDED
@@ -0,0 +1,713 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { DynamicStructuredTool } from "@langchain/core/tools";
2
+ import { z } from "zod";
3
+ import type { WebSocket } from "ws";
4
+ import { consoleBuffer } from "./console-buffer";
5
+ import { virtualFileSystem, VirtualFileSystem } from "../services/virtual-fs";
6
+
7
+ interface EditorWebSocketConnection {
8
+ send: (message: {
9
+ type: string;
10
+ payload: Record<string, unknown>;
11
+ timestamp: number;
12
+ }) => void;
13
+ }
14
+
15
+ let wsConnection: EditorWebSocketConnection | null = null;
16
+
17
+ export function setMCPWebSocketConnection(ws: WebSocket) {
18
+ wsConnection = {
19
+ send: (message: {
20
+ type: string;
21
+ payload: Record<string, unknown>;
22
+ timestamp: number;
23
+ }) => {
24
+ if (ws && ws.readyState === ws.OPEN) {
25
+ ws.send(JSON.stringify(message));
26
+ }
27
+ },
28
+ };
29
+ }
30
+
31
+ /**
32
+ * MCPClientManager provides MCP-style tools for editor operations
33
+ * Currently uses local implementation, can be extended to use actual MCP servers
34
+ */
35
+ export class MCPClientManager {
36
+ private tools: DynamicStructuredTool[] = [];
37
+ private initialized = false;
38
+
39
+ async initialize(): Promise<void> {
40
+ if (this.initialized) {
41
+ return;
42
+ }
43
+
44
+ this.tools = this.createEditorTools();
45
+ this.initialized = true;
46
+ }
47
+
48
+ private createEditorTools(): DynamicStructuredTool[] {
49
+ const tools: DynamicStructuredTool[] = [];
50
+
51
+ tools.push(
52
+ new DynamicStructuredTool({
53
+ name: "read_file",
54
+ description:
55
+ "Read a file from the virtual file system - defaults to /game.html",
56
+ schema: z.object({
57
+ path: z
58
+ .string()
59
+ .optional()
60
+ .describe("File path to read (default: /game.html)"),
61
+ }),
62
+ func: async (input: { path?: string }) => {
63
+ const filePath = input.path || VirtualFileSystem.GAME_FILE_PATH;
64
+ const file = virtualFileSystem.readFile(filePath);
65
+
66
+ if (!file) {
67
+ return `Error: File not found: ${filePath}`;
68
+ }
69
+
70
+ return `Current content of ${filePath}:\n${file.content}`;
71
+ },
72
+ }),
73
+ );
74
+
75
+ tools.push(
76
+ new DynamicStructuredTool({
77
+ name: "read_editor",
78
+ description:
79
+ "Read the complete editor content - use for initial exploration or when search returns no results",
80
+ schema: z.object({}),
81
+ func: async () => {
82
+ const file = virtualFileSystem.getGameFile();
83
+ return `Current editor content (html):\n${file.content}`;
84
+ },
85
+ }),
86
+ );
87
+
88
+ tools.push(
89
+ new DynamicStructuredTool({
90
+ name: "read_lines",
91
+ description:
92
+ "Read specific lines from a file - use AFTER search_file to examine found code sections in detail",
93
+ schema: z.object({
94
+ startLine: z
95
+ .number()
96
+ .min(1)
97
+ .describe("The starting line number (1-indexed)"),
98
+ endLine: z
99
+ .number()
100
+ .min(1)
101
+ .optional()
102
+ .describe(
103
+ "The ending line number (inclusive). If not provided, only the start line is returned",
104
+ ),
105
+ path: z
106
+ .string()
107
+ .optional()
108
+ .describe("File path to read from (default: /game.html)"),
109
+ }),
110
+ func: async (input: {
111
+ startLine: number;
112
+ endLine?: number;
113
+ path?: string;
114
+ }) => {
115
+ const result = virtualFileSystem.getLines(
116
+ input.startLine,
117
+ input.endLine,
118
+ );
119
+
120
+ if (result.error) {
121
+ return `Error: ${result.error}`;
122
+ }
123
+
124
+ return result.content;
125
+ },
126
+ }),
127
+ );
128
+
129
+ tools.push(
130
+ new DynamicStructuredTool({
131
+ name: "read_editor_lines",
132
+ description:
133
+ "Read specific lines from the editor - use AFTER search_editor to examine found code sections in detail",
134
+ schema: z.object({
135
+ startLine: z
136
+ .number()
137
+ .min(1)
138
+ .describe("The starting line number (1-indexed)"),
139
+ endLine: z
140
+ .number()
141
+ .min(1)
142
+ .optional()
143
+ .describe(
144
+ "The ending line number (inclusive). If not provided, only the start line is returned",
145
+ ),
146
+ }),
147
+ func: async (input: { startLine: number; endLine?: number }) => {
148
+ const result = virtualFileSystem.getLines(
149
+ input.startLine,
150
+ input.endLine,
151
+ );
152
+
153
+ if (result.error) {
154
+ return `Error: ${result.error}`;
155
+ }
156
+
157
+ return result.content;
158
+ },
159
+ }),
160
+ );
161
+
162
+ tools.push(
163
+ new DynamicStructuredTool({
164
+ name: "search_file",
165
+ description:
166
+ "Search for content in a file and get line numbers - use FIRST to locate specific functions, classes, or components before reading or editing",
167
+ schema: z.object({
168
+ query: z.string().describe("Text or regex pattern to search for"),
169
+ mode: z
170
+ .enum(["text", "regex"])
171
+ .optional()
172
+ .describe(
173
+ "Search mode: 'text' for literal text search, 'regex' for pattern matching (default: text)",
174
+ ),
175
+ path: z
176
+ .string()
177
+ .optional()
178
+ .describe("File path to search in (default: /game.html)"),
179
+ }),
180
+ func: async (input: {
181
+ query: string;
182
+ mode?: "text" | "regex";
183
+ path?: string;
184
+ }) => {
185
+ const mode = input.mode || "text";
186
+ const results = virtualFileSystem.searchContent(input.query, mode);
187
+
188
+ if (results.length === 0) {
189
+ return `No matches found for "${input.query}" in virtual file system`;
190
+ }
191
+
192
+ const totalMatches = results.length;
193
+ const displayMatches = results.slice(0, 10);
194
+
195
+ let output = `Found ${totalMatches} match${totalMatches > 1 ? "es" : ""} for "${input.query}":\n\n`;
196
+
197
+ displayMatches.forEach((match, index) => {
198
+ if (index > 0) output += "\n---\n\n";
199
+ output += `${match.path}:\n`;
200
+ output += match.context.join("\n");
201
+ });
202
+
203
+ if (totalMatches > 10) {
204
+ output += `\n\n(Showing first 10 of ${totalMatches} matches. Use more specific search terms to narrow results)`;
205
+ }
206
+
207
+ return output;
208
+ },
209
+ }),
210
+ );
211
+
212
+ tools.push(
213
+ new DynamicStructuredTool({
214
+ name: "search_editor",
215
+ description:
216
+ "Search for code elements and get line numbers - use FIRST to locate specific functions, classes, or components before reading or editing",
217
+ schema: z.object({
218
+ query: z.string().describe("Text or regex pattern to search for"),
219
+ mode: z
220
+ .enum(["text", "regex"])
221
+ .optional()
222
+ .describe(
223
+ "Search mode: 'text' for literal text search, 'regex' for pattern matching (default: text)",
224
+ ),
225
+ contextLines: z
226
+ .number()
227
+ .min(0)
228
+ .max(5)
229
+ .optional()
230
+ .describe(
231
+ "Number of context lines before/after match (default: 2, max: 5)",
232
+ ),
233
+ }),
234
+ func: async (input: {
235
+ query: string;
236
+ mode?: "text" | "regex";
237
+ contextLines?: number;
238
+ }) => {
239
+ const mode = input.mode || "text";
240
+ const results = virtualFileSystem.searchContent(input.query, mode);
241
+
242
+ if (results.length === 0) {
243
+ return `No matches found for "${input.query}" in editor content`;
244
+ }
245
+
246
+ const totalMatches = results.length;
247
+ const displayMatches = results.slice(0, 10);
248
+
249
+ let output = `Found ${totalMatches} match${totalMatches > 1 ? "es" : ""} for "${input.query}":\n\n`;
250
+
251
+ displayMatches.forEach((match, index) => {
252
+ if (index > 0) output += "\n---\n\n";
253
+ output += match.context.join("\n");
254
+ });
255
+
256
+ if (totalMatches > 10) {
257
+ output += `\n\n(Showing first 10 of ${totalMatches} matches. Use more specific search terms to narrow results)`;
258
+ }
259
+
260
+ return output;
261
+ },
262
+ }),
263
+ );
264
+
265
+ tools.push(
266
+ new DynamicStructuredTool({
267
+ name: "edit_file",
268
+ description:
269
+ "Replace specific text in a file - use for SMALL, targeted changes (max ~20 lines). For large changes, use multiple edit_file calls with plan_tasks",
270
+ schema: z.object({
271
+ oldText: z
272
+ .string()
273
+ .describe(
274
+ "The exact text to find and replace (keep small - max ~20 lines)",
275
+ ),
276
+ newText: z.string().describe("The text to replace it with"),
277
+ path: z
278
+ .string()
279
+ .optional()
280
+ .describe("File path to edit (default: /game.html)"),
281
+ }),
282
+ func: async (input: {
283
+ oldText: string;
284
+ newText: string;
285
+ path?: string;
286
+ }) => {
287
+ const result = virtualFileSystem.editContent(
288
+ input.oldText,
289
+ input.newText,
290
+ );
291
+
292
+ if (!result.success) {
293
+ return `Error: ${result.error}`;
294
+ }
295
+
296
+ const file = virtualFileSystem.getGameFile();
297
+ this.syncEditorContent(file.content);
298
+
299
+ consoleBuffer.clear();
300
+
301
+ const startTime = Date.now();
302
+ const maxWaitTime = 3000;
303
+
304
+ await new Promise((resolve) => setTimeout(resolve, 1000));
305
+
306
+ while (Date.now() - startTime < maxWaitTime) {
307
+ const gameState = consoleBuffer.getGameStateFromMessages();
308
+
309
+ if (gameState.isReady) {
310
+ const messages = consoleBuffer.getRecentMessages();
311
+ consoleBuffer.markAsRead();
312
+ return `Text replaced successfully. Game reloaded without errors.\nRecent console output:\n${messages
313
+ .map((m) => `[${m.type}] ${m.message}`)
314
+ .join("\n")}`;
315
+ }
316
+
317
+ if (gameState.hasError) {
318
+ const messages = consoleBuffer.getRecentMessages();
319
+ consoleBuffer.markAsRead();
320
+ return `Text replaced but game failed to start.\nError: ${gameState.lastError}\nFull console output:\n${messages
321
+ .map((m) => `[${m.type}] ${m.message}`)
322
+ .join("\n")}`;
323
+ }
324
+
325
+ await new Promise((resolve) => setTimeout(resolve, 100));
326
+ }
327
+
328
+ const messages = consoleBuffer.getRecentMessages();
329
+ consoleBuffer.markAsRead();
330
+ return `Text replaced. Game reload status uncertain (timeout).\nConsole output:\n${messages
331
+ .map((m) => `[${m.type}] ${m.message}`)
332
+ .join("\n")}`;
333
+ },
334
+ }),
335
+ );
336
+
337
+ tools.push(
338
+ new DynamicStructuredTool({
339
+ name: "edit_editor",
340
+ description:
341
+ "Replace specific text in the editor - use for SMALL, targeted changes (max ~20 lines). For large changes, use multiple edit_editor calls with plan_tasks",
342
+ schema: z.object({
343
+ oldText: z
344
+ .string()
345
+ .describe(
346
+ "The exact text to find and replace (keep small - max ~20 lines)",
347
+ ),
348
+ newText: z.string().describe("The text to replace it with"),
349
+ }),
350
+ func: async (input: { oldText: string; newText: string }) => {
351
+ const result = virtualFileSystem.editContent(
352
+ input.oldText,
353
+ input.newText,
354
+ );
355
+
356
+ if (!result.success) {
357
+ return `Error: ${result.error}`;
358
+ }
359
+
360
+ const file = virtualFileSystem.getGameFile();
361
+ this.syncEditorContent(file.content);
362
+
363
+ consoleBuffer.clear();
364
+
365
+ const startTime = Date.now();
366
+ const maxWaitTime = 3000;
367
+
368
+ await new Promise((resolve) => setTimeout(resolve, 1000));
369
+
370
+ while (Date.now() - startTime < maxWaitTime) {
371
+ const gameState = consoleBuffer.getGameStateFromMessages();
372
+
373
+ if (gameState.isReady) {
374
+ const messages = consoleBuffer.getRecentMessages();
375
+ consoleBuffer.markAsRead();
376
+ return `Text replaced successfully. Game reloaded without errors.\nRecent console output:\n${messages
377
+ .map((m) => `[${m.type}] ${m.message}`)
378
+ .join("\n")}`;
379
+ }
380
+
381
+ if (gameState.hasError) {
382
+ const messages = consoleBuffer.getRecentMessages();
383
+ consoleBuffer.markAsRead();
384
+ return `Text replaced but game failed to start.\nError: ${gameState.lastError}\nFull console output:\n${messages
385
+ .map((m) => `[${m.type}] ${m.message}`)
386
+ .join("\n")}`;
387
+ }
388
+
389
+ await new Promise((resolve) => setTimeout(resolve, 100));
390
+ }
391
+
392
+ const messages = consoleBuffer.getRecentMessages();
393
+ consoleBuffer.markAsRead();
394
+ return `Text replaced. Game reload status uncertain (timeout).\nConsole output:\n${messages
395
+ .map((m) => `[${m.type}] ${m.message}`)
396
+ .join("\n")}`;
397
+ },
398
+ }),
399
+ );
400
+
401
+ tools.push(
402
+ new DynamicStructuredTool({
403
+ name: "write_file",
404
+ description:
405
+ "Replace entire file content - use ONLY for creating new files or complete rewrites. For modifications, use edit_file with plan_tasks instead",
406
+ schema: z.object({
407
+ content: z.string().describe("The complete file content to write"),
408
+ path: z
409
+ .string()
410
+ .optional()
411
+ .describe("File path to write to (default: /game.html)"),
412
+ }),
413
+ func: async (input: { content: string; path?: string }) => {
414
+ const filePath = input.path || VirtualFileSystem.GAME_FILE_PATH;
415
+
416
+ if (filePath !== VirtualFileSystem.GAME_FILE_PATH) {
417
+ return `Error: Only ${VirtualFileSystem.GAME_FILE_PATH} can be written to`;
418
+ }
419
+
420
+ virtualFileSystem.updateGameContent(input.content);
421
+ this.syncEditorContent(input.content);
422
+
423
+ consoleBuffer.clear();
424
+
425
+ const startTime = Date.now();
426
+ const maxWaitTime = 3000;
427
+
428
+ await new Promise((resolve) => setTimeout(resolve, 1000));
429
+
430
+ while (Date.now() - startTime < maxWaitTime) {
431
+ const gameState = consoleBuffer.getGameStateFromMessages();
432
+
433
+ if (gameState.isReady) {
434
+ const messages = consoleBuffer.getRecentMessages();
435
+ consoleBuffer.markAsRead();
436
+ return `File updated successfully. Game reloaded without errors.\nRecent console output:\n${messages
437
+ .map((m) => `[${m.type}] ${m.message}`)
438
+ .join("\n")}`;
439
+ }
440
+
441
+ if (gameState.hasError) {
442
+ const messages = consoleBuffer.getRecentMessages();
443
+ consoleBuffer.markAsRead();
444
+ return `File updated but game failed to start.\nError: ${gameState.lastError}\nFull console output:\n${messages
445
+ .map((m) => `[${m.type}] ${m.message}`)
446
+ .join("\n")}`;
447
+ }
448
+
449
+ await new Promise((resolve) => setTimeout(resolve, 100));
450
+ }
451
+
452
+ const messages = consoleBuffer.getRecentMessages();
453
+ consoleBuffer.markAsRead();
454
+ return `File updated. Game reload status uncertain (timeout).\nConsole output:\n${messages
455
+ .map((m) => `[${m.type}] ${m.message}`)
456
+ .join("\n")}`;
457
+ },
458
+ }),
459
+ );
460
+
461
+ tools.push(
462
+ new DynamicStructuredTool({
463
+ name: "write_editor",
464
+ description:
465
+ "Replace entire editor content - use ONLY for creating new files or complete rewrites. For modifications, use edit_editor with plan_tasks instead",
466
+ schema: z.object({
467
+ content: z
468
+ .string()
469
+ .describe("The complete code content to write to the editor"),
470
+ }),
471
+ func: async (input: { content: string }) => {
472
+ virtualFileSystem.updateGameContent(input.content);
473
+ this.syncEditorContent(input.content);
474
+
475
+ consoleBuffer.clear();
476
+
477
+ const startTime = Date.now();
478
+ const maxWaitTime = 3000;
479
+
480
+ await new Promise((resolve) => setTimeout(resolve, 1000));
481
+
482
+ while (Date.now() - startTime < maxWaitTime) {
483
+ const gameState = consoleBuffer.getGameStateFromMessages();
484
+
485
+ if (gameState.isReady) {
486
+ const messages = consoleBuffer.getRecentMessages();
487
+ consoleBuffer.markAsRead();
488
+ return `Code updated successfully. Game reloaded without errors.\nRecent console output:\n${messages
489
+ .map((m) => `[${m.type}] ${m.message}`)
490
+ .join("\n")}`;
491
+ }
492
+
493
+ if (gameState.hasError) {
494
+ const messages = consoleBuffer.getRecentMessages();
495
+ consoleBuffer.markAsRead();
496
+ return `Code updated but game failed to start.\nError: ${gameState.lastError}\nFull console output:\n${messages
497
+ .map((m) => `[${m.type}] ${m.message}`)
498
+ .join("\n")}`;
499
+ }
500
+
501
+ await new Promise((resolve) => setTimeout(resolve, 100));
502
+ }
503
+
504
+ const messages = consoleBuffer.getRecentMessages();
505
+ consoleBuffer.markAsRead();
506
+ return `Code updated. Game reload status uncertain (timeout).\nConsole output:\n${messages
507
+ .map((m) => `[${m.type}] ${m.message}`)
508
+ .join("\n")}`;
509
+ },
510
+ }),
511
+ );
512
+
513
+ tools.push(...this.createContext7Tools());
514
+
515
+ return tools;
516
+ }
517
+
518
+ private createContext7Tools(): DynamicStructuredTool[] {
519
+ const tools: DynamicStructuredTool[] = [];
520
+ const apiKey = process.env.CONTEXT7_API_KEY;
521
+
522
+ if (!apiKey) {
523
+ console.warn(
524
+ "CONTEXT7_API_KEY not set, Context7 tools will not be available",
525
+ );
526
+ return tools;
527
+ }
528
+
529
+ tools.push(
530
+ new DynamicStructuredTool({
531
+ name: "resolve_library_id",
532
+ description: "Resolve a library name to Context7-compatible library ID",
533
+ schema: z.object({
534
+ libraryName: z
535
+ .string()
536
+ .describe("The name of the library to resolve"),
537
+ }),
538
+ func: async (input: { libraryName: string }) => {
539
+ try {
540
+ const response = await globalThis.fetch(
541
+ "https://mcp.context7.com/mcp",
542
+ {
543
+ method: "POST",
544
+ headers: {
545
+ "Content-Type": "application/json",
546
+ Accept: "application/json, text/event-stream",
547
+ CONTEXT7_API_KEY: apiKey,
548
+ },
549
+ body: JSON.stringify({
550
+ jsonrpc: "2.0",
551
+ id: Math.floor(Math.random() * 10000),
552
+ method: "tools/call",
553
+ params: {
554
+ name: "resolve-library-id",
555
+ arguments: input,
556
+ },
557
+ }),
558
+ },
559
+ );
560
+
561
+ if (!response.ok) {
562
+ throw new Error(
563
+ `HTTP ${response.status}: ${response.statusText}`,
564
+ );
565
+ }
566
+
567
+ const text = await response.text();
568
+
569
+ const lines = text.split("\n");
570
+ let jsonData = null;
571
+
572
+ for (const line of lines) {
573
+ if (line.startsWith("data: ")) {
574
+ try {
575
+ jsonData = JSON.parse(line.substring(6));
576
+ break;
577
+ } catch {
578
+ // Continue looking for valid JSON
579
+ }
580
+ }
581
+ }
582
+
583
+ if (!jsonData) {
584
+ throw new Error("No valid JSON data found in response");
585
+ }
586
+
587
+ if (jsonData.error) {
588
+ throw new Error(
589
+ jsonData.error.message || JSON.stringify(jsonData.error),
590
+ );
591
+ }
592
+
593
+ return JSON.stringify(jsonData.result, null, 2);
594
+ } catch (error) {
595
+ return `Error resolving library ID for "${input.libraryName}": ${error instanceof Error ? error.message : String(error)}`;
596
+ }
597
+ },
598
+ }),
599
+ );
600
+
601
+ tools.push(
602
+ new DynamicStructuredTool({
603
+ name: "get_library_docs",
604
+ description:
605
+ "Fetch up-to-date documentation for a Context7-compatible library",
606
+ schema: z.object({
607
+ context7CompatibleLibraryID: z
608
+ .string()
609
+ .describe("The Context7 library ID (e.g., '/greensock/gsap')"),
610
+ tokens: z
611
+ .number()
612
+ .optional()
613
+ .describe("Maximum tokens to retrieve (default: 5000)"),
614
+ topic: z
615
+ .string()
616
+ .optional()
617
+ .describe("Specific topic to focus on (e.g., 'animations')"),
618
+ }),
619
+ func: async (input: {
620
+ context7CompatibleLibraryID: string;
621
+ tokens?: number;
622
+ topic?: string;
623
+ }) => {
624
+ try {
625
+ const response = await globalThis.fetch(
626
+ "https://mcp.context7.com/mcp",
627
+ {
628
+ method: "POST",
629
+ headers: {
630
+ "Content-Type": "application/json",
631
+ Accept: "application/json, text/event-stream",
632
+ CONTEXT7_API_KEY: apiKey,
633
+ },
634
+ body: JSON.stringify({
635
+ jsonrpc: "2.0",
636
+ id: Math.floor(Math.random() * 10000),
637
+ method: "tools/call",
638
+ params: {
639
+ name: "get-library-docs",
640
+ arguments: {
641
+ context7CompatibleLibraryID:
642
+ input.context7CompatibleLibraryID,
643
+ tokens: input.tokens || 5000,
644
+ topic: input.topic,
645
+ },
646
+ },
647
+ }),
648
+ },
649
+ );
650
+
651
+ if (!response.ok) {
652
+ throw new Error(
653
+ `HTTP ${response.status}: ${response.statusText}`,
654
+ );
655
+ }
656
+
657
+ const text = await response.text();
658
+
659
+ const lines = text.split("\n");
660
+ let jsonData = null;
661
+
662
+ for (const line of lines) {
663
+ if (line.startsWith("data: ")) {
664
+ try {
665
+ jsonData = JSON.parse(line.substring(6));
666
+ break;
667
+ } catch {
668
+ // Continue looking for valid JSON
669
+ }
670
+ }
671
+ }
672
+
673
+ if (!jsonData) {
674
+ throw new Error("No valid JSON data found in response");
675
+ }
676
+
677
+ if (jsonData.error) {
678
+ throw new Error(
679
+ jsonData.error.message || JSON.stringify(jsonData.error),
680
+ );
681
+ }
682
+
683
+ return JSON.stringify(jsonData.result, null, 2);
684
+ } catch (error) {
685
+ return `Error fetching docs for "${input.context7CompatibleLibraryID}": ${error instanceof Error ? error.message : String(error)}`;
686
+ }
687
+ },
688
+ }),
689
+ );
690
+
691
+ return tools;
692
+ }
693
+
694
+ private syncEditorContent(content: string): void {
695
+ if (wsConnection) {
696
+ wsConnection.send({
697
+ type: "editor_update",
698
+ payload: { content },
699
+ timestamp: Date.now(),
700
+ });
701
+ }
702
+ }
703
+
704
+ getTools(): DynamicStructuredTool[] {
705
+ return this.tools;
706
+ }
707
+
708
+ async cleanup(): Promise<void> {
709
+ this.initialized = false;
710
+ }
711
+ }
712
+
713
+ export const mcpClientManager = new MCPClientManager();
src/lib/server/task-tracker.ts CHANGED
@@ -133,4 +133,4 @@ export const viewTasksTool = new DynamicStructuredTool({
133
  },
134
  });
135
 
136
- export const taskTrackerTools = [planTasksTool, updateTaskTool, viewTasksTool];
 
133
  },
134
  });
135
 
136
+ export const taskTrackerTools = [planTasksTool, updateTaskTool, viewTasksTool];
src/lib/server/tools.ts CHANGED
@@ -1,312 +1,44 @@
1
- import { DynamicStructuredTool, DynamicTool } from "@langchain/core/tools";
2
- import { z } from "zod";
3
  import { consoleBuffer } from "./console-buffer";
4
 
5
- let currentEditorContent = `<canvas id="game-canvas"></canvas>
6
-
7
- <world canvas="#game-canvas" sky="#87ceeb">
8
- <!-- Ground -->
9
- <static-part pos="0 -0.5 0" shape="box" size="20 1 20" color="#90ee90"></static-part>
10
-
11
- <!-- Ball -->
12
- <dynamic-part pos="-2 4 -3" shape="sphere" size="1" color="#ff4500"></dynamic-part>
13
- </world>
14
-
15
- <script type="module">
16
- import * as GAME from 'vibegame';
17
-
18
- GAME.run();
19
- </script>`;
20
-
21
- interface WebSocketConnection {
22
- send: (message: { type: string; payload: Record<string, unknown> }) => void;
23
- }
24
-
25
- let wsConnection: WebSocketConnection | null = null;
26
-
27
- export function setWebSocketConnection(ws: WebSocketConnection) {
28
- wsConnection = ws;
29
- }
30
-
31
- export function updateEditorContent(content: string) {
32
- currentEditorContent = content;
33
- }
34
-
35
- export const readEditorTool = new DynamicTool({
36
- name: "read_editor",
37
- description:
38
- "Read the complete editor content - use for initial exploration or when search returns no results",
39
- func: async () => {
40
- return `Current editor content (html):\n${currentEditorContent}`;
41
- },
42
- });
43
-
44
- export const readEditorLinesTool = new DynamicStructuredTool({
45
- name: "read_editor_lines",
46
- description:
47
- "Read specific lines from the editor - use AFTER search_editor to examine found code sections in detail",
48
- schema: z.object({
49
- startLine: z
50
- .number()
51
- .min(1)
52
- .describe("The starting line number (1-indexed)"),
53
- endLine: z
54
- .number()
55
- .min(1)
56
- .optional()
57
- .describe(
58
- "The ending line number (inclusive). If not provided, only the start line is returned",
59
- ),
60
- }),
61
- func: async (input: { startLine: number; endLine?: number }) => {
62
- const lines = currentEditorContent.split("\n");
63
- const totalLines = lines.length;
64
-
65
- if (input.startLine > totalLines) {
66
- return `Error: Start line ${input.startLine} exceeds total lines (${totalLines})`;
67
- }
68
-
69
- const endLine = input.endLine || input.startLine;
70
- if (endLine > totalLines) {
71
- return `Error: End line ${endLine} exceeds total lines (${totalLines})`;
72
- }
73
-
74
- if (input.startLine > endLine) {
75
- return `Error: Start line (${input.startLine}) cannot be greater than end line (${endLine})`;
76
- }
77
-
78
- const selectedLines = lines.slice(input.startLine - 1, endLine);
79
- const lineNumbers: number[] = [];
80
- for (let i = input.startLine; i <= endLine; i++) {
81
- lineNumbers.push(i);
82
- }
83
-
84
- const result = selectedLines
85
- .map((line, index) => `${lineNumbers[index]}: ${line}`)
86
- .join("\n");
87
-
88
- return `Lines ${input.startLine}-${endLine} of ${totalLines}:\n${result}`;
89
- },
90
- });
91
-
92
- export const editEditorTool = new DynamicStructuredTool({
93
- name: "edit_editor",
94
- description:
95
- "Replace specific text in the editor - use for SMALL, targeted changes (max ~20 lines). For large changes, use multiple edit_editor calls with plan_tasks",
96
- schema: z.object({
97
- oldText: z.string().describe("The exact text to find and replace (keep small - max ~20 lines)"),
98
- newText: z.string().describe("The text to replace it with"),
99
- }),
100
- func: async (input: { oldText: string; newText: string }) => {
101
- if (!currentEditorContent.includes(input.oldText)) {
102
- return `Error: Could not find the specified text to replace. Make sure the oldText matches exactly, including whitespace.`;
103
- }
104
-
105
- const occurrences = currentEditorContent.split(input.oldText).length - 1;
106
- if (occurrences > 1) {
107
- return `Warning: Found ${occurrences} occurrences of the text. Use write_editor for multiple replacements or be more specific.`;
108
- }
109
-
110
- const newContent = currentEditorContent.replace(
111
- input.oldText,
112
- input.newText,
113
- );
114
- currentEditorContent = newContent;
115
-
116
- consoleBuffer.clear();
117
-
118
- if (wsConnection) {
119
- wsConnection.send({
120
- type: "editor_update",
121
- payload: { content: newContent },
122
- });
123
- }
124
-
125
- const startTime = Date.now();
126
- const maxWaitTime = 3000;
127
-
128
- await new Promise((resolve) => setTimeout(resolve, 1000));
129
-
130
- while (Date.now() - startTime < maxWaitTime) {
131
- const gameState = consoleBuffer.getGameStateFromMessages();
132
-
133
- if (gameState.isReady) {
134
- const messages = consoleBuffer.getRecentMessages();
135
- consoleBuffer.markAsRead();
136
- return `Text replaced successfully. Game reloaded without errors.\nRecent console output:\n${messages.map((m) => `[${m.type}] ${m.message}`).join("\n")}`;
137
- }
138
-
139
- if (gameState.hasError) {
140
- const messages = consoleBuffer.getRecentMessages();
141
- consoleBuffer.markAsRead();
142
- return `Text replaced but game failed to start.\nError: ${gameState.lastError}\nFull console output:\n${messages.map((m) => `[${m.type}] ${m.message}`).join("\n")}`;
143
- }
144
-
145
- await new Promise((resolve) => setTimeout(resolve, 100));
146
- }
147
-
148
- const messages = consoleBuffer.getRecentMessages();
149
- consoleBuffer.markAsRead();
150
- return `Text replaced. Game reload status uncertain (timeout).\nConsole output:\n${messages.map((m) => `[${m.type}] ${m.message}`).join("\n")}`;
151
- },
152
- });
153
-
154
- export const writeEditorTool = new DynamicStructuredTool({
155
- name: "write_editor",
156
- description:
157
- "Replace entire editor content - use ONLY for creating new files or complete rewrites. For modifications, use edit_editor with plan_tasks instead",
158
- schema: z.object({
159
- content: z.string().describe("The complete code content to write to the editor"),
160
- }),
161
- func: async (input: { content: string }) => {
162
- currentEditorContent = input.content;
163
-
164
- consoleBuffer.clear();
165
-
166
- if (wsConnection) {
167
- wsConnection.send({
168
- type: "editor_update",
169
- payload: { content: input.content },
170
- });
171
- }
172
-
173
- const startTime = Date.now();
174
- const maxWaitTime = 3000;
175
-
176
- await new Promise((resolve) => setTimeout(resolve, 1000));
177
-
178
- while (Date.now() - startTime < maxWaitTime) {
179
- const gameState = consoleBuffer.getGameStateFromMessages();
180
-
181
- if (gameState.isReady) {
182
- const messages = consoleBuffer.getRecentMessages();
183
- consoleBuffer.markAsRead();
184
- return `Code updated successfully. Game reloaded without errors.\nRecent console output:\n${messages.map((m) => `[${m.type}] ${m.message}`).join("\n")}`;
185
- }
186
-
187
- if (gameState.hasError) {
188
- const messages = consoleBuffer.getRecentMessages();
189
- consoleBuffer.markAsRead();
190
- return `Code updated but game failed to start.\nError: ${gameState.lastError}\nFull console output:\n${messages.map((m) => `[${m.type}] ${m.message}`).join("\n")}`;
191
- }
192
-
193
- await new Promise((resolve) => setTimeout(resolve, 100));
194
- }
195
-
196
- const messages = consoleBuffer.getRecentMessages();
197
- consoleBuffer.markAsRead();
198
- return `Code updated. Game reload status uncertain (timeout).\nConsole output:\n${messages.map((m) => `[${m.type}] ${m.message}`).join("\n")}`;
199
- },
200
- });
201
-
202
- export const searchEditorTool = new DynamicStructuredTool({
203
- name: "search_editor",
204
- description:
205
- "Search for code elements and get line numbers - use FIRST to locate specific functions, classes, or components before reading or editing",
206
- schema: z.object({
207
- query: z.string().describe("Text or regex pattern to search for"),
208
- mode: z
209
- .enum(["text", "regex"])
210
- .optional()
211
- .describe(
212
- "Search mode: 'text' for literal text search, 'regex' for pattern matching (default: text)",
213
- ),
214
- contextLines: z
215
- .number()
216
- .min(0)
217
- .max(5)
218
- .optional()
219
- .describe(
220
- "Number of context lines before/after match (default: 2, max: 5)",
221
- ),
222
- }),
223
- func: async (input: {
224
- query: string;
225
- mode?: "text" | "regex";
226
- contextLines?: number;
227
- }) => {
228
- const lines = currentEditorContent.split("\n");
229
- const totalLines = lines.length;
230
- const mode = input.mode || "text";
231
- const contextLines = input.contextLines ?? 2;
232
-
233
- const matches: Array<{
234
- lineNumber: number;
235
- line: string;
236
- context: string[];
237
- }> = [];
238
-
239
- for (let i = 0; i < lines.length; i++) {
240
- let isMatch = false;
241
-
242
- if (mode === "text") {
243
- isMatch = lines[i].includes(input.query);
244
- } else if (mode === "regex") {
245
- try {
246
- const regex = new RegExp(input.query);
247
- isMatch = regex.test(lines[i]);
248
- } catch {
249
- return `Error: Invalid regex pattern "${input.query}"`;
250
- }
251
- }
252
-
253
- if (isMatch) {
254
- const startContext = Math.max(0, i - contextLines);
255
- const endContext = Math.min(lines.length - 1, i + contextLines);
256
-
257
- const contextArray: string[] = [];
258
- for (let j = startContext; j <= endContext; j++) {
259
- const lineNum = j + 1;
260
- const prefix = j === i ? ">>> " : " ";
261
- contextArray.push(`${prefix}${lineNum}: ${lines[j]}`);
262
- }
263
-
264
- matches.push({
265
- lineNumber: i + 1,
266
- line: lines[i],
267
- context: contextArray,
268
- });
269
- }
270
- }
271
-
272
- if (matches.length === 0) {
273
- return `No matches found for "${input.query}" in editor content (${totalLines} lines searched)`;
274
- }
275
-
276
- const totalMatches = matches.length;
277
- const displayMatches = matches.slice(0, 10);
278
-
279
- let output = `Found ${totalMatches} match${totalMatches > 1 ? "es" : ""} for "${input.query}":\n\n`;
280
-
281
- displayMatches.forEach((match, index) => {
282
- if (index > 0) output += "\n---\n\n";
283
- output += match.context.join("\n");
284
- });
285
-
286
- if (totalMatches > 10) {
287
- output += `\n\n(Showing first 10 of ${totalMatches} matches. Use more specific search terms to narrow results)`;
288
- }
289
-
290
- return output;
291
- },
292
- });
293
-
294
  export const observeConsoleTool = new DynamicTool({
295
  name: "observe_console",
296
  description:
297
  "Read console messages and game state - use to check for errors after making changes",
298
  func: async () => {
299
  const messages = consoleBuffer.getRecentMessages();
 
300
  const gameState = consoleBuffer.getGameStateFromMessages();
301
 
302
  consoleBuffer.markAsRead();
303
 
304
  if (messages.length === 0) {
305
- return "No new console messages.";
306
- }
307
-
308
- let output = "Console Messages:\n";
309
- output += messages.map((msg) => `[${msg.type}] ${msg.message}`).join("\n");
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
310
 
311
  output += "\n\nGame State:";
312
  output += `\n- Loading: ${gameState.isLoading}`;
@@ -315,6 +47,7 @@ export const observeConsoleTool = new DynamicTool({
315
  if (gameState.lastError) {
316
  output += `\n- Last Error: ${gameState.lastError}`;
317
  }
 
318
 
319
  return output;
320
  },
@@ -322,12 +55,4 @@ export const observeConsoleTool = new DynamicTool({
322
 
323
  import { taskTrackerTools } from "./task-tracker";
324
 
325
- export const tools = [
326
- readEditorTool,
327
- readEditorLinesTool,
328
- searchEditorTool,
329
- editEditorTool,
330
- writeEditorTool,
331
- observeConsoleTool,
332
- ...taskTrackerTools,
333
- ];
 
1
+ import { DynamicTool } from "@langchain/core/tools";
 
2
  import { consoleBuffer } from "./console-buffer";
3
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
4
  export const observeConsoleTool = new DynamicTool({
5
  name: "observe_console",
6
  description:
7
  "Read console messages and game state - use to check for errors after making changes",
8
  func: async () => {
9
  const messages = consoleBuffer.getRecentMessages();
10
+ const allMessages = consoleBuffer.getAllMessages();
11
  const gameState = consoleBuffer.getGameStateFromMessages();
12
 
13
  consoleBuffer.markAsRead();
14
 
15
  if (messages.length === 0) {
16
+ const debugInfo = `No new console messages since last check.
17
+ Total messages in buffer: ${allMessages.length}
18
+ Game State Analysis:
19
+ - Loading: ${gameState.isLoading}
20
+ - Ready: ${gameState.isReady}
21
+ - Has Error: ${gameState.hasError}
22
+ ${gameState.lastError ? `- Last Error: ${gameState.lastError}` : ""}
23
+
24
+ Last 5 messages in buffer:
25
+ ${allMessages
26
+ .slice(-5)
27
+ .map(
28
+ (msg) =>
29
+ `[${new Date(msg.timestamp).toISOString()}] [${msg.type}] ${msg.message}`,
30
+ )
31
+ .join("\n")}`;
32
+ return debugInfo;
33
+ }
34
+
35
+ let output = `Console Messages (${messages.length} new):\n`;
36
+ output += messages
37
+ .map(
38
+ (msg) =>
39
+ `[${new Date(msg.timestamp).toISOString()}] [${msg.type}] ${msg.message}`,
40
+ )
41
+ .join("\n");
42
 
43
  output += "\n\nGame State:";
44
  output += `\n- Loading: ${gameState.isLoading}`;
 
47
  if (gameState.lastError) {
48
  output += `\n- Last Error: ${gameState.lastError}`;
49
  }
50
+ output += `\n\nTotal buffer size: ${allMessages.length} messages`;
51
 
52
  return output;
53
  },
 
55
 
56
  import { taskTrackerTools } from "./task-tracker";
57
 
58
+ export const tools = [observeConsoleTool, ...taskTrackerTools];
 
 
 
 
 
 
 
 
src/lib/services/agent.ts CHANGED
@@ -1,9 +1,7 @@
1
  import { websocketService, type WebSocketMessage } from "./websocket";
2
  import { messageHandler } from "./message-handler";
 
3
  import { authStore } from "./auth";
4
- import { agentStore, type ChatMessage } from "../stores/agent";
5
- import { editorStore } from "../stores/editor";
6
- import { get } from "svelte/store";
7
 
8
  export class AgentService {
9
  private isInitialized = false;
@@ -13,7 +11,9 @@ export class AgentService {
13
  if (this.isInitialized) return;
14
 
15
  this.unsubscribeHandlers.push(
16
- websocketService.onConnection(this.handleConnection.bind(this)),
 
 
17
  websocketService.onMessage(this.handleMessage.bind(this)),
18
  );
19
 
@@ -29,39 +29,15 @@ export class AgentService {
29
  }
30
 
31
  sendMessage(content: string): void {
32
- if (!websocketService.isConnected()) {
33
- agentStore.setError("Not connected to server");
34
- return;
35
- }
36
-
37
- const userMessage: ChatMessage = {
38
- id: `user_${Date.now()}`,
39
- role: "user",
40
- content,
41
- timestamp: Date.now(),
42
- streaming: false,
43
- };
44
-
45
- agentStore.addMessage(userMessage);
46
- agentStore.setError(null);
47
-
48
- websocketService.send({
49
- type: "chat",
50
- payload: { content },
51
- timestamp: Date.now(),
52
- });
53
  }
54
 
55
  stopConversation(): void {
56
- if (!websocketService.isConnected()) {
57
- return;
58
- }
59
 
60
- websocketService.send({
61
- type: "abort",
62
- payload: {},
63
- timestamp: Date.now(),
64
- });
65
  }
66
 
67
  sendRawMessage(message: unknown): void {
@@ -78,55 +54,16 @@ export class AgentService {
78
  type: "auth",
79
  payload: { token },
80
  timestamp: Date.now(),
81
- });
82
  }
83
- }
84
- }
85
-
86
- private handleConnection(connected: boolean): void {
87
- agentStore.setConnected(connected);
88
-
89
- if (connected) {
90
- this.authenticate();
91
- this.syncEditor();
92
  }
93
  }
94
 
95
  private handleMessage(message: WebSocketMessage): void {
96
  messageHandler.handleMessage(message);
97
  }
98
-
99
- private authenticate(): void {
100
- const token = authStore.getToken();
101
- if (token) {
102
- websocketService.send({
103
- type: "auth",
104
- payload: { token },
105
- timestamp: Date.now(),
106
- });
107
- } else {
108
- agentStore.setError(
109
- "Authentication required. Please sign in with Hugging Face.",
110
- );
111
- agentStore.setConnected(false);
112
- websocketService.disconnect();
113
- }
114
- }
115
-
116
- private syncEditor(): void {
117
- const editorState = get(editorStore);
118
- if (editorState?.content) {
119
- setTimeout(() => {
120
- if (websocketService.isConnected()) {
121
- websocketService.send({
122
- type: "editor_sync",
123
- payload: { content: editorState.content },
124
- timestamp: Date.now(),
125
- });
126
- }
127
- }, 500);
128
- }
129
- }
130
  }
131
 
132
  export const agentService = new AgentService();
 
1
  import { websocketService, type WebSocketMessage } from "./websocket";
2
  import { messageHandler } from "./message-handler";
3
+ import { chatController } from "../controllers/chat-controller";
4
  import { authStore } from "./auth";
 
 
 
5
 
6
  export class AgentService {
7
  private isInitialized = false;
 
11
  if (this.isInitialized) return;
12
 
13
  this.unsubscribeHandlers.push(
14
+ websocketService.onConnection((connected) => {
15
+ chatController.handleConnectionChange(connected);
16
+ }),
17
  websocketService.onMessage(this.handleMessage.bind(this)),
18
  );
19
 
 
29
  }
30
 
31
  sendMessage(content: string): void {
32
+ chatController.sendMessage(content);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
33
  }
34
 
35
  stopConversation(): void {
36
+ chatController.stopConversation();
37
+ }
 
38
 
39
+ clearConversation(): void {
40
+ chatController.clearConversation();
 
 
 
41
  }
42
 
43
  sendRawMessage(message: unknown): void {
 
54
  type: "auth",
55
  payload: { token },
56
  timestamp: Date.now(),
57
+ } as WebSocketMessage);
58
  }
59
+ } else {
60
+ this.connect();
 
 
 
 
 
 
 
61
  }
62
  }
63
 
64
  private handleMessage(message: WebSocketMessage): void {
65
  messageHandler.handleMessage(message);
66
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
67
  }
68
 
69
  export const agentService = new AgentService();
src/lib/services/content-manager.ts ADDED
@@ -0,0 +1,248 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { writable, derived, get } from "svelte/store";
2
+ import { virtualFileSystem } from "./virtual-fs";
3
+ import { websocketService } from "./websocket";
4
+
5
+ export interface ContentState {
6
+ content: string;
7
+ language: string;
8
+ theme: string;
9
+ lastModified: Date;
10
+ version: number;
11
+ isUISynced: boolean;
12
+ isAgentSynced: boolean;
13
+ }
14
+
15
+ export interface ContentChange {
16
+ content: string;
17
+ source: "ui" | "agent" | "init";
18
+ timestamp: Date;
19
+ version: number;
20
+ }
21
+
22
+ /**
23
+ * ContentManager: Single source of truth for editor content
24
+ * Manages bidirectional sync between UI and agent with conflict resolution
25
+ */
26
+ class ContentManager {
27
+ private static instance: ContentManager | null = null;
28
+
29
+ private readonly DEFAULT_CONTENT = `<world canvas="#game-canvas" sky="#87ceeb">
30
+ <!-- Ground -->
31
+ <static-part pos="0 -0.5 0" shape="box" size="20 1 20" color="#90ee90"></static-part>
32
+
33
+ <!-- Ball -->
34
+ <dynamic-part pos="-2 4 -3" shape="sphere" size="1" color="#ff4500"></dynamic-part>
35
+ </world>
36
+
37
+ <script>
38
+ console.log("Game script loaded!");
39
+ </script>`;
40
+
41
+ private readonly contentStore = writable<ContentState>({
42
+ content: this.DEFAULT_CONTENT,
43
+ language: "html",
44
+ theme: "vs-dark",
45
+ lastModified: new Date(),
46
+ version: 1,
47
+ isUISynced: true,
48
+ isAgentSynced: true,
49
+ });
50
+
51
+ private syncTimeout: number | null = null;
52
+ private readonly DEBOUNCE_MS = 300;
53
+ private isUpdating = false;
54
+
55
+ private constructor() {
56
+ this.setupSyncSubscription();
57
+ }
58
+
59
+ static getInstance(): ContentManager {
60
+ if (!ContentManager.instance) {
61
+ ContentManager.instance = new ContentManager();
62
+ }
63
+ return ContentManager.instance;
64
+ }
65
+
66
+ /**
67
+ * Public reactive store for UI components
68
+ */
69
+ readonly content = derived(this.contentStore, ($state) => ({
70
+ content: $state.content,
71
+ language: $state.language,
72
+ theme: $state.theme,
73
+ lastModified: $state.lastModified,
74
+ version: $state.version,
75
+ }));
76
+
77
+ /**
78
+ * Public store subscription method
79
+ */
80
+ subscribe = this.content.subscribe;
81
+
82
+ /**
83
+ * Update content from UI (Monaco editor)
84
+ * Debounced for smooth typing experience
85
+ */
86
+ updateFromUI(content: string): void {
87
+ if (this.isUpdating) return;
88
+
89
+ this.updateContent(content, "ui");
90
+ this.debouncedAgentSync();
91
+ }
92
+
93
+ /**
94
+ * Update content from agent (MCP tools)
95
+ * Always overwrites UI content - agent wins conflicts
96
+ */
97
+ updateFromAgent(content: string): void {
98
+ if (this.isUpdating) return;
99
+
100
+ this.isUpdating = true;
101
+ this.updateContent(content, "agent");
102
+ this.clearSyncTimeout();
103
+ this.isUpdating = false;
104
+ }
105
+
106
+ /**
107
+ * Initialize content (on app start)
108
+ */
109
+ initialize(content?: string): void {
110
+ const initialContent = content || this.DEFAULT_CONTENT;
111
+ this.updateContent(initialContent, "init");
112
+
113
+ // Sync to VFS immediately but not to WebSocket yet (may not be connected)
114
+ virtualFileSystem.updateGameContent(initialContent);
115
+
116
+ this.contentStore.update((state) => ({
117
+ ...state,
118
+ isAgentSynced: true,
119
+ }));
120
+ }
121
+
122
+ /**
123
+ * Get current content (synchronous)
124
+ */
125
+ getCurrentContent(): string {
126
+ return get(this.contentStore).content;
127
+ }
128
+
129
+ /**
130
+ * Get current state (synchronous)
131
+ */
132
+ getCurrentState(): ContentState {
133
+ return get(this.contentStore);
134
+ }
135
+
136
+ /**
137
+ * Force full sync (for reconnection scenarios)
138
+ */
139
+ forceFullSync(): void {
140
+ this.immediateFullSync();
141
+ }
142
+
143
+ /**
144
+ * Update language setting
145
+ */
146
+ setLanguage(language: string): void {
147
+ this.contentStore.update((state) => ({
148
+ ...state,
149
+ language,
150
+ lastModified: new Date(),
151
+ }));
152
+ }
153
+
154
+ /**
155
+ * Update theme setting
156
+ */
157
+ setTheme(theme: string): void {
158
+ this.contentStore.update((state) => ({
159
+ ...state,
160
+ theme,
161
+ lastModified: new Date(),
162
+ }));
163
+ }
164
+
165
+ /**
166
+ * Reset to default content
167
+ */
168
+ reset(): void {
169
+ this.updateContent(this.DEFAULT_CONTENT, "init");
170
+ this.immediateFullSync();
171
+ }
172
+
173
+ private updateContent(
174
+ content: string,
175
+ source: ContentChange["source"],
176
+ ): void {
177
+ this.contentStore.update((state) => {
178
+ // Prevent unnecessary updates
179
+ if (state.content === content) return state;
180
+
181
+ return {
182
+ ...state,
183
+ content,
184
+ lastModified: new Date(),
185
+ version: state.version + 1,
186
+ isUISynced: source === "ui" || source === "init",
187
+ isAgentSynced: source === "agent" || source === "init",
188
+ };
189
+ });
190
+ }
191
+
192
+ private setupSyncSubscription(): void {
193
+ this.contentStore.subscribe((state) => {
194
+ // Only sync if content actually changed and we're not in an update cycle
195
+ if (!this.isUpdating) {
196
+ if (!state.isAgentSynced) {
197
+ this.syncToAgent(state.content);
198
+ }
199
+ }
200
+ });
201
+ }
202
+
203
+ private debouncedAgentSync(): void {
204
+ this.clearSyncTimeout();
205
+
206
+ this.syncTimeout = window.setTimeout(() => {
207
+ const state = get(this.contentStore);
208
+ if (!state.isAgentSynced) {
209
+ this.syncToAgent(state.content);
210
+ }
211
+ }, this.DEBOUNCE_MS);
212
+ }
213
+
214
+ private immediateFullSync(): void {
215
+ this.clearSyncTimeout();
216
+ const content = get(this.contentStore).content;
217
+ this.syncToAgent(content);
218
+ }
219
+
220
+ private syncToAgent(content: string): void {
221
+ // Update virtual file system
222
+ virtualFileSystem.updateGameContent(content);
223
+
224
+ // Send to WebSocket if connected
225
+ if (websocketService.isConnected()) {
226
+ websocketService.send({
227
+ type: "editor_sync",
228
+ payload: { content },
229
+ timestamp: Date.now(),
230
+ });
231
+ }
232
+
233
+ // Mark as synced
234
+ this.contentStore.update((state) => ({
235
+ ...state,
236
+ isAgentSynced: true,
237
+ }));
238
+ }
239
+
240
+ private clearSyncTimeout(): void {
241
+ if (this.syncTimeout !== null) {
242
+ clearTimeout(this.syncTimeout);
243
+ this.syncTimeout = null;
244
+ }
245
+ }
246
+ }
247
+
248
+ export const contentManager = ContentManager.getInstance();
src/lib/services/context.md CHANGED
@@ -1,52 +1,42 @@
1
  # Services
2
 
3
- Business logic layer with pure functions and singleton services
4
 
5
  ## Purpose
6
 
7
- - Encapsulate complex business logic
8
- - Provide clean APIs for operations
9
- - Keep components simple and declarative
10
 
11
  ## Layout
12
 
13
  ```
14
  services/
15
- ├── context.md # This file
16
- ├── auth.ts # Hugging Face OAuth authentication
17
- ├── agent.ts # High-level agent coordination
18
- ├── websocket.ts # WebSocket connection management
19
- ├── message-handler.ts # WebSocket message processing
20
- ├── game-engine.ts # Game lifecycle management
21
- ├── console-sync.ts # VibeGame console synchronization
22
- └── html-parser.ts # Game HTML parsing
 
23
  ```
24
 
25
  ## Scope
26
 
27
- - In-scope: Business logic, side effects, API calls, authentication
28
- - Out-of-scope: UI rendering, state storage
29
 
30
  ## Entrypoints
31
 
32
- - `authStore.login()` - Start OAuth flow
33
- - `authStore.logout()` - Clear authentication
34
- - `authStore.getToken()` - Get current token
35
- - `authStore.isTokenValid()` - Check token validity
36
- - `agentService.connect()` - Connect to WebSocket server
37
- - `agentService.disconnect()` - Disconnect from server
38
- - `agentService.sendMessage()` - Send chat message
39
- - `agentService.sendRawMessage()` - Send raw WebSocket message
40
- - `gameEngine.start(worldContent, scripts)` - Start game with parsed world and scripts
41
- - `gameEngine.stop()` - Destroy game instance and clean up
42
- - `gameEngine.isRunning()` - Check if game is active
43
- - `consoleSyncService.setup()` - Intercept console methods
44
- - `consoleSyncService.teardown()` - Restore original console
45
- - `HTMLParser.extractGameContent(html)` - Parse world and scripts from HTML
46
 
47
  ## Dependencies
48
 
49
- - @huggingface/hub for OAuth
50
  - VibeGame for game engine
51
- - Stores for state updates
52
- - No UI dependencies
 
1
  # Services
2
 
3
+ Core business logic with centralized content management for JSFiddle-style game development.
4
 
5
  ## Purpose
6
 
7
+ Single source of truth content management, game lifecycle, and virtual file operations
 
 
8
 
9
  ## Layout
10
 
11
  ```
12
  services/
13
+ ├── context.md # This file
14
+ ├── content-manager.ts # Single source of truth for editor content
15
+ ├── auth.ts # Hugging Face OAuth
16
+ ├── websocket.ts # WebSocket connection
17
+ ├── message-handler.ts # Message routing with segment processing
18
+ ├── game-engine.ts # VibeGame lifecycle with DOM-based rendering
19
+ ├── html-document-parser.ts # HTML parsing using DOMParser
20
+ ├── virtual-fs.ts # Virtual file system for editor content
21
+ └── console-sync.ts # Console interception
22
  ```
23
 
24
  ## Scope
25
 
26
+ - In-scope: Content synchronization, game lifecycle, HTML parsing, virtual file operations
27
+ - Out-of-scope: UI rendering, component state
28
 
29
  ## Entrypoints
30
 
31
+ - `contentManager` - Single reactive store for all editor content with bidirectional sync
32
+ - `gameEngine` - JSFiddle-style game lifecycle management
33
+ - `virtualFileSystem` - Virtual file operations for editor content
34
+ - `HTMLDocumentParser` - HTML parsing
35
+ - `messageHandler` - Message routing and segment processing
 
 
 
 
 
 
 
 
 
36
 
37
  ## Dependencies
38
 
39
+ - Svelte stores for reactive content management
40
  - VibeGame for game engine
41
+ - DOMParser for HTML parsing
42
+ - @huggingface/hub for OAuth
src/lib/services/game-engine.ts CHANGED
@@ -1,8 +1,11 @@
1
  import * as GAME from "vibegame";
2
  import type { System, Plugin, Component, BuilderOptions } from "vibegame";
3
  import { gameStore } from "../stores/game";
4
- import { consoleStore } from "../stores/console";
5
  import { uiStore } from "../stores/ui";
 
 
 
 
6
 
7
  type GameInstance = Awaited<ReturnType<typeof GAME.run>>;
8
 
@@ -19,95 +22,59 @@ export class GameEngine {
19
  return GameEngine.instance;
20
  }
21
 
22
- async start(worldContent: string, scripts: string[] = []): Promise<void> {
23
  if (this.gameInstance) {
24
- consoleStore.addMessage("info", "Stopping previous game instance...");
25
  this.stop();
26
  await new Promise((resolve) => setTimeout(resolve, 100));
27
  }
28
 
29
  gameStore.setStarting(true);
30
- consoleStore.addMessage("info", "🎮 Starting game...");
31
  uiStore.setError(null);
32
 
33
  try {
34
- const container = document.getElementById("world-container");
35
- if (!container) {
36
- throw new Error("World container not found");
37
- }
 
 
 
 
 
 
 
 
 
 
38
 
39
- container.innerHTML = worldContent;
40
-
41
- GAME.resetBuilder();
42
-
43
- const gameProxy = {
44
- withSystem: (system: System) => {
45
- GAME.withSystem(system);
46
- return gameProxy;
47
- },
48
- withPlugin: (plugin: Plugin) => {
49
- GAME.withPlugin(plugin);
50
- return gameProxy;
51
- },
52
- withComponent: (name: string, component: Component) => {
53
- GAME.withComponent(name, component);
54
- return gameProxy;
55
- },
56
- configure: (options: BuilderOptions) => {
57
- GAME.configure(options);
58
- return gameProxy;
59
- },
60
- withoutDefaultPlugins: () => {
61
- GAME.withoutDefaultPlugins();
62
- return gameProxy;
63
- },
64
- run: () => {
65
- console.warn(
66
- "GAME.run() is not available in user scripts - the framework handles game lifecycle",
67
- );
68
- return Promise.resolve({
69
- stop: () => {},
70
- destroy: () => {},
71
- step: () => {},
72
- getState: () => null,
73
- });
74
- },
75
- defineComponent: GAME.defineComponent,
76
- defineQuery: GAME.defineQuery,
77
- Types: GAME.Types,
78
- };
79
-
80
- (window as unknown as { GAME: typeof gameProxy }).GAME = gameProxy;
81
-
82
- let scriptExecutionFailed = false;
83
- for (const script of scripts) {
84
- try {
85
- const cleanedScript = script.replace(/GAME\.run\(\)/g, "");
86
- eval(cleanedScript);
87
- } catch (scriptError) {
88
- scriptExecutionFailed = true;
89
- const errorMsg =
90
- scriptError instanceof Error
91
- ? scriptError.message
92
- : String(scriptError);
93
- console.error("Error executing user script:", errorMsg);
94
- consoleStore.addMessage("error", `Script error: ${errorMsg}`);
95
- }
96
- }
97
 
98
- (window as unknown as { GAME: typeof gameProxy | null }).GAME = null;
 
99
 
100
- if (scriptExecutionFailed) {
101
- throw new Error("Script execution failed - game not started");
102
- }
 
 
103
 
104
- this.gameInstance = await GAME.run();
105
- gameStore.setInstance(this.gameInstance);
106
- consoleStore.addMessage("info", "✅ Game started!");
 
 
 
 
 
107
  } catch (error: unknown) {
108
  const errorMsg = error instanceof Error ? error.message : String(error);
109
  uiStore.setError(errorMsg);
110
- consoleStore.addMessage("error", `❌ Error: ${errorMsg}`);
111
  gameStore.setInstance(null);
112
  this.gameInstance = null;
113
  } finally {
@@ -115,14 +82,99 @@ export class GameEngine {
115
  }
116
  }
117
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
118
  stop(): void {
119
  if (this.gameInstance) {
120
  try {
121
  this.gameInstance.destroy();
122
- consoleStore.addMessage("info", "Game instance destroyed");
123
  } catch (error) {
124
  console.error("Error destroying game:", error);
125
- consoleStore.addMessage("error", `Error destroying game: ${error}`);
126
  }
127
  this.gameInstance = null;
128
  gameStore.setInstance(null);
@@ -130,9 +182,7 @@ export class GameEngine {
130
 
131
  const container = document.getElementById("world-container");
132
  if (container) {
133
- while (container.firstChild) {
134
- container.removeChild(container.firstChild);
135
- }
136
  }
137
 
138
  GAME.resetBuilder();
 
1
  import * as GAME from "vibegame";
2
  import type { System, Plugin, Component, BuilderOptions } from "vibegame";
3
  import { gameStore } from "../stores/game";
 
4
  import { uiStore } from "../stores/ui";
5
+ import {
6
+ HTMLDocumentParser,
7
+ type ParsedDocument,
8
+ } from "./html-document-parser";
9
 
10
  type GameInstance = Awaited<ReturnType<typeof GAME.run>>;
11
 
 
22
  return GameEngine.instance;
23
  }
24
 
25
+ async startFromDocument(htmlContent: string): Promise<void> {
26
  if (this.gameInstance) {
 
27
  this.stop();
28
  await new Promise((resolve) => setTimeout(resolve, 100));
29
  }
30
 
31
  gameStore.setStarting(true);
32
+ console.info("🎮 Starting game...");
33
  uiStore.setError(null);
34
 
35
  try {
36
+ const parsed = HTMLDocumentParser.parseDocument(htmlContent);
37
+ this.renderDocument(parsed);
38
+ await this.initializeGame(parsed.scripts);
39
+ console.info("✅ Game started!");
40
+ } catch (error: unknown) {
41
+ const errorMsg = error instanceof Error ? error.message : String(error);
42
+ uiStore.setError(errorMsg);
43
+ console.error(`❌ Error: ${errorMsg}`);
44
+ gameStore.setInstance(null);
45
+ this.gameInstance = null;
46
+ } finally {
47
+ gameStore.setStarting(false);
48
+ }
49
+ }
50
 
51
+ async start(worldContent: string, scripts: string[] = []): Promise<void> {
52
+ const parsed: ParsedDocument = {
53
+ world: worldContent,
54
+ scripts,
55
+ };
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
56
 
57
+ await this.startFromParsed(parsed);
58
+ }
59
 
60
+ private async startFromParsed(parsed: ParsedDocument): Promise<void> {
61
+ if (this.gameInstance) {
62
+ this.stop();
63
+ await new Promise((resolve) => setTimeout(resolve, 100));
64
+ }
65
 
66
+ gameStore.setStarting(true);
67
+ console.info("🎮 Starting game...");
68
+ uiStore.setError(null);
69
+
70
+ try {
71
+ this.renderDocument(parsed);
72
+ await this.initializeGame(parsed.scripts);
73
+ console.info("✅ Game started!");
74
  } catch (error: unknown) {
75
  const errorMsg = error instanceof Error ? error.message : String(error);
76
  uiStore.setError(errorMsg);
77
+ console.error(`❌ Error: ${errorMsg}`);
78
  gameStore.setInstance(null);
79
  this.gameInstance = null;
80
  } finally {
 
82
  }
83
  }
84
 
85
+ private renderDocument(parsed: ParsedDocument): void {
86
+ const container = document.getElementById("world-container");
87
+ if (!container) {
88
+ throw new Error("World container not found");
89
+ }
90
+
91
+ this.clearContainer(container);
92
+ container.innerHTML = parsed.world;
93
+ }
94
+
95
+ private clearContainer(container: HTMLElement): void {
96
+ while (container.firstChild) {
97
+ container.removeChild(container.firstChild);
98
+ }
99
+ }
100
+
101
+ private async initializeGame(scripts: string[]): Promise<void> {
102
+ GAME.resetBuilder();
103
+
104
+ const gameProxy = this.createGameProxy();
105
+ (window as unknown as { GAME: typeof gameProxy }).GAME = gameProxy;
106
+
107
+ let scriptExecutionFailed = false;
108
+ for (const script of scripts) {
109
+ try {
110
+ const cleanedScript = script.replace(/GAME\.run\(\)/g, "");
111
+ eval(cleanedScript);
112
+ } catch (scriptError) {
113
+ scriptExecutionFailed = true;
114
+ const errorMsg =
115
+ scriptError instanceof Error
116
+ ? scriptError.message
117
+ : String(scriptError);
118
+ console.error("Script error:", errorMsg);
119
+ }
120
+ }
121
+
122
+ (window as unknown as { GAME: typeof gameProxy | null }).GAME = null;
123
+
124
+ if (scriptExecutionFailed) {
125
+ throw new Error("Script execution failed - game not started");
126
+ }
127
+
128
+ this.gameInstance = await GAME.run();
129
+ gameStore.setInstance(this.gameInstance);
130
+ }
131
+
132
+ private createGameProxy() {
133
+ return {
134
+ withSystem: (system: System) => {
135
+ GAME.withSystem(system);
136
+ return this.createGameProxy();
137
+ },
138
+ withPlugin: (plugin: Plugin) => {
139
+ GAME.withPlugin(plugin);
140
+ return this.createGameProxy();
141
+ },
142
+ withComponent: (name: string, component: Component) => {
143
+ GAME.withComponent(name, component);
144
+ return this.createGameProxy();
145
+ },
146
+ configure: (options: BuilderOptions) => {
147
+ GAME.configure(options);
148
+ return this.createGameProxy();
149
+ },
150
+ withoutDefaultPlugins: () => {
151
+ GAME.withoutDefaultPlugins();
152
+ return this.createGameProxy();
153
+ },
154
+ run: () => {
155
+ console.warn(
156
+ "GAME.run() is not available in user scripts - the framework handles game lifecycle",
157
+ );
158
+ return Promise.resolve({
159
+ stop: () => {},
160
+ destroy: () => {},
161
+ step: () => {},
162
+ getState: () => null,
163
+ });
164
+ },
165
+ defineComponent: GAME.defineComponent,
166
+ defineQuery: GAME.defineQuery,
167
+ Types: GAME.Types,
168
+ };
169
+ }
170
+
171
  stop(): void {
172
  if (this.gameInstance) {
173
  try {
174
  this.gameInstance.destroy();
175
+ console.info("Game instance destroyed");
176
  } catch (error) {
177
  console.error("Error destroying game:", error);
 
178
  }
179
  this.gameInstance = null;
180
  gameStore.setInstance(null);
 
182
 
183
  const container = document.getElementById("world-container");
184
  if (container) {
185
+ this.clearContainer(container);
 
 
186
  }
187
 
188
  GAME.resetBuilder();
src/lib/services/html-document-parser.ts ADDED
@@ -0,0 +1,56 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ export interface ParsedDocument {
2
+ world: string;
3
+ scripts: string[];
4
+ }
5
+
6
+ /**
7
+ * Simple HTML Document Parser for JSFiddle-style content processing
8
+ * Parses complete HTML documents and extracts world/script content
9
+ */
10
+ export class HTMLDocumentParser {
11
+ static parseDocument(html: string): ParsedDocument {
12
+ try {
13
+ const parser = new DOMParser();
14
+ const doc = parser.parseFromString(html, "text/html");
15
+
16
+ const world = this.extractWorld(doc);
17
+ const scripts = this.extractScripts(doc);
18
+
19
+ return {
20
+ world: world || '<world canvas="#game-canvas"></world>',
21
+ scripts,
22
+ };
23
+ } catch {
24
+ return {
25
+ world: '<world canvas="#game-canvas"></world>',
26
+ scripts: [],
27
+ };
28
+ }
29
+ }
30
+
31
+ private static extractWorld(doc: Document): string {
32
+ const worldElements = doc.getElementsByTagName("world");
33
+
34
+ if (worldElements.length === 0) {
35
+ return "";
36
+ }
37
+
38
+ return worldElements[0].outerHTML;
39
+ }
40
+
41
+ private static extractScripts(doc: Document): string[] {
42
+ const scripts: string[] = [];
43
+ const scriptElements = doc.getElementsByTagName("script");
44
+
45
+ for (let i = 0; i < scriptElements.length; i++) {
46
+ const script = scriptElements[i];
47
+ const content = script.textContent || script.innerHTML;
48
+
49
+ if (content && content.trim()) {
50
+ scripts.push(content.trim());
51
+ }
52
+ }
53
+
54
+ return scripts;
55
+ }
56
+ }
src/lib/services/html-parser.ts DELETED
@@ -1,38 +0,0 @@
1
- export interface GameContent {
2
- world: string;
3
- scripts: string[];
4
- }
5
-
6
- export class HTMLParser {
7
- static extractGameContent(html: string): GameContent {
8
- const worldMatch = html.match(/<world[^>]*>[\s\S]*?<\/world>/);
9
- const world = worldMatch
10
- ? worldMatch[0]
11
- : '<world canvas="#game-canvas"></world>';
12
-
13
- const scripts: string[] = [];
14
- const scriptRegex = /<script[^>]*>([\s\S]*?)<\/script>/gi;
15
- let match;
16
-
17
- while ((match = scriptRegex.exec(html)) !== null) {
18
- const scriptContent = match[1].trim();
19
- if (scriptContent) {
20
- scripts.push(scriptContent);
21
- }
22
- }
23
-
24
- return { world, scripts };
25
- }
26
-
27
- static validateGameHTML(html: string): { valid: boolean; error?: string } {
28
- if (!html.includes("<world")) {
29
- return { valid: false, error: "Missing <world> tag" };
30
- }
31
-
32
- if (!html.includes("canvas=")) {
33
- return { valid: false, error: "Missing canvas attribute in <world> tag" };
34
- }
35
-
36
- return { valid: true };
37
- }
38
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
src/lib/services/message-handler.ts CHANGED
@@ -1,201 +1,238 @@
1
  import type { WebSocketMessage } from "./websocket";
2
- import { agentStore } from "../stores/agent";
3
- import { editorStore } from "../stores/editor";
4
- import type {
5
- ChatMessage,
6
- MessageSegment,
7
- MessageSegmentType,
8
- } from "../stores/agent";
9
-
10
- interface StreamState {
11
- currentStreamId: string | null;
12
- streamingContent: string;
13
- }
14
 
15
  export class MessageHandler {
16
- private streamState: StreamState = {
17
- currentStreamId: null,
18
- streamingContent: "",
19
- };
20
 
21
  handleMessage(message: WebSocketMessage): void {
22
- const handlers: Record<string, () => void> = {
23
- status: () => this.handleStatus(message),
24
- stream_start: () => this.handleStreamStart(message),
25
- stream_token: () => this.handleStreamToken(message),
26
- stream_end: () => this.handleStreamEnd(message),
27
- chat: () => this.handleChat(message),
28
- error: () => this.handleError(message),
29
- editor_update: () => this.handleEditorUpdate(message),
30
- segment_start: () => this.handleSegmentStart(message),
31
- segment_token: () => this.handleSegmentToken(message),
32
- segment_end: () => this.handleSegmentEnd(message),
33
- };
34
-
35
- const handler = handlers[message.type];
36
- if (handler) {
37
- handler();
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
38
  }
39
  }
40
 
41
  private handleStatus(message: WebSocketMessage): void {
42
  const { processing, connected } = message.payload;
43
-
44
  if (processing !== undefined) {
45
- agentStore.setProcessing(processing as boolean);
46
  }
47
  if (connected !== undefined) {
48
- agentStore.setConnected(connected as boolean);
49
  }
50
  }
51
 
52
  private handleStreamStart(message: WebSocketMessage): void {
53
  const messageId =
54
  (message.payload.messageId as string) || `assistant_${Date.now()}`;
55
- this.streamState.currentStreamId = messageId;
56
- this.streamState.streamingContent = "";
 
 
57
 
58
- const newMessage: ChatMessage = {
59
  id: messageId,
60
  role: "assistant",
61
  content: "",
62
  timestamp: Date.now(),
63
- streaming: true,
64
  segments: [],
65
- };
66
-
67
- agentStore.addMessage(newMessage);
68
- agentStore.setStreamingStatus("streaming");
69
  }
70
 
71
  private handleStreamToken(message: WebSocketMessage): void {
72
- const messageId = message.payload.messageId as string;
73
  const token = (message.payload.token as string) || "";
74
-
75
- if (!messageId) {
76
- console.error("stream_token without messageId");
77
- return;
78
  }
79
-
80
- this.streamState.streamingContent += token;
81
- agentStore.updateMessageContent(
82
- messageId,
83
- this.streamState.streamingContent,
84
- );
85
  }
86
 
87
- private handleStreamEnd(message: WebSocketMessage): void {
88
- const messageId = message.payload.messageId as string;
89
- const content =
90
- (message.payload.content as string) || this.streamState.streamingContent;
91
-
92
- if (!messageId) {
93
- console.error("stream_end without messageId");
94
- return;
95
  }
96
-
97
- agentStore.updateMessage(messageId, { content, streaming: false });
98
- agentStore.setStreamingStatus("idle");
99
-
100
- this.streamState.currentStreamId = null;
101
- this.streamState.streamingContent = "";
102
  }
103
 
104
  private handleChat(message: WebSocketMessage): void {
105
- if (this.streamState.currentStreamId) {
106
- agentStore.updateMessage(this.streamState.currentStreamId, {
107
- streaming: false,
 
 
 
 
 
108
  });
109
- agentStore.setStreamingStatus("idle");
110
- this.streamState.currentStreamId = null;
111
- this.streamState.streamingContent = "";
112
- } else {
113
- const { role, content } = message.payload;
114
- if (role && content) {
115
- const newMessage: ChatMessage = {
116
- id: `msg_${Date.now()}`,
117
- role: role as "user" | "assistant" | "system",
118
- content: content as string,
119
- timestamp: Date.now(),
120
- };
121
- agentStore.addMessage(newMessage);
122
- }
123
  }
124
  }
125
 
126
  private handleError(message: WebSocketMessage): void {
127
- agentStore.setError((message.payload.error as string) || null);
128
- agentStore.setProcessing(false);
129
- agentStore.setStreamingStatus("idle");
130
  }
131
 
132
  private handleEditorUpdate(message: WebSocketMessage): void {
133
  const content = message.payload.content as string;
134
  if (content) {
135
- editorStore.setContent(content);
136
  }
137
  }
138
 
139
  private handleSegmentStart(message: WebSocketMessage): void {
140
- const messageId = message.payload.messageId as string;
141
- const segmentId =
142
- (message.payload.segmentId as string) || `seg_${Date.now()}`;
143
- const segmentType = message.payload.segmentType as MessageSegmentType;
144
-
145
- if (!messageId) {
146
- console.error("segment_start without messageId");
147
- return;
148
- }
149
 
150
- const newSegment: MessageSegment = {
151
- id: segmentId,
152
- type: segmentType,
153
  content: "",
154
- toolName: message.payload.toolName as string | undefined,
155
- toolArgs: message.payload.toolArgs as Record<string, unknown> | undefined,
156
  startTime: Date.now(),
157
  streaming: segmentType === "text",
158
- toolStatus: segmentType === "tool-invocation" ? "pending" : undefined,
159
  };
160
 
161
- agentStore.addSegment(messageId, newSegment);
 
162
  }
163
 
164
  private handleSegmentToken(message: WebSocketMessage): void {
165
- const messageId = message.payload.messageId as string;
166
- const segmentId = message.payload.segmentId as string;
167
- const token = (message.payload.token as string) || "";
168
-
169
- if (!messageId || !segmentId) {
170
- console.error("segment_token missing messageId or segmentId");
171
- return;
 
 
 
 
172
  }
173
-
174
- agentStore.updateSegmentContent(messageId, segmentId, token);
175
  }
176
 
177
  private handleSegmentEnd(message: WebSocketMessage): void {
178
- const messageId = message.payload.messageId as string;
179
- const segmentId = message.payload.segmentId as string;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
180
 
181
- if (!messageId || !segmentId) {
182
- console.error("segment_end missing messageId or segmentId");
183
- return;
184
  }
 
185
 
186
- const updates: Partial<MessageSegment> = {
187
- streaming: false,
188
- content: message.payload.content as string | undefined,
189
- toolOutput: message.payload.toolOutput as string | undefined,
190
- toolResult: message.payload.toolResult as string | undefined,
191
- toolStatus: message.payload.toolStatus as MessageSegment["toolStatus"],
192
- toolError: message.payload.toolError as string | undefined,
193
- endTime: Date.now(),
194
- consoleOutput: message.payload.consoleOutput as string[] | undefined,
195
- };
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
196
 
197
- agentStore.updateSegment(messageId, segmentId, updates);
198
- agentStore.mergeSegmentsIfNeeded(messageId);
199
  }
200
  }
201
 
 
1
  import type { WebSocketMessage } from "./websocket";
2
+ import { chatStore } from "../stores/chat-store";
3
+ import { contentManager } from "./content-manager";
4
+ import type { MessageSegment } from "../models/chat-data";
 
 
 
 
 
 
 
 
 
5
 
6
  export class MessageHandler {
7
+ private currentMessageId: string | null = null;
8
+ private currentSegments: Map<string, MessageSegment> = new Map();
9
+ private completedSegments: MessageSegment[] = [];
10
+ private latestTodoSegmentId: string | null = null;
11
 
12
  handleMessage(message: WebSocketMessage): void {
13
+ switch (message.type) {
14
+ case "status":
15
+ this.handleStatus(message);
16
+ break;
17
+ case "stream_start":
18
+ this.handleStreamStart(message);
19
+ break;
20
+ case "stream_token":
21
+ this.handleStreamToken(message);
22
+ break;
23
+ case "stream_end":
24
+ this.handleStreamEnd();
25
+ break;
26
+ case "chat":
27
+ this.handleChat(message);
28
+ break;
29
+ case "error":
30
+ this.handleError(message);
31
+ break;
32
+ case "editor_update":
33
+ this.handleEditorUpdate(message);
34
+ break;
35
+ case "segment_start":
36
+ this.handleSegmentStart(message);
37
+ break;
38
+ case "segment_token":
39
+ this.handleSegmentToken(message);
40
+ break;
41
+ case "segment_end":
42
+ this.handleSegmentEnd(message);
43
+ break;
44
+ case "tool_start":
45
+ case "tool_end":
46
+ break;
47
  }
48
  }
49
 
50
  private handleStatus(message: WebSocketMessage): void {
51
  const { processing, connected } = message.payload;
 
52
  if (processing !== undefined) {
53
+ chatStore.setProcessing(processing as boolean);
54
  }
55
  if (connected !== undefined) {
56
+ chatStore.setConnected(connected as boolean);
57
  }
58
  }
59
 
60
  private handleStreamStart(message: WebSocketMessage): void {
61
  const messageId =
62
  (message.payload.messageId as string) || `assistant_${Date.now()}`;
63
+ this.currentMessageId = messageId;
64
+ this.currentSegments.clear();
65
+ this.completedSegments = [];
66
+ this.latestTodoSegmentId = null;
67
 
68
+ chatStore.addMessage({
69
  id: messageId,
70
  role: "assistant",
71
  content: "",
72
  timestamp: Date.now(),
 
73
  segments: [],
74
+ });
 
 
 
75
  }
76
 
77
  private handleStreamToken(message: WebSocketMessage): void {
 
78
  const token = (message.payload.token as string) || "";
79
+ if (this.currentMessageId && token) {
80
+ chatStore.appendToLastMessage(token);
 
 
81
  }
 
 
 
 
 
 
82
  }
83
 
84
+ private handleStreamEnd(): void {
85
+ if (this.currentMessageId) {
86
+ this.updateMessageSegments();
 
 
 
 
 
87
  }
88
+ this.currentMessageId = null;
89
+ this.currentSegments.clear();
90
+ this.completedSegments = [];
 
 
 
91
  }
92
 
93
  private handleChat(message: WebSocketMessage): void {
94
+ const { content } = message.payload;
95
+ if (content) {
96
+ const messageId = `assistant_${Date.now()}`;
97
+ chatStore.addMessage({
98
+ id: messageId,
99
+ role: "assistant",
100
+ content: content as string,
101
+ timestamp: Date.now(),
102
  });
 
 
 
 
 
 
 
 
 
 
 
 
 
 
103
  }
104
  }
105
 
106
  private handleError(message: WebSocketMessage): void {
107
+ chatStore.setError((message.payload.error as string) || null);
 
 
108
  }
109
 
110
  private handleEditorUpdate(message: WebSocketMessage): void {
111
  const content = message.payload.content as string;
112
  if (content) {
113
+ contentManager.updateFromAgent(content);
114
  }
115
  }
116
 
117
  private handleSegmentStart(message: WebSocketMessage): void {
118
+ const { segmentId, segmentType, toolName, toolArgs } = message.payload;
119
+ if (!segmentId || !this.currentMessageId) return;
 
 
 
 
 
 
 
120
 
121
+ const segment: MessageSegment = {
122
+ id: segmentId as string,
123
+ type: segmentType as MessageSegment["type"],
124
  content: "",
125
+ toolName: toolName as string | undefined,
126
+ toolArgs: toolArgs as Record<string, unknown> | undefined,
127
  startTime: Date.now(),
128
  streaming: segmentType === "text",
 
129
  };
130
 
131
+ this.currentSegments.set(segmentId as string, segment);
132
+ this.updateMessageSegments();
133
  }
134
 
135
  private handleSegmentToken(message: WebSocketMessage): void {
136
+ const { segmentId, token } = message.payload;
137
+ if (!segmentId || !token) return;
138
+
139
+ const segment = this.currentSegments.get(segmentId as string);
140
+ if (segment) {
141
+ const updatedSegment = {
142
+ ...segment,
143
+ content: segment.content + (token as string),
144
+ };
145
+ this.currentSegments.set(segmentId as string, updatedSegment);
146
+ this.updateMessageSegments();
147
  }
 
 
148
  }
149
 
150
  private handleSegmentEnd(message: WebSocketMessage): void {
151
+ const {
152
+ segmentId,
153
+ content,
154
+ toolStatus,
155
+ toolOutput,
156
+ toolError,
157
+ consoleOutput,
158
+ } = message.payload;
159
+ if (!segmentId) return;
160
+
161
+ const segment = this.currentSegments.get(segmentId as string);
162
+ if (segment) {
163
+ const completedSegment: MessageSegment = {
164
+ ...segment,
165
+ content: content ? (content as string) : segment.content,
166
+ toolStatus: toolStatus
167
+ ? (toolStatus as MessageSegment["toolStatus"])
168
+ : segment.toolStatus,
169
+ toolOutput: toolOutput ? (toolOutput as string) : segment.toolOutput,
170
+ toolError: toolError ? (toolError as string) : segment.toolError,
171
+ consoleOutput: consoleOutput
172
+ ? (consoleOutput as string[])
173
+ : segment.consoleOutput,
174
+ endTime: Date.now(),
175
+ streaming: false,
176
+ };
177
+
178
+ // Special handling for todo tools - merge with existing
179
+ if (this.isTodoTool(completedSegment)) {
180
+ this.handleTodoSegment(completedSegment);
181
+ } else {
182
+ this.completedSegments.push(completedSegment);
183
+ }
184
 
185
+ this.currentSegments.delete(segmentId as string);
186
+ this.updateMessageSegments();
 
187
  }
188
+ }
189
 
190
+ private isTodoTool(segment: MessageSegment): boolean {
191
+ return !!segment.toolName?.includes("task");
192
+ }
193
+
194
+ private handleTodoSegment(segment: MessageSegment): void {
195
+ // Remove previous todo segment and replace with new one
196
+ if (this.latestTodoSegmentId) {
197
+ this.completedSegments = this.completedSegments.filter(
198
+ (s) => s.id !== this.latestTodoSegmentId,
199
+ );
200
+ }
201
+
202
+ this.latestTodoSegmentId = segment.id;
203
+ this.completedSegments.push(segment);
204
+ }
205
+
206
+ private updateMessageSegments(): void {
207
+ if (!this.currentMessageId) return;
208
+
209
+ const allSegments = [
210
+ ...this.completedSegments,
211
+ ...Array.from(this.currentSegments.values()),
212
+ ];
213
+
214
+ const content = this.buildContentFromSegments(allSegments);
215
+
216
+ chatStore.setLastMessageContent(content);
217
+ chatStore.setLastMessageSegments(allSegments);
218
+ }
219
+
220
+ private buildContentFromSegments(segments: MessageSegment[]): string {
221
+ let content = "";
222
+
223
+ for (const segment of segments) {
224
+ if (segment.type === "text" && segment.content) {
225
+ content += segment.content;
226
+ } else if (segment.type === "tool-invocation") {
227
+ // Tool invocations are now handled in the UI
228
+ continue;
229
+ } else if (segment.type === "tool-result") {
230
+ // Tool results are now handled in the UI
231
+ continue;
232
+ }
233
+ }
234
 
235
+ return content.trim();
 
236
  }
237
  }
238
 
src/lib/services/segment-formatter.ts ADDED
@@ -0,0 +1,185 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import type { MessageSegment } from "../models/chat-data";
2
+ import type { TodoListView } from "../models/segment-view";
3
+ import { parseTodoList } from "../models/segment-view";
4
+
5
+ export class SegmentFormatter {
6
+ private static todoListCache: Map<string, TodoListView> = new Map();
7
+
8
+ static formatSegmentContent(segment: MessageSegment): string {
9
+ switch (segment.type) {
10
+ case "text":
11
+ return segment.content;
12
+
13
+ case "reasoning":
14
+ return segment.content;
15
+
16
+ case "tool-invocation":
17
+ return this.formatToolInvocation(segment);
18
+
19
+ case "tool-result":
20
+ return this.formatToolResult(segment);
21
+
22
+ default:
23
+ return segment.content;
24
+ }
25
+ }
26
+
27
+ static formatToolInvocation(segment: MessageSegment): string {
28
+ const args = segment.toolArgs
29
+ ? JSON.stringify(segment.toolArgs, null, 2)
30
+ : "No arguments";
31
+
32
+ return `Tool: ${segment.toolName}\nArguments:\n${args}`;
33
+ }
34
+
35
+ static formatToolResult(segment: MessageSegment): string {
36
+ if (segment.toolError) {
37
+ return `❌ Error: ${segment.toolError}`;
38
+ }
39
+
40
+ if (segment.toolName?.includes("task")) {
41
+ return this.formatTodoResult(segment);
42
+ }
43
+
44
+ if (segment.toolName === "observe_console") {
45
+ return this.formatConsoleOutput(segment);
46
+ }
47
+
48
+ return segment.toolOutput || segment.content || "No output";
49
+ }
50
+
51
+ static formatTodoResult(segment: MessageSegment): string {
52
+ const content = segment.toolOutput || segment.content;
53
+ const todoList = parseTodoList(content);
54
+
55
+ if (todoList) {
56
+ this.todoListCache.set(segment.id, todoList);
57
+ return this.renderTodoList(todoList);
58
+ }
59
+
60
+ return content;
61
+ }
62
+
63
+ static formatConsoleOutput(segment: MessageSegment): string {
64
+ const output = segment.toolOutput || segment.content;
65
+ const lines = output.split("\n");
66
+
67
+ const formatted = lines
68
+ .map((line) => {
69
+ if (line.includes("[error]")) {
70
+ return `🔴 ${line}`;
71
+ } else if (line.includes("[warn]")) {
72
+ return `🟡 ${line}`;
73
+ } else if (line.includes("[info]")) {
74
+ return `🔵 ${line}`;
75
+ } else if (line.includes("[debug]")) {
76
+ return `⚪ ${line}`;
77
+ }
78
+ return line;
79
+ })
80
+ .join("\n");
81
+
82
+ return formatted;
83
+ }
84
+
85
+ static renderTodoList(todoList: TodoListView): string {
86
+ const header = `📋 Tasks (${todoList.completedCount}/${todoList.totalCount} completed)\n`;
87
+ const separator = "─".repeat(40) + "\n";
88
+
89
+ const tasks = todoList.tasks
90
+ .map((task) => `${task.emoji} [${task.id}] ${task.description}`)
91
+ .join("\n");
92
+
93
+ return header + separator + tasks;
94
+ }
95
+
96
+ static getLatestTodoList(): TodoListView | null {
97
+ if (this.todoListCache.size === 0) {
98
+ return null;
99
+ }
100
+
101
+ let latest: TodoListView | null = null;
102
+ let latestTime = 0;
103
+
104
+ for (const todoList of this.todoListCache.values()) {
105
+ if (todoList.lastUpdated > latestTime) {
106
+ latest = todoList;
107
+ latestTime = todoList.lastUpdated;
108
+ }
109
+ }
110
+
111
+ return latest;
112
+ }
113
+
114
+ static shouldCollapseByDefault(segment: MessageSegment): boolean {
115
+ if (segment.type !== "tool-invocation" && segment.type !== "tool-result") {
116
+ return false;
117
+ }
118
+
119
+ if (segment.toolError) {
120
+ return false;
121
+ }
122
+
123
+ if (segment.toolName?.includes("task")) {
124
+ return false;
125
+ }
126
+
127
+ const output = segment.toolOutput || segment.content || "";
128
+ const lineCount = output.split("\n").length;
129
+
130
+ return lineCount > 10;
131
+ }
132
+
133
+ static getSegmentIcon(segment: MessageSegment): string {
134
+ const iconMap: Record<string, string> = {
135
+ text: "💬",
136
+ reasoning: "🤔",
137
+ "tool-invocation": "🔧",
138
+ "tool-result": "📊",
139
+ };
140
+
141
+ if (segment.toolName) {
142
+ const toolIcons: Record<string, string> = {
143
+ plan_tasks: "📋",
144
+ update_task: "✏️",
145
+ view_tasks: "👀",
146
+ observe_console: "📺",
147
+ read_file: "📖",
148
+ write_file: "✍️",
149
+ edit_file: "✏️",
150
+ };
151
+ return toolIcons[segment.toolName] || iconMap[segment.type] || "📄";
152
+ }
153
+
154
+ return iconMap[segment.type] || "📄";
155
+ }
156
+
157
+ static formatDuration(ms: number): string {
158
+ if (ms < 1000) {
159
+ return `${ms}ms`;
160
+ } else if (ms < 60000) {
161
+ return `${(ms / 1000).toFixed(1)}s`;
162
+ } else {
163
+ const minutes = Math.floor(ms / 60000);
164
+ const seconds = Math.floor((ms % 60000) / 1000);
165
+ return `${minutes}m ${seconds}s`;
166
+ }
167
+ }
168
+
169
+ static truncateContent(content: string, maxLength: number = 100): string {
170
+ if (content.length <= maxLength) {
171
+ return content;
172
+ }
173
+
174
+ const truncated = content.substring(0, maxLength);
175
+ const lastSpace = truncated.lastIndexOf(" ");
176
+
177
+ if (lastSpace > maxLength * 0.8) {
178
+ return truncated.substring(0, lastSpace) + "...";
179
+ }
180
+
181
+ return truncated + "...";
182
+ }
183
+ }
184
+
185
+ export const segmentFormatter = new SegmentFormatter();