215 lines
8.0 KiB
TypeScript
215 lines
8.0 KiB
TypeScript
import React, { 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';
|
|
|
|
interface VideoEngine {
|
|
id: string;
|
|
name: string;
|
|
provider: string;
|
|
apiBase: string;
|
|
apiKey: string;
|
|
modelName: string;
|
|
supportedRatios: string[];
|
|
supportedResolutions: string[];
|
|
maxDuration: number;
|
|
isActive: boolean;
|
|
priority: number;
|
|
}
|
|
|
|
const MOCK_ENGINES: VideoEngine[] = [
|
|
{
|
|
id: 've-1', name: 'Seedance 2.0', provider: 'seedance',
|
|
apiBase: 'https://ark.cn-beijing.volces.com/api/v3',
|
|
apiKey: 'sk-****', modelName: 'seedance-2.0',
|
|
supportedRatios: ['16:9', '9:16', '1:1', '4:3'],
|
|
supportedResolutions: ['720p', '1080p', '4K'],
|
|
maxDuration: 60, isActive: true, priority: 1,
|
|
},
|
|
];
|
|
|
|
const AdminVideoEngines: React.FC = () => {
|
|
const [engines, setEngines] = useState<VideoEngine[]>(MOCK_ENGINES);
|
|
const [modal, setModal] = useState<{ open: boolean; engine: VideoEngine | null }>({ open: false, engine: null });
|
|
const [form] = Form.useForm();
|
|
|
|
const handleSave = async () => {
|
|
try {
|
|
const values = await form.validateFields();
|
|
if (modal.engine) {
|
|
setEngines(prev => prev.map(e => e.id === modal.engine!.id ? { ...e, ...values } : e));
|
|
message.success('已更新');
|
|
} else {
|
|
const newEngine: VideoEngine = {
|
|
id: `ve-${Date.now()}`,
|
|
...values,
|
|
};
|
|
setEngines(prev => [...prev, newEngine]);
|
|
message.success('已添加');
|
|
}
|
|
setModal({ open: false, engine: null });
|
|
form.resetFields();
|
|
} catch { /* validation */ }
|
|
};
|
|
|
|
const handleDelete = (id: string) => {
|
|
setEngines(prev => prev.filter(e => e.id !== id));
|
|
message.success('已删除');
|
|
};
|
|
|
|
const openEdit = (engine?: VideoEngine) => {
|
|
setModal({ open: true, engine: engine || null });
|
|
if (engine) {
|
|
form.setFieldsValue(engine);
|
|
} else {
|
|
form.resetFields();
|
|
form.setFieldsValue({
|
|
isActive: true, priority: 0, maxDuration: 60,
|
|
supportedRatios: ['16:9', '9:16', '1:1'],
|
|
supportedResolutions: ['720p', '1080p'],
|
|
});
|
|
}
|
|
};
|
|
|
|
const columns = [
|
|
{
|
|
title: '引擎名称', key: 'name', width: 180,
|
|
render: (_: any, r: VideoEngine) => (
|
|
<div style={{ display: 'flex', alignItems: 'center', gap: 10 }}>
|
|
<div style={{
|
|
width: 36, height: 36, borderRadius: 8,
|
|
background: r.isActive
|
|
? 'linear-gradient(135deg, #6366f1, #8b5cf6)'
|
|
: 'linear-gradient(135deg, #94a3b8, #cbd5e1)',
|
|
display: 'flex', alignItems: 'center', justifyContent: 'center',
|
|
color: '#fff', fontSize: 16,
|
|
}}><PlayCircleOutlined /></div>
|
|
<div>
|
|
<Typography.Text strong>{r.name}</Typography.Text>
|
|
<div style={{ color: '#94a3b8', fontSize: 12 }}>{r.provider}</div>
|
|
</div>
|
|
</div>
|
|
),
|
|
},
|
|
{
|
|
title: 'API地址', dataIndex: 'apiBase', width: 250,
|
|
render: (v: string) => <Typography.Text type="secondary" style={{ fontSize: 12 }} ellipsis>{v}</Typography.Text>,
|
|
},
|
|
{
|
|
title: '支持比例', dataIndex: 'supportedRatios', width: 180,
|
|
render: (ratios: string[]) => ratios.map(r => <Tag key={r}>{r}</Tag>),
|
|
},
|
|
{
|
|
title: '支持分辨率', dataIndex: 'supportedResolutions', width: 150,
|
|
render: (res: string[]) => res.map(r => <Tag key={r} color="blue">{r}</Tag>),
|
|
},
|
|
{
|
|
title: '最大时长', dataIndex: 'maxDuration', width: 80,
|
|
render: (v: number) => `${v}s`,
|
|
},
|
|
{
|
|
title: '状态', dataIndex: 'isActive', width: 80,
|
|
render: (v: boolean) => <Tag color={v ? 'green' : 'default'}>{v ? '启用' : '停用'}</Tag>,
|
|
},
|
|
{
|
|
title: '操作', key: 'action', width: 150, fixed: 'right' as const,
|
|
render: (_: any, r: VideoEngine) => (
|
|
<Space size={4}>
|
|
<Button type="link" size="small" icon={<EditOutlined />} onClick={() => openEdit(r)}>编辑</Button>
|
|
<Popconfirm title="确定删除?" onConfirm={() => handleDelete(r.id)}>
|
|
<Button type="link" size="small" danger icon={<DeleteOutlined />}>删除</Button>
|
|
</Popconfirm>
|
|
</Space>
|
|
),
|
|
},
|
|
];
|
|
|
|
return (
|
|
<div>
|
|
<Card bordered={false} style={{ borderRadius: 12, border: '1px solid #f0f0f5' }}>
|
|
<div style={{ display: 'flex', justifyContent: 'space-between', marginBottom: 16 }}>
|
|
<Space>
|
|
<PlayCircleOutlined style={{ fontSize: 18, color: '#6366f1' }} />
|
|
<Typography.Text strong style={{ fontSize: 16 }}>视频引擎配置</Typography.Text>
|
|
<Tag color="purple">{engines.length} 个引擎</Tag>
|
|
</Space>
|
|
<Button type="primary" icon={<PlusOutlined />} onClick={() => openEdit()} style={{ borderRadius: 8 }}>
|
|
添加引擎
|
|
</Button>
|
|
</div>
|
|
|
|
<Table
|
|
columns={columns}
|
|
dataSource={engines}
|
|
rowKey="id"
|
|
pagination={false}
|
|
scroll={{ x: 900 }}
|
|
/>
|
|
</Card>
|
|
|
|
<Modal
|
|
title={<Space><PlayCircleOutlined />{modal.engine ? '编辑引擎' : '添加引擎'}</Space>}
|
|
open={modal.open}
|
|
onOk={handleSave}
|
|
onCancel={() => { setModal({ open: false, engine: null }); form.resetFields(); }}
|
|
okText="保存" cancelText="取消" width={560}
|
|
>
|
|
<Form form={form} layout="vertical" style={{ marginTop: 16 }}>
|
|
<div style={{ display: 'flex', gap: 16 }}>
|
|
<Form.Item name="name" label="引擎名称" style={{ flex: 1 }}
|
|
rules={[{ required: true }]}>
|
|
<Input placeholder="Seedance 2.0" size="large" />
|
|
</Form.Item>
|
|
<Form.Item name="provider" label="提供商" style={{ flex: 1 }}
|
|
rules={[{ required: true }]}>
|
|
<Select size="large" options={[
|
|
{ value: 'seedance', label: 'Seedance (火山引擎)' },
|
|
{ value: 'kling', label: 'Kling (快手)' },
|
|
{ value: 'runway', label: 'Runway' },
|
|
{ value: 'pika', label: 'Pika' },
|
|
]} />
|
|
</Form.Item>
|
|
</div>
|
|
<Form.Item name="apiBase" label="API地址" rules={[{ required: true }]}>
|
|
<Input placeholder="https://ark.cn-beijing.volces.com/api/v3" size="large" />
|
|
</Form.Item>
|
|
<Form.Item name="apiKey" label="API Key">
|
|
<Input.Password placeholder="sk-****" size="large" />
|
|
</Form.Item>
|
|
<Form.Item name="modelName" label="模型名称">
|
|
<Input placeholder="seedance-2.0" size="large" />
|
|
</Form.Item>
|
|
<div style={{ display: 'flex', gap: 16 }}>
|
|
<Form.Item name="supportedRatios" label="支持比例" style={{ flex: 1 }}>
|
|
<Select mode="multiple" size="large" options={[
|
|
{ value: '16:9' }, { value: '9:16' }, { value: '1:1' }, { value: '4:3' },
|
|
]} />
|
|
</Form.Item>
|
|
<Form.Item name="supportedResolutions" label="支持分辨率" style={{ flex: 1 }}>
|
|
<Select mode="multiple" size="large" options={[
|
|
{ value: '720p' }, { value: '1080p' }, { value: '4K' },
|
|
]} />
|
|
</Form.Item>
|
|
</div>
|
|
<div style={{ display: 'flex', gap: 16 }}>
|
|
<Form.Item name="maxDuration" label="最大时长(秒)" style={{ flex: 1 }}>
|
|
<InputNumber min={5} max={300} style={{ width: '100%' }} size="large" />
|
|
</Form.Item>
|
|
<Form.Item name="priority" label="优先级" style={{ flex: 1 }}>
|
|
<InputNumber min={0} max={10} style={{ width: '100%' }} size="large" />
|
|
</Form.Item>
|
|
<Form.Item name="isActive" label="启用" valuePropName="checked" style={{ paddingTop: 30 }}>
|
|
<Switch />
|
|
</Form.Item>
|
|
</div>
|
|
</Form>
|
|
</Modal>
|
|
</div>
|
|
);
|
|
};
|
|
|
|
export default AdminVideoEngines;
|