mirror of
https://github.com/BerriAI/litellm.git
synced 2026-10-04 02:31:27 +00:00
feat(ui): i18n for tag management
This commit is contained in:
parent
f6bf079554
commit
2f069a0a5f
7 changed files with 293 additions and 103 deletions
|
|
@ -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}
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
)}
|
||||
|
|
|
|||
|
|
@ -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}}"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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}}"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue