'use client'; import { useCallback, useEffect, useMemo, useRef, useState } from 'react'; type Mode = 'simple' | 'custom'; type Clip = { id: string; title?: string; status?: string; audio_url?: string; image_url?: string; video_url?: string; metadata?: { tags?: string; prompt?: string; duration?: number }; }; const MODELS = [ { id: 'chirp-v3-5', label: 'v3.5 (고품질)', desc: '가장 풍부한 사운드' }, { id: 'chirp-v3-0', label: 'v3.0 (균형)', desc: '속도·품질 밸런스' }, ]; const TAG_PRESETS = [ 'k-pop', 'lo-fi', 'city pop', 'ballad', 'edm', 'trap', 'rock', 'jazz', 'acoustic', 'cinematic', 'synthwave', 'ambient', ]; const LS_KEY = 'jsm_studio_clip_ids'; export default function StudioPage() { const [mode, setMode] = useState('simple'); const [model, setModel] = useState('chirp-v3-5'); const [prompt, setPrompt] = useState(''); const [title, setTitle] = useState(''); const [lyrics, setLyrics] = useState(''); const [tags, setTags] = useState(''); const [instrumental, setInstrumental] = useState(false); const [submitting, setSubmitting] = useState(false); const [error, setError] = useState(null); const [clips, setClips] = useState([]); const pollRef = useRef(null); const activeIds = useMemo(() => clips.map((c) => c.id).join(','), [clips]); const loadFromLS = useCallback(() => { if (typeof window === 'undefined') return []; try { const raw = localStorage.getItem(LS_KEY); return raw ? (JSON.parse(raw) as string[]) : []; } catch { return []; } }, []); const saveToLS = useCallback((ids: string[]) => { if (typeof window === 'undefined') return; localStorage.setItem(LS_KEY, JSON.stringify(ids.slice(0, 30))); }, []); const fetchStatus = useCallback(async (idsCsv: string) => { if (!idsCsv) return; try { const res = await fetch(`/api/studio/status?ids=${encodeURIComponent(idsCsv)}`); const json = await res.json(); if (json.ok && Array.isArray(json.data)) { setClips((prev) => { const map = new Map(prev.map((c) => [c.id, c])); for (const c of json.data as Clip[]) map.set(c.id, { ...map.get(c.id), ...c }); return Array.from(map.values()); }); } } catch { /* silent */ } }, []); useEffect(() => { const ids = loadFromLS(); if (ids.length) { setClips(ids.map((id) => ({ id, status: 'loading' }))); fetchStatus(ids.join(',')); } }, [loadFromLS, fetchStatus]); useEffect(() => { const pending = clips.some((c) => c.status !== 'complete' && c.status !== 'error'); if (pollRef.current) window.clearInterval(pollRef.current); if (pending && activeIds) { pollRef.current = window.setInterval(() => fetchStatus(activeIds), 8000); } return () => { if (pollRef.current) window.clearInterval(pollRef.current); }; }, [clips, activeIds, fetchStatus]); const onSubmit = async () => { setError(null); if (mode === 'simple' && !prompt.trim()) { setError('프롬프트를 입력해주세요.'); return; } if (mode === 'custom' && !lyrics.trim() && !instrumental) { setError('가사를 입력하거나 Instrumental을 켜주세요.'); return; } setSubmitting(true); try { const res = await fetch('/api/studio/generate', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ mode, model, prompt: prompt.trim(), title: title.trim(), lyrics: lyrics.trim(), tags: tags.trim(), make_instrumental: instrumental, }), }); const json = await res.json(); if (!res.ok || !json.ok) { setError(json.error ?? '생성 실패'); return; } const newClips: Clip[] = (Array.isArray(json.data) ? json.data : []).map((c: Clip) => ({ ...c, status: c.status ?? 'submitted', })); if (!newClips.length) { setError('응답에 결과가 없습니다. API URL 응답 포맷을 확인하세요.'); return; } setClips((prev) => { const merged = [...newClips, ...prev]; saveToLS(merged.map((c) => c.id)); return merged; }); } catch (e) { setError(e instanceof Error ? e.message : String(e)); } finally { setSubmitting(false); } }; const addTag = (t: string) => { const cur = tags.split(',').map((x) => x.trim()).filter(Boolean); if (cur.includes(t)) return; setTags([...cur, t].join(', ')); }; return (
{/* 헤더 */}
JAENGSEUNG STUDIO

프롬프트 한 줄로 트랙 만들기

Suno 엔진 기반 · Custom 모드로 가사·태그·보컬까지 세밀 제어

⚡ v1 Studio · Live
{/* 좌측: 제어판 */}
{/* 모드 토글 */}
{(['simple', 'custom'] as Mode[]).map((m) => ( ))}
{mode === 'simple' ? (