Skip to content

Commit 8e63e73

Browse files
committed
gui: replace emoji with Lucide SVG icons for sol/terra/luna
- model-display.ts: render inline IconSun/IconGlobe/IconMoon (React elements) instead of emoji characters - ui.tsx: SelectOption.label accepts ReactNode for rich dropdown labels - icons.tsx: remove duplicate icon declarations Icons render in all surfaces: Dashboard dropdowns, Models list, Subagents, Usage tables, Logs. No emoji — SVG stroke icons only.
1 parent 8078d88 commit 8e63e73

3 files changed

Lines changed: 36 additions & 16 deletions

File tree

gui/src/icons.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ export const IconGithub = (p: P) => (<svg {...S(p)}><path d="M9 19c-5 1.5-5-2.5-
2828
export const IconPower = (p: P) => (<svg {...S(p)}><path d="M18.4 5.6a9 9 0 1 1-12.8 0"/><path d="M12 2v10"/></svg>);
2929
export const IconExternal = (p: P) => (<svg {...S(p)}><path d="M15 3h6v6M10 14 21 3M18 13v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h6"/></svg>);
3030
export const IconKey = (p: P) => (<svg {...S(p)}><circle cx="7.5" cy="15.5" r="4.5"/><path d="m10.7 12.3 9.6-9.6M16 7l3 3M14 9l2 2"/></svg>);
31+
3132
export const IconLock = (p: P) => (<svg {...S(p)}><rect x="4" y="11" width="16" height="10" rx="2"/><path d="M8 11V7a4 4 0 0 1 8 0v4"/></svg>);
3233
export const IconTicket = (p: P) => (<svg {...S(p)}><path d="M2 9a3 3 0 0 1 0 6v2a2 2 0 0 0 2 2h16a2 2 0 0 0 2-2v-2a3 3 0 0 1 0-6V7a2 2 0 0 0-2-2H4a2 2 0 0 0-2 2Z"/><path d="M13 5v2"/><path d="M13 17v2"/><path d="M13 11v2"/></svg>);
3334
export const IconLink = (p: P) => (<svg {...S(p)}><path d="M9 17H7A5 5 0 0 1 7 7h2M15 7h2a5 5 0 0 1 0 10h-2M8 12h8"/></svg>);

gui/src/model-display.ts

Lines changed: 34 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,42 @@
11
/**
2-
* Global model display-name mapping: prepends a visual icon for recognized model slugs.
3-
* Used across all GUI surfaces (dropdowns, tables, badges) so the 5.6 trio is instantly
4-
* distinguishable.
2+
* Global model display-name mapping: renders an inline SVG icon for recognized
3+
* model slugs. Used across all GUI surfaces (dropdowns, tables, badges) so the
4+
* 5.6 trio is instantly distinguishable. No emoji — Lucide-style SVG only.
55
*/
6+
import { createElement, type ReactNode } from "react";
7+
import { IconSun, IconGlobe, IconMoon } from "./icons";
68

7-
const MODEL_ICONS: Record<string, string> = {
8-
"gpt-5.6-sol": "\u2600\uFE0F", // ☀️ sun
9-
"gpt-5.6-terra": "\uD83C\uDF0D", // 🌍 earth
10-
"gpt-5.6-luna": "\uD83C\uDF19", // 🌙 moon
9+
type IconComponent = typeof IconSun;
10+
11+
const MODEL_ICON_MAP: Record<string, IconComponent> = {
12+
"gpt-5.6-sol": IconSun,
13+
"gpt-5.6-terra": IconGlobe,
14+
"gpt-5.6-luna": IconMoon,
1115
};
1216

13-
/** Return a display label with an icon prefix for recognized slugs, else the raw slug. */
14-
export function modelLabel(slug: string): string {
15-
// Check bare slug first, then try the tail after a provider prefix (e.g. "openrouter/openai/gpt-5.6-sol").
16-
const icon = MODEL_ICONS[slug] ?? MODEL_ICONS[slug.slice(slug.lastIndexOf("/") + 1)];
17-
return icon ? `${icon} ${slug}` : slug;
17+
const ICON_STYLE = { width: 14, height: 14, flexShrink: 0, verticalAlign: "text-bottom" as const, marginRight: 4 };
18+
19+
/** Resolve the bare slug from a potentially provider-prefixed id. */
20+
function bareSlug(slug: string): string {
21+
return slug.slice(slug.lastIndexOf("/") + 1);
22+
}
23+
24+
/** Return the icon component for a model slug, or null. */
25+
function resolveIcon(slug: string): IconComponent | null {
26+
return MODEL_ICON_MAP[slug] ?? MODEL_ICON_MAP[bareSlug(slug)] ?? null;
27+
}
28+
29+
/** Render a model name with an inline SVG icon prefix (ReactNode). Falls back to plain text. */
30+
export function modelLabel(slug: string): ReactNode {
31+
const Icon = resolveIcon(slug);
32+
if (!Icon) return slug;
33+
return createElement("span", { style: { display: "inline-flex", alignItems: "center", gap: 4 } },
34+
createElement(Icon, { style: ICON_STYLE }),
35+
slug,
36+
);
1837
}
1938

20-
/** Return just the icon for a slug, or empty string if none. */
21-
export function modelIcon(slug: string): string {
22-
return MODEL_ICONS[slug] ?? MODEL_ICONS[slug.slice(slug.lastIndexOf("/") + 1)] ?? "";
39+
/** True when this slug has a visual icon. */
40+
export function hasModelIcon(slug: string): boolean {
41+
return resolveIcon(slug) !== null;
2342
}

gui/src/ui.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ export function Notice({ tone, children }: { tone: "ok" | "err"; children: React
2121
);
2222
}
2323

24-
export interface SelectOption { value: string; label: string }
24+
export interface SelectOption { value: string; label: React.ReactNode }
2525

2626
export function Select({ value, options, onChange, disabled, label, style }: {
2727
value: string;

0 commit comments

Comments
 (0)