import { useEffect, useState, type FormEvent } from 'react'; import { Database, Pencil, Plus, RotateCcw, Save, ServerCog, Settings2, ShieldCheck, Trash2 } from 'lucide-react'; import type { ClientCustomizationSettings, ClientCustomizationSettingsUpdateRequest, 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'; import { UnifiedIdentityPanel } from './UnifiedIdentityPanel'; type SystemSettingsTab = 'fileStorage' | 'clientCustomization' | 'identity'; type ClientCustomizationForm = { clientEnglishName: string; clientName: string; iconPath: string; platformName: string; }; 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', 'request_asset']; const sceneOptions = [ { value: 'upload', label: '上传', description: 'OpenAPI / 管理端主动上传文件' }, { value: 'image_result', label: '生成媒体结果', description: '模型返回 base64 / buffer 图片、音频或视频后的转存' }, { value: 'request_asset', label: '请求素材', description: '将 multipart / base64 输入转换为跨节点可读取的公共 URL' }, ]; 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 临时写入本地静态文件;数据库仅保存占位符,默认 24 小时内按需恢复' }, ]; export function SystemSettingsPanel(props: { token: string; channels: FileStorageChannel[]; clientCustomizationSettings: ClientCustomizationSettings | null; message: string; settings: FileStorageSettings | null; state: LoadState; onDeleteFileStorageChannel: (channelId: string) => Promise; onSaveClientCustomizationSettings: (input: ClientCustomizationSettingsUpdateRequest) => 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 [clientCustomizationForm, setClientCustomizationForm] = useState(() => clientCustomizationSettingsToForm(props.clientCustomizationSettings)); const [settingsPolicy, setSettingsPolicy] = useState(() => normalizeResultUploadPolicy(props.settings?.resultUploadPolicy)); const [localError, setLocalError] = useState(''); useEffect(() => { setSettingsPolicy(normalizeResultUploadPolicy(props.settings?.resultUploadPolicy)); }, [props.settings?.resultUploadPolicy]); useEffect(() => { setClientCustomizationForm(clientCustomizationSettingsToForm(props.clientCustomizationSettings)); }, [props.clientCustomizationSettings]); 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 : '文件存储全局策略保存失败'); } } async function saveClientCustomization() { setLocalError(''); try { await props.onSaveClientCustomizationSettings(clientCustomizationFormToPayload(clientCustomizationForm)); } catch (err) { setLocalError(err instanceof Error ? err.message : '客户端自定义设置保存失败'); } } return (
系统设置

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

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

{localError || props.message}

} }, { value: 'clientCustomization', label: '客户端自定义', icon: }, { value: 'identity', label: '统一认证', icon: }, ]} onValueChange={setActiveTab} />
{activeTab === 'fileStorage' && (
全局生成媒体转存策略 对图片、音频和视频生成结果统一生效;启用转存但没有可用渠道时,回退到保留 24 小时的网关本地静态资源。
文件存储渠道 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 && ( 暂无文件存储渠道 )}
)} {activeTab === 'clientCustomization' && (
客户端自定义 配置公开展示用的客户端名称、平台名和图标路径;公开 API 无需鉴权读取。
)} {activeTab === 'identity' && } )} open={dialogOpen} title={editingChannel ? '编辑文件存储渠道' : '新增文件存储渠道'} onClose={closeDialog} onSubmit={submit} >