修复chat生成参数提交错误BUG|模型引擎积分设置关联开发优化成功|视频/图片生成引擎API开发完成

This commit is contained in:
2026-05-28 13:00:27 +08:00
parent 5021cfd35b
commit edef601f7c
17 changed files with 890 additions and 483 deletions
+7
View File
@@ -6,6 +6,7 @@ import { api, setToken, clearToken } from './client';
import type {
User, CreditRecord, Project, GenerationRecord, GenerationParams,
Industry, AdminUser, AdminStats, ModelConfig, SystemConfig, AdminNotification,
GenerationAiEnginesResponse,
} from '../types';
// ── Auth ──────────────────────────────────────────────────
@@ -302,3 +303,9 @@ export async function adminGenerateVideo(
): Promise<void> {
await api.post(`/admin/generation-records/${recordId}/generate`, { aspect_ratio: aspectRatio, resolution, image_size });
}
// ── Generation AI Engines (Admin) ─────────────────────────────
export async function getGenerationAiEngines(): Promise<GenerationAiEnginesResponse> {
return api.get<GenerationAiEnginesResponse>(`/generation-ai/engines`);
}
+113 -39
View File
@@ -1,44 +1,74 @@
import React, { useEffect, useState } from 'react';
import React, { useEffect, useMemo, useState } from 'react';
import {
Button, Card, Form, InputNumber, message, Modal, Popconfirm, Select, Space, Table, Tag, Typography,
} from 'antd';
import {
CalculatorOutlined, PlusOutlined, EditOutlined, DeleteOutlined, FontSizeOutlined,
} from '@ant-design/icons';
import { getCreditRatios, saveCreditRatio, deleteCreditRatio, getModelConfigs, getSystemConfigs, updateSystemConfig } from '../api';
import type { ModelConfig } from '../types';
import {
getCreditRatios, saveCreditRatio,
deleteCreditRatio, getSystemConfigs,
updateSystemConfig, getGenerationAiEngines,
} from '../api';
import type { GenerationAiEngineOption } from '../types';
type CreditGenType = 'image' | 'video';
interface CreditRatio {
id: string;
modelConfigId: string;
genType: string;
genType: CreditGenType | string;
resolution: string;
ratio: number;
baseCredits: number;
perSecondCredits: number;
}
interface CreditRatioFormValues {
modelConfigId: string;
genType: CreditGenType;
resolution: string;
ratio: number;
baseCredits: number;
perSecondCredits?: number;
}
const DEFAULT_IMAGE_SIZES = ['2K', '4K'];
const DEFAULT_VIDEO_RESOLUTIONS = ['480p', '720p', '1080p'];
const AdminCreditRatios: React.FC = () => {
const [ratios, setRatios] = useState<CreditRatio[]>([]);
const [models, setModels] = useState<ModelConfig[]>([]);
const [engines, setEngines] = useState<GenerationAiEngineOption[]>([]);
const [loading, setLoading] = useState(false);
const [modal, setModal] = useState<{ open: boolean; ratio: CreditRatio | null }>({ open: false, ratio: null });
const [form] = Form.useForm();
const [form] = Form.useForm<CreditRatioFormValues>();
const [textRate, setTextRate] = useState<number>(10);
const [textRateConfig, setTextRateConfig] = useState<{ id: string } | null>(null);
const [savingTextRate, setSavingTextRate] = useState(false);
const genType = Form.useWatch('genType', form);
const genType = Form.useWatch('genType', form) || 'video';
const selectedEngineId = Form.useWatch('modelConfigId', form);
const load = async () => {
setLoading(true);
try {
const [ratioData, modelData, sysConfigs] = await Promise.all([
const [ratioData, sysConfigs, enginesData] = await Promise.all([
getCreditRatios(),
getModelConfigs(),
getSystemConfigs(),
getGenerationAiEngines(),
]);
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,
}));
setRatios(ratioData);
setModels(modelData);
setEngines([...videoEngines, ...imageEngines]);
const textCfg = sysConfigs.find((c: any) => c.key === 'text_credits_per_1000_tokens');
if (textCfg) {
setTextRate(Number(textCfg.value) || 10);
@@ -53,7 +83,36 @@ const AdminCreditRatios: React.FC = () => {
useEffect(() => { load(); }, []);
const modelNameOf = (id: string) => models.find(m => m.id === id)?.name || id;
const currentEngineOptions = useMemo(() => (
engines
.filter(engine => engine.genType === genType)
.map(engine => ({
value: engine.id,
label: `${engine.name}${engine.modelName ? `${engine.modelName}` : ''}`,
}))
), [engines, genType]);
const selectedEngine = useMemo(() => (
engines.find(engine => engine.id === selectedEngineId && engine.genType === genType)
), [engines, genType, selectedEngineId]);
const resolutionOptions = useMemo(() => {
if (genType === 'image') {
const sizes = selectedEngine?.supportedSizes ? Object.keys(selectedEngine.supportedSizes) : [];
return (sizes.length ? sizes : DEFAULT_IMAGE_SIZES).map(size => ({ value: size, label: size }));
}
const resolutions = selectedEngine?.supportedResolutions || [];
return (resolutions.length ? resolutions : DEFAULT_VIDEO_RESOLUTIONS).map(resolution => ({
value: resolution,
label: resolution,
}));
}, [genType, selectedEngine]);
const engineNameOf = (id: string, rowGenType?: string) => {
const engine = engines.find(item => item.id === id && (!rowGenType || item.genType === rowGenType));
return engine?.name || id;
};
const handleSave = async () => {
try {
@@ -64,7 +123,7 @@ const AdminCreditRatios: React.FC = () => {
resolution: values.resolution,
ratio: values.ratio,
base_credits: values.baseCredits,
per_second_credits: values.genType === 'image' ? 0 : values.perSecondCredits,
per_second_credits: values.genType === 'image' ? 0 : (values.perSecondCredits || 0),
};
if (modal.ratio) {
await saveCreditRatio({ id: modal.ratio.id, ...payload });
@@ -108,7 +167,14 @@ const AdminCreditRatios: React.FC = () => {
const openEdit = (ratio?: CreditRatio) => {
setModal({ open: true, ratio: ratio || null });
if (ratio) {
form.setFieldsValue(ratio);
form.setFieldsValue({
modelConfigId: ratio.modelConfigId,
genType: ratio.genType === 'image' ? 'image' : 'video',
resolution: ratio.resolution,
ratio: ratio.ratio,
baseCredits: ratio.baseCredits,
perSecondCredits: ratio.perSecondCredits,
});
} else {
form.resetFields();
form.setFieldsValue({ genType: 'video', ratio: 1.0, baseCredits: 60, perSecondCredits: 2 });
@@ -121,8 +187,8 @@ const AdminCreditRatios: React.FC = () => {
render: (v: string) => <Tag color={v === 'image' ? 'cyan' : 'orange'}>{v === 'image' ? '图片' : '视频'}</Tag>,
},
{
title: '模型', dataIndex: 'modelConfigId', width: 150,
render: (v: string) => <Tag color="purple">{modelNameOf(v)}</Tag>,
title: '引擎', dataIndex: 'modelConfigId', width: 180,
render: (v: string, r: CreditRatio) => <Tag color={r.genType === 'image' ? 'cyan' : 'purple'}>{engineNameOf(v, r.genType)}</Tag>,
},
{
title: '分辨率/尺寸', dataIndex: 'resolution', width: 110,
@@ -145,7 +211,9 @@ const AdminCreditRatios: React.FC = () => {
},
{
title: '每秒积分', dataIndex: 'perSecondCredits', width: 100,
render: (v: number) => <Typography.Text>{v} /</Typography.Text>,
render: (v: number, r: CreditRatio) => (
<Typography.Text>{r.genType === 'image' ? '-' : `${v} 积分/秒`}</Typography.Text>
),
},
{
title: '示例计算', key: 'example', width: 120,
@@ -206,7 +274,7 @@ const AdminCreditRatios: React.FC = () => {
</div>
</Card>
{/* Video Credit Ratios */}
{/* Generation Credit Ratios */}
<Card bordered={false} style={{ borderRadius: 12, border: '1px solid #f0f0f5' }}>
<div style={{ display: 'flex', justifyContent: 'space-between', marginBottom: 16 }}>
<Space>
@@ -220,7 +288,7 @@ const AdminCreditRatios: React.FC = () => {
</div>
<Typography.Text type="secondary" style={{ display: 'block', marginBottom: 16, fontSize: 13 }}>
( + x ) x
( + x ) x x
</Typography.Text>
<Table
@@ -229,7 +297,7 @@ const AdminCreditRatios: React.FC = () => {
rowKey="id"
loading={loading}
pagination={false}
scroll={{ x: 800 }}
scroll={{ x: 860 }}
/>
</Card>
@@ -238,37 +306,43 @@ const AdminCreditRatios: React.FC = () => {
open={modal.open}
onOk={handleSave}
onCancel={() => { setModal({ open: false, ratio: null }); form.resetFields(); }}
okText="保存" cancelText="取消" width={480}
okText="保存" cancelText="取消" width={520}
>
<Form form={form} layout="vertical" style={{ marginTop: 16 }}>
<Form.Item name="modelConfigId" label="型" rules={[{ required: true, message: '请选择型' }]}>
<Select size="large" options={models.map(m => ({ value: m.id, label: m.name }))} />
<Form.Item name="genType" label="生成类型" rules={[{ required: true, message: '请选择生成类型' }]}>
<Select
size="large"
options={[
{ value: 'video', label: '视频' },
{ value: 'image', label: '图片' },
]}
onChange={() => {
form.setFieldsValue({ modelConfigId: undefined as any, resolution: undefined as any });
}}
/>
</Form.Item>
<Form.Item name="genType" label="生成类型" rules={[{ required: true }]}>
<Select size="large" options={[
{ value: 'video', label: '视频' },
{ value: 'image', label: '图片' },
]} />
<Form.Item name="modelConfigId" label="引擎" rules={[{ required: true, message: '请选择引擎' }]}>
<Select
size="large"
placeholder="请选择引擎"
options={currentEngineOptions}
showSearch
optionFilterProp="label"
onChange={() => form.setFieldsValue({ resolution: undefined as any })}
/>
</Form.Item>
<Form.Item name="resolution" label={genType === 'image' ? '图片尺寸' : '分辨率'} rules={[{ required: true }]}>
<Select size="large" options={genType === 'image' ? [
{ value: '2K', label: '2K' },
{ value: '4K', label: '4K' },
] : [
{ value: '480p', label: '480p' },
{ value: '720p', label: '720p' },
{ value: '1080p', label: '1080p' },
]} />
<Form.Item name="resolution" label={genType === 'image' ? '图片尺寸' : '分辨率'} rules={[{ required: true, message: genType === 'image' ? '请选择图片尺寸' : '请选择分辨率' }]}>
<Select size="large" placeholder={genType === 'image' ? '请选择图片尺寸' : '请选择分辨率'} options={resolutionOptions} />
</Form.Item>
<div style={{ display: 'flex', gap: 16 }}>
<Form.Item name="ratio" label="倍率" style={{ flex: 1 }} rules={[{ required: true }]}>
<Form.Item name="ratio" label="倍率" style={{ flex: 1 }} rules={[{ required: true, message: '请输入倍率' }]}>
<InputNumber min={0.1} max={10} step={0.1} style={{ width: '100%' }} size="large" />
</Form.Item>
<Form.Item name="baseCredits" label="基础积分" style={{ flex: 1 }} rules={[{ required: true }]}>
<Form.Item name="baseCredits" label="基础积分" style={{ flex: 1 }} rules={[{ required: true, message: '请输入基础积分' }]}>
<InputNumber min={0} max={1000} style={{ width: '100%' }} size="large" />
</Form.Item>
{genType !== 'image' && (
<Form.Item name="perSecondCredits" label="每秒积分" style={{ flex: 1 }} rules={[{ required: true }]}>
<Form.Item name="perSecondCredits" label="每秒积分" style={{ flex: 1 }} rules={[{ required: true, message: '请输入每秒积分' }]}>
<InputNumber min={0} max={100} style={{ width: '100%' }} size="large" />
</Form.Item>
)}
+50
View File
@@ -147,6 +147,56 @@ export interface AdminNotification {
createdAt: string;
}
export type GenerationAiGenType = 'image' | 'video';
export interface GenerationAiImageEngine {
id: string;
name: string;
provider: string;
modelName: string;
supportedModels: string[];
supportedSizes: Record<string, Record<string, string>>;
defaultSize: string;
priority: number;
}
export interface GenerationAiVideoEngine {
id: string;
name: string;
provider: string;
modelName: string;
supportedModels: string[];
supportedRatios: string[];
supportedResolutions: string[];
supportedDurations: number[];
maxDuration: number;
priority: number;
}
export interface GenerationAiEnginesResponse {
engine: {
image: GenerationAiImageEngine[];
video: GenerationAiVideoEngine[];
};
}
export interface GenerationAiEngineOption {
id: string;
name: string;
provider: string;
modelName: string;
supportedModels?: string[];
supportedRatios?: string[];
supportedResolutions?: string[];
supportedDurations?: number[];
supportedSizes?: Record<string, Record<string, string>>;
defaultSize?: string;
maxDuration?: number;
priority: number;
genType: GenerationAiGenType;
}
export interface AdminGenerationRecord {
id: string;
userId: string;