mirror of
https://github.com/CherryHQ/cherry-studio.git
synced 2025-12-28 21:42:27 +08:00
feat(video): add mock data and improve video panel handling
- Add mock video data for testing purposes - Improve video panel state management with useEffect - Export video types from index file
This commit is contained in:
parent
83114ee0c1
commit
942c239d14
@ -1,6 +1,137 @@
|
|||||||
import { SystemProviderId } from '@renderer/types'
|
import { SystemProviderId, Video } from '@renderer/types'
|
||||||
|
|
||||||
// Hard-encoded for now. We may implement a function to filter video generation model from provider.models.
|
// Hard-encoded for now. We may implement a function to filter video generation model from provider.models.
|
||||||
export const videoModelsMap = {
|
export const videoModelsMap = {
|
||||||
openai: ['sora-2', 'sora-2-pro'] as const
|
openai: ['sora-2', 'sora-2-pro'] as const
|
||||||
} as const satisfies Partial<Record<SystemProviderId, string[]>>
|
} as const satisfies Partial<Record<SystemProviderId, string[]>>
|
||||||
|
|
||||||
|
// Mock data for testing
|
||||||
|
export 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'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
|||||||
@ -1,6 +1,7 @@
|
|||||||
import { cn, Progress, Spinner } from '@heroui/react'
|
import { cn, Progress, Spinner } from '@heroui/react'
|
||||||
|
import { mockVideos } from '@renderer/config/models/video'
|
||||||
import { useVideos } from '@renderer/hooks/video/useVideos'
|
import { useVideos } from '@renderer/hooks/video/useVideos'
|
||||||
import { Video } from '@renderer/types/video'
|
import { Video } from '@renderer/types'
|
||||||
import { CheckCircleIcon, CircleXIcon, ClockIcon, DownloadIcon } from 'lucide-react'
|
import { CheckCircleIcon, CircleXIcon, ClockIcon, DownloadIcon } from 'lucide-react'
|
||||||
import { useTranslation } from 'react-i18next'
|
import { useTranslation } from 'react-i18next'
|
||||||
|
|
||||||
@ -9,137 +10,6 @@ export type VideoListProps = { providerId: string; activeVideoId?: string; setAc
|
|||||||
export const VideoList = ({ providerId, activeVideoId, setActiveVideoId }: VideoListProps) => {
|
export const VideoList = ({ providerId, activeVideoId, setActiveVideoId }: VideoListProps) => {
|
||||||
const { videos } = useVideos(providerId)
|
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
|
// Use mock data instead of real videos for now
|
||||||
const displayVideos = mockVideos
|
const displayVideos = mockVideos
|
||||||
|
|
||||||
|
|||||||
@ -2,13 +2,14 @@
|
|||||||
|
|
||||||
import { Divider } from '@heroui/react'
|
import { Divider } from '@heroui/react'
|
||||||
import { Navbar, NavbarCenter } from '@renderer/components/app/Navbar'
|
import { Navbar, NavbarCenter } from '@renderer/components/app/Navbar'
|
||||||
|
import { mockVideos } from '@renderer/config/models/video'
|
||||||
import { useProvider } from '@renderer/hooks/useProvider'
|
import { useProvider } from '@renderer/hooks/useProvider'
|
||||||
import { SystemProviderIds } from '@renderer/types'
|
import { SystemProviderIds } from '@renderer/types'
|
||||||
import { CreateVideoParams } from '@renderer/types/video'
|
import { CreateVideoParams } from '@renderer/types/video'
|
||||||
import { deepUpdate } from '@renderer/utils/deepUpdate'
|
import { deepUpdate } from '@renderer/utils/deepUpdate'
|
||||||
import { isVideoModel } from '@renderer/utils/model/video'
|
import { isVideoModel } from '@renderer/utils/model/video'
|
||||||
import { DeepPartial } from 'ai'
|
import { DeepPartial } from 'ai'
|
||||||
import { useCallback, useState } from 'react'
|
import { useCallback, useMemo, useState } from 'react'
|
||||||
import { useTranslation } from 'react-i18next'
|
import { useTranslation } from 'react-i18next'
|
||||||
|
|
||||||
import { ModelSetting } from './settings/ModelSetting'
|
import { ModelSetting } from './settings/ModelSetting'
|
||||||
@ -46,6 +47,8 @@ export const VideoPage = () => {
|
|||||||
[updateParams]
|
[updateParams]
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const activeVideo = useMemo(() => mockVideos.find((v) => v.id === activeVideoId), [activeVideoId])
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex flex-1 flex-col">
|
<div className="flex flex-1 flex-col">
|
||||||
<Navbar>
|
<Navbar>
|
||||||
@ -65,8 +68,7 @@ export const VideoPage = () => {
|
|||||||
{provider.type === 'openai-response' && <OpenAIParamSettings params={params} updateParams={updateParams} />}
|
{provider.type === 'openai-response' && <OpenAIParamSettings params={params} updateParams={updateParams} />}
|
||||||
</div>
|
</div>
|
||||||
<Divider orientation="vertical" />
|
<Divider orientation="vertical" />
|
||||||
{/* TODO: pass video prop. retrieve correct video by swr */}
|
<VideoPanel provider={provider} params={params} updateParams={updateParams} video={activeVideo} />
|
||||||
<VideoPanel provider={provider} params={params} updateParams={updateParams} />
|
|
||||||
<Divider orientation="vertical" />
|
<Divider orientation="vertical" />
|
||||||
{/* Video list */}
|
{/* Video list */}
|
||||||
<VideoList providerId={providerId} activeVideoId={activeVideoId} setActiveVideoId={setActiveVideoId} />
|
<VideoList providerId={providerId} activeVideoId={activeVideoId} setActiveVideoId={setActiveVideoId} />
|
||||||
|
|||||||
@ -9,7 +9,7 @@ import { MB } from '@shared/config/constant'
|
|||||||
import { DeepPartial } from 'ai'
|
import { DeepPartial } from 'ai'
|
||||||
import { isEmpty } from 'lodash'
|
import { isEmpty } from 'lodash'
|
||||||
import { ArrowUp, CircleXIcon, ImageIcon } from 'lucide-react'
|
import { ArrowUp, CircleXIcon, ImageIcon } from 'lucide-react'
|
||||||
import { useCallback, useMemo, useRef, useState } from 'react'
|
import { useCallback, useEffect, useMemo, useRef, useState } from 'react'
|
||||||
import { useTranslation } from 'react-i18next'
|
import { useTranslation } from 'react-i18next'
|
||||||
|
|
||||||
import { VideoViewer } from './VideoViewer'
|
import { VideoViewer } from './VideoViewer'
|
||||||
@ -35,6 +35,13 @@ export const VideoPanel = ({ provider, video, params, updateParams }: VideoPanel
|
|||||||
() => !isProcessing && !isEmpty(params.params.prompt),
|
() => !isProcessing && !isEmpty(params.params.prompt),
|
||||||
[isProcessing, params.params.prompt]
|
[isProcessing, params.params.prompt]
|
||||||
)
|
)
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (video) {
|
||||||
|
updateParams({ params: { prompt: video.prompt } })
|
||||||
|
}
|
||||||
|
}, [updateParams, video])
|
||||||
|
|
||||||
const handleCreateVideo = useCallback(async () => {
|
const handleCreateVideo = useCallback(async () => {
|
||||||
if (!couldCreateVideo) return
|
if (!couldCreateVideo) return
|
||||||
setIsProcessing(true)
|
setIsProcessing(true)
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
import { Button, Progress, Radio, RadioGroup, Spinner } from '@heroui/react'
|
import { Button, Progress, Radio, RadioGroup, Spinner } from '@heroui/react'
|
||||||
import { Video, VideoStatus } from '@renderer/types/video'
|
import { Video, VideoStatus } from '@renderer/types/video'
|
||||||
import { CheckCircleIcon, CircleXIcon } from 'lucide-react'
|
import { CheckCircleIcon, CircleXIcon } from 'lucide-react'
|
||||||
import { useState } from 'react'
|
import { useEffect, useState } from 'react'
|
||||||
import { useTranslation } from 'react-i18next'
|
import { useTranslation } from 'react-i18next'
|
||||||
|
|
||||||
export interface VideoProps {
|
export interface VideoProps {
|
||||||
@ -12,6 +12,9 @@ export const VideoViewer = ({ video: _video }: VideoProps) => {
|
|||||||
const { t } = useTranslation()
|
const { t } = useTranslation()
|
||||||
const [video, setVideo] = useState<Video | undefined>(_video)
|
const [video, setVideo] = useState<Video | undefined>(_video)
|
||||||
const [loadSuccess, setLoadSuccess] = useState<boolean | undefined>(undefined)
|
const [loadSuccess, setLoadSuccess] = useState<boolean | undefined>(undefined)
|
||||||
|
useEffect(() => {
|
||||||
|
setVideo(_video)
|
||||||
|
}, [_video])
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
{/* For test */}
|
{/* For test */}
|
||||||
|
|||||||
@ -22,6 +22,7 @@ export * from './mcp'
|
|||||||
export * from './notification'
|
export * from './notification'
|
||||||
export * from './ocr'
|
export * from './ocr'
|
||||||
export * from './provider'
|
export * from './provider'
|
||||||
|
export * from './video'
|
||||||
|
|
||||||
export type Assistant = {
|
export type Assistant = {
|
||||||
id: string
|
id: string
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user