|
|
|
@@ -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>
|
|
|
|
|