fix(training-studio): drop a version label that only repeats the model name

A single-version card whose version carries the family name rendered
"Illustrious · Illustrious" — in the run list, and as the selected-model title in
the flow. `versionSuffix` returns the label only when it differs from the card
name, and both call sites use it, so the rule is stated once rather than copied
into the flow and the orchestrator row mapper.

Found while explaining to Justin why the sample rows say "SDXL · Illustrious":
those fixtures predate Illustrious and Pony becoming their own cards, and
answering what real data WOULD say surfaced this.

Not pushed yet at the time of writing. This is Luis's open PR (#4701); how it
lands is his call.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017VMK1EQxkfREacD6PWFU7t
(cherry picked from commit 40d09455ff4d2e26a1f7980c211e0c5e792b9ae2)
This commit is contained in:
Justin Maier
2026-09-11 17:55:01 -06:00
parent ae86199046
commit fe93a06d1d
3 changed files with 33 additions and 5 deletions
@@ -660,6 +660,16 @@ export const cardByType = (type: string): ModelCard | undefined =>
export const cardsForMedia = (media: Media): ModelCard[] =>
MODEL_CARDS.filter((c) => c.media === media);
/**
* The version label to show beside a card's name, blank when it only repeats it. A single-version card
* whose version carries the family name would otherwise read "Illustrious · Illustrious". Shared by the
* flow and the orchestrator row mapper so the rule is stated once.
*/
export function versionSuffix(cardName: string, versionLabel: string | undefined): string {
if (!versionLabel) return '';
return versionLabel.trim().toLowerCase() === cardName.trim().toLowerCase() ? '' : versionLabel;
}
/**
* How old a base model is, for the card. Relative inside six months ("3 months ago") because that is the
* window where recency is the point; absolute after it ("Aug 2024"), because "26 months ago" is arithmetic
@@ -1,5 +1,11 @@
import type { Workflow, WorkflowStatus } from '@civitai/client';
import { cardByEcosystem, cardByType, findByAir, type Media } from './trainingModels';
import {
cardByEcosystem,
cardByType,
findByAir,
versionSuffix,
type Media,
} from './trainingModels';
/** Tags every training workflow carries. `TRAINING_TAG` mirrors the main app's
* `TRAINING_WORKFLOW_TAG`; `CIVITAI_TAG` is the platform namespace. The main app's queryWorkflows
@@ -38,7 +44,11 @@ export const RUN_STATE_BADGE: Record<RunState, { label: string; cls: string; dot
cls: 'text-primary bg-primary/15',
dot: 'bg-primary animate-pulse',
},
published: { label: 'Published', cls: 'text-emerald-400 bg-emerald-500/15', dot: 'bg-emerald-400' },
published: {
label: 'Published',
cls: 'text-emerald-400 bg-emerald-500/15',
dot: 'bg-emerald-400',
},
failed: { label: 'Failed', cls: 'text-red-400 bg-red-500/15', dot: 'bg-red-400' },
};
@@ -160,7 +170,13 @@ function resolveWorkflow(w: Workflow) {
output,
progress,
media: card?.media ?? 'image',
base: card ? `${card.name}${version ? ` · ${version.label}` : ''}` : 'Training run',
base: card
? `${card.name}${
versionSuffix(card.name, version?.label)
? ` · ${versionSuffix(card.name, version?.label)}`
: ''
}`
: 'Training run',
code: card?.code ?? '??',
// TODO(write-path): main-app runs carry no name tag, so we show the trigger word or a fallback. The
// Start slice should stamp a `name` (and a `name:<slug>` workflow tag) so runs are titled properly.
@@ -5,6 +5,7 @@ import {
cardByType,
cardsForMedia,
loraTypeById,
versionSuffix,
type LabelType,
type Media,
type ModelCard,
@@ -90,8 +91,9 @@ export function isCustom(run: Run): boolean {
export function runVersionLabel(run: Run): string {
if (isCustom(run)) return 'Custom';
const v = runCard(run).versions.find((x) => x.key === run.versionKey);
return v?.label ?? '';
const card = runCard(run);
const v = card.versions.find((x) => x.key === run.versionKey);
return versionSuffix(card.name, v?.label);
}
export function newRun(card: ModelCard): Run {