fix: preserve streamed output and add markdown rendering to playground

**Problem:**
- Tool playground was only showing partial output
- When 'complete' event arrived, it replaced streamed text with final output
- Output was displayed as plain text instead of formatted markdown

**Changes:**
- Fixed streaming bug in ToolPlayground component
  - Removed line that overwrote accumulated text on 'complete' event
  - Now preserves all streamed chunks for full output display
- Added react-markdown with GitHub Flavored Markdown support
  - Install react-markdown and remark-gfm packages
  - Added @tailwindcss/typography plugin for prose styling
  - Replaced plain <pre> with <ReactMarkdown> component
  - Applied prose classes for proper markdown formatting
- Added type="button" to button elements for accessibility

**Files changed:**
- apps/web/src/components/ToolPlayground.tsx
  - Comment out setOutput(data.output) on complete event
  - Import ReactMarkdown and remarkGfm
  - Replace pre element with ReactMarkdown component
  - Add prose styling classes
  - Add type="button" to buttons
- apps/web/tailwind.config.ts
  - Add @tailwindcss/typography plugin

**Result:**
- Full streamed output now displays correctly
- Markdown is rendered with proper formatting (headings, lists, code blocks, etc.)
- Better UX for AI-generated tool responses

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
Ajax Davis 2025-11-30 19:28:53 +10:00
parent a92c2c250c
commit 268112f96e
4 changed files with 32 additions and 5 deletions

View file

@ -34,6 +34,7 @@
"zod": "^4.0.0"
},
"devDependencies": {
"@tailwindcss/typography": "^0.5.19",
"@tpmjs/eslint-config": "workspace:*",
"@tpmjs/tailwind-config": "workspace:*",
"@tpmjs/tsconfig": "workspace:*",

View file

@ -8,6 +8,8 @@
import type { TokenBreakdown as TokenData } from '@/lib/ai-agent/tool-executor-agent';
import type { Tool } from '@tpmjs/db';
import { useState } from 'react';
import ReactMarkdown from 'react-markdown';
import remarkGfm from 'remark-gfm';
import { TokenBreakdown } from './TokenBreakdown';
interface ToolPlaygroundProps {
@ -119,7 +121,8 @@ export function ToolPlayground({ tool }: ToolPlaygroundProps): React.ReactElemen
break;
case 'complete':
setOutput(data.output);
// Don't replace output - keep the streamed text
// setOutput(data.output); // Removed: this was overwriting streamed content
setTokens(data.tokenBreakdown);
setLogs((prev) => [
...prev,
@ -208,6 +211,7 @@ export function ToolPlayground({ tool }: ToolPlaygroundProps): React.ReactElemen
{tabs.map((tab) => (
<button
key={tab.id}
type="button"
onClick={() => setActiveTab(tab.id)}
className={`px-4 py-3 text-sm font-medium transition-colors relative ${
activeTab === tab.id
@ -248,6 +252,7 @@ export function ToolPlayground({ tool }: ToolPlaygroundProps): React.ReactElemen
</div>
<button
type="button"
onClick={handleExecute}
disabled={isExecuting || !prompt.trim()}
className="px-6 py-3 bg-primary text-primary-foreground rounded-lg font-medium hover:bg-primary/90 disabled:opacity-50 disabled:cursor-not-allowed transition-colors"
@ -292,10 +297,8 @@ export function ToolPlayground({ tool }: ToolPlaygroundProps): React.ReactElemen
<p className="text-sm text-red-600 dark:text-red-500 mt-1">{error}</p>
</div>
) : output ? (
<div className="rounded-lg border border-border bg-muted/30 p-4">
<pre className="text-sm text-foreground whitespace-pre-wrap font-mono">
{output}
</pre>
<div className="rounded-lg border border-border bg-muted/30 p-6 prose prose-sm dark:prose-invert max-w-none">
<ReactMarkdown remarkPlugins={[remarkGfm]}>{output}</ReactMarkdown>
</div>
) : isExecuting ? (
<div className="flex items-center justify-center py-12">

View file

@ -8,4 +8,5 @@ export default {
'./src/components/**/*.{ts,tsx}',
'../../packages/ui/src/**/*.ts',
],
plugins: [...(baseConfig.plugins || []), require('@tailwindcss/typography')],
} satisfies Config;