mirror of
https://github.com/onyx-dot-app/onyx.git
synced 2026-10-19 01:50:51 -05:00
fix(llm): one-line provider cards, translated custom provider names (#16120)
This commit is contained in:
1 parent
9d36a5bbd8
commit
8958c9af28
21 files changed
+240
-43
No files matched your search
@@ -7,6 +7,7 @@ import { Divider } from "@opal/components";
|
||||
import { ContentAction } from "@opal/layouts";
|
||||
import LLMProviderCard from "@/sections/onboarding/components/LLMProviderCard";
|
||||
import { getProvider } from "@/lib/languageModels/utils";
|
||||
import { useProviderNames } from "@/lib/languageModels/useProviderNames";
|
||||
import { useLLMProviderOptions } from "@/lib/hooks/useLLMProviderOptions";
|
||||
import {
|
||||
CRAFT_PROVIDERS,
|
||||
@@ -25,6 +26,7 @@ const craftKeys: string[] = CRAFT_PROVIDERS;
|
||||
|
||||
export default function CraftLlmSetup() {
|
||||
const t = useTranslations("craft.onboarding.llmSetup");
|
||||
const providerNames = useProviderNames();
|
||||
const { openProviderModal } = useOnboarding();
|
||||
const { llmProviderOptions } = useLLMProviderOptions();
|
||||
|
||||
@@ -51,7 +53,7 @@ export default function CraftLlmSetup() {
|
||||
<Divider />
|
||||
<div className="grid grid-cols-1 sm:grid-cols-2 gap-1 w-full max-h-56 overflow-y-auto [&>*:last-child:nth-child(odd)]:col-span-full">
|
||||
{providerKeys.map((key) => {
|
||||
const { productName, companyName } = getProvider(key);
|
||||
const { productName, companyName } = providerNames(getProvider(key));
|
||||
return (
|
||||
<LLMProviderCard
|
||||
key={key}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
"use client";
|
||||
|
||||
import { useProviderNames } from "@/lib/languageModels/useProviderNames";
|
||||
import { useState, useCallback, useMemo } from "react";
|
||||
import {
|
||||
MAX_MODELS,
|
||||
@@ -48,15 +49,18 @@ export default function useMultiModelChat(
|
||||
// (e.g. a stale admin/personal default pointing at a hidden model), so
|
||||
// `currentLlmModel` below can still match it — mirrors ModelSelector's
|
||||
// `currentModelName` param.
|
||||
const providerNames = useProviderNames();
|
||||
const llmOptions = useMemo(
|
||||
() =>
|
||||
llmManager.llmProviders
|
||||
? buildLlmOptions(
|
||||
llmManager.llmProviders,
|
||||
llmManager.currentLlm.modelName
|
||||
llmManager.currentLlm.modelName,
|
||||
false,
|
||||
providerNames
|
||||
)
|
||||
: [],
|
||||
[llmManager.llmProviders, llmManager.currentLlm.modelName]
|
||||
[llmManager.llmProviders, llmManager.currentLlm.modelName, providerNames]
|
||||
);
|
||||
|
||||
// In single-model mode, derive the displayed model directly from
|
||||
|
||||
@@ -7417,6 +7417,16 @@
|
||||
"error": "تعذر تحميل نماذج التوجيه.",
|
||||
"retry": "إعادة المحاولة"
|
||||
}
|
||||
},
|
||||
"providers": {
|
||||
"custom": {
|
||||
"productName": "نماذج مخصصة",
|
||||
"companyName": "مزوّدون متوافقون مع LiteLLM"
|
||||
},
|
||||
"openAICompatible": {
|
||||
"productName": "نماذج مخصصة",
|
||||
"companyName": "نقاط نهاية متوافقة مع OpenAI"
|
||||
}
|
||||
}
|
||||
},
|
||||
"mcpActions": {
|
||||
|
||||
@@ -7417,6 +7417,16 @@
|
||||
"error": "Router-Modelle konnten nicht geladen werden.",
|
||||
"retry": "Erneut versuchen"
|
||||
}
|
||||
},
|
||||
"providers": {
|
||||
"custom": {
|
||||
"productName": "Benutzerdefinierte Modelle",
|
||||
"companyName": "LiteLLM-kompatible Anbieter"
|
||||
},
|
||||
"openAICompatible": {
|
||||
"productName": "Benutzerdefinierte Modelle",
|
||||
"companyName": "OpenAI-kompatible Endpunkte"
|
||||
}
|
||||
}
|
||||
},
|
||||
"mcpActions": {
|
||||
|
||||
@@ -7417,6 +7417,16 @@
|
||||
"error": "Failed to load router models.",
|
||||
"retry": "Retry"
|
||||
}
|
||||
},
|
||||
"providers": {
|
||||
"custom": {
|
||||
"productName": "Custom Models",
|
||||
"companyName": "LiteLLM-compatible providers"
|
||||
},
|
||||
"openAICompatible": {
|
||||
"productName": "Custom Models",
|
||||
"companyName": "OpenAI-compatible endpoints"
|
||||
}
|
||||
}
|
||||
},
|
||||
"mcpActions": {
|
||||
|
||||
@@ -7417,6 +7417,16 @@
|
||||
"error": "No se pudieron cargar los modelos de enrutamiento.",
|
||||
"retry": "Reintentar"
|
||||
}
|
||||
},
|
||||
"providers": {
|
||||
"custom": {
|
||||
"productName": "Modelos personalizados",
|
||||
"companyName": "Proveedores compatibles con LiteLLM"
|
||||
},
|
||||
"openAICompatible": {
|
||||
"productName": "Modelos personalizados",
|
||||
"companyName": "Endpoints compatibles con OpenAI"
|
||||
}
|
||||
}
|
||||
},
|
||||
"mcpActions": {
|
||||
|
||||
@@ -7417,6 +7417,16 @@
|
||||
"error": "Impossible de charger les modèles de routage.",
|
||||
"retry": "Réessayer"
|
||||
}
|
||||
},
|
||||
"providers": {
|
||||
"custom": {
|
||||
"productName": "Modèles personnalisés",
|
||||
"companyName": "Fournisseurs compatibles LiteLLM"
|
||||
},
|
||||
"openAICompatible": {
|
||||
"productName": "Modèles personnalisés",
|
||||
"companyName": "Points de terminaison compatibles OpenAI"
|
||||
}
|
||||
}
|
||||
},
|
||||
"mcpActions": {
|
||||
|
||||
@@ -7417,6 +7417,16 @@
|
||||
"error": "ルーターモデルを読み込めませんでした。",
|
||||
"retry": "再試行"
|
||||
}
|
||||
},
|
||||
"providers": {
|
||||
"custom": {
|
||||
"productName": "カスタムモデル",
|
||||
"companyName": "LiteLLM 互換プロバイダー"
|
||||
},
|
||||
"openAICompatible": {
|
||||
"productName": "カスタムモデル",
|
||||
"companyName": "OpenAI 互換エンドポイント"
|
||||
}
|
||||
}
|
||||
},
|
||||
"mcpActions": {
|
||||
|
||||
@@ -7417,6 +7417,16 @@
|
||||
"error": "라우터 모델을 불러오지 못했습니다.",
|
||||
"retry": "다시 시도"
|
||||
}
|
||||
},
|
||||
"providers": {
|
||||
"custom": {
|
||||
"productName": "사용자 지정 모델",
|
||||
"companyName": "LiteLLM 호환 공급자"
|
||||
},
|
||||
"openAICompatible": {
|
||||
"productName": "사용자 지정 모델",
|
||||
"companyName": "OpenAI 호환 엔드포인트"
|
||||
}
|
||||
}
|
||||
},
|
||||
"mcpActions": {
|
||||
|
||||
@@ -7417,6 +7417,16 @@
|
||||
"error": "Falha ao carregar os modelos de roteamento.",
|
||||
"retry": "Tentar novamente"
|
||||
}
|
||||
},
|
||||
"providers": {
|
||||
"custom": {
|
||||
"productName": "Modelos personalizados",
|
||||
"companyName": "Provedores compatíveis com LiteLLM"
|
||||
},
|
||||
"openAICompatible": {
|
||||
"productName": "Modelos personalizados",
|
||||
"companyName": "Endpoints compatíveis com OpenAI"
|
||||
}
|
||||
}
|
||||
},
|
||||
"mcpActions": {
|
||||
|
||||
@@ -7417,6 +7417,16 @@
|
||||
"error": "无法加载路由器模型。",
|
||||
"retry": "重试"
|
||||
}
|
||||
},
|
||||
"providers": {
|
||||
"custom": {
|
||||
"productName": "自定义模型",
|
||||
"companyName": "兼容 LiteLLM 的提供商"
|
||||
},
|
||||
"openAICompatible": {
|
||||
"productName": "自定义模型",
|
||||
"companyName": "兼容 OpenAI 的端点"
|
||||
}
|
||||
}
|
||||
},
|
||||
"mcpActions": {
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
"use client";
|
||||
|
||||
import { useProviderNames } from "@/lib/languageModels/useProviderNames";
|
||||
import { useCallback, useEffect, useMemo, useRef, useState } from "react";
|
||||
import { useTranslations } from "next-intl";
|
||||
import { InputSingleSelect } from "@opal/components";
|
||||
@@ -101,8 +102,12 @@ export default function SimpleModelSelector<Nullable extends boolean = false>({
|
||||
modelPaging,
|
||||
}: SimpleModelSelectorProps<Nullable>) {
|
||||
const t = useTranslations("common.modelSelectors");
|
||||
const providerNames = useProviderNames();
|
||||
const options = useMemo((): SelectOptions => {
|
||||
const models = buildModelSelectOptions(providers, { grouped });
|
||||
const models = buildModelSelectOptions(providers, {
|
||||
grouped,
|
||||
providerNames,
|
||||
});
|
||||
if (!globalDefault) return models;
|
||||
return [
|
||||
{
|
||||
@@ -113,7 +118,7 @@ export default function SimpleModelSelector<Nullable extends boolean = false>({
|
||||
},
|
||||
...models,
|
||||
];
|
||||
}, [providers, grouped, globalDefault, t]);
|
||||
}, [providers, grouped, globalDefault, t, providerNames]);
|
||||
|
||||
// Server search only when nothing loaded matches. The query is remembered
|
||||
// so a merged page, which changes `modelPaging`, does not re-run it.
|
||||
|
||||
@@ -110,8 +110,9 @@ export const PROVIDERS: Record<string, ProviderEntry> = {
|
||||
},
|
||||
[LLMProviderName.OPENAI_COMPATIBLE]: {
|
||||
icon: SvgPlug,
|
||||
productName: "OpenAI-Compatible",
|
||||
companyName: "OpenAI-Compatible",
|
||||
productName: LLMProviderName.OPENAI_COMPATIBLE,
|
||||
companyName: LLMProviderName.OPENAI_COMPATIBLE,
|
||||
copyKey: "openAICompatible",
|
||||
Modal: OpenAICompatibleModal,
|
||||
},
|
||||
[LLMProviderName.NEBIUS_TOKENFACTORY]: {
|
||||
@@ -134,8 +135,9 @@ export const PROVIDERS: Record<string, ProviderEntry> = {
|
||||
},
|
||||
[LLMProviderName.CUSTOM]: {
|
||||
icon: SvgServer,
|
||||
productName: "Custom Models",
|
||||
companyName: "models from other LiteLLM-compatible providers",
|
||||
productName: LLMProviderName.CUSTOM,
|
||||
companyName: LLMProviderName.CUSTOM,
|
||||
copyKey: "custom",
|
||||
Modal: CustomModal,
|
||||
},
|
||||
};
|
||||
|
||||
@@ -6,6 +6,7 @@ import type {
|
||||
} from "@opal/components";
|
||||
import type { IconProps } from "@opal/types";
|
||||
import { getModelIcon, getProvider } from "@/lib/languageModels/utils";
|
||||
import type { ProviderNames } from "@/lib/languageModels/useProviderNames";
|
||||
import { AGGREGATOR_PROVIDERS } from "@/lib/languageModels/svc";
|
||||
import type {
|
||||
DefaultModel,
|
||||
@@ -15,6 +16,7 @@ import type {
|
||||
LLMProviderDescriptor,
|
||||
ModelOptionProvider,
|
||||
ReasoningEffortOverride,
|
||||
ProviderEntry,
|
||||
} from "@/lib/languageModels/types";
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
@@ -66,16 +68,33 @@ export function llmOptionKey(option: {
|
||||
* selectable model options. Hidden models require an existing selection or
|
||||
* an explicit admin opt-in.
|
||||
*/
|
||||
/** `useProviderNames()`: a provider entry's names as the UI shows them. */
|
||||
export type ProviderNamesResolver = (entry: ProviderEntry) => ProviderNames;
|
||||
|
||||
/**
|
||||
* Pass `providerNames` wherever the options are shown, so a provider whose
|
||||
* names are copy (custom, OpenAI-compatible) is labelled in the user's
|
||||
* language.
|
||||
*/
|
||||
export function buildLlmOptions(
|
||||
llmProviders: ModelOptionProvider[] | undefined,
|
||||
currentModelName?: string,
|
||||
includeHiddenModels = false
|
||||
includeHiddenModels = false,
|
||||
providerNames?: ProviderNamesResolver
|
||||
): LLMOption[] {
|
||||
if (!llmProviders) return [];
|
||||
|
||||
const seenKeys = new Set<string>();
|
||||
const options: LLMOption[] = [];
|
||||
|
||||
// The custom provider types share a product name, so an unnamed one is
|
||||
// labelled by its description to keep its model group apart.
|
||||
function unnamedProviderLabel(providerKey: string): string {
|
||||
const entry = getProvider(providerKey);
|
||||
const names = providerNames ? providerNames(entry) : entry;
|
||||
return entry.copyKey ? names.companyName : names.productName;
|
||||
}
|
||||
|
||||
llmProviders.forEach((llmProvider) => {
|
||||
llmProvider.model_configurations
|
||||
.filter(
|
||||
@@ -93,7 +112,7 @@ export function buildLlmOptions(
|
||||
provider: llmProvider.provider,
|
||||
providerId: llmProvider.id,
|
||||
providerDisplayName:
|
||||
llmProvider.name || getProvider(llmProvider.provider).productName,
|
||||
llmProvider.name || unnamedProviderLabel(llmProvider.provider),
|
||||
modelName: mc.name,
|
||||
modelConfigurationId: mc.id ?? null,
|
||||
displayName: mc.effectiveDisplayName,
|
||||
@@ -305,6 +324,8 @@ export interface BuildModelSelectOptionsOptions {
|
||||
* grouping workspace-wide. Defaults to true.
|
||||
*/
|
||||
grouped?: boolean;
|
||||
/** `useProviderNames()`, for the provider group titles. */
|
||||
providerNames?: ProviderNamesResolver;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -314,10 +335,10 @@ export interface BuildModelSelectOptionsOptions {
|
||||
*/
|
||||
export function buildModelSelectOptions(
|
||||
providers: ModelOptionProvider[],
|
||||
{ grouped = true }: BuildModelSelectOptionsOptions = {}
|
||||
{ grouped = true, providerNames }: BuildModelSelectOptionsOptions = {}
|
||||
): SelectOptions {
|
||||
const dividers: SelectDivider[] = groupLlmOptions(
|
||||
buildLlmOptions(providers, undefined, true)
|
||||
buildLlmOptions(providers, undefined, true, providerNames)
|
||||
)
|
||||
.map((group) => ({
|
||||
title: group.displayName,
|
||||
|
||||
@@ -467,9 +467,18 @@ export interface CostOverrideUpsert {
|
||||
cache_read_cost_per_mtok: number | null;
|
||||
}
|
||||
|
||||
/**
|
||||
* A provider whose names are copy rather than brand names. Its names come from
|
||||
* `admin.languageModels.providers.<key>`; show them with `useProviderNames`.
|
||||
*/
|
||||
export type ProviderCopyKey = "custom" | "openAICompatible";
|
||||
|
||||
export interface ProviderEntry {
|
||||
icon: IconFunctionComponent;
|
||||
/** A brand name, or the provider key when `copyKey` holds its names. */
|
||||
productName: string;
|
||||
/** A brand name, or the provider key when `copyKey` holds its names. */
|
||||
companyName: string;
|
||||
copyKey?: ProviderCopyKey;
|
||||
Modal: React.ComponentType<LLMProviderFormProps>;
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
"use client";
|
||||
|
||||
import { useCallback } from "react";
|
||||
import { useTranslations } from "next-intl";
|
||||
import type { ProviderEntry } from "@/lib/languageModels/types";
|
||||
|
||||
/** A provider's names as the UI shows them. */
|
||||
export interface ProviderNames {
|
||||
productName: string;
|
||||
companyName: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolves a provider entry's names for display: brand names as they are, and
|
||||
* the translated copy for a provider whose names are copy (`copyKey`).
|
||||
*/
|
||||
export function useProviderNames(): (
|
||||
entry: Pick<ProviderEntry, "productName" | "companyName" | "copyKey">
|
||||
) => ProviderNames {
|
||||
const t = useTranslations("admin.languageModels.providers");
|
||||
return useCallback(
|
||||
(entry) =>
|
||||
entry.copyKey
|
||||
? {
|
||||
productName: t(`${entry.copyKey}.productName`),
|
||||
companyName: t(`${entry.copyKey}.companyName`),
|
||||
}
|
||||
: { productName: entry.productName, companyName: entry.companyName },
|
||||
[t]
|
||||
);
|
||||
}
|
||||
@@ -47,6 +47,7 @@ import {
|
||||
import { setDefaultLlmModelAndRefresh } from "@/lib/languageModels/cache";
|
||||
import { getProvider, modelDisplayName } from "@/lib/languageModels/utils";
|
||||
import { useAdminLanguageModels } from "@/lib/languageModels/hooks";
|
||||
import { useProviderNames } from "@/lib/languageModels/useProviderNames";
|
||||
import { useSWRConfig } from "swr";
|
||||
import {
|
||||
SvgArrowExchange,
|
||||
@@ -1098,11 +1099,10 @@ function ModalWrapperInner({
|
||||
? t("setup.submitButton.pristineTooltip")
|
||||
: undefined;
|
||||
|
||||
const {
|
||||
icon: providerIcon,
|
||||
companyName: providerDisplayName,
|
||||
productName: providerProductName,
|
||||
} = getProvider(providerName);
|
||||
const providerEntry = getProvider(providerName);
|
||||
const providerIcon = providerEntry.icon;
|
||||
const { companyName: providerDisplayName, productName: providerProductName } =
|
||||
useProviderNames()(providerEntry);
|
||||
|
||||
const title = llmProvider
|
||||
? markdown(
|
||||
|
||||
@@ -45,6 +45,7 @@ import {
|
||||
reasoningStopIndex,
|
||||
} from "@/sections/model-selector/setting-controls";
|
||||
import { useLanguageModelsForCurrentAgent } from "@/lib/languageModels/hooks";
|
||||
import { useProviderNames } from "@/lib/languageModels/useProviderNames";
|
||||
import { useUser } from "@/providers/UserProvider";
|
||||
import { useSettings } from "@/lib/settings/hooks";
|
||||
import type {
|
||||
@@ -457,9 +458,16 @@ export default function ModelSelectorContent({
|
||||
const internalScrollRef = useRef<HTMLDivElement>(null);
|
||||
const scrollContainerRef = externalScrollRef ?? internalScrollRef;
|
||||
|
||||
const providerNames = useProviderNames();
|
||||
const llmOptions = useMemo(
|
||||
() => buildLlmOptions(llmProviders, currentModelName, includeHiddenModels),
|
||||
[llmProviders, currentModelName, includeHiddenModels]
|
||||
() =>
|
||||
buildLlmOptions(
|
||||
llmProviders,
|
||||
currentModelName,
|
||||
includeHiddenModels,
|
||||
providerNames
|
||||
),
|
||||
[llmProviders, currentModelName, includeHiddenModels, providerNames]
|
||||
);
|
||||
|
||||
// "Auto" selects the admin-configured router; router rows never list.
|
||||
|
||||
@@ -13,6 +13,7 @@ import {
|
||||
} from "@/interfaces/onboarding";
|
||||
import { WellKnownLLMProviderDescriptor } from "@/lib/languageModels/types";
|
||||
import { getProvider } from "@/lib/languageModels/utils";
|
||||
import { useProviderNames } from "@/lib/languageModels/useProviderNames";
|
||||
import ProviderSetupModal from "@/sections/modals/languageModels/ProviderSetupModal";
|
||||
import { Disabled } from "@opal/core";
|
||||
import { ModelIcon } from "@/lib/languageModels/components";
|
||||
@@ -94,6 +95,7 @@ const LLMStep = memo(
|
||||
disabled,
|
||||
}: LLMStepProps) => {
|
||||
const t = useTranslations("onboarding");
|
||||
const providerNames = useProviderNames();
|
||||
const { appName } = useSettings();
|
||||
const { llmProviderOptions, isLoading } = useLLMProviderOptions();
|
||||
const llmDescriptors = llmProviderOptions ?? [];
|
||||
@@ -188,8 +190,8 @@ const LLMStep = memo(
|
||||
|
||||
{/* Render provider cards */}
|
||||
{llmDescriptors.map((llmDescriptor) => {
|
||||
const { productName, companyName } = getProvider(
|
||||
llmDescriptor.name
|
||||
const { productName, companyName } = providerNames(
|
||||
getProvider(llmDescriptor.name)
|
||||
);
|
||||
return (
|
||||
<div
|
||||
|
||||
@@ -11,6 +11,7 @@ import { SvgCheck, SvgEdit, SvgPlus, SvgTrash, SvgX } from "@opal/icons";
|
||||
import { markdown } from "@opal/utils";
|
||||
import { SimpleModelSelector } from "@/lib/languageModels/components";
|
||||
import { getProvider } from "@/lib/languageModels/utils";
|
||||
import { useProviderNames } from "@/lib/languageModels/useProviderNames";
|
||||
import {
|
||||
filterModelConfigurations,
|
||||
findLlmOptionById,
|
||||
@@ -26,12 +27,18 @@ import {
|
||||
} from "@/lib/languageModels/costOverrides";
|
||||
import type { CostOverride } from "@/lib/languageModels/types";
|
||||
|
||||
/** `allProvidersLabel` is passed in: this module cannot call translation hooks. */
|
||||
/**
|
||||
* The label and resolver are passed in: this function cannot call hooks.
|
||||
* `providerNames` is `useProviderNames()`.
|
||||
*/
|
||||
function getProviderDisplayName(
|
||||
provider: string,
|
||||
allProvidersLabel: string
|
||||
allProvidersLabel: string,
|
||||
providerNames: ReturnType<typeof useProviderNames>
|
||||
): string {
|
||||
return provider ? getProvider(provider).companyName : allProvidersLabel;
|
||||
return provider
|
||||
? providerNames(getProvider(provider)).companyName
|
||||
: allProvidersLabel;
|
||||
}
|
||||
|
||||
// Accepts integers/decimals only; "" is allowed mid-edit, validated on submit.
|
||||
@@ -59,6 +66,7 @@ interface OverrideFormProps {
|
||||
|
||||
function OverrideForm({ existing, onDone }: OverrideFormProps) {
|
||||
const t = useTranslations("admin.costOverrides");
|
||||
const providerNames = useProviderNames();
|
||||
const { mutate } = useSWRConfig();
|
||||
const { llmProviders, modelPaging } = useAdminLanguageModels();
|
||||
const { hide_provider_grouping: hideProviderGrouping } = useSettings();
|
||||
@@ -89,7 +97,7 @@ function OverrideForm({ existing, onDone }: OverrideFormProps) {
|
||||
const parsedCache = parseRate(cacheRate);
|
||||
const cacheValid = cacheRate.trim() === "" || parsedCache !== null;
|
||||
const modelLabel = model
|
||||
? `${getProviderDisplayName(provider, t("allProviders.label"))} · ${model}`
|
||||
? `${getProviderDisplayName(provider, t("allProviders.label"), providerNames)} · ${model}`
|
||||
: "";
|
||||
const canSubmit =
|
||||
model.trim() !== "" &&
|
||||
@@ -228,13 +236,15 @@ interface OverrideRowProps {
|
||||
|
||||
function OverrideRow({ override }: OverrideRowProps) {
|
||||
const t = useTranslations("admin.costOverrides");
|
||||
const providerNames = useProviderNames();
|
||||
const locale = useLocale();
|
||||
const { mutate } = useSWRConfig();
|
||||
const [editing, setEditing] = useState(false);
|
||||
const [deleting, setDeleting] = useState(false);
|
||||
const providerDisplayName = getProviderDisplayName(
|
||||
override.provider,
|
||||
t("allProviders.label")
|
||||
t("allProviders.label"),
|
||||
providerNames
|
||||
);
|
||||
|
||||
async function handleDelete() {
|
||||
|
||||
@@ -9,6 +9,7 @@ import {
|
||||
useAdminLanguageModel,
|
||||
useAdminLanguageModels,
|
||||
} from "@/lib/languageModels/hooks";
|
||||
import { useProviderNames } from "@/lib/languageModels/useProviderNames";
|
||||
import { PageLoader } from "@opal/loaders";
|
||||
import { Content, ContentAction, InputHorizontal, toast } from "@opal/layouts";
|
||||
import {
|
||||
@@ -55,10 +56,6 @@ import CostOverridesPanel from "@/views/admin/CostOverridesPanel";
|
||||
|
||||
const route = ADMIN_ROUTES.LLM_MODELS;
|
||||
|
||||
function providerDisplayName(provider: LLMProviderView): string {
|
||||
return provider.name || getProvider(provider.provider, provider).productName;
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Provider grouping (keyed by provider name from the API)
|
||||
// ============================================================================
|
||||
@@ -133,7 +130,11 @@ function ExistingProviderCard({
|
||||
}
|
||||
};
|
||||
|
||||
const { icon, companyName, Modal } = getProvider(provider.provider, provider);
|
||||
const providerEntry = getProvider(provider.provider, provider);
|
||||
const { icon, Modal } = providerEntry;
|
||||
const providerNames = useProviderNames()(providerEntry);
|
||||
const { companyName } = providerNames;
|
||||
const displayName = provider.name || providerNames.productName;
|
||||
|
||||
return (
|
||||
<>
|
||||
@@ -144,9 +145,7 @@ function ExistingProviderCard({
|
||||
{deleteModal.isOpen && (
|
||||
<ConfirmationModalLayout
|
||||
icon={SvgTrash}
|
||||
title={markdown(
|
||||
t("deleteModal.title", { provider: providerDisplayName(provider) })
|
||||
)}
|
||||
title={markdown(t("deleteModal.title", { provider: displayName }))}
|
||||
onClose={() => deleteModal.toggle(false)}
|
||||
submit={
|
||||
<Button
|
||||
@@ -168,7 +167,7 @@ function ExistingProviderCard({
|
||||
<Text font="main-ui-body" color="text-03">
|
||||
{markdown(
|
||||
t("deleteModal.description", {
|
||||
provider: providerDisplayName(provider),
|
||||
provider: displayName,
|
||||
})
|
||||
)}
|
||||
</Text>
|
||||
@@ -194,13 +193,15 @@ function ExistingProviderCard({
|
||||
// A name to select the card by. The Edit and Delete buttons inside
|
||||
// are labelled "Edit <name>" / "Delete <name>", so an exact match on
|
||||
// the bare name reaches the card alone.
|
||||
aria-label={providerDisplayName(provider)}
|
||||
aria-label={displayName}
|
||||
onClick={() => setIsOpen(true)}
|
||||
>
|
||||
<ContentAction
|
||||
icon={icon}
|
||||
title={providerDisplayName(provider)}
|
||||
title={displayName}
|
||||
description={companyName}
|
||||
titleMaxLines={1}
|
||||
descriptionMaxLines={1}
|
||||
sizePreset="main-ui"
|
||||
variant="section"
|
||||
padding={2}
|
||||
@@ -219,7 +220,7 @@ function ExistingProviderCard({
|
||||
icon={SvgTrash}
|
||||
prominence="tertiary"
|
||||
aria-label={t("providerCard.deleteButton.ariaLabel", {
|
||||
provider: providerDisplayName(provider),
|
||||
provider: displayName,
|
||||
})}
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
@@ -231,7 +232,7 @@ function ExistingProviderCard({
|
||||
icon={SvgSettings}
|
||||
prominence="tertiary"
|
||||
aria-label={t("providerCard.editButton.ariaLabel", {
|
||||
provider: providerDisplayName(provider),
|
||||
provider: displayName,
|
||||
})}
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
@@ -262,7 +263,9 @@ function NewProviderCard({
|
||||
}: NewProviderCardProps) {
|
||||
const t = useTranslations("admin.languageModels");
|
||||
const [isOpen, setIsOpen] = useState(false);
|
||||
const { icon, productName, companyName, Modal } = getProvider(providerName);
|
||||
const providerEntry = getProvider(providerName);
|
||||
const { icon, Modal } = providerEntry;
|
||||
const { productName, companyName } = useProviderNames()(providerEntry);
|
||||
|
||||
return (
|
||||
<SelectCard
|
||||
@@ -282,6 +285,8 @@ function NewProviderCard({
|
||||
icon={icon}
|
||||
title={productName}
|
||||
description={companyName}
|
||||
titleMaxLines={1}
|
||||
descriptionMaxLines={1}
|
||||
sizePreset="main-ui"
|
||||
variant="section"
|
||||
padding={2}
|
||||
@@ -294,7 +299,11 @@ function NewProviderCard({
|
||||
setIsOpen(true);
|
||||
}}
|
||||
>
|
||||
{t("newProviderCard.connectButton.label")}
|
||||
{/* A provider without a brand (OpenAI-compatible) is set up,
|
||||
like the custom card, rather than connected. */}
|
||||
{providerEntry.copyKey
|
||||
? t("newCustomProviderCard.setUpButton.label")
|
||||
: t("newProviderCard.connectButton.label")}
|
||||
</Button>
|
||||
}
|
||||
/>
|
||||
@@ -318,7 +327,9 @@ function NewCustomProviderCard({
|
||||
}: NewCustomProviderCardProps) {
|
||||
const t = useTranslations("admin.languageModels");
|
||||
const [isOpen, setIsOpen] = useState(false);
|
||||
const { icon, productName, companyName, Modal } = getProvider("custom");
|
||||
const providerEntry = getProvider("custom");
|
||||
const { icon, Modal } = providerEntry;
|
||||
const { productName, companyName } = useProviderNames()(providerEntry);
|
||||
|
||||
return (
|
||||
<>
|
||||
@@ -336,6 +347,8 @@ function NewCustomProviderCard({
|
||||
icon={icon}
|
||||
title={productName}
|
||||
description={companyName}
|
||||
titleMaxLines={1}
|
||||
descriptionMaxLines={1}
|
||||
sizePreset="main-ui"
|
||||
variant="section"
|
||||
padding={2}
|
||||
|
||||
Reference in new issue
Block a user