authorgravatar for julius0216@outlook.comJulius Marminge <julius0216@outlook.com> 2026-02-27 10:10:57-08:00
committergravatar for jmarminge@gmail.comJulius Marminge <jmarminge@gmail.com> 2026-03-05 23:00:41-08:00
log2c110e8ce24f4fe0af7afb22e5859e70b73f6f18
treea38e1f4924d23904063f3809268ab0b310102771
parent5707a6ee1dc470198ecb01a0b6181c5082c7a81f

Show provider logos in composer model trigger

Co-authored-by: codex <codex@users.noreply.github.com>

2 files changed, 34 insertions(+), 123 deletions(-)

apps/web/src/components/ChatView.tsx+21-2
...@@ -119,7 +119,7 @@ import {...@@ -119,7 +119,7 @@ import {
119import { Separator } from "./ui/separator";119import { Separator } from "./ui/separator";
120import { Group, GroupSeparator } from "./ui/group";120import { Group, GroupSeparator } from "./ui/group";
121import { Menu, MenuItem, MenuPopup, MenuShortcut, MenuTrigger } from "./ui/menu";121import { Menu, MenuItem, MenuPopup, MenuShortcut, MenuTrigger } from "./ui/menu";
122import { CursorIcon, Icon, VisualStudioCode, Zed } from "./Icons";122import { ClaudeAI, CursorIcon, Icon, OpenAI } from "./Icons";
123import { cn, isMacPlatform, isWindowsPlatform } from "~/lib/utils";123import { cn, isMacPlatform, isWindowsPlatform } from "~/lib/utils";
124import { Badge } from "./ui/badge";124import { Badge } from "./ui/badge";
125import { Tooltip, TooltipPopup, TooltipTrigger } from "./ui/tooltip";125import { Tooltip, TooltipPopup, TooltipTrigger } from "./ui/tooltip";
...@@ -3887,6 +3887,11 @@ const PROVIDER_MODEL_GROUPS: ReadonlyArray<{...@@ -3887,6 +3887,11 @@ const PROVIDER_MODEL_GROUPS: ReadonlyArray<{
3887 options: getModelOptions(option.value),3887 options: getModelOptions(option.value),
3888}));3888}));
38893889
3890const PROVIDER_ICON_BY_PROVIDER: Record<ProviderKind, Icon> = {
3891 codex: OpenAI,
3892 claudeCode: ClaudeAI,
3893};
3894
3890const ProviderModelPicker = memo(function ProviderModelPicker(props: {3895const ProviderModelPicker = memo(function ProviderModelPicker(props: {
3891 provider: ProviderKind;3896 provider: ProviderKind;
3892 model: ModelSlug;3897 model: ModelSlug;
...@@ -3898,6 +3903,11 @@ const ProviderModelPicker = memo(function ProviderModelPicker(props: {...@@ -3898,6 +3903,11 @@ const ProviderModelPicker = memo(function ProviderModelPicker(props: {
3898 value: `${provider}:${slug}`,3903 value: `${provider}:${slug}`,
3899 })),3904 })),
3900 );3905 );
3906 const selectedModelLabel =
3907 PROVIDER_MODEL_GROUPS.find((group) => group.provider === props.provider)?.options.find(
3908 (option) => option.slug === props.model,
3909 )?.name ?? props.model;
3910 const ProviderIcon = PROVIDER_ICON_BY_PROVIDER[props.provider];
39013911
3902 return (3912 return (
3903 <Select3913 <Select
...@@ -3924,7 +3934,16 @@ const ProviderModelPicker = memo(function ProviderModelPicker(props: {...@@ -3924,7 +3934,16 @@ const ProviderModelPicker = memo(function ProviderModelPicker(props: {
3924 }3934 }
3925 >3935 >
3926 <SelectTrigger size="sm" variant="ghost">3936 <SelectTrigger size="sm" variant="ghost">
3927 <SelectValue />3937 <span className="flex min-w-0 items-center gap-2">
3938 <ProviderIcon
3939 aria-hidden="true"
3940 className={cn(
3941 "size-4 shrink-0",
3942 props.provider === "claudeCode" ? "" : "text-foreground/85",
3943 )}
3944 />
3945 <span className="truncate">{selectedModelLabel}</span>
3946 </span>
3928 </SelectTrigger>3947 </SelectTrigger>
3929 <SelectPopup alignItemWithTrigger={false}>3948 <SelectPopup alignItemWithTrigger={false}>
3930 {PROVIDER_MODEL_GROUPS.map(({ provider, label, options }) => (3949 {PROVIDER_MODEL_GROUPS.map(({ provider, label, options }) => (
apps/web/src/components/Icons.tsx+13-121
...@@ -20,125 +20,17 @@ export const CursorIcon: Icon = (props) => (...@@ -20,125 +20,17 @@ export const CursorIcon: Icon = (props) => (
20 </svg>20 </svg>
21);21);
2222
23export const VisualStudioCode: Icon = (props) => {23export const OpenAI: Icon = (props) => (
24 const id = useId();24 <svg {...props} preserveAspectRatio="xMidYMid" viewBox="0 0 256 260" fill="currentColor">
25 const maskId = `${id}-vscode-a`;25 <path d="M239.184 106.203a64.716 64.716 0 0 0-5.576-53.103C219.452 28.459 191 15.784 163.213 21.74A65.586 65.586 0 0 0 52.096 45.22a64.716 64.716 0 0 0-43.23 31.36c-14.31 24.602-11.061 55.634 8.033 76.74a64.665 64.665 0 0 0 5.525 53.102c14.174 24.65 42.644 37.324 70.446 31.36a64.72 64.72 0 0 0 48.754 21.744c28.481.025 53.714-18.361 62.414-45.481a64.767 64.767 0 0 0 43.229-31.36c14.137-24.558 10.875-55.423-8.083-76.483Zm-97.56 136.338a48.397 48.397 0 0 1-31.105-11.255l1.535-.87 51.67-29.825a8.595 8.595 0 0 0 4.247-7.367v-72.85l21.845 12.636c.218.111.37.32.409.563v60.367c-.056 26.818-21.783 48.545-48.601 48.601Zm-104.466-44.61a48.345 48.345 0 0 1-5.781-32.589l1.534.921 51.722 29.826a8.339 8.339 0 0 0 8.441 0l63.181-36.425v25.221a.87.87 0 0 1-.358.665l-52.335 30.184c-23.257 13.398-52.97 5.431-66.404-17.803ZM23.549 85.38a48.499 48.499 0 0 1 25.58-21.333v61.39a8.288 8.288 0 0 0 4.195 7.316l62.874 36.272-21.845 12.636a.819.819 0 0 1-.767 0L41.353 151.53c-23.211-13.454-31.171-43.144-17.804-66.405v.256Zm179.466 41.695-63.08-36.63L161.73 77.86a.819.819 0 0 1 .768 0l52.233 30.184a48.6 48.6 0 0 1-7.316 87.635v-61.391a8.544 8.544 0 0 0-4.4-7.213Zm21.742-32.69-1.535-.922-51.619-30.081a8.39 8.39 0 0 0-8.492 0L99.98 99.808V74.587a.716.716 0 0 1 .307-.665l52.233-30.133a48.652 48.652 0 0 1 72.236 50.391v.205ZM88.061 139.097l-21.845-12.585a.87.87 0 0 1-.41-.614V65.685a48.652 48.652 0 0 1 79.757-37.346l-1.535.87-51.67 29.825a8.595 8.595 0 0 0-4.246 7.367l-.051 72.697Zm11.868-25.58 28.138-16.217 28.188 16.218v32.434l-28.086 16.218-28.188-16.218-.052-32.434Z" />
26 const topShadowFilterId = `${id}-vscode-b`;26 </svg>
27 const sideShadowFilterId = `${id}-vscode-c`;27);
28 const overlayGradientId = `${id}-vscode-d`;
29
30 return (
31 <svg {...props} fill="none" viewBox="0 0 100 100">
32 <mask id={maskId} width="100" height="100" x="0" y="0" maskUnits="userSpaceOnUse">
33 <path
34 fill="#fff"
35 fillRule="evenodd"
36 d="M70.912 99.317a6.223 6.223 0 0 0 4.96-.19l20.589-9.907A6.25 6.25 0 0 0 100 83.587V16.413a6.25 6.25 0 0 0-3.54-5.632L75.874.874a6.226 6.226 0 0 0-7.104 1.21L29.355 38.04 12.187 25.01a4.162 4.162 0 0 0-5.318.236l-5.506 5.009a4.168 4.168 0 0 0-.004 6.162L16.247 50 1.36 63.583a4.168 4.168 0 0 0 .004 6.162l5.506 5.01a4.162 4.162 0 0 0 5.318.236l17.168-13.032L68.77 97.917a6.217 6.217 0 0 0 2.143 1.4ZM75.015 27.3 45.11 50l29.906 22.701V27.3Z"
37 clipRule="evenodd"
38 />
39 </mask>
40 <g mask={`url(#${maskId})`}>
41 <path
42 fill="#0065A9"
43 d="M96.461 10.796 75.857.876a6.23 6.23 0 0 0-7.107 1.207l-67.451 61.5a4.167 4.167 0 0 0 .004 6.162l5.51 5.009a4.167 4.167 0 0 0 5.32.236l81.228-61.62c2.725-2.067 6.639-.124 6.639 3.297v-.24a6.25 6.25 0 0 0-3.539-5.63Z"
44 />
45 <g filter={`url(#${topShadowFilterId})`}>
46 <path
47 fill="#007ACC"
48 d="m96.461 89.204-20.604 9.92a6.229 6.229 0 0 1-7.107-1.207l-67.451-61.5a4.167 4.167 0 0 1 .004-6.162l5.51-5.009a4.167 4.167 0 0 1 5.32-.236l81.228 61.62c2.725 2.067 6.639.124 6.639-3.297v.24a6.25 6.25 0 0 1-3.539 5.63Z"
49 />
50 </g>
51 <g filter={`url(#${sideShadowFilterId})`}>
52 <path
53 fill="#1F9CF0"
54 d="M75.858 99.126a6.232 6.232 0 0 1-7.108-1.21c2.306 2.307 6.25.674 6.25-2.588V4.672c0-3.262-3.944-4.895-6.25-2.589a6.232 6.232 0 0 1 7.108-1.21l20.6 9.908A6.25 6.25 0 0 1 100 16.413v67.174a6.25 6.25 0 0 1-3.541 5.633l-20.601 9.906Z"
55 />
56 </g>
57 <path
58 fill={`url(#${overlayGradientId})`}
59 fillRule="evenodd"
60 d="M70.851 99.317a6.224 6.224 0 0 0 4.96-.19L96.4 89.22a6.25 6.25 0 0 0 3.54-5.633V16.413a6.25 6.25 0 0 0-3.54-5.632L75.812.874a6.226 6.226 0 0 0-7.104 1.21L29.294 38.04 12.126 25.01a4.162 4.162 0 0 0-5.317.236l-5.507 5.009a4.168 4.168 0 0 0-.004 6.162L16.186 50 1.298 63.583a4.168 4.168 0 0 0 .004 6.162l5.507 5.009a4.162 4.162 0 0 0 5.317.236L29.294 61.96l39.414 35.958a6.218 6.218 0 0 0 2.143 1.4ZM74.954 27.3 45.048 50l29.906 22.701V27.3Z"
61 clipRule="evenodd"
62 opacity=".25"
63 style={{ mixBlendMode: "overlay" }}
64 />
65 </g>
66 <defs>
67 <filter
68 id={topShadowFilterId}
69 width="116.727"
70 height="92.246"
71 x="-8.394"
72 y="15.829"
73 colorInterpolationFilters="sRGB"
74 filterUnits="userSpaceOnUse"
75 >
76 <feFlood floodOpacity="0" result="BackgroundImageFix" />
77 <feColorMatrix
78 in="SourceAlpha"
79 values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0"
80 />
81 <feOffset />
82 <feGaussianBlur stdDeviation="4.167" />
83 <feColorMatrix values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.25 0" />
84 <feBlend in2="BackgroundImageFix" mode="overlay" result="effect1_dropShadow" />
85 <feBlend in="SourceGraphic" in2="effect1_dropShadow" result="shape" />
86 </filter>
87 <filter
88 id={sideShadowFilterId}
89 width="47.917"
90 height="116.151"
91 x="60.417"
92 y="-8.076"
93 colorInterpolationFilters="sRGB"
94 filterUnits="userSpaceOnUse"
95 >
96 <feFlood floodOpacity="0" result="BackgroundImageFix" />
97 <feColorMatrix
98 in="SourceAlpha"
99 values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0"
100 />
101 <feOffset />
102 <feGaussianBlur stdDeviation="4.167" />
103 <feColorMatrix values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.25 0" />
104 <feBlend in2="BackgroundImageFix" mode="overlay" result="effect1_dropShadow" />
105 <feBlend in="SourceGraphic" in2="effect1_dropShadow" result="shape" />
106 </filter>
107 <linearGradient
108 id={overlayGradientId}
109 x1="49.939"
110 x2="49.939"
111 y1=".258"
112 y2="99.742"
113 gradientUnits="userSpaceOnUse"
114 >
115 <stop stopColor="#fff" />
116 <stop offset="1" stopColor="#fff" stopOpacity="0" />
117 </linearGradient>
118 </defs>
119 </svg>
120 );
121};
122
123export const Zed: Icon = (props) => {
124 const id = useId();
125 const clipPathId = `${id}-zed-logo-a`;
12628
127 return (29export const ClaudeAI: Icon = (props) => (
128 <svg {...props} fill="none" viewBox="0 0 96 96">30 <svg {...props} preserveAspectRatio="xMidYMid" viewBox="0 0 256 257">
129 <g clipPath={`url(#${clipPathId})`}>31 <path
130 <path32 fill="#D97757"
131 fill="currentColor"33 d="m50.228 170.321 50.357-28.257.843-2.463-.843-1.361h-2.462l-8.426-.518-28.775-.778-24.952-1.037-24.175-1.296-6.092-1.297L0 125.796l.583-3.759 5.12-3.434 7.324.648 16.202 1.101 24.304 1.685 17.629 1.037 26.118 2.722h4.148l.583-1.685-1.426-1.037-1.101-1.037-25.147-17.045-27.22-18.017-14.258-10.37-7.713-5.25-3.888-4.925-1.685-10.758 7-7.713 9.397.649 2.398.648 9.527 7.323 20.35 15.75L94.817 91.9l3.889 3.24 1.555-1.102.195-.777-1.75-2.917-14.453-26.118-15.425-26.572-6.87-11.018-1.814-6.61c-.648-2.723-1.102-4.991-1.102-7.778l7.972-10.823L71.42 0 82.05 1.426l4.472 3.888 6.61 15.101 10.694 23.786 16.591 32.34 4.861 9.592 2.592 8.879.973 2.722h1.685v-1.556l1.36-18.211 2.528-22.36 2.463-28.776.843-8.1 4.018-9.722 7.971-5.25 6.222 2.981 5.12 7.324-.713 4.73-3.046 19.768-5.962 30.98-3.889 20.739h2.268l2.593-2.593 10.499-13.934 17.628-22.036 7.778-8.749 9.073-9.657 5.833-4.601h11.018l8.1 12.055-3.628 12.443-11.342 14.388-9.398 12.184-13.48 18.147-8.426 14.518.778 1.166 2.01-.194 30.46-6.481 16.462-2.982 19.637-3.37 8.88 4.148.971 4.213-3.5 8.62-20.998 5.184-24.628 4.926-36.682 8.685-.454.324.519.648 16.526 1.555 7.065.389h17.304l32.21 2.398 8.426 5.574 5.055 6.805-.843 5.184-12.962 6.611-17.498-4.148-40.83-9.721-14-3.5h-1.944v1.167l11.666 11.406 21.387 19.314 26.767 24.887 1.36 6.157-3.434 4.86-3.63-.518-23.526-17.693-9.073-7.972-20.545-17.304h-1.36v1.814l4.73 6.935 25.017 37.59 1.296 11.536-1.814 3.76-6.481 2.268-7.13-1.297-14.647-20.544-15.1-23.138-12.185-20.739-1.49.843-7.194 77.448-3.37 3.953-7.778 2.981-6.48-4.925-3.436-7.972 3.435-15.749 4.148-20.544 3.37-16.333 3.046-20.285 1.815-6.74-.13-.454-1.49.194-15.295 20.999-23.267 31.433-18.406 19.702-4.407 1.75-7.648-3.954.713-7.064 4.277-6.286 25.47-32.405 15.36-20.092 9.917-11.6-.065-1.686h-.583L44.07 198.125l-12.055 1.555-5.185-4.86.648-7.972 2.463-2.593 20.35-13.999-.064.065Z"
132 fillRule="evenodd"34 />
133 d="M9 6a3 3 0 0 0-3 3v66H0V9a9 9 0 0 1 9-9h80.379c4.009 0 6.016 4.847 3.182 7.682L43.055 57.187H57V51h6v7.688a4.5 4.5 0 0 1-4.5 4.5H37.055L26.743 73.5H73.5V36h6v37.5a6 6 0 0 1-6 6H20.743L10.243 90H87a3 3 0 0 0 3-3V21h6v66a9 9 0 0 1-9 9H6.621c-4.009 0-6.016-4.847-3.182-7.682L52.757 39H39v6h-6v-7.5a4.5 4.5 0 0 1 4.5-4.5h21.257l10.5-10.5H22.5V60h-6V22.5a6 6 0 0 1 6-6h52.757L85.757 6H9Z"35 </svg>
134 clipRule="evenodd"36);
135 />
136 </g>
137 <defs>
138 <clipPath id={clipPathId}>
139 <path fill="#fff" d="M0 0h96v96H0z" />
140 </clipPath>
141 </defs>
142 </svg>
143 );
144};