✨ refactor: system settings UI for consistent, compact layouts
Redesign the system settings interface to align with the rest of the console experience by using fixed header actions, removing redundant subtitles, respecting global content width, and standardizing responsive form layouts. Introduce reusable settings layout primitives for forms, switch rows, grouped controls, nested control sections, title status indicators, and page action portals. Replace duplicated card-style switch markup with explicit compact components, improve nested switch readability, and reduce visual noise across authentication, billing, content, integrations, maintenance, models, and request-limit settings. Also complete missing i18n translations, remove obsolete subtitle translation keys, refine i18n sync reporting, fix sidebar truncation for long labels, and verify the frontend with type checking and lint diagnostics.
This commit is contained in:
parent
92a0959448
commit
b08febaa3c
106
web/default/scripts/sync-i18n.mjs
vendored
106
web/default/scripts/sync-i18n.mjs
vendored
@ -29,6 +29,90 @@ const OBFUSCATED_KEYS = [
|
||||
},
|
||||
]
|
||||
|
||||
const BRAND_AND_LITERAL_KEYS = new Set([
|
||||
'AI Proxy',
|
||||
'AIGC2D',
|
||||
'Alipay',
|
||||
'Anthropic',
|
||||
'API URL',
|
||||
'API2GPT',
|
||||
'AccessKey / SecretAccessKey',
|
||||
'AZURE_OPENAI_ENDPOINT *',
|
||||
'Baidu V2',
|
||||
'ChatGPT',
|
||||
'Claude',
|
||||
'Client ID',
|
||||
'Client Secret',
|
||||
'Cloudflare',
|
||||
'Cohere',
|
||||
'DeepSeek',
|
||||
'Discord',
|
||||
'DoubaoVideo',
|
||||
'FastGPT',
|
||||
'Gemini',
|
||||
'Gemini Image 4K',
|
||||
'GitHub',
|
||||
'Jimeng',
|
||||
'JustSong',
|
||||
'LingYiWanWu',
|
||||
'LinuxDO',
|
||||
'Midjourney',
|
||||
'MidjourneyPlus',
|
||||
'Midjourney-Proxy',
|
||||
'MiniMax',
|
||||
'Mistral',
|
||||
'MokaAI',
|
||||
'Moonshot',
|
||||
'New API',
|
||||
'New API <noreply@example.com>',
|
||||
'NewAPI',
|
||||
'OAuth Client Secret',
|
||||
'OhMyGPT',
|
||||
'Ollama',
|
||||
'One API',
|
||||
'OpenAI',
|
||||
'OpenAIMax',
|
||||
'OpenRouter',
|
||||
'Pancake',
|
||||
'Passkey',
|
||||
'Perplexity',
|
||||
'QuantumNous',
|
||||
'Quota:',
|
||||
'Replicate',
|
||||
'SiliconFlow',
|
||||
'Stripe',
|
||||
'Submodel',
|
||||
'SunoAPI',
|
||||
'Telegram',
|
||||
'Tencent',
|
||||
'TTFT P50',
|
||||
'TTFT P95',
|
||||
'TTFT P99',
|
||||
'Uptime Kuma',
|
||||
'Uptime Kuma URL',
|
||||
'Vertex AI',
|
||||
'VolcEngine',
|
||||
'Waffo Pancake Dashboard',
|
||||
'Waffo Pancake MoR',
|
||||
'WeChat',
|
||||
'WeChat Pay',
|
||||
'Webhook URL',
|
||||
'Webhook URL:',
|
||||
'Well-Known URL',
|
||||
'Worker URL',
|
||||
'Xinference',
|
||||
'Xunfei',
|
||||
'Zhipu V4',
|
||||
'"default": "us-central1", "claude-3-5-sonnet-20240620": "europe-west1"',
|
||||
'edit_this',
|
||||
'footer.columns.related.links.midjourney',
|
||||
'footer.columns.related.links.newApiKeyTool',
|
||||
'my-status',
|
||||
'new-api-key-tool',
|
||||
'price_xxx',
|
||||
'whsec_xxx',
|
||||
])
|
||||
|
||||
function isPlainObject(v) {
|
||||
return typeof v === 'object' && v !== null && !Array.isArray(v)
|
||||
}
|
||||
@ -97,6 +181,24 @@ function isLikelyUntranslated({ locale, baseValue, value }) {
|
||||
|
||||
// Skip short tokens / acronyms / ids
|
||||
const s = baseValue.trim()
|
||||
if (BRAND_AND_LITERAL_KEYS.has(s)) return false
|
||||
if (
|
||||
/^https?:\/\//.test(s) ||
|
||||
/^\/[\w/-]+/.test(s) ||
|
||||
/^[\w.-]+@[\w.-]+$/.test(s) ||
|
||||
/^smtp\./i.test(s) ||
|
||||
/^socks5:/i.test(s) ||
|
||||
/^org-/.test(s) ||
|
||||
/^gpt-/i.test(s) ||
|
||||
/^checkout\./.test(s) ||
|
||||
/^footer\./.test(s) ||
|
||||
/^[A-Z0-9_ *./:-]+$/.test(s) ||
|
||||
s.startsWith('{') ||
|
||||
s.startsWith('[') ||
|
||||
s.includes(' ')
|
||||
) {
|
||||
return false
|
||||
}
|
||||
if (s.length < 6) return false
|
||||
if (!/[A-Za-z]{3,}/.test(s)) return false
|
||||
|
||||
@ -187,6 +289,8 @@ async function main() {
|
||||
|
||||
if (Object.keys(extras).length > 0) {
|
||||
await fs.writeFile(path.join(extrasDir, `${locale}.extras.json`), stableStringify(extras), 'utf8')
|
||||
} else {
|
||||
await fs.rm(path.join(extrasDir, `${locale}.extras.json`), { force: true })
|
||||
}
|
||||
if (Object.keys(untranslated).length > 0) {
|
||||
await fs.writeFile(
|
||||
@ -194,6 +298,8 @@ async function main() {
|
||||
stableStringify(untranslated),
|
||||
'utf8',
|
||||
)
|
||||
} else {
|
||||
await fs.rm(path.join(reportsDir, `${locale}.untranslated.json`), { force: true })
|
||||
}
|
||||
|
||||
// Rewrite locale file in base order (even for en to normalize formatting)
|
||||
|
||||
@ -231,9 +231,9 @@ export function ChatPresetsItem({ item }: { item: NavChatPresets }) {
|
||||
<DropdownMenuTrigger
|
||||
render={<SidebarMenuButton tooltip={item.title} />}
|
||||
>
|
||||
{item.icon && <item.icon className='h-4 w-4' />}
|
||||
<span>{item.title}</span>
|
||||
<ChevronRight className='ms-auto h-4 w-4 opacity-70' />
|
||||
{item.icon && <item.icon className='h-4 w-4 shrink-0' />}
|
||||
<span className='min-w-0 flex-1 truncate'>{item.title}</span>
|
||||
<ChevronRight className='ms-auto h-4 w-4 shrink-0 opacity-70' />
|
||||
</DropdownMenuTrigger>
|
||||
<DropdownMenuContent align='start'>
|
||||
{visiblePresets.map((preset) => (
|
||||
@ -261,9 +261,9 @@ export function ChatPresetsItem({ item }: { item: NavChatPresets }) {
|
||||
className='group/collapsible-trigger'
|
||||
render={<SidebarMenuButton />}
|
||||
>
|
||||
{item.icon && <item.icon />}
|
||||
<span>{item.title}</span>
|
||||
<ChevronRight className='ms-auto transition-transform duration-200 group-data-[panel-open]/collapsible-trigger:rotate-90' />
|
||||
{item.icon && <item.icon className='shrink-0' />}
|
||||
<span className='min-w-0 flex-1 truncate'>{item.title}</span>
|
||||
<ChevronRight className='ms-auto size-4 shrink-0 transition-transform duration-200 group-data-[panel-open]/collapsible-trigger:rotate-90' />
|
||||
</CollapsibleTrigger>
|
||||
<CollapsibleContent className='CollapsibleContent'>
|
||||
<SidebarMenuSub>
|
||||
|
||||
@ -112,7 +112,7 @@ export function NavGroup({ title, items }: NavGroupProps) {
|
||||
* Navigation badge component
|
||||
*/
|
||||
function NavBadge({ children }: { children: ReactNode }) {
|
||||
return <Badge className='px-1 py-0 text-xs'>{children}</Badge>
|
||||
return <Badge className='shrink-0 px-1 py-0 text-xs'>{children}</Badge>
|
||||
}
|
||||
|
||||
/**
|
||||
@ -127,8 +127,8 @@ function SidebarMenuLink({ item, href }: { item: NavLink; href: string }) {
|
||||
tooltip={item.title}
|
||||
render={<Link to={item.url} onClick={() => setOpenMobile(false)} />}
|
||||
>
|
||||
{item.icon && <item.icon />}
|
||||
<span>{item.title}</span>
|
||||
{item.icon && <item.icon className='shrink-0' />}
|
||||
<span className='min-w-0 flex-1 truncate'>{item.title}</span>
|
||||
{item.badge && <NavBadge>{item.badge}</NavBadge>}
|
||||
</SidebarMenuButton>
|
||||
</SidebarMenuItem>
|
||||
@ -170,10 +170,10 @@ function SidebarMenuCollapsible({
|
||||
className='group/collapsible-trigger'
|
||||
render={<SidebarMenuButton tooltip={item.title} />}
|
||||
>
|
||||
{item.icon && <item.icon />}
|
||||
<span>{item.title}</span>
|
||||
{item.icon && <item.icon className='shrink-0' />}
|
||||
<span className='min-w-0 flex-1 truncate'>{item.title}</span>
|
||||
{item.badge && <NavBadge>{item.badge}</NavBadge>}
|
||||
<ChevronRight className='ms-auto transition-transform duration-200 group-data-[panel-open]/collapsible-trigger:rotate-90' />
|
||||
<ChevronRight className='ms-auto size-4 shrink-0 transition-transform duration-200 group-data-[panel-open]/collapsible-trigger:rotate-90' />
|
||||
</CollapsibleTrigger>
|
||||
<CollapsibleContent className='CollapsibleContent'>
|
||||
<SidebarMenuSub>
|
||||
@ -185,8 +185,8 @@ function SidebarMenuCollapsible({
|
||||
<Link to={subItem.url} onClick={() => setOpenMobile(false)} />
|
||||
}
|
||||
>
|
||||
{subItem.icon && <subItem.icon />}
|
||||
<span>{subItem.title}</span>
|
||||
{subItem.icon && <subItem.icon className='shrink-0' />}
|
||||
<span className='min-w-0 flex-1 truncate'>{subItem.title}</span>
|
||||
{subItem.badge && <NavBadge>{subItem.badge}</NavBadge>}
|
||||
</SidebarMenuSubButton>
|
||||
</SidebarMenuSubItem>
|
||||
@ -219,10 +219,10 @@ function SidebarMenuCollapsedDropdown({
|
||||
/>
|
||||
}
|
||||
>
|
||||
{item.icon && <item.icon />}
|
||||
<span>{item.title}</span>
|
||||
{item.icon && <item.icon className='shrink-0' />}
|
||||
<span className='min-w-0 flex-1 truncate'>{item.title}</span>
|
||||
{item.badge && <NavBadge>{item.badge}</NavBadge>}
|
||||
<ChevronRight className='ms-auto transition-transform duration-200 group-data-[popup-open]/dropdown-trigger:rotate-90' />
|
||||
<ChevronRight className='ms-auto size-4 shrink-0 transition-transform duration-200 group-data-[popup-open]/dropdown-trigger:rotate-90' />
|
||||
</DropdownMenuTrigger>
|
||||
<DropdownMenuContent side='right' align='start' sideOffset={4}>
|
||||
<DropdownMenuGroup>
|
||||
|
||||
@ -33,11 +33,6 @@ function SectionPageLayoutTitle(_props: SlotProps) {
|
||||
}
|
||||
SectionPageLayoutTitle.displayName = 'SectionPageLayout.Title'
|
||||
|
||||
function SectionPageLayoutDescription(_props: SlotProps) {
|
||||
return null
|
||||
}
|
||||
SectionPageLayoutDescription.displayName = 'SectionPageLayout.Description'
|
||||
|
||||
function SectionPageLayoutActions(_props: SlotProps) {
|
||||
return null
|
||||
}
|
||||
@ -87,13 +82,13 @@ export function SectionPageLayout(props: SectionPageLayoutProps) {
|
||||
<div className='mb-2 sm:mb-3'>{breadcrumb}</div>
|
||||
)}
|
||||
<div className='flex flex-wrap items-center justify-between gap-x-3 gap-y-2 sm:gap-x-4'>
|
||||
<div className='min-w-0'>
|
||||
<div className='min-w-0 flex-1'>
|
||||
<h2 className='truncate text-base font-bold tracking-tight sm:text-lg'>
|
||||
{title}
|
||||
</h2>
|
||||
</div>
|
||||
{actions != null && (
|
||||
<div className='flex shrink-0 flex-wrap items-center gap-2 sm:gap-x-4'>
|
||||
<div className='flex shrink-0 flex-wrap items-center justify-end gap-2 sm:gap-x-4'>
|
||||
{actions}
|
||||
</div>
|
||||
)}
|
||||
@ -114,7 +109,6 @@ export function SectionPageLayout(props: SectionPageLayoutProps) {
|
||||
}
|
||||
|
||||
SectionPageLayout.Title = SectionPageLayoutTitle
|
||||
SectionPageLayout.Description = SectionPageLayoutDescription
|
||||
SectionPageLayout.Actions = SectionPageLayoutActions
|
||||
SectionPageLayout.Content = SectionPageLayoutContent
|
||||
SectionPageLayout.Breadcrumb = SectionPageLayoutBreadcrumb
|
||||
|
||||
3
web/default/src/features/channels/index.tsx
vendored
3
web/default/src/features/channels/index.tsx
vendored
@ -29,9 +29,6 @@ export function Channels() {
|
||||
<ChannelsProvider>
|
||||
<SectionPageLayout>
|
||||
<SectionPageLayout.Title>{t('Channels')}</SectionPageLayout.Title>
|
||||
<SectionPageLayout.Description>
|
||||
{t('Manage API channels and provider configurations')}
|
||||
</SectionPageLayout.Description>
|
||||
<SectionPageLayout.Actions>
|
||||
<ChannelsPrimaryButtons />
|
||||
</SectionPageLayout.Actions>
|
||||
|
||||
11
web/default/src/features/dashboard/index.tsx
vendored
11
web/default/src/features/dashboard/index.tsx
vendored
@ -130,21 +130,15 @@ function PerformanceOverviewFallback() {
|
||||
)
|
||||
}
|
||||
|
||||
const SECTION_META: Record<
|
||||
DashboardSectionId,
|
||||
{ titleKey: string; descriptionKey: string }
|
||||
> = {
|
||||
const SECTION_META: Record<DashboardSectionId, { titleKey: string }> = {
|
||||
overview: {
|
||||
titleKey: 'Overview',
|
||||
descriptionKey: 'View dashboard overview and statistics',
|
||||
},
|
||||
models: {
|
||||
titleKey: 'Model Call Analytics',
|
||||
descriptionKey: 'View model call count analytics and charts',
|
||||
},
|
||||
users: {
|
||||
titleKey: 'User Analytics',
|
||||
descriptionKey: 'View user consumption statistics and charts',
|
||||
},
|
||||
}
|
||||
|
||||
@ -227,9 +221,6 @@ export function Dashboard() {
|
||||
return (
|
||||
<SectionPageLayout>
|
||||
<SectionPageLayout.Title>{t(meta.titleKey)}</SectionPageLayout.Title>
|
||||
<SectionPageLayout.Description>
|
||||
{t(meta.descriptionKey)}
|
||||
</SectionPageLayout.Description>
|
||||
<SectionPageLayout.Content>
|
||||
<div className='space-y-3 sm:space-y-4'>
|
||||
{activeSection !== 'overview' && (
|
||||
|
||||
@ -26,19 +26,16 @@ const DASHBOARD_SECTIONS = [
|
||||
{
|
||||
id: 'overview',
|
||||
titleKey: 'Overview',
|
||||
descriptionKey: 'View dashboard overview and statistics',
|
||||
build: () => null,
|
||||
},
|
||||
{
|
||||
id: 'models',
|
||||
titleKey: 'Model Call Analytics',
|
||||
descriptionKey: 'View model call count analytics and charts',
|
||||
build: () => null,
|
||||
},
|
||||
{
|
||||
id: 'users',
|
||||
titleKey: 'User Analytics',
|
||||
descriptionKey: 'View user consumption statistics and charts',
|
||||
adminOnly: true,
|
||||
build: () => null,
|
||||
},
|
||||
|
||||
3
web/default/src/features/keys/index.tsx
vendored
3
web/default/src/features/keys/index.tsx
vendored
@ -29,9 +29,6 @@ export function ApiKeys() {
|
||||
<ApiKeysProvider>
|
||||
<SectionPageLayout>
|
||||
<SectionPageLayout.Title>{t('API Keys')}</SectionPageLayout.Title>
|
||||
<SectionPageLayout.Description>
|
||||
{t('Manage your API keys for accessing the service')}
|
||||
</SectionPageLayout.Description>
|
||||
<SectionPageLayout.Actions>
|
||||
<ApiKeysPrimaryButtons />
|
||||
</SectionPageLayout.Actions>
|
||||
|
||||
10
web/default/src/features/models/index.tsx
vendored
10
web/default/src/features/models/index.tsx
vendored
@ -42,17 +42,12 @@ import {
|
||||
|
||||
const route = getRouteApi('/_authenticated/models/$section')
|
||||
|
||||
const SECTION_META: Record<
|
||||
ModelsSectionId,
|
||||
{ titleKey: string; descriptionKey: string }
|
||||
> = {
|
||||
const SECTION_META: Record<ModelsSectionId, { titleKey: string }> = {
|
||||
metadata: {
|
||||
titleKey: 'Metadata',
|
||||
descriptionKey: 'Manage model metadata and configuration',
|
||||
},
|
||||
deployments: {
|
||||
titleKey: 'Deployments',
|
||||
descriptionKey: 'Manage model deployments',
|
||||
},
|
||||
}
|
||||
|
||||
@ -126,9 +121,6 @@ function ModelsContent() {
|
||||
<>
|
||||
<SectionPageLayout>
|
||||
<SectionPageLayout.Title>{t(meta.titleKey)}</SectionPageLayout.Title>
|
||||
<SectionPageLayout.Description>
|
||||
{t(meta.descriptionKey)}
|
||||
</SectionPageLayout.Description>
|
||||
<SectionPageLayout.Actions>
|
||||
{activeSection === 'metadata' ? (
|
||||
<ModelsPrimaryButtons />
|
||||
|
||||
@ -25,13 +25,11 @@ const MODELS_SECTIONS = [
|
||||
{
|
||||
id: 'metadata',
|
||||
titleKey: 'Metadata',
|
||||
descriptionKey: 'Manage model metadata and configuration',
|
||||
build: () => null, // Content is rendered directly in the page component
|
||||
},
|
||||
{
|
||||
id: 'deployments',
|
||||
titleKey: 'Deployments',
|
||||
descriptionKey: 'Manage model deployments',
|
||||
build: () => null, // Content is rendered directly in the page component
|
||||
},
|
||||
] as const
|
||||
|
||||
@ -31,9 +31,6 @@ export function Redemptions() {
|
||||
<SectionPageLayout.Title>
|
||||
{t('Redemption Codes')}
|
||||
</SectionPageLayout.Title>
|
||||
<SectionPageLayout.Description>
|
||||
{t('Manage redemption codes for quota top-up')}
|
||||
</SectionPageLayout.Description>
|
||||
<SectionPageLayout.Actions>
|
||||
<RedemptionsPrimaryButtons />
|
||||
</SectionPageLayout.Actions>
|
||||
|
||||
@ -38,9 +38,6 @@ function SubscriptionsContent() {
|
||||
<SectionPageLayout.Title>
|
||||
{t('Subscription Management')}
|
||||
</SectionPageLayout.Title>
|
||||
<SectionPageLayout.Description>
|
||||
{t('Manage subscription plan creation, pricing and status')}
|
||||
</SectionPageLayout.Description>
|
||||
<SectionPageLayout.Actions>
|
||||
<div className='flex items-center gap-2'>
|
||||
<Alert variant='default' className='hidden px-3 py-2 sm:flex'>
|
||||
|
||||
@ -21,7 +21,6 @@ import * as z from 'zod'
|
||||
import { useForm } from 'react-hook-form'
|
||||
import { zodResolver } from '@hookform/resolvers/zod'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { Button } from '@/components/ui/button'
|
||||
import {
|
||||
Form,
|
||||
FormControl,
|
||||
@ -33,6 +32,12 @@ import {
|
||||
} from '@/components/ui/form'
|
||||
import { Switch } from '@/components/ui/switch'
|
||||
import { Textarea } from '@/components/ui/textarea'
|
||||
import {
|
||||
SettingsForm,
|
||||
SettingsSwitchContent,
|
||||
SettingsSwitchItem,
|
||||
} from '../components/settings-form-layout'
|
||||
import { SettingsPageFormActions } from '../components/settings-page-context'
|
||||
import { SettingsSection } from '../components/settings-section'
|
||||
import { useResetForm } from '../hooks/use-reset-form'
|
||||
import { useUpdateOption } from '../hooks/use-update-option'
|
||||
@ -100,32 +105,31 @@ export function BasicAuthSection({ defaultValues }: BasicAuthSectionProps) {
|
||||
}
|
||||
|
||||
return (
|
||||
<SettingsSection
|
||||
title={t('Basic Authentication')}
|
||||
description={t('Configure password-based login and registration')}
|
||||
>
|
||||
<SettingsSection title={t('Basic Authentication')}>
|
||||
<Form {...form}>
|
||||
<form onSubmit={form.handleSubmit(onSubmit)} className='space-y-6'>
|
||||
<SettingsForm onSubmit={form.handleSubmit(onSubmit)}>
|
||||
<SettingsPageFormActions
|
||||
onSave={form.handleSubmit(onSubmit)}
|
||||
isSaving={updateOption.isPending}
|
||||
/>
|
||||
<FormField
|
||||
control={form.control}
|
||||
name='PasswordLoginEnabled'
|
||||
render={({ field }) => (
|
||||
<FormItem className='flex flex-row items-center justify-between rounded-lg border p-4'>
|
||||
<div className='space-y-0.5'>
|
||||
<FormLabel className='text-base'>
|
||||
{t('Password Login')}
|
||||
</FormLabel>
|
||||
<SettingsSwitchItem>
|
||||
<SettingsSwitchContent>
|
||||
<FormLabel>{t('Password Login')}</FormLabel>
|
||||
<FormDescription>
|
||||
{t('Allow users to log in with password')}
|
||||
</FormDescription>
|
||||
</div>
|
||||
</SettingsSwitchContent>
|
||||
<FormControl>
|
||||
<Switch
|
||||
checked={field.value}
|
||||
onCheckedChange={field.onChange}
|
||||
/>
|
||||
</FormControl>
|
||||
</FormItem>
|
||||
</SettingsSwitchItem>
|
||||
)}
|
||||
/>
|
||||
|
||||
@ -133,22 +137,20 @@ export function BasicAuthSection({ defaultValues }: BasicAuthSectionProps) {
|
||||
control={form.control}
|
||||
name='RegisterEnabled'
|
||||
render={({ field }) => (
|
||||
<FormItem className='flex flex-row items-center justify-between rounded-lg border p-4'>
|
||||
<div className='space-y-0.5'>
|
||||
<FormLabel className='text-base'>
|
||||
{t('Registration Enabled')}
|
||||
</FormLabel>
|
||||
<SettingsSwitchItem>
|
||||
<SettingsSwitchContent>
|
||||
<FormLabel>{t('Registration Enabled')}</FormLabel>
|
||||
<FormDescription>
|
||||
{t('Allow new users to register')}
|
||||
</FormDescription>
|
||||
</div>
|
||||
</SettingsSwitchContent>
|
||||
<FormControl>
|
||||
<Switch
|
||||
checked={field.value}
|
||||
onCheckedChange={field.onChange}
|
||||
/>
|
||||
</FormControl>
|
||||
</FormItem>
|
||||
</SettingsSwitchItem>
|
||||
)}
|
||||
/>
|
||||
|
||||
@ -156,22 +158,20 @@ export function BasicAuthSection({ defaultValues }: BasicAuthSectionProps) {
|
||||
control={form.control}
|
||||
name='PasswordRegisterEnabled'
|
||||
render={({ field }) => (
|
||||
<FormItem className='flex flex-row items-center justify-between rounded-lg border p-4'>
|
||||
<div className='space-y-0.5'>
|
||||
<FormLabel className='text-base'>
|
||||
{t('Password Registration')}
|
||||
</FormLabel>
|
||||
<SettingsSwitchItem>
|
||||
<SettingsSwitchContent>
|
||||
<FormLabel>{t('Password Registration')}</FormLabel>
|
||||
<FormDescription>
|
||||
{t('Allow registration with password')}
|
||||
</FormDescription>
|
||||
</div>
|
||||
</SettingsSwitchContent>
|
||||
<FormControl>
|
||||
<Switch
|
||||
checked={field.value}
|
||||
onCheckedChange={field.onChange}
|
||||
/>
|
||||
</FormControl>
|
||||
</FormItem>
|
||||
</SettingsSwitchItem>
|
||||
)}
|
||||
/>
|
||||
|
||||
@ -179,22 +179,20 @@ export function BasicAuthSection({ defaultValues }: BasicAuthSectionProps) {
|
||||
control={form.control}
|
||||
name='EmailVerificationEnabled'
|
||||
render={({ field }) => (
|
||||
<FormItem className='flex flex-row items-center justify-between rounded-lg border p-4'>
|
||||
<div className='space-y-0.5'>
|
||||
<FormLabel className='text-base'>
|
||||
{t('Email Verification')}
|
||||
</FormLabel>
|
||||
<SettingsSwitchItem>
|
||||
<SettingsSwitchContent>
|
||||
<FormLabel>{t('Email Verification')}</FormLabel>
|
||||
<FormDescription>
|
||||
{t('Require email verification for new accounts')}
|
||||
</FormDescription>
|
||||
</div>
|
||||
</SettingsSwitchContent>
|
||||
<FormControl>
|
||||
<Switch
|
||||
checked={field.value}
|
||||
onCheckedChange={field.onChange}
|
||||
/>
|
||||
</FormControl>
|
||||
</FormItem>
|
||||
</SettingsSwitchItem>
|
||||
)}
|
||||
/>
|
||||
|
||||
@ -202,22 +200,20 @@ export function BasicAuthSection({ defaultValues }: BasicAuthSectionProps) {
|
||||
control={form.control}
|
||||
name='EmailDomainRestrictionEnabled'
|
||||
render={({ field }) => (
|
||||
<FormItem className='flex flex-row items-center justify-between rounded-lg border p-4'>
|
||||
<div className='space-y-0.5'>
|
||||
<FormLabel className='text-base'>
|
||||
{t('Email Domain Restriction')}
|
||||
</FormLabel>
|
||||
<SettingsSwitchItem>
|
||||
<SettingsSwitchContent>
|
||||
<FormLabel>{t('Email Domain Restriction')}</FormLabel>
|
||||
<FormDescription>
|
||||
{t('Only allow specific email domains')}
|
||||
</FormDescription>
|
||||
</div>
|
||||
</SettingsSwitchContent>
|
||||
<FormControl>
|
||||
<Switch
|
||||
checked={field.value}
|
||||
onCheckedChange={field.onChange}
|
||||
/>
|
||||
</FormControl>
|
||||
</FormItem>
|
||||
</SettingsSwitchItem>
|
||||
)}
|
||||
/>
|
||||
|
||||
@ -225,22 +221,20 @@ export function BasicAuthSection({ defaultValues }: BasicAuthSectionProps) {
|
||||
control={form.control}
|
||||
name='EmailAliasRestrictionEnabled'
|
||||
render={({ field }) => (
|
||||
<FormItem className='flex flex-row items-center justify-between rounded-lg border p-4'>
|
||||
<div className='space-y-0.5'>
|
||||
<FormLabel className='text-base'>
|
||||
{t('Email Alias Restriction')}
|
||||
</FormLabel>
|
||||
<SettingsSwitchItem>
|
||||
<SettingsSwitchContent>
|
||||
<FormLabel>{t('Email Alias Restriction')}</FormLabel>
|
||||
<FormDescription>
|
||||
{t('Block email aliases (e.g., user+alias@domain.com)')}
|
||||
</FormDescription>
|
||||
</div>
|
||||
</SettingsSwitchContent>
|
||||
<FormControl>
|
||||
<Switch
|
||||
checked={field.value}
|
||||
onCheckedChange={field.onChange}
|
||||
/>
|
||||
</FormControl>
|
||||
</FormItem>
|
||||
</SettingsSwitchItem>
|
||||
)}
|
||||
/>
|
||||
|
||||
@ -266,11 +260,7 @@ export function BasicAuthSection({ defaultValues }: BasicAuthSectionProps) {
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
|
||||
<Button type='submit' disabled={updateOption.isPending}>
|
||||
{updateOption.isPending ? t('Saving...') : t('Save Changes')}
|
||||
</Button>
|
||||
</form>
|
||||
</SettingsForm>
|
||||
</Form>
|
||||
</SettingsSection>
|
||||
)
|
||||
|
||||
@ -21,7 +21,6 @@ import * as z from 'zod'
|
||||
import { useForm } from 'react-hook-form'
|
||||
import { zodResolver } from '@hookform/resolvers/zod'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { Button } from '@/components/ui/button'
|
||||
import {
|
||||
Form,
|
||||
FormControl,
|
||||
@ -33,6 +32,12 @@ import {
|
||||
} from '@/components/ui/form'
|
||||
import { Input } from '@/components/ui/input'
|
||||
import { Switch } from '@/components/ui/switch'
|
||||
import {
|
||||
SettingsForm,
|
||||
SettingsSwitchContent,
|
||||
SettingsSwitchItem,
|
||||
} from '../components/settings-form-layout'
|
||||
import { SettingsPageFormActions } from '../components/settings-page-context'
|
||||
import { SettingsSection } from '../components/settings-section'
|
||||
import { useUpdateOption } from '../hooks/use-update-option'
|
||||
|
||||
@ -75,40 +80,33 @@ export function BotProtectionSection({
|
||||
}
|
||||
|
||||
return (
|
||||
<SettingsSection
|
||||
title={t('Bot Protection')}
|
||||
description={t(
|
||||
'Protect login and registration with Cloudflare Turnstile'
|
||||
)}
|
||||
>
|
||||
<SettingsSection title={t('Bot Protection')}>
|
||||
<Form {...form}>
|
||||
<form
|
||||
onSubmit={form.handleSubmit(onSubmit)}
|
||||
className='space-y-6'
|
||||
autoComplete='off'
|
||||
>
|
||||
<SettingsForm onSubmit={form.handleSubmit(onSubmit)} autoComplete='off'>
|
||||
<SettingsPageFormActions
|
||||
onSave={form.handleSubmit(onSubmit)}
|
||||
isSaving={updateOption.isPending}
|
||||
/>
|
||||
<FormField
|
||||
control={form.control}
|
||||
name='TurnstileCheckEnabled'
|
||||
render={({ field }) => (
|
||||
<FormItem className='flex flex-row items-center justify-between rounded-lg border p-4'>
|
||||
<div className='space-y-0.5'>
|
||||
<FormLabel className='text-base'>
|
||||
{t('Enable Turnstile')}
|
||||
</FormLabel>
|
||||
<SettingsSwitchItem>
|
||||
<SettingsSwitchContent>
|
||||
<FormLabel>{t('Enable Turnstile')}</FormLabel>
|
||||
<FormDescription>
|
||||
{t(
|
||||
'Protect login and registration with Cloudflare Turnstile'
|
||||
)}
|
||||
</FormDescription>
|
||||
</div>
|
||||
</SettingsSwitchContent>
|
||||
<FormControl>
|
||||
<Switch
|
||||
checked={field.value}
|
||||
onCheckedChange={field.onChange}
|
||||
/>
|
||||
</FormControl>
|
||||
</FormItem>
|
||||
</SettingsSwitchItem>
|
||||
)}
|
||||
/>
|
||||
|
||||
@ -148,11 +146,7 @@ export function BotProtectionSection({
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
|
||||
<Button type='submit' disabled={updateOption.isPending}>
|
||||
{updateOption.isPending ? t('Saving...') : t('Save Changes')}
|
||||
</Button>
|
||||
</form>
|
||||
</SettingsForm>
|
||||
</Form>
|
||||
</SettingsSection>
|
||||
)
|
||||
|
||||
@ -29,6 +29,7 @@ import {
|
||||
SelectTrigger,
|
||||
SelectValue,
|
||||
} from '@/components/ui/select'
|
||||
import { SettingsControlGroup } from '../../../components/settings-form-layout'
|
||||
import { OAUTH_PRESETS, type CustomOAuthFormValues } from '../types'
|
||||
|
||||
type PresetSelectorProps = {
|
||||
@ -102,7 +103,7 @@ export function PresetSelector(props: PresetSelectorProps) {
|
||||
}
|
||||
|
||||
return (
|
||||
<div className='space-y-3 rounded-lg border border-dashed p-4'>
|
||||
<SettingsControlGroup className='space-y-3 border-dashed'>
|
||||
<p className='text-sm font-medium'>{t('Quick Setup from Preset')}</p>
|
||||
<div className='grid grid-cols-1 gap-3 sm:grid-cols-2'>
|
||||
<div className='space-y-1.5'>
|
||||
@ -140,6 +141,6 @@ export function PresetSelector(props: PresetSelectorProps) {
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</SettingsControlGroup>
|
||||
)
|
||||
}
|
||||
|
||||
@ -50,6 +50,11 @@ import {
|
||||
import { Separator } from '@/components/ui/separator'
|
||||
import { Switch } from '@/components/ui/switch'
|
||||
import { Textarea } from '@/components/ui/textarea'
|
||||
import {
|
||||
SettingsForm,
|
||||
SettingsSwitchContent,
|
||||
SettingsSwitchItem,
|
||||
} from '../../../components/settings-form-layout'
|
||||
import {
|
||||
useCreateProvider,
|
||||
useUpdateProvider,
|
||||
@ -185,7 +190,7 @@ export function ProviderFormDialog(props: ProviderFormDialogProps) {
|
||||
</DialogHeader>
|
||||
|
||||
<Form {...form}>
|
||||
<form onSubmit={form.handleSubmit(onSubmit)} className='space-y-6'>
|
||||
<SettingsForm onSubmit={form.handleSubmit(onSubmit)}>
|
||||
{/* Preset Selector (only for creating) */}
|
||||
{!isEditing && <PresetSelector form={form} />}
|
||||
|
||||
@ -197,22 +202,20 @@ export function ProviderFormDialog(props: ProviderFormDialogProps) {
|
||||
control={form.control}
|
||||
name='enabled'
|
||||
render={({ field }) => (
|
||||
<FormItem className='flex flex-row items-center justify-between rounded-lg border p-4'>
|
||||
<div className='space-y-0.5'>
|
||||
<FormLabel className='text-base'>
|
||||
{t('Enabled')}
|
||||
</FormLabel>
|
||||
<SettingsSwitchItem>
|
||||
<SettingsSwitchContent>
|
||||
<FormLabel>{t('Enabled')}</FormLabel>
|
||||
<FormDescription>
|
||||
{t('Allow users to sign in with this provider')}
|
||||
</FormDescription>
|
||||
</div>
|
||||
</SettingsSwitchContent>
|
||||
<FormControl>
|
||||
<Switch
|
||||
checked={field.value}
|
||||
onCheckedChange={field.onChange}
|
||||
/>
|
||||
</FormControl>
|
||||
</FormItem>
|
||||
</SettingsSwitchItem>
|
||||
)}
|
||||
/>
|
||||
|
||||
@ -602,7 +605,7 @@ export function ProviderFormDialog(props: ProviderFormDialogProps) {
|
||||
: t('Create Provider')}
|
||||
</Button>
|
||||
</DialogFooter>
|
||||
</form>
|
||||
</SettingsForm>
|
||||
</Form>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
|
||||
@ -50,12 +50,7 @@ export function CustomOAuthSection() {
|
||||
|
||||
if (isLoading) {
|
||||
return (
|
||||
<SettingsSection
|
||||
title={t('Custom OAuth Providers')}
|
||||
description={t(
|
||||
'Configure custom OAuth providers for user authentication'
|
||||
)}
|
||||
>
|
||||
<SettingsSection title={t('Custom OAuth Providers')}>
|
||||
<div className='text-muted-foreground py-8 text-center text-sm'>
|
||||
{t('Loading...')}
|
||||
</div>
|
||||
@ -64,12 +59,7 @@ export function CustomOAuthSection() {
|
||||
}
|
||||
|
||||
return (
|
||||
<SettingsSection
|
||||
title={t('Custom OAuth Providers')}
|
||||
description={t(
|
||||
'Configure custom OAuth providers for user authentication'
|
||||
)}
|
||||
>
|
||||
<SettingsSection title={t('Custom OAuth Providers')}>
|
||||
<ProviderTable
|
||||
providers={providers}
|
||||
onEdit={handleEdit}
|
||||
|
||||
@ -21,6 +21,7 @@ import type { AuthSettings } from '../types'
|
||||
import {
|
||||
AUTH_DEFAULT_SECTION,
|
||||
getAuthSectionContent,
|
||||
getAuthSectionMeta,
|
||||
} from './section-registry.tsx'
|
||||
|
||||
const defaultAuthSettings: AuthSettings = {
|
||||
@ -74,6 +75,7 @@ export function AuthSettings() {
|
||||
defaultSettings={defaultAuthSettings}
|
||||
defaultSection={AUTH_DEFAULT_SECTION}
|
||||
getSectionContent={getAuthSectionContent}
|
||||
getSectionMeta={getAuthSectionMeta}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
@ -21,10 +21,8 @@ import * as z from 'zod'
|
||||
import axios from 'axios'
|
||||
import { useForm } from 'react-hook-form'
|
||||
import { zodResolver } from '@hookform/resolvers/zod'
|
||||
import { RotateCcw } from 'lucide-react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { toast } from 'sonner'
|
||||
import { Button } from '@/components/ui/button'
|
||||
import {
|
||||
Form,
|
||||
FormControl,
|
||||
@ -39,6 +37,12 @@ import { Switch } from '@/components/ui/switch'
|
||||
import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs'
|
||||
import { FormDirtyIndicator } from '../components/form-dirty-indicator'
|
||||
import { FormNavigationGuard } from '../components/form-navigation-guard'
|
||||
import {
|
||||
SettingsForm,
|
||||
SettingsSwitchContent,
|
||||
SettingsSwitchItem,
|
||||
} from '../components/settings-form-layout'
|
||||
import { SettingsPageFormActions } from '../components/settings-page-context'
|
||||
import { SettingsSection } from '../components/settings-section'
|
||||
import { useUpdateOption } from '../hooks/use-update-option'
|
||||
|
||||
@ -69,6 +73,9 @@ const oauthSchema = z.object({
|
||||
WeChatAccountQRCodeImageURL: z.string().optional(),
|
||||
})
|
||||
|
||||
const oauthTabContentClassName =
|
||||
'grid min-w-0 gap-x-5 gap-y-6 lg:grid-cols-2 [&>[data-slot=form-item]]:min-w-0 lg:[&>[data-slot=form-item]:has([data-slot=switch])]:col-span-2'
|
||||
|
||||
type OAuthFormValues = z.infer<typeof oauthSchema>
|
||||
|
||||
type OAuthSectionProps = {
|
||||
@ -250,12 +257,15 @@ export function OAuthSection({ defaultValues }: OAuthSectionProps) {
|
||||
<>
|
||||
<FormNavigationGuard when={form.formState.isDirty} />
|
||||
|
||||
<SettingsSection
|
||||
title={t('OAuth Integrations')}
|
||||
description={t('Configure third-party authentication providers')}
|
||||
>
|
||||
<SettingsSection title={t('OAuth Integrations')}>
|
||||
<Form {...form}>
|
||||
<form onSubmit={form.handleSubmit(onSubmit)} className='space-y-6'>
|
||||
<SettingsForm onSubmit={form.handleSubmit(onSubmit)}>
|
||||
<SettingsPageFormActions
|
||||
onSave={form.handleSubmit(onSubmit)}
|
||||
onReset={handleReset}
|
||||
isSaving={updateOption.isPending}
|
||||
isResetDisabled={!form.formState.isDirty}
|
||||
/>
|
||||
<FormDirtyIndicator isDirty={form.formState.isDirty} />
|
||||
|
||||
<Tabs value={activeTab} onValueChange={setActiveTab}>
|
||||
@ -268,27 +278,25 @@ export function OAuthSection({ defaultValues }: OAuthSectionProps) {
|
||||
<TabsTrigger value='wechat'>{t('WeChat')}</TabsTrigger>
|
||||
</TabsList>
|
||||
|
||||
<TabsContent value='github' className='space-y-4'>
|
||||
<TabsContent value='github' className={oauthTabContentClassName}>
|
||||
<FormField
|
||||
control={form.control}
|
||||
name='GitHubOAuthEnabled'
|
||||
render={({ field }) => (
|
||||
<FormItem className='flex flex-row items-center justify-between rounded-lg border p-4'>
|
||||
<div className='space-y-0.5'>
|
||||
<FormLabel className='text-base'>
|
||||
{t('Enable GitHub OAuth')}
|
||||
</FormLabel>
|
||||
<SettingsSwitchItem>
|
||||
<SettingsSwitchContent>
|
||||
<FormLabel>{t('Enable GitHub OAuth')}</FormLabel>
|
||||
<FormDescription>
|
||||
{t('Allow users to sign in with GitHub')}
|
||||
</FormDescription>
|
||||
</div>
|
||||
</SettingsSwitchContent>
|
||||
<FormControl>
|
||||
<Switch
|
||||
checked={field.value}
|
||||
onCheckedChange={field.onChange}
|
||||
/>
|
||||
</FormControl>
|
||||
</FormItem>
|
||||
</SettingsSwitchItem>
|
||||
)}
|
||||
/>
|
||||
|
||||
@ -330,27 +338,25 @@ export function OAuthSection({ defaultValues }: OAuthSectionProps) {
|
||||
/>
|
||||
</TabsContent>
|
||||
|
||||
<TabsContent value='discord' className='space-y-4'>
|
||||
<TabsContent value='discord' className={oauthTabContentClassName}>
|
||||
<FormField
|
||||
control={form.control}
|
||||
name='discord.enabled'
|
||||
render={({ field }) => (
|
||||
<FormItem className='flex flex-row items-center justify-between rounded-lg border p-4'>
|
||||
<div className='space-y-0.5'>
|
||||
<FormLabel className='text-base'>
|
||||
{t('Enable Discord OAuth')}
|
||||
</FormLabel>
|
||||
<SettingsSwitchItem>
|
||||
<SettingsSwitchContent>
|
||||
<FormLabel>{t('Enable Discord OAuth')}</FormLabel>
|
||||
<FormDescription>
|
||||
{t('Allow users to sign in with Discord')}
|
||||
</FormDescription>
|
||||
</div>
|
||||
</SettingsSwitchContent>
|
||||
<FormControl>
|
||||
<Switch
|
||||
checked={field.value}
|
||||
onCheckedChange={field.onChange}
|
||||
/>
|
||||
</FormControl>
|
||||
</FormItem>
|
||||
</SettingsSwitchItem>
|
||||
)}
|
||||
/>
|
||||
|
||||
@ -393,27 +399,25 @@ export function OAuthSection({ defaultValues }: OAuthSectionProps) {
|
||||
/>
|
||||
</TabsContent>
|
||||
|
||||
<TabsContent value='oidc' className='space-y-4'>
|
||||
<TabsContent value='oidc' className={oauthTabContentClassName}>
|
||||
<FormField
|
||||
control={form.control}
|
||||
name='oidc.enabled'
|
||||
render={({ field }) => (
|
||||
<FormItem className='flex flex-row items-center justify-between rounded-lg border p-4'>
|
||||
<div className='space-y-0.5'>
|
||||
<FormLabel className='text-base'>
|
||||
{t('Enable OIDC')}
|
||||
</FormLabel>
|
||||
<SettingsSwitchItem>
|
||||
<SettingsSwitchContent>
|
||||
<FormLabel>{t('Enable OIDC')}</FormLabel>
|
||||
<FormDescription>
|
||||
{t('Allow users to sign in with OpenID Connect')}
|
||||
</FormDescription>
|
||||
</div>
|
||||
</SettingsSwitchContent>
|
||||
<FormControl>
|
||||
<Switch
|
||||
checked={field.value}
|
||||
onCheckedChange={field.onChange}
|
||||
/>
|
||||
</FormControl>
|
||||
</FormItem>
|
||||
</SettingsSwitchItem>
|
||||
)}
|
||||
/>
|
||||
|
||||
@ -537,27 +541,28 @@ export function OAuthSection({ defaultValues }: OAuthSectionProps) {
|
||||
/>
|
||||
</TabsContent>
|
||||
|
||||
<TabsContent value='telegram' className='space-y-4'>
|
||||
<TabsContent
|
||||
value='telegram'
|
||||
className={oauthTabContentClassName}
|
||||
>
|
||||
<FormField
|
||||
control={form.control}
|
||||
name='TelegramOAuthEnabled'
|
||||
render={({ field }) => (
|
||||
<FormItem className='flex flex-row items-center justify-between rounded-lg border p-4'>
|
||||
<div className='space-y-0.5'>
|
||||
<FormLabel className='text-base'>
|
||||
{t('Enable Telegram OAuth')}
|
||||
</FormLabel>
|
||||
<SettingsSwitchItem>
|
||||
<SettingsSwitchContent>
|
||||
<FormLabel>{t('Enable Telegram OAuth')}</FormLabel>
|
||||
<FormDescription>
|
||||
{t('Allow users to sign in with Telegram')}
|
||||
</FormDescription>
|
||||
</div>
|
||||
</SettingsSwitchContent>
|
||||
<FormControl>
|
||||
<Switch
|
||||
checked={field.value}
|
||||
onCheckedChange={field.onChange}
|
||||
/>
|
||||
</FormControl>
|
||||
</FormItem>
|
||||
</SettingsSwitchItem>
|
||||
)}
|
||||
/>
|
||||
|
||||
@ -599,27 +604,25 @@ export function OAuthSection({ defaultValues }: OAuthSectionProps) {
|
||||
/>
|
||||
</TabsContent>
|
||||
|
||||
<TabsContent value='linuxdo' className='space-y-4'>
|
||||
<TabsContent value='linuxdo' className={oauthTabContentClassName}>
|
||||
<FormField
|
||||
control={form.control}
|
||||
name='LinuxDOOAuthEnabled'
|
||||
render={({ field }) => (
|
||||
<FormItem className='flex flex-row items-center justify-between rounded-lg border p-4'>
|
||||
<div className='space-y-0.5'>
|
||||
<FormLabel className='text-base'>
|
||||
{t('Enable LinuxDO OAuth')}
|
||||
</FormLabel>
|
||||
<SettingsSwitchItem>
|
||||
<SettingsSwitchContent>
|
||||
<FormLabel>{t('Enable LinuxDO OAuth')}</FormLabel>
|
||||
<FormDescription>
|
||||
{t('Allow users to sign in with LinuxDO')}
|
||||
</FormDescription>
|
||||
</div>
|
||||
</SettingsSwitchContent>
|
||||
<FormControl>
|
||||
<Switch
|
||||
checked={field.value}
|
||||
onCheckedChange={field.onChange}
|
||||
/>
|
||||
</FormControl>
|
||||
</FormItem>
|
||||
</SettingsSwitchItem>
|
||||
)}
|
||||
/>
|
||||
|
||||
@ -678,27 +681,25 @@ export function OAuthSection({ defaultValues }: OAuthSectionProps) {
|
||||
/>
|
||||
</TabsContent>
|
||||
|
||||
<TabsContent value='wechat' className='space-y-4'>
|
||||
<TabsContent value='wechat' className={oauthTabContentClassName}>
|
||||
<FormField
|
||||
control={form.control}
|
||||
name='WeChatAuthEnabled'
|
||||
render={({ field }) => (
|
||||
<FormItem className='flex flex-row items-center justify-between rounded-lg border p-4'>
|
||||
<div className='space-y-0.5'>
|
||||
<FormLabel className='text-base'>
|
||||
{t('Enable WeChat Auth')}
|
||||
</FormLabel>
|
||||
<SettingsSwitchItem>
|
||||
<SettingsSwitchContent>
|
||||
<FormLabel>{t('Enable WeChat Auth')}</FormLabel>
|
||||
<FormDescription>
|
||||
{t('Allow users to sign in with WeChat')}
|
||||
</FormDescription>
|
||||
</div>
|
||||
</SettingsSwitchContent>
|
||||
<FormControl>
|
||||
<Switch
|
||||
checked={field.value}
|
||||
onCheckedChange={field.onChange}
|
||||
/>
|
||||
</FormControl>
|
||||
</FormItem>
|
||||
</SettingsSwitchItem>
|
||||
)}
|
||||
/>
|
||||
|
||||
@ -758,22 +759,7 @@ export function OAuthSection({ defaultValues }: OAuthSectionProps) {
|
||||
/>
|
||||
</TabsContent>
|
||||
</Tabs>
|
||||
|
||||
<div className='flex gap-2'>
|
||||
<Button type='submit' disabled={updateOption.isPending}>
|
||||
{updateOption.isPending ? t('Saving...') : t('Save Changes')}
|
||||
</Button>
|
||||
<Button
|
||||
type='button'
|
||||
variant='outline'
|
||||
onClick={handleReset}
|
||||
disabled={!form.formState.isDirty || updateOption.isPending}
|
||||
>
|
||||
<RotateCcw className='mr-2 h-4 w-4' />
|
||||
{t('Reset')}
|
||||
</Button>
|
||||
</div>
|
||||
</form>
|
||||
</SettingsForm>
|
||||
</Form>
|
||||
</SettingsSection>
|
||||
</>
|
||||
|
||||
@ -21,7 +21,6 @@ import * as z from 'zod'
|
||||
import { useForm } from 'react-hook-form'
|
||||
import { zodResolver } from '@hookform/resolvers/zod'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { Button } from '@/components/ui/button'
|
||||
import {
|
||||
Form,
|
||||
FormControl,
|
||||
@ -42,6 +41,12 @@ import {
|
||||
} from '@/components/ui/select'
|
||||
import { Switch } from '@/components/ui/switch'
|
||||
import { Textarea } from '@/components/ui/textarea'
|
||||
import {
|
||||
SettingsForm,
|
||||
SettingsSwitchContent,
|
||||
SettingsSwitchItem,
|
||||
} from '../components/settings-form-layout'
|
||||
import { SettingsPageFormActions } from '../components/settings-page-context'
|
||||
import { SettingsSection } from '../components/settings-section'
|
||||
import { useResetForm } from '../hooks/use-reset-form'
|
||||
import { useUpdateOption } from '../hooks/use-update-option'
|
||||
@ -156,34 +161,33 @@ export function PasskeySection({ defaultValues }: PasskeySectionProps) {
|
||||
}
|
||||
|
||||
return (
|
||||
<SettingsSection
|
||||
title={t('Passkey Authentication')}
|
||||
description={t('Configure Passkey (WebAuthn) login settings')}
|
||||
>
|
||||
<SettingsSection title={t('Passkey Authentication')}>
|
||||
<Form {...form}>
|
||||
<form onSubmit={form.handleSubmit(onSubmit)} className='space-y-6'>
|
||||
<SettingsForm onSubmit={form.handleSubmit(onSubmit)}>
|
||||
<SettingsPageFormActions
|
||||
onSave={form.handleSubmit(onSubmit)}
|
||||
isSaving={updateOption.isPending}
|
||||
/>
|
||||
<FormField
|
||||
control={form.control}
|
||||
name='passkey.enabled'
|
||||
render={({ field }) => (
|
||||
<FormItem className='flex flex-row items-center justify-between rounded-lg border p-4'>
|
||||
<div className='space-y-0.5'>
|
||||
<FormLabel className='text-base'>
|
||||
{t('Enable Passkey')}
|
||||
</FormLabel>
|
||||
<SettingsSwitchItem>
|
||||
<SettingsSwitchContent>
|
||||
<FormLabel>{t('Enable Passkey')}</FormLabel>
|
||||
<FormDescription>
|
||||
{t(
|
||||
'Allow users to register and sign in with Passkey (WebAuthn)'
|
||||
)}
|
||||
</FormDescription>
|
||||
</div>
|
||||
</SettingsSwitchContent>
|
||||
<FormControl>
|
||||
<Switch
|
||||
checked={field.value}
|
||||
onCheckedChange={field.onChange}
|
||||
/>
|
||||
</FormControl>
|
||||
</FormItem>
|
||||
</SettingsSwitchItem>
|
||||
)}
|
||||
/>
|
||||
|
||||
@ -323,24 +327,22 @@ export function PasskeySection({ defaultValues }: PasskeySectionProps) {
|
||||
control={form.control}
|
||||
name='passkey.allow_insecure_origin'
|
||||
render={({ field }) => (
|
||||
<FormItem className='flex flex-row items-center justify-between rounded-lg border p-4'>
|
||||
<div className='space-y-0.5'>
|
||||
<FormLabel className='text-base'>
|
||||
{t('Allow Insecure Origins')}
|
||||
</FormLabel>
|
||||
<SettingsSwitchItem>
|
||||
<SettingsSwitchContent>
|
||||
<FormLabel>{t('Allow Insecure Origins')}</FormLabel>
|
||||
<FormDescription>
|
||||
{t(
|
||||
'Permit Passkey registration on non-HTTPS origins (only recommended for development)'
|
||||
)}
|
||||
</FormDescription>
|
||||
</div>
|
||||
</SettingsSwitchContent>
|
||||
<FormControl>
|
||||
<Switch
|
||||
checked={field.value}
|
||||
onCheckedChange={field.onChange}
|
||||
/>
|
||||
</FormControl>
|
||||
</FormItem>
|
||||
</SettingsSwitchItem>
|
||||
)}
|
||||
/>
|
||||
|
||||
@ -367,9 +369,7 @@ export function PasskeySection({ defaultValues }: PasskeySectionProps) {
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
|
||||
<Button type='submit'>{t('Save Changes')}</Button>
|
||||
</form>
|
||||
</SettingsForm>
|
||||
</Form>
|
||||
</SettingsSection>
|
||||
)
|
||||
|
||||
@ -28,7 +28,6 @@ const AUTH_SECTIONS = [
|
||||
{
|
||||
id: 'basic-auth',
|
||||
titleKey: 'Basic Authentication',
|
||||
descriptionKey: 'Configure password-based login and registration',
|
||||
build: (settings: AuthSettings) => (
|
||||
<BasicAuthSection
|
||||
defaultValues={{
|
||||
@ -46,7 +45,6 @@ const AUTH_SECTIONS = [
|
||||
{
|
||||
id: 'oauth',
|
||||
titleKey: 'OAuth Integrations',
|
||||
descriptionKey: 'Configure third-party authentication providers',
|
||||
build: (settings: AuthSettings) => (
|
||||
<OAuthSection
|
||||
defaultValues={{
|
||||
@ -82,7 +80,6 @@ const AUTH_SECTIONS = [
|
||||
{
|
||||
id: 'passkey',
|
||||
titleKey: 'Passkey Authentication',
|
||||
descriptionKey: 'Configure Passkey (WebAuthn) login settings',
|
||||
build: (settings: AuthSettings) => (
|
||||
<PasskeySection
|
||||
defaultValues={{
|
||||
@ -111,7 +108,6 @@ const AUTH_SECTIONS = [
|
||||
{
|
||||
id: 'bot-protection',
|
||||
titleKey: 'Bot Protection',
|
||||
descriptionKey: 'Protect login and registration with Cloudflare Turnstile',
|
||||
build: (settings: AuthSettings) => (
|
||||
<BotProtectionSection
|
||||
defaultValues={{
|
||||
@ -125,7 +121,6 @@ const AUTH_SECTIONS = [
|
||||
{
|
||||
id: 'custom-oauth',
|
||||
titleKey: 'Custom OAuth',
|
||||
descriptionKey: 'Configure custom OAuth providers for user authentication',
|
||||
build: () => <CustomOAuthSection />,
|
||||
},
|
||||
] as const
|
||||
@ -143,3 +138,4 @@ export const AUTH_SECTION_IDS = authRegistry.sectionIds
|
||||
export const AUTH_DEFAULT_SECTION = authRegistry.defaultSection
|
||||
export const getAuthSectionNavItems = authRegistry.getSectionNavItems
|
||||
export const getAuthSectionContent = authRegistry.getSectionContent
|
||||
export const getAuthSectionMeta = authRegistry.getSectionMeta
|
||||
|
||||
@ -21,6 +21,7 @@ import type { BillingSettings } from '../types'
|
||||
import {
|
||||
BILLING_DEFAULT_SECTION,
|
||||
getBillingSectionContent,
|
||||
getBillingSectionMeta,
|
||||
} from './section-registry.tsx'
|
||||
|
||||
const defaultBillingSettings: BillingSettings = {
|
||||
@ -113,6 +114,7 @@ export function BillingSettings() {
|
||||
defaultSettings={defaultBillingSettings}
|
||||
defaultSection={BILLING_DEFAULT_SECTION}
|
||||
getSectionContent={getBillingSectionContent}
|
||||
getSectionMeta={getBillingSectionMeta}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
@ -54,7 +54,6 @@ const BILLING_SECTIONS = [
|
||||
{
|
||||
id: 'quota',
|
||||
titleKey: 'Quota Settings',
|
||||
descriptionKey: 'Configure user quota allocation and rewards',
|
||||
build: (settings: BillingSettings) => (
|
||||
<QuotaSettingsSection
|
||||
defaultValues={{
|
||||
@ -81,7 +80,6 @@ const BILLING_SECTIONS = [
|
||||
{
|
||||
id: 'currency',
|
||||
titleKey: 'Currency & Display',
|
||||
descriptionKey: 'Configure currency conversion and quota display options',
|
||||
build: (settings: BillingSettings) => (
|
||||
<PricingSection
|
||||
defaultValues={{
|
||||
@ -105,11 +103,9 @@ const BILLING_SECTIONS = [
|
||||
{
|
||||
id: 'model-pricing',
|
||||
titleKey: 'Model Pricing',
|
||||
descriptionKey: 'Configure model pricing ratios and tool prices',
|
||||
build: (settings: BillingSettings) => (
|
||||
<RatioSettingsCard
|
||||
titleKey='Model Pricing'
|
||||
descriptionKey='Configure model pricing ratios and tool prices'
|
||||
modelDefaults={getModelDefaults(settings)}
|
||||
groupDefaults={getGroupDefaults(settings)}
|
||||
toolPricesDefault={settings['tool_price_setting.prices']}
|
||||
@ -120,11 +116,9 @@ const BILLING_SECTIONS = [
|
||||
{
|
||||
id: 'group-pricing',
|
||||
titleKey: 'Group Pricing',
|
||||
descriptionKey: 'Configure group ratios and group-specific pricing rules',
|
||||
build: (settings: BillingSettings) => (
|
||||
<RatioSettingsCard
|
||||
titleKey='Group Pricing'
|
||||
descriptionKey='Configure group ratios and group-specific pricing rules'
|
||||
modelDefaults={getModelDefaults(settings)}
|
||||
groupDefaults={getGroupDefaults(settings)}
|
||||
toolPricesDefault={settings['tool_price_setting.prices']}
|
||||
@ -135,7 +129,6 @@ const BILLING_SECTIONS = [
|
||||
{
|
||||
id: 'payment',
|
||||
titleKey: 'Payment Gateway',
|
||||
descriptionKey: 'Configure payment gateway integrations',
|
||||
build: (settings: BillingSettings) => (
|
||||
<PaymentSettingsSection
|
||||
defaultValues={{
|
||||
@ -196,7 +189,6 @@ const BILLING_SECTIONS = [
|
||||
{
|
||||
id: 'checkin',
|
||||
titleKey: 'Check-in Rewards',
|
||||
descriptionKey: 'Configure daily check-in rewards for users',
|
||||
build: (settings: BillingSettings) => (
|
||||
<CheckinSettingsSection
|
||||
defaultValues={{
|
||||
@ -225,3 +217,4 @@ export const BILLING_SECTION_IDS = billingRegistry.sectionIds
|
||||
export const BILLING_DEFAULT_SECTION = billingRegistry.defaultSection
|
||||
export const getBillingSectionNavItems = billingRegistry.getSectionNavItems
|
||||
export const getBillingSectionContent = billingRegistry.getSectionContent
|
||||
export const getBillingSectionMeta = billingRegistry.getSectionMeta
|
||||
|
||||
@ -16,9 +16,8 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
For commercial licensing, please contact support@quantumnous.com
|
||||
*/
|
||||
import { Info } from 'lucide-react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { Alert, AlertDescription } from '@/components/ui/alert'
|
||||
import { SettingsPageTitleStatusPortal } from './settings-page-context'
|
||||
|
||||
type FormDirtyIndicatorProps = {
|
||||
isDirty: boolean
|
||||
@ -26,7 +25,7 @@ type FormDirtyIndicatorProps = {
|
||||
}
|
||||
|
||||
/**
|
||||
* Visual indicator that the form has unsaved changes
|
||||
* Compact page-title status indicator for unsaved form changes.
|
||||
*
|
||||
* @example
|
||||
* ```tsx
|
||||
@ -41,14 +40,11 @@ export function FormDirtyIndicator({
|
||||
if (!isDirty) return null
|
||||
|
||||
return (
|
||||
<Alert
|
||||
variant='default'
|
||||
className='border-orange-500/50 bg-orange-50 dark:bg-orange-950/20'
|
||||
>
|
||||
<Info className='h-4 w-4 text-orange-600 dark:text-orange-500' />
|
||||
<AlertDescription className='text-orange-800 dark:text-orange-400'>
|
||||
{message ?? t('You have unsaved changes')}
|
||||
</AlertDescription>
|
||||
</Alert>
|
||||
<SettingsPageTitleStatusPortal>
|
||||
<span className='inline-flex h-5 items-center gap-1.5 rounded-full bg-amber-500/10 px-2 text-[11px] font-medium whitespace-nowrap text-amber-700 ring-1 ring-amber-500/20 ring-inset dark:bg-amber-400/10 dark:text-amber-300 dark:ring-amber-400/20'>
|
||||
<span className='size-1.5 rounded-full bg-amber-500 dark:bg-amber-300' />
|
||||
{message ? t(message) : t('Unsaved changes')}
|
||||
</span>
|
||||
</SettingsPageTitleStatusPortal>
|
||||
)
|
||||
}
|
||||
|
||||
@ -16,6 +16,7 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
For commercial licensing, please contact support@quantumnous.com
|
||||
*/
|
||||
import { cn } from '@/lib/utils'
|
||||
import {
|
||||
AccordionItem,
|
||||
AccordionTrigger,
|
||||
@ -25,7 +26,6 @@ import {
|
||||
type SettingsAccordionProps = {
|
||||
value: string
|
||||
title: string
|
||||
description?: string
|
||||
children: React.ReactNode
|
||||
className?: string
|
||||
}
|
||||
@ -33,18 +33,14 @@ type SettingsAccordionProps = {
|
||||
export function SettingsAccordion({
|
||||
value,
|
||||
title,
|
||||
description,
|
||||
children,
|
||||
className,
|
||||
}: SettingsAccordionProps) {
|
||||
return (
|
||||
<AccordionItem value={value} className={className}>
|
||||
<AccordionItem value={value} className={cn(className)}>
|
||||
<AccordionTrigger className='hover:no-underline'>
|
||||
<div className='flex flex-col gap-1 text-left'>
|
||||
<div className='text-base font-semibold'>{title}</div>
|
||||
{description && (
|
||||
<div className='text-muted-foreground text-sm'>{description}</div>
|
||||
)}
|
||||
</div>
|
||||
</AccordionTrigger>
|
||||
<AccordionContent className='pt-4'>{children}</AccordionContent>
|
||||
|
||||
182
web/default/src/features/system-settings/components/settings-form-layout.tsx
vendored
Normal file
182
web/default/src/features/system-settings/components/settings-form-layout.tsx
vendored
Normal file
@ -0,0 +1,182 @@
|
||||
/*
|
||||
Copyright (C) 2023-2026 QuantumNous
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU Affero General Public License as
|
||||
published by the Free Software Foundation, either version 3 of the
|
||||
License, or (at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU Affero General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Affero General Public License
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
For commercial licensing, please contact support@quantumnous.com
|
||||
*/
|
||||
import type { ComponentProps, ReactNode } from 'react'
|
||||
import { cn } from '@/lib/utils'
|
||||
import { FormItem } from '@/components/ui/form'
|
||||
import { Label } from '@/components/ui/label'
|
||||
import { Switch } from '@/components/ui/switch'
|
||||
|
||||
type SettingsFormGridProps = {
|
||||
children: ReactNode
|
||||
className?: string
|
||||
}
|
||||
|
||||
type SettingsFormGridItemProps = SettingsFormGridProps & {
|
||||
span?: 'default' | 'full'
|
||||
}
|
||||
|
||||
type SettingsSwitchItemProps = ComponentProps<typeof FormItem>
|
||||
type SettingsSwitchRowProps = ComponentProps<'div'>
|
||||
type SettingsControlGroupProps = ComponentProps<'div'>
|
||||
type SettingsControlChildrenProps = ComponentProps<'div'>
|
||||
type SettingsSwitchFieldProps = SettingsSwitchRowProps & {
|
||||
checked: boolean
|
||||
onCheckedChange: (checked: boolean) => void
|
||||
label: ReactNode
|
||||
description?: ReactNode
|
||||
disabled?: boolean
|
||||
}
|
||||
|
||||
const settingsSwitchRowClassName =
|
||||
'flex min-w-0 flex-row items-center justify-between gap-4 border-b py-2.5 last:border-b-0'
|
||||
|
||||
export function SettingsFormGrid(props: SettingsFormGridProps) {
|
||||
return (
|
||||
<div
|
||||
data-settings-form-span='full'
|
||||
className={cn(
|
||||
'grid min-w-0 gap-x-5 gap-y-6 lg:grid-cols-2',
|
||||
props.className
|
||||
)}
|
||||
>
|
||||
{props.children}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export function SettingsFormGridItem(props: SettingsFormGridItemProps) {
|
||||
return (
|
||||
<div
|
||||
data-settings-form-span={props.span === 'full' ? 'full' : undefined}
|
||||
className={cn(
|
||||
'min-w-0',
|
||||
props.span === 'full' && 'lg:col-span-2',
|
||||
props.className
|
||||
)}
|
||||
>
|
||||
{props.children}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export function SettingsSwitchItem({
|
||||
className,
|
||||
...props
|
||||
}: SettingsSwitchItemProps) {
|
||||
return (
|
||||
<FormItem
|
||||
data-settings-form-span='full'
|
||||
className={cn(settingsSwitchRowClassName, className)}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
export function SettingsSwitchRow({
|
||||
className,
|
||||
...props
|
||||
}: SettingsSwitchRowProps) {
|
||||
return (
|
||||
<div
|
||||
data-settings-form-span='full'
|
||||
className={cn(settingsSwitchRowClassName, className)}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
export function SettingsSwitchField({
|
||||
checked,
|
||||
onCheckedChange,
|
||||
label,
|
||||
description,
|
||||
disabled,
|
||||
className,
|
||||
...props
|
||||
}: SettingsSwitchFieldProps) {
|
||||
return (
|
||||
<SettingsSwitchRow className={className} {...props}>
|
||||
<SettingsSwitchContent>
|
||||
<Label className='text-sm font-medium'>{label}</Label>
|
||||
{description ? (
|
||||
<p className='text-muted-foreground text-xs'>{description}</p>
|
||||
) : null}
|
||||
</SettingsSwitchContent>
|
||||
<Switch
|
||||
checked={checked}
|
||||
onCheckedChange={onCheckedChange}
|
||||
disabled={disabled}
|
||||
/>
|
||||
</SettingsSwitchRow>
|
||||
)
|
||||
}
|
||||
|
||||
export function SettingsSwitchContent(props: SettingsFormGridProps) {
|
||||
return (
|
||||
<div className={cn('min-w-0 space-y-0.5', props.className)}>
|
||||
{props.children}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export function SettingsControlGroup({
|
||||
className,
|
||||
...props
|
||||
}: SettingsControlGroupProps) {
|
||||
return (
|
||||
<div
|
||||
data-settings-form-span='full'
|
||||
className={cn(
|
||||
'bg-muted/20 min-w-0 space-y-3 rounded-xl border px-3 py-2.5',
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
export function SettingsControlChildren({
|
||||
className,
|
||||
...props
|
||||
}: SettingsControlChildrenProps) {
|
||||
return (
|
||||
<div
|
||||
className={cn('border-border/70 ml-2 min-w-0 border-l pl-3', className)}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
export function SettingsForm({ className, ...props }: ComponentProps<'form'>) {
|
||||
return (
|
||||
<form
|
||||
className={cn(
|
||||
'grid min-w-0 gap-x-5 gap-y-6 lg:grid-cols-2',
|
||||
'lg:[&>*:not([data-slot=form-item])]:col-span-2',
|
||||
'lg:[&>[data-settings-form-span=full]]:col-span-2',
|
||||
'lg:[&>[data-slot=alert]]:col-span-2',
|
||||
'[&>[data-slot=form-item]]:min-w-0',
|
||||
'lg:[&>[data-slot=form-item]:has(textarea)]:col-span-2',
|
||||
'lg:[&>[data-slot=form-item]:has([data-slot=switch])]:col-span-2',
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
}
|
||||
146
web/default/src/features/system-settings/components/settings-page-context.tsx
vendored
Normal file
146
web/default/src/features/system-settings/components/settings-page-context.tsx
vendored
Normal file
@ -0,0 +1,146 @@
|
||||
/*
|
||||
Copyright (C) 2023-2026 QuantumNous
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU Affero General Public License as
|
||||
published by the Free Software Foundation, either version 3 of the
|
||||
License, or (at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU Affero General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Affero General Public License
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
For commercial licensing, please contact support@quantumnous.com
|
||||
*/
|
||||
import {
|
||||
createContext,
|
||||
useContext,
|
||||
type ComponentProps,
|
||||
type ReactNode,
|
||||
type RefObject,
|
||||
} from 'react'
|
||||
import { RotateCcw, Save } from 'lucide-react'
|
||||
import { createPortal } from 'react-dom'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { Button } from '@/components/ui/button'
|
||||
|
||||
type SettingsPageContextValue = {
|
||||
actionsContainer: HTMLDivElement | null
|
||||
titleStatusContainer: HTMLSpanElement | null
|
||||
suppressSectionHeader: boolean
|
||||
}
|
||||
|
||||
const SettingsPageContext = createContext<SettingsPageContextValue>({
|
||||
actionsContainer: null,
|
||||
titleStatusContainer: null,
|
||||
suppressSectionHeader: false,
|
||||
})
|
||||
|
||||
type SettingsPageProviderProps = {
|
||||
actionsContainer: HTMLDivElement | null
|
||||
titleStatusContainer?: HTMLSpanElement | null
|
||||
children: ReactNode
|
||||
suppressSectionHeader?: boolean
|
||||
}
|
||||
|
||||
export function SettingsPageProvider(props: SettingsPageProviderProps) {
|
||||
return (
|
||||
<SettingsPageContext.Provider
|
||||
value={{
|
||||
actionsContainer: props.actionsContainer,
|
||||
titleStatusContainer: props.titleStatusContainer ?? null,
|
||||
suppressSectionHeader: props.suppressSectionHeader ?? true,
|
||||
}}
|
||||
>
|
||||
{props.children}
|
||||
</SettingsPageContext.Provider>
|
||||
)
|
||||
}
|
||||
|
||||
export function useSuppressSettingsSectionHeader() {
|
||||
return useContext(SettingsPageContext).suppressSectionHeader
|
||||
}
|
||||
|
||||
type SettingsPageTitleStatusPortalProps = {
|
||||
children: ReactNode
|
||||
}
|
||||
|
||||
export function SettingsPageTitleStatusPortal(
|
||||
props: SettingsPageTitleStatusPortalProps
|
||||
) {
|
||||
const { titleStatusContainer } = useContext(SettingsPageContext)
|
||||
|
||||
if (!titleStatusContainer) return null
|
||||
|
||||
return createPortal(props.children, titleStatusContainer)
|
||||
}
|
||||
|
||||
type SettingsPageActionsPortalProps = {
|
||||
children: ReactNode
|
||||
}
|
||||
|
||||
export function SettingsPageActionsPortal(
|
||||
props: SettingsPageActionsPortalProps
|
||||
) {
|
||||
const { actionsContainer } = useContext(SettingsPageContext)
|
||||
|
||||
if (!actionsContainer) return null
|
||||
|
||||
return createPortal(
|
||||
<div className='flex flex-wrap items-center justify-end gap-2'>
|
||||
{props.children}
|
||||
</div>,
|
||||
actionsContainer
|
||||
)
|
||||
}
|
||||
|
||||
type SettingsPageFormActionsProps = {
|
||||
onSave: () => void
|
||||
onReset?: () => void
|
||||
isSaving?: boolean
|
||||
isSaveDisabled?: boolean
|
||||
isResetDisabled?: boolean
|
||||
saveLabel?: string
|
||||
savingLabel?: string
|
||||
resetLabel?: string
|
||||
resetVariant?: ComponentProps<typeof Button>['variant']
|
||||
saveButtonRef?: RefObject<HTMLButtonElement | null>
|
||||
}
|
||||
|
||||
export function SettingsPageFormActions(props: SettingsPageFormActionsProps) {
|
||||
const { t } = useTranslation()
|
||||
const saveLabel = props.isSaving
|
||||
? (props.savingLabel ?? 'Saving...')
|
||||
: (props.saveLabel ?? 'Save Changes')
|
||||
|
||||
return (
|
||||
<SettingsPageActionsPortal>
|
||||
{props.onReset && (
|
||||
<Button
|
||||
type='button'
|
||||
size='sm'
|
||||
variant={props.resetVariant ?? 'outline'}
|
||||
onClick={props.onReset}
|
||||
disabled={props.isResetDisabled || props.isSaving}
|
||||
>
|
||||
<RotateCcw data-icon='inline-start' />
|
||||
<span>{t(props.resetLabel ?? 'Reset')}</span>
|
||||
</Button>
|
||||
)}
|
||||
<Button
|
||||
ref={props.saveButtonRef}
|
||||
type='button'
|
||||
size='sm'
|
||||
onClick={props.onSave}
|
||||
disabled={props.isSaving || props.isSaveDisabled}
|
||||
>
|
||||
<Save data-icon='inline-start' />
|
||||
<span>{t(saveLabel)}</span>
|
||||
</Button>
|
||||
</SettingsPageActionsPortal>
|
||||
)
|
||||
}
|
||||
@ -16,13 +16,18 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
For commercial licensing, please contact support@quantumnous.com
|
||||
*/
|
||||
import { useMemo, useState, type ReactNode } from 'react'
|
||||
import { useParams } from '@tanstack/react-router'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { SectionPageLayout } from '@/components/layout'
|
||||
import { useSystemOptions, getOptionValue } from '../hooks/use-system-options'
|
||||
import type { SystemOption } from '../types'
|
||||
import { SettingsPageProvider } from './settings-page-context'
|
||||
|
||||
type SettingsPageProps<
|
||||
TSettings extends Record<string, string | number | boolean | unknown[]>,
|
||||
TSectionId extends string,
|
||||
TExtraArgs extends unknown[] = [],
|
||||
> = {
|
||||
routePath: string
|
||||
defaultSettings: TSettings
|
||||
@ -30,9 +35,57 @@ type SettingsPageProps<
|
||||
getSectionContent: (
|
||||
sectionId: TSectionId,
|
||||
settings: TSettings,
|
||||
...extraArgs: unknown[]
|
||||
) => React.ReactNode
|
||||
extraArgs?: unknown[]
|
||||
...extraArgs: TExtraArgs
|
||||
) => ReactNode
|
||||
getSectionMeta: (sectionId: TSectionId) => {
|
||||
titleKey: string
|
||||
}
|
||||
extraArgs?: TExtraArgs
|
||||
loadingMessage?: string
|
||||
resolveSettings?: (
|
||||
settings: TSettings,
|
||||
raw: SystemOption[] | undefined
|
||||
) => TSettings
|
||||
}
|
||||
|
||||
type SettingsPageFrameProps = {
|
||||
title: ReactNode
|
||||
children: ReactNode
|
||||
}
|
||||
|
||||
function SettingsPageFrame(props: SettingsPageFrameProps) {
|
||||
const [actionsContainer, setActionsContainer] =
|
||||
useState<HTMLDivElement | null>(null)
|
||||
const [titleStatusContainer, setTitleStatusContainer] =
|
||||
useState<HTMLSpanElement | null>(null)
|
||||
|
||||
return (
|
||||
<SettingsPageProvider
|
||||
actionsContainer={actionsContainer}
|
||||
titleStatusContainer={titleStatusContainer}
|
||||
>
|
||||
<SectionPageLayout>
|
||||
<SectionPageLayout.Title>
|
||||
<span className='inline-flex max-w-full min-w-0 items-center gap-2 align-middle'>
|
||||
<span className='truncate'>{props.title}</span>
|
||||
<span
|
||||
ref={setTitleStatusContainer}
|
||||
className='inline-flex shrink-0'
|
||||
/>
|
||||
</span>
|
||||
</SectionPageLayout.Title>
|
||||
<SectionPageLayout.Actions>
|
||||
<div
|
||||
ref={setActionsContainer}
|
||||
className='flex flex-wrap items-center justify-end gap-2'
|
||||
/>
|
||||
</SectionPageLayout.Actions>
|
||||
<SectionPageLayout.Content>
|
||||
<div className='flex w-full flex-col gap-4'>{props.children}</div>
|
||||
</SectionPageLayout.Content>
|
||||
</SectionPageLayout>
|
||||
</SettingsPageProvider>
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
@ -42,39 +95,53 @@ type SettingsPageProps<
|
||||
export function SettingsPage<
|
||||
TSettings extends Record<string, string | number | boolean | unknown[]>,
|
||||
TSectionId extends string,
|
||||
TExtraArgs extends unknown[] = [],
|
||||
>({
|
||||
routePath,
|
||||
defaultSettings,
|
||||
defaultSection,
|
||||
getSectionContent,
|
||||
extraArgs = [],
|
||||
}: SettingsPageProps<TSettings, TSectionId>) {
|
||||
getSectionMeta,
|
||||
extraArgs,
|
||||
loadingMessage = 'Loading settings...',
|
||||
resolveSettings,
|
||||
}: SettingsPageProps<TSettings, TSectionId, TExtraArgs>) {
|
||||
const { t } = useTranslation()
|
||||
const { data, isLoading } = useSystemOptions()
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
const params = useParams({ from: routePath as any })
|
||||
const activeSection = (params?.section ?? defaultSection) as TSectionId
|
||||
const sectionMeta = getSectionMeta(activeSection)
|
||||
|
||||
const settings = useMemo(() => {
|
||||
const baseSettings = getOptionValue(
|
||||
data?.data,
|
||||
defaultSettings
|
||||
) as TSettings
|
||||
return resolveSettings
|
||||
? resolveSettings(baseSettings, data?.data)
|
||||
: baseSettings
|
||||
}, [data?.data, defaultSettings, resolveSettings])
|
||||
|
||||
if (isLoading) {
|
||||
return (
|
||||
<div className='flex items-center justify-center py-12'>
|
||||
<div className='text-muted-foreground'>{t('Loading settings...')}</div>
|
||||
</div>
|
||||
<SettingsPageFrame title={t(sectionMeta.titleKey)}>
|
||||
<div className='text-muted-foreground flex min-h-40 items-center justify-center text-sm'>
|
||||
{t(loadingMessage)}
|
||||
</div>
|
||||
</SettingsPageFrame>
|
||||
)
|
||||
}
|
||||
|
||||
const settings = getOptionValue(data?.data, defaultSettings) as TSettings
|
||||
const activeSection = (params?.section ?? defaultSection) as TSectionId
|
||||
const sectionContent = getSectionContent(
|
||||
activeSection,
|
||||
settings,
|
||||
...extraArgs
|
||||
...((extraArgs ?? []) as TExtraArgs)
|
||||
)
|
||||
|
||||
return (
|
||||
<div className='flex h-full w-full flex-1 flex-col'>
|
||||
<div className='faded-bottom h-full w-full overflow-y-auto scroll-smooth pe-4 pb-12'>
|
||||
<div className='space-y-4'>{sectionContent}</div>
|
||||
</div>
|
||||
</div>
|
||||
<SettingsPageFrame title={t(sectionMeta.titleKey)}>
|
||||
{sectionContent}
|
||||
</SettingsPageFrame>
|
||||
)
|
||||
}
|
||||
|
||||
@ -16,10 +16,12 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
For commercial licensing, please contact support@quantumnous.com
|
||||
*/
|
||||
import { cn } from '@/lib/utils'
|
||||
import { useSuppressSettingsSectionHeader } from './settings-page-context'
|
||||
|
||||
type SettingsSectionProps = {
|
||||
title: string
|
||||
titleProps?: React.HTMLAttributes<HTMLHeadingElement>
|
||||
description?: string
|
||||
children: React.ReactNode
|
||||
className?: string
|
||||
}
|
||||
@ -27,32 +29,23 @@ type SettingsSectionProps = {
|
||||
export function SettingsSection({
|
||||
title,
|
||||
titleProps,
|
||||
description,
|
||||
children,
|
||||
className,
|
||||
}: SettingsSectionProps) {
|
||||
const baseClassName = 'space-y-4'
|
||||
const sectionClassName = className
|
||||
? `${baseClassName} ${className}`
|
||||
: baseClassName
|
||||
const suppressHeader = useSuppressSettingsSectionHeader()
|
||||
|
||||
return (
|
||||
<section className={sectionClassName}>
|
||||
<div className='space-y-1'>
|
||||
<h3
|
||||
{...titleProps}
|
||||
className={
|
||||
titleProps?.className
|
||||
? `text-base font-semibold ${titleProps.className}`
|
||||
: 'text-base font-semibold'
|
||||
}
|
||||
>
|
||||
{title}
|
||||
</h3>
|
||||
{description && (
|
||||
<p className='text-muted-foreground text-sm'>{description}</p>
|
||||
)}
|
||||
</div>
|
||||
<section className={cn('flex flex-col gap-4', className)}>
|
||||
{!suppressHeader && (
|
||||
<div className='flex flex-col gap-1'>
|
||||
<h3
|
||||
{...titleProps}
|
||||
className={cn('text-base font-semibold', titleProps?.className)}
|
||||
>
|
||||
{title}
|
||||
</h3>
|
||||
</div>
|
||||
)}
|
||||
{children}
|
||||
</section>
|
||||
)
|
||||
|
||||
@ -62,7 +62,6 @@ import {
|
||||
SelectTrigger,
|
||||
SelectValue,
|
||||
} from '@/components/ui/select'
|
||||
import { Switch } from '@/components/ui/switch'
|
||||
import {
|
||||
Table,
|
||||
TableBody,
|
||||
@ -74,6 +73,7 @@ import {
|
||||
import { Textarea } from '@/components/ui/textarea'
|
||||
import { DateTimePicker } from '@/components/datetime-picker'
|
||||
import { StatusBadge } from '@/components/status-badge'
|
||||
import { SettingsSwitchField } from '../components/settings-form-layout'
|
||||
import { SettingsSection } from '../components/settings-section'
|
||||
import { useUpdateOption } from '../hooks/use-update-option'
|
||||
|
||||
@ -319,10 +319,7 @@ export function AnnouncementsSection({
|
||||
}
|
||||
|
||||
return (
|
||||
<SettingsSection
|
||||
title={t('Announcements')}
|
||||
description={t('Broadcast short system notices on the dashboard')}
|
||||
>
|
||||
<SettingsSection title={t('Announcements')}>
|
||||
<div className='space-y-4'>
|
||||
<div className='flex flex-wrap items-center justify-between gap-2'>
|
||||
<div className='flex flex-wrap items-center gap-2'>
|
||||
@ -350,12 +347,12 @@ export function AnnouncementsSection({
|
||||
{updateOption.isPending ? t('Saving...') : t('Save Settings')}
|
||||
</Button>
|
||||
</div>
|
||||
<div className='flex items-center gap-2'>
|
||||
<span className='text-muted-foreground text-sm'>
|
||||
{t('Enabled')}
|
||||
</span>
|
||||
<Switch checked={isEnabled} onCheckedChange={handleToggleEnabled} />
|
||||
</div>
|
||||
<SettingsSwitchField
|
||||
checked={isEnabled}
|
||||
onCheckedChange={handleToggleEnabled}
|
||||
label={t('Enabled')}
|
||||
className='border-b-0 py-0'
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className='rounded-md border'>
|
||||
|
||||
@ -62,7 +62,6 @@ import {
|
||||
SelectTrigger,
|
||||
SelectValue,
|
||||
} from '@/components/ui/select'
|
||||
import { Switch } from '@/components/ui/switch'
|
||||
import {
|
||||
Table,
|
||||
TableBody,
|
||||
@ -72,6 +71,7 @@ import {
|
||||
TableRow,
|
||||
} from '@/components/ui/table'
|
||||
import { StatusBadge } from '@/components/status-badge'
|
||||
import { SettingsSwitchField } from '../components/settings-form-layout'
|
||||
import { SettingsSection } from '../components/settings-section'
|
||||
import { useUpdateOption } from '../hooks/use-update-option'
|
||||
|
||||
@ -275,10 +275,7 @@ export function ApiInfoSection({ enabled, data }: ApiInfoSectionProps) {
|
||||
const getColorClass = (color: string) => getBgColorClass(color)
|
||||
|
||||
return (
|
||||
<SettingsSection
|
||||
title={t('API Addresses')}
|
||||
description={t('Curate quick links to your different Domains')}
|
||||
>
|
||||
<SettingsSection title={t('API Addresses')}>
|
||||
<div className='space-y-4'>
|
||||
<div className='flex flex-wrap items-center justify-between gap-2'>
|
||||
<div className='flex flex-wrap items-center gap-2'>
|
||||
@ -306,12 +303,12 @@ export function ApiInfoSection({ enabled, data }: ApiInfoSectionProps) {
|
||||
{updateOption.isPending ? t('Saving...') : t('Save Settings')}
|
||||
</Button>
|
||||
</div>
|
||||
<div className='flex items-center gap-2'>
|
||||
<span className='text-muted-foreground text-sm'>
|
||||
{t('Enabled')}
|
||||
</span>
|
||||
<Switch checked={isEnabled} onCheckedChange={handleToggleEnabled} />
|
||||
</div>
|
||||
<SettingsSwitchField
|
||||
checked={isEnabled}
|
||||
onCheckedChange={handleToggleEnabled}
|
||||
label={t('Enabled')}
|
||||
className='border-b-0 py-0'
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className='rounded-md border'>
|
||||
|
||||
@ -21,7 +21,6 @@ import * as z from 'zod'
|
||||
import { useForm } from 'react-hook-form'
|
||||
import { zodResolver } from '@hookform/resolvers/zod'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { Button } from '@/components/ui/button'
|
||||
import {
|
||||
Form,
|
||||
FormControl,
|
||||
@ -33,6 +32,8 @@ import {
|
||||
} from '@/components/ui/form'
|
||||
import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs'
|
||||
import { Textarea } from '@/components/ui/textarea'
|
||||
import { SettingsForm } from '../components/settings-form-layout'
|
||||
import { SettingsPageFormActions } from '../components/settings-page-context'
|
||||
import { SettingsSection } from '../components/settings-section'
|
||||
import { useUpdateOption } from '../hooks/use-update-option'
|
||||
import { ChatSettingsVisualEditor } from './chat-settings-visual-editor'
|
||||
@ -125,13 +126,15 @@ export function ChatSettingsSection({
|
||||
}
|
||||
|
||||
return (
|
||||
<SettingsSection
|
||||
title={t('Chat Presets')}
|
||||
description={t('Configure predefined chat links surfaced to end users.')}
|
||||
>
|
||||
<SettingsSection title={t('Chat Presets')}>
|
||||
<Form {...form}>
|
||||
{/* eslint-disable-next-line react-hooks/refs */}
|
||||
<form onSubmit={form.handleSubmit(onSubmit)} className='space-y-6'>
|
||||
<SettingsForm onSubmit={form.handleSubmit(onSubmit)}>
|
||||
<SettingsPageFormActions
|
||||
onSave={form.handleSubmit(onSubmit)}
|
||||
isSaving={updateOption.isPending}
|
||||
saveLabel='Save chat settings'
|
||||
/>
|
||||
<Tabs
|
||||
value={editMode}
|
||||
onValueChange={(value) => setEditMode(value as 'visual' | 'json')}
|
||||
@ -186,11 +189,7 @@ export function ChatSettingsSection({
|
||||
/>
|
||||
</TabsContent>
|
||||
</Tabs>
|
||||
|
||||
<Button type='submit' disabled={updateOption.isPending}>
|
||||
{updateOption.isPending ? t('Saving...') : t('Save chat settings')}
|
||||
</Button>
|
||||
</form>
|
||||
</SettingsForm>
|
||||
</Form>
|
||||
</SettingsSection>
|
||||
)
|
||||
|
||||
@ -21,7 +21,6 @@ import * as z from 'zod'
|
||||
import { useForm } from 'react-hook-form'
|
||||
import { zodResolver } from '@hookform/resolvers/zod'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { Button } from '@/components/ui/button'
|
||||
import {
|
||||
Form,
|
||||
FormControl,
|
||||
@ -41,6 +40,12 @@ import {
|
||||
SelectValue,
|
||||
} from '@/components/ui/select'
|
||||
import { Switch } from '@/components/ui/switch'
|
||||
import {
|
||||
SettingsForm,
|
||||
SettingsSwitchContent,
|
||||
SettingsSwitchItem,
|
||||
} from '../components/settings-form-layout'
|
||||
import { SettingsPageFormActions } from '../components/settings-page-context'
|
||||
import { SettingsSection } from '../components/settings-section'
|
||||
import { useUpdateOption } from '../hooks/use-update-option'
|
||||
|
||||
@ -89,29 +94,28 @@ export function DashboardSection({ defaultValues }: DashboardSectionProps) {
|
||||
const isEnabled = form.watch('DataExportEnabled')
|
||||
|
||||
return (
|
||||
<SettingsSection
|
||||
title={t('Data Dashboard')}
|
||||
description={t('Configure experimental data export for the dashboard')}
|
||||
>
|
||||
<SettingsSection title={t('Data Dashboard')}>
|
||||
<Form {...form}>
|
||||
<form onSubmit={form.handleSubmit(onSubmit)} className='space-y-6'>
|
||||
<SettingsForm onSubmit={form.handleSubmit(onSubmit)}>
|
||||
<SettingsPageFormActions
|
||||
onSave={form.handleSubmit(onSubmit)}
|
||||
isSaving={updateOption.isPending}
|
||||
/>
|
||||
<FormField
|
||||
control={form.control}
|
||||
name='DataExportEnabled'
|
||||
render={({ field }) => (
|
||||
<FormItem className='flex flex-row items-center justify-between rounded-lg border p-4'>
|
||||
<div className='space-y-0.5'>
|
||||
<FormLabel className='text-base'>
|
||||
{t('Enable Data Dashboard')}
|
||||
</FormLabel>
|
||||
</div>
|
||||
<SettingsSwitchItem>
|
||||
<SettingsSwitchContent>
|
||||
<FormLabel>{t('Enable Data Dashboard')}</FormLabel>
|
||||
</SettingsSwitchContent>
|
||||
<FormControl>
|
||||
<Switch
|
||||
checked={field.value}
|
||||
onCheckedChange={field.onChange}
|
||||
/>
|
||||
</FormControl>
|
||||
</FormItem>
|
||||
</SettingsSwitchItem>
|
||||
)}
|
||||
/>
|
||||
|
||||
@ -183,11 +187,7 @@ export function DashboardSection({ defaultValues }: DashboardSectionProps) {
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<Button type='submit' disabled={updateOption.isPending}>
|
||||
{updateOption.isPending ? t('Saving...') : t('Save Changes')}
|
||||
</Button>
|
||||
</form>
|
||||
</SettingsForm>
|
||||
</Form>
|
||||
</SettingsSection>
|
||||
)
|
||||
|
||||
@ -21,17 +21,21 @@ import * as z from 'zod'
|
||||
import { useForm } from 'react-hook-form'
|
||||
import { zodResolver } from '@hookform/resolvers/zod'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { Button } from '@/components/ui/button'
|
||||
import {
|
||||
Form,
|
||||
FormControl,
|
||||
FormDescription,
|
||||
FormField,
|
||||
FormItem,
|
||||
FormLabel,
|
||||
FormMessage,
|
||||
} from '@/components/ui/form'
|
||||
import { Switch } from '@/components/ui/switch'
|
||||
import {
|
||||
SettingsForm,
|
||||
SettingsSwitchContent,
|
||||
SettingsSwitchItem,
|
||||
} from '../components/settings-form-layout'
|
||||
import { SettingsPageFormActions } from '../components/settings-page-context'
|
||||
import { SettingsSection } from '../components/settings-section'
|
||||
import { useUpdateOption } from '../hooks/use-update-option'
|
||||
|
||||
@ -124,12 +128,14 @@ export function DrawingSettingsSection({
|
||||
]
|
||||
|
||||
return (
|
||||
<SettingsSection
|
||||
title={t('Drawing')}
|
||||
description={t('Fine-tune Midjourney integration and guardrails.')}
|
||||
>
|
||||
<SettingsSection title={t('Drawing')}>
|
||||
<Form {...form}>
|
||||
<form onSubmit={form.handleSubmit(onSubmit)} className='space-y-6'>
|
||||
<SettingsForm onSubmit={form.handleSubmit(onSubmit)}>
|
||||
<SettingsPageFormActions
|
||||
onSave={form.handleSubmit(onSubmit)}
|
||||
isSaving={updateOption.isPending}
|
||||
saveLabel='Save drawing settings'
|
||||
/>
|
||||
<div className='space-y-4'>
|
||||
{switches.map((item) => (
|
||||
<FormField
|
||||
@ -137,11 +143,11 @@ export function DrawingSettingsSection({
|
||||
control={form.control}
|
||||
name={item.name}
|
||||
render={({ field }) => (
|
||||
<FormItem className='flex flex-row items-start justify-between rounded-lg border p-4'>
|
||||
<div className='space-y-0.5 pe-4'>
|
||||
<FormLabel className='text-base'>{item.label}</FormLabel>
|
||||
<SettingsSwitchItem>
|
||||
<SettingsSwitchContent>
|
||||
<FormLabel>{item.label}</FormLabel>
|
||||
<FormDescription>{item.description}</FormDescription>
|
||||
</div>
|
||||
</SettingsSwitchContent>
|
||||
<FormControl>
|
||||
<Switch
|
||||
checked={field.value}
|
||||
@ -149,18 +155,12 @@ export function DrawingSettingsSection({
|
||||
/>
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
</SettingsSwitchItem>
|
||||
)}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
|
||||
<Button type='submit' disabled={updateOption.isPending}>
|
||||
{updateOption.isPending
|
||||
? t('Saving...')
|
||||
: t('Save drawing settings')}
|
||||
</Button>
|
||||
</form>
|
||||
</SettingsForm>
|
||||
</Form>
|
||||
</SettingsSection>
|
||||
)
|
||||
|
||||
@ -53,7 +53,6 @@ import {
|
||||
FormMessage,
|
||||
} from '@/components/ui/form'
|
||||
import { Input } from '@/components/ui/input'
|
||||
import { Switch } from '@/components/ui/switch'
|
||||
import {
|
||||
Table,
|
||||
TableBody,
|
||||
@ -63,6 +62,7 @@ import {
|
||||
TableRow,
|
||||
} from '@/components/ui/table'
|
||||
import { Textarea } from '@/components/ui/textarea'
|
||||
import { SettingsSwitchField } from '../components/settings-form-layout'
|
||||
import { SettingsSection } from '../components/settings-section'
|
||||
import { useUpdateOption } from '../hooks/use-update-option'
|
||||
|
||||
@ -238,12 +238,7 @@ export function FAQSection({ enabled, data }: FAQSectionProps) {
|
||||
}
|
||||
|
||||
return (
|
||||
<SettingsSection
|
||||
title={t('FAQ')}
|
||||
description={t(
|
||||
'Maintain a list of common questions for the dashboard help panel'
|
||||
)}
|
||||
>
|
||||
<SettingsSection title={t('FAQ')}>
|
||||
<div className='space-y-4'>
|
||||
<div className='flex flex-wrap items-center justify-between gap-2'>
|
||||
<div className='flex flex-wrap items-center gap-2'>
|
||||
@ -271,12 +266,12 @@ export function FAQSection({ enabled, data }: FAQSectionProps) {
|
||||
{updateOption.isPending ? t('Saving...') : t('Save Settings')}
|
||||
</Button>
|
||||
</div>
|
||||
<div className='flex items-center gap-2'>
|
||||
<span className='text-muted-foreground text-sm'>
|
||||
{t('Enabled')}
|
||||
</span>
|
||||
<Switch checked={isEnabled} onCheckedChange={handleToggleEnabled} />
|
||||
</div>
|
||||
<SettingsSwitchField
|
||||
checked={isEnabled}
|
||||
onCheckedChange={handleToggleEnabled}
|
||||
label={t('Enabled')}
|
||||
className='border-b-0 py-0'
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className='rounded-md border'>
|
||||
|
||||
@ -16,14 +16,12 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
For commercial licensing, please contact support@quantumnous.com
|
||||
*/
|
||||
import { useMemo } from 'react'
|
||||
import { useParams } from '@tanstack/react-router'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { getOptionValue, useSystemOptions } from '../hooks/use-system-options'
|
||||
import type { ContentSettings } from '../types'
|
||||
import { SettingsPage } from '../components/settings-page'
|
||||
import type { ContentSettings, SystemOption } from '../types'
|
||||
import {
|
||||
CONTENT_DEFAULT_SECTION,
|
||||
getContentSectionContent,
|
||||
getContentSectionMeta,
|
||||
} from './section-registry.tsx'
|
||||
|
||||
const defaultContentSettings: ContentSettings = {
|
||||
@ -47,84 +45,53 @@ const defaultContentSettings: ContentSettings = {
|
||||
MjActionCheckSuccessEnabled: false,
|
||||
}
|
||||
|
||||
export function ContentSettings() {
|
||||
const { t } = useTranslation()
|
||||
const { data, isLoading } = useSystemOptions()
|
||||
const params = useParams({
|
||||
from: '/_authenticated/system-settings/content/$section',
|
||||
})
|
||||
function resolveContentSettings(
|
||||
settings: ContentSettings,
|
||||
raw: SystemOption[] | undefined
|
||||
): ContentSettings {
|
||||
if (!raw || raw.length === 0) return settings
|
||||
|
||||
const settings = useMemo(() => {
|
||||
const resolved = getOptionValue(data?.data, defaultContentSettings)
|
||||
const optionMap = new Map(raw.map((item) => [item.key, item.value]))
|
||||
const next = { ...settings }
|
||||
|
||||
const optionMap = new Map(
|
||||
(data?.data ?? []).map((item) => [item.key, item.value])
|
||||
)
|
||||
const legacyMap = [
|
||||
{ current: 'console_setting.announcements', legacy: 'Announcements' },
|
||||
{ current: 'console_setting.api_info', legacy: 'ApiInfo' },
|
||||
{ current: 'console_setting.faq', legacy: 'FAQ' },
|
||||
] as const
|
||||
|
||||
if (!optionMap.has('console_setting.announcements')) {
|
||||
const legacy = optionMap.get('Announcements')
|
||||
if (legacy !== undefined) {
|
||||
resolved['console_setting.announcements'] = legacy
|
||||
for (const { current, legacy } of legacyMap) {
|
||||
if (!optionMap.has(current)) {
|
||||
const legacyValue = optionMap.get(legacy)
|
||||
if (legacyValue !== undefined) {
|
||||
next[current] = legacyValue
|
||||
}
|
||||
}
|
||||
|
||||
if (!optionMap.has('console_setting.api_info')) {
|
||||
const legacy = optionMap.get('ApiInfo')
|
||||
if (legacy !== undefined) {
|
||||
resolved['console_setting.api_info'] = legacy
|
||||
}
|
||||
}
|
||||
|
||||
if (!optionMap.has('console_setting.faq')) {
|
||||
const legacy = optionMap.get('FAQ')
|
||||
if (legacy !== undefined) {
|
||||
resolved['console_setting.faq'] = legacy
|
||||
}
|
||||
}
|
||||
|
||||
if (!optionMap.has('console_setting.uptime_kuma_groups')) {
|
||||
const legacyUrl = optionMap.get('UptimeKumaUrl')
|
||||
const legacySlug = optionMap.get('UptimeKumaSlug')
|
||||
if (legacyUrl && legacySlug) {
|
||||
resolved['console_setting.uptime_kuma_groups'] = JSON.stringify([
|
||||
{
|
||||
id: 1,
|
||||
categoryName: 'Legacy',
|
||||
url: legacyUrl,
|
||||
slug: legacySlug,
|
||||
},
|
||||
])
|
||||
}
|
||||
}
|
||||
|
||||
return resolved
|
||||
}, [data?.data])
|
||||
|
||||
if (isLoading) {
|
||||
return (
|
||||
<div className='flex items-center justify-center py-12'>
|
||||
<div className='text-muted-foreground'>
|
||||
{t('Loading content settings...')}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
const activeSection = (params?.section ?? CONTENT_DEFAULT_SECTION) as
|
||||
| 'dashboard'
|
||||
| 'announcements'
|
||||
| 'api-info'
|
||||
| 'faq'
|
||||
| 'uptime-kuma'
|
||||
| 'chat'
|
||||
| 'drawing'
|
||||
const sectionContent = getContentSectionContent(activeSection, settings)
|
||||
if (!optionMap.has('console_setting.uptime_kuma_groups')) {
|
||||
const legacyUrl = optionMap.get('UptimeKumaUrl')
|
||||
const legacySlug = optionMap.get('UptimeKumaSlug')
|
||||
if (legacyUrl && legacySlug) {
|
||||
next['console_setting.uptime_kuma_groups'] = JSON.stringify([
|
||||
{ id: 1, categoryName: 'Legacy', url: legacyUrl, slug: legacySlug },
|
||||
])
|
||||
}
|
||||
}
|
||||
|
||||
return next
|
||||
}
|
||||
|
||||
export function ContentSettings() {
|
||||
return (
|
||||
<div className='flex h-full w-full flex-1 flex-col'>
|
||||
<div className='faded-bottom h-full w-full overflow-y-auto scroll-smooth pe-4 pb-12'>
|
||||
<div className='space-y-4'>{sectionContent}</div>
|
||||
</div>
|
||||
</div>
|
||||
<SettingsPage
|
||||
routePath='/_authenticated/system-settings/content/$section'
|
||||
defaultSettings={defaultContentSettings}
|
||||
defaultSection={CONTENT_DEFAULT_SECTION}
|
||||
getSectionContent={getContentSectionContent}
|
||||
getSectionMeta={getContentSectionMeta}
|
||||
loadingMessage='Loading content settings...'
|
||||
resolveSettings={resolveContentSettings}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
@ -21,7 +21,6 @@ import * as z from 'zod'
|
||||
import { useForm } from 'react-hook-form'
|
||||
import { zodResolver } from '@hookform/resolvers/zod'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { Button } from '@/components/ui/button'
|
||||
import {
|
||||
Form,
|
||||
FormControl,
|
||||
@ -34,13 +33,18 @@ import {
|
||||
import { Switch } from '@/components/ui/switch'
|
||||
import { Textarea } from '@/components/ui/textarea'
|
||||
import { SettingsAccordion } from '../components/settings-accordion'
|
||||
import {
|
||||
SettingsForm,
|
||||
SettingsSwitchContent,
|
||||
SettingsSwitchItem,
|
||||
} from '../components/settings-form-layout'
|
||||
import { SettingsPageFormActions } from '../components/settings-page-context'
|
||||
import { useUpdateOption } from '../hooks/use-update-option'
|
||||
import { formatJsonForEditor, normalizeJsonString } from './utils'
|
||||
|
||||
type JsonToggleSectionProps = {
|
||||
value: string
|
||||
title: string
|
||||
description?: string
|
||||
toggleDescription?: string
|
||||
optionKey: string
|
||||
enabledKey: string
|
||||
@ -63,7 +67,6 @@ type JsonToggleFormValues = {
|
||||
export function JsonToggleSection({
|
||||
value,
|
||||
title,
|
||||
description,
|
||||
toggleDescription,
|
||||
optionKey,
|
||||
enabledKey,
|
||||
@ -157,30 +160,33 @@ export function JsonToggleSection({
|
||||
}
|
||||
|
||||
return (
|
||||
<SettingsAccordion value={value} title={title} description={description}>
|
||||
<SettingsAccordion value={value} title={title}>
|
||||
<Form {...form}>
|
||||
{/* eslint-disable-next-line react-hooks/refs */}
|
||||
<form onSubmit={form.handleSubmit(onSubmit)} className='space-y-6'>
|
||||
<SettingsForm onSubmit={form.handleSubmit(onSubmit)}>
|
||||
<SettingsPageFormActions
|
||||
onSave={form.handleSubmit(onSubmit)}
|
||||
isSaving={updateOption.isPending}
|
||||
saveLabel={submitLabel}
|
||||
/>
|
||||
<FormField
|
||||
control={form.control}
|
||||
name='enabled'
|
||||
render={({ field }) => (
|
||||
<FormItem className='flex flex-row items-center justify-between rounded-lg border p-4'>
|
||||
<div className='space-y-0.5'>
|
||||
<FormLabel className='text-base'>
|
||||
{t('Module availability')}
|
||||
</FormLabel>
|
||||
<SettingsSwitchItem>
|
||||
<SettingsSwitchContent>
|
||||
<FormLabel>{t('Module availability')}</FormLabel>
|
||||
{toggleDescription && (
|
||||
<FormDescription>{t(toggleDescription)}</FormDescription>
|
||||
)}
|
||||
</div>
|
||||
</SettingsSwitchContent>
|
||||
<FormControl>
|
||||
<Switch
|
||||
checked={field.value}
|
||||
onCheckedChange={field.onChange}
|
||||
/>
|
||||
</FormControl>
|
||||
</FormItem>
|
||||
</SettingsSwitchItem>
|
||||
)}
|
||||
/>
|
||||
|
||||
@ -203,11 +209,7 @@ export function JsonToggleSection({
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
|
||||
<Button type='submit' disabled={updateOption.isPending}>
|
||||
{updateOption.isPending ? t('Saving...') : t(submitLabel)}
|
||||
</Button>
|
||||
</form>
|
||||
</SettingsForm>
|
||||
</Form>
|
||||
</SettingsAccordion>
|
||||
)
|
||||
|
||||
@ -41,7 +41,6 @@ const CONTENT_SECTIONS = [
|
||||
{
|
||||
id: 'dashboard',
|
||||
titleKey: 'Data Dashboard',
|
||||
descriptionKey: 'Configure data export settings for dashboard',
|
||||
build: (settings: ContentSettings) => (
|
||||
<DashboardSection
|
||||
defaultValues={{
|
||||
@ -57,7 +56,6 @@ const CONTENT_SECTIONS = [
|
||||
{
|
||||
id: 'announcements',
|
||||
titleKey: 'Announcements',
|
||||
descriptionKey: 'Configure system announcements',
|
||||
build: (settings: ContentSettings) => (
|
||||
<AnnouncementsSection
|
||||
enabled={settings['console_setting.announcements_enabled']}
|
||||
@ -68,7 +66,6 @@ const CONTENT_SECTIONS = [
|
||||
{
|
||||
id: 'api-info',
|
||||
titleKey: 'API Addresses',
|
||||
descriptionKey: 'Configure API information display',
|
||||
build: (settings: ContentSettings) => (
|
||||
<ApiInfoSection
|
||||
enabled={settings['console_setting.api_info_enabled']}
|
||||
@ -79,7 +76,6 @@ const CONTENT_SECTIONS = [
|
||||
{
|
||||
id: 'faq',
|
||||
titleKey: 'FAQ',
|
||||
descriptionKey: 'Configure frequently asked questions',
|
||||
build: (settings: ContentSettings) => (
|
||||
<FAQSection
|
||||
enabled={settings['console_setting.faq_enabled']}
|
||||
@ -90,7 +86,6 @@ const CONTENT_SECTIONS = [
|
||||
{
|
||||
id: 'uptime-kuma',
|
||||
titleKey: 'Uptime Kuma',
|
||||
descriptionKey: 'Configure Uptime Kuma monitoring integration',
|
||||
build: (settings: ContentSettings) => (
|
||||
<UptimeKumaSection
|
||||
enabled={settings['console_setting.uptime_kuma_enabled']}
|
||||
@ -101,7 +96,6 @@ const CONTENT_SECTIONS = [
|
||||
{
|
||||
id: 'chat',
|
||||
titleKey: 'Chat Presets',
|
||||
descriptionKey: 'Configure chat-related settings',
|
||||
build: (settings: ContentSettings) => (
|
||||
<ChatSettingsSection defaultValue={settings.Chats} />
|
||||
),
|
||||
@ -109,7 +103,6 @@ const CONTENT_SECTIONS = [
|
||||
{
|
||||
id: 'drawing',
|
||||
titleKey: 'Drawing',
|
||||
descriptionKey: 'Configure drawing and Midjourney settings',
|
||||
build: (settings: ContentSettings) => (
|
||||
<DrawingSettingsSection
|
||||
defaultValues={{
|
||||
@ -141,3 +134,4 @@ export const CONTENT_SECTION_IDS = contentRegistry.sectionIds
|
||||
export const CONTENT_DEFAULT_SECTION = contentRegistry.defaultSection
|
||||
export const getContentSectionNavItems = contentRegistry.getSectionNavItems
|
||||
export const getContentSectionContent = contentRegistry.getSectionContent
|
||||
export const getContentSectionMeta = contentRegistry.getSectionMeta
|
||||
|
||||
@ -53,7 +53,6 @@ import {
|
||||
FormMessage,
|
||||
} from '@/components/ui/form'
|
||||
import { Input } from '@/components/ui/input'
|
||||
import { Switch } from '@/components/ui/switch'
|
||||
import {
|
||||
Table,
|
||||
TableBody,
|
||||
@ -62,6 +61,7 @@ import {
|
||||
TableHeader,
|
||||
TableRow,
|
||||
} from '@/components/ui/table'
|
||||
import { SettingsSwitchField } from '../components/settings-form-layout'
|
||||
import { SettingsSection } from '../components/settings-section'
|
||||
import { useUpdateOption } from '../hooks/use-update-option'
|
||||
|
||||
@ -247,12 +247,7 @@ export function UptimeKumaSection({ enabled, data }: UptimeKumaSectionProps) {
|
||||
}
|
||||
|
||||
return (
|
||||
<SettingsSection
|
||||
title={t('Uptime Kuma')}
|
||||
description={t(
|
||||
'Expose grouped Uptime Kuma status pages directly on the dashboard'
|
||||
)}
|
||||
>
|
||||
<SettingsSection title={t('Uptime Kuma')}>
|
||||
<div className='space-y-4'>
|
||||
<div className='flex flex-wrap items-center justify-between gap-2'>
|
||||
<div className='flex flex-wrap items-center gap-2'>
|
||||
@ -280,12 +275,12 @@ export function UptimeKumaSection({ enabled, data }: UptimeKumaSectionProps) {
|
||||
{updateOption.isPending ? t('Saving...') : t('Save Settings')}
|
||||
</Button>
|
||||
</div>
|
||||
<div className='flex items-center gap-2'>
|
||||
<span className='text-muted-foreground text-sm'>
|
||||
{t('Enabled')}
|
||||
</span>
|
||||
<Switch checked={isEnabled} onCheckedChange={handleToggleEnabled} />
|
||||
</div>
|
||||
<SettingsSwitchField
|
||||
checked={isEnabled}
|
||||
onCheckedChange={handleToggleEnabled}
|
||||
label={t('Enabled')}
|
||||
className='border-b-0 py-0'
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className='rounded-md border'>
|
||||
|
||||
@ -31,7 +31,6 @@ import {
|
||||
import { Input } from '@/components/ui/input'
|
||||
import { Label } from '@/components/ui/label'
|
||||
import { Separator } from '@/components/ui/separator'
|
||||
import { Switch } from '@/components/ui/switch'
|
||||
import {
|
||||
Table,
|
||||
TableBody,
|
||||
@ -43,6 +42,8 @@ import {
|
||||
import { Textarea } from '@/components/ui/textarea'
|
||||
import { ConfirmDialog } from '@/components/confirm-dialog'
|
||||
import { StatusBadge } from '@/components/status-badge'
|
||||
import { SettingsSwitchField } from '../../components/settings-form-layout'
|
||||
import { SettingsPageActionsPortal } from '../../components/settings-page-context'
|
||||
import { SettingsSection } from '../../components/settings-section'
|
||||
import { useUpdateOption } from '../../hooks/use-update-option'
|
||||
import { getCacheStats, clearAllCache, clearRuleCache } from './api'
|
||||
@ -333,12 +334,7 @@ export function ChannelAffinitySection(props: Props) {
|
||||
|
||||
return (
|
||||
<>
|
||||
<SettingsSection
|
||||
title={t('Channel Affinity')}
|
||||
description={t(
|
||||
'Prioritize reusing the last successful channel based on keys extracted from request context (sticky routing)'
|
||||
)}
|
||||
>
|
||||
<SettingsSection title={t('Channel Affinity')}>
|
||||
<Alert>
|
||||
<AlertDescription className='text-xs'>
|
||||
{t(
|
||||
@ -349,10 +345,12 @@ export function ChannelAffinitySection(props: Props) {
|
||||
|
||||
{/* Basic Settings */}
|
||||
<div className='grid grid-cols-1 gap-4 md:grid-cols-3'>
|
||||
<div className='flex items-center gap-2'>
|
||||
<Switch checked={enabled} onCheckedChange={setEnabled} />
|
||||
<Label>{t('Enable')}</Label>
|
||||
</div>
|
||||
<SettingsSwitchField
|
||||
checked={enabled}
|
||||
onCheckedChange={setEnabled}
|
||||
label={t('Enable')}
|
||||
className='border-b-0 py-0'
|
||||
/>
|
||||
<div className='grid gap-1.5'>
|
||||
<Label>{t('Max Entries')}</Label>
|
||||
<Input
|
||||
@ -373,23 +371,18 @@ export function ChannelAffinitySection(props: Props) {
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className='flex items-center gap-2'>
|
||||
<Switch
|
||||
checked={switchOnSuccess}
|
||||
onCheckedChange={setSwitchOnSuccess}
|
||||
/>
|
||||
<Label>{t('Switch affinity on success')}</Label>
|
||||
<span className='text-muted-foreground text-xs'>
|
||||
{t(
|
||||
'If the affinity channel fails and retry succeeds on another channel, update affinity to the successful channel.'
|
||||
)}
|
||||
</span>
|
||||
</div>
|
||||
<SettingsSwitchField
|
||||
checked={switchOnSuccess}
|
||||
onCheckedChange={setSwitchOnSuccess}
|
||||
label={t('Switch affinity on success')}
|
||||
description={t(
|
||||
'If the affinity channel fails and retry succeeds on another channel, update affinity to the successful channel.'
|
||||
)}
|
||||
/>
|
||||
|
||||
<Separator />
|
||||
|
||||
{/* Toolbar */}
|
||||
<div className='flex flex-wrap items-center gap-2'>
|
||||
<SettingsPageActionsPortal>
|
||||
<Button
|
||||
variant={editMode === 'visual' ? 'default' : 'outline'}
|
||||
size='sm'
|
||||
@ -472,7 +465,7 @@ export function ChannelAffinitySection(props: Props) {
|
||||
{cacheStats.cache_capacity}
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
</SettingsPageActionsPortal>
|
||||
|
||||
{/* Rules Table or JSON Editor */}
|
||||
{editMode === 'visual' ? (
|
||||
|
||||
@ -45,8 +45,8 @@ import {
|
||||
SelectValue,
|
||||
} from '@/components/ui/select'
|
||||
import { Separator } from '@/components/ui/separator'
|
||||
import { Switch } from '@/components/ui/switch'
|
||||
import { Textarea } from '@/components/ui/textarea'
|
||||
import { SettingsSwitchField } from '../../components/settings-form-layout'
|
||||
import { RULE_TEMPLATES } from './constants'
|
||||
import type { AffinityRule, KeySource } from './types'
|
||||
|
||||
@ -264,13 +264,11 @@ export function RuleEditorDialog(props: Props) {
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className='flex items-center gap-2'>
|
||||
<Switch
|
||||
checked={form.watch('skip_retry_on_failure')}
|
||||
onCheckedChange={(v) => form.setValue('skip_retry_on_failure', v)}
|
||||
/>
|
||||
<Label>{t('Skip retry on failure')}</Label>
|
||||
</div>
|
||||
<SettingsSwitchField
|
||||
checked={form.watch('skip_retry_on_failure')}
|
||||
onCheckedChange={(v) => form.setValue('skip_retry_on_failure', v)}
|
||||
label={t('Skip retry on failure')}
|
||||
/>
|
||||
|
||||
<Separator />
|
||||
|
||||
@ -415,34 +413,29 @@ export function RuleEditorDialog(props: Props) {
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className='grid grid-cols-3 gap-3'>
|
||||
<div className='flex items-center gap-2'>
|
||||
<Switch
|
||||
checked={form.watch('include_using_group')}
|
||||
onCheckedChange={(v) =>
|
||||
form.setValue('include_using_group', v)
|
||||
}
|
||||
/>
|
||||
<Label className='text-xs'>{t('Include Group')}</Label>
|
||||
</div>
|
||||
<div className='flex items-center gap-2'>
|
||||
<Switch
|
||||
checked={form.watch('include_model_name')}
|
||||
onCheckedChange={(v) =>
|
||||
form.setValue('include_model_name', v)
|
||||
}
|
||||
/>
|
||||
<Label className='text-xs'>{t('Include Model')}</Label>
|
||||
</div>
|
||||
<div className='flex items-center gap-2'>
|
||||
<Switch
|
||||
checked={form.watch('include_rule_name')}
|
||||
onCheckedChange={(v) =>
|
||||
form.setValue('include_rule_name', v)
|
||||
}
|
||||
/>
|
||||
<Label className='text-xs'>{t('Include Rule Name')}</Label>
|
||||
</div>
|
||||
<div className='grid gap-3 sm:grid-cols-3'>
|
||||
<SettingsSwitchField
|
||||
checked={form.watch('include_using_group')}
|
||||
onCheckedChange={(v) =>
|
||||
form.setValue('include_using_group', v)
|
||||
}
|
||||
label={t('Include Group')}
|
||||
className='border-b-0 py-0'
|
||||
/>
|
||||
<SettingsSwitchField
|
||||
checked={form.watch('include_model_name')}
|
||||
onCheckedChange={(v) =>
|
||||
form.setValue('include_model_name', v)
|
||||
}
|
||||
label={t('Include Model')}
|
||||
className='border-b-0 py-0'
|
||||
/>
|
||||
<SettingsSwitchField
|
||||
checked={form.watch('include_rule_name')}
|
||||
onCheckedChange={(v) => form.setValue('include_rule_name', v)}
|
||||
label={t('Include Rule Name')}
|
||||
className='border-b-0 py-0'
|
||||
/>
|
||||
</div>
|
||||
</CollapsibleContent>
|
||||
</Collapsible>
|
||||
|
||||
@ -21,7 +21,6 @@ import { useForm, type Resolver } from 'react-hook-form'
|
||||
import { zodResolver } from '@hookform/resolvers/zod'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { toast } from 'sonner'
|
||||
import { Button } from '@/components/ui/button'
|
||||
import {
|
||||
Form,
|
||||
FormControl,
|
||||
@ -33,6 +32,12 @@ import {
|
||||
} from '@/components/ui/form'
|
||||
import { Input } from '@/components/ui/input'
|
||||
import { Switch } from '@/components/ui/switch'
|
||||
import {
|
||||
SettingsForm,
|
||||
SettingsSwitchContent,
|
||||
SettingsSwitchItem,
|
||||
} from '../components/settings-form-layout'
|
||||
import { SettingsPageFormActions } from '../components/settings-page-context'
|
||||
import { SettingsSection } from '../components/settings-section'
|
||||
import { useUpdateOption } from '../hooks/use-update-option'
|
||||
|
||||
@ -105,31 +110,28 @@ export function CheckinSettingsSection({
|
||||
}
|
||||
|
||||
return (
|
||||
<SettingsSection
|
||||
title={t('Check-in Settings')}
|
||||
description={t('Configure daily check-in rewards for users')}
|
||||
>
|
||||
<SettingsSection title={t('Check-in Settings')}>
|
||||
<Form {...form}>
|
||||
<form
|
||||
onSubmit={form.handleSubmit(onSubmit)}
|
||||
autoComplete='off'
|
||||
className='space-y-6'
|
||||
>
|
||||
<SettingsForm onSubmit={form.handleSubmit(onSubmit)} autoComplete='off'>
|
||||
<SettingsPageFormActions
|
||||
onSave={form.handleSubmit(onSubmit)}
|
||||
isSaving={updateOption.isPending || isSubmitting}
|
||||
isSaveDisabled={!isDirty}
|
||||
saveLabel='Save check-in settings'
|
||||
/>
|
||||
<FormField
|
||||
control={form.control}
|
||||
name='enabled'
|
||||
render={({ field }) => (
|
||||
<FormItem className='flex flex-row items-center justify-between rounded-lg border p-4'>
|
||||
<div className='space-y-0.5'>
|
||||
<FormLabel className='text-base'>
|
||||
{t('Enable check-in feature')}
|
||||
</FormLabel>
|
||||
<SettingsSwitchItem>
|
||||
<SettingsSwitchContent>
|
||||
<FormLabel>{t('Enable check-in feature')}</FormLabel>
|
||||
<FormDescription>
|
||||
{t(
|
||||
'Allow users to check in daily for random quota rewards'
|
||||
)}
|
||||
</FormDescription>
|
||||
</div>
|
||||
</SettingsSwitchContent>
|
||||
<FormControl>
|
||||
<Switch
|
||||
checked={field.value}
|
||||
@ -137,7 +139,7 @@ export function CheckinSettingsSection({
|
||||
disabled={updateOption.isPending || isSubmitting}
|
||||
/>
|
||||
</FormControl>
|
||||
</FormItem>
|
||||
</SettingsSwitchItem>
|
||||
)}
|
||||
/>
|
||||
|
||||
@ -188,16 +190,7 @@ export function CheckinSettingsSection({
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<Button
|
||||
type='submit'
|
||||
disabled={!isDirty || updateOption.isPending || isSubmitting}
|
||||
>
|
||||
{updateOption.isPending || isSubmitting
|
||||
? t('Saving...')
|
||||
: t('Save check-in settings')}
|
||||
</Button>
|
||||
</form>
|
||||
</SettingsForm>
|
||||
</Form>
|
||||
</SettingsSection>
|
||||
)
|
||||
|
||||
@ -19,10 +19,8 @@ For commercial licensing, please contact support@quantumnous.com
|
||||
import * as z from 'zod'
|
||||
import type { Resolver } from 'react-hook-form'
|
||||
import { zodResolver } from '@hookform/resolvers/zod'
|
||||
import { RotateCcw } from 'lucide-react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { DEFAULT_CURRENCY_CONFIG } from '@/stores/system-config-store'
|
||||
import { Button } from '@/components/ui/button'
|
||||
import {
|
||||
Form,
|
||||
FormControl,
|
||||
@ -44,6 +42,12 @@ import {
|
||||
import { Switch } from '@/components/ui/switch'
|
||||
import { FormDirtyIndicator } from '../components/form-dirty-indicator'
|
||||
import { FormNavigationGuard } from '../components/form-navigation-guard'
|
||||
import {
|
||||
SettingsForm,
|
||||
SettingsSwitchContent,
|
||||
SettingsSwitchItem,
|
||||
} from '../components/settings-form-layout'
|
||||
import { SettingsPageFormActions } from '../components/settings-page-context'
|
||||
import { SettingsSection } from '../components/settings-section'
|
||||
import { useSettingsForm } from '../hooks/use-settings-form'
|
||||
import { useUpdateOption } from '../hooks/use-update-option'
|
||||
@ -141,12 +145,15 @@ export function PricingSection({ defaultValues }: PricingSectionProps) {
|
||||
<>
|
||||
<FormNavigationGuard when={isDirty} />
|
||||
|
||||
<SettingsSection
|
||||
title={t('Pricing & Display')}
|
||||
description={t('Configure pricing model and display options')}
|
||||
>
|
||||
<SettingsSection title={t('Pricing & Display')}>
|
||||
<Form {...form}>
|
||||
<form onSubmit={handleSubmit} className='space-y-6'>
|
||||
<SettingsForm onSubmit={handleSubmit}>
|
||||
<SettingsPageFormActions
|
||||
onSave={handleSubmit}
|
||||
onReset={handleReset}
|
||||
isSaving={updateOption.isPending || isSubmitting}
|
||||
isResetDisabled={!isDirty}
|
||||
/>
|
||||
<FormDirtyIndicator isDirty={isDirty} />
|
||||
{showQuotaPerUnit && (
|
||||
<FormField
|
||||
@ -320,11 +327,9 @@ export function PricingSection({ defaultValues }: PricingSectionProps) {
|
||||
control={form.control}
|
||||
name='DisplayInCurrencyEnabled'
|
||||
render={({ field }) => (
|
||||
<FormItem className='flex flex-row items-center justify-between rounded-lg border p-4'>
|
||||
<div className='space-y-0.5'>
|
||||
<FormLabel className='text-base'>
|
||||
{t('Display in Currency')}
|
||||
</FormLabel>
|
||||
<SettingsSwitchItem>
|
||||
<SettingsSwitchContent>
|
||||
<FormLabel>{t('Display in Currency')}</FormLabel>
|
||||
<FormDescription>
|
||||
{displayType === 'TOKENS'
|
||||
? t(
|
||||
@ -332,14 +337,14 @@ export function PricingSection({ defaultValues }: PricingSectionProps) {
|
||||
)
|
||||
: t('Show prices in currency instead of quota.')}
|
||||
</FormDescription>
|
||||
</div>
|
||||
</SettingsSwitchContent>
|
||||
<FormControl>
|
||||
<Switch
|
||||
checked={field.value}
|
||||
onCheckedChange={field.onChange}
|
||||
/>
|
||||
</FormControl>
|
||||
</FormItem>
|
||||
</SettingsSwitchItem>
|
||||
)}
|
||||
/>
|
||||
)}
|
||||
@ -348,43 +353,23 @@ export function PricingSection({ defaultValues }: PricingSectionProps) {
|
||||
control={form.control}
|
||||
name='DisplayTokenStatEnabled'
|
||||
render={({ field }) => (
|
||||
<FormItem className='flex flex-row items-center justify-between rounded-lg border p-4'>
|
||||
<div className='space-y-0.5'>
|
||||
<FormLabel className='text-base'>
|
||||
{t('Display Token Statistics')}
|
||||
</FormLabel>
|
||||
<SettingsSwitchItem>
|
||||
<SettingsSwitchContent>
|
||||
<FormLabel>{t('Display Token Statistics')}</FormLabel>
|
||||
<FormDescription>
|
||||
{t('Show token usage statistics in the UI')}
|
||||
</FormDescription>
|
||||
</div>
|
||||
</SettingsSwitchContent>
|
||||
<FormControl>
|
||||
<Switch
|
||||
checked={field.value}
|
||||
onCheckedChange={field.onChange}
|
||||
/>
|
||||
</FormControl>
|
||||
</FormItem>
|
||||
</SettingsSwitchItem>
|
||||
)}
|
||||
/>
|
||||
|
||||
<div className='flex gap-2'>
|
||||
<Button
|
||||
type='submit'
|
||||
disabled={updateOption.isPending || isSubmitting}
|
||||
>
|
||||
{updateOption.isPending ? t('Saving...') : t('Save Changes')}
|
||||
</Button>
|
||||
<Button
|
||||
type='button'
|
||||
variant='outline'
|
||||
onClick={handleReset}
|
||||
disabled={!isDirty || updateOption.isPending || isSubmitting}
|
||||
>
|
||||
<RotateCcw className='mr-2 h-4 w-4' />
|
||||
{t('Reset')}
|
||||
</Button>
|
||||
</div>
|
||||
</form>
|
||||
</SettingsForm>
|
||||
</Form>
|
||||
</SettingsSection>
|
||||
</>
|
||||
|
||||
@ -22,7 +22,6 @@ import type { Resolver } from 'react-hook-form'
|
||||
import { zodResolver } from '@hookform/resolvers/zod'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { Alert, AlertDescription } from '@/components/ui/alert'
|
||||
import { Button } from '@/components/ui/button'
|
||||
import {
|
||||
Form,
|
||||
FormControl,
|
||||
@ -36,6 +35,14 @@ import { Input } from '@/components/ui/input'
|
||||
import { Switch } from '@/components/ui/switch'
|
||||
import { FormDirtyIndicator } from '../components/form-dirty-indicator'
|
||||
import { FormNavigationGuard } from '../components/form-navigation-guard'
|
||||
import {
|
||||
SettingsForm,
|
||||
SettingsSwitchContent,
|
||||
SettingsSwitchItem,
|
||||
SettingsFormGrid,
|
||||
SettingsFormGridItem,
|
||||
} from '../components/settings-form-layout'
|
||||
import { SettingsPageFormActions } from '../components/settings-page-context'
|
||||
import { SettingsSection } from '../components/settings-section'
|
||||
import { useSettingsForm } from '../hooks/use-settings-form'
|
||||
import { useUpdateOption } from '../hooks/use-update-option'
|
||||
@ -94,10 +101,7 @@ export function QuotaSettingsSection({
|
||||
})
|
||||
|
||||
return (
|
||||
<SettingsSection
|
||||
title={t('Quota Settings')}
|
||||
description={t('Configure user quota allocation and rewards')}
|
||||
>
|
||||
<SettingsSection title={t('Quota Settings')}>
|
||||
<FormNavigationGuard when={isDirty} />
|
||||
|
||||
{!complianceConfirmed ? (
|
||||
@ -111,177 +115,176 @@ export function QuotaSettingsSection({
|
||||
) : null}
|
||||
|
||||
<Form {...form}>
|
||||
<form onSubmit={handleSubmit} className='space-y-6'>
|
||||
<SettingsForm onSubmit={handleSubmit}>
|
||||
<SettingsPageFormActions
|
||||
onSave={handleSubmit}
|
||||
isSaving={updateOption.isPending || isSubmitting}
|
||||
/>
|
||||
<FormDirtyIndicator isDirty={isDirty} />
|
||||
<FormField
|
||||
control={form.control}
|
||||
name='QuotaForNewUser'
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>{t('New User Quota')}</FormLabel>
|
||||
<FormControl>
|
||||
<Input
|
||||
type='number'
|
||||
value={field.value ?? ''}
|
||||
onChange={handleNumberChange(field.onChange)}
|
||||
name={field.name}
|
||||
onBlur={field.onBlur}
|
||||
ref={field.ref}
|
||||
/>
|
||||
</FormControl>
|
||||
<FormDescription>
|
||||
{t('Initial quota given to new users')}
|
||||
</FormDescription>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
|
||||
<FormField
|
||||
control={form.control}
|
||||
name='PreConsumedQuota'
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>{t('Pre-Consumed Quota')}</FormLabel>
|
||||
<FormControl>
|
||||
<Input
|
||||
type='number'
|
||||
value={field.value ?? ''}
|
||||
onChange={handleNumberChange(field.onChange)}
|
||||
name={field.name}
|
||||
onBlur={field.onBlur}
|
||||
ref={field.ref}
|
||||
/>
|
||||
</FormControl>
|
||||
<FormDescription>
|
||||
{t('Quota consumed before charging users')}
|
||||
</FormDescription>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
|
||||
<FormField
|
||||
control={form.control}
|
||||
name='QuotaForInviter'
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>{t('Inviter Reward')}</FormLabel>
|
||||
<FormControl>
|
||||
<Input
|
||||
type='number'
|
||||
value={field.value ?? ''}
|
||||
onChange={handleNumberChange(field.onChange)}
|
||||
name={field.name}
|
||||
onBlur={field.onBlur}
|
||||
ref={field.ref}
|
||||
/>
|
||||
</FormControl>
|
||||
<FormDescription>
|
||||
{t('Quota given to users who invite others')}
|
||||
</FormDescription>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
|
||||
<FormField
|
||||
control={form.control}
|
||||
name='QuotaForInvitee'
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>{t('Invitee Reward')}</FormLabel>
|
||||
<FormControl>
|
||||
<Input
|
||||
type='number'
|
||||
value={field.value ?? ''}
|
||||
onChange={handleNumberChange(field.onChange)}
|
||||
name={field.name}
|
||||
onBlur={field.onBlur}
|
||||
ref={field.ref}
|
||||
/>
|
||||
</FormControl>
|
||||
<FormDescription>
|
||||
{t('Quota given to invited users')}
|
||||
</FormDescription>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
|
||||
<FormField
|
||||
control={form.control}
|
||||
name='quota_setting.enable_free_model_pre_consume'
|
||||
render={({ field }) => (
|
||||
<FormItem className='flex flex-row items-center justify-between rounded-lg border p-4'>
|
||||
<div className='space-y-0.5'>
|
||||
<FormLabel className='text-base'>
|
||||
{t('Pre-Consume for Free Models')}
|
||||
</FormLabel>
|
||||
<SettingsFormGrid>
|
||||
<FormField
|
||||
control={form.control}
|
||||
name='QuotaForNewUser'
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>{t('New User Quota')}</FormLabel>
|
||||
<FormControl>
|
||||
<Input
|
||||
type='number'
|
||||
value={field.value ?? ''}
|
||||
onChange={handleNumberChange(field.onChange)}
|
||||
name={field.name}
|
||||
onBlur={field.onBlur}
|
||||
ref={field.ref}
|
||||
/>
|
||||
</FormControl>
|
||||
<FormDescription>
|
||||
{t(
|
||||
'When enabled, zero-cost models also pre-consume quota before final settlement.'
|
||||
)}
|
||||
{t('Initial quota given to new users')}
|
||||
</FormDescription>
|
||||
</div>
|
||||
<FormControl>
|
||||
<Switch
|
||||
checked={field.value}
|
||||
onCheckedChange={field.onChange}
|
||||
disabled={updateOption.isPending}
|
||||
/>
|
||||
</FormControl>
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
|
||||
<FormField
|
||||
control={form.control}
|
||||
name='TopUpLink'
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>{t('Top-Up Link')}</FormLabel>
|
||||
<FormControl>
|
||||
<Input
|
||||
placeholder={t('https://example.com/topup')}
|
||||
{...field}
|
||||
/>
|
||||
</FormControl>
|
||||
<FormDescription>
|
||||
{t('External link for users to purchase quota')}
|
||||
</FormDescription>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
<FormField
|
||||
control={form.control}
|
||||
name='PreConsumedQuota'
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>{t('Pre-Consumed Quota')}</FormLabel>
|
||||
<FormControl>
|
||||
<Input
|
||||
type='number'
|
||||
value={field.value ?? ''}
|
||||
onChange={handleNumberChange(field.onChange)}
|
||||
name={field.name}
|
||||
onBlur={field.onBlur}
|
||||
ref={field.ref}
|
||||
/>
|
||||
</FormControl>
|
||||
<FormDescription>
|
||||
{t('Quota consumed before charging users')}
|
||||
</FormDescription>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
|
||||
<FormField
|
||||
control={form.control}
|
||||
name='general_setting.docs_link'
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>{t('Documentation Link')}</FormLabel>
|
||||
<FormControl>
|
||||
<Input
|
||||
placeholder={t('https://docs.example.com')}
|
||||
{...field}
|
||||
/>
|
||||
</FormControl>
|
||||
<FormDescription>
|
||||
{t('Link to your documentation site')}
|
||||
</FormDescription>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
<FormField
|
||||
control={form.control}
|
||||
name='QuotaForInviter'
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>{t('Inviter Reward')}</FormLabel>
|
||||
<FormControl>
|
||||
<Input
|
||||
type='number'
|
||||
value={field.value ?? ''}
|
||||
onChange={handleNumberChange(field.onChange)}
|
||||
name={field.name}
|
||||
onBlur={field.onBlur}
|
||||
ref={field.ref}
|
||||
/>
|
||||
</FormControl>
|
||||
<FormDescription>
|
||||
{t('Quota given to users who invite others')}
|
||||
</FormDescription>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
|
||||
<Button
|
||||
type='submit'
|
||||
disabled={updateOption.isPending || isSubmitting}
|
||||
>
|
||||
{updateOption.isPending ? t('Saving...') : t('Save Changes')}
|
||||
</Button>
|
||||
</form>
|
||||
<FormField
|
||||
control={form.control}
|
||||
name='QuotaForInvitee'
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>{t('Invitee Reward')}</FormLabel>
|
||||
<FormControl>
|
||||
<Input
|
||||
type='number'
|
||||
value={field.value ?? ''}
|
||||
onChange={handleNumberChange(field.onChange)}
|
||||
name={field.name}
|
||||
onBlur={field.onBlur}
|
||||
ref={field.ref}
|
||||
/>
|
||||
</FormControl>
|
||||
<FormDescription>
|
||||
{t('Quota given to invited users')}
|
||||
</FormDescription>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
|
||||
<SettingsFormGridItem span='full'>
|
||||
<FormField
|
||||
control={form.control}
|
||||
name='quota_setting.enable_free_model_pre_consume'
|
||||
render={({ field }) => (
|
||||
<SettingsSwitchItem>
|
||||
<SettingsSwitchContent>
|
||||
<FormLabel>{t('Pre-Consume for Free Models')}</FormLabel>
|
||||
<FormDescription>
|
||||
{t(
|
||||
'When enabled, zero-cost models also pre-consume quota before final settlement.'
|
||||
)}
|
||||
</FormDescription>
|
||||
</SettingsSwitchContent>
|
||||
<FormControl>
|
||||
<Switch
|
||||
checked={field.value}
|
||||
onCheckedChange={field.onChange}
|
||||
disabled={updateOption.isPending}
|
||||
/>
|
||||
</FormControl>
|
||||
</SettingsSwitchItem>
|
||||
)}
|
||||
/>
|
||||
</SettingsFormGridItem>
|
||||
|
||||
<FormField
|
||||
control={form.control}
|
||||
name='TopUpLink'
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>{t('Top-Up Link')}</FormLabel>
|
||||
<FormControl>
|
||||
<Input
|
||||
placeholder={t('https://example.com/topup')}
|
||||
{...field}
|
||||
/>
|
||||
</FormControl>
|
||||
<FormDescription>
|
||||
{t('External link for users to purchase quota')}
|
||||
</FormDescription>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
|
||||
<FormField
|
||||
control={form.control}
|
||||
name='general_setting.docs_link'
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>{t('Documentation Link')}</FormLabel>
|
||||
<FormControl>
|
||||
<Input
|
||||
placeholder={t('https://docs.example.com')}
|
||||
{...field}
|
||||
/>
|
||||
</FormControl>
|
||||
<FormDescription>
|
||||
{t('Link to your documentation site')}
|
||||
</FormDescription>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
</SettingsFormGrid>
|
||||
</SettingsForm>
|
||||
</Form>
|
||||
</SettingsSection>
|
||||
)
|
||||
|
||||
@ -20,7 +20,6 @@ import * as z from 'zod'
|
||||
import { useForm } from 'react-hook-form'
|
||||
import { zodResolver } from '@hookform/resolvers/zod'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { Button } from '@/components/ui/button'
|
||||
import {
|
||||
Form,
|
||||
FormControl,
|
||||
@ -32,6 +31,12 @@ import {
|
||||
} from '@/components/ui/form'
|
||||
import { Input } from '@/components/ui/input'
|
||||
import { Switch } from '@/components/ui/switch'
|
||||
import {
|
||||
SettingsForm,
|
||||
SettingsSwitchContent,
|
||||
SettingsSwitchItem,
|
||||
} from '../components/settings-form-layout'
|
||||
import { SettingsPageFormActions } from '../components/settings-page-context'
|
||||
import { SettingsSection } from '../components/settings-section'
|
||||
import { useResetForm } from '../hooks/use-reset-form'
|
||||
import { useUpdateOption } from '../hooks/use-update-option'
|
||||
@ -73,12 +78,13 @@ export function SystemBehaviorSection({
|
||||
}
|
||||
|
||||
return (
|
||||
<SettingsSection
|
||||
title={t('System Behavior')}
|
||||
description={t('Configure system-wide behavior and defaults')}
|
||||
>
|
||||
<SettingsSection title={t('System Behavior')}>
|
||||
<Form {...form}>
|
||||
<form onSubmit={form.handleSubmit(onSubmit)} className='space-y-6'>
|
||||
<SettingsForm onSubmit={form.handleSubmit(onSubmit)}>
|
||||
<SettingsPageFormActions
|
||||
onSave={form.handleSubmit(onSubmit)}
|
||||
isSaving={updateOption.isPending}
|
||||
/>
|
||||
<FormField
|
||||
control={form.control}
|
||||
name='RetryTimes'
|
||||
@ -109,22 +115,20 @@ export function SystemBehaviorSection({
|
||||
control={form.control}
|
||||
name='DefaultCollapseSidebar'
|
||||
render={({ field }) => (
|
||||
<FormItem className='flex flex-row items-center justify-between rounded-lg border p-4'>
|
||||
<div className='space-y-0.5'>
|
||||
<FormLabel className='text-base'>
|
||||
{t('Default Collapse Sidebar')}
|
||||
</FormLabel>
|
||||
<SettingsSwitchItem>
|
||||
<SettingsSwitchContent>
|
||||
<FormLabel>{t('Default Collapse Sidebar')}</FormLabel>
|
||||
<FormDescription>
|
||||
{t('Sidebar collapsed by default for new users')}
|
||||
</FormDescription>
|
||||
</div>
|
||||
</SettingsSwitchContent>
|
||||
<FormControl>
|
||||
<Switch
|
||||
checked={field.value}
|
||||
onCheckedChange={field.onChange}
|
||||
/>
|
||||
</FormControl>
|
||||
</FormItem>
|
||||
</SettingsSwitchItem>
|
||||
)}
|
||||
/>
|
||||
|
||||
@ -132,22 +136,20 @@ export function SystemBehaviorSection({
|
||||
control={form.control}
|
||||
name='DemoSiteEnabled'
|
||||
render={({ field }) => (
|
||||
<FormItem className='flex flex-row items-center justify-between rounded-lg border p-4'>
|
||||
<div className='space-y-0.5'>
|
||||
<FormLabel className='text-base'>
|
||||
{t('Demo Site Mode')}
|
||||
</FormLabel>
|
||||
<SettingsSwitchItem>
|
||||
<SettingsSwitchContent>
|
||||
<FormLabel>{t('Demo Site Mode')}</FormLabel>
|
||||
<FormDescription>
|
||||
{t('Enable demo mode with limited functionality')}
|
||||
</FormDescription>
|
||||
</div>
|
||||
</SettingsSwitchContent>
|
||||
<FormControl>
|
||||
<Switch
|
||||
checked={field.value}
|
||||
onCheckedChange={field.onChange}
|
||||
/>
|
||||
</FormControl>
|
||||
</FormItem>
|
||||
</SettingsSwitchItem>
|
||||
)}
|
||||
/>
|
||||
|
||||
@ -155,29 +157,23 @@ export function SystemBehaviorSection({
|
||||
control={form.control}
|
||||
name='SelfUseModeEnabled'
|
||||
render={({ field }) => (
|
||||
<FormItem className='flex flex-row items-center justify-between rounded-lg border p-4'>
|
||||
<div className='space-y-0.5'>
|
||||
<FormLabel className='text-base'>
|
||||
{t('Self-Use Mode')}
|
||||
</FormLabel>
|
||||
<SettingsSwitchItem>
|
||||
<SettingsSwitchContent>
|
||||
<FormLabel>{t('Self-Use Mode')}</FormLabel>
|
||||
<FormDescription>
|
||||
{t('Optimize system for self-hosted single-user usage')}
|
||||
</FormDescription>
|
||||
</div>
|
||||
</SettingsSwitchContent>
|
||||
<FormControl>
|
||||
<Switch
|
||||
checked={field.value}
|
||||
onCheckedChange={field.onChange}
|
||||
/>
|
||||
</FormControl>
|
||||
</FormItem>
|
||||
</SettingsSwitchItem>
|
||||
)}
|
||||
/>
|
||||
|
||||
<Button type='submit' disabled={updateOption.isPending}>
|
||||
{updateOption.isPending ? t('Saving...') : t('Save Changes')}
|
||||
</Button>
|
||||
</form>
|
||||
</SettingsForm>
|
||||
</Form>
|
||||
</SettingsSection>
|
||||
)
|
||||
|
||||
@ -19,9 +19,7 @@ For commercial licensing, please contact support@quantumnous.com
|
||||
import * as z from 'zod'
|
||||
import type { Resolver } from 'react-hook-form'
|
||||
import { zodResolver } from '@hookform/resolvers/zod'
|
||||
import { RotateCcw } from 'lucide-react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { Button } from '@/components/ui/button'
|
||||
import {
|
||||
Form,
|
||||
FormControl,
|
||||
@ -43,6 +41,12 @@ import {
|
||||
import { Textarea } from '@/components/ui/textarea'
|
||||
import { FormDirtyIndicator } from '../components/form-dirty-indicator'
|
||||
import { FormNavigationGuard } from '../components/form-navigation-guard'
|
||||
import {
|
||||
SettingsForm,
|
||||
SettingsFormGrid,
|
||||
SettingsFormGridItem,
|
||||
} from '../components/settings-form-layout'
|
||||
import { SettingsPageFormActions } from '../components/settings-page-context'
|
||||
import { SettingsSection } from '../components/settings-section'
|
||||
import { useSettingsForm } from '../hooks/use-settings-form'
|
||||
import { useUpdateOption } from '../hooks/use-update-option'
|
||||
@ -139,250 +143,243 @@ export function SystemInfoSection({ defaultValues }: SystemInfoSectionProps) {
|
||||
<>
|
||||
<FormNavigationGuard when={isDirty} />
|
||||
|
||||
<SettingsSection
|
||||
title={t('System Information')}
|
||||
description={t('Configure basic system information and branding')}
|
||||
>
|
||||
<SettingsSection title={t('System Information')}>
|
||||
<Form {...form}>
|
||||
<form onSubmit={handleSubmit} className='space-y-6'>
|
||||
<SettingsForm onSubmit={handleSubmit}>
|
||||
<SettingsPageFormActions
|
||||
onSave={handleSubmit}
|
||||
onReset={handleReset}
|
||||
isSaving={isSubmitting || updateOption.isPending}
|
||||
isResetDisabled={!isDirty}
|
||||
/>
|
||||
<FormDirtyIndicator isDirty={isDirty} />
|
||||
<FormField
|
||||
control={form.control}
|
||||
name='theme.frontend'
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>{t('Frontend Theme')}</FormLabel>
|
||||
<Select
|
||||
items={[
|
||||
{ value: 'default', label: t('Default (New Frontend)') },
|
||||
{
|
||||
value: 'classic',
|
||||
label: t('Classic (Legacy Frontend)'),
|
||||
},
|
||||
]}
|
||||
onValueChange={field.onChange}
|
||||
value={field.value}
|
||||
>
|
||||
<SettingsFormGrid>
|
||||
<FormField
|
||||
control={form.control}
|
||||
name='theme.frontend'
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>{t('Frontend Theme')}</FormLabel>
|
||||
<Select
|
||||
items={[
|
||||
{
|
||||
value: 'default',
|
||||
label: t('Default (New Frontend)'),
|
||||
},
|
||||
{
|
||||
value: 'classic',
|
||||
label: t('Classic (Legacy Frontend)'),
|
||||
},
|
||||
]}
|
||||
onValueChange={field.onChange}
|
||||
value={field.value}
|
||||
>
|
||||
<FormControl>
|
||||
<SelectTrigger className='w-full'>
|
||||
<SelectValue />
|
||||
</SelectTrigger>
|
||||
</FormControl>
|
||||
<SelectContent alignItemWithTrigger={false}>
|
||||
<SelectGroup>
|
||||
<SelectItem value='default'>
|
||||
{t('Default (New Frontend)')}
|
||||
</SelectItem>
|
||||
<SelectItem value='classic'>
|
||||
{t('Classic (Legacy Frontend)')}
|
||||
</SelectItem>
|
||||
</SelectGroup>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
<FormDescription>
|
||||
{t(
|
||||
'Switch between the new frontend and the classic frontend. Changes take effect after page reload.'
|
||||
)}
|
||||
</FormDescription>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
|
||||
<FormField
|
||||
control={form.control}
|
||||
name='SystemName'
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>{t('System Name')}</FormLabel>
|
||||
<FormControl>
|
||||
<SelectTrigger className='w-full'>
|
||||
<SelectValue />
|
||||
</SelectTrigger>
|
||||
<Input placeholder={t('New API')} {...field} />
|
||||
</FormControl>
|
||||
<SelectContent alignItemWithTrigger={false}>
|
||||
<SelectGroup>
|
||||
<SelectItem value='default'>
|
||||
{t('Default (New Frontend)')}
|
||||
</SelectItem>
|
||||
<SelectItem value='classic'>
|
||||
{t('Classic (Legacy Frontend)')}
|
||||
</SelectItem>
|
||||
</SelectGroup>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
<FormDescription>
|
||||
{t(
|
||||
'Switch between the new frontend and the classic frontend. Changes take effect after page reload.'
|
||||
)}
|
||||
</FormDescription>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
<FormDescription>
|
||||
{t('The name displayed across the application')}
|
||||
</FormDescription>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
|
||||
<FormField
|
||||
control={form.control}
|
||||
name='SystemName'
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>{t('System Name')}</FormLabel>
|
||||
<FormControl>
|
||||
<Input placeholder={t('New API')} {...field} />
|
||||
</FormControl>
|
||||
<FormDescription>
|
||||
{t('The name displayed across the application')}
|
||||
</FormDescription>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
|
||||
<FormField
|
||||
control={form.control}
|
||||
name='ServerAddress'
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>{t('Server Address')}</FormLabel>
|
||||
<FormControl>
|
||||
<Input placeholder='https://yourdomain.com' {...field} />
|
||||
</FormControl>
|
||||
<FormDescription>
|
||||
{t(
|
||||
'The public URL of your server, used for OAuth callbacks, webhooks, and other external integrations'
|
||||
)}
|
||||
</FormDescription>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
|
||||
<FormField
|
||||
control={form.control}
|
||||
name='Logo'
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>{t('Logo URL')}</FormLabel>
|
||||
<FormControl>
|
||||
<Input
|
||||
placeholder={t('https://example.com/logo.png')}
|
||||
{...field}
|
||||
/>
|
||||
</FormControl>
|
||||
<FormDescription>
|
||||
{t('URL to your logo image (optional)')}
|
||||
</FormDescription>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
|
||||
<FormField
|
||||
control={form.control}
|
||||
name='Footer'
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>{t('Footer')}</FormLabel>
|
||||
<FormControl>
|
||||
<Textarea
|
||||
placeholder={t(
|
||||
'© 2025 Your Company. All rights reserved.'
|
||||
<FormField
|
||||
control={form.control}
|
||||
name='ServerAddress'
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>{t('Server Address')}</FormLabel>
|
||||
<FormControl>
|
||||
<Input placeholder='https://yourdomain.com' {...field} />
|
||||
</FormControl>
|
||||
<FormDescription>
|
||||
{t(
|
||||
'The public URL of your server, used for OAuth callbacks, webhooks, and other external integrations'
|
||||
)}
|
||||
{...field}
|
||||
/>
|
||||
</FormControl>
|
||||
<FormDescription>
|
||||
{t('Footer text displayed at the bottom of pages')}
|
||||
</FormDescription>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
</FormDescription>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
|
||||
<FormField
|
||||
control={form.control}
|
||||
name='About'
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>{t('About')}</FormLabel>
|
||||
<FormControl>
|
||||
<Textarea
|
||||
placeholder={t(
|
||||
'Enter HTML code (e.g., <p>About us...</p>) or a URL (e.g., https://example.com) to embed as iframe'
|
||||
<FormField
|
||||
control={form.control}
|
||||
name='Logo'
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>{t('Logo URL')}</FormLabel>
|
||||
<FormControl>
|
||||
<Input
|
||||
placeholder={t('https://example.com/logo.png')}
|
||||
{...field}
|
||||
/>
|
||||
</FormControl>
|
||||
<FormDescription>
|
||||
{t('URL to your logo image (optional)')}
|
||||
</FormDescription>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
|
||||
<FormField
|
||||
control={form.control}
|
||||
name='Footer'
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>{t('Footer')}</FormLabel>
|
||||
<FormControl>
|
||||
<Textarea
|
||||
placeholder={t(
|
||||
'© 2025 Your Company. All rights reserved.'
|
||||
)}
|
||||
rows={4}
|
||||
{...field}
|
||||
/>
|
||||
</FormControl>
|
||||
<FormDescription>
|
||||
{t('Footer text displayed at the bottom of pages')}
|
||||
</FormDescription>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
|
||||
<FormField
|
||||
control={form.control}
|
||||
name='About'
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>{t('About')}</FormLabel>
|
||||
<FormControl>
|
||||
<Textarea
|
||||
placeholder={t(
|
||||
'Enter HTML code (e.g., <p>About us...</p>) or a URL (e.g., https://example.com) to embed as iframe'
|
||||
)}
|
||||
rows={4}
|
||||
{...field}
|
||||
/>
|
||||
</FormControl>
|
||||
<FormDescription>
|
||||
{t(
|
||||
'Supports HTML markup or iframe embedding. Enter HTML code directly, or provide a complete URL to automatically embed it as an iframe.'
|
||||
)}
|
||||
rows={4}
|
||||
{...field}
|
||||
/>
|
||||
</FormControl>
|
||||
<FormDescription>
|
||||
{t(
|
||||
'Supports HTML markup or iframe embedding. Enter HTML code directly, or provide a complete URL to automatically embed it as an iframe.'
|
||||
)}
|
||||
</FormDescription>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
</FormDescription>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
|
||||
<FormField
|
||||
control={form.control}
|
||||
name='HomePageContent'
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>{t('Home Page Content')}</FormLabel>
|
||||
<FormControl>
|
||||
<Textarea
|
||||
placeholder={t('Welcome to our New API...')}
|
||||
rows={6}
|
||||
{...field}
|
||||
/>
|
||||
</FormControl>
|
||||
<FormDescription>
|
||||
{t(
|
||||
'Content displayed on the home page (supports Markdown)'
|
||||
)}
|
||||
</FormDescription>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
<SettingsFormGridItem span='full'>
|
||||
<FormField
|
||||
control={form.control}
|
||||
name='HomePageContent'
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>{t('Home Page Content')}</FormLabel>
|
||||
<FormControl>
|
||||
<Textarea
|
||||
placeholder={t('Welcome to our New API...')}
|
||||
rows={6}
|
||||
{...field}
|
||||
/>
|
||||
</FormControl>
|
||||
<FormDescription>
|
||||
{t(
|
||||
'Content displayed on the home page (supports Markdown)'
|
||||
)}
|
||||
</FormDescription>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
</SettingsFormGridItem>
|
||||
|
||||
<FormField
|
||||
control={form.control}
|
||||
name='legal.user_agreement'
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>{t('User Agreement')}</FormLabel>
|
||||
<FormControl>
|
||||
<Textarea
|
||||
placeholder={t(
|
||||
'Provide Markdown, HTML, or an external URL for the user agreement'
|
||||
<FormField
|
||||
control={form.control}
|
||||
name='legal.user_agreement'
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>{t('User Agreement')}</FormLabel>
|
||||
<FormControl>
|
||||
<Textarea
|
||||
placeholder={t(
|
||||
'Provide Markdown, HTML, or an external URL for the user agreement'
|
||||
)}
|
||||
rows={6}
|
||||
{...field}
|
||||
/>
|
||||
</FormControl>
|
||||
<FormDescription>
|
||||
{t(
|
||||
'Leave empty to disable the agreement requirement. Supports Markdown, HTML, or a full URL to redirect users.'
|
||||
)}
|
||||
rows={6}
|
||||
{...field}
|
||||
/>
|
||||
</FormControl>
|
||||
<FormDescription>
|
||||
{t(
|
||||
'Leave empty to disable the agreement requirement. Supports Markdown, HTML, or a full URL to redirect users.'
|
||||
)}
|
||||
</FormDescription>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
</FormDescription>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
|
||||
<FormField
|
||||
control={form.control}
|
||||
name='legal.privacy_policy'
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>{t('Privacy Policy')}</FormLabel>
|
||||
<FormControl>
|
||||
<Textarea
|
||||
placeholder={t(
|
||||
'Provide Markdown, HTML, or an external URL for the privacy policy'
|
||||
<FormField
|
||||
control={form.control}
|
||||
name='legal.privacy_policy'
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>{t('Privacy Policy')}</FormLabel>
|
||||
<FormControl>
|
||||
<Textarea
|
||||
placeholder={t(
|
||||
'Provide Markdown, HTML, or an external URL for the privacy policy'
|
||||
)}
|
||||
rows={6}
|
||||
{...field}
|
||||
/>
|
||||
</FormControl>
|
||||
<FormDescription>
|
||||
{t(
|
||||
'Leave empty to disable the privacy policy requirement. Supports Markdown, HTML, or a full URL to redirect users.'
|
||||
)}
|
||||
rows={6}
|
||||
{...field}
|
||||
/>
|
||||
</FormControl>
|
||||
<FormDescription>
|
||||
{t(
|
||||
'Leave empty to disable the privacy policy requirement. Supports Markdown, HTML, or a full URL to redirect users.'
|
||||
)}
|
||||
</FormDescription>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
|
||||
<div className='flex gap-2'>
|
||||
<Button
|
||||
type='submit'
|
||||
disabled={isSubmitting || updateOption.isPending}
|
||||
>
|
||||
{updateOption.isPending ? t('Saving...') : t('Save Changes')}
|
||||
</Button>
|
||||
<Button
|
||||
type='button'
|
||||
variant='outline'
|
||||
onClick={handleReset}
|
||||
disabled={!isDirty || updateOption.isPending || isSubmitting}
|
||||
>
|
||||
<RotateCcw className='mr-2 h-4 w-4' />
|
||||
{t('Reset')}
|
||||
</Button>
|
||||
</div>
|
||||
</form>
|
||||
</FormDescription>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
</SettingsFormGrid>
|
||||
</SettingsForm>
|
||||
</Form>
|
||||
</SettingsSection>
|
||||
</>
|
||||
|
||||
@ -17,14 +17,7 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
For commercial licensing, please contact support@quantumnous.com
|
||||
*/
|
||||
import { Outlet } from '@tanstack/react-router'
|
||||
import { Main } from '@/components/layout'
|
||||
|
||||
export function SystemSettings() {
|
||||
return (
|
||||
<Main>
|
||||
<div className='min-h-0 flex-1 px-4 pt-6 pb-4'>
|
||||
<Outlet />
|
||||
</div>
|
||||
</Main>
|
||||
)
|
||||
return <Outlet />
|
||||
}
|
||||
|
||||
@ -20,7 +20,6 @@ import * as z from 'zod'
|
||||
import { useForm } from 'react-hook-form'
|
||||
import { zodResolver } from '@hookform/resolvers/zod'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { Button } from '@/components/ui/button'
|
||||
import {
|
||||
Form,
|
||||
FormControl,
|
||||
@ -32,6 +31,12 @@ import {
|
||||
} from '@/components/ui/form'
|
||||
import { Input } from '@/components/ui/input'
|
||||
import { Switch } from '@/components/ui/switch'
|
||||
import {
|
||||
SettingsForm,
|
||||
SettingsSwitchContent,
|
||||
SettingsSwitchItem,
|
||||
} from '../components/settings-form-layout'
|
||||
import { SettingsPageFormActions } from '../components/settings-page-context'
|
||||
import { SettingsSection } from '../components/settings-section'
|
||||
import { useResetForm } from '../hooks/use-reset-form'
|
||||
import { useUpdateOption } from '../hooks/use-update-option'
|
||||
@ -138,16 +143,14 @@ export function EmailSettingsSection({
|
||||
}
|
||||
|
||||
return (
|
||||
<SettingsSection
|
||||
title={t('SMTP Email')}
|
||||
description={t('Configure outgoing email server for notifications')}
|
||||
>
|
||||
<SettingsSection title={t('SMTP Email')}>
|
||||
<Form {...form}>
|
||||
<form
|
||||
onSubmit={form.handleSubmit(onSubmit)}
|
||||
className='space-y-6'
|
||||
autoComplete='off'
|
||||
>
|
||||
<SettingsForm onSubmit={form.handleSubmit(onSubmit)} autoComplete='off'>
|
||||
<SettingsPageFormActions
|
||||
onSave={form.handleSubmit(onSubmit)}
|
||||
isSaving={updateOption.isPending}
|
||||
saveLabel='Save SMTP settings'
|
||||
/>
|
||||
<FormField
|
||||
control={form.control}
|
||||
name='SMTPServer'
|
||||
@ -198,22 +201,20 @@ export function EmailSettingsSection({
|
||||
control={form.control}
|
||||
name='SMTPSSLEnabled'
|
||||
render={({ field }) => (
|
||||
<FormItem className='flex flex-row items-center justify-between rounded-lg border p-4'>
|
||||
<div className='space-y-0.5'>
|
||||
<FormLabel className='text-base'>
|
||||
{t('Enable SSL/TLS')}
|
||||
</FormLabel>
|
||||
<SettingsSwitchItem>
|
||||
<SettingsSwitchContent>
|
||||
<FormLabel>{t('Enable SSL/TLS')}</FormLabel>
|
||||
<FormDescription>
|
||||
{t('Use secure connection when sending emails')}
|
||||
</FormDescription>
|
||||
</div>
|
||||
</SettingsSwitchContent>
|
||||
<FormControl>
|
||||
<Switch
|
||||
checked={field.value}
|
||||
onCheckedChange={field.onChange}
|
||||
/>
|
||||
</FormControl>
|
||||
</FormItem>
|
||||
</SettingsSwitchItem>
|
||||
)}
|
||||
/>
|
||||
|
||||
@ -221,22 +222,20 @@ export function EmailSettingsSection({
|
||||
control={form.control}
|
||||
name='SMTPForceAuthLogin'
|
||||
render={({ field }) => (
|
||||
<FormItem className='flex flex-row items-center justify-between rounded-lg border p-4'>
|
||||
<div className='space-y-0.5'>
|
||||
<FormLabel className='text-base'>
|
||||
{t('Force AUTH LOGIN')}
|
||||
</FormLabel>
|
||||
<SettingsSwitchItem>
|
||||
<SettingsSwitchContent>
|
||||
<FormLabel>{t('Force AUTH LOGIN')}</FormLabel>
|
||||
<FormDescription>
|
||||
{t('Force SMTP authentication using AUTH LOGIN method')}
|
||||
</FormDescription>
|
||||
</div>
|
||||
</SettingsSwitchContent>
|
||||
<FormControl>
|
||||
<Switch
|
||||
checked={field.value}
|
||||
onCheckedChange={field.onChange}
|
||||
/>
|
||||
</FormControl>
|
||||
</FormItem>
|
||||
</SettingsSwitchItem>
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
@ -307,11 +306,7 @@ export function EmailSettingsSection({
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
|
||||
<Button type='submit' disabled={updateOption.isPending}>
|
||||
{updateOption.isPending ? t('Saving...') : t('Save SMTP settings')}
|
||||
</Button>
|
||||
</form>
|
||||
</SettingsForm>
|
||||
</Form>
|
||||
</SettingsSection>
|
||||
)
|
||||
|
||||
@ -37,6 +37,12 @@ import {
|
||||
import { Input } from '@/components/ui/input'
|
||||
import { Switch } from '@/components/ui/switch'
|
||||
import { testDeploymentConnectionWithKey } from '@/features/models/api'
|
||||
import {
|
||||
SettingsForm,
|
||||
SettingsSwitchContent,
|
||||
SettingsSwitchItem,
|
||||
} from '../components/settings-form-layout'
|
||||
import { SettingsPageFormActions } from '../components/settings-page-context'
|
||||
import { SettingsSection } from '../components/settings-section'
|
||||
import { useUpdateOption } from '../hooks/use-update-option'
|
||||
|
||||
@ -129,29 +135,26 @@ export function IoNetDeploymentSettingsSection({
|
||||
}
|
||||
|
||||
return (
|
||||
<SettingsSection
|
||||
title={t('io.net Deployments')}
|
||||
description={t('Configure io.net API key for model deployments')}
|
||||
>
|
||||
<SettingsSection title={t('io.net Deployments')}>
|
||||
<Form {...form}>
|
||||
<form
|
||||
onSubmit={form.handleSubmit(onSubmit)}
|
||||
autoComplete='off'
|
||||
className='space-y-6'
|
||||
>
|
||||
<SettingsForm onSubmit={form.handleSubmit(onSubmit)} autoComplete='off'>
|
||||
<SettingsPageFormActions
|
||||
onSave={form.handleSubmit(onSubmit)}
|
||||
isSaving={updateOption.isPending || isSubmitting}
|
||||
isSaveDisabled={!isDirty}
|
||||
saveLabel='Save io.net settings'
|
||||
/>
|
||||
<FormField
|
||||
control={form.control}
|
||||
name='enabled'
|
||||
render={({ field }) => (
|
||||
<FormItem className='flex flex-row items-center justify-between rounded-lg border p-4'>
|
||||
<div className='space-y-0.5'>
|
||||
<FormLabel className='text-base'>
|
||||
{t('Enable io.net deployments')}
|
||||
</FormLabel>
|
||||
<SettingsSwitchItem>
|
||||
<SettingsSwitchContent>
|
||||
<FormLabel>{t('Enable io.net deployments')}</FormLabel>
|
||||
<FormDescription>
|
||||
{t('Enable io.net model deployment service in console')}
|
||||
</FormDescription>
|
||||
</div>
|
||||
</SettingsSwitchContent>
|
||||
<FormControl>
|
||||
<Switch
|
||||
checked={field.value}
|
||||
@ -159,7 +162,7 @@ export function IoNetDeploymentSettingsSection({
|
||||
disabled={updateOption.isPending || isSubmitting}
|
||||
/>
|
||||
</FormControl>
|
||||
</FormItem>
|
||||
</SettingsSwitchItem>
|
||||
)}
|
||||
/>
|
||||
|
||||
@ -253,16 +256,7 @@ export function IoNetDeploymentSettingsSection({
|
||||
) : null}
|
||||
</>
|
||||
) : null}
|
||||
|
||||
<Button
|
||||
type='submit'
|
||||
disabled={!isDirty || updateOption.isPending || isSubmitting}
|
||||
>
|
||||
{updateOption.isPending || isSubmitting
|
||||
? t('Saving...')
|
||||
: t('Save io.net settings')}
|
||||
</Button>
|
||||
</form>
|
||||
</SettingsForm>
|
||||
</Form>
|
||||
</SettingsSection>
|
||||
)
|
||||
|
||||
@ -23,7 +23,6 @@ import { zodResolver } from '@hookform/resolvers/zod'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { toast } from 'sonner'
|
||||
import { parseHttpStatusCodeRules } from '@/lib/http-status-code-rules'
|
||||
import { Button } from '@/components/ui/button'
|
||||
import {
|
||||
Form,
|
||||
FormControl,
|
||||
@ -36,6 +35,12 @@ import {
|
||||
import { Input } from '@/components/ui/input'
|
||||
import { Switch } from '@/components/ui/switch'
|
||||
import { Textarea } from '@/components/ui/textarea'
|
||||
import {
|
||||
SettingsForm,
|
||||
SettingsSwitchContent,
|
||||
SettingsSwitchItem,
|
||||
} from '../components/settings-form-layout'
|
||||
import { SettingsPageFormActions } from '../components/settings-page-context'
|
||||
import { SettingsSection } from '../components/settings-section'
|
||||
import { useResetForm } from '../hooks/use-reset-form'
|
||||
import { useUpdateOption } from '../hooks/use-update-option'
|
||||
@ -243,35 +248,33 @@ export function MonitoringSettingsSection({
|
||||
}
|
||||
|
||||
return (
|
||||
<SettingsSection
|
||||
title={t('Monitoring & Alerts')}
|
||||
description={t(
|
||||
'Automatically test channels and notify users when limits are hit'
|
||||
)}
|
||||
>
|
||||
<SettingsSection title={t('Monitoring & Alerts')}>
|
||||
<Form {...form}>
|
||||
<form onSubmit={form.handleSubmit(onSubmit)} className='space-y-6'>
|
||||
<SettingsForm onSubmit={form.handleSubmit(onSubmit)}>
|
||||
<SettingsPageFormActions
|
||||
onSave={form.handleSubmit(onSubmit)}
|
||||
isSaving={updateOption.isPending}
|
||||
saveLabel='Save monitoring rules'
|
||||
/>
|
||||
<div className='grid gap-6 md:grid-cols-2'>
|
||||
<FormField
|
||||
control={form.control}
|
||||
name='monitor_setting.auto_test_channel_enabled'
|
||||
render={({ field }) => (
|
||||
<FormItem className='flex flex-row items-center justify-between rounded-lg border p-4'>
|
||||
<div className='space-y-0.5'>
|
||||
<FormLabel className='text-base'>
|
||||
{t('Scheduled channel tests')}
|
||||
</FormLabel>
|
||||
<SettingsSwitchItem>
|
||||
<SettingsSwitchContent>
|
||||
<FormLabel>{t('Scheduled channel tests')}</FormLabel>
|
||||
<FormDescription>
|
||||
{t('Automatically probe all channels in the background')}
|
||||
</FormDescription>
|
||||
</div>
|
||||
</SettingsSwitchContent>
|
||||
<FormControl>
|
||||
<Switch
|
||||
checked={field.value}
|
||||
onCheckedChange={field.onChange}
|
||||
/>
|
||||
</FormControl>
|
||||
</FormItem>
|
||||
</SettingsSwitchItem>
|
||||
)}
|
||||
/>
|
||||
|
||||
@ -364,22 +367,20 @@ export function MonitoringSettingsSection({
|
||||
control={form.control}
|
||||
name='AutomaticDisableChannelEnabled'
|
||||
render={({ field }) => (
|
||||
<FormItem className='flex flex-row items-center justify-between rounded-lg border p-4'>
|
||||
<div className='space-y-0.5'>
|
||||
<FormLabel className='text-base'>
|
||||
{t('Disable on failure')}
|
||||
</FormLabel>
|
||||
<SettingsSwitchItem>
|
||||
<SettingsSwitchContent>
|
||||
<FormLabel>{t('Disable on failure')}</FormLabel>
|
||||
<FormDescription>
|
||||
{t('Automatically disable channels when tests fail')}
|
||||
</FormDescription>
|
||||
</div>
|
||||
</SettingsSwitchContent>
|
||||
<FormControl>
|
||||
<Switch
|
||||
checked={field.value}
|
||||
onCheckedChange={field.onChange}
|
||||
/>
|
||||
</FormControl>
|
||||
</FormItem>
|
||||
</SettingsSwitchItem>
|
||||
)}
|
||||
/>
|
||||
|
||||
@ -387,22 +388,20 @@ export function MonitoringSettingsSection({
|
||||
control={form.control}
|
||||
name='AutomaticEnableChannelEnabled'
|
||||
render={({ field }) => (
|
||||
<FormItem className='flex flex-row items-center justify-between rounded-lg border p-4'>
|
||||
<div className='space-y-0.5'>
|
||||
<FormLabel className='text-base'>
|
||||
{t('Re-enable on success')}
|
||||
</FormLabel>
|
||||
<SettingsSwitchItem>
|
||||
<SettingsSwitchContent>
|
||||
<FormLabel>{t('Re-enable on success')}</FormLabel>
|
||||
<FormDescription>
|
||||
{t('Bring channels back online after successful checks')}
|
||||
</FormDescription>
|
||||
</div>
|
||||
</SettingsSwitchContent>
|
||||
<FormControl>
|
||||
<Switch
|
||||
checked={field.value}
|
||||
onCheckedChange={field.onChange}
|
||||
/>
|
||||
</FormControl>
|
||||
</FormItem>
|
||||
</SettingsSwitchItem>
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
@ -492,13 +491,7 @@ export function MonitoringSettingsSection({
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<Button type='submit' disabled={updateOption.isPending}>
|
||||
{updateOption.isPending
|
||||
? t('Saving...')
|
||||
: t('Save monitoring rules')}
|
||||
</Button>
|
||||
</form>
|
||||
</SettingsForm>
|
||||
</Form>
|
||||
</SettingsSection>
|
||||
)
|
||||
|
||||
@ -47,6 +47,12 @@ import { Switch } from '@/components/ui/switch'
|
||||
import { Textarea } from '@/components/ui/textarea'
|
||||
import { RiskAcknowledgementDialog } from '@/components/risk-acknowledgement-dialog'
|
||||
import { confirmPaymentCompliance } from '../api'
|
||||
import {
|
||||
SettingsForm,
|
||||
SettingsSwitchContent,
|
||||
SettingsSwitchItem,
|
||||
} from '../components/settings-form-layout'
|
||||
import { SettingsPageFormActions } from '../components/settings-page-context'
|
||||
import { SettingsSection } from '../components/settings-section'
|
||||
import { useUpdateOption } from '../hooks/use-update-option'
|
||||
import { AmountDiscountVisualEditor } from './amount-discount-visual-editor'
|
||||
@ -278,25 +284,67 @@ export function PaymentSettingsSection({
|
||||
})
|
||||
}, [defaultsSignature, form])
|
||||
|
||||
const saveGeneralSettings = async () => {
|
||||
const values = form.getValues()
|
||||
const onSubmit = async (values: PaymentFormValues) => {
|
||||
const sanitized = {
|
||||
Price: values.Price as number,
|
||||
MinTopUp: values.MinTopUp as number,
|
||||
PayAddress: removeTrailingSlash(values.PayAddress),
|
||||
EpayId: values.EpayId.trim(),
|
||||
EpayKey: values.EpayKey.trim(),
|
||||
Price: values.Price,
|
||||
MinTopUp: values.MinTopUp,
|
||||
CustomCallbackAddress: removeTrailingSlash(values.CustomCallbackAddress),
|
||||
PayMethods: values.PayMethods.trim(),
|
||||
AmountOptions: values.AmountOptions.trim(),
|
||||
AmountDiscount: values.AmountDiscount.trim(),
|
||||
StripeApiSecret: values.StripeApiSecret.trim(),
|
||||
StripeWebhookSecret: values.StripeWebhookSecret.trim(),
|
||||
StripePriceId: values.StripePriceId.trim(),
|
||||
StripeUnitPrice: values.StripeUnitPrice,
|
||||
StripeMinTopUp: values.StripeMinTopUp,
|
||||
StripePromotionCodesEnabled: values.StripePromotionCodesEnabled,
|
||||
CreemApiKey: values.CreemApiKey.trim(),
|
||||
CreemWebhookSecret: values.CreemWebhookSecret.trim(),
|
||||
CreemTestMode: values.CreemTestMode,
|
||||
CreemProducts: values.CreemProducts.trim(),
|
||||
}
|
||||
|
||||
const initial = {
|
||||
PayAddress: removeTrailingSlash(initialRef.current.PayAddress),
|
||||
EpayId: initialRef.current.EpayId.trim(),
|
||||
EpayKey: initialRef.current.EpayKey.trim(),
|
||||
Price: initialRef.current.Price,
|
||||
MinTopUp: initialRef.current.MinTopUp,
|
||||
CustomCallbackAddress: removeTrailingSlash(
|
||||
initialRef.current.CustomCallbackAddress
|
||||
),
|
||||
PayMethods: initialRef.current.PayMethods.trim(),
|
||||
AmountOptions: initialRef.current.AmountOptions.trim(),
|
||||
AmountDiscount: initialRef.current.AmountDiscount.trim(),
|
||||
StripeApiSecret: initialRef.current.StripeApiSecret.trim(),
|
||||
StripeWebhookSecret: initialRef.current.StripeWebhookSecret.trim(),
|
||||
StripePriceId: initialRef.current.StripePriceId.trim(),
|
||||
StripeUnitPrice: initialRef.current.StripeUnitPrice,
|
||||
StripeMinTopUp: initialRef.current.StripeMinTopUp,
|
||||
StripePromotionCodesEnabled:
|
||||
initialRef.current.StripePromotionCodesEnabled,
|
||||
CreemApiKey: initialRef.current.CreemApiKey.trim(),
|
||||
CreemWebhookSecret: initialRef.current.CreemWebhookSecret.trim(),
|
||||
CreemTestMode: initialRef.current.CreemTestMode,
|
||||
CreemProducts: initialRef.current.CreemProducts.trim(),
|
||||
}
|
||||
|
||||
const updates: Array<{ key: string; value: string | number }> = []
|
||||
const updates: Array<{ key: string; value: string | number | boolean }> = []
|
||||
|
||||
if (sanitized.PayAddress !== initial.PayAddress) {
|
||||
updates.push({ key: 'PayAddress', value: sanitized.PayAddress })
|
||||
}
|
||||
|
||||
if (sanitized.EpayId !== initial.EpayId) {
|
||||
updates.push({ key: 'EpayId', value: sanitized.EpayId })
|
||||
}
|
||||
|
||||
if (sanitized.EpayKey && sanitized.EpayKey !== initial.EpayKey) {
|
||||
updates.push({ key: 'EpayKey', value: sanitized.EpayKey })
|
||||
}
|
||||
|
||||
if (sanitized.Price !== initial.Price) {
|
||||
updates.push({ key: 'Price', value: sanitized.Price })
|
||||
@ -306,6 +354,13 @@ export function PaymentSettingsSection({
|
||||
updates.push({ key: 'MinTopUp', value: sanitized.MinTopUp })
|
||||
}
|
||||
|
||||
if (sanitized.CustomCallbackAddress !== initial.CustomCallbackAddress) {
|
||||
updates.push({
|
||||
key: 'CustomCallbackAddress',
|
||||
value: sanitized.CustomCallbackAddress,
|
||||
})
|
||||
}
|
||||
|
||||
if (
|
||||
normalizeJsonForComparison(sanitized.PayMethods) !==
|
||||
normalizeJsonForComparison(initial.PayMethods)
|
||||
@ -333,87 +388,6 @@ export function PaymentSettingsSection({
|
||||
})
|
||||
}
|
||||
|
||||
if (updates.length === 0) {
|
||||
return
|
||||
}
|
||||
|
||||
for (const update of updates) {
|
||||
await updateOption.mutateAsync(update)
|
||||
}
|
||||
}
|
||||
|
||||
const saveEpaySettings = async () => {
|
||||
const values = form.getValues()
|
||||
const sanitized = {
|
||||
PayAddress: removeTrailingSlash(values.PayAddress),
|
||||
EpayId: values.EpayId.trim(),
|
||||
EpayKey: values.EpayKey.trim(),
|
||||
CustomCallbackAddress: removeTrailingSlash(values.CustomCallbackAddress),
|
||||
}
|
||||
|
||||
const initial = {
|
||||
PayAddress: removeTrailingSlash(initialRef.current.PayAddress),
|
||||
EpayId: initialRef.current.EpayId.trim(),
|
||||
EpayKey: initialRef.current.EpayKey.trim(),
|
||||
CustomCallbackAddress: removeTrailingSlash(
|
||||
initialRef.current.CustomCallbackAddress
|
||||
),
|
||||
}
|
||||
|
||||
const updates: Array<{ key: string; value: string }> = []
|
||||
|
||||
if (sanitized.PayAddress !== initial.PayAddress) {
|
||||
updates.push({ key: 'PayAddress', value: sanitized.PayAddress })
|
||||
}
|
||||
|
||||
if (sanitized.EpayId !== initial.EpayId) {
|
||||
updates.push({ key: 'EpayId', value: sanitized.EpayId })
|
||||
}
|
||||
|
||||
if (sanitized.EpayKey && sanitized.EpayKey !== initial.EpayKey) {
|
||||
updates.push({ key: 'EpayKey', value: sanitized.EpayKey })
|
||||
}
|
||||
|
||||
if (sanitized.CustomCallbackAddress !== initial.CustomCallbackAddress) {
|
||||
updates.push({
|
||||
key: 'CustomCallbackAddress',
|
||||
value: sanitized.CustomCallbackAddress,
|
||||
})
|
||||
}
|
||||
|
||||
if (updates.length === 0) {
|
||||
return
|
||||
}
|
||||
|
||||
for (const update of updates) {
|
||||
await updateOption.mutateAsync(update)
|
||||
}
|
||||
}
|
||||
|
||||
const saveStripeSettings = async () => {
|
||||
const values = form.getValues()
|
||||
const sanitized = {
|
||||
StripeApiSecret: values.StripeApiSecret.trim(),
|
||||
StripeWebhookSecret: values.StripeWebhookSecret.trim(),
|
||||
StripePriceId: values.StripePriceId.trim(),
|
||||
StripeUnitPrice: values.StripeUnitPrice as number,
|
||||
StripeMinTopUp: values.StripeMinTopUp as number,
|
||||
StripePromotionCodesEnabled:
|
||||
values.StripePromotionCodesEnabled as boolean,
|
||||
}
|
||||
|
||||
const initial = {
|
||||
StripeApiSecret: initialRef.current.StripeApiSecret.trim(),
|
||||
StripeWebhookSecret: initialRef.current.StripeWebhookSecret.trim(),
|
||||
StripePriceId: initialRef.current.StripePriceId.trim(),
|
||||
StripeUnitPrice: initialRef.current.StripeUnitPrice,
|
||||
StripeMinTopUp: initialRef.current.StripeMinTopUp,
|
||||
StripePromotionCodesEnabled:
|
||||
initialRef.current.StripePromotionCodesEnabled,
|
||||
}
|
||||
|
||||
const updates: Array<{ key: string; value: string | number | boolean }> = []
|
||||
|
||||
if (
|
||||
sanitized.StripeApiSecret &&
|
||||
sanitized.StripeApiSecret !== initial.StripeApiSecret
|
||||
@ -453,33 +427,6 @@ export function PaymentSettingsSection({
|
||||
})
|
||||
}
|
||||
|
||||
if (updates.length === 0) {
|
||||
return
|
||||
}
|
||||
|
||||
for (const update of updates) {
|
||||
await updateOption.mutateAsync(update)
|
||||
}
|
||||
}
|
||||
|
||||
const saveCreemSettings = async () => {
|
||||
const values = form.getValues()
|
||||
const sanitized = {
|
||||
CreemApiKey: values.CreemApiKey.trim(),
|
||||
CreemWebhookSecret: values.CreemWebhookSecret.trim(),
|
||||
CreemTestMode: values.CreemTestMode as boolean,
|
||||
CreemProducts: values.CreemProducts.trim(),
|
||||
}
|
||||
|
||||
const initial = {
|
||||
CreemApiKey: initialRef.current.CreemApiKey.trim(),
|
||||
CreemWebhookSecret: initialRef.current.CreemWebhookSecret.trim(),
|
||||
CreemTestMode: initialRef.current.CreemTestMode,
|
||||
CreemProducts: initialRef.current.CreemProducts.trim(),
|
||||
}
|
||||
|
||||
const updates: Array<{ key: string; value: string | boolean }> = []
|
||||
|
||||
if (
|
||||
sanitized.CreemApiKey &&
|
||||
sanitized.CreemApiKey !== initial.CreemApiKey
|
||||
@ -508,162 +455,13 @@ export function PaymentSettingsSection({
|
||||
updates.push({ key: 'CreemProducts', value: sanitized.CreemProducts })
|
||||
}
|
||||
|
||||
if (updates.length === 0) {
|
||||
return
|
||||
}
|
||||
|
||||
for (const update of updates) {
|
||||
await updateOption.mutateAsync(update)
|
||||
}
|
||||
}
|
||||
|
||||
const onSubmit = async (values: PaymentFormValues) => {
|
||||
const sanitized = {
|
||||
PayAddress: removeTrailingSlash(values.PayAddress),
|
||||
EpayId: values.EpayId.trim(),
|
||||
EpayKey: values.EpayKey.trim(),
|
||||
Price: values.Price,
|
||||
MinTopUp: values.MinTopUp,
|
||||
CustomCallbackAddress: removeTrailingSlash(values.CustomCallbackAddress),
|
||||
PayMethods: values.PayMethods.trim(),
|
||||
AmountOptions: values.AmountOptions.trim(),
|
||||
AmountDiscount: values.AmountDiscount.trim(),
|
||||
StripeApiSecret: values.StripeApiSecret.trim(),
|
||||
StripeWebhookSecret: values.StripeWebhookSecret.trim(),
|
||||
StripePriceId: values.StripePriceId.trim(),
|
||||
StripeUnitPrice: values.StripeUnitPrice,
|
||||
StripeMinTopUp: values.StripeMinTopUp,
|
||||
StripePromotionCodesEnabled: values.StripePromotionCodesEnabled,
|
||||
}
|
||||
|
||||
const initial = {
|
||||
PayAddress: removeTrailingSlash(initialRef.current.PayAddress),
|
||||
EpayId: initialRef.current.EpayId.trim(),
|
||||
EpayKey: initialRef.current.EpayKey.trim(),
|
||||
Price: initialRef.current.Price,
|
||||
MinTopUp: initialRef.current.MinTopUp,
|
||||
CustomCallbackAddress: removeTrailingSlash(
|
||||
initialRef.current.CustomCallbackAddress
|
||||
),
|
||||
PayMethods: initialRef.current.PayMethods.trim(),
|
||||
AmountOptions: initialRef.current.AmountOptions.trim(),
|
||||
AmountDiscount: initialRef.current.AmountDiscount.trim(),
|
||||
StripeApiSecret: initialRef.current.StripeApiSecret.trim(),
|
||||
StripeWebhookSecret: initialRef.current.StripeWebhookSecret.trim(),
|
||||
StripePriceId: initialRef.current.StripePriceId.trim(),
|
||||
StripeUnitPrice: initialRef.current.StripeUnitPrice,
|
||||
StripeMinTopUp: initialRef.current.StripeMinTopUp,
|
||||
StripePromotionCodesEnabled:
|
||||
initialRef.current.StripePromotionCodesEnabled,
|
||||
}
|
||||
|
||||
const updates: Array<{ key: string; value: string | number | boolean }> = []
|
||||
|
||||
if (sanitized.PayAddress !== initial.PayAddress) {
|
||||
updates.push({ key: 'PayAddress', value: sanitized.PayAddress })
|
||||
}
|
||||
|
||||
if (sanitized.EpayId !== initial.EpayId) {
|
||||
updates.push({ key: 'EpayId', value: sanitized.EpayId })
|
||||
}
|
||||
|
||||
if (sanitized.EpayKey && sanitized.EpayKey !== initial.EpayKey) {
|
||||
updates.push({ key: 'EpayKey', value: sanitized.EpayKey })
|
||||
}
|
||||
|
||||
if (sanitized.Price !== initial.Price) {
|
||||
updates.push({ key: 'Price', value: sanitized.Price })
|
||||
}
|
||||
|
||||
if (sanitized.MinTopUp !== initial.MinTopUp) {
|
||||
updates.push({ key: 'MinTopUp', value: sanitized.MinTopUp })
|
||||
}
|
||||
|
||||
if (sanitized.CustomCallbackAddress !== initial.CustomCallbackAddress) {
|
||||
updates.push({
|
||||
key: 'CustomCallbackAddress',
|
||||
value: sanitized.CustomCallbackAddress,
|
||||
})
|
||||
}
|
||||
|
||||
if (
|
||||
normalizeJsonForComparison(sanitized.PayMethods) !==
|
||||
normalizeJsonForComparison(initial.PayMethods)
|
||||
) {
|
||||
updates.push({ key: 'PayMethods', value: sanitized.PayMethods })
|
||||
}
|
||||
|
||||
if (
|
||||
normalizeJsonForComparison(sanitized.AmountOptions) !==
|
||||
normalizeJsonForComparison(initial.AmountOptions)
|
||||
) {
|
||||
updates.push({
|
||||
key: 'payment_setting.amount_options',
|
||||
value: sanitized.AmountOptions,
|
||||
})
|
||||
}
|
||||
|
||||
if (
|
||||
normalizeJsonForComparison(sanitized.AmountDiscount) !==
|
||||
normalizeJsonForComparison(initial.AmountDiscount)
|
||||
) {
|
||||
updates.push({
|
||||
key: 'payment_setting.amount_discount',
|
||||
value: sanitized.AmountDiscount,
|
||||
})
|
||||
}
|
||||
|
||||
if (
|
||||
sanitized.StripeApiSecret &&
|
||||
sanitized.StripeApiSecret !== initial.StripeApiSecret
|
||||
) {
|
||||
updates.push({ key: 'StripeApiSecret', value: sanitized.StripeApiSecret })
|
||||
}
|
||||
|
||||
if (
|
||||
sanitized.StripeWebhookSecret &&
|
||||
sanitized.StripeWebhookSecret !== initial.StripeWebhookSecret
|
||||
) {
|
||||
updates.push({
|
||||
key: 'StripeWebhookSecret',
|
||||
value: sanitized.StripeWebhookSecret,
|
||||
})
|
||||
}
|
||||
|
||||
if (sanitized.StripePriceId !== initial.StripePriceId) {
|
||||
updates.push({ key: 'StripePriceId', value: sanitized.StripePriceId })
|
||||
}
|
||||
|
||||
if (sanitized.StripeUnitPrice !== initial.StripeUnitPrice) {
|
||||
updates.push({ key: 'StripeUnitPrice', value: sanitized.StripeUnitPrice })
|
||||
}
|
||||
|
||||
if (sanitized.StripeMinTopUp !== initial.StripeMinTopUp) {
|
||||
updates.push({ key: 'StripeMinTopUp', value: sanitized.StripeMinTopUp })
|
||||
}
|
||||
|
||||
if (
|
||||
sanitized.StripePromotionCodesEnabled !==
|
||||
initial.StripePromotionCodesEnabled
|
||||
) {
|
||||
updates.push({
|
||||
key: 'StripePromotionCodesEnabled',
|
||||
value: sanitized.StripePromotionCodesEnabled,
|
||||
})
|
||||
}
|
||||
|
||||
for (const update of updates) {
|
||||
await updateOption.mutateAsync(update)
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<SettingsSection
|
||||
title={t('Payment Gateway')}
|
||||
description={t(
|
||||
'Configure recharge pricing and payment gateway integrations'
|
||||
)}
|
||||
>
|
||||
<SettingsSection title={t('Payment Gateway')}>
|
||||
{!complianceConfirmed ? (
|
||||
<Alert variant='destructive' className='mb-6'>
|
||||
<ShieldAlert className='h-4 w-4' />
|
||||
@ -729,14 +527,19 @@ export function PaymentSettingsSection({
|
||||
|
||||
{/* eslint-disable react-hooks/refs */}
|
||||
<Form {...form}>
|
||||
<form
|
||||
<SettingsForm
|
||||
onSubmit={form.handleSubmit(onSubmit)}
|
||||
className={cn(
|
||||
'space-y-8',
|
||||
'gap-y-8',
|
||||
!complianceConfirmed && 'pointer-events-none opacity-40'
|
||||
)}
|
||||
data-no-autosubmit='true'
|
||||
>
|
||||
<SettingsPageFormActions
|
||||
onSave={form.handleSubmit(onSubmit)}
|
||||
isSaving={updateOption.isPending}
|
||||
saveLabel='Save all settings'
|
||||
/>
|
||||
<div className='space-y-4'>
|
||||
<div>
|
||||
<h3 className='text-lg font-medium'>{t('General Settings')}</h3>
|
||||
@ -964,20 +767,6 @@ export function PaymentSettingsSection({
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<Button
|
||||
type='button'
|
||||
onClick={(e) => {
|
||||
e.preventDefault()
|
||||
e.stopPropagation()
|
||||
saveGeneralSettings()
|
||||
}}
|
||||
disabled={updateOption.isPending}
|
||||
>
|
||||
{updateOption.isPending
|
||||
? t('Saving...')
|
||||
: t('Save general settings')}
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
<Separator />
|
||||
@ -1079,20 +868,6 @@ export function PaymentSettingsSection({
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<Button
|
||||
type='button'
|
||||
onClick={(e) => {
|
||||
e.preventDefault()
|
||||
e.stopPropagation()
|
||||
saveEpaySettings()
|
||||
}}
|
||||
disabled={updateOption.isPending}
|
||||
>
|
||||
{updateOption.isPending
|
||||
? t('Saving...')
|
||||
: t('Save Epay settings')}
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
<Separator />
|
||||
@ -1266,39 +1041,23 @@ export function PaymentSettingsSection({
|
||||
control={form.control}
|
||||
name='StripePromotionCodesEnabled'
|
||||
render={({ field }) => (
|
||||
<FormItem className='flex flex-row items-center justify-between rounded-lg border p-4'>
|
||||
<div className='space-y-0.5'>
|
||||
<FormLabel className='text-base'>
|
||||
{t('Promotion codes')}
|
||||
</FormLabel>
|
||||
<SettingsSwitchItem>
|
||||
<SettingsSwitchContent>
|
||||
<FormLabel>{t('Promotion codes')}</FormLabel>
|
||||
<FormDescription>
|
||||
{t('Allow users to enter promo codes')}
|
||||
</FormDescription>
|
||||
</div>
|
||||
</SettingsSwitchContent>
|
||||
<FormControl>
|
||||
<Switch
|
||||
checked={field.value}
|
||||
onCheckedChange={field.onChange}
|
||||
/>
|
||||
</FormControl>
|
||||
</FormItem>
|
||||
</SettingsSwitchItem>
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<Button
|
||||
type='button'
|
||||
onClick={(e) => {
|
||||
e.preventDefault()
|
||||
e.stopPropagation()
|
||||
saveStripeSettings()
|
||||
}}
|
||||
disabled={updateOption.isPending}
|
||||
>
|
||||
{updateOption.isPending
|
||||
? t('Saving...')
|
||||
: t('Save Stripe settings')}
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
<Separator />
|
||||
@ -1378,22 +1137,20 @@ export function PaymentSettingsSection({
|
||||
control={form.control}
|
||||
name='CreemTestMode'
|
||||
render={({ field }) => (
|
||||
<FormItem className='flex flex-row items-center justify-between rounded-lg border p-4'>
|
||||
<div className='space-y-0.5'>
|
||||
<FormLabel className='text-base'>
|
||||
{t('Test Mode')}
|
||||
</FormLabel>
|
||||
<SettingsSwitchItem>
|
||||
<SettingsSwitchContent>
|
||||
<FormLabel>{t('Test Mode')}</FormLabel>
|
||||
<FormDescription>
|
||||
{t('Enable test mode for Creem payments')}
|
||||
</FormDescription>
|
||||
</div>
|
||||
</SettingsSwitchContent>
|
||||
<FormControl>
|
||||
<Switch
|
||||
checked={field.value}
|
||||
onCheckedChange={field.onChange}
|
||||
/>
|
||||
</FormControl>
|
||||
</FormItem>
|
||||
</SettingsSwitchItem>
|
||||
)}
|
||||
/>
|
||||
|
||||
@ -1448,26 +1205,8 @@ export function PaymentSettingsSection({
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
|
||||
<Button
|
||||
type='button'
|
||||
onClick={(e) => {
|
||||
e.preventDefault()
|
||||
e.stopPropagation()
|
||||
saveCreemSettings()
|
||||
}}
|
||||
disabled={updateOption.isPending}
|
||||
>
|
||||
{updateOption.isPending
|
||||
? t('Saving...')
|
||||
: t('Save Creem settings')}
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
<Button type='submit' disabled={updateOption.isPending}>
|
||||
{updateOption.isPending ? t('Saving...') : t('Save all settings')}
|
||||
</Button>
|
||||
</form>
|
||||
</SettingsForm>
|
||||
</Form>
|
||||
|
||||
<Separator />
|
||||
|
||||
@ -41,6 +41,8 @@ import {
|
||||
SelectValue,
|
||||
} from '@/components/ui/select'
|
||||
import { Textarea } from '@/components/ui/textarea'
|
||||
import { SettingsForm } from '../components/settings-form-layout'
|
||||
import { SettingsPageActionsPortal } from '../components/settings-page-context'
|
||||
import { removeTrailingSlash } from './utils'
|
||||
import {
|
||||
type CatalogStore,
|
||||
@ -451,6 +453,16 @@ export function WaffoPancakeSettingsSection(props: Props) {
|
||||
|
||||
return (
|
||||
<div className='space-y-4 pt-4'>
|
||||
<SettingsPageActionsPortal>
|
||||
<Button
|
||||
type='button'
|
||||
size='sm'
|
||||
onClick={handleSave}
|
||||
disabled={saving || !chosenStoreID || !chosenProductID}
|
||||
>
|
||||
{saving ? t('Saving...') : t('Save Waffo Pancake settings')}
|
||||
</Button>
|
||||
</SettingsPageActionsPortal>
|
||||
<div>
|
||||
<h3 className='text-lg font-medium'>{t('Waffo Pancake MoR')}</h3>
|
||||
<p className='text-muted-foreground text-sm'>
|
||||
@ -460,9 +472,9 @@ export function WaffoPancakeSettingsSection(props: Props) {
|
||||
</p>
|
||||
</div>
|
||||
<Form {...form}>
|
||||
<form
|
||||
<SettingsForm
|
||||
onSubmit={(e) => e.preventDefault()}
|
||||
className='space-y-4'
|
||||
className='gap-y-4'
|
||||
data-no-autosubmit='true'
|
||||
>
|
||||
{/* Blue box — webhook configuration only. */}
|
||||
@ -685,13 +697,6 @@ export function WaffoPancakeSettingsSection(props: Props) {
|
||||
) : null}
|
||||
|
||||
<div className='flex items-center gap-3'>
|
||||
<Button
|
||||
type='button'
|
||||
onClick={handleSave}
|
||||
disabled={saving || !chosenStoreID || !chosenProductID}
|
||||
>
|
||||
{saving ? t('Saving...') : t('Save Waffo Pancake settings')}
|
||||
</Button>
|
||||
{storeID || productID ? (
|
||||
<div className='text-muted-foreground flex flex-wrap gap-x-3 gap-y-1 text-xs'>
|
||||
{storeID ? (
|
||||
@ -714,7 +719,7 @@ export function WaffoPancakeSettingsSection(props: Props) {
|
||||
) : null}
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</SettingsForm>
|
||||
</Form>
|
||||
</div>
|
||||
)
|
||||
|
||||
@ -33,7 +33,6 @@ import {
|
||||
import { Input } from '@/components/ui/input'
|
||||
import { Label } from '@/components/ui/label'
|
||||
import { Separator } from '@/components/ui/separator'
|
||||
import { Switch } from '@/components/ui/switch'
|
||||
import {
|
||||
Table,
|
||||
TableBody,
|
||||
@ -43,6 +42,8 @@ import {
|
||||
TableRow,
|
||||
} from '@/components/ui/table'
|
||||
import { Textarea } from '@/components/ui/textarea'
|
||||
import { SettingsSwitchField } from '../components/settings-form-layout'
|
||||
import { SettingsPageActionsPortal } from '../components/settings-page-context'
|
||||
import { useUpdateOption } from '../hooks/use-update-option'
|
||||
|
||||
export interface WaffoSettingsValues {
|
||||
@ -212,6 +213,16 @@ export function WaffoSettingsSection(props: Props) {
|
||||
return (
|
||||
<>
|
||||
<div className='space-y-4 pt-4'>
|
||||
<SettingsPageActionsPortal>
|
||||
<Button
|
||||
type='button'
|
||||
size='sm'
|
||||
onClick={handleSave}
|
||||
disabled={loading}
|
||||
>
|
||||
{loading ? t('Saving...') : t('Save Waffo settings')}
|
||||
</Button>
|
||||
</SettingsPageActionsPortal>
|
||||
<div>
|
||||
<h3 className='text-lg font-medium'>
|
||||
{t('Waffo Aggregator Gateway')}
|
||||
@ -230,21 +241,19 @@ export function WaffoSettingsSection(props: Props) {
|
||||
</AlertDescription>
|
||||
</Alert>
|
||||
|
||||
<div className='grid grid-cols-2 gap-4'>
|
||||
<div className='flex items-center gap-2'>
|
||||
<Switch
|
||||
checked={form.watch('WaffoEnabled')}
|
||||
onCheckedChange={(v) => form.setValue('WaffoEnabled', v)}
|
||||
/>
|
||||
<Label>{t('Enable Waffo')}</Label>
|
||||
</div>
|
||||
<div className='flex items-center gap-2'>
|
||||
<Switch
|
||||
checked={form.watch('WaffoSandbox')}
|
||||
onCheckedChange={(v) => form.setValue('WaffoSandbox', v)}
|
||||
/>
|
||||
<Label>{t('Sandbox mode')}</Label>
|
||||
</div>
|
||||
<div className='grid gap-4 sm:grid-cols-2'>
|
||||
<SettingsSwitchField
|
||||
checked={form.watch('WaffoEnabled')}
|
||||
onCheckedChange={(v) => form.setValue('WaffoEnabled', v)}
|
||||
label={t('Enable Waffo')}
|
||||
className='border-b-0 py-0'
|
||||
/>
|
||||
<SettingsSwitchField
|
||||
checked={form.watch('WaffoSandbox')}
|
||||
onCheckedChange={(v) => form.setValue('WaffoSandbox', v)}
|
||||
label={t('Sandbox mode')}
|
||||
className='border-b-0 py-0'
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className='grid grid-cols-2 gap-4'>
|
||||
@ -416,10 +425,6 @@ export function WaffoSettingsSection(props: Props) {
|
||||
</TableBody>
|
||||
</Table>
|
||||
</div>
|
||||
|
||||
<Button onClick={handleSave} disabled={loading}>
|
||||
{loading ? t('Saving...') : t('Save Changes')}
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
<Dialog open={methodDialogOpen} onOpenChange={setMethodDialogOpen}>
|
||||
|
||||
@ -20,7 +20,6 @@ import * as z from 'zod'
|
||||
import { useForm } from 'react-hook-form'
|
||||
import { zodResolver } from '@hookform/resolvers/zod'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { Button } from '@/components/ui/button'
|
||||
import {
|
||||
Form,
|
||||
FormControl,
|
||||
@ -32,6 +31,12 @@ import {
|
||||
} from '@/components/ui/form'
|
||||
import { Input } from '@/components/ui/input'
|
||||
import { Switch } from '@/components/ui/switch'
|
||||
import {
|
||||
SettingsForm,
|
||||
SettingsSwitchContent,
|
||||
SettingsSwitchItem,
|
||||
} from '../components/settings-form-layout'
|
||||
import { SettingsPageFormActions } from '../components/settings-page-context'
|
||||
import { SettingsSection } from '../components/settings-section'
|
||||
import { useResetForm } from '../hooks/use-reset-form'
|
||||
import { useUpdateOption } from '../hooks/use-update-option'
|
||||
@ -100,18 +105,14 @@ export function WorkerSettingsSection({
|
||||
}
|
||||
|
||||
return (
|
||||
<SettingsSection
|
||||
title={t('Worker Proxy')}
|
||||
description={t(
|
||||
'Configure upstream worker or proxy service for outbound requests'
|
||||
)}
|
||||
>
|
||||
<SettingsSection title={t('Worker Proxy')}>
|
||||
<Form {...form}>
|
||||
<form
|
||||
onSubmit={form.handleSubmit(onSubmit)}
|
||||
autoComplete='off'
|
||||
className='space-y-6'
|
||||
>
|
||||
<SettingsForm onSubmit={form.handleSubmit(onSubmit)} autoComplete='off'>
|
||||
<SettingsPageFormActions
|
||||
onSave={form.handleSubmit(onSubmit)}
|
||||
isSaving={updateOption.isPending}
|
||||
saveLabel='Save Worker settings'
|
||||
/>
|
||||
<FormField
|
||||
control={form.control}
|
||||
name='WorkerUrl'
|
||||
@ -167,33 +168,25 @@ export function WorkerSettingsSection({
|
||||
control={form.control}
|
||||
name='WorkerAllowHttpImageRequestEnabled'
|
||||
render={({ field }) => (
|
||||
<FormItem className='flex flex-row items-center justify-between rounded-lg border p-4'>
|
||||
<div className='space-y-0.5'>
|
||||
<FormLabel className='text-base'>
|
||||
{t('Allow HTTP image requests')}
|
||||
</FormLabel>
|
||||
<SettingsSwitchItem>
|
||||
<SettingsSwitchContent>
|
||||
<FormLabel>{t('Allow HTTP image requests')}</FormLabel>
|
||||
<FormDescription>
|
||||
{t(
|
||||
'Enable when proxying workers that fetch images over HTTP.'
|
||||
)}
|
||||
</FormDescription>
|
||||
</div>
|
||||
</SettingsSwitchContent>
|
||||
<FormControl>
|
||||
<Switch
|
||||
checked={field.value}
|
||||
onCheckedChange={field.onChange}
|
||||
/>
|
||||
</FormControl>
|
||||
</FormItem>
|
||||
</SettingsSwitchItem>
|
||||
)}
|
||||
/>
|
||||
|
||||
<Button type='submit' disabled={updateOption.isPending}>
|
||||
{updateOption.isPending
|
||||
? t('Saving...')
|
||||
: t('Save Worker settings')}
|
||||
</Button>
|
||||
</form>
|
||||
</SettingsForm>
|
||||
</Form>
|
||||
</SettingsSection>
|
||||
)
|
||||
|
||||
@ -21,17 +21,23 @@ import * as z from 'zod'
|
||||
import { useForm } from 'react-hook-form'
|
||||
import { zodResolver } from '@hookform/resolvers/zod'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { Button } from '@/components/ui/button'
|
||||
import {
|
||||
Form,
|
||||
FormControl,
|
||||
FormDescription,
|
||||
FormField,
|
||||
FormItem,
|
||||
FormLabel,
|
||||
FormMessage,
|
||||
} from '@/components/ui/form'
|
||||
import { Switch } from '@/components/ui/switch'
|
||||
import {
|
||||
SettingsControlChildren,
|
||||
SettingsForm,
|
||||
SettingsSwitchContent,
|
||||
SettingsControlGroup,
|
||||
SettingsSwitchItem,
|
||||
} from '../components/settings-form-layout'
|
||||
import { SettingsPageFormActions } from '../components/settings-page-context'
|
||||
import { SettingsSection } from '../components/settings-section'
|
||||
import { useUpdateOption } from '../hooks/use-update-option'
|
||||
import {
|
||||
@ -201,12 +207,16 @@ export function HeaderNavigationSection({
|
||||
]
|
||||
|
||||
return (
|
||||
<SettingsSection
|
||||
title={t('Header navigation')}
|
||||
description={t('Enable or disable top navigation modules globally.')}
|
||||
>
|
||||
<SettingsSection title={t('Header navigation')}>
|
||||
<Form {...form}>
|
||||
<form onSubmit={form.handleSubmit(onSubmit)} className='space-y-6'>
|
||||
<SettingsForm onSubmit={form.handleSubmit(onSubmit)}>
|
||||
<SettingsPageFormActions
|
||||
onSave={form.handleSubmit(onSubmit)}
|
||||
onReset={resetToDefault}
|
||||
isSaving={updateOption.isPending}
|
||||
resetLabel='Reset to default'
|
||||
saveLabel='Save navigation'
|
||||
/>
|
||||
<div className='grid gap-4 md:grid-cols-2'>
|
||||
{simpleModules.map((module) => (
|
||||
<FormField
|
||||
@ -214,13 +224,11 @@ export function HeaderNavigationSection({
|
||||
control={form.control}
|
||||
name={module.key}
|
||||
render={({ field }) => (
|
||||
<FormItem className='flex flex-row items-start justify-between rounded-lg border p-4'>
|
||||
<div className='space-y-0.5 pe-4'>
|
||||
<FormLabel className='text-base'>
|
||||
{module.title}
|
||||
</FormLabel>
|
||||
<SettingsSwitchItem>
|
||||
<SettingsSwitchContent>
|
||||
<FormLabel>{module.title}</FormLabel>
|
||||
<FormDescription>{module.description}</FormDescription>
|
||||
</div>
|
||||
</SettingsSwitchContent>
|
||||
<FormControl>
|
||||
<Switch
|
||||
checked={field.value}
|
||||
@ -228,7 +236,7 @@ export function HeaderNavigationSection({
|
||||
/>
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
</SettingsSwitchItem>
|
||||
)}
|
||||
/>
|
||||
))}
|
||||
@ -236,18 +244,16 @@ export function HeaderNavigationSection({
|
||||
|
||||
<div className='grid gap-4 lg:grid-cols-2'>
|
||||
{accessModules.map((module) => (
|
||||
<div key={module.enabledKey} className='rounded-lg border p-4'>
|
||||
<SettingsControlGroup key={module.enabledKey}>
|
||||
<FormField
|
||||
control={form.control}
|
||||
name={module.enabledKey}
|
||||
render={({ field }) => (
|
||||
<FormItem className='flex flex-row items-start justify-between rounded-lg border p-4'>
|
||||
<div className='space-y-0.5 pe-4'>
|
||||
<FormLabel className='text-base'>
|
||||
{module.title}
|
||||
</FormLabel>
|
||||
<SettingsSwitchItem>
|
||||
<SettingsSwitchContent>
|
||||
<FormLabel>{module.title}</FormLabel>
|
||||
<FormDescription>{module.description}</FormDescription>
|
||||
</div>
|
||||
</SettingsSwitchContent>
|
||||
<FormControl>
|
||||
<Switch
|
||||
checked={field.value}
|
||||
@ -255,7 +261,7 @@ export function HeaderNavigationSection({
|
||||
/>
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
</SettingsSwitchItem>
|
||||
)}
|
||||
/>
|
||||
|
||||
@ -263,39 +269,30 @@ export function HeaderNavigationSection({
|
||||
control={form.control}
|
||||
name={module.requireAuthKey}
|
||||
render={({ field }) => (
|
||||
<FormItem className='mt-4 flex flex-row items-start justify-between rounded-lg border border-dashed p-4'>
|
||||
<div className='space-y-0.5 pe-4'>
|
||||
<FormLabel className='text-base'>
|
||||
{module.requireAuthTitle}
|
||||
</FormLabel>
|
||||
<FormDescription>
|
||||
{module.requireAuthDescription}
|
||||
</FormDescription>
|
||||
</div>
|
||||
<FormControl>
|
||||
<Switch
|
||||
checked={field.value}
|
||||
onCheckedChange={field.onChange}
|
||||
disabled={!form.watch(module.requireAuthDependsOn)}
|
||||
/>
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
<SettingsControlChildren>
|
||||
<SettingsSwitchItem className='border-b-0 py-2'>
|
||||
<SettingsSwitchContent>
|
||||
<FormLabel>{module.requireAuthTitle}</FormLabel>
|
||||
<FormDescription>
|
||||
{module.requireAuthDescription}
|
||||
</FormDescription>
|
||||
</SettingsSwitchContent>
|
||||
<FormControl>
|
||||
<Switch
|
||||
checked={field.value}
|
||||
onCheckedChange={field.onChange}
|
||||
disabled={!form.watch(module.requireAuthDependsOn)}
|
||||
/>
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
</SettingsSwitchItem>
|
||||
</SettingsControlChildren>
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
</SettingsControlGroup>
|
||||
))}
|
||||
</div>
|
||||
|
||||
<div className='flex flex-wrap gap-3'>
|
||||
<Button type='button' variant='outline' onClick={resetToDefault}>
|
||||
{t('Reset to default')}
|
||||
</Button>
|
||||
<Button type='submit' disabled={updateOption.isPending}>
|
||||
{updateOption.isPending ? t('Saving...') : t('Save navigation')}
|
||||
</Button>
|
||||
</div>
|
||||
</form>
|
||||
</SettingsForm>
|
||||
</Form>
|
||||
</SettingsSection>
|
||||
)
|
||||
|
||||
@ -39,13 +39,19 @@ import {
|
||||
FormControl,
|
||||
FormDescription,
|
||||
FormField,
|
||||
FormItem,
|
||||
FormLabel,
|
||||
FormMessage,
|
||||
} from '@/components/ui/form'
|
||||
import { Switch } from '@/components/ui/switch'
|
||||
import { DateTimePicker } from '@/components/datetime-picker'
|
||||
import { deleteLogsBefore } from '../api'
|
||||
import {
|
||||
SettingsControlGroup,
|
||||
SettingsForm,
|
||||
SettingsSwitchContent,
|
||||
SettingsSwitchItem,
|
||||
} from '../components/settings-form-layout'
|
||||
import { SettingsPageFormActions } from '../components/settings-page-context'
|
||||
import { SettingsSection } from '../components/settings-section'
|
||||
import { useUpdateOption } from '../hooks/use-update-option'
|
||||
|
||||
@ -161,27 +167,27 @@ export function LogSettingsSection({
|
||||
}
|
||||
|
||||
return (
|
||||
<SettingsSection
|
||||
title={t('Log Maintenance')}
|
||||
description={t('Control log retention and clean historical data.')}
|
||||
>
|
||||
<SettingsSection title={t('Log Maintenance')}>
|
||||
<Form {...form}>
|
||||
<form onSubmit={form.handleSubmit(onSubmit)} className='space-y-6'>
|
||||
<SettingsForm onSubmit={form.handleSubmit(onSubmit)}>
|
||||
<SettingsPageFormActions
|
||||
onSave={form.handleSubmit(onSubmit)}
|
||||
isSaving={updateOption.isPending}
|
||||
saveLabel='Save log settings'
|
||||
/>
|
||||
<FormField
|
||||
control={form.control}
|
||||
name='LogConsumeEnabled'
|
||||
render={({ field }) => (
|
||||
<FormItem className='flex flex-row items-start justify-between rounded-lg border p-4'>
|
||||
<div className='space-y-0.5 pe-4'>
|
||||
<FormLabel className='text-base'>
|
||||
{t('Record quota usage')}
|
||||
</FormLabel>
|
||||
<SettingsSwitchItem>
|
||||
<SettingsSwitchContent>
|
||||
<FormLabel>{t('Record quota usage')}</FormLabel>
|
||||
<FormDescription>
|
||||
{t(
|
||||
'Track per-request consumption to power usage analytics. Keeping this on increases database writes.'
|
||||
)}
|
||||
</FormDescription>
|
||||
</div>
|
||||
</SettingsSwitchContent>
|
||||
<FormControl>
|
||||
<Switch
|
||||
checked={field.value}
|
||||
@ -189,11 +195,11 @@ export function LogSettingsSection({
|
||||
/>
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
</SettingsSwitchItem>
|
||||
)}
|
||||
/>
|
||||
|
||||
<div className='space-y-4 rounded-lg border p-4'>
|
||||
<SettingsControlGroup className='space-y-3'>
|
||||
<div>
|
||||
<h4 className='text-sm font-medium'>{t('Clean history logs')}</h4>
|
||||
<p className='text-muted-foreground text-sm'>
|
||||
@ -223,12 +229,8 @@ export function LogSettingsSection({
|
||||
{isCleaning ? t('Cleaning...') : t('Clean logs')}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<Button type='submit' disabled={updateOption.isPending}>
|
||||
{updateOption.isPending ? t('Saving...') : t('Save log settings')}
|
||||
</Button>
|
||||
</form>
|
||||
</SettingsControlGroup>
|
||||
</SettingsForm>
|
||||
</Form>
|
||||
<AlertDialog open={showConfirmDialog} onOpenChange={setShowConfirmDialog}>
|
||||
<AlertDialogContent>
|
||||
|
||||
@ -21,7 +21,6 @@ import * as z from 'zod'
|
||||
import { useForm } from 'react-hook-form'
|
||||
import { zodResolver } from '@hookform/resolvers/zod'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { Button } from '@/components/ui/button'
|
||||
import {
|
||||
Form,
|
||||
FormControl,
|
||||
@ -31,6 +30,8 @@ import {
|
||||
FormMessage,
|
||||
} from '@/components/ui/form'
|
||||
import { Textarea } from '@/components/ui/textarea'
|
||||
import { SettingsForm } from '../components/settings-form-layout'
|
||||
import { SettingsPageFormActions } from '../components/settings-page-context'
|
||||
import { SettingsSection } from '../components/settings-section'
|
||||
import { useUpdateOption } from '../hooks/use-update-option'
|
||||
|
||||
@ -70,14 +71,14 @@ export function NoticeSection({ defaultValue }: NoticeSectionProps) {
|
||||
}
|
||||
|
||||
return (
|
||||
<SettingsSection
|
||||
title={t('System Notice')}
|
||||
description={t(
|
||||
'Broadcast a global banner to users. Markdown is supported.'
|
||||
)}
|
||||
>
|
||||
<SettingsSection title={t('System Notice')}>
|
||||
<Form {...form}>
|
||||
<form onSubmit={form.handleSubmit(onSubmit)} className='space-y-6'>
|
||||
<SettingsForm onSubmit={form.handleSubmit(onSubmit)}>
|
||||
<SettingsPageFormActions
|
||||
onSave={form.handleSubmit(onSubmit)}
|
||||
isSaving={updateOption.isPending}
|
||||
saveLabel='Save notice'
|
||||
/>
|
||||
<FormField
|
||||
control={form.control}
|
||||
name='Notice'
|
||||
@ -97,11 +98,7 @@ export function NoticeSection({ defaultValue }: NoticeSectionProps) {
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
|
||||
<Button type='submit' disabled={updateOption.isPending}>
|
||||
{updateOption.isPending ? t('Saving...') : t('Save notice')}
|
||||
</Button>
|
||||
</form>
|
||||
</SettingsForm>
|
||||
</Form>
|
||||
</SettingsSection>
|
||||
)
|
||||
|
||||
@ -59,6 +59,12 @@ import {
|
||||
import { Separator } from '@/components/ui/separator'
|
||||
import { Switch } from '@/components/ui/switch'
|
||||
import { StatusBadge } from '@/components/status-badge'
|
||||
import {
|
||||
SettingsForm,
|
||||
SettingsSwitchContent,
|
||||
SettingsSwitchItem,
|
||||
} from '../components/settings-form-layout'
|
||||
import { SettingsPageFormActions } from '../components/settings-page-context'
|
||||
import { SettingsSection } from '../components/settings-section'
|
||||
import { useResetForm } from '../hooks/use-reset-form'
|
||||
import { useUpdateOption } from '../hooks/use-update-option'
|
||||
@ -294,14 +300,13 @@ export function PerformanceSection(props: Props) {
|
||||
: 0
|
||||
|
||||
return (
|
||||
<SettingsSection
|
||||
title={t('Performance Settings')}
|
||||
description={t(
|
||||
'Disk cache, system performance monitoring, and operation statistics'
|
||||
)}
|
||||
>
|
||||
<SettingsSection title={t('Performance Settings')}>
|
||||
<Form {...form}>
|
||||
<form onSubmit={form.handleSubmit(onSubmit)} className='space-y-6'>
|
||||
<SettingsForm onSubmit={form.handleSubmit(onSubmit)}>
|
||||
<SettingsPageFormActions
|
||||
onSave={form.handleSubmit(onSubmit)}
|
||||
isSaving={updateOption.isPending}
|
||||
/>
|
||||
{/* Disk Cache Settings */}
|
||||
<div>
|
||||
<h4 className='font-medium'>{t('Disk Cache Settings')}</h4>
|
||||
@ -317,15 +322,17 @@ export function PerformanceSection(props: Props) {
|
||||
control={form.control}
|
||||
name='performance_setting.disk_cache_enabled'
|
||||
render={({ field }) => (
|
||||
<FormItem className='flex items-center gap-2'>
|
||||
<SettingsSwitchItem>
|
||||
<SettingsSwitchContent>
|
||||
<FormLabel>{t('Enable Disk Cache')}</FormLabel>
|
||||
</SettingsSwitchContent>
|
||||
<FormControl>
|
||||
<Switch
|
||||
checked={field.value}
|
||||
onCheckedChange={field.onChange}
|
||||
/>
|
||||
</FormControl>
|
||||
<FormLabel>{t('Enable Disk Cache')}</FormLabel>
|
||||
</FormItem>
|
||||
</SettingsSwitchItem>
|
||||
)}
|
||||
/>
|
||||
<FormField
|
||||
@ -415,15 +422,17 @@ export function PerformanceSection(props: Props) {
|
||||
control={form.control}
|
||||
name='performance_setting.monitor_enabled'
|
||||
render={({ field }) => (
|
||||
<FormItem className='flex items-center gap-2'>
|
||||
<SettingsSwitchItem>
|
||||
<SettingsSwitchContent>
|
||||
<FormLabel>{t('Enable Performance Monitoring')}</FormLabel>
|
||||
</SettingsSwitchContent>
|
||||
<FormControl>
|
||||
<Switch
|
||||
checked={field.value}
|
||||
onCheckedChange={field.onChange}
|
||||
/>
|
||||
</FormControl>
|
||||
<FormLabel>{t('Enable Performance Monitoring')}</FormLabel>
|
||||
</FormItem>
|
||||
</SettingsSwitchItem>
|
||||
)}
|
||||
/>
|
||||
<FormField
|
||||
@ -492,15 +501,19 @@ export function PerformanceSection(props: Props) {
|
||||
control={form.control}
|
||||
name='perf_metrics_setting.enabled'
|
||||
render={({ field }) => (
|
||||
<FormItem className='flex items-center gap-2'>
|
||||
<SettingsSwitchItem>
|
||||
<SettingsSwitchContent>
|
||||
<FormLabel>
|
||||
{t('Enable model performance metrics')}
|
||||
</FormLabel>
|
||||
</SettingsSwitchContent>
|
||||
<FormControl>
|
||||
<Switch
|
||||
checked={field.value}
|
||||
onCheckedChange={field.onChange}
|
||||
/>
|
||||
</FormControl>
|
||||
<FormLabel>{t('Enable model performance metrics')}</FormLabel>
|
||||
</FormItem>
|
||||
</SettingsSwitchItem>
|
||||
)}
|
||||
/>
|
||||
<FormField
|
||||
@ -573,11 +586,7 @@ export function PerformanceSection(props: Props) {
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<Button type='submit' disabled={updateOption.isPending}>
|
||||
{updateOption.isPending ? t('Saving...') : t('Save Changes')}
|
||||
</Button>
|
||||
</form>
|
||||
</SettingsForm>
|
||||
</Form>
|
||||
|
||||
<Separator />
|
||||
|
||||
@ -19,16 +19,22 @@ For commercial licensing, please contact support@quantumnous.com
|
||||
import { useEffect, useMemo } from 'react'
|
||||
import { useForm } from 'react-hook-form'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { Button } from '@/components/ui/button'
|
||||
import {
|
||||
Form,
|
||||
FormControl,
|
||||
FormDescription,
|
||||
FormField,
|
||||
FormItem,
|
||||
FormLabel,
|
||||
} from '@/components/ui/form'
|
||||
import { Switch } from '@/components/ui/switch'
|
||||
import {
|
||||
SettingsControlChildren,
|
||||
SettingsForm,
|
||||
SettingsSwitchContent,
|
||||
SettingsControlGroup,
|
||||
SettingsSwitchItem,
|
||||
} from '../components/settings-form-layout'
|
||||
import { SettingsPageFormActions } from '../components/settings-page-context'
|
||||
import { SettingsSection } from '../components/settings-section'
|
||||
import { useUpdateOption } from '../hooks/use-update-option'
|
||||
import {
|
||||
@ -175,14 +181,16 @@ export function SidebarModulesSection({
|
||||
const sections = Object.entries(config)
|
||||
|
||||
return (
|
||||
<SettingsSection
|
||||
title={t('Sidebar modules')}
|
||||
description={t(
|
||||
'Control which sidebar areas and modules are available to all users.'
|
||||
)}
|
||||
>
|
||||
<SettingsSection title={t('Sidebar modules')}>
|
||||
<Form {...form}>
|
||||
<form onSubmit={form.handleSubmit(onSubmit)} className='space-y-6'>
|
||||
<SettingsForm onSubmit={form.handleSubmit(onSubmit)}>
|
||||
<SettingsPageFormActions
|
||||
onSave={form.handleSubmit(onSubmit)}
|
||||
onReset={resetToDefault}
|
||||
isSaving={updateOption.isPending}
|
||||
resetLabel='Reset to default'
|
||||
saveLabel='Save sidebar modules'
|
||||
/>
|
||||
{sections.map(([sectionKey, sectionConfig]) => {
|
||||
const sectionInfo = sectionMeta[sectionKey] ?? {
|
||||
title: toTitleCase(sectionKey),
|
||||
@ -193,32 +201,30 @@ export function SidebarModulesSection({
|
||||
)
|
||||
|
||||
return (
|
||||
<div key={sectionKey} className='rounded-lg border p-4'>
|
||||
<SettingsControlGroup key={sectionKey}>
|
||||
<FormField
|
||||
control={form.control}
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
name={`${sectionKey}.enabled` as any}
|
||||
render={({ field }) => (
|
||||
<FormItem className='flex flex-row items-start justify-between rounded-lg border p-4'>
|
||||
<div className='space-y-0.5 pe-4'>
|
||||
<FormLabel className='text-base'>
|
||||
{sectionInfo.title}
|
||||
</FormLabel>
|
||||
<SettingsSwitchItem>
|
||||
<SettingsSwitchContent>
|
||||
<FormLabel>{sectionInfo.title}</FormLabel>
|
||||
<FormDescription>
|
||||
{sectionInfo.description}
|
||||
</FormDescription>
|
||||
</div>
|
||||
</SettingsSwitchContent>
|
||||
<FormControl>
|
||||
<Switch
|
||||
checked={Boolean(field.value)}
|
||||
onCheckedChange={field.onChange}
|
||||
/>
|
||||
</FormControl>
|
||||
</FormItem>
|
||||
</SettingsSwitchItem>
|
||||
)}
|
||||
/>
|
||||
|
||||
<div className='mt-4 grid gap-4 md:grid-cols-2'>
|
||||
<SettingsControlChildren className='grid gap-3 md:grid-cols-2'>
|
||||
{modules.map(([moduleKey]) => {
|
||||
const moduleInfo = moduleMeta[sectionKey]?.[moduleKey] ?? {
|
||||
title: toTitleCase(moduleKey),
|
||||
@ -231,15 +237,13 @@ export function SidebarModulesSection({
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
name={`${sectionKey}.${moduleKey}` as any}
|
||||
render={({ field }) => (
|
||||
<FormItem className='flex flex-row items-start justify-between rounded-lg border p-4'>
|
||||
<div className='space-y-0.5 pe-4'>
|
||||
<FormLabel className='text-base'>
|
||||
{moduleInfo.title}
|
||||
</FormLabel>
|
||||
<SettingsSwitchItem className='border-b-0 py-2'>
|
||||
<SettingsSwitchContent>
|
||||
<FormLabel>{moduleInfo.title}</FormLabel>
|
||||
<FormDescription>
|
||||
{moduleInfo.description}
|
||||
</FormDescription>
|
||||
</div>
|
||||
</SettingsSwitchContent>
|
||||
<FormControl>
|
||||
<Switch
|
||||
checked={Boolean(field.value)}
|
||||
@ -250,27 +254,16 @@ export function SidebarModulesSection({
|
||||
}
|
||||
/>
|
||||
</FormControl>
|
||||
</FormItem>
|
||||
</SettingsSwitchItem>
|
||||
)}
|
||||
/>
|
||||
)
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
</SettingsControlChildren>
|
||||
</SettingsControlGroup>
|
||||
)
|
||||
})}
|
||||
|
||||
<div className='flex flex-wrap gap-3'>
|
||||
<Button type='button' variant='outline' onClick={resetToDefault}>
|
||||
{t('Reset to default')}
|
||||
</Button>
|
||||
<Button type='submit' disabled={updateOption.isPending}>
|
||||
{updateOption.isPending
|
||||
? t('Saving...')
|
||||
: t('Save sidebar modules')}
|
||||
</Button>
|
||||
</div>
|
||||
</form>
|
||||
</SettingsForm>
|
||||
</Form>
|
||||
</SettingsSection>
|
||||
)
|
||||
|
||||
@ -110,10 +110,7 @@ export function UpdateCheckerSection({
|
||||
|
||||
return (
|
||||
<>
|
||||
<SettingsSection
|
||||
title={t('System maintenance')}
|
||||
description={t('Review current version and fetch release notes.')}
|
||||
>
|
||||
<SettingsSection title={t('System maintenance')}>
|
||||
<div className='space-y-6'>
|
||||
<div className='grid gap-4 md:grid-cols-2'>
|
||||
<div className='rounded-lg border p-4'>
|
||||
|
||||
@ -22,7 +22,6 @@ import { useForm } from 'react-hook-form'
|
||||
import { zodResolver } from '@hookform/resolvers/zod'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { toast } from 'sonner'
|
||||
import { Button } from '@/components/ui/button'
|
||||
import {
|
||||
Form,
|
||||
FormControl,
|
||||
@ -35,6 +34,13 @@ import {
|
||||
import { Input } from '@/components/ui/input'
|
||||
import { Switch } from '@/components/ui/switch'
|
||||
import { Textarea } from '@/components/ui/textarea'
|
||||
import {
|
||||
SettingsForm,
|
||||
SettingsSwitchContent,
|
||||
SettingsControlGroup,
|
||||
SettingsSwitchItem,
|
||||
} from '../components/settings-form-layout'
|
||||
import { SettingsPageFormActions } from '../components/settings-page-context'
|
||||
import { SettingsSection } from '../components/settings-section'
|
||||
import { useUpdateOption } from '../hooks/use-update-option'
|
||||
import {
|
||||
@ -173,15 +179,14 @@ export function ClaudeSettingsCard({ defaultValues }: ClaudeSettingsCardProps) {
|
||||
}
|
||||
|
||||
return (
|
||||
<SettingsSection
|
||||
title={t('Claude')}
|
||||
description={t(
|
||||
'Override Anthropic headers, defaults, and thinking adapter behavior'
|
||||
)}
|
||||
>
|
||||
<SettingsSection title={t('Claude')}>
|
||||
<Form {...form}>
|
||||
{/* eslint-disable-next-line react-hooks/refs */}
|
||||
<form onSubmit={form.handleSubmit(onSubmit)} className='space-y-6'>
|
||||
<SettingsForm onSubmit={form.handleSubmit(onSubmit)}>
|
||||
<SettingsPageFormActions
|
||||
onSave={form.handleSubmit(onSubmit)}
|
||||
isSaving={updateOption.isPending}
|
||||
/>
|
||||
<FormField
|
||||
control={form.control}
|
||||
name='claude.model_headers_settings'
|
||||
@ -219,29 +224,27 @@ export function ClaudeSettingsCard({ defaultValues }: ClaudeSettingsCardProps) {
|
||||
)}
|
||||
/>
|
||||
|
||||
<div className='space-y-4 rounded-lg border p-4'>
|
||||
<SettingsControlGroup>
|
||||
<FormField
|
||||
control={form.control}
|
||||
name='claude.thinking_adapter_enabled'
|
||||
render={({ field }) => (
|
||||
<FormItem className='flex flex-row items-center justify-between'>
|
||||
<div className='space-y-0.5'>
|
||||
<FormLabel className='text-base'>
|
||||
{t('Thinking Adapter')}
|
||||
</FormLabel>
|
||||
<SettingsSwitchItem>
|
||||
<SettingsSwitchContent>
|
||||
<FormLabel>{t('Thinking Adapter')}</FormLabel>
|
||||
<FormDescription>
|
||||
{t(
|
||||
'Translate `-thinking` suffixes into Anthropic native thinking models while keeping pricing predictable.'
|
||||
)}
|
||||
</FormDescription>
|
||||
</div>
|
||||
</SettingsSwitchContent>
|
||||
<FormControl>
|
||||
<Switch
|
||||
checked={field.value}
|
||||
onCheckedChange={field.onChange}
|
||||
/>
|
||||
</FormControl>
|
||||
</FormItem>
|
||||
</SettingsSwitchItem>
|
||||
)}
|
||||
/>
|
||||
|
||||
@ -267,12 +270,8 @@ export function ClaudeSettingsCard({ defaultValues }: ClaudeSettingsCardProps) {
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<Button type='submit' disabled={updateOption.isPending}>
|
||||
{updateOption.isPending ? t('Saving...') : t('Save Changes')}
|
||||
</Button>
|
||||
</form>
|
||||
</SettingsControlGroup>
|
||||
</SettingsForm>
|
||||
</Form>
|
||||
</SettingsSection>
|
||||
)
|
||||
|
||||
@ -22,7 +22,6 @@ import { useForm } from 'react-hook-form'
|
||||
import { zodResolver } from '@hookform/resolvers/zod'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { toast } from 'sonner'
|
||||
import { Button } from '@/components/ui/button'
|
||||
import {
|
||||
Form,
|
||||
FormControl,
|
||||
@ -35,6 +34,13 @@ import {
|
||||
import { Input } from '@/components/ui/input'
|
||||
import { Switch } from '@/components/ui/switch'
|
||||
import { Textarea } from '@/components/ui/textarea'
|
||||
import {
|
||||
SettingsForm,
|
||||
SettingsSwitchContent,
|
||||
SettingsControlGroup,
|
||||
SettingsSwitchItem,
|
||||
} from '../components/settings-form-layout'
|
||||
import { SettingsPageFormActions } from '../components/settings-page-context'
|
||||
import { SettingsSection } from '../components/settings-section'
|
||||
import { useUpdateOption } from '../hooks/use-update-option'
|
||||
import {
|
||||
@ -226,14 +232,13 @@ export function GeminiSettingsCard({ defaultValues }: GeminiSettingsCardProps) {
|
||||
)
|
||||
|
||||
return (
|
||||
<SettingsSection
|
||||
title={t('Gemini')}
|
||||
description={t(
|
||||
'Configure Gemini safety behavior, version overrides, and thinking adapter'
|
||||
)}
|
||||
>
|
||||
<SettingsSection title={t('Gemini')}>
|
||||
<Form {...form}>
|
||||
<form onSubmit={form.handleSubmit(onSubmit)} className='space-y-6'>
|
||||
<SettingsForm onSubmit={form.handleSubmit(onSubmit)}>
|
||||
<SettingsPageFormActions
|
||||
onSave={form.handleSubmit(onSubmit)}
|
||||
isSaving={updateOption.isPending}
|
||||
/>
|
||||
<FormField
|
||||
control={form.control}
|
||||
name='gemini.safety_settings'
|
||||
@ -295,16 +300,14 @@ export function GeminiSettingsCard({ defaultValues }: GeminiSettingsCardProps) {
|
||||
)}
|
||||
/>
|
||||
|
||||
<div className='space-y-4 rounded-lg border p-4'>
|
||||
<SettingsControlGroup>
|
||||
<FormField
|
||||
control={form.control}
|
||||
name='gemini.thinking_adapter_enabled'
|
||||
render={({ field }) => (
|
||||
<FormItem className='flex flex-row items-center justify-between'>
|
||||
<div className='space-y-0.5'>
|
||||
<FormLabel className='text-base'>
|
||||
{t('Thinking Adapter')}
|
||||
</FormLabel>
|
||||
<SettingsSwitchItem>
|
||||
<SettingsSwitchContent>
|
||||
<FormLabel>{t('Thinking Adapter')}</FormLabel>
|
||||
<FormDescription>
|
||||
{t('Supports `-thinking`, `-thinking-')}
|
||||
{'{{budget}}'}
|
||||
@ -312,14 +315,14 @@ export function GeminiSettingsCard({ defaultValues }: GeminiSettingsCardProps) {
|
||||
'`, and `-nothinking` suffixes while routing to the correct Gemini variant.'
|
||||
)}
|
||||
</FormDescription>
|
||||
</div>
|
||||
</SettingsSwitchContent>
|
||||
<FormControl>
|
||||
<Switch
|
||||
checked={field.value}
|
||||
onCheckedChange={field.onChange}
|
||||
/>
|
||||
</FormControl>
|
||||
</FormItem>
|
||||
</SettingsSwitchItem>
|
||||
)}
|
||||
/>
|
||||
|
||||
@ -353,15 +356,15 @@ export function GeminiSettingsCard({ defaultValues }: GeminiSettingsCardProps) {
|
||||
)}
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
</SettingsControlGroup>
|
||||
|
||||
<FormField
|
||||
control={form.control}
|
||||
name='gemini.function_call_thought_signature_enabled'
|
||||
render={({ field }) => (
|
||||
<FormItem className='flex flex-row items-center justify-between rounded-lg border p-4'>
|
||||
<div className='space-y-0.5'>
|
||||
<FormLabel className='text-base'>
|
||||
<SettingsSwitchItem>
|
||||
<SettingsSwitchContent>
|
||||
<FormLabel>
|
||||
{t('Enable FunctionCall thoughtSignature Fill')}
|
||||
</FormLabel>
|
||||
<FormDescription>
|
||||
@ -369,14 +372,14 @@ export function GeminiSettingsCard({ defaultValues }: GeminiSettingsCardProps) {
|
||||
'Fill thoughtSignature only for Gemini/Vertex channels using the OpenAI format'
|
||||
)}
|
||||
</FormDescription>
|
||||
</div>
|
||||
</SettingsSwitchContent>
|
||||
<FormControl>
|
||||
<Switch
|
||||
checked={field.value}
|
||||
onCheckedChange={field.onChange}
|
||||
/>
|
||||
</FormControl>
|
||||
</FormItem>
|
||||
</SettingsSwitchItem>
|
||||
)}
|
||||
/>
|
||||
|
||||
@ -384,31 +387,25 @@ export function GeminiSettingsCard({ defaultValues }: GeminiSettingsCardProps) {
|
||||
control={form.control}
|
||||
name='gemini.remove_function_response_id_enabled'
|
||||
render={({ field }) => (
|
||||
<FormItem className='flex flex-row items-center justify-between rounded-lg border p-4'>
|
||||
<div className='space-y-0.5'>
|
||||
<FormLabel className='text-base'>
|
||||
{t('Remove functionResponse.id field')}
|
||||
</FormLabel>
|
||||
<SettingsSwitchItem>
|
||||
<SettingsSwitchContent>
|
||||
<FormLabel>{t('Remove functionResponse.id field')}</FormLabel>
|
||||
<FormDescription>
|
||||
{t(
|
||||
'Vertex AI does not support functionResponse.id. Enable this to remove the field automatically.'
|
||||
)}
|
||||
</FormDescription>
|
||||
</div>
|
||||
</SettingsSwitchContent>
|
||||
<FormControl>
|
||||
<Switch
|
||||
checked={field.value}
|
||||
onCheckedChange={field.onChange}
|
||||
/>
|
||||
</FormControl>
|
||||
</FormItem>
|
||||
</SettingsSwitchItem>
|
||||
)}
|
||||
/>
|
||||
|
||||
<Button type='submit' disabled={updateOption.isPending}>
|
||||
{updateOption.isPending ? t('Saving...') : t('Save Changes')}
|
||||
</Button>
|
||||
</form>
|
||||
</SettingsForm>
|
||||
</Form>
|
||||
</SettingsSection>
|
||||
)
|
||||
|
||||
@ -38,6 +38,12 @@ import { Separator } from '@/components/ui/separator'
|
||||
import { Switch } from '@/components/ui/switch'
|
||||
import { Textarea } from '@/components/ui/textarea'
|
||||
import { StatusBadge } from '@/components/status-badge'
|
||||
import {
|
||||
SettingsForm,
|
||||
SettingsSwitchContent,
|
||||
SettingsSwitchItem,
|
||||
} from '../components/settings-form-layout'
|
||||
import { SettingsPageFormActions } from '../components/settings-page-context'
|
||||
import { SettingsSection } from '../components/settings-section'
|
||||
import { useUpdateOption } from '../hooks/use-update-option'
|
||||
|
||||
@ -186,36 +192,33 @@ export function GlobalSettingsCard({ defaultValues }: GlobalSettingsCardProps) {
|
||||
}
|
||||
|
||||
return (
|
||||
<SettingsSection
|
||||
title={t('Global Model Configuration')}
|
||||
description={t(
|
||||
'Control passthrough behavior and connection keep-alive settings'
|
||||
)}
|
||||
>
|
||||
<SettingsSection title={t('Global Model Configuration')}>
|
||||
<Form {...form}>
|
||||
<form onSubmit={form.handleSubmit(onSubmit)} className='space-y-6'>
|
||||
<SettingsForm onSubmit={form.handleSubmit(onSubmit)}>
|
||||
<SettingsPageFormActions
|
||||
onSave={form.handleSubmit(onSubmit)}
|
||||
isSaving={updateOption.isPending}
|
||||
/>
|
||||
<FormField
|
||||
control={form.control}
|
||||
name='global.pass_through_request_enabled'
|
||||
render={({ field }) => (
|
||||
<FormItem className='flex flex-row items-center justify-between rounded-lg border p-4'>
|
||||
<div className='space-y-0.5'>
|
||||
<FormLabel className='text-base'>
|
||||
{t('Enable Request Passthrough')}
|
||||
</FormLabel>
|
||||
<SettingsSwitchItem>
|
||||
<SettingsSwitchContent>
|
||||
<FormLabel>{t('Enable Request Passthrough')}</FormLabel>
|
||||
<FormDescription>
|
||||
{t(
|
||||
'Forward requests directly to upstream providers without any post-processing.'
|
||||
)}
|
||||
</FormDescription>
|
||||
</div>
|
||||
</SettingsSwitchContent>
|
||||
<FormControl>
|
||||
<Switch
|
||||
checked={field.value}
|
||||
onCheckedChange={field.onChange}
|
||||
/>
|
||||
</FormControl>
|
||||
</FormItem>
|
||||
</SettingsSwitchItem>
|
||||
)}
|
||||
/>
|
||||
|
||||
@ -349,24 +352,22 @@ export function GlobalSettingsCard({ defaultValues }: GlobalSettingsCardProps) {
|
||||
control={form.control}
|
||||
name='general_setting.ping_interval_enabled'
|
||||
render={({ field }) => (
|
||||
<FormItem className='flex flex-row items-center justify-between rounded-lg border p-4'>
|
||||
<div className='space-y-0.5'>
|
||||
<FormLabel className='text-base'>
|
||||
{t('Keep-alive Ping')}
|
||||
</FormLabel>
|
||||
<SettingsSwitchItem>
|
||||
<SettingsSwitchContent>
|
||||
<FormLabel>{t('Keep-alive Ping')}</FormLabel>
|
||||
<FormDescription>
|
||||
{t(
|
||||
'Periodically send ping frames to keep streaming connections active.'
|
||||
)}
|
||||
</FormDescription>
|
||||
</div>
|
||||
</SettingsSwitchContent>
|
||||
<FormControl>
|
||||
<Switch
|
||||
checked={field.value}
|
||||
onCheckedChange={field.onChange}
|
||||
/>
|
||||
</FormControl>
|
||||
</FormItem>
|
||||
</SettingsSwitchItem>
|
||||
)}
|
||||
/>
|
||||
|
||||
@ -402,11 +403,7 @@ export function GlobalSettingsCard({ defaultValues }: GlobalSettingsCardProps) {
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
|
||||
<Button type='submit' disabled={updateOption.isPending}>
|
||||
{updateOption.isPending ? t('Saving...') : t('Save Changes')}
|
||||
</Button>
|
||||
</form>
|
||||
</SettingsForm>
|
||||
</Form>
|
||||
</SettingsSection>
|
||||
)
|
||||
|
||||
@ -20,7 +20,6 @@ import * as z from 'zod'
|
||||
import { useForm } from 'react-hook-form'
|
||||
import { zodResolver } from '@hookform/resolvers/zod'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { Button } from '@/components/ui/button'
|
||||
import {
|
||||
Form,
|
||||
FormControl,
|
||||
@ -31,6 +30,12 @@ import {
|
||||
} from '@/components/ui/form'
|
||||
import { Input } from '@/components/ui/input'
|
||||
import { Switch } from '@/components/ui/switch'
|
||||
import {
|
||||
SettingsForm,
|
||||
SettingsSwitchContent,
|
||||
SettingsSwitchItem,
|
||||
} from '../components/settings-form-layout'
|
||||
import { SettingsPageFormActions } from '../components/settings-page-context'
|
||||
import { SettingsSection } from '../components/settings-section'
|
||||
import { useResetForm } from '../hooks/use-reset-form'
|
||||
import { useUpdateOption } from '../hooks/use-update-option'
|
||||
@ -79,24 +84,19 @@ export function GrokSettingsCard(props: Props) {
|
||||
const enabled = form.watch('grok.violation_deduction_enabled')
|
||||
|
||||
return (
|
||||
<SettingsSection
|
||||
title={t('Grok Settings')}
|
||||
description={t('Configure xAI Grok model specific settings')}
|
||||
>
|
||||
<SettingsSection title={t('Grok Settings')}>
|
||||
<Form {...form}>
|
||||
<form onSubmit={form.handleSubmit(onSubmit)} className='space-y-6'>
|
||||
<SettingsForm onSubmit={form.handleSubmit(onSubmit)}>
|
||||
<SettingsPageFormActions
|
||||
onSave={form.handleSubmit(onSubmit)}
|
||||
isSaving={updateOption.isPending}
|
||||
/>
|
||||
<FormField
|
||||
control={form.control}
|
||||
name='grok.violation_deduction_enabled'
|
||||
render={({ field }) => (
|
||||
<FormItem className='flex items-center gap-2'>
|
||||
<FormControl>
|
||||
<Switch
|
||||
checked={field.value}
|
||||
onCheckedChange={field.onChange}
|
||||
/>
|
||||
</FormControl>
|
||||
<div>
|
||||
<SettingsSwitchItem>
|
||||
<SettingsSwitchContent>
|
||||
<FormLabel>{t('Enable violation deduction')}</FormLabel>
|
||||
<FormDescription>
|
||||
{t(
|
||||
@ -111,8 +111,14 @@ export function GrokSettingsCard(props: Props) {
|
||||
{t('Official documentation')}
|
||||
</a>
|
||||
</FormDescription>
|
||||
</div>
|
||||
</FormItem>
|
||||
</SettingsSwitchContent>
|
||||
<FormControl>
|
||||
<Switch
|
||||
checked={field.value}
|
||||
onCheckedChange={field.onChange}
|
||||
/>
|
||||
</FormControl>
|
||||
</SettingsSwitchItem>
|
||||
)}
|
||||
/>
|
||||
|
||||
@ -139,11 +145,7 @@ export function GrokSettingsCard(props: Props) {
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
|
||||
<Button type='submit' disabled={updateOption.isPending}>
|
||||
{updateOption.isPending ? t('Saving...') : t('Save Changes')}
|
||||
</Button>
|
||||
</form>
|
||||
</SettingsForm>
|
||||
</Form>
|
||||
</SettingsSection>
|
||||
)
|
||||
|
||||
@ -45,6 +45,12 @@ import {
|
||||
} from '@/components/ui/sheet'
|
||||
import { Switch } from '@/components/ui/switch'
|
||||
import { Textarea } from '@/components/ui/textarea'
|
||||
import {
|
||||
SettingsForm,
|
||||
SettingsSwitchContent,
|
||||
SettingsSwitchItem,
|
||||
} from '../components/settings-form-layout'
|
||||
import { SettingsPageActionsPortal } from '../components/settings-page-context'
|
||||
import { GroupRatioVisualEditor } from './group-ratio-visual-editor'
|
||||
import { GroupSpecialUsableRulesEditor } from './group-special-usable-editor'
|
||||
|
||||
@ -112,6 +118,16 @@ export const GroupRatioForm = memo(function GroupRatioForm({
|
||||
<GroupPricingGuide open={guideOpen} onOpenChange={setGuideOpen} />
|
||||
|
||||
<Form {...form}>
|
||||
<SettingsPageActionsPortal>
|
||||
<Button
|
||||
type='button'
|
||||
size='sm'
|
||||
onClick={form.handleSubmit(onSave)}
|
||||
disabled={isSaving}
|
||||
>
|
||||
{isSaving ? t('Saving...') : t('Save group ratios')}
|
||||
</Button>
|
||||
</SettingsPageActionsPortal>
|
||||
{editMode === 'visual' ? (
|
||||
<div className='space-y-6'>
|
||||
<GroupRatioVisualEditor
|
||||
@ -136,33 +152,27 @@ export const GroupRatioForm = memo(function GroupRatioForm({
|
||||
control={form.control}
|
||||
name='DefaultUseAutoGroup'
|
||||
render={({ field }) => (
|
||||
<FormItem className='flex flex-row items-center justify-between rounded-lg border p-4'>
|
||||
<div className='space-y-0.5'>
|
||||
<FormLabel className='text-base'>
|
||||
{t('Default to auto groups')}
|
||||
</FormLabel>
|
||||
<SettingsSwitchItem>
|
||||
<SettingsSwitchContent>
|
||||
<FormLabel>{t('Default to auto groups')}</FormLabel>
|
||||
<FormDescription>
|
||||
{t(
|
||||
'When enabled, newly created tokens start in the first auto group.'
|
||||
)}
|
||||
</FormDescription>
|
||||
</div>
|
||||
</SettingsSwitchContent>
|
||||
<FormControl>
|
||||
<Switch
|
||||
checked={field.value}
|
||||
onCheckedChange={field.onChange}
|
||||
/>
|
||||
</FormControl>
|
||||
</FormItem>
|
||||
</SettingsSwitchItem>
|
||||
)}
|
||||
/>
|
||||
|
||||
<Button onClick={form.handleSubmit(onSave)} disabled={isSaving}>
|
||||
{isSaving ? t('Saving...') : t('Save group ratios')}
|
||||
</Button>
|
||||
</div>
|
||||
) : (
|
||||
<form onSubmit={form.handleSubmit(onSave)} className='space-y-6'>
|
||||
<SettingsForm onSubmit={form.handleSubmit(onSave)}>
|
||||
<FormField
|
||||
control={form.control}
|
||||
name='GroupRatio'
|
||||
@ -284,31 +294,25 @@ export const GroupRatioForm = memo(function GroupRatioForm({
|
||||
control={form.control}
|
||||
name='DefaultUseAutoGroup'
|
||||
render={({ field }) => (
|
||||
<FormItem className='flex flex-row items-center justify-between rounded-lg border p-4'>
|
||||
<div className='space-y-0.5'>
|
||||
<FormLabel className='text-base'>
|
||||
{t('Default to auto groups')}
|
||||
</FormLabel>
|
||||
<SettingsSwitchItem>
|
||||
<SettingsSwitchContent>
|
||||
<FormLabel>{t('Default to auto groups')}</FormLabel>
|
||||
<FormDescription>
|
||||
{t(
|
||||
'When enabled, newly created tokens start in the first auto group.'
|
||||
)}
|
||||
</FormDescription>
|
||||
</div>
|
||||
</SettingsSwitchContent>
|
||||
<FormControl>
|
||||
<Switch
|
||||
checked={field.value}
|
||||
onCheckedChange={field.onChange}
|
||||
/>
|
||||
</FormControl>
|
||||
</FormItem>
|
||||
</SettingsSwitchItem>
|
||||
)}
|
||||
/>
|
||||
|
||||
<Button type='submit' disabled={isSaving}>
|
||||
{isSaving ? t('Saving...') : t('Save group ratios')}
|
||||
</Button>
|
||||
</form>
|
||||
</SettingsForm>
|
||||
)}
|
||||
</Form>
|
||||
</div>
|
||||
|
||||
@ -21,6 +21,7 @@ import type { ModelSettings } from '../types'
|
||||
import {
|
||||
MODELS_DEFAULT_SECTION,
|
||||
getModelsSectionContent,
|
||||
getModelsSectionMeta,
|
||||
} from './section-registry.tsx'
|
||||
|
||||
const defaultModelSettings: ModelSettings = {
|
||||
@ -77,6 +78,7 @@ export function ModelSettings() {
|
||||
defaultSettings={defaultModelSettings}
|
||||
defaultSection={MODELS_DEFAULT_SECTION}
|
||||
getSectionContent={getModelsSectionContent}
|
||||
getSectionMeta={getModelsSectionMeta}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
@ -33,11 +33,9 @@ import {
|
||||
} from '@/components/ui/collapsible'
|
||||
import {
|
||||
Field,
|
||||
FieldContent,
|
||||
FieldDescription,
|
||||
FieldGroup,
|
||||
FieldLabel,
|
||||
FieldTitle,
|
||||
} from '@/components/ui/field'
|
||||
import {
|
||||
Form,
|
||||
@ -62,9 +60,12 @@ import {
|
||||
SheetHeader,
|
||||
SheetTitle,
|
||||
} from '@/components/ui/sheet'
|
||||
import { Switch } from '@/components/ui/switch'
|
||||
import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs'
|
||||
import { combineBillingExpr } from '@/features/pricing/lib/billing-expr'
|
||||
import {
|
||||
SettingsControlGroup,
|
||||
SettingsSwitchField,
|
||||
} from '../components/settings-form-layout'
|
||||
import { formatPricingNumber } from './pricing-format'
|
||||
import { TieredPricingEditor } from './tiered-pricing-editor'
|
||||
|
||||
@ -1005,36 +1006,29 @@ function PriceLane(props: {
|
||||
const effectiveDisabled = props.disabled || !props.enabled
|
||||
|
||||
return (
|
||||
<Field
|
||||
className={cn(
|
||||
'rounded-lg border p-3',
|
||||
effectiveDisabled && 'bg-muted/35'
|
||||
)}
|
||||
<SettingsControlGroup
|
||||
className={cn('space-y-3', effectiveDisabled && 'opacity-75')}
|
||||
data-disabled={effectiveDisabled || undefined}
|
||||
>
|
||||
<div className='flex items-start justify-between gap-3'>
|
||||
<FieldContent>
|
||||
<FieldTitle>{props.title}</FieldTitle>
|
||||
<FieldDescription>{props.description}</FieldDescription>
|
||||
</FieldContent>
|
||||
<Switch
|
||||
checked={props.enabled}
|
||||
disabled={props.disabled}
|
||||
onCheckedChange={props.onEnabledChange}
|
||||
aria-label={props.title}
|
||||
/>
|
||||
</div>
|
||||
<SettingsSwitchField
|
||||
checked={props.enabled}
|
||||
disabled={props.disabled}
|
||||
onCheckedChange={props.onEnabledChange}
|
||||
label={props.title}
|
||||
description={props.description}
|
||||
aria-label={props.title}
|
||||
/>
|
||||
<PriceInput
|
||||
value={props.value}
|
||||
placeholder={props.placeholder}
|
||||
disabled={effectiveDisabled}
|
||||
onChange={props.onChange}
|
||||
/>
|
||||
<FieldDescription>
|
||||
<p className='text-muted-foreground text-xs'>
|
||||
{props.enabled
|
||||
? t('USD price per 1M tokens.')
|
||||
: t('Disabled lanes are omitted on save.')}
|
||||
</FieldDescription>
|
||||
</Field>
|
||||
</p>
|
||||
</SettingsControlGroup>
|
||||
)
|
||||
}
|
||||
|
||||
@ -32,6 +32,12 @@ import {
|
||||
} from '@/components/ui/form'
|
||||
import { Switch } from '@/components/ui/switch'
|
||||
import { Textarea } from '@/components/ui/textarea'
|
||||
import {
|
||||
SettingsForm,
|
||||
SettingsSwitchContent,
|
||||
SettingsSwitchItem,
|
||||
} from '../components/settings-form-layout'
|
||||
import { SettingsPageActionsPortal } from '../components/settings-page-context'
|
||||
import { ModelRatioVisualEditor } from './model-ratio-visual-editor'
|
||||
|
||||
type ModelFormValues = {
|
||||
@ -99,6 +105,25 @@ export const ModelRatioForm = memo(function ModelRatioForm({
|
||||
</div>
|
||||
|
||||
<Form {...form}>
|
||||
<SettingsPageActionsPortal>
|
||||
<Button
|
||||
type='button'
|
||||
variant='destructive'
|
||||
size='sm'
|
||||
onClick={onReset}
|
||||
disabled={isResetting}
|
||||
>
|
||||
{t('Reset prices')}
|
||||
</Button>
|
||||
<Button
|
||||
type='button'
|
||||
size='sm'
|
||||
onClick={form.handleSubmit(onSave)}
|
||||
disabled={isSaving}
|
||||
>
|
||||
{isSaving ? t('Saving...') : t('Save model prices')}
|
||||
</Button>
|
||||
</SettingsPageActionsPortal>
|
||||
{editMode === 'visual' ? (
|
||||
<div className='space-y-6'>
|
||||
<ModelRatioVisualEditor
|
||||
@ -127,43 +152,27 @@ export const ModelRatioForm = memo(function ModelRatioForm({
|
||||
control={form.control}
|
||||
name='ExposeRatioEnabled'
|
||||
render={({ field }) => (
|
||||
<FormItem className='flex flex-row items-center justify-between rounded-lg border p-4'>
|
||||
<div className='space-y-0.5'>
|
||||
<FormLabel className='text-base'>
|
||||
{t('Expose ratio API')}
|
||||
</FormLabel>
|
||||
<SettingsSwitchItem>
|
||||
<SettingsSwitchContent>
|
||||
<FormLabel>{t('Expose ratio API')}</FormLabel>
|
||||
<FormDescription>
|
||||
{t(
|
||||
'Allow clients to query configured ratios via `/api/ratio`.'
|
||||
)}
|
||||
</FormDescription>
|
||||
</div>
|
||||
</SettingsSwitchContent>
|
||||
<FormControl>
|
||||
<Switch
|
||||
checked={field.value}
|
||||
onCheckedChange={field.onChange}
|
||||
/>
|
||||
</FormControl>
|
||||
</FormItem>
|
||||
</SettingsSwitchItem>
|
||||
)}
|
||||
/>
|
||||
|
||||
<div className='flex flex-wrap gap-4'>
|
||||
<Button onClick={form.handleSubmit(onSave)} disabled={isSaving}>
|
||||
{isSaving ? t('Saving...') : t('Save model prices')}
|
||||
</Button>
|
||||
<Button
|
||||
type='button'
|
||||
variant='destructive'
|
||||
onClick={onReset}
|
||||
disabled={isResetting}
|
||||
>
|
||||
{t('Reset prices')}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
) : (
|
||||
<form onSubmit={form.handleSubmit(onSave)} className='space-y-6'>
|
||||
<SettingsForm onSubmit={form.handleSubmit(onSave)}>
|
||||
<FormField
|
||||
control={form.control}
|
||||
name='ModelPrice'
|
||||
@ -318,41 +327,25 @@ export const ModelRatioForm = memo(function ModelRatioForm({
|
||||
control={form.control}
|
||||
name='ExposeRatioEnabled'
|
||||
render={({ field }) => (
|
||||
<FormItem className='flex flex-row items-center justify-between rounded-lg border p-4'>
|
||||
<div className='space-y-0.5'>
|
||||
<FormLabel className='text-base'>
|
||||
{t('Expose ratio API')}
|
||||
</FormLabel>
|
||||
<SettingsSwitchItem>
|
||||
<SettingsSwitchContent>
|
||||
<FormLabel>{t('Expose ratio API')}</FormLabel>
|
||||
<FormDescription>
|
||||
{t(
|
||||
'Allow clients to query configured ratios via `/api/ratio`.'
|
||||
)}
|
||||
</FormDescription>
|
||||
</div>
|
||||
</SettingsSwitchContent>
|
||||
<FormControl>
|
||||
<Switch
|
||||
checked={field.value}
|
||||
onCheckedChange={field.onChange}
|
||||
/>
|
||||
</FormControl>
|
||||
</FormItem>
|
||||
</SettingsSwitchItem>
|
||||
)}
|
||||
/>
|
||||
|
||||
<div className='flex flex-wrap gap-4'>
|
||||
<Button type='submit' disabled={isSaving}>
|
||||
{isSaving ? t('Saving...') : t('Save model prices')}
|
||||
</Button>
|
||||
<Button
|
||||
type='button'
|
||||
variant='destructive'
|
||||
onClick={onReset}
|
||||
disabled={isResetting}
|
||||
>
|
||||
{t('Reset prices')}
|
||||
</Button>
|
||||
</div>
|
||||
</form>
|
||||
</SettingsForm>
|
||||
)}
|
||||
</Form>
|
||||
</div>
|
||||
|
||||
@ -204,7 +204,6 @@ type RatioSettingsCardProps = {
|
||||
groupDefaults: GroupFormValues
|
||||
toolPricesDefault: string
|
||||
titleKey?: string
|
||||
descriptionKey?: string
|
||||
visibleTabs?: RatioTabId[]
|
||||
}
|
||||
|
||||
@ -213,7 +212,6 @@ export function RatioSettingsCard({
|
||||
groupDefaults,
|
||||
toolPricesDefault,
|
||||
titleKey = 'Pricing Ratios',
|
||||
descriptionKey = 'Configure model, caching, and group ratios used for billing',
|
||||
visibleTabs = ['models', 'groups', 'tool-prices', 'upstream-sync'],
|
||||
}: RatioSettingsCardProps) {
|
||||
const { t } = useTranslation()
|
||||
@ -497,7 +495,7 @@ export function RatioSettingsCard({
|
||||
}
|
||||
|
||||
return (
|
||||
<SettingsSection title={t(titleKey)} description={t(descriptionKey)}>
|
||||
<SettingsSection title={t(titleKey)}>
|
||||
{visibleTabs.length === 1 ? (
|
||||
renderTabContent(defaultTab)
|
||||
) : (
|
||||
|
||||
@ -39,7 +39,6 @@ const MODELS_SECTIONS = [
|
||||
{
|
||||
id: 'global',
|
||||
titleKey: 'Global Model Configuration',
|
||||
descriptionKey: 'Configure global model settings',
|
||||
build: (settings: ModelSettings) => (
|
||||
<GlobalSettingsCard
|
||||
defaultValues={{
|
||||
@ -68,7 +67,6 @@ const MODELS_SECTIONS = [
|
||||
{
|
||||
id: 'gemini',
|
||||
titleKey: 'Gemini',
|
||||
descriptionKey: 'Configure Gemini model settings',
|
||||
build: (settings: ModelSettings) => (
|
||||
<GeminiSettingsCard
|
||||
defaultValues={{
|
||||
@ -93,7 +91,6 @@ const MODELS_SECTIONS = [
|
||||
{
|
||||
id: 'claude',
|
||||
titleKey: 'Claude',
|
||||
descriptionKey: 'Configure Claude model settings',
|
||||
build: (settings: ModelSettings) => (
|
||||
<ClaudeSettingsCard
|
||||
defaultValues={{
|
||||
@ -112,7 +109,6 @@ const MODELS_SECTIONS = [
|
||||
{
|
||||
id: 'grok',
|
||||
titleKey: 'Grok',
|
||||
descriptionKey: 'Configure xAI Grok model settings',
|
||||
build: (settings: ModelSettings) => (
|
||||
<GrokSettingsCard
|
||||
defaultValues={{
|
||||
@ -127,7 +123,6 @@ const MODELS_SECTIONS = [
|
||||
{
|
||||
id: 'channel-affinity',
|
||||
titleKey: 'Channel Affinity',
|
||||
descriptionKey: 'Configure channel affinity (sticky routing) rules',
|
||||
build: (settings: ModelSettings) => (
|
||||
<ChannelAffinitySection
|
||||
defaultValues={{
|
||||
@ -148,7 +143,6 @@ const MODELS_SECTIONS = [
|
||||
{
|
||||
id: 'model-deployment',
|
||||
titleKey: 'Model Deployment',
|
||||
descriptionKey: 'Configure model deployment provider settings',
|
||||
build: (settings: ModelSettings) => (
|
||||
<IoNetDeploymentSettingsSection
|
||||
defaultValues={{
|
||||
@ -173,3 +167,4 @@ export const MODELS_SECTION_IDS = modelsRegistry.sectionIds
|
||||
export const MODELS_DEFAULT_SECTION = modelsRegistry.defaultSection
|
||||
export const getModelsSectionNavItems = modelsRegistry.getSectionNavItems
|
||||
export const getModelsSectionContent = modelsRegistry.getSectionContent
|
||||
export const getModelsSectionMeta = modelsRegistry.getSectionMeta
|
||||
|
||||
@ -16,15 +16,13 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
For commercial licensing, please contact support@quantumnous.com
|
||||
*/
|
||||
import { useMemo } from 'react'
|
||||
import { useParams } from '@tanstack/react-router'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { useStatus } from '@/hooks/use-status'
|
||||
import { getOptionValue, useSystemOptions } from '../hooks/use-system-options'
|
||||
import { SettingsPage } from '../components/settings-page'
|
||||
import type { OperationsSettings } from '../types'
|
||||
import {
|
||||
OPERATIONS_DEFAULT_SECTION,
|
||||
getOperationsSectionContent,
|
||||
getOperationsSectionMeta,
|
||||
} from './section-registry.tsx'
|
||||
|
||||
const defaultOperationsSettings: OperationsSettings = {
|
||||
@ -68,46 +66,20 @@ const defaultOperationsSettings: OperationsSettings = {
|
||||
}
|
||||
|
||||
export function OperationsSettings() {
|
||||
const { t } = useTranslation()
|
||||
const { data, isLoading } = useSystemOptions()
|
||||
const { status } = useStatus()
|
||||
const params = useParams({
|
||||
from: '/_authenticated/system-settings/operations/$section',
|
||||
})
|
||||
|
||||
const settings = useMemo(
|
||||
() => getOptionValue(data?.data, defaultOperationsSettings),
|
||||
[data?.data]
|
||||
)
|
||||
|
||||
if (isLoading) {
|
||||
return (
|
||||
<div className='text-muted-foreground flex h-full w-full flex-1 items-center justify-center'>
|
||||
{t('Loading maintenance settings...')}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
const activeSection = (params?.section ?? OPERATIONS_DEFAULT_SECTION) as
|
||||
| 'behavior'
|
||||
| 'monitoring'
|
||||
| 'email'
|
||||
| 'worker'
|
||||
| 'logs'
|
||||
| 'performance'
|
||||
| 'update-checker'
|
||||
const sectionContent = getOperationsSectionContent(
|
||||
activeSection,
|
||||
settings,
|
||||
status?.version as string | undefined,
|
||||
status?.start_time as number | null | undefined
|
||||
)
|
||||
|
||||
return (
|
||||
<div className='flex h-full w-full flex-1 flex-col'>
|
||||
<div className='faded-bottom h-full w-full overflow-y-auto scroll-smooth pe-4 pb-12'>
|
||||
<div className='space-y-4'>{sectionContent}</div>
|
||||
</div>
|
||||
</div>
|
||||
<SettingsPage
|
||||
routePath='/_authenticated/system-settings/operations/$section'
|
||||
defaultSettings={defaultOperationsSettings}
|
||||
defaultSection={OPERATIONS_DEFAULT_SECTION}
|
||||
getSectionContent={getOperationsSectionContent}
|
||||
getSectionMeta={getOperationsSectionMeta}
|
||||
extraArgs={[
|
||||
status?.version as string | undefined,
|
||||
status?.start_time as number | null | undefined,
|
||||
]}
|
||||
loadingMessage='Loading maintenance settings...'
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
@ -30,7 +30,6 @@ const OPERATIONS_SECTIONS = [
|
||||
{
|
||||
id: 'behavior',
|
||||
titleKey: 'System Behavior',
|
||||
descriptionKey: 'Configure system-wide behavior and defaults',
|
||||
build: (settings: OperationsSettings) => (
|
||||
<SystemBehaviorSection
|
||||
defaultValues={{
|
||||
@ -45,7 +44,6 @@ const OPERATIONS_SECTIONS = [
|
||||
{
|
||||
id: 'monitoring',
|
||||
titleKey: 'Monitoring & Alerts',
|
||||
descriptionKey: 'Configure channel monitoring and automation',
|
||||
build: (settings: OperationsSettings) => (
|
||||
<MonitoringSettingsSection
|
||||
defaultValues={{
|
||||
@ -68,7 +66,6 @@ const OPERATIONS_SECTIONS = [
|
||||
{
|
||||
id: 'email',
|
||||
titleKey: 'SMTP Email',
|
||||
descriptionKey: 'Configure SMTP email settings',
|
||||
build: (settings: OperationsSettings) => (
|
||||
<EmailSettingsSection
|
||||
defaultValues={{
|
||||
@ -86,7 +83,6 @@ const OPERATIONS_SECTIONS = [
|
||||
{
|
||||
id: 'worker',
|
||||
titleKey: 'Worker Proxy',
|
||||
descriptionKey: 'Configure worker service settings',
|
||||
build: (settings: OperationsSettings) => (
|
||||
<WorkerSettingsSection
|
||||
defaultValues={{
|
||||
@ -101,7 +97,6 @@ const OPERATIONS_SECTIONS = [
|
||||
{
|
||||
id: 'logs',
|
||||
titleKey: 'Log Maintenance',
|
||||
descriptionKey: 'Configure log consumption settings',
|
||||
build: (settings: OperationsSettings) => (
|
||||
<LogSettingsSection
|
||||
defaultEnabled={Boolean(settings.LogConsumeEnabled)}
|
||||
@ -111,7 +106,6 @@ const OPERATIONS_SECTIONS = [
|
||||
{
|
||||
id: 'performance',
|
||||
titleKey: 'Performance',
|
||||
descriptionKey: 'Disk cache, system monitoring and performance stats',
|
||||
build: (settings: OperationsSettings) => (
|
||||
<PerformanceSection
|
||||
defaultValues={{
|
||||
@ -146,7 +140,6 @@ const OPERATIONS_SECTIONS = [
|
||||
{
|
||||
id: 'update-checker',
|
||||
titleKey: 'System maintenance',
|
||||
descriptionKey: 'Check for system updates',
|
||||
build: (
|
||||
_settings: OperationsSettings,
|
||||
currentVersion?: string | null,
|
||||
@ -178,3 +171,4 @@ export const OPERATIONS_DEFAULT_SECTION = operationsRegistry.defaultSection
|
||||
export const getOperationsSectionNavItems =
|
||||
operationsRegistry.getSectionNavItems
|
||||
export const getOperationsSectionContent = operationsRegistry.getSectionContent
|
||||
export const getOperationsSectionMeta = operationsRegistry.getSectionMeta
|
||||
|
||||
@ -35,6 +35,12 @@ import {
|
||||
import { Input } from '@/components/ui/input'
|
||||
import { Switch } from '@/components/ui/switch'
|
||||
import { Textarea } from '@/components/ui/textarea'
|
||||
import {
|
||||
SettingsForm,
|
||||
SettingsSwitchContent,
|
||||
SettingsSwitchItem,
|
||||
} from '../components/settings-form-layout'
|
||||
import { SettingsPageFormActions } from '../components/settings-page-context'
|
||||
import { SettingsSection } from '../components/settings-section'
|
||||
import { useUpdateOption } from '../hooks/use-update-option'
|
||||
import { RateLimitVisualEditor } from './rate-limit-visual-editor'
|
||||
@ -107,36 +113,34 @@ export function RateLimitSection({ defaultValues }: RateLimitSectionProps) {
|
||||
}
|
||||
|
||||
return (
|
||||
<SettingsSection
|
||||
title={t('Rate Limiting')}
|
||||
description={t(
|
||||
'Control request frequency to prevent abuse and manage system load.'
|
||||
)}
|
||||
>
|
||||
<SettingsSection title={t('Rate Limiting')}>
|
||||
<Form {...form}>
|
||||
<form onSubmit={form.handleSubmit(onSubmit)} className='space-y-6'>
|
||||
<SettingsForm onSubmit={form.handleSubmit(onSubmit)}>
|
||||
<SettingsPageFormActions
|
||||
onSave={form.handleSubmit(onSubmit)}
|
||||
isSaving={updateOption.isPending}
|
||||
saveLabel='Save rate limits'
|
||||
/>
|
||||
<FormField
|
||||
control={form.control}
|
||||
name='ModelRequestRateLimitEnabled'
|
||||
render={({ field }) => (
|
||||
<FormItem className='flex flex-row items-center justify-between rounded-lg border p-4'>
|
||||
<div className='space-y-0.5'>
|
||||
<FormLabel className='text-base'>
|
||||
{t('Enable rate limiting')}
|
||||
</FormLabel>
|
||||
<SettingsSwitchItem>
|
||||
<SettingsSwitchContent>
|
||||
<FormLabel>{t('Enable rate limiting')}</FormLabel>
|
||||
<FormDescription>
|
||||
{t(
|
||||
'This controls model request rate limiting. Web/API route throttling is configured by environment variables and may still return 429.'
|
||||
)}
|
||||
</FormDescription>
|
||||
</div>
|
||||
</SettingsSwitchContent>
|
||||
<FormControl>
|
||||
<Switch
|
||||
checked={field.value}
|
||||
onCheckedChange={field.onChange}
|
||||
/>
|
||||
</FormControl>
|
||||
</FormItem>
|
||||
</SettingsSwitchItem>
|
||||
)}
|
||||
/>
|
||||
|
||||
@ -306,11 +310,7 @@ export function RateLimitSection({ defaultValues }: RateLimitSectionProps) {
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
|
||||
<Button type='submit' disabled={updateOption.isPending}>
|
||||
{updateOption.isPending ? t('Saving...') : t('Save rate limits')}
|
||||
</Button>
|
||||
</form>
|
||||
</SettingsForm>
|
||||
</Form>
|
||||
</SettingsSection>
|
||||
)
|
||||
|
||||
@ -21,7 +21,6 @@ import * as z from 'zod'
|
||||
import { useForm } from 'react-hook-form'
|
||||
import { zodResolver } from '@hookform/resolvers/zod'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { Button } from '@/components/ui/button'
|
||||
import {
|
||||
Form,
|
||||
FormControl,
|
||||
@ -33,6 +32,12 @@ import {
|
||||
} from '@/components/ui/form'
|
||||
import { Switch } from '@/components/ui/switch'
|
||||
import { Textarea } from '@/components/ui/textarea'
|
||||
import {
|
||||
SettingsForm,
|
||||
SettingsSwitchContent,
|
||||
SettingsSwitchItem,
|
||||
} from '../components/settings-form-layout'
|
||||
import { SettingsPageFormActions } from '../components/settings-page-context'
|
||||
import { SettingsSection } from '../components/settings-section'
|
||||
import { useUpdateOption } from '../hooks/use-update-option'
|
||||
|
||||
@ -74,35 +79,35 @@ export function SensitiveWordsSection({
|
||||
}
|
||||
|
||||
return (
|
||||
<SettingsSection
|
||||
title={t('Sensitive Words')}
|
||||
description={t('Configure keyword filtering for prompts and responses.')}
|
||||
>
|
||||
<SettingsSection title={t('Sensitive Words')}>
|
||||
<Form {...form}>
|
||||
<form onSubmit={form.handleSubmit(onSubmit)} className='space-y-6'>
|
||||
<SettingsForm onSubmit={form.handleSubmit(onSubmit)}>
|
||||
<SettingsPageFormActions
|
||||
onSave={form.handleSubmit(onSubmit)}
|
||||
isSaving={updateOption.isPending}
|
||||
saveLabel='Save sensitive words'
|
||||
/>
|
||||
<div className='space-y-4'>
|
||||
<FormField
|
||||
control={form.control}
|
||||
name='CheckSensitiveEnabled'
|
||||
render={({ field }) => (
|
||||
<FormItem className='flex flex-row items-center justify-between rounded-lg border p-4'>
|
||||
<div className='space-y-0.5'>
|
||||
<FormLabel className='text-base'>
|
||||
{t('Enable filtering')}
|
||||
</FormLabel>
|
||||
<SettingsSwitchItem>
|
||||
<SettingsSwitchContent>
|
||||
<FormLabel>{t('Enable filtering')}</FormLabel>
|
||||
<FormDescription>
|
||||
{t(
|
||||
'Blocks messages when sensitive keywords are detected.'
|
||||
)}
|
||||
</FormDescription>
|
||||
</div>
|
||||
</SettingsSwitchContent>
|
||||
<FormControl>
|
||||
<Switch
|
||||
checked={field.value}
|
||||
onCheckedChange={field.onChange}
|
||||
/>
|
||||
</FormControl>
|
||||
</FormItem>
|
||||
</SettingsSwitchItem>
|
||||
)}
|
||||
/>
|
||||
|
||||
@ -110,24 +115,22 @@ export function SensitiveWordsSection({
|
||||
control={form.control}
|
||||
name='CheckSensitiveOnPromptEnabled'
|
||||
render={({ field }) => (
|
||||
<FormItem className='flex flex-row items-center justify-between rounded-lg border p-4'>
|
||||
<div className='space-y-0.5'>
|
||||
<FormLabel className='text-base'>
|
||||
{t('Inspect user prompts')}
|
||||
</FormLabel>
|
||||
<SettingsSwitchItem>
|
||||
<SettingsSwitchContent>
|
||||
<FormLabel>{t('Inspect user prompts')}</FormLabel>
|
||||
<FormDescription>
|
||||
{t(
|
||||
'When enabled, prompts are scanned before reaching upstream models.'
|
||||
)}
|
||||
</FormDescription>
|
||||
</div>
|
||||
</SettingsSwitchContent>
|
||||
<FormControl>
|
||||
<Switch
|
||||
checked={field.value}
|
||||
onCheckedChange={field.onChange}
|
||||
/>
|
||||
</FormControl>
|
||||
</FormItem>
|
||||
</SettingsSwitchItem>
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
@ -154,13 +157,7 @@ export function SensitiveWordsSection({
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
|
||||
<Button type='submit' disabled={updateOption.isPending}>
|
||||
{updateOption.isPending
|
||||
? t('Saving...')
|
||||
: t('Save sensitive words')}
|
||||
</Button>
|
||||
</form>
|
||||
</SettingsForm>
|
||||
</Form>
|
||||
</SettingsSection>
|
||||
)
|
||||
|
||||
@ -22,7 +22,6 @@ import { useForm } from 'react-hook-form'
|
||||
import { zodResolver } from '@hookform/resolvers/zod'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { toast } from 'sonner'
|
||||
import { Button } from '@/components/ui/button'
|
||||
import {
|
||||
Form,
|
||||
FormControl,
|
||||
@ -43,6 +42,12 @@ import {
|
||||
} from '@/components/ui/select'
|
||||
import { Switch } from '@/components/ui/switch'
|
||||
import { Textarea } from '@/components/ui/textarea'
|
||||
import {
|
||||
SettingsForm,
|
||||
SettingsSwitchContent,
|
||||
SettingsSwitchItem,
|
||||
} from '../components/settings-form-layout'
|
||||
import { SettingsPageFormActions } from '../components/settings-page-context'
|
||||
import { SettingsSection } from '../components/settings-section'
|
||||
import { useUpdateOption } from '../hooks/use-update-option'
|
||||
|
||||
@ -198,34 +203,32 @@ export function SSRFSection({ defaultValues }: SSRFSectionProps) {
|
||||
const ipFilterMode = form.watch('fetch_setting.ip_filter_mode')
|
||||
|
||||
return (
|
||||
<SettingsSection
|
||||
title={t('SSRF Protection')}
|
||||
description={t(
|
||||
'Prevent server-side request forgery attacks by controlling outbound requests.'
|
||||
)}
|
||||
>
|
||||
<SettingsSection title={t('SSRF Protection')}>
|
||||
<Form {...form}>
|
||||
<form onSubmit={form.handleSubmit(onSubmit)} className='space-y-6'>
|
||||
<SettingsForm onSubmit={form.handleSubmit(onSubmit)}>
|
||||
<SettingsPageFormActions
|
||||
onSave={form.handleSubmit(onSubmit)}
|
||||
isSaving={updateOption.isPending}
|
||||
saveLabel='Save SSRF settings'
|
||||
/>
|
||||
<FormField
|
||||
control={form.control}
|
||||
name='fetch_setting.enable_ssrf_protection'
|
||||
render={({ field }) => (
|
||||
<FormItem className='flex flex-row items-center justify-between rounded-lg border p-4'>
|
||||
<div className='space-y-0.5'>
|
||||
<FormLabel className='text-base'>
|
||||
{t('Enable SSRF Protection')}
|
||||
</FormLabel>
|
||||
<SettingsSwitchItem>
|
||||
<SettingsSwitchContent>
|
||||
<FormLabel>{t('Enable SSRF Protection')}</FormLabel>
|
||||
<FormDescription>
|
||||
{t('Prevent server-side request forgery attacks')}
|
||||
</FormDescription>
|
||||
</div>
|
||||
</SettingsSwitchContent>
|
||||
<FormControl>
|
||||
<Switch
|
||||
checked={field.value}
|
||||
onCheckedChange={field.onChange}
|
||||
/>
|
||||
</FormControl>
|
||||
</FormItem>
|
||||
</SettingsSwitchItem>
|
||||
)}
|
||||
/>
|
||||
|
||||
@ -233,24 +236,22 @@ export function SSRFSection({ defaultValues }: SSRFSectionProps) {
|
||||
control={form.control}
|
||||
name='fetch_setting.allow_private_ip'
|
||||
render={({ field }) => (
|
||||
<FormItem className='flex flex-row items-center justify-between rounded-lg border p-4'>
|
||||
<div className='space-y-0.5'>
|
||||
<FormLabel className='text-base'>
|
||||
{t('Allow Private IPs')}
|
||||
</FormLabel>
|
||||
<SettingsSwitchItem>
|
||||
<SettingsSwitchContent>
|
||||
<FormLabel>{t('Allow Private IPs')}</FormLabel>
|
||||
<FormDescription>
|
||||
{t(
|
||||
'Allow requests to private IP ranges (10.0.0.0/8, 172.16.0.0/12, 192.168.0.0/16)'
|
||||
)}
|
||||
</FormDescription>
|
||||
</div>
|
||||
</SettingsSwitchContent>
|
||||
<FormControl>
|
||||
<Switch
|
||||
checked={field.value}
|
||||
onCheckedChange={field.onChange}
|
||||
/>
|
||||
</FormControl>
|
||||
</FormItem>
|
||||
</SettingsSwitchItem>
|
||||
)}
|
||||
/>
|
||||
|
||||
@ -408,9 +409,9 @@ export function SSRFSection({ defaultValues }: SSRFSectionProps) {
|
||||
control={form.control}
|
||||
name='fetch_setting.apply_ip_filter_for_domain'
|
||||
render={({ field }) => (
|
||||
<FormItem className='flex flex-row items-center justify-between rounded-lg border p-4'>
|
||||
<div className='space-y-0.5'>
|
||||
<FormLabel className='text-base'>
|
||||
<SettingsSwitchItem>
|
||||
<SettingsSwitchContent>
|
||||
<FormLabel>
|
||||
{t('Apply IP Filter to Resolved Domains')}
|
||||
</FormLabel>
|
||||
<FormDescription>
|
||||
@ -418,21 +419,17 @@ export function SSRFSection({ defaultValues }: SSRFSectionProps) {
|
||||
'Check resolved IPs against IP filters even when accessing by domain'
|
||||
)}
|
||||
</FormDescription>
|
||||
</div>
|
||||
</SettingsSwitchContent>
|
||||
<FormControl>
|
||||
<Switch
|
||||
checked={field.value}
|
||||
onCheckedChange={field.onChange}
|
||||
/>
|
||||
</FormControl>
|
||||
</FormItem>
|
||||
</SettingsSwitchItem>
|
||||
)}
|
||||
/>
|
||||
|
||||
<Button type='submit' disabled={updateOption.isPending}>
|
||||
{updateOption.isPending ? t('Saving...') : t('Save SSRF settings')}
|
||||
</Button>
|
||||
</form>
|
||||
</SettingsForm>
|
||||
</Form>
|
||||
</SettingsSection>
|
||||
)
|
||||
|
||||
@ -21,6 +21,7 @@ import type { SecuritySettings } from '../types'
|
||||
import {
|
||||
SECURITY_DEFAULT_SECTION,
|
||||
getSecuritySectionContent,
|
||||
getSecuritySectionMeta,
|
||||
} from './section-registry.tsx'
|
||||
|
||||
const defaultSecuritySettings: SecuritySettings = {
|
||||
@ -49,6 +50,7 @@ export function SecuritySettings() {
|
||||
defaultSettings={defaultSecuritySettings}
|
||||
defaultSection={SECURITY_DEFAULT_SECTION}
|
||||
getSectionContent={getSecuritySectionContent}
|
||||
getSectionMeta={getSecuritySectionMeta}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
@ -26,7 +26,6 @@ const SECURITY_SECTIONS = [
|
||||
{
|
||||
id: 'rate-limit',
|
||||
titleKey: 'Rate Limiting',
|
||||
descriptionKey: 'Configure model request rate limiting',
|
||||
build: (settings: SecuritySettings) => (
|
||||
<RateLimitSection
|
||||
defaultValues={{
|
||||
@ -44,7 +43,6 @@ const SECURITY_SECTIONS = [
|
||||
{
|
||||
id: 'sensitive-words',
|
||||
titleKey: 'Sensitive Words',
|
||||
descriptionKey: 'Configure sensitive word filtering',
|
||||
build: (settings: SecuritySettings) => (
|
||||
<SensitiveWordsSection
|
||||
defaultValues={{
|
||||
@ -58,7 +56,6 @@ const SECURITY_SECTIONS = [
|
||||
{
|
||||
id: 'ssrf',
|
||||
titleKey: 'SSRF Protection',
|
||||
descriptionKey: 'Configure SSRF (Server-Side Request Forgery) protection',
|
||||
build: (settings: SecuritySettings) => (
|
||||
<SSRFSection
|
||||
defaultValues={{
|
||||
@ -98,3 +95,4 @@ export const SECURITY_SECTION_IDS = securityRegistry.sectionIds
|
||||
export const SECURITY_DEFAULT_SECTION = securityRegistry.defaultSection
|
||||
export const getSecuritySectionNavItems = securityRegistry.getSectionNavItems
|
||||
export const getSecuritySectionContent = securityRegistry.getSectionContent
|
||||
export const getSecuritySectionMeta = securityRegistry.getSectionMeta
|
||||
|
||||
@ -21,6 +21,7 @@ import type { SiteSettings } from '../types'
|
||||
import {
|
||||
SITE_DEFAULT_SECTION,
|
||||
getSiteSectionContent,
|
||||
getSiteSectionMeta,
|
||||
} from './section-registry.tsx'
|
||||
|
||||
const defaultSiteSettings: SiteSettings = {
|
||||
@ -45,6 +46,7 @@ export function SiteSettings() {
|
||||
defaultSettings={defaultSiteSettings}
|
||||
defaultSection={SITE_DEFAULT_SECTION}
|
||||
getSectionContent={getSiteSectionContent}
|
||||
getSectionMeta={getSiteSectionMeta}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
@ -33,7 +33,6 @@ const SITE_SECTIONS = [
|
||||
{
|
||||
id: 'system-info',
|
||||
titleKey: 'System Information',
|
||||
descriptionKey: 'Configure basic system information and branding',
|
||||
build: (settings: SiteSettings) => (
|
||||
<SystemInfoSection
|
||||
defaultValues={{
|
||||
@ -57,7 +56,6 @@ const SITE_SECTIONS = [
|
||||
{
|
||||
id: 'notice',
|
||||
titleKey: 'System Notice',
|
||||
descriptionKey: 'Configure system maintenance notice',
|
||||
build: (settings: SiteSettings) => (
|
||||
<NoticeSection defaultValue={settings.Notice ?? ''} />
|
||||
),
|
||||
@ -65,7 +63,6 @@ const SITE_SECTIONS = [
|
||||
{
|
||||
id: 'header-navigation',
|
||||
titleKey: 'Header navigation',
|
||||
descriptionKey: 'Configure header navigation modules',
|
||||
build: (settings: SiteSettings) => {
|
||||
const headerNavConfig = parseHeaderNavModules(settings.HeaderNavModules)
|
||||
const headerNavSerialized = serializeHeaderNavModules(headerNavConfig)
|
||||
@ -80,7 +77,6 @@ const SITE_SECTIONS = [
|
||||
{
|
||||
id: 'sidebar-modules',
|
||||
titleKey: 'Sidebar modules',
|
||||
descriptionKey: 'Configure sidebar modules for admin',
|
||||
build: (settings: SiteSettings) => {
|
||||
const sidebarConfig = parseSidebarModulesAdmin(
|
||||
settings.SidebarModulesAdmin
|
||||
@ -109,3 +105,4 @@ export const SITE_SECTION_IDS = siteRegistry.sectionIds
|
||||
export const SITE_DEFAULT_SECTION = siteRegistry.defaultSection
|
||||
export const getSiteSectionNavItems = siteRegistry.getSectionNavItems
|
||||
export const getSiteSectionContent = siteRegistry.getSectionContent
|
||||
export const getSiteSectionMeta = siteRegistry.getSectionMeta
|
||||
|
||||
@ -25,7 +25,6 @@ import type { TFunction } from 'i18next'
|
||||
export type SectionDefinition<TSettings, TExtraArgs extends unknown[] = []> = {
|
||||
id: string
|
||||
titleKey: string
|
||||
descriptionKey: string
|
||||
build: (settings: TSettings, ...extraArgs: TExtraArgs) => ReactNode
|
||||
}
|
||||
|
||||
@ -82,9 +81,13 @@ export function createSectionRegistry<
|
||||
settings: TSettings,
|
||||
...extraArgs: TExtraArgs
|
||||
) {
|
||||
return getSectionMeta(sectionId).build(settings, ...extraArgs)
|
||||
}
|
||||
|
||||
function getSectionMeta(sectionId: SectionId) {
|
||||
const section =
|
||||
sections.find((item) => item.id === sectionId) ?? sections[0]
|
||||
return section.build(settings, ...extraArgs)
|
||||
return section
|
||||
}
|
||||
|
||||
return {
|
||||
@ -92,5 +95,6 @@ export function createSectionRegistry<
|
||||
defaultSection,
|
||||
getSectionNavItems,
|
||||
getSectionContent,
|
||||
getSectionMeta,
|
||||
}
|
||||
}
|
||||
|
||||
11
web/default/src/features/usage-logs/index.tsx
vendored
11
web/default/src/features/usage-logs/index.tsx
vendored
@ -39,21 +39,15 @@ import {
|
||||
const route = getRouteApi('/_authenticated/usage-logs/$section')
|
||||
const TASK_LOG_SECTIONS = ['drawing', 'task'] as const
|
||||
|
||||
const SECTION_META: Record<
|
||||
UsageLogsSectionId,
|
||||
{ titleKey: string; descriptionKey: string }
|
||||
> = {
|
||||
const SECTION_META: Record<UsageLogsSectionId, { titleKey: string }> = {
|
||||
common: {
|
||||
titleKey: 'Common Logs',
|
||||
descriptionKey: 'View and manage your API usage logs',
|
||||
},
|
||||
drawing: {
|
||||
titleKey: 'Drawing Logs',
|
||||
descriptionKey: 'View and manage your drawing logs',
|
||||
},
|
||||
task: {
|
||||
titleKey: 'Task Logs',
|
||||
descriptionKey: 'View and manage your task logs',
|
||||
},
|
||||
}
|
||||
|
||||
@ -120,9 +114,6 @@ function UsageLogsContent() {
|
||||
<SectionPageLayout.Title>
|
||||
{t(pageMeta.titleKey)}
|
||||
</SectionPageLayout.Title>
|
||||
<SectionPageLayout.Description>
|
||||
{t(pageMeta.descriptionKey)}
|
||||
</SectionPageLayout.Description>
|
||||
<SectionPageLayout.Content>
|
||||
<div className='space-y-4'>
|
||||
{showTaskSwitcher && (
|
||||
|
||||
@ -25,19 +25,16 @@ const USAGE_LOGS_SECTIONS = [
|
||||
{
|
||||
id: 'common',
|
||||
titleKey: 'Common Logs',
|
||||
descriptionKey: 'View and manage your API usage logs',
|
||||
build: () => null, // Content is rendered directly in the page component
|
||||
},
|
||||
{
|
||||
id: 'drawing',
|
||||
titleKey: 'Drawing Logs',
|
||||
descriptionKey: 'View and manage your drawing logs',
|
||||
build: () => null, // Content is rendered directly in the page component
|
||||
},
|
||||
{
|
||||
id: 'task',
|
||||
titleKey: 'Task Logs',
|
||||
descriptionKey: 'View and manage your task logs',
|
||||
build: () => null, // Content is rendered directly in the page component
|
||||
},
|
||||
] as const
|
||||
|
||||
3
web/default/src/features/users/index.tsx
vendored
3
web/default/src/features/users/index.tsx
vendored
@ -32,9 +32,6 @@ function UsersContent() {
|
||||
<>
|
||||
<SectionPageLayout>
|
||||
<SectionPageLayout.Title>{t('Users')}</SectionPageLayout.Title>
|
||||
<SectionPageLayout.Description>
|
||||
{t('Manage users and their permissions')}
|
||||
</SectionPageLayout.Description>
|
||||
<SectionPageLayout.Actions>
|
||||
<UsersPrimaryButtons />
|
||||
</SectionPageLayout.Actions>
|
||||
|
||||
3
web/default/src/features/wallet/index.tsx
vendored
3
web/default/src/features/wallet/index.tsx
vendored
@ -261,9 +261,6 @@ export function Wallet(props: WalletProps) {
|
||||
<>
|
||||
<SectionPageLayout>
|
||||
<SectionPageLayout.Title>{t('Wallet')}</SectionPageLayout.Title>
|
||||
<SectionPageLayout.Description>
|
||||
{t('Manage your balance and payment methods')}
|
||||
</SectionPageLayout.Description>
|
||||
<SectionPageLayout.Content>
|
||||
<div className='mx-auto flex w-full max-w-7xl flex-col gap-4 sm:gap-5'>
|
||||
<WalletStatsCard user={user} loading={userLoading} />
|
||||
|
||||
@ -11,31 +11,31 @@
|
||||
"file": "fr.json",
|
||||
"missingCount": 0,
|
||||
"extrasCount": 0,
|
||||
"untranslatedCount": 21
|
||||
"untranslatedCount": 0
|
||||
},
|
||||
"ja": {
|
||||
"file": "ja.json",
|
||||
"missingCount": 0,
|
||||
"extrasCount": 0,
|
||||
"untranslatedCount": 120
|
||||
"untranslatedCount": 0
|
||||
},
|
||||
"ru": {
|
||||
"file": "ru.json",
|
||||
"missingCount": 0,
|
||||
"extrasCount": 0,
|
||||
"untranslatedCount": 135
|
||||
"untranslatedCount": 0
|
||||
},
|
||||
"vi": {
|
||||
"file": "vi.json",
|
||||
"missingCount": 0,
|
||||
"extrasCount": 0,
|
||||
"untranslatedCount": 23
|
||||
"untranslatedCount": 0
|
||||
},
|
||||
"zh": {
|
||||
"file": "zh.json",
|
||||
"missingCount": 0,
|
||||
"extrasCount": 0,
|
||||
"untranslatedCount": 99
|
||||
"untranslatedCount": 0
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,23 +0,0 @@
|
||||
{
|
||||
"acknowledge the related legal risks": "acknowledge the related legal risks",
|
||||
"Confirm and enable": "Confirm and enable",
|
||||
"Each tier supports up to 2 conditions. The last tier without conditions is the fallback.": "Each tier supports up to 2 conditions. The last tier without conditions is the fallback.",
|
||||
"Failed to confirm compliance": "Failed to confirm compliance",
|
||||
"I have read and understood the above compliance reminder": "I have read and understood the above compliance reminder",
|
||||
"I have read and understood the above compliance reminder, acknowledge the related legal risks, and confirm that I bear legal responsibility arising from deployment, operation, and charging behavior.": "I have read and understood the above compliance reminder, acknowledge the related legal risks, and confirm that I bear legal responsibility arising from deployment, operation, and charging behavior.",
|
||||
"If you provide generative AI services to the public in mainland China, you will fulfill legal obligations including filing, security assessment, content safety, complaint handling, generated content labeling, log retention, and personal information protection.": "If you provide generative AI services to the public in mainland China, you will fulfill legal obligations including filing, security assessment, content safety, complaint handling, generated content labeling, log retention, and personal information protection.",
|
||||
"operation and charging behavior": "operation and charging behavior",
|
||||
"Payment, redemption codes, subscription plans, and invitation rewards are locked until the root administrator confirms the compliance terms.": "Payment, redemption codes, subscription plans, and invitation rewards are locked until the root administrator confirms the compliance terms.",
|
||||
"Please type the following text to confirm:": "Please type the following text to confirm:",
|
||||
"Redemption codes are disabled until the administrator confirms compliance terms.": "Redemption codes are disabled until the administrator confirms compliance terms.",
|
||||
"Referral reward transfer is disabled until the administrator confirms compliance terms.": "Referral reward transfer is disabled until the administrator confirms compliance terms.",
|
||||
"Subscription plan creation and changes are locked until the administrator confirms compliance terms in Payment Gateway settings.": "Subscription plan creation and changes are locked until the administrator confirms compliance terms in Payment Gateway settings.",
|
||||
"The entered text does not match the required text.": "The entered text does not match the required text.",
|
||||
"This confirmation unlocks payment, redemption code, subscription plan, and invitation reward features. Please read the statements carefully.": "This confirmation unlocks payment, redemption code, subscription plan, and invitation reward features. Please read the statements carefully.",
|
||||
"Type the confirmation text here": "Type the confirmation text here",
|
||||
"You commit not to use this system to implement, assist with, or indirectly implement acts that violate applicable laws and regulations, regulatory requirements, platform rules, public interests, or the lawful rights and interests of third parties.": "You commit not to use this system to implement, assist with, or indirectly implement acts that violate applicable laws and regulations, regulatory requirements, platform rules, public interests, or the lawful rights and interests of third parties.",
|
||||
"You commit to using upstream APIs, accounts, keys, quotas, and service capabilities only within the scope of lawful authorization obtained from upstream service providers, model service providers, or relevant rights holders, and will not conduct unauthorized resale, trafficking, distribution, or other non-compliant commercialization.": "You commit to using upstream APIs, accounts, keys, quotas, and service capabilities only within the scope of lawful authorization obtained from upstream service providers, model service providers, or relevant rights holders, and will not conduct unauthorized resale, trafficking, distribution, or other non-compliant commercialization.",
|
||||
"You have legally obtained authorization for the connected model APIs, accounts, keys, and quotas.": "You have legally obtained authorization for the connected model APIs, accounts, keys, and quotas.",
|
||||
"You understand and independently bear legal responsibility arising from deployment, operation, and charging behavior.": "You understand and independently bear legal responsibility arising from deployment, operation, and charging behavior.",
|
||||
"You understand this compliance reminder is only for risk notice and does not constitute legal advice, a compliance review conclusion, or a guarantee of the legality of your use of this system; you should consult professional legal or compliance advisors based on your actual business scenario.": "You understand this compliance reminder is only for risk notice and does not constitute legal advice, a compliance review conclusion, or a guarantee of the legality of your use of this system; you should consult professional legal or compliance advisors based on your actual business scenario."
|
||||
}
|
||||
@ -1,122 +0,0 @@
|
||||
{
|
||||
"[{\"ChatGPT\":\"https://chat.openai.com\"},{\"Lobe Chat\":\"https://chat-preview.lobehub.com/?settings={...}\"}]": "[{\"ChatGPT\":\"https://chat.openai.com\"},{\"Lobe Chat\":\"https://chat-preview.lobehub.com/?settings={...}\"}]",
|
||||
"[{\"name\":\"支付宝\",\"type\":\"alipay\",\"color\":\"#1677FF\"}]": "[{\"name\":\"Alipay\",\"type\":\"alipay\",\"color\":\"#1677FF\"}]",
|
||||
"/status/": "/status/",
|
||||
"/your/endpoint": "/your/endpoint",
|
||||
"acknowledge the related legal risks": "acknowledge the related legal risks",
|
||||
"AIGC2D": "AIGC2D",
|
||||
"Alipay": "Alipay",
|
||||
"Anthropic": "Anthropic",
|
||||
"API URL": "API URL",
|
||||
"API2GPT": "API2GPT",
|
||||
"AZURE_OPENAI_ENDPOINT *": "AZURE_OPENAI_ENDPOINT *",
|
||||
"checkout.session.completed": "checkout.session.completed",
|
||||
"checkout.session.expired": "checkout.session.expired",
|
||||
"Claude": "Claude",
|
||||
"Cloudflare": "Cloudflare",
|
||||
"Cohere": "Cohere",
|
||||
"Compliance confirmation required": "Compliance confirmation required",
|
||||
"Compliance confirmed": "Compliance confirmed",
|
||||
"Compliance confirmed successfully": "Compliance confirmed successfully",
|
||||
"Confirm and enable": "Confirm and enable",
|
||||
"Confirm compliance": "Confirm compliance",
|
||||
"Confirm compliance terms": "Confirm compliance terms",
|
||||
"confirm that I bear legal responsibility arising from deployment": "confirm that I bear legal responsibility arising from deployment",
|
||||
"Confirmed at {{time}} by user #{{userId}}": "Confirmed at {{time}} by user #{{userId}}",
|
||||
"DeepSeek": "DeepSeek",
|
||||
"Discord": "Discord",
|
||||
"DoubaoVideo": "DoubaoVideo",
|
||||
"Each tier supports up to 2 conditions. The last tier without conditions is the fallback.": "Each tier supports up to 2 conditions. The last tier without conditions is the fallback.",
|
||||
"edit_this": "edit_this",
|
||||
"Failed to confirm compliance": "Failed to confirm compliance",
|
||||
"FastGPT": "FastGPT",
|
||||
"footer.columns.related.links.midjourney": "Midjourney-Proxy",
|
||||
"footer.columns.related.links.newApiKeyTool": "new-api-key-tool",
|
||||
"Gemini": "Gemini",
|
||||
"Gemini Image 4K": "Gemini Image 4K",
|
||||
"GitHub": "GitHub",
|
||||
"gpt-3.5-turbo": "gpt-3.5-turbo",
|
||||
"gpt-3.5-turbo-0125": "gpt-3.5-turbo-0125",
|
||||
"https://api.day.app/yourkey/{{title}}/{{content}}": "https://api.day.app/yourkey/{{title}}/{{content}}",
|
||||
"https://api.example.com": "https://api.example.com",
|
||||
"https://ark.ap-southeast.bytepluses.com": "https://ark.ap-southeast.bytepluses.com",
|
||||
"https://ark.cn-beijing.volces.com": "https://ark.cn-beijing.volces.com",
|
||||
"https://cloud.siliconflow.cn/i/hij0YNTZ": "https://cloud.siliconflow.cn/i/hij0YNTZ",
|
||||
"https://docs.example.com": "https://docs.example.com",
|
||||
"https://example.com": "https://example.com",
|
||||
"https://example.com/logo.png": "https://example.com/logo.png",
|
||||
"https://example.com/qr-code.png": "https://example.com/qr-code.png",
|
||||
"https://example.com/topup": "https://example.com/topup",
|
||||
"https://example.com/webhook": "https://example.com/webhook",
|
||||
"https://gateway.example.com": "https://gateway.example.com",
|
||||
"https://github.com/QuantumNous/new-api": "https://github.com/QuantumNous/new-api",
|
||||
"https://gotify.example.com": "https://gotify.example.com",
|
||||
"https://pay.example.com": "https://pay.example.com",
|
||||
"https://provider.com/.well-known/openid-configuration": "https://provider.com/.well-known/openid-configuration",
|
||||
"https://status.example.com": "https://status.example.com",
|
||||
"https://wechat-server.example.com": "https://wechat-server.example.com",
|
||||
"https://worker.example.workers.dev": "https://worker.example.workers.dev",
|
||||
"https://your-server.example.com": "https://your-server.example.com",
|
||||
"I have read and understood the above compliance reminder": "I have read and understood the above compliance reminder",
|
||||
"I have read and understood the above compliance reminder, acknowledge the related legal risks, and confirm that I bear legal responsibility arising from deployment, operation, and charging behavior.": "I have read and understood the above compliance reminder, acknowledge the related legal risks, and confirm that I bear legal responsibility arising from deployment, operation, and charging behavior.",
|
||||
"If you provide generative AI services to the public in mainland China, you will fulfill legal obligations including filing, security assessment, content safety, complaint handling, generated content labeling, log retention, and personal information protection.": "If you provide generative AI services to the public in mainland China, you will fulfill legal obligations including filing, security assessment, content safety, complaint handling, generated content labeling, log retention, and personal information protection.",
|
||||
"Jimeng": "Jimeng",
|
||||
"JustSong": "JustSong",
|
||||
"LingYiWanWu": "LingYiWanWu",
|
||||
"LinuxDO": "LinuxDO",
|
||||
"Midjourney": "Midjourney",
|
||||
"MidjourneyPlus": "MidjourneyPlus",
|
||||
"MiniMax": "MiniMax",
|
||||
"Mistral": "Mistral",
|
||||
"MokaAI": "MokaAI",
|
||||
"Moonshot": "Moonshot",
|
||||
"my-status": "my-status",
|
||||
"name@example.com": "name@example.com",
|
||||
"NewAPI": "NewAPI",
|
||||
"Non-zero invitation rewards require compliance confirmation in Payment Gateway settings.": "Non-zero invitation rewards require compliance confirmation in Payment Gateway settings.",
|
||||
"noreply@example.com": "noreply@example.com",
|
||||
"OhMyGPT": "OhMyGPT",
|
||||
"Ollama": "Ollama",
|
||||
"OpenAI": "OpenAI",
|
||||
"OpenAIMax": "OpenAIMax",
|
||||
"OpenRouter": "OpenRouter",
|
||||
"operation and charging behavior": "operation and charging behavior",
|
||||
"org-...": "org-...",
|
||||
"Passkey": "Passkey",
|
||||
"Payment, redemption codes, subscription plans, and invitation rewards are locked until the root administrator confirms the compliance terms.": "Payment, redemption codes, subscription plans, and invitation rewards are locked until the root administrator confirms the compliance terms.",
|
||||
"Perplexity": "Perplexity",
|
||||
"Please type the following text to confirm:": "Please type the following text to confirm:",
|
||||
"price_xxx": "price_xxx",
|
||||
"QuantumNous": "QuantumNous",
|
||||
"Redemption codes are disabled until the administrator confirms compliance terms.": "Redemption codes are disabled until the administrator confirms compliance terms.",
|
||||
"Referral reward transfer is disabled until the administrator confirms compliance terms.": "Referral reward transfer is disabled until the administrator confirms compliance terms.",
|
||||
"Replicate": "Replicate",
|
||||
"SiliconFlow": "SiliconFlow",
|
||||
"smtp.example.com": "smtp.example.com",
|
||||
"socks5://user:pass@host:port": "socks5://user:pass@host:port",
|
||||
"Stripe": "Stripe",
|
||||
"Submodel": "Submodel",
|
||||
"Subscription plan creation and changes are locked until the administrator confirms compliance terms in Payment Gateway settings.": "Subscription plan creation and changes are locked until the administrator confirms compliance terms in Payment Gateway settings.",
|
||||
"SunoAPI": "SunoAPI",
|
||||
"Telegram": "Telegram",
|
||||
"The entered text does not match the required text.": "The entered text does not match the required text.",
|
||||
"This confirmation unlocks payment, redemption code, subscription plan, and invitation reward features. Please read the statements carefully.": "This confirmation unlocks payment, redemption code, subscription plan, and invitation reward features. Please read the statements carefully.",
|
||||
"Token prices": "Token prices",
|
||||
"TTFT P50": "TTFT P50",
|
||||
"TTFT P95": "TTFT P95",
|
||||
"TTFT P99": "TTFT P99",
|
||||
"Type the confirmation text here": "Type the confirmation text here",
|
||||
"Vertex AI": "Vertex AI",
|
||||
"VolcEngine": "VolcEngine",
|
||||
"Webhook URL": "Webhook URL",
|
||||
"Webhook URL:": "Webhook URL:",
|
||||
"WeChat Pay": "WeChat Pay",
|
||||
"whsec_xxx": "whsec_xxx",
|
||||
"Xinference": "Xinference",
|
||||
"Xunfei": "Xunfei",
|
||||
"You commit not to use this system to implement, assist with, or indirectly implement acts that violate applicable laws and regulations, regulatory requirements, platform rules, public interests, or the lawful rights and interests of third parties.": "You commit not to use this system to implement, assist with, or indirectly implement acts that violate applicable laws and regulations, regulatory requirements, platform rules, public interests, or the lawful rights and interests of third parties.",
|
||||
"You commit to using upstream APIs, accounts, keys, quotas, and service capabilities only within the scope of lawful authorization obtained from upstream service providers, model service providers, or relevant rights holders, and will not conduct unauthorized resale, trafficking, distribution, or other non-compliant commercialization.": "You commit to using upstream APIs, accounts, keys, quotas, and service capabilities only within the scope of lawful authorization obtained from upstream service providers, model service providers, or relevant rights holders, and will not conduct unauthorized resale, trafficking, distribution, or other non-compliant commercialization.",
|
||||
"You have legally obtained authorization for the connected model APIs, accounts, keys, and quotas.": "You have legally obtained authorization for the connected model APIs, accounts, keys, and quotas.",
|
||||
"You understand and independently bear legal responsibility arising from deployment, operation, and charging behavior.": "You understand and independently bear legal responsibility arising from deployment, operation, and charging behavior.",
|
||||
"You understand this compliance reminder is only for risk notice and does not constitute legal advice, a compliance review conclusion, or a guarantee of the legality of your use of this system; you should consult professional legal or compliance advisors based on your actual business scenario.": "You understand this compliance reminder is only for risk notice and does not constitute legal advice, a compliance review conclusion, or a guarantee of the legality of your use of this system; you should consult professional legal or compliance advisors based on your actual business scenario."
|
||||
}
|
||||
@ -1,137 +0,0 @@
|
||||
{
|
||||
"\"default\": \"us-central1\", \"claude-3-5-sonnet-20240620\": \"europe-west1\"": "\"default\": \"us-central1\", \"claude-3-5-sonnet-20240620\": \"europe-west1\"",
|
||||
"[{\"ChatGPT\":\"https://chat.openai.com\"},{\"Lobe Chat\":\"https://chat-preview.lobehub.com/?settings={...}\"}]": "[{\"ChatGPT\":\"https://chat.openai.com\"},{\"Lobe Chat\":\"https://chat-preview.lobehub.com/?settings={...}\"}]",
|
||||
"[{\"name\":\"支付宝\",\"type\":\"alipay\",\"color\":\"#1677FF\"}]": "[{\"name\":\"Alipay\",\"type\":\"alipay\",\"color\":\"#1677FF\"}]",
|
||||
"{\"original-model\": \"replacement-model\"}": "{\"original-model\": \"replacement-model\"}",
|
||||
"/status/": "/status/",
|
||||
"/your/endpoint": "/your/endpoint",
|
||||
"AccessKey / SecretAccessKey": "AccessKey / SecretAccessKey",
|
||||
"acknowledge the related legal risks": "acknowledge the related legal risks",
|
||||
"AI Proxy": "AI Proxy",
|
||||
"AIGC2D": "AIGC2D",
|
||||
"Alipay": "Alipay",
|
||||
"All conditions must match before this tier is used.": "All conditions must match before this tier is used.",
|
||||
"Anthropic": "Anthropic",
|
||||
"API2GPT": "API2GPT",
|
||||
"AZURE_OPENAI_ENDPOINT *": "AZURE_OPENAI_ENDPOINT *",
|
||||
"Baidu V2": "Baidu V2",
|
||||
"Base input and output token prices for this tier.": "Base input and output token prices for this tier.",
|
||||
"Cache pricing": "Cache pricing",
|
||||
"checkout.session.completed": "checkout.session.completed",
|
||||
"checkout.session.expired": "checkout.session.expired",
|
||||
"Cloudflare": "Cloudflare",
|
||||
"Cohere": "Cohere",
|
||||
"Compliance confirmation required": "Compliance confirmation required",
|
||||
"Compliance confirmed": "Compliance confirmed",
|
||||
"Compliance confirmed successfully": "Compliance confirmed successfully",
|
||||
"Confirm and enable": "Confirm and enable",
|
||||
"Confirm compliance": "Confirm compliance",
|
||||
"Confirm compliance terms": "Confirm compliance terms",
|
||||
"confirm that I bear legal responsibility arising from deployment": "confirm that I bear legal responsibility arising from deployment",
|
||||
"Confirmed at {{time}} by user #{{userId}}": "Confirmed at {{time}} by user #{{userId}}",
|
||||
"Core pricing": "Core pricing",
|
||||
"DeepSeek": "DeepSeek",
|
||||
"Discord": "Discord",
|
||||
"DoubaoVideo": "DoubaoVideo",
|
||||
"Each tier supports up to 2 conditions. The last tier without conditions is the fallback.": "Each tier supports up to 2 conditions. The last tier without conditions is the fallback.",
|
||||
"example.com blocked-site.com": "example.com blocked-site.com",
|
||||
"example.com company.com": "example.com company.com",
|
||||
"Failed to confirm compliance": "Failed to confirm compliance",
|
||||
"Fallback tier": "Fallback tier",
|
||||
"FastGPT": "FastGPT",
|
||||
"footer.columns.related.links.midjourney": "Midjourney-Proxy",
|
||||
"footer.columns.related.links.newApiKeyTool": "new-api-key-tool",
|
||||
"Gemini": "Gemini",
|
||||
"Gemini Image 4K": "Gemini Image 4K",
|
||||
"GitHub": "GitHub",
|
||||
"gpt-3.5-turbo": "gpt-3.5-turbo",
|
||||
"gpt-3.5-turbo-0125": "gpt-3.5-turbo-0125",
|
||||
"https://api.day.app/yourkey/{{title}}/{{content}}": "https://api.day.app/yourkey/{{title}}/{{content}}",
|
||||
"https://api.example.com": "https://api.example.com",
|
||||
"https://ark.ap-southeast.bytepluses.com": "https://ark.ap-southeast.bytepluses.com",
|
||||
"https://ark.cn-beijing.volces.com": "https://ark.cn-beijing.volces.com",
|
||||
"https://cloud.siliconflow.cn/i/hij0YNTZ": "https://cloud.siliconflow.cn/i/hij0YNTZ",
|
||||
"https://docs.example.com": "https://docs.example.com",
|
||||
"https://example.com": "https://example.com",
|
||||
"https://example.com/logo.png": "https://example.com/logo.png",
|
||||
"https://example.com/qr-code.png": "https://example.com/qr-code.png",
|
||||
"https://example.com/topup": "https://example.com/topup",
|
||||
"https://example.com/webhook": "https://example.com/webhook",
|
||||
"https://gateway.example.com": "https://gateway.example.com",
|
||||
"https://github.com/QuantumNous/new-api": "https://github.com/QuantumNous/new-api",
|
||||
"https://gotify.example.com": "https://gotify.example.com",
|
||||
"https://pay.example.com": "https://pay.example.com",
|
||||
"https://provider.com/.well-known/openid-configuration": "https://provider.com/.well-known/openid-configuration",
|
||||
"https://status.example.com": "https://status.example.com",
|
||||
"https://wechat-server.example.com": "https://wechat-server.example.com",
|
||||
"https://worker.example.workers.dev": "https://worker.example.workers.dev",
|
||||
"https://your-server.example.com": "https://your-server.example.com",
|
||||
"I have read and understood the above compliance reminder": "I have read and understood the above compliance reminder",
|
||||
"I have read and understood the above compliance reminder, acknowledge the related legal risks, and confirm that I bear legal responsibility arising from deployment, operation, and charging behavior.": "I have read and understood the above compliance reminder, acknowledge the related legal risks, and confirm that I bear legal responsibility arising from deployment, operation, and charging behavior.",
|
||||
"If you provide generative AI services to the public in mainland China, you will fulfill legal obligations including filing, security assessment, content safety, complaint handling, generated content labeling, log retention, and personal information protection.": "If you provide generative AI services to the public in mainland China, you will fulfill legal obligations including filing, security assessment, content safety, complaint handling, generated content labeling, log retention, and personal information protection.",
|
||||
"Jimeng": "Jimeng",
|
||||
"JustSong": "JustSong",
|
||||
"LingYiWanWu": "LingYiWanWu",
|
||||
"LinuxDO": "LinuxDO",
|
||||
"Media pricing": "Media pricing",
|
||||
"Midjourney": "Midjourney",
|
||||
"MidjourneyPlus": "MidjourneyPlus",
|
||||
"MiniMax": "MiniMax",
|
||||
"Mistral": "Mistral",
|
||||
"MokaAI": "MokaAI",
|
||||
"Moonshot": "Moonshot",
|
||||
"name@example.com": "name@example.com",
|
||||
"NewAPI": "NewAPI",
|
||||
"No separate media pricing configured.": "No separate media pricing configured.",
|
||||
"Non-zero invitation rewards require compliance confirmation in Payment Gateway settings.": "Non-zero invitation rewards require compliance confirmation in Payment Gateway settings.",
|
||||
"noreply@example.com": "noreply@example.com",
|
||||
"OAuth Client Secret": "OAuth Client Secret",
|
||||
"OhMyGPT": "OhMyGPT",
|
||||
"Ollama": "Ollama",
|
||||
"OpenAI": "OpenAI",
|
||||
"OpenAIMax": "OpenAIMax",
|
||||
"OpenRouter": "OpenRouter",
|
||||
"operation and charging behavior": "operation and charging behavior",
|
||||
"Passkey": "Passkey",
|
||||
"Payment, redemption codes, subscription plans, and invitation rewards are locked until the root administrator confirms the compliance terms.": "Payment, redemption codes, subscription plans, and invitation rewards are locked until the root administrator confirms the compliance terms.",
|
||||
"Perplexity": "Perplexity",
|
||||
"Please type the following text to confirm:": "Please type the following text to confirm:",
|
||||
"price_xxx": "price_xxx",
|
||||
"QuantumNous": "QuantumNous",
|
||||
"Redemption codes are disabled until the administrator confirms compliance terms.": "Redemption codes are disabled until the administrator confirms compliance terms.",
|
||||
"Referral reward transfer is disabled until the administrator confirms compliance terms.": "Referral reward transfer is disabled until the administrator confirms compliance terms.",
|
||||
"Replicate": "Replicate",
|
||||
"Separate image/audio prices are enabled.": "Separate image/audio prices are enabled.",
|
||||
"Set separate prices for cache reads and writes.": "Set separate prices for cache reads and writes.",
|
||||
"SiliconFlow": "SiliconFlow",
|
||||
"smtp.example.com": "smtp.example.com",
|
||||
"socks5://user:pass@host:port": "socks5://user:pass@host:port",
|
||||
"Stripe": "Stripe",
|
||||
"Submodel": "Submodel",
|
||||
"Subscription plan creation and changes are locked until the administrator confirms compliance terms in Payment Gateway settings.": "Subscription plan creation and changes are locked until the administrator confirms compliance terms in Payment Gateway settings.",
|
||||
"SunoAPI": "SunoAPI",
|
||||
"Telegram": "Telegram",
|
||||
"Tencent": "Tencent",
|
||||
"The entered text does not match the required text.": "The entered text does not match the required text.",
|
||||
"This confirmation unlocks payment, redemption code, subscription plan, and invitation reward features. Please read the statements carefully.": "This confirmation unlocks payment, redemption code, subscription plan, and invitation reward features. Please read the statements carefully.",
|
||||
"This tier catches any request that did not match earlier tiers.": "This tier catches any request that did not match earlier tiers.",
|
||||
"Tier conditions": "Tier conditions",
|
||||
"Token prices": "Token prices",
|
||||
"TTFT P50": "TTFT P50",
|
||||
"TTFT P95": "TTFT P95",
|
||||
"TTFT P99": "TTFT P99",
|
||||
"Type the confirmation text here": "Type the confirmation text here",
|
||||
"Vertex AI": "Vertex AI",
|
||||
"VolcEngine": "VolcEngine",
|
||||
"WeChat": "WeChat",
|
||||
"WeChat Pay": "WeChat Pay",
|
||||
"whsec_xxx": "whsec_xxx",
|
||||
"Xinference": "Xinference",
|
||||
"Xunfei": "Xunfei",
|
||||
"You commit not to use this system to implement, assist with, or indirectly implement acts that violate applicable laws and regulations, regulatory requirements, platform rules, public interests, or the lawful rights and interests of third parties.": "You commit not to use this system to implement, assist with, or indirectly implement acts that violate applicable laws and regulations, regulatory requirements, platform rules, public interests, or the lawful rights and interests of third parties.",
|
||||
"You commit to using upstream APIs, accounts, keys, quotas, and service capabilities only within the scope of lawful authorization obtained from upstream service providers, model service providers, or relevant rights holders, and will not conduct unauthorized resale, trafficking, distribution, or other non-compliant commercialization.": "You commit to using upstream APIs, accounts, keys, quotas, and service capabilities only within the scope of lawful authorization obtained from upstream service providers, model service providers, or relevant rights holders, and will not conduct unauthorized resale, trafficking, distribution, or other non-compliant commercialization.",
|
||||
"You have legally obtained authorization for the connected model APIs, accounts, keys, and quotas.": "You have legally obtained authorization for the connected model APIs, accounts, keys, and quotas.",
|
||||
"You understand and independently bear legal responsibility arising from deployment, operation, and charging behavior.": "You understand and independently bear legal responsibility arising from deployment, operation, and charging behavior.",
|
||||
"You understand this compliance reminder is only for risk notice and does not constitute legal advice, a compliance review conclusion, or a guarantee of the legality of your use of this system; you should consult professional legal or compliance advisors based on your actual business scenario.": "You understand this compliance reminder is only for risk notice and does not constitute legal advice, a compliance review conclusion, or a guarantee of the legality of your use of this system; you should consult professional legal or compliance advisors based on your actual business scenario.",
|
||||
"Zhipu V4": "Zhipu V4"
|
||||
}
|
||||
@ -1,25 +0,0 @@
|
||||
{
|
||||
"acknowledge the related legal risks": "acknowledge the related legal risks",
|
||||
"Base input and output token prices for this tier.": "Base input and output token prices for this tier.",
|
||||
"Confirm and enable": "Confirm and enable",
|
||||
"Each tier supports up to 2 conditions. The last tier without conditions is the fallback.": "Each tier supports up to 2 conditions. The last tier without conditions is the fallback.",
|
||||
"Failed to confirm compliance": "Failed to confirm compliance",
|
||||
"I have read and understood the above compliance reminder": "I have read and understood the above compliance reminder",
|
||||
"I have read and understood the above compliance reminder, acknowledge the related legal risks, and confirm that I bear legal responsibility arising from deployment, operation, and charging behavior.": "I have read and understood the above compliance reminder, acknowledge the related legal risks, and confirm that I bear legal responsibility arising from deployment, operation, and charging behavior.",
|
||||
"If you provide generative AI services to the public in mainland China, you will fulfill legal obligations including filing, security assessment, content safety, complaint handling, generated content labeling, log retention, and personal information protection.": "If you provide generative AI services to the public in mainland China, you will fulfill legal obligations including filing, security assessment, content safety, complaint handling, generated content labeling, log retention, and personal information protection.",
|
||||
"operation and charging behavior": "operation and charging behavior",
|
||||
"Payment, redemption codes, subscription plans, and invitation rewards are locked until the root administrator confirms the compliance terms.": "Payment, redemption codes, subscription plans, and invitation rewards are locked until the root administrator confirms the compliance terms.",
|
||||
"Please type the following text to confirm:": "Please type the following text to confirm:",
|
||||
"Redemption codes are disabled until the administrator confirms compliance terms.": "Redemption codes are disabled until the administrator confirms compliance terms.",
|
||||
"Referral reward transfer is disabled until the administrator confirms compliance terms.": "Referral reward transfer is disabled until the administrator confirms compliance terms.",
|
||||
"Set separate prices for cache reads and writes.": "Set separate prices for cache reads and writes.",
|
||||
"Subscription plan creation and changes are locked until the administrator confirms compliance terms in Payment Gateway settings.": "Subscription plan creation and changes are locked until the administrator confirms compliance terms in Payment Gateway settings.",
|
||||
"The entered text does not match the required text.": "The entered text does not match the required text.",
|
||||
"This confirmation unlocks payment, redemption code, subscription plan, and invitation reward features. Please read the statements carefully.": "This confirmation unlocks payment, redemption code, subscription plan, and invitation reward features. Please read the statements carefully.",
|
||||
"Type the confirmation text here": "Type the confirmation text here",
|
||||
"You commit not to use this system to implement, assist with, or indirectly implement acts that violate applicable laws and regulations, regulatory requirements, platform rules, public interests, or the lawful rights and interests of third parties.": "You commit not to use this system to implement, assist with, or indirectly implement acts that violate applicable laws and regulations, regulatory requirements, platform rules, public interests, or the lawful rights and interests of third parties.",
|
||||
"You commit to using upstream APIs, accounts, keys, quotas, and service capabilities only within the scope of lawful authorization obtained from upstream service providers, model service providers, or relevant rights holders, and will not conduct unauthorized resale, trafficking, distribution, or other non-compliant commercialization.": "You commit to using upstream APIs, accounts, keys, quotas, and service capabilities only within the scope of lawful authorization obtained from upstream service providers, model service providers, or relevant rights holders, and will not conduct unauthorized resale, trafficking, distribution, or other non-compliant commercialization.",
|
||||
"You have legally obtained authorization for the connected model APIs, accounts, keys, and quotas.": "You have legally obtained authorization for the connected model APIs, accounts, keys, and quotas.",
|
||||
"You understand and independently bear legal responsibility arising from deployment, operation, and charging behavior.": "You understand and independently bear legal responsibility arising from deployment, operation, and charging behavior.",
|
||||
"You understand this compliance reminder is only for risk notice and does not constitute legal advice, a compliance review conclusion, or a guarantee of the legality of your use of this system; you should consult professional legal or compliance advisors based on your actual business scenario.": "You understand this compliance reminder is only for risk notice and does not constitute legal advice, a compliance review conclusion, or a guarantee of the legality of your use of this system; you should consult professional legal or compliance advisors based on your actual business scenario."
|
||||
}
|
||||
@ -1,101 +0,0 @@
|
||||
{
|
||||
"\"default\": \"us-central1\", \"claude-3-5-sonnet-20240620\": \"europe-west1\"": "\"default\": \"us-central1\", \"claude-3-5-sonnet-20240620\": \"europe-west1\"",
|
||||
"[{\"ChatGPT\":\"https://chat.openai.com\"},{\"Lobe Chat\":\"https://chat-preview.lobehub.com/?settings={...}\"}]": "[{\"ChatGPT\":\"https://chat.openai.com\"},{\"Lobe Chat\":\"https://chat-preview.lobehub.com/?settings={...}\"}]",
|
||||
"{\"original-model\": \"replacement-model\"}": "{\"original-model\": \"replacement-model\"}",
|
||||
"/status/": "/status/",
|
||||
"/your/endpoint": "/your/endpoint",
|
||||
"AccessKey / SecretAccessKey": "AccessKey / SecretAccessKey",
|
||||
"AI Proxy": "AI Proxy",
|
||||
"AIGC2D": "AIGC2D",
|
||||
"Anthropic": "Anthropic",
|
||||
"API URL": "API URL",
|
||||
"API2GPT": "API2GPT",
|
||||
"AZURE_OPENAI_ENDPOINT *": "AZURE_OPENAI_ENDPOINT *",
|
||||
"checkout.session.completed": "checkout.session.completed",
|
||||
"checkout.session.expired": "checkout.session.expired",
|
||||
"Claude": "Claude",
|
||||
"Client ID": "Client ID",
|
||||
"Client Secret": "Client Secret",
|
||||
"Cloudflare": "Cloudflare",
|
||||
"Cohere": "Cohere",
|
||||
"DeepSeek": "DeepSeek",
|
||||
"Discord": "Discord",
|
||||
"DoubaoVideo": "DoubaoVideo",
|
||||
"edit_this": "edit_this",
|
||||
"example.com blocked-site.com": "example.com blocked-site.com",
|
||||
"example.com company.com": "example.com company.com",
|
||||
"FastGPT": "FastGPT",
|
||||
"footer.columns.related.links.midjourney": "Midjourney-Proxy",
|
||||
"footer.columns.related.links.newApiKeyTool": "new-api-key-tool",
|
||||
"footer.columns.related.links.oneApi": "One API",
|
||||
"Gemini": "Gemini",
|
||||
"GitHub": "GitHub",
|
||||
"gpt-3.5-turbo": "gpt-3.5-turbo",
|
||||
"gpt-3.5-turbo-0125": "gpt-3.5-turbo-0125",
|
||||
"https://api.day.app/yourkey/{{title}}/{{content}}": "https://api.day.app/yourkey/{{title}}/{{content}}",
|
||||
"https://api.example.com": "https://api.example.com",
|
||||
"https://ark.ap-southeast.bytepluses.com": "https://ark.ap-southeast.bytepluses.com",
|
||||
"https://ark.cn-beijing.volces.com": "https://ark.cn-beijing.volces.com",
|
||||
"https://cloud.siliconflow.cn/i/hij0YNTZ": "https://cloud.siliconflow.cn/i/hij0YNTZ",
|
||||
"https://docs.example.com": "https://docs.example.com",
|
||||
"https://example.com": "https://example.com",
|
||||
"https://example.com/logo.png": "https://example.com/logo.png",
|
||||
"https://example.com/qr-code.png": "https://example.com/qr-code.png",
|
||||
"https://example.com/topup": "https://example.com/topup",
|
||||
"https://example.com/webhook": "https://example.com/webhook",
|
||||
"https://gateway.example.com": "https://gateway.example.com",
|
||||
"https://github.com/QuantumNous/new-api": "https://github.com/QuantumNous/new-api",
|
||||
"https://gotify.example.com": "https://gotify.example.com",
|
||||
"https://pay.example.com": "https://pay.example.com",
|
||||
"https://provider.com/.well-known/openid-configuration": "https://provider.com/.well-known/openid-configuration",
|
||||
"https://status.example.com": "https://status.example.com",
|
||||
"https://wechat-server.example.com": "https://wechat-server.example.com",
|
||||
"https://worker.example.workers.dev": "https://worker.example.workers.dev",
|
||||
"https://your-server.example.com": "https://your-server.example.com",
|
||||
"Jimeng": "Jimeng",
|
||||
"JustSong": "JustSong",
|
||||
"LingYiWanWu": "LingYiWanWu",
|
||||
"LinuxDO": "LinuxDO",
|
||||
"Midjourney": "Midjourney",
|
||||
"MidjourneyPlus": "MidjourneyPlus",
|
||||
"MiniMax": "MiniMax",
|
||||
"Mistral": "Mistral",
|
||||
"MokaAI": "MokaAI",
|
||||
"Moonshot": "Moonshot",
|
||||
"name@example.com": "name@example.com",
|
||||
"New API": "New API",
|
||||
"New API <noreply@example.com>": "New API <noreply@example.com>",
|
||||
"NewAPI": "NewAPI",
|
||||
"noreply@example.com": "noreply@example.com",
|
||||
"OhMyGPT": "OhMyGPT",
|
||||
"Ollama": "Ollama",
|
||||
"One API": "One API",
|
||||
"OpenAI": "OpenAI",
|
||||
"OpenAIMax": "OpenAIMax",
|
||||
"OpenRouter": "OpenRouter",
|
||||
"org-...": "org-...",
|
||||
"Passkey": "Passkey",
|
||||
"Perplexity": "Perplexity",
|
||||
"price_xxx": "price_xxx",
|
||||
"QuantumNous": "QuantumNous",
|
||||
"Quota:": "Quota:",
|
||||
"Replicate": "Replicate",
|
||||
"SiliconFlow": "SiliconFlow",
|
||||
"smtp.example.com": "smtp.example.com",
|
||||
"socks5://user:pass@host:port": "socks5://user:pass@host:port",
|
||||
"Stripe": "Stripe",
|
||||
"Submodel": "Submodel",
|
||||
"SunoAPI": "SunoAPI",
|
||||
"Telegram": "Telegram",
|
||||
"TTFT P50": "TTFT P50",
|
||||
"TTFT P95": "TTFT P95",
|
||||
"TTFT P99": "TTFT P99",
|
||||
"Uptime Kuma": "Uptime Kuma",
|
||||
"Uptime Kuma URL": "Uptime Kuma URL",
|
||||
"Vertex AI": "Vertex AI",
|
||||
"Webhook URL:": "Webhook URL:",
|
||||
"Well-Known URL": "Well-Known URL",
|
||||
"whsec_xxx": "whsec_xxx",
|
||||
"Worker URL": "Worker URL",
|
||||
"Xinference": "Xinference"
|
||||
}
|
||||
52
web/default/src/i18n/locales/en.json
vendored
52
web/default/src/i18n/locales/en.json
vendored
@ -449,7 +449,6 @@
|
||||
"Automatically replaces upstream callback URLs with the server address.": "Automatically replaces upstream callback URLs with the server address.",
|
||||
"Automatically selects the best available group with circuit breaker mechanism": "Automatically selects the best available group with circuit breaker mechanism",
|
||||
"Automatically sync model list when upstream changes are detected": "Automatically sync model list when upstream changes are detected",
|
||||
"Automatically test channels and notify users when limits are hit": "Automatically test channels and notify users when limits are hit",
|
||||
"Availability (last 24h)": "Availability (last 24h)",
|
||||
"Available": "Available",
|
||||
"Available disk space": "Available disk space",
|
||||
@ -561,8 +560,6 @@
|
||||
"Bound product:": "Bound product:",
|
||||
"Bound store:": "Bound store:",
|
||||
"Bring channels back online after successful checks": "Bring channels back online after successful checks",
|
||||
"Broadcast a global banner to users. Markdown is supported.": "Broadcast a global banner to users. Markdown is supported.",
|
||||
"Broadcast short system notices on the dashboard": "Broadcast short system notices on the dashboard",
|
||||
"Browse and compare": "Browse and compare",
|
||||
"Browse available models and pricing": "Browse available models and pricing",
|
||||
"Browse rankings by category": "Browse rankings by category",
|
||||
@ -828,43 +825,21 @@
|
||||
"Configure API documentation links for the dashboard": "Configure API documentation links for the dashboard",
|
||||
"Configure at:": "Configure at:",
|
||||
"Configure available payment methods. Provide a JSON array.": "Configure available payment methods. Provide a JSON array.",
|
||||
"Configure basic system information and branding": "Configure basic system information and branding",
|
||||
"Configure channel affinity (sticky routing) rules": "Configure channel affinity (sticky routing) rules",
|
||||
"Configure Creem products. Provide a JSON array.": "Configure Creem products. Provide a JSON array.",
|
||||
"Configure currency conversion and quota display options": "Configure currency conversion and quota display options",
|
||||
"Configure custom OAuth providers for user authentication": "Configure custom OAuth providers for user authentication",
|
||||
"Configure daily check-in rewards for users": "Configure daily check-in rewards for users",
|
||||
"Configure discount rates based on recharge amounts": "Configure discount rates based on recharge amounts",
|
||||
"Configure experimental data export for the dashboard": "Configure experimental data export for the dashboard",
|
||||
"Configure Gemini safety behavior, version overrides, and thinking adapter": "Configure Gemini safety behavior, version overrides, and thinking adapter",
|
||||
"Configure group ratios and group-specific pricing rules": "Configure group ratios and group-specific pricing rules",
|
||||
"Configure in your Creem dashboard": "Configure in your Creem dashboard",
|
||||
"Configure io.net API key for model deployments": "Configure io.net API key for model deployments",
|
||||
"Configure keyword filtering for prompts and responses.": "Configure keyword filtering for prompts and responses.",
|
||||
"Configure model deployment provider settings": "Configure model deployment provider settings",
|
||||
"Configure model pricing ratios and tool prices": "Configure model pricing ratios and tool prices",
|
||||
"Configure model, caching, and group ratios used for billing": "Configure model, caching, and group ratios used for billing",
|
||||
"Configure monitoring status page groups for the dashboard": "Configure monitoring status page groups for the dashboard",
|
||||
"Configure outgoing email server for notifications": "Configure outgoing email server for notifications",
|
||||
"Configure Passkey (WebAuthn) login settings": "Configure Passkey (WebAuthn) login settings",
|
||||
"Configure password-based login and registration": "Configure password-based login and registration",
|
||||
"Configure per-model ratio for image inputs or outputs.": "Configure per-model ratio for image inputs or outputs.",
|
||||
"Configure per-tool unit prices ($/1K calls). Per-request models do not incur additional tool fees.": "Configure per-tool unit prices ($/1K calls). Per-request models do not incur additional tool fees.",
|
||||
"Configure predefined chat links surfaced to end users.": "Configure predefined chat links surfaced to end users.",
|
||||
"Configure pricing model and display options": "Configure pricing model and display options",
|
||||
"Configure pricing ratios for a specific model.": "Configure pricing ratios for a specific model.",
|
||||
"Configure rate limiting rules for a specific user group.": "Configure rate limiting rules for a specific user group.",
|
||||
"Configure recharge pricing and payment gateway integrations": "Configure recharge pricing and payment gateway integrations",
|
||||
"Configure system-wide behavior and defaults": "Configure system-wide behavior and defaults",
|
||||
"Configure the ratio for this group.": "Configure the ratio for this group.",
|
||||
"Configure third-party authentication providers": "Configure third-party authentication providers",
|
||||
"Configure upstream providers and routing.": "Configure upstream providers and routing.",
|
||||
"Configure upstream worker or proxy service for outbound requests": "Configure upstream worker or proxy service for outbound requests",
|
||||
"Configure user quota allocation and rewards": "Configure user quota allocation and rewards",
|
||||
"Configure Waffo Pancake hosted checkout integration for USD-priced top-ups": "Configure Waffo Pancake hosted checkout integration for USD-priced top-ups",
|
||||
"Configure Waffo payment aggregation platform integration": "Configure Waffo payment aggregation platform integration",
|
||||
"Configure xAI Grok model settings": "Configure xAI Grok model settings",
|
||||
"Configure xAI Grok model specific settings": "Configure xAI Grok model specific settings",
|
||||
"Configure your account behavior preferences": "Configure your account behavior preferences",
|
||||
"Configure your account preferences and integrations": "Configure your account preferences and integrations",
|
||||
"Configured routes and latency checks": "Configured routes and latency checks",
|
||||
@ -932,11 +907,7 @@
|
||||
"Continue with Telegram": "Continue with Telegram",
|
||||
"Continue with WeChat": "Continue with WeChat",
|
||||
"Contract review, compliance, summarisation": "Contract review, compliance, summarisation",
|
||||
"Control log retention and clean historical data.": "Control log retention and clean historical data.",
|
||||
"Control passthrough behavior and connection keep-alive settings": "Control passthrough behavior and connection keep-alive settings",
|
||||
"Control request frequency to prevent abuse and manage system load.": "Control request frequency to prevent abuse and manage system load.",
|
||||
"Control which models are exposed and which groups may use them.": "Control which models are exposed and which groups may use them.",
|
||||
"Control which sidebar areas and modules are available to all users.": "Control which sidebar areas and modules are available to all users.",
|
||||
"Controls how much the model thinks before answering": "Controls how much the model thinks before answering",
|
||||
"Controls whether user verification (biometrics/PIN) is required during Passkey flows.": "Controls whether user verification (biometrics/PIN) is required during Passkey flows.",
|
||||
"Conversion rate from USD to your custom currency": "Conversion rate from USD to your custom currency",
|
||||
@ -1041,7 +1012,6 @@
|
||||
"Creem products must be a JSON array": "Creem products must be a JSON array",
|
||||
"Cross-group": "Cross-group",
|
||||
"Cross-group retry": "Cross-group retry",
|
||||
"Curate quick links to your different Domains": "Curate quick links to your different Domains",
|
||||
"Currency": "Currency",
|
||||
"Currency & Display": "Currency & Display",
|
||||
"Current Balance": "Current Balance",
|
||||
@ -1395,7 +1365,6 @@
|
||||
"Enable OIDC": "Enable OIDC",
|
||||
"Enable or disable this channel": "Enable or disable this channel",
|
||||
"Enable or disable this model": "Enable or disable this model",
|
||||
"Enable or disable top navigation modules globally.": "Enable or disable top navigation modules globally.",
|
||||
"Enable Passkey": "Enable Passkey",
|
||||
"Enable Performance Monitoring": "Enable Performance Monitoring",
|
||||
"Enable rate limiting": "Enable rate limiting",
|
||||
@ -1549,7 +1518,6 @@
|
||||
"Expired at": "Expired at",
|
||||
"Expired time cannot be earlier than current time": "Expired time cannot be earlier than current time",
|
||||
"Expires": "Expires",
|
||||
"Expose grouped Uptime Kuma status pages directly on the dashboard": "Expose grouped Uptime Kuma status pages directly on the dashboard",
|
||||
"Expose ratio API": "Expose ratio API",
|
||||
"Exposes the pricing/models catalog in the top navigation.": "Exposes the pricing/models catalog in the top navigation.",
|
||||
"Expression": "Expression",
|
||||
@ -1760,7 +1728,6 @@
|
||||
"Final cost = base × multiplier when conditions match": "Final cost = base × multiplier when conditions match",
|
||||
"Final price multiplier (0.95 = 5% discount": "Final price multiplier (0.95 = 5% discount",
|
||||
"Finance": "Finance",
|
||||
"Fine-tune Midjourney integration and guardrails.": "Fine-tune Midjourney integration and guardrails.",
|
||||
"Finish Time": "Finish Time",
|
||||
"First API request": "First API request",
|
||||
"First/Last Frame to Video": "First/Last Frame to Video",
|
||||
@ -2237,30 +2204,21 @@
|
||||
"Low balance": "Low balance",
|
||||
"Lowest median first-token latency": "Lowest median first-token latency",
|
||||
"m": "m",
|
||||
"Maintain a list of common questions for the dashboard help panel": "Maintain a list of common questions for the dashboard help panel",
|
||||
"Maintenance": "Maintenance",
|
||||
"Make it easier for teammates to pick the right group.": "Make it easier for teammates to pick the right group.",
|
||||
"Manage": "Manage",
|
||||
"Manage account bindings for this user": "Manage account bindings for this user",
|
||||
"Manage API channels and provider configurations": "Manage API channels and provider configurations",
|
||||
"Manage Bindings": "Manage Bindings",
|
||||
"Manage catalog visibility and pricing.": "Manage catalog visibility and pricing.",
|
||||
"Manage custom OAuth providers for user authentication": "Manage custom OAuth providers for user authentication",
|
||||
"Manage Keys": "Manage Keys",
|
||||
"Manage local models for:": "Manage local models for:",
|
||||
"Manage model deployments": "Manage model deployments",
|
||||
"Manage model metadata and configuration": "Manage model metadata and configuration",
|
||||
"Manage multi-key status and configuration for this channel": "Manage multi-key status and configuration for this channel",
|
||||
"Manage Ollama Models": "Manage Ollama Models",
|
||||
"Manage redemption codes for quota top-up": "Manage redemption codes for quota top-up",
|
||||
"Manage server log files. Log files accumulate over time; regular cleanup is recommended to free disk space.": "Manage server log files. Log files accumulate over time; regular cleanup is recommended to free disk space.",
|
||||
"Manage subscription plan creation, pricing and status": "Manage subscription plan creation, pricing and status",
|
||||
"Manage subscription plans and pricing.": "Manage subscription plans and pricing.",
|
||||
"Manage Subscriptions": "Manage Subscriptions",
|
||||
"Manage users and their permissions": "Manage users and their permissions",
|
||||
"Manage Vendors": "Manage Vendors",
|
||||
"Manage your API keys for accessing the service": "Manage your API keys for accessing the service",
|
||||
"Manage your balance and payment methods": "Manage your balance and payment methods",
|
||||
"Manage your security settings and account access": "Manage your security settings and account access",
|
||||
"Manual Disabled": "Manual Disabled",
|
||||
"Map fields from the user info response to local user attributes. Supports nested paths (e.g. ocs.data.id).": "Map fields from the user info response to local user attributes. Supports nested paths (e.g. ocs.data.id).",
|
||||
@ -2793,7 +2751,6 @@
|
||||
"Overnight range": "Overnight range",
|
||||
"override": "override",
|
||||
"Override": "Override",
|
||||
"Override Anthropic headers, defaults, and thinking adapter behavior": "Override Anthropic headers, defaults, and thinking adapter behavior",
|
||||
"Override auto-discovered endpoint": "Override auto-discovered endpoint",
|
||||
"Override request headers": "Override request headers",
|
||||
"Override request headers (JSON format)": "Override request headers (JSON format)",
|
||||
@ -3039,7 +2996,6 @@
|
||||
"Press Enter or comma to add tags": "Press Enter or comma to add tags",
|
||||
"Press Enter to use \"{{value}}\"": "Press Enter to use \"{{value}}\"",
|
||||
"Prevent server-side request forgery attacks": "Prevent server-side request forgery attacks",
|
||||
"Prevent server-side request forgery attacks by controlling outbound requests.": "Prevent server-side request forgery attacks by controlling outbound requests.",
|
||||
"Preview": "Preview",
|
||||
"Previous": "Previous",
|
||||
"Previous branch": "Previous branch",
|
||||
@ -3102,7 +3058,6 @@
|
||||
"Prompt Details": "Prompt Details",
|
||||
"Prompt price ($/1M tokens)": "Prompt price ($/1M tokens)",
|
||||
"Proprietary": "Proprietary",
|
||||
"Protect login and registration with Cloudflare Turnstile": "Protect login and registration with Cloudflare Turnstile",
|
||||
"Provide a JSON object where each key maps to an endpoint definition.": "Provide a JSON object where each key maps to an endpoint definition.",
|
||||
"Provide a valid URL starting with http:// or https://": "Provide a valid URL starting with http:// or https://",
|
||||
"Provide Markdown, HTML, or an external URL for the privacy policy": "Provide Markdown, HTML, or an external URL for the privacy policy",
|
||||
@ -3388,7 +3343,6 @@
|
||||
"Reveal key": "Reveal key",
|
||||
"Revenue": "Revenue",
|
||||
"Review & initialize": "Review & initialize",
|
||||
"Review current version and fetch release notes.": "Review current version and fetch release notes.",
|
||||
"Review model rates before scaling traffic": "Review model rates before scaling traffic",
|
||||
"Review your payment details": "Review your payment details",
|
||||
"Review your purchase details before proceeding.": "Review your purchase details before proceeding.",
|
||||
@ -4354,16 +4308,11 @@
|
||||
"Vidu": "Vidu",
|
||||
"View": "View",
|
||||
"View all currently available models": "View all currently available models",
|
||||
"View and manage your API usage logs": "View and manage your API usage logs",
|
||||
"View and manage your drawing logs": "View and manage your drawing logs",
|
||||
"View and manage your task logs": "View and manage your task logs",
|
||||
"View dashboard overview and statistics": "View dashboard overview and statistics",
|
||||
"View detailed information about this user including balance, usage statistics, and invitation details.": "View detailed information about this user including balance, usage statistics, and invitation details.",
|
||||
"View details": "View details",
|
||||
"View document": "View document",
|
||||
"View logs": "View logs",
|
||||
"View mode": "View mode",
|
||||
"View model call count analytics and charts": "View model call count analytics and charts",
|
||||
"View model statistics and charts": "View model statistics and charts",
|
||||
"View Pricing": "View Pricing",
|
||||
"View the complete details for this": "View the complete details for this",
|
||||
@ -4371,7 +4320,6 @@
|
||||
"View the complete error message and details": "View the complete error message and details",
|
||||
"View the complete prompt and its English translation": "View the complete prompt and its English translation",
|
||||
"View the generated image": "View the generated image",
|
||||
"View user consumption statistics and charts": "View user consumption statistics and charts",
|
||||
"View your topup transaction records and payment history": "View your topup transaction records and payment history",
|
||||
"Violation Code": "Violation Code",
|
||||
"Violation deduction amount": "Violation deduction amount",
|
||||
|
||||
167
web/default/src/i18n/locales/fr.json
vendored
167
web/default/src/i18n/locales/fr.json
vendored
@ -120,7 +120,7 @@
|
||||
"Account ID *": "ID de compte *",
|
||||
"Account Info": "Informations du compte",
|
||||
"Account used when authenticating with the SMTP server": "Compte utilisé lors de l'authentification auprès du serveur SMTP",
|
||||
"acknowledge the related legal risks": "acknowledge the related legal risks",
|
||||
"acknowledge the related legal risks": "reconnais les risques juridiques associés",
|
||||
"Across all groups": "Tous groupes confondus",
|
||||
"Action confirmation": "Confirmation de l'action",
|
||||
"Actions": "Actions",
|
||||
@ -247,7 +247,7 @@
|
||||
"Alipay": "Alipay",
|
||||
"All": "Tout",
|
||||
"All categories": "Toutes catégories",
|
||||
"All conditions must match before this tier is used.": "Toutes les conditions doivent correspondre pour utiliser ce palier.",
|
||||
"All conditions must match before this tier is used.": "Toutes les conditions doivent correspondre avant que ce palier soit utilisé.",
|
||||
"All edits are overwrite operations. Leave fields empty to keep current values unchanged.": "Toutes les modifications sont des opérations d'écrasement. Laissez les champs vides pour conserver les valeurs actuelles inchangées.",
|
||||
"All files exceed the maximum size.": "Tous les fichiers dépassent la taille maximale.",
|
||||
"All Groups": "Tous les groupes",
|
||||
@ -449,7 +449,6 @@
|
||||
"Automatically replaces upstream callback URLs with the server address.": "Remplace automatiquement les URL des callbacks en amont par l'adresse du serveur.",
|
||||
"Automatically selects the best available group with circuit breaker mechanism": "Sélectionne automatiquement le meilleur groupe disponible avec un mécanisme de disjoncteur de circuit",
|
||||
"Automatically sync model list when upstream changes are detected": "Synchroniser automatiquement la liste des modèles lorsque des changements en amont sont détectés",
|
||||
"Automatically test channels and notify users when limits are hit": "Tester automatiquement les canaux et notifier les utilisateurs lorsque les limites sont atteintes",
|
||||
"Availability (last 24h)": "Disponibilité (dernières 24 h)",
|
||||
"Available": "Disponible",
|
||||
"Available disk space": "Espace disque disponible",
|
||||
@ -494,7 +493,7 @@
|
||||
"Bark Push URL": "URL de notification Bark",
|
||||
"Base address provided by your Epay service": "Adresse de base fournie par votre service Epay",
|
||||
"Base amount. Actual deduction = base amount × system group rate.": "Montant de base. Déduction réelle = montant de base × taux du groupe système.",
|
||||
"Base input and output token prices for this tier.": "Prix de base des tokens d’entrée et de sortie pour ce palier.",
|
||||
"Base input and output token prices for this tier.": "Prix de base des tokens en entrée et en sortie pour ce palier.",
|
||||
"Base input price only": "Prix d’entrée de base uniquement",
|
||||
"Base Limits": "Limites de base",
|
||||
"Base multipliers applied when users select specific groups.": "Multiplicateurs de base appliqués lorsque les utilisateurs sélectionnent des groupes spécifiques.",
|
||||
@ -531,6 +530,7 @@
|
||||
"Billing Process": "Processus de facturation",
|
||||
"Billing Source": "Source de facturation",
|
||||
"Bind": "Lier",
|
||||
"Bind a Pancake store + product": "Associer une boutique et un produit Pancake",
|
||||
"Bind an email address to your account.": "Associez une adresse e-mail à votre compte.",
|
||||
"Bind Email": "Lier l'e-mail",
|
||||
"Bind Telegram Account": "Lier le compte Telegram",
|
||||
@ -557,9 +557,9 @@
|
||||
"Bound": "Lié",
|
||||
"Bound Channels": "Canaux liés",
|
||||
"Bound Only": "Liés uniquement",
|
||||
"Bound product:": "Produit associé :",
|
||||
"Bound store:": "Boutique associée :",
|
||||
"Bring channels back online after successful checks": "Remettre les canaux en ligne après des vérifications réussies",
|
||||
"Broadcast a global banner to users. Markdown is supported.": "Diffuser une bannière globale aux utilisateurs. Le Markdown est pris en charge.",
|
||||
"Broadcast short system notices on the dashboard": "Diffuser de courtes notifications système sur le tableau de bord",
|
||||
"Browse and compare": "Parcourir et comparer",
|
||||
"Browse available models and pricing": "Parcourir les modèles disponibles et les tarifs",
|
||||
"Browse rankings by category": "Parcourir les classements par catégorie",
|
||||
@ -797,9 +797,9 @@
|
||||
"Completion price": "Prix de complétion",
|
||||
"Completion price ($/1M tokens)": "Prix de la complétion (USD/1M jetons)",
|
||||
"Completion ratio": "Ratio de complétion",
|
||||
"Compliance confirmation required": "Compliance confirmation required",
|
||||
"Compliance confirmed": "Compliance confirmed",
|
||||
"Compliance confirmed successfully": "Compliance confirmed successfully",
|
||||
"Compliance confirmation required": "Confirmation de conformité requise",
|
||||
"Compliance confirmed": "Conformité confirmée",
|
||||
"Compliance confirmed successfully": "Conformité confirmée avec succès",
|
||||
"Concatenate channel system prompt with user's prompt": "Concaténer l'invite système du canal avec l'invite de l'utilisateur",
|
||||
"Condition Path": "Chemin de condition",
|
||||
"Condition Settings": "Paramètres de condition",
|
||||
@ -825,49 +825,27 @@
|
||||
"Configure API documentation links for the dashboard": "Configurer les liens de documentation API pour le tableau de bord",
|
||||
"Configure at:": "Configurer à :",
|
||||
"Configure available payment methods. Provide a JSON array.": "Configurer les méthodes de paiement disponibles. Fournir un tableau JSON.",
|
||||
"Configure basic system information and branding": "Configurer les informations système de base et l'image de marque",
|
||||
"Configure channel affinity (sticky routing) rules": "Configurer les règles d'affinité de canal (routage persistant)",
|
||||
"Configure Creem products. Provide a JSON array.": "Configurez les produits Creem. Fournissez un tableau JSON.",
|
||||
"Configure currency conversion and quota display options": "Configurer la conversion de devise et les options d'affichage des quotas",
|
||||
"Configure custom OAuth providers for user authentication": "Configurer des fournisseurs OAuth personnalisés pour l'authentification des utilisateurs",
|
||||
"Configure daily check-in rewards for users": "Configurer les récompenses de connexion quotidienne pour les utilisateurs",
|
||||
"Configure discount rates based on recharge amounts": "Configurer les taux de réduction basés sur les montants de recharge",
|
||||
"Configure experimental data export for the dashboard": "Configurer l'exportation de données expérimentales pour le tableau de bord",
|
||||
"Configure Gemini safety behavior, version overrides, and thinking adapter": "Configurer le comportement de sécurité Gemini, les remplacements de version et l'adaptateur de réflexion",
|
||||
"Configure group ratios and group-specific pricing rules": "Configurer les ratios de groupe et les règles de tarification propres aux groupes",
|
||||
"Configure in your Creem dashboard": "Configurez dans votre tableau de bord Creem",
|
||||
"Configure io.net API key for model deployments": "Configurer la clé API io.net pour les déploiements de modèles",
|
||||
"Configure keyword filtering for prompts and responses.": "Configurer le filtrage par mots-clés pour les invites et les réponses.",
|
||||
"Configure model deployment provider settings": "Configurer les paramètres du fournisseur de déploiement de modèles",
|
||||
"Configure model pricing ratios and tool prices": "Configurer les ratios de tarification des modèles et les prix des outils",
|
||||
"Configure model, caching, and group ratios used for billing": "Configurer les ratios de modèle, de mise en cache et de groupe utilisés pour la facturation",
|
||||
"Configure monitoring status page groups for the dashboard": "Configurer les groupes de pages d'état de surveillance pour le tableau de bord",
|
||||
"Configure outgoing email server for notifications": "Configurer le serveur de messagerie sortant pour les notifications",
|
||||
"Configure Passkey (WebAuthn) login settings": "Configurer les paramètres de connexion Passkey (WebAuthn)",
|
||||
"Configure password-based login and registration": "Configurer la connexion et l'inscription basées sur un mot de passe",
|
||||
"Configure per-model ratio for image inputs or outputs.": "Configurer le ratio par modèle pour les entrées ou sorties d'images.",
|
||||
"Configure per-tool unit prices ($/1K calls). Per-request models do not incur additional tool fees.": "Définissez le prix unitaire de chaque outil ($/1K appels). Les modèles facturés à la requête n'entraînent pas de frais d'outils supplémentaires.",
|
||||
"Configure predefined chat links surfaced to end users.": "Configurer les liens de discussion prédéfinis affichés aux utilisateurs finaux.",
|
||||
"Configure pricing model and display options": "Configurer le modèle de tarification et les options d'affichage",
|
||||
"Configure pricing ratios for a specific model.": "Configurer les ratios de tarification pour un modèle spécifique.",
|
||||
"Configure rate limiting rules for a specific user group.": "Configurer les règles de limitation de débit pour un groupe d'utilisateurs spécifique.",
|
||||
"Configure recharge pricing and payment gateway integrations": "Configurer la tarification des recharges et les intégrations de passerelles de paiement",
|
||||
"Configure system-wide behavior and defaults": "Configurer le comportement et les valeurs par défaut à l'échelle du système",
|
||||
"Configure the ratio for this group.": "Configurer le ratio pour ce groupe.",
|
||||
"Configure third-party authentication providers": "Configurer les fournisseurs d'authentification tiers",
|
||||
"Configure upstream providers and routing.": "Configurer les fournisseurs en amont et le routage.",
|
||||
"Configure upstream worker or proxy service for outbound requests": "Configurer le service de travailleur en amont ou de proxy pour les requêtes sortantes",
|
||||
"Configure user quota allocation and rewards": "Configurer l'allocation de quotas utilisateur et les récompenses",
|
||||
"Configure Waffo Pancake hosted checkout integration for USD-priced top-ups": "Configurer l'intégration du parcours de paiement hébergé Waffo Pancake pour les rechargements en USD",
|
||||
"Configure Waffo payment aggregation platform integration": "Configurer l'intégration de la plateforme d'agrégation de paiement Waffo",
|
||||
"Configure xAI Grok model settings": "Configurer les paramètres du modèle xAI Grok",
|
||||
"Configure xAI Grok model specific settings": "Configurer les paramètres spécifiques du modèle xAI Grok",
|
||||
"Configure your account behavior preferences": "Configurer les préférences de comportement de votre compte",
|
||||
"Configure your account preferences and integrations": "Configurer les préférences et les intégrations de votre compte",
|
||||
"Configured routes and latency checks": "Routes configurées et contrôles de latence",
|
||||
"Confirm": "Confirmer",
|
||||
"Confirm Action": "Confirmer l'action",
|
||||
"Confirm and enable": "Confirm and enable",
|
||||
"Confirm and enable": "Confirmer et activer",
|
||||
"Confirm Batch Update": "Confirmer la mise à jour par lot",
|
||||
"Confirm Billing Conflicts": "Confirmer les conflits de facturation",
|
||||
"Confirm Changes": "Confirmer les modifications",
|
||||
@ -875,8 +853,8 @@
|
||||
"Confirm cleanup of inactive disk cache?": "Confirmer le nettoyage du cache disque inactif ?",
|
||||
"Confirm clearing all channel affinity cache": "Confirmer la suppression de tout le cache d'affinité de canal",
|
||||
"Confirm clearing cache for this rule": "Confirmer la suppression du cache de cette règle",
|
||||
"Confirm compliance": "Confirm compliance",
|
||||
"Confirm compliance terms": "Confirm compliance terms",
|
||||
"Confirm compliance": "Confirmer la conformité",
|
||||
"Confirm compliance terms": "Confirmer les conditions de conformité",
|
||||
"Confirm Creem Purchase": "Confirmer l'achat Creem",
|
||||
"Confirm delete": "Confirmer la suppression",
|
||||
"Confirm disable": "Confirmer la désactivation",
|
||||
@ -891,11 +869,11 @@
|
||||
"auth.resetPasswordConfirm.description": "Confirmez la demande de réinitialisation pour générer un nouveau mot de passe.",
|
||||
"Confirm Selection": "Confirmer la sélection",
|
||||
"Confirm settings and finish setup": "Confirmez les paramètres et terminez la configuration",
|
||||
"confirm that I bear legal responsibility arising from deployment": "confirm that I bear legal responsibility arising from deployment",
|
||||
"confirm that I bear legal responsibility arising from deployment": "confirme assumer la responsabilité juridique découlant du déploiement",
|
||||
"Confirm Unbind": "Confirmer la dissociation",
|
||||
"Confirm your identity before removing this Passkey from your account.": "Confirmez votre identité avant de supprimer cette Passkey de votre compte.",
|
||||
"Confirm your identity with Two-factor Authentication before registering a Passkey.": "Confirmez votre identité avec l’authentification à deux facteurs avant d’enregistrer une Passkey.",
|
||||
"Confirmed at {{time}} by user #{{userId}}": "Confirmed at {{time}} by user #{{userId}}",
|
||||
"Confirmed at {{time}} by user #{{userId}}": "Confirmé le {{time}} par l’utilisateur #{{userId}}",
|
||||
"Conflict": "Conflit",
|
||||
"Connect": "Connecter",
|
||||
"Connect through OpenAI, Claude, Gemini, and other compatible API routes": "Connectez-vous via OpenAI, Claude, Gemini et d'autres routes API compatibles",
|
||||
@ -929,11 +907,7 @@
|
||||
"Continue with Telegram": "Continuer avec Telegram",
|
||||
"Continue with WeChat": "Continuer avec WeChat",
|
||||
"Contract review, compliance, summarisation": "Revue de contrats, conformité, résumé",
|
||||
"Control log retention and clean historical data.": "Contrôler la rétention des journaux et nettoyer les données historiques.",
|
||||
"Control passthrough behavior and connection keep-alive settings": "Contrôler le comportement de passthrough et les paramètres de maintien de connexion",
|
||||
"Control request frequency to prevent abuse and manage system load.": "Contrôler la fréquence des requêtes pour prévenir les abus et gérer la charge système.",
|
||||
"Control which models are exposed and which groups may use them.": "Contrôlez les modèles exposés et les groupes autorisés à les utiliser.",
|
||||
"Control which sidebar areas and modules are available to all users.": "Contrôler les zones et modules de la barre latérale disponibles pour tous les utilisateurs.",
|
||||
"Controls how much the model thinks before answering": "Contrôle la quantité de raisonnement avant la réponse",
|
||||
"Controls whether user verification (biometrics/PIN) is required during Passkey flows.": "Contrôle si la vérification de l'utilisateur (biométrie/PIN) est requise lors des flux de Passkey.",
|
||||
"Conversion rate from USD to your custom currency": "Taux de conversion de l'USD vers votre devise personnalisée",
|
||||
@ -1023,9 +997,13 @@
|
||||
"Create, revoke, and audit API tokens.": "Créer, révoquer et auditer les jetons API.",
|
||||
"Created": "Créé",
|
||||
"Created At": "Créé le",
|
||||
"Creating...": "Création...",
|
||||
"Creation failed": "Échec de la création",
|
||||
"Credential generated": "Identifiant généré",
|
||||
"Credential refreshed": "Identifiant actualisé",
|
||||
"Credentials": "Identifiants",
|
||||
"Credentials verification failed": "Échec de la vérification des identifiants",
|
||||
"Credentials verification failed — double-check Merchant ID and API private key.": "Échec de la vérification des identifiants — vérifiez l’ID marchand et la clé privée API.",
|
||||
"Credit remaining": "Crédit restant",
|
||||
"Creem API key (leave blank unless updating)": "Clé API Creem (laissez vide sauf si mise à jour)",
|
||||
"Creem Gateway": "Passerelle Creem",
|
||||
@ -1034,7 +1012,6 @@
|
||||
"Creem products must be a JSON array": "Les produits Creem doivent être un tableau JSON",
|
||||
"Cross-group": "Inter-groupes",
|
||||
"Cross-group retry": "Nouvelle tentative inter-groupes",
|
||||
"Curate quick links to your different Domains": "Organiser des liens rapides vers vos différents domaines",
|
||||
"Currency": "Devise",
|
||||
"Currency & Display": "Devise et affichage",
|
||||
"Current Balance": "Solde actuel",
|
||||
@ -1322,7 +1299,7 @@
|
||||
"Each line represents one keyword. Leave blank to disable the list but keep the switch states.": "Chaque ligne représente un mot-clé. Laissez vide pour désactiver la liste mais conserver les états des interrupteurs.",
|
||||
"Each tier supports 0~2 conditions (over len, p, c); the last tier is the catch-all without conditions. Use len (full input length, including cache hits) for tier conditions to avoid mis-routing when cache hits reduce p.": "Chaque palier accepte 0 à 2 conditions (sur len, p, c) ; le dernier palier est le filet de sécurité sans condition. Utilisez len (longueur d'entrée complète, y compris les cache hits) pour les conditions de palier afin d'éviter les routages erronés lorsque les cache hits réduisent p.",
|
||||
"Each tier supports up to 2 conditions; the last tier is the catch-all without conditions. Use full input length for tier conditions to avoid mis-routing when cache hits reduce billable input tokens.": "Chaque palier accepte jusqu’à 2 conditions ; le dernier palier sert de repli sans condition. Utilisez la longueur complète de l’entrée pour éviter un mauvais aiguillage lorsque les lectures de cache réduisent les tokens d’entrée facturables.",
|
||||
"Each tier supports up to 2 conditions. The last tier without conditions is the fallback.": "Each tier supports up to 2 conditions. The last tier without conditions is the fallback.",
|
||||
"Each tier supports up to 2 conditions. The last tier without conditions is the fallback.": "Chaque palier prend en charge jusqu’à 2 conditions. Le dernier palier sans condition sert de repli.",
|
||||
"Earn rewards when your referrals add funds. Transfer accumulated rewards to your balance anytime.": "Gagnez des récompenses lorsque vos filleuls ajoutent des fonds. Transférez les récompenses accumulées à votre solde à tout moment.",
|
||||
"Edit": "Modifier",
|
||||
"Edit {{title}}": "Modifier {{title}}",
|
||||
@ -1388,7 +1365,6 @@
|
||||
"Enable OIDC": "Activer OIDC",
|
||||
"Enable or disable this channel": "Activer ou désactiver ce canal",
|
||||
"Enable or disable this model": "Activer ou désactiver ce modèle",
|
||||
"Enable or disable top navigation modules globally.": "Activer ou désactiver globalement les modules de navigation supérieure.",
|
||||
"Enable Passkey": "Activer Passkey",
|
||||
"Enable Performance Monitoring": "Activer la surveillance des performances",
|
||||
"Enable rate limiting": "Activer la limitation de débit",
|
||||
@ -1542,7 +1518,6 @@
|
||||
"Expired at": "Expiré le",
|
||||
"Expired time cannot be earlier than current time": "L'heure d'expiration ne peut pas être antérieure à l'heure actuelle",
|
||||
"Expires": "Expire",
|
||||
"Expose grouped Uptime Kuma status pages directly on the dashboard": "Exposer les pages d'état groupées d'Uptime Kuma directement sur le tableau de bord",
|
||||
"Expose ratio API": "Exposer l'API de ratio",
|
||||
"Exposes the pricing/models catalog in the top navigation.": "Expose le catalogue des prix/modèles dans la navigation supérieure.",
|
||||
"Expression": "Expression",
|
||||
@ -1575,7 +1550,7 @@
|
||||
"Failed to clean logs": "Échec du nettoyage des journaux",
|
||||
"Failed to complete order": "Échec de la finalisation de la commande",
|
||||
"Failed to complete Passkey login": "Impossible de terminer la connexion Passkey",
|
||||
"Failed to confirm compliance": "Failed to confirm compliance",
|
||||
"Failed to confirm compliance": "Échec de la confirmation de conformité",
|
||||
"Failed to contact GitHub releases API": "Impossible de contacter l'API GitHub Releases",
|
||||
"Failed to copy": "Échec de la copie",
|
||||
"Failed to copy channel": "Échec de la copie du canal",
|
||||
@ -1717,6 +1692,8 @@
|
||||
"Fill Codex CLI / Claude CLI Templates": "Remplir les modèles Codex CLI / Claude CLI",
|
||||
"Fill example (all channels)": "Remplir l'exemple (tous les canaux)",
|
||||
"Fill example (specific channels)": "Remplir l'exemple (canaux spécifiques)",
|
||||
"Fill in both Merchant ID and API Private Key before creating.": "Renseignez l’ID marchand et la clé privée API avant de créer.",
|
||||
"Fill in the credentials above to begin.": "Renseignez les identifiants ci-dessus pour commencer.",
|
||||
"Fill in the following info to create a new subscription plan": "Remplissez les informations suivantes pour créer un nouveau plan d'abonnement",
|
||||
"Fill Related Models": "Remplir les modèles associés",
|
||||
"Fill Template": "Remplir le modèle",
|
||||
@ -1751,7 +1728,6 @@
|
||||
"Final cost = base × multiplier when conditions match": "Coût final = base × multiplicateur lorsque les conditions correspondent",
|
||||
"Final price multiplier (0.95 = 5% discount": "Multiplicateur de prix final (0.95 = 5% de réduction",
|
||||
"Finance": "Finance",
|
||||
"Fine-tune Midjourney integration and guardrails.": "Affiner l'intégration et les garde-fous de Midjourney.",
|
||||
"Finish Time": "Heure de fin",
|
||||
"First API request": "Première requête API",
|
||||
"First/Last Frame to Video": "Première/Dernière image vers vidéo",
|
||||
@ -1988,8 +1964,8 @@
|
||||
"Human-readable name shown to users during Passkey prompts.": "Nom lisible par l'homme affiché aux utilisateurs lors des invites de clé d'accès (Passkey).",
|
||||
"I confirm enabling high-risk retry": "Je confirme l'activation de la relance à haut risque",
|
||||
"I have read and agree to the": "J'ai lu et j'accepte les",
|
||||
"I have read and understood the above compliance reminder": "I have read and understood the above compliance reminder",
|
||||
"I have read and understood the above compliance reminder, acknowledge the related legal risks, and confirm that I bear legal responsibility arising from deployment, operation, and charging behavior.": "I have read and understood the above compliance reminder, acknowledge the related legal risks, and confirm that I bear legal responsibility arising from deployment, operation, and charging behavior.",
|
||||
"I have read and understood the above compliance reminder": "J’ai lu et compris le rappel de conformité ci-dessus",
|
||||
"I have read and understood the above compliance reminder, acknowledge the related legal risks, and confirm that I bear legal responsibility arising from deployment, operation, and charging behavior.": "J’ai lu et compris le rappel de conformité ci-dessus, je reconnais les risques juridiques associés et confirme assumer la responsabilité juridique liée au déploiement, à l’exploitation et à la facturation.",
|
||||
"I understand that disabling 2FA will remove all protection and backup codes": "Je comprends que la désactivation de la 2FA supprimera toute protection et les codes de secours",
|
||||
"Icon": "Icône",
|
||||
"Icon file must be 100 KB or smaller": "Le fichier icône doit être de 100 Ko ou moins",
|
||||
@ -2001,7 +1977,7 @@
|
||||
"If default auto group is enabled, newly created tokens start with auto instead of an empty group.": "Si le groupe auto par défaut est activé, les nouveaux jetons commencent avec auto au lieu d’un groupe vide.",
|
||||
"If the affinity channel fails and retry succeeds on another channel, update affinity to the successful channel.": "Si le canal affinitaire échoue et qu'une nouvelle tentative réussit sur un autre canal, mettre à jour l'affinité vers le canal ayant réussi.",
|
||||
"If this keeps happening, please report it on GitHub Issues.": "Si cela continue, veuillez le signaler sur GitHub Issues.",
|
||||
"If you provide generative AI services to the public in mainland China, you will fulfill legal obligations including filing, security assessment, content safety, complaint handling, generated content labeling, log retention, and personal information protection.": "If you provide generative AI services to the public in mainland China, you will fulfill legal obligations including filing, security assessment, content safety, complaint handling, generated content labeling, log retention, and personal information protection.",
|
||||
"If you provide generative AI services to the public in mainland China, you will fulfill legal obligations including filing, security assessment, content safety, complaint handling, generated content labeling, log retention, and personal information protection.": "Si vous fournissez des services d’IA générative au public en Chine continentale, vous remplirez les obligations légales applicables, notamment le dépôt, l’évaluation de sécurité, la sécurité du contenu, le traitement des plaintes, l’étiquetage du contenu généré, la conservation des journaux et la protection des informations personnelles.",
|
||||
"Ignored upstream models": "Modèles amont ignorés",
|
||||
"Image": "Image",
|
||||
"Image Generation": "Génération d'images",
|
||||
@ -2228,30 +2204,21 @@
|
||||
"Low balance": "Solde faible",
|
||||
"Lowest median first-token latency": "Latence médiane de premier jeton la plus faible",
|
||||
"m": "m",
|
||||
"Maintain a list of common questions for the dashboard help panel": "Maintenir une liste de questions courantes pour le panneau d'aide du tableau de bord",
|
||||
"Maintenance": "Maintenance",
|
||||
"Make it easier for teammates to pick the right group.": "Faciliter le choix du bon groupe pour les coéquipiers.",
|
||||
"Manage": "Gestion",
|
||||
"Manage account bindings for this user": "Gérer les liaisons de compte pour cet utilisateur",
|
||||
"Manage API channels and provider configurations": "Gérer les canaux d'API et les configurations des fournisseurs",
|
||||
"Manage Bindings": "Gérer les liaisons",
|
||||
"Manage catalog visibility and pricing.": "Gérer la visibilité du catalogue et les prix.",
|
||||
"Manage custom OAuth providers for user authentication": "Gérer les fournisseurs OAuth personnalisés pour l'authentification des utilisateurs",
|
||||
"Manage Keys": "Gérer les clés",
|
||||
"Manage local models for:": "Gérer les modèles locaux pour :",
|
||||
"Manage model deployments": "Gérer les déploiements de modèles",
|
||||
"Manage model metadata and configuration": "Gérer les métadonnées et la configuration des modèles",
|
||||
"Manage multi-key status and configuration for this channel": "Gérer le statut multi-clés et la configuration pour ce canal",
|
||||
"Manage Ollama Models": "Gérer les modèles Ollama",
|
||||
"Manage redemption codes for quota top-up": "Gérer les codes d'échange pour la recharge de quota",
|
||||
"Manage server log files. Log files accumulate over time; regular cleanup is recommended to free disk space.": "Gérer les fichiers journaux du serveur. Les fichiers journaux s'accumulent au fil du temps ; un nettoyage régulier est recommandé.",
|
||||
"Manage subscription plan creation, pricing and status": "Gérer la création, la tarification et le statut des abonnements",
|
||||
"Manage subscription plans and pricing.": "Gérer les plans d'abonnement et les tarifs.",
|
||||
"Manage Subscriptions": "Gérer les abonnements",
|
||||
"Manage users and their permissions": "Gérer les utilisateurs et leurs permissions",
|
||||
"Manage Vendors": "Gérer les fournisseurs",
|
||||
"Manage your API keys for accessing the service": "Gérer vos clés API pour accéder au service",
|
||||
"Manage your balance and payment methods": "Gérer votre solde et vos méthodes de paiement",
|
||||
"Manage your security settings and account access": "Gérer vos paramètres de sécurité et l'accès à votre compte",
|
||||
"Manual Disabled": "Désactivé manuellement",
|
||||
"Map fields from the user info response to local user attributes. Supports nested paths (e.g. ocs.data.id).": "Mapper les champs de la réponse des informations utilisateur vers les attributs utilisateur locaux. Supporte les chemins imbriqués (par exemple ocs.data.id).",
|
||||
@ -2295,7 +2262,7 @@
|
||||
"Maximum tokens per response": "Nombre maximal de jetons par réponse",
|
||||
"maxRequests ≥ 0, maxSuccess ≥ 1, both ≤ 2,147,483,647": "maxRequests ≥ 0, maxSuccess ≥ 1, les deux ≤ 2 147 483 647",
|
||||
"May be used for training by upstream provider": "Peut être utilisé pour l'entraînement par le fournisseur amont",
|
||||
"Media pricing": "Tarification multimodale",
|
||||
"Media pricing": "Tarification multimédia",
|
||||
"Median time-to-first-token (TTFT) sampled hourly per group": "Latence médiane jusqu'au premier jeton (TTFT) échantillonnée par heure et par groupe",
|
||||
"Medical Q&A, mental health support": "Q&R médicales, soutien en santé mentale",
|
||||
"Memory Hits": "Hits mémoire",
|
||||
@ -2323,6 +2290,8 @@
|
||||
"Minimum Trust Level": "Niveau de confiance minimum",
|
||||
"Minimum:": "Minimum :",
|
||||
"Minor blips in the last 30 days": "Légères perturbations sur les 30 derniers jours",
|
||||
"Mint a fresh pair below — or pick an existing one further down. Click Save when ready.": "Créez une nouvelle paire ci-dessous, ou choisissez une paire existante plus bas. Cliquez sur Enregistrer lorsque vous êtes prêt.",
|
||||
"Creates a Pancake product in the saved store using this plan’s title and price. Requires Waffo Pancake to be fully configured in Payment settings first.": "Crée un produit Pancake dans la boutique enregistrée avec le titre et le prix de ce forfait. Waffo Pancake doit d’abord être entièrement configuré dans les paramètres de paiement.",
|
||||
"Minute": "Minute",
|
||||
"minutes": "minutes",
|
||||
"Missing code": "Code manquant",
|
||||
@ -2617,8 +2586,9 @@
|
||||
"No Retry": "Pas de réessai",
|
||||
"No rules yet": "Aucune règle",
|
||||
"No rules yet. Add a group below to get started.": "Aucune règle pour le moment. Ajoutez un groupe ci-dessous pour commencer.",
|
||||
"No separate media pricing configured.": "Aucune tarification multimodale séparée n’est configurée.",
|
||||
"No separate media pricing configured.": "Aucune tarification multimédia séparée n’est configurée.",
|
||||
"No status code mappings configured.": "Aucun mappage de codes de statut configuré.",
|
||||
"No stores on this merchant yet. Set a return URL and click Create to mint your first pair.": "Ce marchand n’a pas encore de boutique. Définissez une URL de retour puis cliquez sur Créer pour générer votre première paire.",
|
||||
"No subscription plans yet": "Aucun plan d'abonnement pour le moment",
|
||||
"No subscription records": "Aucun enregistrement d'abonnement",
|
||||
"No Sync": "Pas de synchronisation",
|
||||
@ -2640,7 +2610,7 @@
|
||||
"No X Found": "Aucun X trouvé",
|
||||
"Node Name": "Nom du nœud",
|
||||
"Non-stream": "Non-streaming",
|
||||
"Non-zero invitation rewards require compliance confirmation in Payment Gateway settings.": "Non-zero invitation rewards require compliance confirmation in Payment Gateway settings.",
|
||||
"Non-zero invitation rewards require compliance confirmation in Payment Gateway settings.": "Les récompenses d’invitation non nulles nécessitent une confirmation de conformité dans les paramètres de la passerelle de paiement.",
|
||||
"None": "Aucun",
|
||||
"noreply@example.com": "noreply@example.com",
|
||||
"Normalized:": "Normalisé :",
|
||||
@ -2739,7 +2709,7 @@
|
||||
"OpenRouter": "OpenRouter",
|
||||
"opens in an external client. Trigger it from the sidebar or API key actions to launch the configured application.": "s'ouvre dans un client externe. Déclenchez-le depuis la barre latérale ou les actions de clé API pour lancer l'application configurée.",
|
||||
"Operation": "Opération",
|
||||
"operation and charging behavior": "operation and charging behavior",
|
||||
"operation and charging behavior": "exploitation et facturation",
|
||||
"Operation failed": "Opération échouée",
|
||||
"Operation successful": "Opération réussie",
|
||||
"Operation Type": "Type d'opération",
|
||||
@ -2762,6 +2732,7 @@
|
||||
"Opus Model": "Modèle Opus",
|
||||
"Or continue with": "Ou continuer avec",
|
||||
"Or enter this key manually:": "Ou entrez cette clé manuellement :",
|
||||
"or pick existing": "ou choisir une existante",
|
||||
"Order completed successfully": "Commande terminée avec succès",
|
||||
"Order History": "Historique des commandes",
|
||||
"Order Payment Method": "Moyen de paiement (commande)",
|
||||
@ -2780,7 +2751,6 @@
|
||||
"Overnight range": "Plage nocturne",
|
||||
"override": "remplacer",
|
||||
"Override": "Remplacer",
|
||||
"Override Anthropic headers, defaults, and thinking adapter behavior": "Remplacer les en-têtes, les valeurs par défaut et le comportement de l'adaptateur de réflexion d'Anthropic",
|
||||
"Override auto-discovered endpoint": "Remplacer le point de terminaison auto-découvert",
|
||||
"Override request headers": "Remplacer les en-têtes de requête",
|
||||
"Override request headers (JSON format)": "Surcharge des en-têtes de requête (format JSON)",
|
||||
@ -2796,6 +2766,7 @@
|
||||
"Page {{current}} of {{total}}": "Page {{current}} sur {{total}}",
|
||||
"PaLM": "PaLM",
|
||||
"Pan": "Panoramique",
|
||||
"Pancake": "Pancake",
|
||||
"Param Override": "Remplacement de paramètre",
|
||||
"Parameter": "Paramètre",
|
||||
"Parameter configuration error": "Erreur de configuration des paramètres",
|
||||
@ -2859,6 +2830,7 @@
|
||||
"Pay": "Pay",
|
||||
"Pay-as-you-go with real-time usage monitoring": "Paiement à l'usage avec suivi de la consommation en temps réel",
|
||||
"Payment": "Paiement",
|
||||
"Payment aggregator mode — onboard with your own registered company (offshore entity). Built for Enterprise.": "Mode agrégateur de paiement — embarquez avec votre propre société enregistrée (entité offshore). Conçu pour les entreprises.",
|
||||
"Payment Channel": "Canal de paiement",
|
||||
"Payment Gateway": "Passerelle de paiement",
|
||||
"Payment initiated": "Paiement initié",
|
||||
@ -2872,7 +2844,8 @@
|
||||
"Payment page opened": "Page de paiement ouverte",
|
||||
"Payment request failed": "Requête de paiement échouée",
|
||||
"Payment return URL": "URL de retour de paiement",
|
||||
"Payment, redemption codes, subscription plans, and invitation rewards are locked until the root administrator confirms the compliance terms.": "Payment, redemption codes, subscription plans, and invitation rewards are locked until the root administrator confirms the compliance terms.",
|
||||
"Payment return URL is empty. Create the product without a SuccessURL redirect?": "L’URL de retour de paiement est vide. Créer le produit sans redirection SuccessURL ?",
|
||||
"Payment, redemption codes, subscription plans, and invitation rewards are locked until the root administrator confirms the compliance terms.": "Les paiements, codes de兑换, forfaits d’abonnement et récompenses d’invitation sont verrouillés jusqu’à ce que l’administrateur racine confirme les conditions de conformité.",
|
||||
"Peak": "Pic",
|
||||
"Peak throughput": "Débit de pointe",
|
||||
"Penalises repetition of frequent tokens": "Pénalise la répétition des jetons fréquents",
|
||||
@ -2915,11 +2888,14 @@
|
||||
"Personal use": "Usage personnel",
|
||||
"Personal use mode": "Mode usage personnel",
|
||||
"Pick a date": "Choisir une date",
|
||||
"Pick or create both a store and a product before saving.": "Choisissez ou créez à la fois une boutique et un produit avant d’enregistrer.",
|
||||
"Ping Interval (seconds)": "Intervalle de ping (secondes)",
|
||||
"Plan": "Plan",
|
||||
"Plan Name": "Nom du plan",
|
||||
"Plan price must be greater than zero": "Le prix du forfait doit être supérieur à zéro",
|
||||
"Plan Subtitle": "Sous-titre du plan",
|
||||
"Plan Title": "Titre du plan",
|
||||
"Plan title is required": "Le titre du forfait est requis",
|
||||
"Planned maintenance on Friday at 22:00 UTC...": "Maintenance planifiée vendredi à 22:00 UTC...",
|
||||
"Platform": "Plateforme",
|
||||
"Playground": "Aire de jeux",
|
||||
@ -2968,7 +2944,7 @@
|
||||
"Please set Ollama API Base URL first": "Veuillez d'abord définir l'URL de base de l'API Ollama",
|
||||
"Please sign in to view {{module}}.": "Veuillez vous connecter pour voir {{module}}.",
|
||||
"Please try again later.": "Veuillez réessayer plus tard.",
|
||||
"Please type the following text to confirm:": "Please type the following text to confirm:",
|
||||
"Please type the following text to confirm:": "Veuillez saisir le texte suivant pour confirmer :",
|
||||
"Please upload key file(s)": "Veuillez télécharger le (s) fichier(s) clé (s)",
|
||||
"Please wait a moment before trying again.": "Veuillez patienter un instant avant de réessayer.",
|
||||
"Please wait a moment, human check is initializing...": "Veuillez patienter un instant, la vérification humaine s'initialise...",
|
||||
@ -3020,7 +2996,6 @@
|
||||
"Press Enter or comma to add tags": "Appuyez sur Entrée ou sur la virgule pour ajouter des tags",
|
||||
"Press Enter to use \"{{value}}\"": "Appuyez sur Entrée pour utiliser « {{value}} »",
|
||||
"Prevent server-side request forgery attacks": "Prévenir les attaques de falsification de requêtes côté serveur",
|
||||
"Prevent server-side request forgery attacks by controlling outbound requests.": "Prévenir les attaques de falsification de requêtes côté serveur en contrôlant les requêtes sortantes.",
|
||||
"Preview": "Aperçu",
|
||||
"Previous": "Précédent",
|
||||
"Previous branch": "Branche précédente",
|
||||
@ -3083,7 +3058,6 @@
|
||||
"Prompt Details": "Détails de l'invite",
|
||||
"Prompt price ($/1M tokens)": "Prix du prompt ($/1M de jetons)",
|
||||
"Proprietary": "Propriétaire",
|
||||
"Protect login and registration with Cloudflare Turnstile": "Protéger la connexion et l'inscription avec Cloudflare Turnstile",
|
||||
"Provide a JSON object where each key maps to an endpoint definition.": "Fournissez un objet JSON où chaque clé correspond à une définition de point de terminaison.",
|
||||
"Provide a valid URL starting with http:// or https://": "Fournissez une URL valide commençant par http:// ou https://",
|
||||
"Provide Markdown, HTML, or an external URL for the privacy policy": "Fournir du Markdown, du HTML ou une URL externe pour la politique de confidentialité",
|
||||
@ -3197,7 +3171,7 @@
|
||||
"Redemption code updated successfully": "Code d'échange mis à jour avec succès",
|
||||
"Redemption code(s) created successfully": "Code(s) d'échange créé(s) avec succès",
|
||||
"Redemption Codes": "Codes d'échange",
|
||||
"Redemption codes are disabled until the administrator confirms compliance terms.": "Redemption codes are disabled until the administrator confirms compliance terms.",
|
||||
"Redemption codes are disabled until the administrator confirms compliance terms.": "Les codes de兑换 sont désactivés jusqu’à ce que l’administrateur confirme les conditions de conformité.",
|
||||
"redemption codes.": "codes de rachat.",
|
||||
"Redemption failed": "Rédemption échouée",
|
||||
"Redemption successful! Added: {{quota}}": "Échange réussi ! Ajouté : {{quota}}",
|
||||
@ -3209,7 +3183,7 @@
|
||||
"Reference Video": "Vidéo de référence",
|
||||
"Referral link:": "Lien de parrainage :",
|
||||
"Referral Program": "Programme de parrainage",
|
||||
"Referral reward transfer is disabled until the administrator confirms compliance terms.": "Referral reward transfer is disabled until the administrator confirms compliance terms.",
|
||||
"Referral reward transfer is disabled until the administrator confirms compliance terms.": "Le transfert des récompenses de parrainage est désactivé jusqu’à ce que l’administrateur confirme les conditions de conformité.",
|
||||
"Refine models by provider, group, type, and tags.": "Affinez les modèles par fournisseur, groupe, type et tags.",
|
||||
"Refresh": "Actualiser",
|
||||
"Refresh Cache": "Actualiser le cache",
|
||||
@ -3225,6 +3199,7 @@
|
||||
"Regex": "Regex",
|
||||
"Regex Pattern": "Expression régulière",
|
||||
"Regex Replace": "Remplacement regex",
|
||||
"Register each URL into the matching Test Mode / Production Mode webhook slot in the Pancake dashboard. Separate endpoints prevent test traffic from accidentally crediting production accounts.": "Enregistrez chaque URL dans l’emplacement webhook correspondant au mode test ou production dans le tableau de bord Pancake. Des points de terminaison séparés évitent que le trafic de test crédite accidentellement les comptes de production.",
|
||||
"Register Passkey": "Enregistrer un Passkey",
|
||||
"Registration Enabled": "Inscription activée",
|
||||
"Registry (optional)": "Registre (optionnel)",
|
||||
@ -3368,7 +3343,6 @@
|
||||
"Reveal key": "Révéler la clé",
|
||||
"Revenue": "Revenu",
|
||||
"Review & initialize": "Vérifier et initialiser",
|
||||
"Review current version and fetch release notes.": "Examiner la version actuelle et récupérer les notes de publication.",
|
||||
"Review model rates before scaling traffic": "Consulter les tarifs des modèles avant d'augmenter le trafic",
|
||||
"Review your payment details": "Vérifier vos détails de paiement",
|
||||
"Review your purchase details before proceeding.": "Vérifiez les détails de votre achat avant de continuer.",
|
||||
@ -3504,8 +3478,10 @@
|
||||
"Select a group type": "Sélectionner un type de groupe",
|
||||
"Select a model to edit pricing": "Sélectionnez un modèle pour modifier sa tarification",
|
||||
"Select a preset...": "Sélectionner un préréglage...",
|
||||
"Select a product": "Sélectionner un produit",
|
||||
"Select a role": "Sélectionner un rôle",
|
||||
"Select a rule to edit.": "Sélectionnez une règle à modifier.",
|
||||
"Select a store": "Sélectionner une boutique",
|
||||
"Select a timestamp before clearing logs.": "Sélectionnez un horodatage avant de vider les journaux.",
|
||||
"Select a usage mode to continue": "Sélectionnez un mode d'utilisation pour continuer",
|
||||
"Select a verification method first": "Sélectionnez d'abord une méthode de vérification",
|
||||
@ -3580,7 +3556,7 @@
|
||||
"Sending...": "Envoi en cours...",
|
||||
"Sensitive Words": "Mots sensibles",
|
||||
"Sent the API key to FluentRead.": "Clé API envoyée à FluentRead.",
|
||||
"Separate image/audio prices are enabled.": "Les prix séparés image/audio sont activés.",
|
||||
"Separate image/audio prices are enabled.": "Les prix séparés pour l’image et l’audio sont activés.",
|
||||
"Serve multiple users or teams with billing and quota control.": "Servir plusieurs utilisateurs ou équipes avec gestion de la facturation et des quotas.",
|
||||
"Server Address": "Adresse du serveur",
|
||||
"Server IP": "IP du serveur",
|
||||
@ -3604,7 +3580,7 @@
|
||||
"Set quota amount and limits": "Définir le quota et les limites",
|
||||
"Set Request Header": "Définir un en-tête de requête",
|
||||
"Set runtime request header: override entire value, or manipulate comma-separated tokens": "Définir l'en-tête de requête : remplacer la valeur ou manipuler les tokens séparés par des virgules",
|
||||
"Set separate prices for cache reads and writes.": "Définissez des prix séparés pour les lectures et écritures du cache.",
|
||||
"Set separate prices for cache reads and writes.": "Définissez des prix distincts pour les lectures et écritures de cache.",
|
||||
"Set Tag": "Définir un tag",
|
||||
"Set tag for selected channels": "Définir un tag pour les canaux sélectionnés",
|
||||
"Set the language used across the interface": "Définir la langue utilisée dans l'interface",
|
||||
@ -3700,6 +3676,7 @@
|
||||
"Standard price": "Prix standard",
|
||||
"Start": "Début",
|
||||
"Start a conversation to see messages here": "Démarrez une conversation pour voir les messages ici",
|
||||
"Start collecting payments globally without registering a company. Built for indie developers, OPC sole proprietorships, and startups. Waffo Pancake acts as your Merchant of Record, taking on the compliance burden of global payment collection — consumption tax, invoicing, subscription management, refunds, and chargebacks. Solo developers can launch fast and stay focused on product instead of compliance. Onboard in minutes — one prompt to a full integration.": "Commencez à encaisser des paiements dans le monde entier sans créer de société. Conçu pour les développeurs indépendants, les entrepreneurs individuels OPC et les startups. Waffo Pancake agit comme Merchant of Record et prend en charge la conformité liée à l’encaissement mondial : taxes à la consommation, facturation, gestion des abonnements, remboursements et rétrofacturations. Les développeurs solo peuvent lancer rapidement leur produit et rester concentrés sur celui-ci plutôt que sur la conformité. Intégration en quelques minutes, d’une seule invite à une intégration complète.",
|
||||
"Start for free with generous limits. No credit card required.": "Commencez gratuitement avec des limites généreuses. Aucune carte de crédit requise.",
|
||||
"Start Time": "Heure de début",
|
||||
"Static page describing the platform.": "Page statique décrivant la plateforme.",
|
||||
@ -3720,6 +3697,8 @@
|
||||
"Step": "Étape",
|
||||
"Stop": "Arrêter",
|
||||
"Stop Retry": "Arrêter la relance",
|
||||
"Store": "Store",
|
||||
"Store + product created": "Boutique + produit créés",
|
||||
"Store ID": "ID du magasin",
|
||||
"Store ID is required": "L'ID de magasin est requis",
|
||||
"Stored value is not echoed back for security": "Par sécurité, la valeur enregistrée n'est pas affichée",
|
||||
@ -3753,8 +3732,9 @@
|
||||
"Subscription First": "Abonnement en priorité",
|
||||
"Subscription Management": "Gestion des abonnements",
|
||||
"Subscription Only": "Abonnement uniquement",
|
||||
"Subscription plan creation and changes are locked until the administrator confirms compliance terms in Payment Gateway settings.": "Subscription plan creation and changes are locked until the administrator confirms compliance terms in Payment Gateway settings.",
|
||||
"Subscription plan creation and changes are locked until the administrator confirms compliance terms in Payment Gateway settings.": "La création et la modification des forfaits d’abonnement sont verrouillées jusqu’à ce que l’administrateur confirme les conditions de conformité dans les paramètres de la passerelle de paiement.",
|
||||
"Subscription Plans": "Plans d'abonnement",
|
||||
"Subscription plans do NOT use the bound Product — each plan has its own dedicated Pancake product, set in the Subscriptions admin (or auto-minted via the \"+ Create\" button there).": "Les forfaits d’abonnement n’utilisent PAS le produit associé : chaque forfait dispose de son propre produit Pancake dédié, défini dans l’administration des abonnements (ou créé automatiquement via le bouton « + Créer »).",
|
||||
"Subtract": "Soustraire",
|
||||
"Success": "Succès",
|
||||
"Success rate": "Taux de réussite",
|
||||
@ -3879,8 +3859,11 @@
|
||||
"The administrator has not configured a user agreement yet.": "L'administrateur n'a pas encore configuré d'accord utilisateur.",
|
||||
"The administrator has not configured any about content yet. You can set it in the settings page, supporting HTML or URL.": "L'administrateur n'a pas encore configuré de contenu 'À propos'. Vous pouvez le définir dans la page des paramètres, en supportant le HTML ou l'URL.",
|
||||
"The binding will complete automatically after authorization": "La liaison se terminera automatiquement après l'autorisation",
|
||||
"The bound Product powers wallet top-ups: when a user enters any amount, new-api runs the checkout against this single Pancake product and overrides the price per session — no need to pre-create $1 / $5 / $10 SKUs.": "Le produit associé alimente les recharges de portefeuille : lorsqu’un utilisateur saisit un montant, new-api lance le paiement sur ce produit Pancake unique et remplace le prix pour la session, sans devoir précréer des SKU de 1 $, 5 $ ou 10 $.",
|
||||
"The bound Store is the parent container for every Pancake product new-api creates from this admin — both the wallet top-up product and any subscription-plan products. One store is enough; pin a different one only if you genuinely run separate Pancake catalogs.": "La boutique associée est le conteneur parent de tous les produits Pancake que new-api crée depuis cette administration, y compris le produit de recharge de portefeuille et les produits de forfaits d’abonnement. Une seule boutique suffit ; choisissez-en une autre uniquement si vous gérez réellement des catalogues Pancake séparés.",
|
||||
"The effective domain for Passkey registration. Must match the current domain or be its parent domain.": "Le domaine effectif pour l'enregistrement de la clé d'accès. Doit correspondre au domaine actuel ou être son domaine parent.",
|
||||
"The entered text does not match the required text.": "The entered text does not match the required text.",
|
||||
"The entered text does not match the required text.": "Le texte saisi ne correspond pas au texte requis.",
|
||||
"The environment (test vs production) is decided by the key you paste here — use the Test key while integrating, then swap to the Production key when going live.": "L’environnement (test ou production) est déterminé par la clé collée ici : utilisez la clé de test pendant l’intégration, puis remplacez-la par la clé de production lors de la mise en ligne.",
|
||||
"The exact model identifier as used in API requests.": "L'identifiant exact du modèle tel qu'utilisé dans les requêtes API.",
|
||||
"The following models have billing type conflicts (fixed price vs ratio billing). Confirm to proceed with the changes.": "Les modèles suivants présentent des conflits de type de facturation (prix fixe vs facturation au ratio). Confirmez pour procéder aux changements.",
|
||||
"The following models in the model redirect have not been added to the \"Models\" list and may fail during invocation due to missing available models:": "Les modèles suivants dans la redirection du modèle n'ont pas été ajoutés à la liste \"Modèles\" et peuvent échouer lors de l'invocation en raison de modèles disponibles manquants :",
|
||||
@ -3912,7 +3895,7 @@
|
||||
"This action will permanently remove 2FA protection from your account.": "Cette action supprimera définitivement la protection 2FA de votre compte.",
|
||||
"This channel is not an Ollama channel.": "Ce canal n'est pas un canal Ollama.",
|
||||
"This channel type does not support fetching models": "Ce type de canal ne prend pas en charge la récupération de modèles",
|
||||
"This confirmation unlocks payment, redemption code, subscription plan, and invitation reward features. Please read the statements carefully.": "This confirmation unlocks payment, redemption code, subscription plan, and invitation reward features. Please read the statements carefully.",
|
||||
"This confirmation unlocks payment, redemption code, subscription plan, and invitation reward features. Please read the statements carefully.": "Cette confirmation déverrouille les fonctionnalités de paiement, de codes de兑换, de forfaits d’abonnement et de récompenses d’invitation. Veuillez lire attentivement les déclarations.",
|
||||
"This controls model request rate limiting. Web/API route throttling is configured by environment variables and may still return 429.": "Ce réglage contrôle la limitation des requêtes de modèles. La limitation des routes Web/API se configure via les variables d'environnement et peut encore renvoyer 429.",
|
||||
"This data may be unreliable, use with caution": "Ces données peuvent être peu fiables, utilisez-les avec prudence",
|
||||
"This device does not support Passkey": "Cet appareil ne prend pas en charge Passkey",
|
||||
@ -3931,7 +3914,7 @@
|
||||
"This project must be used in compliance with the": "Ce projet doit être utilisé conformément aux",
|
||||
"This record was written by a pre-upgrade instance and lacks audit info. Upgrade the instance to record server IP, callback IP, payment method and system version.": "Cet enregistrement provient d’une instance avant la mise à niveau et n’inclut pas d’audits. Mettez à jour l’instance pour enregistrer l’IP du serveur, l’IP de callback, le moyen de paiement et la version du système.",
|
||||
"This site currently has {{count}} models enabled": "Ce site compte actuellement {{count}} modèles activés",
|
||||
"This tier catches any request that did not match earlier tiers.": "Ce palier récupère toute requête qui n’a pas correspondu aux paliers précédents.",
|
||||
"This tier catches any request that did not match earlier tiers.": "Ce palier récupère toute requête qui ne correspond à aucun palier précédent.",
|
||||
"this token group": "ce groupe de jetons",
|
||||
"this user group": "ce groupe d'utilisateurs",
|
||||
"This user has no bindings": "Cet utilisateur n'a aucune liaison",
|
||||
@ -4002,7 +3985,7 @@
|
||||
"Token price for cache reads.": "Prix par token pour les lectures du cache.",
|
||||
"Token price for creating cache entries.": "Prix par token pour la création d’entrées de cache.",
|
||||
"Token price for image input.": "Prix par token pour l’entrée image.",
|
||||
"Token prices": "Token prices",
|
||||
"Token prices": "Prix des tokens",
|
||||
"Token regenerated and copied to clipboard": "Jeton régénéré et copié dans le presse-papiers",
|
||||
"Token share by model author across the last 24 hours": "Part des tokens par auteur de modèle sur les dernières 24 heures",
|
||||
"Token share by model author across the past few weeks": "Part des tokens par auteur de modèle au cours des dernières semaines",
|
||||
@ -4114,7 +4097,7 @@
|
||||
"Type (common)": "Type (commun)",
|
||||
"Type *": "Type *",
|
||||
"Type a command or search...": "Tapez une commande ou recherchez...",
|
||||
"Type the confirmation text here": "Type the confirmation text here",
|
||||
"Type the confirmation text here": "Saisissez ici le texte de confirmation",
|
||||
"Type-Specific Settings": "Paramètres spécifiques au type",
|
||||
"Type:": "Type :",
|
||||
"UI granularity only — data is still aggregated hourly": "Granularité de l'interface uniquement — les données sont toujours agrégées par heure",
|
||||
@ -4239,6 +4222,7 @@
|
||||
"used": "utilisé",
|
||||
"Used": "Utilisé",
|
||||
"Used / Remaining": "Utilisé / Restant",
|
||||
"Used as SuccessURL on the new product. You'll be prompted to confirm if left blank.": "Utilisé comme SuccessURL sur le nouveau produit. Une confirmation vous sera demandée si ce champ est laissé vide.",
|
||||
"Used for load balancing. Higher weight = more requests": "Utilisé pour l'équilibrage de charge. Poids plus élevé = plus de requêtes",
|
||||
"Used in URLs and API routes": "Utilisé dans les URLs et les routes API",
|
||||
"Used Quota": "Quota utilisé",
|
||||
@ -4313,6 +4297,7 @@
|
||||
"Verify routing with Playground or your client": "Vérifiez le routage avec Playground ou votre client",
|
||||
"Verify Setup": "Vérifier la configuration",
|
||||
"Verify your database connection": "Vérifiez votre connexion à la base de données",
|
||||
"Verifying credentials and pulling stores from your Pancake account...": "Vérification des identifiants et récupération des boutiques depuis votre compte Pancake...",
|
||||
"Version Overrides": "Remplacements de version",
|
||||
"Vertex AI": "Vertex AI",
|
||||
"Vertex AI does not support functionResponse.id. Enable this to remove the field automatically.": "Vertex AI ne prend pas en charge functionResponse.id. Activez ceci pour supprimer automatiquement ce champ.",
|
||||
@ -4323,16 +4308,11 @@
|
||||
"Vidu": "Vidu",
|
||||
"View": "Afficher",
|
||||
"View all currently available models": "Voir tous les modèles actuellement disponibles",
|
||||
"View and manage your API usage logs": "Afficher et gérer vos journaux d'utilisation de l'API",
|
||||
"View and manage your drawing logs": "Afficher et gérer vos journaux de dessin",
|
||||
"View and manage your task logs": "Afficher et gérer vos journaux de tâches",
|
||||
"View dashboard overview and statistics": "Afficher le tableau de bord, aperçu et statistiques",
|
||||
"View detailed information about this user including balance, usage statistics, and invitation details.": "Afficher des informations détaillées sur cet utilisateur, y compris le solde, les statistiques d'utilisation et les détails d'invitation.",
|
||||
"View details": "Voir les détails",
|
||||
"View document": "Afficher le document",
|
||||
"View logs": "Voir les logs",
|
||||
"View mode": "Mode d'affichage",
|
||||
"View model call count analytics and charts": "Afficher les analyses et graphiques du nombre d'appels par modèle",
|
||||
"View model statistics and charts": "Afficher les statistiques et graphiques des modèles",
|
||||
"View Pricing": "Voir les tarifs",
|
||||
"View the complete details for this": "Voir les détails complets de ce",
|
||||
@ -4340,7 +4320,6 @@
|
||||
"View the complete error message and details": "Voir le message d'erreur et les détails complets",
|
||||
"View the complete prompt and its English translation": "Voir l'invite complète et sa traduction anglaise",
|
||||
"View the generated image": "Voir l'image générée",
|
||||
"View user consumption statistics and charts": "Voir les statistiques et graphiques de consommation",
|
||||
"View your topup transaction records and payment history": "Afficher vos enregistrements de transactions de recharge et votre historique de paiement",
|
||||
"Violation Code": "Code de violation",
|
||||
"Violation deduction amount": "Montant de la déduction pour violation",
|
||||
@ -4362,7 +4341,14 @@
|
||||
"Visual Parameter Override": "Remplacement visuel des paramètres",
|
||||
"VolcEngine": "VolcEngine",
|
||||
"vs. previous": "vs. précédent",
|
||||
"Waffo Aggregator Gateway": "Passerelle agrégatrice Waffo",
|
||||
"Waffo Pancake Dashboard": "Waffo Pancake Dashboard",
|
||||
"Waffo Pancake MoR": "Waffo Pancake MoR",
|
||||
"Waffo Pancake Payment Gateway": "Passerelle de paiement Waffo Pancake",
|
||||
"Waffo Pancake product created": "Produit Waffo Pancake créé",
|
||||
"Waffo Pancake product creation failed": "Échec de la création du produit Waffo Pancake",
|
||||
"Waffo Pancake save failed": "Échec de l’enregistrement Waffo Pancake",
|
||||
"Waffo Pancake settings saved": "Paramètres Waffo Pancake enregistrés",
|
||||
"Waffo Payment": "Paiement Waffo",
|
||||
"Waffo Payment Gateway": "Passerelle de paiement Waffo",
|
||||
"Waffo Public Key (Production)": "Clé publique Waffo (Production)",
|
||||
@ -4393,6 +4379,8 @@
|
||||
"Webhook Secret": "Secret du Webhook",
|
||||
"Webhook signing secret (leave blank unless updating)": "Secret de signature du Webhook (laisser vide sauf en cas de mise à jour)",
|
||||
"Webhook URL": "URL du Webhook",
|
||||
"Webhook URL (Production):": "URL du webhook (production) :",
|
||||
"Webhook URL (Test):": "URL du webhook (test) :",
|
||||
"Webhook URL:": "URL du Webhook :",
|
||||
"Website is under maintenance!": "Le site web est en maintenance !",
|
||||
"WeChat": "WeChat",
|
||||
@ -4432,6 +4420,7 @@
|
||||
"Whitelist (Only allow listed domains)": "Liste blanche (Autoriser uniquement les domaines listés)",
|
||||
"Whitelist (Only allow listed IPs)": "Liste blanche (Autoriser uniquement les adresses IP listées)",
|
||||
"whsec_xxx": "whsec_xxx",
|
||||
"Why only one store + product?": "Pourquoi une seule boutique + un seul produit ?",
|
||||
"Window:": "Fenêtre :",
|
||||
"Wire encoding for the embedding vectors": "Encodage filaire pour les vecteurs",
|
||||
"with conflicts": "avec des conflits",
|
||||
@ -4454,16 +4443,16 @@
|
||||
"You can close this tab once the binding completes or a success message appears in the original window.": "Vous pouvez fermer cet onglet une fois la liaison terminée ou qu'un message de succès apparaît dans la fenêtre d'origine.",
|
||||
"You can manually add them in \"Custom Model Names\", click \"Fill\" and then submit, or use the operations below to handle automatically.": "Vous pouvez les ajouter manuellement dans \"Noms de modèles personnalisés\", cliquer sur \"Remplir\" puis soumettre, ou utiliser les opérations ci-dessous pour les gérer automatiquement.",
|
||||
"You can only check in once per day": "Vous ne pouvez vous connecter qu'une fois par jour",
|
||||
"You commit not to use this system to implement, assist with, or indirectly implement acts that violate applicable laws and regulations, regulatory requirements, platform rules, public interests, or the lawful rights and interests of third parties.": "You commit not to use this system to implement, assist with, or indirectly implement acts that violate applicable laws and regulations, regulatory requirements, platform rules, public interests, or the lawful rights and interests of third parties.",
|
||||
"You commit to using upstream APIs, accounts, keys, quotas, and service capabilities only within the scope of lawful authorization obtained from upstream service providers, model service providers, or relevant rights holders, and will not conduct unauthorized resale, trafficking, distribution, or other non-compliant commercialization.": "You commit to using upstream APIs, accounts, keys, quotas, and service capabilities only within the scope of lawful authorization obtained from upstream service providers, model service providers, or relevant rights holders, and will not conduct unauthorized resale, trafficking, distribution, or other non-compliant commercialization.",
|
||||
"You commit not to use this system to implement, assist with, or indirectly implement acts that violate applicable laws and regulations, regulatory requirements, platform rules, public interests, or the lawful rights and interests of third parties.": "Vous vous engagez à ne pas utiliser ce système pour mettre en œuvre, faciliter ou indirectement réaliser des actes violant les lois et règlements applicables, les exigences réglementaires, les règles des plateformes, l’intérêt public ou les droits et intérêts légitimes de tiers.",
|
||||
"You commit to using upstream APIs, accounts, keys, quotas, and service capabilities only within the scope of lawful authorization obtained from upstream service providers, model service providers, or relevant rights holders, and will not conduct unauthorized resale, trafficking, distribution, or other non-compliant commercialization.": "Vous vous engagez à utiliser les API, comptes, clés, quotas et capacités de service en amont uniquement dans le cadre d’une autorisation légale obtenue auprès des fournisseurs de services en amont, fournisseurs de modèles ou ayants droit concernés, et à ne pas effectuer de revente, trafic, distribution ou autre commercialisation non conforme sans autorisation.",
|
||||
"You don't have necessary permission": "Vous n'avez pas la permission nécessaire",
|
||||
"You have legally obtained authorization for the connected model APIs, accounts, keys, and quotas.": "You have legally obtained authorization for the connected model APIs, accounts, keys, and quotas.",
|
||||
"You have legally obtained authorization for the connected model APIs, accounts, keys, and quotas.": "Vous avez légalement obtenu l’autorisation pour les API de modèles, comptes, clés et quotas connectés.",
|
||||
"You have unsaved changes": "Vous avez des modifications non enregistrées",
|
||||
"You have unsaved changes. Are you sure you want to leave?": "Vous avez des modifications non enregistrées. Êtes-vous sûr de vouloir quitter ?",
|
||||
"You Pay": "Vous payez",
|
||||
"You save": "Vous économisez",
|
||||
"You understand and independently bear legal responsibility arising from deployment, operation, and charging behavior.": "You understand and independently bear legal responsibility arising from deployment, operation, and charging behavior.",
|
||||
"You understand this compliance reminder is only for risk notice and does not constitute legal advice, a compliance review conclusion, or a guarantee of the legality of your use of this system; you should consult professional legal or compliance advisors based on your actual business scenario.": "You understand this compliance reminder is only for risk notice and does not constitute legal advice, a compliance review conclusion, or a guarantee of the legality of your use of this system; you should consult professional legal or compliance advisors based on your actual business scenario.",
|
||||
"You understand and independently bear legal responsibility arising from deployment, operation, and charging behavior.": "Vous comprenez et assumez indépendamment la responsabilité juridique découlant du déploiement, de l’exploitation et de la facturation.",
|
||||
"You understand this compliance reminder is only for risk notice and does not constitute legal advice, a compliance review conclusion, or a guarantee of the legality of your use of this system; you should consult professional legal or compliance advisors based on your actual business scenario.": "Vous comprenez que ce rappel de conformité est uniquement un avis de risque et ne constitue ni un conseil juridique, ni une conclusion d’examen de conformité, ni une garantie de la légalité de votre utilisation de ce système ; vous devez consulter des conseillers juridiques ou conformité professionnels selon votre situation réelle.",
|
||||
"You will be redirected to Telegram to complete the binding process.": "Vous serez redirigé vers Telegram pour terminer le processus de liaison.",
|
||||
"You'll be redirected automatically. You can return to the previous page if nothing happens after a few seconds.": "Vous serez redirigé automatiquement. Vous pouvez revenir à la page précédente si rien ne se passe après quelques secondes.",
|
||||
"your AI integration?": "votre intégration IA ?",
|
||||
|
||||
175
web/default/src/i18n/locales/ja.json
vendored
175
web/default/src/i18n/locales/ja.json
vendored
@ -120,7 +120,7 @@
|
||||
"Account ID *": "アカウントID *",
|
||||
"Account Info": "アカウント情報",
|
||||
"Account used when authenticating with the SMTP server": "SMTPサーバーで認証する際に使用されるアカウント",
|
||||
"acknowledge the related legal risks": "acknowledge the related legal risks",
|
||||
"acknowledge the related legal risks": "関連する法的リスクを認識します",
|
||||
"Across all groups": "全グループを通じて",
|
||||
"Action confirmation": "操作確認",
|
||||
"Actions": "操作",
|
||||
@ -247,7 +247,7 @@
|
||||
"Alipay": "Alipay",
|
||||
"All": "すべて",
|
||||
"All categories": "すべてのカテゴリ",
|
||||
"All conditions must match before this tier is used.": "この階層を使用するには、すべての条件に一致する必要があります。",
|
||||
"All conditions must match before this tier is used.": "この段階を使用するには、すべての条件に一致する必要があります。",
|
||||
"All edits are overwrite operations. Leave fields empty to keep current values unchanged.": "すべての編集は上書き操作です。現在の値を変更しないままにするには、フィールドを空のままにしてください。",
|
||||
"All files exceed the maximum size.": "すべてのファイルが最大サイズを超えています。",
|
||||
"All Groups": "すべてのグループ",
|
||||
@ -449,7 +449,6 @@
|
||||
"Automatically replaces upstream callback URLs with the server address.": "アップストリームコールバック URL をサーバーアドレスに自動的に置き換えます。",
|
||||
"Automatically selects the best available group with circuit breaker mechanism": "回路ブレーカーメカニズム付きで最適な利用可能なグループを自動的に選択",
|
||||
"Automatically sync model list when upstream changes are detected": "アップストリームの変更が検出されたときにモデルリストを自動的に同期",
|
||||
"Automatically test channels and notify users when limits are hit": "チャネルを自動的にテストし、制限に達したときにユーザーに通知する",
|
||||
"Availability (last 24h)": "可用性(過去 24 時間)",
|
||||
"Available": "空き",
|
||||
"Available disk space": "利用可能なディスク容量",
|
||||
@ -494,7 +493,7 @@
|
||||
"Bark Push URL": "BarkプッシュURL",
|
||||
"Base address provided by your Epay service": "Epayサービスによって提供されるベースアドレス",
|
||||
"Base amount. Actual deduction = base amount × system group rate.": "基本金額。実際の控除 = 基本金額 × システムグループ倍率。",
|
||||
"Base input and output token prices for this tier.": "この階層の基本入力・出力トークン価格。",
|
||||
"Base input and output token prices for this tier.": "この段階の入力および出力トークンの基本価格です。",
|
||||
"Base input price only": "基本入力価格のみ",
|
||||
"Base Limits": "基本枠",
|
||||
"Base multipliers applied when users select specific groups.": "ユーザーが特定のグループを選択したときに適用される基本乗数。",
|
||||
@ -531,6 +530,7 @@
|
||||
"Billing Process": "課金プロセス",
|
||||
"Billing Source": "課金ソース",
|
||||
"Bind": "バインド",
|
||||
"Bind a Pancake store + product": "Pancake のストアと商品を紐付ける",
|
||||
"Bind an email address to your account.": "アカウントにメールアドレスを紐付けます。",
|
||||
"Bind Email": "メールアドレス連携",
|
||||
"Bind Telegram Account": "Telegram連携",
|
||||
@ -557,9 +557,9 @@
|
||||
"Bound": "連携済み",
|
||||
"Bound Channels": "バインドされたチャネル",
|
||||
"Bound Only": "バインド済みのみ",
|
||||
"Bound product:": "紐付け済み商品:",
|
||||
"Bound store:": "紐付け済みストア:",
|
||||
"Bring channels back online after successful checks": "チェックが成功した後、チャネルをオンラインに戻します",
|
||||
"Broadcast a global banner to users. Markdown is supported.": "ユーザーにグローバルバナーをブロードキャストします。Markdownがサポートされています。",
|
||||
"Broadcast short system notices on the dashboard": "ダッシュボードに短いシステム通知をブロードキャストします",
|
||||
"Browse and compare": "参照と比較",
|
||||
"Browse available models and pricing": "利用可能なモデルと料金を確認",
|
||||
"Browse rankings by category": "カテゴリ別にランキングを表示",
|
||||
@ -586,7 +586,7 @@
|
||||
"Cache Directory Info": "キャッシュディレクトリ情報",
|
||||
"Cache Entries": "キャッシュエントリ",
|
||||
"Cache mode": "キャッシュモード",
|
||||
"Cache pricing": "キャッシュ価格",
|
||||
"Cache pricing": "キャッシュ料金",
|
||||
"Cache ratio": "キャッシュ倍率",
|
||||
"Cache Read": "キャッシュ読み取り",
|
||||
"Cache read price": "キャッシュ読み取り価格",
|
||||
@ -797,9 +797,9 @@
|
||||
"Completion price": "補完価格",
|
||||
"Completion price ($/1M tokens)": "完了価格 (トークン100万あたり$)",
|
||||
"Completion ratio": "補完倍率",
|
||||
"Compliance confirmation required": "Compliance confirmation required",
|
||||
"Compliance confirmed": "Compliance confirmed",
|
||||
"Compliance confirmed successfully": "Compliance confirmed successfully",
|
||||
"Compliance confirmation required": "コンプライアンス確認が必要です",
|
||||
"Compliance confirmed": "コンプライアンス確認済み",
|
||||
"Compliance confirmed successfully": "コンプライアンス確認が完了しました",
|
||||
"Concatenate channel system prompt with user's prompt": "チャネルのシステムプロンプトをユーザーのプロンプトと連結する",
|
||||
"Condition Path": "条件パス",
|
||||
"Condition Settings": "条件設定",
|
||||
@ -825,49 +825,27 @@
|
||||
"Configure API documentation links for the dashboard": "ダッシュボード用のAPIドキュメントリンクを設定",
|
||||
"Configure at:": "設定場所:",
|
||||
"Configure available payment methods. Provide a JSON array.": "利用可能な支払い方法を設定します。JSON配列を提供してください。",
|
||||
"Configure basic system information and branding": "基本的なシステム情報とブランディングを設定",
|
||||
"Configure channel affinity (sticky routing) rules": "チャネルアフィニティ(スティッキールーティング)ルールの設定",
|
||||
"Configure Creem products. Provide a JSON array.": "Creem製品を設定。JSON配列を提供してください。",
|
||||
"Configure currency conversion and quota display options": "通貨換算とクォータ表示オプションを設定します",
|
||||
"Configure custom OAuth providers for user authentication": "ユーザー認証のためのカスタムOAuthプロバイダーを設定",
|
||||
"Configure daily check-in rewards for users": "ユーザーの毎日のチェックイン報酬を設定する",
|
||||
"Configure discount rates based on recharge amounts": "チャージ金額に基づいた割引率を設定",
|
||||
"Configure experimental data export for the dashboard": "ダッシュボード用の実験的なデータエクスポートを設定",
|
||||
"Configure Gemini safety behavior, version overrides, and thinking adapter": "Geminiの安全動作、バージョン上書き、および思考アダプターを設定",
|
||||
"Configure group ratios and group-specific pricing rules": "グループ倍率とグループ固有の料金ルールを設定します",
|
||||
"Configure in your Creem dashboard": "Creem ダッシュボードで設定",
|
||||
"Configure io.net API key for model deployments": "モデルデプロイ用の io.net API キーを設定します",
|
||||
"Configure keyword filtering for prompts and responses.": "プロンプトと応答のキーワードフィルタリングを設定します。",
|
||||
"Configure model deployment provider settings": "モデルデプロイプロバイダー設定を構成します",
|
||||
"Configure model pricing ratios and tool prices": "モデル料金倍率とツール料金を設定します",
|
||||
"Configure model, caching, and group ratios used for billing": "請求に使用されるモデル、キャッシュ、およびグループ比率を設定します。",
|
||||
"Configure monitoring status page groups for the dashboard": "ダッシュボードの監視ステータスページグループを設定します。",
|
||||
"Configure outgoing email server for notifications": "通知用の送信メールサーバーを設定します。",
|
||||
"Configure Passkey (WebAuthn) login settings": "パスキー (WebAuthn) ログイン設定を設定",
|
||||
"Configure password-based login and registration": "パスワードベースのログインと登録を設定します。",
|
||||
"Configure per-model ratio for image inputs or outputs.": "画像の入力または出力のモデルごとの比率を設定します。",
|
||||
"Configure per-tool unit prices ($/1K calls). Per-request models do not incur additional tool fees.": "ツールごとの単価($/1K 回)を設定します。リクエスト課金モデルでは追加工具料金はかかりません。",
|
||||
"Configure predefined chat links surfaced to end users.": "エンドユーザーに表示される事前定義されたチャットリンクを設定します。",
|
||||
"Configure pricing model and display options": "料金モデルと表示オプションを設定します。",
|
||||
"Configure pricing ratios for a specific model.": "特定のモデルの料金比率を設定します。",
|
||||
"Configure rate limiting rules for a specific user group.": "特定のユーザーグループのレート制限ルールを設定します。",
|
||||
"Configure recharge pricing and payment gateway integrations": "リチャージ料金と決済ゲートウェイの統合を設定します。",
|
||||
"Configure system-wide behavior and defaults": "システム全体の動作とデフォルトを設定します。",
|
||||
"Configure the ratio for this group.": "このグループの比率を設定します。",
|
||||
"Configure third-party authentication providers": "サードパーティの認証プロバイダーを設定します。",
|
||||
"Configure upstream providers and routing.": "アップストリームプロバイダーとルーティングを設定。",
|
||||
"Configure upstream worker or proxy service for outbound requests": "アウトバウンドリクエストのアップストリームワーカーまたはプロキシサービスを設定します。",
|
||||
"Configure user quota allocation and rewards": "ユーザーのクォータ割り当てと報酬を設定します。",
|
||||
"Configure Waffo Pancake hosted checkout integration for USD-priced top-ups": "USD 建てのチャージ用に Waffo Pancake のホスト型チェックアウト連携を設定",
|
||||
"Configure Waffo payment aggregation platform integration": "Waffo決済アグリゲーションプラットフォームの連携を設定",
|
||||
"Configure xAI Grok model settings": "xAI Grokモデルの設定",
|
||||
"Configure xAI Grok model specific settings": "xAI Grok モデル固有の設定を構成",
|
||||
"Configure your account behavior preferences": "アカウントの動作設定を設定します。",
|
||||
"Configure your account preferences and integrations": "アカウントの設定と統合を設定します。",
|
||||
"Configured routes and latency checks": "設定済みルートとレイテンシ確認",
|
||||
"Confirm": "確認",
|
||||
"Confirm Action": "アクションの確認",
|
||||
"Confirm and enable": "Confirm and enable",
|
||||
"Confirm and enable": "確認して有効化",
|
||||
"Confirm Batch Update": "バッチ更新の確認",
|
||||
"Confirm Billing Conflicts": "請求の競合を確認",
|
||||
"Confirm Changes": "変更を確認",
|
||||
@ -875,8 +853,8 @@
|
||||
"Confirm cleanup of inactive disk cache?": "非アクティブなディスクキャッシュをクリーンアップしますか?",
|
||||
"Confirm clearing all channel affinity cache": "全チャネルアフィニティキャッシュのクリアを確認",
|
||||
"Confirm clearing cache for this rule": "このルールのキャッシュクリアを確認",
|
||||
"Confirm compliance": "Confirm compliance",
|
||||
"Confirm compliance terms": "Confirm compliance terms",
|
||||
"Confirm compliance": "コンプライアンスを確認",
|
||||
"Confirm compliance terms": "コンプライアンス条件を確認",
|
||||
"Confirm Creem Purchase": "Creem 購入を確認",
|
||||
"Confirm delete": "削除の確認",
|
||||
"Confirm disable": "無効化を確認",
|
||||
@ -891,11 +869,11 @@
|
||||
"auth.resetPasswordConfirm.description": "新しいパスワードを生成するには、リセット要求を確認してください。",
|
||||
"Confirm Selection": "選択の確認",
|
||||
"Confirm settings and finish setup": "設定を確認してセットアップを完了",
|
||||
"confirm that I bear legal responsibility arising from deployment": "confirm that I bear legal responsibility arising from deployment",
|
||||
"confirm that I bear legal responsibility arising from deployment": "デプロイに起因する法的責任を負うことを確認します",
|
||||
"Confirm Unbind": "連携解除を確認",
|
||||
"Confirm your identity before removing this Passkey from your account.": "このパスキーをアカウントから削除する前に、本人確認を行ってください。",
|
||||
"Confirm your identity with Two-factor Authentication before registering a Passkey.": "パスキーを登録する前に、二要素認証で本人確認を行ってください。",
|
||||
"Confirmed at {{time}} by user #{{userId}}": "Confirmed at {{time}} by user #{{userId}}",
|
||||
"Confirmed at {{time}} by user #{{userId}}": "ユーザー #{{userId}} が {{time}} に確認しました",
|
||||
"Conflict": "競合",
|
||||
"Connect": "接続",
|
||||
"Connect through OpenAI, Claude, Gemini, and other compatible API routes": "OpenAI、Claude、Gemini、その他の互換APIルートから接続",
|
||||
@ -929,11 +907,7 @@
|
||||
"Continue with Telegram": "Telegram で続行",
|
||||
"Continue with WeChat": "WeChat で続行",
|
||||
"Contract review, compliance, summarisation": "契約レビュー・コンプライアンス・要約",
|
||||
"Control log retention and clean historical data.": "ログの保持を制御し、履歴データをクリーンアップします。",
|
||||
"Control passthrough behavior and connection keep-alive settings": "パススルーの動作と接続のキープアライブ設定を制御します。",
|
||||
"Control request frequency to prevent abuse and manage system load.": "乱用を防ぎ、システム負荷を管理するためにリクエスト頻度を制御します。",
|
||||
"Control which models are exposed and which groups may use them.": "公開するモデルと、それらを利用できるグループを制御します。",
|
||||
"Control which sidebar areas and modules are available to all users.": "すべてのユーザーが利用できるサイドバー領域とモジュールを制御します。",
|
||||
"Controls how much the model thinks before answering": "モデルが回答前に考える深さを制御します",
|
||||
"Controls whether user verification (biometrics/PIN) is required during Passkey flows.": "Passkeyフロー中にユーザー認証(生体認証/PIN)が必要かどうかを制御します。",
|
||||
"Conversion rate from USD to your custom currency": "USDからカスタム通貨への換算レート",
|
||||
@ -981,7 +955,7 @@
|
||||
"Core concepts": "基本概念",
|
||||
"Core Configuration": "コア設定",
|
||||
"Core Features": "主要機能",
|
||||
"Core pricing": "基本価格",
|
||||
"Core pricing": "基本料金",
|
||||
"Cost": "コスト",
|
||||
"Cost in USD per request, regardless of tokens used.": "使用されたトークンに関係なく、リクエストあたりのUSDでのコスト。",
|
||||
"Cost Tracking": "コスト追跡",
|
||||
@ -1023,9 +997,13 @@
|
||||
"Create, revoke, and audit API tokens.": "APIトークンを作成、取り消し、監査。",
|
||||
"Created": "作成済み",
|
||||
"Created At": "作成日時",
|
||||
"Creating...": "作成中...",
|
||||
"Creation failed": "作成に失敗しました",
|
||||
"Credential generated": "認証情報を生成しました",
|
||||
"Credential refreshed": "認証情報を更新しました",
|
||||
"Credentials": "認証情報",
|
||||
"Credentials verification failed": "認証情報の検証に失敗しました",
|
||||
"Credentials verification failed — double-check Merchant ID and API private key.": "認証情報の検証に失敗しました。Merchant ID と API 秘密鍵を再確認してください。",
|
||||
"Credit remaining": "残りクレジット",
|
||||
"Creem API key (leave blank unless updating)": "Creem API キー (更新しない限り空白のまま)",
|
||||
"Creem Gateway": "Creem ゲートウェイ",
|
||||
@ -1034,7 +1012,6 @@
|
||||
"Creem products must be a JSON array": "Creem 製品は JSON 配列でなければなりません",
|
||||
"Cross-group": "グループ横断",
|
||||
"Cross-group retry": "グループ横断リトライ",
|
||||
"Curate quick links to your different Domains": "異なるドメインへのクイックリンクを厳選します",
|
||||
"Currency": "通貨",
|
||||
"Currency & Display": "通貨と表示",
|
||||
"Current Balance": "現在の残高",
|
||||
@ -1322,7 +1299,7 @@
|
||||
"Each line represents one keyword. Leave blank to disable the list but keep the switch states.": "各行は1つのキーワードを表します。リストを無効にするが、スイッチの状態を維持するには、空白のままにしてください。",
|
||||
"Each tier supports 0~2 conditions (over len, p, c); the last tier is the catch-all without conditions. Use len (full input length, including cache hits) for tier conditions to avoid mis-routing when cache hits reduce p.": "各層は 0~2 個の条件(len、p、c に対して)を設定でき、最後の層は条件なしのキャッチオール層です。キャッシュヒットによって p が下がり層が誤判定されるのを防ぐため、層の条件には len(キャッシュヒットを含む完全な入力長)を使用してください。",
|
||||
"Each tier supports up to 2 conditions; the last tier is the catch-all without conditions. Use full input length for tier conditions to avoid mis-routing when cache hits reduce billable input tokens.": "各階層は最大2つの条件をサポートします。最後の階層は条件なしのフォールバックです。キャッシュヒットで課金対象の入力トークンが減っても誤った階層にならないよう、条件には完全な入力長を使用してください。",
|
||||
"Each tier supports up to 2 conditions. The last tier without conditions is the fallback.": "Each tier supports up to 2 conditions. The last tier without conditions is the fallback.",
|
||||
"Each tier supports up to 2 conditions. The last tier without conditions is the fallback.": "各段階は最大 2 つの条件に対応します。条件のない最後の段階がフォールバックです。",
|
||||
"Earn rewards when your referrals add funds. Transfer accumulated rewards to your balance anytime.": "紹介者が資金を追加すると報酬を獲得できます。いつでも蓄積された報酬を残高に振り替えることができます。",
|
||||
"Edit": "編集",
|
||||
"Edit {{title}}": "{{title}}を編集",
|
||||
@ -1388,7 +1365,6 @@
|
||||
"Enable OIDC": "OIDCを有効にする",
|
||||
"Enable or disable this channel": "このチャネルを有効または無効にする",
|
||||
"Enable or disable this model": "このモデルを有効または無効にする",
|
||||
"Enable or disable top navigation modules globally.": "トップナビゲーションモジュールをグローバルに有効または無効にします。",
|
||||
"Enable Passkey": "Passkeyを有効にする",
|
||||
"Enable Performance Monitoring": "パフォーマンス監視を有効にする",
|
||||
"Enable rate limiting": "レート制限を有効にする",
|
||||
@ -1542,7 +1518,6 @@
|
||||
"Expired at": "有効期限",
|
||||
"Expired time cannot be earlier than current time": "有効期限は現在時刻より早く設定できません",
|
||||
"Expires": "有効期限",
|
||||
"Expose grouped Uptime Kuma status pages directly on the dashboard": "グループ化されたUptime Kumaステータスページをダッシュボードに直接公開する",
|
||||
"Expose ratio API": "倍率APIを公開",
|
||||
"Exposes the pricing/models catalog in the top navigation.": "価格/モデルカタログをトップナビゲーションに表示します。",
|
||||
"Expression": "式",
|
||||
@ -1575,7 +1550,7 @@
|
||||
"Failed to clean logs": "ログのクリーンアップに失敗しました",
|
||||
"Failed to complete order": "注文の完了に失敗しました",
|
||||
"Failed to complete Passkey login": "Passkeyログインの完了に失敗しました",
|
||||
"Failed to confirm compliance": "Failed to confirm compliance",
|
||||
"Failed to confirm compliance": "コンプライアンス確認に失敗しました",
|
||||
"Failed to contact GitHub releases API": "GitHub Releases API に接続できませんでした",
|
||||
"Failed to copy": "コピーに失敗しました",
|
||||
"Failed to copy channel": "チャネルのコピーに失敗しました",
|
||||
@ -1688,7 +1663,7 @@
|
||||
"Failed to update user": "ユーザーの更新に失敗しました",
|
||||
"Failure keywords": "失敗キーワード",
|
||||
"Fair": "公平",
|
||||
"Fallback tier": "フォールバック階層",
|
||||
"Fallback tier": "フォールバック段階",
|
||||
"FAQ": "FAQ",
|
||||
"FAQ added. Click \"Save Settings\" to apply.": "FAQ が追加されました。「設定を保存」をクリックして適用してください。",
|
||||
"FAQ deleted. Click \"Save Settings\" to apply.": "FAQ が削除されました。「設定を保存」をクリックして適用してください。",
|
||||
@ -1717,6 +1692,8 @@
|
||||
"Fill Codex CLI / Claude CLI Templates": "Codex CLI / Claude CLI テンプレートを入力",
|
||||
"Fill example (all channels)": "例を入力(全チャンネル)",
|
||||
"Fill example (specific channels)": "例を入力(特定チャンネル)",
|
||||
"Fill in both Merchant ID and API Private Key before creating.": "作成前に Merchant ID と API 秘密鍵の両方を入力してください。",
|
||||
"Fill in the credentials above to begin.": "開始するには上記の認証情報を入力してください。",
|
||||
"Fill in the following info to create a new subscription plan": "以下の情報を入力して新しいサブスクリプションプランを作成",
|
||||
"Fill Related Models": "関連モデルを入力",
|
||||
"Fill Template": "テンプレートを入力",
|
||||
@ -1751,7 +1728,6 @@
|
||||
"Final cost = base × multiplier when conditions match": "条件に一致する場合 最終費用 = 基準 × 倍率",
|
||||
"Final price multiplier (0.95 = 5% discount": "最終価格乗数 (0.95 = 5%割引",
|
||||
"Finance": "金融",
|
||||
"Fine-tune Midjourney integration and guardrails.": "Midjourneyの統合とガードレールを微調整します。",
|
||||
"Finish Time": "完了時刻",
|
||||
"First API request": "最初の API リクエスト",
|
||||
"First/Last Frame to Video": "先頭/末尾フレームから動画",
|
||||
@ -1988,8 +1964,8 @@
|
||||
"Human-readable name shown to users during Passkey prompts.": "パスキーのプロンプト中にユーザーに表示される、人間が読める名前。",
|
||||
"I confirm enabling high-risk retry": "高リスクリトライの有効化を確認します",
|
||||
"I have read and agree to the": "私は以下を読み、同意します",
|
||||
"I have read and understood the above compliance reminder": "I have read and understood the above compliance reminder",
|
||||
"I have read and understood the above compliance reminder, acknowledge the related legal risks, and confirm that I bear legal responsibility arising from deployment, operation, and charging behavior.": "I have read and understood the above compliance reminder, acknowledge the related legal risks, and confirm that I bear legal responsibility arising from deployment, operation, and charging behavior.",
|
||||
"I have read and understood the above compliance reminder": "上記のコンプライアンス注意事項を読み、理解しました",
|
||||
"I have read and understood the above compliance reminder, acknowledge the related legal risks, and confirm that I bear legal responsibility arising from deployment, operation, and charging behavior.": "上記のコンプライアンス注意事項を読み理解し、関連する法的リスクを認識したうえで、デプロイ、運用、課金行為に起因する法的責任を負うことを確認します。",
|
||||
"I understand that disabling 2FA will remove all protection and backup codes": "2FA を無効にすると、すべての保護とバックアップコードが削除されることを理解しています",
|
||||
"Icon": "アイコン",
|
||||
"Icon file must be 100 KB or smaller": "アイコンファイルは100KB以下である必要があります",
|
||||
@ -2001,7 +1977,7 @@
|
||||
"If default auto group is enabled, newly created tokens start with auto instead of an empty group.": "デフォルト auto グループを有効にすると、新規トークンは空グループではなく auto で開始します。",
|
||||
"If the affinity channel fails and retry succeeds on another channel, update affinity to the successful channel.": "アフィニティチャネルが失敗し、別のチャネルでリトライが成功した場合、アフィニティを成功したチャネルに更新します。",
|
||||
"If this keeps happening, please report it on GitHub Issues.": "この問題が続く場合は、GitHub Issues で報告してください。",
|
||||
"If you provide generative AI services to the public in mainland China, you will fulfill legal obligations including filing, security assessment, content safety, complaint handling, generated content labeling, log retention, and personal information protection.": "If you provide generative AI services to the public in mainland China, you will fulfill legal obligations including filing, security assessment, content safety, complaint handling, generated content labeling, log retention, and personal information protection.",
|
||||
"If you provide generative AI services to the public in mainland China, you will fulfill legal obligations including filing, security assessment, content safety, complaint handling, generated content labeling, log retention, and personal information protection.": "中国本土で一般向けに生成 AI サービスを提供する場合、届出、セキュリティ評価、コンテンツ安全、苦情対応、生成コンテンツのラベル表示、ログ保存、個人情報保護などの法的義務を履行します。",
|
||||
"Ignored upstream models": "無視する上流モデル",
|
||||
"Image": "画像",
|
||||
"Image Generation": "画像生成",
|
||||
@ -2228,30 +2204,21 @@
|
||||
"Low balance": "残高不足",
|
||||
"Lowest median first-token latency": "最初のトークンまでの中央値レイテンシの最小値",
|
||||
"m": "m",
|
||||
"Maintain a list of common questions for the dashboard help panel": "ダッシュボードのヘルプパネル用のよくある質問のリストを維持する",
|
||||
"Maintenance": "メンテナンス",
|
||||
"Make it easier for teammates to pick the right group.": "チームメイトが適切なグループを選択しやすくする。",
|
||||
"Manage": "管理",
|
||||
"Manage account bindings for this user": "このユーザーのアカウントバインドを管理",
|
||||
"Manage API channels and provider configurations": "APIチャネルとプロバイダー構成を管理する",
|
||||
"Manage Bindings": "バインド管理",
|
||||
"Manage catalog visibility and pricing.": "カタログの表示と価格設定を管理。",
|
||||
"Manage custom OAuth providers for user authentication": "ユーザー認証用のカスタムOAuthプロバイダーの管理",
|
||||
"Manage Keys": "キーの管理",
|
||||
"Manage local models for:": "次のローカルモデルを管理します。",
|
||||
"Manage model deployments": "モデルデプロイを管理する",
|
||||
"Manage model metadata and configuration": "モデルのメタデータと設定を管理する",
|
||||
"Manage multi-key status and configuration for this channel": "このチャネルのマルチキーのステータスと構成を管理する",
|
||||
"Manage Ollama Models": "オラマモデルの管理",
|
||||
"Manage redemption codes for quota top-up": "クォータのチャージ用の引き換えコードを管理する",
|
||||
"Manage server log files. Log files accumulate over time; regular cleanup is recommended to free disk space.": "サーバーログファイルを管理します。ログファイルは時間とともに蓄積されるため、定期的なクリーンアップでディスク容量を解放することを推奨します。",
|
||||
"Manage subscription plan creation, pricing and status": "サブスクリプションプランの作成、価格設定、ステータスを管理",
|
||||
"Manage subscription plans and pricing.": "サブスクリプションプランと価格設定を管理します。",
|
||||
"Manage Subscriptions": "サブスクリプションの管理",
|
||||
"Manage users and their permissions": "ユーザーとその権限を管理する",
|
||||
"Manage Vendors": "ベンダーの管理",
|
||||
"Manage your API keys for accessing the service": "サービスにアクセスするためのAPIキーを管理する",
|
||||
"Manage your balance and payment methods": "残高と支払い方法を管理する",
|
||||
"Manage your security settings and account access": "セキュリティ設定とアカウントアクセスを管理する",
|
||||
"Manual Disabled": "手動無効",
|
||||
"Map fields from the user info response to local user attributes. Supports nested paths (e.g. ocs.data.id).": "ユーザー情報レスポンスのフィールドをローカルユーザー属性にマッピングします。ネストされたパスをサポートします (例: ocs.data.id)。",
|
||||
@ -2295,7 +2262,7 @@
|
||||
"Maximum tokens per response": "1 回の応答あたりの最大トークン数",
|
||||
"maxRequests ≥ 0, maxSuccess ≥ 1, both ≤ 2,147,483,647": "maxRequests ≥ 0、maxSuccess ≥ 1、両方とも ≤ 2,147,483,647",
|
||||
"May be used for training by upstream provider": "上流プロバイダーが学習に利用する可能性があります",
|
||||
"Media pricing": "メディア価格",
|
||||
"Media pricing": "メディア料金",
|
||||
"Median time-to-first-token (TTFT) sampled hourly per group": "グループ別に毎時サンプリングした最初のトークンまでの中央値レイテンシ (TTFT)",
|
||||
"Medical Q&A, mental health support": "医療Q&A・メンタルヘルスサポート",
|
||||
"Memory Hits": "メモリヒット",
|
||||
@ -2323,6 +2290,8 @@
|
||||
"Minimum Trust Level": "最小トラストレベル",
|
||||
"Minimum:": "最小:",
|
||||
"Minor blips in the last 30 days": "直近 30 日で軽微な障害あり",
|
||||
"Mint a fresh pair below — or pick an existing one further down. Click Save when ready.": "下で新しいペアを作成するか、さらに下で既存のものを選択してください。準備ができたら保存をクリックします。",
|
||||
"Creates a Pancake product in the saved store using this plan’s title and price. Requires Waffo Pancake to be fully configured in Payment settings first.": "保存済みストアに、このプランのタイトルと価格を使って Pancake 商品を作成します。事前に支払い設定で Waffo Pancake を完全に設定する必要があります。",
|
||||
"Minute": "分",
|
||||
"minutes": "分",
|
||||
"Missing code": "コードが不足しています",
|
||||
@ -2617,8 +2586,9 @@
|
||||
"No Retry": "リトライなし",
|
||||
"No rules yet": "ルールがありません",
|
||||
"No rules yet. Add a group below to get started.": "まだルールがありません。下にグループを追加して開始してください。",
|
||||
"No separate media pricing configured.": "個別のメディア価格は設定されていません。",
|
||||
"No separate media pricing configured.": "個別のメディア料金は設定されていません。",
|
||||
"No status code mappings configured.": "ステータスコードのマッピングが設定されていません。",
|
||||
"No stores on this merchant yet. Set a return URL and click Create to mint your first pair.": "このマーチャントにはまだストアがありません。戻り URL を設定し、作成をクリックして最初のペアを作成してください。",
|
||||
"No subscription plans yet": "サブスクリプションプランがありません",
|
||||
"No subscription records": "サブスクリプション記録がありません",
|
||||
"No Sync": "同期なし",
|
||||
@ -2640,7 +2610,7 @@
|
||||
"No X Found": "X が見つかりません",
|
||||
"Node Name": "ノード名",
|
||||
"Non-stream": "非ストリーミング",
|
||||
"Non-zero invitation rewards require compliance confirmation in Payment Gateway settings.": "Non-zero invitation rewards require compliance confirmation in Payment Gateway settings.",
|
||||
"Non-zero invitation rewards require compliance confirmation in Payment Gateway settings.": "0 以外の招待報酬には、支払いゲートウェイ設定でのコンプライアンス確認が必要です。",
|
||||
"None": "なし",
|
||||
"noreply@example.com": "noreply@example.com",
|
||||
"Normalized:": "正規化:",
|
||||
@ -2739,7 +2709,7 @@
|
||||
"OpenRouter": "OpenRouter",
|
||||
"opens in an external client. Trigger it from the sidebar or API key actions to launch the configured application.": "外部クライアントで開きます。サイドバーまたはAPIキーアクションからトリガーして、設定されたアプリケーションを起動します。",
|
||||
"Operation": "操作",
|
||||
"operation and charging behavior": "operation and charging behavior",
|
||||
"operation and charging behavior": "運用および課金行為",
|
||||
"Operation failed": "操作に失敗しました",
|
||||
"Operation successful": "操作が成功しました",
|
||||
"Operation Type": "操作タイプ",
|
||||
@ -2762,6 +2732,7 @@
|
||||
"Opus Model": "Opus モデル",
|
||||
"Or continue with": "または、以下で続行",
|
||||
"Or enter this key manually:": "または、このキーを手動で入力してください:",
|
||||
"or pick existing": "または既存のものを選択",
|
||||
"Order completed successfully": "注文が正常に完了しました",
|
||||
"Order History": "注文履歴",
|
||||
"Order Payment Method": "注文の支払い方法",
|
||||
@ -2780,7 +2751,6 @@
|
||||
"Overnight range": "日跨ぎ範囲",
|
||||
"override": "上書き",
|
||||
"Override": "上書き",
|
||||
"Override Anthropic headers, defaults, and thinking adapter behavior": "Anthropicのヘッダー、デフォルト、および思考アダプターの動作を上書きする",
|
||||
"Override auto-discovered endpoint": "自動検出されたエンドポイントを上書きする",
|
||||
"Override request headers": "リクエストヘッダーを上書きする",
|
||||
"Override request headers (JSON format)": "リクエストヘッダーのオーバーライド (JSON 形式)",
|
||||
@ -2796,6 +2766,7 @@
|
||||
"Page {{current}} of {{total}}": "{{total}} ページ中 {{current}} ページ目",
|
||||
"PaLM": "PaLM",
|
||||
"Pan": "パン",
|
||||
"Pancake": "Pancake",
|
||||
"Param Override": "パラメータ上書き",
|
||||
"Parameter": "パラメータ",
|
||||
"Parameter configuration error": "パラメータ設定エラー",
|
||||
@ -2859,6 +2830,7 @@
|
||||
"Pay": "Pay",
|
||||
"Pay-as-you-go with real-time usage monitoring": "リアルタイム使用量監視付き従量課金制",
|
||||
"Payment": "支払い",
|
||||
"Payment aggregator mode — onboard with your own registered company (offshore entity). Built for Enterprise.": "決済アグリゲーターモード — 自社の登録済み法人(オフショア法人)でオンボーディングします。エンタープライズ向けです。",
|
||||
"Payment Channel": "決済チャネル",
|
||||
"Payment Gateway": "決済ゲートウェイ",
|
||||
"Payment initiated": "支払いが開始されました",
|
||||
@ -2872,7 +2844,8 @@
|
||||
"Payment page opened": "決済ページが開きました",
|
||||
"Payment request failed": "支払いリクエストが失敗しました",
|
||||
"Payment return URL": "決済完了後のリダイレクトURL",
|
||||
"Payment, redemption codes, subscription plans, and invitation rewards are locked until the root administrator confirms the compliance terms.": "Payment, redemption codes, subscription plans, and invitation rewards are locked until the root administrator confirms the compliance terms.",
|
||||
"Payment return URL is empty. Create the product without a SuccessURL redirect?": "支払い戻り URL が空です。SuccessURL リダイレクトなしで商品を作成しますか?",
|
||||
"Payment, redemption codes, subscription plans, and invitation rewards are locked until the root administrator confirms the compliance terms.": "ルート管理者がコンプライアンス条件を確認するまで、支払い、引換コード、サブスクリプションプラン、招待報酬はロックされます。",
|
||||
"Peak": "ピーク",
|
||||
"Peak throughput": "ピークスループット",
|
||||
"Penalises repetition of frequent tokens": "頻出トークンの繰り返しを抑制します",
|
||||
@ -2915,11 +2888,14 @@
|
||||
"Personal use": "個人利用",
|
||||
"Personal use mode": "個人利用モード",
|
||||
"Pick a date": "日付を選択",
|
||||
"Pick or create both a store and a product before saving.": "保存する前に、ストアと商品の両方を選択または作成してください。",
|
||||
"Ping Interval (seconds)": "Ping間隔(秒)",
|
||||
"Plan": "プラン",
|
||||
"Plan Name": "プラン名",
|
||||
"Plan price must be greater than zero": "プラン価格は 0 より大きい必要があります",
|
||||
"Plan Subtitle": "プランサブタイトル",
|
||||
"Plan Title": "プラン名",
|
||||
"Plan title is required": "プランタイトルは必須です",
|
||||
"Planned maintenance on Friday at 22:00 UTC...": "金曜日 22:00 UTC に計画メンテナンスがあります...",
|
||||
"Platform": "プラットフォーム",
|
||||
"Playground": "プレイグラウンド",
|
||||
@ -2968,7 +2944,7 @@
|
||||
"Please set Ollama API Base URL first": "最初にOllama APIベースURLを設定してください",
|
||||
"Please sign in to view {{module}}.": "{{module}} を表示するにはログインしてください。",
|
||||
"Please try again later.": "後でもう一度お試しください。",
|
||||
"Please type the following text to confirm:": "Please type the following text to confirm:",
|
||||
"Please type the following text to confirm:": "確認するには次のテキストを入力してください:",
|
||||
"Please upload key file(s)": "キーファイルをアップロードしてください",
|
||||
"Please wait a moment before trying again.": "しばらく待ってからもう一度お試しください。",
|
||||
"Please wait a moment, human check is initializing...": "しばらくお待ちください、人間チェックを初期化中です...",
|
||||
@ -3020,7 +2996,6 @@
|
||||
"Press Enter or comma to add tags": "Enterキーまたはコンマを押してタグを追加",
|
||||
"Press Enter to use \"{{value}}\"": "Enter キーを押して「{{value}}」を使用",
|
||||
"Prevent server-side request forgery attacks": "サーバーサイドリクエストフォージェリ攻撃を防ぐ",
|
||||
"Prevent server-side request forgery attacks by controlling outbound requests.": "アウトバウンドリクエストを制御することで、サーバーサイドリクエストフォージェリ攻撃を防ぎます。",
|
||||
"Preview": "プレビュー",
|
||||
"Previous": "前へ",
|
||||
"Previous branch": "前のブランチ",
|
||||
@ -3083,7 +3058,6 @@
|
||||
"Prompt Details": "プロンプトの詳細",
|
||||
"Prompt price ($/1M tokens)": "プロンプト価格 (100万トークンあたり$)",
|
||||
"Proprietary": "プロプライエタリ",
|
||||
"Protect login and registration with Cloudflare Turnstile": "Cloudflare Turnstileでログインと登録を保護する",
|
||||
"Provide a JSON object where each key maps to an endpoint definition.": "各キーがエンドポイント定義にマップされる JSON オブジェクトを提供してください。",
|
||||
"Provide a valid URL starting with http:// or https://": "http:// または https:// で始まる有効な URL を入力してください",
|
||||
"Provide Markdown, HTML, or an external URL for the privacy policy": "プライバシーポリシーにMarkdown、HTML、または外部URLを提供する",
|
||||
@ -3197,7 +3171,7 @@
|
||||
"Redemption code updated successfully": "引き換えコードを正常に更新しました",
|
||||
"Redemption code(s) created successfully": "引き換えコードが正常に作成されました",
|
||||
"Redemption Codes": "引き換えコード",
|
||||
"Redemption codes are disabled until the administrator confirms compliance terms.": "Redemption codes are disabled until the administrator confirms compliance terms.",
|
||||
"Redemption codes are disabled until the administrator confirms compliance terms.": "管理者がコンプライアンス条件を確認するまで、引換コードは無効です。",
|
||||
"redemption codes.": "引き換えコード。",
|
||||
"Redemption failed": "交換に失敗しました",
|
||||
"Redemption successful! Added: {{quota}}": "引き換え成功!追加:{{quota}}",
|
||||
@ -3209,7 +3183,7 @@
|
||||
"Reference Video": "参照動画",
|
||||
"Referral link:": "紹介リンク:",
|
||||
"Referral Program": "紹介プログラム",
|
||||
"Referral reward transfer is disabled until the administrator confirms compliance terms.": "Referral reward transfer is disabled until the administrator confirms compliance terms.",
|
||||
"Referral reward transfer is disabled until the administrator confirms compliance terms.": "管理者がコンプライアンス条件を確認するまで、紹介報酬の移転は無効です。",
|
||||
"Refine models by provider, group, type, and tags.": "プロバイダー、グループ、タイプ、タグでモデルを絞り込みます。",
|
||||
"Refresh": "更新",
|
||||
"Refresh Cache": "キャッシュ更新",
|
||||
@ -3225,6 +3199,7 @@
|
||||
"Regex": "正規表現",
|
||||
"Regex Pattern": "正規表現パターン",
|
||||
"Regex Replace": "正規表現置換",
|
||||
"Register each URL into the matching Test Mode / Production Mode webhook slot in the Pancake dashboard. Separate endpoints prevent test traffic from accidentally crediting production accounts.": "Pancake ダッシュボードで、各 URL を対応するテストモードまたは本番モードの Webhook スロットに登録してください。エンドポイントを分けることで、テスト通信が誤って本番アカウントに入金されることを防ぎます。",
|
||||
"Register Passkey": "Passkeyの登録",
|
||||
"Registration Enabled": "登録が有効",
|
||||
"Registry (optional)": "レジストリ (オプション)",
|
||||
@ -3368,7 +3343,6 @@
|
||||
"Reveal key": "キーを表示",
|
||||
"Revenue": "収益",
|
||||
"Review & initialize": "確認して初期化",
|
||||
"Review current version and fetch release notes.": "現在のバージョンを確認し、リリースノートを取得します。",
|
||||
"Review model rates before scaling traffic": "トラフィック拡大前にモデル料金を確認",
|
||||
"Review your payment details": "支払い詳細を確認",
|
||||
"Review your purchase details before proceeding.": "続行前に購入詳細を確認してください。",
|
||||
@ -3504,8 +3478,10 @@
|
||||
"Select a group type": "グループタイプを選択",
|
||||
"Select a model to edit pricing": "料金を編集するモデルを選択",
|
||||
"Select a preset...": "プリセットを選択...",
|
||||
"Select a product": "商品を選択",
|
||||
"Select a role": "ロールを選択",
|
||||
"Select a rule to edit.": "編集するルールを選択してください。",
|
||||
"Select a store": "ストアを選択",
|
||||
"Select a timestamp before clearing logs.": "ログをクリアする前にタイムスタンプを選択してください。",
|
||||
"Select a usage mode to continue": "続行するには使用モードを選択してください",
|
||||
"Select a verification method first": "まず検証方法を選択してください",
|
||||
@ -3580,7 +3556,7 @@
|
||||
"Sending...": "送信中...",
|
||||
"Sensitive Words": "機密語",
|
||||
"Sent the API key to FluentRead.": "API キーを FluentRead に送信しました。",
|
||||
"Separate image/audio prices are enabled.": "画像/音声の個別価格が有効です。",
|
||||
"Separate image/audio prices are enabled.": "画像/音声の個別料金が有効です。",
|
||||
"Serve multiple users or teams with billing and quota control.": "課金とクォータ管理で複数のユーザーやチームにサービスを提供します。",
|
||||
"Server Address": "サーバーURL",
|
||||
"Server IP": "サーバー IP",
|
||||
@ -3604,7 +3580,7 @@
|
||||
"Set quota amount and limits": "クォータ量と制限を設定",
|
||||
"Set Request Header": "リクエストヘッダーを設定",
|
||||
"Set runtime request header: override entire value, or manipulate comma-separated tokens": "ランタイムリクエストヘッダーを設定:値全体を上書き、またはカンマ区切りトークンを操作",
|
||||
"Set separate prices for cache reads and writes.": "キャッシュ読み取りと書き込みに個別価格を設定します。",
|
||||
"Set separate prices for cache reads and writes.": "キャッシュ読み取りと書き込みに個別の料金を設定します。",
|
||||
"Set Tag": "タグを設定",
|
||||
"Set tag for selected channels": "選択したチャネルにタグを設定",
|
||||
"Set the language used across the interface": "インターフェースで使用する言語を設定します",
|
||||
@ -3700,6 +3676,7 @@
|
||||
"Standard price": "標準価格",
|
||||
"Start": "開始",
|
||||
"Start a conversation to see messages here": "会話を開始すると、ここにメッセージが表示されます",
|
||||
"Start collecting payments globally without registering a company. Built for indie developers, OPC sole proprietorships, and startups. Waffo Pancake acts as your Merchant of Record, taking on the compliance burden of global payment collection — consumption tax, invoicing, subscription management, refunds, and chargebacks. Solo developers can launch fast and stay focused on product instead of compliance. Onboard in minutes — one prompt to a full integration.": "法人を設立せずに世界中で決済を受け付けられます。個人開発者、OPC 個人事業主、スタートアップ向けに設計されています。Waffo Pancake は Merchant of Record として、消費税、請求書、サブスクリプション管理、返金、チャージバックなど、グローバル決済のコンプライアンス負担を引き受けます。個人開発者はコンプライアンスではなくプロダクトに集中しながら素早くローンチできます。数分でオンボーディングし、1 つのプロンプトから完全な統合まで進められます。",
|
||||
"Start for free with generous limits. No credit card required.": "豊富な無料枠で始められます。クレジットカードは不要です。",
|
||||
"Start Time": "開始時間",
|
||||
"Static page describing the platform.": "プラットフォームを説明する静的ページ。",
|
||||
@ -3720,6 +3697,8 @@
|
||||
"Step": "ステップ",
|
||||
"Stop": "停止",
|
||||
"Stop Retry": "リトライ停止",
|
||||
"Store": "Store",
|
||||
"Store + product created": "ストア + 商品を作成しました",
|
||||
"Store ID": "ストア ID",
|
||||
"Store ID is required": "ストア ID は必須です",
|
||||
"Stored value is not echoed back for security": "セキュリティのため、保存済みの値は表示されません",
|
||||
@ -3753,8 +3732,9 @@
|
||||
"Subscription First": "サブスクリプション優先",
|
||||
"Subscription Management": "サブスクリプション管理",
|
||||
"Subscription Only": "サブスクリプションのみ",
|
||||
"Subscription plan creation and changes are locked until the administrator confirms compliance terms in Payment Gateway settings.": "Subscription plan creation and changes are locked until the administrator confirms compliance terms in Payment Gateway settings.",
|
||||
"Subscription plan creation and changes are locked until the administrator confirms compliance terms in Payment Gateway settings.": "管理者が支払いゲートウェイ設定でコンプライアンス条件を確認するまで、サブスクリプションプランの作成と変更はロックされます。",
|
||||
"Subscription Plans": "サブスクリプションプラン",
|
||||
"Subscription plans do NOT use the bound Product — each plan has its own dedicated Pancake product, set in the Subscriptions admin (or auto-minted via the \"+ Create\" button there).": "サブスクリプションプランは紐付け済み商品を使用しません。各プランには専用の Pancake 商品があり、サブスクリプション管理画面で設定します(または「+ 作成」ボタンで自動作成します)。",
|
||||
"Subtract": "減算",
|
||||
"Success": "成功",
|
||||
"Success rate": "成功率",
|
||||
@ -3879,8 +3859,11 @@
|
||||
"The administrator has not configured a user agreement yet.": "管理者がまだ利用規約を設定していません。",
|
||||
"The administrator has not configured any about content yet. You can set it in the settings page, supporting HTML or URL.": "管理者はまだ「概要」コンテンツを設定していません。設定ページでHTMLまたはURLをサポートして設定できます。",
|
||||
"The binding will complete automatically after authorization": "認証後、バインディングは自動的に完了します",
|
||||
"The bound Product powers wallet top-ups: when a user enters any amount, new-api runs the checkout against this single Pancake product and overrides the price per session — no need to pre-create $1 / $5 / $10 SKUs.": "紐付け済み商品はウォレットチャージに使用されます。ユーザーが任意の金額を入力すると、new-api はこの単一の Pancake 商品でチェックアウトを実行し、セッションごとに価格を上書きします。$1 / $5 / $10 の SKU を事前作成する必要はありません。",
|
||||
"The bound Store is the parent container for every Pancake product new-api creates from this admin — both the wallet top-up product and any subscription-plan products. One store is enough; pin a different one only if you genuinely run separate Pancake catalogs.": "紐付け済みストアは、この管理画面から new-api が作成するすべての Pancake 商品の親コンテナです。ウォレットチャージ商品とサブスクリプションプラン商品が含まれます。通常は 1 つのストアで十分です。別々の Pancake カタログを本当に運用する場合のみ別のストアを固定してください。",
|
||||
"The effective domain for Passkey registration. Must match the current domain or be its parent domain.": "Passkey登録のための有効なドメイン。現在のドメインまたはその親ドメインと一致する必要があります。",
|
||||
"The entered text does not match the required text.": "The entered text does not match the required text.",
|
||||
"The entered text does not match the required text.": "入力したテキストが必要なテキストと一致しません。",
|
||||
"The environment (test vs production) is decided by the key you paste here — use the Test key while integrating, then swap to the Production key when going live.": "環境(テスト/本番)はここに貼り付けるキーで決まります。統合中はテストキーを使用し、本番公開時に本番キーへ切り替えてください。",
|
||||
"The exact model identifier as used in API requests.": "APIリクエストで使用される正確なモデル識別子。",
|
||||
"The following models have billing type conflicts (fixed price vs ratio billing). Confirm to proceed with the changes.": "以下のモデルには請求タイプ(固定価格 vs 比率請求)の競合があります。変更を続行するには確認してください。",
|
||||
"The following models in the model redirect have not been added to the \"Models\" list and may fail during invocation due to missing available models:": "モデルリダイレクト内の以下のモデルは\"モデル\"リストに追加されていないため、利用可能なモデルが不足して呼び出しが失敗する可能性があります:",
|
||||
@ -3912,7 +3895,7 @@
|
||||
"This action will permanently remove 2FA protection from your account.": "この操作により、アカウントから2FA保護が完全に削除されます。",
|
||||
"This channel is not an Ollama channel.": "このチャンネルはOllamaチャンネルではありません。",
|
||||
"This channel type does not support fetching models": "このチャンネルタイプはモデルの取得をサポートしていません",
|
||||
"This confirmation unlocks payment, redemption code, subscription plan, and invitation reward features. Please read the statements carefully.": "This confirmation unlocks payment, redemption code, subscription plan, and invitation reward features. Please read the statements carefully.",
|
||||
"This confirmation unlocks payment, redemption code, subscription plan, and invitation reward features. Please read the statements carefully.": "この確認により、支払い、引換コード、サブスクリプションプラン、招待報酬の機能が解除されます。各項目をよく読んでください。",
|
||||
"This controls model request rate limiting. Web/API route throttling is configured by environment variables and may still return 429.": "これはモデルリクエストのレート制限を制御します。Web/API ルートのスロットリングは環境変数で設定され、引き続き 429 を返す場合があります。",
|
||||
"This data may be unreliable, use with caution": "このデータは信頼できない可能性があります。注意して使用してください",
|
||||
"This device does not support Passkey": "このデバイスはPasskeyをサポートしていません",
|
||||
@ -3931,7 +3914,7 @@
|
||||
"This project must be used in compliance with the": "このプロジェクトは、以下を遵守して使用する必要があります",
|
||||
"This record was written by a pre-upgrade instance and lacks audit info. Upgrade the instance to record server IP, callback IP, payment method and system version.": "古いバージョンのインスタンスがこの記録を書き込み、監査情報がありません。最新に更新し、サーバーIP・コールバックIP・支払方法・OSバージョンの記録を有効にしてください。",
|
||||
"This site currently has {{count}} models enabled": "このサイトでは現在 {{count}} 個のモデルが有効です",
|
||||
"This tier catches any request that did not match earlier tiers.": "この階層は、前の階層に一致しなかったすべてのリクエストを受けます。",
|
||||
"This tier catches any request that did not match earlier tiers.": "この段階は、前の段階に一致しなかったすべてのリクエストを受け取ります。",
|
||||
"this token group": "このトークングループ",
|
||||
"this user group": "このユーザーグループ",
|
||||
"This user has no bindings": "このユーザーには連携がありません",
|
||||
@ -3954,7 +3937,7 @@
|
||||
"Throughput short": "TPS",
|
||||
"Throughput trend": "スループット推移",
|
||||
"Tier": "ティア",
|
||||
"Tier conditions": "階層条件",
|
||||
"Tier conditions": "段階条件",
|
||||
"Tier name": "ティア名",
|
||||
"Tiered": "段階的",
|
||||
"Tiered (billing expression)": "段階的(課金式)",
|
||||
@ -4002,7 +3985,7 @@
|
||||
"Token price for cache reads.": "キャッシュ読み取りのトークン価格。",
|
||||
"Token price for creating cache entries.": "キャッシュ作成のトークン価格。",
|
||||
"Token price for image input.": "画像入力のトークン価格。",
|
||||
"Token prices": "Token prices",
|
||||
"Token prices": "トークン価格",
|
||||
"Token regenerated and copied to clipboard": "トークンが再生成され、クリップボードにコピーされました",
|
||||
"Token share by model author across the last 24 hours": "過去 24 時間におけるモデル提供者別のトークンシェア",
|
||||
"Token share by model author across the past few weeks": "過去数週間におけるモデル提供者別のトークンシェア",
|
||||
@ -4114,7 +4097,7 @@
|
||||
"Type (common)": "タイプ(共通)",
|
||||
"Type *": "タイプ *",
|
||||
"Type a command or search...": "コマンドまたは検索を入力...",
|
||||
"Type the confirmation text here": "Type the confirmation text here",
|
||||
"Type the confirmation text here": "確認テキストをここに入力",
|
||||
"Type-Specific Settings": "タイプ固有の設定",
|
||||
"Type:": "タイプ:",
|
||||
"UI granularity only — data is still aggregated hourly": "UIの粒度のみ — データは引き続き時間単位で集計されます",
|
||||
@ -4239,6 +4222,7 @@
|
||||
"used": "使用済み",
|
||||
"Used": "使用済み",
|
||||
"Used / Remaining": "使用済み / 残り",
|
||||
"Used as SuccessURL on the new product. You'll be prompted to confirm if left blank.": "新しい商品の SuccessURL として使用されます。空のままにすると確認を求められます。",
|
||||
"Used for load balancing. Higher weight = more requests": "ロードバランシングに使用されます。重みが高いほどリクエスト数が増えます",
|
||||
"Used in URLs and API routes": "URLとAPIルートで使用されます",
|
||||
"Used Quota": "使用済みクォータ",
|
||||
@ -4313,6 +4297,7 @@
|
||||
"Verify routing with Playground or your client": "Playground またはクライアントでルーティングを確認",
|
||||
"Verify Setup": "設定を確認",
|
||||
"Verify your database connection": "データベース接続を確認",
|
||||
"Verifying credentials and pulling stores from your Pancake account...": "認証情報を検証し、Pancake アカウントからストアを取得しています...",
|
||||
"Version Overrides": "バージョンオーバーライド",
|
||||
"Vertex AI": "Vertex AI",
|
||||
"Vertex AI does not support functionResponse.id. Enable this to remove the field automatically.": "Vertex AI は functionResponse.id フィールドをサポートしません。有効にすると自動的に削除します。",
|
||||
@ -4323,16 +4308,11 @@
|
||||
"Vidu": "Vidu",
|
||||
"View": "表示",
|
||||
"View all currently available models": "現在利用可能なすべてのモデルを表示",
|
||||
"View and manage your API usage logs": "API使用ログの表示と管理",
|
||||
"View and manage your drawing logs": "描画ログの表示と管理",
|
||||
"View and manage your task logs": "タスクログの表示と管理",
|
||||
"View dashboard overview and statistics": "ダッシュボードの概要と統計を表示",
|
||||
"View detailed information about this user including balance, usage statistics, and invitation details.": "残高、使用統計、招待の詳細など、このユーザーに関する詳細情報を表示します。",
|
||||
"View details": "詳細を表示",
|
||||
"View document": "ドキュメントを表示",
|
||||
"View logs": "ログを表示",
|
||||
"View mode": "表示モード",
|
||||
"View model call count analytics and charts": "モデル呼び出し回数の分析とグラフを表示",
|
||||
"View model statistics and charts": "モデルの統計とグラフを表示",
|
||||
"View Pricing": "価格を見る",
|
||||
"View the complete details for this": "この",
|
||||
@ -4340,7 +4320,6 @@
|
||||
"View the complete error message and details": "エラーメッセージと詳細を表示",
|
||||
"View the complete prompt and its English translation": "プロンプト全文と英語訳を表示",
|
||||
"View the generated image": "生成された画像を表示",
|
||||
"View user consumption statistics and charts": "ユーザー消費統計とチャートを表示",
|
||||
"View your topup transaction records and payment history": "チャージ取引記録と支払い履歴を表示",
|
||||
"Violation Code": "違反コード",
|
||||
"Violation deduction amount": "違反控除金額",
|
||||
@ -4362,7 +4341,14 @@
|
||||
"Visual Parameter Override": "パラメータ上書きのビジュアル編集",
|
||||
"VolcEngine": "VolcEngine",
|
||||
"vs. previous": "前期比",
|
||||
"Waffo Aggregator Gateway": "Waffo アグリゲーターゲートウェイ",
|
||||
"Waffo Pancake Dashboard": "Waffo Pancake Dashboard",
|
||||
"Waffo Pancake MoR": "Waffo Pancake MoR",
|
||||
"Waffo Pancake Payment Gateway": "Waffo Pancake 決済ゲートウェイ",
|
||||
"Waffo Pancake product created": "Waffo Pancake 商品を作成しました",
|
||||
"Waffo Pancake product creation failed": "Waffo Pancake 商品の作成に失敗しました",
|
||||
"Waffo Pancake save failed": "Waffo Pancake の保存に失敗しました",
|
||||
"Waffo Pancake settings saved": "Waffo Pancake 設定を保存しました",
|
||||
"Waffo Payment": "Waffo決済",
|
||||
"Waffo Payment Gateway": "Waffo決済ゲートウェイ",
|
||||
"Waffo Public Key (Production)": "Waffo公開鍵(本番)",
|
||||
@ -4393,6 +4379,8 @@
|
||||
"Webhook Secret": "Webhookシークレット",
|
||||
"Webhook signing secret (leave blank unless updating)": "Webhook署名シークレット (更新する場合を除き、空白のままにしてください)",
|
||||
"Webhook URL": "Webhook URL",
|
||||
"Webhook URL (Production):": "Webhook URL(本番):",
|
||||
"Webhook URL (Test):": "Webhook URL(テスト):",
|
||||
"Webhook URL:": "Webhook URL:",
|
||||
"Website is under maintenance!": "ウェブサイトはメンテナンス中です!",
|
||||
"WeChat": "WeChat Pay",
|
||||
@ -4432,6 +4420,7 @@
|
||||
"Whitelist (Only allow listed domains)": "ホワイトリスト (リストされたドメインのみを許可)",
|
||||
"Whitelist (Only allow listed IPs)": "ホワイトリスト (リストされたIPのみを許可)",
|
||||
"whsec_xxx": "whsec_xxx",
|
||||
"Why only one store + product?": "なぜストア + 商品は 1 つだけなのか?",
|
||||
"Window:": "ウィンドウ:",
|
||||
"Wire encoding for the embedding vectors": "ベクトルの転送エンコーディング",
|
||||
"with conflicts": "競合あり",
|
||||
@ -4454,16 +4443,16 @@
|
||||
"You can close this tab once the binding completes or a success message appears in the original window.": "バインディングが完了するか、元のウィンドウに成功メッセージが表示されたら、このタブを閉じることができます。",
|
||||
"You can manually add them in \"Custom Model Names\", click \"Fill\" and then submit, or use the operations below to handle automatically.": "\"カスタムモデル名\"で手動で追加し、\"入力\"をクリックしてから送信するか、以下の操作を使用して自動的に処理できます。",
|
||||
"You can only check in once per day": "チェックインできるのは1日1回のみです",
|
||||
"You commit not to use this system to implement, assist with, or indirectly implement acts that violate applicable laws and regulations, regulatory requirements, platform rules, public interests, or the lawful rights and interests of third parties.": "You commit not to use this system to implement, assist with, or indirectly implement acts that violate applicable laws and regulations, regulatory requirements, platform rules, public interests, or the lawful rights and interests of third parties.",
|
||||
"You commit to using upstream APIs, accounts, keys, quotas, and service capabilities only within the scope of lawful authorization obtained from upstream service providers, model service providers, or relevant rights holders, and will not conduct unauthorized resale, trafficking, distribution, or other non-compliant commercialization.": "You commit to using upstream APIs, accounts, keys, quotas, and service capabilities only within the scope of lawful authorization obtained from upstream service providers, model service providers, or relevant rights holders, and will not conduct unauthorized resale, trafficking, distribution, or other non-compliant commercialization.",
|
||||
"You commit not to use this system to implement, assist with, or indirectly implement acts that violate applicable laws and regulations, regulatory requirements, platform rules, public interests, or the lawful rights and interests of third parties.": "適用される法令、規制要件、プラットフォーム規則、公共の利益、または第三者の正当な権利利益に違反する行為を、このシステムを用いて実施、支援、または間接的に実施しないことを約束します。",
|
||||
"You commit to using upstream APIs, accounts, keys, quotas, and service capabilities only within the scope of lawful authorization obtained from upstream service providers, model service providers, or relevant rights holders, and will not conduct unauthorized resale, trafficking, distribution, or other non-compliant commercialization.": "上流 API、アカウント、キー、クォータ、サービス機能を、上流サービス提供者、モデルサービス提供者、または関連する権利者から取得した合法的な許可の範囲内でのみ使用し、無許可の再販売、転売、配布、その他の不適切な商業利用を行わないことを約束します。",
|
||||
"You don't have necessary permission": "必要な権限がありません",
|
||||
"You have legally obtained authorization for the connected model APIs, accounts, keys, and quotas.": "You have legally obtained authorization for the connected model APIs, accounts, keys, and quotas.",
|
||||
"You have legally obtained authorization for the connected model APIs, accounts, keys, and quotas.": "接続されたモデル API、アカウント、キー、クォータについて合法的な許可を取得しています。",
|
||||
"You have unsaved changes": "未保存の変更があります",
|
||||
"You have unsaved changes. Are you sure you want to leave?": "未保存の変更があります。離れてもよろしいですか?",
|
||||
"You Pay": "お支払い額",
|
||||
"You save": "節約額",
|
||||
"You understand and independently bear legal responsibility arising from deployment, operation, and charging behavior.": "You understand and independently bear legal responsibility arising from deployment, operation, and charging behavior.",
|
||||
"You understand this compliance reminder is only for risk notice and does not constitute legal advice, a compliance review conclusion, or a guarantee of the legality of your use of this system; you should consult professional legal or compliance advisors based on your actual business scenario.": "You understand this compliance reminder is only for risk notice and does not constitute legal advice, a compliance review conclusion, or a guarantee of the legality of your use of this system; you should consult professional legal or compliance advisors based on your actual business scenario.",
|
||||
"You understand and independently bear legal responsibility arising from deployment, operation, and charging behavior.": "デプロイ、運用、課金行為に起因する法的責任を理解し、独立して負います。",
|
||||
"You understand this compliance reminder is only for risk notice and does not constitute legal advice, a compliance review conclusion, or a guarantee of the legality of your use of this system; you should consult professional legal or compliance advisors based on your actual business scenario.": "このコンプライアンス注意事項はリスク通知にすぎず、法的助言、コンプライアンス審査の結論、または本システム利用の合法性の保証ではないことを理解しています。実際の事業状況に応じて、専門の法律またはコンプライアンス担当者に相談してください。",
|
||||
"You will be redirected to Telegram to complete the binding process.": "バインドプロセスを完了するためにTelegramにリダイレクトされます。",
|
||||
"You'll be redirected automatically. You can return to the previous page if nothing happens after a few seconds.": "自動的にリダイレクトされます。数秒経っても何も起こらない場合は、前のページに戻ることができます。",
|
||||
"your AI integration?": "AIインテグレーションを?",
|
||||
|
||||
175
web/default/src/i18n/locales/ru.json
vendored
175
web/default/src/i18n/locales/ru.json
vendored
@ -120,7 +120,7 @@
|
||||
"Account ID *": "ID аккаунта *",
|
||||
"Account Info": "Информация об аккаунте",
|
||||
"Account used when authenticating with the SMTP server": "Учетная запись, используемая при аутентификации с SMTP-сервером",
|
||||
"acknowledge the related legal risks": "acknowledge the related legal risks",
|
||||
"acknowledge the related legal risks": "признаю связанные правовые риски",
|
||||
"Across all groups": "По всем группам",
|
||||
"Action confirmation": "Подтверждение действия",
|
||||
"Actions": "Операции",
|
||||
@ -247,7 +247,7 @@
|
||||
"Alipay": "Alipay",
|
||||
"All": "Все",
|
||||
"All categories": "Все категории",
|
||||
"All conditions must match before this tier is used.": "All conditions must match before this tier is used.",
|
||||
"All conditions must match before this tier is used.": "Все условия должны совпасть, прежде чем будет использован этот уровень.",
|
||||
"All edits are overwrite operations. Leave fields empty to keep current values unchanged.": "Все изменения являются операциями перезаписи. Оставьте поля пустыми, чтобы сохранить текущие значения без изменений.",
|
||||
"All files exceed the maximum size.": "Все файлы превышают максимальный размер.",
|
||||
"All Groups": "Все группы",
|
||||
@ -449,7 +449,6 @@
|
||||
"Automatically replaces upstream callback URLs with the server address.": "Автоматически заменяет URL обратных вызовов upstream на адрес сервера.",
|
||||
"Automatically selects the best available group with circuit breaker mechanism": "Автоматически выбирает лучшую доступную группу с механизмом circuit breaker",
|
||||
"Automatically sync model list when upstream changes are detected": "Автоматически синхронизировать список моделей при обнаружении изменений у провайдера",
|
||||
"Automatically test channels and notify users when limits are hit": "Автоматически тестировать каналы и уведомлять пользователей при достижении лимитов",
|
||||
"Availability (last 24h)": "Доступность (последние 24 ч)",
|
||||
"Available": "Доступно",
|
||||
"Available disk space": "Доступное дисковое пространство",
|
||||
@ -494,7 +493,7 @@
|
||||
"Bark Push URL": "URL для push-уведомлений Bark",
|
||||
"Base address provided by your Epay service": "Базовый адрес, предоставленный вашим сервисом Epay",
|
||||
"Base amount. Actual deduction = base amount × system group rate.": "Базовая сумма. Фактический вычет = базовая сумма × коэффициент группы.",
|
||||
"Base input and output token prices for this tier.": "Base input and output token prices for this tier.",
|
||||
"Base input and output token prices for this tier.": "Базовые цены входных и выходных токенов для этого уровня.",
|
||||
"Base input price only": "Только базовая цена входа",
|
||||
"Base Limits": "Базовые лимиты",
|
||||
"Base multipliers applied when users select specific groups.": "Базовые множители, применяемые, когда пользователи выбирают определенные группы.",
|
||||
@ -531,6 +530,7 @@
|
||||
"Billing Process": "Процесс тарификации",
|
||||
"Billing Source": "Источник биллинга",
|
||||
"Bind": "Привязать",
|
||||
"Bind a Pancake store + product": "Привязать магазин и продукт Pancake",
|
||||
"Bind an email address to your account.": "Привяжите адрес электронной почты к вашему аккаунту.",
|
||||
"Bind Email": "Привязать Email",
|
||||
"Bind Telegram Account": "Привязать аккаунт Telegram",
|
||||
@ -557,9 +557,9 @@
|
||||
"Bound": "Привязано",
|
||||
"Bound Channels": "Привязанные каналы",
|
||||
"Bound Only": "Только привязанные",
|
||||
"Bound product:": "Привязанный продукт:",
|
||||
"Bound store:": "Привязанный магазин:",
|
||||
"Bring channels back online after successful checks": "Вернуть каналы в онлайн после успешных проверок",
|
||||
"Broadcast a global banner to users. Markdown is supported.": "Транслировать глобальный баннер пользователям. Поддерживается Markdown.",
|
||||
"Broadcast short system notices on the dashboard": "Транслировать короткие системные уведомления на панели управления",
|
||||
"Browse and compare": "Просмотр и сравнение",
|
||||
"Browse available models and pricing": "Просмотрите доступные модели и цены",
|
||||
"Browse rankings by category": "Просмотр рейтингов по категориям",
|
||||
@ -586,7 +586,7 @@
|
||||
"Cache Directory Info": "Информация о каталоге кэша",
|
||||
"Cache Entries": "Записи кэша",
|
||||
"Cache mode": "Режим кэша",
|
||||
"Cache pricing": "Cache pricing",
|
||||
"Cache pricing": "Цены кэша",
|
||||
"Cache ratio": "Коэффициент кэша",
|
||||
"Cache Read": "Чтение кэша",
|
||||
"Cache read price": "Цена чтения кэша",
|
||||
@ -797,9 +797,9 @@
|
||||
"Completion price": "Цена завершения",
|
||||
"Completion price ($/1M tokens)": "Цена завершения ($/1 млн токенов)",
|
||||
"Completion ratio": "Коэффициент завершения",
|
||||
"Compliance confirmation required": "Compliance confirmation required",
|
||||
"Compliance confirmed": "Compliance confirmed",
|
||||
"Compliance confirmed successfully": "Compliance confirmed successfully",
|
||||
"Compliance confirmation required": "Требуется подтверждение соответствия",
|
||||
"Compliance confirmed": "Соответствие подтверждено",
|
||||
"Compliance confirmed successfully": "Соответствие успешно подтверждено",
|
||||
"Concatenate channel system prompt with user's prompt": "Объединить системный промпт канала с промптом пользователя",
|
||||
"Condition Path": "Путь условия",
|
||||
"Condition Settings": "Настройки условия",
|
||||
@ -825,49 +825,27 @@
|
||||
"Configure API documentation links for the dashboard": "Настроить ссылки на документацию API для панели управления",
|
||||
"Configure at:": "Настроить в:",
|
||||
"Configure available payment methods. Provide a JSON array.": "Настроить доступные способы оплаты. Предоставьте JSON-массив.",
|
||||
"Configure basic system information and branding": "Настроить основную информацию о системе и брендинг",
|
||||
"Configure channel affinity (sticky routing) rules": "Настроить правила привязки к каналу (липкая маршрутизация)",
|
||||
"Configure Creem products. Provide a JSON array.": "Настройте продукты Creem. Укажите массив JSON.",
|
||||
"Configure currency conversion and quota display options": "Настройте конвертацию валюты и параметры отображения квот",
|
||||
"Configure custom OAuth providers for user authentication": "Настройка пользовательских OAuth-провайдеров для аутентификации пользователей",
|
||||
"Configure daily check-in rewards for users": "Настроить ежедневные награды за регистрацию для пользователей",
|
||||
"Configure discount rates based on recharge amounts": "Настроить скидки в зависимости от сумм пополнения",
|
||||
"Configure experimental data export for the dashboard": "Настроить экспериментальный экспорт данных для панели управления",
|
||||
"Configure Gemini safety behavior, version overrides, and thinking adapter": "Настроить поведение безопасности Gemini, переопределения версий и адаптер мышления",
|
||||
"Configure group ratios and group-specific pricing rules": "Настройте коэффициенты групп и правила тарификации для групп",
|
||||
"Configure in your Creem dashboard": "Настройте в панели управления Creem",
|
||||
"Configure io.net API key for model deployments": "Настройте API-ключ io.net для развертывания моделей",
|
||||
"Configure keyword filtering for prompts and responses.": "Настроить фильтрацию по ключевым словам для запросов и ответов.",
|
||||
"Configure model deployment provider settings": "Настройте параметры провайдера развертывания моделей",
|
||||
"Configure model pricing ratios and tool prices": "Настройте коэффициенты тарификации моделей и цены инструментов",
|
||||
"Configure model, caching, and group ratios used for billing": "Настроить модель, кэширование и групповые коэффициенты, используемые для выставления счетов",
|
||||
"Configure monitoring status page groups for the dashboard": "Настроить группы страниц состояния мониторинга для панели управления",
|
||||
"Configure outgoing email server for notifications": "Настроить исходящий почтовый сервер для уведомлений",
|
||||
"Configure Passkey (WebAuthn) login settings": "Настроить настройки входа с помощью ключа доступа (WebAuthn)",
|
||||
"Configure password-based login and registration": "Настроить вход и регистрацию по паролю",
|
||||
"Configure per-model ratio for image inputs or outputs.": "Настроить коэффициент для каждой модели для ввода или вывода изображений.",
|
||||
"Configure per-tool unit prices ($/1K calls). Per-request models do not incur additional tool fees.": "Настройте стоимость единицы на инструмент ($/1K вызовов). Для моделей с оплатой за запрос доп. плата за инструменты не взимается.",
|
||||
"Configure predefined chat links surfaced to end users.": "Настроить предопределенные ссылки чата, отображаемые конечным пользователям.",
|
||||
"Configure pricing model and display options": "Настроить модель ценообразования и параметры отображения",
|
||||
"Configure pricing ratios for a specific model.": "Настроить коэффициенты ценообразования для конкретной модели.",
|
||||
"Configure rate limiting rules for a specific user group.": "Настроить правила ограничения скорости для конкретной группы пользователей.",
|
||||
"Configure recharge pricing and payment gateway integrations": "Настроить цены пополнения и интеграции платежных шлюзов",
|
||||
"Configure system-wide behavior and defaults": "Настроить общесистемное поведение и значения по умолчанию",
|
||||
"Configure the ratio for this group.": "Настроить коэффициент для этой группы.",
|
||||
"Configure third-party authentication providers": "Настроить сторонних поставщиков аутентификации",
|
||||
"Configure upstream providers and routing.": "Настроить провайдеров верхнего уровня и маршрутизацию.",
|
||||
"Configure upstream worker or proxy service for outbound requests": "Настроить вышестоящий рабочий или прокси-сервис для исходящих запросов",
|
||||
"Configure user quota allocation and rewards": "Настроить распределение пользовательских квот и вознаграждений",
|
||||
"Configure Waffo Pancake hosted checkout integration for USD-priced top-ups": "Настроить хостовую интеграцию Waffo Pancake (hosted checkout) для пополнений в USD",
|
||||
"Configure Waffo payment aggregation platform integration": "Настроить интеграцию платёжной платформы Waffo",
|
||||
"Configure xAI Grok model settings": "Настроить параметры модели xAI Grok",
|
||||
"Configure xAI Grok model specific settings": "Настроить параметры модели xAI Grok",
|
||||
"Configure your account behavior preferences": "Настроить предпочтения поведения вашей учетной записи",
|
||||
"Configure your account preferences and integrations": "Настроить параметры и интеграции вашей учетной записи",
|
||||
"Configured routes and latency checks": "Настроенные маршруты и проверки задержки",
|
||||
"Confirm": "Подтверждение",
|
||||
"Confirm Action": "Подтвердить действие",
|
||||
"Confirm and enable": "Confirm and enable",
|
||||
"Confirm and enable": "Подтвердить и включить",
|
||||
"Confirm Batch Update": "Подтвердить пакетное обновление",
|
||||
"Confirm Billing Conflicts": "Подтвердить конфликты выставления счетов",
|
||||
"Confirm Changes": "Подтвердить изменения",
|
||||
@ -875,8 +853,8 @@
|
||||
"Confirm cleanup of inactive disk cache?": "Подтвердить очистку неактивного дискового кэша?",
|
||||
"Confirm clearing all channel affinity cache": "Подтвердите очистку всего кэша привязки к каналу",
|
||||
"Confirm clearing cache for this rule": "Подтвердите очистку кэша этого правила",
|
||||
"Confirm compliance": "Confirm compliance",
|
||||
"Confirm compliance terms": "Confirm compliance terms",
|
||||
"Confirm compliance": "Подтвердить соответствие",
|
||||
"Confirm compliance terms": "Подтвердить условия соответствия",
|
||||
"Confirm Creem Purchase": "Подтвердить покупку Creem",
|
||||
"Confirm delete": "Подтвердить удаление",
|
||||
"Confirm disable": "Подтвердить отключение",
|
||||
@ -891,11 +869,11 @@
|
||||
"auth.resetPasswordConfirm.description": "Подтвердите запрос на сброс, чтобы создать новый пароль.",
|
||||
"Confirm Selection": "Подтвердить выбор",
|
||||
"Confirm settings and finish setup": "Подтвердите настройки и завершите установку",
|
||||
"confirm that I bear legal responsibility arising from deployment": "confirm that I bear legal responsibility arising from deployment",
|
||||
"confirm that I bear legal responsibility arising from deployment": "подтверждаю, что несу юридическую ответственность, возникающую из развертывания",
|
||||
"Confirm Unbind": "Подтвердить отвязку",
|
||||
"Confirm your identity before removing this Passkey from your account.": "Подтвердите свою личность перед удалением этой Passkey из вашей учётной записи.",
|
||||
"Confirm your identity with Two-factor Authentication before registering a Passkey.": "Подтвердите свою личность с помощью двухфакторной аутентификации перед регистрацией Passkey.",
|
||||
"Confirmed at {{time}} by user #{{userId}}": "Confirmed at {{time}} by user #{{userId}}",
|
||||
"Confirmed at {{time}} by user #{{userId}}": "Подтверждено {{time}} пользователем #{{userId}}",
|
||||
"Conflict": "Противоречие",
|
||||
"Connect": "Подключение",
|
||||
"Connect through OpenAI, Claude, Gemini, and other compatible API routes": "Подключайтесь через OpenAI, Claude, Gemini и другие совместимые API-маршруты",
|
||||
@ -929,11 +907,7 @@
|
||||
"Continue with Telegram": "Продолжить с Telegram",
|
||||
"Continue with WeChat": "Продолжить с WeChat",
|
||||
"Contract review, compliance, summarisation": "Анализ контрактов, комплаенс, резюме",
|
||||
"Control log retention and clean historical data.": "Контролировать хранение логов и очистку исторических данных.",
|
||||
"Control passthrough behavior and connection keep-alive settings": "Контролировать сквозное поведение и настройки поддержания соединения",
|
||||
"Control request frequency to prevent abuse and manage system load.": "Контролировать частоту запросов для предотвращения злоупотреблений и управления нагрузкой на систему.",
|
||||
"Control which models are exposed and which groups may use them.": "Управляйте тем, какие модели доступны и какие группы могут их использовать.",
|
||||
"Control which sidebar areas and modules are available to all users.": "Контролировать, какие области боковой панели и модули доступны всем пользователям.",
|
||||
"Controls how much the model thinks before answering": "Регулирует глубину размышлений модели перед ответом",
|
||||
"Controls whether user verification (biometrics/PIN) is required during Passkey flows.": "Определяет, требуется ли проверка пользователя (биометрия/PIN) во время процессов Passkey.",
|
||||
"Conversion rate from USD to your custom currency": "Курс конвертации из USD в вашу пользовательскую валюту",
|
||||
@ -981,7 +955,7 @@
|
||||
"Core concepts": "Основные понятия",
|
||||
"Core Configuration": "Основная конфигурация",
|
||||
"Core Features": "Основные функции",
|
||||
"Core pricing": "Core pricing",
|
||||
"Core pricing": "Основные цены",
|
||||
"Cost": "Стоимость",
|
||||
"Cost in USD per request, regardless of tokens used.": "Стоимость в долларах США за запрос, независимо от использованных токенов.",
|
||||
"Cost Tracking": "Отслеживание затрат",
|
||||
@ -1023,9 +997,13 @@
|
||||
"Create, revoke, and audit API tokens.": "Создать, отозвать и аудитировать токены API.",
|
||||
"Created": "Создано",
|
||||
"Created At": "Дата создания",
|
||||
"Creating...": "Создание...",
|
||||
"Creation failed": "Создание не удалось",
|
||||
"Credential generated": "Учётные данные созданы",
|
||||
"Credential refreshed": "Учётные данные обновлены",
|
||||
"Credentials": "Учетные данные",
|
||||
"Credentials verification failed": "Не удалось проверить учетные данные",
|
||||
"Credentials verification failed — double-check Merchant ID and API private key.": "Не удалось проверить учетные данные — проверьте Merchant ID и приватный ключ API.",
|
||||
"Credit remaining": "Остаток средств",
|
||||
"Creem API key (leave blank unless updating)": "Ключ API Creem (оставьте пустым, если не обновляете)",
|
||||
"Creem Gateway": "Шлюз Creem",
|
||||
@ -1034,7 +1012,6 @@
|
||||
"Creem products must be a JSON array": "Продукты Creem должны быть JSON-массивом",
|
||||
"Cross-group": "Межгрупповой",
|
||||
"Cross-group retry": "Повтор между группами",
|
||||
"Curate quick links to your different Domains": "Подбирайте быстрые ссылки на ваши различные домены",
|
||||
"Currency": "Валюта",
|
||||
"Currency & Display": "Валюта и отображение",
|
||||
"Current Balance": "Текущий баланс",
|
||||
@ -1322,7 +1299,7 @@
|
||||
"Each line represents one keyword. Leave blank to disable the list but keep the switch states.": "Каждая строка представляет одно ключевое слово. Оставьте пустым, чтобы отключить список, но сохранить состояния переключателей.",
|
||||
"Each tier supports 0~2 conditions (over len, p, c); the last tier is the catch-all without conditions. Use len (full input length, including cache hits) for tier conditions to avoid mis-routing when cache hits reduce p.": "Каждый уровень поддерживает 0–2 условия (по len, p, c); последний уровень — резервный, без условий. Используйте len (полная длина ввода, включая попадания в кэш) для условий уровня, чтобы избежать ошибочной маршрутизации, когда попадания в кэш уменьшают p.",
|
||||
"Each tier supports up to 2 conditions; the last tier is the catch-all without conditions. Use full input length for tier conditions to avoid mis-routing when cache hits reduce billable input tokens.": "Каждый уровень поддерживает до 2 условий; последний уровень является резервным и не содержит условий. Используйте полную длину входа для условий уровня, чтобы кэш-попадания не снижали оплачиваемые входные токены и не приводили к неверному маршруту.",
|
||||
"Each tier supports up to 2 conditions. The last tier without conditions is the fallback.": "Each tier supports up to 2 conditions. The last tier without conditions is the fallback.",
|
||||
"Each tier supports up to 2 conditions. The last tier without conditions is the fallback.": "Каждый уровень поддерживает до 2 условий. Последний уровень без условий используется как резервный.",
|
||||
"Earn rewards when your referrals add funds. Transfer accumulated rewards to your balance anytime.": "Получайте вознаграждения, когда ваши рефералы пополняют счет. Переводите накопленные вознаграждения на свой баланс в любое время.",
|
||||
"Edit": "Редактировать",
|
||||
"Edit {{title}}": "Редактировать {{title}}",
|
||||
@ -1388,7 +1365,6 @@
|
||||
"Enable OIDC": "Включить OIDC",
|
||||
"Enable or disable this channel": "Включить или отключить этот канал",
|
||||
"Enable or disable this model": "Включить или отключить эту модель",
|
||||
"Enable or disable top navigation modules globally.": "Включить или отключить модули верхней навигации глобально.",
|
||||
"Enable Passkey": "Включить Passkey",
|
||||
"Enable Performance Monitoring": "Включить мониторинг производительности",
|
||||
"Enable rate limiting": "Включить ограничение скорости",
|
||||
@ -1542,7 +1518,6 @@
|
||||
"Expired at": "Истекает",
|
||||
"Expired time cannot be earlier than current time": "Время истечения срока действия не может быть раньше текущего времени",
|
||||
"Expires": "Истекает",
|
||||
"Expose grouped Uptime Kuma status pages directly on the dashboard": "Отображать сгруппированные страницы статуса Uptime Kuma непосредственно на панели управления",
|
||||
"Expose ratio API": "Интерфейс экспонирования коэффициента",
|
||||
"Exposes the pricing/models catalog in the top navigation.": "Отображает каталог цен/моделей в верхней навигации.",
|
||||
"Expression": "Выражение",
|
||||
@ -1575,7 +1550,7 @@
|
||||
"Failed to clean logs": "Не удалось очистить логи",
|
||||
"Failed to complete order": "Не удалось завершить заказ",
|
||||
"Failed to complete Passkey login": "Не удалось завершить вход с Passkey",
|
||||
"Failed to confirm compliance": "Failed to confirm compliance",
|
||||
"Failed to confirm compliance": "Не удалось подтвердить соответствие",
|
||||
"Failed to contact GitHub releases API": "Не удалось связаться с API GitHub Releases",
|
||||
"Failed to copy": "Не удалось скопировать",
|
||||
"Failed to copy channel": "Не удалось скопировать канал",
|
||||
@ -1688,7 +1663,7 @@
|
||||
"Failed to update user": "Не удалось обновить пользователя",
|
||||
"Failure keywords": "Ключевые слова сбоя",
|
||||
"Fair": "Удовлетворительно",
|
||||
"Fallback tier": "Fallback tier",
|
||||
"Fallback tier": "Резервный уровень",
|
||||
"FAQ": "Часто задаваемые вопросы",
|
||||
"FAQ added. Click \"Save Settings\" to apply.": "FAQ добавлен. Нажмите \"Сохранить настройки\" чтобы применить.",
|
||||
"FAQ deleted. Click \"Save Settings\" to apply.": "FAQ удалён. Нажмите \"Сохранить настройки\" чтобы применить.",
|
||||
@ -1717,6 +1692,8 @@
|
||||
"Fill Codex CLI / Claude CLI Templates": "Заполнить шаблоны Codex CLI / Claude CLI",
|
||||
"Fill example (all channels)": "Подставить пример (все каналы)",
|
||||
"Fill example (specific channels)": "Подставить пример (указанные каналы)",
|
||||
"Fill in both Merchant ID and API Private Key before creating.": "Перед созданием заполните Merchant ID и приватный ключ API.",
|
||||
"Fill in the credentials above to begin.": "Чтобы начать, заполните учетные данные выше.",
|
||||
"Fill in the following info to create a new subscription plan": "Заполните следующую информацию для создания нового плана подписки",
|
||||
"Fill Related Models": "Заполнить связанные модели",
|
||||
"Fill Template": "Заполнить шаблон",
|
||||
@ -1751,7 +1728,6 @@
|
||||
"Final cost = base × multiplier when conditions match": "Итоговая стоимость = база × множитель, если условия совпадают",
|
||||
"Final price multiplier (0.95 = 5% discount": "Конечный множитель цены (0.95 = скидка 5%",
|
||||
"Finance": "Финансы",
|
||||
"Fine-tune Midjourney integration and guardrails.": "Тонкая настройка интеграции Midjourney и защитных механизмов.",
|
||||
"Finish Time": "Время завершения",
|
||||
"First API request": "Первый API-запрос",
|
||||
"First/Last Frame to Video": "Первый/последний кадр в видео",
|
||||
@ -1988,8 +1964,8 @@
|
||||
"Human-readable name shown to users during Passkey prompts.": "Понятное для человека имя, отображаемое пользователям во время запросов Passkey.",
|
||||
"I confirm enabling high-risk retry": "Я подтверждаю включение высокорискового повтора",
|
||||
"I have read and agree to the": "Я прочитал и согласен с",
|
||||
"I have read and understood the above compliance reminder": "I have read and understood the above compliance reminder",
|
||||
"I have read and understood the above compliance reminder, acknowledge the related legal risks, and confirm that I bear legal responsibility arising from deployment, operation, and charging behavior.": "I have read and understood the above compliance reminder, acknowledge the related legal risks, and confirm that I bear legal responsibility arising from deployment, operation, and charging behavior.",
|
||||
"I have read and understood the above compliance reminder": "Я прочитал и понял приведенное выше напоминание о соответствии",
|
||||
"I have read and understood the above compliance reminder, acknowledge the related legal risks, and confirm that I bear legal responsibility arising from deployment, operation, and charging behavior.": "Я прочитал и понял приведенное выше напоминание о соответствии, признаю связанные правовые риски и подтверждаю, что несу юридическую ответственность за развертывание, эксплуатацию и взимание платы.",
|
||||
"I understand that disabling 2FA will remove all protection and backup codes": "Я понимаю, что отключение 2FA удалит всю защиту и резервные коды",
|
||||
"Icon": "Значок",
|
||||
"Icon file must be 100 KB or smaller": "Файл иконки должен быть не более 100 КБ",
|
||||
@ -2001,7 +1977,7 @@
|
||||
"If default auto group is enabled, newly created tokens start with auto instead of an empty group.": "Если группа auto включена по умолчанию, новые токены создаются с auto вместо пустой группы.",
|
||||
"If the affinity channel fails and retry succeeds on another channel, update affinity to the successful channel.": "Если привязанный канал не работает и повторная попытка удалась через другой канал, привязка обновляется на успешный канал.",
|
||||
"If this keeps happening, please report it on GitHub Issues.": "Если проблема повторяется, сообщите о ней в GitHub Issues.",
|
||||
"If you provide generative AI services to the public in mainland China, you will fulfill legal obligations including filing, security assessment, content safety, complaint handling, generated content labeling, log retention, and personal information protection.": "If you provide generative AI services to the public in mainland China, you will fulfill legal obligations including filing, security assessment, content safety, complaint handling, generated content labeling, log retention, and personal information protection.",
|
||||
"If you provide generative AI services to the public in mainland China, you will fulfill legal obligations including filing, security assessment, content safety, complaint handling, generated content labeling, log retention, and personal information protection.": "Если вы предоставляете услуги генеративного ИИ населению материкового Китая, вы будете выполнять юридические обязанности, включая регистрацию, оценку безопасности, безопасность контента, обработку жалоб, маркировку сгенерированного контента, хранение журналов и защиту персональных данных.",
|
||||
"Ignored upstream models": "Игнорируемые upstream-модели",
|
||||
"Image": "Изображение",
|
||||
"Image Generation": "Генерация изображений",
|
||||
@ -2228,30 +2204,21 @@
|
||||
"Low balance": "Низкий баланс",
|
||||
"Lowest median first-token latency": "Минимальная медианная задержка первого токена",
|
||||
"m": "m",
|
||||
"Maintain a list of common questions for the dashboard help panel": "Вести список часто задаваемых вопросов для панели помощи дашборда",
|
||||
"Maintenance": "Обслуживание",
|
||||
"Make it easier for teammates to pick the right group.": "Упростите выбор правильной группы для товарищей по команде.",
|
||||
"Manage": "Управление",
|
||||
"Manage account bindings for this user": "Управление привязками аккаунта пользователя",
|
||||
"Manage API channels and provider configurations": "Управление каналами API и конфигурациями провайдеров",
|
||||
"Manage Bindings": "Управление привязками",
|
||||
"Manage catalog visibility and pricing.": "Управление видимостью каталога и ценообразованием.",
|
||||
"Manage custom OAuth providers for user authentication": "Управление пользовательскими поставщиками OAuth для аутентификации пользователей",
|
||||
"Manage Keys": "Управление ключами",
|
||||
"Manage local models for:": "Управление локальными моделями для:",
|
||||
"Manage model deployments": "Управление развёртываниями моделей",
|
||||
"Manage model metadata and configuration": "Управление метаданными и конфигурацией моделей",
|
||||
"Manage multi-key status and configuration for this channel": "Управление статусом и конфигурацией нескольких ключей для этого канала",
|
||||
"Manage Ollama Models": "Управление моделями Ollama",
|
||||
"Manage redemption codes for quota top-up": "Управление кодами активации для пополнения квоты",
|
||||
"Manage server log files. Log files accumulate over time; regular cleanup is recommended to free disk space.": "Управление файлами журналов сервера. Файлы журналов накапливаются со временем; рекомендуется регулярная очистка.",
|
||||
"Manage subscription plan creation, pricing and status": "Управление созданием, ценообразованием и статусом подписок",
|
||||
"Manage subscription plans and pricing.": "Управление планами подписок и ценообразованием.",
|
||||
"Manage Subscriptions": "Управление подписками",
|
||||
"Manage users and their permissions": "Управление пользователями и их разрешениями",
|
||||
"Manage Vendors": "Управление поставщиками",
|
||||
"Manage your API keys for accessing the service": "Управление ключами API для доступа к сервису",
|
||||
"Manage your balance and payment methods": "Управление балансом и способами оплаты",
|
||||
"Manage your security settings and account access": "Управление настройками безопасности и доступом к аккаунту",
|
||||
"Manual Disabled": "Ручное отключение",
|
||||
"Map fields from the user info response to local user attributes. Supports nested paths (e.g. ocs.data.id).": "Сопоставление полей из ответа информации о пользователе с локальными атрибутами пользователя. Поддерживает вложенные пути (например, ocs.data.id).",
|
||||
@ -2295,7 +2262,7 @@
|
||||
"Maximum tokens per response": "Максимум токенов на ответ",
|
||||
"maxRequests ≥ 0, maxSuccess ≥ 1, both ≤ 2,147,483,647": "maxRequests ≥ 0, maxSuccess ≥ 1, оба ≤ 2,147,483,647",
|
||||
"May be used for training by upstream provider": "Может использоваться поставщиком для обучения",
|
||||
"Media pricing": "Media pricing",
|
||||
"Media pricing": "Цены для медиа",
|
||||
"Median time-to-first-token (TTFT) sampled hourly per group": "Медианная задержка первого токена (TTFT), измеряемая ежечасно по группам",
|
||||
"Medical Q&A, mental health support": "Медицинские Q&A, поддержка ментального здоровья",
|
||||
"Memory Hits": "Попаданий памяти",
|
||||
@ -2323,6 +2290,8 @@
|
||||
"Minimum Trust Level": "Минимальный уровень доверия",
|
||||
"Minimum:": "Минимум:",
|
||||
"Minor blips in the last 30 days": "Небольшие сбои за последние 30 дней",
|
||||
"Mint a fresh pair below — or pick an existing one further down. Click Save when ready.": "Создайте новую пару ниже или выберите существующую дальше. Когда будете готовы, нажмите Сохранить.",
|
||||
"Creates a Pancake product in the saved store using this plan’s title and price. Requires Waffo Pancake to be fully configured in Payment settings first.": "Создает продукт Pancake в сохраненном магазине с названием и ценой этого плана. Сначала необходимо полностью настроить Waffo Pancake в настройках платежей.",
|
||||
"Minute": "Минута",
|
||||
"minutes": "минут",
|
||||
"Missing code": "Код отсутствует",
|
||||
@ -2617,8 +2586,9 @@
|
||||
"No Retry": "Без повтора",
|
||||
"No rules yet": "Правила отсутствуют",
|
||||
"No rules yet. Add a group below to get started.": "Правил пока нет. Добавьте группу ниже, чтобы начать.",
|
||||
"No separate media pricing configured.": "No separate media pricing configured.",
|
||||
"No separate media pricing configured.": "Отдельные цены для медиа не настроены.",
|
||||
"No status code mappings configured.": "Сопоставления кодов состояния не настроены.",
|
||||
"No stores on this merchant yet. Set a return URL and click Create to mint your first pair.": "У этого продавца пока нет магазинов. Укажите URL возврата и нажмите Создать, чтобы создать первую пару.",
|
||||
"No subscription plans yet": "Планов подписки пока нет",
|
||||
"No subscription records": "Нет записей подписок",
|
||||
"No Sync": "Без синхронизации",
|
||||
@ -2640,7 +2610,7 @@
|
||||
"No X Found": "X не найдено",
|
||||
"Node Name": "Имя узла",
|
||||
"Non-stream": "Не потоковый",
|
||||
"Non-zero invitation rewards require compliance confirmation in Payment Gateway settings.": "Non-zero invitation rewards require compliance confirmation in Payment Gateway settings.",
|
||||
"Non-zero invitation rewards require compliance confirmation in Payment Gateway settings.": "Ненулевые награды за приглашения требуют подтверждения соответствия в настройках платежного шлюза.",
|
||||
"None": "Нет",
|
||||
"noreply@example.com": "noreply@example.com",
|
||||
"Normalized:": "Нормализовано:",
|
||||
@ -2739,7 +2709,7 @@
|
||||
"OpenRouter": "OpenRouter",
|
||||
"opens in an external client. Trigger it from the sidebar or API key actions to launch the configured application.": "открывается во внешнем клиенте. Запустите его из боковой панели или действий с ключом API, чтобы запустить настроенное приложение.",
|
||||
"Operation": "Операция",
|
||||
"operation and charging behavior": "operation and charging behavior",
|
||||
"operation and charging behavior": "эксплуатацию и взимание платы",
|
||||
"Operation failed": "Операция не удалась",
|
||||
"Operation successful": "Операция выполнена успешно",
|
||||
"Operation Type": "Тип операции",
|
||||
@ -2762,6 +2732,7 @@
|
||||
"Opus Model": "Модель Opus",
|
||||
"Or continue with": "Или продолжить с",
|
||||
"Or enter this key manually:": "Или введите этот ключ вручную:",
|
||||
"or pick existing": "или выбрать существующую",
|
||||
"Order completed successfully": "Заказ успешно завершен",
|
||||
"Order History": "История заказов",
|
||||
"Order Payment Method": "Способ оплаты (заказа)",
|
||||
@ -2780,7 +2751,6 @@
|
||||
"Overnight range": "Диапазон через полночь",
|
||||
"override": "переопределить",
|
||||
"Override": "Перезаписать",
|
||||
"Override Anthropic headers, defaults, and thinking adapter behavior": "Переопределить заголовки Anthropic, значения по умолчанию и поведение адаптера мышления",
|
||||
"Override auto-discovered endpoint": "Переопределить автоматически обнаруженную конечную точку",
|
||||
"Override request headers": "Переопределить заголовки запроса",
|
||||
"Override request headers (JSON format)": "Переопределение заголовков запроса (формат JSON)",
|
||||
@ -2796,6 +2766,7 @@
|
||||
"Page {{current}} of {{total}}": "Страница {{current}} из {{total}}",
|
||||
"PaLM": "PaLM",
|
||||
"Pan": "Панорама",
|
||||
"Pancake": "Pancake",
|
||||
"Param Override": "Переопределение параметров",
|
||||
"Parameter": "Параметр",
|
||||
"Parameter configuration error": "Ошибка конфигурации параметров",
|
||||
@ -2859,6 +2830,7 @@
|
||||
"Pay": "Pay",
|
||||
"Pay-as-you-go with real-time usage monitoring": "Оплата по мере использования с мониторингом в реальном времени",
|
||||
"Payment": "Платеж",
|
||||
"Payment aggregator mode — onboard with your own registered company (offshore entity). Built for Enterprise.": "Режим платежного агрегатора — подключение через вашу зарегистрированную компанию (офшорное юрлицо). Создано для Enterprise.",
|
||||
"Payment Channel": "Платёжный канал",
|
||||
"Payment Gateway": "Платежный шлюз",
|
||||
"Payment initiated": "Платёж инициирован",
|
||||
@ -2872,7 +2844,8 @@
|
||||
"Payment page opened": "Страница оплаты открыта",
|
||||
"Payment request failed": "Запрос на оплату не удался",
|
||||
"Payment return URL": "URL возврата после оплаты",
|
||||
"Payment, redemption codes, subscription plans, and invitation rewards are locked until the root administrator confirms the compliance terms.": "Payment, redemption codes, subscription plans, and invitation rewards are locked until the root administrator confirms the compliance terms.",
|
||||
"Payment return URL is empty. Create the product without a SuccessURL redirect?": "URL возврата платежа пуст. Создать продукт без перенаправления SuccessURL?",
|
||||
"Payment, redemption codes, subscription plans, and invitation rewards are locked until the root administrator confirms the compliance terms.": "Платежи, коды пополнения, планы подписки и награды за приглашения заблокированы, пока root-администратор не подтвердит условия соответствия.",
|
||||
"Peak": "Пик",
|
||||
"Peak throughput": "Пиковая пропускная способность",
|
||||
"Penalises repetition of frequent tokens": "Штрафует повторение частых токенов",
|
||||
@ -2915,11 +2888,14 @@
|
||||
"Personal use": "Личное использование",
|
||||
"Personal use mode": "Режим личного использования",
|
||||
"Pick a date": "Выберите дату",
|
||||
"Pick or create both a store and a product before saving.": "Перед сохранением выберите или создайте и магазин, и продукт.",
|
||||
"Ping Interval (seconds)": "Интервал Ping (секунды)",
|
||||
"Plan": "План",
|
||||
"Plan Name": "Название плана",
|
||||
"Plan price must be greater than zero": "Цена плана должна быть больше нуля",
|
||||
"Plan Subtitle": "Подзаголовок плана",
|
||||
"Plan Title": "Название плана",
|
||||
"Plan title is required": "Название плана обязательно",
|
||||
"Planned maintenance on Friday at 22:00 UTC...": "Запланированное обслуживание в пятницу в 22:00 UTC...",
|
||||
"Platform": "Платформа",
|
||||
"Playground": "Песочница",
|
||||
@ -2968,7 +2944,7 @@
|
||||
"Please set Ollama API Base URL first": "Сначала установите базовый URL-адрес API Ollama",
|
||||
"Please sign in to view {{module}}.": "Войдите, чтобы просмотреть {{module}}.",
|
||||
"Please try again later.": "Пожалуйста, попробуйте еще раз позже.",
|
||||
"Please type the following text to confirm:": "Please type the following text to confirm:",
|
||||
"Please type the following text to confirm:": "Введите следующий текст для подтверждения:",
|
||||
"Please upload key file(s)": "Загрузите ключевой файл(ы)",
|
||||
"Please wait a moment before trying again.": "Пожалуйста, подождите немного и попробуйте снова.",
|
||||
"Please wait a moment, human check is initializing...": "Пожалуйста, подождите немного, инициализация проверки человеком...",
|
||||
@ -3020,7 +2996,6 @@
|
||||
"Press Enter or comma to add tags": "Нажмите Enter или запятую, чтобы добавить теги",
|
||||
"Press Enter to use \"{{value}}\"": "Нажмите Enter, чтобы использовать «{{value}}»",
|
||||
"Prevent server-side request forgery attacks": "Предотвращение атак подделки запросов на стороне сервера",
|
||||
"Prevent server-side request forgery attacks by controlling outbound requests.": "Предотвращение атак подделки запросов на стороне сервера путем контроля исходящих запросов.",
|
||||
"Preview": "Предварительный просмотр",
|
||||
"Previous": "Предыдущий шаг",
|
||||
"Previous branch": "Предыдущая ветка",
|
||||
@ -3083,7 +3058,6 @@
|
||||
"Prompt Details": "Детали промпта",
|
||||
"Prompt price ($/1M tokens)": "Цена промпта ($/1 млн токенов)",
|
||||
"Proprietary": "Проприетарная",
|
||||
"Protect login and registration with Cloudflare Turnstile": "Защитите вход и регистрацию с помощью Cloudflare Turnstile",
|
||||
"Provide a JSON object where each key maps to an endpoint definition.": "Предоставьте JSON-объект, в котором каждый ключ соответствует определению конечной точки.",
|
||||
"Provide a valid URL starting with http:// or https://": "Укажите действительный URL, начинающийся с http:// или https://",
|
||||
"Provide Markdown, HTML, or an external URL for the privacy policy": "Укажите Markdown, HTML или внешний URL для политики конфиденциальности",
|
||||
@ -3197,7 +3171,7 @@
|
||||
"Redemption code updated successfully": "Код активации успешно обновлен",
|
||||
"Redemption code(s) created successfully": "Код(ы) активации успешно создан(ы)",
|
||||
"Redemption Codes": "Коды активации",
|
||||
"Redemption codes are disabled until the administrator confirms compliance terms.": "Redemption codes are disabled until the administrator confirms compliance terms.",
|
||||
"Redemption codes are disabled until the administrator confirms compliance terms.": "Коды пополнения отключены, пока администратор не подтвердит условия соответствия.",
|
||||
"redemption codes.": "коды активации.",
|
||||
"Redemption failed": "Погашение не удалось",
|
||||
"Redemption successful! Added: {{quota}}": "Погашение успешно! Добавлено: {{quota}}",
|
||||
@ -3209,7 +3183,7 @@
|
||||
"Reference Video": "Эталонное видео",
|
||||
"Referral link:": "Реферальная ссылка:",
|
||||
"Referral Program": "Реферальная программа",
|
||||
"Referral reward transfer is disabled until the administrator confirms compliance terms.": "Referral reward transfer is disabled until the administrator confirms compliance terms.",
|
||||
"Referral reward transfer is disabled until the administrator confirms compliance terms.": "Перевод реферальных наград отключен, пока администратор не подтвердит условия соответствия.",
|
||||
"Refine models by provider, group, type, and tags.": "Уточняйте список моделей по поставщику, группе, типу и тегам.",
|
||||
"Refresh": "Обновить",
|
||||
"Refresh Cache": "Обновить кэш",
|
||||
@ -3225,6 +3199,7 @@
|
||||
"Regex": "Регулярное выражение",
|
||||
"Regex Pattern": "Регулярное выражение",
|
||||
"Regex Replace": "Замена по regex",
|
||||
"Register each URL into the matching Test Mode / Production Mode webhook slot in the Pancake dashboard. Separate endpoints prevent test traffic from accidentally crediting production accounts.": "Зарегистрируйте каждый URL в соответствующем слоте вебхука Test Mode / Production Mode в панели Pancake. Раздельные конечные точки предотвращают случайное зачисление тестового трафика на производственные аккаунты.",
|
||||
"Register Passkey": "Регистрация Passkey",
|
||||
"Registration Enabled": "Регистрация включена",
|
||||
"Registry (optional)": "Реестр (необязательно)",
|
||||
@ -3368,7 +3343,6 @@
|
||||
"Reveal key": "Показать ключ",
|
||||
"Revenue": "Доход",
|
||||
"Review & initialize": "Проверить и инициализировать",
|
||||
"Review current version and fetch release notes.": "Просмотреть текущую версию и получить примечания к выпуску.",
|
||||
"Review model rates before scaling traffic": "Проверьте тарифы моделей перед масштабированием трафика",
|
||||
"Review your payment details": "Проверьте свои платежные данные",
|
||||
"Review your purchase details before proceeding.": "Просмотрите детали покупки перед продолжением.",
|
||||
@ -3504,8 +3478,10 @@
|
||||
"Select a group type": "Выбрать тип группы",
|
||||
"Select a model to edit pricing": "Выберите модель для редактирования тарифа",
|
||||
"Select a preset...": "Выберите предустановку...",
|
||||
"Select a product": "Выберите продукт",
|
||||
"Select a role": "Выбрать роль",
|
||||
"Select a rule to edit.": "Выберите правило для редактирования.",
|
||||
"Select a store": "Выберите магазин",
|
||||
"Select a timestamp before clearing logs.": "Выберите временную метку перед очисткой журналов.",
|
||||
"Select a usage mode to continue": "Выберите режим использования для продолжения",
|
||||
"Select a verification method first": "Сначала выберите метод верификации",
|
||||
@ -3580,7 +3556,7 @@
|
||||
"Sending...": "Отправка...",
|
||||
"Sensitive Words": "Чувствительные слова",
|
||||
"Sent the API key to FluentRead.": "API-ключ отправлен в FluentRead.",
|
||||
"Separate image/audio prices are enabled.": "Separate image/audio prices are enabled.",
|
||||
"Separate image/audio prices are enabled.": "Отдельные цены для изображений и аудио включены.",
|
||||
"Serve multiple users or teams with billing and quota control.": "Обслуживание нескольких пользователей или команд с управлением биллингом и квотами.",
|
||||
"Server Address": "Адрес сервера",
|
||||
"Server IP": "IP сервера",
|
||||
@ -3604,7 +3580,7 @@
|
||||
"Set quota amount and limits": "Настройте квоту и лимиты",
|
||||
"Set Request Header": "Установить заголовок запроса",
|
||||
"Set runtime request header: override entire value, or manipulate comma-separated tokens": "Установить заголовок запроса: переопределить значение или управлять токенами через запятую",
|
||||
"Set separate prices for cache reads and writes.": "Set separate prices for cache reads and writes.",
|
||||
"Set separate prices for cache reads and writes.": "Задайте отдельные цены для чтения и записи кэша.",
|
||||
"Set Tag": "Установить тег",
|
||||
"Set tag for selected channels": "Установить тег для выбранных каналов",
|
||||
"Set the language used across the interface": "Настроить язык интерфейса",
|
||||
@ -3700,6 +3676,7 @@
|
||||
"Standard price": "Стандартная цена",
|
||||
"Start": "Начало",
|
||||
"Start a conversation to see messages here": "Начните разговор, чтобы увидеть сообщения здесь",
|
||||
"Start collecting payments globally without registering a company. Built for indie developers, OPC sole proprietorships, and startups. Waffo Pancake acts as your Merchant of Record, taking on the compliance burden of global payment collection — consumption tax, invoicing, subscription management, refunds, and chargebacks. Solo developers can launch fast and stay focused on product instead of compliance. Onboard in minutes — one prompt to a full integration.": "Начните принимать платежи по всему миру без регистрации компании. Подходит для независимых разработчиков, индивидуальных предпринимателей OPC и стартапов. Waffo Pancake выступает как Merchant of Record и берет на себя комплаенс глобального приема платежей: потребительские налоги, выставление счетов, управление подписками, возвраты и чарджбеки. Одиночные разработчики могут быстро запуститься и сосредоточиться на продукте, а не на комплаенсе. Подключение за минуты — от одного запроса до полной интеграции.",
|
||||
"Start for free with generous limits. No credit card required.": "Начните бесплатно с щедрыми лимитами. Кредитная карта не требуется.",
|
||||
"Start Time": "Время начала",
|
||||
"Static page describing the platform.": "Статическая страница, описывающая платформу.",
|
||||
@ -3720,6 +3697,8 @@
|
||||
"Step": "Шаг",
|
||||
"Stop": "Остановить",
|
||||
"Stop Retry": "Остановить повтор",
|
||||
"Store": "Store",
|
||||
"Store + product created": "Магазин и продукт созданы",
|
||||
"Store ID": "ID магазина",
|
||||
"Store ID is required": "Требуется ID магазина",
|
||||
"Stored value is not echoed back for security": "В целях безопасности сохранённое значение не отображается",
|
||||
@ -3753,8 +3732,9 @@
|
||||
"Subscription First": "Подписка в приоритете",
|
||||
"Subscription Management": "Управление подписками",
|
||||
"Subscription Only": "Только подписка",
|
||||
"Subscription plan creation and changes are locked until the administrator confirms compliance terms in Payment Gateway settings.": "Subscription plan creation and changes are locked until the administrator confirms compliance terms in Payment Gateway settings.",
|
||||
"Subscription plan creation and changes are locked until the administrator confirms compliance terms in Payment Gateway settings.": "Создание и изменение планов подписки заблокированы, пока администратор не подтвердит условия соответствия в настройках платежного шлюза.",
|
||||
"Subscription Plans": "Планы подписки",
|
||||
"Subscription plans do NOT use the bound Product — each plan has its own dedicated Pancake product, set in the Subscriptions admin (or auto-minted via the \"+ Create\" button there).": "Планы подписки НЕ используют привязанный продукт — у каждого плана есть собственный продукт Pancake, задаваемый в администрировании подписок (или автоматически создаваемый кнопкой «+ Создать»).",
|
||||
"Subtract": "Вычесть",
|
||||
"Success": "Успешно",
|
||||
"Success rate": "Доля успешных запросов",
|
||||
@ -3879,8 +3859,11 @@
|
||||
"The administrator has not configured a user agreement yet.": "Администратор ещё не настроил пользовательское соглашение.",
|
||||
"The administrator has not configured any about content yet. You can set it in the settings page, supporting HTML or URL.": "Администратор еще не настроил содержимое раздела «О программе». Вы можете установить его на странице настроек, поддерживается HTML или URL.",
|
||||
"The binding will complete automatically after authorization": "Привязка завершится автоматически после авторизации",
|
||||
"The bound Product powers wallet top-ups: when a user enters any amount, new-api runs the checkout against this single Pancake product and overrides the price per session — no need to pre-create $1 / $5 / $10 SKUs.": "Привязанный продукт используется для пополнения кошелька: когда пользователь вводит любую сумму, new-api запускает оплату через этот единственный продукт Pancake и переопределяет цену для каждой сессии — не нужно заранее создавать SKU на $1 / $5 / $10.",
|
||||
"The bound Store is the parent container for every Pancake product new-api creates from this admin — both the wallet top-up product and any subscription-plan products. One store is enough; pin a different one only if you genuinely run separate Pancake catalogs.": "Привязанный магазин является родительским контейнером для всех продуктов Pancake, которые new-api создает из этой админки: как продукта пополнения кошелька, так и продуктов планов подписки. Одного магазина достаточно; выбирайте другой только если действительно ведете отдельные каталоги Pancake.",
|
||||
"The effective domain for Passkey registration. Must match the current domain or be its parent domain.": "Действующий домен для регистрации Passkey. Должен совпадать с текущим доменом или быть его родительским доменом.",
|
||||
"The entered text does not match the required text.": "The entered text does not match the required text.",
|
||||
"The entered text does not match the required text.": "Введенный текст не совпадает с требуемым.",
|
||||
"The environment (test vs production) is decided by the key you paste here — use the Test key while integrating, then swap to the Production key when going live.": "Окружение (тестовое или рабочее) определяется ключом, который вы вставляете здесь: используйте тестовый ключ при интеграции, затем замените его на рабочий при запуске.",
|
||||
"The exact model identifier as used in API requests.": "Точный идентификатор модели, используемый в запросах API.",
|
||||
"The following models have billing type conflicts (fixed price vs ratio billing). Confirm to proceed with the changes.": "Следующие модели имеют конфликты типов тарификации (фиксированная цена против тарификации по соотношению). Подтвердите, чтобы продолжить изменения.",
|
||||
"The following models in the model redirect have not been added to the \"Models\" list and may fail during invocation due to missing available models:": "Следующие модели в перенаправлении модели не были добавлены в список \"Модели\" и могут не работать при вызове из-за отсутствия доступных моделей:",
|
||||
@ -3912,7 +3895,7 @@
|
||||
"This action will permanently remove 2FA protection from your account.": "Это действие безвозвратно удалит защиту 2FA из вашей учетной записи.",
|
||||
"This channel is not an Ollama channel.": "Этот канал не является каналом Ollama.",
|
||||
"This channel type does not support fetching models": "Этот тип канала не поддерживает получение моделей",
|
||||
"This confirmation unlocks payment, redemption code, subscription plan, and invitation reward features. Please read the statements carefully.": "This confirmation unlocks payment, redemption code, subscription plan, and invitation reward features. Please read the statements carefully.",
|
||||
"This confirmation unlocks payment, redemption code, subscription plan, and invitation reward features. Please read the statements carefully.": "Это подтверждение разблокирует функции платежей, кодов пополнения, планов подписки и наград за приглашения. Внимательно прочитайте заявления.",
|
||||
"This controls model request rate limiting. Web/API route throttling is configured by environment variables and may still return 429.": "Этот параметр управляет ограничением частоты запросов к моделям. Ограничение маршрутов Web/API настраивается переменными окружения и всё ещё может возвращать 429.",
|
||||
"This data may be unreliable, use with caution": "Эти данные могут быть ненадежными, используйте с осторожностью",
|
||||
"This device does not support Passkey": "Это устройство не поддерживает Passkey",
|
||||
@ -3931,7 +3914,7 @@
|
||||
"This project must be used in compliance with the": "Этот проект должен использоваться в соответствии с",
|
||||
"This record was written by a pre-upgrade instance and lacks audit info. Upgrade the instance to record server IP, callback IP, payment method and system version.": "Запись создана экземпляром до обновления и не содержит сведений аудита. Обновите экземпляр, чтобы фиксировать IP сервера, IP callback, способ оплаты и версию ОС.",
|
||||
"This site currently has {{count}} models enabled": "На этом сайте сейчас включено моделей: {{count}}",
|
||||
"This tier catches any request that did not match earlier tiers.": "This tier catches any request that did not match earlier tiers.",
|
||||
"This tier catches any request that did not match earlier tiers.": "Этот уровень обрабатывает все запросы, которые не совпали с предыдущими уровнями.",
|
||||
"this token group": "эта группа токенов",
|
||||
"this user group": "эта группа пользователей",
|
||||
"This user has no bindings": "У этого пользователя нет привязок",
|
||||
@ -3954,7 +3937,7 @@
|
||||
"Throughput short": "TPS",
|
||||
"Throughput trend": "Тренд пропускной способности",
|
||||
"Tier": "Уровень",
|
||||
"Tier conditions": "Tier conditions",
|
||||
"Tier conditions": "Условия уровня",
|
||||
"Tier name": "Название уровня",
|
||||
"Tiered": "Ступенчато",
|
||||
"Tiered (billing expression)": "Ступенчато (платёжное выражение)",
|
||||
@ -4002,7 +3985,7 @@
|
||||
"Token price for cache reads.": "Цена токенов для чтения кэша.",
|
||||
"Token price for creating cache entries.": "Цена токенов для создания записей кэша.",
|
||||
"Token price for image input.": "Цена токенов для входного изображения.",
|
||||
"Token prices": "Token prices",
|
||||
"Token prices": "Цены токенов",
|
||||
"Token regenerated and copied to clipboard": "Токен перегенерирован и скопирован в буфер обмена",
|
||||
"Token share by model author across the last 24 hours": "Доля токенов по автору модели за последние 24 часа",
|
||||
"Token share by model author across the past few weeks": "Доля токенов по автору модели за последние недели",
|
||||
@ -4114,7 +4097,7 @@
|
||||
"Type (common)": "Тип (общий)",
|
||||
"Type *": "Тип *",
|
||||
"Type a command or search...": "Введите команду или поиск...",
|
||||
"Type the confirmation text here": "Type the confirmation text here",
|
||||
"Type the confirmation text here": "Введите текст подтверждения здесь",
|
||||
"Type-Specific Settings": "Настройки для конкретного типа",
|
||||
"Type:": "Тип:",
|
||||
"UI granularity only — data is still aggregated hourly": "Только детализация пользовательского интерфейса — данные по-прежнему агрегируются ежечасно",
|
||||
@ -4239,6 +4222,7 @@
|
||||
"used": "использовано",
|
||||
"Used": "Использовано",
|
||||
"Used / Remaining": "Использовано / Осталось",
|
||||
"Used as SuccessURL on the new product. You'll be prompted to confirm if left blank.": "Используется как SuccessURL для нового продукта. Если оставить пустым, потребуется подтверждение.",
|
||||
"Used for load balancing. Higher weight = more requests": "Используется для балансировки нагрузки. Больший вес = больше запросов",
|
||||
"Used in URLs and API routes": "Используется в URL и маршрутах API",
|
||||
"Used Quota": "Лимит потребления",
|
||||
@ -4313,6 +4297,7 @@
|
||||
"Verify routing with Playground or your client": "Проверьте маршрутизацию через Playground или ваш клиент",
|
||||
"Verify Setup": "Проверить настройку",
|
||||
"Verify your database connection": "Проверьте подключение к базе данных",
|
||||
"Verifying credentials and pulling stores from your Pancake account...": "Проверяем учетные данные и загружаем магазины из вашего аккаунта Pancake...",
|
||||
"Version Overrides": "Переопределения версий",
|
||||
"Vertex AI": "Vertex AI",
|
||||
"Vertex AI does not support functionResponse.id. Enable this to remove the field automatically.": "Vertex AI не поддерживает functionResponse.id. Включите, чтобы автоматически удалить это поле.",
|
||||
@ -4323,16 +4308,11 @@
|
||||
"Vidu": "Vidu",
|
||||
"View": "Просмотр",
|
||||
"View all currently available models": "Просмотреть все доступные модели",
|
||||
"View and manage your API usage logs": "Просмотр и управление журналами использования API",
|
||||
"View and manage your drawing logs": "Просмотр и управление журналами рисования",
|
||||
"View and manage your task logs": "Просмотр и управление журналами задач",
|
||||
"View dashboard overview and statistics": "Просмотр обзора и статистики панели управления",
|
||||
"View detailed information about this user including balance, usage statistics, and invitation details.": "Просмотр подробной информации об этом пользователе, включая баланс, статистику использования и данные приглашения.",
|
||||
"View details": "Просмотреть детали",
|
||||
"View document": "Просмотреть документ",
|
||||
"View logs": "Просмотреть логи",
|
||||
"View mode": "Режим отображения",
|
||||
"View model call count analytics and charts": "Просмотр аналитики и графиков количества вызовов моделей",
|
||||
"View model statistics and charts": "Просмотр статистики и графиков моделей",
|
||||
"View Pricing": "Посмотреть цены",
|
||||
"View the complete details for this": "Просмотр полных деталей этой",
|
||||
@ -4340,7 +4320,6 @@
|
||||
"View the complete error message and details": "Просмотр полного сообщения об ошибке и деталей",
|
||||
"View the complete prompt and its English translation": "Просмотр полного промпта и его перевода на английский",
|
||||
"View the generated image": "Просмотр сгенерированного изображения",
|
||||
"View user consumption statistics and charts": "Просмотр статистики и графиков потребления",
|
||||
"View your topup transaction records and payment history": "Просмотреть записи о пополнении счета и историю платежей",
|
||||
"Violation Code": "Код нарушения",
|
||||
"Violation deduction amount": "Сумма вычета за нарушение",
|
||||
@ -4362,7 +4341,14 @@
|
||||
"Visual Parameter Override": "Визуальное переопределение параметров",
|
||||
"VolcEngine": "VolcEngine",
|
||||
"vs. previous": "к предыдущему",
|
||||
"Waffo Aggregator Gateway": "Шлюз-агрегатор Waffo",
|
||||
"Waffo Pancake Dashboard": "Waffo Pancake Dashboard",
|
||||
"Waffo Pancake MoR": "Waffo Pancake MoR",
|
||||
"Waffo Pancake Payment Gateway": "Платёжный шлюз Waffo Pancake",
|
||||
"Waffo Pancake product created": "Продукт Waffo Pancake создан",
|
||||
"Waffo Pancake product creation failed": "Не удалось создать продукт Waffo Pancake",
|
||||
"Waffo Pancake save failed": "Не удалось сохранить Waffo Pancake",
|
||||
"Waffo Pancake settings saved": "Настройки Waffo Pancake сохранены",
|
||||
"Waffo Payment": "Оплата Waffo",
|
||||
"Waffo Payment Gateway": "Платёжный шлюз Waffo",
|
||||
"Waffo Public Key (Production)": "Публичный ключ Waffo (Продакшн)",
|
||||
@ -4393,6 +4379,8 @@
|
||||
"Webhook Secret": "Секрет веб-хука",
|
||||
"Webhook signing secret (leave blank unless updating)": "Секрет подписи веб-хука (оставьте пустым, если не обновляете)",
|
||||
"Webhook URL": "Адрес Webhook",
|
||||
"Webhook URL (Production):": "URL вебхука (Production):",
|
||||
"Webhook URL (Test):": "URL вебхука (Test):",
|
||||
"Webhook URL:": "URL веб-хука:",
|
||||
"Website is under maintenance!": "Сайт находится на техническом обслуживании!",
|
||||
"WeChat": "WeChat",
|
||||
@ -4432,6 +4420,7 @@
|
||||
"Whitelist (Only allow listed domains)": "Белый список (Разрешить только указанные домены)",
|
||||
"Whitelist (Only allow listed IPs)": "Белый список (Разрешить только указанные IP-адреса)",
|
||||
"whsec_xxx": "whsec_xxx",
|
||||
"Why only one store + product?": "Почему только один магазин и продукт?",
|
||||
"Window:": "Окно:",
|
||||
"Wire encoding for the embedding vectors": "Кодирование векторов в передаче",
|
||||
"with conflicts": "с конфликтами",
|
||||
@ -4454,16 +4443,16 @@
|
||||
"You can close this tab once the binding completes or a success message appears in the original window.": "Вы можете закрыть эту вкладку, как только привязка завершится или в исходном окне появится сообщение об успехе.",
|
||||
"You can manually add them in \"Custom Model Names\", click \"Fill\" and then submit, or use the operations below to handle automatically.": "Вы можете вручную добавить их в \"Пользовательские имена моделей\", нажать \"Заполнить\", а затем отправить, или использовать операции ниже для автоматической обработки.",
|
||||
"You can only check in once per day": "Вы можете заселяться только один раз в день",
|
||||
"You commit not to use this system to implement, assist with, or indirectly implement acts that violate applicable laws and regulations, regulatory requirements, platform rules, public interests, or the lawful rights and interests of third parties.": "You commit not to use this system to implement, assist with, or indirectly implement acts that violate applicable laws and regulations, regulatory requirements, platform rules, public interests, or the lawful rights and interests of third parties.",
|
||||
"You commit to using upstream APIs, accounts, keys, quotas, and service capabilities only within the scope of lawful authorization obtained from upstream service providers, model service providers, or relevant rights holders, and will not conduct unauthorized resale, trafficking, distribution, or other non-compliant commercialization.": "You commit to using upstream APIs, accounts, keys, quotas, and service capabilities only within the scope of lawful authorization obtained from upstream service providers, model service providers, or relevant rights holders, and will not conduct unauthorized resale, trafficking, distribution, or other non-compliant commercialization.",
|
||||
"You commit not to use this system to implement, assist with, or indirectly implement acts that violate applicable laws and regulations, regulatory requirements, platform rules, public interests, or the lawful rights and interests of third parties.": "Вы обязуетесь не использовать эту систему для совершения, содействия или косвенного совершения действий, нарушающих применимые законы и нормы, регуляторные требования, правила платформ, общественные интересы либо законные права и интересы третьих лиц.",
|
||||
"You commit to using upstream APIs, accounts, keys, quotas, and service capabilities only within the scope of lawful authorization obtained from upstream service providers, model service providers, or relevant rights holders, and will not conduct unauthorized resale, trafficking, distribution, or other non-compliant commercialization.": "Вы обязуетесь использовать вышестоящие API, аккаунты, ключи, квоты и сервисные возможности только в пределах законного разрешения, полученного от вышестоящих поставщиков услуг, поставщиков моделей или соответствующих правообладателей, и не осуществлять несанкционированную перепродажу, оборот, распространение или иную несоответствующую коммерциализацию.",
|
||||
"You don't have necessary permission": "У вас нет необходимых разрешений",
|
||||
"You have legally obtained authorization for the connected model APIs, accounts, keys, and quotas.": "You have legally obtained authorization for the connected model APIs, accounts, keys, and quotas.",
|
||||
"You have legally obtained authorization for the connected model APIs, accounts, keys, and quotas.": "Вы законно получили разрешение на подключенные API моделей, аккаунты, ключи и квоты.",
|
||||
"You have unsaved changes": "У вас есть несохранённые изменения",
|
||||
"You have unsaved changes. Are you sure you want to leave?": "У вас есть несохранённые изменения. Вы уверены, что хотите уйти?",
|
||||
"You Pay": "Вы платите",
|
||||
"You save": "Вы экономите",
|
||||
"You understand and independently bear legal responsibility arising from deployment, operation, and charging behavior.": "You understand and independently bear legal responsibility arising from deployment, operation, and charging behavior.",
|
||||
"You understand this compliance reminder is only for risk notice and does not constitute legal advice, a compliance review conclusion, or a guarantee of the legality of your use of this system; you should consult professional legal or compliance advisors based on your actual business scenario.": "You understand this compliance reminder is only for risk notice and does not constitute legal advice, a compliance review conclusion, or a guarantee of the legality of your use of this system; you should consult professional legal or compliance advisors based on your actual business scenario.",
|
||||
"You understand and independently bear legal responsibility arising from deployment, operation, and charging behavior.": "Вы понимаете и самостоятельно несете юридическую ответственность, возникающую из развертывания, эксплуатации и взимания платы.",
|
||||
"You understand this compliance reminder is only for risk notice and does not constitute legal advice, a compliance review conclusion, or a guarantee of the legality of your use of this system; you should consult professional legal or compliance advisors based on your actual business scenario.": "Вы понимаете, что это напоминание о соответствии является только уведомлением о рисках и не является юридической консультацией, заключением проверки соответствия или гарантией законности использования этой системы; вам следует обратиться к профессиональным юридическим или комплаенс-консультантам с учетом вашей реальной бизнес-ситуации.",
|
||||
"You will be redirected to Telegram to complete the binding process.": "Вы будете перенаправлены в Telegram для завершения процесса привязки.",
|
||||
"You'll be redirected automatically. You can return to the previous page if nothing happens after a few seconds.": "Вы будете автоматически перенаправлены. Если через несколько секунд ничего не происходит, вы можете вернуться на предыдущую страницу.",
|
||||
"your AI integration?": "вашу интеграцию с ИИ?",
|
||||
|
||||
175
web/default/src/i18n/locales/vi.json
vendored
175
web/default/src/i18n/locales/vi.json
vendored
@ -120,7 +120,7 @@
|
||||
"Account ID *": "ID tài khoản *",
|
||||
"Account Info": "Thông tin tài khoản",
|
||||
"Account used when authenticating with the SMTP server": "Tài khoản được sử dụng khi xác thực với máy chủ SMTP",
|
||||
"acknowledge the related legal risks": "acknowledge the related legal risks",
|
||||
"acknowledge the related legal risks": "thừa nhận các rủi ro pháp lý liên quan",
|
||||
"Across all groups": "Trên mọi nhóm",
|
||||
"Action confirmation": "Xác nhận hành động",
|
||||
"Actions": "Hành động",
|
||||
@ -247,7 +247,7 @@
|
||||
"Alipay": "Alipay",
|
||||
"All": "All",
|
||||
"All categories": "Tất cả danh mục",
|
||||
"All conditions must match before this tier is used.": "All conditions must match before this tier is used.",
|
||||
"All conditions must match before this tier is used.": "Tất cả điều kiện phải khớp trước khi tầng này được sử dụng.",
|
||||
"All edits are overwrite operations. Leave fields empty to keep current values unchanged.": "Tất cả các chỉnh sửa đều là thao tác ghi đè. Để trống các trường để giữ nguyên giá trị hiện tại.",
|
||||
"All files exceed the maximum size.": "Tất cả các tệp vượt quá kích thước tối đa.",
|
||||
"All Groups": "Tất cả các nhóm",
|
||||
@ -449,7 +449,6 @@
|
||||
"Automatically replaces upstream callback URLs with the server address.": "Tự động thay thế URL callback upstream bằng địa chỉ máy chủ.",
|
||||
"Automatically selects the best available group with circuit breaker mechanism": "Tự động chọn nhóm tốt nhất hiện có với cơ chế ngắt mạch",
|
||||
"Automatically sync model list when upstream changes are detected": "Tự động đồng bộ danh sách mô hình khi phát hiện thay đổi từ nguồn",
|
||||
"Automatically test channels and notify users when limits are hit": "Tự động kiểm tra các kênh và thông báo cho người dùng khi đạt đến giới hạn",
|
||||
"Availability (last 24h)": "Khả dụng (24 giờ qua)",
|
||||
"Available": "Khả dụng",
|
||||
"Available disk space": "Dung lượng đĩa khả dụng",
|
||||
@ -494,7 +493,7 @@
|
||||
"Bark Push URL": "URL đẩy Bark",
|
||||
"Base address provided by your Epay service": "Địa chỉ cơ sở được cung cấp bởi dịch vụ Epay của bạn",
|
||||
"Base amount. Actual deduction = base amount × system group rate.": "Số tiền cơ sở. Số tiền trừ thực tế = số tiền cơ sở × tỷ lệ nhóm hệ thống.",
|
||||
"Base input and output token prices for this tier.": "Base input and output token prices for this tier.",
|
||||
"Base input and output token prices for this tier.": "Giá token đầu vào và đầu ra cơ bản cho tầng này.",
|
||||
"Base input price only": "Chỉ có giá đầu vào cơ bản",
|
||||
"Base Limits": "Giới hạn cơ bản",
|
||||
"Base multipliers applied when users select specific groups.": "Hệ số nhân cơ bản được áp dụng khi người dùng chọn các nhóm cụ thể.",
|
||||
@ -531,6 +530,7 @@
|
||||
"Billing Process": "Quá trình tính phí",
|
||||
"Billing Source": "Nguồn thanh toán",
|
||||
"Bind": "Buộc",
|
||||
"Bind a Pancake store + product": "Liên kết cửa hàng + sản phẩm Pancake",
|
||||
"Bind an email address to your account.": "Liên kết địa chỉ email với tài khoản của bạn.",
|
||||
"Bind Email": "Liên kết Email",
|
||||
"Bind Telegram Account": "Liên kết tài khoản Telegram",
|
||||
@ -557,9 +557,9 @@
|
||||
"Bound": "Ràng buộc",
|
||||
"Bound Channels": "Các kênh ràng buộc",
|
||||
"Bound Only": "Chỉ đã liên kết",
|
||||
"Bound product:": "Sản phẩm đã liên kết:",
|
||||
"Bound store:": "Cửa hàng đã liên kết:",
|
||||
"Bring channels back online after successful checks": "Khôi phục các kênh trực tuyến sau khi kiểm tra thành công",
|
||||
"Broadcast a global banner to users. Markdown is supported.": "Phát một biểu ngữ toàn cầu đến người dùng. Hỗ trợ Markdown.",
|
||||
"Broadcast short system notices on the dashboard": "Phát các thông báo hệ thống ngắn trên bảng điều khiển",
|
||||
"Browse and compare": "Duyệt và so sánh",
|
||||
"Browse available models and pricing": "Duyệt mô hình khả dụng và giá",
|
||||
"Browse rankings by category": "Duyệt bảng xếp hạng theo danh mục",
|
||||
@ -586,7 +586,7 @@
|
||||
"Cache Directory Info": "Thông tin thư mục bộ nhớ đệm",
|
||||
"Cache Entries": "Mục bộ nhớ đệm",
|
||||
"Cache mode": "Chế độ bộ đệm",
|
||||
"Cache pricing": "Cache pricing",
|
||||
"Cache pricing": "Giá bộ nhớ đệm",
|
||||
"Cache ratio": "Tỷ lệ bộ nhớ đệm",
|
||||
"Cache Read": "Đọc bộ nhớ đệm",
|
||||
"Cache read price": "Giá đọc cache",
|
||||
@ -797,9 +797,9 @@
|
||||
"Completion price": "Giá hoàn thành",
|
||||
"Completion price ($/1M tokens)": "Giá hoàn thành ($/1M tokens)",
|
||||
"Completion ratio": "Tỷ lệ hoàn thành",
|
||||
"Compliance confirmation required": "Compliance confirmation required",
|
||||
"Compliance confirmed": "Compliance confirmed",
|
||||
"Compliance confirmed successfully": "Compliance confirmed successfully",
|
||||
"Compliance confirmation required": "Cần xác nhận tuân thủ",
|
||||
"Compliance confirmed": "Đã xác nhận tuân thủ",
|
||||
"Compliance confirmed successfully": "Xác nhận tuân thủ thành công",
|
||||
"Concatenate channel system prompt with user's prompt": "Nối lời nhắc hệ thống kênh với lời nhắc của người dùng",
|
||||
"Condition Path": "Đường dẫn điều kiện",
|
||||
"Condition Settings": "Cài đặt điều kiện",
|
||||
@ -825,49 +825,27 @@
|
||||
"Configure API documentation links for the dashboard": "Cấu hình các liên kết tài liệu API cho bảng điều khiển",
|
||||
"Configure at:": "Cấu hình tại:",
|
||||
"Configure available payment methods. Provide a JSON array.": "Cấu hình các phương thức thanh toán khả dụng. Cung cấp một mảng JSON.",
|
||||
"Configure basic system information and branding": "Cấu hình thông tin hệ thống cơ bản và nhận diện thương hiệu",
|
||||
"Configure channel affinity (sticky routing) rules": "Cấu hình quy tắc ưu tiên kênh (định tuyến dính)",
|
||||
"Configure Creem products. Provide a JSON array.": "Cấu hình sản phẩm Creem. Cung cấp một mảng JSON.",
|
||||
"Configure currency conversion and quota display options": "Cấu hình quy đổi tiền tệ và tùy chọn hiển thị hạn mức",
|
||||
"Configure custom OAuth providers for user authentication": "Cấu hình nhà cung cấp OAuth tùy chỉnh cho xác thực người dùng",
|
||||
"Configure daily check-in rewards for users": "Cấu hình phần thưởng điểm danh hàng ngày cho người dùng",
|
||||
"Configure discount rates based on recharge amounts": "Cấu hình tỷ lệ chiết khấu dựa trên số tiền nạp",
|
||||
"Configure experimental data export for the dashboard": "Cấu hình xuất dữ liệu thử nghiệm cho bảng điều khiển",
|
||||
"Configure Gemini safety behavior, version overrides, and thinking adapter": "Cấu hình hành vi an toàn Gemini, ghi đè phiên bản và bộ điều hợp tư duy",
|
||||
"Configure group ratios and group-specific pricing rules": "Cấu hình tỷ lệ nhóm và quy tắc định giá riêng theo nhóm",
|
||||
"Configure in your Creem dashboard": "Cấu hình trong bảng điều khiển Creem của bạn",
|
||||
"Configure io.net API key for model deployments": "Cấu hình khóa API io.net cho triển khai mô hình",
|
||||
"Configure keyword filtering for prompts and responses.": "Định cấu hình lọc từ khóa để xem lời nhắc và câu trả lời.",
|
||||
"Configure model deployment provider settings": "Cấu hình nhà cung cấp triển khai mô hình",
|
||||
"Configure model pricing ratios and tool prices": "Cấu hình tỷ lệ định giá mô hình và giá công cụ",
|
||||
"Configure model, caching, and group ratios used for billing": "Cấu hình mô hình, bộ nhớ đệm và tỷ lệ nhóm được sử dụng để tính phí.",
|
||||
"Configure monitoring status page groups for the dashboard": "Cấu hình các nhóm trang trạng thái giám sát cho bảng điều khiển",
|
||||
"Configure outgoing email server for notifications": "Cấu hình máy chủ email gửi đi cho thông báo",
|
||||
"Configure Passkey (WebAuthn) login settings": "Cấu hình cài đặt đăng nhập Passkey (WebAuthn)",
|
||||
"Configure password-based login and registration": "Cấu hình đăng nhập và đăng ký dựa trên mật khẩu",
|
||||
"Configure per-model ratio for image inputs or outputs.": "Cấu hình tỷ lệ theo mô hình cho đầu vào hoặc đầu ra hình ảnh.",
|
||||
"Configure per-tool unit prices ($/1K calls). Per-request models do not incur additional tool fees.": "Cấu hình giá theo từng công cụ ($/1K lần gọi). Mô hình tính phí theo request không phát sinh thêm phí công cụ.",
|
||||
"Configure predefined chat links surfaced to end users.": "Cấu hình các liên kết trò chuyện được xác định trước hiển thị cho người dùng cuối.",
|
||||
"Configure pricing model and display options": "Cấu hình mô hình giá và tùy chọn hiển thị",
|
||||
"Configure pricing ratios for a specific model.": "Cấu hình tỷ lệ định giá cho một mô hình cụ thể.",
|
||||
"Configure rate limiting rules for a specific user group.": "Cấu hình quy tắc giới hạn tốc độ cho một nhóm người dùng cụ thể.",
|
||||
"Configure recharge pricing and payment gateway integrations": "Cấu hình giá nạp tiền và tích hợp cổng thanh toán",
|
||||
"Configure system-wide behavior and defaults": "Cấu hình hành vi và mặc định toàn hệ thống",
|
||||
"Configure the ratio for this group.": "Cấu hình tỷ lệ cho nhóm này.",
|
||||
"Configure third-party authentication providers": "Cấu hình nhà cung cấp xác thực bên thứ ba",
|
||||
"Configure upstream providers and routing.": "Cấu hình nhà cung cấp upstream và định tuyến.",
|
||||
"Configure upstream worker or proxy service for outbound requests": "Cấu hình worker thượng nguồn hoặc dịch vụ proxy cho các yêu cầu đi",
|
||||
"Configure user quota allocation and rewards": "Cấu hình phân bổ hạn ngạch người dùng và phần thưởng",
|
||||
"Configure Waffo Pancake hosted checkout integration for USD-priced top-ups": "Cấu hình tích hợp thanh toán Waffo Pancake (hosted checkout) cho nạp tiền theo USD",
|
||||
"Configure Waffo payment aggregation platform integration": "Cấu hình tích hợp nền tảng tổng hợp thanh toán Waffo",
|
||||
"Configure xAI Grok model settings": "Cấu hình mô hình xAI Grok",
|
||||
"Configure xAI Grok model specific settings": "Cấu hình cài đặt riêng cho mô hình xAI Grok",
|
||||
"Configure your account behavior preferences": "Cấu hình tùy chọn hành vi tài khoản của bạn",
|
||||
"Configure your account preferences and integrations": "Cấu hình các tùy chọn và tích hợp tài khoản của bạn",
|
||||
"Configured routes and latency checks": "Tuyến đã cấu hình và kiểm tra độ trễ",
|
||||
"Confirm": "Xác nhận",
|
||||
"Confirm Action": "Xác nhận hành động",
|
||||
"Confirm and enable": "Confirm and enable",
|
||||
"Confirm and enable": "Xác nhận và bật",
|
||||
"Confirm Batch Update": "Xác nhận Cập nhật Hàng loạt",
|
||||
"Confirm Billing Conflicts": "Xác nhận xung đột thanh toán",
|
||||
"Confirm Changes": "Xác nhận thay đổi",
|
||||
@ -875,8 +853,8 @@
|
||||
"Confirm cleanup of inactive disk cache?": "Xác nhận dọn dẹp bộ nhớ đệm đĩa không hoạt động?",
|
||||
"Confirm clearing all channel affinity cache": "Xác nhận xóa toàn bộ bộ nhớ đệm ưu tiên kênh",
|
||||
"Confirm clearing cache for this rule": "Xác nhận xóa bộ nhớ đệm của quy tắc này",
|
||||
"Confirm compliance": "Confirm compliance",
|
||||
"Confirm compliance terms": "Confirm compliance terms",
|
||||
"Confirm compliance": "Xác nhận tuân thủ",
|
||||
"Confirm compliance terms": "Xác nhận điều khoản tuân thủ",
|
||||
"Confirm Creem Purchase": "Xác nhận mua Creem",
|
||||
"Confirm delete": "Xác nhận xóa",
|
||||
"Confirm disable": "Xác nhận vô hiệu hóa",
|
||||
@ -891,11 +869,11 @@
|
||||
"auth.resetPasswordConfirm.description": "Xác nhận yêu cầu đặt lại để tạo mật khẩu mới.",
|
||||
"Confirm Selection": "Xác nhận lựa chọn",
|
||||
"Confirm settings and finish setup": "Xác nhận cài đặt và hoàn tất thiết lập",
|
||||
"confirm that I bear legal responsibility arising from deployment": "confirm that I bear legal responsibility arising from deployment",
|
||||
"confirm that I bear legal responsibility arising from deployment": "xác nhận rằng tôi chịu trách nhiệm pháp lý phát sinh từ việc triển khai",
|
||||
"Confirm Unbind": "Xác nhận hủy liên kết",
|
||||
"Confirm your identity before removing this Passkey from your account.": "Hãy xác minh danh tính trước khi gỡ Passkey khỏi tài khoản.",
|
||||
"Confirm your identity with Two-factor Authentication before registering a Passkey.": "Hãy xác minh danh tính bằng Xác thực hai yếu tố trước khi đăng ký Passkey.",
|
||||
"Confirmed at {{time}} by user #{{userId}}": "Confirmed at {{time}} by user #{{userId}}",
|
||||
"Confirmed at {{time}} by user #{{userId}}": "Được xác nhận lúc {{time}} bởi người dùng #{{userId}}",
|
||||
"Conflict": "Xung đột",
|
||||
"Connect": "Kết nối",
|
||||
"Connect through OpenAI, Claude, Gemini, and other compatible API routes": "Kết nối qua OpenAI, Claude, Gemini và các tuyến API tương thích khác",
|
||||
@ -929,11 +907,7 @@
|
||||
"Continue with Telegram": "Tiếp tục với Telegram",
|
||||
"Continue with WeChat": "Tiếp tục với WeChat",
|
||||
"Contract review, compliance, summarisation": "Rà soát hợp đồng, tuân thủ, tóm tắt",
|
||||
"Control log retention and clean historical data.": "Kiểm soát việc lưu giữ nhật ký và làm sạch dữ liệu lịch sử.",
|
||||
"Control passthrough behavior and connection keep-alive settings": "Kiểm soát hành vi truyền qua và cài đặt duy trì kết nối",
|
||||
"Control request frequency to prevent abuse and manage system load.": "Kiểm soát tần suất yêu cầu để ngăn chặn lạm dụng và quản lý tải hệ thống.",
|
||||
"Control which models are exposed and which groups may use them.": "Kiểm soát mô hình được hiển thị và nhóm nào có thể sử dụng chúng.",
|
||||
"Control which sidebar areas and modules are available to all users.": "Kiểm soát những khu vực thanh bên và mô-đun nào khả dụng cho tất cả người dùng.",
|
||||
"Controls how much the model thinks before answering": "Điều chỉnh mức suy luận trước khi trả lời",
|
||||
"Controls whether user verification (biometrics/PIN) is required during Passkey flows.": "Kiểm soát xem liệu có yêu cầu xác minh người dùng (sinh trắc học/mã PIN) trong các luồng Passkey hay không.",
|
||||
"Conversion rate from USD to your custom currency": "Tỷ giá chuyển đổi từ USD sang đơn vị tiền tệ tùy chỉnh của bạn",
|
||||
@ -981,7 +955,7 @@
|
||||
"Core concepts": "Khái niệm chính",
|
||||
"Core Configuration": "Cấu hình chính",
|
||||
"Core Features": "Tính năng cốt lõi",
|
||||
"Core pricing": "Core pricing",
|
||||
"Core pricing": "Giá cốt lõi",
|
||||
"Cost": "Chi phí",
|
||||
"Cost in USD per request, regardless of tokens used.": "Chi phí bằng USD cho mỗi yêu cầu, bất kể số lượng token được sử dụng.",
|
||||
"Cost Tracking": "Theo dõi chi phí",
|
||||
@ -1023,9 +997,13 @@
|
||||
"Create, revoke, and audit API tokens.": "Tạo, thu hồi và kiểm toán token API.",
|
||||
"Created": "Đã tạo",
|
||||
"Created At": "Ngày tạo",
|
||||
"Creating...": "Đang tạo...",
|
||||
"Creation failed": "Tạo thất bại",
|
||||
"Credential generated": "Đã tạo thông tin xác thực",
|
||||
"Credential refreshed": "Đã làm mới thông tin xác thực",
|
||||
"Credentials": "Thông tin xác thực",
|
||||
"Credentials verification failed": "Xác minh thông tin xác thực thất bại",
|
||||
"Credentials verification failed — double-check Merchant ID and API private key.": "Xác minh thông tin xác thực thất bại — hãy kiểm tra lại Merchant ID và khóa riêng API.",
|
||||
"Credit remaining": "Tín dụng còn lại",
|
||||
"Creem API key (leave blank unless updating)": "Khóa API Creem (để trống trừ khi cập nhật)",
|
||||
"Creem Gateway": "Cổng Creem",
|
||||
@ -1034,7 +1012,6 @@
|
||||
"Creem products must be a JSON array": "Sản phẩm Creem phải là mảng JSON",
|
||||
"Cross-group": "Liên nhóm",
|
||||
"Cross-group retry": "Thử lại liên nhóm",
|
||||
"Curate quick links to your different Domains": "Sắp xếp các liên kết nhanh đến các Miền khác nhau của bạn",
|
||||
"Currency": "Tiền tệ",
|
||||
"Currency & Display": "Tiền tệ & hiển thị",
|
||||
"Current Balance": "Số Dư Hiện Tại",
|
||||
@ -1322,7 +1299,7 @@
|
||||
"Each line represents one keyword. Leave blank to disable the list but keep the switch states.": "Mỗi dòng đại diện cho một từ khóa. Để trống để tắt danh sách nhưng vẫn giữ trạng thái công tắc.",
|
||||
"Each tier supports 0~2 conditions (over len, p, c); the last tier is the catch-all without conditions. Use len (full input length, including cache hits) for tier conditions to avoid mis-routing when cache hits reduce p.": "Mỗi bậc hỗ trợ 0~2 điều kiện (đối với len, p, c); bậc cuối là bậc dự phòng không cần điều kiện. Hãy dùng len (độ dài đầu vào đầy đủ, bao gồm cả cache hits) cho điều kiện bậc để tránh định tuyến sai khi cache hits làm giảm p.",
|
||||
"Each tier supports up to 2 conditions; the last tier is the catch-all without conditions. Use full input length for tier conditions to avoid mis-routing when cache hits reduce billable input tokens.": "Mỗi tầng hỗ trợ tối đa 2 điều kiện; tầng cuối cùng là tầng dự phòng không có điều kiện. Hãy dùng độ dài đầu vào đầy đủ cho điều kiện tầng để tránh chọn sai tầng khi cache hit làm giảm token đầu vào tính phí.",
|
||||
"Each tier supports up to 2 conditions. The last tier without conditions is the fallback.": "Each tier supports up to 2 conditions. The last tier without conditions is the fallback.",
|
||||
"Each tier supports up to 2 conditions. The last tier without conditions is the fallback.": "Mỗi tầng hỗ trợ tối đa 2 điều kiện. Tầng cuối cùng không có điều kiện là tầng dự phòng.",
|
||||
"Earn rewards when your referrals add funds. Transfer accumulated rewards to your balance anytime.": "Nhận phần thưởng khi người bạn giới thiệu nạp tiền",
|
||||
"Edit": "Chỉnh sửa",
|
||||
"Edit {{title}}": "Chỉnh sửa {{title}}",
|
||||
@ -1388,7 +1365,6 @@
|
||||
"Enable OIDC": "Bật OIDC",
|
||||
"Enable or disable this channel": "Bật hoặc tắt kênh này",
|
||||
"Enable or disable this model": "Bật hoặc tắt mô hình này",
|
||||
"Enable or disable top navigation modules globally.": "Bật hoặc tắt các mô-đun điều hướng trên cùng toàn cục.",
|
||||
"Enable Passkey": "Bật khóa truy cập",
|
||||
"Enable Performance Monitoring": "Bật giám sát hiệu suất",
|
||||
"Enable rate limiting": "Bật giới hạn tốc độ",
|
||||
@ -1542,7 +1518,6 @@
|
||||
"Expired at": "Hết hạn lúc",
|
||||
"Expired time cannot be earlier than current time": "Thời gian hết hạn không thể sớm hơn thời gian hiện tại",
|
||||
"Expires": "Hết hạn",
|
||||
"Expose grouped Uptime Kuma status pages directly on the dashboard": "Hiển thị các trang trạng thái Uptime Kuma đã nhóm trực tiếp trên bảng điều khiển",
|
||||
"Expose ratio API": "Cung cấp API tỷ lệ",
|
||||
"Exposes the pricing/models catalog in the top navigation.": "Hiển thị danh mục giá/mô hình trên thanh điều hướng đầu trang.",
|
||||
"Expression": "Biểu thức",
|
||||
@ -1575,7 +1550,7 @@
|
||||
"Failed to clean logs": "Không thể dọn sạch nhật ký",
|
||||
"Failed to complete order": "Không thể hoàn thành đơn hàng",
|
||||
"Failed to complete Passkey login": "Không thể hoàn tất đăng nhập Passkey",
|
||||
"Failed to confirm compliance": "Failed to confirm compliance",
|
||||
"Failed to confirm compliance": "Không thể xác nhận tuân thủ",
|
||||
"Failed to contact GitHub releases API": "Không thể kết nối API GitHub Releases",
|
||||
"Failed to copy": "Sao chép thất bại",
|
||||
"Failed to copy channel": "Không thể sao chép kênh",
|
||||
@ -1688,7 +1663,7 @@
|
||||
"Failed to update user": "Không thể cập nhật người dùng",
|
||||
"Failure keywords": "Từ khóa thất bại",
|
||||
"Fair": "Công bằng",
|
||||
"Fallback tier": "Fallback tier",
|
||||
"Fallback tier": "Tầng dự phòng",
|
||||
"FAQ": "FAQ",
|
||||
"FAQ added. Click \"Save Settings\" to apply.": "Đã thêm FAQ. Nhấp \"Lưu cài đặt\" để áp dụng.",
|
||||
"FAQ deleted. Click \"Save Settings\" to apply.": "Đã xóa FAQ. Nhấp \"Lưu cài đặt\" để áp dụng.",
|
||||
@ -1717,6 +1692,8 @@
|
||||
"Fill Codex CLI / Claude CLI Templates": "Điền mẫu Codex CLI / Claude CLI",
|
||||
"Fill example (all channels)": "Điền ví dụ (tất cả kênh)",
|
||||
"Fill example (specific channels)": "Điền ví dụ (kênh cụ thể)",
|
||||
"Fill in both Merchant ID and API Private Key before creating.": "Nhập cả Merchant ID và khóa riêng API trước khi tạo.",
|
||||
"Fill in the credentials above to begin.": "Nhập thông tin xác thực ở trên để bắt đầu.",
|
||||
"Fill in the following info to create a new subscription plan": "Điền thông tin sau để tạo gói đăng ký mới",
|
||||
"Fill Related Models": "Điền Mô hình Liên quan",
|
||||
"Fill Template": "Điền Mẫu",
|
||||
@ -1751,7 +1728,6 @@
|
||||
"Final cost = base × multiplier when conditions match": "Chi phí cuối = cơ sở × hệ số khi thỏa điều kiện",
|
||||
"Final price multiplier (0.95 = 5% discount": "Hệ số nhân giá cuối cùng (0.95 = giảm giá 5%)",
|
||||
"Finance": "Tài chính",
|
||||
"Fine-tune Midjourney integration and guardrails.": "Tinh chỉnh tích hợp Midjourney và các cơ chế bảo vệ.",
|
||||
"Finish Time": "Thời gian hoàn thành",
|
||||
"First API request": "Yêu cầu API đầu tiên",
|
||||
"First/Last Frame to Video": "Khung đầu/cuối sang video",
|
||||
@ -1988,8 +1964,8 @@
|
||||
"Human-readable name shown to users during Passkey prompts.": "Tên dễ đọc hiển thị cho người dùng trong quá trình nhắc nhở Passkey.",
|
||||
"I confirm enabling high-risk retry": "Tôi xác nhận bật thử lại rủi ro cao",
|
||||
"I have read and agree to the": "Tôi đã đọc và đồng ý với",
|
||||
"I have read and understood the above compliance reminder": "I have read and understood the above compliance reminder",
|
||||
"I have read and understood the above compliance reminder, acknowledge the related legal risks, and confirm that I bear legal responsibility arising from deployment, operation, and charging behavior.": "I have read and understood the above compliance reminder, acknowledge the related legal risks, and confirm that I bear legal responsibility arising from deployment, operation, and charging behavior.",
|
||||
"I have read and understood the above compliance reminder": "Tôi đã đọc và hiểu nhắc nhở tuân thủ ở trên",
|
||||
"I have read and understood the above compliance reminder, acknowledge the related legal risks, and confirm that I bear legal responsibility arising from deployment, operation, and charging behavior.": "Tôi đã đọc và hiểu nhắc nhở tuân thủ ở trên, thừa nhận các rủi ro pháp lý liên quan và xác nhận rằng tôi chịu trách nhiệm pháp lý phát sinh từ việc triển khai, vận hành và thu phí.",
|
||||
"I understand that disabling 2FA will remove all protection and backup codes": "Tôi hiểu rằng việc vô",
|
||||
"Icon": "Biểu tượng",
|
||||
"Icon file must be 100 KB or smaller": "Tệp biểu tượng phải có kích thước 100 KB hoặc nhỏ hơn",
|
||||
@ -2001,7 +1977,7 @@
|
||||
"If default auto group is enabled, newly created tokens start with auto instead of an empty group.": "Nếu bật nhóm auto mặc định, token mới sẽ bắt đầu với auto thay vì nhóm trống.",
|
||||
"If the affinity channel fails and retry succeeds on another channel, update affinity to the successful channel.": "Nếu kênh ưu tiên thất bại và thử lại thành công trên kênh khác, cập nhật ưu tiên sang kênh thành công.",
|
||||
"If this keeps happening, please report it on GitHub Issues.": "Nếu sự cố tiếp tục xảy ra, vui lòng báo cáo trên GitHub Issues.",
|
||||
"If you provide generative AI services to the public in mainland China, you will fulfill legal obligations including filing, security assessment, content safety, complaint handling, generated content labeling, log retention, and personal information protection.": "If you provide generative AI services to the public in mainland China, you will fulfill legal obligations including filing, security assessment, content safety, complaint handling, generated content labeling, log retention, and personal information protection.",
|
||||
"If you provide generative AI services to the public in mainland China, you will fulfill legal obligations including filing, security assessment, content safety, complaint handling, generated content labeling, log retention, and personal information protection.": "Nếu bạn cung cấp dịch vụ AI tạo sinh cho công chúng tại Trung Quốc đại lục, bạn sẽ thực hiện các nghĩa vụ pháp lý bao gồm đăng ký, đánh giá an toàn, an toàn nội dung, xử lý khiếu nại, gắn nhãn nội dung được tạo, lưu giữ nhật ký và bảo vệ thông tin cá nhân.",
|
||||
"Ignored upstream models": "Mô hình upstream bị bỏ qua",
|
||||
"Image": "Hình ảnh",
|
||||
"Image Generation": "Tạo hình ảnh",
|
||||
@ -2228,30 +2204,21 @@
|
||||
"Low balance": "Số dư thấp",
|
||||
"Lowest median first-token latency": "Độ trễ trung vị token đầu tiên thấp nhất",
|
||||
"m": "m",
|
||||
"Maintain a list of common questions for the dashboard help panel": "Duy trì danh sách các câu hỏi thường gặp cho bảng trợ giúp của bảng điều khiển",
|
||||
"Maintenance": "Bảo trì",
|
||||
"Make it easier for teammates to pick the right group.": "Giúp đồng đội dễ dàng chọn đúng nhóm hơn.",
|
||||
"Manage": "Quản lý",
|
||||
"Manage account bindings for this user": "Quản lý liên kết tài khoản cho người dùng này",
|
||||
"Manage API channels and provider configurations": "Quản lý các kênh API và cấu hình nhà cung cấp",
|
||||
"Manage Bindings": "Quản lý liên kết",
|
||||
"Manage catalog visibility and pricing.": "Quản lý hiển thị danh mục và giá cả.",
|
||||
"Manage custom OAuth providers for user authentication": "Quản lý nhà cung cấp OAuth tùy chỉnh để xác thực người dùng",
|
||||
"Manage Keys": "Quản lý Khóa",
|
||||
"Manage local models for:": "Quản lý mô hình cục bộ cho:",
|
||||
"Manage model deployments": "Quản lý triển khai mô hình",
|
||||
"Manage model metadata and configuration": "Quản lý siêu dữ liệu và cấu hình mô hình",
|
||||
"Manage multi-key status and configuration for this channel": "Quản lý trạng thái và cấu hình đa khóa cho kênh này",
|
||||
"Manage Ollama Models": "Quản lý mô hình Ollama",
|
||||
"Manage redemption codes for quota top-up": "Quản lý mã quy đổi để nạp thêm hạn mức",
|
||||
"Manage server log files. Log files accumulate over time; regular cleanup is recommended to free disk space.": "Quản lý tệp nhật ký máy chủ. Tệp nhật ký tích lũy theo thời gian; nên dọn dẹp định kỳ để giải phóng dung lượng đĩa.",
|
||||
"Manage subscription plan creation, pricing and status": "Quản lý tạo, định giá và trạng thái gói đăng ký",
|
||||
"Manage subscription plans and pricing.": "Quản lý gói đăng ký và giá cả.",
|
||||
"Manage Subscriptions": "Quản lý đăng ký",
|
||||
"Manage users and their permissions": "Quản lý người dùng và quyền của họ",
|
||||
"Manage Vendors": "Quản lý Nhà cung cấp",
|
||||
"Manage your API keys for accessing the service": "Quản lý các khóa API của bạn để truy cập dịch vụ",
|
||||
"Manage your balance and payment methods": "Quản lý số dư và phương thức thanh toán của bạn",
|
||||
"Manage your security settings and account access": "Quản lý cài đặt bảo mật và quyền truy cập tài khoản của bạn",
|
||||
"Manual Disabled": "Vô hiệu hóa thủ công",
|
||||
"Map fields from the user info response to local user attributes. Supports nested paths (e.g. ocs.data.id).": "Ánh xạ các trường từ phản hồi thông tin người dùng sang thuộc tính người dùng cục bộ. Hỗ trợ đường dẫn lồng nhau (ví dụ: ocs.data.id).",
|
||||
@ -2295,7 +2262,7 @@
|
||||
"Maximum tokens per response": "Số token tối đa mỗi phản hồi",
|
||||
"maxRequests ≥ 0, maxSuccess ≥ 1, both ≤ 2,147,483,647": "maxRequests ≥ 0, maxSuccess ≥ 1, cả hai đều ≤ 2,147,483,647",
|
||||
"May be used for training by upstream provider": "Có thể được nhà cung cấp dùng để huấn luyện",
|
||||
"Media pricing": "Media pricing",
|
||||
"Media pricing": "Giá phương tiện",
|
||||
"Median time-to-first-token (TTFT) sampled hourly per group": "Độ trễ token đầu tiên trung vị (TTFT) lấy mẫu mỗi giờ theo nhóm",
|
||||
"Medical Q&A, mental health support": "Hỏi đáp y tế, hỗ trợ sức khỏe tinh thần",
|
||||
"Memory Hits": "Lượt truy cập bộ nhớ",
|
||||
@ -2323,6 +2290,8 @@
|
||||
"Minimum Trust Level": "Mức độ tin cậy tối thiểu",
|
||||
"Minimum:": "Tối thiểu:",
|
||||
"Minor blips in the last 30 days": "Vài gián đoạn nhỏ trong 30 ngày qua",
|
||||
"Mint a fresh pair below — or pick an existing one further down. Click Save when ready.": "Tạo một cặp mới bên dưới, hoặc chọn một cặp hiện có ở phía dưới. Nhấn Lưu khi đã sẵn sàng.",
|
||||
"Creates a Pancake product in the saved store using this plan’s title and price. Requires Waffo Pancake to be fully configured in Payment settings first.": "Tạo một sản phẩm Pancake trong cửa hàng đã lưu bằng tiêu đề và giá của gói này. Trước tiên cần cấu hình đầy đủ Waffo Pancake trong cài đặt Thanh toán.",
|
||||
"Minute": "Phút",
|
||||
"minutes": "phút",
|
||||
"Missing code": "Thiếu mã",
|
||||
@ -2617,8 +2586,9 @@
|
||||
"No Retry": "Không thử lại",
|
||||
"No rules yet": "Chưa có quy tắc",
|
||||
"No rules yet. Add a group below to get started.": "Chưa có quy tắc nào. Thêm một nhóm bên dưới để bắt đầu.",
|
||||
"No separate media pricing configured.": "No separate media pricing configured.",
|
||||
"No separate media pricing configured.": "Chưa cấu hình giá phương tiện riêng.",
|
||||
"No status code mappings configured.": "Chưa cấu hình ánh xạ mã trạng thái.",
|
||||
"No stores on this merchant yet. Set a return URL and click Create to mint your first pair.": "Merchant này chưa có cửa hàng. Đặt URL trả về và nhấn Tạo để tạo cặp đầu tiên.",
|
||||
"No subscription plans yet": "Chưa có gói đăng ký nào",
|
||||
"No subscription records": "Không có bản ghi đăng ký",
|
||||
"No Sync": "Không đồng bộ",
|
||||
@ -2640,7 +2610,7 @@
|
||||
"No X Found": "Không tìm thấy X",
|
||||
"Node Name": "Tên nút",
|
||||
"Non-stream": "Không phát trực tuyến",
|
||||
"Non-zero invitation rewards require compliance confirmation in Payment Gateway settings.": "Non-zero invitation rewards require compliance confirmation in Payment Gateway settings.",
|
||||
"Non-zero invitation rewards require compliance confirmation in Payment Gateway settings.": "Phần thưởng mời khác 0 yêu cầu xác nhận tuân thủ trong cài đặt Cổng thanh toán.",
|
||||
"None": "Không có",
|
||||
"noreply@example.com": "noreply@example.com",
|
||||
"Normalized:": "Chuẩn hóa:",
|
||||
@ -2739,7 +2709,7 @@
|
||||
"OpenRouter": "OpenRouter",
|
||||
"opens in an external client. Trigger it from the sidebar or API key actions to launch the configured application.": "mở trong một ứng dụng bên ngoài. Kích hoạt nó từ thanh bên hoặc các hành động khóa API để khởi chạy ứng dụng đã cấu hình.",
|
||||
"Operation": "Thao tác",
|
||||
"operation and charging behavior": "operation and charging behavior",
|
||||
"operation and charging behavior": "hành vi vận hành và thu phí",
|
||||
"Operation failed": "Thao tác thất bại",
|
||||
"Operation successful": "Thao tác thành công",
|
||||
"Operation Type": "Loại thao tác",
|
||||
@ -2762,6 +2732,7 @@
|
||||
"Opus Model": "Mô hình Opus",
|
||||
"Or continue with": "Hoặc tiếp tục với",
|
||||
"Or enter this key manually:": "Hoặc nhập khóa này thủ công:",
|
||||
"or pick existing": "hoặc chọn cặp hiện có",
|
||||
"Order completed successfully": "Đơn hàng đã hoàn thành thành công",
|
||||
"Order History": "Lịch sử đơn hàng",
|
||||
"Order Payment Method": "Phương thức thanh toán đơn hàng",
|
||||
@ -2780,7 +2751,6 @@
|
||||
"Overnight range": "Khoảng qua nửa đêm",
|
||||
"override": "ghi đè",
|
||||
"Override": "Ghi đè",
|
||||
"Override Anthropic headers, defaults, and thinking adapter behavior": "Ghi đè tiêu đề, cài đặt mặc định và hành vi bộ điều hợp tư duy của Anthropic",
|
||||
"Override auto-discovered endpoint": "Ghi đè điểm cuối tự động phát hiện",
|
||||
"Override request headers": "Ghi đè tiêu đề yêu cầu",
|
||||
"Override request headers (JSON format)": "Ghi đè tiêu đề yêu cầu (định dạng JSON)",
|
||||
@ -2796,6 +2766,7 @@
|
||||
"Page {{current}} of {{total}}": "Trang {{current}} / {{total}}",
|
||||
"PaLM": "PaLM",
|
||||
"Pan": "Pan",
|
||||
"Pancake": "Pancake",
|
||||
"Param Override": "Ghi đè tham số",
|
||||
"Parameter": "Tham số",
|
||||
"Parameter configuration error": "Lỗi cấu hình tham số",
|
||||
@ -2859,6 +2830,7 @@
|
||||
"Pay": "Pay",
|
||||
"Pay-as-you-go with real-time usage monitoring": "Thanh toán theo mức sử dụng với theo dõi mức sử dụng theo thời gian thực",
|
||||
"Payment": "Thanh toán",
|
||||
"Payment aggregator mode — onboard with your own registered company (offshore entity). Built for Enterprise.": "Chế độ tổng hợp thanh toán — đăng ký bằng công ty đã đăng ký của bạn (pháp nhân offshore). Dành cho doanh nghiệp.",
|
||||
"Payment Channel": "Kênh thanh toán",
|
||||
"Payment Gateway": "Cổng thanh toán",
|
||||
"Payment initiated": "Đã khởi tạo thanh toán",
|
||||
@ -2872,7 +2844,8 @@
|
||||
"Payment page opened": "Đã mở trang thanh toán",
|
||||
"Payment request failed": "Yêu cầu thanh toán thất bại",
|
||||
"Payment return URL": "URL trả về thanh toán",
|
||||
"Payment, redemption codes, subscription plans, and invitation rewards are locked until the root administrator confirms the compliance terms.": "Payment, redemption codes, subscription plans, and invitation rewards are locked until the root administrator confirms the compliance terms.",
|
||||
"Payment return URL is empty. Create the product without a SuccessURL redirect?": "URL trả về thanh toán đang trống. Tạo sản phẩm mà không có chuyển hướng SuccessURL?",
|
||||
"Payment, redemption codes, subscription plans, and invitation rewards are locked until the root administrator confirms the compliance terms.": "Thanh toán, mã đổi thưởng, gói đăng ký và phần thưởng mời sẽ bị khóa cho đến khi quản trị viên root xác nhận điều khoản tuân thủ.",
|
||||
"Peak": "Đỉnh",
|
||||
"Peak throughput": "Thông lượng đỉnh",
|
||||
"Penalises repetition of frequent tokens": "Phạt việc lặp các token phổ biến",
|
||||
@ -2915,11 +2888,14 @@
|
||||
"Personal use": "Sử dụng cá nhân",
|
||||
"Personal use mode": "Chế độ sử dụng cá nhân",
|
||||
"Pick a date": "Chọn ngày",
|
||||
"Pick or create both a store and a product before saving.": "Hãy chọn hoặc tạo cả cửa hàng và sản phẩm trước khi lưu.",
|
||||
"Ping Interval (seconds)": "Thời gian Ping (giây)",
|
||||
"Plan": "Gói",
|
||||
"Plan Name": "Tên gói",
|
||||
"Plan price must be greater than zero": "Giá gói phải lớn hơn 0",
|
||||
"Plan Subtitle": "Phụ đề gói",
|
||||
"Plan Title": "Tiêu đề gói",
|
||||
"Plan title is required": "Tiêu đề gói là bắt buộc",
|
||||
"Planned maintenance on Friday at 22:00 UTC...": "Bảo trì theo kế hoạch vào thứ Sáu lúc 22:00 UTC...",
|
||||
"Platform": "Nền tảng",
|
||||
"Playground": "Sân chơi",
|
||||
@ -2968,7 +2944,7 @@
|
||||
"Please set Ollama API Base URL first": "Vui lòng đặt URL cơ sở API Ollama trước",
|
||||
"Please sign in to view {{module}}.": "Vui lòng đăng nhập để xem {{module}}.",
|
||||
"Please try again later.": "Vui lòng thử lại sau.",
|
||||
"Please type the following text to confirm:": "Please type the following text to confirm:",
|
||||
"Please type the following text to confirm:": "Vui lòng nhập văn bản sau để xác nhận:",
|
||||
"Please upload key file(s)": "Vui lòng tải lên (các) tệp khóa",
|
||||
"Please wait a moment before trying again.": "Vui lòng chờ một lát rồi thử lại.",
|
||||
"Please wait a moment, human check is initializing...": "Vui lòng đợi một chút, kiểm tra con người đang khởi tạo...",
|
||||
@ -3020,7 +2996,6 @@
|
||||
"Press Enter or comma to add tags": "Nhấn Enter hoặc dấu phẩy để thêm thẻ",
|
||||
"Press Enter to use \"{{value}}\"": "Nhấn Enter để dùng \"{{value}}\"",
|
||||
"Prevent server-side request forgery attacks": "Ngăn chặn các cuộc tấn công giả mạo yêu cầu phía máy chủ",
|
||||
"Prevent server-side request forgery attacks by controlling outbound requests.": "Ngăn chặn các cuộc tấn công giả mạo yêu cầu phía máy chủ bằng cách kiểm soát các yêu cầu gửi đi.",
|
||||
"Preview": "Xem trước",
|
||||
"Previous": "Trước",
|
||||
"Previous branch": "Nhánh trước",
|
||||
@ -3083,7 +3058,6 @@
|
||||
"Prompt Details": "Chi tiết lời nhắc",
|
||||
"Prompt price ($/1M tokens)": "Giá prompt ($/1 triệu token)",
|
||||
"Proprietary": "Độc quyền",
|
||||
"Protect login and registration with Cloudflare Turnstile": "Bảo vệ đăng nhập và đăng ký bằng Cloudflare Turnstile",
|
||||
"Provide a JSON object where each key maps to an endpoint definition.": "Cung cấp một đối tượng JSON nơi mỗi khóa ánh xạ đến một định nghĩa điểm cuối.",
|
||||
"Provide a valid URL starting with http:// or https://": "Cung cấp URL hợp lệ bắt đầu bằng http:// hoặc https://",
|
||||
"Provide Markdown, HTML, or an external URL for the privacy policy": "Cung cấp Markdown, HTML, hoặc một URL bên ngoài cho chính sách quyền riêng tư",
|
||||
@ -3197,7 +3171,7 @@
|
||||
"Redemption code updated successfully": "Mã đổi thưởng đã cập nhật thành công",
|
||||
"Redemption code(s) created successfully": "Mã đổi thưởng đã tạo thành công",
|
||||
"Redemption Codes": "Mã đổi thưởng",
|
||||
"Redemption codes are disabled until the administrator confirms compliance terms.": "Redemption codes are disabled until the administrator confirms compliance terms.",
|
||||
"Redemption codes are disabled until the administrator confirms compliance terms.": "Mã đổi thưởng bị tắt cho đến khi quản trị viên xác nhận điều khoản tuân thủ.",
|
||||
"redemption codes.": "Mã đổi thưởng",
|
||||
"Redemption failed": "Đổi thưởng thất bại",
|
||||
"Redemption successful! Added: {{quota}}": "Đổi mã thành công! Đã thêm: {{quota}}",
|
||||
@ -3209,7 +3183,7 @@
|
||||
"Reference Video": "Video tham chiếu",
|
||||
"Referral link:": "Liên kết giới thiệu:",
|
||||
"Referral Program": "Chương trình Giới thiệu",
|
||||
"Referral reward transfer is disabled until the administrator confirms compliance terms.": "Referral reward transfer is disabled until the administrator confirms compliance terms.",
|
||||
"Referral reward transfer is disabled until the administrator confirms compliance terms.": "Chuyển phần thưởng giới thiệu bị tắt cho đến khi quản trị viên xác nhận điều khoản tuân thủ.",
|
||||
"Refine models by provider, group, type, and tags.": "Tinh chỉnh mô hình theo nhà cung cấp, nhóm, loại và thẻ.",
|
||||
"Refresh": "Làm mới",
|
||||
"Refresh Cache": "Làm mới bộ nhớ đệm",
|
||||
@ -3225,6 +3199,7 @@
|
||||
"Regex": "Biểu thức chính quy",
|
||||
"Regex Pattern": "Mẫu biểu thức chính quy",
|
||||
"Regex Replace": "Thay thế regex",
|
||||
"Register each URL into the matching Test Mode / Production Mode webhook slot in the Pancake dashboard. Separate endpoints prevent test traffic from accidentally crediting production accounts.": "Đăng ký từng URL vào ô webhook Test Mode / Production Mode tương ứng trong bảng điều khiển Pancake. Endpoint riêng biệt giúp tránh việc lưu lượng thử nghiệm vô tình cộng tiền vào tài khoản sản xuất.",
|
||||
"Register Passkey": "Đăng ký Passkey",
|
||||
"Registration Enabled": "Đăng ký đã bật",
|
||||
"Registry (optional)": "Registry (tùy chọn)",
|
||||
@ -3368,7 +3343,6 @@
|
||||
"Reveal key": "Display key",
|
||||
"Revenue": "Doanh thu",
|
||||
"Review & initialize": "Xem lại và khởi tạo",
|
||||
"Review current version and fetch release notes.": "Xem xét phiên bản hiện tại và lấy ghi chú phát hành.",
|
||||
"Review model rates before scaling traffic": "Xem giá mô hình trước khi mở rộng lưu lượng",
|
||||
"Review your payment details": "Xem lại chi tiết thanh toán của bạn",
|
||||
"Review your purchase details before proceeding.": "Xem lại chi tiết mua hàng trước khi tiếp tục.",
|
||||
@ -3504,8 +3478,10 @@
|
||||
"Select a group type": "Chọn loại nhóm",
|
||||
"Select a model to edit pricing": "Chọn mô hình để chỉnh sửa giá",
|
||||
"Select a preset...": "Chọn cấu hình sẵn...",
|
||||
"Select a product": "Chọn sản phẩm",
|
||||
"Select a role": "Chọn vai trò",
|
||||
"Select a rule to edit.": "Chọn một quy tắc để chỉnh sửa.",
|
||||
"Select a store": "Chọn cửa hàng",
|
||||
"Select a timestamp before clearing logs.": "Chọn một dấu thời gian trước khi xóa nhật ký.",
|
||||
"Select a usage mode to continue": "Chọn chế độ sử dụng để tiếp tục",
|
||||
"Select a verification method first": "Vui lòng chọn phương thức xác thực trước",
|
||||
@ -3580,7 +3556,7 @@
|
||||
"Sending...": "Đang gửi...",
|
||||
"Sensitive Words": "Từ ngữ nhạy cảm",
|
||||
"Sent the API key to FluentRead.": "Đã gửi khóa API đến FluentRead.",
|
||||
"Separate image/audio prices are enabled.": "Separate image/audio prices are enabled.",
|
||||
"Separate image/audio prices are enabled.": "Giá riêng cho hình ảnh/âm thanh đã được bật.",
|
||||
"Serve multiple users or teams with billing and quota control.": "Phục vụ nhiều người dùng hoặc nhóm với quản lý thanh toán và hạn mức.",
|
||||
"Server Address": "Địa chỉ máy chủ",
|
||||
"Server IP": "IP máy chủ",
|
||||
@ -3604,7 +3580,7 @@
|
||||
"Set quota amount and limits": "Thiết lập hạn mức và giới hạn",
|
||||
"Set Request Header": "Đặt header yêu cầu",
|
||||
"Set runtime request header: override entire value, or manipulate comma-separated tokens": "Đặt header yêu cầu runtime: ghi đè toàn bộ giá trị hoặc thao tác token phân cách bằng dấu phẩy",
|
||||
"Set separate prices for cache reads and writes.": "Set separate prices for cache reads and writes.",
|
||||
"Set separate prices for cache reads and writes.": "Đặt giá riêng cho đọc và ghi bộ nhớ đệm.",
|
||||
"Set Tag": "Gán Thẻ",
|
||||
"Set tag for selected channels": "Đặt thẻ cho các kênh đã chọn",
|
||||
"Set the language used across the interface": "Đặt ngôn ngữ sử dụng trong giao diện",
|
||||
@ -3700,6 +3676,7 @@
|
||||
"Standard price": "Giá tiêu chuẩn",
|
||||
"Start": "Bắt đầu",
|
||||
"Start a conversation to see messages here": "Bắt đầu một cuộc trò chuyện để xem tin nhắn tại đây",
|
||||
"Start collecting payments globally without registering a company. Built for indie developers, OPC sole proprietorships, and startups. Waffo Pancake acts as your Merchant of Record, taking on the compliance burden of global payment collection — consumption tax, invoicing, subscription management, refunds, and chargebacks. Solo developers can launch fast and stay focused on product instead of compliance. Onboard in minutes — one prompt to a full integration.": "Bắt đầu thu thanh toán toàn cầu mà không cần đăng ký công ty. Dành cho lập trình viên độc lập, chủ sở hữu OPC và startup. Waffo Pancake đóng vai trò Merchant of Record, chịu trách nhiệm tuân thủ cho việc thu thanh toán toàn cầu — thuế tiêu dùng, hóa đơn, quản lý đăng ký, hoàn tiền và tranh chấp thanh toán. Lập trình viên cá nhân có thể ra mắt nhanh và tập trung vào sản phẩm thay vì tuân thủ. Onboard trong vài phút — từ một prompt đến tích hợp hoàn chỉnh.",
|
||||
"Start for free with generous limits. No credit card required.": "Bắt đầu miễn phí với giới hạn hào phóng. Không cần thẻ tín dụng.",
|
||||
"Start Time": "Thời gian bắt đầu",
|
||||
"Static page describing the platform.": "Trang tĩnh mô tả nền tảng.",
|
||||
@ -3720,6 +3697,8 @@
|
||||
"Step": "Bước",
|
||||
"Stop": "Dừng lại",
|
||||
"Stop Retry": "Dừng thử lại",
|
||||
"Store": "Store",
|
||||
"Store + product created": "Đã tạo cửa hàng + sản phẩm",
|
||||
"Store ID": "Mã cửa hàng",
|
||||
"Store ID is required": "Bắt buộc nhập Store ID",
|
||||
"Stored value is not echoed back for security": "Vì bảo mật, giá trị đã lưu không được hiển thị lại",
|
||||
@ -3753,8 +3732,9 @@
|
||||
"Subscription First": "Ưu tiên đăng ký",
|
||||
"Subscription Management": "Quản lý đăng ký",
|
||||
"Subscription Only": "Chỉ đăng ký",
|
||||
"Subscription plan creation and changes are locked until the administrator confirms compliance terms in Payment Gateway settings.": "Subscription plan creation and changes are locked until the administrator confirms compliance terms in Payment Gateway settings.",
|
||||
"Subscription plan creation and changes are locked until the administrator confirms compliance terms in Payment Gateway settings.": "Việc tạo và thay đổi gói đăng ký bị khóa cho đến khi quản trị viên xác nhận điều khoản tuân thủ trong cài đặt Cổng thanh toán.",
|
||||
"Subscription Plans": "Gói đăng ký",
|
||||
"Subscription plans do NOT use the bound Product — each plan has its own dedicated Pancake product, set in the Subscriptions admin (or auto-minted via the \"+ Create\" button there).": "Gói đăng ký KHÔNG dùng Sản phẩm đã liên kết — mỗi gói có một sản phẩm Pancake riêng, được đặt trong quản trị Đăng ký (hoặc tự động tạo bằng nút \"+ Create\" tại đó).",
|
||||
"Subtract": "Trừ",
|
||||
"Success": "Thành công",
|
||||
"Success rate": "Tỷ lệ thành công",
|
||||
@ -3879,8 +3859,11 @@
|
||||
"The administrator has not configured a user agreement yet.": "Quản trị viên chưa cấu hình thỏa thuận người dùng.",
|
||||
"The administrator has not configured any about content yet. You can set it in the settings page, supporting HTML or URL.": "Quản trị viên chưa cấu hình bất kỳ nội dung giới thiệu nào. Bạn có thể thiết lập nó trong trang cài đặt, hỗ trợ HTML hoặc URL.",
|
||||
"The binding will complete automatically after authorization": "Việc liên kết sẽ hoàn tất tự động sau khi ủy quyền",
|
||||
"The bound Product powers wallet top-ups: when a user enters any amount, new-api runs the checkout against this single Pancake product and overrides the price per session — no need to pre-create $1 / $5 / $10 SKUs.": "Sản phẩm đã liên kết dùng cho nạp ví: khi người dùng nhập bất kỳ số tiền nào, new-api chạy thanh toán trên một sản phẩm Pancake duy nhất này và ghi đè giá theo từng phiên — không cần tạo trước SKU $1 / $5 / $10.",
|
||||
"The bound Store is the parent container for every Pancake product new-api creates from this admin — both the wallet top-up product and any subscription-plan products. One store is enough; pin a different one only if you genuinely run separate Pancake catalogs.": "Cửa hàng đã liên kết là vùng chứa cha cho mọi sản phẩm Pancake mà new-api tạo từ trang quản trị này — bao gồm sản phẩm nạp ví và mọi sản phẩm gói đăng ký. Một cửa hàng là đủ; chỉ ghim cửa hàng khác nếu bạn thực sự vận hành các catalog Pancake riêng.",
|
||||
"The effective domain for Passkey registration. Must match the current domain or be its parent domain.": "Mi",
|
||||
"The entered text does not match the required text.": "The entered text does not match the required text.",
|
||||
"The entered text does not match the required text.": "Văn bản đã nhập không khớp với văn bản yêu cầu.",
|
||||
"The environment (test vs production) is decided by the key you paste here — use the Test key while integrating, then swap to the Production key when going live.": "Môi trường (test hay production) được quyết định bởi khóa bạn dán tại đây — dùng khóa Test khi tích hợp, sau đó đổi sang khóa Production khi chạy chính thức.",
|
||||
"The exact model identifier as used in API requests.": "Mã định danh mô hình chính xác như được sử dụng trong các yêu cầu API.",
|
||||
"The following models have billing type conflicts (fixed price vs ratio billing). Confirm to proceed with the changes.": "Các mô hình sau có xung đột loại thanh toán (giá cố định so với thanh toán theo tỷ lệ). Xác nhận để tiếp tục với các thay đổi.",
|
||||
"The following models in the model redirect have not been added to the \"Models\" list and may fail during invocation due to missing available models:": "Các mô hình sau trong chuyển hướng mô hình chưa được thêm vào danh sách \"Mô hình\" và có thể gọi thất bại do thiếu các mô hình có sẵn:",
|
||||
@ -3912,7 +3895,7 @@
|
||||
"This action will permanently remove 2FA protection from your account.": "Hành động này sẽ vĩnh viễn gỡ bỏ tính năng bảo vệ",
|
||||
"This channel is not an Ollama channel.": "Kênh này không phải là kênh Ollama.",
|
||||
"This channel type does not support fetching models": "Loại kênh này không hỗ trợ lấy mô hình",
|
||||
"This confirmation unlocks payment, redemption code, subscription plan, and invitation reward features. Please read the statements carefully.": "This confirmation unlocks payment, redemption code, subscription plan, and invitation reward features. Please read the statements carefully.",
|
||||
"This confirmation unlocks payment, redemption code, subscription plan, and invitation reward features. Please read the statements carefully.": "Xác nhận này mở khóa các tính năng thanh toán, mã đổi thưởng, gói đăng ký và phần thưởng mời. Vui lòng đọc kỹ các tuyên bố.",
|
||||
"This controls model request rate limiting. Web/API route throttling is configured by environment variables and may still return 429.": "Thiết lập này kiểm soát giới hạn tốc độ yêu cầu mô hình. Giới hạn tuyến Web/API được cấu hình bằng biến môi trường và vẫn có thể trả về 429.",
|
||||
"This data may be unreliable, use with caution": "Dữ liệu này có thể không đáng tin cậy, sử dụng thận trọng",
|
||||
"This device does not support Passkey": "Thiết bị này không hỗ trợ Passkey",
|
||||
@ -3931,7 +3914,7 @@
|
||||
"This project must be used in compliance with the": "Dự án này phải được sử dụng tuân thủ theo",
|
||||
"This record was written by a pre-upgrade instance and lacks audit info. Upgrade the instance to record server IP, callback IP, payment method and system version.": "Bản ghi này do bản cũ tạo và thiếu thông tin audit. Nâng cấp bản cài để lưu IP máy chủ, IP callback, hình thức thanh toán và phiên bản hệ thống.",
|
||||
"This site currently has {{count}} models enabled": "Trang này hiện đã bật {{count}} mô hình",
|
||||
"This tier catches any request that did not match earlier tiers.": "This tier catches any request that did not match earlier tiers.",
|
||||
"This tier catches any request that did not match earlier tiers.": "Tầng này bắt mọi yêu cầu không khớp với các tầng trước.",
|
||||
"this token group": "nhóm token này",
|
||||
"this user group": "nhóm người dùng này",
|
||||
"This user has no bindings": "Người dùng này không có liên kết nào",
|
||||
@ -3954,7 +3937,7 @@
|
||||
"Throughput short": "TPS",
|
||||
"Throughput trend": "Xu hướng thông lượng",
|
||||
"Tier": "Bậc",
|
||||
"Tier conditions": "Tier conditions",
|
||||
"Tier conditions": "Điều kiện tầng",
|
||||
"Tier name": "Tên bậc",
|
||||
"Tiered": "Nhiều bậc",
|
||||
"Tiered (billing expression)": "Nhiều bậc (công thức tính phí)",
|
||||
@ -4002,7 +3985,7 @@
|
||||
"Token price for cache reads.": "Giá token cho lượt đọc cache.",
|
||||
"Token price for creating cache entries.": "Giá token cho việc tạo mục cache.",
|
||||
"Token price for image input.": "Giá token cho đầu vào hình ảnh.",
|
||||
"Token prices": "Token prices",
|
||||
"Token prices": "Giá token",
|
||||
"Token regenerated and copied to clipboard": "Token đã được tạo lại và sao chép vào bộ nhớ tạm",
|
||||
"Token share by model author across the last 24 hours": "Tỷ lệ token theo nhà phát triển trong 24 giờ qua",
|
||||
"Token share by model author across the past few weeks": "Tỷ lệ token theo nhà phát triển trong vài tuần qua",
|
||||
@ -4114,7 +4097,7 @@
|
||||
"Type (common)": "Loại (phổ biến)",
|
||||
"Type *": "Nhập *",
|
||||
"Type a command or search...": "Nhập lệnh hoặc tìm kiếm...",
|
||||
"Type the confirmation text here": "Type the confirmation text here",
|
||||
"Type the confirmation text here": "Nhập văn bản xác nhận tại đây",
|
||||
"Type-Specific Settings": "Cài đặt theo loại",
|
||||
"Type:": "Loại:",
|
||||
"UI granularity only — data is still aggregated hourly": "Chỉ là độ chi tiết UI — dữ liệu vẫn được tổng hợp theo giờ",
|
||||
@ -4239,6 +4222,7 @@
|
||||
"used": "đã sử dụng, cũ",
|
||||
"Used": "Đã sử dụng",
|
||||
"Used / Remaining": "Đã dùng / Còn lại",
|
||||
"Used as SuccessURL on the new product. You'll be prompted to confirm if left blank.": "Được dùng làm SuccessURL trên sản phẩm mới. Bạn sẽ được yêu cầu xác nhận nếu để trống.",
|
||||
"Used for load balancing. Higher weight = more requests": "Được sử dụng để cân bằng tải. Trọng số càng cao = càng nhiều yêu cầu",
|
||||
"Used in URLs and API routes": "Sử dụng trong URL và các tuyến API",
|
||||
"Used Quota": "Hạn mức đã sử dụng",
|
||||
@ -4313,6 +4297,7 @@
|
||||
"Verify routing with Playground or your client": "Xác minh định tuyến bằng Playground hoặc client của bạn",
|
||||
"Verify Setup": "Xác minh thiết lập",
|
||||
"Verify your database connection": "Xác minh kết nối cơ sở dữ liệu của bạn",
|
||||
"Verifying credentials and pulling stores from your Pancake account...": "Đang xác minh thông tin xác thực và lấy cửa hàng từ tài khoản Pancake của bạn...",
|
||||
"Version Overrides": "Ghi đè phiên bản",
|
||||
"Vertex AI": "Vertex AI",
|
||||
"Vertex AI does not support functionResponse.id. Enable this to remove the field automatically.": "Vertex AI không hỗ trợ functionResponse.id. Bật để tự động loại bỏ trường này.",
|
||||
@ -4323,16 +4308,11 @@
|
||||
"Vidu": "Vidu",
|
||||
"View": "Xem",
|
||||
"View all currently available models": "Xem tất cả mô hình hiện có",
|
||||
"View and manage your API usage logs": "Xem và quản lý nhật ký sử dụng API của bạn",
|
||||
"View and manage your drawing logs": "Xem và quản lý nhật ký vẽ của bạn",
|
||||
"View and manage your task logs": "Xem và quản lý nhật ký tác vụ của bạn",
|
||||
"View dashboard overview and statistics": "Xem tổng quan và thống kê bảng điều khiển",
|
||||
"View detailed information about this user including balance, usage statistics, and invitation details.": "Xem thông tin chi tiết về người dùng này bao gồm số dư, thống kê sử dụng và chi tiết lời mời.",
|
||||
"View details": "Xem chi tiết",
|
||||
"View document": "Xem tài liệu",
|
||||
"View logs": "Xem nhật ký",
|
||||
"View mode": "Chế độ xem",
|
||||
"View model call count analytics and charts": "Xem phân tích và biểu đồ số lượt gọi mô hình",
|
||||
"View model statistics and charts": "Xem thống kê và biểu đồ mô hình",
|
||||
"View Pricing": "View price",
|
||||
"View the complete details for this": "Xem chi tiết đầy đủ của",
|
||||
@ -4340,7 +4320,6 @@
|
||||
"View the complete error message and details": "Xem toàn bộ thông báo lỗi và chi tiết",
|
||||
"View the complete prompt and its English translation": "Xem toàn bộ lời nhắc và bản dịch tiếng Anh",
|
||||
"View the generated image": "Xem ảnh đã tạo",
|
||||
"View user consumption statistics and charts": "Xem thống kê và biểu đồ tiêu thụ",
|
||||
"View your topup transaction records and payment history": "Xem lịch sử giao dịch nạp tiền và lịch sử thanh toán của bạn",
|
||||
"Violation Code": "Mã vi phạm",
|
||||
"Violation deduction amount": "Số tiền trừ vi phạm",
|
||||
@ -4362,7 +4341,14 @@
|
||||
"Visual Parameter Override": "Ghi đè tham số trực quan",
|
||||
"VolcEngine": "VolcEngine",
|
||||
"vs. previous": "so với kỳ trước",
|
||||
"Waffo Aggregator Gateway": "Cổng tổng hợp Waffo",
|
||||
"Waffo Pancake Dashboard": "Waffo Pancake Dashboard",
|
||||
"Waffo Pancake MoR": "Waffo Pancake MoR",
|
||||
"Waffo Pancake Payment Gateway": "Cổng thanh toán Waffo Pancake",
|
||||
"Waffo Pancake product created": "Đã tạo sản phẩm Waffo Pancake",
|
||||
"Waffo Pancake product creation failed": "Tạo sản phẩm Waffo Pancake thất bại",
|
||||
"Waffo Pancake save failed": "Lưu Waffo Pancake thất bại",
|
||||
"Waffo Pancake settings saved": "Đã lưu cài đặt Waffo Pancake",
|
||||
"Waffo Payment": "Thanh toán Waffo",
|
||||
"Waffo Payment Gateway": "Cổng thanh toán Waffo",
|
||||
"Waffo Public Key (Production)": "Waffo Public Key (Sản xuất)",
|
||||
@ -4393,6 +4379,8 @@
|
||||
"Webhook Secret": "Bí mật Webhook",
|
||||
"Webhook signing secret (leave blank unless updating)": "Mã bí mật ký webhook (để trống trừ khi cập nhật)",
|
||||
"Webhook URL": "URL Webhook",
|
||||
"Webhook URL (Production):": "Webhook URL (Production):",
|
||||
"Webhook URL (Test):": "Webhook URL (Test):",
|
||||
"Webhook URL:": "URL Webhook:",
|
||||
"Website is under maintenance!": "Website đang bảo trì!",
|
||||
"WeChat": "WeChat",
|
||||
@ -4432,6 +4420,7 @@
|
||||
"Whitelist (Only allow listed domains)": "Danh sách trắng (Chỉ cho phép các tên miền được liệt kê)",
|
||||
"Whitelist (Only allow listed IPs)": "Danh sách trắng (Chỉ cho phép các IP đã liệt kê)",
|
||||
"whsec_xxx": "whsec_xxx",
|
||||
"Why only one store + product?": "Tại sao chỉ một cửa hàng + sản phẩm?",
|
||||
"Window:": "Cửa sổ:",
|
||||
"Wire encoding for the embedding vectors": "Định dạng truyền cho vector embedding",
|
||||
"with conflicts": "với các xung đột",
|
||||
@ -4454,16 +4443,16 @@
|
||||
"You can close this tab once the binding completes or a success message appears in the original window.": "Bạn có thể đóng tab này sau khi quá trình liên kết hoàn tất hoặc thông báo thành công xuất hiện trong cửa sổ gốc.",
|
||||
"You can manually add them in \"Custom Model Names\", click \"Fill\" and then submit, or use the operations below to handle automatically.": "Bạn có thể thêm chúng theo cách thủ công trong \"Tên mô hình tùy chỉnh\", nhấp vào \"Điền\" rồi gửi, hoặc sử dụng các thao tác bên dưới để xử lý tự động.",
|
||||
"You can only check in once per day": "Bạn chỉ có thể điểm danh một lần mỗi ngày",
|
||||
"You commit not to use this system to implement, assist with, or indirectly implement acts that violate applicable laws and regulations, regulatory requirements, platform rules, public interests, or the lawful rights and interests of third parties.": "You commit not to use this system to implement, assist with, or indirectly implement acts that violate applicable laws and regulations, regulatory requirements, platform rules, public interests, or the lawful rights and interests of third parties.",
|
||||
"You commit to using upstream APIs, accounts, keys, quotas, and service capabilities only within the scope of lawful authorization obtained from upstream service providers, model service providers, or relevant rights holders, and will not conduct unauthorized resale, trafficking, distribution, or other non-compliant commercialization.": "You commit to using upstream APIs, accounts, keys, quotas, and service capabilities only within the scope of lawful authorization obtained from upstream service providers, model service providers, or relevant rights holders, and will not conduct unauthorized resale, trafficking, distribution, or other non-compliant commercialization.",
|
||||
"You commit not to use this system to implement, assist with, or indirectly implement acts that violate applicable laws and regulations, regulatory requirements, platform rules, public interests, or the lawful rights and interests of third parties.": "Bạn cam kết không sử dụng hệ thống này để thực hiện, hỗ trợ hoặc gián tiếp thực hiện các hành vi vi phạm luật và quy định hiện hành, yêu cầu quản lý, quy tắc nền tảng, lợi ích công cộng hoặc quyền và lợi ích hợp pháp của bên thứ ba.",
|
||||
"You commit to using upstream APIs, accounts, keys, quotas, and service capabilities only within the scope of lawful authorization obtained from upstream service providers, model service providers, or relevant rights holders, and will not conduct unauthorized resale, trafficking, distribution, or other non-compliant commercialization.": "Bạn cam kết chỉ sử dụng API upstream, tài khoản, khóa, hạn mức và năng lực dịch vụ trong phạm vi ủy quyền hợp pháp nhận được từ nhà cung cấp dịch vụ upstream, nhà cung cấp mô hình hoặc chủ thể quyền liên quan, và sẽ không thực hiện bán lại, giao dịch, phân phối trái phép hoặc thương mại hóa không tuân thủ khác.",
|
||||
"You don't have necessary permission": "Bạn không có quyền cần thiết",
|
||||
"You have legally obtained authorization for the connected model APIs, accounts, keys, and quotas.": "You have legally obtained authorization for the connected model APIs, accounts, keys, and quotas.",
|
||||
"You have legally obtained authorization for the connected model APIs, accounts, keys, and quotas.": "Bạn đã nhận được ủy quyền hợp pháp cho API mô hình, tài khoản, khóa và hạn mức được kết nối.",
|
||||
"You have unsaved changes": "Bạn có thay đổi chưa được lưu",
|
||||
"You have unsaved changes. Are you sure you want to leave?": "Bạn có thay đổi chưa được lưu. Bạn có chắc chắn muốn rời đi không?",
|
||||
"You Pay": "Bạn thanh toán",
|
||||
"You save": "Bạn tiết kiệm",
|
||||
"You understand and independently bear legal responsibility arising from deployment, operation, and charging behavior.": "You understand and independently bear legal responsibility arising from deployment, operation, and charging behavior.",
|
||||
"You understand this compliance reminder is only for risk notice and does not constitute legal advice, a compliance review conclusion, or a guarantee of the legality of your use of this system; you should consult professional legal or compliance advisors based on your actual business scenario.": "You understand this compliance reminder is only for risk notice and does not constitute legal advice, a compliance review conclusion, or a guarantee of the legality of your use of this system; you should consult professional legal or compliance advisors based on your actual business scenario.",
|
||||
"You understand and independently bear legal responsibility arising from deployment, operation, and charging behavior.": "Bạn hiểu và tự chịu trách nhiệm pháp lý phát sinh từ việc triển khai, vận hành và thu phí.",
|
||||
"You understand this compliance reminder is only for risk notice and does not constitute legal advice, a compliance review conclusion, or a guarantee of the legality of your use of this system; you should consult professional legal or compliance advisors based on your actual business scenario.": "Bạn hiểu rằng nhắc nhở tuân thủ này chỉ là thông báo rủi ro, không cấu thành tư vấn pháp lý, kết luận rà soát tuân thủ hoặc bảo đảm tính hợp pháp của việc sử dụng hệ thống; bạn nên tham khảo cố vấn pháp lý hoặc tuân thủ chuyên nghiệp dựa trên tình huống kinh doanh thực tế.",
|
||||
"You will be redirected to Telegram to complete the binding process.": "Bạn sẽ được chuyển hướng đến Telegram để hoàn tất quá trình liên kết.",
|
||||
"You'll be redirected automatically. You can return to the previous page if nothing happens after a few seconds.": "Bạn sẽ được chuyển hướng tự động. Bạn có thể quay lại trang trước nếu không có gì xảy ra sau vài giây.",
|
||||
"your AI integration?": "tích hợp AI của bạn?",
|
||||
|
||||
172
web/default/src/i18n/locales/zh.json
vendored
172
web/default/src/i18n/locales/zh.json
vendored
@ -120,7 +120,7 @@
|
||||
"Account ID *": "账户 ID *",
|
||||
"Account Info": "账户信息",
|
||||
"Account used when authenticating with the SMTP server": "用于与 SMTP 服务器进行身份验证的账户",
|
||||
"acknowledge the related legal risks": "知悉相关法律风险",
|
||||
"acknowledge the related legal risks": "确认相关法律风险",
|
||||
"Across all groups": "跨所有分组",
|
||||
"Action confirmation": "操作确认",
|
||||
"Actions": "操作",
|
||||
@ -247,7 +247,7 @@
|
||||
"Alipay": "支付宝",
|
||||
"All": "全部",
|
||||
"All categories": "全部分类",
|
||||
"All conditions must match before this tier is used.": "所有条件都满足后才会使用该档位。",
|
||||
"All conditions must match before this tier is used.": "所有条件都匹配后才会使用此阶梯。",
|
||||
"All edits are overwrite operations. Leave fields empty to keep current values unchanged.": "所有编辑都是覆盖操作。留空字段将保持当前值不变。",
|
||||
"All files exceed the maximum size.": "所有文件都超过最大尺寸。",
|
||||
"All Groups": "所有分组",
|
||||
@ -449,7 +449,6 @@
|
||||
"Automatically replaces upstream callback URLs with the server address.": "自动将上游回调 URL 替换为服务器地址。",
|
||||
"Automatically selects the best available group with circuit breaker mechanism": "自动选择可用分组,失败时触发熔断切换",
|
||||
"Automatically sync model list when upstream changes are detected": "检测到上游模型变更时自动同步模型列表",
|
||||
"Automatically test channels and notify users when limits are hit": "自动测试渠道并在达到限制时通知用户",
|
||||
"Availability (last 24h)": "可用率(最近 24 小时)",
|
||||
"Available": "可用",
|
||||
"Available disk space": "可用磁盘空间",
|
||||
@ -494,7 +493,7 @@
|
||||
"Bark Push URL": "Bark 推送 URL",
|
||||
"Base address provided by your Epay service": "您的 Epay 服务提供的基础地址",
|
||||
"Base amount. Actual deduction = base amount × system group rate.": "基础金额,实际扣费 = 基础金额 × 系统分组倍率。",
|
||||
"Base input and output token prices for this tier.": "该档位的基础输入与输出 token 价格。",
|
||||
"Base input and output token prices for this tier.": "此阶梯的基础输入和输出 token 价格。",
|
||||
"Base input price only": "仅基础输入价格",
|
||||
"Base Limits": "基础额度",
|
||||
"Base multipliers applied when users select specific groups.": "当用户选择特定分组时应用的基础乘数。",
|
||||
@ -531,7 +530,7 @@
|
||||
"Billing Process": "计费过程",
|
||||
"Billing Source": "计费来源",
|
||||
"Bind": "绑定",
|
||||
"Bind a Pancake store + product": "绑定 Pancake 店铺 + 商品",
|
||||
"Bind a Pancake store + product": "绑定 Pancake 店铺和产品",
|
||||
"Bind an email address to your account.": "将邮箱地址绑定到您的账户。",
|
||||
"Bind Email": "绑定邮箱",
|
||||
"Bind Telegram Account": "绑定 Telegram 账户",
|
||||
@ -561,8 +560,6 @@
|
||||
"Bound product:": "已绑定产品:",
|
||||
"Bound store:": "已绑定店铺:",
|
||||
"Bring channels back online after successful checks": "检查成功后使渠道恢复在线",
|
||||
"Broadcast a global banner to users. Markdown is supported.": "向用户广播全局横幅。支持 Markdown。",
|
||||
"Broadcast short system notices on the dashboard": "在仪表板上广播简短的系统通知",
|
||||
"Browse and compare": "浏览和比较",
|
||||
"Browse available models and pricing": "浏览可用模型和价格",
|
||||
"Browse rankings by category": "按行业浏览排行",
|
||||
@ -589,7 +586,7 @@
|
||||
"Cache Directory Info": "缓存目录信息",
|
||||
"Cache Entries": "缓存条目",
|
||||
"Cache mode": "缓存模式",
|
||||
"Cache pricing": "缓存价格",
|
||||
"Cache pricing": "缓存定价",
|
||||
"Cache ratio": "缓存倍率",
|
||||
"Cache Read": "缓存读取",
|
||||
"Cache read price": "缓存读取价格",
|
||||
@ -800,9 +797,9 @@
|
||||
"Completion price": "补全价格",
|
||||
"Completion price ($/1M tokens)": "完成价格(美元/百万令牌)",
|
||||
"Completion ratio": "补全倍率",
|
||||
"Compliance confirmation required": "需要确认合规声明",
|
||||
"Compliance confirmed": "合规声明已确认",
|
||||
"Compliance confirmed successfully": "合规声明确认成功",
|
||||
"Compliance confirmation required": "需要确认合规条款",
|
||||
"Compliance confirmed": "合规已确认",
|
||||
"Compliance confirmed successfully": "合规确认成功",
|
||||
"Concatenate channel system prompt with user's prompt": "将渠道系统提示与用户的提示连接起来",
|
||||
"Condition Path": "条件路径",
|
||||
"Condition Settings": "条件项设置",
|
||||
@ -828,43 +825,21 @@
|
||||
"Configure API documentation links for the dashboard": "配置仪表板的 API 文档链接",
|
||||
"Configure at:": "配置位置:",
|
||||
"Configure available payment methods. Provide a JSON array.": "配置可用的支付方式。提供一个 JSON 数组。",
|
||||
"Configure basic system information and branding": "配置基本系统信息和品牌",
|
||||
"Configure channel affinity (sticky routing) rules": "配置渠道亲和性(粘滞选路)规则",
|
||||
"Configure Creem products. Provide a JSON array.": "配置 Creem 产品。提供 JSON 数组。",
|
||||
"Configure currency conversion and quota display options": "配置货币换算和额度展示选项",
|
||||
"Configure custom OAuth providers for user authentication": "配置自定义OAuth提供商用于用户认证",
|
||||
"Configure daily check-in rewards for users": "配置用户每日签到奖励",
|
||||
"Configure discount rates based on recharge amounts": "配置基于充值金额的折扣率",
|
||||
"Configure experimental data export for the dashboard": "配置仪表板的实验性数据导出",
|
||||
"Configure Gemini safety behavior, version overrides, and thinking adapter": "配置 Gemini 安全行为、版本覆盖和思维适配器",
|
||||
"Configure group ratios and group-specific pricing rules": "配置分组倍率和分组专属定价规则",
|
||||
"Configure in your Creem dashboard": "在您的 Creem 仪表板中配置",
|
||||
"Configure io.net API key for model deployments": "配置 io.net API Key 用于模型部署",
|
||||
"Configure keyword filtering for prompts and responses.": "配置用于提示和响应的关键词过滤。",
|
||||
"Configure model deployment provider settings": "配置模型部署提供商设置",
|
||||
"Configure model pricing ratios and tool prices": "配置模型定价倍率和工具价格",
|
||||
"Configure model, caching, and group ratios used for billing": "配置用于计费的模型、缓存和分组比例",
|
||||
"Configure monitoring status page groups for the dashboard": "配置用于仪表板的监控状态页面分组",
|
||||
"Configure outgoing email server for notifications": "配置用于通知的发送邮件服务器",
|
||||
"Configure Passkey (WebAuthn) login settings": "配置 Passkey (WebAuthn) 登录设置",
|
||||
"Configure password-based login and registration": "配置基于密码的登录和注册",
|
||||
"Configure per-model ratio for image inputs or outputs.": "配置图像输入或输出的每模型比例。",
|
||||
"Configure per-tool unit prices ($/1K calls). Per-request models do not incur additional tool fees.": "为每个工具配置单价($/1K 次调用)。按请求计费的模型不额外收取工具费用。",
|
||||
"Configure predefined chat links surfaced to end users.": "配置向终端用户展示的预定义聊天链接。",
|
||||
"Configure pricing model and display options": "配置定价模型和显示选项",
|
||||
"Configure pricing ratios for a specific model.": "配置特定模型的定价比例。",
|
||||
"Configure rate limiting rules for a specific user group.": "配置特定用户分组的速率限制规则。",
|
||||
"Configure recharge pricing and payment gateway integrations": "配置充值定价和支付网关集成",
|
||||
"Configure system-wide behavior and defaults": "配置系统范围的行为和默认设置",
|
||||
"Configure the ratio for this group.": "配置此分组的比例。",
|
||||
"Configure third-party authentication providers": "配置第三方身份验证提供商",
|
||||
"Configure upstream providers and routing.": "配置上游提供者和路由。",
|
||||
"Configure upstream worker or proxy service for outbound requests": "配置出站请求的上游工作程序或代理服务",
|
||||
"Configure user quota allocation and rewards": "配置用户额度分配和奖励",
|
||||
"Configure Waffo Pancake hosted checkout integration for USD-priced top-ups": "配置 Waffo Pancake 托管结账,用于美元计价的充值",
|
||||
"Configure Waffo payment aggregation platform integration": "配置 Waffo 支付聚合平台集成",
|
||||
"Configure xAI Grok model settings": "配置 xAI Grok 模型设置",
|
||||
"Configure xAI Grok model specific settings": "配置 xAI Grok 模型特定设置",
|
||||
"Configure your account behavior preferences": "配置您的账户行为偏好",
|
||||
"Configure your account preferences and integrations": "配置您的账户偏好和集成",
|
||||
"Configured routes and latency checks": "已配置路由和延迟检测",
|
||||
@ -878,8 +853,8 @@
|
||||
"Confirm cleanup of inactive disk cache?": "确认清理不活跃的磁盘缓存?",
|
||||
"Confirm clearing all channel affinity cache": "确认清空全部渠道亲和性缓存",
|
||||
"Confirm clearing cache for this rule": "确认清空该规则缓存",
|
||||
"Confirm compliance": "确认合规声明",
|
||||
"Confirm compliance terms": "确认合规声明",
|
||||
"Confirm compliance": "确认合规",
|
||||
"Confirm compliance terms": "确认合规条款",
|
||||
"Confirm Creem Purchase": "确认 Creem 购买",
|
||||
"Confirm delete": "确认删除",
|
||||
"Confirm disable": "确认禁用",
|
||||
@ -894,11 +869,11 @@
|
||||
"auth.resetPasswordConfirm.description": "确认重置请求以生成新密码。",
|
||||
"Confirm Selection": "确认选择",
|
||||
"Confirm settings and finish setup": "确认设置并完成安装",
|
||||
"confirm that I bear legal responsibility arising from deployment": "并确认自行承担部署",
|
||||
"confirm that I bear legal responsibility arising from deployment": "确认我承担因部署产生的法律责任",
|
||||
"Confirm Unbind": "确认解绑",
|
||||
"Confirm your identity before removing this Passkey from your account.": "在解绑当前账号的 Passkey 前请确认你的身份。",
|
||||
"Confirm your identity with Two-factor Authentication before registering a Passkey.": "在注册 Passkey 前请使用两步验证确认你的身份。",
|
||||
"Confirmed at {{time}} by user #{{userId}}": "确认时间:{{time}},确认用户:#{{userId}}",
|
||||
"Confirmed at {{time}} by user #{{userId}}": "用户 #{{userId}} 于 {{time}} 确认",
|
||||
"Conflict": "矛盾",
|
||||
"Connect": "连接",
|
||||
"Connect through OpenAI, Claude, Gemini, and other compatible API routes": "通过 OpenAI、Claude、Gemini 以及其他兼容 API 路由接入",
|
||||
@ -932,11 +907,7 @@
|
||||
"Continue with Telegram": "使用 Telegram 继续",
|
||||
"Continue with WeChat": "使用 微信 继续",
|
||||
"Contract review, compliance, summarisation": "合同审阅、合规与摘要",
|
||||
"Control log retention and clean historical data.": "控制日志保留期限并清理历史数据。",
|
||||
"Control passthrough behavior and connection keep-alive settings": "控制透传行为和连接保持活动设置",
|
||||
"Control request frequency to prevent abuse and manage system load.": "控制请求频率以防止滥用和管理系统负载。",
|
||||
"Control which models are exposed and which groups may use them.": "控制对外暴露的模型,以及哪些分组可以使用它们。",
|
||||
"Control which sidebar areas and modules are available to all users.": "控制哪些侧边栏区域和模块对所有用户可用。",
|
||||
"Controls how much the model thinks before answering": "控制模型回答前的推理深度",
|
||||
"Controls whether user verification (biometrics/PIN) is required during Passkey flows.": "控制在通行密钥流程中是否需要用户验证(生物识别/PIN)。",
|
||||
"Conversion rate from USD to your custom currency": "从美元到您的自定义货币的转换率",
|
||||
@ -984,7 +955,7 @@
|
||||
"Core concepts": "核心概念",
|
||||
"Core Configuration": "核心配置",
|
||||
"Core Features": "核心功能",
|
||||
"Core pricing": "核心价格",
|
||||
"Core pricing": "核心定价",
|
||||
"Cost": "费用",
|
||||
"Cost in USD per request, regardless of tokens used.": "每请求的美元费用,不考虑使用的令牌数。",
|
||||
"Cost Tracking": "成本跟踪",
|
||||
@ -1032,7 +1003,7 @@
|
||||
"Credential refreshed": "凭据已刷新",
|
||||
"Credentials": "凭证",
|
||||
"Credentials verification failed": "凭证验证失败",
|
||||
"Credentials verification failed — double-check Merchant ID and API private key.": "凭证验证失败——请仔细核对商户 ID 和 API 私钥。",
|
||||
"Credentials verification failed — double-check Merchant ID and API private key.": "凭证验证失败,请检查 Merchant ID 和 API 私钥。",
|
||||
"Credit remaining": "剩余额度",
|
||||
"Creem API key (leave blank unless updating)": "Creem API 密钥(除非更新,否则留空)",
|
||||
"Creem Gateway": "Creem 网关",
|
||||
@ -1041,7 +1012,6 @@
|
||||
"Creem products must be a JSON array": "Creem 产品必须是 JSON 数组",
|
||||
"Cross-group": "跨分组",
|
||||
"Cross-group retry": "跨分组重试",
|
||||
"Curate quick links to your different Domains": "整理到不同域的快速链接",
|
||||
"Currency": "货币",
|
||||
"Currency & Display": "货币与展示",
|
||||
"Current Balance": "当前余额",
|
||||
@ -1329,7 +1299,7 @@
|
||||
"Each line represents one keyword. Leave blank to disable the list but keep the switch states.": "每行代表一个关键词。留空以禁用列表,但保留开关状态。",
|
||||
"Each tier supports 0~2 conditions (over len, p, c); the last tier is the catch-all without conditions. Use len (full input length, including cache hits) for tier conditions to avoid mis-routing when cache hits reduce p.": "每个档位支持 0~2 个条件(针对 len、p、c),最后一档为兜底档无需条件。建议条件使用 len(完整输入长度,含缓存命中),避免缓存命中降低 p 导致档位误判。",
|
||||
"Each tier supports up to 2 conditions; the last tier is the catch-all without conditions. Use full input length for tier conditions to avoid mis-routing when cache hits reduce billable input tokens.": "每个档位最多支持 2 个条件;最后一个档位是不带条件的兜底档。建议使用完整输入长度作为档位条件,避免缓存命中减少计费输入 token 后误判档位。",
|
||||
"Each tier supports up to 2 conditions. The last tier without conditions is the fallback.": "每个档位最多 2 个条件,最后一个无条件档位为兜底档。",
|
||||
"Each tier supports up to 2 conditions. The last tier without conditions is the fallback.": "每个阶梯最多支持 2 个条件。最后一个无条件阶梯作为兜底。",
|
||||
"Earn rewards when your referrals add funds. Transfer accumulated rewards to your balance anytime.": "当您的推荐人充值时即可获得奖励。随时将累计奖励转移到您的余额。",
|
||||
"Edit": "编辑",
|
||||
"Edit {{title}}": "编辑{{title}}",
|
||||
@ -1395,7 +1365,6 @@
|
||||
"Enable OIDC": "启用 OIDC",
|
||||
"Enable or disable this channel": "启用或禁用此渠道",
|
||||
"Enable or disable this model": "启用或禁用此模型",
|
||||
"Enable or disable top navigation modules globally.": "全局启用或禁用顶部导航模块。",
|
||||
"Enable Passkey": "启用 Passkey",
|
||||
"Enable Performance Monitoring": "启用性能监控",
|
||||
"Enable rate limiting": "启用速率限制",
|
||||
@ -1549,7 +1518,6 @@
|
||||
"Expired at": "过期于",
|
||||
"Expired time cannot be earlier than current time": "过期时间不能早于当前时间",
|
||||
"Expires": "过期",
|
||||
"Expose grouped Uptime Kuma status pages directly on the dashboard": "直接在仪表板上显示分组的 Uptime Kuma 状态页面",
|
||||
"Expose ratio API": "暴露倍率接口",
|
||||
"Exposes the pricing/models catalog in the top navigation.": "在顶部导航中显示定价/模型目录。",
|
||||
"Expression": "表达式",
|
||||
@ -1582,7 +1550,7 @@
|
||||
"Failed to clean logs": "清理日志失败",
|
||||
"Failed to complete order": "完成订单失败",
|
||||
"Failed to complete Passkey login": "无法完成 Passkey 登录",
|
||||
"Failed to confirm compliance": "确认失败",
|
||||
"Failed to confirm compliance": "确认合规失败",
|
||||
"Failed to contact GitHub releases API": "无法连接 GitHub Releases API",
|
||||
"Failed to copy": "复制失败",
|
||||
"Failed to copy channel": "复制渠道失败",
|
||||
@ -1695,7 +1663,7 @@
|
||||
"Failed to update user": "更新用户失败",
|
||||
"Failure keywords": "失败关键词",
|
||||
"Fair": "公平",
|
||||
"Fallback tier": "兜底档位",
|
||||
"Fallback tier": "兜底阶梯",
|
||||
"FAQ": "常见问答",
|
||||
"FAQ added. Click \"Save Settings\" to apply.": "FAQ 已添加。点击 \"保存设置\" 以应用。",
|
||||
"FAQ deleted. Click \"Save Settings\" to apply.": "FAQ 已删除。点击 \"保存设置\" 以应用。",
|
||||
@ -1724,7 +1692,7 @@
|
||||
"Fill Codex CLI / Claude CLI Templates": "填充 Codex CLI / Claude CLI 模板",
|
||||
"Fill example (all channels)": "填充示例(全部频道)",
|
||||
"Fill example (specific channels)": "填充示例(指定频道)",
|
||||
"Fill in both Merchant ID and API Private Key before creating.": "请先填写商户 ID 和 API 私钥再进行创建。",
|
||||
"Fill in both Merchant ID and API Private Key before creating.": "创建前请填写 Merchant ID 和 API 私钥。",
|
||||
"Fill in the credentials above to begin.": "请先填写上方凭证。",
|
||||
"Fill in the following info to create a new subscription plan": "填写以下信息创建新的订阅套餐",
|
||||
"Fill Related Models": "填入相关模型",
|
||||
@ -1760,7 +1728,6 @@
|
||||
"Final cost = base × multiplier when conditions match": "匹配条件时,最终费用 = 基础费用 × 倍率",
|
||||
"Final price multiplier (0.95 = 5% discount": "最终价格乘数 (0.95 = 5% 折扣",
|
||||
"Finance": "金融",
|
||||
"Fine-tune Midjourney integration and guardrails.": "微调 Midjourney 集成和防护栏。",
|
||||
"Finish Time": "完成时间",
|
||||
"First API request": "首个 API 请求",
|
||||
"First/Last Frame to Video": "首尾生视频",
|
||||
@ -1998,7 +1965,7 @@
|
||||
"I confirm enabling high-risk retry": "我确认开启高危重试",
|
||||
"I have read and agree to the": "我已阅读并同意",
|
||||
"I have read and understood the above compliance reminder": "我已阅读并理解上述合规提醒",
|
||||
"I have read and understood the above compliance reminder, acknowledge the related legal risks, and confirm that I bear legal responsibility arising from deployment, operation, and charging behavior.": "我已阅读并理解上述合规提醒,知悉相关法律风险,并确认自行承担部署、运营和收费行为产生的法律责任",
|
||||
"I have read and understood the above compliance reminder, acknowledge the related legal risks, and confirm that I bear legal responsibility arising from deployment, operation, and charging behavior.": "我已阅读并理解上述合规提醒,确认相关法律风险,并确认承担因部署、运营和收费行为产生的法律责任。",
|
||||
"I understand that disabling 2FA will remove all protection and backup codes": "我理解禁用 2FA 将移除所有保护和备份代码",
|
||||
"Icon": "图标",
|
||||
"Icon file must be 100 KB or smaller": "图标文件必须小于等于 100 KB",
|
||||
@ -2010,7 +1977,7 @@
|
||||
"If default auto group is enabled, newly created tokens start with auto instead of an empty group.": "如果启用默认 auto 分组,新建令牌会默认使用 auto,而不是空分组。",
|
||||
"If the affinity channel fails and retry succeeds on another channel, update affinity to the successful channel.": "如果亲和到的渠道失败,重试到其他渠道成功后,将亲和更新到成功的渠道。",
|
||||
"If this keeps happening, please report it on GitHub Issues.": "如果问题持续出现,请到 GitHub Issues 反馈。",
|
||||
"If you provide generative AI services to the public in mainland China, you will fulfill legal obligations including filing, security assessment, content safety, complaint handling, generated content labeling, log retention, and personal information protection.": "如向中华人民共和国境内公众提供生成式人工智能服务,你将依法履行备案登记、安全评估、内容安全、投诉举报、生成合成内容标识、日志留存、个人信息保护等合规义务。",
|
||||
"If you provide generative AI services to the public in mainland China, you will fulfill legal obligations including filing, security assessment, content safety, complaint handling, generated content labeling, log retention, and personal information protection.": "如果你在中国大陆向公众提供生成式人工智能服务,你将履行备案、安全评估、内容安全、投诉处理、生成内容标识、日志留存和个人信息保护等法律义务。",
|
||||
"Ignored upstream models": "已忽略上游模型",
|
||||
"Image": "图片",
|
||||
"Image Generation": "图片生成",
|
||||
@ -2237,30 +2204,21 @@
|
||||
"Low balance": "余额偏低",
|
||||
"Lowest median first-token latency": "最低首 token 延迟中位数",
|
||||
"m": "分钟",
|
||||
"Maintain a list of common questions for the dashboard help panel": "维护仪表板帮助面板的常见问题列表",
|
||||
"Maintenance": "维护",
|
||||
"Make it easier for teammates to pick the right group.": "让队友更容易选择正确的分组。",
|
||||
"Manage": "管理",
|
||||
"Manage account bindings for this user": "管理此用户的账户绑定",
|
||||
"Manage API channels and provider configurations": "管理 API 渠道和提供商配置",
|
||||
"Manage Bindings": "管理绑定",
|
||||
"Manage catalog visibility and pricing.": "管理目录可见性和定价。",
|
||||
"Manage custom OAuth providers for user authentication": "管理用于用户认证的自定义 OAuth 提供商",
|
||||
"Manage Keys": "管理密钥",
|
||||
"Manage local models for:": "管理本地模型:",
|
||||
"Manage model deployments": "管理模型部署",
|
||||
"Manage model metadata and configuration": "管理模型元信息和配置",
|
||||
"Manage multi-key status and configuration for this channel": "管理此渠道的多密钥状态和配置",
|
||||
"Manage Ollama Models": "管理 Ollama 模型",
|
||||
"Manage redemption codes for quota top-up": "管理用于配额充值的兑换码",
|
||||
"Manage server log files. Log files accumulate over time; regular cleanup is recommended to free disk space.": "管理服务器运行日志文件。日志文件会随运行时间不断累积,建议定期清理以释放磁盘空间。",
|
||||
"Manage subscription plan creation, pricing and status": "管理订阅套餐的创建、定价和启停",
|
||||
"Manage subscription plans and pricing.": "管理订阅计划和定价。",
|
||||
"Manage Subscriptions": "管理订阅",
|
||||
"Manage users and their permissions": "管理用户及其权限",
|
||||
"Manage Vendors": "管理供应商",
|
||||
"Manage your API keys for accessing the service": "管理您用于访问服务的 API 密钥",
|
||||
"Manage your balance and payment methods": "管理您的余额和付款方式",
|
||||
"Manage your security settings and account access": "管理您的安全设置和账户访问",
|
||||
"Manual Disabled": "手动禁用",
|
||||
"Map fields from the user info response to local user attributes. Supports nested paths (e.g. ocs.data.id).": "将用户信息响应中的字段映射到本地用户属性。支持嵌套路径(例如 ocs.data.id)。",
|
||||
@ -2304,7 +2262,7 @@
|
||||
"Maximum tokens per response": "单次响应最大 token 数",
|
||||
"maxRequests ≥ 0, maxSuccess ≥ 1, both ≤ 2,147,483,647": "maxRequests ≥ 0, maxSuccess ≥ 1,两者均 ≤ 2,147,483,647",
|
||||
"May be used for training by upstream provider": "可能被上游提供商用于训练",
|
||||
"Media pricing": "多模态价格",
|
||||
"Media pricing": "媒体定价",
|
||||
"Median time-to-first-token (TTFT) sampled hourly per group": "按小时采样的各分组首 token 延迟(TTFT)中位数",
|
||||
"Medical Q&A, mental health support": "医疗问答与心理健康支持",
|
||||
"Memory Hits": "内存命中",
|
||||
@ -2332,8 +2290,8 @@
|
||||
"Minimum Trust Level": "最低信任级别",
|
||||
"Minimum:": "最低:",
|
||||
"Minor blips in the last 30 days": "近 30 天内有轻微抖动",
|
||||
"Mint a fresh pair below — or pick an existing one further down. Click Save when ready.": "下方可新建一对——或继续下滑选择已有的。完成后点击保存。",
|
||||
"Creates a Pancake product in the saved store using this plan’s title and price. Requires Waffo Pancake to be fully configured in Payment settings first.": "在已保存的店铺中用此套餐的标题和价格创建一个 Pancake 商品。需要先在支付设置中完成 Waffo Pancake 配置。",
|
||||
"Mint a fresh pair below — or pick an existing one further down. Click Save when ready.": "在下方创建新的配对,或继续向下选择已有配对。准备好后点击保存。",
|
||||
"Creates a Pancake product in the saved store using this plan’s title and price. Requires Waffo Pancake to be fully configured in Payment settings first.": "使用此套餐的标题和价格,在已保存的店铺中创建 Pancake 产品。需要先在支付设置中完整配置 Waffo Pancake。",
|
||||
"Minute": "分钟",
|
||||
"minutes": "分钟",
|
||||
"Missing code": "缺少代码",
|
||||
@ -2628,9 +2586,9 @@
|
||||
"No Retry": "不重试",
|
||||
"No rules yet": "暂无规则",
|
||||
"No rules yet. Add a group below to get started.": "暂无规则。请在下方添加分组以开始。",
|
||||
"No separate media pricing configured.": "未单独配置多模态价格。",
|
||||
"No separate media pricing configured.": "未配置单独的媒体定价。",
|
||||
"No status code mappings configured.": "未配置状态码映射。",
|
||||
"No stores on this merchant yet. Set a return URL and click Create to mint your first pair.": "该商户暂无店铺。填写支付返回地址后点击「创建」生成第一对。",
|
||||
"No stores on this merchant yet. Set a return URL and click Create to mint your first pair.": "此商户暂无店铺。设置返回 URL 后点击创建来生成第一组配对。",
|
||||
"No subscription plans yet": "暂无订阅套餐",
|
||||
"No subscription records": "暂无订阅记录",
|
||||
"No Sync": "不同步",
|
||||
@ -2652,7 +2610,7 @@
|
||||
"No X Found": "未找到 X",
|
||||
"Node Name": "节点名称",
|
||||
"Non-stream": "非流式",
|
||||
"Non-zero invitation rewards require compliance confirmation in Payment Gateway settings.": "设置非零邀请奖励额度前,需要先在支付设置中确认合规声明。",
|
||||
"Non-zero invitation rewards require compliance confirmation in Payment Gateway settings.": "非零邀请奖励需要先在支付网关设置中确认合规条款。",
|
||||
"None": "无",
|
||||
"noreply@example.com": "noreply@example.com",
|
||||
"Normalized:": "已归一化:",
|
||||
@ -2751,7 +2709,7 @@
|
||||
"OpenRouter": "OpenRouter",
|
||||
"opens in an external client. Trigger it from the sidebar or API key actions to launch the configured application.": "在外部客户端中打开。从侧边栏或 API 密钥操作中触发,以启动配置的应用。",
|
||||
"Operation": "操作",
|
||||
"operation and charging behavior": "运营和收费行为产生的法律责任",
|
||||
"operation and charging behavior": "运营和收费行为",
|
||||
"Operation failed": "操作失败",
|
||||
"Operation successful": "操作成功",
|
||||
"Operation Type": "操作类型",
|
||||
@ -2774,7 +2732,7 @@
|
||||
"Opus Model": "Opus 模型",
|
||||
"Or continue with": "或继续使用",
|
||||
"Or enter this key manually:": "或手动输入此密钥:",
|
||||
"or pick existing": "或选择已有",
|
||||
"or pick existing": "或选择已有项",
|
||||
"Order completed successfully": "订单已成功完成",
|
||||
"Order History": "订单历史",
|
||||
"Order Payment Method": "订单支付方式",
|
||||
@ -2793,7 +2751,6 @@
|
||||
"Overnight range": "跨日范围",
|
||||
"override": "覆盖",
|
||||
"Override": "覆盖",
|
||||
"Override Anthropic headers, defaults, and thinking adapter behavior": "覆盖 Anthropic 标头、默认值和思维适配器行为",
|
||||
"Override auto-discovered endpoint": "覆盖自动发现的端点",
|
||||
"Override request headers": "覆盖请求标头",
|
||||
"Override request headers (JSON format)": "覆盖请求头(JSON 格式)",
|
||||
@ -2873,7 +2830,7 @@
|
||||
"Pay": "支付",
|
||||
"Pay-as-you-go with real-time usage monitoring": "按量付费,实时监控使用情况",
|
||||
"Payment": "支付",
|
||||
"Payment aggregator mode — onboard with your own registered company (offshore entity). Built for Enterprise.": "支付聚合模式——使用你自己的注册公司(海外主体)来入驻,适合 Enterprise 企业",
|
||||
"Payment aggregator mode — onboard with your own registered company (offshore entity). Built for Enterprise.": "支付聚合模式:使用你自己的注册公司(离岸实体)入驻。面向企业场景构建。",
|
||||
"Payment Channel": "支付渠道",
|
||||
"Payment Gateway": "支付网关",
|
||||
"Payment initiated": "已发起支付",
|
||||
@ -2887,8 +2844,8 @@
|
||||
"Payment page opened": "已打开支付页面",
|
||||
"Payment request failed": "支付请求失败",
|
||||
"Payment return URL": "支付返回地址",
|
||||
"Payment return URL is empty. Create the product without a SuccessURL redirect?": "支付返回地址为空。是否在不绑定 SuccessURL 的情况下创建商品?",
|
||||
"Payment, redemption codes, subscription plans, and invitation rewards are locked until the root administrator confirms the compliance terms.": "确认前,支付、兑换码、订阅计划和邀请返利功能将保持锁定。",
|
||||
"Payment return URL is empty. Create the product without a SuccessURL redirect?": "支付返回 URL 为空。是否在没有 SuccessURL 跳转的情况下创建产品?",
|
||||
"Payment, redemption codes, subscription plans, and invitation rewards are locked until the root administrator confirms the compliance terms.": "在根管理员确认合规条款之前,支付、兑换码、订阅套餐和邀请奖励功能将保持锁定。",
|
||||
"Peak": "峰值",
|
||||
"Peak throughput": "峰值吞吐",
|
||||
"Penalises repetition of frequent tokens": "惩罚高频 token 的重复出现",
|
||||
@ -2931,14 +2888,14 @@
|
||||
"Personal use": "个人使用",
|
||||
"Personal use mode": "个人使用模式",
|
||||
"Pick a date": "选择日期",
|
||||
"Pick or create both a store and a product before saving.": "保存前请先选择或新建店铺和商品。",
|
||||
"Pick or create both a store and a product before saving.": "保存前请同时选择或创建店铺和产品。",
|
||||
"Ping Interval (seconds)": "Ping 间隔(秒)",
|
||||
"Plan": "套餐",
|
||||
"Plan Name": "套餐名称",
|
||||
"Plan price must be greater than zero": "套餐价格必须大于 0",
|
||||
"Plan Subtitle": "套餐副标题",
|
||||
"Plan Title": "套餐标题",
|
||||
"Plan title is required": "请填写套餐标题",
|
||||
"Plan title is required": "套餐标题为必填项",
|
||||
"Planned maintenance on Friday at 22:00 UTC...": "计划于周五 22:00 UTC 进行维护...",
|
||||
"Platform": "平台",
|
||||
"Playground": "游乐场",
|
||||
@ -2987,7 +2944,7 @@
|
||||
"Please set Ollama API Base URL first": "请先设置 Ollama API Base URL",
|
||||
"Please sign in to view {{module}}.": "请登录后查看 {{module}}。",
|
||||
"Please try again later.": "请稍后再试。",
|
||||
"Please type the following text to confirm:": "请输入以下文字以确认:",
|
||||
"Please type the following text to confirm:": "请输入以下文本进行确认:",
|
||||
"Please upload key file(s)": "请上传密钥文件",
|
||||
"Please wait a moment before trying again.": "请稍候再试。",
|
||||
"Please wait a moment, human check is initializing...": "请稍等,人机验证正在初始化...",
|
||||
@ -3039,7 +2996,6 @@
|
||||
"Press Enter or comma to add tags": "按 Enter 或逗号添加标签",
|
||||
"Press Enter to use \"{{value}}\"": "按 Enter 使用「{{value}}」",
|
||||
"Prevent server-side request forgery attacks": "防止服务器端请求伪造攻击",
|
||||
"Prevent server-side request forgery attacks by controlling outbound requests.": "通过控制出站请求来防止服务器端请求伪造攻击。",
|
||||
"Preview": "预览",
|
||||
"Previous": "上一步",
|
||||
"Previous branch": "上一分支",
|
||||
@ -3102,7 +3058,6 @@
|
||||
"Prompt Details": "提示词详情",
|
||||
"Prompt price ($/1M tokens)": "提示词价格(美元/100 万 token)",
|
||||
"Proprietary": "商业闭源",
|
||||
"Protect login and registration with Cloudflare Turnstile": "使用 Cloudflare Turnstile 保护登录和注册",
|
||||
"Provide a JSON object where each key maps to an endpoint definition.": "提供一个 JSON 对象,其中每个键映射到一个端点定义。",
|
||||
"Provide a valid URL starting with http:// or https://": "请提供以 http:// 或 https:// 开头的有效 URL",
|
||||
"Provide Markdown, HTML, or an external URL for the privacy policy": "提供 Markdown、HTML 或外部 URL 作为隐私政策",
|
||||
@ -3216,7 +3171,7 @@
|
||||
"Redemption code updated successfully": "兑换码更新成功",
|
||||
"Redemption code(s) created successfully": "兑换码创建成功",
|
||||
"Redemption Codes": "兑换码",
|
||||
"Redemption codes are disabled until the administrator confirms compliance terms.": "兑换码功能已禁用,管理员需先确认合规声明。",
|
||||
"Redemption codes are disabled until the administrator confirms compliance terms.": "管理员确认合规条款之前,兑换码功能不可用。",
|
||||
"redemption codes.": "兑换码。",
|
||||
"Redemption failed": "兑换失败",
|
||||
"Redemption successful! Added: {{quota}}": "兑换成功!已添加:{{quota}}",
|
||||
@ -3228,7 +3183,7 @@
|
||||
"Reference Video": "参照生视频",
|
||||
"Referral link:": "推荐链接:",
|
||||
"Referral Program": "推荐计划",
|
||||
"Referral reward transfer is disabled until the administrator confirms compliance terms.": "邀请奖励划转已禁用,管理员需先确认合规声明。",
|
||||
"Referral reward transfer is disabled until the administrator confirms compliance terms.": "管理员确认合规条款之前,邀请奖励转移功能不可用。",
|
||||
"Refine models by provider, group, type, and tags.": "按供应商、分组、类型和标签细化模型。",
|
||||
"Refresh": "刷新",
|
||||
"Refresh Cache": "刷新缓存",
|
||||
@ -3244,7 +3199,7 @@
|
||||
"Regex": "正则",
|
||||
"Regex Pattern": "正则表达式",
|
||||
"Regex Replace": "正则替换",
|
||||
"Register each URL into the matching Test Mode / Production Mode webhook slot in the Pancake dashboard. Separate endpoints prevent test traffic from accidentally crediting production accounts.": "将上述 URL 分别注册到 Pancake 控制台的测试模式和生产模式 Webhook 槽位中。独立的端点可以防止测试流量误充值到生产账户。",
|
||||
"Register each URL into the matching Test Mode / Production Mode webhook slot in the Pancake dashboard. Separate endpoints prevent test traffic from accidentally crediting production accounts.": "请在 Pancake 控制台中将每个 URL 注册到对应的测试模式/生产模式 webhook 槽位。分离端点可以避免测试流量误入生产账户。",
|
||||
"Register Passkey": "注册 Passkey",
|
||||
"Registration Enabled": "注册已启用",
|
||||
"Registry (optional)": "注册表 (可选)",
|
||||
@ -3388,7 +3343,6 @@
|
||||
"Reveal key": "显示密钥",
|
||||
"Revenue": "收入",
|
||||
"Review & initialize": "审核并初始化",
|
||||
"Review current version and fetch release notes.": "查看当前版本并获取发布说明。",
|
||||
"Review model rates before scaling traffic": "扩展流量前查看模型费率",
|
||||
"Review your payment details": "查看您的付款详情",
|
||||
"Review your purchase details before proceeding.": "在继续之前,请审阅您的购买详情。",
|
||||
@ -3524,7 +3478,7 @@
|
||||
"Select a group type": "选择分组类型",
|
||||
"Select a model to edit pricing": "选择一个模型编辑定价",
|
||||
"Select a preset...": "选择一个预设...",
|
||||
"Select a product": "选择商品",
|
||||
"Select a product": "选择产品",
|
||||
"Select a role": "选择角色",
|
||||
"Select a rule to edit.": "请选择一条规则进行编辑。",
|
||||
"Select a store": "选择店铺",
|
||||
@ -3602,7 +3556,7 @@
|
||||
"Sending...": "发送中...",
|
||||
"Sensitive Words": "敏感词",
|
||||
"Sent the API key to FluentRead.": "API 密钥已发送至 FluentRead。",
|
||||
"Separate image/audio prices are enabled.": "已启用独立图像/音频价格。",
|
||||
"Separate image/audio prices are enabled.": "已启用图像/音频单独定价。",
|
||||
"Serve multiple users or teams with billing and quota control.": "为多个用户或团队提供计费和配额管理服务。",
|
||||
"Server Address": "服务器地址",
|
||||
"Server IP": "服务器 IP",
|
||||
@ -3626,7 +3580,7 @@
|
||||
"Set quota amount and limits": "设置令牌可用额度和数量",
|
||||
"Set Request Header": "设置请求头",
|
||||
"Set runtime request header: override entire value, or manipulate comma-separated tokens": "设置运行期请求头:可直接覆盖整条值,也可对逗号分隔的 token 做处理",
|
||||
"Set separate prices for cache reads and writes.": "为缓存读取与写入设置独立价格。",
|
||||
"Set separate prices for cache reads and writes.": "为缓存读取和写入设置单独价格。",
|
||||
"Set Tag": "设置标签",
|
||||
"Set tag for selected channels": "为选定的渠道设置标签",
|
||||
"Set the language used across the interface": "设置界面显示语言",
|
||||
@ -3722,7 +3676,7 @@
|
||||
"Standard price": "标准价格",
|
||||
"Start": "开始",
|
||||
"Start a conversation to see messages here": "开始对话以在此处查看消息",
|
||||
"Start collecting payments globally without registering a company. Built for indie developers, OPC sole proprietorships, and startups. Waffo Pancake acts as your Merchant of Record, taking on the compliance burden of global payment collection — consumption tax, invoicing, subscription management, refunds, and chargebacks. Solo developers can launch fast and stay focused on product instead of compliance. Onboard in minutes — one prompt to a full integration.": "无需注册公司即可开始全球收款,适合个人 / OPC 一人公司 / Startup。Waffo Pancake 作为你的 Merchant of Record,替你承担全球收款的合规责任:消费税、开票(Invoice)、订阅管理、退款与拒付处理。独立开发者可以直接上线,专注产品而非合规。极速入驻,一个 Prompt 完成集成。",
|
||||
"Start collecting payments globally without registering a company. Built for indie developers, OPC sole proprietorships, and startups. Waffo Pancake acts as your Merchant of Record, taking on the compliance burden of global payment collection — consumption tax, invoicing, subscription management, refunds, and chargebacks. Solo developers can launch fast and stay focused on product instead of compliance. Onboard in minutes — one prompt to a full integration.": "无需注册公司即可开始全球收款。面向独立开发者、OPC 个体经营者和初创团队构建。Waffo Pancake 作为你的登记商户(Merchant of Record),承担全球收款相关的合规负担,包括消费税、开票、订阅管理、退款和拒付。个人开发者可以快速上线,专注产品而不是合规事务。几分钟即可完成入驻,从一个提示词到完整集成。",
|
||||
"Start for free with generous limits. No credit card required.": "免费开始使用,额度充足,无需绑定信用卡。",
|
||||
"Start Time": "起始时间",
|
||||
"Static page describing the platform.": "描述平台的静态页面。",
|
||||
@ -3744,7 +3698,7 @@
|
||||
"Stop": "停止",
|
||||
"Stop Retry": "停止重试",
|
||||
"Store": "店铺",
|
||||
"Store + product created": "店铺 + 商品已创建",
|
||||
"Store + product created": "店铺和产品已创建",
|
||||
"Store ID": "商店 ID",
|
||||
"Store ID is required": "商店 ID 为必填项",
|
||||
"Stored value is not echoed back for security": "出于安全考虑,已存储的值不会回显",
|
||||
@ -3778,9 +3732,9 @@
|
||||
"Subscription First": "优先订阅",
|
||||
"Subscription Management": "订阅管理",
|
||||
"Subscription Only": "仅用订阅",
|
||||
"Subscription plan creation and changes are locked until the administrator confirms compliance terms in Payment Gateway settings.": "订阅套餐创建和变更已锁定,管理员需先在支付设置中确认合规声明。",
|
||||
"Subscription plan creation and changes are locked until the administrator confirms compliance terms in Payment Gateway settings.": "管理员在支付网关设置中确认合规条款之前,订阅套餐的创建和修改会被锁定。",
|
||||
"Subscription Plans": "订阅套餐",
|
||||
"Subscription plans do NOT use the bound Product — each plan has its own dedicated Pancake product, set in the Subscriptions admin (or auto-minted via the \"+ Create\" button there).": "订阅套餐不使用这里绑定的商品 —— 每个套餐在「订阅」管理页有自己专属的 Pancake 商品,可以手动填写或点 \"+ 新建\" 一键创建。",
|
||||
"Subscription plans do NOT use the bound Product — each plan has its own dedicated Pancake product, set in the Subscriptions admin (or auto-minted via the \"+ Create\" button there).": "订阅套餐不会使用已绑定的产品。每个套餐都有独立的 Pancake 产品,可在订阅管理中设置,或通过其中的“+ 创建”按钮自动生成。",
|
||||
"Subtract": "减少",
|
||||
"Success": "成功",
|
||||
"Success rate": "成功率",
|
||||
@ -3905,11 +3859,11 @@
|
||||
"The administrator has not configured a user agreement yet.": "管理员尚未配置用户协议。",
|
||||
"The administrator has not configured any about content yet. You can set it in the settings page, supporting HTML or URL.": "管理员尚未配置任何关于内容。您可以在设置页面中进行设置,支持 HTML 或 URL。",
|
||||
"The binding will complete automatically after authorization": "授权后绑定将自动完成",
|
||||
"The bound Product powers wallet top-ups: when a user enters any amount, new-api runs the checkout against this single Pancake product and overrides the price per session — no need to pre-create $1 / $5 / $10 SKUs.": "绑定的商品用于钱包充值:用户输入任意金额时,new-api 用这一个 Pancake 商品发起结账并按用户输入动态设置价格 —— 不需要预先创建 $1 / $5 / $10 等一堆 SKU。",
|
||||
"The bound Store is the parent container for every Pancake product new-api creates from this admin — both the wallet top-up product and any subscription-plan products. One store is enough; pin a different one only if you genuinely run separate Pancake catalogs.": "绑定的店铺是 new-api 从此处创建的所有 Pancake 商品(钱包充值商品 + 各订阅套餐商品)的父容器。一个店铺足够使用;只有在确实需要使用独立的 Pancake 商品目录时,才需要绑定到不同的店铺。",
|
||||
"The bound Product powers wallet top-ups: when a user enters any amount, new-api runs the checkout against this single Pancake product and overrides the price per session — no need to pre-create $1 / $5 / $10 SKUs.": "已绑定产品用于钱包充值:当用户输入任意金额时,new-api 会基于这个单一 Pancake 产品发起结账,并按会话覆盖价格,无需预先创建 $1 / $5 / $10 的 SKU。",
|
||||
"The bound Store is the parent container for every Pancake product new-api creates from this admin — both the wallet top-up product and any subscription-plan products. One store is enough; pin a different one only if you genuinely run separate Pancake catalogs.": "已绑定店铺是 new-api 从此管理端创建的所有 Pancake 产品的父容器,包括钱包充值产品和订阅套餐产品。一个店铺通常足够;只有在确实运营多个 Pancake 目录时才需要绑定不同店铺。",
|
||||
"The effective domain for Passkey registration. Must match the current domain or be its parent domain.": "用于 Passkey 注册的有效域。必须与当前域匹配或为其父域。",
|
||||
"The entered text does not match the required text.": "输入内容与要求文案不一致。",
|
||||
"The environment (test vs production) is decided by the key you paste here — use the Test key while integrating, then swap to the Production key when going live.": "测试 / 生产环境由你粘进来的 API 私钥本身决定——集成阶段用 Test Key,正式上线时再换成 Production Key。",
|
||||
"The entered text does not match the required text.": "输入文本与要求文本不匹配。",
|
||||
"The environment (test vs production) is decided by the key you paste here — use the Test key while integrating, then swap to the Production key when going live.": "环境(测试或生产)由你在此粘贴的密钥决定。集成期间使用测试密钥,上线时再切换为生产密钥。",
|
||||
"The exact model identifier as used in API requests.": "API 请求中使用的确切模型标识符。",
|
||||
"The following models have billing type conflicts (fixed price vs ratio billing). Confirm to proceed with the changes.": "以下模型存在计费类型冲突(固定价格 vs 比例计费)。确认以继续更改。",
|
||||
"The following models in the model redirect have not been added to the \"Models\" list and may fail during invocation due to missing available models:": "模型重定向里的下列模型尚未添加到\"模型\"列表,调用时会因为缺少可用模型而失败:",
|
||||
@ -3941,7 +3895,7 @@
|
||||
"This action will permanently remove 2FA protection from your account.": "此操作将永久移除您账户的 2FA 保护。",
|
||||
"This channel is not an Ollama channel.": "该渠道不是 Ollama 渠道。",
|
||||
"This channel type does not support fetching models": "此通道类型不支持获取模型",
|
||||
"This confirmation unlocks payment, redemption code, subscription plan, and invitation reward features. Please read the statements carefully.": "该操作将启用支付、兑换码、订阅计划和邀请返利相关功能。请仔细阅读并确认以下声明。",
|
||||
"This confirmation unlocks payment, redemption code, subscription plan, and invitation reward features. Please read the statements carefully.": "此确认会解锁支付、兑换码、订阅套餐和邀请奖励功能。请仔细阅读相关声明。",
|
||||
"This controls model request rate limiting. Web/API route throttling is configured by environment variables and may still return 429.": "此处仅控制模型请求速率限制。Web/API 路由限流由环境变量配置,仍可能返回 429。",
|
||||
"This data may be unreliable, use with caution": "此数据可能不可靠,请谨慎使用",
|
||||
"This device does not support Passkey": "此设备不支持 Passkey",
|
||||
@ -3960,7 +3914,7 @@
|
||||
"This project must be used in compliance with the": "此项目的使用必须遵守",
|
||||
"This record was written by a pre-upgrade instance and lacks audit info. Upgrade the instance to record server IP, callback IP, payment method and system version.": "该记录由旧版本实例写入,缺少审计信息,建议将实例升级至最新版本以便记录服务器 IP、回调 IP、支付方式与系统版本等审计字段。",
|
||||
"This site currently has {{count}} models enabled": "本站当前已启用模型,总计 {{count}} 个",
|
||||
"This tier catches any request that did not match earlier tiers.": "该档位会兜底匹配所有未命中前面档位的请求。",
|
||||
"This tier catches any request that did not match earlier tiers.": "此阶梯会兜底处理未匹配前面阶梯的请求。",
|
||||
"this token group": "此令牌分组",
|
||||
"this user group": "此用户分组",
|
||||
"This user has no bindings": "该用户无任何绑定",
|
||||
@ -3983,7 +3937,7 @@
|
||||
"Throughput short": "吞吐",
|
||||
"Throughput trend": "吞吐量趋势",
|
||||
"Tier": "档位",
|
||||
"Tier conditions": "档位条件",
|
||||
"Tier conditions": "阶梯条件",
|
||||
"Tier name": "档位名称",
|
||||
"Tiered": "阶梯",
|
||||
"Tiered (billing expression)": "阶梯计费(计费表达式)",
|
||||
@ -4143,7 +4097,7 @@
|
||||
"Type (common)": "类型(常用)",
|
||||
"Type *": "类型 *",
|
||||
"Type a command or search...": "输入命令或搜索...",
|
||||
"Type the confirmation text here": "请输入确认文案",
|
||||
"Type the confirmation text here": "在此输入确认文本",
|
||||
"Type-Specific Settings": "特定类型设置",
|
||||
"Type:": "类型:",
|
||||
"UI granularity only — data is still aggregated hourly": "仅 UI 粒度 — 数据仍按小时汇总",
|
||||
@ -4268,7 +4222,7 @@
|
||||
"used": "已使用",
|
||||
"Used": "已使用",
|
||||
"Used / Remaining": "已使用 / 剩余",
|
||||
"Used as SuccessURL on the new product. You'll be prompted to confirm if left blank.": "作为新商品的 SuccessURL。留空时会再次提示确认。",
|
||||
"Used as SuccessURL on the new product. You'll be prompted to confirm if left blank.": "用作新产品的 SuccessURL。若留空,系统会要求你确认。",
|
||||
"Used for load balancing. Higher weight = more requests": "用于负载均衡。权重越高 = 请求越多",
|
||||
"Used in URLs and API routes": "用于URL和API路由",
|
||||
"Used Quota": "消耗额度",
|
||||
@ -4354,16 +4308,11 @@
|
||||
"Vidu": "Vidu",
|
||||
"View": "查看",
|
||||
"View all currently available models": "查看当前可用的所有模型",
|
||||
"View and manage your API usage logs": "查看和管理您的 API 使用日志",
|
||||
"View and manage your drawing logs": "查看和管理您的绘图日志",
|
||||
"View and manage your task logs": "查看和管理您的任务日志",
|
||||
"View dashboard overview and statistics": "查看仪表板概览和统计信息",
|
||||
"View detailed information about this user including balance, usage statistics, and invitation details.": "查看此用户的详细信息,包括余额、使用统计和邀请详情。",
|
||||
"View details": "查看详情",
|
||||
"View document": "查看文档",
|
||||
"View logs": "查看日志",
|
||||
"View mode": "视图模式",
|
||||
"View model call count analytics and charts": "查看模型调用次数统计和图表",
|
||||
"View model statistics and charts": "查看模型统计和图表",
|
||||
"View Pricing": "查看定价",
|
||||
"View the complete details for this": "查看此条",
|
||||
@ -4371,7 +4320,6 @@
|
||||
"View the complete error message and details": "查看完整错误信息与详情",
|
||||
"View the complete prompt and its English translation": "查看完整提示词及其英文翻译",
|
||||
"View the generated image": "查看生成的图片",
|
||||
"View user consumption statistics and charts": "查看用户消耗统计和图表",
|
||||
"View your topup transaction records and payment history": "查看您的充值交易记录和付款历史",
|
||||
"Violation Code": "违规代码",
|
||||
"Violation deduction amount": "违规扣费金额",
|
||||
@ -4393,7 +4341,7 @@
|
||||
"Visual Parameter Override": "可视化参数覆盖",
|
||||
"VolcEngine": "字节火山方舟、豆包通用",
|
||||
"vs. previous": "相较上期",
|
||||
"Waffo Aggregator Gateway": "Waffo 支付聚合网关",
|
||||
"Waffo Aggregator Gateway": "Waffo 聚合网关",
|
||||
"Waffo Pancake Dashboard": "Waffo Pancake 控制台",
|
||||
"Waffo Pancake MoR": "Waffo Pancake MoR",
|
||||
"Waffo Pancake Payment Gateway": "Waffo Pancake 支付网关",
|
||||
@ -4472,7 +4420,7 @@
|
||||
"Whitelist (Only allow listed domains)": "白名单(仅允许列出的域)",
|
||||
"Whitelist (Only allow listed IPs)": "白名单(仅允许列出的 IP)",
|
||||
"whsec_xxx": "whsec_xxx",
|
||||
"Why only one store + product?": "为什么只绑定一个店铺 + 商品?",
|
||||
"Why only one store + product?": "为什么只需要一个店铺和产品?",
|
||||
"Window:": "窗口:",
|
||||
"Wire encoding for the embedding vectors": "向量传输的编码格式",
|
||||
"with conflicts": "有冲突",
|
||||
@ -4495,16 +4443,16 @@
|
||||
"You can close this tab once the binding completes or a success message appears in the original window.": "绑定完成后或原窗口出现成功消息后,您可以关闭此标签页。",
|
||||
"You can manually add them in \"Custom Model Names\", click \"Fill\" and then submit, or use the operations below to handle automatically.": "你可以在\"自定义模型名称\"处手动添加它们,然后点击\"填入\"后再提交,或者直接使用下方操作自动处理。",
|
||||
"You can only check in once per day": "每日仅可签到一次,请勿重复签到",
|
||||
"You commit not to use this system to implement, assist with, or indirectly implement acts that violate applicable laws and regulations, regulatory requirements, platform rules, public interests, or the lawful rights and interests of third parties.": "你承诺不会利用本系统实施、协助实施或变相实施违反适用法律法规、监管要求、平台规则、社会公共利益或第三方合法权益的行为。",
|
||||
"You commit to using upstream APIs, accounts, keys, quotas, and service capabilities only within the scope of lawful authorization obtained from upstream service providers, model service providers, or relevant rights holders, and will not conduct unauthorized resale, trafficking, distribution, or other non-compliant commercialization.": "你承诺仅在已取得上游服务商、模型服务提供方或相关权利方合法授权的范围内使用其 API、账号、密钥、额度及服务能力,不进行未经授权的转售、倒卖、分销或其他违规商业化使用。",
|
||||
"You commit not to use this system to implement, assist with, or indirectly implement acts that violate applicable laws and regulations, regulatory requirements, platform rules, public interests, or the lawful rights and interests of third parties.": "你承诺不会使用本系统实施、协助实施或间接实施违反适用法律法规、监管要求、平台规则、公共利益或第三方合法权益的行为。",
|
||||
"You commit to using upstream APIs, accounts, keys, quotas, and service capabilities only within the scope of lawful authorization obtained from upstream service providers, model service providers, or relevant rights holders, and will not conduct unauthorized resale, trafficking, distribution, or other non-compliant commercialization.": "你承诺仅在从上游服务提供商、模型服务提供商或相关权利人处获得合法授权的范围内使用上游 API、账户、密钥、额度和服务能力,并不会进行未经授权的转售、倒卖、分发或其他不合规商业化行为。",
|
||||
"You don't have necessary permission": "您没有必要的权限",
|
||||
"You have legally obtained authorization for the connected model APIs, accounts, keys, and quotas.": "你已合法取得所接入模型 API、账号、密钥和额度的授权。",
|
||||
"You have legally obtained authorization for the connected model APIs, accounts, keys, and quotas.": "你已合法取得所连接模型 API、账户、密钥和额度的授权。",
|
||||
"You have unsaved changes": "您有未保存的更改",
|
||||
"You have unsaved changes. Are you sure you want to leave?": "您有未保存的更改。确定要离开吗?",
|
||||
"You Pay": "您支付",
|
||||
"You save": "您节省",
|
||||
"You understand and independently bear legal responsibility arising from deployment, operation, and charging behavior.": "你理解并自行承担部署、运营和收费行为产生的法律责任。",
|
||||
"You understand this compliance reminder is only for risk notice and does not constitute legal advice, a compliance review conclusion, or a guarantee of the legality of your use of this system; you should consult professional legal or compliance advisors based on your actual business scenario.": "你理解本合规提醒仅用于风险提示,不构成法律意见、合规审查结论或对你使用本系统行为合法性的保证;你应根据实际业务场景自行咨询专业法律或合规顾问。",
|
||||
"You understand and independently bear legal responsibility arising from deployment, operation, and charging behavior.": "你理解并独立承担因部署、运营和收费行为产生的法律责任。",
|
||||
"You understand this compliance reminder is only for risk notice and does not constitute legal advice, a compliance review conclusion, or a guarantee of the legality of your use of this system; you should consult professional legal or compliance advisors based on your actual business scenario.": "你理解此合规提醒仅用于风险提示,不构成法律意见、合规审查结论或对你使用本系统合法性的保证;你应结合实际业务场景咨询专业法律或合规顾问。",
|
||||
"You will be redirected to Telegram to complete the binding process.": "您将被重定向到 Telegram 以完成绑定过程。",
|
||||
"You'll be redirected automatically. You can return to the previous page if nothing happens after a few seconds.": "您将被自动重定向。如果几秒钟后无反应,您可以返回上一页。",
|
||||
"your AI integration?": "你的 AI 集成了吗?",
|
||||
|
||||
3
web/default/src/i18n/static-keys.ts
vendored
3
web/default/src/i18n/static-keys.ts
vendored
@ -202,7 +202,6 @@ export const STATIC_I18N_KEYS = [
|
||||
|
||||
// Channel Affinity section
|
||||
'Channel Affinity',
|
||||
'Configure channel affinity (sticky routing) rules',
|
||||
|
||||
// Models constants
|
||||
'Exact Match',
|
||||
@ -340,7 +339,6 @@ export const STATIC_I18N_KEYS = [
|
||||
|
||||
// Subscription management
|
||||
'Subscription Management',
|
||||
'Manage subscription plan creation, pricing and status',
|
||||
'Stripe/Creem requires creating products on the third-party platform and entering the ID',
|
||||
'Create Plan',
|
||||
'Active',
|
||||
@ -457,7 +455,6 @@ export const STATIC_I18N_KEYS = [
|
||||
|
||||
// Grok settings
|
||||
'Grok Settings',
|
||||
'Configure xAI Grok model specific settings',
|
||||
'Enable violation deduction',
|
||||
'When enabled, violation requests will incur additional charges.',
|
||||
'Official documentation',
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user