import { useState, type FormEvent, type KeyboardEvent } from "react"; import { Link } from "react-router-dom"; import { ChevronRight, Loader2, Settings as SettingsIcon, Sparkles, Shuffle, Flame, Wand2, } from "lucide-react"; import { EqualizerIcon } from "./EqualizerIcon"; import type { InputValues, Language, Vocals } from "../lib/types"; import { formatElapsed, useElapsed } from "../lib/useElapsed"; import { randomStyle } from "../lib/llm"; import { useToast } from "../lib/toast"; export type { InputValues }; const LANGUAGES: { value: Language; label: string }[] = [ { value: "English", label: "English" }, { value: "Deutsch", label: "Deutsch" }, { value: "Español", label: "Español" }, { value: "Français", label: "Français" }, { value: "Italiano", label: "Italiano" }, { value: "Português", label: "Português" }, { value: "Polski", label: "Polski" }, { value: "Other", label: "Other" }, ]; const EXAMPLE_IDEAS: { text: string; mood: string; vocals: Vocals }[] = [ { text: "melancholic lo-fi house beat for a rainy sunday morning", mood: "melancholic", vocals: "instrumental", }, { text: "euphoric summer pop anthem with a singalong chorus", mood: "euphoric", vocals: "vocals", }, { text: "tense cinematic underscore for a thriller chase scene", mood: "tense", vocals: "instrumental", }, { text: "dreamy ambient ballad about missing someone far away", mood: "longing", vocals: "vocals", }, ]; interface InputPanelProps { values: InputValues; onChange: (next: InputValues) => void; onSubmit: () => void; loading: boolean; disabled?: boolean; cancelButton?: React.ReactNode; } export function InputPanel({ values, onChange, onSubmit, loading, disabled, cancelButton, }: InputPanelProps) { const [styleLoading, setStyleLoading] = useState( null, ); const [optionsOpen, setOptionsOpen] = useState(false); const elapsed = useElapsed(loading); const toast = useToast(); const set = (key: K, value: InputValues[K]) => onChange({ ...values, [key]: value }); const canSubmit = !loading && !disabled && values.idea.trim().length > 0; const submit = (e?: FormEvent) => { e?.preventDefault(); if (canSubmit) onSubmit(); }; const onCmdEnter = (e: KeyboardEvent) => { if ((e.metaKey || e.ctrlKey) && e.key === "Enter") { e.preventDefault(); submit(); } }; const fillExample = () => { const ex = EXAMPLE_IDEAS[Math.floor(Math.random() * EXAMPLE_IDEAS.length)]; onChange({ ...values, idea: ex.text, mood: values.mood || ex.mood, vocals: ex.vocals, }); }; const handleStyleRandom = async (mode: "normal" | "crazy") => { if (styleLoading) return; const ctrl = new AbortController(); setStyleLoading(mode); try { const style = await randomStyle(mode, ctrl.signal); onChange({ ...values, style_hint: style }); } catch (err) { if (err instanceof DOMException && err.name === "AbortError") return; toast.error( err instanceof Error ? err.message : `Could not generate a ${mode} style — please try again`, ); } finally { setStyleLoading((curr) => (curr === mode ? null : curr)); } }; return (
{/* Brand */}

MelodyMuse

{/* Main textarea */}