Spaces:
Running
Running
Commit
·
bc7e9cd
1
Parent(s):
12673bf
refactor
Browse filesThis view is limited to 50 files because it contains too many changes.
See raw diff
- agents.md +155 -0
- bun.lock +214 -39
- eslint.config.js +4 -0
- layers/structure.md +1 -2
- llms.txt +0 -1140
- package.json +7 -4
- src/App.svelte +4 -0
- src/lib/components/Editor.svelte +32 -12
- src/lib/components/chat/ChatPanel.svelte +251 -653
- src/lib/components/chat/ExampleMessages.svelte +168 -172
- src/lib/components/chat/InProgressBlock.svelte +0 -474
- src/lib/components/chat/MarkdownRenderer.svelte +0 -202
- src/lib/components/chat/Message.svelte +76 -0
- src/lib/components/chat/MessageContent.svelte +74 -0
- src/lib/components/chat/MessageInput.svelte +294 -0
- src/lib/components/chat/MessageList.svelte +169 -0
- src/lib/components/chat/MessageSegment.svelte +0 -70
- src/lib/components/chat/ReasoningBlock.svelte +0 -293
- src/lib/components/chat/StreamingIndicator.svelte +0 -157
- src/lib/components/chat/StreamingText.svelte +0 -146
- src/lib/components/chat/TextRenderer.svelte +68 -0
- src/lib/components/chat/ToolInvocation.svelte +0 -520
- src/lib/components/chat/context.md +22 -16
- src/lib/components/chat/segments/TodoSegment.svelte +128 -0
- src/lib/components/chat/segments/ToolBlock.svelte +236 -0
- src/lib/components/editor/CodeEditor.svelte +3 -9
- src/lib/components/game/GameCanvas.svelte +8 -10
- src/lib/components/layout/AppHeader.svelte +5 -5
- src/lib/controllers/animation-controller.ts +190 -0
- src/lib/controllers/chat-controller.ts +116 -0
- src/lib/controllers/context.md +34 -0
- src/lib/models/chat-data.ts +89 -0
- src/lib/models/context.md +28 -0
- src/lib/models/segment-view.ts +188 -0
- src/lib/server/api.ts +30 -10
- src/lib/server/console-buffer.ts +14 -5
- src/lib/server/context.md +13 -12
- src/lib/server/documentation.ts +1 -1
- src/lib/server/langgraph-agent.ts +91 -105
- src/lib/server/mcp-client.ts +713 -0
- src/lib/server/task-tracker.ts +1 -1
- src/lib/server/tools.ts +30 -305
- src/lib/services/agent.ts +12 -75
- src/lib/services/content-manager.ts +248 -0
- src/lib/services/context.md +21 -31
- src/lib/services/game-engine.ts +129 -79
- src/lib/services/html-document-parser.ts +56 -0
- src/lib/services/html-parser.ts +0 -38
- src/lib/services/message-handler.ts +168 -131
- 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.
|
| 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].
|
| 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].
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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].
|
| 152 |
|
| 153 |
-
"@rollup/rollup-android-arm64": ["@rollup/[email protected].
|
| 154 |
|
| 155 |
-
"@rollup/rollup-darwin-arm64": ["@rollup/[email protected].
|
| 156 |
|
| 157 |
-
"@rollup/rollup-darwin-x64": ["@rollup/[email protected].
|
| 158 |
|
| 159 |
-
"@rollup/rollup-freebsd-arm64": ["@rollup/[email protected].
|
| 160 |
|
| 161 |
-
"@rollup/rollup-freebsd-x64": ["@rollup/[email protected].
|
| 162 |
|
| 163 |
-
"@rollup/rollup-linux-arm-gnueabihf": ["@rollup/[email protected].
|
| 164 |
|
| 165 |
-
"@rollup/rollup-linux-arm-musleabihf": ["@rollup/[email protected].
|
| 166 |
|
| 167 |
-
"@rollup/rollup-linux-arm64-gnu": ["@rollup/[email protected].
|
| 168 |
|
| 169 |
-
"@rollup/rollup-linux-arm64-musl": ["@rollup/[email protected].
|
| 170 |
|
| 171 |
-
"@rollup/rollup-linux-
|
| 172 |
|
| 173 |
-
"@rollup/rollup-linux-ppc64-gnu": ["@rollup/[email protected].
|
| 174 |
|
| 175 |
-
"@rollup/rollup-linux-riscv64-gnu": ["@rollup/[email protected].
|
| 176 |
|
| 177 |
-
"@rollup/rollup-linux-riscv64-musl": ["@rollup/[email protected].
|
| 178 |
|
| 179 |
-
"@rollup/rollup-linux-s390x-gnu": ["@rollup/[email protected].
|
| 180 |
|
| 181 |
-
"@rollup/rollup-linux-x64-gnu": ["@rollup/[email protected].
|
| 182 |
|
| 183 |
-
"@rollup/rollup-linux-x64-musl": ["@rollup/[email protected].
|
| 184 |
|
| 185 |
-
"@rollup/rollup-openharmony-arm64": ["@rollup/[email protected].
|
| 186 |
|
| 187 |
-
"@rollup/rollup-win32-arm64-msvc": ["@rollup/[email protected].
|
| 188 |
|
| 189 |
-
"@rollup/rollup-win32-ia32-msvc": ["@rollup/[email protected].
|
| 190 |
|
| 191 |
-
"@rollup/rollup-win32-x64-msvc": ["@rollup/[email protected].
|
| 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.
|
| 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.
|
|
|
|
|
|
|
| 222 |
|
| 223 |
-
"@typescript-eslint/
|
| 224 |
|
| 225 |
-
"@typescript-eslint/
|
| 226 |
|
| 227 |
-
"@typescript-eslint/
|
| 228 |
|
| 229 |
-
"@typescript-eslint/
|
| 230 |
|
| 231 |
-
"@typescript-eslint/
|
| 232 |
|
| 233 |
-
"@typescript-eslint/
|
| 234 |
|
| 235 |
-
"@typescript-eslint/
|
| 236 |
|
| 237 |
-
"@typescript-eslint/
|
| 238 |
|
| 239 |
-
"
|
| 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@
|
| 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].
|
|
|
|
|
|
|
| 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.
|
|
|
|
|
|
|
| 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 |
-
"
|
|
|
|
|
|
|
| 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 |
-
├──
|
| 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 |
-
"
|
| 13 |
-
"
|
| 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.
|
| 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
|
| 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:
|
| 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 |
-
|
| 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 |
-
|
| 3 |
-
|
| 4 |
-
|
| 5 |
-
|
| 6 |
-
|
| 7 |
-
|
| 8 |
-
|
| 9 |
-
|
| 10 |
-
|
| 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 |
-
|
|
|
|
|
|
|
|
|
|
| 115 |
|
| 116 |
-
|
| 117 |
-
|
| 118 |
-
|
| 119 |
-
isUserScrolling = false;
|
| 120 |
-
}
|
| 121 |
}
|
| 122 |
|
| 123 |
-
|
| 124 |
-
|
| 125 |
-
|
| 126 |
-
|
|
|
|
| 127 |
}
|
|
|
|
| 128 |
|
| 129 |
-
|
| 130 |
-
|
| 131 |
-
|
| 132 |
-
|
| 133 |
-
|
| 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 |
-
|
| 152 |
-
|
| 153 |
-
|
| 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 |
-
|
| 175 |
-
|
| 176 |
-
|
| 177 |
-
}
|
| 178 |
-
}
|
| 179 |
|
| 180 |
-
|
| 181 |
-
|
| 182 |
-
|
| 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 |
-
|
| 200 |
-
|
| 201 |
-
|
| 202 |
-
handleSubmit();
|
| 203 |
-
}
|
| 204 |
-
}
|
| 205 |
|
| 206 |
-
|
| 207 |
-
|
| 208 |
-
|
| 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 |
-
|
| 227 |
-
|
| 228 |
-
|
| 229 |
-
scale: 0.95,
|
| 230 |
-
duration: 0.1,
|
| 231 |
-
ease: "power2.in",
|
| 232 |
-
});
|
| 233 |
-
}
|
| 234 |
-
}
|
| 235 |
|
| 236 |
-
|
| 237 |
-
|
| 238 |
-
|
| 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 |
-
|
| 267 |
-
|
| 268 |
-
|
| 269 |
-
|
| 270 |
-
|
| 271 |
-
|
| 272 |
-
|
| 273 |
-
|
| 274 |
-
|
| 275 |
-
|
| 276 |
-
|
| 277 |
-
|
| 278 |
-
|
| 279 |
-
|
| 280 |
-
|
| 281 |
-
|
| 282 |
-
|
| 283 |
-
|
| 284 |
-
|
| 285 |
-
|
| 286 |
-
|
| 287 |
-
|
| 288 |
-
|
| 289 |
-
|
| 290 |
-
|
| 291 |
-
|
| 292 |
-
|
| 293 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 294 |
</button>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 295 |
{/if}
|
| 296 |
-
|
| 297 |
-
|
| 298 |
-
|
| 299 |
-
|
| 300 |
-
|
| 301 |
-
|
| 302 |
-
|
| 303 |
-
|
| 304 |
-
|
| 305 |
-
|
| 306 |
-
|
| 307 |
-
|
| 308 |
-
|
| 309 |
-
|
| 310 |
-
|
| 311 |
-
|
| 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 |
-
|
| 393 |
-
|
| 394 |
-
|
| 395 |
-
|
| 396 |
-
|
| 397 |
-
|
| 398 |
-
|
| 399 |
-
|
| 400 |
-
|
| 401 |
-
|
| 402 |
-
|
| 403 |
-
|
| 404 |
-
|
| 405 |
-
|
| 406 |
-
|
| 407 |
-
|
| 408 |
-
|
| 409 |
-
|
| 410 |
-
|
| 411 |
-
|
| 412 |
-
|
| 413 |
-
|
| 414 |
-
|
| 415 |
-
|
| 416 |
-
|
| 417 |
-
|
| 418 |
-
|
| 419 |
-
|
| 420 |
-
|
| 421 |
-
|
| 422 |
-
|
| 423 |
-
|
| 424 |
-
|
| 425 |
-
|
| 426 |
-
|
| 427 |
-
|
| 428 |
-
|
| 429 |
-
|
| 430 |
-
|
| 431 |
-
|
| 432 |
-
|
| 433 |
-
|
| 434 |
-
|
| 435 |
-
|
| 436 |
-
|
| 437 |
-
|
| 438 |
-
|
| 439 |
-
|
| 440 |
-
|
| 441 |
-
|
| 442 |
-
|
| 443 |
-
|
| 444 |
-
|
| 445 |
-
|
| 446 |
-
|
| 447 |
-
|
| 448 |
-
|
| 449 |
-
|
| 450 |
-
|
| 451 |
-
|
| 452 |
-
|
| 453 |
-
|
| 454 |
-
|
| 455 |
-
|
| 456 |
-
|
| 457 |
-
|
| 458 |
-
|
| 459 |
-
|
| 460 |
-
|
| 461 |
-
|
| 462 |
-
|
| 463 |
-
|
| 464 |
-
|
| 465 |
-
|
| 466 |
-
|
| 467 |
-
|
| 468 |
-
|
| 469 |
-
|
| 470 |
-
|
| 471 |
-
|
| 472 |
-
|
| 473 |
-
|
| 474 |
-
|
| 475 |
-
|
| 476 |
-
.
|
| 477 |
-
|
| 478 |
-
|
| 479 |
-
|
| 480 |
-
|
| 481 |
-
|
| 482 |
-
|
| 483 |
-
|
| 484 |
-
|
| 485 |
-
|
| 486 |
-
|
| 487 |
-
|
| 488 |
-
|
| 489 |
-
|
| 490 |
-
|
| 491 |
-
|
| 492 |
-
|
| 493 |
-
|
| 494 |
-
|
| 495 |
-
|
| 496 |
-
|
| 497 |
-
|
| 498 |
-
|
| 499 |
-
|
| 500 |
-
|
| 501 |
-
|
| 502 |
-
|
| 503 |
-
|
| 504 |
-
.
|
| 505 |
-
|
| 506 |
-
|
| 507 |
-
|
| 508 |
-
|
| 509 |
-
|
| 510 |
-
|
| 511 |
-
|
| 512 |
-
|
| 513 |
-
|
| 514 |
-
|
| 515 |
-
|
| 516 |
-
|
| 517 |
-
|
| 518 |
-
|
| 519 |
-
|
| 520 |
-
|
| 521 |
-
|
| 522 |
-
|
| 523 |
-
|
| 524 |
-
|
| 525 |
-
|
| 526 |
-
|
| 527 |
-
|
| 528 |
-
|
| 529 |
-
|
| 530 |
-
|
| 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 |
-
|
| 3 |
-
|
| 4 |
-
|
| 5 |
-
|
| 6 |
-
|
| 7 |
-
|
| 8 |
-
|
| 9 |
-
|
| 10 |
-
|
| 11 |
-
|
| 12 |
-
|
| 13 |
-
|
| 14 |
-
|
| 15 |
-
|
| 16 |
-
|
| 17 |
-
|
| 18 |
-
|
| 19 |
-
|
| 20 |
-
|
| 21 |
-
|
| 22 |
-
|
| 23 |
-
|
| 24 |
-
|
| 25 |
-
|
| 26 |
-
|
| 27 |
-
|
| 28 |
-
|
| 29 |
-
|
| 30 |
-
|
| 31 |
-
|
| 32 |
-
|
| 33 |
-
});
|
| 34 |
});
|
|
|
|
| 35 |
|
| 36 |
-
|
| 37 |
-
|
| 38 |
-
|
| 39 |
-
|
| 40 |
-
|
| 41 |
-
});
|
| 42 |
});
|
| 43 |
-
|
| 44 |
-
|
| 45 |
-
|
| 46 |
-
|
| 47 |
-
|
| 48 |
-
|
| 49 |
-
|
| 50 |
-
|
| 51 |
-
|
| 52 |
-
|
| 53 |
-
|
| 54 |
-
|
| 55 |
-
|
| 56 |
-
|
| 57 |
-
}
|
| 58 |
-
|
| 59 |
-
|
| 60 |
-
|
| 61 |
-
|
| 62 |
-
|
| 63 |
-
|
| 64 |
-
|
| 65 |
-
|
| 66 |
-
|
| 67 |
-
|
| 68 |
-
}
|
| 69 |
-
|
| 70 |
-
|
| 71 |
-
|
| 72 |
-
|
| 73 |
-
|
| 74 |
-
|
| 75 |
-
|
| 76 |
-
|
| 77 |
-
}
|
| 78 |
-
|
| 79 |
-
|
| 80 |
-
|
| 81 |
-
|
| 82 |
-
|
| 83 |
-
|
| 84 |
-
|
| 85 |
-
|
| 86 |
-
}
|
|
|
|
| 87 |
</script>
|
| 88 |
|
| 89 |
<div class="examples-container" transition:fade={{ duration: 200 }}>
|
| 90 |
-
|
| 91 |
-
|
| 92 |
-
|
| 93 |
-
|
| 94 |
-
|
| 95 |
-
|
| 96 |
-
|
| 97 |
-
|
| 98 |
-
|
| 99 |
-
|
| 100 |
-
|
| 101 |
-
|
| 102 |
-
|
| 103 |
-
|
| 104 |
-
|
| 105 |
-
|
| 106 |
-
|
| 107 |
-
|
| 108 |
-
|
| 109 |
</div>
|
| 110 |
|
| 111 |
<style>
|
| 112 |
-
|
| 113 |
-
|
| 114 |
-
|
| 115 |
-
|
| 116 |
-
|
| 117 |
-
|
| 118 |
-
|
| 119 |
-
|
| 120 |
-
|
| 121 |
-
|
| 122 |
-
|
| 123 |
-
|
| 124 |
-
|
| 125 |
-
|
| 126 |
-
|
| 127 |
-
|
| 128 |
-
|
| 129 |
-
|
| 130 |
-
|
| 131 |
-
|
| 132 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 133 |
}
|
| 134 |
-
|
| 135 |
-
|
| 136 |
-
0%, 100% {
|
| 137 |
-
opacity: 0.5;
|
| 138 |
-
}
|
| 139 |
-
50% {
|
| 140 |
-
opacity: 0.8;
|
| 141 |
-
}
|
| 142 |
}
|
| 143 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 144 |
.examples-grid {
|
| 145 |
-
|
| 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
|
| 4 |
|
| 5 |
## Components
|
| 6 |
|
| 7 |
-
- `ChatPanel.svelte` - Main
|
| 8 |
-
- `
|
| 9 |
-
- `
|
| 10 |
-
- `
|
| 11 |
-
- `
|
| 12 |
-
- `
|
| 13 |
-
- `
|
| 14 |
-
- `
|
|
|
|
| 15 |
|
| 16 |
## Architecture
|
| 17 |
|
| 18 |
-
|
| 19 |
-
|
| 20 |
-
-
|
| 21 |
-
-
|
| 22 |
-
-
|
| 23 |
-
|
| 24 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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 {
|
| 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 |
-
|
| 13 |
-
|
| 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 {
|
| 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 ($
|
| 16 |
-
previousContent = $
|
| 17 |
clearTimeout(reloadTimer);
|
| 18 |
|
| 19 |
const now = Date.now();
|
|
@@ -25,18 +24,17 @@
|
|
| 25 |
}
|
| 26 |
lastRestartTime = currentTime;
|
| 27 |
|
| 28 |
-
|
| 29 |
-
await gameEngine.start(world, scripts);
|
| 30 |
}, 1500);
|
| 31 |
}
|
| 32 |
}
|
| 33 |
|
| 34 |
onMount(async () => {
|
| 35 |
-
previousContent = $
|
|
|
|
| 36 |
setTimeout(async () => {
|
| 37 |
-
if (!$gameStore.instance && $gameStore.isAutoRunning) {
|
| 38 |
-
|
| 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 {
|
| 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 |
-
|
| 13 |
-
|
|
|
|
| 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: {
|
|
|
|
|
|
|
|
|
|
| 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() -
|
| 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) =>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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
|
| 8 |
-
- **langgraph-agent.ts** - LangGraph agent with
|
| 9 |
-
- **
|
| 10 |
-
- **
|
| 11 |
-
- **console-buffer.ts** - Console message
|
| 12 |
-
- **documentation.ts** - VibeGame documentation loader
|
| 13 |
|
| 14 |
## Architecture
|
| 15 |
|
| 16 |
-
LangGraph
|
| 17 |
|
| 18 |
-
-
|
| 19 |
-
-
|
|
|
|
|
|
|
| 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 |
-
- `
|
| 29 |
-
- `
|
| 30 |
-
- `
|
| 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 = "
|
| 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 |
-
|
| 11 |
-
|
| 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 |
-
|
| 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
|
| 313 |
-
|
| 314 |
-
|
| 315 |
-
-
|
| 316 |
-
-
|
| 317 |
-
-
|
| 318 |
-
-
|
| 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 |
-
|
| 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 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 336 |
|
| 337 |
-
|
| 338 |
-
|
| 339 |
-
|
|
|
|
|
|
|
| 340 |
|
| 341 |
-
|
| 342 |
-
|
|
|
|
|
|
|
|
|
|
| 343 |
|
| 344 |
-
|
| 345 |
-
|
|
|
|
|
|
|
| 346 |
|
| 347 |
-
|
| 348 |
-
|
| 349 |
-
|
| 350 |
|
| 351 |
-
|
| 352 |
-
Example: TOOL: read_editor ARGS: {}
|
| 353 |
|
| 354 |
-
|
| 355 |
-
|
|
|
|
|
|
|
| 356 |
|
| 357 |
-
|
| 358 |
-
|
|
|
|
| 359 |
|
| 360 |
-
|
| 361 |
-
|
|
|
|
|
|
|
| 362 |
|
| 363 |
-
9. observe_console - Check console for errors
|
| 364 |
-
Example: TOOL: observe_console ARGS: {}
|
| 365 |
|
| 366 |
-
|
| 367 |
-
|
| 368 |
-
|
| 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 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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 (
|
| 515 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 516 |
|
| 517 |
if (call.name === "edit_editor" && call.args.oldText) {
|
| 518 |
const oldText = call.args.oldText as string;
|
| 519 |
|
| 520 |
-
if (oldText.split(
|
| 521 |
results.push(
|
| 522 |
new ToolMessage({
|
| 523 |
-
content: `Error: The edit is too large (${oldText.split(
|
| 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 |
-
|
| 564 |
-
|
| 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 |
-
|
| 583 |
-
|
| 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 {
|
|
|
|
|
|
|
|
|
|
| 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 {
|
| 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 |
-
|
| 306 |
-
|
| 307 |
-
|
| 308 |
-
|
| 309 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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(
|
|
|
|
|
|
|
| 17 |
websocketService.onMessage(this.handleMessage.bind(this)),
|
| 18 |
);
|
| 19 |
|
|
@@ -29,39 +29,15 @@ export class AgentService {
|
|
| 29 |
}
|
| 30 |
|
| 31 |
sendMessage(content: string): void {
|
| 32 |
-
|
| 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 |
-
|
| 57 |
-
|
| 58 |
-
}
|
| 59 |
|
| 60 |
-
|
| 61 |
-
|
| 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 |
-
|
| 4 |
|
| 5 |
## Purpose
|
| 6 |
|
| 7 |
-
|
| 8 |
-
- Provide clean APIs for operations
|
| 9 |
-
- Keep components simple and declarative
|
| 10 |
|
| 11 |
## Layout
|
| 12 |
|
| 13 |
```
|
| 14 |
services/
|
| 15 |
-
├── context.md
|
| 16 |
-
├──
|
| 17 |
-
├──
|
| 18 |
-
├── websocket.ts
|
| 19 |
-
├── message-handler.ts
|
| 20 |
-
├── game-engine.ts
|
| 21 |
-
├──
|
| 22 |
-
|
|
|
|
| 23 |
```
|
| 24 |
|
| 25 |
## Scope
|
| 26 |
|
| 27 |
-
- In-scope:
|
| 28 |
-
- Out-of-scope: UI rendering, state
|
| 29 |
|
| 30 |
## Entrypoints
|
| 31 |
|
| 32 |
-
- `
|
| 33 |
-
- `
|
| 34 |
-
- `
|
| 35 |
-
- `
|
| 36 |
-
- `
|
| 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 |
-
-
|
| 50 |
- VibeGame for game engine
|
| 51 |
-
-
|
| 52 |
-
-
|
|
|
|
| 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
|
| 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 |
-
|
| 31 |
uiStore.setError(null);
|
| 32 |
|
| 33 |
try {
|
| 34 |
-
const
|
| 35 |
-
|
| 36 |
-
|
| 37 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 38 |
|
| 39 |
-
|
| 40 |
-
|
| 41 |
-
|
| 42 |
-
|
| 43 |
-
|
| 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 |
-
|
|
|
|
| 99 |
|
| 100 |
-
|
| 101 |
-
|
| 102 |
-
|
|
|
|
|
|
|
| 103 |
|
| 104 |
-
|
| 105 |
-
|
| 106 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 107 |
} catch (error: unknown) {
|
| 108 |
const errorMsg = error instanceof Error ? error.message : String(error);
|
| 109 |
uiStore.setError(errorMsg);
|
| 110 |
-
|
| 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 |
-
|
| 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 |
-
|
| 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 {
|
| 3 |
-
import {
|
| 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
|
| 17 |
-
|
| 18 |
-
|
| 19 |
-
|
| 20 |
|
| 21 |
handleMessage(message: WebSocketMessage): void {
|
| 22 |
-
|
| 23 |
-
status:
|
| 24 |
-
|
| 25 |
-
|
| 26 |
-
|
| 27 |
-
|
| 28 |
-
|
| 29 |
-
|
| 30 |
-
|
| 31 |
-
|
| 32 |
-
|
| 33 |
-
|
| 34 |
-
|
| 35 |
-
|
| 36 |
-
|
| 37 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 38 |
}
|
| 39 |
}
|
| 40 |
|
| 41 |
private handleStatus(message: WebSocketMessage): void {
|
| 42 |
const { processing, connected } = message.payload;
|
| 43 |
-
|
| 44 |
if (processing !== undefined) {
|
| 45 |
-
|
| 46 |
}
|
| 47 |
if (connected !== undefined) {
|
| 48 |
-
|
| 49 |
}
|
| 50 |
}
|
| 51 |
|
| 52 |
private handleStreamStart(message: WebSocketMessage): void {
|
| 53 |
const messageId =
|
| 54 |
(message.payload.messageId as string) || `assistant_${Date.now()}`;
|
| 55 |
-
this.
|
| 56 |
-
this.
|
|
|
|
|
|
|
| 57 |
|
| 58 |
-
|
| 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 |
-
|
| 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(
|
| 88 |
-
|
| 89 |
-
|
| 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 |
-
|
| 98 |
-
|
| 99 |
-
|
| 100 |
-
this.streamState.currentStreamId = null;
|
| 101 |
-
this.streamState.streamingContent = "";
|
| 102 |
}
|
| 103 |
|
| 104 |
private handleChat(message: WebSocketMessage): void {
|
| 105 |
-
|
| 106 |
-
|
| 107 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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 |
-
|
| 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 |
-
|
| 136 |
}
|
| 137 |
}
|
| 138 |
|
| 139 |
private handleSegmentStart(message: WebSocketMessage): void {
|
| 140 |
-
const
|
| 141 |
-
|
| 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
|
| 151 |
-
id: segmentId,
|
| 152 |
-
type: segmentType,
|
| 153 |
content: "",
|
| 154 |
-
toolName:
|
| 155 |
-
toolArgs:
|
| 156 |
startTime: Date.now(),
|
| 157 |
streaming: segmentType === "text",
|
| 158 |
-
toolStatus: segmentType === "tool-invocation" ? "pending" : undefined,
|
| 159 |
};
|
| 160 |
|
| 161 |
-
|
|
|
|
| 162 |
}
|
| 163 |
|
| 164 |
private handleSegmentToken(message: WebSocketMessage): void {
|
| 165 |
-
const
|
| 166 |
-
|
| 167 |
-
|
| 168 |
-
|
| 169 |
-
if (
|
| 170 |
-
|
| 171 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 172 |
}
|
| 173 |
-
|
| 174 |
-
agentStore.updateSegmentContent(messageId, segmentId, token);
|
| 175 |
}
|
| 176 |
|
| 177 |
private handleSegmentEnd(message: WebSocketMessage): void {
|
| 178 |
-
const
|
| 179 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 180 |
|
| 181 |
-
|
| 182 |
-
|
| 183 |
-
return;
|
| 184 |
}
|
|
|
|
| 185 |
|
| 186 |
-
|
| 187 |
-
|
| 188 |
-
|
| 189 |
-
|
| 190 |
-
|
| 191 |
-
|
| 192 |
-
|
| 193 |
-
|
| 194 |
-
|
| 195 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 196 |
|
| 197 |
-
|
| 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();
|