diff --git a/studio/frontend/src/features/images/images-page.tsx b/studio/frontend/src/features/images/images-page.tsx
index e263a372d4..27fecd1f69 100644
--- a/studio/frontend/src/features/images/images-page.tsx
+++ b/studio/frontend/src/features/images/images-page.tsx
@@ -11,6 +11,7 @@ import {
Delete02Icon,
Download01Icon,
Edit03Icon,
+ Image03Icon,
ImageAdd02Icon,
ImageUpload01Icon,
InformationCircleIcon,
@@ -22,6 +23,9 @@ import {
ZoomInAreaIcon,
} from "@hugeicons/core-free-icons";
import { HugeiconsIcon } from "@hugeicons/react";
+// The raw Radix trigger: the app's TooltipTrigger adds a click-to-toggle handler that
+// has no business inside a menu row.
+import { Tooltip as TooltipPrimitive } from "radix-ui";
import { TestTubeOutlineIcon } from "@/lib/hugeicons-derived";
import { Button } from "@/components/ui/button";
@@ -48,6 +52,7 @@ import { Slider } from "@/components/ui/slider";
import { Spinner } from "@/components/ui/spinner";
import { Switch } from "@/components/ui/switch";
import { Textarea } from "@/components/ui/textarea";
+import { Tooltip, TooltipContent } from "@/components/ui/tooltip";
import { InfoHint } from "@/components/ui/info-hint";
import { ModelSelector } from "@/features/model-picker/components/model-selector";
import { IMAGE_GEN_TASKS } from "@/features/model-picker/components/model-selector/pickers";
@@ -120,42 +125,42 @@ const WORKFLOW_TABS: Array<{
label: "Transform",
icon: MagicWand01Icon,
requires: "img2img",
- hint: "Redraw an uploaded image guided by your prompt (img2img)",
+ hint: "Redraw an image from your prompt",
},
{
id: "inpaint",
label: "Inpaint",
icon: PaintBrush02Icon,
requires: "inpaint",
- hint: "Paint over a region to regenerate just that area, keeping the rest",
+ hint: "Regenerate a painted region",
},
{
id: "extend",
label: "Extend",
icon: ArrowExpand01Icon,
requires: "outpaint",
- hint: "Outpaint: grow the canvas and fill the new edges from your prompt",
+ hint: "Grow the canvas and fill the edges",
},
{
id: "upscale",
label: "Upscale",
icon: ZoomInAreaIcon,
requires: "upscale",
- hint: "Hires fix: enlarge an uploaded image and re-detail it at higher resolution",
+ hint: "Enlarge and re-detail an image",
},
{
id: "reference",
label: "Reference",
icon: ImageUpload01Icon,
requires: "reference",
- hint: "Generate a new image guided by a reference image + your prompt (FLUX.2)",
+ hint: "Generate guided by a reference image",
},
{
id: "edit",
label: "Edit",
icon: Edit03Icon,
requires: "edit",
- hint: "Instruction editing: change an image with a prompt (Qwen-Image-Edit)",
+ hint: "Change an image with an instruction",
},
];
@@ -2312,20 +2317,16 @@ export function ImagesPage({ active = true }: { active?: boolean }) {
onDeploy={handleDeployAdapter}
/>
) : (
- /* ── Controls rail + preview canvas. Padding mirrors the other tabs
- (Export, Data Recipes): px-5 / sm:px-9, with a roomy bottom. pt-3 keeps the
- cards off the model selector row. ── */
-
- {/* Controls and preview share one card, split by a divider, so the page
- reads as a single surface instead of two floating boxes. */}
-
-
-
- {/* Workflow picker. Seven workflows don't fit a segmented strip in a 340px
- rail, so it's a dropdown: the trigger carries the current workflow and
- its hint, and each row explains itself. A row is disabled until the
- loaded model supports it (status.workflows), with the reason in place of
- the hint. New workflows slot in without shrinking anything. */}
+ /* ── Controls rail + preview canvas. No card: both sit on the page background
+ like the Hub, divided by a rule, so nothing is spent on box chrome. Padding
+ mirrors the other tabs (Export, Data Recipes): px-5 / sm:px-9. ── */
+
+
+ {/* pl-0.5 keeps focus rings off the scroll container's edge. */}
+
+ {/* Workflow picker. Seven workflows don't fit a segmented strip in this rail,
+ so it's a dropdown; a row stays disabled until the loaded model supports
+ it (status.workflows). New workflows slot in without shrinking anything. */}
@@ -2347,40 +2348,45 @@ export function ImagesPage({ active = true }: { active?: boolean }) {
/>
- {/* DropdownMenuContent already tracks the trigger width, so the rows get
- the full rail width for their hints. */}
{WORKFLOW_TABS.map((t) => {
const enabled = workflowEnabled(t);
return (
- setWorkflow(t.id)}
- // The selected row's description shows under the trigger, and a
- // disabled row explains itself on hover, so rows stay one line.
- title={
- enabled
+ // Rows stay one line; what the workflow does (or why it is
+ // unavailable) arrives as a tooltip after a short hover. The
+ // trigger wraps the row rather than being the row: a disabled item
+ // has pointer-events: none, so it would never hover.
+
+
+
{status?.loaded
? "Enter a prompt and hit Generate."
@@ -2980,7 +2987,7 @@ export function ImagesPage({ active = true }: { active?: boolean }) {
{(images.length > 0 || busy === "generating") && (
{
// Near the right edge: pull the next older page (infinite scroll).
const el = e.currentTarget;
@@ -3028,21 +3035,20 @@ export function ImagesPage({ active = true }: { active?: boolean }) {
)}
-
{/* Right-docked Advanced panel (mirrors Chat's settings panel): closed by default,
opened by the single fixed top-bar toggle above (which never moves between states,
like Chat's run-settings toggle), so the optimisation controls are discoverable
without being docked open or buried at the bottom of the left rail. */}
{advancedOpen && (
-
-
+
+
Advanced
-
+
Load-time tuning. Changes apply on the next load; Reapply reloads the current model.
diff --git a/studio/frontend/src/features/images/train/dataset-labeling-grid.tsx b/studio/frontend/src/features/images/train/dataset-labeling-grid.tsx
index 49c2178754..07ca862c96 100644
--- a/studio/frontend/src/features/images/train/dataset-labeling-grid.tsx
+++ b/studio/frontend/src/features/images/train/dataset-labeling-grid.tsx
@@ -259,7 +259,7 @@ export function DatasetLabelingGrid({
)}
-
+
{pageRecords.map((r) => (
{names === null ? (
diff --git a/studio/frontend/src/features/images/train/diffusion-train-panel.tsx b/studio/frontend/src/features/images/train/diffusion-train-panel.tsx
index 0942a694bf..0e2d46f466 100644
--- a/studio/frontend/src/features/images/train/diffusion-train-panel.tsx
+++ b/studio/frontend/src/features/images/train/diffusion-train-panel.tsx
@@ -53,7 +53,11 @@ import {
import { DatasetLabelingGrid, LabelingGridToggle } from "./dataset-labeling-grid";
import { DatasetShowcase } from "./dataset-showcase";
import { DiffusionCharts } from "./diffusion-charts";
-import { ExampleDatasetCards, runExampleImport } from "./example-dataset-cards";
+import {
+ ExampleDatasetCards,
+ runExampleImport,
+ shortExampleLabel,
+} from "./example-dataset-cards";
// The families the Train tab can train, in the popularity order the user asked for. This is
// the fallback used when the backend's /info does not yet report families (older backend);
@@ -1020,10 +1024,12 @@ export function DiffusionTrainPanel({
);
return (
-
- {/* Left: configure */}
-
-
+
+ {/* Left: configure. No card here or on the right: both panes sit on the page
+ background like the Hub, divided by a rule. */}
+
+ {/* pl-0.5 keeps focus rings off the scroll container's edge. */}
+
Train a LoRA
@@ -1098,18 +1104,19 @@ export function DiffusionTrainPanel({
+ {/* Rows stay short: name plus image count. Caption counts show under the
+ picker, and an example's license shows on its card below. */}
{(info?.datasets ?? []).map((d) => (
- {d.name} ({d.image_count} image{d.image_count === 1 ? "" : "s"}
- {d.caption_count > 0 ? `, ${d.caption_count} captions` : ""})
+ {d.name} - {d.image_count} image{d.image_count === 1 ? "" : "s"}
))}
{pendingExamples.length > 0 && (
- Examples (one-click import)
+ Examples
{pendingExamples.map((ex) => (
- {ex.label} ({ex.image_cap} images, {ex.license})
+ {shortExampleLabel(ex.label)} - {ex.image_cap} images
))}
@@ -1124,7 +1131,7 @@ export function DiffusionTrainPanel({
)}
{dataset === UPLOAD_DATASET ? (
-