import React, { useEffect, useState } from 'react'; import { Button, Card, Form, InputNumber, message, Modal, Popconfirm, Select, Space, Table, Tag, Typography, } from 'antd'; import type { ColumnsType } from 'antd/es/table'; import { PlusOutlined, EditOutlined, DeleteOutlined, DollarOutlined, } from '@ant-design/icons'; import { getApiModelPricings, saveApiModelPricing, deleteApiModelPricing, getGenerationAiEngines, } from '../api'; import type { GenerationAiEngineOption } from '../types'; type PricingGenType = 'image' | 'video'; interface ApiModelPricing { id: string; modelConfigId: string; genType: PricingGenType | string; resolution: string; priceRatio: number; basePrice: number; perSecondPrice: number; inputVideoRatio: number; inputVideoBasePrice: number; inputVideoPerSecondPrice: number; inputImageRatio: number; inputImageBasePrice: number; inputImagePerImagePrice: number; } const DEFAULT_IMAGE_SIZES = ['2K', '4K']; const DEFAULT_VIDEO_RESOLUTIONS = ['480p', '720p', '1080p']; const AdminApiModelPricings: React.FC = () => { const [pricings, setPricings] = useState([]); const [engines, setEngines] = useState([]); const [loading, setLoading] = useState(false); const [modal, setModal] = useState<{ open: boolean; pricing: ApiModelPricing | null }>({ open: false, pricing: null }); const [form] = Form.useForm(); const genType = Form.useWatch('genType', form) || 'video'; const selectedEngineId = Form.useWatch('modelConfigId', form); const load = async () => { setLoading(true); try { const [pricingData, enginesData] = await Promise.all([ getApiModelPricings(), getGenerationAiEngines(), ]); setPricings(pricingData); const imageEngines: GenerationAiEngineOption[] = (enginesData?.engine?.image || []).map(engine => ({ ...engine, genType: 'image' as const, })); const videoEngines: GenerationAiEngineOption[] = (enginesData?.engine?.video || []).map(engine => ({ ...engine, genType: 'video' as const, })); setEngines([...imageEngines, ...videoEngines]); } catch { message.error('加载失败'); } finally { setLoading(false); } }; useEffect(() => { load(); }, []); const filteredEngines = engines.filter(e => e.genType === genType); const selectedEngine = engines.find(e => e.id === selectedEngineId); const resolutions: string[] = genType === 'video' ? (selectedEngine?.supportedResolutions?.length ? selectedEngine.supportedResolutions : DEFAULT_VIDEO_RESOLUTIONS) : (selectedEngine?.supportedSizes?.length ? Object.keys(selectedEngine.supportedSizes) : DEFAULT_IMAGE_SIZES); const openEdit = (pricing: ApiModelPricing | null = null) => { if (pricing) { form.setFieldsValue({ modelConfigId: pricing.modelConfigId, genType: pricing.genType, resolution: pricing.resolution, priceRatio: pricing.priceRatio, basePrice: pricing.basePrice, perSecondPrice: pricing.perSecondPrice, inputVideoRatio: pricing.inputVideoRatio, inputVideoBasePrice: pricing.inputVideoBasePrice, inputVideoPerSecondPrice: pricing.inputVideoPerSecondPrice, inputImageRatio: pricing.inputImageRatio, inputImageBasePrice: pricing.inputImageBasePrice, inputImagePerImagePrice: pricing.inputImagePerImagePrice, }); } else { form.resetFields(); form.setFieldsValue({ genType: 'video', priceRatio: 1.0, basePrice: 0.0, perSecondPrice: 0.00, inputVideoRatio: 1.0, inputVideoBasePrice: 0, inputVideoPerSecondPrice: 0, inputImageRatio: 1.0, inputImageBasePrice: 0, inputImagePerImagePrice: 0, }); } setModal({ open: true, pricing }); }; const handleSave = async () => { try { const values = await form.validateFields(); const payload = { ...(modal.pricing?.id ? { id: modal.pricing.id } : {}), modelConfigId: values.modelConfigId, genType: values.genType, resolution: values.resolution, priceRatio: values.priceRatio, basePrice: values.basePrice, perSecondPrice: values.perSecondPrice || 0, inputVideoRatio: values.inputVideoRatio || 1.0, inputVideoBasePrice: values.inputVideoBasePrice || 0, inputVideoPerSecondPrice: values.inputVideoPerSecondPrice || 0, inputImageRatio: values.inputImageRatio || 1.0, inputImageBasePrice: values.inputImageBasePrice || 0, inputImagePerImagePrice: values.inputImagePerImagePrice || 0, }; await saveApiModelPricing(payload); message.success('保存成功'); setModal({ open: false, pricing: null }); form.resetFields(); load(); } catch (e: any) { if (e?.errorFields) return; message.error('保存失败'); } }; const handleDelete = async (id: string) => { try { await deleteApiModelPricing(id); message.success('已删除'); load(); } catch { message.error('删除失败'); } }; const columns: ColumnsType = [ { title: '类型', dataIndex: 'genType', width: 80, render: (v: string) => {v === 'video' ? '视频' : '图片'}, }, { title: '引擎', dataIndex: 'modelConfigId', width: 160, render: (v: string) => { const engine = engines.find(e => e.id === v); return engine?.name || v; }, }, { title: '分辨率', dataIndex: 'resolution', width: 80, }, { title: '价格系数', dataIndex: 'priceRatio', width: 90, render: (v: number) => = 2 ? '#f5222d' : v >= 1.5 ? '#faad14' : '#52c41a' }}>{v}, }, { title: '基础价格(元)', dataIndex: 'basePrice', width: 110, }, { title: '每秒价格(元)', dataIndex: 'perSecondPrice', width: 120, }, { title: '传入视频(元)/每秒', dataIndex: 'inputVideoBasePrice', width: 120, }, { title: '传入图片(元)/每张', dataIndex: 'inputImageBasePrice', width: 120, }, { title: '操作', key: 'actions', fixed: 'right', width: 150, render: (_: any, r: ApiModelPricing) => ( handleDelete(r.id)}> ), }, ]; return (
API 模型价格配置 {pricings.length} 条
{ setModal({ open: false, pricing: null }); form.resetFields(); }} okText="保存" cancelText="取消" width={560} >
{genType === 'video' && ( )} 传入媒体附加费
); }; export default AdminApiModelPricings;