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
40 changes: 39 additions & 1 deletion frontend/package-lock.json

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

3 changes: 2 additions & 1 deletion frontend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,8 @@
"react": "^19.1.1",
"react-dom": "^19.1.1",
"sonner": "^2.0.7",
"tailwind-merge": "^3.3.1"
"tailwind-merge": "^3.3.1",
"zustand": "^4.5.2"
},
"devDependencies": {
"@eslint/js": "^9.33.0",
Expand Down
3 changes: 3 additions & 0 deletions frontend/src/components/InputAndControlsPanel.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ interface InputAndControlsPanelProps {
timelinePrompts?: TimelinePrompt[];
transitionSteps: number;
onTransitionStepsChange: (steps: number) => void;
cloudMode?: boolean;
}

export function InputAndControlsPanel({
Expand Down Expand Up @@ -84,6 +85,7 @@ export function InputAndControlsPanel({
timelinePrompts: _timelinePrompts = [],
transitionSteps,
onTransitionStepsChange,
cloudMode = false,
}: InputAndControlsPanelProps) {
// Helper function to determine if playhead is at the end of timeline
const isAtEndOfTimeline = () => {
Expand Down Expand Up @@ -271,6 +273,7 @@ export function InputAndControlsPanel({
transitionSteps={transitionSteps}
onTransitionStepsChange={onTransitionStepsChange}
timelinePrompts={_timelinePrompts}
cloudMode={cloudMode}
/>
)}
</div>
Expand Down
130 changes: 69 additions & 61 deletions frontend/src/components/PromptInput.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ interface PromptInputProps {
transitionSteps?: number;
onTransitionStepsChange?: (steps: number) => void;
timelinePrompts?: TimelinePrompt[];
cloudMode?: boolean;
}

export function PromptInput({
Expand All @@ -51,6 +52,7 @@ export function PromptInput({
transitionSteps = 4,
onTransitionStepsChange,
timelinePrompts = [],
cloudMode = false,
}: PromptInputProps) {
const [isProcessing, setIsProcessing] = useState(false);
const [focusedIndex, setFocusedIndex] = useState<number | null>(null);
Expand Down Expand Up @@ -160,7 +162,8 @@ export function PromptInput({
/>
</div>

<div className="space-y-2">
<div className="space-y-2">
{!cloudMode && (
<TemporalTransitionControls
transitionSteps={transitionSteps}
onTransitionStepsChange={steps => onTransitionStepsChange?.(steps)}
Expand All @@ -171,42 +174,43 @@ export function PromptInput({
disabled={disabled || !isStreaming || timelinePrompts.length === 0}
className="space-y-2"
/>
)}

{/* Add/Submit buttons - Bottom row */}
<div className="flex items-center justify-end gap-2">
{managedPrompts.length < 4 && (
<Button
onMouseDown={e => {
e.preventDefault();
handleAddPrompt();
}}
disabled={disabled}
size="sm"
variant="ghost"
className="rounded-full w-8 h-8 p-0"
>
<Plus className="h-4 w-4" />
</Button>
)}
{/* Add/Submit buttons - Bottom row */}
<div className="flex items-center justify-end gap-2">
{managedPrompts.length < 4 && (
<Button
onMouseDown={e => {
e.preventDefault();
handleSubmit();
handleAddPrompt();
}}
disabled={
disabled ||
!managedPrompts.some(p => p.text.trim()) ||
isProcessing
}
disabled={disabled}
size="sm"
className="rounded-full w-8 h-8 p-0 bg-black hover:bg-gray-800 text-white disabled:opacity-50 disabled:cursor-not-allowed"
variant="ghost"
className="rounded-full w-8 h-8 p-0"
>
{isProcessing ? "..." : <ArrowUp className="h-4 w-4" />}
<Plus className="h-4 w-4" />
</Button>
</div>
)}
<Button
onMouseDown={e => {
e.preventDefault();
handleSubmit();
}}
disabled={
disabled ||
!managedPrompts.some(p => p.text.trim()) ||
isProcessing
}
size="sm"
className="rounded-full w-8 h-8 p-0 bg-black hover:bg-gray-800 text-white disabled:opacity-50 disabled:cursor-not-allowed"
>
{isProcessing ? "..." : <ArrowUp className="h-4 w-4" />}
</Button>
</div>
</div>
);
</div>
);
}

// Multiple prompts mode: show weights and controls
Expand Down Expand Up @@ -241,43 +245,47 @@ export function PromptInput({
})}

<div className="space-y-2">
{/* Spatial Blend - only for multiple prompts */}
{managedPrompts.length >= 2 && (
<div className="flex items-center justify-between gap-2">
<span className="text-xs text-muted-foreground">
Spatial Blend:
</span>
<Select
value={interpolationMethod}
onValueChange={value =>
onInterpolationMethodChange?.(value as "linear" | "slerp")
{!cloudMode && (
<>
{/* Spatial Blend - only for multiple prompts */}
{managedPrompts.length >= 2 && (
<div className="flex items-center justify-between gap-2">
<span className="text-xs text-muted-foreground">
Spatial Blend:
</span>
<Select
value={interpolationMethod}
onValueChange={value =>
onInterpolationMethodChange?.(value as "linear" | "slerp")
}
disabled={disabled}
>
<SelectTrigger className="w-24 h-6 text-xs">
<SelectValue />
</SelectTrigger>
<SelectContent>
<SelectItem value="linear">Linear</SelectItem>
<SelectItem value="slerp" disabled={managedPrompts.length > 2}>
Slerp
</SelectItem>
</SelectContent>
</Select>
</div>
)}

<TemporalTransitionControls
transitionSteps={transitionSteps}
onTransitionStepsChange={steps => onTransitionStepsChange?.(steps)}
temporalInterpolationMethod={temporalInterpolationMethod}
onTemporalInterpolationMethodChange={method =>
onTemporalInterpolationMethodChange?.(method)
}
disabled={disabled}
>
<SelectTrigger className="w-24 h-6 text-xs">
<SelectValue />
</SelectTrigger>
<SelectContent>
<SelectItem value="linear">Linear</SelectItem>
<SelectItem value="slerp" disabled={managedPrompts.length > 2}>
Slerp
</SelectItem>
</SelectContent>
</Select>
</div>
disabled={disabled || !isStreaming || timelinePrompts.length === 0}
className="space-y-2"
/>
</>
)}

<TemporalTransitionControls
transitionSteps={transitionSteps}
onTransitionStepsChange={steps => onTransitionStepsChange?.(steps)}
temporalInterpolationMethod={temporalInterpolationMethod}
onTemporalInterpolationMethodChange={method =>
onTemporalInterpolationMethodChange?.(method)
}
disabled={disabled || !isStreaming || timelinePrompts.length === 0}
className="space-y-2"
/>

{/* Add/Submit buttons - Bottom row */}
<div className="flex items-center justify-end gap-2">
{managedPrompts.length < 4 && (
Expand Down
4 changes: 3 additions & 1 deletion frontend/src/components/PromptInputWithTimeline.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -255,7 +255,7 @@ export function PromptInputWithTimeline({
}
return false;
}
return isStreaming; // Already streaming
return true; // Already streaming
}, [isStreaming, onStartStream]);

// Check if at end of timeline
Expand Down Expand Up @@ -289,8 +289,10 @@ export function PromptInputWithTimeline({
setIsLive(true);
onLiveStateChange?.(true);

console.log("[PromptInputWithTimeline] handleStartPlayback", prompts.length);
// Only create a new live prompt if there are no prompts at all in the timeline
if (prompts.length === 0) {
console.log("[PromptInputWithTimeline] Creating new live prompt ", prompts);
const streamStartedAgain = await initializeStream();
if (streamStartedAgain) {
const livePrompt = buildLivePromptFromCurrent(
Expand Down
Loading
Loading