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:
@@ -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'
|
||||
|
||||
Reference in New Issue
Block a user