import { useEffect } from 'react' import * as z from 'zod' import { useForm } from 'react-hook-form' import { zodResolver } from '@hookform/resolvers/zod' import { useTranslation } from 'react-i18next' import { toast } from 'sonner' import { Alert, AlertDescription, AlertTitle } from '@/components/ui/alert' import { Button } from '@/components/ui/button' import { Form, FormControl, FormDescription, FormField, FormItem, FormLabel, FormMessage, } from '@/components/ui/form' import { Input } from '@/components/ui/input' 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 { SettingsSection } from '../components/settings-section' import { useUpdateOption } from '../hooks/use-update-option' const thinkingBlacklistExample = JSON.stringify( ['moonshotai/kimi-k2-thinking', 'kimi-k2-thinking'], null, 2 ) const chatToResponsesPolicyExample = JSON.stringify( { enabled: true, all_channels: false, channel_ids: [1, 2], model_patterns: ['^gpt-4o.*$', '^gpt-5.*$'], }, null, 2 ) const chatToResponsesPolicyAllChannelsExample = JSON.stringify( { enabled: true, all_channels: true, model_patterns: ['^gpt-4o.*$', '^gpt-5.*$'], }, null, 2 ) const jsonString = z.string().refine((value) => { const trimmed = value.trim() if (!trimmed) return true try { JSON.parse(trimmed) return true } catch { return false } }, 'Invalid JSON format') const schema = z.object({ global: z.object({ pass_through_request_enabled: z.boolean(), thinking_model_blacklist: jsonString, chat_completions_to_responses_policy: jsonString, }), general_setting: z.object({ ping_interval_enabled: z.boolean(), ping_interval_seconds: z.coerce.number().min(1), }), }) type GlobalModelSettingsFormValues = z.output type GlobalModelSettingsFormInput = z.input type FlatGlobalModelSettings = { 'global.pass_through_request_enabled': boolean 'global.thinking_model_blacklist': string 'global.chat_completions_to_responses_policy': string 'general_setting.ping_interval_enabled': boolean 'general_setting.ping_interval_seconds': number } const flattenGlobalValues = ( values: GlobalModelSettingsFormValues ): FlatGlobalModelSettings => ({ 'global.pass_through_request_enabled': values.global.pass_through_request_enabled, 'global.thinking_model_blacklist': normalizeJsonText( values.global.thinking_model_blacklist, '[]' ), 'global.chat_completions_to_responses_policy': normalizeJsonText( values.global.chat_completions_to_responses_policy, '{}' ), 'general_setting.ping_interval_enabled': values.general_setting.ping_interval_enabled, 'general_setting.ping_interval_seconds': values.general_setting.ping_interval_seconds, }) function normalizeJsonText(value: string, fallback: string) { const trimmed = (value ?? '').toString().trim() return trimmed ? trimmed : fallback } type GlobalSettingsCardProps = { defaultValues: GlobalModelSettingsFormValues } export function GlobalSettingsCard({ defaultValues }: GlobalSettingsCardProps) { const { t } = useTranslation() const updateOption = useUpdateOption() const form = useForm< GlobalModelSettingsFormInput, unknown, GlobalModelSettingsFormValues >({ resolver: zodResolver(schema), defaultValues: defaultValues as GlobalModelSettingsFormInput, }) useEffect(() => { form.reset(defaultValues as GlobalModelSettingsFormInput) }, [defaultValues, form]) const pingEnabled = form.watch('general_setting.ping_interval_enabled') const formatJsonField = ( field: | 'global.thinking_model_blacklist' | 'global.chat_completions_to_responses_policy' ) => { const raw = form.getValues(field) if (!raw || !raw.trim()) return try { const formatted = JSON.stringify(JSON.parse(raw), null, 2) form.setValue(field, formatted, { shouldDirty: true }) } catch { toast.error(t('Invalid JSON format')) } } const onSubmit = async (values: GlobalModelSettingsFormValues) => { const flattenedDefaults = flattenGlobalValues(defaultValues) const flattenedValues = flattenGlobalValues(values) const updates = Object.entries(flattenedValues).filter( ([key, value]) => value !== flattenedDefaults[key as keyof FlatGlobalModelSettings] ) if (updates.length === 0) { toast.info(t('No changes to save')) return } for (const [key, value] of updates) { await updateOption.mutateAsync({ key, value, }) } } return (
(
{t('Enable Request Passthrough')} {t( 'Forward requests directly to upstream providers without any post-processing.' )}
)} /> ( {t('Disable thinking processing models')}