feat(ui): i18n for tag management

This commit is contained in:
吹雪 2026-06-11 05:16:21 +09:00
parent f6bf079554
commit 2f069a0a5f
7 changed files with 293 additions and 103 deletions

View file

@ -1,5 +1,6 @@
import React, { useEffect, useState } from "react";
import { Select } from "antd";
import { useTranslation } from "react-i18next";
import { Tag } from "./types";
import { tagListCall } from "../networking";
@ -11,6 +12,7 @@ interface TagSelectorProps {
}
const TagSelector: React.FC<TagSelectorProps> = ({ onChange, value, className, accessToken }) => {
const { t } = useTranslation();
const [tags, setTags] = useState<Tag[]>([]);
const [loading, setLoading] = useState(false);
@ -35,7 +37,7 @@ const TagSelector: React.FC<TagSelectorProps> = ({ onChange, value, className, a
<Select
mode="tags"
showSearch
placeholder="Select or create tags"
placeholder={t("tagManagement.tagSelector.placeholder")}
onChange={onChange}
value={value}
loading={loading}

View file

@ -21,6 +21,7 @@ import {
} from "@tremor/react";
import { Tooltip } from "antd";
import React from "react";
import { useTranslation } from "react-i18next";
import { Tag } from "./types";
interface TagTableProps {
@ -34,22 +35,19 @@ const DYNAMIC_SPEND_TAG_DESCRIPTION =
"This is just a spend tag that was passed dynamically in a request. It does not control any LLM models.";
const TagTable: React.FC<TagTableProps> = ({ data, onEdit, onDelete, onSelectTag }) => {
const { t } = useTranslation();
const [sorting, setSorting] = React.useState<SortingState>([{ id: "created_at", desc: true }]);
const columns: ColumnDef<Tag>[] = [
{
header: "Tag Name",
header: () => t("tagManagement.tagTable.colTagName"),
accessorKey: "name",
cell: ({ row }) => {
const tag = row.original;
const isDynamicSpendTag = tag.description === DYNAMIC_SPEND_TAG_DESCRIPTION;
return (
<div className="overflow-hidden">
<Tooltip
title={
isDynamicSpendTag ? "You cannot view the information of a dynamically generated spend tag" : tag.name
}
>
<Tooltip title={isDynamicSpendTag ? t("tagManagement.tagTable.dynamicTagViewDisabled") : tag.name}>
<Button
size="xs"
variant="light"
@ -65,7 +63,7 @@ const TagTable: React.FC<TagTableProps> = ({ data, onEdit, onDelete, onSelectTag
},
},
{
header: "Description",
header: () => t("common.description"),
accessorKey: "description",
cell: ({ row }) => {
const tag = row.original;
@ -77,7 +75,7 @@ const TagTable: React.FC<TagTableProps> = ({ data, onEdit, onDelete, onSelectTag
},
},
{
header: "Allowed Models",
header: () => t("tagManagement.tagTable.colAllowedModels"),
accessorKey: "models",
cell: ({ row }) => {
const tag = row.original;
@ -85,7 +83,7 @@ const TagTable: React.FC<TagTableProps> = ({ data, onEdit, onDelete, onSelectTag
<div style={{ display: "flex", flexDirection: "column" }}>
{tag?.models?.length === 0 ? (
<Badge size="xs" className="mb-1" color="red">
All Models
{t("tagManagement.tagTable.allModels")}
</Badge>
) : (
tag?.models?.map((modelId) => (
@ -101,7 +99,7 @@ const TagTable: React.FC<TagTableProps> = ({ data, onEdit, onDelete, onSelectTag
},
},
{
header: "Created",
header: () => t("tagManagement.tagTable.colCreated"),
accessorKey: "created_at",
sortingFn: "datetime",
cell: ({ row }) => {
@ -111,23 +109,23 @@ const TagTable: React.FC<TagTableProps> = ({ data, onEdit, onDelete, onSelectTag
},
{
id: "actions",
header: "Actions",
header: () => t("common.actions"),
cell: ({ row }) => {
const tag = row.original;
const isDynamicSpendTag = tag.description === DYNAMIC_SPEND_TAG_DESCRIPTION;
return (
<div className="flex space-x-2">
{isDynamicSpendTag ? (
<Tooltip title="Dynamically generated spend tags cannot be edited">
<Tooltip title={t("tagManagement.tagTable.dynamicTagEditDisabled")}>
<Icon
icon={PencilAltIcon}
size="sm"
className="opacity-50 cursor-not-allowed"
aria-label="Edit tag (disabled)"
aria-label={t("tagManagement.tagTable.editTagDisabledAriaLabel")}
/>
</Tooltip>
) : (
<Tooltip title="Edit tag">
<Tooltip title={t("tagManagement.tagTable.editTagTooltip")}>
<Icon
icon={PencilAltIcon}
size="sm"
@ -137,16 +135,16 @@ const TagTable: React.FC<TagTableProps> = ({ data, onEdit, onDelete, onSelectTag
</Tooltip>
)}
{isDynamicSpendTag ? (
<Tooltip title="Dynamically generated spend tags cannot be deleted">
<Tooltip title={t("tagManagement.tagTable.dynamicTagDeleteDisabled")}>
<Icon
icon={TrashIcon}
size="sm"
className="opacity-50 cursor-not-allowed"
aria-label="Delete tag (disabled)"
aria-label={t("tagManagement.tagTable.deleteTagDisabledAriaLabel")}
/>
</Tooltip>
) : (
<Tooltip title="Delete tag">
<Tooltip title={t("tagManagement.tagTable.deleteTagTooltip")}>
<Icon
icon={TrashIcon}
size="sm"
@ -232,7 +230,7 @@ const TagTable: React.FC<TagTableProps> = ({ data, onEdit, onDelete, onSelectTag
<TableRow>
<TableCell colSpan={columns.length} className="h-8 text-center">
<div className="text-center text-gray-500">
<p>No tags found</p>
<p>{t("tagManagement.tagTable.noTagsFound")}</p>
</div>
</TableCell>
</TableRow>

View file

@ -2,6 +2,7 @@ import { InfoCircleOutlined } from "@ant-design/icons";
import { Accordion, AccordionBody, AccordionHeader, Button, TextInput, Title } from "@tremor/react";
import { Form, Input, Modal, Select as Select2, Tooltip } from "antd";
import React from "react";
import { Trans, useTranslation } from "react-i18next";
import BudgetDurationDropdown from "../../common_components/budget_duration_dropdown";
import NumericalInput from "../../shared/numerical_input";
@ -23,6 +24,7 @@ interface CreateTagModalProps {
}
const CreateTagModal: React.FC<CreateTagModalProps> = ({ visible, onCancel, onSubmit, availableModels }) => {
const { t } = useTranslation();
const [form] = Form.useForm();
const handleFinish = (values: any) => {
@ -36,28 +38,38 @@ const CreateTagModal: React.FC<CreateTagModalProps> = ({ visible, onCancel, onSu
};
return (
<Modal title="Create New Tag" open={visible} width={800} footer={null} onCancel={handleCancel}>
<Modal
title={t("tagManagement.createTagModal.title")}
open={visible}
width={800}
footer={null}
onCancel={handleCancel}
>
<Form form={form} onFinish={handleFinish} labelCol={{ span: 8 }} wrapperCol={{ span: 16 }} labelAlign="left">
<Form.Item label="Tag Name" name="tag_name" rules={[{ required: true, message: "Please input a tag name" }]}>
<Form.Item
label={t("tagManagement.createTagModal.tagNameLabel")}
name="tag_name"
rules={[{ required: true, message: t("tagManagement.createTagModal.tagNameRequired") }]}
>
<TextInput />
</Form.Item>
<Form.Item label="Description" name="description">
<Form.Item label={t("common.description")} name="description">
<Input.TextArea rows={4} />
</Form.Item>
<Form.Item
label={
<span>
Allowed Models
<Tooltip title="Select which models are allowed to process requests from this tag">
{t("tagManagement.createTagModal.allowedModelsLabel")}
<Tooltip title={t("tagManagement.createTagModal.allowedModelsTooltip")}>
<InfoCircleOutlined style={{ marginLeft: "4px" }} />
</Tooltip>
</span>
}
name="allowed_llms"
>
<Select2 mode="multiple" placeholder="Select Models">
<Select2 mode="multiple" placeholder={t("tagManagement.createTagModal.selectModelsPlaceholder")}>
{availableModels.map((model) => (
<Select2.Option key={model.model_info.id} value={model.model_info.id}>
<div>
@ -71,15 +83,15 @@ const CreateTagModal: React.FC<CreateTagModalProps> = ({ visible, onCancel, onSu
<Accordion className="mt-4 mb-4">
<AccordionHeader>
<Title className="m-0">Budget & Rate Limits (Optional)</Title>
<Title className="m-0">{t("tagManagement.createTagModal.budgetRateLimitsOptional")}</Title>
</AccordionHeader>
<AccordionBody>
<Form.Item
className="mt-4"
label={
<span>
Max Budget (USD){" "}
<Tooltip title="Maximum amount in USD this tag can spend. When reached, requests with this tag will be blocked">
{t("tagManagement.createTagModal.maxBudgetLabel")}{" "}
<Tooltip title={t("tagManagement.createTagModal.maxBudgetTooltip")}>
<InfoCircleOutlined style={{ marginLeft: "4px" }} />
</Tooltip>
</span>
@ -92,8 +104,8 @@ const CreateTagModal: React.FC<CreateTagModalProps> = ({ visible, onCancel, onSu
className="mt-4"
label={
<span>
Reset Budget{" "}
<Tooltip title="How often the budget should reset. For example, setting 'daily' will reset the budget every 24 hours">
{t("tagManagement.createTagModal.resetBudgetLabel")}{" "}
<Tooltip title={t("tagManagement.createTagModal.resetBudgetTooltip")}>
<InfoCircleOutlined style={{ marginLeft: "4px" }} />
</Tooltip>
</span>
@ -105,23 +117,26 @@ const CreateTagModal: React.FC<CreateTagModalProps> = ({ visible, onCancel, onSu
<div className="mt-4 p-3 bg-gray-50 rounded-md border border-gray-200">
<p className="text-sm text-gray-600">
TPM/RPM limits for tags are not currently supported. If you need this feature, please{" "}
<a
href="https://github.com/BerriAI/litellm/issues/new"
target="_blank"
rel="noopener noreferrer"
className="text-blue-600 hover:text-blue-800 underline"
>
create a GitHub issue
</a>
.
<Trans
i18nKey="tagManagement.createTagModal.tpmRpmNotSupported"
components={{
githubLink: (
<a
href="https://github.com/BerriAI/litellm/issues/new"
target="_blank"
rel="noopener noreferrer"
className="text-blue-600 hover:text-blue-800 underline"
/>
),
}}
/>
</p>
</div>
</AccordionBody>
</Accordion>
<div style={{ textAlign: "right", marginTop: "10px" }}>
<Button type="submit">Create Tag</Button>
<Button type="submit">{t("tagManagement.createTagModal.createTagButton")}</Button>
</div>
</Form>
</Modal>

View file

@ -1,6 +1,7 @@
import React, { useState, useEffect } from "react";
import { Icon, Button, Col, Text, Grid } from "@tremor/react";
import { RefreshIcon } from "@heroicons/react/outline";
import { Trans, useTranslation } from "react-i18next";
import TagInfoView from "./tag_info";
import { modelInfoCall } from "../networking";
import { tagCreateCall, tagListCall, tagDeleteCall } from "../networking";
@ -26,6 +27,7 @@ interface TagProps {
}
const TagManagement: React.FC<TagProps> = ({ accessToken, userID, userRole }) => {
const { t } = useTranslation();
const [tags, setTags] = useState<Tag[]>([]);
const [isCreateModalVisible, setIsCreateModalVisible] = useState(false);
const [selectedTagId, setSelectedTagId] = useState<string | null>(null);
@ -43,7 +45,7 @@ const TagManagement: React.FC<TagProps> = ({ accessToken, userID, userRole }) =>
setTags(Object.values(response));
} catch (error) {
console.error("Error fetching tags:", error);
NotificationsManager.fromBackend("Error fetching tags: " + error);
NotificationsManager.fromBackend(t("tagManagement.index.errorFetchingTags", { error }));
}
};
@ -66,12 +68,12 @@ const TagManagement: React.FC<TagProps> = ({ accessToken, userID, userRole }) =>
rpm_limit: formValues.rpm_limit,
budget_duration: formValues.budget_duration,
});
NotificationsManager.success("Tag created successfully");
NotificationsManager.success(t("tagManagement.index.tagCreatedSuccess"));
setIsCreateModalVisible(false);
fetchTags();
} catch (error) {
console.error("Error creating tag:", error);
NotificationsManager.fromBackend("Error creating tag: " + error);
NotificationsManager.fromBackend(t("tagManagement.index.errorCreatingTag", { error }));
}
};
@ -84,11 +86,11 @@ const TagManagement: React.FC<TagProps> = ({ accessToken, userID, userRole }) =>
if (!accessToken || !tagToDelete) return;
try {
await tagDeleteCall(accessToken, tagToDelete);
NotificationsManager.success("Tag deleted successfully");
NotificationsManager.success(t("tagManagement.index.tagDeletedSuccess"));
fetchTags();
} catch (error) {
console.error("Error deleting tag:", error);
NotificationsManager.fromBackend("Error deleting tag: " + error);
NotificationsManager.fromBackend(t("tagManagement.index.errorDeletingTag", { error }));
}
setIsDeleteModalOpen(false);
setTagToDelete(null);
@ -104,7 +106,7 @@ const TagManagement: React.FC<TagProps> = ({ accessToken, userID, userRole }) =>
}
} catch (error) {
console.error("Error fetching models:", error);
NotificationsManager.fromBackend("Error fetching models: " + error);
NotificationsManager.fromBackend(t("tagManagement.index.errorFetchingModels", { error }));
}
};
fetchModels();
@ -131,9 +133,9 @@ const TagManagement: React.FC<TagProps> = ({ accessToken, userID, userRole }) =>
) : (
<div className="gap-2 p-8 h-[75vh] w-full mt-2">
<div className="flex justify-between mt-2 w-full items-center mb-4">
<h1>Tag Management</h1>
<h1>{t("tagManagement.index.pageTitle")}</h1>
<div className="flex items-center space-x-2">
{lastRefreshed && <Text>Last Refreshed: {lastRefreshed}</Text>}
{lastRefreshed && <Text>{t("tagManagement.index.lastRefreshed", { time: lastRefreshed })}</Text>}
<Icon
icon={RefreshIcon}
variant="shadow"
@ -145,19 +147,25 @@ const TagManagement: React.FC<TagProps> = ({ accessToken, userID, userRole }) =>
</div>
<Text className="mb-4">
Click on a tag name to view and edit its details.
{t("tagManagement.index.clickTagNameHint")}
<p>
You can use tags to restrict the usage of certain LLMs based on tags passed in the request. Read more
about tag routing{" "}
<a href="https://docs.litellm.ai/docs/proxy/tag_routing" target="_blank" rel="noopener noreferrer">
here
</a>
.
<Trans
i18nKey="tagManagement.index.tagRoutingDescription"
components={{
docsLink: (
<a
href="https://docs.litellm.ai/docs/proxy/tag_routing"
target="_blank"
rel="noopener noreferrer"
/>
),
}}
/>
</p>
</Text>
<Button className="mb-4" onClick={() => setIsCreateModalVisible(true)}>
+ Create New Tag
{t("tagManagement.index.createNewTagButton")}
</Button>
<Grid numItems={1} className="gap-2 pt-2 pb-2 h-[75vh] w-full mt-2">
@ -193,16 +201,18 @@ const TagManagement: React.FC<TagProps> = ({ accessToken, userID, userRole }) =>
<div className="bg-white px-4 pt-5 pb-4 sm:p-6 sm:pb-4">
<div className="sm:flex sm:items-start">
<div className="mt-3 text-center sm:mt-0 sm:ml-4 sm:text-left">
<h3 className="text-lg leading-6 font-medium text-gray-900">Delete Tag</h3>
<h3 className="text-lg leading-6 font-medium text-gray-900">
{t("tagManagement.index.deleteTagTitle")}
</h3>
<div className="mt-2">
<p className="text-sm text-gray-500">Are you sure you want to delete this tag?</p>
<p className="text-sm text-gray-500">{t("tagManagement.index.deleteTagConfirm")}</p>
</div>
</div>
</div>
</div>
<div className="bg-gray-50 px-4 py-3 sm:px-6 sm:flex sm:flex-row-reverse">
<Button onClick={confirmDelete} color="red" className="ml-2">
Delete
{t("common.delete")}
</Button>
<Button
onClick={() => {
@ -210,7 +220,7 @@ const TagManagement: React.FC<TagProps> = ({ accessToken, userID, userRole }) =>
setTagToDelete(null);
}}
>
Cancel
{t("common.cancel")}
</Button>
</div>
</div>

View file

@ -12,6 +12,7 @@ import {
} from "@tremor/react";
import { Form, Input, Select as Select2, Tooltip } from "antd";
import { InfoCircleOutlined } from "@ant-design/icons";
import { Trans, useTranslation } from "react-i18next";
import { fetchUserModels } from "../organisms/create_key_button";
import { getModelDisplayName } from "../key_team_helpers/fetch_available_models_team_key";
import { tagInfoCall, tagUpdateCall } from "../networking";
@ -32,6 +33,7 @@ interface TagInfoViewProps {
}
const TagInfoView: React.FC<TagInfoViewProps> = ({ tagId, onClose, accessToken, is_admin, editTag }) => {
const { t } = useTranslation();
const [form] = Form.useForm();
const [tagDetails, setTagDetails] = useState<Tag | null>(null);
const [isEditing, setIsEditing] = useState<boolean>(editTag);
@ -67,7 +69,7 @@ const TagInfoView: React.FC<TagInfoViewProps> = ({ tagId, onClose, accessToken,
}
} catch (error) {
console.error("Error fetching tag details:", error);
NotificationsManager.fromBackend("Error fetching tag details: " + error);
NotificationsManager.fromBackend(t("tagManagement.tagInfo.errorFetchingTagDetails", { error }));
}
};
@ -95,17 +97,17 @@ const TagInfoView: React.FC<TagInfoViewProps> = ({ tagId, onClose, accessToken,
rpm_limit: values.rpm_limit,
budget_duration: values.budget_duration,
});
NotificationsManager.success("Tag updated successfully");
NotificationsManager.success(t("tagManagement.tagInfo.tagUpdatedSuccess"));
setIsEditing(false);
fetchTagDetails();
} catch (error) {
console.error("Error updating tag:", error);
NotificationsManager.fromBackend("Error updating tag: " + error);
NotificationsManager.fromBackend(t("tagManagement.tagInfo.errorUpdatingTag", { error }));
}
};
if (!tagDetails) {
return <div>Loading...</div>;
return <div>{t("common.loading")}</div>;
}
return (
@ -113,10 +115,10 @@ const TagInfoView: React.FC<TagInfoViewProps> = ({ tagId, onClose, accessToken,
<div className="flex justify-between items-center mb-6">
<div>
<Button onClick={onClose} className="mb-4">
← Back to Tags
{t("tagManagement.tagInfo.backToTags")}
</Button>
<div className="flex items-center gap-2">
<Text className="font-medium">Tag Name:</Text>
<Text className="font-medium">{t("tagManagement.tagInfo.tagNameLabel")}</Text>
<span className="font-mono px-2 py-1 bg-gray-100 rounded text-sm border border-gray-200">
{tagDetails.name}
</span>
@ -132,34 +134,40 @@ const TagInfoView: React.FC<TagInfoViewProps> = ({ tagId, onClose, accessToken,
}`}
/>
</div>
<Text className="text-gray-500">{tagDetails.description || "No description"}</Text>
<Text className="text-gray-500">{tagDetails.description || t("tagManagement.tagInfo.noDescription")}</Text>
</div>
{is_admin && !isEditing && <Button onClick={() => setIsEditing(true)}>Edit Tag</Button>}
{is_admin && !isEditing && (
<Button onClick={() => setIsEditing(true)}>{t("tagManagement.tagInfo.editTagButton")}</Button>
)}
</div>
{isEditing ? (
<Card>
<Form form={form} onFinish={handleSave} layout="vertical" initialValues={tagDetails}>
<Form.Item label="Tag Name" name="name" rules={[{ required: true, message: "Please input a tag name" }]}>
<Form.Item
label={t("tagManagement.tagInfo.tagNameLabel")}
name="name"
rules={[{ required: true, message: t("tagManagement.tagInfo.tagNameRequired") }]}
>
<Input className="rounded-md border-gray-300" />
</Form.Item>
<Form.Item label="Description" name="description">
<Form.Item label={t("common.description")} name="description">
<Input.TextArea rows={4} />
</Form.Item>
<Form.Item
label={
<span>
Allowed Models
<Tooltip title="Select which models are allowed to process this type of data">
{t("tagManagement.tagInfo.allowedModelsLabel")}
<Tooltip title={t("tagManagement.tagInfo.allowedModelsTooltip")}>
<InfoCircleOutlined style={{ marginLeft: "4px" }} />
</Tooltip>
</span>
}
name="models"
>
<Select2 mode="multiple" placeholder="Select Models">
<Select2 mode="multiple" placeholder={t("tagManagement.tagInfo.selectModelsPlaceholder")}>
{userModels.map((modelId) => (
<Select2.Option key={modelId} value={modelId}>
{getModelDisplayName(modelId)}
@ -170,14 +178,14 @@ const TagInfoView: React.FC<TagInfoViewProps> = ({ tagId, onClose, accessToken,
<Accordion className="mt-4 mb-4">
<AccordionHeader>
<TremorTitle className="m-0">Budget & Rate Limits</TremorTitle>
<TremorTitle className="m-0">{t("tagManagement.tagInfo.budgetRateLimits")}</TremorTitle>
</AccordionHeader>
<AccordionBody>
<Form.Item
label={
<span>
Max Budget (USD){" "}
<Tooltip title="Maximum amount in USD this tag can spend">
{t("tagManagement.tagInfo.maxBudgetLabel")}{" "}
<Tooltip title={t("tagManagement.tagInfo.maxBudgetTooltip")}>
<InfoCircleOutlined style={{ marginLeft: "4px" }} />
</Tooltip>
</span>
@ -190,8 +198,8 @@ const TagInfoView: React.FC<TagInfoViewProps> = ({ tagId, onClose, accessToken,
<Form.Item
label={
<span>
Reset Budget{" "}
<Tooltip title="How often the budget should reset">
{t("tagManagement.tagInfo.resetBudgetLabel")}{" "}
<Tooltip title={t("tagManagement.tagInfo.resetBudgetTooltip")}>
<InfoCircleOutlined style={{ marginLeft: "4px" }} />
</Tooltip>
</span>
@ -203,45 +211,48 @@ const TagInfoView: React.FC<TagInfoViewProps> = ({ tagId, onClose, accessToken,
<div className="mt-4 p-3 bg-gray-50 rounded-md border border-gray-200">
<p className="text-sm text-gray-600">
TPM/RPM limits for tags are not currently supported. If you need this feature, please{" "}
<a
href="https://github.com/BerriAI/litellm/issues/new"
target="_blank"
rel="noopener noreferrer"
className="text-blue-600 hover:text-blue-800 underline"
>
create a GitHub issue
</a>
.
<Trans
i18nKey="tagManagement.tagInfo.tpmRpmNotSupported"
components={{
githubLink: (
<a
href="https://github.com/BerriAI/litellm/issues/new"
target="_blank"
rel="noopener noreferrer"
className="text-blue-600 hover:text-blue-800 underline"
/>
),
}}
/>
</p>
</div>
</AccordionBody>
</Accordion>
<div className="flex justify-end space-x-2">
<Button onClick={() => setIsEditing(false)}>Cancel</Button>
<Button type="submit">Save Changes</Button>
<Button onClick={() => setIsEditing(false)}>{t("common.cancel")}</Button>
<Button type="submit">{t("tagManagement.tagInfo.saveChanges")}</Button>
</div>
</Form>
</Card>
) : (
<div className="space-y-6">
<Card>
<Title>Tag Details</Title>
<Title>{t("tagManagement.tagInfo.tagDetails")}</Title>
<div className="space-y-4 mt-4">
<div>
<Text className="font-medium">Name</Text>
<Text className="font-medium">{t("common.name")}</Text>
<Text>{tagDetails.name}</Text>
</div>
<div>
<Text className="font-medium">Description</Text>
<Text className="font-medium">{t("common.description")}</Text>
<Text>{tagDetails.description || "-"}</Text>
</div>
<div>
<Text className="font-medium">Allowed Models</Text>
<Text className="font-medium">{t("tagManagement.tagInfo.allowedModelsLabel")}</Text>
<div className="flex flex-wrap gap-2 mt-2">
{!tagDetails.models || tagDetails.models.length === 0 ? (
<Badge color="red">All Models</Badge>
<Badge color="red">{t("tagManagement.tagInfo.allModels")}</Badge>
) : (
tagDetails.models.map((modelId) => (
<Badge key={modelId} color="blue">
@ -252,11 +263,11 @@ const TagInfoView: React.FC<TagInfoViewProps> = ({ tagId, onClose, accessToken,
</div>
</div>
<div>
<Text className="font-medium">Created</Text>
<Text className="font-medium">{t("tagManagement.tagInfo.created")}</Text>
<Text>{tagDetails.created_at ? new Date(tagDetails.created_at).toLocaleString() : "-"}</Text>
</div>
<div>
<Text className="font-medium">Last Updated</Text>
<Text className="font-medium">{t("tagManagement.tagInfo.lastUpdated")}</Text>
<Text>{tagDetails.updated_at ? new Date(tagDetails.updated_at).toLocaleString() : "-"}</Text>
</div>
</div>
@ -264,32 +275,32 @@ const TagInfoView: React.FC<TagInfoViewProps> = ({ tagId, onClose, accessToken,
{tagDetails.litellm_budget_table && (
<Card>
<Title>Budget & Rate Limits</Title>
<Title>{t("tagManagement.tagInfo.budgetRateLimits")}</Title>
<div className="space-y-4 mt-4">
{tagDetails.litellm_budget_table.max_budget !== undefined &&
tagDetails.litellm_budget_table.max_budget !== null && (
<div>
<Text className="font-medium">Max Budget</Text>
<Text className="font-medium">{t("tagManagement.tagInfo.maxBudget")}</Text>
<Text>${tagDetails.litellm_budget_table.max_budget}</Text>
</div>
)}
{tagDetails.litellm_budget_table.budget_duration && (
<div>
<Text className="font-medium">Budget Duration</Text>
<Text className="font-medium">{t("tagManagement.tagInfo.budgetDuration")}</Text>
<Text>{tagDetails.litellm_budget_table.budget_duration}</Text>
</div>
)}
{tagDetails.litellm_budget_table.tpm_limit !== undefined &&
tagDetails.litellm_budget_table.tpm_limit !== null && (
<div>
<Text className="font-medium">TPM Limit</Text>
<Text className="font-medium">{t("tagManagement.tagInfo.tpmLimit")}</Text>
<Text>{tagDetails.litellm_budget_table.tpm_limit.toLocaleString()}</Text>
</div>
)}
{tagDetails.litellm_budget_table.rpm_limit !== undefined &&
tagDetails.litellm_budget_table.rpm_limit !== null && (
<div>
<Text className="font-medium">RPM Limit</Text>
<Text className="font-medium">{t("tagManagement.tagInfo.rpmLimit")}</Text>
<Text>{tagDetails.litellm_budget_table.rpm_limit.toLocaleString()}</Text>
</div>
)}

View file

@ -7385,5 +7385,82 @@
"description": "Help us improve LiteLLM! Share your experience in 5 quick questions.",
"buttonText": "Share feedback"
}
},
"tagManagement": {
"tagSelector": {
"placeholder": "Select or create tags"
},
"tagTable": {
"colTagName": "Tag Name",
"colAllowedModels": "Allowed Models",
"colCreated": "Created",
"allModels": "All Models",
"dynamicTagViewDisabled": "You cannot view the information of a dynamically generated spend tag",
"dynamicTagEditDisabled": "Dynamically generated spend tags cannot be edited",
"dynamicTagDeleteDisabled": "Dynamically generated spend tags cannot be deleted",
"editTagTooltip": "Edit tag",
"editTagDisabledAriaLabel": "Edit tag (disabled)",
"deleteTagTooltip": "Delete tag",
"deleteTagDisabledAriaLabel": "Delete tag (disabled)",
"noTagsFound": "No tags found"
},
"createTagModal": {
"title": "Create New Tag",
"tagNameLabel": "Tag Name",
"tagNameRequired": "Please input a tag name",
"allowedModelsLabel": "Allowed Models",
"allowedModelsTooltip": "Select which models are allowed to process requests from this tag",
"selectModelsPlaceholder": "Select Models",
"budgetRateLimitsOptional": "Budget & Rate Limits (Optional)",
"maxBudgetLabel": "Max Budget (USD)",
"maxBudgetTooltip": "Maximum amount in USD this tag can spend. When reached, requests with this tag will be blocked",
"resetBudgetLabel": "Reset Budget",
"resetBudgetTooltip": "How often the budget should reset. For example, setting 'daily' will reset the budget every 24 hours",
"tpmRpmNotSupported": "TPM/RPM limits for tags are not currently supported. If you need this feature, please <githubLink>create a GitHub issue</githubLink>.",
"createTagButton": "+ Create New Tag"
},
"index": {
"pageTitle": "Tag Management",
"lastRefreshed": "Last Refreshed: {{time}}",
"clickTagNameHint": "Click on a tag name to view and edit its details.",
"tagRoutingDescription": "You can use tags to restrict the usage of certain LLMs based on tags passed in the request. Read more about tag routing <docsLink>here</docsLink>.",
"createNewTagButton": "+ Create New Tag",
"deleteTagTitle": "Delete Tag",
"deleteTagConfirm": "Are you sure you want to delete this tag?",
"errorFetchingTags": "Error fetching tags: {{error}}",
"tagCreatedSuccess": "Tag created successfully",
"errorCreatingTag": "Error creating tag: {{error}}",
"tagDeletedSuccess": "Tag deleted successfully",
"errorDeletingTag": "Error deleting tag: {{error}}",
"errorFetchingModels": "Error fetching models: {{error}}"
},
"tagInfo": {
"backToTags": "← Back to Tags",
"tagNameLabel": "Tag Name:",
"noDescription": "No description",
"editTagButton": "Edit Tag",
"tagNameRequired": "Please input a tag name",
"allowedModelsLabel": "Allowed Models",
"allowedModelsTooltip": "Select which models are allowed to process this type of data",
"selectModelsPlaceholder": "Select Models",
"budgetRateLimits": "Budget & Rate Limits",
"maxBudgetLabel": "Max Budget (USD)",
"maxBudgetTooltip": "Maximum amount in USD this tag can spend",
"resetBudgetLabel": "Reset Budget",
"resetBudgetTooltip": "How often the budget should reset",
"tpmRpmNotSupported": "TPM/RPM limits for tags are not currently supported. If you need this feature, please <githubLink>create a GitHub issue</githubLink>.",
"saveChanges": "Save Changes",
"tagDetails": "Tag Details",
"allModels": "All Models",
"created": "Created",
"lastUpdated": "Last Updated",
"maxBudget": "Max Budget",
"budgetDuration": "Budget Duration",
"tpmLimit": "TPM Limit",
"rpmLimit": "RPM Limit",
"errorFetchingTagDetails": "Error fetching tag details: {{error}}",
"tagUpdatedSuccess": "Tag updated successfully",
"errorUpdatingTag": "Error updating tag: {{error}}"
}
}
}

View file

@ -7385,5 +7385,82 @@
"description": "帮助我们改进 LiteLLM!通过 5 个简短问题分享您的使用体验。",
"buttonText": "分享反馈"
}
},
"tagManagement": {
"tagSelector": {
"placeholder": "选择或创建标签"
},
"tagTable": {
"colTagName": "标签名称",
"colAllowedModels": "允许的模型",
"colCreated": "创建时间",
"allModels": "所有模型",
"dynamicTagViewDisabled": "您无法查看动态生成的花费标签的信息",
"dynamicTagEditDisabled": "动态生成的花费标签无法编辑",
"dynamicTagDeleteDisabled": "动态生成的花费标签无法删除",
"editTagTooltip": "编辑标签",
"editTagDisabledAriaLabel": "编辑标签(已禁用)",
"deleteTagTooltip": "删除标签",
"deleteTagDisabledAriaLabel": "删除标签(已禁用)",
"noTagsFound": "未找到标签"
},
"createTagModal": {
"title": "创建新标签",
"tagNameLabel": "标签名称",
"tagNameRequired": "请输入标签名称",
"allowedModelsLabel": "允许的模型",
"allowedModelsTooltip": "选择允许处理此标签请求的模型",
"selectModelsPlaceholder": "选择模型",
"budgetRateLimitsOptional": "预算与速率限制(可选)",
"maxBudgetLabel": "最大预算(美元)",
"maxBudgetTooltip": "此标签可花费的最大金额(美元)。达到上限后,携带此标签的请求将被拒绝",
"resetBudgetLabel": "重置预算",
"resetBudgetTooltip": "预算重置频率。例如,设置为「每天」将每 24 小时重置一次预算",
"tpmRpmNotSupported": "标签的 TPM/RPM 限制暂不支持。如需此功能,请 <githubLink>创建 GitHub Issue</githubLink>。",
"createTagButton": "+ 创建新标签"
},
"index": {
"pageTitle": "标签管理",
"lastRefreshed": "最后刷新:{{time}}",
"clickTagNameHint": "点击标签名称可查看并编辑其详情。",
"tagRoutingDescription": "您可以使用标签,根据请求中携带的标签来限制特定 LLM 的使用。了解更多标签路由内容,请查阅 <docsLink>文档</docsLink>。",
"createNewTagButton": "+ 创建新标签",
"deleteTagTitle": "删除标签",
"deleteTagConfirm": "确定要删除此标签吗?",
"errorFetchingTags": "获取标签失败:{{error}}",
"tagCreatedSuccess": "标签创建成功",
"errorCreatingTag": "创建标签失败:{{error}}",
"tagDeletedSuccess": "标签删除成功",
"errorDeletingTag": "删除标签失败:{{error}}",
"errorFetchingModels": "获取模型失败:{{error}}"
},
"tagInfo": {
"backToTags": "← 返回标签列表",
"tagNameLabel": "标签名称:",
"noDescription": "暂无描述",
"editTagButton": "编辑标签",
"tagNameRequired": "请输入标签名称",
"allowedModelsLabel": "允许的模型",
"allowedModelsTooltip": "选择允许处理此类数据的模型",
"selectModelsPlaceholder": "选择模型",
"budgetRateLimits": "预算与速率限制",
"maxBudgetLabel": "最大预算(美元)",
"maxBudgetTooltip": "此标签可花费的最大金额(美元)",
"resetBudgetLabel": "重置预算",
"resetBudgetTooltip": "预算重置频率",
"tpmRpmNotSupported": "标签的 TPM/RPM 限制暂不支持。如需此功能,请 <githubLink>创建 GitHub Issue</githubLink>。",
"saveChanges": "保存更改",
"tagDetails": "标签详情",
"allModels": "所有模型",
"created": "创建时间",
"lastUpdated": "最后更新",
"maxBudget": "最大预算",
"budgetDuration": "预算周期",
"tpmLimit": "TPM 限制",
"rpmLimit": "RPM 限制",
"errorFetchingTagDetails": "获取标签详情失败:{{error}}",
"tagUpdatedSuccess": "标签更新成功",
"errorUpdatingTag": "更新标签失败:{{error}}"
}
}
}