FN-8354: add Oh My Pi provider logo
Render the official Oh My Pi mark throughout provider selection and analytics surfaces. - Add a tokenized OMP SVG icon and provider aliases. - Infer OMP-tagged model identifiers without misattributing bare models. - Cover OMP icon rendering and inference with dashboard tests. Files changed: packages/dashboard/app/components/ProviderIcon.tsx | 26 +++++++++++++++++++++- .../app/components/__tests__/ProviderIcon.test.tsx | 23 +++++++++++++++++++ packages/dashboard/app/styles.css | 1 + .../app/utils/__tests__/providerIconKey.test.ts | 8 +++++++ packages/dashboard/app/utils/providerIconKey.ts | 9 ++++++++ 5 files changed, 66 insertions(+), 1 deletion(-) Fusion-Task-Id: FN-8354 Fusion-Task-Lineage: 71d5638c-b41e-4960-9391-c7a441d04f51 Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai>
This commit is contained in:
@@ -526,6 +526,27 @@ function GitHubIcon({ size, color, label = "GitHub" }: { size: number; color: st
|
||||
);
|
||||
}
|
||||
|
||||
/*
|
||||
FNXC:ProviderIcons 2026-07-18-18:24:
|
||||
FN-8354: OMP ACP is a first-class provider, so its shared dashboard icon must use the official omp.sh favicon's stepped T mark rather than the generic Cpu fallback. Render its official single-color silhouette with the provider token so it remains legible in either theme.
|
||||
Source: https://omp.sh/favicon.svg
|
||||
*/
|
||||
function OmpIcon({ size, color, label = "Oh My Pi" }: { size: number; color: string; label?: string }) {
|
||||
return (
|
||||
<svg
|
||||
width={size}
|
||||
height={size}
|
||||
viewBox="0 0 64 64"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
data-testid="omp-icon"
|
||||
aria-label={label}
|
||||
>
|
||||
<path d="M14 16h36v8H40v32h-8V24h-6v22h-8V24h-4z" fill={color} />
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
|
||||
// Hermes — caduceus (winged staff) mark, single-color so it adapts to theme.
|
||||
function HermesIcon({ size, color, label = "Hermes" }: { size: number; color: string; label?: string }) {
|
||||
return (
|
||||
@@ -817,7 +838,10 @@ const providerConfig: Record<
|
||||
vercel: { component: VercelIcon, color: "var(--provider-vercel)" },
|
||||
"vercel-ai-gateway": { component: VercelIcon, color: "var(--provider-vercel)", label: "Vercel AI Gateway" },
|
||||
|
||||
// Runtime-plugin marks (Hermes / OpenClaw / Paperclip).
|
||||
// Runtime-plugin marks (OMP / Hermes / OpenClaw / Paperclip).
|
||||
"omp-cli": { component: OmpIcon, color: "var(--provider-omp)", label: "Oh My Pi — via omp ACP" },
|
||||
omp: { component: OmpIcon, color: "var(--provider-omp)", label: "Oh My Pi" },
|
||||
"oh-my-pi": { component: OmpIcon, color: "var(--provider-omp)", label: "Oh My Pi" },
|
||||
hermes: { component: HermesIcon, color: "var(--provider-hermes)", label: "Hermes" },
|
||||
"hermes-agent": { component: HermesIcon, color: "var(--provider-hermes)", label: "Hermes" },
|
||||
hermesagent: { component: HermesIcon, color: "var(--provider-hermes)", label: "Hermes" },
|
||||
|
||||
@@ -74,6 +74,29 @@ describe("ProviderIcon", () => {
|
||||
expect(document.querySelector('[data-provider="cursor"] svg:not([data-testid])')).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("renders the OMP brand icon, auth label, and tokenized color for omp-cli", () => {
|
||||
render(<ProviderIcon provider="omp-cli" />);
|
||||
const svg = screen.getByTestId("omp-icon");
|
||||
expect(svg).toBeInTheDocument();
|
||||
expect(screen.getByLabelText("Oh My Pi — via omp ACP")).toBeInTheDocument();
|
||||
expect(svg.parentElement).toHaveAttribute("data-provider", "omp-cli");
|
||||
expect(svg.parentElement).toHaveStyle({ color: "var(--provider-omp)" });
|
||||
expect(svg.querySelector("path")).toHaveAttribute("fill", "var(--provider-omp)");
|
||||
});
|
||||
|
||||
it.each(["omp", "oh-my-pi"])("renders the OMP brand icon for the %s alias", (provider) => {
|
||||
render(<ProviderIcon provider={provider} />);
|
||||
const svg = screen.getByTestId("omp-icon");
|
||||
expect(svg.parentElement).toHaveAttribute("data-provider", provider);
|
||||
expect(svg.parentElement).toHaveStyle({ color: "var(--provider-omp)" });
|
||||
});
|
||||
|
||||
it("infers OMP model-provider strings to the OMP brand icon", () => {
|
||||
render(<ProviderIcon provider="omp/claude-sonnet-4" />);
|
||||
expect(screen.getByTestId("omp-icon")).toBeInTheDocument();
|
||||
expect(document.querySelector('[data-provider="omp/claude-sonnet-4"] svg:not([data-testid])')).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("renders grok-cli icon reusing the xAI brand mark", () => {
|
||||
render(<ProviderIcon provider="grok-cli" />);
|
||||
const svg = screen.getByTestId("xai-icon");
|
||||
|
||||
@@ -385,6 +385,7 @@ svg.spinning {
|
||||
--provider-cursor-cli: #7c3aed;
|
||||
--provider-ollama: #d4a27f;
|
||||
/* Runtime-plugin marks. */
|
||||
--provider-omp: var(--text);
|
||||
--provider-hermes: #d4961c;
|
||||
--provider-openclaw: #ff4f40;
|
||||
--provider-paperclip: var(--text);
|
||||
|
||||
@@ -13,6 +13,9 @@ describe("inferProviderIconKey", () => {
|
||||
["Cursor", "cursor-cli"],
|
||||
["cursor-agent", "cursor-cli"],
|
||||
["cursor/gpt-5", "cursor-cli"],
|
||||
["omp-cli/MiniMax-M2.5", "omp-cli"],
|
||||
["omp/claude-sonnet-4", "omp-cli"],
|
||||
["Oh My Pi", "omp-cli"],
|
||||
["ollama/llama3", "ollama"],
|
||||
["minimax-text-01", "minimax"],
|
||||
["zhipu-glm-4", "zai"],
|
||||
@@ -32,6 +35,11 @@ describe("inferProviderIconKey", () => {
|
||||
expect(inferProviderIconKey(input)).toBe(expected);
|
||||
});
|
||||
|
||||
it("does not attribute bare model ids to OMP", () => {
|
||||
expect(inferProviderIconKey("MiniMax-M2.5")).toBe("minimax");
|
||||
expect(inferProviderIconKey("claude-sonnet-4")).not.toBe("omp-cli");
|
||||
});
|
||||
|
||||
it("returns unknown ids unchanged so ProviderIcon can render its fallback", () => {
|
||||
expect(inferProviderIconKey("custom-model-v1")).toBe("custom-model-v1");
|
||||
expect(inferProviderIconKey("not-a-glmish-model")).toBe("not-a-glmish-model");
|
||||
|
||||
@@ -6,6 +6,8 @@ FNXC:ProviderIcons 2026-06-28-20:46:
|
||||
Standalone GLM model ids such as glm-5.1, glm-4.5-air, and glm-5v-turbo are Z.ai/Zhipu-family models even when the analytics label omits zai/zhipu. Match GLM only at model-id segment boundaries so unrelated words do not hijack the Z.ai provider icon.
|
||||
*/
|
||||
const GLM_MODEL_SEGMENT_PATTERN = /(?:^|[/:_-])glm(?:$|[\d._:-]|-[\da-z])/;
|
||||
const OMP_PROVIDER_SEGMENT_PATTERN = /(?:^|[/:_\s-])omp(?:$|[/:_\s-])/;
|
||||
const OMP_PROVIDER_NAME_PATTERN = /oh[-\s]my[-\s]pi/;
|
||||
|
||||
export function inferProviderIconKey(modelOrProviderName: string): string {
|
||||
const normalized = modelOrProviderName.toLowerCase();
|
||||
@@ -19,6 +21,13 @@ export function inferProviderIconKey(modelOrProviderName: string): string {
|
||||
if (normalized.includes("cursor")) {
|
||||
return "cursor-cli";
|
||||
}
|
||||
/*
|
||||
FNXC:ProviderIcons 2026-07-18-18:24:
|
||||
FN-8354: omp ACP model ids can contain another vendor model name (for example omp/claude-sonnet-4), but must retain the OMP brand mark on selection and analytics surfaces. Match only an OMP segment or the Oh My Pi name so unrelated strings and untagged model ids keep existing inference/fallback behavior.
|
||||
*/
|
||||
if (OMP_PROVIDER_SEGMENT_PATTERN.test(normalized) || OMP_PROVIDER_NAME_PATTERN.test(normalized)) {
|
||||
return "omp-cli";
|
||||
}
|
||||
if (normalized.includes("claude") || normalized.includes("anthropic")) {
|
||||
return "anthropic";
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user