Skip to content
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@
"dev": "node scripts/dev-server.mjs",
"postinstall": "node scripts/fix-node-pty-permissions.mjs",
"start": "node dist/cli.js serve",
"test": "tsx src/config.test.ts && tsx src/request-meta.test.ts && tsx src/incoming-artifacts.test.ts && tsx src/artifact-download.test.ts && tsx src/ui/card-types.test.ts && tsx src/ui/patch-display.test.ts && tsx src/ui/tool-display.test.ts && tsx src/apply-patch.test.ts && tsx src/process-platform.test.ts && tsx src/process-sessions.test.ts && tsx src/mcp-sessions.test.ts && tsx src/server-shutdown.test.ts && tsx src/local-agent-runtime.test.ts && tsx src/local-agent-adapters.test.ts && tsx src/local-agent-availability.test.ts && tsx src/local-agent-profiles.test.ts && tsx src/local-agent-targets.test.ts && tsx src/local-agent-store.test.ts && tsx src/roots.test.ts && tsx src/skills.test.ts && tsx src/workspaces.test.ts && tsx src/workspace-conversation.test.ts && tsx src/review-checkpoints.test.ts && tsx src/server.test.ts && tsx src/oauth-store.test.ts && tsx src/cli.test.ts",
"test": "tsx src/config.test.ts && tsx src/request-meta.test.ts && tsx src/incoming-artifacts.test.ts && tsx src/artifact-download.test.ts && tsx src/ui/card-types.test.ts && tsx src/ui/icons.test.ts && tsx src/ui/patch-display.test.ts && tsx src/ui/tool-display.test.ts && tsx src/apply-patch.test.ts && tsx src/process-platform.test.ts && tsx src/process-sessions.test.ts && tsx src/mcp-sessions.test.ts && tsx src/server-shutdown.test.ts && tsx src/local-agent-runtime.test.ts && tsx src/local-agent-adapters.test.ts && tsx src/local-agent-availability.test.ts && tsx src/local-agent-profiles.test.ts && tsx src/local-agent-targets.test.ts && tsx src/local-agent-store.test.ts && tsx src/roots.test.ts && tsx src/skills.test.ts && tsx src/workspaces.test.ts && tsx src/workspace-conversation.test.ts && tsx src/review-checkpoints.test.ts && tsx src/server.test.ts && tsx src/oauth-store.test.ts && tsx src/cli.test.ts",
"typecheck": "tsc -p tsconfig.json --noEmit"
},
"keywords": [],
Expand Down
11 changes: 11 additions & 0 deletions src/ui/assets/provider-logos/cursor-light.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
26 changes: 26 additions & 0 deletions src/ui/assets/provider-logos/pi-auto.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
28 changes: 28 additions & 0 deletions src/ui/icons.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import assert from "node:assert/strict";
import { getProviderLogo } from "./icons.js";

const codexLogo = getProviderLogo("codex");
assert.ok(codexLogo);
assert.equal(codexLogo.light, codexLogo.dark);
assert.equal(codexLogo.invertInLight, true);
assert.match(codexLogo.light, /openai-dark/);

const copilotLogo = getProviderLogo("copilot");
assert.ok(copilotLogo);
assert.equal(copilotLogo.light, copilotLogo.dark);
assert.equal(copilotLogo.invertInLight, true);
assert.match(copilotLogo.light, /copilot-dark/);

const cursorLogo = getProviderLogo("cursor");
assert.ok(cursorLogo);
assert.notEqual(cursorLogo.light, cursorLogo.dark);
assert.match(cursorLogo.light, /cursor-light/);
assert.match(cursorLogo.dark, /cursor-dark/);

const piLogo = getProviderLogo("pi");
assert.ok(piLogo);
assert.equal(piLogo.light, piLogo.dark);
assert.match(piLogo.light, /pi-auto/);

assert.deepEqual(getProviderLogo(" CoDeX "), codexLogo);
assert.equal(getProviderLogo("unknown"), undefined);
45 changes: 37 additions & 8 deletions src/ui/icons.ts
Original file line number Diff line number Diff line change
Expand Up @@ -52,16 +52,45 @@ export const toolIcons = {

export type ToolIcon = IconNode;

export interface ProviderLogo {
light: string;
dark: string;
invertInLight?: boolean;
}

const claudeLogo = new URL("./assets/provider-logos/claude.svg", import.meta.url).href;
const codexLogo = new URL(
"./assets/provider-logos/openai-dark.svg",
import.meta.url,
).href;
const copilotLogo = new URL(
"./assets/provider-logos/copilot-dark.svg",
import.meta.url,
).href;
const cursorLightLogo = new URL(
"./assets/provider-logos/cursor-light.svg",
import.meta.url,
).href;
const cursorDarkLogo = new URL(
"./assets/provider-logos/cursor-dark.svg",
import.meta.url,
).href;
const opencodeLogo = new URL(
"./assets/provider-logos/opencode-dark.svg",
import.meta.url,
).href;
const piLogo = new URL("./assets/provider-logos/pi-auto.svg", import.meta.url).href;

const providerLogos = {
claude: new URL("./assets/provider-logos/claude.svg", import.meta.url).href,
codex: new URL("./assets/provider-logos/openai-dark.svg", import.meta.url).href,
copilot: new URL("./assets/provider-logos/copilot-dark.svg", import.meta.url).href,
cursor: new URL("./assets/provider-logos/cursor-dark.svg", import.meta.url).href,
opencode: new URL("./assets/provider-logos/opencode-dark.svg", import.meta.url).href,
pi: new URL("./assets/provider-logos/pi-on-dark.svg", import.meta.url).href,
} as const;
claude: { light: claudeLogo, dark: claudeLogo },
codex: { light: codexLogo, dark: codexLogo, invertInLight: true },
copilot: { light: copilotLogo, dark: copilotLogo, invertInLight: true },
cursor: { light: cursorLightLogo, dark: cursorDarkLogo },
opencode: { light: opencodeLogo, dark: opencodeLogo },
pi: { light: piLogo, dark: piLogo },
} as const satisfies Record<string, ProviderLogo>;

export function getProviderLogo(name: string): string | undefined {
export function getProviderLogo(name: string): ProviderLogo | undefined {
const normalizedName = name.trim().toLowerCase() as keyof typeof providerLogos;
return providerLogos[normalizedName];
}
Expand Down
16 changes: 16 additions & 0 deletions src/ui/workspace-app.css
Original file line number Diff line number Diff line change
Expand Up @@ -458,6 +458,22 @@ body {
object-fit: contain;
}

.workspace-provider-logo-theme-light {
display: none;
}

:root[data-theme="light"] .workspace-provider-logo-theme-light {
display: block;
}

:root[data-theme="light"] .workspace-provider-logo-theme-dark {
display: none;
}

:root[data-theme="light"] .workspace-provider-logo-invert-in-light {
filter: invert(1);
}

.workspace-provider-logo.muted {
opacity: 0.62;
}
Expand Down
38 changes: 30 additions & 8 deletions src/ui/workspace-app.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,13 @@ import {
type ToolName,
type ToolResultCard,
} from "./card-types.js";
import { getProviderLogo, renderIcon, toolIcons, type ToolIcon } from "./icons.js";
import {
getProviderLogo,
renderIcon,
toolIcons,
type ProviderLogo,
type ToolIcon,
} from "./icons.js";
import {
getToolDisplay,
getToolHeaderSummary,
Expand Down Expand Up @@ -577,7 +583,7 @@ function renderWorkspacePayload(container: HTMLElement, card: ToolResultCard): v

interface WorkspaceChip {
label: string;
logo?: string;
logo?: ProviderLogo;
profile?: boolean;
bareLogo?: boolean;
ariaLabel?: string;
Expand Down Expand Up @@ -848,16 +854,32 @@ function renderWorkspaceChips(chips: WorkspaceChip[]): HTMLElement {
item.setAttribute("aria-label", chip.ariaLabel ?? chip.label);
}
if (chip.logo) {
const logo = document.createElement("img");
logo.className = bareLogo
const baseClassName = bareLogo
? "workspace-provider-logo-image"
: chip.profile
? "workspace-agent-profile-logo"
: "workspace-chip-logo";
logo.src = chip.logo;
logo.alt = "";
logo.setAttribute("aria-hidden", "true");
item.append(logo);
const logoSources: Array<{ src: string; theme?: "light" | "dark" }> =
chip.logo.light === chip.logo.dark
? [{ src: chip.logo.light }]
: [
{ src: chip.logo.light, theme: "light" },
{ src: chip.logo.dark, theme: "dark" },
];
for (const source of logoSources) {
const logo = document.createElement("img");
logo.className = [
baseClassName,
chip.logo.invertInLight
? "workspace-provider-logo-invert-in-light"
: undefined,
source.theme ? `workspace-provider-logo-theme-${source.theme}` : undefined,
].filter(Boolean).join(" ");
logo.src = source.src;
logo.alt = "";
logo.setAttribute("aria-hidden", "true");
item.append(logo);
}
}
if (!bareLogo) {
item.append(element("span", { className: "workspace-chip-label", text: chip.label }));
Expand Down