feat(ui): i18n for add_model batch 1

This commit is contained in:
吹雪 2026-06-11 00:36:21 +09:00
parent 13276aea0a
commit 203eaff982
8 changed files with 516 additions and 179 deletions

View file

@ -7,6 +7,7 @@ import type { FormInstance } from "antd";
import { Select as AntdSelect, Button, Card, Col, Form, Modal, Row, Tooltip, Typography, Alert } from "antd";
import type { UploadProps } from "antd/es/upload";
import React, { useEffect, useMemo, useState } from "react";
import { Trans, useTranslation } from "react-i18next";
import TeamDropdown from "../common_components/team_dropdown";
import type { Team } from "../key_team_helpers/key_list";
import { type CredentialItem, type ProviderCreateInfo, modelAvailableCall } from "../networking";
@ -17,7 +18,7 @@ import ConditionalPublicModelName from "./conditional_public_model_name";
import LiteLLMModelNameField from "./litellm_model_name";
import ConnectionErrorDisplay from "./model_connection_test";
import ProviderSpecificFields from "./provider_specific_fields";
import { TEST_MODES } from "./add_model_modes";
import { getTestModes } from "./add_model_modes";
import useAuthorized from "@/app/(dashboard)/hooks/useAuthorized";
interface AddModelFormProps {
@ -51,6 +52,7 @@ const AddModelForm: React.FC<AddModelFormProps> = ({
teams,
credentials,
}) => {
const { t } = useTranslation();
const [testMode, setTestMode] = useState<string>("chat");
const [isResultModalVisible, setIsResultModalVisible] = useState<boolean>(false);
const [isTestingConnection, setIsTestingConnection] = useState<boolean>(false);
@ -86,6 +88,8 @@ const AddModelForm: React.FC<AddModelFormProps> = ({
fetchModelAccessGroups();
}, [accessToken]);
const testModes = useMemo(() => getTestModes(t), [t]);
const sortedProviderMetadata: ProviderCreateInfo[] = useMemo(() => {
if (!providerMetadata) {
return [];
@ -96,7 +100,7 @@ const AddModelForm: React.FC<AddModelFormProps> = ({
const providerMetadataErrorText = providerMetadataError
? providerMetadataError instanceof Error
? providerMetadataError.message
: "Failed to load providers"
: t("addModel.addModelForm.providerLoadFailed")
: null;
const isAdmin = all_admin_roles.includes(userRole);
@ -104,7 +108,7 @@ const AddModelForm: React.FC<AddModelFormProps> = ({
return (
<>
<Title level={2}>Add Model</Title>
<Title level={2}>{t("addModel.addModelForm.title")}</Title>
<Card>
<Form
@ -126,10 +130,10 @@ const AddModelForm: React.FC<AddModelFormProps> = ({
{isTeamAdmin && !isAdmin && (
<>
<Form.Item
label="Select Team"
label={t("addModel.addModelForm.selectTeamLabel")}
name="team_id"
rules={[{ required: true, message: "Please select a team to continue" }]}
tooltip="Select the team for which you want to add this model"
rules={[{ required: true, message: t("addModel.addModelForm.selectTeamRequired") }]}
tooltip={t("addModel.addModelForm.selectTeamTooltip")}
>
<TeamDropdown
onChange={(value) => {
@ -139,8 +143,8 @@ const AddModelForm: React.FC<AddModelFormProps> = ({
</Form.Item>
{!teamAdminSelectedTeam && (
<Alert
message="Team Selection Required"
description="As a team admin, you need to select your team first before adding models."
message={t("addModel.addModelForm.teamSelectionRequiredTitle")}
description={t("addModel.addModelForm.teamSelectionRequiredDesc")}
type="info"
showIcon
className="mb-4"
@ -151,10 +155,10 @@ const AddModelForm: React.FC<AddModelFormProps> = ({
{(isAdmin || (isTeamAdmin && teamAdminSelectedTeam)) && (
<>
<Form.Item
rules={[{ required: true, message: "Required" }]}
label="Provider:"
rules={[{ required: true, message: t("addModel.addModelForm.providerRequired") }]}
label={t("addModel.addModelForm.providerLabel")}
name="custom_llm_provider"
tooltip="E.g. OpenAI, Azure OpenAI, Anthropic, Bedrock, etc."
tooltip={t("addModel.addModelForm.providerTooltip")}
labelCol={{ span: 10 }}
labelAlign="left"
>
@ -162,7 +166,11 @@ const AddModelForm: React.FC<AddModelFormProps> = ({
virtual={false}
showSearch
loading={isProviderMetadataLoading}
placeholder={isProviderMetadataLoading ? "Loading providers..." : "Select a provider"}
placeholder={
isProviderMetadataLoading
? t("addModel.addModelForm.providerLoadingPlaceholder")
: t("addModel.addModelForm.providerSelectPlaceholder")
}
optionFilterProp="data-label"
onChange={(value) => {
setSelectedProvider(value as Providers);
@ -207,22 +215,31 @@ const AddModelForm: React.FC<AddModelFormProps> = ({
<ConditionalPublicModelName />
{/* Select Mode */}
<Form.Item label="Mode" name="mode" className="mb-1">
<Form.Item label={t("addModel.addModelForm.modeLabel")} name="mode" className="mb-1">
<AntdSelect
style={{ width: "100%" }}
value={testMode}
onChange={(value) => setTestMode(value)}
options={TEST_MODES}
options={testModes}
/>
</Form.Item>
<Row>
<Col span={10}></Col>
<Col span={10}>
<Text className="mb-5 mt-1">
<strong>Optional</strong> - LiteLLM endpoint to use when health checking this model{" "}
<Link href="https://docs.litellm.ai/docs/proxy/health#health" target="_blank">
Learn more
</Link>
<Trans
i18nKey="addModel.addModelForm.modeHelp"
components={{
strong: <strong key="strong" />,
learnMoreLink: (
<Link
key="learnMoreLink"
href="https://docs.litellm.ai/docs/proxy/health#health"
target="_blank"
/>
),
}}
/>
</Text>
</Col>
</Row>
@ -230,18 +247,22 @@ const AddModelForm: React.FC<AddModelFormProps> = ({
{/* Credentials */}
<div className="mb-4">
<Typography.Text className="text-sm text-gray-500 mb-2">
Either select existing credentials OR enter new provider credentials below
{t("addModel.addModelForm.credentialsSectionDesc")}
</Typography.Text>
</div>
<Form.Item label="Existing Credentials" name="litellm_credential_name" initialValue={null}>
<Form.Item
label={t("addModel.addModelForm.existingCredentialsLabel")}
name="litellm_credential_name"
initialValue={null}
>
<AntdSelect
showSearch
placeholder="Select or search for existing credentials"
placeholder={t("addModel.addModelForm.existingCredentialsPlaceholder")}
optionFilterProp="children"
filterOption={(input, option) => (option?.label ?? "").toLowerCase().includes(input.toLowerCase())}
options={[
{ value: null, label: "None" },
{ value: null, label: t("addModel.addModelForm.credentialsNone") },
...credentials.map((credential) => ({
value: credential.credential_name,
label: credential.credential_name,
@ -279,22 +300,20 @@ const AddModelForm: React.FC<AddModelFormProps> = ({
</Form.Item>
<div className="flex items-center my-4">
<div className="flex-grow border-t border-gray-200"></div>
<span className="px-4 text-gray-500 text-sm">Additional Model Info Settings</span>
<span className="px-4 text-gray-500 text-sm">
{t("addModel.addModelForm.additionalModelInfoDivider")}
</span>
<div className="flex-grow border-t border-gray-200"></div>
</div>
{/* Team-only Model Switch - Only show for proxy admins, not team admins */}
{(isAdmin || !isTeamAdmin) && (
<Form.Item
label="Team-BYOK Model"
tooltip="Only use this model + credential combination for this team. Useful when teams want to onboard their own OpenAI keys."
label={t("addModel.addModelForm.teamByokLabel")}
tooltip={t("addModel.addModelForm.teamByokTooltip")}
className="mb-4"
>
<Tooltip
title={
!premiumUser
? "This is an enterprise-only feature. Upgrade to premium to restrict model+credential combinations to a specific team."
: ""
}
title={!premiumUser ? t("addModel.addModelForm.teamByokPremiumTooltip") : ""}
placement="top"
>
<Switch
@ -314,14 +333,14 @@ const AddModelForm: React.FC<AddModelFormProps> = ({
{/* Conditional Team Selection */}
{isTeamOnly && (isAdmin || !isTeamAdmin) && (
<Form.Item
label="Select Team"
label={t("addModel.addModelForm.selectTeamByokLabel")}
name="team_id"
className="mb-4"
tooltip="Only keys for this team will be able to call this model."
tooltip={t("addModel.addModelForm.selectTeamByokTooltip")}
rules={[
{
required: isTeamOnly && !isAdmin,
message: "Please select a team.",
message: t("addModel.addModelForm.selectTeamByokRequired"),
},
]}
>
@ -331,15 +350,15 @@ const AddModelForm: React.FC<AddModelFormProps> = ({
{isAdmin && (
<>
<Form.Item
label="Model Access Group"
label={t("addModel.addModelForm.modelAccessGroupLabel")}
name="model_access_group"
className="mb-4"
tooltip="Use model access groups to give users access to select models, and add new ones to the group over time."
tooltip={t("addModel.addModelForm.modelAccessGroupTooltip")}
>
<AntdSelect
mode="tags"
showSearch
placeholder="Select existing groups or type to create new ones"
placeholder={t("addModel.addModelForm.modelAccessGroupPlaceholder")}
optionFilterProp="children"
tokenSeparators={[","]}
options={modelAccessGroups.map((group) => ({
@ -363,15 +382,17 @@ const AddModelForm: React.FC<AddModelFormProps> = ({
</>
)}
<div className="flex justify-between items-center mb-4">
<Tooltip title="Get help on our github">
<Typography.Link href="https://github.com/BerriAI/litellm/issues">Need Help?</Typography.Link>
<Tooltip title={t("addModel.addModelForm.needHelpTooltip")}>
<Typography.Link href="https://github.com/BerriAI/litellm/issues">
{t("addModel.addModelForm.needHelp")}
</Typography.Link>
</Tooltip>
<div className="space-x-2">
<Button data-testid="test-connect-btn" onClick={handleTestConnection} loading={isTestingConnection}>
Test Connect
{t("addModel.addModelForm.testConnectButton")}
</Button>
<Button data-testid="add-model-btn" htmlType="submit">
Add Model
{t("addModel.addModelForm.addModelButton")}
</Button>
</div>
</div>
@ -381,7 +402,7 @@ const AddModelForm: React.FC<AddModelFormProps> = ({
{/* Test Connection Results Modal */}
<Modal
title="Connection Test Results"
title={t("addModel.addModelForm.connectionTestResultsTitle")}
open={isResultModalVisible}
onCancel={() => {
setIsResultModalVisible(false);
@ -395,7 +416,7 @@ const AddModelForm: React.FC<AddModelFormProps> = ({
setIsTestingConnection(false);
}}
>
Close
{t("common.close")}
</Button>,
]}
width={700}

View file

@ -1,6 +1,7 @@
import { InfoCircleOutlined } from "@ant-design/icons";
import { Select as AntdSelect, Card, Divider, Space, Tooltip, Typography } from "antd";
import React from "react";
import { useTranslation } from "react-i18next";
import { ModelGroup } from "../playground/llm_calls/fetch_models";
const { Text } = Typography;
@ -18,30 +19,41 @@ interface ComplexityRouterConfigProps {
onChange: (tiers: ComplexityTiers) => void;
}
const TIER_DESCRIPTIONS: Record<keyof ComplexityTiers, { label: string; description: string; examples: string }> = {
SIMPLE: {
label: "Simple",
description: "Basic questions, greetings, simple factual queries",
examples: '"Hello!", "What is Python?", "Thanks!"',
},
MEDIUM: {
label: "Medium",
description: "Standard queries requiring some reasoning or explanation",
examples: '"Explain how REST APIs work", "Debug this error"',
},
COMPLEX: {
label: "Complex",
description: "Technical, multi-part requests requiring deep knowledge",
examples: '"Design a microservices architecture", "Implement a rate limiter"',
},
REASONING: {
label: "Reasoning",
description: "Chain-of-thought, analysis, explicit reasoning requests",
examples: '"Think step by step...", "Analyze the pros and cons..."',
},
};
const ComplexityRouterConfig: React.FC<ComplexityRouterConfigProps> = ({ modelInfo, value, onChange }) => {
const { t } = useTranslation();
const TIER_CONFIGS: Array<{
key: keyof ComplexityTiers;
labelKey: string;
descriptionKey: string;
examplesKey: string;
}> = [
{
key: "SIMPLE",
labelKey: "addModel.complexityRouterConfig.simpleTierLabel",
descriptionKey: "addModel.complexityRouterConfig.simpleTierDescription",
examplesKey: "addModel.complexityRouterConfig.simpleTierExamples",
},
{
key: "MEDIUM",
labelKey: "addModel.complexityRouterConfig.mediumTierLabel",
descriptionKey: "addModel.complexityRouterConfig.mediumTierDescription",
examplesKey: "addModel.complexityRouterConfig.mediumTierExamples",
},
{
key: "COMPLEX",
labelKey: "addModel.complexityRouterConfig.complexTierLabel",
descriptionKey: "addModel.complexityRouterConfig.complexTierDescription",
examplesKey: "addModel.complexityRouterConfig.complexTierExamples",
},
{
key: "REASONING",
labelKey: "addModel.complexityRouterConfig.reasoningTierLabel",
descriptionKey: "addModel.complexityRouterConfig.reasoningTierDescription",
examplesKey: "addModel.complexityRouterConfig.reasoningTierExamples",
},
];
// Prepare model options for dropdowns
const modelOptions = modelInfo.map((model) => ({
value: model.model_group,
@ -59,40 +71,43 @@ const ComplexityRouterConfig: React.FC<ComplexityRouterConfigProps> = ({ modelIn
<div className="w-full max-w-none">
<Space align="center" style={{ marginBottom: 16 }}>
<Typography.Title level={4} style={{ margin: 0 }}>
Complexity Tier Configuration
{t("addModel.complexityRouterConfig.title")}
</Typography.Title>
<Tooltip title="Map each complexity tier to a model. Simple queries use cheaper/faster models, complex queries use more capable models.">
<Tooltip title={t("addModel.complexityRouterConfig.titleTooltip")}>
<InfoCircleOutlined className="text-gray-400" />
</Tooltip>
</Space>
<Text type="secondary" style={{ display: "block", marginBottom: 24 }}>
The complexity router automatically classifies requests by complexity using rule-based scoring (no API calls,
&lt;1ms latency). Configure which model handles each tier.
{t("addModel.complexityRouterConfig.subtitle")}
</Text>
<Card>
{(Object.keys(TIER_DESCRIPTIONS) as Array<keyof ComplexityTiers>).map((tier, index) => {
const tierInfo = TIER_DESCRIPTIONS[tier];
{TIER_CONFIGS.map((tierConfig, index) => {
const label = t(tierConfig.labelKey);
const description = t(tierConfig.descriptionKey);
const examples = t(tierConfig.examplesKey);
return (
<div key={tier}>
<div key={tierConfig.key}>
{index > 0 && <Divider style={{ margin: "16px 0" }} />}
<div className="mb-4">
<div className="flex items-center gap-2 mb-2">
<Text strong style={{ fontSize: 16 }}>
{tierInfo.label} Tier
{t("addModel.complexityRouterConfig.tierLabel", { label })}
</Text>
<Tooltip title={tierInfo.description}>
<Tooltip title={description}>
<InfoCircleOutlined className="text-gray-400" />
</Tooltip>
</div>
<Text type="secondary" style={{ display: "block", marginBottom: 8, fontSize: 12 }}>
Examples: {tierInfo.examples}
{t("addModel.complexityRouterConfig.examplesLabel", { examples })}
</Text>
<AntdSelect
value={value[tier]}
onChange={(model) => handleTierChange(tier, model)}
placeholder={`Select model for ${tierInfo.label.toLowerCase()} queries`}
value={value[tierConfig.key]}
onChange={(model) => handleTierChange(tierConfig.key, model)}
placeholder={t("addModel.complexityRouterConfig.selectModelPlaceholder", {
label: label.toLowerCase(),
})}
showSearch
style={{ width: "100%" }}
options={modelOptions}
@ -107,12 +122,10 @@ const ComplexityRouterConfig: React.FC<ComplexityRouterConfigProps> = ({ modelIn
<Card className="bg-gray-50">
<Text strong style={{ display: "block", marginBottom: 8 }}>
How Classification Works
{t("addModel.complexityRouterConfig.howClassificationWorksTitle")}
</Text>
<Text type="secondary" style={{ fontSize: 13 }}>
The router scores each request across 7 dimensions: token count, code presence, reasoning markers, technical
terms, simple indicators, multi-step patterns, and question complexity. The weighted score determines the
tier:
{t("addModel.complexityRouterConfig.howClassificationWorksDesc")}
</Text>
<ul style={{ marginTop: 8, marginBottom: 0, paddingLeft: 20, fontSize: 13, color: "rgba(0, 0, 0, 0.45)" }}>
<li>

View file

@ -14,6 +14,7 @@ import {
Typography,
} from "antd";
import React, { useEffect, useState } from "react";
import { useTranslation } from "react-i18next";
import { ModelGroup } from "../playground/llm_calls/fetch_models";
const { Text } = Typography;
@ -48,6 +49,7 @@ interface RouterConfigBuilderProps {
}
const RouterConfigBuilder: React.FC<RouterConfigBuilderProps> = ({ modelInfo, value, onChange }) => {
const { t } = useTranslation();
const [routes, setRoutes] = useState<Route[]>([]);
const [showJsonPreview, setShowJsonPreview] = useState<boolean>(false);
const [expandedRoutes, setExpandedRoutes] = useState<string[]>([]);
@ -106,8 +108,8 @@ const RouterConfigBuilder: React.FC<RouterConfigBuilderProps> = ({ modelInfo, va
};
// Handle updating a route
const updateRoute = (routeId: string, field: keyof Route, value: any) => {
const updatedRoutes = routes.map((route) => (route.id === routeId ? { ...route, [field]: value } : route));
const updateRoute = (routeId: string, field: keyof Route, val: any) => {
const updatedRoutes = routes.map((route) => (route.id === routeId ? { ...route, [field]: val } : route));
setRoutes(updatedRoutes);
updateConfig(updatedRoutes);
};
@ -156,21 +158,21 @@ const RouterConfigBuilder: React.FC<RouterConfigBuilderProps> = ({ modelInfo, va
<Flex justify="space-between" align="center" gap="middle" style={{ width: "100%", marginBottom: 24 }}>
<Space align="center">
<Typography.Title level={4} style={{ margin: 0 }}>
Routes Configuration
{t("addModel.routerConfigBuilder.title")}
</Typography.Title>
<Tooltip title="Configure routing logic to automatically select the best model based on user input patterns">
<Tooltip title={t("addModel.routerConfigBuilder.titleTooltip")}>
<InfoCircleOutlined className="text-gray-400" />
</Tooltip>
</Space>
<Button type="primary" icon={<PlusOutlined />} onClick={addRoute} className="bg-blue-600 hover:bg-blue-700">
Add Route
{t("addModel.routerConfigBuilder.addRouteButton")}
</Button>
</Flex>
{/* Routes */}
{routes.length === 0 ? (
<Card>
<Empty description='No routes configured. Click "Add Route" to get started.' />
<Empty description={t("addModel.routerConfigBuilder.noRoutesDesc")} />
</Card>
) : (
<Collapse
@ -181,7 +183,10 @@ const RouterConfigBuilder: React.FC<RouterConfigBuilderProps> = ({ modelInfo, va
key: route.id,
label: (
<Text style={{ fontSize: 16 }}>
Route {index + 1}: {route.model || "Unnamed"}
{t("addModel.routerConfigBuilder.routeLabel", {
index: index + 1,
model: route.model || t("addModel.routerConfigBuilder.routeUnnamed"),
})}
</Text>
),
extra: (
@ -200,11 +205,11 @@ const RouterConfigBuilder: React.FC<RouterConfigBuilderProps> = ({ modelInfo, va
<Card key={route.id}>
{/* Model Selection */}
<div className="mb-4 w-full">
<Text className="text-sm font-medium mb-2 block">Model</Text>
<Text className="text-sm font-medium mb-2 block">{t("addModel.routerConfigBuilder.modelLabel")}</Text>
<AntdSelect
value={route.model}
onChange={(value) => updateRoute(route.id, "model", value)}
placeholder="Select model"
onChange={(val) => updateRoute(route.id, "model", val)}
placeholder={t("addModel.routerConfigBuilder.modelPlaceholder")}
showSearch
style={{ width: "100%" }}
options={modelOptions}
@ -213,11 +218,13 @@ const RouterConfigBuilder: React.FC<RouterConfigBuilderProps> = ({ modelInfo, va
{/* Description */}
<div className="mb-4 w-full">
<Text className="text-sm font-medium mb-2 block">Description</Text>
<Text className="text-sm font-medium mb-2 block">
{t("addModel.routerConfigBuilder.descriptionLabel")}
</Text>
<TextArea
value={route.description}
onChange={(e) => updateRoute(route.id, "description", e.target.value)}
placeholder="Describe when this route should be used..."
placeholder={t("addModel.routerConfigBuilder.descriptionPlaceholder")}
rows={2}
style={{ width: "100%" }}
/>
@ -226,14 +233,14 @@ const RouterConfigBuilder: React.FC<RouterConfigBuilderProps> = ({ modelInfo, va
{/* Score Threshold */}
<div className="mb-4 w-full">
<div className="flex items-center gap-2 mb-2">
<Text className="text-sm font-medium">Score Threshold</Text>
<Tooltip title="Minimum similarity score to route to this model (0-1)">
<Text className="text-sm font-medium">{t("addModel.routerConfigBuilder.scoreThresholdLabel")}</Text>
<Tooltip title={t("addModel.routerConfigBuilder.scoreThresholdTooltip")}>
<InfoCircleOutlined className="text-gray-400" />
</Tooltip>
</div>
<InputNumber
value={route.score_threshold}
onChange={(value) => updateRoute(route.id, "score_threshold", value || 0)}
onChange={(val) => updateRoute(route.id, "score_threshold", val || 0)}
min={0}
max={1}
step={0.1}
@ -245,19 +252,19 @@ const RouterConfigBuilder: React.FC<RouterConfigBuilderProps> = ({ modelInfo, va
{/* Example Utterances */}
<div className="w-full">
<div className="flex items-center gap-2 mb-2">
<Text className="text-sm font-medium">Example Utterances</Text>
<Tooltip title="Training examples for this route. Type an utterance and press Enter to add it.">
<Text className="text-sm font-medium">
{t("addModel.routerConfigBuilder.exampleUtterancesLabel")}
</Text>
<Tooltip title={t("addModel.routerConfigBuilder.exampleUtterancesTooltip")}>
<InfoCircleOutlined className="text-gray-400" />
</Tooltip>
</div>
<Text className="text-xs text-gray-500 mb-2">
Type an utterance and press Enter to add it. You can also paste multiple lines.
</Text>
<Text className="text-xs text-gray-500 mb-2">{t("addModel.routerConfigBuilder.utterancesHint")}</Text>
<AntdSelect
mode="tags"
value={route.utterances}
onChange={(utterances) => updateRoute(route.id, "utterances", utterances)}
placeholder="Type an utterance and press Enter..."
placeholder={t("addModel.routerConfigBuilder.utterancesPlaceholder")}
style={{ width: "100%" }}
tokenSeparators={["\n"]}
maxTagCount="responsive"
@ -273,9 +280,11 @@ const RouterConfigBuilder: React.FC<RouterConfigBuilderProps> = ({ modelInfo, va
{/* JSON Preview */}
<Divider />
<div className="flex justify-between items-center mb-4 w-full">
<Text className="text-lg font-semibold">JSON Preview</Text>
<Text className="text-lg font-semibold">{t("addModel.routerConfigBuilder.jsonPreviewLabel")}</Text>
<Button type="link" onClick={() => setShowJsonPreview(!showJsonPreview)} className="text-blue-600 p-0">
{showJsonPreview ? "Hide" : "Show"}
{showJsonPreview
? t("addModel.routerConfigBuilder.hideButton")
: t("addModel.routerConfigBuilder.showButton")}
</Button>
</div>

View file

@ -2,6 +2,7 @@ import React, { useEffect, useState } from "react";
import { Card, Form, Button, Tooltip, Typography, Select as AntdSelect, Modal, Radio, Badge, Space } from "antd";
import type { FormInstance } from "antd";
import { Text, TextInput } from "@tremor/react";
import { useTranslation } from "react-i18next";
import { modelAvailableCall } from "../networking";
import ConnectionErrorDisplay from "./model_connection_test";
import { all_admin_roles } from "@/utils/roles";
@ -31,6 +32,7 @@ interface ComplexityTiers {
const { Title, Link } = Typography;
const AddAutoRouterTab: React.FC<AddAutoRouterTabProps> = ({ form, handleOk, accessToken, userRole }) => {
const { t } = useTranslation();
// State for connection testing
const [isResultModalVisible, setIsResultModalVisible] = useState<boolean>(false);
const [isTestingConnection, setIsTestingConnection] = useState<boolean>(false);
@ -95,7 +97,7 @@ const AddAutoRouterTab: React.FC<AddAutoRouterTabProps> = ({ form, handleOk, acc
// Check basic required fields first
if (!currentFormValues.auto_router_name) {
NotificationManager.fromBackend("Please enter an Auto Router Name");
NotificationManager.fromBackend(t("addModel.addAutoRouterTab.enterRouterNameError"));
return;
}
@ -104,7 +106,7 @@ const AddAutoRouterTab: React.FC<AddAutoRouterTabProps> = ({ form, handleOk, acc
// Complexity Router validation
const filledTiers = Object.values(complexityTiers).filter(Boolean);
if (filledTiers.length === 0) {
NotificationManager.fromBackend("Please select at least one model for a complexity tier");
NotificationManager.fromBackend(t("addModel.addAutoRouterTab.selectTierModelError"));
return;
}
@ -143,12 +145,12 @@ const AddAutoRouterTab: React.FC<AddAutoRouterTabProps> = ({ form, handleOk, acc
})
.catch((error) => {
console.error("Validation failed:", error);
NotificationManager.fromBackend("Please fill in all required fields");
NotificationManager.fromBackend(t("addModel.addAutoRouterTab.fillRequiredFieldsError"));
});
} else {
// Semantic Router validation (existing logic)
if (!currentFormValues.auto_router_default_model) {
NotificationManager.fromBackend("Please select a Default Model");
NotificationManager.fromBackend(t("addModel.addAutoRouterTab.selectDefaultModelError"));
return;
}
@ -160,7 +162,7 @@ const AddAutoRouterTab: React.FC<AddAutoRouterTabProps> = ({ form, handleOk, acc
// Custom validation for router config
if (!routerConfig || !routerConfig.routes || routerConfig.routes.length === 0) {
NotificationManager.fromBackend("Please configure at least one route for the auto router");
NotificationManager.fromBackend(t("addModel.addAutoRouterTab.configureRouteError"));
return;
}
@ -170,9 +172,7 @@ const AddAutoRouterTab: React.FC<AddAutoRouterTabProps> = ({ form, handleOk, acc
);
if (invalidRoutes.length > 0) {
NotificationManager.fromBackend(
"Please ensure all routes have a target model, description, and at least one utterance",
);
NotificationManager.fromBackend(t("addModel.addAutoRouterTab.invalidRoutesError"));
return;
}
@ -195,17 +195,17 @@ const AddAutoRouterTab: React.FC<AddAutoRouterTabProps> = ({ form, handleOk, acc
const missingFields = fieldErrors.map((field: any) => {
const fieldName = field.name[0];
const friendlyNames: { [key: string]: string } = {
auto_router_name: "Auto Router Name",
auto_router_default_model: "Default Model",
auto_router_embedding_model: "Embedding Model",
auto_router_name: t("addModel.addAutoRouterTab.autoRouterNameLabel"),
auto_router_default_model: t("addModel.addAutoRouterTab.defaultModelLabel"),
auto_router_embedding_model: t("addModel.addAutoRouterTab.embeddingModelLabel"),
};
return friendlyNames[fieldName] || fieldName;
});
NotificationManager.fromBackend(
`Please fill in the following required fields: ${missingFields.join(", ")}`,
t("addModel.addAutoRouterTab.missingFieldsError", { fields: missingFields.join(", ") }),
);
} else {
NotificationManager.fromBackend("Please fill in all required fields");
NotificationManager.fromBackend(t("addModel.addAutoRouterTab.fillRequiredFieldsError"));
}
});
}
@ -213,23 +213,20 @@ const AddAutoRouterTab: React.FC<AddAutoRouterTabProps> = ({ form, handleOk, acc
return (
<>
<Title level={2}>Add Auto Router</Title>
<Text className="text-gray-600 mb-6">
Create an auto router that automatically selects the best model based on request complexity or semantic
matching.
</Text>
<Title level={2}>{t("addModel.addAutoRouterTab.title")}</Title>
<Text className="text-gray-600 mb-6">{t("addModel.addAutoRouterTab.subtitle")}</Text>
<Card className="mb-4">
<div className="mb-4">
<Text className="text-sm font-medium mb-2 block">Router Type</Text>
<Text className="text-sm font-medium mb-2 block">{t("addModel.addAutoRouterTab.routerTypeLabel")}</Text>
<Radio.Group value={routerType} onChange={(e) => setRouterType(e.target.value)} className="w-full">
<Space direction="vertical" className="w-full">
<Radio value="complexity" className="w-full">
<div className="flex items-center gap-2">
<ThunderboltOutlined className="text-yellow-500" />
<span className="font-medium">Complexity Router</span>
<span className="font-medium">{t("addModel.addAutoRouterTab.complexityRouterLabel")}</span>
<Badge
count="Recommended"
count={t("addModel.addAutoRouterTab.complexityRouterBadge")}
style={{
backgroundColor: "#52c41a",
fontSize: "10px",
@ -238,21 +235,22 @@ const AddAutoRouterTab: React.FC<AddAutoRouterTabProps> = ({ form, handleOk, acc
/>
</div>
<div className="text-xs text-gray-500 ml-6 mt-1">
Automatically routes based on request complexity. No training data needed — just pick 4 models and go.
{t("addModel.addAutoRouterTab.complexityRouterDesc")}
<br />
<span className="text-green-600">✓ Zero API calls</span> ·{" "}
<span className="text-green-600">✓ &lt;1ms latency</span> ·{" "}
<span className="text-green-600">✓ No cost</span>
<span className="text-green-600">
✓ {t("addModel.addAutoRouterTab.complexityRouterZeroApi")}
</span> ·{" "}
<span className="text-green-600">✓ {t("addModel.addAutoRouterTab.complexityRouterLatency")}</span> ·{" "}
<span className="text-green-600">✓ {t("addModel.addAutoRouterTab.complexityRouterNoCost")}</span>
</div>
</Radio>
<Radio value="semantic" className="w-full mt-2">
<div className="flex items-center gap-2">
<BranchesOutlined className="text-blue-500" />
<span className="font-medium">Semantic Router</span>
<span className="font-medium">{t("addModel.addAutoRouterTab.semanticRouterLabel")}</span>
</div>
<div className="text-xs text-gray-500 ml-6 mt-1">
Routes based on semantic similarity to example utterances. Requires embedding model and training
examples.
{t("addModel.addAutoRouterTab.semanticRouterDesc")}
</div>
</Radio>
</Space>
@ -270,14 +268,14 @@ const AddAutoRouterTab: React.FC<AddAutoRouterTabProps> = ({ form, handleOk, acc
>
{/* Auto Router Name */}
<Form.Item
rules={[{ required: true, message: "Auto router name is required" }]}
label="Auto Router Name"
rules={[{ required: true, message: t("addModel.addAutoRouterTab.autoRouterNameRequired") }]}
label={t("addModel.addAutoRouterTab.autoRouterNameLabel")}
name="auto_router_name"
tooltip="Unique name for this auto router configuration"
tooltip={t("addModel.addAutoRouterTab.autoRouterNameTooltip")}
labelCol={{ span: 10 }}
labelAlign="left"
>
<TextInput placeholder="e.g., smart_router, auto_router_1" />
<TextInput placeholder={t("addModel.addAutoRouterTab.autoRouterNamePlaceholder")} />
</Form.Item>
{/* Conditional rendering based on router type */}
@ -309,15 +307,17 @@ const AddAutoRouterTab: React.FC<AddAutoRouterTabProps> = ({ form, handleOk, acc
{/* Auto Router Default Model */}
<Form.Item
rules={[{ required: routerType === "semantic", message: "Default model is required" }]}
label="Default Model"
rules={[
{ required: routerType === "semantic", message: t("addModel.addAutoRouterTab.defaultModelRequired") },
]}
label={t("addModel.addAutoRouterTab.defaultModelLabel")}
name="auto_router_default_model"
tooltip="Fallback model to use when auto routing logic cannot determine the best model"
tooltip={t("addModel.addAutoRouterTab.defaultModelTooltip")}
labelCol={{ span: 10 }}
labelAlign="left"
>
<AntdSelect
placeholder="Select a default model"
placeholder={t("addModel.addAutoRouterTab.defaultModelPlaceholder")}
onChange={(value) => {
setShowCustomDefaultModel(value === "custom");
}}
@ -326,7 +326,7 @@ const AddAutoRouterTab: React.FC<AddAutoRouterTabProps> = ({ form, handleOk, acc
value: model_group,
label: model_group,
})),
{ value: "custom", label: "Enter custom model name" },
{ value: "custom", label: t("addModel.addAutoRouterTab.enterCustomModelName") },
]}
style={{ width: "100%" }}
showSearch={true}
@ -335,15 +335,15 @@ const AddAutoRouterTab: React.FC<AddAutoRouterTabProps> = ({ form, handleOk, acc
{/* Auto Router Embedding Model */}
<Form.Item
label="Embedding Model"
label={t("addModel.addAutoRouterTab.embeddingModelLabel")}
name="auto_router_embedding_model"
tooltip="Optional: Embedding model to use for semantic routing decisions"
tooltip={t("addModel.addAutoRouterTab.embeddingModelTooltip")}
labelCol={{ span: 10 }}
labelAlign="left"
>
<AntdSelect
value={form.getFieldValue("auto_router_embedding_model")}
placeholder="Select an embedding model (optional)"
placeholder={t("addModel.addAutoRouterTab.embeddingModelPlaceholder")}
onChange={(value) => {
setShowCustomEmbeddingModel(value === "custom");
form.setFieldValue("auto_router_embedding_model", value);
@ -353,7 +353,7 @@ const AddAutoRouterTab: React.FC<AddAutoRouterTabProps> = ({ form, handleOk, acc
value: model_group,
label: model_group,
})),
{ value: "custom", label: "Enter custom model name" },
{ value: "custom", label: t("addModel.addAutoRouterTab.enterCustomModelName") },
]}
style={{ width: "100%" }}
showSearch={true}
@ -365,22 +365,24 @@ const AddAutoRouterTab: React.FC<AddAutoRouterTabProps> = ({ form, handleOk, acc
<div className="flex items-center my-4">
<div className="flex-grow border-t border-gray-200"></div>
<span className="px-4 text-gray-500 text-sm">Additional Settings</span>
<span className="px-4 text-gray-500 text-sm">
{t("addModel.addAutoRouterTab.additionalSettingsDivider")}
</span>
<div className="flex-grow border-t border-gray-200"></div>
</div>
{/* Model Access Groups - Admin only */}
{isAdmin && (
<Form.Item
label="Model Access Group"
label={t("addModel.addAutoRouterTab.modelAccessGroupLabel")}
name="model_access_group"
className="mb-4"
tooltip="Use model access groups to control who can access this auto router"
tooltip={t("addModel.addAutoRouterTab.modelAccessGroupTooltip")}
>
<AntdSelect
mode="tags"
showSearch
placeholder="Select existing groups or type to create new ones"
placeholder={t("addModel.addAutoRouterTab.modelAccessGroupPlaceholder")}
optionFilterProp="children"
tokenSeparators={[","]}
options={modelAccessGroups.map((group) => ({
@ -394,12 +396,14 @@ const AddAutoRouterTab: React.FC<AddAutoRouterTabProps> = ({ form, handleOk, acc
)}
<div className="flex justify-between items-center mb-4">
<Tooltip title="Get help on our github">
<Typography.Link href="https://github.com/BerriAI/litellm/issues">Need Help?</Typography.Link>
<Tooltip title={t("addModel.addAutoRouterTab.needHelpTooltip")}>
<Typography.Link href="https://github.com/BerriAI/litellm/issues">
{t("addModel.addAutoRouterTab.needHelp")}
</Typography.Link>
</Tooltip>
<div className="space-x-2">
<Button onClick={handleTestConnection} loading={isTestingConnection}>
Test Connection
{t("addModel.addAutoRouterTab.testConnectionButton")}
</Button>
<Button
type="primary"
@ -408,7 +412,7 @@ const AddAutoRouterTab: React.FC<AddAutoRouterTabProps> = ({ form, handleOk, acc
handleAutoRouterSubmit();
}}
>
Add Auto Router
{t("addModel.addAutoRouterTab.addAutoRouterButton")}
</Button>
</div>
</div>
@ -417,7 +421,7 @@ const AddAutoRouterTab: React.FC<AddAutoRouterTabProps> = ({ form, handleOk, acc
{/* Test Connection Results Modal */}
<Modal
title="Connection Test Results"
title={t("addModel.addAutoRouterTab.connectionTestResultsTitle")}
open={isResultModalVisible}
onCancel={() => {
setIsResultModalVisible(false);
@ -431,7 +435,7 @@ const AddAutoRouterTab: React.FC<AddAutoRouterTabProps> = ({ form, handleOk, acc
setIsTestingConnection(false);
}}
>
Close
{t("common.close")}
</Button>,
]}
width={700}

View file

@ -1,24 +1,24 @@
// Define the available test modes
export const TEST_MODES = [
{ value: "chat", label: "Chat - /chat/completions" },
{ value: "completion", label: "Completion - /completions" },
{ value: "embedding", label: "Embedding - /embeddings" },
{ value: "audio_speech", label: "Audio Speech - /audio/speech" },
{ value: "audio_transcription", label: "Audio Transcription - /audio/transcriptions" },
{ value: "image_generation", label: "Image Generation - /images/generations" },
{ value: "video_generation", label: "Video Generation - /videos" },
{ value: "rerank", label: "Rerank - /rerank" },
{ value: "realtime", label: "Realtime - /realtime" },
{ value: "batch", label: "Batch - /batch" },
{ value: "ocr", label: "OCR - /ocr" },
import type { TFunction } from "i18next";
export const getTestModes = (t: TFunction) => [
{ value: "chat", label: t("addModel.addModelModes.chatLabel") },
{ value: "completion", label: t("addModel.addModelModes.completionLabel") },
{ value: "embedding", label: t("addModel.addModelModes.embeddingLabel") },
{ value: "audio_speech", label: t("addModel.addModelModes.audioSpeechLabel") },
{ value: "audio_transcription", label: t("addModel.addModelModes.audioTranscriptionLabel") },
{ value: "image_generation", label: t("addModel.addModelModes.imageGenerationLabel") },
{ value: "video_generation", label: t("addModel.addModelModes.videoGenerationLabel") },
{ value: "rerank", label: t("addModel.addModelModes.rerankLabel") },
{ value: "realtime", label: t("addModel.addModelModes.realtimeLabel") },
{ value: "batch", label: t("addModel.addModelModes.batchLabel") },
{ value: "ocr", label: t("addModel.addModelModes.ocrLabel") },
];
// Define the available auto router routing strategies
export const AUTO_ROUTER_MODES = [
{ value: "simple-shuffle", label: "Simple Shuffle - Random selection from available models" },
{ value: "least-busy", label: "Least Busy - Route to model with lowest current load" },
{ value: "latency-based", label: "Latency Based - Route to model with best response time" },
{ value: "cost-based", label: "Cost Based - Route to most cost-effective model" },
{ value: "usage-based", label: "Usage Based - Route based on historical usage patterns" },
{ value: "custom", label: "Custom - Use custom routing logic defined in config" },
export const getAutoRouterModes = (t: TFunction) => [
{ value: "simple-shuffle", label: t("addModel.addModelModes.simpleShuffleLabel") },
{ value: "least-busy", label: t("addModel.addModelModes.leastBusyLabel") },
{ value: "latency-based", label: t("addModel.addModelModes.latencyBasedLabel") },
{ value: "cost-based", label: t("addModel.addModelModes.costBasedLabel") },
{ value: "usage-based", label: t("addModel.addModelModes.usageBasedLabel") },
{ value: "custom", label: t("addModel.addModelModes.customLabel") },
];

View file

@ -3,6 +3,7 @@ import type { FormInstance } from "antd";
import { Form } from "antd";
import type { UploadProps } from "antd/es/upload";
import React from "react";
import { useTranslation } from "react-i18next";
import type { Team } from "../key_team_helpers/key_list";
import { type CredentialItem } from "../networking";
import { Providers } from "../provider_info_helpers";
@ -43,6 +44,7 @@ const AddModelTab: React.FC<AddModelTabProps> = ({
accessToken,
userRole,
}) => {
const { t } = useTranslation();
// Create separate form instance for auto router
const [autoRouterForm] = Form.useForm();
@ -61,8 +63,8 @@ const AddModelTab: React.FC<AddModelTabProps> = ({
<>
<TabGroup className="w-full">
<TabList className="mb-4">
<Tab>Add Model</Tab>
<Tab>Add Auto Router</Tab>
<Tab>{t("addModel.addModelTab.addModelTab")}</Tab>
<Tab>{t("addModel.addModelTab.addAutoRouterTab")}</Tab>
</TabList>
<TabPanels>
<TabPanel>

View file

@ -5070,5 +5070,149 @@
"send": "Send",
"refineHint": "Give instructions to add, remove, or change competitors. Press Enter to send."
}
},
"addModel": {
"addModelForm": {
"title": "Add Model",
"selectTeamLabel": "Select Team",
"selectTeamRequired": "Please select a team to continue",
"selectTeamTooltip": "Select the team for which you want to add this model",
"teamSelectionRequiredTitle": "Team Selection Required",
"teamSelectionRequiredDesc": "As a team admin, you need to select your team first before adding models.",
"providerLabel": "Provider:",
"providerRequired": "Required",
"providerTooltip": "E.g. OpenAI, Azure OpenAI, Anthropic, Bedrock, etc.",
"providerLoadingPlaceholder": "Loading providers...",
"providerLoadFailed": "Failed to load providers",
"providerSelectPlaceholder": "Select a provider",
"modeLabel": "Mode",
"modeHelp": "<strong>Optional</strong> - LiteLLM endpoint to use when health checking this model <learnMoreLink>Learn more</learnMoreLink>",
"credentialsSectionDesc": "Either select existing credentials OR enter new provider credentials below",
"existingCredentialsLabel": "Existing Credentials",
"existingCredentialsPlaceholder": "Select or search for existing credentials",
"credentialsNone": "None",
"additionalModelInfoDivider": "Additional Model Info Settings",
"teamByokLabel": "Team-BYOK Model",
"teamByokTooltip": "Only use this model + credential combination for this team. Useful when teams want to onboard their own OpenAI keys.",
"teamByokPremiumTooltip": "This is an enterprise-only feature. Upgrade to premium to restrict model+credential combinations to a specific team.",
"selectTeamByokLabel": "Select Team",
"selectTeamByokTooltip": "Only keys for this team will be able to call this model.",
"selectTeamByokRequired": "Please select a team.",
"modelAccessGroupLabel": "Model Access Group",
"modelAccessGroupTooltip": "Use model access groups to give users access to select models, and add new ones to the group over time.",
"modelAccessGroupPlaceholder": "Select existing groups or type to create new ones",
"needHelpTooltip": "Get help on our github",
"needHelp": "Need Help?",
"testConnectButton": "Test Connect",
"addModelButton": "Add Model",
"connectionTestResultsTitle": "Connection Test Results"
},
"addModelModes": {
"chatLabel": "Chat - /chat/completions",
"completionLabel": "Completion - /completions",
"embeddingLabel": "Embedding - /embeddings",
"audioSpeechLabel": "Audio Speech - /audio/speech",
"audioTranscriptionLabel": "Audio Transcription - /audio/transcriptions",
"imageGenerationLabel": "Image Generation - /images/generations",
"videoGenerationLabel": "Video Generation - /videos",
"rerankLabel": "Rerank - /rerank",
"realtimeLabel": "Realtime - /realtime",
"batchLabel": "Batch - /batch",
"ocrLabel": "OCR - /ocr",
"simpleShuffleLabel": "Simple Shuffle - Random selection from available models",
"leastBusyLabel": "Least Busy - Route to model with lowest current load",
"latencyBasedLabel": "Latency Based - Route to model with best response time",
"costBasedLabel": "Cost Based - Route to most cost-effective model",
"usageBasedLabel": "Usage Based - Route based on historical usage patterns",
"customLabel": "Custom - Use custom routing logic defined in config"
},
"addModelTab": {
"addModelTab": "Add Model",
"addAutoRouterTab": "Add Auto Router"
},
"addAutoRouterTab": {
"title": "Add Auto Router",
"subtitle": "Create an auto router that automatically selects the best model based on request complexity or semantic matching.",
"routerTypeLabel": "Router Type",
"complexityRouterLabel": "Complexity Router",
"complexityRouterBadge": "Recommended",
"complexityRouterDesc": "Automatically routes based on request complexity. No training data needed — just pick 4 models and go.",
"complexityRouterZeroApi": "Zero API calls",
"complexityRouterLatency": "<1ms latency",
"complexityRouterNoCost": "No cost",
"semanticRouterLabel": "Semantic Router",
"semanticRouterDesc": "Routes based on semantic similarity to example utterances. Requires embedding model and training examples.",
"autoRouterNameLabel": "Auto Router Name",
"autoRouterNameRequired": "Auto router name is required",
"autoRouterNameTooltip": "Unique name for this auto router configuration",
"autoRouterNamePlaceholder": "e.g., smart_router, auto_router_1",
"defaultModelLabel": "Default Model",
"defaultModelRequired": "Default model is required",
"defaultModelTooltip": "Fallback model to use when auto routing logic cannot determine the best model",
"defaultModelPlaceholder": "Select a default model",
"enterCustomModelName": "Enter custom model name",
"embeddingModelLabel": "Embedding Model",
"embeddingModelTooltip": "Optional: Embedding model to use for semantic routing decisions",
"embeddingModelPlaceholder": "Select an embedding model (optional)",
"additionalSettingsDivider": "Additional Settings",
"modelAccessGroupLabel": "Model Access Group",
"modelAccessGroupTooltip": "Use model access groups to control who can access this auto router",
"modelAccessGroupPlaceholder": "Select existing groups or type to create new ones",
"needHelpTooltip": "Get help on our github",
"needHelp": "Need Help?",
"testConnectionButton": "Test Connection",
"addAutoRouterButton": "Add Auto Router",
"connectionTestResultsTitle": "Connection Test Results",
"enterRouterNameError": "Please enter an Auto Router Name",
"selectTierModelError": "Please select at least one model for a complexity tier",
"fillRequiredFieldsError": "Please fill in all required fields",
"selectDefaultModelError": "Please select a Default Model",
"configureRouteError": "Please configure at least one route for the auto router",
"invalidRoutesError": "Please ensure all routes have a target model, description, and at least one utterance",
"missingFieldsError": "Please fill in the following required fields: {{fields}}"
},
"complexityRouterConfig": {
"title": "Complexity Tier Configuration",
"titleTooltip": "Map each complexity tier to a model. Simple queries use cheaper/faster models, complex queries use more capable models.",
"subtitle": "The complexity router automatically classifies requests by complexity using rule-based scoring (no API calls, <1ms latency). Configure which model handles each tier.",
"simpleTierLabel": "Simple",
"simpleTierDescription": "Basic questions, greetings, simple factual queries",
"simpleTierExamples": "\"Hello!\", \"What is Python?\", \"Thanks!\"",
"mediumTierLabel": "Medium",
"mediumTierDescription": "Standard queries requiring some reasoning or explanation",
"mediumTierExamples": "\"Explain how REST APIs work\", \"Debug this error\"",
"complexTierLabel": "Complex",
"complexTierDescription": "Technical, multi-part requests requiring deep knowledge",
"complexTierExamples": "\"Design a microservices architecture\", \"Implement a rate limiter\"",
"reasoningTierLabel": "Reasoning",
"reasoningTierDescription": "Chain-of-thought, analysis, explicit reasoning requests",
"reasoningTierExamples": "\"Think step by step...\", \"Analyze the pros and cons...\"",
"tierLabel": "{{label}} Tier",
"examplesLabel": "Examples: {{examples}}",
"selectModelPlaceholder": "Select model for {{label}} queries",
"howClassificationWorksTitle": "How Classification Works",
"howClassificationWorksDesc": "The router scores each request across 7 dimensions: token count, code presence, reasoning markers, technical terms, simple indicators, multi-step patterns, and question complexity. The weighted score determines the tier:"
},
"routerConfigBuilder": {
"title": "Routes Configuration",
"titleTooltip": "Configure routing logic to automatically select the best model based on user input patterns",
"addRouteButton": "Add Route",
"noRoutesDesc": "No routes configured. Click \"Add Route\" to get started.",
"routeLabel": "Route {{index}}: {{model}}",
"routeUnnamed": "Unnamed",
"modelLabel": "Model",
"modelPlaceholder": "Select model",
"descriptionLabel": "Description",
"descriptionPlaceholder": "Describe when this route should be used...",
"scoreThresholdLabel": "Score Threshold",
"scoreThresholdTooltip": "Minimum similarity score to route to this model (0-1)",
"exampleUtterancesLabel": "Example Utterances",
"exampleUtterancesTooltip": "Training examples for this route. Type an utterance and press Enter to add it.",
"utterancesHint": "Type an utterance and press Enter to add it. You can also paste multiple lines.",
"utterancesPlaceholder": "Type an utterance and press Enter...",
"jsonPreviewLabel": "JSON Preview",
"showButton": "Show",
"hideButton": "Hide"
}
}
}

View file

@ -5070,5 +5070,149 @@
"send": "发送",
"refineHint": "给出添加、移除或修改竞品的指令,按 Enter 发送。"
}
},
"addModel": {
"addModelForm": {
"title": "添加模型",
"selectTeamLabel": "选择团队",
"selectTeamRequired": "请选择团队以继续",
"selectTeamTooltip": "选择您要为其添加此模型的团队",
"teamSelectionRequiredTitle": "需要选择团队",
"teamSelectionRequiredDesc": "作为团队管理员,您需要先选择团队,然后才能添加模型。",
"providerLabel": "提供商:",
"providerRequired": "必填",
"providerTooltip": "例如:OpenAI、Azure OpenAI、Anthropic、Bedrock 等",
"providerLoadingPlaceholder": "正在加载提供商...",
"providerLoadFailed": "加载提供商失败",
"providerSelectPlaceholder": "选择提供商",
"modeLabel": "模式",
"modeHelp": "<strong>可选</strong> - 对此模型进行健康检查时使用的 LiteLLM 端点 <learnMoreLink>了解更多</learnMoreLink>",
"credentialsSectionDesc": "选择已有凭据或在下方输入新的提供商凭据",
"existingCredentialsLabel": "已有凭据",
"existingCredentialsPlaceholder": "选择或搜索已有凭据",
"credentialsNone": "无",
"additionalModelInfoDivider": "附加模型信息设置",
"teamByokLabel": "团队自带密钥模型",
"teamByokTooltip": "仅将此模型与凭据组合用于该团队。适用于团队希望使用自己的 OpenAI 密钥的情况。",
"teamByokPremiumTooltip": "这是企业专属功能。升级到高级版以将模型与凭据组合限制为特定团队。",
"selectTeamByokLabel": "选择团队",
"selectTeamByokTooltip": "只有此团队的密钥才能调用此模型。",
"selectTeamByokRequired": "请选择一个团队。",
"modelAccessGroupLabel": "模型访问组",
"modelAccessGroupTooltip": "使用模型访问组为用户授予对特定模型的访问权限,并可随时向组中添加新模型。",
"modelAccessGroupPlaceholder": "选择已有组或输入新组名",
"needHelpTooltip": "在 GitHub 上获取帮助",
"needHelp": "需要帮助?",
"testConnectButton": "测试连接",
"addModelButton": "添加模型",
"connectionTestResultsTitle": "连接测试结果"
},
"addModelModes": {
"chatLabel": "Chat - /chat/completions",
"completionLabel": "Completion - /completions",
"embeddingLabel": "Embedding - /embeddings",
"audioSpeechLabel": "Audio Speech - /audio/speech",
"audioTranscriptionLabel": "Audio Transcription - /audio/transcriptions",
"imageGenerationLabel": "Image Generation - /images/generations",
"videoGenerationLabel": "Video Generation - /videos",
"rerankLabel": "Rerank - /rerank",
"realtimeLabel": "Realtime - /realtime",
"batchLabel": "Batch - /batch",
"ocrLabel": "OCR - /ocr",
"simpleShuffleLabel": "Simple Shuffle - 从可用模型中随机选择",
"leastBusyLabel": "Least Busy - 路由到当前负载最低的模型",
"latencyBasedLabel": "Latency Based - 路由到响应时间最短的模型",
"costBasedLabel": "Cost Based - 路由到最具成本效益的模型",
"usageBasedLabel": "Usage Based - 基于历史用量模式进行路由",
"customLabel": "Custom - 使用配置中定义的自定义路由逻辑"
},
"addModelTab": {
"addModelTab": "添加模型",
"addAutoRouterTab": "添加自动路由器"
},
"addAutoRouterTab": {
"title": "添加自动路由器",
"subtitle": "创建一个自动路由器,根据请求复杂度或语义匹配自动选择最佳模型。",
"routerTypeLabel": "路由器类型",
"complexityRouterLabel": "复杂度路由器",
"complexityRouterBadge": "推荐",
"complexityRouterDesc": "根据请求复杂度自动路由。无需训练数据——只需选择 4 个模型即可开始。",
"complexityRouterZeroApi": "零 API 调用",
"complexityRouterLatency": "<1ms 延迟",
"complexityRouterNoCost": "零成本",
"semanticRouterLabel": "语义路由器",
"semanticRouterDesc": "根据与示例话语的语义相似度进行路由。需要嵌入模型和训练示例。",
"autoRouterNameLabel": "自动路由器名称",
"autoRouterNameRequired": "自动路由器名称为必填项",
"autoRouterNameTooltip": "此自动路由器配置的唯一名称",
"autoRouterNamePlaceholder": "例如:smart_router、auto_router_1",
"defaultModelLabel": "默认模型",
"defaultModelRequired": "默认模型为必填项",
"defaultModelTooltip": "当自动路由逻辑无法确定最佳模型时使用的回退模型",
"defaultModelPlaceholder": "选择默认模型",
"enterCustomModelName": "输入自定义模型名称",
"embeddingModelLabel": "嵌入模型",
"embeddingModelTooltip": "可选:用于语义路由决策的嵌入模型",
"embeddingModelPlaceholder": "选择嵌入模型(可选)",
"additionalSettingsDivider": "附加设置",
"modelAccessGroupLabel": "模型访问组",
"modelAccessGroupTooltip": "使用模型访问组控制谁可以访问此自动路由器",
"modelAccessGroupPlaceholder": "选择已有组或输入新组名",
"needHelpTooltip": "在 GitHub 上获取帮助",
"needHelp": "需要帮助?",
"testConnectionButton": "测试连接",
"addAutoRouterButton": "添加自动路由器",
"connectionTestResultsTitle": "连接测试结果",
"enterRouterNameError": "请输入自动路由器名称",
"selectTierModelError": "请为复杂度层级至少选择一个模型",
"fillRequiredFieldsError": "请填写所有必填字段",
"selectDefaultModelError": "请选择默认模型",
"configureRouteError": "请为自动路由器至少配置一条路由",
"invalidRoutesError": "请确保所有路由都有目标模型、描述和至少一个话语示例",
"missingFieldsError": "请填写以下必填字段:{{fields}}"
},
"complexityRouterConfig": {
"title": "复杂度层级配置",
"titleTooltip": "将每个复杂度层级映射到一个模型。简单查询使用更便宜/更快的模型,复杂查询使用更强大的模型。",
"subtitle": "复杂度路由器使用基于规则的评分(无 API 调用,<1ms 延迟)自动按复杂度对请求进行分类。配置每个层级由哪个模型处理。",
"simpleTierLabel": "简单",
"simpleTierDescription": "基本问题、问候语、简单事实查询",
"simpleTierExamples": "\"你好!\"、\"Python 是什么?\"、\"谢谢!\"",
"mediumTierLabel": "中等",
"mediumTierDescription": "需要一定推理或解释的标准查询",
"mediumTierExamples": "\"解释 REST API 的工作原理\"、\"调试此错误\"",
"complexTierLabel": "复杂",
"complexTierDescription": "需要深度知识的技术性、多部分请求",
"complexTierExamples": "\"设计微服务架构\"、\"实现速率限制器\"",
"reasoningTierLabel": "推理",
"reasoningTierDescription": "思维链、分析、明确的推理请求",
"reasoningTierExamples": "\"请逐步思考...\"、\"分析优缺点...\"",
"tierLabel": "{{label}}层级",
"examplesLabel": "示例:{{examples}}",
"selectModelPlaceholder": "为{{label}}查询选择模型",
"howClassificationWorksTitle": "分类原理",
"howClassificationWorksDesc": "路由器从 7 个维度对每个请求进行评分:Token 数量、代码存在性、推理标记、技术术语、简单指标、多步骤模式和问题复杂度。加权分数决定层级:"
},
"routerConfigBuilder": {
"title": "路由配置",
"titleTooltip": "配置路由逻辑,根据用户输入模式自动选择最佳模型",
"addRouteButton": "添加路由",
"noRoutesDesc": "未配置路由。点击「添加路由」开始。",
"routeLabel": "路由 {{index}}:{{model}}",
"routeUnnamed": "未命名",
"modelLabel": "模型",
"modelPlaceholder": "选择模型",
"descriptionLabel": "描述",
"descriptionPlaceholder": "描述何时应使用此路由...",
"scoreThresholdLabel": "分数阈值",
"scoreThresholdTooltip": "路由到此模型的最低相似度分数(0-1)",
"exampleUtterancesLabel": "示例话语",
"exampleUtterancesTooltip": "此路由的训练示例。输入话语后按 Enter 添加。",
"utterancesHint": "输入话语后按 Enter 添加,也可粘贴多行内容。",
"utterancesPlaceholder": "输入话语后按 Enter...",
"jsonPreviewLabel": "JSON 预览",
"showButton": "显示",
"hideButton": "隐藏"
}
}
}