diff --git a/src/renderer/src/pages/video/VideoList.tsx b/src/renderer/src/pages/video/VideoList.tsx index 3ade2e6dac..93c92f68f4 100644 --- a/src/renderer/src/pages/video/VideoList.tsx +++ b/src/renderer/src/pages/video/VideoList.tsx @@ -1,13 +1,151 @@ +import { Progress, Spinner } from '@heroui/react' import { useVideos } from '@renderer/hooks/video/useVideos' import { Video } from '@renderer/types/video' +import { CheckCircleIcon, CircleXIcon, ClockIcon, DownloadIcon } from 'lucide-react' +import { useTranslation } from 'react-i18next' export type VideoListProps = { providerId: string } export const VideoList = ({ providerId }: VideoListProps) => { const { videos } = useVideos(providerId) + + // Mock data for testing + const mockVideos: Video[] = [ + { + id: '1', + type: 'openai', + status: 'downloaded', + prompt: 'A beautiful sunset over the ocean with waves crashing', + thumbnail: 'https://picsum.photos/200/200?random=1', + fileId: 'file-001', + metadata: { + id: 'video-001', + object: 'video', + created_at: Math.floor(Date.now() / 1000), + completed_at: Math.floor(Date.now() / 1000), + expires_at: null, + error: null, + model: 'sora-2', + progress: 100, + remixed_from_video_id: null, + seconds: '4', + size: '1280x720', + status: 'completed' + } + }, + { + id: '2', + type: 'openai', + status: 'in_progress', + prompt: 'A cat playing with a ball of yarn in slow motion', + progress: 65, + metadata: { + id: 'video-002', + object: 'video', + created_at: Math.floor(Date.now() / 1000), + completed_at: null, + expires_at: null, + error: null, + model: 'sora-2-pro', + progress: 65, + remixed_from_video_id: null, + seconds: '8', + size: '1792x1024', + status: 'in_progress' + } + }, + { + id: '3', + type: 'openai', + status: 'queued', + prompt: 'Time-lapse of flowers blooming in a garden', + metadata: { + id: 'video-003', + object: 'video', + created_at: Math.floor(Date.now() / 1000), + completed_at: null, + expires_at: null, + error: null, + model: 'sora-2', + progress: 0, + remixed_from_video_id: null, + seconds: '12', + size: '1280x720', + status: 'queued' + } + }, + { + id: '4', + type: 'openai', + prompt: 'Birds flying in formation against blue sky', + status: 'downloading', + progress: 80, + thumbnail: 'https://picsum.photos/200/200?random=4', + metadata: { + id: 'video-004', + object: 'video', + created_at: Math.floor(Date.now() / 1000), + completed_at: Math.floor(Date.now() / 1000), + expires_at: null, + error: null, + model: 'sora-2-pro', + progress: 100, + remixed_from_video_id: null, + seconds: '8', + size: '1792x1024', + status: 'completed' + } + }, + { + id: '5', + type: 'openai', + status: 'failed', + error: { code: '400', message: 'Video generation failed' }, + prompt: 'Mountain landscape with snow peaks and forest', + metadata: { + id: 'video-005', + object: 'video', + created_at: Math.floor(Date.now() / 1000), + completed_at: Math.floor(Date.now() / 1000), + expires_at: null, + error: { code: '400', message: 'Video generation failed' }, + model: 'sora-2', + progress: 0, + remixed_from_video_id: null, + seconds: '4', + size: '1280x720', + status: 'failed' + } + }, + { + id: '6', + type: 'openai', + status: 'completed', + thumbnail: 'https://picsum.photos/200/200?random=6', + prompt: 'City street at night with neon lights reflecting on wet pavement', + metadata: { + id: 'video-006', + object: 'video', + created_at: Math.floor(Date.now() / 1000), + completed_at: Math.floor(Date.now() / 1000), + expires_at: null, + error: null, + model: 'sora-2-pro', + progress: 100, + remixed_from_video_id: null, + seconds: '12', + size: '1024x1792', + status: 'completed' + } + } + ] + + // Use mock data instead of real videos for now + const displayVideos = mockVideos + return ( -
- {videos.map((video) => ( +
+ {displayVideos.map((video) => ( ))}
@@ -15,6 +153,99 @@ export const VideoList = ({ providerId }: VideoListProps) => { } const VideoListItem = ({ video }: { video: Video }) => { - // TODO: get thumbnail from video - return
{video.metadata.id}
+ const { t } = useTranslation() + + const getStatusIcon = () => { + switch (video.status) { + case 'queued': + return + case 'in_progress': + return + case 'completed': + return + case 'downloading': + return + case 'downloaded': + return null // No indicator for downloaded state + case 'failed': + return + default: + return null + } + } + + const getStatusColor = () => { + switch (video.status) { + case 'queued': + return 'border-default-300 bg-default-100' + case 'in_progress': + return 'border-primary-300 bg-primary-50' + case 'completed': + return 'border-success-300 bg-success-50' + case 'downloading': + return 'border-primary-300 bg-primary-50' + case 'downloaded': + return 'border-success-300 bg-success-50' + case 'failed': + return 'border-danger-300 bg-danger-50' + default: + return 'border-default-200 bg-default-50' + } + } + + const showProgress = video.status === 'in_progress' || video.status === 'downloading' + const showThumbnail = video.status === 'completed' || video.status === 'downloading' || video.status === 'downloaded' + + return ( +
+ {/* Thumbnail placeholder */} +
+ {showThumbnail ? ( + Video thumbnail + ) : ( +
+
🎬
+
+ )} +
+ + {/* Status overlay */} +
+ + {/* Status indicator */} + {getStatusIcon() && ( +
+ {getStatusIcon()} + {t(`video.status.${video.status}`)} +
+ )} + + {/* Progress bar for in_progress and downloading states */} + {showProgress && ( +
+ +
+ )} + + {/* Video info overlay */} +
+
+

{video.metadata.id}

+ {video.prompt &&

{video.prompt}

} +
+
+ + {/* Failed state overlay */} + {video.status === 'failed' && ( +
+ )} +
+ ) } diff --git a/src/renderer/src/pages/video/VideoPage.tsx b/src/renderer/src/pages/video/VideoPage.tsx index 7ec571d3ae..041bf10ee8 100644 --- a/src/renderer/src/pages/video/VideoPage.tsx +++ b/src/renderer/src/pages/video/VideoPage.tsx @@ -50,7 +50,7 @@ export const VideoPage = () => { {t('video.title')} -
+
{/* Settings */}
diff --git a/src/renderer/src/types/video.ts b/src/renderer/src/types/video.ts index 4d41a3320a..f4482c4284 100644 --- a/src/renderer/src/types/video.ts +++ b/src/renderer/src/types/video.ts @@ -10,6 +10,7 @@ interface VideoBase { id: string type: VideoEndpointType status: VideoStatus + prompt: string } interface OpenAIVideoBase { @@ -28,15 +29,21 @@ export interface VideoInProgress extends VideoBase { } export interface VideoCompleted extends VideoBase { status: 'completed' + /** When generation completed, firstly try to retrieve thumbnail. */ + thumbnail: string } export interface VideoDownloading extends VideoBase { + /** Downloading video content */ status: 'downloading' + thumbnail: string /** integer percent */ progress: number } export interface Videodownloaded extends VideoBase { status: 'downloaded' + thumbnail: string + /** Managed by fileManager */ fileId: string }