import { useState, useEffect } from 'react' import StructuredPromptEditor, { PromptStructure } from './StructuredPromptEditor' import MaestroButton from './MaestroButton' interface ModelInfo { name: string max_duration: number default_duration: number description: string } interface Props { onGenerate: () => void } type PromptMode = 'basic' | 'advanced' export default function Generator({ onGenerate }: Props) { const [prompt, setPrompt] = useState('') const [model, setModel] = useState('lyria-3-clip-preview') const [duration, setDuration] = useState(30) const [isLoading, setIsLoading] = useState(false) const [error, setError] = useState('') const [models, setModels] = useState>({}) const [promptMode, setPromptMode] = useState('basic') const [structuredPrompt, setStructuredPrompt] = useState({}) useEffect(() => { fetch('/api/models') .then(res => res.json()) .then(data => { setModels(data.models || {}) setModel(data.default || 'lyria-3-clip-preview') const defaultInfo = data.models?.[data.default] setDuration(defaultInfo?.default_duration || 30) }) .catch(() => {}) // Use hardcoded defaults on failure }, []) const handleModelChange = (newModel: string) => { setModel(newModel) const info = models[newModel] if (info) { setDuration(info.default_duration) } // Reset to basic mode when switching to Clip Preview if (newModel === 'lyria-3-clip-preview') { setPromptMode('basic') } } const currentModelInfo = models[model] const maxDuration = currentModelInfo?.max_duration || 30 const minDuration = 5 const isPro = model === 'lyria-3-pro-preview' const handleSubmit = async (e: React.FormEvent) => { e.preventDefault() if (promptMode === 'basic' && !prompt.trim()) return if (promptMode === 'advanced' && Object.values(structuredPrompt).every(v => !v?.trim())) return setIsLoading(true) setError('') try { const body: Record = { prompt: prompt.trim(), model, duration, } if (promptMode === 'advanced' && isPro) { body.prompt_structure = structuredPrompt } const res = await fetch('/api/generate', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(body) }) if (!res.ok) { try { const errorData = await res.json() throw new Error(errorData.error || errorData.detail || 'Generation failed') } catch (parseErr) { if (parseErr instanceof Error && parseErr.message !== 'Generation failed') throw parseErr throw new Error('Generation failed') } } const data = await res.json() if (data.error) { throw new Error(data.error) } setPrompt('') setStructuredPrompt({}) onGenerate() } catch (err) { setError(err instanceof Error ? err.message : 'Something went wrong') } finally { setIsLoading(false) } } return (

Generate

{/* Model & Duration */}
setDuration(Number(e.target.value))} className="w-full mt-2 accent-kithara-accent" />
{currentModelInfo?.name || 'Clip Preview'} {duration}s / {maxDuration}s max
{/* Prompt Mode Toggle (Pro only) */} {isPro && (
)} {/* Basic Prompt */} {(promptMode === 'basic' || !isPro) && (
{ setPrompt(_result) setError('') }} onError={(msg) => setError(msg)} />