Spaces:
Running
Running
Commit
·
7ad6991
1
Parent(s):
66ed511
examples prompts, stop button
Browse files- eslint.config.js +2 -0
- src/lib/components/chat/ChatPanel.svelte +169 -37
- src/lib/components/chat/ExampleMessages.svelte +179 -0
- src/lib/components/chat/context.md +4 -2
- src/lib/components/layout/AppHeader.svelte +51 -3
- src/lib/server/api.ts +43 -6
- src/lib/server/context.md +4 -2
- src/lib/server/langgraph-agent.ts +22 -3
- src/lib/services/agent.ts +12 -0
- src/lib/services/console-sync.ts +3 -1
eslint.config.js
CHANGED
|
@@ -35,6 +35,8 @@ export default [
|
|
| 35 |
clearInterval: "readonly",
|
| 36 |
sessionStorage: "readonly",
|
| 37 |
localStorage: "readonly",
|
|
|
|
|
|
|
| 38 |
},
|
| 39 |
},
|
| 40 |
plugins: {
|
|
|
|
| 35 |
clearInterval: "readonly",
|
| 36 |
sessionStorage: "readonly",
|
| 37 |
localStorage: "readonly",
|
| 38 |
+
AbortController: "readonly",
|
| 39 |
+
AbortSignal: "readonly",
|
| 40 |
},
|
| 41 |
},
|
| 42 |
plugins: {
|
src/lib/components/chat/ChatPanel.svelte
CHANGED
|
@@ -9,12 +9,15 @@
|
|
| 9 |
import MarkdownRenderer from "./MarkdownRenderer.svelte";
|
| 10 |
import InProgressBlock from "./InProgressBlock.svelte";
|
| 11 |
import MessageSegment from "./MessageSegment.svelte";
|
|
|
|
| 12 |
|
| 13 |
let inputValue = "";
|
| 14 |
let messagesContainer: HTMLDivElement;
|
| 15 |
let sendButton: HTMLButtonElement;
|
|
|
|
| 16 |
let inputTextarea: HTMLTextAreaElement;
|
| 17 |
let authPromptBtn: HTMLButtonElement;
|
|
|
|
| 18 |
|
| 19 |
let hasConnected = false;
|
| 20 |
|
|
@@ -134,6 +137,54 @@
|
|
| 134 |
}
|
| 135 |
}
|
| 136 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 137 |
function handleKeydown(event: KeyboardEvent) {
|
| 138 |
if (event.key === "Enter" && !event.shiftKey) {
|
| 139 |
event.preventDefault();
|
|
@@ -182,8 +233,8 @@
|
|
| 182 |
}
|
| 183 |
|
| 184 |
$: {
|
| 185 |
-
if (sendButton) {
|
| 186 |
-
if (!$authStore.isAuthenticated || !$isConnected ||
|
| 187 |
gsap.to(sendButton, {
|
| 188 |
opacity: 0.3,
|
| 189 |
duration: 0.2,
|
|
@@ -201,6 +252,24 @@
|
|
| 201 |
</script>
|
| 202 |
|
| 203 |
<div class="chat-panel">
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 204 |
{#if isUserScrolling}
|
| 205 |
<button
|
| 206 |
class="scroll-to-bottom"
|
|
@@ -224,7 +293,7 @@
|
|
| 224 |
</div>
|
| 225 |
{:else}
|
| 226 |
{#if $agentStore.messages.length === 0 && $isConnected}
|
| 227 |
-
<
|
| 228 |
{/if}
|
| 229 |
|
| 230 |
{#each $agentStore.messages as message (message.id)}
|
|
@@ -282,19 +351,30 @@
|
|
| 282 |
disabled={!$authStore.isAuthenticated || !$isConnected || $isProcessing}
|
| 283 |
rows="1"
|
| 284 |
/>
|
| 285 |
-
|
| 286 |
-
|
| 287 |
-
|
| 288 |
-
|
| 289 |
-
|
| 290 |
-
|
| 291 |
-
|
| 292 |
-
|
| 293 |
-
|
| 294 |
-
|
| 295 |
-
|
| 296 |
-
|
| 297 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 298 |
</div>
|
| 299 |
</div>
|
| 300 |
|
|
@@ -309,6 +389,51 @@
|
|
| 309 |
position: relative;
|
| 310 |
}
|
| 311 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 312 |
.scroll-to-bottom {
|
| 313 |
position: absolute;
|
| 314 |
bottom: 4.5rem;
|
|
@@ -459,6 +584,34 @@
|
|
| 459 |
cursor: not-allowed;
|
| 460 |
}
|
| 461 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 462 |
::-webkit-scrollbar {
|
| 463 |
width: 6px;
|
| 464 |
}
|
|
@@ -518,25 +671,4 @@
|
|
| 518 |
transform: translateY(0);
|
| 519 |
}
|
| 520 |
|
| 521 |
-
.ready-message {
|
| 522 |
-
color: rgba(255, 255, 255, 0.2);
|
| 523 |
-
font-size: 0.875rem;
|
| 524 |
-
text-align: center;
|
| 525 |
-
font-style: italic;
|
| 526 |
-
display: flex;
|
| 527 |
-
align-items: center;
|
| 528 |
-
justify-content: center;
|
| 529 |
-
height: 100%;
|
| 530 |
-
min-height: 200px;
|
| 531 |
-
animation: gentle-fade 3s ease-in-out infinite;
|
| 532 |
-
}
|
| 533 |
-
|
| 534 |
-
@keyframes gentle-fade {
|
| 535 |
-
0%, 100% {
|
| 536 |
-
opacity: 0.7;
|
| 537 |
-
}
|
| 538 |
-
50% {
|
| 539 |
-
opacity: 1;
|
| 540 |
-
}
|
| 541 |
-
}
|
| 542 |
</style>
|
|
|
|
| 9 |
import MarkdownRenderer from "./MarkdownRenderer.svelte";
|
| 10 |
import InProgressBlock from "./InProgressBlock.svelte";
|
| 11 |
import MessageSegment from "./MessageSegment.svelte";
|
| 12 |
+
import ExampleMessages from "./ExampleMessages.svelte";
|
| 13 |
|
| 14 |
let inputValue = "";
|
| 15 |
let messagesContainer: HTMLDivElement;
|
| 16 |
let sendButton: HTMLButtonElement;
|
| 17 |
+
let stopButton: HTMLButtonElement;
|
| 18 |
let inputTextarea: HTMLTextAreaElement;
|
| 19 |
let authPromptBtn: HTMLButtonElement;
|
| 20 |
+
let clearButton: HTMLButtonElement;
|
| 21 |
|
| 22 |
let hasConnected = false;
|
| 23 |
|
|
|
|
| 137 |
}
|
| 138 |
}
|
| 139 |
|
| 140 |
+
function handleStop() {
|
| 141 |
+
if ($isProcessing) {
|
| 142 |
+
if (stopButton) {
|
| 143 |
+
gsap.to(stopButton, {
|
| 144 |
+
scale: 0.9,
|
| 145 |
+
duration: 0.1,
|
| 146 |
+
ease: "power2.in",
|
| 147 |
+
onComplete: () => {
|
| 148 |
+
if (stopButton) {
|
| 149 |
+
gsap.to(stopButton, {
|
| 150 |
+
scale: 1,
|
| 151 |
+
duration: 0.2,
|
| 152 |
+
ease: "elastic.out(1, 0.5)",
|
| 153 |
+
});
|
| 154 |
+
}
|
| 155 |
+
},
|
| 156 |
+
});
|
| 157 |
+
}
|
| 158 |
+
|
| 159 |
+
agentService.stopConversation();
|
| 160 |
+
}
|
| 161 |
+
}
|
| 162 |
+
|
| 163 |
+
function handleExampleMessage(message: string) {
|
| 164 |
+
if ($authStore.isAuthenticated && $isConnected && !$isProcessing) {
|
| 165 |
+
agentService.sendMessage(message);
|
| 166 |
+
}
|
| 167 |
+
}
|
| 168 |
+
|
| 169 |
+
function handleClearConversation() {
|
| 170 |
+
if (clearButton) {
|
| 171 |
+
gsap.to(clearButton, {
|
| 172 |
+
scale: 0.9,
|
| 173 |
+
duration: 0.1,
|
| 174 |
+
ease: "power2.in",
|
| 175 |
+
onComplete: () => {
|
| 176 |
+
gsap.to(clearButton, {
|
| 177 |
+
scale: 1,
|
| 178 |
+
duration: 0.2,
|
| 179 |
+
ease: "elastic.out(1, 0.5)",
|
| 180 |
+
});
|
| 181 |
+
},
|
| 182 |
+
});
|
| 183 |
+
}
|
| 184 |
+
|
| 185 |
+
agentStore.clearMessages();
|
| 186 |
+
}
|
| 187 |
+
|
| 188 |
function handleKeydown(event: KeyboardEvent) {
|
| 189 |
if (event.key === "Enter" && !event.shiftKey) {
|
| 190 |
event.preventDefault();
|
|
|
|
| 233 |
}
|
| 234 |
|
| 235 |
$: {
|
| 236 |
+
if (sendButton && !$isProcessing) {
|
| 237 |
+
if (!$authStore.isAuthenticated || !$isConnected || !inputValue.trim()) {
|
| 238 |
gsap.to(sendButton, {
|
| 239 |
opacity: 0.3,
|
| 240 |
duration: 0.2,
|
|
|
|
| 252 |
</script>
|
| 253 |
|
| 254 |
<div class="chat-panel">
|
| 255 |
+
<div class="chat-header">
|
| 256 |
+
<span class="chat-title">Chat</span>
|
| 257 |
+
{#if $agentStore.messages.length > 0 && $authStore.isAuthenticated && !$isProcessing}
|
| 258 |
+
<button
|
| 259 |
+
bind:this={clearButton}
|
| 260 |
+
class="clear-button"
|
| 261 |
+
on:click={handleClearConversation}
|
| 262 |
+
title="Clear conversation"
|
| 263 |
+
transition:fade={{ duration: 200 }}
|
| 264 |
+
>
|
| 265 |
+
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
| 266 |
+
<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" />
|
| 267 |
+
</svg>
|
| 268 |
+
<span>Clear</span>
|
| 269 |
+
</button>
|
| 270 |
+
{/if}
|
| 271 |
+
</div>
|
| 272 |
+
|
| 273 |
{#if isUserScrolling}
|
| 274 |
<button
|
| 275 |
class="scroll-to-bottom"
|
|
|
|
| 293 |
</div>
|
| 294 |
{:else}
|
| 295 |
{#if $agentStore.messages.length === 0 && $isConnected}
|
| 296 |
+
<ExampleMessages onSendMessage={handleExampleMessage} />
|
| 297 |
{/if}
|
| 298 |
|
| 299 |
{#each $agentStore.messages as message (message.id)}
|
|
|
|
| 351 |
disabled={!$authStore.isAuthenticated || !$isConnected || $isProcessing}
|
| 352 |
rows="1"
|
| 353 |
/>
|
| 354 |
+
{#if $isProcessing}
|
| 355 |
+
<button
|
| 356 |
+
bind:this={stopButton}
|
| 357 |
+
on:click={handleStop}
|
| 358 |
+
class="stop-btn"
|
| 359 |
+
title="Stop conversation"
|
| 360 |
+
>
|
| 361 |
+
■
|
| 362 |
+
</button>
|
| 363 |
+
{:else}
|
| 364 |
+
<button
|
| 365 |
+
bind:this={sendButton}
|
| 366 |
+
on:click={handleSubmit}
|
| 367 |
+
on:mouseenter={handleButtonMouseEnter}
|
| 368 |
+
on:mouseleave={handleButtonMouseLeave}
|
| 369 |
+
on:mousedown={handleButtonMouseDown}
|
| 370 |
+
on:mouseup={handleButtonMouseUp}
|
| 371 |
+
disabled={!$authStore.isAuthenticated || !$isConnected || !inputValue.trim()}
|
| 372 |
+
class="send-btn"
|
| 373 |
+
title="Send message"
|
| 374 |
+
>
|
| 375 |
+
→
|
| 376 |
+
</button>
|
| 377 |
+
{/if}
|
| 378 |
</div>
|
| 379 |
</div>
|
| 380 |
|
|
|
|
| 389 |
position: relative;
|
| 390 |
}
|
| 391 |
|
| 392 |
+
.chat-header {
|
| 393 |
+
display: flex;
|
| 394 |
+
align-items: center;
|
| 395 |
+
justify-content: space-between;
|
| 396 |
+
padding: 0.5rem 0.75rem;
|
| 397 |
+
background: rgba(0, 0, 0, 0.3);
|
| 398 |
+
border-bottom: 1px solid rgba(255, 255, 255, 0.1);
|
| 399 |
+
min-height: 2.5rem;
|
| 400 |
+
}
|
| 401 |
+
|
| 402 |
+
.chat-title {
|
| 403 |
+
font-size: 0.875rem;
|
| 404 |
+
font-weight: 500;
|
| 405 |
+
color: rgba(255, 255, 255, 0.7);
|
| 406 |
+
}
|
| 407 |
+
|
| 408 |
+
.clear-button {
|
| 409 |
+
display: flex;
|
| 410 |
+
align-items: center;
|
| 411 |
+
gap: 0.25rem;
|
| 412 |
+
padding: 0.25rem 0.5rem;
|
| 413 |
+
background: transparent;
|
| 414 |
+
border: 1px solid rgba(255, 255, 255, 0.2);
|
| 415 |
+
border-radius: 4px;
|
| 416 |
+
color: rgba(255, 255, 255, 0.6);
|
| 417 |
+
font-size: 0.75rem;
|
| 418 |
+
cursor: pointer;
|
| 419 |
+
transition: all 0.2s ease;
|
| 420 |
+
}
|
| 421 |
+
|
| 422 |
+
.clear-button:hover {
|
| 423 |
+
background: rgba(244, 67, 54, 0.1);
|
| 424 |
+
border-color: rgba(244, 67, 54, 0.3);
|
| 425 |
+
color: rgba(244, 67, 54, 0.9);
|
| 426 |
+
}
|
| 427 |
+
|
| 428 |
+
.clear-button:active {
|
| 429 |
+
transform: scale(0.95);
|
| 430 |
+
}
|
| 431 |
+
|
| 432 |
+
.clear-button svg {
|
| 433 |
+
width: 14px;
|
| 434 |
+
height: 14px;
|
| 435 |
+
}
|
| 436 |
+
|
| 437 |
.scroll-to-bottom {
|
| 438 |
position: absolute;
|
| 439 |
bottom: 4.5rem;
|
|
|
|
| 584 |
cursor: not-allowed;
|
| 585 |
}
|
| 586 |
|
| 587 |
+
.stop-btn {
|
| 588 |
+
padding: 0.4rem 0.8rem;
|
| 589 |
+
background: rgba(244, 67, 54, 0.2);
|
| 590 |
+
color: #f44336;
|
| 591 |
+
border: 1px solid rgba(244, 67, 54, 0.3);
|
| 592 |
+
border-radius: 4px;
|
| 593 |
+
cursor: pointer;
|
| 594 |
+
font-size: 1rem;
|
| 595 |
+
transform-origin: center;
|
| 596 |
+
will-change: transform;
|
| 597 |
+
animation: pulse 1.5s ease-in-out infinite;
|
| 598 |
+
}
|
| 599 |
+
|
| 600 |
+
@keyframes pulse {
|
| 601 |
+
0%, 100% {
|
| 602 |
+
opacity: 1;
|
| 603 |
+
}
|
| 604 |
+
50% {
|
| 605 |
+
opacity: 0.6;
|
| 606 |
+
}
|
| 607 |
+
}
|
| 608 |
+
|
| 609 |
+
.stop-btn:hover {
|
| 610 |
+
background: rgba(244, 67, 54, 0.3);
|
| 611 |
+
border-color: rgba(244, 67, 54, 0.5);
|
| 612 |
+
animation: none;
|
| 613 |
+
}
|
| 614 |
+
|
| 615 |
::-webkit-scrollbar {
|
| 616 |
width: 6px;
|
| 617 |
}
|
|
|
|
| 671 |
transform: translateY(0);
|
| 672 |
}
|
| 673 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 674 |
</style>
|
src/lib/components/chat/ExampleMessages.svelte
ADDED
|
@@ -0,0 +1,179 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
<script lang="ts">
|
| 2 |
+
import { onMount } 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 |
+
function handleClick(text: string) {
|
| 37 |
+
onSendMessage(text);
|
| 38 |
+
}
|
| 39 |
+
|
| 40 |
+
function handleMouseEnter(event: MouseEvent) {
|
| 41 |
+
const card = event.currentTarget as HTMLButtonElement;
|
| 42 |
+
gsap.to(card, {
|
| 43 |
+
scale: 1.03,
|
| 44 |
+
boxShadow: "0 6px 24px rgba(65, 105, 225, 0.25)",
|
| 45 |
+
borderColor: "rgba(65, 105, 225, 0.5)",
|
| 46 |
+
duration: 0.1,
|
| 47 |
+
ease: "power3.out"
|
| 48 |
+
});
|
| 49 |
+
}
|
| 50 |
+
|
| 51 |
+
function handleMouseLeave(event: MouseEvent) {
|
| 52 |
+
const card = event.currentTarget as HTMLButtonElement;
|
| 53 |
+
gsap.to(card, {
|
| 54 |
+
scale: 1,
|
| 55 |
+
boxShadow: "0 2px 10px rgba(0, 0, 0, 0.2)",
|
| 56 |
+
borderColor: "rgba(255, 255, 255, 0.05)",
|
| 57 |
+
duration: 0.12,
|
| 58 |
+
ease: "power2.inOut"
|
| 59 |
+
});
|
| 60 |
+
}
|
| 61 |
+
|
| 62 |
+
function handleMouseDown(event: MouseEvent) {
|
| 63 |
+
const card = event.currentTarget as HTMLButtonElement;
|
| 64 |
+
gsap.to(card, {
|
| 65 |
+
scale: 0.97,
|
| 66 |
+
duration: 0.05,
|
| 67 |
+
ease: "power1.in"
|
| 68 |
+
});
|
| 69 |
+
}
|
| 70 |
+
|
| 71 |
+
function handleMouseUp(event: MouseEvent) {
|
| 72 |
+
const card = event.currentTarget as HTMLButtonElement;
|
| 73 |
+
gsap.to(card, {
|
| 74 |
+
scale: 1.03,
|
| 75 |
+
duration: 0.08,
|
| 76 |
+
ease: "back.out(1.7)"
|
| 77 |
+
});
|
| 78 |
+
}
|
| 79 |
+
</script>
|
| 80 |
+
|
| 81 |
+
<div class="examples-container" transition:fade={{ duration: 200 }}>
|
| 82 |
+
<div class="examples-header">
|
| 83 |
+
<span class="header-text">Try an example</span>
|
| 84 |
+
</div>
|
| 85 |
+
<div class="examples-grid">
|
| 86 |
+
{#each examples as example, i}
|
| 87 |
+
<button
|
| 88 |
+
bind:this={exampleCards[i]}
|
| 89 |
+
class="example-card"
|
| 90 |
+
on:click={() => handleClick(example.text)}
|
| 91 |
+
on:mouseenter={handleMouseEnter}
|
| 92 |
+
on:mouseleave={handleMouseLeave}
|
| 93 |
+
on:mousedown={handleMouseDown}
|
| 94 |
+
on:mouseup={handleMouseUp}
|
| 95 |
+
>
|
| 96 |
+
<span class="example-icon">{example.icon}</span>
|
| 97 |
+
<span class="example-text">{example.text}</span>
|
| 98 |
+
</button>
|
| 99 |
+
{/each}
|
| 100 |
+
</div>
|
| 101 |
+
</div>
|
| 102 |
+
|
| 103 |
+
<style>
|
| 104 |
+
.examples-container {
|
| 105 |
+
display: flex;
|
| 106 |
+
flex-direction: column;
|
| 107 |
+
align-items: center;
|
| 108 |
+
justify-content: center;
|
| 109 |
+
padding: 2rem 1rem;
|
| 110 |
+
height: 100%;
|
| 111 |
+
min-height: 300px;
|
| 112 |
+
gap: 1.5rem;
|
| 113 |
+
}
|
| 114 |
+
|
| 115 |
+
.examples-header {
|
| 116 |
+
color: rgba(255, 255, 255, 0.3);
|
| 117 |
+
font-size: 0.75rem;
|
| 118 |
+
text-transform: uppercase;
|
| 119 |
+
letter-spacing: 0.1em;
|
| 120 |
+
font-weight: 500;
|
| 121 |
+
}
|
| 122 |
+
|
| 123 |
+
.header-text {
|
| 124 |
+
animation: gentle-pulse 3s ease-in-out infinite;
|
| 125 |
+
}
|
| 126 |
+
|
| 127 |
+
@keyframes gentle-pulse {
|
| 128 |
+
0%, 100% {
|
| 129 |
+
opacity: 0.5;
|
| 130 |
+
}
|
| 131 |
+
50% {
|
| 132 |
+
opacity: 0.8;
|
| 133 |
+
}
|
| 134 |
+
}
|
| 135 |
+
|
| 136 |
+
.examples-grid {
|
| 137 |
+
display: grid;
|
| 138 |
+
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
|
| 139 |
+
gap: 0.75rem;
|
| 140 |
+
width: 100%;
|
| 141 |
+
max-width: 600px;
|
| 142 |
+
}
|
| 143 |
+
|
| 144 |
+
.example-card {
|
| 145 |
+
display: flex;
|
| 146 |
+
align-items: center;
|
| 147 |
+
gap: 0.5rem;
|
| 148 |
+
padding: 0.5rem 1rem;
|
| 149 |
+
background: rgba(255, 255, 255, 0.02);
|
| 150 |
+
border: 1px solid rgba(255, 255, 255, 0.05);
|
| 151 |
+
border-radius: 6px;
|
| 152 |
+
cursor: pointer;
|
| 153 |
+
transition: all 0.2s ease;
|
| 154 |
+
font-family: "Monaco", "Menlo", monospace;
|
| 155 |
+
font-size: 0.875rem;
|
| 156 |
+
color: rgba(255, 255, 255, 0.85);
|
| 157 |
+
will-change: transform, box-shadow;
|
| 158 |
+
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.2);
|
| 159 |
+
}
|
| 160 |
+
|
| 161 |
+
.example-card:hover {
|
| 162 |
+
background: rgba(65, 105, 225, 0.05);
|
| 163 |
+
}
|
| 164 |
+
|
| 165 |
+
.example-icon {
|
| 166 |
+
font-size: 1rem;
|
| 167 |
+
opacity: 0.8;
|
| 168 |
+
}
|
| 169 |
+
|
| 170 |
+
.example-text {
|
| 171 |
+
flex: 1;
|
| 172 |
+
}
|
| 173 |
+
|
| 174 |
+
@media (max-width: 600px) {
|
| 175 |
+
.examples-grid {
|
| 176 |
+
grid-template-columns: 1fr;
|
| 177 |
+
}
|
| 178 |
+
}
|
| 179 |
+
</style>
|
src/lib/components/chat/context.md
CHANGED
|
@@ -1,10 +1,11 @@
|
|
| 1 |
# Chat Context
|
| 2 |
|
| 3 |
-
AI chat interface with real-time streaming.
|
| 4 |
|
| 5 |
## Components
|
| 6 |
|
| 7 |
-
- `ChatPanel.svelte` - Main chat UI with smooth scroll
|
|
|
|
| 8 |
- `MessageSegment.svelte` - Renders text, tool invocations, and results
|
| 9 |
- `StreamingText.svelte` - Optimized character streaming with state persistence
|
| 10 |
- `ToolInvocation.svelte` - Tool execution and results display
|
|
@@ -19,3 +20,4 @@ AI chat interface with real-time streaming.
|
|
| 19 |
- Batch character processing (3 chars/cycle) at 120 chars/sec
|
| 20 |
- Tool invocations displayed through dedicated segments
|
| 21 |
- Clean separation between text and tool content
|
|
|
|
|
|
| 1 |
# Chat Context
|
| 2 |
|
| 3 |
+
AI chat interface with real-time streaming and conversation control.
|
| 4 |
|
| 5 |
## Components
|
| 6 |
|
| 7 |
+
- `ChatPanel.svelte` - Main chat UI with header bar, clear button, smooth scroll, stop button when processing
|
| 8 |
+
- `ExampleMessages.svelte` - Clickable prompt suggestions when chat is empty
|
| 9 |
- `MessageSegment.svelte` - Renders text, tool invocations, and results
|
| 10 |
- `StreamingText.svelte` - Optimized character streaming with state persistence
|
| 11 |
- `ToolInvocation.svelte` - Tool execution and results display
|
|
|
|
| 20 |
- Batch character processing (3 chars/cycle) at 120 chars/sec
|
| 21 |
- Tool invocations displayed through dedicated segments
|
| 22 |
- Clean separation between text and tool content
|
| 23 |
+
- Abortable conversations via stop button during processing
|
src/lib/components/layout/AppHeader.svelte
CHANGED
|
@@ -66,6 +66,15 @@
|
|
| 66 |
<span class="app-icon">🥕</span>
|
| 67 |
<span class="app-name">VibeGame</span>
|
| 68 |
</div>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 69 |
</div>
|
| 70 |
|
| 71 |
<div class="header-center">
|
|
@@ -137,20 +146,21 @@
|
|
| 137 |
|
| 138 |
.header-left {
|
| 139 |
justify-content: flex-start;
|
|
|
|
| 140 |
}
|
| 141 |
-
|
| 142 |
.app-title {
|
| 143 |
display: flex;
|
| 144 |
align-items: center;
|
| 145 |
gap: 0.5rem;
|
| 146 |
user-select: none;
|
| 147 |
}
|
| 148 |
-
|
| 149 |
.app-icon {
|
| 150 |
font-size: 1.25rem;
|
| 151 |
line-height: 1;
|
| 152 |
}
|
| 153 |
-
|
| 154 |
.app-name {
|
| 155 |
font-size: 0.875rem;
|
| 156 |
font-weight: 600;
|
|
@@ -158,6 +168,44 @@
|
|
| 158 |
letter-spacing: 0.025em;
|
| 159 |
}
|
| 160 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 161 |
.header-center {
|
| 162 |
justify-content: center;
|
| 163 |
}
|
|
|
|
| 66 |
<span class="app-icon">🥕</span>
|
| 67 |
<span class="app-name">VibeGame</span>
|
| 68 |
</div>
|
| 69 |
+
<a href="https://github.com/dylanebert/VibeGame"
|
| 70 |
+
target="_blank"
|
| 71 |
+
rel="noopener noreferrer"
|
| 72 |
+
class="github-link"
|
| 73 |
+
aria-label="View on GitHub">
|
| 74 |
+
<svg width="16" height="16" viewBox="0 0 16 16" fill="currentColor">
|
| 75 |
+
<path d="M8 0C3.58 0 0 3.58 0 8c0 3.54 2.29 6.53 5.47 7.59.4.07.55-.17.55-.38 0-.19-.01-.82-.01-1.49-2.01.37-2.53-.49-2.69-.94-.09-.23-.48-.94-.82-1.13-.28-.15-.68-.52-.01-.53.63-.01 1.08.58 1.23.82.72 1.21 1.87.87 2.33.66.07-.52.28-.87.51-1.07-1.78-.2-3.64-.89-3.64-3.95 0-.87.31-1.59.82-2.15-.08-.2-.36-1.02.08-2.12 0 0 .67-.21 2.2.82.64-.18 1.32-.27 2-.27.68 0 1.36.09 2 .27 1.53-1.04 2.2-.82 2.2-.82.44 1.1.16 1.92.08 2.12.51.56.82 1.27.82 2.15 0 3.07-1.87 3.75-3.65 3.95.29.25.54.73.54 1.48 0 1.07-.01 1.93-.01 2.2 0 .21.15.46.55.38A8.013 8.013 0 0016 8c0-4.42-3.58-8-8-8z"/>
|
| 76 |
+
</svg>
|
| 77 |
+
</a>
|
| 78 |
</div>
|
| 79 |
|
| 80 |
<div class="header-center">
|
|
|
|
| 146 |
|
| 147 |
.header-left {
|
| 148 |
justify-content: flex-start;
|
| 149 |
+
gap: 0.75rem;
|
| 150 |
}
|
| 151 |
+
|
| 152 |
.app-title {
|
| 153 |
display: flex;
|
| 154 |
align-items: center;
|
| 155 |
gap: 0.5rem;
|
| 156 |
user-select: none;
|
| 157 |
}
|
| 158 |
+
|
| 159 |
.app-icon {
|
| 160 |
font-size: 1.25rem;
|
| 161 |
line-height: 1;
|
| 162 |
}
|
| 163 |
+
|
| 164 |
.app-name {
|
| 165 |
font-size: 0.875rem;
|
| 166 |
font-weight: 600;
|
|
|
|
| 168 |
letter-spacing: 0.025em;
|
| 169 |
}
|
| 170 |
|
| 171 |
+
.github-link {
|
| 172 |
+
display: flex;
|
| 173 |
+
align-items: center;
|
| 174 |
+
padding: 0.25rem;
|
| 175 |
+
border-radius: 0.25rem;
|
| 176 |
+
color: rgba(251, 248, 244, 0.5);
|
| 177 |
+
transition: all 0.2s cubic-bezier(0.4, 0, 0.2, 1);
|
| 178 |
+
position: relative;
|
| 179 |
+
}
|
| 180 |
+
|
| 181 |
+
.github-link::before {
|
| 182 |
+
content: '';
|
| 183 |
+
position: absolute;
|
| 184 |
+
inset: 0;
|
| 185 |
+
border-radius: 0.25rem;
|
| 186 |
+
background: rgba(139, 115, 85, 0.08);
|
| 187 |
+
opacity: 0;
|
| 188 |
+
transition: opacity 0.2s ease-out;
|
| 189 |
+
}
|
| 190 |
+
|
| 191 |
+
.github-link:hover {
|
| 192 |
+
color: rgba(251, 248, 244, 0.9);
|
| 193 |
+
transform: scale(1.05);
|
| 194 |
+
}
|
| 195 |
+
|
| 196 |
+
.github-link:hover::before {
|
| 197 |
+
opacity: 1;
|
| 198 |
+
}
|
| 199 |
+
|
| 200 |
+
.github-link:active {
|
| 201 |
+
transform: scale(0.98);
|
| 202 |
+
}
|
| 203 |
+
|
| 204 |
+
.github-link svg {
|
| 205 |
+
width: 16px;
|
| 206 |
+
height: 16px;
|
| 207 |
+
}
|
| 208 |
+
|
| 209 |
.header-center {
|
| 210 |
justify-content: center;
|
| 211 |
}
|
src/lib/server/api.ts
CHANGED
|
@@ -18,7 +18,8 @@ export interface WebSocketMessage {
|
|
| 18 |
| "editor_update"
|
| 19 |
| "editor_sync"
|
| 20 |
| "tool_execution"
|
| 21 |
-
| "console_sync"
|
|
|
|
| 22 |
payload: {
|
| 23 |
content?: string;
|
| 24 |
role?: string;
|
|
@@ -41,7 +42,12 @@ export interface WebSocketMessage {
|
|
| 41 |
class WebSocketManager {
|
| 42 |
private connections: Map<
|
| 43 |
WebSocket,
|
| 44 |
-
{
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 45 |
> = new Map();
|
| 46 |
|
| 47 |
handleConnection(ws: WebSocket, _request: IncomingMessage) {
|
|
@@ -124,6 +130,18 @@ class WebSocketManager {
|
|
| 124 |
}
|
| 125 |
break;
|
| 126 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 127 |
case "chat":
|
| 128 |
try {
|
| 129 |
if (!connectionData?.agent) {
|
|
@@ -137,6 +155,8 @@ class WebSocketManager {
|
|
| 137 |
throw new Error("No message content provided");
|
| 138 |
}
|
| 139 |
|
|
|
|
|
|
|
| 140 |
this.sendMessage(ws, {
|
| 141 |
type: "status",
|
| 142 |
payload: { processing: true },
|
|
@@ -170,6 +190,7 @@ class WebSocketManager {
|
|
| 170 |
});
|
| 171 |
},
|
| 172 |
messageId,
|
|
|
|
| 173 |
);
|
| 174 |
|
| 175 |
connectionData.messages.push(new AIMessage(response));
|
|
@@ -190,10 +211,26 @@ class WebSocketManager {
|
|
| 190 |
});
|
| 191 |
} catch (error) {
|
| 192 |
console.error("Error processing chat message:", error);
|
| 193 |
-
|
| 194 |
-
|
| 195 |
-
|
| 196 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 197 |
}
|
| 198 |
break;
|
| 199 |
|
|
|
|
| 18 |
| "editor_update"
|
| 19 |
| "editor_sync"
|
| 20 |
| "tool_execution"
|
| 21 |
+
| "console_sync"
|
| 22 |
+
| "abort";
|
| 23 |
payload: {
|
| 24 |
content?: string;
|
| 25 |
role?: string;
|
|
|
|
| 42 |
class WebSocketManager {
|
| 43 |
private connections: Map<
|
| 44 |
WebSocket,
|
| 45 |
+
{
|
| 46 |
+
token?: string;
|
| 47 |
+
agent?: LangGraphAgent;
|
| 48 |
+
messages?: BaseMessage[];
|
| 49 |
+
abortController?: AbortController;
|
| 50 |
+
}
|
| 51 |
> = new Map();
|
| 52 |
|
| 53 |
handleConnection(ws: WebSocket, _request: IncomingMessage) {
|
|
|
|
| 130 |
}
|
| 131 |
break;
|
| 132 |
|
| 133 |
+
case "abort":
|
| 134 |
+
if (connectionData?.abortController) {
|
| 135 |
+
connectionData.abortController.abort();
|
| 136 |
+
|
| 137 |
+
this.sendMessage(ws, {
|
| 138 |
+
type: "status",
|
| 139 |
+
payload: { processing: false, message: "Conversation stopped" },
|
| 140 |
+
timestamp: Date.now(),
|
| 141 |
+
});
|
| 142 |
+
}
|
| 143 |
+
break;
|
| 144 |
+
|
| 145 |
case "chat":
|
| 146 |
try {
|
| 147 |
if (!connectionData?.agent) {
|
|
|
|
| 155 |
throw new Error("No message content provided");
|
| 156 |
}
|
| 157 |
|
| 158 |
+
connectionData.abortController = new AbortController();
|
| 159 |
+
|
| 160 |
this.sendMessage(ws, {
|
| 161 |
type: "status",
|
| 162 |
payload: { processing: true },
|
|
|
|
| 190 |
});
|
| 191 |
},
|
| 192 |
messageId,
|
| 193 |
+
connectionData.abortController.signal,
|
| 194 |
);
|
| 195 |
|
| 196 |
connectionData.messages.push(new AIMessage(response));
|
|
|
|
| 211 |
});
|
| 212 |
} catch (error) {
|
| 213 |
console.error("Error processing chat message:", error);
|
| 214 |
+
|
| 215 |
+
if (error instanceof Error && error.name === "AbortError") {
|
| 216 |
+
this.sendMessage(ws, {
|
| 217 |
+
type: "stream_end",
|
| 218 |
+
payload: {
|
| 219 |
+
messageId: `msg_${Date.now()}`,
|
| 220 |
+
content: "",
|
| 221 |
+
},
|
| 222 |
+
timestamp: Date.now(),
|
| 223 |
+
});
|
| 224 |
+
} else {
|
| 225 |
+
this.sendError(
|
| 226 |
+
ws,
|
| 227 |
+
error instanceof Error ? error.message : "Unknown error",
|
| 228 |
+
);
|
| 229 |
+
}
|
| 230 |
+
} finally {
|
| 231 |
+
if (connectionData) {
|
| 232 |
+
connectionData.abortController = undefined;
|
| 233 |
+
}
|
| 234 |
}
|
| 235 |
break;
|
| 236 |
|
src/lib/server/context.md
CHANGED
|
@@ -4,8 +4,8 @@ WebSocket server with LangGraph agent for AI-assisted game development.
|
|
| 4 |
|
| 5 |
## Key Components
|
| 6 |
|
| 7 |
-
- **api.ts** - WebSocket message routing
|
| 8 |
-
- **langgraph-agent.ts** - LangGraph agent with buffered streaming
|
| 9 |
- **tools.ts** - Editor manipulation: full read/write, line-based reading, text/regex search, search-replace editing
|
| 10 |
- **console-buffer.ts** - Console message storage
|
| 11 |
- **documentation.ts** - VibeGame documentation loader
|
|
@@ -17,11 +17,13 @@ LangGraph state machine with real-time streaming:
|
|
| 17 |
- Buffers and filters tool patterns from text segments
|
| 18 |
- Tool invocations handled separately from text content
|
| 19 |
- Explicit message IDs required for all segment operations
|
|
|
|
| 20 |
|
| 21 |
## Message Protocol
|
| 22 |
|
| 23 |
- `auth` - HF token authentication
|
| 24 |
- `chat` - User messages
|
|
|
|
| 25 |
- `stream_start/token/end` - Legacy streaming
|
| 26 |
- `segment_start/token/end` - Segment streaming
|
| 27 |
- `editor_sync` - Sync editor content
|
|
|
|
| 4 |
|
| 5 |
## Key Components
|
| 6 |
|
| 7 |
+
- **api.ts** - WebSocket message routing with abort handling
|
| 8 |
+
- **langgraph-agent.ts** - LangGraph agent with buffered streaming and abort signals
|
| 9 |
- **tools.ts** - Editor manipulation: full read/write, line-based reading, text/regex search, search-replace editing
|
| 10 |
- **console-buffer.ts** - Console message storage
|
| 11 |
- **documentation.ts** - VibeGame documentation loader
|
|
|
|
| 17 |
- Buffers and filters tool patterns from text segments
|
| 18 |
- Tool invocations handled separately from text content
|
| 19 |
- Explicit message IDs required for all segment operations
|
| 20 |
+
- AbortController for canceling running conversations
|
| 21 |
|
| 22 |
## Message Protocol
|
| 23 |
|
| 24 |
- `auth` - HF token authentication
|
| 25 |
- `chat` - User messages
|
| 26 |
+
- `abort` - Stop running conversation
|
| 27 |
- `stream_start/token/end` - Legacy streaming
|
| 28 |
- `segment_start/token/end` - Segment streaming
|
| 29 |
- `editor_sync` - Sync editor content
|
src/lib/server/langgraph-agent.ts
CHANGED
|
@@ -72,10 +72,16 @@ export class LangGraphAgent {
|
|
| 72 |
let currentSegmentContent = "";
|
| 73 |
let buffer = "";
|
| 74 |
const messageId = config?.metadata?.messageId;
|
|
|
|
|
|
|
|
|
|
| 75 |
|
| 76 |
const toolRegex = /TOOL:\s*(\w+)\s+ARGS:\s*({[^}]*})/g;
|
| 77 |
|
| 78 |
-
for await (const token of this.streamModelResponse(
|
|
|
|
|
|
|
|
|
|
| 79 |
fullResponse += token;
|
| 80 |
config?.writer?.({ type: "token", content: token });
|
| 81 |
buffer += token;
|
|
@@ -309,7 +315,10 @@ CRITICAL INSTRUCTIONS:
|
|
| 309 |
VIBEGAME CONTEXT:
|
| 310 |
${this.documentation}
|
| 311 |
|
| 312 |
-
|
|
|
|
|
|
|
|
|
|
| 313 |
|
| 314 |
AVAILABLE TOOLS:
|
| 315 |
1. search_editor - Find text/patterns in code
|
|
@@ -340,6 +349,7 @@ IMPORTANT: You are an executor. Take action immediately using tools, don't expla
|
|
| 340 |
|
| 341 |
private async *streamModelResponse(
|
| 342 |
messages: Array<{ role: string; content: string }>,
|
|
|
|
| 343 |
): AsyncGenerator<string, string, unknown> {
|
| 344 |
if (!this.client) {
|
| 345 |
throw new Error("Agent not initialized");
|
|
@@ -355,6 +365,10 @@ IMPORTANT: You are an executor. Take action immediately using tools, don't expla
|
|
| 355 |
});
|
| 356 |
|
| 357 |
for await (const chunk of stream) {
|
|
|
|
|
|
|
|
|
|
|
|
|
| 358 |
const token = chunk.choices[0]?.delta?.content || "";
|
| 359 |
if (token) {
|
| 360 |
fullContent += token;
|
|
@@ -622,6 +636,7 @@ IMPORTANT: You are an executor. Take action immediately using tools, don't expla
|
|
| 622 |
messageHistory: BaseMessage[] = [],
|
| 623 |
onStream?: (chunk: string) => void,
|
| 624 |
messageId?: string,
|
|
|
|
| 625 |
): Promise<string> {
|
| 626 |
if (!this.client) {
|
| 627 |
throw new Error("Agent not initialized");
|
|
@@ -634,13 +649,17 @@ IMPORTANT: You are an executor. Take action immediately using tools, don't expla
|
|
| 634 |
},
|
| 635 |
{
|
| 636 |
streamMode: ["custom", "updates"] as const,
|
| 637 |
-
metadata: { messageId },
|
| 638 |
},
|
| 639 |
);
|
| 640 |
|
| 641 |
let fullResponse = "";
|
| 642 |
|
| 643 |
for await (const chunk of stream) {
|
|
|
|
|
|
|
|
|
|
|
|
|
| 644 |
if (Array.isArray(chunk)) {
|
| 645 |
const [mode, data] = chunk;
|
| 646 |
if (mode === "custom" && data?.type === "token") {
|
|
|
|
| 72 |
let currentSegmentContent = "";
|
| 73 |
let buffer = "";
|
| 74 |
const messageId = config?.metadata?.messageId;
|
| 75 |
+
const abortSignal = config?.metadata?.abortSignal as
|
| 76 |
+
| AbortSignal
|
| 77 |
+
| undefined;
|
| 78 |
|
| 79 |
const toolRegex = /TOOL:\s*(\w+)\s+ARGS:\s*({[^}]*})/g;
|
| 80 |
|
| 81 |
+
for await (const token of this.streamModelResponse(
|
| 82 |
+
messages,
|
| 83 |
+
abortSignal,
|
| 84 |
+
)) {
|
| 85 |
fullResponse += token;
|
| 86 |
config?.writer?.({ type: "token", content: token });
|
| 87 |
buffer += token;
|
|
|
|
| 315 |
VIBEGAME CONTEXT:
|
| 316 |
${this.documentation}
|
| 317 |
|
| 318 |
+
IMPORTANT:
|
| 319 |
+
- The game auto-reloads on every change.
|
| 320 |
+
- The GAME import is automatically provided by the framework.
|
| 321 |
+
- The player is automatically created at [0, 0, 0] if not specified.
|
| 322 |
|
| 323 |
AVAILABLE TOOLS:
|
| 324 |
1. search_editor - Find text/patterns in code
|
|
|
|
| 349 |
|
| 350 |
private async *streamModelResponse(
|
| 351 |
messages: Array<{ role: string; content: string }>,
|
| 352 |
+
abortSignal?: AbortSignal,
|
| 353 |
): AsyncGenerator<string, string, unknown> {
|
| 354 |
if (!this.client) {
|
| 355 |
throw new Error("Agent not initialized");
|
|
|
|
| 365 |
});
|
| 366 |
|
| 367 |
for await (const chunk of stream) {
|
| 368 |
+
if (abortSignal?.aborted) {
|
| 369 |
+
throw new Error("AbortError");
|
| 370 |
+
}
|
| 371 |
+
|
| 372 |
const token = chunk.choices[0]?.delta?.content || "";
|
| 373 |
if (token) {
|
| 374 |
fullContent += token;
|
|
|
|
| 636 |
messageHistory: BaseMessage[] = [],
|
| 637 |
onStream?: (chunk: string) => void,
|
| 638 |
messageId?: string,
|
| 639 |
+
abortSignal?: AbortSignal,
|
| 640 |
): Promise<string> {
|
| 641 |
if (!this.client) {
|
| 642 |
throw new Error("Agent not initialized");
|
|
|
|
| 649 |
},
|
| 650 |
{
|
| 651 |
streamMode: ["custom", "updates"] as const,
|
| 652 |
+
metadata: { messageId, abortSignal },
|
| 653 |
},
|
| 654 |
);
|
| 655 |
|
| 656 |
let fullResponse = "";
|
| 657 |
|
| 658 |
for await (const chunk of stream) {
|
| 659 |
+
if (abortSignal?.aborted) {
|
| 660 |
+
throw new Error("AbortError");
|
| 661 |
+
}
|
| 662 |
+
|
| 663 |
if (Array.isArray(chunk)) {
|
| 664 |
const [mode, data] = chunk;
|
| 665 |
if (mode === "custom" && data?.type === "token") {
|
src/lib/services/agent.ts
CHANGED
|
@@ -52,6 +52,18 @@ export class AgentService {
|
|
| 52 |
});
|
| 53 |
}
|
| 54 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 55 |
sendRawMessage(message: unknown): void {
|
| 56 |
if (websocketService.isConnected()) {
|
| 57 |
websocketService.send(message as WebSocketMessage);
|
|
|
|
| 52 |
});
|
| 53 |
}
|
| 54 |
|
| 55 |
+
stopConversation(): void {
|
| 56 |
+
if (!websocketService.isConnected()) {
|
| 57 |
+
return;
|
| 58 |
+
}
|
| 59 |
+
|
| 60 |
+
websocketService.send({
|
| 61 |
+
type: "abort",
|
| 62 |
+
payload: {},
|
| 63 |
+
timestamp: Date.now(),
|
| 64 |
+
});
|
| 65 |
+
}
|
| 66 |
+
|
| 67 |
sendRawMessage(message: unknown): void {
|
| 68 |
if (websocketService.isConnected()) {
|
| 69 |
websocketService.send(message as WebSocketMessage);
|
src/lib/services/console-sync.ts
CHANGED
|
@@ -30,7 +30,9 @@ export class ConsoleSyncService {
|
|
| 30 |
firstArg.includes("[VibeGame] Console forwarding") ||
|
| 31 |
firstArg.includes("[DEBUG]") ||
|
| 32 |
firstArg.includes("hot updated:") ||
|
| 33 |
-
firstArg.includes(
|
|
|
|
|
|
|
| 34 |
) {
|
| 35 |
return;
|
| 36 |
}
|
|
|
|
| 30 |
firstArg.includes("[VibeGame] Console forwarding") ||
|
| 31 |
firstArg.includes("[DEBUG]") ||
|
| 32 |
firstArg.includes("hot updated:") ||
|
| 33 |
+
firstArg.includes(
|
| 34 |
+
"using deprecated parameters for the initialization function",
|
| 35 |
+
)
|
| 36 |
) {
|
| 37 |
return;
|
| 38 |
}
|