271 lines
8.8 KiB
TypeScript
Vendored
271 lines
8.8 KiB
TypeScript
Vendored
/*
|
|
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 { useEffect, useMemo } from 'react'
|
|
import { useForm } from 'react-hook-form'
|
|
import { useTranslation } from 'react-i18next'
|
|
import {
|
|
Form,
|
|
FormControl,
|
|
FormDescription,
|
|
FormField,
|
|
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 {
|
|
SIDEBAR_MODULES_DEFAULT,
|
|
type SidebarModulesAdminConfig,
|
|
serializeSidebarModulesAdmin,
|
|
} from './config'
|
|
|
|
type SidebarModulesSectionProps = {
|
|
config: SidebarModulesAdminConfig
|
|
initialSerialized: string
|
|
}
|
|
|
|
type SidebarFormValues = SidebarModulesAdminConfig
|
|
|
|
const toTitleCase = (value: string) =>
|
|
value.replaceAll(/[_-]+/g, ' ').replaceAll(/\b\w/g, (char) => char.toUpperCase())
|
|
|
|
export function SidebarModulesSection({
|
|
config,
|
|
initialSerialized,
|
|
}: SidebarModulesSectionProps) {
|
|
const { t } = useTranslation()
|
|
const updateOption = useUpdateOption()
|
|
|
|
const sectionMeta: Record<string, { title: string; description: string }> = {
|
|
chat: {
|
|
title: t('Chat area'),
|
|
description: t('Playground experiments and live conversations.'),
|
|
},
|
|
console: {
|
|
title: t('Console area'),
|
|
description: t('Dashboards, tokens, and usage analytics.'),
|
|
},
|
|
personal: {
|
|
title: t('Personal area'),
|
|
description: t('Wallet management and personal preferences.'),
|
|
},
|
|
admin: {
|
|
title: t('Admin area'),
|
|
description: t('Global configuration and administrative tools.'),
|
|
},
|
|
}
|
|
|
|
const moduleMeta: Record<
|
|
string,
|
|
Record<string, { title: string; description: string }>
|
|
> = {
|
|
chat: {
|
|
playground: {
|
|
title: t('Playground'),
|
|
description: t('Experiment with prompts and models in real time.'),
|
|
},
|
|
chat: {
|
|
title: t('Chat'),
|
|
description: t('Access previous conversations and start new ones.'),
|
|
},
|
|
},
|
|
console: {
|
|
detail: {
|
|
title: t('Dashboard'),
|
|
description: t('Aggregated usage metrics and trend charts.'),
|
|
},
|
|
token: {
|
|
title: t('Token management'),
|
|
description: t('Create, revoke, and audit API tokens.'),
|
|
},
|
|
log: {
|
|
title: t('Usage logs'),
|
|
description: t('Detailed request logs for investigations.'),
|
|
},
|
|
midjourney: {
|
|
title: t('Drawing logs'),
|
|
description: t('History of MjProxy-style image tasks.'),
|
|
},
|
|
task: {
|
|
title: t('Task logs'),
|
|
description: t('Background job tracker for queued work.'),
|
|
},
|
|
},
|
|
personal: {
|
|
topup: {
|
|
title: t('Wallet'),
|
|
description: t('Top up balance and view billing history.'),
|
|
},
|
|
personal: {
|
|
title: t('Profile'),
|
|
description: t('Personal settings and profile management.'),
|
|
},
|
|
},
|
|
admin: {
|
|
channel: {
|
|
title: t('Channels'),
|
|
description: t('Configure upstream providers and routing.'),
|
|
},
|
|
models: {
|
|
title: t('Models'),
|
|
description: t('Manage catalog visibility and pricing.'),
|
|
},
|
|
redemption: {
|
|
title: t('Redeem codes'),
|
|
description: t('Create and review invite or credit codes.'),
|
|
},
|
|
user: {
|
|
title: t('Users'),
|
|
description: t('Administer user accounts and roles.'),
|
|
},
|
|
setting: {
|
|
title: t('System settings'),
|
|
description: t('Advanced platform configuration.'),
|
|
},
|
|
subscription: {
|
|
title: t('Subscription Management'),
|
|
description: t('Manage subscription plans and pricing.'),
|
|
},
|
|
},
|
|
}
|
|
const formDefaults = useMemo(() => config, [config])
|
|
|
|
const form = useForm<SidebarFormValues>({
|
|
defaultValues: formDefaults,
|
|
})
|
|
|
|
useEffect(() => {
|
|
form.reset(formDefaults)
|
|
}, [formDefaults, form])
|
|
|
|
const onSubmit = async (values: SidebarFormValues) => {
|
|
const serialized = serializeSidebarModulesAdmin(values)
|
|
if (serialized === initialSerialized) {
|
|
return
|
|
}
|
|
|
|
await updateOption.mutateAsync({
|
|
key: 'SidebarModulesAdmin',
|
|
value: serialized,
|
|
})
|
|
}
|
|
|
|
const resetToDefault = () => {
|
|
form.reset(SIDEBAR_MODULES_DEFAULT)
|
|
}
|
|
|
|
const sections = Object.entries(config)
|
|
|
|
return (
|
|
<SettingsSection title={t('Sidebar modules')}>
|
|
<Form {...form}>
|
|
<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),
|
|
description: t('Custom sidebar section'),
|
|
}
|
|
const modules = Object.entries(sectionConfig).filter(
|
|
([moduleKey]) => moduleKey !== 'enabled'
|
|
)
|
|
|
|
return (
|
|
<SettingsControlGroup key={sectionKey}>
|
|
<FormField
|
|
control={form.control}
|
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
name={`${sectionKey}.enabled` as any}
|
|
render={({ field }) => (
|
|
<SettingsSwitchItem>
|
|
<SettingsSwitchContent>
|
|
<FormLabel>{sectionInfo.title}</FormLabel>
|
|
<FormDescription>
|
|
{sectionInfo.description}
|
|
</FormDescription>
|
|
</SettingsSwitchContent>
|
|
<FormControl>
|
|
<Switch
|
|
checked={Boolean(field.value)}
|
|
onCheckedChange={field.onChange}
|
|
/>
|
|
</FormControl>
|
|
</SettingsSwitchItem>
|
|
)}
|
|
/>
|
|
|
|
<SettingsControlChildren className='grid gap-3 md:grid-cols-2'>
|
|
{modules.map(([moduleKey]) => {
|
|
const moduleInfo = moduleMeta[sectionKey]?.[moduleKey] ?? {
|
|
title: toTitleCase(moduleKey),
|
|
description: t('Custom module'),
|
|
}
|
|
return (
|
|
<FormField
|
|
key={`${sectionKey}.${moduleKey}`}
|
|
control={form.control}
|
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
name={`${sectionKey}.${moduleKey}` as any}
|
|
render={({ field }) => (
|
|
<SettingsSwitchItem className='py-2'>
|
|
<SettingsSwitchContent>
|
|
<FormLabel>{moduleInfo.title}</FormLabel>
|
|
<FormDescription>
|
|
{moduleInfo.description}
|
|
</FormDescription>
|
|
</SettingsSwitchContent>
|
|
<FormControl>
|
|
<Switch
|
|
checked={Boolean(field.value)}
|
|
onCheckedChange={field.onChange}
|
|
disabled={
|
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
!form.watch(`${sectionKey}.enabled` as any)
|
|
}
|
|
/>
|
|
</FormControl>
|
|
</SettingsSwitchItem>
|
|
)}
|
|
/>
|
|
)
|
|
})}
|
|
</SettingsControlChildren>
|
|
</SettingsControlGroup>
|
|
)
|
|
})}
|
|
</SettingsForm>
|
|
</Form>
|
|
</SettingsSection>
|
|
)
|
|
}
|