Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 26 additions & 0 deletions frontend/src/components/SettingsPanel.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,9 @@ interface SettingsPanelProps {
onVaceEnabledChange?: (enabled: boolean) => void;
vaceContextScale?: number;
onVaceContextScaleChange?: (scale: number) => void;
// RIFE settings
rifeEnabled?: boolean;
onRifeEnabledChange?: (enabled: boolean) => void;
}

export function SettingsPanel({
Expand Down Expand Up @@ -126,6 +129,8 @@ export function SettingsPanel({
onVaceEnabledChange,
vaceContextScale = 1.0,
onVaceContextScaleChange,
rifeEnabled = true,
onRifeEnabledChange,
}: SettingsPanelProps) {
// Local slider state management hooks
const noiseScaleSlider = useLocalSliderValue(noiseScale, onNoiseScaleChange);
Expand Down Expand Up @@ -389,6 +394,27 @@ export function SettingsPanel({
</div>
)}

{/* RIFE Toggle */}
<div className="space-y-2">
<div className="flex items-center justify-between gap-2">
<LabelWithTooltip
label="RIFE Interpolation"
tooltip="Enable RIFE (Real-Time Intermediate Flow Estimation) frame interpolation to double the frame rate of the output video. This increases smoothness but may add latency. Requires pipeline reload to take effect."
className="text-sm font-medium"
/>
<Toggle
pressed={rifeEnabled}
onPressedChange={onRifeEnabledChange || (() => {})}
variant="outline"
size="sm"
className="h-7"
disabled={isStreaming || isLoading}
>
{rifeEnabled ? "ON" : "OFF"}
</Toggle>
</div>
</div>

{currentPipeline?.supportsLoRA && (
<div className="space-y-4">
<LoRAManager
Expand Down
39 changes: 38 additions & 1 deletion frontend/src/components/StatusBar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,17 @@ interface StatusBarProps {
className?: string;
fps?: number;
bitrate?: number;
originalFPS?: number | null;
interpolatedFPS?: number | null;
}

export function StatusBar({ className = "", fps, bitrate }: StatusBarProps) {
export function StatusBar({
className = "",
fps,
bitrate,
originalFPS,
interpolatedFPS,
}: StatusBarProps) {
const MetricItem = ({
label,
value,
Expand Down Expand Up @@ -35,13 +43,42 @@ export function StatusBar({ className = "", fps, bitrate }: StatusBarProps) {

const fpsValue = fps !== undefined && fps > 0 ? fps.toFixed(1) : "N/A";
const bitrateValue = formatBitrate(bitrate);
const originalFPSValue =
originalFPS !== null && originalFPS !== undefined && originalFPS > 0
? originalFPS.toFixed(1)
: null;
const interpolatedFPSValue =
interpolatedFPS !== null &&
interpolatedFPS !== undefined &&
interpolatedFPS > 0
? interpolatedFPS.toFixed(1)
: null;

// Show detailed FPS (Original + Interpolated) only when RIFE is enabled (interpolatedFPS exists)
// Otherwise show regular FPS
const showDetailedFPS = interpolatedFPSValue !== null;

return (
<div
className={`border-t bg-muted/30 px-6 py-2 flex items-center justify-end flex-shrink-0 ${className}`}
>
<div className="flex items-center gap-6">
{showDetailedFPS ? (
<>
{originalFPSValue !== null && (
<MetricItem label="Original FPS" value={originalFPSValue} unit=" fps" />
)}
{interpolatedFPSValue !== null && (
<MetricItem
label="Interpolated FPS"
value={interpolatedFPSValue}
unit=" fps"
/>
)}
</>
) : (
<MetricItem label="FPS" value={fpsValue} />
)}
<MetricItem label="Bitrate" value={bitrateValue} />
</div>
</div>
Expand Down
1 change: 1 addition & 0 deletions frontend/src/hooks/useStreamState.ts
Original file line number Diff line number Diff line change
Expand Up @@ -148,6 +148,7 @@ export function useStreamState() {
paused: false,
loraMergeStrategy: "permanent_merge",
inputMode: initialDefaults.inputMode,
rifeEnabled: true, // RIFE enabled by default
});

const [promptData, setPromptData] = useState<PromptData>({
Expand Down
27 changes: 27 additions & 0 deletions frontend/src/hooks/useWebRTC.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ interface InitialParameters {
kv_cache_attention_bias?: number;
vace_ref_images?: string[];
vace_context_scale?: number;
rife_enabled?: boolean;
}

interface UseWebRTCOptions {
Expand All @@ -38,6 +39,8 @@ export function useWebRTC(options?: UseWebRTCOptions) {
useState<RTCPeerConnectionState>("new");
const [isConnecting, setIsConnecting] = useState(false);
const [isStreaming, setIsStreaming] = useState(false);
const [originalFPS, setOriginalFPS] = useState<number | null>(null);
const [interpolatedFPS, setInterpolatedFPS] = useState<number | null>(null);

const peerConnectionRef = useRef<RTCPeerConnection | null>(null);
const dataChannelRef = useRef<RTCDataChannel | null>(null);
Expand Down Expand Up @@ -115,11 +118,28 @@ export function useWebRTC(options?: UseWebRTCOptions) {
peerConnectionRef.current.close();
peerConnectionRef.current = null;
}
// Reset FPS data
setOriginalFPS(null);
setInterpolatedFPS(null);

// Notify parent component
if (options?.onStreamStop) {
options.onStreamStop();
}
}

// Handle FPS update notification from backend
if (data.type === "fps_update") {
if (typeof data.original_fps === "number") {
setOriginalFPS(data.original_fps);
}
if (typeof data.interpolated_fps === "number") {
setInterpolatedFPS(data.interpolated_fps);
} else {
// Clear interpolated FPS if not provided (RIFE disabled)
setInterpolatedFPS(null);
}
}
} catch (error) {
console.error("Failed to parse data channel message:", error);
}
Expand Down Expand Up @@ -327,6 +347,7 @@ export function useWebRTC(options?: UseWebRTCOptions) {
spout_receiver?: { enabled: boolean; name: string };
vace_ref_images?: string[];
vace_context_scale?: number;
rife_enabled?: boolean;
}) => {
if (
dataChannelRef.current &&
Expand Down Expand Up @@ -373,6 +394,10 @@ export function useWebRTC(options?: UseWebRTCOptions) {
// Clear any queued ICE candidates
queuedCandidatesRef.current = [];

// Reset FPS data
setOriginalFPS(null);
setInterpolatedFPS(null);

setRemoteStream(null);
setConnectionState("new");
setIsStreaming(false);
Expand All @@ -397,5 +422,7 @@ export function useWebRTC(options?: UseWebRTCOptions) {
stopStream,
updateVideoTrack,
sendParameterUpdate,
originalFPS,
interpolatedFPS,
};
}
3 changes: 2 additions & 1 deletion frontend/src/lib/api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ export interface WebRTCOfferRequest {
kv_cache_attention_bias?: number;
vace_ref_images?: string[];
vace_context_scale?: number;
rife_enabled?: boolean;
};
}

Expand Down Expand Up @@ -424,4 +425,4 @@ export const getPipelineSchemas =

const result = await response.json();
return result;
};
};
30 changes: 29 additions & 1 deletion frontend/src/pages/StreamPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -157,6 +157,8 @@ export function StreamPage() {
stopStream,
updateVideoTrack,
sendParameterUpdate,
originalFPS,
interpolatedFPS,
} = useWebRTC();

// Computed loading state - true when downloading models, loading pipeline, or connecting WebRTC
Expand Down Expand Up @@ -299,6 +301,8 @@ export function StreamPage() {
inputMode: modeToUse,
denoisingSteps: defaults.denoisingSteps,
resolution,
// RIFE is enabled by default for all pipelines
rifeEnabled: settings.rifeEnabled ?? true,
noiseScale: defaults.noiseScale,
noiseController: defaults.noiseController,
loras: [], // Clear LoRA controls when switching pipelines
Expand Down Expand Up @@ -487,6 +491,16 @@ export function StreamPage() {
// Note: This setting requires pipeline reload, so we don't send parameter update here
};

const handleRifeEnabledChange = (enabled: boolean) => {
updateSettings({ rifeEnabled: enabled });
// Send RIFE enabled update to backend if streaming
if (isStreaming) {
sendParameterUpdate({
rife_enabled: enabled,
});
}
};

const handleRefImagesChange = (images: string[]) => {
updateSettings({ refImages: images });
};
Expand Down Expand Up @@ -749,6 +763,9 @@ export function StreamPage() {
loadParams = { ...loadParams, ...vaceParams };
}

// Add RIFE parameter
loadParams.rife_enabled = settings.rifeEnabled ?? true;

console.log(
`Loading ${pipelineIdToUse} with resolution ${resolution.width}x${resolution.height}`,
loadParams
Expand Down Expand Up @@ -791,6 +808,7 @@ export function StreamPage() {
spout_receiver?: { enabled: boolean; name: string };
vace_ref_images?: string[];
vace_context_scale?: number;
rife_enabled?: boolean;
} = {
// Signal the intended input mode to the backend so it doesn't
// briefly fall back to text mode before video frames arrive
Expand Down Expand Up @@ -841,6 +859,9 @@ export function StreamPage() {
initialParameters.spout_receiver = settings.spoutReceiver;
}

// RIFE interpolation
initialParameters.rife_enabled = settings.rifeEnabled ?? true;

// Reset paused state when starting a fresh stream
updateSettings({ paused: false });

Expand Down Expand Up @@ -1117,12 +1138,19 @@ export function StreamPage() {
onVaceEnabledChange={handleVaceEnabledChange}
vaceContextScale={settings.vaceContextScale ?? 1.0}
onVaceContextScaleChange={handleVaceContextScaleChange}
rifeEnabled={settings.rifeEnabled ?? true}
onRifeEnabledChange={handleRifeEnabledChange}
/>
</div>
</div>

{/* Status Bar */}
<StatusBar fps={webrtcStats.fps} bitrate={webrtcStats.bitrate} />
<StatusBar
fps={webrtcStats.fps}
bitrate={webrtcStats.bitrate}
originalFPS={originalFPS}
interpolatedFPS={interpolatedFPS}
/>

{/* Download Dialog */}
{pipelineNeedsModels && (
Expand Down
2 changes: 2 additions & 0 deletions frontend/src/types/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,8 @@ export interface SettingsState {
vaceEnabled?: boolean;
refImages?: string[];
vaceContextScale?: number;
// RIFE-specific settings
rifeEnabled?: boolean;
}

export interface PipelineInfo {
Expand Down
Loading