Spaces:
Running
Running
| <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> |