import { useEffect, useState, type FormEvent } from 'react'; import { Database, Pencil, Plus, RotateCcw, Save, ServerCog, Trash2 } from 'lucide-react'; import type { FileStorageChannel, FileStorageChannelUpsertRequest, FileStorageSettings, FileStorageSettingsUpdateRequest } from '@easyai-ai-gateway/contracts'; import { Badge, Button, Card, CardContent, CardHeader, CardTitle, ConfirmDialog, FormDialog, Input, Label, Select, Tabs, Textarea } from '../../components/ui'; import type { LoadState } from '../../types'; type SystemSettingsTab = 'fileStorage'; type FileStorageChannelForm = { apiKey: string; apiKeyPreview: string; channelKey: string; configJson: string; name: string; priority: string; provider: string; retryPolicyJson: string; scenes: string[]; status: string; uploadUrl: string; }; const defaultUploadUrl = 'http://127.0.0.1:3001/v1/files/upload'; const defaultRetryPolicy = { enabled: true, maxRetries: 3, backoffSeconds: [60, 120, 180], strategy: 'exponential', }; const providerOptions = [ { value: 'server_main_openapi', label: 'server-main OpenAPI' }, { value: 'aliyun_oss', label: '阿里云 OSS' }, { value: 'tencent_cos', label: '腾讯云 COS' }, ]; const defaultScenes = ['upload', 'image_result']; const sceneOptions = [ { value: 'upload', label: '上传', description: 'OpenAPI / 管理端主动上传文件' }, { value: 'image_result', label: '返图', description: '模型返回 base64 / buffer 图片或视频后的转存' }, ]; const resultUploadPolicyOptions = [ { value: 'default', label: '默认:仅非链接资源转存', description: 'URL 结果直接保存;base64 / buffer 等结果转存后保存 URL' }, { value: 'upload_all', label: '全部转存', description: 'URL、base64、buffer 等返图结果都会转存到当前文件渠道' }, { value: 'upload_none', label: '全部不转存', description: '链接结果直接保存;base64 / buffer 结果写入网关本地静态托管后保存 URL' }, ]; export function SystemSettingsPanel(props: { channels: FileStorageChannel[]; message: string; settings: FileStorageSettings | null; state: LoadState; onDeleteFileStorageChannel: (channelId: string) => Promise; onSaveFileStorageChannel: (input: FileStorageChannelUpsertRequest, channelId?: string) => Promise; onSaveFileStorageSettings: (input: FileStorageSettingsUpdateRequest) => Promise; }) { const [activeTab, setActiveTab] = useState('fileStorage'); const [dialogOpen, setDialogOpen] = useState(false); const [editingChannel, setEditingChannel] = useState(null); const [pendingDeleteChannel, setPendingDeleteChannel] = useState(null); const [form, setForm] = useState(() => defaultChannelForm()); const [settingsPolicy, setSettingsPolicy] = useState(() => normalizeResultUploadPolicy(props.settings?.resultUploadPolicy)); const [localError, setLocalError] = useState(''); useEffect(() => { setSettingsPolicy(normalizeResultUploadPolicy(props.settings?.resultUploadPolicy)); }, [props.settings?.resultUploadPolicy]); function openCreateDialog() { setEditingChannel(null); setForm(defaultChannelForm(`server-main-${Date.now().toString(36)}`)); setLocalError(''); setDialogOpen(true); } function editChannel(channel: FileStorageChannel) { setEditingChannel(channel); setForm(channelToForm(channel)); setLocalError(''); setDialogOpen(true); } function closeDialog() { setEditingChannel(null); setForm(defaultChannelForm()); setLocalError(''); setDialogOpen(false); } async function submit(event: FormEvent) { event.preventDefault(); setLocalError(''); if (form.scenes.length === 0) { setLocalError('请至少选择一个适用场景。'); return; } try { await props.onSaveFileStorageChannel(formToPayload(form), editingChannel?.id); closeDialog(); } catch (err) { setLocalError(err instanceof Error ? err.message : '文件存储渠道保存失败'); } } async function deleteChannel(channel: FileStorageChannel) { try { await props.onDeleteFileStorageChannel(channel.id); setPendingDeleteChannel(null); if (editingChannel?.id === channel.id) closeDialog(); } catch (err) { setLocalError(err instanceof Error ? err.message : '文件存储渠道删除失败'); } } async function saveSettings() { setLocalError(''); try { await props.onSaveFileStorageSettings({ resultUploadPolicy: normalizeResultUploadPolicy(settingsPolicy) }); } catch (err) { setLocalError(err instanceof Error ? err.message : '文件存储全局策略保存失败'); } } return (
系统设置

集中维护网关级配置;文件存储渠道按优先级轮转,单渠道使用 60/120/180 秒退避重试。

{props.channels.length} 个文件渠道
{(props.message || localError) &&

{localError || props.message}

} }]} onValueChange={setActiveTab} />
{activeTab === 'fileStorage' && (
全局返图转存策略 对所有返图场景统一生效;渠道只负责上传目标、凭证、重试和轮转。
文件存储渠道 server-main OpenAPI 渠道只需要上传路由和 API Key。
{props.channels.map((channel) => (
{channel.name} {channel.channelKey}
{channel.status}
渠道: {providerLabel(channel.provider)} 场景: {sceneSummary(channel.scenes)} 优先级: {channel.priority} 重试: {retryPolicySummary(channel.retryPolicy)} {channel.uploadUrl && 上传路由: {channel.uploadUrl}} {apiKeyPreview(channel) && API Key: {apiKeyPreview(channel)}} {channel.lastError && 最近错误: {channel.lastError}}
))} {!props.channels.length && ( 暂无文件存储渠道 )}
)} )} open={dialogOpen} title={editingChannel ? '编辑文件存储渠道' : '新增文件存储渠道'} onClose={closeDialog} onSubmit={submit} >