feat(default): reorganize system settings pricing UI

Refine the default system settings structure and model pricing editor so pricing configuration is easier to scan and edit.
This commit is contained in:
CaIon
2026-05-06 16:29:45 +08:00
parent 9acf5fecae
commit 0f9f094a48
62 changed files with 3655 additions and 2343 deletions
@@ -1,89 +0,0 @@
import { useParams } from '@tanstack/react-router'
import { useTranslation } from 'react-i18next'
import { parseCurrencyDisplayType } from '@/lib/currency'
import { useSystemOptions, getOptionValue } from '../hooks/use-system-options'
import type { GeneralSettings } from '../types'
import {
GENERAL_DEFAULT_SECTION,
getGeneralSectionContent,
} from './section-registry.tsx'
const defaultGeneralSettings: GeneralSettings = {
'theme.frontend': 'default',
Notice: '',
SystemName: 'New API',
Logo: '',
Footer: '',
About: '',
HomePageContent: '',
ServerAddress: '',
'legal.user_agreement': '',
'legal.privacy_policy': '',
QuotaForNewUser: 0,
PreConsumedQuota: 0,
QuotaForInviter: 0,
QuotaForInvitee: 0,
TopUpLink: '',
'general_setting.docs_link': '',
'quota_setting.enable_free_model_pre_consume': true,
QuotaPerUnit: 500000,
USDExchangeRate: 7,
'general_setting.quota_display_type': 'USD',
'general_setting.custom_currency_symbol': '¤',
'general_setting.custom_currency_exchange_rate': 1,
RetryTimes: 0,
DisplayInCurrencyEnabled: true,
DisplayTokenStatEnabled: true,
DefaultCollapseSidebar: false,
DemoSiteEnabled: false,
SelfUseModeEnabled: false,
'checkin_setting.enabled': false,
'checkin_setting.min_quota': 1000,
'checkin_setting.max_quota': 10000,
'channel_affinity_setting.enabled': false,
'channel_affinity_setting.switch_on_success': true,
'channel_affinity_setting.max_entries': 100000,
'channel_affinity_setting.default_ttl_seconds': 3600,
'channel_affinity_setting.rules': '[]',
}
export function GeneralSettings() {
const { t } = useTranslation()
const { data, isLoading } = useSystemOptions()
const params = useParams({
from: '/_authenticated/system-settings/general/$section',
})
if (isLoading) {
return (
<div className='flex items-center justify-center py-12'>
<div className='text-muted-foreground'>{t('Loading settings...')}</div>
</div>
)
}
const settings = getOptionValue(data?.data, defaultGeneralSettings)
const quotaDisplayType = parseCurrencyDisplayType(
settings['general_setting.quota_display_type']
)
const activeSection = (params?.section ?? GENERAL_DEFAULT_SECTION) as
| 'system-info'
| 'quota'
| 'pricing'
| 'checkin'
| 'behavior'
| 'channel-affinity'
const sectionContent = getGeneralSectionContent(
activeSection,
settings,
quotaDisplayType
)
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>
)
}
@@ -1,148 +0,0 @@
import type { GeneralSettings } from '../types'
import { createSectionRegistry } from '../utils/section-registry'
import { ChannelAffinitySection } from './channel-affinity'
import { CheckinSettingsSection } from './checkin-settings-section'
import { PricingSection } from './pricing-section'
import { QuotaSettingsSection } from './quota-settings-section'
import { SystemBehaviorSection } from './system-behavior-section'
import { SystemInfoSection } from './system-info-section'
const GENERAL_SECTIONS = [
{
id: 'system-info',
titleKey: 'System Information',
descriptionKey: 'Configure basic system information and branding',
build: (settings: GeneralSettings) => (
<SystemInfoSection
defaultValues={{
theme: {
frontend: settings['theme.frontend'] as 'default' | 'classic',
},
Notice: settings.Notice,
SystemName: settings.SystemName,
Logo: settings.Logo,
Footer: settings.Footer,
About: settings.About,
HomePageContent: settings.HomePageContent,
ServerAddress: settings.ServerAddress,
legal: {
user_agreement: settings['legal.user_agreement'],
privacy_policy: settings['legal.privacy_policy'],
},
}}
/>
),
},
{
id: 'quota',
titleKey: 'Quota Settings',
descriptionKey: 'Configure user quota allocation and rewards',
build: (settings: GeneralSettings) => (
<QuotaSettingsSection
defaultValues={{
QuotaForNewUser: settings.QuotaForNewUser,
PreConsumedQuota: settings.PreConsumedQuota,
QuotaForInviter: settings.QuotaForInviter,
QuotaForInvitee: settings.QuotaForInvitee,
TopUpLink: settings.TopUpLink,
'general_setting.docs_link': settings['general_setting.docs_link'],
'quota_setting.enable_free_model_pre_consume':
settings['quota_setting.enable_free_model_pre_consume'],
}}
/>
),
},
{
id: 'pricing',
titleKey: 'Pricing & Display',
descriptionKey: 'Configure pricing model and display options',
build: (
settings: GeneralSettings,
quotaDisplayType: 'USD' | 'CNY' | 'TOKENS' | 'CUSTOM'
) => (
<PricingSection
defaultValues={{
QuotaPerUnit: settings.QuotaPerUnit,
USDExchangeRate: settings.USDExchangeRate,
DisplayInCurrencyEnabled: settings.DisplayInCurrencyEnabled,
DisplayTokenStatEnabled: settings.DisplayTokenStatEnabled,
general_setting: {
quota_display_type: quotaDisplayType,
custom_currency_symbol:
settings['general_setting.custom_currency_symbol'] ?? '¤',
custom_currency_exchange_rate:
settings['general_setting.custom_currency_exchange_rate'] ?? 1,
},
}}
/>
),
},
{
id: 'checkin',
titleKey: 'Check-in Settings',
descriptionKey: 'Configure daily check-in rewards for users',
build: (settings: GeneralSettings) => (
<CheckinSettingsSection
defaultValues={{
enabled: settings['checkin_setting.enabled'],
minQuota: settings['checkin_setting.min_quota'],
maxQuota: settings['checkin_setting.max_quota'],
}}
/>
),
},
{
id: 'behavior',
titleKey: 'System Behavior',
descriptionKey: 'Configure system-wide behavior and defaults',
build: (settings: GeneralSettings) => (
<SystemBehaviorSection
defaultValues={{
RetryTimes: settings.RetryTimes,
DefaultCollapseSidebar: settings.DefaultCollapseSidebar,
DemoSiteEnabled: settings.DemoSiteEnabled,
SelfUseModeEnabled: settings.SelfUseModeEnabled,
}}
/>
),
},
{
id: 'channel-affinity',
titleKey: 'Channel Affinity',
descriptionKey: 'Configure channel affinity (sticky routing) rules',
build: (settings: GeneralSettings) => (
<ChannelAffinitySection
defaultValues={{
'channel_affinity_setting.enabled':
settings['channel_affinity_setting.enabled'],
'channel_affinity_setting.switch_on_success':
settings['channel_affinity_setting.switch_on_success'],
'channel_affinity_setting.max_entries':
settings['channel_affinity_setting.max_entries'],
'channel_affinity_setting.default_ttl_seconds':
settings['channel_affinity_setting.default_ttl_seconds'],
'channel_affinity_setting.rules':
settings['channel_affinity_setting.rules'],
}}
/>
),
},
] as const
export type GeneralSectionId = (typeof GENERAL_SECTIONS)[number]['id']
const generalRegistry = createSectionRegistry<
GeneralSectionId,
GeneralSettings,
['USD' | 'CNY' | 'TOKENS' | 'CUSTOM']
>({
sections: GENERAL_SECTIONS,
defaultSection: 'system-info',
basePath: '/system-settings/general',
urlStyle: 'path',
})
export const GENERAL_SECTION_IDS = generalRegistry.sectionIds
export const GENERAL_DEFAULT_SECTION = generalRegistry.defaultSection
export const getGeneralSectionNavItems = generalRegistry.getSectionNavItems
export const getGeneralSectionContent = generalRegistry.getSectionContent
@@ -32,7 +32,6 @@ const _systemInfoSchema = z.object({
theme: z.object({
frontend: z.enum(['default', 'classic']),
}),
Notice: z.string().optional(),
SystemName: z.string().min(1),
ServerAddress: z.string().optional(),
Logo: z.string().url().optional().or(z.literal('')),
@@ -65,7 +64,6 @@ export function SystemInfoSection({ defaultValues }: SystemInfoSectionProps) {
frontend:
defaultValues.theme?.frontend === 'classic' ? 'classic' : 'default',
},
Notice: normalizeValue(defaultValues.Notice),
SystemName: normalizeValue(defaultValues.SystemName),
ServerAddress: normalizeValue(defaultValues.ServerAddress),
Logo: normalizeValue(defaultValues.Logo),
@@ -82,7 +80,6 @@ export function SystemInfoSection({ defaultValues }: SystemInfoSectionProps) {
theme: z.object({
frontend: z.enum(['default', 'classic']),
}),
Notice: z.string().optional(),
SystemName: z.string().min(1, {
error: () => t('System name is required'),
}),
@@ -161,31 +158,6 @@ export function SystemInfoSection({ defaultValues }: SystemInfoSectionProps) {
)}
/>
<FormField
control={form.control}
name='Notice'
render={({ field }) => (
<FormItem>
<FormLabel>{t('Notice')}</FormLabel>
<FormControl>
<Textarea
placeholder={t(
'Enter announcement content (supports Markdown & HTML)'
)}
rows={6}
{...field}
/>
</FormControl>
<FormDescription>
{t(
'Announcement displayed to users (supports Markdown & HTML)'
)}
</FormDescription>
<FormMessage />
</FormItem>
)}
/>
<FormField
control={form.control}
name='SystemName'