/* Copyright (C) 2025 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 React, { useEffect, useState, useRef } from 'react'; import { Banner, Button, Col, Form, Row, Spin, Progress, Descriptions, Tag, Popconfirm, Typography, } from '@douyinfe/semi-ui'; import { compareObjects, API, showError, showSuccess, showWarning, } from '../../../helpers'; import { useTranslation } from 'react-i18next'; const { Text } = Typography; // 格式化字节大小 function formatBytes(bytes, decimals = 2) { if (bytes === null || bytes === undefined || isNaN(bytes)) return '0 Bytes'; if (bytes === 0) return '0 Bytes'; if (bytes < 0) return '-' + formatBytes(-bytes, decimals); const k = 1024; const dm = decimals < 0 ? 0 : decimals; const sizes = ['Bytes', 'KB', 'MB', 'GB', 'TB']; const i = Math.floor(Math.log(bytes) / Math.log(k)); if (i < 0 || i >= sizes.length) return bytes + ' Bytes'; return parseFloat((bytes / Math.pow(k, i)).toFixed(dm)) + ' ' + sizes[i]; } export default function SettingsPerformance(props) { const { t } = useTranslation(); const [loading, setLoading] = useState(false); const [statsLoading, setStatsLoading] = useState(false); const [stats, setStats] = useState(null); const [inputs, setInputs] = useState({ 'performance_setting.disk_cache_enabled': false, 'performance_setting.disk_cache_threshold_mb': 10, 'performance_setting.disk_cache_max_size_mb': 1024, 'performance_setting.disk_cache_path': '', }); const refForm = useRef(); const [inputsRow, setInputsRow] = useState(inputs); function handleFieldChange(fieldName) { return (value) => { setInputs((inputs) => ({ ...inputs, [fieldName]: value })); }; } function onSubmit() { const updateArray = compareObjects(inputs, inputsRow); if (!updateArray.length) return showWarning(t('你似乎并没有修改什么')); const requestQueue = updateArray.map((item) => { let value = ''; if (typeof inputs[item.key] === 'boolean') { value = String(inputs[item.key]); } else { value = String(inputs[item.key]); } return API.put('/api/option/', { key: item.key, value, }); }); setLoading(true); Promise.all(requestQueue) .then((res) => { if (requestQueue.length === 1) { if (res.includes(undefined)) return; } else if (requestQueue.length > 1) { if (res.includes(undefined)) return showError(t('部分保存失败,请重试')); } showSuccess(t('保存成功')); props.refresh(); fetchStats(); }) .catch(() => { showError(t('保存失败,请重试')); }) .finally(() => { setLoading(false); }); } async function fetchStats() { setStatsLoading(true); try { const res = await API.get('/api/performance/stats'); if (res.data.success) { setStats(res.data.data); } } catch (error) { console.error('Failed to fetch performance stats:', error); } finally { setStatsLoading(false); } } async function clearDiskCache() { try { const res = await API.delete('/api/performance/disk_cache'); if (res.data.success) { showSuccess(t('磁盘缓存已清理')); fetchStats(); } else { showError(res.data.message || t('清理失败')); } } catch (error) { showError(t('清理失败')); } } async function resetStats() { try { const res = await API.post('/api/performance/reset_stats'); if (res.data.success) { showSuccess(t('统计已重置')); fetchStats(); } } catch (error) { showError(t('重置失败')); } } async function forceGC() { try { const res = await API.post('/api/performance/gc'); if (res.data.success) { showSuccess(t('GC 已执行')); fetchStats(); } } catch (error) { showError(t('GC 执行失败')); } } useEffect(() => { const currentInputs = {}; for (let key in props.options) { if (Object.keys(inputs).includes(key)) { if (typeof inputs[key] === 'boolean') { currentInputs[key] = props.options[key] === 'true' || props.options[key] === true; } else if (typeof inputs[key] === 'number') { currentInputs[key] = parseInt(props.options[key]) || inputs[key]; } else { currentInputs[key] = props.options[key]; } } } setInputs({ ...inputs, ...currentInputs }); setInputsRow({ ...inputs, ...currentInputs }); if (refForm.current) { refForm.current.setValues({ ...inputs, ...currentInputs }); } fetchStats(); }, [props.options]); const diskCacheUsagePercent = stats?.cache_stats?.disk_cache_max_bytes > 0 ? ( (stats.cache_stats.current_disk_usage_bytes / stats.cache_stats.disk_cache_max_bytes) * 100 ).toFixed(1) : 0; return ( <>
(refForm.current = formAPI)} style={{ marginBottom: 15 }} > 0 ? t('可用空间: {{free}} / 总空间: {{total}}', { free: formatBytes(stats.disk_space_info.free), total: formatBytes(stats.disk_space_info.total), }) : t('磁盘缓存占用的最大空间') } min={100} max={102400} onChange={handleFieldChange( 'performance_setting.disk_cache_max_size_mb', )} disabled={!inputs['performance_setting.disk_cache_enabled']} /> {/* 只在非容器环境显示缓存目录配置 */} {!stats?.config?.is_running_in_container && ( )}
{/* 性能统计 */}
{stats && ( <> {/* 缓存使用情况 */}
{t('请求体磁盘缓存')} 80 ? 'var(--semi-color-danger)' : 'var(--semi-color-primary)' } />
{formatBytes( stats.cache_stats.current_disk_usage_bytes, )}{' '} / {formatBytes(stats.cache_stats.disk_cache_max_bytes)} {t('活跃文件')}: {stats.cache_stats.active_disk_files}
{t('磁盘命中')}: {stats.cache_stats.disk_cache_hits}
{t('请求体内存缓存')}
{t('当前缓存大小')}:{' '} {formatBytes( stats.cache_stats.current_memory_usage_bytes, )} {t('活跃缓存数')}:{' '} {stats.cache_stats.active_memory_buffers}
{t('内存命中')}: {stats.cache_stats.memory_cache_hits}
{/* 缓存目录磁盘空间 */} {stats.disk_space_info?.total > 0 && (
{t('缓存目录磁盘空间')} 90 ? 'var(--semi-color-danger)' : stats.disk_space_info.used_percent > 70 ? 'var(--semi-color-warning)' : 'var(--semi-color-primary)' } />
{t('已用')}: {formatBytes(stats.disk_space_info.used)} {t('可用')}: {formatBytes(stats.disk_space_info.free)} {t('总计')}:{' '} {formatBytes(stats.disk_space_info.total)}
{stats.disk_space_info.free < inputs['performance_setting.disk_cache_max_size_mb'] * 1024 * 1024 && ( )}
)} {/* 系统内存统计 */} )}
); }