mirror of
https://github.com/BerriAI/litellm.git
synced 2026-10-04 02:31:27 +00:00
feat(ui): i18n for api reference and models pages
This commit is contained in:
parent
20b4eb3fdb
commit
29d8b86112
7 changed files with 260 additions and 86 deletions
|
|
@ -3,6 +3,7 @@ import React from "react";
|
|||
import { Text, Tab, TabGroup, TabList, TabPanel, TabPanels, Grid } from "@tremor/react";
|
||||
import CodeBlock from "./components/CodeBlock";
|
||||
import DocLink from "@/app/(dashboard)/api-reference/components/DocLink";
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
interface ApiRefProps {
|
||||
proxySettings: {
|
||||
|
|
@ -12,6 +13,7 @@ interface ApiRefProps {
|
|||
}
|
||||
|
||||
const APIReferenceView: React.FC<ApiRefProps> = ({ proxySettings }) => {
|
||||
const { t } = useTranslation();
|
||||
let base_url = "<your_proxy_base_url>";
|
||||
const customDocBaseUrl = proxySettings?.LITELLM_UI_API_DOC_BASE_URL;
|
||||
if (customDocBaseUrl && customDocBaseUrl.trim()) {
|
||||
|
|
@ -27,21 +29,18 @@ const APIReferenceView: React.FC<ApiRefProps> = ({ proxySettings }) => {
|
|||
{/* Header row with Docs link on the right */}
|
||||
<div className="flex items-center justify-between">
|
||||
<p className="text-2xl text-tremor-content-strong dark:text-dark-tremor-content-strong font-semibold">
|
||||
OpenAI Compatible Proxy: API Reference
|
||||
{t("pages.apiReference.title")}
|
||||
</p>
|
||||
<DocLink className="ml-3 shrink-0" href="https://docs.litellm.ai/docs/proxy/user_keys" />
|
||||
</div>
|
||||
|
||||
<Text className="mt-2 mb-2">
|
||||
LiteLLM is OpenAI Compatible. This means your API Key works with the OpenAI SDK. Just replace the base_url
|
||||
to point to your litellm proxy. Example Below{" "}
|
||||
</Text>
|
||||
<Text className="mt-2 mb-2">{t("pages.apiReference.description")}</Text>
|
||||
|
||||
<TabGroup>
|
||||
<TabList>
|
||||
<Tab>OpenAI Python SDK</Tab>
|
||||
<Tab>LlamaIndex</Tab>
|
||||
<Tab>Langchain Py</Tab>
|
||||
<Tab>{t("pages.apiReference.tabOpenAI")}</Tab>
|
||||
<Tab>{t("pages.apiReference.tabLlamaIndex")}</Tab>
|
||||
<Tab>{t("pages.apiReference.tabLangchain")}</Tab>
|
||||
</TabList>
|
||||
<TabPanels>
|
||||
<TabPanel>
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ import { useState } from "react";
|
|||
import { CheckIcon, ClipboardIcon } from "lucide-react";
|
||||
import { Prism as SyntaxHighlighter } from "react-syntax-highlighter";
|
||||
import { oneLight } from "react-syntax-highlighter/dist/esm/styles/prism";
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
interface CodeBlockProps {
|
||||
code: string;
|
||||
|
|
@ -9,6 +10,7 @@ interface CodeBlockProps {
|
|||
}
|
||||
|
||||
const CodeBlock = ({ code, language }: CodeBlockProps) => {
|
||||
const { t } = useTranslation();
|
||||
const [copied, setCopied] = useState(false);
|
||||
const copyToClipboard = () => {
|
||||
navigator.clipboard.writeText(code);
|
||||
|
|
@ -21,7 +23,7 @@ const CodeBlock = ({ code, language }: CodeBlockProps) => {
|
|||
<button
|
||||
onClick={copyToClipboard}
|
||||
className="absolute top-3 right-3 p-2 rounded-md bg-gray-100 hover:bg-gray-200 text-gray-600 z-10"
|
||||
aria-label="Copy code"
|
||||
aria-label={t("pages.apiReference.copyCode")}
|
||||
>
|
||||
{copied ? <CheckIcon size={16} /> : <ClipboardIcon size={16} />}
|
||||
</button>
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
import React from "react";
|
||||
import { ExternalLink } from "lucide-react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
function cn(...parts: Array<string | undefined>) {
|
||||
return parts.filter(Boolean).join(" ");
|
||||
|
|
@ -11,21 +12,22 @@ export type DocLinkProps = {
|
|||
};
|
||||
|
||||
const DocLink = ({ href, className }: DocLinkProps) => {
|
||||
const { t } = useTranslation();
|
||||
return (
|
||||
<a
|
||||
href={href}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
title="Open documentation in a new tab"
|
||||
title={t("pages.apiReference.docLinkTitle")}
|
||||
className={cn(
|
||||
"inline-flex items-center gap-2 rounded-xl border border-zinc-200 bg-white/80 px-3.5 py-2 text-sm font-medium text-zinc-700 shadow-sm",
|
||||
"hover:bg-white focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-blue-500 active:translate-y-[0.5px]",
|
||||
className,
|
||||
)}
|
||||
>
|
||||
<span>API Reference Docs</span>
|
||||
<span>{t("pages.apiReference.docLinkText")}</span>
|
||||
<ExternalLink aria-hidden className="h-4 w-4 opacity-80" />
|
||||
<span className="sr-only">(opens in a new tab)</span>
|
||||
<span className="sr-only">{t("pages.apiReference.docLinkSrOnly")}</span>
|
||||
</a>
|
||||
);
|
||||
};
|
||||
|
|
|
|||
|
|
@ -20,6 +20,7 @@ import type { UploadProps } from "antd";
|
|||
import { Form } from "antd";
|
||||
import { PlusCircleOutlined } from "@ant-design/icons";
|
||||
import React, { useEffect, useMemo, useState } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import AddModelTab from "../../../components/add_model/add_model_tab";
|
||||
import HealthCheckComponent from "../../../components/model_dashboard/HealthCheckComponent";
|
||||
import ModelGroupAliasSettings from "../../../components/model_group_alias_settings";
|
||||
|
|
@ -49,6 +50,7 @@ interface GlobalRetryPolicyObject {
|
|||
const HEALTH_PAGE_SIZE = 50;
|
||||
|
||||
const ModelsAndEndpointsView: React.FC<ModelDashboardProps> = ({ premiumUser, teams }) => {
|
||||
const { t } = useTranslation();
|
||||
const { accessToken, token, userRole, userId: userID } = useAuthorized();
|
||||
const [addModelForm] = Form.useForm();
|
||||
const [lastRefreshed, setLastRefreshed] = useState("");
|
||||
|
|
@ -178,9 +180,9 @@ const ModelsAndEndpointsView: React.FC<ModelDashboardProps> = ({ premiumUser, te
|
|||
},
|
||||
onChange(info) {
|
||||
if (info.file.status === "done") {
|
||||
NotificationsManager.success(`${info.file.name} file uploaded successfully`);
|
||||
NotificationsManager.success(t("pages.modelsAndEndpoints.fileUploadSuccess", { name: info.file.name }));
|
||||
} else if (info.file.status === "error") {
|
||||
NotificationsManager.fromBackend(`${info.file.name} file upload failed.`);
|
||||
NotificationsManager.fromBackend(t("pages.modelsAndEndpoints.fileUploadFailed", { name: info.file.name }));
|
||||
}
|
||||
},
|
||||
};
|
||||
|
|
@ -207,17 +209,19 @@ const ModelsAndEndpointsView: React.FC<ModelDashboardProps> = ({ premiumUser, te
|
|||
if (globalRetryPolicy) {
|
||||
payload.router_settings.retry_policy = globalRetryPolicy;
|
||||
}
|
||||
NotificationsManager.success("Global retry settings saved successfully");
|
||||
NotificationsManager.success(t("pages.modelsAndEndpoints.globalRetrySaved"));
|
||||
} else {
|
||||
if (modelGroupRetryPolicy) {
|
||||
payload.router_settings.model_group_retry_policy = modelGroupRetryPolicy;
|
||||
}
|
||||
NotificationsManager.success(`Retry settings saved successfully for ${selectedModelGroup}`);
|
||||
NotificationsManager.success(
|
||||
t("pages.modelsAndEndpoints.modelGroupRetrySaved", { modelGroup: selectedModelGroup }),
|
||||
);
|
||||
}
|
||||
|
||||
await setCallbacksCall(accessToken, payload);
|
||||
} catch (error) {
|
||||
NotificationsManager.fromBackend("Failed to save retry settings");
|
||||
NotificationsManager.fromBackend(t("pages.modelsAndEndpoints.failedToSaveRetry"));
|
||||
}
|
||||
};
|
||||
|
||||
|
|
@ -266,7 +270,7 @@ const ModelsAndEndpointsView: React.FC<ModelDashboardProps> = ({ premiumUser, te
|
|||
return `${field.name.join(".")}: ${field.errors.join(", ")}`;
|
||||
})
|
||||
.join(" | ") || "Unknown validation error";
|
||||
NotificationsManager.fromBackend(`Please fill in the following required fields: ${errorMessages}`);
|
||||
NotificationsManager.fromBackend(t("pages.modelsAndEndpoints.validationError", { fields: errorMessages }));
|
||||
}
|
||||
};
|
||||
|
||||
|
|
@ -297,11 +301,11 @@ const ModelsAndEndpointsView: React.FC<ModelDashboardProps> = ({ premiumUser, te
|
|||
{/* Model Management Header */}
|
||||
<div className="flex justify-between items-center mb-4">
|
||||
<div>
|
||||
<h2 className="text-lg font-semibold">Model Management</h2>
|
||||
<h2 className="text-lg font-semibold">{t("pages.modelsAndEndpoints.modelManagement")}</h2>
|
||||
{!all_admin_roles.includes(userRole) ? (
|
||||
<p className="text-sm text-gray-600">Add models for teams you are an admin for.</p>
|
||||
<p className="text-sm text-gray-600">{t("pages.modelsAndEndpoints.addModelsForTeams")}</p>
|
||||
) : (
|
||||
<p className="text-sm text-gray-600">Add and manage models for the proxy</p>
|
||||
<p className="text-sm text-gray-600">{t("pages.modelsAndEndpoints.addAndManageModels")}</p>
|
||||
)}
|
||||
</div>
|
||||
{!showMissingProviderBanner && (
|
||||
|
|
@ -312,7 +316,7 @@ const ModelsAndEndpointsView: React.FC<ModelDashboardProps> = ({ premiumUser, te
|
|||
className="inline-flex items-center gap-1.5 px-3 py-1.5 text-xs font-medium text-[#6366f1] hover:text-[#5558e3] border border-[#6366f1] hover:border-[#5558e3] rounded-lg transition-colors"
|
||||
>
|
||||
<PlusCircleOutlined style={{ fontSize: "12px" }} />
|
||||
Request Provider
|
||||
{t("pages.modelsAndEndpoints.requestProvider")}
|
||||
</a>
|
||||
)}
|
||||
</div>
|
||||
|
|
@ -324,11 +328,10 @@ const ModelsAndEndpointsView: React.FC<ModelDashboardProps> = ({ premiumUser, te
|
|||
<PlusCircleOutlined style={{ fontSize: "18px", color: "#6366f1" }} />
|
||||
</div>
|
||||
<div className="flex-1 min-w-0">
|
||||
<h4 className="text-gray-900 font-semibold text-sm m-0">Missing a provider?</h4>
|
||||
<p className="text-gray-500 text-xs m-0 mt-0.5">
|
||||
The LiteLLM engineering team is constantly adding support for new LLM models, providers, endpoints. If
|
||||
you don't see the one you need, let us know and we'll prioritize it.
|
||||
</p>
|
||||
<h4 className="text-gray-900 font-semibold text-sm m-0">
|
||||
{t("pages.modelsAndEndpoints.missingProviderTitle")}
|
||||
</h4>
|
||||
<p className="text-gray-500 text-xs m-0 mt-0.5">{t("pages.modelsAndEndpoints.missingProviderDesc")}</p>
|
||||
</div>
|
||||
<a
|
||||
href="https://models.litellm.ai/?request=true"
|
||||
|
|
@ -336,7 +339,7 @@ const ModelsAndEndpointsView: React.FC<ModelDashboardProps> = ({ premiumUser, te
|
|||
rel="noopener noreferrer"
|
||||
className="flex-shrink-0 inline-flex items-center gap-2 px-4 py-2 bg-[#6366f1] hover:bg-[#5558e3] text-white text-sm font-medium rounded-lg transition-colors"
|
||||
>
|
||||
Request Provider
|
||||
{t("pages.modelsAndEndpoints.requestProvider")}
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
className="h-4 w-4"
|
||||
|
|
@ -358,7 +361,7 @@ const ModelsAndEndpointsView: React.FC<ModelDashboardProps> = ({ premiumUser, te
|
|||
localStorage.setItem("hideMissingProviderBanner", "true");
|
||||
}}
|
||||
className="flex-shrink-0 p-1 text-gray-400 hover:text-gray-600 hover:bg-gray-100 rounded-full transition-colors"
|
||||
aria-label="Dismiss banner"
|
||||
aria-label={t("pages.modelsAndEndpoints.dismissBanner")}
|
||||
>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
|
|
@ -399,7 +402,13 @@ const ModelsAndEndpointsView: React.FC<ModelDashboardProps> = ({ premiumUser, te
|
|||
const isAdmin = all_admin_roles.includes(userRole);
|
||||
const visibleTabs: Array<{ tab: React.ReactElement; panel: React.ReactElement }> = [
|
||||
{
|
||||
tab: <Tab key="all-models">{isAdmin ? "All Models" : "Your Models"}</Tab>,
|
||||
tab: (
|
||||
<Tab key="all-models">
|
||||
{isAdmin
|
||||
? t("pages.modelsAndEndpoints.tabAllModels")
|
||||
: t("pages.modelsAndEndpoints.tabYourModels")}
|
||||
</Tab>
|
||||
),
|
||||
panel: (
|
||||
<AllModelsTab
|
||||
key="all-models"
|
||||
|
|
@ -415,7 +424,7 @@ const ModelsAndEndpointsView: React.FC<ModelDashboardProps> = ({ premiumUser, te
|
|||
];
|
||||
if (!shouldHideAddModelTab) {
|
||||
visibleTabs.push({
|
||||
tab: <Tab key="add-model">Add Model</Tab>,
|
||||
tab: <Tab key="add-model">{t("pages.modelsAndEndpoints.tabAddModel")}</Tab>,
|
||||
panel: (
|
||||
<TabPanel key="add-model" className="h-full">
|
||||
<AddModelTab
|
||||
|
|
@ -441,7 +450,7 @@ const ModelsAndEndpointsView: React.FC<ModelDashboardProps> = ({ premiumUser, te
|
|||
if (isAdmin) {
|
||||
visibleTabs.push(
|
||||
{
|
||||
tab: <Tab key="llm-credentials">LLM Credentials</Tab>,
|
||||
tab: <Tab key="llm-credentials">{t("pages.modelsAndEndpoints.tabLLMCredentials")}</Tab>,
|
||||
panel: (
|
||||
<TabPanel key="llm-credentials">
|
||||
<CredentialsPanel uploadProps={uploadProps} />
|
||||
|
|
@ -449,7 +458,7 @@ const ModelsAndEndpointsView: React.FC<ModelDashboardProps> = ({ premiumUser, te
|
|||
),
|
||||
},
|
||||
{
|
||||
tab: <Tab key="pass-through">Pass-Through Endpoints</Tab>,
|
||||
tab: <Tab key="pass-through">{t("pages.modelsAndEndpoints.tabPassThrough")}</Tab>,
|
||||
panel: (
|
||||
<TabPanel key="pass-through">
|
||||
<PassThroughSettings
|
||||
|
|
@ -463,7 +472,7 @@ const ModelsAndEndpointsView: React.FC<ModelDashboardProps> = ({ premiumUser, te
|
|||
),
|
||||
},
|
||||
{
|
||||
tab: <Tab key="health-status">Health Status</Tab>,
|
||||
tab: <Tab key="health-status">{t("pages.modelsAndEndpoints.tabHealthStatus")}</Tab>,
|
||||
panel: (
|
||||
<TabPanel key="health-status">
|
||||
<HealthCheckComponent
|
||||
|
|
@ -483,7 +492,7 @@ const ModelsAndEndpointsView: React.FC<ModelDashboardProps> = ({ premiumUser, te
|
|||
),
|
||||
},
|
||||
{
|
||||
tab: <Tab key="model-retry-settings">Model Retry Settings</Tab>,
|
||||
tab: <Tab key="model-retry-settings">{t("pages.modelsAndEndpoints.tabModelRetrySettings")}</Tab>,
|
||||
panel: (
|
||||
<ModelRetrySettingsTab
|
||||
key="model-retry-settings"
|
||||
|
|
@ -500,7 +509,7 @@ const ModelsAndEndpointsView: React.FC<ModelDashboardProps> = ({ premiumUser, te
|
|||
),
|
||||
},
|
||||
{
|
||||
tab: <Tab key="model-group-alias">Model Group Alias</Tab>,
|
||||
tab: <Tab key="model-group-alias">{t("pages.modelsAndEndpoints.tabModelGroupAlias")}</Tab>,
|
||||
panel: (
|
||||
<TabPanel key="model-group-alias">
|
||||
<ModelGroupAliasSettings
|
||||
|
|
@ -512,7 +521,7 @@ const ModelsAndEndpointsView: React.FC<ModelDashboardProps> = ({ premiumUser, te
|
|||
),
|
||||
},
|
||||
{
|
||||
tab: <Tab key="price-data-reload">Price Data Reload</Tab>,
|
||||
tab: <Tab key="price-data-reload">{t("pages.modelsAndEndpoints.tabPriceDataReload")}</Tab>,
|
||||
panel: <PriceDataManagementTab key="price-data-reload" />,
|
||||
},
|
||||
);
|
||||
|
|
@ -527,7 +536,11 @@ const ModelsAndEndpointsView: React.FC<ModelDashboardProps> = ({ premiumUser, te
|
|||
<div className="flex">{visibleTabs.map((t) => t.tab)}</div>
|
||||
|
||||
<div className="flex items-center space-x-2 self-center">
|
||||
{lastRefreshed && <span className="text-xs text-gray-500">Last Refreshed: {lastRefreshed}</span>}
|
||||
{lastRefreshed && (
|
||||
<span className="text-xs text-gray-500">
|
||||
{t("pages.modelsAndEndpoints.lastRefreshed", { time: lastRefreshed })}
|
||||
</span>
|
||||
)}
|
||||
<Icon
|
||||
icon={RefreshIcon}
|
||||
variant="shadow"
|
||||
|
|
|
|||
|
|
@ -16,6 +16,7 @@ import { Badge, Button, Select, Skeleton, Space, Typography } from "antd";
|
|||
import ModelSettingsModal from "@/components/model_dashboard/ModelSettingsModal/ModelSettingsModal";
|
||||
import debounce from "lodash/debounce";
|
||||
import { useEffect, useMemo, useState } from "react";
|
||||
import { useTranslation, Trans } from "react-i18next";
|
||||
import { useModelsInfo } from "../../hooks/models/useModels";
|
||||
import { transformModelData } from "../utils/modelDataTransformer";
|
||||
type ModelViewMode = "all" | "current_team";
|
||||
|
|
@ -38,6 +39,7 @@ const AllModelsTab = ({
|
|||
setSelectedModelId,
|
||||
setSelectedTeamId,
|
||||
}: AllModelsTabProps) => {
|
||||
const { t } = useTranslation();
|
||||
const { data: modelCostMapData, isLoading: isLoadingModelCostMap } = useModelCostMap();
|
||||
const { accessToken, userId, userRole, premiumUser } = useAuthorized();
|
||||
const { data: teams, isLoading: isLoadingTeams } = useTeams();
|
||||
|
|
@ -204,7 +206,7 @@ const AllModelsTab = ({
|
|||
try {
|
||||
setDeleteLoading(true);
|
||||
await modelDeleteCall(accessToken, deleteModalModelId);
|
||||
NotificationsManager.success("Model deleted successfully");
|
||||
NotificationsManager.success(t("pages.allModelsTab.modelDeletedSuccess"));
|
||||
queryClient.invalidateQueries({ queryKey: ["models", "list"] });
|
||||
refetchModels();
|
||||
} catch (error) {
|
||||
|
|
@ -223,7 +225,9 @@ const AllModelsTab = ({
|
|||
try {
|
||||
setPausingModelId(modelId);
|
||||
await modelPatchUpdateCall(accessToken, { blocked }, modelId);
|
||||
NotificationsManager.success(blocked ? "Model paused" : "Model resumed");
|
||||
NotificationsManager.success(
|
||||
blocked ? t("pages.allModelsTab.modelPaused") : t("pages.allModelsTab.modelResumed"),
|
||||
);
|
||||
// invalidateQueries already schedules a refetch for active observers
|
||||
// on this key — no need to also call refetchModels() (would double-fetch).
|
||||
queryClient.invalidateQueries({ queryKey: ["models", "list"] });
|
||||
|
|
@ -244,7 +248,7 @@ const AllModelsTab = ({
|
|||
<div className="border-b px-6 py-4 bg-gray-50">
|
||||
<div className="flex items-center justify-between">
|
||||
<div className="flex items-center gap-4">
|
||||
<Text className="text-lg font-semibold text-gray-900">Current Team:</Text>
|
||||
<Text className="text-lg font-semibold text-gray-900">{t("pages.allModelsTab.currentTeam")}</Text>
|
||||
<div className="w-80">
|
||||
{isLoading ? (
|
||||
<Skeleton.Input active block size="large" />
|
||||
|
|
@ -277,7 +281,7 @@ const AllModelsTab = ({
|
|||
label: (
|
||||
<Space direction="horizontal" align="center">
|
||||
<Badge color="blue" size="small" />
|
||||
<Text style={{ fontSize: 16 }}>Personal</Text>
|
||||
<Text style={{ fontSize: 16 }}>{t("pages.allModelsTab.personal")}</Text>
|
||||
</Space>
|
||||
),
|
||||
},
|
||||
|
|
@ -300,7 +304,7 @@ const AllModelsTab = ({
|
|||
</div>
|
||||
</div>
|
||||
<div className="flex items-center gap-4">
|
||||
<Text className="text-lg font-semibold text-gray-900">View:</Text>
|
||||
<Text className="text-lg font-semibold text-gray-900">{t("pages.allModelsTab.view")}</Text>
|
||||
<div className="w-64">
|
||||
{isLoading ? (
|
||||
<Skeleton.Input active block size="large" />
|
||||
|
|
@ -317,7 +321,7 @@ const AllModelsTab = ({
|
|||
label: (
|
||||
<Space direction="horizontal" align="center">
|
||||
<Badge color="purple" size="small" />
|
||||
<Text style={{ fontSize: 16 }}>Current Team Models</Text>
|
||||
<Text style={{ fontSize: 16 }}>{t("pages.allModelsTab.currentTeamModels")}</Text>
|
||||
</Space>
|
||||
),
|
||||
},
|
||||
|
|
@ -326,7 +330,7 @@ const AllModelsTab = ({
|
|||
label: (
|
||||
<Space direction="horizontal" align="center">
|
||||
<Badge color="gray" size="small" />
|
||||
<Text style={{ fontSize: 16 }}>All Available Models</Text>
|
||||
<Text style={{ fontSize: 16 }}>{t("pages.allModelsTab.allAvailableModels")}</Text>
|
||||
</Space>
|
||||
),
|
||||
},
|
||||
|
|
@ -343,25 +347,35 @@ const AllModelsTab = ({
|
|||
<div className="text-xs text-gray-500">
|
||||
{currentTeam === "personal" ? (
|
||||
<span>
|
||||
To access these models: Create a Virtual Key without selecting a team on the{" "}
|
||||
<a
|
||||
href="/public?login=success&page=api-keys"
|
||||
className="text-gray-600 hover:text-gray-800 underline"
|
||||
>
|
||||
Virtual Keys page
|
||||
</a>
|
||||
<Trans
|
||||
i18nKey="pages.allModelsTab.accessInfoPersonal"
|
||||
components={{
|
||||
a: (
|
||||
<a
|
||||
href="/public?login=success&page=api-keys"
|
||||
className="text-gray-600 hover:text-gray-800 underline"
|
||||
/>
|
||||
),
|
||||
}}
|
||||
/>
|
||||
</span>
|
||||
) : (
|
||||
<span>
|
||||
To access these models: Create a Virtual Key and select Team as "
|
||||
{typeof currentTeam !== "string" ? currentTeam.team_alias || currentTeam.team_id : ""}" on
|
||||
the{" "}
|
||||
<a
|
||||
href="/public?login=success&page=api-keys"
|
||||
className="text-gray-600 hover:text-gray-800 underline"
|
||||
>
|
||||
Virtual Keys page
|
||||
</a>
|
||||
<Trans
|
||||
i18nKey="pages.allModelsTab.accessInfoTeam"
|
||||
values={{
|
||||
teamName:
|
||||
typeof currentTeam !== "string" ? currentTeam.team_alias || currentTeam.team_id : "",
|
||||
}}
|
||||
components={{
|
||||
a: (
|
||||
<a
|
||||
href="/public?login=success&page=api-keys"
|
||||
className="text-gray-600 hover:text-gray-800 underline"
|
||||
/>
|
||||
),
|
||||
}}
|
||||
/>
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
|
|
@ -379,7 +393,7 @@ const AllModelsTab = ({
|
|||
<div className="relative w-64">
|
||||
<input
|
||||
type="text"
|
||||
placeholder="Search model names..."
|
||||
placeholder={t("pages.allModelsTab.searchPlaceholder")}
|
||||
data-testid="model-search-input"
|
||||
className="w-full px-3 py-2 pl-8 border rounded-md text-sm focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-blue-500"
|
||||
value={modelNameSearch}
|
||||
|
|
@ -413,7 +427,7 @@ const AllModelsTab = ({
|
|||
d="M3 4a1 1 0 011-1h16a1 1 0 011 1v2.586a1 1 0 01-.293.707l-6.414 6.414a1 1 0 00-.293.707V17l-4 4v-6.586a1 1 0 00-.293-.707L3.293 7.293A1 1 0 013 6.586V4z"
|
||||
/>
|
||||
</svg>
|
||||
Filters
|
||||
{t("pages.allModelsTab.filters")}
|
||||
</button>
|
||||
|
||||
{/* Reset Filters Button */}
|
||||
|
|
@ -429,7 +443,7 @@ const AllModelsTab = ({
|
|||
d="M4 4v5h.582m15.356 2A8.001 8.001 0 004.582 9m0 0H9m11 11v-5h-.581m0 0a8.003 8.003 0 01-15.357-2m15.357 2H15"
|
||||
/>
|
||||
</svg>
|
||||
Reset Filters
|
||||
{t("pages.allModelsTab.resetFilters")}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
|
|
@ -437,7 +451,7 @@ const AllModelsTab = ({
|
|||
<Button
|
||||
icon={<SettingOutlined />}
|
||||
onClick={() => setIsModelSettingsModalVisible(true)}
|
||||
title="Model Settings"
|
||||
title={t("pages.allModelsTab.modelSettings")}
|
||||
/>
|
||||
</div>
|
||||
|
||||
|
|
@ -450,11 +464,11 @@ const AllModelsTab = ({
|
|||
className="w-full"
|
||||
value={selectedModelGroup ?? "all"}
|
||||
onChange={(value) => setSelectedModelGroup(value === "all" ? "all" : value)}
|
||||
placeholder="Filter by Public Model Name"
|
||||
placeholder={t("pages.allModelsTab.filterByPublicModelName")}
|
||||
showSearch
|
||||
options={[
|
||||
{ value: "all", label: "All Models" },
|
||||
{ value: "wildcard", label: "Wildcard Models (*)" },
|
||||
{ value: "all", label: t("pages.allModelsTab.allModels") },
|
||||
{ value: "wildcard", label: t("pages.allModelsTab.wildcardModels") },
|
||||
...availableModelGroups.map((group, idx) => ({
|
||||
value: group,
|
||||
label: group,
|
||||
|
|
@ -469,10 +483,10 @@ const AllModelsTab = ({
|
|||
className="w-full"
|
||||
value={selectedModelAccessGroupFilter ?? "all"}
|
||||
onChange={(value) => setSelectedModelAccessGroupFilter(value === "all" ? null : value)}
|
||||
placeholder="Filter by Model Access Group"
|
||||
placeholder={t("pages.allModelsTab.filterByModelAccessGroup")}
|
||||
showSearch
|
||||
options={[
|
||||
{ value: "all", label: "All Model Access Groups" },
|
||||
{ value: "all", label: t("pages.allModelsTab.allModelAccessGroups") },
|
||||
...availableModelAccessGroups.map((accessGroup, idx) => ({
|
||||
value: accessGroup,
|
||||
label: accessGroup,
|
||||
|
|
@ -490,8 +504,12 @@ const AllModelsTab = ({
|
|||
) : (
|
||||
<span data-testid="models-results-count" className="text-sm text-gray-700">
|
||||
{paginationMeta.total_count > 0
|
||||
? `Showing ${(currentPage - 1) * pageSize + 1} - ${Math.min(currentPage * pageSize, paginationMeta.total_count)} of ${paginationMeta.total_count} results`
|
||||
: "Showing 0 results"}
|
||||
? t("pages.allModelsTab.showingResults", {
|
||||
from: (currentPage - 1) * pageSize + 1,
|
||||
to: Math.min(currentPage * pageSize, paginationMeta.total_count),
|
||||
total: paginationMeta.total_count,
|
||||
})
|
||||
: t("pages.allModelsTab.showingZeroResults")}
|
||||
</span>
|
||||
)}
|
||||
|
||||
|
|
@ -510,7 +528,7 @@ const AllModelsTab = ({
|
|||
currentPage === 1 ? "bg-gray-100 text-gray-400 cursor-not-allowed" : "hover:bg-gray-50"
|
||||
}`}
|
||||
>
|
||||
Previous
|
||||
{t("common.previous")}
|
||||
</button>
|
||||
)}
|
||||
|
||||
|
|
@ -530,7 +548,7 @@ const AllModelsTab = ({
|
|||
: "hover:bg-gray-50"
|
||||
}`}
|
||||
>
|
||||
Next
|
||||
{t("common.next")}
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
|
|
@ -569,28 +587,28 @@ const AllModelsTab = ({
|
|||
|
||||
<DeleteResourceModal
|
||||
isOpen={!!deleteModalModelId}
|
||||
title="Delete Model"
|
||||
alertMessage="This action cannot be undone."
|
||||
message="Are you sure you want to delete this model?"
|
||||
resourceInformationTitle="Model Information"
|
||||
title={t("pages.allModelsTab.deleteModelTitle")}
|
||||
alertMessage={t("pages.allModelsTab.deleteAlertMessage")}
|
||||
message={t("pages.allModelsTab.deleteMessage")}
|
||||
resourceInformationTitle={t("pages.allModelsTab.modelInformation")}
|
||||
resourceInformation={
|
||||
modelToDelete
|
||||
? [
|
||||
{
|
||||
label: "Model Name",
|
||||
value: modelToDelete.model_name || "Not Set",
|
||||
label: t("pages.allModelsTab.modelName"),
|
||||
value: modelToDelete.model_name || t("pages.allModelsTab.notSet"),
|
||||
},
|
||||
{
|
||||
label: "LiteLLM Model Name",
|
||||
value: modelToDelete.litellm_model_name || "Not Set",
|
||||
label: t("pages.allModelsTab.litellmModelName"),
|
||||
value: modelToDelete.litellm_model_name || t("pages.allModelsTab.notSet"),
|
||||
},
|
||||
{
|
||||
label: "Provider",
|
||||
value: modelToDelete.provider || "Not Set",
|
||||
label: t("pages.allModelsTab.provider"),
|
||||
value: modelToDelete.provider || t("pages.allModelsTab.notSet"),
|
||||
},
|
||||
{
|
||||
label: "Created By",
|
||||
value: modelToDelete.model_info?.created_by || "Not Set",
|
||||
label: t("pages.allModelsTab.createdBy"),
|
||||
value: modelToDelete.model_info?.created_by || t("pages.allModelsTab.notSet"),
|
||||
},
|
||||
]
|
||||
: []
|
||||
|
|
|
|||
|
|
@ -8512,5 +8512,75 @@
|
|||
"less": "less",
|
||||
"more": "more"
|
||||
}
|
||||
},
|
||||
"pages": {
|
||||
"apiReference": {
|
||||
"title": "OpenAI Compatible Proxy: API Reference",
|
||||
"description": "LiteLLM is OpenAI Compatible. This means your API Key works with the OpenAI SDK. Just replace the base_url to point to your litellm proxy. Example Below",
|
||||
"tabOpenAI": "OpenAI Python SDK",
|
||||
"tabLlamaIndex": "LlamaIndex",
|
||||
"tabLangchain": "Langchain Py",
|
||||
"copyCode": "Copy code",
|
||||
"docLinkTitle": "Open documentation in a new tab",
|
||||
"docLinkText": "API Reference Docs",
|
||||
"docLinkSrOnly": "(opens in a new tab)"
|
||||
},
|
||||
"modelsAndEndpoints": {
|
||||
"modelManagement": "Model Management",
|
||||
"addModelsForTeams": "Add models for teams you are an admin for.",
|
||||
"addAndManageModels": "Add and manage models for the proxy",
|
||||
"requestProvider": "Request Provider",
|
||||
"missingProviderTitle": "Missing a provider?",
|
||||
"missingProviderDesc": "The LiteLLM engineering team is constantly adding support for new LLM models, providers, endpoints. If you don't see the one you need, let us know and we'll prioritize it.",
|
||||
"dismissBanner": "Dismiss banner",
|
||||
"tabAllModels": "All Models",
|
||||
"tabYourModels": "Your Models",
|
||||
"tabAddModel": "Add Model",
|
||||
"tabLLMCredentials": "LLM Credentials",
|
||||
"tabPassThrough": "Pass-Through Endpoints",
|
||||
"tabHealthStatus": "Health Status",
|
||||
"tabModelRetrySettings": "Model Retry Settings",
|
||||
"tabModelGroupAlias": "Model Group Alias",
|
||||
"tabPriceDataReload": "Price Data Reload",
|
||||
"lastRefreshed": "Last Refreshed: {{time}}",
|
||||
"fileUploadSuccess": "{{name}} file uploaded successfully",
|
||||
"fileUploadFailed": "{{name}} file upload failed.",
|
||||
"globalRetrySaved": "Global retry settings saved successfully",
|
||||
"modelGroupRetrySaved": "Retry settings saved successfully for {{modelGroup}}",
|
||||
"failedToSaveRetry": "Failed to save retry settings",
|
||||
"validationError": "Please fill in the following required fields: {{fields}}"
|
||||
},
|
||||
"allModelsTab": {
|
||||
"currentTeam": "Current Team:",
|
||||
"personal": "Personal",
|
||||
"view": "View:",
|
||||
"currentTeamModels": "Current Team Models",
|
||||
"allAvailableModels": "All Available Models",
|
||||
"accessInfoPersonal": "To access these models: Create a Virtual Key without selecting a team on the <a>Virtual Keys page</a>",
|
||||
"accessInfoTeam": "To access these models: Create a Virtual Key and select Team as \"{{teamName}}\" on the <a>Virtual Keys page</a>",
|
||||
"searchPlaceholder": "Search model names...",
|
||||
"filters": "Filters",
|
||||
"resetFilters": "Reset Filters",
|
||||
"filterByPublicModelName": "Filter by Public Model Name",
|
||||
"allModels": "All Models",
|
||||
"wildcardModels": "Wildcard Models (*)",
|
||||
"filterByModelAccessGroup": "Filter by Model Access Group",
|
||||
"allModelAccessGroups": "All Model Access Groups",
|
||||
"showingResults": "Showing {{from}} - {{to}} of {{total}} results",
|
||||
"showingZeroResults": "Showing 0 results",
|
||||
"modelSettings": "Model Settings",
|
||||
"deleteModelTitle": "Delete Model",
|
||||
"deleteAlertMessage": "This action cannot be undone.",
|
||||
"deleteMessage": "Are you sure you want to delete this model?",
|
||||
"modelInformation": "Model Information",
|
||||
"modelName": "Model Name",
|
||||
"litellmModelName": "LiteLLM Model Name",
|
||||
"provider": "Provider",
|
||||
"createdBy": "Created By",
|
||||
"notSet": "Not Set",
|
||||
"modelDeletedSuccess": "Model deleted successfully",
|
||||
"modelPaused": "Model paused",
|
||||
"modelResumed": "Model resumed"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -8512,5 +8512,75 @@
|
|||
"less": "收起",
|
||||
"more": "展开"
|
||||
}
|
||||
},
|
||||
"pages": {
|
||||
"apiReference": {
|
||||
"title": "OpenAI 兼容代理:API 参考",
|
||||
"description": "LiteLLM 与 OpenAI 兼容,这意味着您的 API 密钥可与 OpenAI SDK 配合使用。只需将 base_url 替换为指向您的 litellm 代理即可。示例如下",
|
||||
"tabOpenAI": "OpenAI Python SDK",
|
||||
"tabLlamaIndex": "LlamaIndex",
|
||||
"tabLangchain": "Langchain Py",
|
||||
"copyCode": "复制代码",
|
||||
"docLinkTitle": "在新标签页中打开文档",
|
||||
"docLinkText": "API 参考文档",
|
||||
"docLinkSrOnly": "(在新标签页中打开)"
|
||||
},
|
||||
"modelsAndEndpoints": {
|
||||
"modelManagement": "模型管理",
|
||||
"addModelsForTeams": "为您担任管理员的团队添加模型。",
|
||||
"addAndManageModels": "为代理添加和管理模型",
|
||||
"requestProvider": "申请提供商",
|
||||
"missingProviderTitle": "缺少某个提供商?",
|
||||
"missingProviderDesc": "LiteLLM 工程团队持续添加对新 LLM 模型、提供商和端点的支持。如果您没有找到所需的提供商,请告知我们,我们将优先处理。",
|
||||
"dismissBanner": "关闭横幅",
|
||||
"tabAllModels": "全部模型",
|
||||
"tabYourModels": "我的模型",
|
||||
"tabAddModel": "添加模型",
|
||||
"tabLLMCredentials": "LLM 凭证",
|
||||
"tabPassThrough": "透传端点",
|
||||
"tabHealthStatus": "健康检查",
|
||||
"tabModelRetrySettings": "模型重试设置",
|
||||
"tabModelGroupAlias": "模型组别名",
|
||||
"tabPriceDataReload": "价格数据重载",
|
||||
"lastRefreshed": "上次刷新:{{time}}",
|
||||
"fileUploadSuccess": "{{name}} 文件上传成功",
|
||||
"fileUploadFailed": "{{name}} 文件上传失败。",
|
||||
"globalRetrySaved": "全局重试设置保存成功",
|
||||
"modelGroupRetrySaved": "{{modelGroup}} 的重试设置保存成功",
|
||||
"failedToSaveRetry": "保存重试设置失败",
|
||||
"validationError": "请填写以下必填字段:{{fields}}"
|
||||
},
|
||||
"allModelsTab": {
|
||||
"currentTeam": "当前团队:",
|
||||
"personal": "个人",
|
||||
"view": "视图:",
|
||||
"currentTeamModels": "当前团队模型",
|
||||
"allAvailableModels": "全部可用模型",
|
||||
"accessInfoPersonal": "要访问这些模型:在<a>虚拟密钥页面</a>创建一个虚拟密钥,不要选择团队",
|
||||
"accessInfoTeam": "要访问这些模型:在<a>虚拟密钥页面</a>创建虚拟密钥并选择团队\"{{teamName}}\"",
|
||||
"searchPlaceholder": "搜索模型名称...",
|
||||
"filters": "筛选",
|
||||
"resetFilters": "重置筛选",
|
||||
"filterByPublicModelName": "按公开模型名称筛选",
|
||||
"allModels": "全部模型",
|
||||
"wildcardModels": "通配符模型 (*)",
|
||||
"filterByModelAccessGroup": "按模型访问组筛选",
|
||||
"allModelAccessGroups": "全部模型访问组",
|
||||
"showingResults": "显示第 {{from}} - {{to}} 条,共 {{total}} 条",
|
||||
"showingZeroResults": "共 0 条结果",
|
||||
"modelSettings": "模型设置",
|
||||
"deleteModelTitle": "删除模型",
|
||||
"deleteAlertMessage": "此操作无法撤销。",
|
||||
"deleteMessage": "确定要删除此模型吗?",
|
||||
"modelInformation": "模型信息",
|
||||
"modelName": "模型名称",
|
||||
"litellmModelName": "LiteLLM 模型名称",
|
||||
"provider": "提供商",
|
||||
"createdBy": "创建者",
|
||||
"notSet": "未设置",
|
||||
"modelDeletedSuccess": "模型已成功删除",
|
||||
"modelPaused": "模型已暂停",
|
||||
"modelResumed": "模型已恢复"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue