Colors (GSD palette) untouched. New visual vocabulary across the whole GUI: - warm-tinted layered shadow tokens (--shadow-xs/sm/md) replacing flat rings; cards/dialogs framed with hairline border + real elevation - strict radii scale: xl cards / lg inputs / md controls / 2xl dialogs; buttons move from pills to rounded-md - third type register: --font-mono + tabular-nums on SQL, timers, dates, ids; standardized .thot-label micro-label tier - prose: framed tables with uppercase headers, elevated code blocks - SqlViewer badges moved from hardcoded Tailwind greens/reds to success/ destructive token tints; ResultsPanel inline styles replaced with classes - artifact gate: first option rendered as filled primary action Verified offline via Playwright against a mock backend replaying a real psd session (no VPN needed). tsc clean, 115/115 vitest. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
65 lines
2.7 KiB
TypeScript
65 lines
2.7 KiB
TypeScript
import { useState } from "react";
|
|
import { ChevronDown, ChevronUp, X } from "lucide-react";
|
|
import ReactMarkdown from "react-markdown";
|
|
import remarkGfm from "remark-gfm";
|
|
import { useSessionStore } from "../store/sessionStore";
|
|
import { Button } from "../components/ui/button";
|
|
|
|
const TAIL_PARAGRAPHS = 5;
|
|
|
|
/** Left drawer showing "what's happening": the tail of the model's streamed markdown
|
|
* output, rendered as markdown (not raw text) so headings/emphasis/lists come through
|
|
* and paragraphs stay visually separated. Collapsed it shows the last few paragraphs
|
|
* (refreshing as the stream grows); the expand toggle reveals the full stream. Opened
|
|
* on demand from the rotating activity icon. */
|
|
export function ModelActivityPanel({ onClose }: { onClose: () => void }) {
|
|
const transcript = useSessionStore((s) => s.transcript);
|
|
const [expanded, setExpanded] = useState(false);
|
|
|
|
// Blank lines are markdown's paragraph separator — split on them (not on every "\n",
|
|
// which would also break single soft-wrapped lines apart) so each block below is a
|
|
// real paragraph and the tail cut lands on paragraph boundaries.
|
|
const paragraphs = transcript
|
|
.map((e) => e.text)
|
|
.join("\n\n")
|
|
.split(/\n{2,}/)
|
|
.map((p) => p.trim())
|
|
.filter((p) => p !== "");
|
|
const shown = expanded ? paragraphs : paragraphs.slice(-TAIL_PARAGRAPHS);
|
|
const canExpand = paragraphs.length > TAIL_PARAGRAPHS;
|
|
const source = shown.join("\n\n");
|
|
|
|
return (
|
|
<aside className="flex w-[30vw] max-w-[30vw] shrink-0 flex-col border-r border-border bg-sidebar">
|
|
<div className="flex items-center justify-between border-b border-border/60 px-3 py-3">
|
|
<h2 className="font-heading text-sm font-semibold text-foreground">Model activity</h2>
|
|
<div className="flex items-center gap-1">
|
|
{canExpand && (
|
|
<Button
|
|
size="icon-sm"
|
|
variant="ghost"
|
|
aria-label={expanded ? "Show less" : "Show more"}
|
|
aria-expanded={expanded}
|
|
onClick={() => setExpanded((v) => !v)}
|
|
>
|
|
{expanded ? <ChevronUp className="size-4" /> : <ChevronDown className="size-4" />}
|
|
</Button>
|
|
)}
|
|
<Button size="icon-sm" variant="ghost" aria-label="Close model activity" onClick={onClose}>
|
|
<X className="size-4" />
|
|
</Button>
|
|
</div>
|
|
</div>
|
|
<div className="flex-1 overflow-y-auto px-3 py-5">
|
|
{shown.length === 0 ? (
|
|
<p className="text-sm text-muted-foreground">No activity yet.</p>
|
|
) : (
|
|
<div className="thot-prose max-w-[74ch]">
|
|
<ReactMarkdown remarkPlugins={[remarkGfm]}>{source}</ReactMarkdown>
|
|
</div>
|
|
)}
|
|
</div>
|
|
</aside>
|
|
);
|
|
}
|