jam-tracks / frontend /src /components /generation /GenerationPanel.jsx
Mina Emadi
added the music generation functionality
59dcfdf
Raw
History Blame Contribute Delete
6.55 kB
import React, { useState, useCallback, useEffect, useRef } from 'react'
import ModelSelector from './ModelSelector'
import SeedRegionInfo from './SeedRegionInfo'
import TaskTypeSelector from './TaskTypeSelector'
import MusicGenParams from './MusicGenParams'
import ACEStepParams from './ACEStepParams'
import GenerationProgress from './GenerationProgress'
import GenerationOutput from './GenerationOutput'
// Map model+taskType to the param key used in the request
function getTaskTypeKey(model) {
if (model === 'ace-step') return 'task_type'
return null
}
export default function GenerationPanel({
sessionId,
detection,
regionStart,
regionEnd,
hasRegion,
onPlaySection,
generation, // all state from useGeneration hook
}) {
const [isCollapsed, setIsCollapsed] = useState(false)
const [isExtracting, setIsExtracting] = useState(false)
const {
selectedModel,
setSelectedModel,
modelParams,
setModelParam,
isGenerating,
generationResult,
generationProgress,
modelAvailability,
seedData,
doExtractSeed,
startGeneration,
setGenerationResult,
} = generation
const currentParams = modelParams[selectedModel] || {}
// Get the current task type for the selected model
const taskTypeKey = getTaskTypeKey(selectedModel)
const taskType = taskTypeKey ? currentParams[taskTypeKey] : 'continuation'
const handleTaskTypeChange = useCallback((newType) => {
if (taskTypeKey) {
setModelParam(selectedModel, taskTypeKey, newType)
}
}, [selectedModel, taskTypeKey, setModelParam])
const handleParamChange = useCallback((key, value) => {
setModelParam(selectedModel, key, value)
}, [selectedModel, setModelParam])
// Auto-extract seed when region changes (debounced with longer delay)
const regionRef = useRef({ start: regionStart, end: regionEnd })
useEffect(() => {
regionRef.current = { start: regionStart, end: regionEnd }
}, [regionStart, regionEnd])
useEffect(() => {
if (!hasRegion || !sessionId) return
if (regionEnd - regionStart < 3) return
const timer = setTimeout(async () => {
// Only extract if region hasn't changed during the delay
if (regionRef.current.start === regionStart && regionRef.current.end === regionEnd) {
setIsExtracting(true)
await doExtractSeed(regionStart, regionEnd)
setIsExtracting(false)
}
}, 1000)
return () => clearTimeout(timer)
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [regionStart, regionEnd, hasRegion, sessionId])
const handleGenerate = useCallback(async () => {
if (!hasRegion) return
await startGeneration(regionStart, regionEnd)
}, [hasRegion, regionStart, regionEnd, startGeneration])
const handleRegenerate = useCallback(async () => {
// Set a new random seed before regenerating
if (selectedModel === 'musicgen') {
setModelParam('musicgen', 'seed', Math.floor(Math.random() * 999999))
} else if (selectedModel === 'ace-step') {
setModelParam('ace-step', 'seed', Math.floor(Math.random() * 999999))
}
// Small delay for state to update
setTimeout(() => handleGenerate(), 50)
}, [selectedModel, setModelParam, handleGenerate])
const handleUseAsSource = useCallback(() => {
// The generated continuation is already stored in the session.
// Clear the current result so the user can generate again.
setGenerationResult(null)
}, [setGenerationResult])
const canGenerate = hasRegion && !isGenerating && (regionEnd - regionStart >= 3)
return (
<div className="card rounded-xl p-4 animate-fade-in">
{/* Header */}
<button
onClick={() => setIsCollapsed(!isCollapsed)}
className="w-full flex items-center justify-between mb-3"
>
<div className="flex items-center gap-2">
<span className="text-[10px] uppercase tracking-widest text-accent-500 font-medium">
AI Generation
</span>
</div>
<span className={`text-gray-500 text-xs transition-transform ${isCollapsed ? '' : 'rotate-180'}`}>
&#9660;
</span>
</button>
{!isCollapsed && (
<div className="space-y-4">
{/* Model selector tabs */}
<ModelSelector
selectedModel={selectedModel}
onModelChange={setSelectedModel}
availability={modelAvailability}
/>
{/* Seed region info */}
<SeedRegionInfo
seedData={seedData}
regionStart={regionStart}
regionEnd={regionEnd}
hasRegion={hasRegion}
onPreviewRegion={onPlaySection}
isExtracting={isExtracting}
/>
{/* Task type selector (per model) */}
<TaskTypeSelector
model={selectedModel}
taskType={taskType}
onTaskTypeChange={handleTaskTypeChange}
/>
{/* Model-specific parameters */}
<div className="transition-all">
{selectedModel === 'musicgen' && (
<MusicGenParams
params={currentParams}
onParamChange={handleParamChange}
/>
)}
{selectedModel === 'ace-step' && (
<ACEStepParams
params={currentParams}
onParamChange={handleParamChange}
detection={detection}
/>
)}
</div>
{/* Generate button */}
<button
onClick={handleGenerate}
disabled={!canGenerate}
className={`w-full py-2.5 rounded-lg text-sm font-medium transition-all ${
canGenerate
? 'bg-accent-500 hover:bg-accent-400 text-white'
: 'bg-white/[0.04] text-gray-600 cursor-not-allowed'
}`}
>
{isGenerating
? 'Generating...'
: `\u25B6 Generate with ${selectedModel === 'musicgen' ? 'MusicGen' : 'ACE-Step'}`
}
</button>
{/* Progress */}
<GenerationProgress
model={selectedModel}
progress={generationProgress}
isGenerating={isGenerating}
/>
{/* Output */}
<GenerationOutput
sessionId={sessionId}
generationResult={generationResult}
onRegenerate={handleRegenerate}
onUseAsSource={handleUseAsSource}
onPlaySection={onPlaySection}
/>
</div>
)}
</div>
)
}