/* 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 . For commercial licensing, please contact support@quantumnous.com */ import { useCallback, useEffect, useState } 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 { api } from '@/lib/api' import dayjs from '@/lib/dayjs' import { Alert, AlertDescription } from '@/components/ui/alert' import { AlertDialog, AlertDialogAction, AlertDialogCancel, AlertDialogContent, AlertDialogDescription, AlertDialogFooter, AlertDialogHeader, AlertDialogTitle, AlertDialogTrigger, } from '@/components/ui/alert-dialog' import { Button } from '@/components/ui/button' import { Form, FormControl, FormDescription, FormField, FormItem, FormLabel, } from '@/components/ui/form' import { Input } from '@/components/ui/input' import { Label } from '@/components/ui/label' import { Progress } from '@/components/ui/progress' import { Select, SelectContent, SelectGroup, SelectItem, SelectTrigger, SelectValue, } from '@/components/ui/select' 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' const perfSchema = z.object({ 'performance_setting.disk_cache_enabled': z.boolean(), 'performance_setting.disk_cache_threshold_mb': z.coerce.number().min(1), 'performance_setting.disk_cache_max_size_mb': z.coerce.number().min(100), 'performance_setting.disk_cache_path': z.string().optional(), 'performance_setting.monitor_enabled': z.boolean(), 'performance_setting.monitor_cpu_threshold': z.coerce.number().min(0), 'performance_setting.monitor_memory_threshold': z.coerce .number() .min(0) .max(100), 'performance_setting.monitor_disk_threshold': z.coerce .number() .min(0) .max(100), 'perf_metrics_setting.enabled': z.boolean(), 'perf_metrics_setting.flush_interval': z.coerce.number().min(1), 'perf_metrics_setting.bucket_time': z.enum(['minute', '5min', 'hour']), 'perf_metrics_setting.retention_days': z.coerce.number().min(0), }) type PerfFormValues = z.infer function formatBytes(bytes: number, decimals = 2): string { if (!bytes || isNaN(bytes)) return '0 Bytes' if (bytes === 0) return '0 Bytes' if (bytes < 0) return '-' + formatBytes(-bytes, decimals) const k = 1024 const sizes = ['Bytes', 'KB', 'MB', 'GB', 'TB'] const i = Math.floor(Math.log(Math.abs(bytes)) / Math.log(k)) if (i < 0 || i >= sizes.length) return bytes + ' Bytes' return parseFloat((bytes / Math.pow(k, i)).toFixed(decimals)) + ' ' + sizes[i] } interface Props { defaultValues: PerfFormValues } type LogInfo = { enabled: boolean log_dir: string file_count: number total_size: number oldest_time?: string newest_time?: string } type PerformanceStats = { cache_stats?: { current_disk_usage_bytes: number disk_cache_max_bytes: number active_disk_files: number disk_cache_hits: number current_memory_usage_bytes: number active_memory_buffers: number memory_cache_hits: number } disk_space_info?: { total: number free: number used: number used_percent: number } memory_stats?: { alloc: number total_alloc: number sys: number num_gc: number num_goroutine: number } disk_cache_info?: { path: string file_count: number total_size: number } config?: { is_running_in_container: boolean } } export function PerformanceSection(props: Props) { const { t } = useTranslation() const updateOption = useUpdateOption() const [stats, setStats] = useState(null) const [logInfo, setLogInfo] = useState(null) const [logCleanupMode, setLogCleanupMode] = useState('by_count') const [logCleanupValue, setLogCleanupValue] = useState(10) const [logCleanupLoading, setLogCleanupLoading] = useState(false) const form = useForm({ // eslint-disable-next-line @typescript-eslint/no-explicit-any resolver: zodResolver(perfSchema) as any, defaultValues: props.defaultValues, }) // eslint-disable-next-line @typescript-eslint/no-explicit-any useResetForm(form as any, props.defaultValues) const fetchStats = useCallback(async () => { try { const res = await api.get('/api/performance/stats') if (res.data.success) setStats(res.data.data) } catch { /* ignore */ } }, []) const fetchLogInfo = useCallback(async () => { try { const res = await api.get('/api/performance/logs') if (res.data.success) setLogInfo(res.data.data) } catch { /* ignore */ } }, []) useEffect(() => { fetchStats() fetchLogInfo() }, [fetchStats, fetchLogInfo]) const onSubmit = async (data: PerfFormValues) => { const entries = Object.entries(data) as [string, unknown][] const updates = entries.filter( ([key, value]) => value !== (props.defaultValues[key as keyof PerfFormValues] as unknown) ) if (updates.length === 0) { toast.info(t('No changes to save')) return } for (const [key, value] of updates) { await updateOption.mutateAsync({ key, value: value as string | number | boolean, }) } toast.success(t('Saved successfully')) fetchStats() } const clearDiskCache = async () => { try { const res = await api.delete('/api/performance/disk_cache') if (res.data.success) { toast.success(t('Disk cache cleared')) fetchStats() } } catch { toast.error(t('Cleanup failed')) } } const resetStats = async () => { try { const res = await api.post('/api/performance/reset_stats') if (res.data.success) { toast.success(t('Statistics reset')) fetchStats() } } catch { toast.error(t('Reset failed')) } } const forceGC = async () => { try { const res = await api.post('/api/performance/gc') if (res.data.success) { toast.success(t('GC executed')) fetchStats() } } catch { toast.error(t('GC execution failed')) } } const cleanupLogFiles = async () => { if (!logCleanupValue || isNaN(logCleanupValue) || logCleanupValue < 1) { toast.error(t('Please enter a valid number')) return } setLogCleanupLoading(true) try { const res = await api.delete( `/api/performance/logs?mode=${logCleanupMode}&value=${logCleanupValue}` ) if (res.data.success) { const { deleted_count, freed_bytes } = res.data.data toast.success( t('Cleaned up {{count}} log files, freed {{size}}', { count: deleted_count, size: formatBytes(freed_bytes), }) ) } else { toast.error(res.data.message || t('Cleanup failed')) } fetchLogInfo() } catch { toast.error(t('Cleanup failed')) } finally { setLogCleanupLoading(false) } } const diskEnabled = form.watch('performance_setting.disk_cache_enabled') const monitorEnabled = form.watch('performance_setting.monitor_enabled') const perfMetricsEnabled = form.watch('perf_metrics_setting.enabled') const maxCacheSizeMb = form.watch( 'performance_setting.disk_cache_max_size_mb' ) const lowDiskSpace = diskEnabled && stats?.disk_space_info && stats.disk_space_info.free > 0 && maxCacheSizeMb > 0 && stats.disk_space_info.free < maxCacheSizeMb * 1024 * 1024 const diskCachePercent = stats?.cache_stats?.disk_cache_max_bytes && stats.cache_stats.disk_cache_max_bytes > 0 ? Math.round( (stats.cache_stats.current_disk_usage_bytes / stats.cache_stats.disk_cache_max_bytes) * 100 ) : 0 return (
{/* Disk Cache Settings */}

{t('Disk Cache Settings')}

{t( 'When enabled, large request bodies are temporarily stored on disk instead of memory, significantly reducing memory usage. SSD recommended.' )}

( {t('Enable Disk Cache')} )} /> ( {t('Disk Cache Threshold (MB)')} {t('Use disk cache when request body exceeds this size')} )} /> ( {t('Max Disk Cache Size (MB)')} {stats?.disk_space_info && stats.disk_space_info.total > 0 && ( {t('Free: {{free}} / Total: {{total}}', { free: formatBytes(stats.disk_space_info.free), total: formatBytes(stats.disk_space_info.total), })} )} )} />
{lowDiskSpace && ( {`${t('Warning')}: ${t('Available disk space')} (${formatBytes(stats?.disk_space_info?.free ?? 0)}) ${t('is less than the configured maximum cache size')} (${maxCacheSizeMb} MB). ${t('This may cause cache failures.')}`} )} {!stats?.config?.is_running_in_container && ( ( {t('Cache Directory')} )} /> )} {/* System Performance Monitor */}

{t('System Performance Monitoring')}

{t( 'When performance monitoring is enabled and system resource usage exceeds the set threshold, new Relay requests will be rejected.' )}

( {t('Enable Performance Monitoring')} )} /> ( {t('CPU Threshold (%)')} )} /> ( {t('Memory Threshold (%)')} )} /> ( {t('Disk Threshold (%)')} )} />

{t('Model performance metrics')}

{t( 'Collect relay latency and success-rate metrics for the model square.' )}

( {t('Enable model performance metrics')} )} /> ( {t('Flush interval (minutes)')} )} /> ( {t('Aggregation bucket')} )} /> ( {t('Retention days')} {t('0 means data is kept permanently')} )} />
{/* Server Log Management */}

{t('Server Log Management')}

{t( 'Manage server log files. Log files accumulate over time; regular cleanup is recommended to free disk space.' )}

{logInfo === null ? null : logInfo.enabled ? (
{t('Log Directory')}: {' '} {logInfo.log_dir}
{t('Log File Count')}: {' '} {logInfo.file_count}
{t('Total Log Size')}: {' '} {formatBytes(logInfo.total_size)}
{logInfo.oldest_time && logInfo.newest_time && (
{t('Date Range')}: {' '} {dayjs(logInfo.oldest_time).format('YYYY-MM-DD')} ~{' '} {dayjs(logInfo.newest_time).format('YYYY-MM-DD')}
)}
setLogCleanupValue(Number(e.target.value))} className='w-[120px]' />
} > {logCleanupLoading ? t('Cleaning...') : t('Clean Up Log Files')} {t('Confirm log file cleanup?')} {logCleanupMode === 'by_count' ? t( 'Only the last {{value}} log files will be retained; the rest will be deleted.', { value: logCleanupValue, } ) : t( 'Log files older than {{value}} days will be deleted.', { value: logCleanupValue, } )} {t('Cancel')} {t('Confirm Cleanup')}
) : ( {t( 'Server logging is not enabled (log directory not configured)' )} )}
{/* Performance Stats Dashboard */}

{t('Performance Monitor')}

}> {t('Clean up inactive cache')} {t('Confirm cleanup of inactive disk cache?')} {t( 'This will delete temporary cache files that have not been used for more than 10 minutes' )} {t('Cancel')} {t('Confirm')}
{stats && ( <>

{t('Request Body Disk Cache')}

{formatBytes( stats.cache_stats?.current_disk_usage_bytes ?? 0 )}{' '} /{' '} {formatBytes(stats.cache_stats?.disk_cache_max_bytes ?? 0)} {t('Active Files')}:{' '} {stats.cache_stats?.active_disk_files ?? 0}
{t('Disk Hits')}: {stats.cache_stats?.disk_cache_hits ?? 0}

{t('Request Body Memory Cache')}

{t('Current Cache Size')}:{' '} {formatBytes( stats.cache_stats?.current_memory_usage_bytes ?? 0 )} {t('Active Cache Count')}:{' '} {stats.cache_stats?.active_memory_buffers ?? 0}
{t('Memory Hits')}:{' '} {stats.cache_stats?.memory_cache_hits ?? 0}
{stats.disk_space_info && stats.disk_space_info.total > 0 && (

{t('Cache Directory Disk Space')}

{t('Used')}: {formatBytes(stats.disk_space_info.used)} {t('Available')}: {formatBytes(stats.disk_space_info.free)} {t('Total')}: {formatBytes(stats.disk_space_info.total)}
)} {stats.memory_stats && (

{t('System Memory Stats')}

{t('Allocated Memory')}: {' '} {formatBytes(stats.memory_stats.alloc)}
{t('Total Allocated')}: {' '} {formatBytes(stats.memory_stats.total_alloc)}
{t('System Memory')}: {' '} {formatBytes(stats.memory_stats.sys)}
{t('GC Count')}: {' '} {stats.memory_stats.num_gc}
Goroutines:{' '} {stats.memory_stats.num_goroutine}
)} {stats.disk_cache_info && (

{t('Cache Directory Info')}

{t('Cache Directory')}: {' '} {stats.disk_cache_info.path}
{t('Directory File Count')}: {' '} {stats.disk_cache_info.file_count}
{t('Directory Total Size')}: {' '} {formatBytes(stats.disk_cache_info.total_size)}
)} )}
) }