Create standalone browser port of LTX Athena with authenticated native API transport

This commit is contained in:
Mikei386 committed 2026-09-29 17:59:23 +02:00
commit 1333b0c36b
202 files changed
+63054

No files matched your search

@@ -0,0 +1,315 @@
import type { components } from '../generated/backend-openapi'
export type VideoGenerationModelSpecsResponse = components['schemas']['GenerateVideoModelsSpecsResponse']
export type VideoGenerationModelSpecItem = components['schemas']['LTXVideoGenerationModelSpecItem']
export type VideoGenerationResolutionSpec = components['schemas']['LTXVideoGenerationResolutionSpec']
export type VideoGenerationOfferingCapabilities = NonNullable<
VideoGenerationModelSpecItem['spec']['capabilities']
>
export type VideoGenerationPipeline = components['schemas']['GenerateVideoRequest']['model']
export type VideoGenerationResolution = components['schemas']['GenerateVideoRequest']['resolution']
export type VideoGenerationDuration = Exclude<
components['schemas']['GenerateVideoRequest']['duration'],
null
>
export type VideoGenerationFps = components['schemas']['GenerateVideoRequest']['fps']
export type VideoGenerationAspectRatio = components['schemas']['GenerateVideoRequest']['aspectRatio']
export interface VideoGenerationSettingsShape {
model: string
duration: number | null
videoResolution: string
fps: number
aspectRatio?: string
audio?: boolean
}
export interface ResolvedVideoGenerationOptions {
modelOptions: VideoGenerationModelSpecItem[]
resolutionOptions: VideoGenerationResolution[]
fpsOptions: VideoGenerationFps[]
durationOptions: VideoGenerationDuration[]
selectedModel: VideoGenerationPipeline | null
selectedResolution: VideoGenerationResolution | null
selectedFps: VideoGenerationFps | null
selectedDuration: VideoGenerationDuration | null
autoDurationAvailable: boolean
hasCompatibleOptions: boolean
}
type DurationSelectionMode = 'preserve' | 'smallest_valid'
/** GenSpace picker floor. The API envelope includes 2–5s so gap fill can request shorts. */
export const GENSPACE_MIN_SELECTABLE_DURATION_S = 6
interface ResolveVideoGenerationOptionsParams<T extends VideoGenerationSettingsShape> {
settings: T
modelSpecs: VideoGenerationModelSpecItem[]
hasAudio?: boolean
minimumDuration?: number
durationSelection?: DurationSelectionMode
}
function getResolutionMap(
item: VideoGenerationModelSpecItem,
options: { hasAudio: boolean },
): Record<string, VideoGenerationResolutionSpec> {
const { hasAudio } = options
if (!hasAudio) {
return item.spec.supported_resolutions_durations
}
// A model with no a2v spec doesn't support audio-conditioned generation at all —
// must not fall back to the plain (non-a2v) matrix, or it looks compatible when it isn't.
return item.spec.a2v_supported_resolutions_durations ?? {}
}
function getResolutionEntries(
item: VideoGenerationModelSpecItem,
options: { hasAudio: boolean },
): Array<[VideoGenerationResolution, VideoGenerationResolutionSpec]> {
return Object.entries(getResolutionMap(item, options)).map(([resolution, spec]) => [
resolution as VideoGenerationResolution,
spec,
])
}
function getDurationsForFps(
resolutionSpec: VideoGenerationResolutionSpec,
fps: VideoGenerationFps,
): VideoGenerationDuration[] {
return (resolutionSpec.fps_to_durations[String(fps)] ?? []) as VideoGenerationDuration[]
}
function filterDurationsByMinimum(
durations: VideoGenerationDuration[],
minimumDuration: number | undefined,
): VideoGenerationDuration[] {
if (minimumDuration === undefined) return durations
return durations.filter((duration) => duration >= minimumDuration)
}
function getCompatibleFps(
resolutionSpec: VideoGenerationResolutionSpec,
options: { minimumDuration: number | undefined },
): VideoGenerationFps[] {
const { minimumDuration } = options
return Object.keys(resolutionSpec.fps_to_durations).map((fps) => Number(fps) as VideoGenerationFps).filter((fps) => (
filterDurationsByMinimum(getDurationsForFps(resolutionSpec, fps), minimumDuration).length > 0
))
}
function getCompatibleResolutionEntries(
item: VideoGenerationModelSpecItem,
options: { hasAudio: boolean; minimumDuration: number | undefined },
): Array<[VideoGenerationResolution, VideoGenerationResolutionSpec]> {
return getResolutionEntries(item, { hasAudio: options.hasAudio }).filter(([, resolutionSpec]) => (
getCompatibleFps(resolutionSpec, { minimumDuration: options.minimumDuration }).length > 0
))
}
function getCompatibleModelOptions(
modelSpecs: VideoGenerationModelSpecItem[],
options: { hasAudio: boolean; minimumDuration: number | undefined },
): VideoGenerationModelSpecItem[] {
const { hasAudio, minimumDuration } = options
// Always filter by resolution compatibility — hasAudio alone (independent of any
// minimumDuration constraint) can exclude a model, e.g. a fast-tier pipeline with no
// a2v spec. Skipping this whenever minimumDuration is unset used to let incompatible
// (audio-unsupported) models stay selectable and get stuck with no valid resolution.
return modelSpecs.filter((item) => (
getCompatibleResolutionEntries(item, { hasAudio, minimumDuration }).length > 0
))
}
function emptyResolvedOptions(
modelOptions: VideoGenerationModelSpecItem[],
extras: Partial<ResolvedVideoGenerationOptions> = {},
): ResolvedVideoGenerationOptions {
return {
modelOptions,
resolutionOptions: [],
fpsOptions: [],
durationOptions: [],
selectedModel: null,
selectedResolution: null,
selectedFps: null,
selectedDuration: null,
autoDurationAvailable: false,
hasCompatibleOptions: false,
...extras,
}
}
function chooseOption<T>(current: string | number | null, options: T[]): T | null {
return options.find((option) => option === current) ?? options[0] ?? null
}
export function getVideoGenerationModelSpecs(
specs: VideoGenerationModelSpecsResponse | null | undefined,
options: { useApiSpecs: boolean },
): VideoGenerationModelSpecItem[] {
const { useApiSpecs } = options
if (!specs) return []
return useApiSpecs ? specs.api_models : specs.local_models
}
export function getLocalOfferingCapabilities(
specs: VideoGenerationModelSpecsResponse | null | undefined,
): VideoGenerationOfferingCapabilities | null {
return specs?.local_models[0]?.spec.capabilities ?? null
}
export function getApiOfferingCapabilities(
specs: VideoGenerationModelSpecsResponse | null | undefined,
pipeline: string | null | undefined,
): VideoGenerationOfferingCapabilities | null {
if (!specs || !pipeline) return null
return specs.api_models.find((item) => item.pipeline === pipeline)?.spec.capabilities ?? null
}
export function resolveVideoGenerationOptions<T extends VideoGenerationSettingsShape>({
settings,
modelSpecs,
hasAudio = false,
minimumDuration,
durationSelection = 'preserve',
}: ResolveVideoGenerationOptionsParams<T>): ResolvedVideoGenerationOptions {
const modelOptions = getCompatibleModelOptions(modelSpecs, { hasAudio, minimumDuration })
const selectedModelItem = modelOptions.find((item) => item.pipeline === settings.model) ?? modelOptions[0] ?? null
if (!selectedModelItem) {
return emptyResolvedOptions(modelOptions)
}
const resolutionEntries = getCompatibleResolutionEntries(selectedModelItem, { hasAudio, minimumDuration })
const resolutionOptions = resolutionEntries.map(([resolution]) => resolution)
const selectedResolution = chooseOption(settings.videoResolution, resolutionOptions)
if (!selectedResolution) {
return emptyResolvedOptions(modelOptions, { selectedModel: selectedModelItem.pipeline, resolutionOptions })
}
const selectedResolutionSpec = resolutionEntries.find(([resolution]) => resolution === selectedResolution)?.[1] ?? null
if (!selectedResolutionSpec) {
return emptyResolvedOptions(modelOptions, {
selectedModel: selectedModelItem.pipeline,
resolutionOptions,
selectedResolution,
})
}
const fpsOptions = getCompatibleFps(selectedResolutionSpec, { minimumDuration })
const selectedFps = chooseOption(settings.fps, fpsOptions)
if (!selectedFps) {
return emptyResolvedOptions(modelOptions, {
selectedModel: selectedModelItem.pipeline,
resolutionOptions,
selectedResolution,
fpsOptions,
})
}
const durationOptions = filterDurationsByMinimum(
getDurationsForFps(selectedResolutionSpec, selectedFps),
minimumDuration,
)
const autoDurationAvailable = !hasAudio && Boolean(selectedModelItem.spec.capabilities?.auto_duration)
const selectedDuration = durationSelection === 'smallest_valid'
? durationOptions[0] ?? null
: autoDurationAvailable && settings.duration === null
? null
: chooseOption(settings.duration, durationOptions)
return {
modelOptions,
resolutionOptions,
fpsOptions,
durationOptions,
selectedModel: selectedModelItem.pipeline,
selectedResolution,
selectedFps,
selectedDuration,
autoDurationAvailable,
hasCompatibleOptions: selectedDuration !== null || autoDurationAvailable,
}
}
export function sanitizeVideoGenerationSettings<T extends VideoGenerationSettingsShape>(
settings: T,
modelSpecs: VideoGenerationModelSpecItem[],
options: {
hasAudio?: boolean
minimumDuration?: number
durationSelection?: DurationSelectionMode
} = {},
): T | null {
const resolved = resolveVideoGenerationOptions({
settings,
modelSpecs,
hasAudio: options.hasAudio,
minimumDuration: options.minimumDuration,
durationSelection: options.durationSelection,
})
if (
!resolved.hasCompatibleOptions
|| !resolved.selectedModel
|| !resolved.selectedResolution
|| !resolved.selectedFps
|| (resolved.selectedDuration === null && !resolved.autoDurationAvailable)
) {
return null
}
return {
...settings,
model: resolved.selectedModel,
videoResolution: resolved.selectedResolution,
fps: resolved.selectedFps,
duration: resolved.selectedDuration,
aspectRatio: (settings.aspectRatio === '9:16' ? '9:16' : '16:9') as VideoGenerationAspectRatio,
}
}
export function areVideoGenerationSettingsEquivalent<T extends VideoGenerationSettingsShape>(
left: T,
right: T,
): boolean {
return (
left.model === right.model
&& left.duration === right.duration
&& left.videoResolution === right.videoResolution
&& left.fps === right.fps
&& (left.aspectRatio ?? '16:9') === (right.aspectRatio ?? '16:9')
&& (left.audio ?? false) === (right.audio ?? false)
)
}
/**
* Fallback labels for persisted `generationParams.model` / picker pipeline ids.
*
* Prefer `resolvePipelineDisplayName` (backend spec) at generation time. Local ids like
* "fast"/"pro" are shared across LTX versions, so the fallback here stays version-agnostic —
* only API ids (`fast-2.5`, …) encode their version in the id itself.
*/
const PIPELINE_DISPLAY_NAMES: Record<string, string> = {
fast: 'LTX Fast',
pro: 'LTX Pro',
'fast-2.5': 'LTX-2.5 Fast',
'pro-2.5': 'LTX-2.5 Pro',
}
/** Returns a display label for a known video pipeline, or null if unknown/absent. */
export function formatPipelineDisplayName(model: string | undefined | null): string | null {
if (!model) return null
return PIPELINE_DISPLAY_NAMES[model] ?? null
}
/**
* Version-correct label for `pipeline` taken from the backend specs currently in effect.
* Returns null when the pipeline isn't in `modelSpecs`, so callers can fall back.
*/
export function resolvePipelineDisplayName(
modelSpecs: VideoGenerationModelSpecItem[],
pipeline: string | undefined | null,
): string | null {
if (!pipeline) return null
return modelSpecs.find((item) => item.pipeline === pipeline)?.spec.display_name ?? null
}