VibeGame / src /lib /components /chat /TextRenderer.svelte
dylanebert's picture
improved prompting/UX
db9635c
<script lang="ts">
import { onMount, afterUpdate } from "svelte";
import gsap from "gsap";
import { StreamingToolCallParser, filterToolCalls } from "../../utils/tool-call-parser";
export let content: string;
export let streaming: boolean = false;
let textEl: HTMLDivElement;
let cursorEl: HTMLSpanElement;
let lastContentLength = 0;
let newContentEl: HTMLSpanElement;
let parser: StreamingToolCallParser | null = null;
// Initialize parser for streaming mode
$: {
if (streaming && !parser) {
parser = new StreamingToolCallParser();
} else if (!streaming && parser) {
parser = null;
}
}
// Filter content based on streaming state
$: filteredContent = (() => {
if (streaming && parser) {
// Use streaming parser to incrementally process content
parser.reset();
const result = parser.process(content);
return result.safeText;
} else {
// Use static filter for non-streaming content
return filterToolCalls(content);
}
})();
onMount(() => {
if (textEl && !streaming) {
gsap.fromTo(textEl,
{ opacity: 0, y: 3, scale: 0.99 },
{ opacity: 1, y: 0, scale: 1, duration: 0.3, ease: "power2.out" }
);
}
});
// Animate new characters appearing during streaming
afterUpdate(() => {
if (streaming && filteredContent.length > lastContentLength) {
const newChars = filteredContent.slice(lastContentLength);
if (newContentEl && newChars.length > 0) {
gsap.fromTo(newContentEl,
{ opacity: 0, scale: 0.95 },
{ opacity: 1, scale: 1, duration: 0.15, ease: "power2.out" }
);
}
lastContentLength = filteredContent.length;
}
if (!streaming) {
lastContentLength = 0;
}
});
$: if (streaming && cursorEl) {
gsap.to(cursorEl, {
opacity: 0.3,
duration: 0.6,
yoyo: true,
repeat: -1,
ease: "power2.inOut"
});
}
$: if (!streaming && cursorEl) {
gsap.killTweensOf(cursorEl);
gsap.to(cursorEl, {
opacity: 0,
duration: 0.3,
ease: "power2.out",
onComplete: () => {
// Small celebration pulse on complete
if (textEl) {
gsap.to(textEl, {
borderLeftColor: "rgba(76, 175, 80, 0.3)",
duration: 0.3,
yoyo: true,
repeat: 1,
ease: "power2.inOut"
});
}
}
});
}
</script>
<div class="text-renderer" bind:this={textEl}>
<span class="content">
{#if streaming && lastContentLength > 0}
{filteredContent.slice(0, lastContentLength)}
<span bind:this={newContentEl}>{filteredContent.slice(lastContentLength)}</span>
{:else}
{filteredContent}
{/if}
{#if streaming}<span class="cursor" bind:this={cursorEl}>▊</span>{/if}
</span>
</div>
<style>
.text-renderer {
padding: 0.125rem 0;
line-height: 1.6;
white-space: pre-wrap;
word-wrap: break-word;
}
.content {
color: rgba(251, 248, 244, 0.9);
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Monaco", "Menlo", monospace;
font-size: 0.875rem;
line-height: 1.6;
letter-spacing: 0.01em;
}
.cursor {
display: inline-block;
color: rgba(124, 152, 133, 0.7);
font-weight: 400;
margin-left: 1px;
}
</style>