Files
ThothII/frontend/src/shell/ModelActivityPanel.tsx
T
marcopanandClaude Fable 5 4cd1cbc493 style(frontend): look&feel v2 — layered shadows, sharper radii, mono data register
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>
2026-07-05 13:04:46 +02:00

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>
);
}