feat(results): add generated video management

This commit is contained in:
Xiaoxia AI
2026-06-24 06:28:06 +08:00
parent 719dc12d3d
commit 8acbd6c8a7
11 changed files with 230 additions and 29 deletions
+8
View File
@@ -26,6 +26,9 @@ export interface GeneratedVideoItem {
width: number;
height: number;
fps: number;
status?: string;
review_status?: 'pending_review' | 'approved' | 'rejected';
generation_params?: Record<string, unknown>;
}
export const createGenerationTask = async (data: {
@@ -55,6 +58,11 @@ export const getGeneratedVideos = async (projectId: string): Promise<GeneratedVi
return response.data.items;
};
export const updateGeneratedVideoReviewStatus = async (videoId: string, reviewStatus: 'pending_review' | 'approved' | 'rejected'): Promise<GeneratedVideoItem> => {
const response = await apiClient.patch(`/generated-videos/${videoId}/review`, { review_status: reviewStatus });
return response.data;
};
export const getGeneratedVideoDownloadUrl = async (videoId: string): Promise<string> => {
const response = await apiClient.get(`/generated-videos/${videoId}/download-url`);
return response.data.download_url;
+61 -28
View File
@@ -1,14 +1,21 @@
import React, { useEffect } from 'react';
import { useLocation, useParams } from 'react-router-dom';
import { Alert, Button, Card, Empty, List, Space, Spin, Tag, Typography, message } from 'antd';
import { useQuery } from '@tanstack/react-query';
import { getGeneratedVideoDownloadUrl, getGeneratedVideos } from '@/api/generation';
import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query';
import { getGeneratedVideoDownloadUrl, getGeneratedVideos, updateGeneratedVideoReviewStatus } from '@/api/generation';
const reviewLabels: Record<string, { label: string; color: string }> = {
pending_review: { label: '待复核', color: 'gold' },
approved: { label: '可发布', color: 'green' },
rejected: { label: '需返工', color: 'red' },
};
const ProjectResults: React.FC = () => {
const { id } = useParams<{ id: string }>();
const location = useLocation();
const projectId = id || '';
const routedWorkspaceId = (location.state as { workspaceId?: string } | null)?.workspaceId || '';
const queryClient = useQueryClient();
useEffect(() => {
if (projectId && routedWorkspaceId) {
@@ -23,6 +30,16 @@ const ProjectResults: React.FC = () => {
refetchInterval: 5000,
});
const reviewMutation = useMutation({
mutationFn: ({ videoId, reviewStatus }: { videoId: string; reviewStatus: 'approved' | 'rejected' }) =>
updateGeneratedVideoReviewStatus(videoId, reviewStatus),
onSuccess: () => {
message.success('成片复核状态已更新');
queryClient.invalidateQueries({ queryKey: ['generated-videos', projectId] });
},
onError: () => message.error('成片复核状态更新失败'),
});
const handleDownload = async (videoId: string) => {
try {
const url = await getGeneratedVideoDownloadUrl(videoId);
@@ -34,7 +51,7 @@ const ProjectResults: React.FC = () => {
return (
<div style={{ padding: 24 }}>
<Card title="项目成片结果">
<Card title="项目成片中心">
{videosQuery.isError && (
<Alert style={{ marginBottom: 16 }} type="error" showIcon message="生成结果加载失败" />
)}
@@ -44,31 +61,47 @@ const ProjectResults: React.FC = () => {
) : videosQuery.data?.length ? (
<List
dataSource={videosQuery.data}
renderItem={(item) => (
<List.Item
actions={[
<Button key="download" type="link" onClick={() => handleDownload(item.id)}>
获取下载地址
</Button>,
]}
>
<List.Item.Meta
title={
<Space wrap>
<Typography.Text>{item.name}</Typography.Text>
<Tag>{item.width}x{item.height}</Tag>
<Tag>{item.duration}s</Tag>
<Tag>{Math.round(item.file_size / 1024)} KB</Tag>
</Space>
}
description={
<Typography.Text copyable ellipsis style={{ maxWidth: 720 }}>
{item.name}
</Typography.Text>
}
/>
</List.Item>
)}
renderItem={(item) => {
const review = reviewLabels[item.review_status || 'pending_review'] || reviewLabels.pending_review;
return (
<List.Item
actions={[
<Button key="download" type="link" onClick={() => handleDownload(item.id)}>
获取下载地址
</Button>,
<Button key="approve" type="link" onClick={() => reviewMutation.mutate({ videoId: item.id, reviewStatus: 'approved' })}>
标记可发布
</Button>,
<Button key="reject" danger type="link" onClick={() => reviewMutation.mutate({ videoId: item.id, reviewStatus: 'rejected' })}>
标记需返工
</Button>,
]}
>
<List.Item.Meta
title={
<Space wrap>
<Typography.Text>{item.name}</Typography.Text>
<Tag color="blue">{item.status || 'completed'}</Tag>
<Tag color={review.color}>{review.label}</Tag>
<Tag>{item.width}x{item.height}</Tag>
<Tag>{item.duration}s</Tag>
<Tag>{Math.round(item.file_size / 1024)} KB</Tag>
</Space>
}
description={
<Space direction="vertical" size={4}>
<Typography.Text copyable ellipsis style={{ maxWidth: 720 }}>
{item.name}
</Typography.Text>
<Typography.Text type="secondary">
生成参数:素材库 {String(item.generation_params?.asset_library_id || '未记录')},标题 {String(item.generation_params?.title_id || '自动/未选择')}
</Typography.Text>
</Space>
}
/>
</List.Item>
);
}}
/>
) : (
<Empty description="暂无生成结果,先去发起生成任务" />