mirror of
https://github.com/CherryHQ/cherry-studio.git
synced 2025-12-28 21:42:27 +08:00
refactor(video): update video handling and type definitions
- Rename RetrieveVideoParams to RetrieveVideoContentParams for consistency - Move video list management to parent component - Add setVideo action and improve video state updates - Implement video status polling and thumbnail fetching
This commit is contained in:
parent
d8363b5591
commit
8a45fe70d0
@ -12,7 +12,7 @@ import { loggerService } from '@logger'
|
||||
import { getEnableDeveloperMode } from '@renderer/hooks/useSettings'
|
||||
import { addSpan, endSpan } from '@renderer/services/SpanManagerService'
|
||||
import { StartSpanParams } from '@renderer/trace/types/ModelSpanEntity'
|
||||
import type { Assistant, GenerateImageParams, Model, Provider } from '@renderer/types'
|
||||
import type { Assistant, GenerateImageParams, Model, Provider, RetrieveVideoContentParams } from '@renderer/types'
|
||||
import type { AiSdkModel, StreamTextParams } from '@renderer/types/aiCoreTypes'
|
||||
import {
|
||||
CreateVideoParams,
|
||||
@ -524,7 +524,7 @@ export default class ModernAiProvider {
|
||||
/**
|
||||
* We manually implement this method before aisdk supports it well
|
||||
*/
|
||||
public async retrieveVideoContent(params: RetrieveVideoParams): Promise<RetrieveVideoContentResult> {
|
||||
public async retrieveVideoContent(params: RetrieveVideoContentParams): Promise<RetrieveVideoContentResult> {
|
||||
return this.legacyProvider.retrieveVideoContent(params)
|
||||
}
|
||||
|
||||
|
||||
@ -5,7 +5,7 @@ import { isDedicatedImageGenerationModel, isFunctionCallingModel } from '@render
|
||||
import { getProviderByModel } from '@renderer/services/AssistantService'
|
||||
import { withSpanResult } from '@renderer/services/SpanManagerService'
|
||||
import { StartSpanParams } from '@renderer/trace/types/ModelSpanEntity'
|
||||
import type { GenerateImageParams, Model, Provider } from '@renderer/types'
|
||||
import type { GenerateImageParams, Model, Provider, RetrieveVideoContentParams } from '@renderer/types'
|
||||
import type { RequestOptions, SdkModel } from '@renderer/types/sdk'
|
||||
import {
|
||||
CreateVideoParams,
|
||||
@ -210,7 +210,7 @@ export default class AiProvider {
|
||||
}
|
||||
}
|
||||
|
||||
public async retrieveVideoContent(params: RetrieveVideoParams): Promise<RetrieveVideoContentResult> {
|
||||
public async retrieveVideoContent(params: RetrieveVideoContentParams): Promise<RetrieveVideoContentResult> {
|
||||
if (this.apiClient instanceof OpenAIResponseAPIClient && params.type === 'openai') {
|
||||
const response = await this.apiClient.retrieveVideoContent(params)
|
||||
return {
|
||||
|
||||
@ -1,7 +1,21 @@
|
||||
import { loggerService } from '@logger'
|
||||
import { retrieveVideo, retrieveVideoContent } from '@renderer/services/ApiService'
|
||||
import { getProviderById } from '@renderer/services/ProviderService'
|
||||
import { useAppDispatch, useAppSelector } from '@renderer/store'
|
||||
import { addVideoAction, removeVideoAction, setVideosAction, updateVideoAction } from '@renderer/store/video'
|
||||
import {
|
||||
addVideoAction,
|
||||
removeVideoAction,
|
||||
setVideoAction,
|
||||
setVideosAction,
|
||||
updateVideoAction
|
||||
} from '@renderer/store/video'
|
||||
import { SystemProviderIds } from '@renderer/types'
|
||||
import { Video } from '@renderer/types/video'
|
||||
import { video } from 'notion-helper'
|
||||
import { useCallback, useEffect } from 'react'
|
||||
import useSWR from 'swr'
|
||||
|
||||
const logger = loggerService.withContext('useVideo')
|
||||
|
||||
export const useVideos = (providerId: string) => {
|
||||
const videos = useAppSelector((state) => state.video.videoMap[providerId])
|
||||
@ -17,12 +31,19 @@ export const useVideos = (providerId: string) => {
|
||||
)
|
||||
|
||||
const updateVideo = useCallback(
|
||||
(update: Partial<Video> & { id: string }) => {
|
||||
(update: Partial<Omit<Video, 'status'>> & { id: string }) => {
|
||||
dispatch(updateVideoAction({ providerId, update }))
|
||||
},
|
||||
[dispatch, providerId]
|
||||
)
|
||||
|
||||
const setVideo = useCallback(
|
||||
(video: Video) => {
|
||||
dispatch(setVideoAction({ providerId, video }))
|
||||
},
|
||||
[dispatch, providerId]
|
||||
)
|
||||
|
||||
const setVideos = useCallback(
|
||||
(newVideos: Video[]) => {
|
||||
dispatch(setVideosAction({ providerId, videos: newVideos }))
|
||||
@ -43,6 +64,74 @@ export const useVideos = (providerId: string) => {
|
||||
}
|
||||
}, [setVideos, videos])
|
||||
|
||||
// update videos from api
|
||||
const openai = getProviderById(SystemProviderIds.openai)
|
||||
const fetcher = async () => {
|
||||
if (!videos || !openai) return []
|
||||
const openaiVideos = videos.filter((v) => v.type === 'openai')
|
||||
const jobs = openaiVideos.map((v) => retrieveVideo({ type: 'openai', videoId: v.id, provider: openai }))
|
||||
const result = await Promise.allSettled(jobs)
|
||||
return result.filter((p) => p.status === 'fulfilled').map((p) => p.value)
|
||||
}
|
||||
const { data, isLoading, error } = useSWR('video/openai/videos', fetcher, { refreshInterval: 3000 })
|
||||
useEffect(() => {
|
||||
logger.debug('effect', { data, videos })
|
||||
if (!data || !videos) return
|
||||
data.forEach((v) => {
|
||||
const retrievedVideo = v.video
|
||||
const storeVideo = videos.find((v) => v.id === retrievedVideo.id)
|
||||
if (!storeVideo) {
|
||||
logger.warn(`Try to update video ${retrievedVideo.id}, but it's not in the store.`)
|
||||
return
|
||||
}
|
||||
switch (retrievedVideo.status) {
|
||||
case 'in_progress':
|
||||
if (storeVideo.status === 'queued' || storeVideo.status === 'in_progress') {
|
||||
setVideo({
|
||||
...storeVideo,
|
||||
status: 'in_progress',
|
||||
progress: retrievedVideo.progress,
|
||||
metadata: retrievedVideo
|
||||
})
|
||||
}
|
||||
break
|
||||
case 'completed':
|
||||
// Only update it when in_progress/queued -> completed
|
||||
if (storeVideo.status === 'in_progress' || storeVideo.status === 'queued') {
|
||||
setVideo({ ...storeVideo, status: 'completed', thumbnail: null, metadata: retrievedVideo })
|
||||
// try to request thumbnail here.
|
||||
if (!openai) {
|
||||
logger.warn(
|
||||
`Try to fetch thumbnail for video ${retrievedVideo.id}, but provider ${providerId} not found.`
|
||||
)
|
||||
return
|
||||
}
|
||||
retrieveVideoContent({
|
||||
type: 'openai',
|
||||
provider: openai,
|
||||
videoId: retrievedVideo.id,
|
||||
query: { variant: 'thumbnail' }
|
||||
})
|
||||
.then((v) => {
|
||||
// TODO: this is a iamge/webp type response. save it somewhere.
|
||||
logger.debug('thumbnail resposne', v.response)
|
||||
})
|
||||
.catch((e) => {
|
||||
logger.error(`Failed to get thumbnail for video ${retrievedVideo.id}`, e as Error)
|
||||
})
|
||||
}
|
||||
break
|
||||
case 'failed':
|
||||
setVideo({
|
||||
...storeVideo,
|
||||
status: 'failed',
|
||||
error: retrievedVideo.error,
|
||||
metadata: retrievedVideo
|
||||
})
|
||||
}
|
||||
})
|
||||
}, [data])
|
||||
|
||||
return {
|
||||
videos: videos ?? [],
|
||||
addVideo,
|
||||
|
||||
@ -5,16 +5,12 @@ import { CheckCircleIcon, CircleXIcon, ClockIcon, DownloadIcon, PlusIcon } from
|
||||
import { useTranslation } from 'react-i18next'
|
||||
|
||||
export type VideoListProps = {
|
||||
providerId: string
|
||||
videos: Video[]
|
||||
activeVideoId?: string
|
||||
setActiveVideoId: (id: string | undefined) => void
|
||||
}
|
||||
|
||||
export const VideoList = ({ providerId, activeVideoId, setActiveVideoId }: VideoListProps) => {
|
||||
const { videos } = useVideos(providerId)
|
||||
|
||||
// const displayVideos = mockVideos
|
||||
const displayVideos = videos
|
||||
export const VideoList = ({ videos, activeVideoId, setActiveVideoId }: VideoListProps) => {
|
||||
|
||||
return (
|
||||
<div className="w-40 space-y-3 overflow-auto p-2">
|
||||
@ -23,7 +19,7 @@ export const VideoList = ({ providerId, activeVideoId, setActiveVideoId }: Video
|
||||
onClick={() => setActiveVideoId(undefined)}>
|
||||
<PlusIcon size={24} />
|
||||
</div>
|
||||
{displayVideos.map((video) => (
|
||||
{videos.map((video) => (
|
||||
<VideoListItem
|
||||
key={video.id}
|
||||
video={video}
|
||||
|
||||
@ -18,6 +18,7 @@ import { ProviderSetting } from './settings/ProviderSetting'
|
||||
import { SettingsGroup } from './settings/shared'
|
||||
import { VideoList } from './VideoList'
|
||||
import { VideoPanel } from './VideoPanel'
|
||||
import { useVideos } from '@renderer/hooks/video/useVideos'
|
||||
|
||||
export const VideoPage = () => {
|
||||
const { t } = useTranslation()
|
||||
@ -47,6 +48,7 @@ export const VideoPage = () => {
|
||||
[updateParams]
|
||||
)
|
||||
|
||||
const { videos } = useVideos(providerId)
|
||||
const activeVideo = useMemo(() => mockVideos.find((v) => v.id === activeVideoId), [activeVideoId])
|
||||
|
||||
return (
|
||||
@ -71,7 +73,7 @@ export const VideoPage = () => {
|
||||
<VideoPanel provider={provider} params={params} updateParams={updateParams} video={activeVideo} />
|
||||
<Divider orientation="vertical" />
|
||||
{/* Video list */}
|
||||
<VideoList providerId={providerId} activeVideoId={activeVideoId} setActiveVideoId={setActiveVideoId} />
|
||||
<VideoList videos={videos} activeVideoId={activeVideoId} setActiveVideoId={setActiveVideoId} />
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
|
||||
@ -10,7 +10,7 @@ import { isDedicatedImageGenerationModel, isEmbeddingModel } from '@renderer/con
|
||||
import { getStoreSetting } from '@renderer/hooks/useSettings'
|
||||
import i18n from '@renderer/i18n'
|
||||
import store from '@renderer/store'
|
||||
import type { FetchChatCompletionParams } from '@renderer/types'
|
||||
import type { FetchChatCompletionParams, RetrieveVideoContentParams } from '@renderer/types'
|
||||
import { Assistant, MCPServer, MCPTool, Model, Provider } from '@renderer/types'
|
||||
import type { StreamTextParams } from '@renderer/types/aiCoreTypes'
|
||||
import { type Chunk, ChunkType } from '@renderer/types/chunk'
|
||||
@ -414,7 +414,7 @@ export async function retrieveVideo(params: RetrieveVideoParams): Promise<Retrie
|
||||
return ai.retrieveVideo(params)
|
||||
}
|
||||
|
||||
export async function retrieveVideoContent(params: RetrieveVideoParams): Promise<RetrieveVideoContentResult> {
|
||||
export async function retrieveVideoContent(params: RetrieveVideoContentParams): Promise<RetrieveVideoContentResult> {
|
||||
const ai = new AiProviderNew(params.provider)
|
||||
return ai.retrieveVideoContent(params)
|
||||
}
|
||||
|
||||
@ -2,7 +2,7 @@ import { loggerService } from '@logger'
|
||||
import { createSlice, PayloadAction } from '@reduxjs/toolkit'
|
||||
import { Video } from '@renderer/types/video'
|
||||
|
||||
const logger = loggerService.withContext('Store:paintings')
|
||||
const logger = loggerService.withContext('Store:video')
|
||||
|
||||
export interface VideoState {
|
||||
/** Provider ID to videos */
|
||||
@ -32,15 +32,32 @@ const videoSlice = createSlice({
|
||||
},
|
||||
updateVideo: (
|
||||
state: VideoState,
|
||||
action: PayloadAction<{ providerId: string; update: Partial<Video> & { id: string } }>
|
||||
action: PayloadAction<{ providerId: string; update: Partial<Omit<Video, 'status'>> & { id: string } }>
|
||||
) => {
|
||||
const { providerId, update } = action.payload
|
||||
|
||||
const existingIndex = state.videoMap[providerId]?.findIndex((c) => c.id === update.id)
|
||||
if (existingIndex && existingIndex !== -1) {
|
||||
state.videoMap[providerId] = state.videoMap[providerId]?.map((c) => (c.id === update.id ? { ...c, update } : c))
|
||||
const videos = state.videoMap[providerId]
|
||||
if (videos) {
|
||||
let video = videos.find((v) => v.id === update.id)
|
||||
if (video) {
|
||||
video = { ...video, ...update }
|
||||
} else {
|
||||
logger.error(`Video with id ${update.id} not found in ${providerId}`)
|
||||
}
|
||||
} else {
|
||||
logger.error(`Video with id ${update.id} not found in ${providerId}`)
|
||||
logger.error(`Videos with Provider ${providerId} is undefined.`)
|
||||
}
|
||||
},
|
||||
setVideo: (state: VideoState, action: PayloadAction<{ providerId: string; video: Video }>) => {
|
||||
const { providerId, video } = action.payload
|
||||
if (state.videoMap[providerId]) {
|
||||
const index = state.videoMap[providerId].findIndex((v) => v.id === video.id)
|
||||
if (index !== -1) {
|
||||
state.videoMap[providerId][index] = video
|
||||
} else {
|
||||
state.videoMap[providerId].push(video)
|
||||
}
|
||||
} else {
|
||||
state.videoMap[providerId] = [video]
|
||||
}
|
||||
},
|
||||
setVideos: (state: VideoState, action: PayloadAction<{ providerId: string; videos: Video[] }>) => {
|
||||
@ -54,6 +71,7 @@ export const {
|
||||
addVideo: addVideoAction,
|
||||
removeVideo: removeVideoAction,
|
||||
updateVideo: updateVideoAction,
|
||||
setVideo: setVideoAction,
|
||||
setVideos: setVideosAction
|
||||
} = videoSlice.actions
|
||||
|
||||
|
||||
Loading…
Reference in New Issue
Block a user