From 17000516f66ad45cd68e648615ef3d30e5214f02 Mon Sep 17 00:00:00 2001 From: Michael Han <107991372+shimmyshimmer@users.noreply.github.com> Date: Thu, 18 Jun 2026 04:38:56 -0700 Subject: [PATCH] Studio Hub: show Google logo for diffusiongemma and future gemma derivatives (#6432) * Studio Hub: show Google logo for diffusiongemma and future gemma derivatives The provider-logo matcher only did a startsWith() prefix match, so qualifier-prefixed family names like diffusiongemma-* never matched Google's gemma- prefixes and fell back to the owner-initial (Unsloth U) tile. Add an optional per-provider stems list: a case-insensitive substring fallback that runs only after every prefix misses, so prefix precedence (e.g. DeepSeek-R1-Distill- over Qwen) is preserved. Google gets stems: [gemma], which future-proofs the whole *gemma family (paligemma, codegemma, diffusiongemma, etc.) without enumerating each one. * Studio Hub: match gemma stem only at a word boundary Address review: a plain substring stem could over-match contrived names like gemmafy or gemman. Require the stem to end at a word boundary (next char not a letter), so gemma still catches diffusiongemma- and gemma-3n but never gemmafy. Splitting on delimiters would not work here, since the qualifier and family share one token (diffusiongemma). * Trim comments for PR #6432 --------- Co-authored-by: Daniel Han --- .../src/features/hub/lib/provider-logos.ts | 25 +++++++++++++++++-- 1 file changed, 23 insertions(+), 2 deletions(-) diff --git a/studio/frontend/src/features/hub/lib/provider-logos.ts b/studio/frontend/src/features/hub/lib/provider-logos.ts index 9d1a94206b..248127429b 100644 --- a/studio/frontend/src/features/hub/lib/provider-logos.ts +++ b/studio/frontend/src/features/hub/lib/provider-logos.ts @@ -46,6 +46,11 @@ export interface ProviderLogo { * on the family stem so future minor versions are picked up automatically. */ prefixes: readonly string[]; + /** + * Case-insensitive fallback after all prefixes miss; matched at a word boundary + * (`gemma` -> `diffusiongemma-`, `gemma-3n`, not `gemmafy`). Use stems unique to one provider. + */ + stems?: readonly string[]; } export const PROVIDER_LOGOS: readonly ProviderLogo[] = [ @@ -217,6 +222,7 @@ export const PROVIDER_LOGOS: readonly ProviderLogo[] = [ "metricx-", "bert-", ], + stems: ["gemma"], }, // After NVIDIA so `Mistral-NeMo-` wins; generic Mistral-/Mixtral- fall through here. @@ -248,9 +254,18 @@ export const PROVIDER_LOGOS: readonly ProviderLogo[] = [ }, ]; +function stemMatchesAtBoundary(haystack: string, stem: string): boolean { + for (let at = haystack.indexOf(stem); at !== -1; at = haystack.indexOf(stem, at + 1)) { + const next = haystack[at + stem.length]; + if (next === undefined || next < "a" || next > "z") return true; + } + return false; +} + /** - * Resolve a repo name (after `owner/`) to its upstream provider, or null. - * Iterates PROVIDER_LOGOS in declaration order; first prefix match wins. + * Resolve a repo name (after `owner/`) to its provider, or null. Pass 1: prefix + * match in declaration order (first wins). Pass 2: case-insensitive `stems` + * boundary fallback; prefixes always win. */ export function matchProviderLogo(repoName: string): ProviderLogo | null { if (!repoName) return null; @@ -259,6 +274,12 @@ export function matchProviderLogo(repoName: string): ProviderLogo | null { return provider; } } + const lower = repoName.toLowerCase(); + for (const provider of PROVIDER_LOGOS) { + if (provider.stems?.some((stem) => stemMatchesAtBoundary(lower, stem))) { + return provider; + } + } return null; }