Skip to content

Commit 281b9c3

Browse files
committed
Add SafeParagraph component to handle child elements and improve rendering logic for images and code blocks in message.tsx
1 parent 6be50df commit 281b9c3

1 file changed

Lines changed: 38 additions & 0 deletions

File tree

web/src/components/ai-elements/message.tsx

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,9 @@ import { math } from "@streamdown/math";
2121
import { mermaid } from "@streamdown/mermaid";
2222
import { ChevronLeftIcon, ChevronRightIcon } from "lucide-react";
2323
import {
24+
type ReactNode,
2425
createContext,
26+
isValidElement,
2527
memo,
2628
useCallback,
2729
useContext,
@@ -326,13 +328,49 @@ const streamdownPlugins = { cjk, code, math, mermaid };
326328

327329
const linkSafetyOff = { enabled: false } as const;
328330

331+
function hasImageChild(children: ReactNode): boolean {
332+
const arr: ReactNode[] = Array.isArray(children) ? children : [children];
333+
return arr.some(
334+
(c) =>
335+
isValidElement(c) &&
336+
(c.props as Record<string, unknown>)?.node &&
337+
((c.props as Record<string, unknown>).node as { tagName?: string })
338+
?.tagName === "img"
339+
);
340+
}
341+
342+
const SafeParagraph = memo(
343+
({ children, node, ...rest }: Record<string, unknown> & { children?: ReactNode; node?: unknown }) => {
344+
const kids = (Array.isArray(children) ? children : [children]).filter(
345+
(c) => c != null && c !== ""
346+
) as ReactNode[];
347+
348+
if (kids.length === 1 && isValidElement(kids[0])) {
349+
const tag = (kids[0].props as Record<string, unknown>)?.node as { tagName?: string } | undefined;
350+
if (tag?.tagName === "img") return <>{children}</>;
351+
if (tag?.tagName === "code" && "data-block" in (kids[0].props as Record<string, unknown>))
352+
return <>{children}</>;
353+
}
354+
355+
if (hasImageChild(children)) {
356+
return <div {...(rest as React.HTMLAttributes<HTMLDivElement>)}>{children}</div>;
357+
}
358+
359+
return <p {...(rest as React.HTMLAttributes<HTMLParagraphElement>)}>{children}</p>;
360+
}
361+
);
362+
SafeParagraph.displayName = "SafeParagraph";
363+
364+
const streamdownComponents = { p: SafeParagraph } as ComponentProps<typeof Streamdown>["components"];
365+
329366
export const MessageResponse = memo(
330367
({ className, ...props }: MessageResponseProps) => (
331368
<Streamdown
332369
className={cn(
333370
"size-full [&>*:first-child]:mt-0 [&>*:last-child]:mb-0",
334371
className
335372
)}
373+
components={streamdownComponents}
336374
linkSafety={linkSafetyOff}
337375
plugins={streamdownPlugins}
338376
{...props}

0 commit comments

Comments
 (0)