feat(chat): use Streamdown for markdown rendering in chat messages

- Add streamdown package for AI-optimized markdown streaming
- Update dashboard and public agent chat pages to render messages with Streamdown
- Enables proper markdown formatting (code blocks, lists, etc.) in chat responses
This commit is contained in:
Ajax Davis 2026-01-15 05:28:38 +10:00
parent 27e7d45646
commit 1c903cd26e
5 changed files with 252 additions and 5 deletions

View file

@ -57,6 +57,7 @@
"remark-gfm": "^4.0.1",
"resend": "^6.7.0",
"sonner": "^2.0.7",
"streamdown": "^1.6.11",
"zod": "^4.3.5"
},
"devDependencies": {

View file

@ -8,6 +8,7 @@ import Link from 'next/link';
import { useParams, useRouter } from 'next/navigation';
import { useCallback, useEffect, useRef, useState } from 'react';
import { Virtuoso, type VirtuosoHandle } from 'react-virtuoso';
import { Streamdown } from 'streamdown';
import { AppHeader } from '~/components/AppHeader';
import { LikeButton } from '~/components/LikeButton';
@ -807,7 +808,9 @@ export default function PublicAgentChatPage(): React.ReactElement {
{streamingContent && (
<div className="flex justify-start">
<div className="max-w-[80%] rounded-lg p-4 bg-surface-secondary">
<p className="whitespace-pre-wrap text-sm">{streamingContent}</p>
<div className="text-sm prose prose-sm dark:prose-invert max-w-none">
<Streamdown>{streamingContent}</Streamdown>
</div>
<span className="inline-block w-2 h-4 bg-primary animate-pulse ml-1" />
</div>
</div>
@ -884,7 +887,9 @@ export default function PublicAgentChatPage(): React.ReactElement {
{message.role === 'USER' && (
<div className="flex justify-end">
<div className="max-w-[80%] rounded-lg p-4 bg-primary text-primary-foreground">
<p className="whitespace-pre-wrap text-sm">{message.content}</p>
<div className="text-sm prose prose-sm prose-invert max-w-none">
<Streamdown>{message.content}</Streamdown>
</div>
</div>
</div>
)}
@ -896,7 +901,9 @@ export default function PublicAgentChatPage(): React.ReactElement {
{message.content && (
<div className="flex justify-start">
<div className="max-w-[80%] rounded-lg p-4 bg-surface-secondary">
<p className="whitespace-pre-wrap text-sm">{message.content}</p>
<div className="text-sm prose prose-sm dark:prose-invert max-w-none">
<Streamdown>{message.content}</Streamdown>
</div>
{/* Token usage for debugging */}
{(message.inputTokens || message.outputTokens) && (
<div className="mt-2 pt-2 border-t border-border/50 text-[10px] text-foreground-tertiary font-mono">

View file

@ -6,6 +6,7 @@ import { Icon } from '@tpmjs/ui/Icon/Icon';
import Link from 'next/link';
import { useParams, useRouter } from 'next/navigation';
import { useCallback, useEffect, useRef, useState } from 'react';
import { Streamdown } from 'streamdown';
import { DashboardLayout } from '~/components/dashboard/DashboardLayout';
interface Agent {
@ -678,7 +679,9 @@ export default function AgentChatPage(): React.ReactElement {
: 'bg-surface-secondary'
}`}
>
<p className="whitespace-pre-wrap text-sm">{message.content}</p>
<div className="text-sm prose prose-sm dark:prose-invert max-w-none">
<Streamdown>{message.content}</Streamdown>
</div>
</div>
</div>
)}
@ -707,7 +710,9 @@ export default function AgentChatPage(): React.ReactElement {
{streamingContent && (
<div className="flex justify-start">
<div className="max-w-[80%] rounded-lg p-4 bg-surface-secondary">
<p className="whitespace-pre-wrap text-sm">{streamingContent}</p>
<div className="text-sm prose prose-sm dark:prose-invert max-w-none">
<Streamdown>{streamingContent}</Streamdown>
</div>
<span className="inline-block w-2 h-4 bg-primary animate-pulse ml-1" />
</div>
</div>