Fix Train tab example cards and Create/Train tab layout

The example-dataset cards used a two-column grid in the ~340px config
column, which wrapped titles one word per line and let the long license
text overrun into the neighbouring card. Switch to one card per row with a
horizontal layout: title with a compact truncated license badge (full text
in the tooltip), a two-line clamped description, and the Import button on
the right.

The Create/Train switch had an icon inside the Train trigger that overhung
the pill corner. Drop the icon, make both triggers a fixed equal width so
the active pill sits flush in the top bar.
This commit is contained in:
Daniel Han 2026-07-02 20:55:48 +00:00
commit 7cd7c588f2
2 changed files with 61 additions and 41 deletions

View file

@ -3,7 +3,6 @@
import { useCallback, useEffect, useMemo, useRef, useState, type ReactNode } from "react";
import {
AiMagicIcon,
ArrowLeftRightIcon,
ArrowReloadHorizontalIcon,
Delete02Icon,
@ -465,34 +464,41 @@ function Field({
function AdvancedSelect({
label,
hint,
desc,
value,
onValueChange,
options,
}: {
label: string;
hint?: ReactNode;
// A short always-visible description under the row (the hint tooltip carries the full
// detail). Used for controls whose label alone does not convey what they do.
desc?: string;
value: string;
onValueChange: (v: string) => void;
options: Array<[string, string]>;
}) {
return (
<div className="flex items-center justify-between gap-2">
<span className="flex items-center gap-1 text-xs font-medium text-muted-foreground">
{label}
{hint && <InfoHint>{hint}</InfoHint>}
</span>
<Select value={value} onValueChange={onValueChange}>
<SelectTrigger className="h-8 w-[160px] text-xs">
<SelectValue />
</SelectTrigger>
<SelectContent>
{options.map(([v, l]) => (
<SelectItem key={v} value={v} className="text-xs">
{l}
</SelectItem>
))}
</SelectContent>
</Select>
<div className="flex flex-col gap-1">
<div className="flex items-center justify-between gap-2">
<span className="flex items-center gap-1 text-xs font-medium text-muted-foreground">
{label}
{hint && <InfoHint>{hint}</InfoHint>}
</span>
<Select value={value} onValueChange={onValueChange}>
<SelectTrigger className="h-8 w-[160px] text-xs">
<SelectValue />
</SelectTrigger>
<SelectContent>
{options.map(([v, l]) => (
<SelectItem key={v} value={v} className="text-xs">
{l}
</SelectItem>
))}
</SelectContent>
</Select>
</div>
{desc && <p className="text-[11px] leading-snug text-muted-foreground/70">{desc}</p>}
</div>
);
}
@ -1865,7 +1871,8 @@ export function ImagesPage({ active = true }: { active?: boolean }) {
to GGUF (or nothing loaded) and otherwise show why it is unavailable. */}
{!status?.loaded || status.model_kind === "gguf" ? (
<AdvancedSelect
label="GGUF speed mode"
label="GGUF compute"
desc="Off runs the GGUF as-is. INT8/FP8/FP4 dequantise the transformer onto low-precision tensor cores for a faster step, at the cost of a larger download and more VRAM."
hint="Optional speed-up for GGUF models. Off runs the GGUF as-is. FP8/INT8/FP4 instead load the FULL base model and quantise its transformer onto low-precision tensor cores: faster per step, but a larger download and more VRAM, and it falls back to the GGUF if it can't fit. Needs CUDA."
value={transformerQuant}
onValueChange={(v) => setTransformerQuant(v as typeof transformerQuant)}
@ -1879,9 +1886,11 @@ export function ImagesPage({ active = true }: { active?: boolean }) {
]}
/>
) : (
<div className="flex items-center justify-between gap-2 text-xs">
<span className="text-muted-foreground">GGUF speed mode</span>
<span className="text-muted-foreground/70">GGUF models only</span>
<div className="flex items-center justify-between gap-2">
<span className="flex items-center gap-1 text-xs font-medium text-muted-foreground">
GGUF compute
</span>
<span className="text-xs text-muted-foreground/60">GGUF models only</span>
</div>
)}
<AdvancedSelect
@ -1964,9 +1973,10 @@ export function ImagesPage({ active = true }: { active?: boolean }) {
generation workspace; Train is the full-page LoRA training workspace. */}
<Tabs value={pageMode} onValueChange={(v) => setPageMode(v as "create" | "train")}>
<TabsList className="h-[34px]">
<TabsTrigger value="create">Create</TabsTrigger>
<TabsTrigger value="train">
<HugeiconsIcon icon={AiMagicIcon} className="mr-1 size-3.5" />
<TabsTrigger value="create" className="w-[64px]">
Create
</TabsTrigger>
<TabsTrigger value="train" className="w-[64px]">
Train
</TabsTrigger>
</TabsList>

View file

@ -14,9 +14,13 @@ import {
listDiffusionDatasetExamples,
} from "../api";
// One-click example-dataset importers. Each card shows the license verbatim so users see
// the terms before importing. On success the parent refreshes its dataset list and selects
// the imported folder (and can seed the trigger prompt from suggested_trigger).
// One-click example-dataset importers. Each card shows the license so users see the terms
// before importing. On success the parent refreshes its dataset list and selects the
// imported folder (and can seed the trigger prompt from suggested_trigger).
//
// Layout: one card per row (the config column is only ~340px, so a two-column grid wrapped
// titles one word per line and let the long license text overrun into the next card). The
// license is a compact truncated badge with the full text in its title tooltip.
export function ExampleDatasetCards({
onImported,
}: {
@ -66,29 +70,35 @@ export function ExampleDatasetCards({
return (
<div className="grid gap-2">
<span className="text-[11px] text-muted-foreground">
Or start from an example dataset:
<span className="text-[11px] font-medium text-muted-foreground">
Or start from an example dataset
</span>
<div className="grid gap-2 sm:grid-cols-2">
<div className="grid gap-2">
{examples.map((ex) => (
<div
key={ex.id}
className="flex flex-col gap-1.5 rounded-lg border border-border p-2.5"
className="flex items-center gap-3 rounded-lg border border-border p-2.5"
>
<div className="flex items-start justify-between gap-2">
<span className="text-xs font-medium">{ex.label}</span>
<Badge variant="secondary" className="shrink-0 text-[10px]">
{ex.license}
</Badge>
<div className="flex min-w-0 flex-1 flex-col gap-1">
<div className="flex min-w-0 items-center gap-1.5">
<span className="truncate text-xs font-medium">{ex.label}</span>
<Badge
variant="secondary"
className="max-w-[120px] shrink-0 truncate text-[10px] font-normal"
title={ex.license}
>
{ex.license}
</Badge>
</div>
<p className="line-clamp-2 text-[11px] leading-snug text-muted-foreground">
{ex.description}
</p>
</div>
<p className="text-[11px] leading-snug text-muted-foreground">
{ex.description}
</p>
<Button
type="button"
size="sm"
variant="secondary"
className="h-7 w-fit text-xs"
className="h-7 shrink-0 self-center px-3 text-xs"
onClick={() => void doImport(ex)}
disabled={busyId !== null}
>