diff --git a/src/components/message-item.tsx b/src/components/message-item.tsx index b34d0ada..f9f27a37 100644 --- a/src/components/message-item.tsx +++ b/src/components/message-item.tsx @@ -1,7 +1,9 @@ import { For, Show } from "solid-js" import type { Message } from "../types/message" +import { partHasRenderableText } from "../types/message" import MessagePart from "./message-part" + interface MessageItemProps { message: Message messageInfo?: any @@ -39,7 +41,11 @@ export default function MessageItem(props: MessageItemProps) { } const hasContent = () => { - return messageParts().length > 0 || errorMessage() !== null + if (errorMessage() !== null) { + return true + } + + return messageParts().some((part) => partHasRenderableText(part)) } const isGenerating = () => { diff --git a/src/components/message-part.tsx b/src/components/message-part.tsx index fe4535e3..5a53ed23 100644 --- a/src/components/message-part.tsx +++ b/src/components/message-part.tsx @@ -4,6 +4,7 @@ import { isItemExpanded, toggleItemExpanded } from "../stores/tool-call-state" import { Markdown } from "./markdown" import { useTheme } from "../lib/theme" import { preferences } from "../stores/preferences" +import { partHasRenderableText } from "../types/message" interface MessagePartProps { part: any @@ -23,7 +24,7 @@ export default function MessagePart(props: MessagePartProps) { return ( - +
@@ -39,7 +40,7 @@ export default function MessagePart(props: MessagePartProps) {
- +
diff --git a/src/stores/sessions.ts b/src/stores/sessions.ts index 6da2f556..4468da78 100644 --- a/src/stores/sessions.ts +++ b/src/stores/sessions.ts @@ -1,7 +1,9 @@ import { createSignal } from "solid-js" import type { Session, Agent, Provider } from "../types/session" import type { Message, MessageDisplayParts } from "../types/message" +import { partHasRenderableText } from "../types/message" import { instances } from "./instances" + import { sseManager } from "../lib/sse-manager" import { preferences } from "./preferences" @@ -88,11 +90,11 @@ export function computeDisplayParts(message: Message, showThinking: boolean): Me const reasoning: any[] = [] for (const part of message.parts) { - if (part.type === "text" && !part.synthetic) { + if (part.type === "text" && !part.synthetic && partHasRenderableText(part)) { text.push(part) } else if (part.type === "tool") { tool.push(part) - } else if (part.type === "reasoning" && showThinking) { + } else if (part.type === "reasoning" && showThinking && partHasRenderableText(part)) { reasoning.push(part) } } diff --git a/src/types/message.ts b/src/types/message.ts index a0b1d2ff..aca4bfa6 100644 --- a/src/types/message.ts +++ b/src/types/message.ts @@ -31,3 +31,44 @@ export interface TextPart { synthetic?: boolean renderCache?: RenderCache } + +function hasTextSegment(segment: unknown): boolean { + if (typeof segment === "string") { + return segment.trim().length > 0 + } + + if (segment && typeof segment === "object") { + const maybeText = (segment as { text?: unknown }).text + if (typeof maybeText === "string") { + return maybeText.trim().length > 0 + } + } + + return false +} + +export function partHasRenderableText(part: any): boolean { + if (!part || typeof part !== "object") { + return false + } + + if (hasTextSegment(part.text)) { + return true + } + + const contentArray = Array.isArray(part?.content) ? part.content : [] + for (const item of contentArray) { + if (hasTextSegment(item)) { + return true + } + } + + const thinkingContent = Array.isArray(part?.thinking?.content) ? part.thinking.content : [] + for (const chunk of thinkingContent) { + if (hasTextSegment(chunk)) { + return true + } + } + + return false +}