VibeGame / src /lib /components /chat /MessageContent.svelte
dylanebert's picture
improved prompting/UX
db9635c
<script lang="ts">
import type { ChatMessage } from "../../models/chat-data";
import TextRenderer from "./TextRenderer.svelte";
import ToolBlock from "./segments/ToolBlock.svelte";
import TodoSegment from "./segments/TodoSegment.svelte";
import { filterToolCalls } from "../../utils/tool-call-parser";
export let message: ChatMessage;
$: hasSegments = message.segments && message.segments.length > 0;
$: isStreaming = message.streaming || (message.segments?.some(s => s.streaming));
// Normalize content for consistent rendering with tool call filtering
$: normalizedContent = hasSegments ? null : filterToolCalls(message.content || "");
// Filter segments for rendering
$: segmentsToRender = hasSegments ? message.segments!.filter((segment) => {
// Skip tool results that are paired with previous invocation (except todos)
if (segment.type === "tool-result") {
const isTodoTool = segment.toolName?.includes("task");
return isTodoTool;
}
return true;
}) : [];
</script>
<div class="message-content">
{#if normalizedContent !== null}
<!-- Fallback content rendering -->
<TextRenderer content={normalizedContent} streaming={isStreaming} />
{#if isStreaming}
<span class="streaming-indicator"></span>
{/if}
{:else if hasSegments}
<!-- Segmented content rendering -->
{#each segmentsToRender as segment (segment.id)}
{#if segment.type === "text"}
<TextRenderer content={filterToolCalls(segment.content)} streaming={segment.streaming || false} />
{:else if segment.type === "tool-invocation"}
<ToolBlock
invocation={segment}
result={message.segments && message.segments[message.segments.indexOf(segment) + 1]?.type === "tool-result"
? message.segments[message.segments.indexOf(segment) + 1]
: null}
/>
{:else if segment.type === "tool-result" && segment.toolName?.includes("task")}
<TodoSegment {segment} />
{/if}
{/each}
{/if}
</div>
<style>
.message-content {
display: flex;
flex-direction: column;
gap: 0.125rem;
}
.streaming-indicator {
display: inline-block;
color: rgba(65, 105, 225, 0.8);
animation: pulse 1.5s ease-in-out infinite;
margin-left: 0.25rem;
}
@keyframes pulse {
0%, 100% {
opacity: 0.3;
}
50% {
opacity: 1;
}
}
</style>