File size: 3,495 Bytes
9b906ea | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 | import OpenHandsLogo from "#/assets/branding/openhands-logo.svg?react";
import TerminalIcon from "#/icons/terminal.svg?react";
import {
CLAUDE_CODE_MARK_PATH,
CLAUDE_CODE_VIEWBOX,
CODEX_MARK_PATH,
CODEX_VIEWBOX,
GEMINI_MARK_PATH,
GEMINI_VIEWBOX,
} from "#/constants/acp-brand-marks";
import type { ACPProviderIcon } from "#/constants/acp-providers";
import { cn } from "#/utils/utils";
/**
* Icons the conversation chip + onboarding tiles can render. Strictly broader
* than {@link ACPProviderIcon} — that type covers ACP CLI subprocesses only
* (Claude Code, Codex, Gemini, generic terminal fallback), whereas this type
* additionally includes the native OpenHands harness.
*/
export type AgentBrandIconKind = "openhands" | ACPProviderIcon;
// The OpenHands wordmark renders at a 3:2 (width:height) ratio. Kept as a
// named constant so the conversation chip and the onboarding tile (24×16)
// stay visually identical — see ``AgentOptionIcon`` in choose-agent-step.tsx.
const OPENHANDS_LOGO_ASPECT_RATIO = 3 / 2;
interface AgentBrandIconProps {
kind: AgentBrandIconKind;
size?: number;
className?: string;
"data-testid"?: string;
}
export function AgentBrandIcon({
kind,
size = 12,
className,
"data-testid": testId,
}: AgentBrandIconProps) {
if (kind === "openhands") {
// The shipped SVG draws the wordmark with ``fill="white"`` paths but
// leaves the two hand shapes as ``fill="transparent"`` (negative space).
// Recolor only the non-transparent paths to ``currentColor`` so the logo
// inherits the chip's text color *without* filling in the hands — a
// blanket ``[&_path]`` selector turns the whole mark into a solid blob.
return (
<OpenHandsLogo
width={Math.round(size * OPENHANDS_LOGO_ASPECT_RATIO)}
height={size}
className={cn(
"shrink-0 [&_path:not([fill=transparent])]:fill-current",
className,
)}
data-testid={testId ?? "agent-brand-icon-openhands"}
aria-hidden
/>
);
}
if (kind === "claude-code") {
return (
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox={CLAUDE_CODE_VIEWBOX}
width={size}
height={size}
className={cn("shrink-0", className)}
data-testid={testId ?? "agent-brand-icon-claude-code"}
aria-hidden
>
<path fill="currentColor" d={CLAUDE_CODE_MARK_PATH} />
</svg>
);
}
if (kind === "codex") {
return (
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox={CODEX_VIEWBOX}
width={size}
height={size}
className={cn("shrink-0", className)}
data-testid={testId ?? "agent-brand-icon-codex"}
aria-hidden
>
<path
fillRule="evenodd"
clipRule="evenodd"
d={CODEX_MARK_PATH}
fill="currentColor"
/>
</svg>
);
}
if (kind === "gemini") {
return (
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox={GEMINI_VIEWBOX}
width={size}
height={size}
className={cn("shrink-0", className)}
data-testid={testId ?? "agent-brand-icon-gemini"}
aria-hidden
>
<path fill="currentColor" d={GEMINI_MARK_PATH} />
</svg>
);
}
return (
<TerminalIcon
width={size}
height={size}
className={cn("shrink-0", className)}
data-testid={testId ?? "agent-brand-icon-generic"}
aria-hidden
/>
);
}
|