mirror of
https://github.com/BerriAI/litellm.git
synced 2026-10-04 02:31:27 +00:00
feat(ui): i18n for admin settings batch 1
This commit is contained in:
parent
2932904741
commit
552da4110d
12 changed files with 637 additions and 154 deletions
|
|
@ -5,33 +5,37 @@ import { useUpdateHashicorpVaultConfig } from "@/app/(dashboard)/hooks/configOve
|
|||
import useAuthorized from "@/app/(dashboard)/hooks/useAuthorized";
|
||||
import NotificationManager from "@/components/molecules/notifications_manager";
|
||||
import { Button, Divider, Form, Input, Modal, Space, Typography } from "antd";
|
||||
import React, { useEffect } from "react";
|
||||
import React, { useEffect, useMemo } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import type { TFunction } from "i18next";
|
||||
import { SENSITIVE_FIELDS, FIELD_LABELS } from "./constants";
|
||||
|
||||
type TranslateFn = TFunction;
|
||||
|
||||
interface FieldGroup {
|
||||
title: string;
|
||||
subtitle?: string;
|
||||
fields: string[];
|
||||
}
|
||||
|
||||
const FIELD_GROUPS: FieldGroup[] = [
|
||||
const getFieldGroups = (t: TFunction): FieldGroup[] => [
|
||||
{
|
||||
title: "Connection",
|
||||
title: t("settingsPages.editHashicorpVaultModal.groupConnection"),
|
||||
fields: ["vault_addr", "vault_namespace", "vault_mount_name", "vault_path_prefix"],
|
||||
},
|
||||
{
|
||||
title: "Token Authentication",
|
||||
subtitle: "Use a Vault token to authenticate. Only one auth method is required.",
|
||||
title: t("settingsPages.editHashicorpVaultModal.groupTokenAuth"),
|
||||
subtitle: t("settingsPages.editHashicorpVaultModal.groupTokenAuthSubtitle"),
|
||||
fields: ["vault_token"],
|
||||
},
|
||||
{
|
||||
title: "AppRole Authentication",
|
||||
subtitle: "Use AppRole credentials to authenticate. Only one auth method is required.",
|
||||
title: t("settingsPages.editHashicorpVaultModal.groupAppRoleAuth"),
|
||||
subtitle: t("settingsPages.editHashicorpVaultModal.groupAppRoleAuthSubtitle"),
|
||||
fields: ["approle_role_id", "approle_secret_id", "approle_mount_path"],
|
||||
},
|
||||
{
|
||||
title: "TLS",
|
||||
subtitle: "Optional client certificate for mTLS.",
|
||||
title: t("settingsPages.editHashicorpVaultModal.groupTls"),
|
||||
subtitle: t("settingsPages.editHashicorpVaultModal.groupTlsSubtitle"),
|
||||
fields: ["client_cert", "client_key", "vault_cert_role"],
|
||||
},
|
||||
];
|
||||
|
|
@ -43,11 +47,14 @@ interface EditHashicorpVaultModalProps {
|
|||
}
|
||||
|
||||
const EditHashicorpVaultModal: React.FC<EditHashicorpVaultModalProps> = ({ isVisible, onCancel, onSuccess }) => {
|
||||
const { t } = useTranslation();
|
||||
const [form] = Form.useForm();
|
||||
const { accessToken } = useAuthorized();
|
||||
const { data } = useHashicorpVaultConfig();
|
||||
const { mutate, isPending } = useUpdateHashicorpVaultConfig(accessToken);
|
||||
|
||||
const fieldGroups = useMemo(() => getFieldGroups(t), [t]);
|
||||
|
||||
const schema = data?.field_schema;
|
||||
const properties = schema?.properties ?? {};
|
||||
const rawValues = data?.values ?? {};
|
||||
|
|
@ -81,7 +88,7 @@ const EditHashicorpVaultModal: React.FC<EditHashicorpVaultModalProps> = ({ isVis
|
|||
|
||||
mutate(config, {
|
||||
onSuccess: () => {
|
||||
NotificationManager.success("Hashicorp Vault configuration updated successfully");
|
||||
NotificationManager.success(t("settingsPages.editHashicorpVaultModal.saveSuccess"));
|
||||
onSuccess();
|
||||
},
|
||||
onError: (err) => {
|
||||
|
|
@ -101,13 +108,15 @@ const EditHashicorpVaultModal: React.FC<EditHashicorpVaultModalProps> = ({ isVis
|
|||
|
||||
const rules =
|
||||
fieldName === "vault_addr"
|
||||
? [{ pattern: /^https?:\/\/.+/, message: "Must start with http:// or https://" }]
|
||||
? [{ pattern: /^https?:\/\/.+/, message: t("settingsPages.editHashicorpVaultModal.urlPatternMessage") }]
|
||||
: undefined;
|
||||
|
||||
const isSensitive = SENSITIVE_FIELDS.has(fieldName);
|
||||
const existingValue = rawValues[fieldName];
|
||||
const hasExistingValue = isSensitive && existingValue != null && existingValue !== "";
|
||||
const placeholder = hasExistingValue ? `Leave blank to keep existing (${existingValue})` : fieldSchema?.description;
|
||||
const placeholder = hasExistingValue
|
||||
? t("settingsPages.editHashicorpVaultModal.leaveBlankToKeep", { existing: existingValue })
|
||||
: fieldSchema?.description;
|
||||
|
||||
return (
|
||||
<Form.Item key={fieldName} name={fieldName} label={FIELD_LABELS[fieldName] ?? fieldName} rules={rules}>
|
||||
|
|
@ -118,23 +127,23 @@ const EditHashicorpVaultModal: React.FC<EditHashicorpVaultModalProps> = ({ isVis
|
|||
|
||||
return (
|
||||
<Modal
|
||||
title="Edit Hashicorp Vault Configuration"
|
||||
title={t("settingsPages.editHashicorpVaultModal.title")}
|
||||
open={isVisible}
|
||||
width={700}
|
||||
footer={
|
||||
<Space>
|
||||
<Button onClick={handleCancel} disabled={isPending}>
|
||||
Cancel
|
||||
{t("common.cancel")}
|
||||
</Button>
|
||||
<Button type="primary" loading={isPending} onClick={() => form.submit()}>
|
||||
{isPending ? "Saving..." : "Save"}
|
||||
{isPending ? t("common.saving") : t("common.save")}
|
||||
</Button>
|
||||
</Space>
|
||||
}
|
||||
onCancel={handleCancel}
|
||||
>
|
||||
<Form form={form} layout="vertical" onFinish={handleSubmit}>
|
||||
{FIELD_GROUPS.map((group, index) => (
|
||||
{fieldGroups.map((group, index) => (
|
||||
<div key={group.title}>
|
||||
{index > 0 && <Divider />}
|
||||
<Typography.Title level={5} style={{ marginBottom: 4 }}>
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@ import NotificationManager from "@/components/molecules/notifications_manager";
|
|||
import { testHashicorpVaultConnection } from "@/app/(dashboard)/hooks/configOverrides/hashicorpVaultApi";
|
||||
import { Alert, Button, Card, Descriptions, Flex, Skeleton, Space, Typography } from "antd";
|
||||
import { Edit, KeyRound, PlugZap, Trash2 } from "lucide-react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { SENSITIVE_FIELDS, FIELD_LABELS } from "./constants";
|
||||
import EditHashicorpVaultModal from "./EditHashicorpVaultModal";
|
||||
import HashicorpVaultEmptyPlaceholder from "./HashicorpVaultEmptyPlaceholder";
|
||||
|
|
@ -28,6 +29,7 @@ const descriptionsConfig = {
|
|||
};
|
||||
|
||||
export default function HashicorpVault() {
|
||||
const { t } = useTranslation();
|
||||
const { accessToken } = useAuthorized();
|
||||
const { data, isLoading, isError, error } = useHashicorpVaultConfig();
|
||||
const { mutate: deleteConfig, isPending: isDeleting } = useDeleteHashicorpVaultConfig(accessToken);
|
||||
|
|
@ -46,7 +48,7 @@ export default function HashicorpVault() {
|
|||
setIsTesting(true);
|
||||
try {
|
||||
const result = await testHashicorpVaultConnection(accessToken);
|
||||
NotificationManager.success(result.message || "Connection to Vault successful!");
|
||||
NotificationManager.success(result.message || t("settingsPages.hashicorpVault.connectionSuccess"));
|
||||
} catch (err) {
|
||||
NotificationManager.fromBackend(err);
|
||||
} finally {
|
||||
|
|
@ -57,7 +59,7 @@ export default function HashicorpVault() {
|
|||
const handleDelete = () => {
|
||||
deleteConfig(undefined, {
|
||||
onSuccess: () => {
|
||||
NotificationManager.success("Hashicorp Vault configuration deleted");
|
||||
NotificationManager.success(t("settingsPages.hashicorpVault.deleteSuccess"));
|
||||
setIsDeleteModalOpen(false);
|
||||
},
|
||||
onError: (err) => {
|
||||
|
|
@ -72,7 +74,11 @@ export default function HashicorpVault() {
|
|||
{ [clearingField]: "" },
|
||||
{
|
||||
onSuccess: () => {
|
||||
NotificationManager.success(`${FIELD_LABELS[clearingField] ?? clearingField} cleared`);
|
||||
NotificationManager.success(
|
||||
t("settingsPages.hashicorpVault.clearSuccess", {
|
||||
fieldLabel: FIELD_LABELS[clearingField] ?? clearingField,
|
||||
}),
|
||||
);
|
||||
setClearingField(null);
|
||||
},
|
||||
onError: (err) => {
|
||||
|
|
@ -85,7 +91,7 @@ export default function HashicorpVault() {
|
|||
const renderValue = (key: string) => {
|
||||
const value = rawValues[key];
|
||||
if (!value) {
|
||||
return <span className="text-gray-400 italic">Not configured</span>;
|
||||
return <span className="text-gray-400 italic">{t("settingsPages.hashicorpVault.notConfigured")}</span>;
|
||||
}
|
||||
if (SENSITIVE_FIELDS.has(key)) {
|
||||
return (
|
||||
|
|
@ -112,7 +118,7 @@ export default function HashicorpVault() {
|
|||
|
||||
return (
|
||||
<Descriptions bordered {...descriptionsConfig}>
|
||||
<Descriptions.Item label="Auth Method">
|
||||
<Descriptions.Item label={t("settingsPages.hashicorpVault.authMethod")}>
|
||||
<Text>{detectAuthMethod(rawValues)}</Text>
|
||||
</Descriptions.Item>
|
||||
{fieldsToShow.map(([key]) => (
|
||||
|
|
@ -134,7 +140,7 @@ export default function HashicorpVault() {
|
|||
<Card>
|
||||
<Alert
|
||||
type="error"
|
||||
message="Could not load Hashicorp Vault configuration"
|
||||
message={t("settingsPages.hashicorpVault.loadError")}
|
||||
description={error instanceof Error ? error.message : undefined}
|
||||
/>
|
||||
</Card>
|
||||
|
|
@ -149,7 +155,7 @@ export default function HashicorpVault() {
|
|||
<Title level={3} style={{ marginBottom: 0 }}>
|
||||
Hashicorp Vault
|
||||
</Title>
|
||||
<Text type="secondary">Manage secret manager configuration</Text>
|
||||
<Text type="secondary">{t("settingsPages.hashicorpVault.manageSecretManager")}</Text>
|
||||
</div>
|
||||
</Flex>
|
||||
|
||||
|
|
@ -157,13 +163,13 @@ export default function HashicorpVault() {
|
|||
{isConfigured && (
|
||||
<>
|
||||
<Button icon={<PlugZap className="w-4 h-4" />} loading={isTesting} onClick={handleTestConnection}>
|
||||
Test Connection
|
||||
{t("settingsPages.hashicorpVault.testConnection")}
|
||||
</Button>
|
||||
<Button icon={<Edit className="w-4 h-4" />} onClick={() => setIsEditModalVisible(true)}>
|
||||
Edit Configuration
|
||||
{t("settingsPages.hashicorpVault.editConfiguration")}
|
||||
</Button>
|
||||
<Button danger icon={<Trash2 className="w-4 h-4" />} onClick={() => setIsDeleteModalOpen(true)}>
|
||||
Delete Configuration
|
||||
{t("settingsPages.hashicorpVault.deleteConfiguration")}
|
||||
</Button>
|
||||
</>
|
||||
)}
|
||||
|
|
@ -174,7 +180,7 @@ export default function HashicorpVault() {
|
|||
<Alert
|
||||
type="info"
|
||||
showIcon
|
||||
message={'Secrets must be stored with the field name "key"'}
|
||||
message={t("settingsPages.hashicorpVault.secretsKeyHint")}
|
||||
description={
|
||||
<>
|
||||
<Text code>vault kv put secret/SECRET_NAME key=secret_value</Text>
|
||||
|
|
@ -183,7 +189,7 @@ export default function HashicorpVault() {
|
|||
href="https://docs.litellm.ai/docs/secret_managers/hashicorp_vault"
|
||||
target="_blank"
|
||||
>
|
||||
View documentation
|
||||
{t("settingsPages.hashicorpVault.viewDocumentation")}
|
||||
</Typography.Link>
|
||||
</>
|
||||
}
|
||||
|
|
@ -207,10 +213,12 @@ export default function HashicorpVault() {
|
|||
|
||||
<DeleteResourceModal
|
||||
isOpen={isDeleteModalOpen}
|
||||
title="Delete Hashicorp Vault Configuration?"
|
||||
message="Models using Vault secrets will lose access to their API keys until a new configuration is saved."
|
||||
resourceInformationTitle="Vault Configuration"
|
||||
resourceInformation={[{ label: "Vault Address", value: rawValues.vault_addr }]}
|
||||
title={t("settingsPages.hashicorpVault.deleteTitle")}
|
||||
message={t("settingsPages.hashicorpVault.deleteMessage")}
|
||||
resourceInformationTitle={t("settingsPages.hashicorpVault.deleteResourceTitle")}
|
||||
resourceInformation={[
|
||||
{ label: t("settingsPages.hashicorpVault.vaultAddressLabel"), value: rawValues.vault_addr },
|
||||
]}
|
||||
onCancel={() => setIsDeleteModalOpen(false)}
|
||||
onOk={handleDelete}
|
||||
confirmLoading={isDeleting}
|
||||
|
|
@ -218,11 +226,16 @@ export default function HashicorpVault() {
|
|||
|
||||
<DeleteResourceModal
|
||||
isOpen={clearingField !== null}
|
||||
title={`Clear ${clearingField ? FIELD_LABELS[clearingField] ?? clearingField : ""}?`}
|
||||
message="This will remove the stored value."
|
||||
resourceInformationTitle="Field"
|
||||
title={t("settingsPages.hashicorpVault.clearTitle", {
|
||||
fieldLabel: clearingField ? FIELD_LABELS[clearingField] ?? clearingField : "",
|
||||
})}
|
||||
message={t("settingsPages.hashicorpVault.clearMessage")}
|
||||
resourceInformationTitle={t("settingsPages.hashicorpVault.clearResourceTitle")}
|
||||
resourceInformation={[
|
||||
{ label: "Field", value: clearingField ? FIELD_LABELS[clearingField] ?? clearingField : "" },
|
||||
{
|
||||
label: t("settingsPages.hashicorpVault.clearFieldLabel"),
|
||||
value: clearingField ? FIELD_LABELS[clearingField] ?? clearingField : "",
|
||||
},
|
||||
]}
|
||||
onCancel={() => setClearingField(null)}
|
||||
onOk={handleClearField}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
import { Empty, Typography, Button } from "antd";
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
const { Title, Paragraph } = Typography;
|
||||
|
||||
|
|
@ -7,21 +8,22 @@ interface HashicorpVaultEmptyPlaceholderProps {
|
|||
}
|
||||
|
||||
export default function HashicorpVaultEmptyPlaceholder({ onAdd }: HashicorpVaultEmptyPlaceholderProps) {
|
||||
const { t } = useTranslation();
|
||||
return (
|
||||
<div className="bg-white p-12 rounded-lg border border-dashed border-gray-300 text-center w-full">
|
||||
<Empty
|
||||
image={Empty.PRESENTED_IMAGE_SIMPLE}
|
||||
description={
|
||||
<div className="space-y-2">
|
||||
<Title level={4}>No Vault Configuration Found</Title>
|
||||
<Title level={4}>{t("settingsPages.hashicorpVaultEmptyPlaceholder.noConfigTitle")}</Title>
|
||||
<Paragraph type="secondary" className="max-w-md mx-auto">
|
||||
Configure Hashicorp Vault to securely manage provider API keys and secrets for your LiteLLM deployment.
|
||||
{t("settingsPages.hashicorpVaultEmptyPlaceholder.noConfigDesc")}
|
||||
</Paragraph>
|
||||
</div>
|
||||
}
|
||||
>
|
||||
<Button type="primary" size="large" onClick={onAdd} className="flex items-center gap-2 mx-auto mt-4">
|
||||
Configure Vault
|
||||
{t("settingsPages.hashicorpVaultEmptyPlaceholder.configureButton")}
|
||||
</Button>
|
||||
</Empty>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -15,8 +15,10 @@ import { parseErrorMessage } from "@/components/shared/errorUtils";
|
|||
import { ClockCircleOutlined } from "@ant-design/icons";
|
||||
import { Button, Card, Form, Input, Skeleton, Space, Switch, Typography } from "antd";
|
||||
import React, { useMemo } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
const LoggingSettings: React.FC = () => {
|
||||
const { t } = useTranslation();
|
||||
const [form] = Form.useForm();
|
||||
const { mutate, isPending } = useStoreRequestInSpendLogs();
|
||||
const { mutate: deleteField, isPending: isDeletingField } = useDeleteProxyConfigField();
|
||||
|
|
@ -53,9 +55,11 @@ const LoggingSettings: React.FC = () => {
|
|||
|
||||
const submitUpdate = () =>
|
||||
mutate(updateParams, {
|
||||
onSuccess: () => NotificationsManager.success("Spend logs settings updated successfully"),
|
||||
onSuccess: () => NotificationsManager.success(t("settingsPages.loggingSettings.saveSuccess")),
|
||||
onError: (error) =>
|
||||
NotificationsManager.fromBackend("Failed to save spend logs settings: " + parseErrorMessage(error)),
|
||||
NotificationsManager.fromBackend(
|
||||
t("settingsPages.loggingSettings.saveFailed", { error: parseErrorMessage(error) }),
|
||||
),
|
||||
});
|
||||
|
||||
if (hasRetentionPeriod) {
|
||||
|
|
@ -76,10 +80,10 @@ const LoggingSettings: React.FC = () => {
|
|||
};
|
||||
|
||||
return (
|
||||
<Card title="Logging Settings">
|
||||
<Card title={t("settingsPages.loggingSettings.cardTitle")}>
|
||||
<Space direction="vertical" size="large" style={{ width: "100%" }}>
|
||||
<Typography.Paragraph style={{ marginBottom: 0 }} type="secondary">
|
||||
Proxy-wide settings that control how request and response data are written to spend logs.
|
||||
{t("settingsPages.loggingSettings.description")}
|
||||
</Typography.Paragraph>
|
||||
|
||||
<Form
|
||||
|
|
@ -90,11 +94,11 @@ const LoggingSettings: React.FC = () => {
|
|||
initialValues={initialValues}
|
||||
>
|
||||
<Form.Item
|
||||
label="Store Prompts in Spend Logs"
|
||||
label={t("settingsPages.loggingSettings.storePromptsLabel")}
|
||||
name="store_prompts_in_spend_logs"
|
||||
tooltip={
|
||||
proxyConfigData?.find((f) => f.field_name === "store_prompts_in_spend_logs")?.field_description ||
|
||||
"When enabled, prompts will be stored in spend logs for tracking and analysis purposes."
|
||||
t("settingsPages.loggingSettings.storePromptsTooltipDefault")
|
||||
}
|
||||
valuePropName="checked"
|
||||
>
|
||||
|
|
@ -109,23 +113,28 @@ const LoggingSettings: React.FC = () => {
|
|||
</Form.Item>
|
||||
|
||||
<Form.Item
|
||||
label="Maximum Spend Logs Retention Period (Optional)"
|
||||
label={t("settingsPages.loggingSettings.retentionPeriodLabel")}
|
||||
name="maximum_spend_logs_retention_period"
|
||||
tooltip={
|
||||
proxyConfigData?.find((f) => f.field_name === "maximum_spend_logs_retention_period")?.field_description ||
|
||||
"Set the maximum retention period for spend logs (e.g., '7d' for 7 days, '30d' for 30 days). Leave empty for no limit."
|
||||
t("settingsPages.loggingSettings.retentionPeriodTooltipDefault")
|
||||
}
|
||||
>
|
||||
{isLoadingConfig ? (
|
||||
<Skeleton.Input active block />
|
||||
) : (
|
||||
<Input placeholder="e.g., 7d, 30d" prefix={<ClockCircleOutlined />} />
|
||||
<Input
|
||||
placeholder={t("settingsPages.loggingSettings.retentionPeriodPlaceholder")}
|
||||
prefix={<ClockCircleOutlined />}
|
||||
/>
|
||||
)}
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item>
|
||||
<Button type="primary" htmlType="submit" loading={isPending || isDeletingField} disabled={isLoadingConfig}>
|
||||
{isPending || isDeletingField ? "Saving..." : "Save Settings"}
|
||||
{isPending || isDeletingField
|
||||
? t("common.saving")
|
||||
: t("settingsPages.loggingSettings.saveSettingsButton")}
|
||||
</Button>
|
||||
</Form.Item>
|
||||
</Form>
|
||||
|
|
|
|||
|
|
@ -21,6 +21,7 @@ import {
|
|||
} from "antd";
|
||||
import { QuestionCircleOutlined, CheckCircleOutlined, SaveOutlined } from "@ant-design/icons";
|
||||
import { useEffect, useState } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { fetchAvailableModels, ModelGroup } from "@/components/playground/llm_calls/fetch_models";
|
||||
import MCPSemanticFilterTestPanel from "./MCPSemanticFilterTestPanel";
|
||||
import { getCurlCommand, runSemanticFilterTest, TestResult } from "./semanticFilterTestUtils";
|
||||
|
|
@ -30,6 +31,7 @@ interface MCPSemanticFilterSettingsProps {
|
|||
}
|
||||
|
||||
export default function MCPSemanticFilterSettings({ accessToken }: MCPSemanticFilterSettingsProps) {
|
||||
const { t } = useTranslation();
|
||||
const { data, isLoading, isError, error } = useMCPSemanticFilterSettings();
|
||||
const {
|
||||
mutate: updateSettings,
|
||||
|
|
@ -89,9 +91,7 @@ export default function MCPSemanticFilterSettings({ accessToken }: MCPSemanticFi
|
|||
setIsDirty(false);
|
||||
setSaveSuccess(true);
|
||||
setTimeout(() => setSaveSuccess(false), 3000);
|
||||
NotificationManager.success(
|
||||
"Settings updated successfully. Changes will be applied across all pods within 10 seconds.",
|
||||
);
|
||||
NotificationManager.success(t("settingsPages.mcpSemanticFilterSettings.saveSuccess"));
|
||||
},
|
||||
onError: (error) => {
|
||||
NotificationManager.fromBackend(error);
|
||||
|
|
@ -117,7 +117,9 @@ export default function MCPSemanticFilterSettings({ accessToken }: MCPSemanticFi
|
|||
};
|
||||
|
||||
if (!accessToken) {
|
||||
return <div className="p-6 text-center text-gray-500">Please log in to configure semantic filter settings.</div>;
|
||||
return (
|
||||
<div className="p-6 text-center text-gray-500">{t("settingsPages.mcpSemanticFilterSettings.loginRequired")}</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
|
|
@ -127,7 +129,7 @@ export default function MCPSemanticFilterSettings({ accessToken }: MCPSemanticFi
|
|||
) : isError ? (
|
||||
<Alert
|
||||
type="error"
|
||||
message="Could not load MCP Semantic Filter settings"
|
||||
message={t("settingsPages.mcpSemanticFilterSettings.loadError")}
|
||||
description={error instanceof Error ? error.message : undefined}
|
||||
style={{ marginBottom: 24 }}
|
||||
/>
|
||||
|
|
@ -135,8 +137,8 @@ export default function MCPSemanticFilterSettings({ accessToken }: MCPSemanticFi
|
|||
<>
|
||||
<Alert
|
||||
type="info"
|
||||
message="Semantic Tool Filtering"
|
||||
description="Filter MCP tools semantically based on query relevance. This reduces context window size and improves tool selection accuracy. Click 'Save Settings' to apply changes across all pods (takes effect within 10 seconds)."
|
||||
message={t("settingsPages.mcpSemanticFilterSettings.infoTitle")}
|
||||
description={t("settingsPages.mcpSemanticFilterSettings.infoDesc")}
|
||||
showIcon
|
||||
style={{ marginBottom: 24 }}
|
||||
/>
|
||||
|
|
@ -144,7 +146,7 @@ export default function MCPSemanticFilterSettings({ accessToken }: MCPSemanticFi
|
|||
{saveSuccess && (
|
||||
<Alert
|
||||
type="success"
|
||||
message="Settings saved successfully"
|
||||
message={t("settingsPages.mcpSemanticFilterSettings.savedSuccessMessage")}
|
||||
icon={<CheckCircleOutlined />}
|
||||
showIcon
|
||||
closable
|
||||
|
|
@ -155,7 +157,7 @@ export default function MCPSemanticFilterSettings({ accessToken }: MCPSemanticFi
|
|||
{updateError && (
|
||||
<Alert
|
||||
type="error"
|
||||
message="Could not update settings"
|
||||
message={t("settingsPages.mcpSemanticFilterSettings.updateErrorTitle")}
|
||||
description={updateError instanceof Error ? updateError.message : undefined}
|
||||
style={{ marginBottom: 16 }}
|
||||
/>
|
||||
|
|
@ -177,8 +179,10 @@ export default function MCPSemanticFilterSettings({ accessToken }: MCPSemanticFi
|
|||
name="enabled"
|
||||
label={
|
||||
<Space>
|
||||
<Typography.Text strong>Enable Semantic Filtering</Typography.Text>
|
||||
<Tooltip title="When enabled, only the most relevant MCP tools will be included in requests based on semantic similarity">
|
||||
<Typography.Text strong>
|
||||
{t("settingsPages.mcpSemanticFilterSettings.enableLabel")}
|
||||
</Typography.Text>
|
||||
<Tooltip title={t("settingsPages.mcpSemanticFilterSettings.enableTooltip")}>
|
||||
<QuestionCircleOutlined style={{ color: "#8c8c8c" }} />
|
||||
</Tooltip>
|
||||
</Space>
|
||||
|
|
@ -193,13 +197,15 @@ export default function MCPSemanticFilterSettings({ accessToken }: MCPSemanticFi
|
|||
</Typography.Text>
|
||||
</Card>
|
||||
|
||||
<Card title="Configuration" style={{ marginBottom: 16 }}>
|
||||
<Card title={t("settingsPages.mcpSemanticFilterSettings.configCardTitle")} style={{ marginBottom: 16 }}>
|
||||
<Form.Item
|
||||
name="embedding_model"
|
||||
label={
|
||||
<Space>
|
||||
<Typography.Text strong>Embedding Model</Typography.Text>
|
||||
<Tooltip title="The model used to generate embeddings for semantic matching">
|
||||
<Typography.Text strong>
|
||||
{t("settingsPages.mcpSemanticFilterSettings.embeddingModelLabel")}
|
||||
</Typography.Text>
|
||||
<Tooltip title={t("settingsPages.mcpSemanticFilterSettings.embeddingModelTooltip")}>
|
||||
<QuestionCircleOutlined style={{ color: "#8c8c8c" }} />
|
||||
</Tooltip>
|
||||
</Space>
|
||||
|
|
@ -210,11 +216,19 @@ export default function MCPSemanticFilterSettings({ accessToken }: MCPSemanticFi
|
|||
label: model.model_group,
|
||||
value: model.model_group,
|
||||
}))}
|
||||
placeholder={loadingModels ? "Loading models..." : "Select embedding model"}
|
||||
placeholder={
|
||||
loadingModels
|
||||
? t("settingsPages.mcpSemanticFilterSettings.loadingModels")
|
||||
: t("settingsPages.mcpSemanticFilterSettings.selectEmbeddingModel")
|
||||
}
|
||||
showSearch
|
||||
disabled={isUpdating || loadingModels}
|
||||
loading={loadingModels}
|
||||
notFoundContent={loadingModels ? "Loading..." : "No embedding models available"}
|
||||
notFoundContent={
|
||||
loadingModels
|
||||
? t("common.loading")
|
||||
: t("settingsPages.mcpSemanticFilterSettings.noEmbeddingModels")
|
||||
}
|
||||
/>
|
||||
</Form.Item>
|
||||
|
||||
|
|
@ -222,8 +236,10 @@ export default function MCPSemanticFilterSettings({ accessToken }: MCPSemanticFi
|
|||
name="top_k"
|
||||
label={
|
||||
<Space>
|
||||
<Typography.Text strong>Top K Results</Typography.Text>
|
||||
<Tooltip title="Maximum number of tools to return after filtering">
|
||||
<Typography.Text strong>
|
||||
{t("settingsPages.mcpSemanticFilterSettings.topKLabel")}
|
||||
</Typography.Text>
|
||||
<Tooltip title={t("settingsPages.mcpSemanticFilterSettings.topKTooltip")}>
|
||||
<QuestionCircleOutlined style={{ color: "#8c8c8c" }} />
|
||||
</Tooltip>
|
||||
</Space>
|
||||
|
|
@ -236,8 +252,10 @@ export default function MCPSemanticFilterSettings({ accessToken }: MCPSemanticFi
|
|||
name="similarity_threshold"
|
||||
label={
|
||||
<Space>
|
||||
<Typography.Text strong>Similarity Threshold</Typography.Text>
|
||||
<Tooltip title="Minimum similarity score (0-1) for a tool to be included">
|
||||
<Typography.Text strong>
|
||||
{t("settingsPages.mcpSemanticFilterSettings.similarityThresholdLabel")}
|
||||
</Typography.Text>
|
||||
<Tooltip title={t("settingsPages.mcpSemanticFilterSettings.similarityThresholdTooltip")}>
|
||||
<QuestionCircleOutlined style={{ color: "#8c8c8c" }} />
|
||||
</Tooltip>
|
||||
</Space>
|
||||
|
|
@ -267,7 +285,7 @@ export default function MCPSemanticFilterSettings({ accessToken }: MCPSemanticFi
|
|||
loading={isUpdating}
|
||||
disabled={!isDirty}
|
||||
>
|
||||
Save Settings
|
||||
{t("settingsPages.mcpSemanticFilterSettings.saveSettingsButton")}
|
||||
</Button>
|
||||
</div>
|
||||
</Form>
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
import { CodeOutlined, PlayCircleOutlined } from "@ant-design/icons";
|
||||
import { Alert, Button, Card, Input, Space, Tabs, Typography } from "antd";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import ModelSelector from "@/components/common_components/ModelSelector";
|
||||
import { TestResult } from "./semanticFilterTestUtils";
|
||||
|
||||
|
|
@ -28,22 +29,23 @@ export default function MCPSemanticFilterTestPanel({
|
|||
testResult,
|
||||
curlCommand,
|
||||
}: MCPSemanticFilterTestPanelProps) {
|
||||
const { t } = useTranslation();
|
||||
return (
|
||||
<Card title="Test Configuration" style={{ marginBottom: 16 }}>
|
||||
<Card title={t("settingsPages.mcpSemanticFilterTestPanel.testConfigCardTitle")} style={{ marginBottom: 16 }}>
|
||||
<Tabs
|
||||
defaultActiveKey="test"
|
||||
items={[
|
||||
{
|
||||
key: "test",
|
||||
label: "Test",
|
||||
label: t("settingsPages.mcpSemanticFilterTestPanel.testTabLabel"),
|
||||
children: (
|
||||
<Space direction="vertical" style={{ width: "100%" }} size="large">
|
||||
<div>
|
||||
<Typography.Text strong style={{ display: "block", marginBottom: 8 }}>
|
||||
<PlayCircleOutlined /> Test Query
|
||||
<PlayCircleOutlined /> {t("settingsPages.mcpSemanticFilterTestPanel.testQueryLabel")}
|
||||
</Typography.Text>
|
||||
<Input.TextArea
|
||||
placeholder="Enter a test query to see which tools would be selected..."
|
||||
placeholder={t("settingsPages.mcpSemanticFilterTestPanel.testQueryPlaceholder")}
|
||||
value={testQuery}
|
||||
onChange={(e) => setTestQuery(e.target.value)}
|
||||
rows={4}
|
||||
|
|
@ -58,7 +60,7 @@ export default function MCPSemanticFilterTestPanel({
|
|||
onChange={setTestModel}
|
||||
disabled={isTesting}
|
||||
showLabel={true}
|
||||
labelText="Select Model"
|
||||
labelText={t("settingsPages.mcpSemanticFilterTestPanel.selectModelLabel")}
|
||||
/>
|
||||
</div>
|
||||
|
||||
|
|
@ -70,31 +72,37 @@ export default function MCPSemanticFilterTestPanel({
|
|||
disabled={!testQuery || !testModel || !filterEnabled}
|
||||
block
|
||||
>
|
||||
Test Filter
|
||||
{t("settingsPages.mcpSemanticFilterTestPanel.testFilterButton")}
|
||||
</Button>
|
||||
|
||||
{!filterEnabled && (
|
||||
<Alert
|
||||
type="warning"
|
||||
message="Semantic filtering is disabled"
|
||||
description="Enable semantic filtering and save settings to test the filter."
|
||||
message={t("settingsPages.mcpSemanticFilterTestPanel.filterDisabledTitle")}
|
||||
description={t("settingsPages.mcpSemanticFilterTestPanel.filterDisabledDesc")}
|
||||
showIcon
|
||||
/>
|
||||
)}
|
||||
|
||||
{testResult && (
|
||||
<div>
|
||||
<Typography.Title level={5}>Results</Typography.Title>
|
||||
<Typography.Title level={5}>
|
||||
{t("settingsPages.mcpSemanticFilterTestPanel.resultsTitle")}
|
||||
</Typography.Title>
|
||||
<Alert
|
||||
type="success"
|
||||
message={`${testResult.selectedTools} tools selected`}
|
||||
description={`Filtered from ${testResult.totalTools} available tools`}
|
||||
message={t("settingsPages.mcpSemanticFilterTestPanel.toolsSelectedMessage", {
|
||||
selectedTools: testResult.selectedTools,
|
||||
})}
|
||||
description={t("settingsPages.mcpSemanticFilterTestPanel.filteredFromMessage", {
|
||||
totalTools: testResult.totalTools,
|
||||
})}
|
||||
showIcon
|
||||
style={{ marginBottom: 16 }}
|
||||
/>
|
||||
<div>
|
||||
<Typography.Text strong style={{ display: "block", marginBottom: 8 }}>
|
||||
Selected Tools:
|
||||
{t("settingsPages.mcpSemanticFilterTestPanel.selectedToolsLabel")}
|
||||
</Typography.Text>
|
||||
<ul style={{ paddingLeft: 20, margin: 0 }}>
|
||||
{testResult.tools.map((tool, index) => (
|
||||
|
|
@ -111,30 +119,36 @@ export default function MCPSemanticFilterTestPanel({
|
|||
},
|
||||
{
|
||||
key: "api",
|
||||
label: "API Usage",
|
||||
label: t("settingsPages.mcpSemanticFilterTestPanel.apiTabLabel"),
|
||||
children: (
|
||||
<div>
|
||||
<Space style={{ marginBottom: 8 }}>
|
||||
<CodeOutlined />
|
||||
<Typography.Text strong>API Usage</Typography.Text>
|
||||
<Typography.Text strong>
|
||||
{t("settingsPages.mcpSemanticFilterTestPanel.apiUsageTitle")}
|
||||
</Typography.Text>
|
||||
</Space>
|
||||
<Typography.Text type="secondary" style={{ display: "block", marginBottom: 8 }}>
|
||||
Use this curl command to test the semantic filter with your current configuration.
|
||||
{t("settingsPages.mcpSemanticFilterTestPanel.apiUsageDesc")}
|
||||
</Typography.Text>
|
||||
<Typography.Text strong style={{ display: "block", marginBottom: 8 }}>
|
||||
Response headers to check:
|
||||
{t("settingsPages.mcpSemanticFilterTestPanel.responseHeadersLabel")}
|
||||
</Typography.Text>
|
||||
<ul style={{ paddingLeft: 20, margin: "0 0 12px 0" }}>
|
||||
<li>
|
||||
<Typography.Text>x-litellm-semantic-filter: shows total tools → selected tools</Typography.Text>
|
||||
<Typography.Text>
|
||||
{t("settingsPages.mcpSemanticFilterTestPanel.semanticFilterHeader")}
|
||||
</Typography.Text>
|
||||
<Typography.Text type="secondary" style={{ display: "block" }}>
|
||||
Example: 10→3
|
||||
{t("settingsPages.mcpSemanticFilterTestPanel.semanticFilterHeaderExample")}
|
||||
</Typography.Text>
|
||||
</li>
|
||||
<li>
|
||||
<Typography.Text>x-litellm-semantic-filter-tools: CSV of selected tool names</Typography.Text>
|
||||
<Typography.Text>
|
||||
{t("settingsPages.mcpSemanticFilterTestPanel.semanticFilterToolsHeader")}
|
||||
</Typography.Text>
|
||||
<Typography.Text type="secondary" style={{ display: "block" }}>
|
||||
Example: wikipedia-fetch,github-search,slack-post
|
||||
{t("settingsPages.mcpSemanticFilterTestPanel.semanticFilterToolsHeaderExample")}
|
||||
</Typography.Text>
|
||||
</li>
|
||||
</ul>
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ import NotificationsManager from "@/components/molecules/notifications_manager";
|
|||
import { parseErrorMessage } from "@/components/shared/errorUtils";
|
||||
import { Button, Form, Modal, Space } from "antd";
|
||||
import React from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import BaseSSOSettingsForm from "./BaseSSOSettingsForm";
|
||||
import { useEditSSOSettings } from "@/app/(dashboard)/hooks/sso/useEditSSOSettings";
|
||||
import { processSSOSettingsPayload } from "../utils";
|
||||
|
|
@ -15,6 +16,7 @@ interface AddSSOSettingsModalProps {
|
|||
}
|
||||
|
||||
const AddSSOSettingsModal: React.FC<AddSSOSettingsModalProps> = ({ isVisible, onCancel, onSuccess }) => {
|
||||
const { t } = useTranslation();
|
||||
const [form] = Form.useForm();
|
||||
const { mutateAsync, isPending } = useEditSSOSettings();
|
||||
|
||||
|
|
@ -24,11 +26,13 @@ const AddSSOSettingsModal: React.FC<AddSSOSettingsModalProps> = ({ isVisible, on
|
|||
|
||||
await mutateAsync(payload, {
|
||||
onSuccess: () => {
|
||||
NotificationsManager.success("SSO settings added successfully");
|
||||
NotificationsManager.success(t("settingsPages.addSSOSettingsModal.addSuccess"));
|
||||
onSuccess();
|
||||
},
|
||||
onError: (error) => {
|
||||
NotificationsManager.fromBackend("Failed to save SSO settings: " + parseErrorMessage(error));
|
||||
NotificationsManager.fromBackend(
|
||||
t("settingsPages.addSSOSettingsModal.saveFailed", { error: parseErrorMessage(error) }),
|
||||
);
|
||||
},
|
||||
});
|
||||
};
|
||||
|
|
@ -40,16 +44,18 @@ const AddSSOSettingsModal: React.FC<AddSSOSettingsModalProps> = ({ isVisible, on
|
|||
|
||||
return (
|
||||
<Modal
|
||||
title="Add SSO"
|
||||
title={t("settingsPages.addSSOSettingsModal.title")}
|
||||
open={isVisible}
|
||||
width={800}
|
||||
footer={
|
||||
<Space>
|
||||
<Button onClick={handleCancel} disabled={isPending}>
|
||||
Cancel
|
||||
{t("common.cancel")}
|
||||
</Button>
|
||||
<Button loading={isPending} onClick={() => form.submit()}>
|
||||
{isPending ? "Adding..." : "Add SSO"}
|
||||
{isPending
|
||||
? t("settingsPages.addSSOSettingsModal.addingButton")
|
||||
: t("settingsPages.addSSOSettingsModal.addSSOButton")}
|
||||
</Button>
|
||||
</Space>
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,8 +3,11 @@
|
|||
import { TextInput } from "@tremor/react";
|
||||
import { Checkbox, Form, Input, Select } from "antd";
|
||||
import React from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { ssoProviderLogoMap, ssoProviderDisplayNames } from "../constants";
|
||||
|
||||
type TranslateFn = (key: string, options?: Record<string, any>) => string;
|
||||
|
||||
export interface BaseSSOSettingsFormProps {
|
||||
form: any; // Replace with proper Form type if available
|
||||
onFormSubmit: (formValues: Record<string, any>) => Promise<void>;
|
||||
|
|
@ -14,22 +17,28 @@ export interface BaseSSOSettingsFormProps {
|
|||
export interface SSOProviderConfig {
|
||||
envVarMap: Record<string, string>;
|
||||
fields: Array<{
|
||||
label: string;
|
||||
labelKey: string;
|
||||
name: string;
|
||||
placeholder?: string;
|
||||
}>;
|
||||
}
|
||||
|
||||
// Define configurations for each SSO provider
|
||||
export const ssoProviderConfigs: Record<string, SSOProviderConfig> = {
|
||||
export const getSSOProviderConfigs = (t: TranslateFn): Record<string, SSOProviderConfig> => ({
|
||||
google: {
|
||||
envVarMap: {
|
||||
google_client_id: "GOOGLE_CLIENT_ID",
|
||||
google_client_secret: "GOOGLE_CLIENT_SECRET",
|
||||
},
|
||||
fields: [
|
||||
{ label: "Google Client ID", name: "google_client_id" },
|
||||
{ label: "Google Client Secret", name: "google_client_secret" },
|
||||
{
|
||||
labelKey: t("settingsPages.baseSSOSettingsForm.fieldLabels.googleClientId"),
|
||||
name: "google_client_id",
|
||||
},
|
||||
{
|
||||
labelKey: t("settingsPages.baseSSOSettingsForm.fieldLabels.googleClientSecret"),
|
||||
name: "google_client_secret",
|
||||
},
|
||||
],
|
||||
},
|
||||
microsoft: {
|
||||
|
|
@ -39,9 +48,18 @@ export const ssoProviderConfigs: Record<string, SSOProviderConfig> = {
|
|||
microsoft_tenant: "MICROSOFT_TENANT",
|
||||
},
|
||||
fields: [
|
||||
{ label: "Microsoft Client ID", name: "microsoft_client_id" },
|
||||
{ label: "Microsoft Client Secret", name: "microsoft_client_secret" },
|
||||
{ label: "Microsoft Tenant", name: "microsoft_tenant" },
|
||||
{
|
||||
labelKey: t("settingsPages.baseSSOSettingsForm.fieldLabels.microsoftClientId"),
|
||||
name: "microsoft_client_id",
|
||||
},
|
||||
{
|
||||
labelKey: t("settingsPages.baseSSOSettingsForm.fieldLabels.microsoftClientSecret"),
|
||||
name: "microsoft_client_secret",
|
||||
},
|
||||
{
|
||||
labelKey: t("settingsPages.baseSSOSettingsForm.fieldLabels.microsoftTenant"),
|
||||
name: "microsoft_tenant",
|
||||
},
|
||||
],
|
||||
},
|
||||
okta: {
|
||||
|
|
@ -53,16 +71,26 @@ export const ssoProviderConfigs: Record<string, SSOProviderConfig> = {
|
|||
generic_userinfo_endpoint: "GENERIC_USERINFO_ENDPOINT",
|
||||
},
|
||||
fields: [
|
||||
{ label: "Generic Client ID", name: "generic_client_id" },
|
||||
{ label: "Generic Client Secret", name: "generic_client_secret" },
|
||||
{
|
||||
label: "Authorization Endpoint",
|
||||
labelKey: t("settingsPages.baseSSOSettingsForm.fieldLabels.genericClientId"),
|
||||
name: "generic_client_id",
|
||||
},
|
||||
{
|
||||
labelKey: t("settingsPages.baseSSOSettingsForm.fieldLabels.genericClientSecret"),
|
||||
name: "generic_client_secret",
|
||||
},
|
||||
{
|
||||
labelKey: t("settingsPages.baseSSOSettingsForm.fieldLabels.authorizationEndpoint"),
|
||||
name: "generic_authorization_endpoint",
|
||||
placeholder: "https://your-domain/authorize",
|
||||
},
|
||||
{ label: "Token Endpoint", name: "generic_token_endpoint", placeholder: "https://your-domain/token" },
|
||||
{
|
||||
label: "Userinfo Endpoint",
|
||||
labelKey: t("settingsPages.baseSSOSettingsForm.fieldLabels.tokenEndpoint"),
|
||||
name: "generic_token_endpoint",
|
||||
placeholder: "https://your-domain/token",
|
||||
},
|
||||
{
|
||||
labelKey: t("settingsPages.baseSSOSettingsForm.fieldLabels.userinfoEndpoint"),
|
||||
name: "generic_userinfo_endpoint",
|
||||
placeholder: "https://your-domain/userinfo",
|
||||
},
|
||||
|
|
@ -77,26 +105,47 @@ export const ssoProviderConfigs: Record<string, SSOProviderConfig> = {
|
|||
generic_userinfo_endpoint: "GENERIC_USERINFO_ENDPOINT",
|
||||
},
|
||||
fields: [
|
||||
{ label: "Generic Client ID", name: "generic_client_id" },
|
||||
{ label: "Generic Client Secret", name: "generic_client_secret" },
|
||||
{ label: "Authorization Endpoint", name: "generic_authorization_endpoint" },
|
||||
{ label: "Token Endpoint", name: "generic_token_endpoint" },
|
||||
{ label: "Userinfo Endpoint", name: "generic_userinfo_endpoint" },
|
||||
{
|
||||
labelKey: t("settingsPages.baseSSOSettingsForm.fieldLabels.genericClientId"),
|
||||
name: "generic_client_id",
|
||||
},
|
||||
{
|
||||
labelKey: t("settingsPages.baseSSOSettingsForm.fieldLabels.genericClientSecret"),
|
||||
name: "generic_client_secret",
|
||||
},
|
||||
{
|
||||
labelKey: t("settingsPages.baseSSOSettingsForm.fieldLabels.authorizationEndpoint"),
|
||||
name: "generic_authorization_endpoint",
|
||||
},
|
||||
{
|
||||
labelKey: t("settingsPages.baseSSOSettingsForm.fieldLabels.tokenEndpoint"),
|
||||
name: "generic_token_endpoint",
|
||||
},
|
||||
{
|
||||
labelKey: t("settingsPages.baseSSOSettingsForm.fieldLabels.userinfoEndpoint"),
|
||||
name: "generic_userinfo_endpoint",
|
||||
},
|
||||
],
|
||||
},
|
||||
};
|
||||
});
|
||||
|
||||
// Helper function to render provider fields
|
||||
export const renderProviderFields = (provider: string) => {
|
||||
const config = ssoProviderConfigs[provider];
|
||||
export const renderProviderFields = (provider: string, t: TranslateFn = (k: string) => k as any) => {
|
||||
const configs = getSSOProviderConfigs(t);
|
||||
const config = configs[provider];
|
||||
if (!config) return null;
|
||||
|
||||
return config.fields.map((field) => (
|
||||
<Form.Item
|
||||
key={field.name}
|
||||
label={field.label}
|
||||
label={field.labelKey}
|
||||
name={field.name}
|
||||
rules={[{ required: true, message: `Please enter the ${field.label.toLowerCase()}` }]}
|
||||
rules={[
|
||||
{
|
||||
required: true,
|
||||
message: t("settingsPages.baseSSOSettingsForm.fieldRequired", { label: field.labelKey.toLowerCase() }),
|
||||
},
|
||||
]}
|
||||
>
|
||||
{field.name.includes("client") ? <Input.Password /> : <TextInput placeholder={field.placeholder} />}
|
||||
</Form.Item>
|
||||
|
|
@ -104,13 +153,14 @@ export const renderProviderFields = (provider: string) => {
|
|||
};
|
||||
|
||||
const BaseSSOSettingsForm: React.FC<BaseSSOSettingsFormProps> = ({ form, onFormSubmit }) => {
|
||||
const { t } = useTranslation();
|
||||
return (
|
||||
<div>
|
||||
<Form form={form} onFinish={onFormSubmit} labelCol={{ span: 8 }} wrapperCol={{ span: 16 }} labelAlign="left">
|
||||
<Form.Item
|
||||
label="SSO Provider"
|
||||
label={t("settingsPages.baseSSOSettingsForm.ssoProviderLabel")}
|
||||
name="sso_provider"
|
||||
rules={[{ required: true, message: "Please select an SSO provider" }]}
|
||||
rules={[{ required: true, message: t("settingsPages.baseSSOSettingsForm.ssoProviderRequired") }]}
|
||||
>
|
||||
<Select>
|
||||
{Object.entries(ssoProviderLogoMap).map(([value, logo]) => (
|
||||
|
|
@ -138,32 +188,32 @@ const BaseSSOSettingsForm: React.FC<BaseSSOSettingsFormProps> = ({ form, onFormS
|
|||
>
|
||||
{({ getFieldValue }) => {
|
||||
const provider = getFieldValue("sso_provider");
|
||||
return provider ? renderProviderFields(provider) : null;
|
||||
return provider ? renderProviderFields(provider, t) : null;
|
||||
}}
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item
|
||||
label="Proxy Admin Email"
|
||||
label={t("settingsPages.baseSSOSettingsForm.proxyAdminEmailLabel")}
|
||||
name="user_email"
|
||||
rules={[{ required: true, message: "Please enter the email of the proxy admin" }]}
|
||||
rules={[{ required: true, message: t("settingsPages.baseSSOSettingsForm.proxyAdminEmailRequired") }]}
|
||||
>
|
||||
<TextInput />
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
label="Proxy Base URL"
|
||||
label={t("settingsPages.baseSSOSettingsForm.proxyBaseUrlLabel")}
|
||||
name="proxy_base_url"
|
||||
normalize={(value) => value?.trim()}
|
||||
rules={[
|
||||
{ required: true, message: "Please enter the proxy base url" },
|
||||
{ required: true, message: t("settingsPages.baseSSOSettingsForm.proxyBaseUrlRequired") },
|
||||
{
|
||||
pattern: /^https?:\/\/.+/,
|
||||
message: "URL must start with http:// or https://",
|
||||
message: t("settingsPages.baseSSOSettingsForm.proxyBaseUrlInvalidScheme"),
|
||||
},
|
||||
{
|
||||
validator: (_, value) => {
|
||||
// Only check for trailing slash if the URL starts with http:// or https://
|
||||
if (value && /^https?:\/\/.+/.test(value) && value.endsWith("/")) {
|
||||
return Promise.reject("URL must not end with a trailing slash");
|
||||
return Promise.reject(t("settingsPages.baseSSOSettingsForm.proxyBaseUrlTrailingSlash"));
|
||||
}
|
||||
return Promise.resolve();
|
||||
},
|
||||
|
|
@ -180,7 +230,11 @@ const BaseSSOSettingsForm: React.FC<BaseSSOSettingsFormProps> = ({ form, onFormS
|
|||
{({ getFieldValue }) => {
|
||||
const provider = getFieldValue("sso_provider");
|
||||
return provider === "okta" || provider === "generic" ? (
|
||||
<Form.Item label="Use Role Mappings" name="use_role_mappings" valuePropName="checked">
|
||||
<Form.Item
|
||||
label={t("settingsPages.baseSSOSettingsForm.useRoleMappingsLabel")}
|
||||
name="use_role_mappings"
|
||||
valuePropName="checked"
|
||||
>
|
||||
<Checkbox />
|
||||
</Form.Item>
|
||||
) : null;
|
||||
|
|
@ -200,9 +254,9 @@ const BaseSSOSettingsForm: React.FC<BaseSSOSettingsFormProps> = ({ form, onFormS
|
|||
const supportsRoleMappings = provider === "okta" || provider === "generic";
|
||||
return useRoleMappings && supportsRoleMappings ? (
|
||||
<Form.Item
|
||||
label="Group Claim"
|
||||
label={t("settingsPages.baseSSOSettingsForm.groupClaimLabel")}
|
||||
name="group_claim"
|
||||
rules={[{ required: true, message: "Please enter the group claim" }]}
|
||||
rules={[{ required: true, message: t("settingsPages.baseSSOSettingsForm.groupClaimRequired") }]}
|
||||
>
|
||||
<TextInput />
|
||||
</Form.Item>
|
||||
|
|
@ -223,28 +277,49 @@ const BaseSSOSettingsForm: React.FC<BaseSSOSettingsFormProps> = ({ form, onFormS
|
|||
const supportsRoleMappings = provider === "okta" || provider === "generic";
|
||||
return useRoleMappings && supportsRoleMappings ? (
|
||||
<>
|
||||
<Form.Item label="Default Role" name="default_role" initialValue="Internal User">
|
||||
<Form.Item
|
||||
label={t("settingsPages.baseSSOSettingsForm.defaultRoleLabel")}
|
||||
name="default_role"
|
||||
initialValue="Internal User"
|
||||
>
|
||||
<Select>
|
||||
<Select.Option value="internal_user_viewer">Internal Viewer</Select.Option>
|
||||
<Select.Option value="internal_user">Internal User</Select.Option>
|
||||
<Select.Option value="proxy_admin_viewer">Admin Viewer</Select.Option>
|
||||
<Select.Option value="proxy_admin">Proxy Admin</Select.Option>
|
||||
<Select.Option value="internal_user_viewer">
|
||||
{t("settingsPages.baseSSOSettingsForm.roleInternalViewer")}
|
||||
</Select.Option>
|
||||
<Select.Option value="internal_user">
|
||||
{t("settingsPages.baseSSOSettingsForm.roleInternalUser")}
|
||||
</Select.Option>
|
||||
<Select.Option value="proxy_admin_viewer">
|
||||
{t("settingsPages.baseSSOSettingsForm.roleAdminViewer")}
|
||||
</Select.Option>
|
||||
<Select.Option value="proxy_admin">
|
||||
{t("settingsPages.baseSSOSettingsForm.roleProxyAdmin")}
|
||||
</Select.Option>
|
||||
</Select>
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item label="Proxy Admin Teams" name="proxy_admin_teams">
|
||||
<Form.Item label={t("settingsPages.baseSSOSettingsForm.proxyAdminTeamsLabel")} name="proxy_admin_teams">
|
||||
<TextInput />
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item label="Admin Viewer Teams" name="admin_viewer_teams">
|
||||
<Form.Item
|
||||
label={t("settingsPages.baseSSOSettingsForm.adminViewerTeamsLabel")}
|
||||
name="admin_viewer_teams"
|
||||
>
|
||||
<TextInput />
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item label="Internal User Teams" name="internal_user_teams">
|
||||
<Form.Item
|
||||
label={t("settingsPages.baseSSOSettingsForm.internalUserTeamsLabel")}
|
||||
name="internal_user_teams"
|
||||
>
|
||||
<TextInput />
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item label="Internal Viewer Teams" name="internal_viewer_teams">
|
||||
<Form.Item
|
||||
label={t("settingsPages.baseSSOSettingsForm.internalViewerTeamsLabel")}
|
||||
name="internal_viewer_teams"
|
||||
>
|
||||
<TextInput />
|
||||
</Form.Item>
|
||||
</>
|
||||
|
|
@ -259,7 +334,11 @@ const BaseSSOSettingsForm: React.FC<BaseSSOSettingsFormProps> = ({ form, onFormS
|
|||
{({ getFieldValue }) => {
|
||||
const provider = getFieldValue("sso_provider");
|
||||
return provider === "okta" || provider === "generic" ? (
|
||||
<Form.Item label="Use Team Mappings" name="use_team_mappings" valuePropName="checked">
|
||||
<Form.Item
|
||||
label={t("settingsPages.baseSSOSettingsForm.useTeamMappingsLabel")}
|
||||
name="use_team_mappings"
|
||||
valuePropName="checked"
|
||||
>
|
||||
<Checkbox />
|
||||
</Form.Item>
|
||||
) : null;
|
||||
|
|
@ -279,9 +358,9 @@ const BaseSSOSettingsForm: React.FC<BaseSSOSettingsFormProps> = ({ form, onFormS
|
|||
const supportsTeamMappings = provider === "okta" || provider === "generic";
|
||||
return useTeamMappings && supportsTeamMappings ? (
|
||||
<Form.Item
|
||||
label="Team IDs JWT Field"
|
||||
label={t("settingsPages.baseSSOSettingsForm.teamIdsJwtFieldLabel")}
|
||||
name="team_ids_jwt_field"
|
||||
rules={[{ required: true, message: "Please enter the team IDs JWT field" }]}
|
||||
rules={[{ required: true, message: t("settingsPages.baseSSOSettingsForm.teamIdsJwtFieldRequired") }]}
|
||||
>
|
||||
<TextInput />
|
||||
</Form.Item>
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
import { useEditSSOSettings } from "@/app/(dashboard)/hooks/sso/useEditSSOSettings";
|
||||
import { useSSOSettings } from "@/app/(dashboard)/hooks/sso/useSSOSettings";
|
||||
import React from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import DeleteResourceModal from "../../../../common_components/DeleteResourceModal";
|
||||
import NotificationsManager from "../../../../molecules/notifications_manager";
|
||||
import { parseErrorMessage } from "../../../../shared/errorUtils";
|
||||
|
|
@ -13,6 +14,7 @@ interface DeleteSSOSettingsModalProps {
|
|||
}
|
||||
|
||||
const DeleteSSOSettingsModal: React.FC<DeleteSSOSettingsModalProps> = ({ isVisible, onCancel, onSuccess }) => {
|
||||
const { t } = useTranslation();
|
||||
const { data: ssoSettings } = useSSOSettings();
|
||||
const { mutateAsync: editSSOSettings, isPending: isEditingSSOSettings } = useEditSSOSettings();
|
||||
|
||||
|
|
@ -38,12 +40,14 @@ const DeleteSSOSettingsModal: React.FC<DeleteSSOSettingsModalProps> = ({ isVisib
|
|||
|
||||
await editSSOSettings(clearSettings, {
|
||||
onSuccess: () => {
|
||||
NotificationsManager.success("SSO settings cleared successfully");
|
||||
NotificationsManager.success(t("settingsPages.deleteSSOSettingsModal.clearSuccess"));
|
||||
onCancel();
|
||||
onSuccess();
|
||||
},
|
||||
onError: (error) => {
|
||||
NotificationsManager.fromBackend("Failed to clear SSO settings: " + parseErrorMessage(error));
|
||||
NotificationsManager.fromBackend(
|
||||
t("settingsPages.deleteSSOSettingsModal.clearFailed", { error: parseErrorMessage(error) }),
|
||||
);
|
||||
},
|
||||
});
|
||||
};
|
||||
|
|
@ -51,12 +55,17 @@ const DeleteSSOSettingsModal: React.FC<DeleteSSOSettingsModalProps> = ({ isVisib
|
|||
return (
|
||||
<DeleteResourceModal
|
||||
isOpen={isVisible}
|
||||
title="Confirm Clear SSO Settings"
|
||||
alertMessage="This action cannot be undone."
|
||||
message="Are you sure you want to clear all SSO settings? Users will no longer be able to login using SSO after this change."
|
||||
resourceInformationTitle="SSO Settings"
|
||||
title={t("settingsPages.deleteSSOSettingsModal.title")}
|
||||
alertMessage={t("settingsPages.deleteSSOSettingsModal.alertMessage")}
|
||||
message={t("settingsPages.deleteSSOSettingsModal.message")}
|
||||
resourceInformationTitle={t("settingsPages.deleteSSOSettingsModal.resourceTitle")}
|
||||
resourceInformation={[
|
||||
{ label: "Provider", value: (ssoSettings?.values && detectSSOProvider(ssoSettings?.values)) || "Generic" },
|
||||
{
|
||||
label: t("settingsPages.deleteSSOSettingsModal.providerLabel"),
|
||||
value:
|
||||
(ssoSettings?.values && detectSSOProvider(ssoSettings?.values)) ||
|
||||
t("settingsPages.deleteSSOSettingsModal.providerDefault"),
|
||||
},
|
||||
]}
|
||||
onCancel={onCancel}
|
||||
onOk={handleClearSSO}
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@
|
|||
|
||||
import { Button, Form, Modal, Space } from "antd";
|
||||
import React, { useEffect } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import BaseSSOSettingsForm from "./BaseSSOSettingsForm";
|
||||
import NotificationsManager from "@/components/molecules/notifications_manager";
|
||||
import { parseErrorMessage } from "@/components/shared/errorUtils";
|
||||
|
|
@ -16,6 +17,7 @@ interface EditSSOSettingsModalProps {
|
|||
}
|
||||
|
||||
const EditSSOSettingsModal: React.FC<EditSSOSettingsModalProps> = ({ isVisible, onCancel, onSuccess }) => {
|
||||
const { t } = useTranslation();
|
||||
const [form] = Form.useForm();
|
||||
|
||||
// Use react-query hooks for SSO settings
|
||||
|
|
@ -104,16 +106,20 @@ const EditSSOSettingsModal: React.FC<EditSSOSettingsModalProps> = ({ isVisible,
|
|||
|
||||
await mutateAsync(payload, {
|
||||
onSuccess: () => {
|
||||
NotificationsManager.success("SSO settings updated successfully");
|
||||
NotificationsManager.success(t("settingsPages.editSSOSettingsModal.updateSuccess"));
|
||||
onSuccess();
|
||||
},
|
||||
onError: (error) => {
|
||||
NotificationsManager.fromBackend("Failed to save SSO settings: " + parseErrorMessage(error));
|
||||
NotificationsManager.fromBackend(
|
||||
t("settingsPages.editSSOSettingsModal.saveFailed", { error: parseErrorMessage(error) }),
|
||||
);
|
||||
},
|
||||
});
|
||||
} catch (error) {
|
||||
// Handle processing errors gracefully
|
||||
NotificationsManager.fromBackend("Failed to process SSO settings: " + parseErrorMessage(error));
|
||||
NotificationsManager.fromBackend(
|
||||
t("settingsPages.editSSOSettingsModal.processFailed", { error: parseErrorMessage(error) }),
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
|
|
@ -124,16 +130,16 @@ const EditSSOSettingsModal: React.FC<EditSSOSettingsModalProps> = ({ isVisible,
|
|||
|
||||
return (
|
||||
<Modal
|
||||
title="Edit SSO Settings"
|
||||
title={t("settingsPages.editSSOSettingsModal.title")}
|
||||
open={isVisible}
|
||||
width={800}
|
||||
footer={
|
||||
<Space>
|
||||
<Button onClick={handleCancel} disabled={isPending}>
|
||||
Cancel
|
||||
{t("common.cancel")}
|
||||
</Button>
|
||||
<Button loading={isPending} onClick={() => form.submit()}>
|
||||
{isPending ? "Saving..." : "Save"}
|
||||
{isPending ? t("common.saving") : t("common.save")}
|
||||
</Button>
|
||||
</Space>
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4195,5 +4195,164 @@
|
|||
"verdictFalsePositive": "False positive — incorrectly blocked",
|
||||
"llmResponse": "LLM response:"
|
||||
}
|
||||
},
|
||||
"settingsPages": {
|
||||
"editHashicorpVaultModal": {
|
||||
"title": "Edit Hashicorp Vault Configuration",
|
||||
"groupConnection": "Connection",
|
||||
"groupTokenAuth": "Token Authentication",
|
||||
"groupTokenAuthSubtitle": "Use a Vault token to authenticate. Only one auth method is required.",
|
||||
"groupAppRoleAuth": "AppRole Authentication",
|
||||
"groupAppRoleAuthSubtitle": "Use AppRole credentials to authenticate. Only one auth method is required.",
|
||||
"groupTls": "TLS",
|
||||
"groupTlsSubtitle": "Optional client certificate for mTLS.",
|
||||
"urlPatternMessage": "Must start with http:// or https://",
|
||||
"leaveBlankToKeep": "Leave blank to keep existing ({{existing}})",
|
||||
"saveSuccess": "Hashicorp Vault configuration updated successfully"
|
||||
},
|
||||
"hashicorpVault": {
|
||||
"manageSecretManager": "Manage secret manager configuration",
|
||||
"testConnection": "Test Connection",
|
||||
"editConfiguration": "Edit Configuration",
|
||||
"deleteConfiguration": "Delete Configuration",
|
||||
"secretsKeyHint": "Secrets must be stored with the field name \"key\"",
|
||||
"viewDocumentation": "View documentation",
|
||||
"authMethod": "Auth Method",
|
||||
"notConfigured": "Not configured",
|
||||
"loadError": "Could not load Hashicorp Vault configuration",
|
||||
"deleteTitle": "Delete Hashicorp Vault Configuration?",
|
||||
"deleteMessage": "Models using Vault secrets will lose access to their API keys until a new configuration is saved.",
|
||||
"deleteResourceTitle": "Vault Configuration",
|
||||
"vaultAddressLabel": "Vault Address",
|
||||
"clearTitle": "Clear {{fieldLabel}}?",
|
||||
"clearMessage": "This will remove the stored value.",
|
||||
"clearResourceTitle": "Field",
|
||||
"clearFieldLabel": "Field",
|
||||
"deleteSuccess": "Hashicorp Vault configuration deleted",
|
||||
"clearSuccess": "{{fieldLabel}} cleared",
|
||||
"connectionSuccess": "Connection to Vault successful!"
|
||||
},
|
||||
"hashicorpVaultEmptyPlaceholder": {
|
||||
"noConfigTitle": "No Vault Configuration Found",
|
||||
"noConfigDesc": "Configure Hashicorp Vault to securely manage provider API keys and secrets for your LiteLLM deployment.",
|
||||
"configureButton": "Configure Vault"
|
||||
},
|
||||
"loggingSettings": {
|
||||
"cardTitle": "Logging Settings",
|
||||
"description": "Proxy-wide settings that control how request and response data are written to spend logs.",
|
||||
"storePromptsLabel": "Store Prompts in Spend Logs",
|
||||
"storePromptsTooltipDefault": "When enabled, prompts will be stored in spend logs for tracking and analysis purposes.",
|
||||
"retentionPeriodLabel": "Maximum Spend Logs Retention Period (Optional)",
|
||||
"retentionPeriodTooltipDefault": "Set the maximum retention period for spend logs (e.g., '7d' for 7 days, '30d' for 30 days). Leave empty for no limit.",
|
||||
"retentionPeriodPlaceholder": "e.g., 7d, 30d",
|
||||
"saveSettingsButton": "Save Settings",
|
||||
"saveSuccess": "Spend logs settings updated successfully",
|
||||
"saveFailed": "Failed to save spend logs settings: {{error}}"
|
||||
},
|
||||
"mcpSemanticFilterSettings": {
|
||||
"loginRequired": "Please log in to configure semantic filter settings.",
|
||||
"loadError": "Could not load MCP Semantic Filter settings",
|
||||
"infoTitle": "Semantic Tool Filtering",
|
||||
"infoDesc": "Filter MCP tools semantically based on query relevance. This reduces context window size and improves tool selection accuracy. Click 'Save Settings' to apply changes across all pods (takes effect within 10 seconds).",
|
||||
"savedSuccessMessage": "Settings saved successfully",
|
||||
"updateErrorTitle": "Could not update settings",
|
||||
"enableLabel": "Enable Semantic Filtering",
|
||||
"enableTooltip": "When enabled, only the most relevant MCP tools will be included in requests based on semantic similarity",
|
||||
"configCardTitle": "Configuration",
|
||||
"embeddingModelLabel": "Embedding Model",
|
||||
"embeddingModelTooltip": "The model used to generate embeddings for semantic matching",
|
||||
"loadingModels": "Loading models...",
|
||||
"selectEmbeddingModel": "Select embedding model",
|
||||
"noEmbeddingModels": "No embedding models available",
|
||||
"topKLabel": "Top K Results",
|
||||
"topKTooltip": "Maximum number of tools to return after filtering",
|
||||
"similarityThresholdLabel": "Similarity Threshold",
|
||||
"similarityThresholdTooltip": "Minimum similarity score (0-1) for a tool to be included",
|
||||
"saveSettingsButton": "Save Settings",
|
||||
"saveSuccess": "Settings updated successfully. Changes will be applied across all pods within 10 seconds."
|
||||
},
|
||||
"mcpSemanticFilterTestPanel": {
|
||||
"testConfigCardTitle": "Test Configuration",
|
||||
"testTabLabel": "Test",
|
||||
"apiTabLabel": "API Usage",
|
||||
"testQueryLabel": "Test Query",
|
||||
"testQueryPlaceholder": "Enter a test query to see which tools would be selected...",
|
||||
"selectModelLabel": "Select Model",
|
||||
"testFilterButton": "Test Filter",
|
||||
"filterDisabledTitle": "Semantic filtering is disabled",
|
||||
"filterDisabledDesc": "Enable semantic filtering and save settings to test the filter.",
|
||||
"resultsTitle": "Results",
|
||||
"toolsSelectedMessage": "{{selectedTools}} tools selected",
|
||||
"filteredFromMessage": "Filtered from {{totalTools}} available tools",
|
||||
"selectedToolsLabel": "Selected Tools:",
|
||||
"apiUsageTitle": "API Usage",
|
||||
"apiUsageDesc": "Use this curl command to test the semantic filter with your current configuration.",
|
||||
"responseHeadersLabel": "Response headers to check:",
|
||||
"semanticFilterHeader": "x-litellm-semantic-filter: shows total tools → selected tools",
|
||||
"semanticFilterHeaderExample": "Example: 10→3",
|
||||
"semanticFilterToolsHeader": "x-litellm-semantic-filter-tools: CSV of selected tool names",
|
||||
"semanticFilterToolsHeaderExample": "Example: wikipedia-fetch,github-search,slack-post"
|
||||
},
|
||||
"addSSOSettingsModal": {
|
||||
"title": "Add SSO",
|
||||
"addingButton": "Adding...",
|
||||
"addSSOButton": "Add SSO",
|
||||
"addSuccess": "SSO settings added successfully",
|
||||
"saveFailed": "Failed to save SSO settings: {{error}}"
|
||||
},
|
||||
"baseSSOSettingsForm": {
|
||||
"ssoProviderLabel": "SSO Provider",
|
||||
"ssoProviderRequired": "Please select an SSO provider",
|
||||
"proxyAdminEmailLabel": "Proxy Admin Email",
|
||||
"proxyAdminEmailRequired": "Please enter the email of the proxy admin",
|
||||
"proxyBaseUrlLabel": "Proxy Base URL",
|
||||
"proxyBaseUrlRequired": "Please enter the proxy base url",
|
||||
"proxyBaseUrlInvalidScheme": "URL must start with http:// or https://",
|
||||
"proxyBaseUrlTrailingSlash": "URL must not end with a trailing slash",
|
||||
"useRoleMappingsLabel": "Use Role Mappings",
|
||||
"groupClaimLabel": "Group Claim",
|
||||
"groupClaimRequired": "Please enter the group claim",
|
||||
"defaultRoleLabel": "Default Role",
|
||||
"roleInternalViewer": "Internal Viewer",
|
||||
"roleInternalUser": "Internal User",
|
||||
"roleAdminViewer": "Admin Viewer",
|
||||
"roleProxyAdmin": "Proxy Admin",
|
||||
"proxyAdminTeamsLabel": "Proxy Admin Teams",
|
||||
"adminViewerTeamsLabel": "Admin Viewer Teams",
|
||||
"internalUserTeamsLabel": "Internal User Teams",
|
||||
"internalViewerTeamsLabel": "Internal Viewer Teams",
|
||||
"useTeamMappingsLabel": "Use Team Mappings",
|
||||
"teamIdsJwtFieldLabel": "Team IDs JWT Field",
|
||||
"teamIdsJwtFieldRequired": "Please enter the team IDs JWT field",
|
||||
"fieldRequired": "Please enter the {{label}}",
|
||||
"fieldLabels": {
|
||||
"googleClientId": "Google Client ID",
|
||||
"googleClientSecret": "Google Client Secret",
|
||||
"microsoftClientId": "Microsoft Client ID",
|
||||
"microsoftClientSecret": "Microsoft Client Secret",
|
||||
"microsoftTenant": "Microsoft Tenant",
|
||||
"genericClientId": "Generic Client ID",
|
||||
"genericClientSecret": "Generic Client Secret",
|
||||
"authorizationEndpoint": "Authorization Endpoint",
|
||||
"tokenEndpoint": "Token Endpoint",
|
||||
"userinfoEndpoint": "Userinfo Endpoint"
|
||||
}
|
||||
},
|
||||
"deleteSSOSettingsModal": {
|
||||
"title": "Confirm Clear SSO Settings",
|
||||
"alertMessage": "This action cannot be undone.",
|
||||
"message": "Are you sure you want to clear all SSO settings? Users will no longer be able to login using SSO after this change.",
|
||||
"resourceTitle": "SSO Settings",
|
||||
"providerLabel": "Provider",
|
||||
"providerDefault": "Generic",
|
||||
"clearSuccess": "SSO settings cleared successfully",
|
||||
"clearFailed": "Failed to clear SSO settings: {{error}}"
|
||||
},
|
||||
"editSSOSettingsModal": {
|
||||
"title": "Edit SSO Settings",
|
||||
"updateSuccess": "SSO settings updated successfully",
|
||||
"saveFailed": "Failed to save SSO settings: {{error}}",
|
||||
"processFailed": "Failed to process SSO settings: {{error}}"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4195,5 +4195,164 @@
|
|||
"verdictFalsePositive": "误报 — 被错误拦截",
|
||||
"llmResponse": "模型响应:"
|
||||
}
|
||||
},
|
||||
"settingsPages": {
|
||||
"editHashicorpVaultModal": {
|
||||
"title": "编辑 Hashicorp Vault 配置",
|
||||
"groupConnection": "连接",
|
||||
"groupTokenAuth": "Token 认证",
|
||||
"groupTokenAuthSubtitle": "使用 Vault Token 进行认证。只需配置一种认证方式。",
|
||||
"groupAppRoleAuth": "AppRole 认证",
|
||||
"groupAppRoleAuthSubtitle": "使用 AppRole 凭据进行认证。只需配置一种认证方式。",
|
||||
"groupTls": "TLS",
|
||||
"groupTlsSubtitle": "用于 mTLS 的可选客户端证书。",
|
||||
"urlPatternMessage": "必须以 http:// 或 https:// 开头",
|
||||
"leaveBlankToKeep": "留空以保留现有值({{existing}})",
|
||||
"saveSuccess": "Hashicorp Vault 配置更新成功"
|
||||
},
|
||||
"hashicorpVault": {
|
||||
"manageSecretManager": "管理密钥管理器配置",
|
||||
"testConnection": "测试连接",
|
||||
"editConfiguration": "编辑配置",
|
||||
"deleteConfiguration": "删除配置",
|
||||
"secretsKeyHint": "密钥必须以字段名 \"key\" 存储",
|
||||
"viewDocumentation": "查看文档",
|
||||
"authMethod": "认证方式",
|
||||
"notConfigured": "未配置",
|
||||
"loadError": "无法加载 Hashicorp Vault 配置",
|
||||
"deleteTitle": "删除 Hashicorp Vault 配置?",
|
||||
"deleteMessage": "使用 Vault 密钥的模型将无法访问其 API 密钥,直到保存新配置为止。",
|
||||
"deleteResourceTitle": "Vault 配置",
|
||||
"vaultAddressLabel": "Vault 地址",
|
||||
"clearTitle": "清除 {{fieldLabel}}?",
|
||||
"clearMessage": "此操作将删除已存储的值。",
|
||||
"clearResourceTitle": "字段",
|
||||
"clearFieldLabel": "字段",
|
||||
"deleteSuccess": "Hashicorp Vault 配置已删除",
|
||||
"clearSuccess": "{{fieldLabel}} 已清除",
|
||||
"connectionSuccess": "Vault 连接成功!"
|
||||
},
|
||||
"hashicorpVaultEmptyPlaceholder": {
|
||||
"noConfigTitle": "未找到 Vault 配置",
|
||||
"noConfigDesc": "配置 Hashicorp Vault 以安全管理您的 LiteLLM 部署的提供商 API 密钥和密钥。",
|
||||
"configureButton": "配置 Vault"
|
||||
},
|
||||
"loggingSettings": {
|
||||
"cardTitle": "日志设置",
|
||||
"description": "控制请求和响应数据写入花费日志方式的代理全局设置。",
|
||||
"storePromptsLabel": "在花费日志中存储提示词",
|
||||
"storePromptsTooltipDefault": "启用后,提示词将存储在花费日志中以用于跟踪和分析。",
|
||||
"retentionPeriodLabel": "花费日志最大保留期(可选)",
|
||||
"retentionPeriodTooltipDefault": "设置花费日志的最大保留期(例如 '7d' 表示 7 天,'30d' 表示 30 天)。留空表示无限制。",
|
||||
"retentionPeriodPlaceholder": "例如:7d、30d",
|
||||
"saveSettingsButton": "保存设置",
|
||||
"saveSuccess": "花费日志设置更新成功",
|
||||
"saveFailed": "保存花费日志设置失败:{{error}}"
|
||||
},
|
||||
"mcpSemanticFilterSettings": {
|
||||
"loginRequired": "请登录以配置语义过滤器设置。",
|
||||
"loadError": "无法加载 MCP 语义过滤器设置",
|
||||
"infoTitle": "语义工具过滤",
|
||||
"infoDesc": "基于查询相关性对 MCP 工具进行语义过滤。这可以减少上下文窗口大小并提高工具选择准确性。点击「保存设置」以在所有 Pod 上应用更改(10 秒内生效)。",
|
||||
"savedSuccessMessage": "设置保存成功",
|
||||
"updateErrorTitle": "无法更新设置",
|
||||
"enableLabel": "启用语义过滤",
|
||||
"enableTooltip": "启用后,请求中将只包含基于语义相似度最相关的 MCP 工具",
|
||||
"configCardTitle": "配置",
|
||||
"embeddingModelLabel": "嵌入模型",
|
||||
"embeddingModelTooltip": "用于生成语义匹配嵌入向量的模型",
|
||||
"loadingModels": "加载模型中...",
|
||||
"selectEmbeddingModel": "选择嵌入模型",
|
||||
"noEmbeddingModels": "没有可用的嵌入模型",
|
||||
"topKLabel": "Top K 结果数",
|
||||
"topKTooltip": "过滤后返回的最大工具数量",
|
||||
"similarityThresholdLabel": "相似度阈值",
|
||||
"similarityThresholdTooltip": "工具被包含所需的最低相似度分数(0-1)",
|
||||
"saveSettingsButton": "保存设置",
|
||||
"saveSuccess": "设置更新成功。更改将在 10 秒内应用到所有 Pod。"
|
||||
},
|
||||
"mcpSemanticFilterTestPanel": {
|
||||
"testConfigCardTitle": "测试配置",
|
||||
"testTabLabel": "测试",
|
||||
"apiTabLabel": "API 用法",
|
||||
"testQueryLabel": "测试查询",
|
||||
"testQueryPlaceholder": "输入测试查询以查看哪些工具将被选中...",
|
||||
"selectModelLabel": "选择模型",
|
||||
"testFilterButton": "测试过滤器",
|
||||
"filterDisabledTitle": "语义过滤已禁用",
|
||||
"filterDisabledDesc": "请启用语义过滤并保存设置后再测试过滤器。",
|
||||
"resultsTitle": "结果",
|
||||
"toolsSelectedMessage": "已选中 {{selectedTools}} 个工具",
|
||||
"filteredFromMessage": "从 {{totalTools}} 个可用工具中过滤",
|
||||
"selectedToolsLabel": "已选工具:",
|
||||
"apiUsageTitle": "API 用法",
|
||||
"apiUsageDesc": "使用此 curl 命令以当前配置测试语义过滤器。",
|
||||
"responseHeadersLabel": "需检查的响应头:",
|
||||
"semanticFilterHeader": "x-litellm-semantic-filter:显示总工具数 → 已选工具数",
|
||||
"semanticFilterHeaderExample": "示例:10→3",
|
||||
"semanticFilterToolsHeader": "x-litellm-semantic-filter-tools:已选工具名称的 CSV",
|
||||
"semanticFilterToolsHeaderExample": "示例:wikipedia-fetch,github-search,slack-post"
|
||||
},
|
||||
"addSSOSettingsModal": {
|
||||
"title": "添加 SSO",
|
||||
"addingButton": "添加中...",
|
||||
"addSSOButton": "添加 SSO",
|
||||
"addSuccess": "SSO 设置添加成功",
|
||||
"saveFailed": "保存 SSO 设置失败:{{error}}"
|
||||
},
|
||||
"baseSSOSettingsForm": {
|
||||
"ssoProviderLabel": "SSO 提供商",
|
||||
"ssoProviderRequired": "请选择一个 SSO 提供商",
|
||||
"proxyAdminEmailLabel": "代理管理员邮箱",
|
||||
"proxyAdminEmailRequired": "请输入代理管理员的邮箱",
|
||||
"proxyBaseUrlLabel": "代理基础 URL",
|
||||
"proxyBaseUrlRequired": "请输入代理基础 URL",
|
||||
"proxyBaseUrlInvalidScheme": "URL 必须以 http:// 或 https:// 开头",
|
||||
"proxyBaseUrlTrailingSlash": "URL 末尾不能有斜杠",
|
||||
"useRoleMappingsLabel": "使用角色映射",
|
||||
"groupClaimLabel": "组声明",
|
||||
"groupClaimRequired": "请输入组声明",
|
||||
"defaultRoleLabel": "默认角色",
|
||||
"roleInternalViewer": "内部查看者",
|
||||
"roleInternalUser": "内部用户",
|
||||
"roleAdminViewer": "管理员查看者",
|
||||
"roleProxyAdmin": "代理管理员",
|
||||
"proxyAdminTeamsLabel": "代理管理员团队",
|
||||
"adminViewerTeamsLabel": "管理员查看者团队",
|
||||
"internalUserTeamsLabel": "内部用户团队",
|
||||
"internalViewerTeamsLabel": "内部查看者团队",
|
||||
"useTeamMappingsLabel": "使用团队映射",
|
||||
"teamIdsJwtFieldLabel": "团队 ID JWT 字段",
|
||||
"teamIdsJwtFieldRequired": "请输入团队 ID JWT 字段",
|
||||
"fieldRequired": "请输入{{label}}",
|
||||
"fieldLabels": {
|
||||
"googleClientId": "Google 客户端 ID",
|
||||
"googleClientSecret": "Google 客户端密钥",
|
||||
"microsoftClientId": "Microsoft 客户端 ID",
|
||||
"microsoftClientSecret": "Microsoft 客户端密钥",
|
||||
"microsoftTenant": "Microsoft 租户",
|
||||
"genericClientId": "通用客户端 ID",
|
||||
"genericClientSecret": "通用客户端密钥",
|
||||
"authorizationEndpoint": "授权端点",
|
||||
"tokenEndpoint": "Token 端点",
|
||||
"userinfoEndpoint": "用户信息端点"
|
||||
}
|
||||
},
|
||||
"deleteSSOSettingsModal": {
|
||||
"title": "确认清除 SSO 设置",
|
||||
"alertMessage": "此操作无法撤销。",
|
||||
"message": "确定要清除所有 SSO 设置吗?此更改后用户将无法再使用 SSO 登录。",
|
||||
"resourceTitle": "SSO 设置",
|
||||
"providerLabel": "提供商",
|
||||
"providerDefault": "通用",
|
||||
"clearSuccess": "SSO 设置已成功清除",
|
||||
"clearFailed": "清除 SSO 设置失败:{{error}}"
|
||||
},
|
||||
"editSSOSettingsModal": {
|
||||
"title": "编辑 SSO 设置",
|
||||
"updateSuccess": "SSO 设置更新成功",
|
||||
"saveFailed": "保存 SSO 设置失败:{{error}}",
|
||||
"processFailed": "处理 SSO 设置失败:{{error}}"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue