import React, { useEffect, useState } from 'react'; import { Button, Card, Form, Input, InputNumber, message, Modal, Popconfirm, Select, Space, Switch, Table, Tag, Typography, } from 'antd'; import { PlayCircleOutlined, PlusOutlined, EditOutlined, DeleteOutlined, } from '@ant-design/icons'; import { getVideoEngines, saveVideoEngine, deleteVideoEngine } from '../api'; interface VideoEngine { id: string; name: string; provider: string; apiBase: string; apiKey: string; modelName: string; supportedRatios: string[]; supportedResolutions: string[]; supportedDurations: number[]; maxDuration: number; maxImageCount: number; maxVideoCount: number; maxAudioCount: number; supportsFirstLastFrame: boolean; supportsUniversalReference: boolean; isActive: boolean; priority: number; multiGenerationEnabled: boolean; maxGenerationCount: number; } function parseJsonArray(val: unknown): any[] { if (Array.isArray(val)) return val; if (typeof val === 'string') { try { return JSON.parse(val); } catch { return []; } } return []; } const AdminVideoEngines: React.FC = () => { const [engines, setEngines] = useState([]); const [loading, setLoading] = useState(false); const [modal, setModal] = useState<{ open: boolean; engine: VideoEngine | null }>({ open: false, engine: null }); const [form] = Form.useForm(); const multiGenerationEnabled = Form.useWatch('multiGenerationEnabled', form) ?? false; const load = async () => { setLoading(true); try { const data = await getVideoEngines(); setEngines(data.map((e: any) => ({ ...e, supportedRatios: parseJsonArray(e.supportedRatios), supportedResolutions: parseJsonArray(e.supportedResolutions), supportedDurations: parseJsonArray(e.supportedDurations), }))); } catch { message.error('加载视频引擎失败'); } finally { setLoading(false); } }; useEffect(() => { load(); }, []); const handleSave = async () => { try { const values = await form.validateFields(); const payload = { name: values.name, provider: values.provider, api_base: values.apiBase, api_key: values.apiKey, model_name: values.modelName, supported_ratios: JSON.stringify(values.supportedRatios || []), supported_resolutions: JSON.stringify(values.supportedResolutions || []), supported_durations: JSON.stringify(values.supportedDurations || []), max_duration: values.maxDuration ?? 15, max_image_count: values.maxImageCount ?? 2, max_video_count: values.maxVideoCount ?? 0, max_audio_count: values.maxAudioCount ?? 0, supports_first_last_frame: values.supportsFirstLastFrame ?? false, supports_universal_reference: values.supportsUniversalReference ?? true, is_active: values.isActive ?? true, priority: values.priority ?? 0, multi_generation_enabled: values.multiGenerationEnabled ?? false, max_generation_count: values.maxGenerationCount ?? 1, }; if (modal.engine) { await saveVideoEngine({ id: modal.engine.id, ...payload }); message.success('已更新'); } else { await saveVideoEngine(payload); message.success('已添加'); } setModal({ open: false, engine: null }); form.resetFields(); load(); } catch (e: any) { if (e?.errorFields) return; message.error(e?.message || '保存失败'); } }; const handleDelete = async (id: string) => { try { await deleteVideoEngine(id); message.success('已删除'); load(); } catch { message.error('删除失败'); } }; const openEdit = (engine?: VideoEngine) => { setModal({ open: true, engine: engine || null }); if (engine) { form.setFieldsValue(engine); } else { form.resetFields(); form.setFieldsValue({ isActive: true, priority: 0, multiGenerationEnabled: false, maxGenerationCount: 1, maxDuration: 15, maxImageCount: 2, maxVideoCount: 0, maxAudioCount: 0, supportsFirstLastFrame: false, supportsUniversalReference: true, supportedRatios: ['16:9', '4:3', '1:1', '3:4', '9:16', '21:9'], supportedResolutions: ['480p', '720p', '1080p'], supportedDurations: [4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30], }); } }; const columns = [ { title: '引擎名称', key: 'name', width: 180, render: (_: any, r: VideoEngine) => (
{r.name}
{r.provider}
), }, { title: '支持比例', dataIndex: 'supportedRatios', width: 200, render: (ratios: string[]) => {ratios.map(r => {r})}, }, { title: '支持分辨率', dataIndex: 'supportedResolutions', width: 150, render: (res: string[]) => {res.map(r => {r})}, }, { title: '支持时长', dataIndex: 'supportedDurations', width: 120, render: (d: number[]) => {d?.length ? `${Math.min(...d)}-${Math.max(...d)}s` : '-'}, }, { title: '最大图片', dataIndex: 'maxImageCount', width: 100, render: (v: number) => {v} 张, }, { title: '最大视频', dataIndex: 'maxVideoCount', width: 100, render: (v: number) => {v} 个, }, { title: '最大音频', dataIndex: 'maxAudioCount', width: 100, render: (v: number) => 0 ? 'geekblue' : 'default'}>{v || 0} 段, }, { title: '首尾帧', dataIndex: 'supportsFirstLastFrame', width: 90, render: (v: boolean) => {v ? '支持' : '不支持'}, }, { title: '全能参考', dataIndex: 'supportsUniversalReference', width: 100, render: (v: boolean) => {v ? '支持' : '不支持'}, }, { title: '多份生成', dataIndex: 'multiGenerationEnabled', width: 100, render: (v: boolean) => {v ? '开启' : '关闭'}, }, { title: '数量上限', dataIndex: 'maxGenerationCount', width: 100, render: (v: number, r: VideoEngine) => ( 1 ? 'magenta' : 'default'}> 最多 {r.multiGenerationEnabled ? (v || 1) : 1} 份 ), }, { title: '状态', dataIndex: 'isActive', width: 80, render: (v: boolean) => {v ? '启用' : '停用'}, }, { title: '操作', key: 'action', width: 150, fixed: 'right' as const, render: (_: any, r: VideoEngine) => ( handleDelete(r.id)}> ), }, ]; return (
视频引擎配置 {engines.length} 个引擎
{modal.engine ? '编辑引擎' : '添加引擎'}} open={modal.open} onOk={handleSave} onCancel={() => { setModal({ open: false, engine: null }); form.resetFields(); }} okText="保存" cancelText="取消" width={620} >
{ const n = Number(value ?? 0); // if (!Number.isInteger(n) || n < 0 || n > 3) { // return Promise.reject(new Error('最大参考音频数必须为 0-3 的整数')); // } return Promise.resolve(); }, }, ]} >
多份生成能力 管理后台只控制是否允许客户端选择多份及最大数量;客户端每次可在 1 到上限之间选择。