Files
LTX-DeskWEB/frontend/hooks/use-retake.ts
T

166 lines
4.7 KiB
TypeScript

import { useCallback, useState } from 'react'
import type { components } from '../generated/backend-openapi'
import { ApiClient } from '../lib/api-client'
import { canCancelLocalJob, withGenerationActive } from '../lib/generation-active'
import { logger } from '../lib/logger'
import { prepareGenerationInput } from '../lib/generation-input'
import { useAppSettings } from '../contexts/AppSettingsContext'
export type RetakeMode = 'replace_audio_and_video' | 'replace_video' | 'replace_audio'
// ltxv-api /v1/retake and /v2/extend accept ltx-2-pro / ltx-2-3-pro.
// Desktop maps those to pipeline "pro".
export type RetakeExtendModel = components['schemas']['RetakeRequest']['model']
// Runtime options for the retake/extend MODEL dropdown. Checked against the OpenAPI union
// so a schema change that adds/removes a value fails typecheck until this list is updated.
export const RETAKE_EXTEND_MODELS = ['pro'] as const satisfies ReadonlyArray<RetakeExtendModel>
/** Map a persisted video pipeline id onto the nearest retake/extend model. */
export function retakeExtendModelFromPipeline(
_model: string | undefined | null,
): RetakeExtendModel {
return 'pro'
}
export interface RetakeSubmitParams {
videoPath: string
startTime: number
duration: number
prompt: string
mode: RetakeMode
resolution?: { width: number; height: number }
model: RetakeExtendModel
}
export interface RetakeResult {
videoPath: string
}
interface UseRetakeState {
isRetaking: boolean
canCancel: boolean
retakeStatus: string
retakeError: string | null
result: RetakeResult | null
}
export function useRetake() {
const { shouldVideoGenerateWithLtxApi, shouldImageGenerateWithFalApi } = useAppSettings()
const [state, setState] = useState<UseRetakeState>({
isRetaking: false,
canCancel: false,
retakeStatus: '',
retakeError: null,
result: null,
})
const submitRetake = useCallback(async (params: RetakeSubmitParams) => {
if (!params.videoPath) return
setState({
isRetaking: true,
canCancel: canCancelLocalJob('video', shouldVideoGenerateWithLtxApi, shouldImageGenerateWithFalApi),
retakeStatus: 'Generating',
retakeError: null,
result: null,
})
await withGenerationActive(async () => {
let preparedVideoPath: string
try {
preparedVideoPath = (await prepareGenerationInput(params.videoPath)) ?? params.videoPath
} catch (error) {
const message = error instanceof Error ? error.message : 'Failed to prepare source video'
logger.error(`Retake input error: ${message}`)
setState({
isRetaking: false,
canCancel: false,
retakeStatus: '',
retakeError: message,
result: null,
})
return
}
const result = await ApiClient.retake({
video_path: preparedVideoPath,
start_time: params.startTime,
duration: params.duration,
prompt: params.prompt,
mode: params.mode,
resolution: params.resolution,
model: params.model,
})
if (!result.ok) {
logger.error(`Retake error: ${result.error.message}`)
setState({
isRetaking: false,
canCancel: false,
retakeStatus: '',
retakeError: result.error.message,
result: null,
})
return
}
const payload = result.data
if (payload.status === 'cancelled') {
setState({
isRetaking: false,
canCancel: false,
retakeStatus: 'Cancelled',
retakeError: null,
result: null,
})
return
}
if ('video_path' in payload) {
setState({
isRetaking: false,
canCancel: false,
retakeStatus: 'Retake complete!',
retakeError: null,
result: {
videoPath: payload.video_path,
},
})
return
}
logger.error(`Retake completed without local video payload: ${JSON.stringify(payload.result)}`)
const errorMsg = 'Retake completed but no local video file was returned'
setState({
isRetaking: false,
canCancel: false,
retakeStatus: '',
retakeError: errorMsg,
result: null,
})
})
}, [shouldImageGenerateWithFalApi, shouldVideoGenerateWithLtxApi])
const resetRetake = useCallback(() => {
setState({
isRetaking: false,
canCancel: false,
retakeStatus: '',
retakeError: null,
result: null,
})
}, [])
return {
submitRetake,
resetRetake,
isRetaking: state.isRetaking,
canCancel: state.canCancel,
retakeStatus: state.retakeStatus,
retakeError: state.retakeError,
retakeResult: state.result,
}
}