feat(ui): i18n for admin settings batch 2

This commit is contained in:
吹雪 2026-06-10 22:36:14 +09:00
parent 552da4110d
commit 07ec61d295
12 changed files with 388 additions and 131 deletions

View file

@ -1,6 +1,7 @@
import { useState } from "react";
import { Button } from "antd";
import { Eye, EyeOff } from "lucide-react";
import { useTranslation } from "react-i18next";
export default function RedactableField({
defaultHidden = true,
@ -9,6 +10,7 @@ export default function RedactableField({
defaultHidden?: boolean;
value: string | null;
}) {
const { t } = useTranslation();
const [isHidden, setIsHidden] = useState(defaultHidden);
return (
@ -21,7 +23,7 @@ export default function RedactableField({
value
)
) : (
<span className="text-gray-400 italic">Not configured</span>
<span className="text-gray-400 italic">{t("settingsPages.redactableField.notConfigured")}</span>
)}
</span>
{value && (

View file

@ -1,23 +1,26 @@
import type { RoleMappings as RoleMappingsType } from "@/app/(dashboard)/hooks/sso/useSSOSettings";
import { Card, Divider, Table, Tag, Typography } from "antd";
import { Users } from "lucide-react";
import { useTranslation } from "react-i18next";
import { defaultRoleDisplayNames } from "./constants";
const { Title, Text } = Typography;
export default function RoleMappings({ roleMappings }: { roleMappings: RoleMappingsType | undefined }) {
const { t } = useTranslation();
if (!roleMappings) {
return null;
}
const roleMappingsColumns = [
{
title: "Role",
title: t("settingsPages.roleMappings.roleColumn"),
dataIndex: "role",
key: "role",
render: (text: string) => <Text strong>{defaultRoleDisplayNames[text]}</Text>,
},
{
title: "Mapped Groups",
title: t("settingsPages.roleMappings.mappedGroupsColumn"),
dataIndex: "groups",
key: "groups",
render: (groups: string[]) => (
@ -29,7 +32,7 @@ export default function RoleMappings({ roleMappings }: { roleMappings: RoleMappi
</Tag>
))
) : (
<Text className="text-gray-400 italic">No groups mapped</Text>
<Text className="text-gray-400 italic">{t("settingsPages.roleMappings.noGroupsMapped")}</Text>
)}
</>
),
@ -39,18 +42,18 @@ export default function RoleMappings({ roleMappings }: { roleMappings: RoleMappi
<Card>
<div className="flex items-center gap-3">
<Users className="w-6 h-6 text-gray-400 mb-2" />
<Title level={3}>Role Mappings</Title>
<Title level={3}>{t("settingsPages.roleMappings.title")}</Title>
</div>
<div className="space-y-8">
<div className="grid grid-cols-2 gap-4">
<div>
<Title level={5}>Group Claim</Title>
<Title level={5}>{t("settingsPages.roleMappings.groupClaim")}</Title>
<div>
<Text code>{roleMappings.group_claim}</Text>
</div>
</div>
<div>
<Title level={5}>Default Role</Title>
<Title level={5}>{t("settingsPages.roleMappings.defaultRole")}</Title>
<div>
<Text strong>{defaultRoleDisplayNames[roleMappings.default_role]}</Text>
</div>

View file

@ -4,6 +4,7 @@ import { useSSOSettings, type SSOSettingsValues } from "@/app/(dashboard)/hooks/
import { Button, Card, Descriptions, Space, Tag, Typography } from "antd";
import { Edit, Shield, Trash2 } from "lucide-react";
import { useState } from "react";
import { useTranslation } from "react-i18next";
import { ssoProviderDisplayNames, ssoProviderLogoMap } from "./constants";
import AddSSOSettingsModal from "./Modals/AddSSOSettingsModal";
import DeleteSSOSettingsModal from "./Modals/DeleteSSOSettingsModal";
@ -17,6 +18,7 @@ import { detectSSOProvider } from "./utils";
const { Title, Text } = Typography;
export default function SSOSettings() {
const { t } = useTranslation();
const { data: ssoSettings, refetch, isLoading } = useSSOSettings();
const [isDeleteModalVisible, setIsDeleteModalVisible] = useState(false);
const [isAddModalVisible, setIsAddModalVisible] = useState(false);
@ -37,11 +39,11 @@ export default function SSOSettings() {
);
const renderSimpleValue = (value?: string | null) =>
value ? value : <span className="text-gray-400 italic">Not configured</span>;
value ? value : <span className="text-gray-400 italic">{t("settingsPages.sSOSettings.notConfigured")}</span>;
const renderTeamMappingsField = (values: SSOSettingsValues) => {
if (!values.team_mappings?.team_ids_jwt_field) {
return <span className="text-gray-400 italic">Not configured</span>;
return <span className="text-gray-400 italic">{t("settingsPages.sSOSettings.notConfigured")}</span>;
}
return <Tag>{values.team_mappings.team_ids_jwt_field}</Tag>;
};
@ -62,58 +64,70 @@ export default function SSOSettings() {
providerText: ssoProviderDisplayNames.google,
fields: [
{
label: "Client ID",
label: t("settingsPages.sSOSettings.clientId"),
render: (values: SSOSettingsValues) => <RedactableField value={values.google_client_id} />,
},
{
label: "Client Secret",
label: t("settingsPages.sSOSettings.clientSecret"),
render: (values: SSOSettingsValues) => <RedactableField value={values.google_client_secret} />,
},
{ label: "Proxy Base URL", render: (values: SSOSettingsValues) => renderSimpleValue(values.proxy_base_url) },
{
label: t("settingsPages.sSOSettings.proxyBaseUrl"),
render: (values: SSOSettingsValues) => renderSimpleValue(values.proxy_base_url),
},
],
},
microsoft: {
providerText: ssoProviderDisplayNames.microsoft,
fields: [
{
label: "Client ID",
label: t("settingsPages.sSOSettings.clientId"),
render: (values: SSOSettingsValues) => <RedactableField value={values.microsoft_client_id} />,
},
{
label: "Client Secret",
label: t("settingsPages.sSOSettings.clientSecret"),
render: (values: SSOSettingsValues) => <RedactableField value={values.microsoft_client_secret} />,
},
{ label: "Tenant", render: (values: any) => renderSimpleValue(values.microsoft_tenant) },
{ label: "Proxy Base URL", render: (values: SSOSettingsValues) => renderSimpleValue(values.proxy_base_url) },
{
label: t("settingsPages.sSOSettings.tenant"),
render: (values: any) => renderSimpleValue(values.microsoft_tenant),
},
{
label: t("settingsPages.sSOSettings.proxyBaseUrl"),
render: (values: SSOSettingsValues) => renderSimpleValue(values.proxy_base_url),
},
],
},
okta: {
providerText: ssoProviderDisplayNames.okta,
fields: [
{
label: "Client ID",
label: t("settingsPages.sSOSettings.clientId"),
render: (values: SSOSettingsValues) => <RedactableField value={values.generic_client_id} />,
},
{
label: "Client Secret",
label: t("settingsPages.sSOSettings.clientSecret"),
render: (values: SSOSettingsValues) => <RedactableField value={values.generic_client_secret} />,
},
{
label: "Authorization Endpoint",
label: t("settingsPages.sSOSettings.authorizationEndpoint"),
render: (values: SSOSettingsValues) => renderEndpointValue(values.generic_authorization_endpoint),
},
{
label: "Token Endpoint",
label: t("settingsPages.sSOSettings.tokenEndpoint"),
render: (values: SSOSettingsValues) => renderEndpointValue(values.generic_token_endpoint),
},
{
label: "User Info Endpoint",
label: t("settingsPages.sSOSettings.userInfoEndpoint"),
render: (values: SSOSettingsValues) => renderEndpointValue(values.generic_userinfo_endpoint),
},
{ label: "Proxy Base URL", render: (values: SSOSettingsValues) => renderSimpleValue(values.proxy_base_url) },
{
label: t("settingsPages.sSOSettings.proxyBaseUrl"),
render: (values: SSOSettingsValues) => renderSimpleValue(values.proxy_base_url),
},
isTeamMappingsEnabled
? {
label: "Team IDs JWT Field",
label: t("settingsPages.sSOSettings.teamIdsJwtField"),
render: (values: SSOSettingsValues) => renderTeamMappingsField(values),
}
: null,
@ -123,29 +137,32 @@ export default function SSOSettings() {
providerText: ssoProviderDisplayNames.generic,
fields: [
{
label: "Client ID",
label: t("settingsPages.sSOSettings.clientId"),
render: (values: SSOSettingsValues) => <RedactableField value={values.generic_client_id} />,
},
{
label: "Client Secret",
label: t("settingsPages.sSOSettings.clientSecret"),
render: (values: SSOSettingsValues) => <RedactableField value={values.generic_client_secret} />,
},
{
label: "Authorization Endpoint",
label: t("settingsPages.sSOSettings.authorizationEndpoint"),
render: (values: SSOSettingsValues) => renderEndpointValue(values.generic_authorization_endpoint),
},
{
label: "Token Endpoint",
label: t("settingsPages.sSOSettings.tokenEndpoint"),
render: (values: SSOSettingsValues) => renderEndpointValue(values.generic_token_endpoint),
},
{
label: "User Info Endpoint",
label: t("settingsPages.sSOSettings.userInfoEndpoint"),
render: (values: SSOSettingsValues) => renderEndpointValue(values.generic_userinfo_endpoint),
},
{ label: "Proxy Base URL", render: (values: SSOSettingsValues) => renderSimpleValue(values.proxy_base_url) },
{
label: t("settingsPages.sSOSettings.proxyBaseUrl"),
render: (values: SSOSettingsValues) => renderSimpleValue(values.proxy_base_url),
},
isTeamMappingsEnabled
? {
label: "Team IDs JWT Field",
label: t("settingsPages.sSOSettings.teamIdsJwtField"),
render: (values: SSOSettingsValues) => renderTeamMappingsField(values),
}
: null,
@ -163,7 +180,7 @@ export default function SSOSettings() {
return (
<Descriptions bordered {...descriptionsConfig}>
<Descriptions.Item label="Provider">
<Descriptions.Item label={t("settingsPages.sSOSettings.provider")}>
<div style={{ display: "flex", alignItems: "center", gap: "8px" }}>
{ssoProviderLogoMap[selectedProvider] && (
<img
@ -200,8 +217,8 @@ export default function SSOSettings() {
<div className="flex items-center gap-3">
<Shield className="w-6 h-6 text-gray-400" />
<div>
<Title level={3}>SSO Configuration</Title>
<Text type="secondary">Manage Single Sign-On authentication settings</Text>
<Title level={3}>{t("settingsPages.sSOSettings.title")}</Title>
<Text type="secondary">{t("settingsPages.sSOSettings.subtitle")}</Text>
</div>
</div>
@ -209,14 +226,14 @@ export default function SSOSettings() {
{isSSOConfigured && (
<>
<Button icon={<Edit className="w-4 h-4" />} onClick={() => setIsEditModalVisible(true)}>
Edit SSO Settings
{t("settingsPages.sSOSettings.editSSOSettings")}
</Button>
<Button
danger
icon={<Trash2 className="w-4 h-4" />}
onClick={() => setIsDeleteModalVisible(true)}
>
Delete SSO Settings
{t("settingsPages.sSOSettings.deleteSSOSettings")}
</Button>
</>
)}

View file

@ -1,4 +1,5 @@
import { Empty, Typography, Button } from "antd";
import { useTranslation } from "react-i18next";
const { Title, Paragraph } = Typography;
@ -7,22 +8,23 @@ interface SSOSettingsEmptyPlaceholderProps {
}
export default function SSOSettingsEmptyPlaceholder({ onAdd }: SSOSettingsEmptyPlaceholderProps) {
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 SSO Configuration Found</Title>
<Title level={4}>{t("settingsPages.sSOSettingsEmptyPlaceholder.noConfigTitle")}</Title>
<Paragraph type="secondary" className="max-w-md mx-auto">
Configure Single Sign-On (SSO) to enable seamless authentication for your team members using your identity
provider.
{t("settingsPages.sSOSettingsEmptyPlaceholder.noConfigDesc")}
</Paragraph>
</div>
}
>
<Button type="primary" size="large" onClick={onAdd} className="flex items-center gap-2 mx-auto mt-4">
Configure SSO
{t("settingsPages.sSOSettingsEmptyPlaceholder.configureButton")}
</Button>
</Empty>
</div>

View file

@ -2,9 +2,11 @@
import { Card, Descriptions, Skeleton, Space, Typography } from "antd";
import { Shield } from "lucide-react";
import { useTranslation } from "react-i18next";
const { Title, Text } = Typography;
export default function SSOSettingsLoadingSkeleton() {
const { t } = useTranslation();
const descriptionsConfig = {
column: {
xxl: 1,
@ -24,8 +26,8 @@ export default function SSOSettingsLoadingSkeleton() {
<div className="flex items-center gap-3">
<Shield className="w-6 h-6 text-gray-400" />
<div>
<Title level={3}>SSO Configuration</Title>
<Text type="secondary">Manage Single Sign-On authentication settings</Text>
<Title level={3}>{t("settingsPages.sSOSettings.title")}</Title>
<Text type="secondary">{t("settingsPages.sSOSettings.subtitle")}</Text>
</div>
</div>

View file

@ -63,15 +63,15 @@ export default function PageVisibilitySettings({
<Space direction="vertical" size="middle" style={{ width: "100%" }}>
<Space direction="vertical" size={4}>
<Space align="center">
<Typography.Text strong>Internal User Page Visibility</Typography.Text>
<Typography.Text strong>{t("settingsPages.pageVisibilitySettings.title")}</Typography.Text>
{!isPageVisibilitySet && (
<Tag color="default" style={{ marginLeft: "8px" }}>
Not set (all pages visible)
{t("settingsPages.pageVisibilitySettings.notSet")}
</Tag>
)}
{isPageVisibilitySet && (
<Tag color="blue" style={{ marginLeft: "8px" }}>
{selectedPages.length} page{selectedPages.length !== 1 ? "s" : ""} selected
{t("settingsPages.pageVisibilitySettings.pagesSelected", { count: selectedPages.length })}
</Tag>
)}
</Space>
@ -79,11 +79,10 @@ export default function PageVisibilitySettings({
<Typography.Text type="secondary">{enabledPagesPropertyDescription}</Typography.Text>
)}
<Typography.Text type="secondary" style={{ fontSize: "12px", fontStyle: "italic" }}>
By default, all pages are visible to internal users. Select specific pages to restrict visibility.
{t("settingsPages.pageVisibilitySettings.defaultHint")}
</Typography.Text>
<Typography.Text type="secondary" style={{ fontSize: "12px", color: "#8b5cf6" }}>
Note: Only pages accessible to internal user roles are shown here. Admin-only pages are excluded as they
cannot be made visible to internal users regardless of this setting.
{t("settingsPages.pageVisibilitySettings.adminOnlyNote")}
</Typography.Text>
</Space>
@ -91,7 +90,7 @@ export default function PageVisibilitySettings({
items={[
{
key: "page-visibility",
label: "Configure Page Visibility",
label: t("settingsPages.pageVisibilitySettings.configureLabel"),
children: (
<Space direction="vertical" size="middle" style={{ width: "100%" }}>
<Checkbox.Group value={selectedPages} onChange={setSelectedPages} style={{ width: "100%" }}>
@ -131,11 +130,11 @@ export default function PageVisibilitySettings({
<Space>
<Button type="primary" onClick={handleSavePageVisibility} loading={isUpdating} disabled={isUpdating}>
Save Page Visibility Settings
{t("settingsPages.pageVisibilitySettings.saveButton")}
</Button>
{isPageVisibilitySet && (
<Button onClick={handleResetToDefault} loading={isUpdating} disabled={isUpdating}>
Reset to Default (All Pages)
{t("settingsPages.pageVisibilitySettings.resetButton")}
</Button>
)}
</Space>

View file

@ -6,8 +6,10 @@ import useAuthorized from "@/app/(dashboard)/hooks/useAuthorized";
import NotificationManager from "@/components/molecules/notifications_manager";
import PageVisibilitySettings from "./PageVisibilitySettings";
import { Alert, Card, Divider, Skeleton, Space, Switch, Typography } from "antd";
import { useTranslation } from "react-i18next";
export default function UISettings() {
const { t } = useTranslation();
const { accessToken } = useAuthorized();
const { data, isLoading, isError, error } = useUISettings();
const { mutate: updateSettings, isPending: isUpdating, error: updateError } = useUpdateUISettings(accessToken);
@ -38,7 +40,7 @@ export default function UISettings() {
{ disable_model_add_for_internal_users: checked },
{
onSuccess: () => {
NotificationManager.success("UI settings updated successfully");
NotificationManager.success(t("settingsPages.uISettings.updateSuccess"));
},
onError: (error) => {
NotificationManager.fromBackend(error);
@ -52,7 +54,7 @@ export default function UISettings() {
{ disable_team_admin_delete_team_user: checked },
{
onSuccess: () => {
NotificationManager.success("UI settings updated successfully");
NotificationManager.success(t("settingsPages.uISettings.updateSuccess"));
},
onError: (error) => {
NotificationManager.fromBackend(error);
@ -66,7 +68,7 @@ export default function UISettings() {
{ disable_ui_nudges: checked },
{
onSuccess: () => {
NotificationManager.success("UI settings updated successfully");
NotificationManager.success(t("settingsPages.uISettings.updateSuccess"));
},
onError: (error) => {
NotificationManager.fromBackend(error);
@ -78,7 +80,7 @@ export default function UISettings() {
const handleUpdatePageVisibility = (settings: { enabled_ui_pages_internal_users: string[] | null }) => {
updateSettings(settings, {
onSuccess: () => {
NotificationManager.success("Page visibility settings updated successfully");
NotificationManager.success(t("settingsPages.uISettings.pageVisibilityUpdateSuccess"));
},
onError: (error) => {
NotificationManager.fromBackend(error);
@ -91,7 +93,7 @@ export default function UISettings() {
{ forward_client_headers_to_llm_api: checked },
{
onSuccess: () => {
NotificationManager.success("UI settings updated successfully");
NotificationManager.success(t("settingsPages.uISettings.updateSuccess"));
},
onError: (error) => {
NotificationManager.fromBackend(error);
@ -105,7 +107,7 @@ export default function UISettings() {
{ forward_llm_provider_auth_headers: checked },
{
onSuccess: () => {
NotificationManager.success("UI settings updated successfully");
NotificationManager.success(t("settingsPages.uISettings.updateSuccess"));
},
onError: (error) => {
NotificationManager.fromBackend(error);
@ -119,7 +121,7 @@ export default function UISettings() {
{ enable_projects_ui: checked },
{
onSuccess: () => {
NotificationManager.success("UI settings updated successfully. Refreshing page...");
NotificationManager.success(t("settingsPages.uISettings.updateSuccessRefreshing"));
setTimeout(() => window.location.reload(), 1000);
},
onError: (error) => {
@ -134,7 +136,7 @@ export default function UISettings() {
{ require_auth_for_public_ai_hub: checked },
{
onSuccess: () => {
NotificationManager.success("UI settings updated successfully");
NotificationManager.success(t("settingsPages.uISettings.updateSuccess"));
},
onError: (error) => {
NotificationManager.fromBackend(error);
@ -148,7 +150,7 @@ export default function UISettings() {
{ disable_agents_for_internal_users: checked },
{
onSuccess: () => {
NotificationManager.success("UI settings updated successfully");
NotificationManager.success(t("settingsPages.uISettings.updateSuccess"));
},
onError: (error) => {
NotificationManager.fromBackend(error);
@ -162,7 +164,7 @@ export default function UISettings() {
{ allow_agents_for_team_admins: checked },
{
onSuccess: () => {
NotificationManager.success("UI settings updated successfully");
NotificationManager.success(t("settingsPages.uISettings.updateSuccess"));
},
onError: (error) => {
NotificationManager.fromBackend(error);
@ -176,7 +178,7 @@ export default function UISettings() {
{ disable_vector_stores_for_internal_users: checked },
{
onSuccess: () => {
NotificationManager.success("UI settings updated successfully");
NotificationManager.success(t("settingsPages.uISettings.updateSuccess"));
},
onError: (error) => {
NotificationManager.fromBackend(error);
@ -190,7 +192,7 @@ export default function UISettings() {
{ allow_vector_stores_for_team_admins: checked },
{
onSuccess: () => {
NotificationManager.success("UI settings updated successfully");
NotificationManager.success(t("settingsPages.uISettings.updateSuccess"));
},
onError: (error) => {
NotificationManager.fromBackend(error);
@ -204,7 +206,7 @@ export default function UISettings() {
{ scope_user_search_to_org: checked },
{
onSuccess: () => {
NotificationManager.success("UI settings updated successfully");
NotificationManager.success(t("settingsPages.uISettings.updateSuccess"));
},
onError: (error) => {
NotificationManager.fromBackend(error);
@ -218,7 +220,7 @@ export default function UISettings() {
{ disable_custom_api_keys: checked },
{
onSuccess: () => {
NotificationManager.success("UI settings updated successfully");
NotificationManager.success(t("settingsPages.uISettings.updateSuccess"));
},
onError: (error) => {
NotificationManager.fromBackend(error);
@ -228,13 +230,13 @@ export default function UISettings() {
};
return (
<Card title="UI Settings">
<Card title={t("settingsPages.uISettings.cardTitle")}>
{isLoading ? (
<Skeleton active />
) : isError ? (
<Alert
type="error"
message="Could not load UI settings"
message={t("settingsPages.uISettings.loadError")}
description={error instanceof Error ? error.message : undefined}
/>
) : (
@ -246,7 +248,7 @@ export default function UISettings() {
{updateError && (
<Alert
type="error"
message="Could not update UI settings"
message={t("settingsPages.uISettings.updateError")}
description={updateError instanceof Error ? updateError.message : undefined}
/>
)}
@ -257,10 +259,10 @@ export default function UISettings() {
disabled={isUpdating}
loading={isUpdating}
onChange={handleToggle}
aria-label={property?.description ?? "Disable model add for internal users"}
aria-label={property?.description ?? t("settingsPages.uISettings.disableModelAddLabel")}
/>
<Space direction="vertical" size={4}>
<Typography.Text strong>Disable model add for internal users</Typography.Text>
<Typography.Text strong>{t("settingsPages.uISettings.disableModelAddLabel")}</Typography.Text>
{property?.description && <Typography.Text type="secondary">{property.description}</Typography.Text>}
</Space>
</Space>
@ -271,10 +273,12 @@ export default function UISettings() {
disabled={isUpdating}
loading={isUpdating}
onChange={handleToggleTeamAdminDelete}
aria-label={disableTeamAdminDeleteProperty?.description ?? "Disable team admin delete team user"}
aria-label={
disableTeamAdminDeleteProperty?.description ?? t("settingsPages.uISettings.disableTeamAdminDeleteLabel")
}
/>
<Space direction="vertical" size={4}>
<Typography.Text strong>Disable team admin delete team user</Typography.Text>
<Typography.Text strong>{t("settingsPages.uISettings.disableTeamAdminDeleteLabel")}</Typography.Text>
{disableTeamAdminDeleteProperty?.description && (
<Typography.Text type="secondary">{disableTeamAdminDeleteProperty.description}</Typography.Text>
)}
@ -287,10 +291,13 @@ export default function UISettings() {
disabled={isUpdating}
loading={isUpdating}
onChange={handleToggleRequireAuthForPublicAIHub}
aria-label={requireAuthForPublicAIHubProperty?.description ?? "Require authentication for public AI Hub"}
aria-label={
requireAuthForPublicAIHubProperty?.description ??
t("settingsPages.uISettings.requireAuthPublicAIHubLabel")
}
/>
<Space direction="vertical" size={4}>
<Typography.Text strong>Require authentication for public AI Hub</Typography.Text>
<Typography.Text strong>{t("settingsPages.uISettings.requireAuthPublicAIHubLabel")}</Typography.Text>
{requireAuthForPublicAIHubProperty?.description && (
<Typography.Text type="secondary">{requireAuthForPublicAIHubProperty.description}</Typography.Text>
)}
@ -303,13 +310,14 @@ export default function UISettings() {
disabled={isUpdating}
loading={isUpdating}
onChange={handleToggleForwardClientHeaders}
aria-label={forwardClientHeadersProperty?.description ?? "Forward client headers to LLM API"}
aria-label={
forwardClientHeadersProperty?.description ?? t("settingsPages.uISettings.forwardClientHeadersLabel")
}
/>
<Space direction="vertical" size={4}>
<Typography.Text strong>Forward client headers to LLM API</Typography.Text>
<Typography.Text strong>{t("settingsPages.uISettings.forwardClientHeadersLabel")}</Typography.Text>
<Typography.Text type="secondary">
{forwardClientHeadersProperty?.description ??
"Forwards client headers (Authorization, anthropic-beta, and x-* custom headers) to the upstream LLM. Enable for Claude Code with a Max subscription (forwards the OAuth token) or to pass custom/tracing headers through to the provider. Independent of the BYOK toggle — enable only the one(s) you need."}
{forwardClientHeadersProperty?.description ?? t("settingsPages.uISettings.forwardClientHeadersDesc")}
</Typography.Text>
</Space>
</Space>
@ -320,13 +328,18 @@ export default function UISettings() {
disabled={isUpdating}
loading={isUpdating}
onChange={handleToggleForwardLLMProviderAuthHeaders}
aria-label={forwardLLMProviderAuthHeadersProperty?.description ?? "Forward LLM provider auth headers"}
aria-label={
forwardLLMProviderAuthHeadersProperty?.description ??
t("settingsPages.uISettings.forwardLLMProviderAuthHeadersLabel")
}
/>
<Space direction="vertical" size={4}>
<Typography.Text strong>Forward LLM provider auth headers</Typography.Text>
<Typography.Text strong>
{t("settingsPages.uISettings.forwardLLMProviderAuthHeadersLabel")}
</Typography.Text>
<Typography.Text type="secondary">
{forwardLLMProviderAuthHeadersProperty?.description ??
"Forwards provider auth headers (x-api-key, x-goog-api-key, api-key, ocp-apim-subscription-key) to the upstream LLM, overriding any deployment-configured key for that request. Enable for Claude Code BYOK (clients bring their own API key). Independent of the client-headers toggle — enable only the one(s) you need."}
t("settingsPages.uISettings.forwardLLMProviderAuthHeadersDesc")}
</Typography.Text>
</Space>
</Space>
@ -338,13 +351,12 @@ export default function UISettings() {
disabled={isUpdating}
loading={isUpdating}
onChange={handleToggleEnableProjectsUI}
aria-label={enableProjectsUIProperty.description ?? "Enable Projects UI"}
aria-label={enableProjectsUIProperty.description ?? t("settingsPages.uISettings.enableProjectsUILabel")}
/>
<Space direction="vertical" size={4}>
<Typography.Text strong>[BETA] Enable Projects (page will refresh)</Typography.Text>
<Typography.Text strong>{t("settingsPages.uISettings.enableProjectsUILabel")}</Typography.Text>
<Typography.Text type="secondary">
{enableProjectsUIProperty.description ??
"If enabled, shows the Projects feature in the UI sidebar and the project field in key management."}
{enableProjectsUIProperty.description ?? t("settingsPages.uISettings.enableProjectsUIDesc")}
</Typography.Text>
</Space>
</Space>
@ -359,10 +371,10 @@ export default function UISettings() {
disabled={isUpdating}
loading={isUpdating}
onChange={handleToggleDisableAgents}
aria-label={disableAgentsProperty?.description ?? "Disable agents for internal users"}
aria-label={disableAgentsProperty?.description ?? t("settingsPages.uISettings.disableAgentsLabel")}
/>
<Space direction="vertical" size={4}>
<Typography.Text strong>Disable agents for internal users</Typography.Text>
<Typography.Text strong>{t("settingsPages.uISettings.disableAgentsLabel")}</Typography.Text>
{disableAgentsProperty?.description && (
<Typography.Text type="secondary">{disableAgentsProperty.description}</Typography.Text>
)}
@ -375,11 +387,13 @@ export default function UISettings() {
disabled={isUpdating || !isAgentsDisabled}
loading={isUpdating}
onChange={handleToggleAllowAgentsTeamAdmins}
aria-label={allowAgentsTeamAdminsProperty?.description ?? "Allow agents for team admins"}
aria-label={
allowAgentsTeamAdminsProperty?.description ?? t("settingsPages.uISettings.allowAgentsTeamAdminsLabel")
}
/>
<Space direction="vertical" size={4}>
<Typography.Text strong type={!isAgentsDisabled ? "secondary" : undefined}>
Allow agents for team admins
{t("settingsPages.uISettings.allowAgentsTeamAdminsLabel")}
</Typography.Text>
{allowAgentsTeamAdminsProperty?.description && (
<Typography.Text type="secondary">{allowAgentsTeamAdminsProperty.description}</Typography.Text>
@ -396,10 +410,12 @@ export default function UISettings() {
disabled={isUpdating}
loading={isUpdating}
onChange={handleToggleDisableVectorStores}
aria-label={disableVectorStoresProperty?.description ?? "Disable vector stores for internal users"}
aria-label={
disableVectorStoresProperty?.description ?? t("settingsPages.uISettings.disableVectorStoresLabel")
}
/>
<Space direction="vertical" size={4}>
<Typography.Text strong>Disable vector stores for internal users</Typography.Text>
<Typography.Text strong>{t("settingsPages.uISettings.disableVectorStoresLabel")}</Typography.Text>
{disableVectorStoresProperty?.description && (
<Typography.Text type="secondary">{disableVectorStoresProperty.description}</Typography.Text>
)}
@ -412,11 +428,14 @@ export default function UISettings() {
disabled={isUpdating || !isVectorStoresDisabled}
loading={isUpdating}
onChange={handleToggleAllowVectorStoresTeamAdmins}
aria-label={allowVectorStoresTeamAdminsProperty?.description ?? "Allow vector stores for team admins"}
aria-label={
allowVectorStoresTeamAdminsProperty?.description ??
t("settingsPages.uISettings.allowVectorStoresTeamAdminsLabel")
}
/>
<Space direction="vertical" size={4}>
<Typography.Text strong type={!isVectorStoresDisabled ? "secondary" : undefined}>
Allow vector stores for team admins
{t("settingsPages.uISettings.allowVectorStoresTeamAdminsLabel")}
</Typography.Text>
{allowVectorStoresTeamAdminsProperty?.description && (
<Typography.Text type="secondary">{allowVectorStoresTeamAdminsProperty.description}</Typography.Text>
@ -433,13 +452,12 @@ export default function UISettings() {
disabled={isUpdating}
loading={isUpdating}
onChange={handleToggleScopeUserSearch}
aria-label={scopeUserSearchProperty?.description ?? "Scope user search to organization"}
aria-label={scopeUserSearchProperty?.description ?? t("settingsPages.uISettings.scopeUserSearchLabel")}
/>
<Space direction="vertical" size={4}>
<Typography.Text strong>Scope user search to organization</Typography.Text>
<Typography.Text strong>{t("settingsPages.uISettings.scopeUserSearchLabel")}</Typography.Text>
<Typography.Text type="secondary">
{scopeUserSearchProperty?.description ??
"If enabled, the user search endpoint restricts results by organization. When off, any authenticated user can search all users."}
{scopeUserSearchProperty?.description ?? t("settingsPages.uISettings.scopeUserSearchDesc")}
</Typography.Text>
</Space>
</Space>
@ -453,13 +471,14 @@ export default function UISettings() {
disabled={isUpdating}
loading={isUpdating}
onChange={handleToggleDisableCustomApiKeys}
aria-label={disableCustomApiKeysProperty?.description ?? "Disable custom Virtual key values"}
aria-label={
disableCustomApiKeysProperty?.description ?? t("settingsPages.uISettings.disableCustomApiKeysLabel")
}
/>
<Space direction="vertical" size={4}>
<Typography.Text strong>Disable custom Virtual key values</Typography.Text>
<Typography.Text strong>{t("settingsPages.uISettings.disableCustomApiKeysLabel")}</Typography.Text>
<Typography.Text type="secondary">
{disableCustomApiKeysProperty?.description ??
"If true, users cannot specify custom key values. All keys must be auto-generated."}
{disableCustomApiKeysProperty?.description ?? t("settingsPages.uISettings.disableCustomApiKeysDesc")}
</Typography.Text>
</Space>
</Space>
@ -473,13 +492,12 @@ export default function UISettings() {
disabled={isUpdating}
loading={isUpdating}
onChange={handleToggleDisableUINudges}
aria-label={disableUINudgesProperty?.description ?? "Disable UI nudges"}
aria-label={disableUINudgesProperty?.description ?? t("settingsPages.uISettings.disableUINudgesLabel")}
/>
<Space direction="vertical" size={4}>
<Typography.Text strong>Disable UI nudges</Typography.Text>
<Typography.Text strong>{t("settingsPages.uISettings.disableUINudgesLabel")}</Typography.Text>
<Typography.Text type="secondary">
{disableUINudgesProperty?.description ??
"If true, suppresses in-product UI nudges (survey and Claude Code feedback popups) for all users."}
{disableUINudgesProperty?.description ?? t("settingsPages.uISettings.disableUINudgesDesc")}
</Typography.Text>
</Space>
</Space>

View file

@ -3,6 +3,7 @@ import type { TableProps } from "antd";
import { Table } from "antd";
import Title from "antd/es/typography/Title";
import React from "react";
import { useTranslation } from "react-i18next";
import TableIconActionButton from "../../../common_components/IconActionButton/TableIconActionButtons/TableIconActionButton";
import { AlertingObject } from "./types";
@ -27,10 +28,10 @@ type CallbackRow = AlertingObject & {
mode?: "success" | "failure" | "info" | string;
};
const CALLBACK_MODES: { value: string; label: string }[] = [
{ value: "success", label: "Success" },
{ value: "failure", label: "Failure" },
{ value: "success_and_failure", label: "Success & Failure" },
const getCallbackModes = (t: ReturnType<typeof useTranslation>["t"]): { value: string; label: string }[] => [
{ value: "success", label: t("settingsPages.loggingCallbacksTable.modeSuccess") },
{ value: "failure", label: t("settingsPages.loggingCallbacksTable.modeFailure") },
{ value: "success_and_failure", label: t("settingsPages.loggingCallbacksTable.modeSuccessAndFailure") },
];
export const LoggingCallbacksTable: React.FC<LoggingCallbacksProps> = ({
@ -41,9 +42,14 @@ export const LoggingCallbacksTable: React.FC<LoggingCallbacksProps> = ({
onDelete = () => {},
onAdd = () => {},
}) => {
const { t } = useTranslation();
const callbackModes = React.useMemo(() => getCallbackModes(t), [t]);
const columns: TableProps<CallbackRow>["columns"] = [
{
title: <span className="font-medium text-gray-700">Callback Name</span>,
title: (
<span className="font-medium text-gray-700">{t("settingsPages.loggingCallbacksTable.callbackNameColumn")}</span>
),
dataIndex: "name",
key: "name",
render: (_: string, record: CallbackRow) => {
@ -54,11 +60,11 @@ export const LoggingCallbacksTable: React.FC<LoggingCallbacksProps> = ({
},
},
{
title: <span className="font-medium text-gray-700">Mode</span>,
title: <span className="font-medium text-gray-700">{t("settingsPages.loggingCallbacksTable.modeColumn")}</span>,
key: "mode",
render: (_: unknown, record: CallbackRow) => {
const mode = record.mode || "success";
const label = CALLBACK_MODES.find((m) => m.value === mode)?.label || mode;
const label = callbackModes.find((m) => m.value === mode)?.label || mode;
const badgeClass =
mode === "success"
? "bg-green-100 text-green-800"
@ -74,14 +80,26 @@ export const LoggingCallbacksTable: React.FC<LoggingCallbacksProps> = ({
width: 240,
},
{
title: <span className="font-medium text-gray-700 text-right w-full block">Actions</span>,
title: <span className="font-medium text-gray-700 text-right w-full block">{t("common.actions")}</span>,
key: "actions",
align: "right",
render: (_: unknown, record: CallbackRow) => (
<div className="flex justify-end gap-2">
<TableIconActionButton variant="Test" tooltipText="Test Callback" onClick={() => onTest(record)} />
<TableIconActionButton variant="Edit" tooltipText="Edit Callback" onClick={() => onEdit(record)} />
<TableIconActionButton variant="Delete" tooltipText="Delete Callback" onClick={() => onDelete(record)} />
<TableIconActionButton
variant="Test"
tooltipText={t("settingsPages.loggingCallbacksTable.testCallbackTooltip")}
onClick={() => onTest(record)}
/>
<TableIconActionButton
variant="Edit"
tooltipText={t("settingsPages.loggingCallbacksTable.editCallbackTooltip")}
onClick={() => onEdit(record)}
/>
<TableIconActionButton
variant="Delete"
tooltipText={t("settingsPages.loggingCallbacksTable.deleteCallbackTooltip")}
onClick={() => onDelete(record)}
/>
</div>
),
width: 240,
@ -91,17 +109,18 @@ export const LoggingCallbacksTable: React.FC<LoggingCallbacksProps> = ({
<>
<div className="w-full mt-4">
<Button onClick={onAdd} className="mx-auto">
+ Add Callback
{t("settingsPages.loggingCallbacksTable.addCallbackButton")}
</Button>
<div className="flex justify-between items-center my-2">
<Title level={4}>Active Logging Callbacks</Title>
<Title level={4}>{t("settingsPages.loggingCallbacksTable.activeCallbacksTitle")}</Title>
</div>
{/* Empty state */}
{callbacks.length === 0 ? (
<div className="flex flex-col items-center justify-center p-8 bg-gray-50 border border-gray-200 rounded-lg">
<div className="text-center">
<h3 className="text-lg font-medium text-gray-700 mb-2">No callbacks configured</h3>
<p className="text-gray-500">Add your first callback to start logging data to external services.</p>
<h3 className="text-lg font-medium text-gray-700 mb-2">
{t("settingsPages.loggingCallbacksTable.emptyStateTitle")}
</h3>
<p className="text-gray-500">{t("settingsPages.loggingCallbacksTable.emptyStateDesc")}</p>
</div>
</div>
) : (

View file

@ -7,6 +7,7 @@
import { Button as TremorButton } from "@tremor/react";
import { Button } from "antd";
import React, { useEffect, useState } from "react";
import { useTranslation } from "react-i18next";
import MessageManager from "@/components/molecules/message_manager";
import NotificationManager from "../../../molecules/notifications_manager";
import { fetchAvailableModels, ModelGroup } from "../../../playground/llm_calls/fetch_models";
@ -25,6 +26,7 @@ interface AddFallbacksProps {
}
export default function AddFallbacks({ models, accessToken, value = [], onChange }: AddFallbacksProps) {
const { t } = useTranslation();
const [isModalVisible, setIsModalVisible] = useState(false);
const [modelInfo, setModelInfo] = useState<ModelGroup[]>([]);
const [modalKey, setModalKey] = useState(0); // Key to force remount of form when modal opens
@ -84,9 +86,7 @@ export default function AddFallbacks({ models, accessToken, value = [], onChange
// Validation
const invalidGroups = groups.filter((g) => !g.primaryModel || g.fallbackModels.length === 0);
if (invalidGroups.length > 0) {
MessageManager.error(
`Please complete configuration for all groups. ${invalidGroups.length} group(s) incomplete.`,
);
MessageManager.error(t("settingsPages.addFallbacks.incompleteGroups", { count: invalidGroups.length }));
return;
}
@ -106,7 +106,7 @@ export default function AddFallbacks({ models, accessToken, value = [], onChange
setIsSaving(true);
try {
await onChange(updatedFallbacks);
NotificationManager.success(`${groups.length} fallback configuration(s) added successfully!`);
NotificationManager.success(t("settingsPages.addFallbacks.addSuccess", { count: groups.length }));
handleCancel();
} catch (error) {
// Error handling is done in handleFallbacksChange, so we don't need to show another notification here
@ -126,7 +126,7 @@ export default function AddFallbacks({ models, accessToken, value = [], onChange
onClick={() => setIsModalVisible(true)}
icon={() => <span className="mr-1">+</span>}
>
Add Fallbacks
{t("settingsPages.addFallbacks.addButton")}
</TremorButton>
<AddFallbacksModal open={isModalVisible} onCancel={handleCancel}>
<FallbackSelectionForm
@ -141,7 +141,7 @@ export default function AddFallbacks({ models, accessToken, value = [], onChange
{groups.length > 0 && (
<div className="flex items-center justify-end space-x-3 pt-6 mt-6 border-t border-gray-100">
<Button type="default" onClick={handleCancel} disabled={isSaving}>
Cancel
{t("common.cancel")}
</Button>
<Button
type="default"
@ -149,7 +149,7 @@ export default function AddFallbacks({ models, accessToken, value = [], onChange
disabled={groups.length === 0 || isSaving}
loading={isSaving}
>
{isSaving ? "Saving Configuration..." : "Save All Configurations"}
{isSaving ? t("settingsPages.addFallbacks.savingButton") : t("settingsPages.addFallbacks.saveButton")}
</Button>
</div>
)}

View file

@ -6,6 +6,7 @@
import { Modal } from "antd";
import { ArrowRight } from "lucide-react";
import React from "react";
import { useTranslation } from "react-i18next";
interface AddFallbacksModalProps {
open: boolean;
@ -14,6 +15,8 @@ interface AddFallbacksModalProps {
}
export function AddFallbacksModal({ open, onCancel, children }: AddFallbacksModalProps) {
const { t } = useTranslation();
return (
<Modal
title={
@ -23,10 +26,8 @@ export function AddFallbacksModal({ open, onCancel, children }: AddFallbacksModa
<ArrowRight className="w-5 h-5 text-indigo-600" />
</div>
<div>
<h2 className="text-lg font-bold m-0">Configure Model Fallbacks</h2>
<p className="text-sm text-gray-500 font-normal m-0">
Manage multiple fallback chains for different models (up to 5 groups at a time)
</p>
<h2 className="text-lg font-bold m-0">{t("settingsPages.addFallbacksModal.title")}</h2>
<p className="text-sm text-gray-500 font-normal m-0">{t("settingsPages.addFallbacksModal.subtitle")}</p>
</div>
</div>
</div>

View file

@ -4353,6 +4353,103 @@
"updateSuccess": "SSO settings updated successfully",
"saveFailed": "Failed to save SSO settings: {{error}}",
"processFailed": "Failed to process SSO settings: {{error}}"
},
"redactableField": {
"notConfigured": "Not configured"
},
"roleMappings": {
"title": "Role Mappings",
"roleColumn": "Role",
"mappedGroupsColumn": "Mapped Groups",
"noGroupsMapped": "No groups mapped",
"groupClaim": "Group Claim",
"defaultRole": "Default Role"
},
"sSOSettings": {
"title": "SSO Configuration",
"subtitle": "Manage Single Sign-On authentication settings",
"notConfigured": "Not configured",
"provider": "Provider",
"clientId": "Client ID",
"clientSecret": "Client Secret",
"proxyBaseUrl": "Proxy Base URL",
"tenant": "Tenant",
"authorizationEndpoint": "Authorization Endpoint",
"tokenEndpoint": "Token Endpoint",
"userInfoEndpoint": "User Info Endpoint",
"teamIdsJwtField": "Team IDs JWT Field",
"editSSOSettings": "Edit SSO Settings",
"deleteSSOSettings": "Delete SSO Settings"
},
"sSOSettingsEmptyPlaceholder": {
"noConfigTitle": "No SSO Configuration Found",
"noConfigDesc": "Configure Single Sign-On (SSO) to enable seamless authentication for your team members using your identity provider.",
"configureButton": "Configure SSO"
},
"pageVisibilitySettings": {
"title": "Internal User Page Visibility",
"notSet": "Not set (all pages visible)",
"pagesSelected_one": "{{count}} page selected",
"pagesSelected_other": "{{count}} pages selected",
"defaultHint": "By default, all pages are visible to internal users. Select specific pages to restrict visibility.",
"adminOnlyNote": "Note: Only pages accessible to internal user roles are shown here. Admin-only pages are excluded as they cannot be made visible to internal users regardless of this setting.",
"configureLabel": "Configure Page Visibility",
"saveButton": "Save Page Visibility Settings",
"resetButton": "Reset to Default (All Pages)"
},
"uISettings": {
"cardTitle": "UI Settings",
"loadError": "Could not load UI settings",
"updateError": "Could not update UI settings",
"updateSuccess": "UI settings updated successfully",
"updateSuccessRefreshing": "UI settings updated successfully. Refreshing page...",
"pageVisibilityUpdateSuccess": "Page visibility settings updated successfully",
"disableModelAddLabel": "Disable model add for internal users",
"disableTeamAdminDeleteLabel": "Disable team admin delete team user",
"requireAuthPublicAIHubLabel": "Require authentication for public AI Hub",
"forwardClientHeadersLabel": "Forward client headers to LLM API",
"forwardClientHeadersDesc": "Forwards client headers (Authorization, anthropic-beta, and x-* custom headers) to the upstream LLM. Enable for Claude Code with a Max subscription (forwards the OAuth token) or to pass custom/tracing headers through to the provider. Independent of the BYOK toggle — enable only the one(s) you need.",
"forwardLLMProviderAuthHeadersLabel": "Forward LLM provider auth headers",
"forwardLLMProviderAuthHeadersDesc": "Forwards provider auth headers (x-api-key, x-goog-api-key, api-key, ocp-apim-subscription-key) to the upstream LLM, overriding any deployment-configured key for that request. Enable for Claude Code BYOK (clients bring their own API key). Independent of the client-headers toggle — enable only the one(s) you need.",
"enableProjectsUILabel": "[BETA] Enable Projects (page will refresh)",
"enableProjectsUIDesc": "If enabled, shows the Projects feature in the UI sidebar and the project field in key management.",
"disableAgentsLabel": "Disable agents for internal users",
"allowAgentsTeamAdminsLabel": "Allow agents for team admins",
"disableVectorStoresLabel": "Disable vector stores for internal users",
"allowVectorStoresTeamAdminsLabel": "Allow vector stores for team admins",
"scopeUserSearchLabel": "Scope user search to organization",
"scopeUserSearchDesc": "If enabled, the user search endpoint restricts results by organization. When off, any authenticated user can search all users.",
"disableCustomApiKeysLabel": "Disable custom Virtual key values",
"disableCustomApiKeysDesc": "If true, users cannot specify custom key values. All keys must be auto-generated.",
"disableUINudgesLabel": "Disable UI nudges",
"disableUINudgesDesc": "If true, suppresses in-product UI nudges (survey and Claude Code feedback popups) for all users."
},
"loggingCallbacksTable": {
"addCallbackButton": "+ Add Callback",
"activeCallbacksTitle": "Active Logging Callbacks",
"callbackNameColumn": "Callback Name",
"modeColumn": "Mode",
"modeSuccess": "Success",
"modeFailure": "Failure",
"modeSuccessAndFailure": "Success & Failure",
"testCallbackTooltip": "Test Callback",
"editCallbackTooltip": "Edit Callback",
"deleteCallbackTooltip": "Delete Callback",
"emptyStateTitle": "No callbacks configured",
"emptyStateDesc": "Add your first callback to start logging data to external services."
},
"addFallbacks": {
"addButton": "Add Fallbacks",
"incompleteGroups_one": "Please complete configuration for all groups. {{count}} group incomplete.",
"incompleteGroups_other": "Please complete configuration for all groups. {{count}} groups incomplete.",
"addSuccess_one": "{{count}} fallback configuration added successfully!",
"addSuccess_other": "{{count}} fallback configurations added successfully!",
"savingButton": "Saving Configuration...",
"saveButton": "Save All Configurations"
},
"addFallbacksModal": {
"title": "Configure Model Fallbacks",
"subtitle": "Manage multiple fallback chains for different models (up to 5 groups at a time)"
}
}
}

View file

@ -4353,6 +4353,103 @@
"updateSuccess": "SSO 设置更新成功",
"saveFailed": "保存 SSO 设置失败:{{error}}",
"processFailed": "处理 SSO 设置失败:{{error}}"
},
"redactableField": {
"notConfigured": "未配置"
},
"roleMappings": {
"title": "角色映射",
"roleColumn": "角色",
"mappedGroupsColumn": "映射的用户组",
"noGroupsMapped": "未映射用户组",
"groupClaim": "Group Claim",
"defaultRole": "默认角色"
},
"sSOSettings": {
"title": "SSO 配置",
"subtitle": "管理单点登录认证设置",
"notConfigured": "未配置",
"provider": "提供商",
"clientId": "Client ID",
"clientSecret": "Client Secret",
"proxyBaseUrl": "代理基础 URL",
"tenant": "租户",
"authorizationEndpoint": "授权端点",
"tokenEndpoint": "Token 端点",
"userInfoEndpoint": "用户信息端点",
"teamIdsJwtField": "团队 ID JWT 字段",
"editSSOSettings": "编辑 SSO 设置",
"deleteSSOSettings": "删除 SSO 设置"
},
"sSOSettingsEmptyPlaceholder": {
"noConfigTitle": "未找到 SSO 配置",
"noConfigDesc": "配置单点登录(SSO)可让团队成员通过您的身份提供商进行无缝认证。",
"configureButton": "配置 SSO"
},
"pageVisibilitySettings": {
"title": "内部用户页面可见性",
"notSet": "未设置(所有页面可见)",
"pagesSelected_one": "已选择 {{count}} 个页面",
"pagesSelected_other": "已选择 {{count}} 个页面",
"defaultHint": "默认情况下,所有页面对内部用户可见。选择特定页面可限制可见性。",
"adminOnlyNote": "注意:此处仅显示内部用户角色可访问的页面。仅管理员可访问的页面已排除,无论此设置如何都无法对内部用户显示。",
"configureLabel": "配置页面可见性",
"saveButton": "保存页面可见性设置",
"resetButton": "重置为默认值(所有页面)"
},
"uISettings": {
"cardTitle": "界面设置",
"loadError": "无法加载界面设置",
"updateError": "无法更新界面设置",
"updateSuccess": "界面设置更新成功",
"updateSuccessRefreshing": "界面设置更新成功,正在刷新页面...",
"pageVisibilityUpdateSuccess": "页面可见性设置更新成功",
"disableModelAddLabel": "禁止内部用户添加模型",
"disableTeamAdminDeleteLabel": "禁止团队管理员删除团队用户",
"requireAuthPublicAIHubLabel": "公共 AI Hub 需要认证",
"forwardClientHeadersLabel": "将客户端请求头转发到 LLM API",
"forwardClientHeadersDesc": "将客户端请求头(Authorization、anthropic-beta 及 x-* 自定义头)转发到上游 LLM。可用于带 Max 订阅的 Claude Code(转发 OAuth Token)或将自定义/追踪头透传到提供商。与 BYOK 开关相互独立,按需启用其一。",
"forwardLLMProviderAuthHeadersLabel": "转发 LLM 提供商认证请求头",
"forwardLLMProviderAuthHeadersDesc": "将提供商认证请求头(x-api-key、x-goog-api-key、api-key、ocp-apim-subscription-key)转发到上游 LLM,覆盖该请求的部署配置密钥。可用于 Claude Code BYOK(客户端自带 API 密钥)。与客户端请求头开关相互独立,按需启用其一。",
"enableProjectsUILabel": "[BETA] 启用项目功能(页面将刷新)",
"enableProjectsUIDesc": "启用后,将在界面侧边栏显示项目功能,并在密钥管理中显示项目字段。",
"disableAgentsLabel": "禁止内部用户使用 Agent",
"allowAgentsTeamAdminsLabel": "允许团队管理员使用 Agent",
"disableVectorStoresLabel": "禁止内部用户使用向量存储",
"allowVectorStoresTeamAdminsLabel": "允许团队管理员使用向量存储",
"scopeUserSearchLabel": "将用户搜索范围限定到组织",
"scopeUserSearchDesc": "启用后,用户搜索端点将按组织限制结果。关闭时,任何已认证用户均可搜索所有用户。",
"disableCustomApiKeysLabel": "禁用自定义虚拟密钥值",
"disableCustomApiKeysDesc": "若为 true,用户无法指定自定义密钥值,所有密钥必须自动生成。",
"disableUINudgesLabel": "禁用界面提示",
"disableUINudgesDesc": "若为 true,将为所有用户屏蔽产品内界面提示(调查问卷和 Claude Code 反馈弹窗)。"
},
"loggingCallbacksTable": {
"addCallbackButton": "+ 添加回调",
"activeCallbacksTitle": "已激活的日志回调",
"callbackNameColumn": "回调名称",
"modeColumn": "模式",
"modeSuccess": "成功",
"modeFailure": "失败",
"modeSuccessAndFailure": "成功与失败",
"testCallbackTooltip": "测试回调",
"editCallbackTooltip": "编辑回调",
"deleteCallbackTooltip": "删除回调",
"emptyStateTitle": "未配置回调",
"emptyStateDesc": "添加您的第一个回调,开始将数据记录到外部服务。"
},
"addFallbacks": {
"addButton": "添加回退",
"incompleteGroups_one": "请完成所有组的配置,{{count}} 个组尚未完成。",
"incompleteGroups_other": "请完成所有组的配置,{{count}} 个组尚未完成。",
"addSuccess_one": "成功添加 {{count}} 个回退配置!",
"addSuccess_other": "成功添加 {{count}} 个回退配置!",
"savingButton": "正在保存配置...",
"saveButton": "保存所有配置"
},
"addFallbacksModal": {
"title": "配置模型回退",
"subtitle": "管理不同模型的多个回退链(每次最多 5 组)"
}
}
}