mirror of
https://github.com/BerriAI/litellm.git
synced 2026-10-04 02:31:27 +00:00
feat(ui): i18n for routing groups
This commit is contained in:
parent
17e3d704df
commit
04778d2dad
5 changed files with 201 additions and 56 deletions
|
|
@ -2,6 +2,7 @@
|
|||
|
||||
import React, { useMemo } from "react";
|
||||
import { Form, Input, Modal, Select, Space, Typography } from "antd";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import type { RoutingGroup, RoutingStrategy } from "./types";
|
||||
|
||||
const { Text, Paragraph } = Typography;
|
||||
|
|
@ -43,6 +44,7 @@ const RoutingGroupModal: React.FC<RoutingGroupModalProps> = ({
|
|||
onSubmit,
|
||||
saving,
|
||||
}) => {
|
||||
const { t } = useTranslation();
|
||||
const [form] = Form.useForm<FormValues>();
|
||||
const selectedStrategy = Form.useWatch("routing_strategy", form);
|
||||
|
||||
|
|
@ -71,7 +73,7 @@ const RoutingGroupModal: React.FC<RoutingGroupModalProps> = ({
|
|||
form.setFields([
|
||||
{
|
||||
name: "routing_strategy_args",
|
||||
errors: ["Must be valid JSON"],
|
||||
errors: [t("routingGroups.routingGroupModal.strategyArgsMustBeJson")],
|
||||
},
|
||||
]);
|
||||
return;
|
||||
|
|
@ -88,12 +90,20 @@ const RoutingGroupModal: React.FC<RoutingGroupModalProps> = ({
|
|||
|
||||
return (
|
||||
<Modal
|
||||
title={mode === "create" ? "Create Routing Group" : `Edit ${initialValue?.group_name ?? ""}`}
|
||||
title={
|
||||
mode === "create"
|
||||
? t("routingGroups.routingGroupModal.titleCreate")
|
||||
: t("routingGroups.routingGroupModal.titleEdit", { groupName: initialValue?.group_name ?? "" })
|
||||
}
|
||||
open={open}
|
||||
onCancel={onClose}
|
||||
onOk={handleSubmit}
|
||||
okText={mode === "create" ? "Create Group" : "Save Changes"}
|
||||
cancelText="Cancel"
|
||||
okText={
|
||||
mode === "create"
|
||||
? t("routingGroups.routingGroupModal.okTextCreate")
|
||||
: t("routingGroups.routingGroupModal.okTextEdit")
|
||||
}
|
||||
cancelText={t("common.cancel")}
|
||||
confirmLoading={saving}
|
||||
destroyOnClose
|
||||
width={560}
|
||||
|
|
@ -106,51 +116,57 @@ const RoutingGroupModal: React.FC<RoutingGroupModalProps> = ({
|
|||
initialValues={initialValues}
|
||||
>
|
||||
<Form.Item
|
||||
label="Group Name"
|
||||
label={t("routingGroups.routingGroupModal.groupNameLabel")}
|
||||
name="group_name"
|
||||
rules={[
|
||||
{ required: true, message: "Group name is required" },
|
||||
{ max: GROUP_NAME_MAX_LENGTH, message: `Must be ${GROUP_NAME_MAX_LENGTH} characters or fewer` },
|
||||
{ required: true, message: t("routingGroups.routingGroupModal.groupNameRequired") },
|
||||
{
|
||||
max: GROUP_NAME_MAX_LENGTH,
|
||||
message: t("routingGroups.routingGroupModal.groupNameMaxLength", { max: GROUP_NAME_MAX_LENGTH }),
|
||||
},
|
||||
{
|
||||
pattern: GROUP_NAME_PATTERN,
|
||||
message: "Only letters, numbers, dot, underscore, and dash are allowed",
|
||||
message: t("routingGroups.routingGroupModal.groupNamePattern"),
|
||||
},
|
||||
{
|
||||
validator: (_, value: string) => {
|
||||
if (!value) return Promise.resolve();
|
||||
if (reservedNames.has(value.trim().toLowerCase())) {
|
||||
return Promise.reject(new Error("A group with this name already exists"));
|
||||
return Promise.reject(new Error(t("routingGroups.routingGroupModal.groupNameExists")));
|
||||
}
|
||||
return Promise.resolve();
|
||||
},
|
||||
},
|
||||
]}
|
||||
extra="Use this name as the model in API calls — LiteLLM routes the request to one of the group's models."
|
||||
extra={t("routingGroups.routingGroupModal.groupNameExtra")}
|
||||
>
|
||||
<Input placeholder="fast-chat" disabled={mode === "edit"} />
|
||||
<Input placeholder={t("routingGroups.routingGroupModal.groupNamePlaceholder")} disabled={mode === "edit"} />
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item
|
||||
label="Models"
|
||||
label={t("routingGroups.routingGroupModal.modelsLabel")}
|
||||
name="models"
|
||||
rules={[{ required: true, message: "Select at least one model" }]}
|
||||
extra="Models from your model list that this group routes between."
|
||||
rules={[{ required: true, message: t("routingGroups.routingGroupModal.modelsRequired") }]}
|
||||
extra={t("routingGroups.routingGroupModal.modelsExtra")}
|
||||
>
|
||||
<Select
|
||||
mode="multiple"
|
||||
allowClear
|
||||
placeholder="Select models"
|
||||
placeholder={t("routingGroups.routingGroupModal.modelsPlaceholder")}
|
||||
options={modelOptions.map((m) => ({ label: m, value: m }))}
|
||||
optionFilterProp="label"
|
||||
/>
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item
|
||||
label="Routing Strategy"
|
||||
label={t("routingGroups.routingGroupModal.routingStrategyLabel")}
|
||||
name="routing_strategy"
|
||||
rules={[{ required: true, message: "Strategy is required" }]}
|
||||
rules={[{ required: true, message: t("routingGroups.routingGroupModal.routingStrategyRequired") }]}
|
||||
>
|
||||
<Select options={availableStrategies.map((s) => ({ label: s, value: s }))} placeholder="Select strategy" />
|
||||
<Select
|
||||
options={availableStrategies.map((s) => ({ label: s, value: s }))}
|
||||
placeholder={t("routingGroups.routingGroupModal.strategyPlaceholder")}
|
||||
/>
|
||||
</Form.Item>
|
||||
|
||||
{selectedStrategy && strategyDescriptions[selectedStrategy] && (
|
||||
|
|
@ -159,21 +175,25 @@ const RoutingGroupModal: React.FC<RoutingGroupModalProps> = ({
|
|||
|
||||
{STRATEGIES_WITH_ARGS.has(String(selectedStrategy)) && (
|
||||
<Form.Item
|
||||
label="Strategy Arguments (JSON)"
|
||||
label={t("routingGroups.routingGroupModal.strategyArgsLabel")}
|
||||
name="routing_strategy_args"
|
||||
extra={
|
||||
selectedStrategy === "latency-based-routing"
|
||||
? 'Example: { "ttl": 3600, "lowest_latency_buffer": 0 }'
|
||||
: 'Example: { "ttl": 60 }'
|
||||
? t("routingGroups.routingGroupModal.strategyArgsExampleLatency")
|
||||
: t("routingGroups.routingGroupModal.strategyArgsExampleUsage")
|
||||
}
|
||||
>
|
||||
<Input.TextArea rows={4} placeholder='{ "ttl": 3600 }' className="font-mono text-xs" />
|
||||
<Input.TextArea
|
||||
rows={4}
|
||||
placeholder={t("routingGroups.routingGroupModal.strategyArgsPlaceholder")}
|
||||
className="font-mono text-xs"
|
||||
/>
|
||||
</Form.Item>
|
||||
)}
|
||||
|
||||
<Space direction="vertical" className="w-full mt-2">
|
||||
<Text type="secondary" className="text-xs">
|
||||
Models not claimed by an explicit group fall through to the proxy's top-level routing strategy.
|
||||
{t("routingGroups.routingGroupModal.fallbackNote")}
|
||||
</Text>
|
||||
</Space>
|
||||
</Form>
|
||||
|
|
|
|||
|
|
@ -4,6 +4,8 @@ import React, { useState } from "react";
|
|||
import { Flex, Table, Tabs, Tag, Tooltip, Typography, Button } from "antd";
|
||||
import type { ColumnsType } from "antd/es/table";
|
||||
import { BranchesOutlined, DeleteOutlined, EditOutlined, CodeOutlined } from "@ant-design/icons";
|
||||
import { Trans, useTranslation } from "react-i18next";
|
||||
import type { TFunction } from "i18next";
|
||||
import type { RoutingGroup } from "./types";
|
||||
|
||||
const { Text, Paragraph } = Typography;
|
||||
|
|
@ -16,16 +18,16 @@ interface RoutingGroupsTableProps {
|
|||
proxyBaseUrl?: string;
|
||||
}
|
||||
|
||||
const formatStrategyLabel = (strategy: string): string => {
|
||||
const formatStrategyLabel = (t: TFunction, strategy: string): string => {
|
||||
switch (strategy) {
|
||||
case "simple-shuffle":
|
||||
return "Simple Shuffle";
|
||||
return t("routingGroups.routingGroupsTable.strategySimpleShuffle");
|
||||
case "least-busy":
|
||||
return "Least Busy";
|
||||
return t("routingGroups.routingGroupsTable.strategyLeastBusy");
|
||||
case "usage-based-routing":
|
||||
return "Usage Based";
|
||||
return t("routingGroups.routingGroupsTable.strategyUsageBased");
|
||||
case "latency-based-routing":
|
||||
return "Latency Based";
|
||||
return t("routingGroups.routingGroupsTable.strategyLatencyBased");
|
||||
default:
|
||||
return strategy;
|
||||
}
|
||||
|
|
@ -94,6 +96,7 @@ const SNIPPET_BLOCK_STYLE: React.CSSProperties = {
|
|||
};
|
||||
|
||||
const RoutingGroupSnippet: React.FC<RoutingGroupSnippetProps> = ({ group, baseUrl }) => {
|
||||
const { t } = useTranslation();
|
||||
const snippets = {
|
||||
curl: buildCurlSnippet(group, baseUrl),
|
||||
python: buildPythonSnippet(group, baseUrl),
|
||||
|
|
@ -103,9 +106,9 @@ const RoutingGroupSnippet: React.FC<RoutingGroupSnippetProps> = ({ group, baseUr
|
|||
const [activeKey, setActiveKey] = useState<SnippetKey>("curl");
|
||||
|
||||
const items = [
|
||||
{ key: "curl", label: "cURL" },
|
||||
{ key: "python", label: "Python (OpenAI SDK)" },
|
||||
{ key: "javascript", label: "JavaScript (OpenAI SDK)" },
|
||||
{ key: "curl", label: t("routingGroups.routingGroupsTable.tabCurl") },
|
||||
{ key: "python", label: t("routingGroups.routingGroupsTable.tabPython") },
|
||||
{ key: "javascript", label: t("routingGroups.routingGroupsTable.tabJavascript") },
|
||||
].map(({ key, label }) => ({
|
||||
key,
|
||||
label,
|
||||
|
|
@ -123,19 +126,23 @@ const RoutingGroupSnippet: React.FC<RoutingGroupSnippetProps> = ({ group, baseUr
|
|||
onChange={(k) => setActiveKey(k as SnippetKey)}
|
||||
items={items}
|
||||
tabBarExtraContent={
|
||||
<Paragraph copyable={{ text: snippets[activeKey], tooltips: ["Copy", "Copied"] }} className="!mb-0" />
|
||||
<Paragraph
|
||||
copyable={{ text: snippets[activeKey], tooltips: [t("common.copy"), t("common.copied")] }}
|
||||
className="!mb-0"
|
||||
/>
|
||||
}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
const RoutingGroupsTable: React.FC<RoutingGroupsTableProps> = ({ groups, loading, onEdit, onDelete, proxyBaseUrl }) => {
|
||||
const { t } = useTranslation();
|
||||
const [expandedRowKeys, setExpandedRowKeys] = useState<React.Key[]>([]);
|
||||
const baseUrl = resolveBaseUrl(proxyBaseUrl);
|
||||
|
||||
const columns: ColumnsType<RoutingGroup> = [
|
||||
{
|
||||
title: "GROUP NAME",
|
||||
title: t("routingGroups.routingGroupsTable.colGroupName"),
|
||||
dataIndex: "group_name",
|
||||
key: "group_name",
|
||||
render: (name: string) => (
|
||||
|
|
@ -145,7 +152,7 @@ const RoutingGroupsTable: React.FC<RoutingGroupsTableProps> = ({ groups, loading
|
|||
),
|
||||
},
|
||||
{
|
||||
title: "MODELS",
|
||||
title: t("routingGroups.routingGroupsTable.colModels"),
|
||||
dataIndex: "models",
|
||||
key: "models",
|
||||
render: (models: string[]) => (
|
||||
|
|
@ -157,24 +164,24 @@ const RoutingGroupsTable: React.FC<RoutingGroupsTableProps> = ({ groups, loading
|
|||
),
|
||||
},
|
||||
{
|
||||
title: "STRATEGY",
|
||||
title: t("routingGroups.routingGroupsTable.colStrategy"),
|
||||
dataIndex: "routing_strategy",
|
||||
key: "routing_strategy",
|
||||
render: (strategy: string) => (
|
||||
<span className="inline-flex items-center gap-1.5">
|
||||
<BranchesOutlined className="text-gray-400" />
|
||||
<Text>{formatStrategyLabel(strategy)}</Text>
|
||||
<Text>{formatStrategyLabel(t, strategy)}</Text>
|
||||
</span>
|
||||
),
|
||||
},
|
||||
{
|
||||
title: "ACTIONS",
|
||||
title: t("common.actions"),
|
||||
key: "actions",
|
||||
width: 120,
|
||||
align: "right",
|
||||
render: (_, group) => (
|
||||
<Flex justify="flex-end" align="center" gap={8}>
|
||||
<Tooltip title="Edit">
|
||||
<Tooltip title={t("common.edit")}>
|
||||
<Button
|
||||
type="text"
|
||||
icon={<EditOutlined />}
|
||||
|
|
@ -184,7 +191,7 @@ const RoutingGroupsTable: React.FC<RoutingGroupsTableProps> = ({ groups, loading
|
|||
}}
|
||||
/>
|
||||
</Tooltip>
|
||||
<Tooltip title="Delete">
|
||||
<Tooltip title={t("common.delete")}>
|
||||
<Button
|
||||
type="text"
|
||||
danger
|
||||
|
|
@ -214,11 +221,14 @@ const RoutingGroupsTable: React.FC<RoutingGroupsTableProps> = ({ groups, loading
|
|||
<div className="bg-gray-50 border border-gray-200 rounded-md p-4 my-2">
|
||||
<Flex align="center" gap={8} className="mb-2">
|
||||
<CodeOutlined className="text-blue-500" />
|
||||
<Text strong>How routing works for this group</Text>
|
||||
<Text strong>{t("routingGroups.routingGroupsTable.howRoutingWorksTitle")}</Text>
|
||||
</Flex>
|
||||
<Paragraph className="text-sm text-gray-600 mb-3">
|
||||
Callers request any model in the group by name — LiteLLM picks a deployment behind the scenes using the{" "}
|
||||
<Text strong>{formatStrategyLabel(group.routing_strategy)}</Text> strategy.
|
||||
<Trans
|
||||
i18nKey="routingGroups.routingGroupsTable.howRoutingWorksDesc"
|
||||
values={{ strategy: formatStrategyLabel(t, group.routing_strategy) }}
|
||||
components={{ strong: <Text strong /> }}
|
||||
/>
|
||||
</Paragraph>
|
||||
<RoutingGroupSnippet group={group} baseUrl={baseUrl} />
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@
|
|||
import React, { useMemo, useState } from "react";
|
||||
import { Button, Card, Flex, Input, Modal, Space, Typography } from "antd";
|
||||
import { PlusOutlined, ReloadOutlined, SearchOutlined } from "@ant-design/icons";
|
||||
import { Trans, useTranslation } from "react-i18next";
|
||||
import { useRoutingGroups, useSaveRoutingGroups } from "@/app/(dashboard)/hooks/routingGroups/useRoutingGroups";
|
||||
import { useRouterFields } from "@/app/(dashboard)/hooks/router/useRouterFields";
|
||||
import { useModelHub } from "@/app/(dashboard)/hooks/models/useModels";
|
||||
|
|
@ -15,6 +16,7 @@ import type { RoutingGroup } from "./types";
|
|||
const { Text } = Typography;
|
||||
|
||||
const RoutingGroups: React.FC = () => {
|
||||
const { t } = useTranslation();
|
||||
const { data, isLoading, refetch, isFetching } = useRoutingGroups();
|
||||
const { data: routerFields } = useRouterFields();
|
||||
const { data: modelHub } = useModelHub();
|
||||
|
|
@ -76,12 +78,12 @@ const RoutingGroups: React.FC = () => {
|
|||
await saveMutation.mutateAsync(next);
|
||||
NotificationsManager.success(
|
||||
drawerMode === "create"
|
||||
? `Created routing group "${incoming.group_name}"`
|
||||
: `Updated routing group "${incoming.group_name}"`,
|
||||
? t("routingGroups.index.createSucceeded", { groupName: incoming.group_name })
|
||||
: t("routingGroups.index.updateSucceeded", { groupName: incoming.group_name }),
|
||||
);
|
||||
setDrawerOpen(false);
|
||||
} catch (err) {
|
||||
NotificationsManager.error(err instanceof Error ? err.message : "Failed to save routing group");
|
||||
NotificationsManager.error(err instanceof Error ? err.message : t("routingGroups.index.saveFailed"));
|
||||
}
|
||||
};
|
||||
|
||||
|
|
@ -90,10 +92,10 @@ const RoutingGroups: React.FC = () => {
|
|||
const next = groups.filter((g) => g.group_name !== deletingGroup.group_name);
|
||||
try {
|
||||
await saveMutation.mutateAsync(next);
|
||||
NotificationsManager.success(`Deleted routing group "${deletingGroup.group_name}"`);
|
||||
NotificationsManager.success(t("routingGroups.index.deleteSucceeded", { groupName: deletingGroup.group_name }));
|
||||
setDeletingGroup(null);
|
||||
} catch (err) {
|
||||
NotificationsManager.error(err instanceof Error ? err.message : "Failed to delete routing group");
|
||||
NotificationsManager.error(err instanceof Error ? err.message : t("routingGroups.index.deleteFailed"));
|
||||
}
|
||||
};
|
||||
|
||||
|
|
@ -104,20 +106,20 @@ const RoutingGroups: React.FC = () => {
|
|||
<Input
|
||||
allowClear
|
||||
prefix={<SearchOutlined className="text-gray-400" />}
|
||||
placeholder="Search groups..."
|
||||
placeholder={t("routingGroups.index.searchPlaceholder")}
|
||||
value={searchQuery}
|
||||
onChange={(e) => setSearchQuery(e.target.value)}
|
||||
className="max-w-sm"
|
||||
/>
|
||||
<Flex align="center" gap={12}>
|
||||
<Button icon={<ReloadOutlined />} onClick={() => refetch()} loading={isFetching && !isLoading}>
|
||||
Refresh
|
||||
{t("common.refresh")}
|
||||
</Button>
|
||||
<Button type="primary" icon={<PlusOutlined />} onClick={openCreate}>
|
||||
Create Group
|
||||
{t("routingGroups.index.createGroup")}
|
||||
</Button>
|
||||
<Text type="secondary" className="text-sm whitespace-nowrap">
|
||||
Showing {filteredGroups.length} {filteredGroups.length === 1 ? "result" : "results"}
|
||||
{t("routingGroups.index.showingResults", { count: filteredGroups.length })}
|
||||
</Text>
|
||||
</Flex>
|
||||
</Flex>
|
||||
|
|
@ -146,16 +148,19 @@ const RoutingGroups: React.FC = () => {
|
|||
|
||||
<Modal
|
||||
open={Boolean(deletingGroup)}
|
||||
title="Delete routing group?"
|
||||
okText="Delete"
|
||||
title={t("routingGroups.index.deleteTitle")}
|
||||
okText={t("common.delete")}
|
||||
okButtonProps={{ danger: true, loading: saveMutation.isPending }}
|
||||
cancelText="Cancel"
|
||||
cancelText={t("common.cancel")}
|
||||
onOk={confirmDelete}
|
||||
onCancel={() => setDeletingGroup(null)}
|
||||
>
|
||||
<Text>
|
||||
Models in <Text strong>{deletingGroup?.group_name}</Text> will fall back to the proxy's top-level routing
|
||||
strategy. This cannot be undone.
|
||||
<Trans
|
||||
i18nKey="routingGroups.index.deleteFallbackNote"
|
||||
values={{ groupName: deletingGroup?.group_name }}
|
||||
components={{ strong: <Text strong /> }}
|
||||
/>
|
||||
</Text>
|
||||
</Modal>
|
||||
</Space>
|
||||
|
|
|
|||
|
|
@ -7729,5 +7729,60 @@
|
|||
"title": "Vector Stores",
|
||||
"empty": "No vector stores configured"
|
||||
}
|
||||
},
|
||||
"routingGroups": {
|
||||
"routingGroupModal": {
|
||||
"titleCreate": "Create Routing Group",
|
||||
"titleEdit": "Edit {{groupName}}",
|
||||
"okTextCreate": "Create Group",
|
||||
"okTextEdit": "Save Changes",
|
||||
"groupNameLabel": "Group Name",
|
||||
"groupNameRequired": "Group name is required",
|
||||
"groupNameMaxLength": "Must be {{max}} characters or fewer",
|
||||
"groupNamePattern": "Only letters, numbers, dot, underscore, and dash are allowed",
|
||||
"groupNameExists": "A group with this name already exists",
|
||||
"groupNameExtra": "Use this name as the model in API calls — LiteLLM routes the request to one of the group's models.",
|
||||
"groupNamePlaceholder": "fast-chat",
|
||||
"modelsLabel": "Models",
|
||||
"modelsRequired": "Select at least one model",
|
||||
"modelsExtra": "Models from your model list that this group routes between.",
|
||||
"modelsPlaceholder": "Select models",
|
||||
"routingStrategyLabel": "Routing Strategy",
|
||||
"routingStrategyRequired": "Strategy is required",
|
||||
"strategyPlaceholder": "Select strategy",
|
||||
"strategyArgsLabel": "Strategy Arguments (JSON)",
|
||||
"strategyArgsExampleLatency": "Example: { \"ttl\": 3600, \"lowest_latency_buffer\": 0 }",
|
||||
"strategyArgsExampleUsage": "Example: { \"ttl\": 60 }",
|
||||
"strategyArgsMustBeJson": "Must be valid JSON",
|
||||
"strategyArgsPlaceholder": "{ \"ttl\": 3600 }",
|
||||
"fallbackNote": "Models not claimed by an explicit group fall through to the proxy's top-level routing strategy."
|
||||
},
|
||||
"routingGroupsTable": {
|
||||
"colGroupName": "GROUP NAME",
|
||||
"colModels": "MODELS",
|
||||
"colStrategy": "STRATEGY",
|
||||
"strategySimpleShuffle": "Simple Shuffle",
|
||||
"strategyLeastBusy": "Least Busy",
|
||||
"strategyUsageBased": "Usage Based",
|
||||
"strategyLatencyBased": "Latency Based",
|
||||
"howRoutingWorksTitle": "How routing works for this group",
|
||||
"howRoutingWorksDesc": "Callers request any model in the group by name — LiteLLM picks a deployment behind the scenes using the <strong>{{strategy}}</strong> strategy.",
|
||||
"tabCurl": "cURL",
|
||||
"tabPython": "Python (OpenAI SDK)",
|
||||
"tabJavascript": "JavaScript (OpenAI SDK)"
|
||||
},
|
||||
"index": {
|
||||
"searchPlaceholder": "Search groups...",
|
||||
"showingResults_one": "Showing {{count}} result",
|
||||
"showingResults_other": "Showing {{count}} results",
|
||||
"createGroup": "Create Group",
|
||||
"deleteTitle": "Delete routing group?",
|
||||
"deleteFallbackNote": "Models in <strong>{{groupName}}</strong> will fall back to the proxy's top-level routing strategy. This cannot be undone.",
|
||||
"createSucceeded": "Created routing group \"{{groupName}}\"",
|
||||
"updateSucceeded": "Updated routing group \"{{groupName}}\"",
|
||||
"deleteSucceeded": "Deleted routing group \"{{groupName}}\"",
|
||||
"saveFailed": "Failed to save routing group",
|
||||
"deleteFailed": "Failed to delete routing group"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -7729,5 +7729,60 @@
|
|||
"title": "向量存储",
|
||||
"empty": "未配置任何向量存储"
|
||||
}
|
||||
},
|
||||
"routingGroups": {
|
||||
"routingGroupModal": {
|
||||
"titleCreate": "创建路由组",
|
||||
"titleEdit": "编辑 {{groupName}}",
|
||||
"okTextCreate": "创建组",
|
||||
"okTextEdit": "保存更改",
|
||||
"groupNameLabel": "组名",
|
||||
"groupNameRequired": "组名不能为空",
|
||||
"groupNameMaxLength": "不能超过 {{max}} 个字符",
|
||||
"groupNamePattern": "只允许字母、数字、点、下划线和短横线",
|
||||
"groupNameExists": "已存在同名组",
|
||||
"groupNameExtra": "在 API 调用中将此名称作为模型使用 — LiteLLM 会将请求路由到组内某个模型。",
|
||||
"groupNamePlaceholder": "fast-chat",
|
||||
"modelsLabel": "模型",
|
||||
"modelsRequired": "至少选择一个模型",
|
||||
"modelsExtra": "此组路由的模型列表中的模型。",
|
||||
"modelsPlaceholder": "选择模型",
|
||||
"routingStrategyLabel": "路由策略",
|
||||
"routingStrategyRequired": "策略不能为空",
|
||||
"strategyPlaceholder": "选择策略",
|
||||
"strategyArgsLabel": "策略参数(JSON)",
|
||||
"strategyArgsExampleLatency": "示例:{ \"ttl\": 3600, \"lowest_latency_buffer\": 0 }",
|
||||
"strategyArgsExampleUsage": "示例:{ \"ttl\": 60 }",
|
||||
"strategyArgsMustBeJson": "必须是有效的 JSON",
|
||||
"strategyArgsPlaceholder": "{ \"ttl\": 3600 }",
|
||||
"fallbackNote": "未被明确指定到组的模型将回退到代理的顶级路由策略。"
|
||||
},
|
||||
"routingGroupsTable": {
|
||||
"colGroupName": "组名",
|
||||
"colModels": "模型",
|
||||
"colStrategy": "策略",
|
||||
"strategySimpleShuffle": "简单随机",
|
||||
"strategyLeastBusy": "最少繁忙",
|
||||
"strategyUsageBased": "基于用量",
|
||||
"strategyLatencyBased": "基于延迟",
|
||||
"howRoutingWorksTitle": "此组的路由方式",
|
||||
"howRoutingWorksDesc": "调用方按名称请求组内任意模型 — LiteLLM 在后台使用 <strong>{{strategy}}</strong> 策略选取部署。",
|
||||
"tabCurl": "cURL",
|
||||
"tabPython": "Python(OpenAI SDK)",
|
||||
"tabJavascript": "JavaScript(OpenAI SDK)"
|
||||
},
|
||||
"index": {
|
||||
"searchPlaceholder": "搜索组...",
|
||||
"showingResults_one": "显示 {{count}} 条结果",
|
||||
"showingResults_other": "显示 {{count}} 条结果",
|
||||
"createGroup": "创建组",
|
||||
"deleteTitle": "删除路由组?",
|
||||
"deleteFallbackNote": "<strong>{{groupName}}</strong> 中的模型将回退到代理的顶级路由策略。此操作不可撤销。",
|
||||
"createSucceeded": "已创建路由组\"{{groupName}}\"",
|
||||
"updateSucceeded": "已更新路由组\"{{groupName}}\"",
|
||||
"deleteSucceeded": "已删除路由组\"{{groupName}}\"",
|
||||
"saveFailed": "保存路由组失败",
|
||||
"deleteFailed": "删除路由组失败"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue