feat(video): add expired state and regenerate button to video viewer

Implement expired video state handling with a regenerate button. The viewer now checks if the video has expired and shows appropriate UI with a regeneration option (currently unimplemented).
This commit is contained in:
icarus 2025-10-13 14:58:45 +08:00
parent 1c89262929
commit 5e4b516402
3 changed files with 33 additions and 6 deletions

View File

@ -4658,6 +4658,7 @@
"reason": "The file may be corrupted or has been deleted externally."
}
},
"expired": "Expired",
"input_reference": {
"add": {
"error": {

View File

@ -76,6 +76,10 @@ export const VideoPanel = ({ provider, video, params, updateParams }: VideoPanel
}
}, [addOpenAIVideo, couldCreateVideo, params, t, video])
const handleRegenerateVideo = useCallback(() => {
window.toast.info('Not implemented')
}, [])
const handleDownloadVideo = async () => {
if (!video) return
if (video.status === 'completed' || video.status === 'downloaded') {
@ -145,7 +149,7 @@ export const VideoPanel = ({ provider, video, params, updateParams }: VideoPanel
<div className="flex flex-1 flex-col p-2">
<div className="m-8 flex-1">
<Skeleton className="h-full w-full rounded-2xl" classNames={{ content: 'h-full w-full' }} isLoaded={true}>
{video && <VideoViewer video={video} onDownload={handleDownloadVideo} />}
{video && <VideoViewer video={video} onDownload={handleDownloadVideo} onRegenerate={handleRegenerateVideo} />}
{!video && <VideoViewer video={video} />}
</Skeleton>
</div>

View File

@ -10,7 +10,8 @@ import {
useDisclosure
} from '@heroui/react'
import { Video, VideoFailed } from '@renderer/types/video'
import { CheckCircleIcon, CircleXIcon } from 'lucide-react'
import dayjs from 'dayjs'
import { CheckCircleIcon, CircleXIcon, Clock9Icon } from 'lucide-react'
import { useMemo, useState } from 'react'
import { useTranslation } from 'react-i18next'
@ -18,13 +19,15 @@ export type VideoViewerProps =
| {
video: undefined
onDownload?: never
onRegenerate?: never
}
| {
video: Video
onDownload: () => void
onRegenerate: () => void
}
export const VideoViewer = ({ video, onDownload }: VideoViewerProps) => {
export const VideoViewer = ({ video, onDownload, onRegenerate }: VideoViewerProps) => {
const { t } = useTranslation()
const [loadSuccess, setLoadSuccess] = useState<boolean | undefined>(undefined)
return (
@ -33,7 +36,9 @@ export const VideoViewer = ({ video, onDownload }: VideoViewerProps) => {
{video === undefined && t('video.undefined')}
{video && video.status === 'queued' && <QueuedVideo />}
{video && video.status === 'in_progress' && <InProgressVideo progress={video.progress} />}
{video && video.status === 'completed' && <CompletedVideo onDownload={onDownload} />}
{video && video.status === 'completed' && (
<CompletedVideo video={video} onDownload={onDownload} onRegenerate={onRegenerate} />
)}
{video && video.status === 'downloading' && <DownloadingVideo progress={video.progress} />}
{video && video.status === 'downloaded' && loadSuccess !== false && (
<video
@ -78,9 +83,26 @@ const InProgressVideo = ({ progress }: { progress: number }) => {
)
}
const CompletedVideo = ({ onDownload }: { onDownload: () => void }) => {
const CompletedVideo = ({
video,
onDownload,
onRegenerate
}: {
video: Video
onDownload: () => void
onRegenerate: () => void
}) => {
const { t } = useTranslation()
const isExpired = video.metadata.expires_at !== null && video.metadata.expires_at < dayjs().unix()
if (isExpired) {
return (
<div className="flex h-full w-full flex-col items-center justify-center gap-2 rounded-2xl bg-warning-200">
<Clock9Icon size={64} className="text-warning" />
<span className="font-bold text-2xl">{t('video.expired')}</span>
<Button onPress={onRegenerate}>{t('common.regenerate')}</Button>
</div>
)
}
return (
<div className="flex h-full w-full flex-col items-center justify-center gap-2 rounded-2xl bg-success-200">
<CheckCircleIcon size={64} className="text-success" />