Rich output rendering: GFM markdown, tool-result blocks, card blocks
Three layers:
1. GFM markdown — add remark-gfm so tables, task lists, strikethrough,
autolinks render properly. Style table elements (overflow-aware
container, muted header, divider rows). Render `[ ]` task list items
as visible checkboxes.
2. Structured tool-result rendering — new `tool-result-renderers.tsx`
dispatches by tool name to render a small UI block beneath each
ToolCallCard:
- list_tenants → table with status pills + plan column
- get_tenant → tenant detail card
- get_platform_stats → KPI tiles (total + per-status)
- list_audit_log → timeline rows with actor_type + action
- list_users → user list with role chips
- suspend_tenant / activate_tenant → tenant card with action confirm
ToolCallCard collapses by default — operators expand for raw JSON.
3. Custom ```card``` blocks the LLM can emit inline:
- {"kind":"pill","status":"…"} — status pill
- {"kind":"stat","label":"…","value":…} — stat tile
- {"kind":"callout","tone":"info|warning|danger|success",…} — callout
Malformed blocks fall through to the prose unchanged. Client strips
well-formed blocks from prose and renders them as components.
Domain primer updated to teach the model the card schemas and remind it
NOT to re-render tool-result data as markdown tables (that's done
automatically — it should add commentary only).
Layers are independent: 1 + 2 always work; 3 is purely additive.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -78,6 +78,13 @@ import {
|
||||
import { ARCADIA_KNOWLEDGE } from "~/lib/arcadia-knowledge"
|
||||
import { formatAdminContextForPrompt } from "~/lib/admin-context"
|
||||
import { ConfirmCard } from "~/components/assistant/confirm-card"
|
||||
import { renderToolResult } from "~/components/assistant/tool-result-renderers"
|
||||
|
||||
function ToolResultBlock({ name, result }: { name: string; result: unknown }) {
|
||||
const rich = renderToolResult(name, result)
|
||||
if (!rich) return null
|
||||
return <div className="px-1">{rich}</div>
|
||||
}
|
||||
|
||||
const SNAPSHOT_KEY = "crema.ai.snapshot"
|
||||
type StoredMessage = { role: "user" | "assistant"; content: string }
|
||||
@@ -549,7 +556,12 @@ function ChatSurface({
|
||||
{calls.length > 0 && (
|
||||
<div className="self-start flex w-full max-w-[80ch] flex-col gap-2">
|
||||
{calls.map((c) => (
|
||||
<ToolCallCard key={c.id} call={c} defaultExpanded={c.status !== "success"} />
|
||||
<div key={c.id} className="flex flex-col gap-2">
|
||||
<ToolCallCard call={c} defaultExpanded={false} />
|
||||
{c.status === "success" && (
|
||||
<ToolResultBlock name={c.name} result={c.result} />
|
||||
)}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
|
||||
Reference in New Issue
Block a user