Skip to content

Commit fd7eea4

Browse files
committed
style: show human-readable provider name in Settings model dropdown
1 parent fbe98b9 commit fd7eea4

File tree

3 files changed

+104
-5
lines changed

3 files changed

+104
-5
lines changed

apps/desktop/src/features/agents/display-helpers.ts

Lines changed: 18 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -13,17 +13,32 @@ export function formatTaskCount(count: number): string {
1313
}
1414

1515
/**
16-
* Auth-action phrases → clean provider display names.
16+
* Auth-action phrases and slug-style IDs → clean provider display names.
1717
*/
1818
const PROVIDER_NAME_MAP: Record<string, string> = {
1919
"Sign in with GitHub": "GitHub Copilot",
2020
"Use API key": "API Key",
21+
"github-copilot": "GitHub Copilot",
22+
"openai": "OpenAI",
23+
"anthropic": "Anthropic",
24+
"google": "Google",
2125
};
2226

27+
/**
28+
* Convert a kebab-case slug to title case (e.g. "my-provider" → "My Provider").
29+
*/
30+
function formatProviderSlug(slug: string): string {
31+
return slug
32+
.split("-")
33+
.map((w) => w.charAt(0).toUpperCase() + w.slice(1))
34+
.join(" ");
35+
}
36+
2337
/**
2438
* Cleans up provider names that read like auth actions
25-
* (e.g. "Sign in with GitHub") into proper display labels.
39+
* (e.g. "Sign in with GitHub") or raw slugs (e.g. "github-copilot")
40+
* into proper display labels.
2641
*/
2742
export function cleanProviderName(name: string): string {
28-
return PROVIDER_NAME_MAP[name] ?? name;
43+
return PROVIDER_NAME_MAP[name] ?? (name.includes("-") ? formatProviderSlug(name) : name);
2944
}

apps/desktop/src/features/settings/components/ProjectSettings.tsx

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ import {
1919
SelectTrigger,
2020
SelectValue,
2121
} from "@/components/ui/select";
22+
import { cleanProviderName } from "@/features/agents/display-helpers";
2223
import type { SidecarClient } from "@/lib/sidecar/client";
2324
import { toast } from "sonner";
2425
import { DeleteProjectDialog } from "./DeleteProjectDialog";
@@ -101,7 +102,10 @@ export function ProjectSettings({
101102
const [models, setModels] = useState<ProviderModelOption[]>([]);
102103
const [isLoadingModels, setIsLoadingModels] = useState(false);
103104

104-
// Build provider list: connected provider IDs with display names from the providers catalog
105+
// Build provider list: connected provider IDs with human-readable display names
106+
const connectionNameMap = new Map(
107+
(authOverview?.connections ?? []).map((c) => [c.providerId, c.providerName]),
108+
);
105109
const providerNameMap = new Map(
106110
(authOverview?.providers ?? []).map((p) => [p.id, p.name]),
107111
);
@@ -110,7 +114,7 @@ export function ProjectSettings({
110114
: [];
111115
const connectedProviders = connectedProviderIds.map((pid) => ({
112116
providerId: pid,
113-
displayName: providerNameMap.get(pid) ?? pid,
117+
displayName: cleanProviderName(connectionNameMap.get(pid) ?? providerNameMap.get(pid) ?? pid),
114118
}));
115119

116120
// Load model catalog when provider changes
Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
import assert from "node:assert/strict";
2+
import test from "node:test";
3+
import { readFileSync } from "node:fs";
4+
import { resolve } from "node:path";
5+
6+
const src = readFileSync(
7+
resolve(import.meta.dirname, "ProjectSettings.tsx"),
8+
"utf-8",
9+
);
10+
11+
const displayHelpersSrc = readFileSync(
12+
resolve(import.meta.dirname, "../../agents/display-helpers.ts"),
13+
"utf-8",
14+
);
15+
16+
// ---------------------------------------------------------------------------
17+
// AC1: Settings PROVIDER dropdown shows human-readable names, not raw slugs
18+
// ---------------------------------------------------------------------------
19+
20+
void test("Settings imports cleanProviderName from display-helpers", () => {
21+
assert.ok(
22+
src.includes("cleanProviderName"),
23+
"ProjectSettings must import cleanProviderName to format provider display names",
24+
);
25+
});
26+
27+
void test("Settings applies cleanProviderName to provider display names", () => {
28+
assert.ok(
29+
src.includes("cleanProviderName("),
30+
"ProjectSettings must call cleanProviderName() when building provider display names",
31+
);
32+
});
33+
34+
// ---------------------------------------------------------------------------
35+
// AC2: Display format matches Connections page provider naming
36+
// ---------------------------------------------------------------------------
37+
38+
void test("Settings uses same cleanProviderName utility as Connections page", () => {
39+
assert.ok(
40+
src.includes('from "@/features/agents/display-helpers"') ||
41+
src.includes("from '../../agents/display-helpers'") ||
42+
src.includes('from "../../agents/display-helpers"'),
43+
"Must import cleanProviderName from the same module the Connections page uses",
44+
);
45+
});
46+
47+
// ---------------------------------------------------------------------------
48+
// AC3: The underlying data value remains the slug (no functional change)
49+
// ---------------------------------------------------------------------------
50+
51+
void test("Select option value remains the raw providerId slug", () => {
52+
assert.ok(
53+
src.includes("value={p.providerId}") || src.includes("value={p.providerId}"),
54+
"SelectItem value must use the raw providerId slug for data binding",
55+
);
56+
});
57+
58+
// ---------------------------------------------------------------------------
59+
// AC4: All provider options use human-readable labels
60+
// ---------------------------------------------------------------------------
61+
62+
void test("display-helpers handles slug-style provider names as fallback", () => {
63+
assert.ok(
64+
displayHelpersSrc.includes("formatProviderSlug") ||
65+
displayHelpersSrc.includes("github-copilot") ||
66+
displayHelpersSrc.includes("split"),
67+
"display-helpers must handle slug-to-display conversion for provider names that are raw slugs",
68+
);
69+
});
70+
71+
// ---------------------------------------------------------------------------
72+
// AC5: Provider name resolution uses connection providerName field
73+
// ---------------------------------------------------------------------------
74+
75+
void test("Settings builds provider name from connections providerName field", () => {
76+
assert.ok(
77+
src.includes("c.providerName") || src.includes("providerName"),
78+
"Must use connection.providerName for name resolution (same source as Connections page)",
79+
);
80+
});

0 commit comments

Comments
 (0)