feat(ui): tidy the start screen and the chat panel details (#962)

Start screen
- The five examples are one row of picture tiles (title under the picture); the old cards cut their descriptions off with an ellipsis
- "Open .drawio file" leaves the example grid and sits with "Start with a blank canvas" below it, both as outlined buttons
- The "Paper to diagram" example gets a thumbnail of its result (public/paper_demo.svg) like the other four
- The caption says what happens: examples use saved answers, so they appear at once and do not call the AI
- Shorter lede

Chat panel
- The version card's "</>" icon button becomes a "Show XML" text link in the summary line; the summary runs under the title row, so Undo is the card's only button
- Settings uses the gear icon; new diagram uses a file-plus icon instead of the pen that reads as "edit"
- The message box has the same side padding as the message column

Copy
- The model picker says "Server model" instead of "Default" when the server's model is in use
- The theme description no longer claims the diagram always stays on a white sheet (the canvas follows the theme)
- en, zh, ja and zh-Hant dictionaries updated together
This commit is contained in:
Dayuan Jiang
2026-10-10 08:22:36 +09:00
committed by GitHub
parent d5096b9628
commit f0f0bc0dc0
10 changed files with 84 additions and 93 deletions
+5 -5
View File
@@ -1,10 +1,10 @@
import {
ChevronDown,
Download,
FilePlus,
MessageSquareDashed,
PanelRightClose,
Settings2,
SquarePen,
Settings,
} from "lucide-react"
import { useRef } from "react"
import { DevXmlSimulator } from "@/components/dev-xml-simulator"
@@ -139,14 +139,14 @@ export function ChatPanel({
disabled={engine.isBusy}
data-testid="new-chat-button"
>
<SquarePen />
<FilePlus />
</IconButton>
<IconButton
label={dict.nav.settings}
onClick={() => openSettings()}
data-testid="settings-button"
>
<Settings2 />
<Settings />
</IconButton>
{!mobile && (
<IconButton
@@ -173,7 +173,7 @@ export function ChatPanel({
/>
)}
<div className="shrink-0 p-2.5 pt-1">
<div className="shrink-0 px-4 pt-1 pb-3">
<Composer
placeholder={
engine.messages.length > 0 &&
+32 -42
View File
@@ -18,14 +18,18 @@ interface Example {
file?: { path: string; name: string; type: string }
}
/** Secondary start buttons: outlined, so they read as clickable */
const startButtonClass =
"inline-flex h-8 items-center gap-1.5 rounded-lg border border-border bg-card px-3 text-[13px] text-foreground transition-colors hover:bg-accent [&_svg]:text-muted-foreground"
async function loadExampleFile(file: NonNullable<Example["file"]>) {
const response = await fetch(getAssetUrl(file.path))
const blob = await response.blob()
return new File([blob], file.name, { type: file.type })
}
/** One start-screen choice: a small picture, a title and one line */
function ExampleCard({
/** One start-screen example: a small picture over its title */
function ExampleTile({
onClick,
title,
description,
@@ -35,6 +39,7 @@ function ExampleCard({
}: {
onClick: () => void
title: string
/** Shown as the tooltip: the title alone names the example */
description: string
picture: React.ReactNode
/** An icon on a tinted tile instead of a diagram on paper */
@@ -45,24 +50,20 @@ function ExampleCard({
<button
type="button"
onClick={onClick}
className="group/example flex min-w-0 items-center gap-2.5 rounded-xl border border-border bg-card p-1.5 pr-2.5 text-left transition-colors hover:border-foreground/25"
title={description}
className="group/example flex min-w-0 flex-col gap-2 rounded-xl border border-border bg-card p-1.5 pb-2.5 text-left transition-colors hover:border-foreground/25"
data-testid={testId}
>
<span
className={cn(
"flex h-12 w-16 shrink-0 items-center justify-center overflow-hidden rounded-lg",
"flex h-16 w-full items-center justify-center overflow-hidden rounded-lg",
plain ? "bg-muted" : "sheet-light bg-white",
)}
>
{picture}
</span>
<span className="min-w-0 flex-1">
<span className="line-clamp-2 text-[13px] leading-snug font-medium text-foreground">
{title}
</span>
<span className="mt-0.5 block truncate text-xs text-muted-foreground">
{description}
</span>
<span className="px-1 text-[13px] leading-snug font-medium text-foreground">
{title}
</span>
</button>
)
@@ -92,6 +93,7 @@ export function LobbyHero({
key: "paper",
title: dict.examples.paperToDiagram,
description: dict.examples.paperDescription,
image: "/paper_demo.svg",
prompt: "Summarize this paper as a diagram",
file: {
path: "/chain-of-thought.txt",
@@ -185,10 +187,12 @@ export function LobbyHero({
placeholder={t.placeholder}
/>
{/* Examples: one row of tiles; the ways to bring your own
diagram follow as a separate group */}
<div
className={cn(
"mt-5 grid gap-2",
compact ? "grid-cols-1" : "grid-cols-3",
compact ? "grid-cols-1" : "grid-cols-5",
)}
>
{examples.map((example) =>
@@ -209,7 +213,7 @@ export function LobbyHero({
</span>
</button>
) : (
<ExampleCard
<ExampleTile
key={example.key}
onClick={() => pickExample(example)}
title={example.title}
@@ -221,7 +225,7 @@ export function LobbyHero({
<img
src={getAssetUrl(example.image)}
alt=""
className="max-h-10 max-w-[56px] object-contain transition-transform duration-300 group-hover/example:scale-[1.06]"
className="max-h-14 max-w-[88%] object-contain transition-transform duration-300 group-hover/example:scale-[1.06]"
/>
) : (
<FileText className="size-5 text-muted-foreground" />
@@ -231,48 +235,34 @@ export function LobbyHero({
/>
),
)}
{!compact && (
<ExampleCard
onClick={onOpenFile}
title={dict.workspace.openFile}
description={t.openFileHint}
picture={
<FileUp className="size-5 text-muted-foreground" />
}
plain
/>
)}
</div>
<p className="mt-3 text-center text-xs text-faint">
<p className="mt-2.5 text-center text-xs text-faint">
{dict.examples.cachedNote}
</p>
<div className="mt-6 flex flex-wrap items-center justify-center gap-x-5 gap-y-2 text-[13px] text-muted-foreground">
{/* Ways to start without an example, as plain buttons */}
<div className="mt-6 flex flex-wrap items-center justify-center gap-2">
{engine.sessions.length > 0 && (
<SessionMenu align="center" onOpenFile={onOpenFile}>
<button
type="button"
className="inline-flex items-center gap-1.5 hover:text-foreground"
>
<button type="button" className={startButtonClass}>
<History className="size-4" />
{t.recent}
</button>
</SessionMenu>
)}
{compact && (
<button
type="button"
onClick={onOpenFile}
className="inline-flex items-center gap-1.5 hover:text-foreground"
>
<FileUp className="size-4" />
{dict.workspace.openFile}
</button>
)}
<button
type="button"
onClick={onOpenFile}
className={startButtonClass}
data-testid="open-file"
>
<FileUp className="size-4" />
{dict.workspace.openFile}
</button>
<button
type="button"
onClick={onDrawYourself}
className="inline-flex items-center gap-1.5 hover:text-foreground"
className={startButtonClass}
data-testid="draw-yourself"
>
<Pencil className="size-4" />
+26 -26
View File
@@ -2,7 +2,6 @@ import {
AlertCircle,
Check,
ChevronDown,
Code2,
Copy,
Library,
PenTool,
@@ -180,17 +179,23 @@ function VersionCard({
</button>
)
// What the AI wrote, on every card (main showed it on every tool call)
const codeButton = (
<button
type="button"
onClick={() => setShowCode((v) => !v)}
aria-label={showCode ? t.hideCode : t.showCode}
title={showCode ? t.hideCode : t.showCode}
className="inline-flex size-7 shrink-0 items-center justify-center rounded-lg text-muted-foreground hover:bg-accent hover:text-foreground"
>
<Code2 className="size-4" />
</button>
// What the AI wrote: a text link in the summary line, so the card's
// only button is the action (undo or restore)
const summaryLine = (
<div className="text-xs text-muted-foreground">
{summary}
{/* The link wraps as one unit with its separator */}
<span className="whitespace-nowrap">
<span aria-hidden> · </span>
<button
type="button"
onClick={() => setShowCode((v) => !v)}
className="underline decoration-foreground/30 underline-offset-2 hover:text-foreground hover:decoration-foreground"
>
{showCode ? t.hideCode : t.showCode}
</button>
</span>
</div>
)
const codePanel = showCode && (
<div className="relative border-t border-border bg-surface-1 px-3 py-2.5">
@@ -231,15 +236,12 @@ function VersionCard({
/>
</button>
{badge}
<div className="min-w-0 flex-1" title={summary}>
<div className="min-w-0 flex-1">
<div className="truncate text-[13px] text-foreground">
{title}
</div>
<div className="truncate text-xs text-muted-foreground">
{summary}
</div>
{summaryLine}
</div>
{codeButton}
{restoreButton}
</div>
{codePanel}
@@ -271,17 +273,12 @@ function VersionCard({
{t.compare}
</span>
</button>
<div className="flex items-center gap-2.5 px-3 py-2.5">
{/* Title row with the action; the summary runs under both */}
<div className="grid grid-cols-[auto_minmax(0,1fr)_auto] items-center gap-x-2.5 px-3 py-2.5">
{badge}
<div className="min-w-0 flex-1" title={summary}>
<div className="truncate text-[13px] font-medium text-foreground">
{title}
</div>
<div className="line-clamp-2 text-xs text-muted-foreground">
{summary}
</div>
<div className="truncate text-[13px] font-medium text-foreground">
{title}
</div>
{codeButton}
{canUndo ? (
<button
type="button"
@@ -300,6 +297,9 @@ function VersionCard({
) : (
restoreButton
)}
<div className="col-span-2 col-start-2 mt-0.5">
{summaryLine}
</div>
</div>
{codePanel}
</div>