Calcium-Ion d7c55b92b2
Some checks failed
Publish Docker image (Multi Registries, native amd64+arm64) / Build & push (amd64) [native] (push) Has been cancelled
Publish Docker image (Multi Registries, native amd64+arm64) / Build & push (arm64) [native] (push) Has been cancelled
Publish Docker image (Multi Registries, native amd64+arm64) / Create multi-arch manifests (Docker Hub) (push) Has been cancelled
feat: disk request body cache (#2780)
* feat: 引入通用 HTTP BodyStorage/DiskCache 缓存配置与管理

- 新增 common/body_storage.go 提供 HTTP 请求体存储抽象和文件缓存能力
- 增加 common/disk_cache_config.go 支持全局磁盘缓存配置
- main.go 挂载缓存初始化流程
- 新增和补充 controller/performance.go (及 unix/windows) 用于缓存性能监控接口
- middleware/body_cleanup.go 自动清理缓存文件
- router 挂载相关接口
- 前端 settings 页面新增性能监控设置 PerformanceSetting
- 优化缓存开关状态和模块热插拔能力
- 其他相关文件同步适配缓存扩展

* fix: 修复 BodyStorage 并发安全和错误处理问题

- 修复 diskStorage.Close() 竞态条件,先获取锁再执行 CAS
- 为 memoryStorage 添加互斥锁和 closed 状态检查
- 修复 CreateBodyStorageFromReader 在磁盘存储失败时的回退逻辑
- 添加缓存命中统计调用 (IncrementDiskCacheHits/IncrementMemoryCacheHits)
- 修复 gin.go 中 Seek 错误被忽略的问题
- 在 api-router 添加 BodyStorageCleanup 中间件
- 修复前端 formatBytes 对异常值的处理

Co-authored-by: Cursor <cursoragent@cursor.com>

---------

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-01-30 01:00:49 +08:00

218 lines
6.3 KiB
JavaScript

/*
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 <https://www.gnu.org/licenses/>.
For commercial licensing, please contact support@quantumnous.com
*/
import React, { useEffect, useState } from 'react';
import { Layout, TabPane, Tabs } from '@douyinfe/semi-ui';
import { useNavigate, useLocation } from 'react-router-dom';
import { useTranslation } from 'react-i18next';
import {
Settings,
Calculator,
Gauge,
Shapes,
Cog,
MoreHorizontal,
LayoutDashboard,
MessageSquare,
Palette,
CreditCard,
Server,
Activity,
} from 'lucide-react';
import SystemSetting from '../../components/settings/SystemSetting';
import { isRoot } from '../../helpers';
import OtherSetting from '../../components/settings/OtherSetting';
import OperationSetting from '../../components/settings/OperationSetting';
import RateLimitSetting from '../../components/settings/RateLimitSetting';
import ModelSetting from '../../components/settings/ModelSetting';
import DashboardSetting from '../../components/settings/DashboardSetting';
import RatioSetting from '../../components/settings/RatioSetting';
import ChatsSetting from '../../components/settings/ChatsSetting';
import DrawingSetting from '../../components/settings/DrawingSetting';
import PaymentSetting from '../../components/settings/PaymentSetting';
import ModelDeploymentSetting from '../../components/settings/ModelDeploymentSetting';
import PerformanceSetting from '../../components/settings/PerformanceSetting';
const Setting = () => {
const { t } = useTranslation();
const navigate = useNavigate();
const location = useLocation();
const [tabActiveKey, setTabActiveKey] = useState('1');
let panes = [];
if (isRoot()) {
panes.push({
tab: (
<span style={{ display: 'flex', alignItems: 'center', gap: '5px' }}>
<Settings size={18} />
{t('运营设置')}
</span>
),
content: <OperationSetting />,
itemKey: 'operation',
});
panes.push({
tab: (
<span style={{ display: 'flex', alignItems: 'center', gap: '5px' }}>
<LayoutDashboard size={18} />
{t('仪表盘设置')}
</span>
),
content: <DashboardSetting />,
itemKey: 'dashboard',
});
panes.push({
tab: (
<span style={{ display: 'flex', alignItems: 'center', gap: '5px' }}>
<MessageSquare size={18} />
{t('聊天设置')}
</span>
),
content: <ChatsSetting />,
itemKey: 'chats',
});
panes.push({
tab: (
<span style={{ display: 'flex', alignItems: 'center', gap: '5px' }}>
<Palette size={18} />
{t('绘图设置')}
</span>
),
content: <DrawingSetting />,
itemKey: 'drawing',
});
panes.push({
tab: (
<span style={{ display: 'flex', alignItems: 'center', gap: '5px' }}>
<CreditCard size={18} />
{t('支付设置')}
</span>
),
content: <PaymentSetting />,
itemKey: 'payment',
});
panes.push({
tab: (
<span style={{ display: 'flex', alignItems: 'center', gap: '5px' }}>
<Calculator size={18} />
{t('分组与模型定价设置')}
</span>
),
content: <RatioSetting />,
itemKey: 'ratio',
});
panes.push({
tab: (
<span style={{ display: 'flex', alignItems: 'center', gap: '5px' }}>
<Gauge size={18} />
{t('速率限制设置')}
</span>
),
content: <RateLimitSetting />,
itemKey: 'ratelimit',
});
panes.push({
tab: (
<span style={{ display: 'flex', alignItems: 'center', gap: '5px' }}>
<Shapes size={18} />
{t('模型相关设置')}
</span>
),
content: <ModelSetting />,
itemKey: 'models',
});
panes.push({
tab: (
<span style={{ display: 'flex', alignItems: 'center', gap: '5px' }}>
<Server size={18} />
{t('模型部署设置')}
</span>
),
content: <ModelDeploymentSetting />,
itemKey: 'model-deployment',
});
panes.push({
tab: (
<span style={{ display: 'flex', alignItems: 'center', gap: '5px' }}>
<Activity size={18} />
{t('性能设置')}
</span>
),
content: <PerformanceSetting />,
itemKey: 'performance',
});
panes.push({
tab: (
<span style={{ display: 'flex', alignItems: 'center', gap: '5px' }}>
<Cog size={18} />
{t('系统设置')}
</span>
),
content: <SystemSetting />,
itemKey: 'system',
});
panes.push({
tab: (
<span style={{ display: 'flex', alignItems: 'center', gap: '5px' }}>
<MoreHorizontal size={18} />
{t('其他设置')}
</span>
),
content: <OtherSetting />,
itemKey: 'other',
});
}
const onChangeTab = (key) => {
setTabActiveKey(key);
navigate(`?tab=${key}`);
};
useEffect(() => {
const searchParams = new URLSearchParams(window.location.search);
const tab = searchParams.get('tab');
if (tab) {
setTabActiveKey(tab);
} else {
onChangeTab('operation');
}
}, [location.search]);
return (
<div className='mt-[60px] px-2'>
<Layout>
<Layout.Content>
<Tabs
type='card'
collapsible
activeKey={tabActiveKey}
onChange={(key) => onChangeTab(key)}
>
{panes.map((pane) => (
<TabPane itemKey={pane.itemKey} tab={pane.tab} key={pane.itemKey}>
{tabActiveKey === pane.itemKey && pane.content}
</TabPane>
))}
</Tabs>
</Layout.Content>
</Layout>
</div>
);
};
export default Setting;