Images Train: plainer field text, no green buttons, columns that stop colliding

- The dataset name, trigger prompt, adapter name and custom base fields now say
  what they are in plain words instead of leaning on example values.
- Import, Upload, Back, Back to settings and Train another are outline buttons,
  not green ones.
- Example thumbnails are landscape tiles, so photos are not cropped to chunky
  squares.
- Settings cells get min-w-0 and the select value truncates, so a long option
  like the nf4 label no longer widens its column into the next one.
- The number stepper sits a little further in from the field edge.
- Create and Train are wider.
This commit is contained in:
Unsloth 2026-07-25 04:22:12 -07:00
commit bfe27c27df
4 changed files with 42 additions and 29 deletions

View file

@ -134,7 +134,7 @@ function Input({ className, type, ...props }: React.ComponentProps<"input">) {
{field}
<span
aria-hidden="true"
className="absolute top-1/2 right-1.5 flex h-[21px] w-4 -translate-y-1/2 flex-col overflow-hidden rounded-[5px] bg-black/[0.07] group-has-[input:disabled]/number:pointer-events-none group-has-[input:disabled]/number:opacity-40 dark:bg-white/[0.12]"
className="absolute top-1/2 right-2.5 flex h-[21px] w-4 -translate-y-1/2 flex-col overflow-hidden rounded-[5px] bg-black/[0.07] group-has-[input:disabled]/number:pointer-events-none group-has-[input:disabled]/number:opacity-40 dark:bg-white/[0.12]"
>
<StepperButton direction={1} />
<StepperButton direction={-1} />

View file

@ -2275,7 +2275,7 @@ export function ImagesPage({ active = true }: { active?: boolean }) {
value={pageMode}
onValueChange={(v) => setPageMode(v as "create" | "train")}
fit={true}
className="pointer-events-auto h-[34px] [&>button]:h-[34px] [&>button]:px-7"
className="pointer-events-auto h-[34px] [&>button]:h-[34px] [&>button]:px-10"
tabs={[
{
value: "create",

View file

@ -123,7 +123,12 @@ function repoIsPrequantized(baseModel: string): boolean {
// Dataset-select option value prefix for a not-yet-imported example; picking it imports.
const EXAMPLE_PREFIX = "example:";
const DATASET_FILE_ACCEPT = ".png,.jpg,.jpeg,.webp,.bmp,.txt,.caption,.jsonl";
const selectClass = "h-8 w-full text-xs";
// min-w-0 + a truncating value: without them a long option ("nf4 (4-bit QLoRA,
// lowest VRAM)") sets the grid column's min width and pushes into its neighbour.
const selectClass =
"h-8 w-full min-w-0 text-xs *:data-[slot=select-value]:min-w-0 *:data-[slot=select-value]:truncate";
// Every settings cell is a grid item, so it needs min-w-0 to be allowed to shrink.
const fieldClass = "grid min-w-0 gap-1.5";
// Merge the backend's reported families (if any) over the presets, keeping the preset
// ordering (popularity) and filling labels/notes/defaults the backend omits.
@ -803,7 +808,7 @@ export function DiffusionTrainPanel({
fallback: number,
extra?: { min?: number; step?: number },
) => (
<div className="grid gap-1.5">
<div className={fieldClass}>
<Label className="text-xs">{label}</Label>
<Input
type="number"
@ -825,7 +830,7 @@ export function DiffusionTrainPanel({
// Run length: a number paired with a compact unit select (Steps / Epochs). Epochs mode
// trains for that many full passes over the dataset; the backend resolves it to steps.
const durationField = (
<div className="grid gap-1.5">
<div className={fieldClass}>
<Label className="text-xs">{durationUnit === "epochs" ? "Epochs" : "Steps"}</Label>
<div className="flex gap-1.5">
<Input
@ -889,7 +894,7 @@ export function DiffusionTrainPanel({
min: 0,
step: 0.00001,
})}
<div className="grid gap-1.5">
<div className={fieldClass}>
<Label className="text-xs">LR schedule</Label>
<Select
value={lrScheduler}
@ -914,7 +919,7 @@ export function DiffusionTrainPanel({
</div>
<div className="grid grid-cols-2 items-start gap-x-6 gap-y-4 lg:grid-cols-3">
<div className="grid gap-1.5">
<div className={fieldClass}>
<Label className="text-xs">Gradient checkpointing</Label>
<Select
value={gradCheckpoint ? "on" : "off"}
@ -934,7 +939,7 @@ export function DiffusionTrainPanel({
</div>
{isDiT ? (
<div className="grid gap-1.5">
<div className={fieldClass}>
<Label className="text-xs">Base precision</Label>
<Select
value={basePrecision}
@ -976,7 +981,7 @@ export function DiffusionTrainPanel({
</p>
</div>
) : (
<div className="grid gap-1.5">
<div className={fieldClass}>
<Label className="text-xs">Precision</Label>
<Select
value={precision}
@ -997,7 +1002,7 @@ export function DiffusionTrainPanel({
</div>
)}
{supportsCompile && (
<div className="grid gap-1.5">
<div className={fieldClass}>
<Label className="text-xs">Compile transformer</Label>
<Select
value={compileTransformer}
@ -1039,7 +1044,7 @@ export function DiffusionTrainPanel({
</div>
{/* Family + base */}
<div className="grid gap-1.5">
<div className={fieldClass}>
<Label className="text-xs">Model family</Label>
<Select value={familyName} onValueChange={setFamilyName}>
<SelectTrigger className={selectClass} aria-label="Model family">
@ -1058,7 +1063,7 @@ export function DiffusionTrainPanel({
)}
</div>
<div className="grid gap-1.5">
<div className={fieldClass}>
<Label className="text-xs">Base model</Label>
<Select value={effectiveBase} onValueChange={setBaseChoice}>
<SelectTrigger className={selectClass} aria-label="Base model">
@ -1076,7 +1081,7 @@ export function DiffusionTrainPanel({
{effectiveBase === CUSTOM_BASE && (
<Input
value={customBase}
placeholder="my-org/my-base or /path/to/pipeline"
placeholder="Hugging Face repo id, or a folder on this machine"
spellCheck={false}
onChange={(e) => setCustomBase(e.target.value)}
className="h-8 text-xs"
@ -1085,7 +1090,7 @@ export function DiffusionTrainPanel({
</div>
{/* Dataset */}
<div className="grid gap-1.5">
<div className={fieldClass}>
<Label className="text-xs">Training images</Label>
<Select
value={dataset}
@ -1131,10 +1136,13 @@ export function DiffusionTrainPanel({
)}
{dataset === UPLOAD_DATASET ? (
<div className="grid gap-1.5">
<div className={fieldClass}>
<Label className="text-xs font-normal text-muted-foreground">
Name for this set of images
</Label>
<Input
value={uploadName}
placeholder="my-style-photos"
placeholder="my-photos"
spellCheck={false}
onChange={(e) => setUploadName(e.target.value)}
className="h-8 text-xs"
@ -1172,7 +1180,7 @@ export function DiffusionTrainPanel({
<Button
type="button"
size="sm"
variant="secondary"
variant="outline"
className="h-7 shrink-0 px-3 text-xs"
onClick={onUpload}
disabled={uploading}
@ -1235,27 +1243,32 @@ export function DiffusionTrainPanel({
The style applies to any prompt after training.
</p>
) : (
<div className="grid gap-1.5">
<Label className="text-xs">
Trigger prompt (how you&apos;ll invoke the style later)
</Label>
<div className={fieldClass}>
<Label className="text-xs">Trigger prompt</Label>
<Input
value={instancePrompt}
placeholder="a photo in SKS style"
placeholder="a photo in mystyle"
onChange={(e) => setInstancePrompt(e.target.value)}
className="h-8 text-xs"
/>
<p className="text-[11px] leading-snug text-muted-foreground">
What every image is described as while training. Put these words in a
prompt later to get the style back.
</p>
</div>
)}
<div className="grid gap-1.5">
<div className={fieldClass}>
<Label className="text-xs">Adapter name</Label>
<Input
value={outputDir}
placeholder="my-style-lora"
placeholder="my-style"
spellCheck={false}
onChange={(e) => setOutputDir(e.target.value)}
className="h-8 text-xs"
/>
<p className="text-[11px] leading-snug text-muted-foreground">
The name this LoRA gets in the Create tab's picker.
</p>
</div>
{/* Start lives here; Stop lives in the run card next to the live stats. */}
@ -1295,7 +1308,7 @@ export function DiffusionTrainPanel({
<Button
type="button"
size="sm"
variant="secondary"
variant="outline"
onClick={() => setViewRun(null)}
>
Back
@ -1474,7 +1487,7 @@ export function DiffusionTrainPanel({
!stoppedWithAdapter && (
<Button
type="button"
variant="secondary"
variant="outline"
className="w-full"
onClick={() => setDismissedJobId(status.job_id)}
>
@ -1504,7 +1517,7 @@ export function DiffusionTrainPanel({
<Button
type="button"
size="sm"
variant="secondary"
variant="outline"
onClick={() => status && setDismissedJobId(status.job_id)}
>
Train another

View file

@ -77,7 +77,7 @@ function ExamplePreviews({ repo }: { repo: string }) {
return (
<div className="flex gap-1">
{urls.map((u) => (
<div key={u} className="size-10 shrink-0 overflow-hidden rounded-[8px] bg-muted">
<div key={u} className="h-9 w-12 shrink-0 overflow-hidden rounded-[8px] bg-muted">
<img src={u} alt="" loading="lazy" className="size-full object-cover" />
</div>
))}
@ -141,7 +141,7 @@ export function ExampleDatasetCards({
<Button
type="button"
size="sm"
variant="secondary"
variant="outline"
className="h-7 shrink-0 self-center px-3 text-xs"
onClick={() => onImport(ex)}
disabled={busyId !== null}