生成任务兼容图片类型
This commit is contained in:
@@ -298,7 +298,7 @@ export async function adminUpdateGenerationStatus(
|
||||
}
|
||||
|
||||
export async function adminGenerateVideo(
|
||||
recordId: string, aspectRatio: string, resolution: string
|
||||
recordId: string, aspectRatio: string, resolution: string, image_size: string
|
||||
): Promise<void> {
|
||||
await api.post(`/admin/generation-records/${recordId}/generate`, { aspect_ratio: aspectRatio, resolution });
|
||||
await api.post(`/admin/generation-records/${recordId}/generate`, { aspect_ratio: aspectRatio, resolution, image_size });
|
||||
}
|
||||
|
||||
@@ -5,7 +5,7 @@ import {
|
||||
import {
|
||||
PlayCircleOutlined, EyeOutlined, ClockCircleOutlined, CheckCircleOutlined,
|
||||
LoadingOutlined, CloseCircleOutlined, SearchOutlined, VideoCameraOutlined,
|
||||
ExclamationCircleOutlined,
|
||||
ExclamationCircleOutlined, FileImageOutlined
|
||||
} from '@ant-design/icons';
|
||||
import { getAdminGenerationRecords, adminUpdateGenerationStatus, adminGenerateVideo } from '../api';
|
||||
import type { AdminGenerationRecord } from '../types';
|
||||
@@ -32,7 +32,7 @@ const AdminGenerationRecords: React.FC = () => {
|
||||
const [filterUserId, setFilterUserId] = useState<string>('');
|
||||
const [preview, setPreview] = useState<AdminGenerationRecord | null>(null);
|
||||
const [updating, setUpdating] = useState<string | null>(null);
|
||||
const [genModal, setGenModal] = useState<{ record: AdminGenerationRecord; ratio: string; resolution: string } | null>(null);
|
||||
const [genModal, setGenModal] = useState<{ record: AdminGenerationRecord; ratio: string; resolution: string; image_size: string } | null>(null);
|
||||
|
||||
const load = async () => {
|
||||
setLoading(true);
|
||||
@@ -63,7 +63,12 @@ const AdminGenerationRecords: React.FC = () => {
|
||||
videoTokensUsed: item.videoTokensUsed || 0,
|
||||
errorMessage: item.errorMessage,
|
||||
createdAt: item.createdAt,
|
||||
generatedAt: item.generatedAt,
|
||||
genType: item.genType,
|
||||
imageSize: item.imageSize,
|
||||
imageUrl: item.imageUrl,
|
||||
imageTokensUsed: item.imageTokensUsed,
|
||||
imageProportion: item.imageProportion,
|
||||
imagePx: item.imagePx,
|
||||
})));
|
||||
setTotal(res.total);
|
||||
} catch {
|
||||
@@ -92,7 +97,7 @@ const AdminGenerationRecords: React.FC = () => {
|
||||
if (!genModal) return;
|
||||
setUpdating(genModal.record.id);
|
||||
try {
|
||||
await adminGenerateVideo(genModal.record.id, genModal.ratio, genModal.resolution);
|
||||
await adminGenerateVideo(genModal.record.id, genModal.ratio, genModal.resolution, genModal.image_size);
|
||||
message.success('已提交视频生成');
|
||||
setGenModal(null);
|
||||
load();
|
||||
@@ -117,6 +122,12 @@ const AdminGenerationRecords: React.FC = () => {
|
||||
title: '项目', dataIndex: 'projectName', width: 120, ellipsis: true,
|
||||
render: (v: string) => <Typography.Text style={{ fontSize: 13 }}>{v}</Typography.Text>,
|
||||
},
|
||||
{
|
||||
title: '类型', dataIndex: 'genType', width: 60, ellipsis: true,
|
||||
render: (v: string) => <Typography.Text style={{ fontSize: 13 }}>
|
||||
{v==='video' ? "视频" : "图片"}
|
||||
</Typography.Text>,
|
||||
},
|
||||
{
|
||||
title: '提示词', key: 'prompt', ellipsis: true,
|
||||
render: (_: any, r: AdminGenerationRecord) => (
|
||||
@@ -130,13 +141,22 @@ const AdminGenerationRecords: React.FC = () => {
|
||||
{
|
||||
title: '参数', key: 'params', width: 140,
|
||||
render: (_: any, r: AdminGenerationRecord) => (
|
||||
r.duration ? (
|
||||
<Space size={4} wrap>
|
||||
<Tag>{r.duration}s</Tag>
|
||||
<Tag>{r.aspectRatio}</Tag>
|
||||
<Tag>{r.resolution}</Tag>
|
||||
</Space>
|
||||
) : <Tag color="default">待配置</Tag>
|
||||
r.genType === 'video'?
|
||||
(r.duration ? (
|
||||
<Space size={4} wrap>
|
||||
<Tag>{r.duration}s</Tag>
|
||||
<Tag>{r.aspectRatio}</Tag>
|
||||
<Tag>{r.resolution}</Tag>
|
||||
</Space>
|
||||
) : <Tag color="default">待配置</Tag>)
|
||||
:
|
||||
(r.imageSize ? (
|
||||
<Space size={4} wrap>
|
||||
<Tag>{r.imageSize}</Tag>
|
||||
<Tag>{r.imageProportion}</Tag>
|
||||
<Tag>{r.imagePx}</Tag>
|
||||
</Space>
|
||||
) : <Tag color="default">待配置</Tag>)
|
||||
),
|
||||
},
|
||||
{
|
||||
@@ -147,7 +167,7 @@ const AdminGenerationRecords: React.FC = () => {
|
||||
<div style={{ color: '#f59e0b' }}>文字: {r.textCreditsCost}</div>
|
||||
)}
|
||||
{r.creditsCost > 0 && (
|
||||
<div style={{ color: '#6366f1' }}>视频: {r.creditsCost}</div>
|
||||
<div style={{ color: '#6366f1' }}>{r.genType === 'video'? "视频" : "图片"}: {r.creditsCost}</div>
|
||||
)}
|
||||
{r.textCreditsCost === 0 && r.creditsCost === 0 && (
|
||||
<Typography.Text style={{ color: '#94a3b8' }}>0</Typography.Text>
|
||||
@@ -193,16 +213,16 @@ const AdminGenerationRecords: React.FC = () => {
|
||||
)}
|
||||
{r.status === 'failed' && (
|
||||
<Button size="small" type="primary" danger loading={updating === r.id}
|
||||
onClick={() => setGenModal({ record: r, ratio: r.aspectRatio || '16:9', resolution: r.resolution || '720p' })}>
|
||||
onClick={() => setGenModal({ record: r, ratio: r.aspectRatio || '16:9', resolution: r.resolution || '720p', image_size: r.imageSize })}>
|
||||
重试生成
|
||||
</Button>
|
||||
)}
|
||||
{r.status === 'prompt_optimized' && (
|
||||
<>
|
||||
<Button size="small" type="primary" loading={updating === r.id}
|
||||
onClick={() => setGenModal({ record: r, ratio: '16:9', resolution: '720p' })}
|
||||
onClick={() => setGenModal({ record: r, ratio: '16:9', resolution: '720p', image_size: r.imageSize })}
|
||||
style={{ background: '#6366f1', border: 'none' }}>
|
||||
生成视频
|
||||
生成{r.genType === 'video'? "视频" : "图片"}
|
||||
</Button>
|
||||
<Button size="small" danger loading={updating === r.id}
|
||||
onClick={() => {
|
||||
@@ -328,7 +348,7 @@ const AdminGenerationRecords: React.FC = () => {
|
||||
<Typography.Text style={{ fontSize: 11, color: '#94a3b8' }}> ({preview.textTokensUsed} tokens)</Typography.Text>
|
||||
</div>
|
||||
<div style={{ flex: 1 }}>
|
||||
<Typography.Text style={{ fontSize: 11, color: '#94a3b8', display: 'block' }}>视频积分</Typography.Text>
|
||||
<Typography.Text style={{ fontSize: 11, color: '#94a3b8', display: 'block' }}>{preview.genType === 'image'? '图片' : '视频'}积分</Typography.Text>
|
||||
<Typography.Text strong style={{ fontSize: 14, color: '#6366f1' }}>{preview.creditsCost}</Typography.Text>
|
||||
{preview.videoTokensUsed > 0 && (
|
||||
<Typography.Text style={{ fontSize: 11, color: '#94a3b8' }}> ({preview.videoTokensUsed} tokens)</Typography.Text>
|
||||
@@ -341,23 +361,47 @@ const AdminGenerationRecords: React.FC = () => {
|
||||
</div>
|
||||
|
||||
{/* Video Params */}
|
||||
{preview.duration ? (
|
||||
<div style={{ display: 'flex', gap: 16, padding: 12, borderRadius: 10, background: '#f8f9fc' }}>
|
||||
{[
|
||||
{ label: '时长', value: `${preview.duration}秒` },
|
||||
{ label: '比例', value: preview.aspectRatio },
|
||||
{ label: '分辨率', value: preview.resolution },
|
||||
].map((item, i) => (
|
||||
<div key={i} style={{ flex: 1 }}>
|
||||
<Typography.Text style={{ fontSize: 11, color: '#94a3b8', display: 'block' }}>{item.label}</Typography.Text>
|
||||
<Typography.Text strong style={{ fontSize: 14 }}>{item.value}</Typography.Text>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
) : (
|
||||
<div style={{ padding: 12, borderRadius: 10, background: '#f8f9fc', textAlign: 'center' }}>
|
||||
<Tag color="default">视频参数待用户配置</Tag>
|
||||
</div>
|
||||
{preview.genType === 'video' && (
|
||||
preview.duration ? (
|
||||
<div style={{ display: 'flex', gap: 16, padding: 12, borderRadius: 10, background: '#f8f9fc' }}>
|
||||
{[
|
||||
{ label: '时长', value: `${preview.duration}秒` },
|
||||
{ label: '比例', value: preview.aspectRatio },
|
||||
{ label: '分辨率', value: preview.resolution },
|
||||
].map((item, i) => (
|
||||
<div key={i} style={{ flex: 1 }}>
|
||||
<Typography.Text style={{ fontSize: 11, color: '#94a3b8', display: 'block' }}>{item.label}</Typography.Text>
|
||||
<Typography.Text strong style={{ fontSize: 14 }}>{item.value}</Typography.Text>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
) : (
|
||||
<div style={{ padding: 12, borderRadius: 10, background: '#f8f9fc', textAlign: 'center' }}>
|
||||
<Tag color="default">视频参数待用户配置</Tag>
|
||||
</div>
|
||||
)
|
||||
)}
|
||||
|
||||
{/* Image Params */}
|
||||
{preview.genType === 'image' && (
|
||||
preview.imageSize ? (
|
||||
<div style={{ display: 'flex', gap: 16, padding: 12, borderRadius: 10, background: '#f8f9fc' }}>
|
||||
{[
|
||||
{ label: '尺寸', value: preview.imagePx },
|
||||
{ label: '比例', value: preview.imageProportion },
|
||||
{ label: '分辨率', value: preview.imageSize },
|
||||
].map((item, i) => (
|
||||
<div key={i} style={{ flex: 1 }}>
|
||||
<Typography.Text style={{ fontSize: 11, color: '#94a3b8', display: 'block' }}>{item.label}</Typography.Text>
|
||||
<Typography.Text strong style={{ fontSize: 14 }}>{item.value}</Typography.Text>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
) : (
|
||||
<div style={{ padding: 12, borderRadius: 10, background: '#f8f9fc', textAlign: 'center' }}>
|
||||
<Tag color="default">图片参数待用户配置</Tag>
|
||||
</div>
|
||||
)
|
||||
)}
|
||||
|
||||
{/* Reference images */}
|
||||
@@ -398,6 +442,18 @@ const AdminGenerationRecords: React.FC = () => {
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Image preview */}
|
||||
{preview.status === 'completed' && preview.imageUrl && preview.genType === 'image' && (
|
||||
<div>
|
||||
<Typography.Text style={{ fontSize: 12, color: '#94a3b8', display: 'block', marginBottom: 6 }}>生成图片</Typography.Text>
|
||||
<img
|
||||
// src={apiUrl(preview.imageUrl)}
|
||||
src={`http://ceshi.apiforeign.minzhong.cn${preview.imageUrl}`}
|
||||
style={{ width: '100%', minHeight: 100, borderRadius: 12, background: '#000' }}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Error message */}
|
||||
{preview.status === 'failed' && preview.errorMessage && (
|
||||
<div style={{ padding: 12, borderRadius: 10, background: 'rgba(239,68,68,0.04)', border: '1px solid rgba(239,68,68,0.15)' }}>
|
||||
@@ -416,7 +472,7 @@ const AdminGenerationRecords: React.FC = () => {
|
||||
|
||||
{/* Generate modal */}
|
||||
<Modal
|
||||
title={<Space><PlayCircleOutlined />生成视频</Space>}
|
||||
title={<Space>{genModal && (genModal.record.genType === 'video' ? <PlayCircleOutlined /> : <FileImageOutlined />)}{genModal && (genModal.record.genType === 'video' ? '生成视频' : '生成图片')}</Space>}
|
||||
open={!!genModal}
|
||||
onCancel={() => setGenModal(null)}
|
||||
onOk={handleGenerate}
|
||||
@@ -427,24 +483,45 @@ const AdminGenerationRecords: React.FC = () => {
|
||||
>
|
||||
{genModal && (
|
||||
<div style={{ display: 'flex', flexDirection: 'column', gap: 16, marginTop: 16 }}>
|
||||
<div style={{ padding: 12, borderRadius: 10, background: '#f8f9fc' }}>
|
||||
<Typography.Text style={{ fontSize: 11, color: '#94a3b8', display: 'block' }}>时长</Typography.Text>
|
||||
<Typography.Text strong>{genModal.record.duration || 5}s</Typography.Text>
|
||||
</div>
|
||||
<div>
|
||||
<Typography.Text style={{ fontSize: 12, color: '#64748b', display: 'block', marginBottom: 6 }}>画面比例</Typography.Text>
|
||||
<Select value={genModal.ratio} onChange={(v) => setGenModal(prev => prev ? { ...prev, ratio: v } : null)}
|
||||
style={{ width: '100%' }}
|
||||
options={['16:9', '4:3', '1:1', '3:4', '9:16', '21:9'].map(r => ({ value: r, label: r }))}
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<Typography.Text style={{ fontSize: 12, color: '#64748b', display: 'block', marginBottom: 6 }}>分辨率</Typography.Text>
|
||||
<Select value={genModal.resolution} onChange={(v) => setGenModal(prev => prev ? { ...prev, resolution: v } : null)}
|
||||
style={{ width: '100%' }}
|
||||
options={['480p', '720p', '1080p'].map(r => ({ value: r, label: r }))}
|
||||
/>
|
||||
</div>
|
||||
{genModal.record.genType === 'video' ?
|
||||
<>
|
||||
<div style={{ padding: 12, borderRadius: 10, background: '#f8f9fc' }}>
|
||||
<Typography.Text style={{ fontSize: 11, color: '#94a3b8', display: 'block' }}>时长</Typography.Text>
|
||||
<Typography.Text strong>{genModal.record.duration || 5}s</Typography.Text>
|
||||
</div>
|
||||
<div>
|
||||
<Typography.Text style={{ fontSize: 12, color: '#64748b', display: 'block', marginBottom: 6 }}>画面比例</Typography.Text>
|
||||
<Select value={genModal.ratio} onChange={(v) => setGenModal(prev => prev ? { ...prev, ratio: v } : null)}
|
||||
style={{ width: '100%' }}
|
||||
options={['16:9', '4:3', '1:1', '3:4', '9:16', '21:9'].map(r => ({ value: r, label: r }))}
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<Typography.Text style={{ fontSize: 12, color: '#64748b', display: 'block', marginBottom: 6 }}>分辨率</Typography.Text>
|
||||
<Select value={genModal.resolution} onChange={(v) => setGenModal(prev => prev ? { ...prev, resolution: v } : null)}
|
||||
style={{ width: '100%' }}
|
||||
options={['480p', '720p', '1080p'].map(r => ({ value: r, label: r }))}
|
||||
/>
|
||||
</div>
|
||||
</> :
|
||||
<>
|
||||
<div style={{ padding: 12, borderRadius: 10, background: '#f8f9fc' }}>
|
||||
<Typography.Text style={{ fontSize: 11, color: '#94a3b8', display: 'block' }}>尺寸</Typography.Text>
|
||||
<Typography.Text strong>{genModal.record.imagePx}</Typography.Text>
|
||||
</div>
|
||||
<div style={{ padding: 12, borderRadius: 10, background: '#f8f9fc' }}>
|
||||
<Typography.Text style={{ fontSize: 11, color: '#94a3b8', display: 'block' }}>比例</Typography.Text>
|
||||
<Typography.Text strong>{genModal.record.imageProportion}</Typography.Text>
|
||||
</div>
|
||||
<div>
|
||||
<Typography.Text style={{ fontSize: 12, color: '#64748b', display: 'block', marginBottom: 6 }}>分辨率</Typography.Text>
|
||||
<Select value={genModal.image_size} onChange={(v) => setGenModal(prev => prev ? { ...prev, image_size: v } : null)}
|
||||
style={{ width: '100%' }}
|
||||
options={['2K', '4K'].map(r => ({ value: r, label: r }))}
|
||||
/>
|
||||
</div>
|
||||
</> }
|
||||
|
||||
</div>
|
||||
)}
|
||||
</Modal>
|
||||
|
||||
@@ -168,4 +168,10 @@ export interface AdminGenerationRecord {
|
||||
errorMessage?: string;
|
||||
createdAt: string;
|
||||
generatedAt?: string;
|
||||
genType?: string,
|
||||
imageSize?: string,
|
||||
imageUrl?: string,
|
||||
imageTokensUsed?: number,
|
||||
imageProportion?: string,
|
||||
imagePx?: string,
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user