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
14 changes: 14 additions & 0 deletions frontend/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

79 changes: 79 additions & 0 deletions frontend/src/components/SettingsPanel.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import { Input } from "./ui/input";
import { Button } from "./ui/button";
import { Toggle } from "./ui/toggle";
import { SliderWithInput } from "./ui/slider-with-input";
import { OrderedMultiSelect } from "./ui/ordered-multi-select";
import { Hammer, Info, Minus, Plus, RotateCcw } from "lucide-react";
import { PARAMETER_METADATA } from "../data/parameterMetadata";
import { DenoisingStepsSlider } from "./DenoisingStepsSlider";
Expand Down Expand Up @@ -87,6 +88,12 @@ interface SettingsPanelProps {
onVaceEnabledChange?: (enabled: boolean) => void;
vaceContextScale?: number;
onVaceContextScaleChange?: (scale: number) => void;
// Preprocessor settings
preprocessorType?: string | null;
onPreprocessorTypeChange?: (type: string | null) => void;
preprocessorTypes?: string[];
onPreprocessorTypesChange?: (types: string[]) => void;
availablePreprocessors?: string[];
}

export function SettingsPanel({
Expand Down Expand Up @@ -126,6 +133,11 @@ export function SettingsPanel({
onVaceEnabledChange,
vaceContextScale = 1.0,
onVaceContextScaleChange,
preprocessorType = null,
onPreprocessorTypeChange,
preprocessorTypes = [],
onPreprocessorTypesChange,
availablePreprocessors = [],
}: SettingsPanelProps) {
// Local slider state management hooks
const noiseScaleSlider = useLocalSliderValue(noiseScale, onNoiseScaleChange);
Expand Down Expand Up @@ -389,6 +401,73 @@ export function SettingsPanel({
</div>
)}

{/* Preprocessor - available for all pipelines in video mode */}
{inputMode === "video" && (
<div className="space-y-2">
<div className="flex items-center justify-between gap-2">
<LabelWithTooltip
label={PARAMETER_METADATA.preprocessorType.label}
tooltip={PARAMETER_METADATA.preprocessorType.tooltip + " You can select multiple preprocessors. Each runs in a separate process."}
className="text-xs text-muted-foreground w-32"
/>
{onPreprocessorTypesChange ? (
<OrderedMultiSelect
options={availablePreprocessors.map((id) => {
// Format preprocessor ID for display
const displayNameMap: Record<string, string> = {
depthanything: "Depth Anything",
passthrough: "Passthrough",
};
const displayName = displayNameMap[id] || id
.split("-")
.map(word => word.charAt(0).toUpperCase() + word.slice(1))
.join(" ");
return { value: id, label: displayName };
})}
value={preprocessorTypes || []}
onChange={onPreprocessorTypesChange}
placeholder="Add preprocessor..."
disabled={isStreaming || isLoading}
className="flex-1"
/>
) : (
// Backward compatibility: single select
<Select
value={preprocessorType || "none"}
onValueChange={value =>
onPreprocessorTypeChange?.(
value === "none" ? null : value
)
}
disabled={isStreaming || isLoading}
>
<SelectTrigger className="flex-1 h-7">
<SelectValue />
</SelectTrigger>
<SelectContent>
<SelectItem value="none">None</SelectItem>
{availablePreprocessors.map((id) => {
const displayNameMap: Record<string, string> = {
depthanything: "Depth Anything",
passthrough: "Passthrough",
};
const displayName = displayNameMap[id] || id
.split("-")
.map(word => word.charAt(0).toUpperCase() + word.slice(1))
.join(" ");
return (
<SelectItem key={id} value={id}>
{displayName}
</SelectItem>
);
})}
</SelectContent>
</Select>
)}
</div>
</div>
)}

{currentPipeline?.supportsLoRA && (
<div className="space-y-4">
<LoRAManager
Expand Down
18 changes: 17 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;
latency?: number | null;
showLatency?: boolean; // Only show latency for V2V mode
}

export function StatusBar({ className = "", fps, bitrate }: StatusBarProps) {
export function StatusBar({
className = "",
fps,
bitrate,
latency,
showLatency = false,
}: StatusBarProps) {
const MetricItem = ({
label,
value,
Expand Down Expand Up @@ -35,6 +43,11 @@ export function StatusBar({ className = "", fps, bitrate }: StatusBarProps) {

const fpsValue = fps !== undefined && fps > 0 ? fps.toFixed(1) : "N/A";
const bitrateValue = formatBitrate(bitrate);
const latencyValue =
latency !== undefined && latency !== null && latency > 0
? latency.toFixed(1)
: "N/A";
const latencyUnit = latencyValue === "N/A" ? "" : "ms";

return (
<div
Expand All @@ -43,6 +56,9 @@ export function StatusBar({ className = "", fps, bitrate }: StatusBarProps) {
<div className="flex items-center gap-6">
<MetricItem label="FPS" value={fpsValue} />
<MetricItem label="Bitrate" value={bitrateValue} />
{showLatency && (
<MetricItem label="Latency" value={latencyValue} unit={latencyUnit} />
)}
</div>
</div>
);
Expand Down
106 changes: 106 additions & 0 deletions frontend/src/components/ui/multi-select.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
import * as React from "react";
import { Check, ChevronDown } from "lucide-react";
import { cn } from "../../lib/utils";
import { Button } from "./button";

interface MultiSelectProps {
options: Array<{ value: string; label: string }>;
value: string[];
onChange: (value: string[]) => void;
placeholder?: string;
disabled?: boolean;
className?: string;
}

export function MultiSelect({
options,
value,
onChange,
placeholder = "Select...",
disabled = false,
className,
}: MultiSelectProps) {
const [open, setOpen] = React.useState(false);
const containerRef = React.useRef<HTMLDivElement>(null);

React.useEffect(() => {
const handleClickOutside = (event: MouseEvent) => {
if (
containerRef.current &&
!containerRef.current.contains(event.target as Node)
) {
setOpen(false);
}
};

if (open) {
document.addEventListener("mousedown", handleClickOutside);
}

return () => {
document.removeEventListener("mousedown", handleClickOutside);
};
}, [open]);

const toggleOption = (optionValue: string) => {
if (disabled) return;
const newValue = value.includes(optionValue)
? value.filter((v) => v !== optionValue)
: [...value, optionValue];
onChange(newValue);
};

const displayText =
value.length === 0
? placeholder
: value.length === 1
? options.find((opt) => opt.value === value[0])?.label || value[0]
: `${value.length} selected`;

return (
<div ref={containerRef} className={cn("relative", className)}>
<Button
type="button"
variant="outline"
className={cn(
"flex h-7 w-full items-center justify-between px-3 text-sm",
!value.length && "text-muted-foreground"
)}
onClick={() => !disabled && setOpen(!open)}
disabled={disabled}
>
<span className="truncate">{displayText}</span>
<ChevronDown
className={cn(
"ml-2 h-4 w-4 shrink-0 opacity-50 transition-transform",
open && "rotate-180"
)}
/>
</Button>
{open && (
<div className="absolute z-50 mt-1 w-full rounded-md border bg-popover shadow-md">
<div className="max-h-60 overflow-auto p-1">
{options.map((option) => {
const isSelected = value.includes(option.value);
return (
<div
key={option.value}
className={cn(
"relative flex cursor-pointer select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none hover:bg-accent hover:text-accent-foreground",
isSelected && "bg-accent"
)}
onClick={() => toggleOption(option.value)}
>
<div className="flex h-4 w-4 items-center justify-center mr-2">
{isSelected && <Check className="h-4 w-4" />}
</div>
<span>{option.label}</span>
</div>
);
})}
</div>
</div>
)}
</div>
);
}
Loading