Skip to content

Commit 67dc819

Browse files
authored
refactor: consolidate LLM provider modal routing (onyx-dot-app#10030)
1 parent 2d12274 commit 67dc819

16 files changed

Lines changed: 453 additions & 665 deletions

File tree

web/src/app/admin/configuration/llm/ModelIcon.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { defaultTailwindCSS } from "@/components/icons/icons";
2-
import { getModelIcon } from "@/lib/llmConfig/providers";
2+
import { getModelIcon } from "@/lib/llmConfig";
33
import { IconProps } from "@opal/types";
44

55
export interface ModelIconProps extends IconProps {
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
export { default } from "@/refresh-pages/admin/LLMProviderConfigurationPage";
1+
export { default } from "@/refresh-pages/admin/LLMConfigurationPage";

web/src/app/app/message/MultiModelPanel.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ import { Button } from "@opal/components";
55
import { Text } from "@opal/components";
66
import { ContentAction } from "@opal/layouts";
77
import { SvgEyeOff, SvgX } from "@opal/icons";
8-
import { getModelIcon } from "@/lib/llmConfig/providers";
8+
import { getModelIcon } from "@/lib/llmConfig";
99
import AgentMessage, {
1010
AgentMessageProps,
1111
} from "@/app/app/message/messageComponents/AgentMessage";

web/src/app/craft/components/BuildLLMPopover.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ import {
1818
isRecommendedModel,
1919
} from "@/app/craft/onboarding/constants";
2020
import { ToggleWarningModal } from "./ToggleWarningModal";
21-
import { getModelIcon } from "@/lib/llmConfig/providers";
21+
import { getModelIcon } from "@/lib/llmConfig";
2222
import { Section } from "@/layouts/general-layouts";
2323
import {
2424
Accordion,

web/src/app/craft/v1/configure/page.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,7 @@ import NotAllowedModal from "@/app/craft/onboarding/components/NotAllowedModal";
4848
import { useOnboarding } from "@/app/craft/onboarding/BuildOnboardingProvider";
4949
import { useLLMProviders } from "@/hooks/useLLMProviders";
5050
import { useUser } from "@/providers/UserProvider";
51-
import { getModelIcon } from "@/lib/llmConfig/providers";
51+
import { getModelIcon } from "@/lib/llmConfig";
5252
import {
5353
getBuildUserPersona,
5454
getPersonaInfo,

web/src/components/llm/LLMSelector.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
import { useMemo } from "react";
44
import { parseLlmDescriptor, structureValue } from "@/lib/llmConfig/utils";
55
import { DefaultModel, LLMProviderDescriptor } from "@/interfaces/llm";
6-
import { getModelIcon } from "@/lib/llmConfig/providers";
6+
import { getModelIcon } from "@/lib/llmConfig";
77
import InputSelect from "@/refresh-components/inputs/InputSelect";
88
import { createIcon } from "@/components/icons/icons";
99

web/src/lib/llmConfig/index.ts

Lines changed: 251 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,251 @@
1+
import type { IconFunctionComponent } from "@opal/types";
2+
import { SvgCpu, SvgPlug, SvgServer } from "@opal/icons";
3+
import {
4+
SvgBifrost,
5+
SvgOpenai,
6+
SvgClaude,
7+
SvgOllama,
8+
SvgAws,
9+
SvgOpenrouter,
10+
SvgAzure,
11+
SvgGemini,
12+
SvgLitellm,
13+
SvgLmStudio,
14+
SvgMicrosoft,
15+
SvgMistral,
16+
SvgDeepseek,
17+
SvgQwen,
18+
SvgGoogle,
19+
} from "@opal/logos";
20+
import { ZAIIcon } from "@/components/icons/icons";
21+
import { LLMProviderFormProps, LLMProviderName } from "@/interfaces/llm";
22+
import type { LLMProviderView } from "@/interfaces/llm";
23+
import OpenAIModal from "@/sections/modals/llmConfig/OpenAIModal";
24+
import AnthropicModal from "@/sections/modals/llmConfig/AnthropicModal";
25+
import OllamaModal from "@/sections/modals/llmConfig/OllamaModal";
26+
import AzureModal from "@/sections/modals/llmConfig/AzureModal";
27+
import BedrockModal from "@/sections/modals/llmConfig/BedrockModal";
28+
import VertexAIModal from "@/sections/modals/llmConfig/VertexAIModal";
29+
import OpenRouterModal from "@/sections/modals/llmConfig/OpenRouterModal";
30+
import CustomModal from "@/sections/modals/llmConfig/CustomModal";
31+
import LMStudioModal from "@/sections/modals/llmConfig/LMStudioModal";
32+
import LiteLLMProxyModal from "@/sections/modals/llmConfig/LiteLLMProxyModal";
33+
import BifrostModal from "@/sections/modals/llmConfig/BifrostModal";
34+
import OpenAICompatibleModal from "@/sections/modals/llmConfig/OpenAICompatibleModal";
35+
36+
// ─── Text (LLM) providers ────────────────────────────────────────────────────
37+
38+
export interface ProviderEntry {
39+
icon: IconFunctionComponent;
40+
productName: string;
41+
companyName: string;
42+
Modal: React.ComponentType<LLMProviderFormProps>;
43+
}
44+
45+
const PROVIDERS: Record<string, ProviderEntry> = {
46+
[LLMProviderName.OPENAI]: {
47+
icon: SvgOpenai,
48+
productName: "GPT",
49+
companyName: "OpenAI",
50+
Modal: OpenAIModal,
51+
},
52+
[LLMProviderName.ANTHROPIC]: {
53+
icon: SvgClaude,
54+
productName: "Claude",
55+
companyName: "Anthropic",
56+
Modal: AnthropicModal,
57+
},
58+
[LLMProviderName.VERTEX_AI]: {
59+
icon: SvgGemini,
60+
productName: "Gemini",
61+
companyName: "Google Cloud Vertex AI",
62+
Modal: VertexAIModal,
63+
},
64+
[LLMProviderName.BEDROCK]: {
65+
icon: SvgAws,
66+
productName: "Amazon Bedrock",
67+
companyName: "AWS",
68+
Modal: BedrockModal,
69+
},
70+
[LLMProviderName.AZURE]: {
71+
icon: SvgAzure,
72+
productName: "Azure OpenAI",
73+
companyName: "Microsoft Azure",
74+
Modal: AzureModal,
75+
},
76+
[LLMProviderName.LITELLM]: {
77+
icon: SvgLitellm,
78+
productName: "LiteLLM",
79+
companyName: "LiteLLM",
80+
Modal: CustomModal,
81+
},
82+
[LLMProviderName.LITELLM_PROXY]: {
83+
icon: SvgLitellm,
84+
productName: "LiteLLM Proxy",
85+
companyName: "LiteLLM Proxy",
86+
Modal: LiteLLMProxyModal,
87+
},
88+
[LLMProviderName.OLLAMA_CHAT]: {
89+
icon: SvgOllama,
90+
productName: "Ollama",
91+
companyName: "Ollama",
92+
Modal: OllamaModal,
93+
},
94+
[LLMProviderName.OPENROUTER]: {
95+
icon: SvgOpenrouter,
96+
productName: "OpenRouter",
97+
companyName: "OpenRouter",
98+
Modal: OpenRouterModal,
99+
},
100+
[LLMProviderName.LM_STUDIO]: {
101+
icon: SvgLmStudio,
102+
productName: "LM Studio",
103+
companyName: "LM Studio",
104+
Modal: LMStudioModal,
105+
},
106+
[LLMProviderName.BIFROST]: {
107+
icon: SvgBifrost,
108+
productName: "Bifrost",
109+
companyName: "Bifrost",
110+
Modal: BifrostModal,
111+
},
112+
[LLMProviderName.OPENAI_COMPATIBLE]: {
113+
icon: SvgPlug,
114+
productName: "OpenAI-Compatible",
115+
companyName: "OpenAI-Compatible",
116+
Modal: OpenAICompatibleModal,
117+
},
118+
[LLMProviderName.CUSTOM]: {
119+
icon: SvgServer,
120+
productName: "Custom Models",
121+
companyName: "models from other LiteLLM-compatible providers",
122+
Modal: CustomModal,
123+
},
124+
};
125+
126+
const DEFAULT_ENTRY: ProviderEntry = {
127+
icon: SvgCpu,
128+
productName: "",
129+
companyName: "",
130+
Modal: CustomModal,
131+
};
132+
133+
// Providers that don't use custom_config themselves — if custom_config is
134+
// present it means the provider was originally created via CustomModal.
135+
const CUSTOM_CONFIG_OVERRIDES = new Set<string>([
136+
LLMProviderName.OPENAI,
137+
LLMProviderName.ANTHROPIC,
138+
LLMProviderName.AZURE,
139+
LLMProviderName.OPENROUTER,
140+
]);
141+
142+
export function getProvider(
143+
providerName: string,
144+
existingProvider?: LLMProviderView
145+
): ProviderEntry {
146+
const entry = PROVIDERS[providerName] ?? {
147+
...DEFAULT_ENTRY,
148+
productName: providerName,
149+
companyName: providerName,
150+
};
151+
152+
if (
153+
existingProvider?.custom_config != null &&
154+
CUSTOM_CONFIG_OVERRIDES.has(providerName)
155+
) {
156+
return { ...entry, Modal: CustomModal };
157+
}
158+
159+
return entry;
160+
}
161+
162+
// ─── Aggregator providers ────────────────────────────────────────────────────
163+
// Providers that host models from multiple vendors (e.g. Bedrock hosts Claude,
164+
// Llama, etc.) Used by the model-icon resolver to prioritise vendor icons.
165+
166+
export const AGGREGATOR_PROVIDERS = new Set([
167+
LLMProviderName.BEDROCK,
168+
"bedrock_converse",
169+
LLMProviderName.OPENROUTER,
170+
LLMProviderName.OLLAMA_CHAT,
171+
LLMProviderName.LM_STUDIO,
172+
LLMProviderName.LITELLM_PROXY,
173+
LLMProviderName.BIFROST,
174+
LLMProviderName.OPENAI_COMPATIBLE,
175+
LLMProviderName.VERTEX_AI,
176+
]);
177+
178+
// ─── Model-aware icon resolver ───────────────────────────────────────────────
179+
180+
const MODEL_ICON_MAP: Record<string, IconFunctionComponent> = {
181+
[LLMProviderName.OPENAI]: SvgOpenai,
182+
[LLMProviderName.ANTHROPIC]: SvgClaude,
183+
[LLMProviderName.OLLAMA_CHAT]: SvgOllama,
184+
[LLMProviderName.LM_STUDIO]: SvgLmStudio,
185+
[LLMProviderName.OPENROUTER]: SvgOpenrouter,
186+
[LLMProviderName.VERTEX_AI]: SvgGemini,
187+
[LLMProviderName.BEDROCK]: SvgAws,
188+
[LLMProviderName.LITELLM_PROXY]: SvgLitellm,
189+
[LLMProviderName.BIFROST]: SvgBifrost,
190+
[LLMProviderName.OPENAI_COMPATIBLE]: SvgPlug,
191+
192+
amazon: SvgAws,
193+
phi: SvgMicrosoft,
194+
mistral: SvgMistral,
195+
ministral: SvgMistral,
196+
llama: SvgCpu,
197+
ollama: SvgOllama,
198+
gemini: SvgGemini,
199+
deepseek: SvgDeepseek,
200+
claude: SvgClaude,
201+
azure: SvgAzure,
202+
microsoft: SvgMicrosoft,
203+
meta: SvgCpu,
204+
google: SvgGoogle,
205+
qwen: SvgQwen,
206+
qwq: SvgQwen,
207+
zai: ZAIIcon,
208+
bedrock_converse: SvgAws,
209+
};
210+
211+
/**
212+
* Model-aware icon resolver that checks both provider name and model name
213+
* to pick the most specific icon (e.g. Claude icon for a Bedrock Claude model).
214+
*/
215+
export function getModelIcon(
216+
providerName: string,
217+
modelName?: string
218+
): IconFunctionComponent {
219+
const lowerProviderName = providerName.toLowerCase();
220+
221+
// For aggregator providers, prioritise showing the vendor icon based on model name
222+
if (AGGREGATOR_PROVIDERS.has(lowerProviderName) && modelName) {
223+
const lowerModelName = modelName.toLowerCase();
224+
for (const [key, icon] of Object.entries(MODEL_ICON_MAP)) {
225+
if (lowerModelName.includes(key)) {
226+
return icon;
227+
}
228+
}
229+
}
230+
231+
// Check if provider name directly matches an icon
232+
if (lowerProviderName in MODEL_ICON_MAP) {
233+
const icon = MODEL_ICON_MAP[lowerProviderName];
234+
if (icon) {
235+
return icon;
236+
}
237+
}
238+
239+
// For non-aggregator providers, check if model name contains any of the keys
240+
if (modelName) {
241+
const lowerModelName = modelName.toLowerCase();
242+
for (const [key, icon] of Object.entries(MODEL_ICON_MAP)) {
243+
if (lowerModelName.includes(key)) {
244+
return icon;
245+
}
246+
}
247+
}
248+
249+
// Fallback to CPU icon if no matches
250+
return SvgCpu;
251+
}

0 commit comments

Comments
 (0)