diff --git a/src/pages/music/MusicStudio.css b/src/pages/music/MusicStudio.css index eae6235..a3de87b 100644 --- a/src/pages/music/MusicStudio.css +++ b/src/pages/music/MusicStudio.css @@ -3330,3 +3330,14 @@ margin-left: 6px; } .cmp-btn-video:hover { background: rgba(56, 189, 248, 0.25); } + +.psm-input-radio { border: 1px solid var(--ms-line, #2a2a3a); padding: 8px 12px; + border-radius: 8px; margin-bottom: 12px; } +.psm-input-radio legend { padding: 0 6px; font-size: 11px; color: var(--ms-muted, #a0a0b0); } +.psm-input-radio label { display: inline-flex; align-items: center; gap: 4px; font-size: 13px; } +.psm-advanced { margin-top: 12px; padding: 8px 0; } +.psm-advanced summary { cursor: pointer; font-size: 12px; color: var(--ms-muted, #a0a0b0); user-select: none; } +.psm-advanced label { display: block; margin: 8px 0; font-size: 12px; } +.psm-advanced input, .psm-advanced select { width: 100%; padding: 6px 8px; + background: rgba(255,255,255,.04); border: 1px solid var(--ms-line, #2a2a3a); + border-radius: 6px; color: var(--ms-text, #f0f0f5); font-size: 12px; } diff --git a/src/pages/music/components/PipelineStartModal.jsx b/src/pages/music/components/PipelineStartModal.jsx index 82473c6..1f7505a 100644 --- a/src/pages/music/components/PipelineStartModal.jsx +++ b/src/pages/music/components/PipelineStartModal.jsx @@ -1,31 +1,134 @@ -import { useState } from 'react'; -import { createPipeline, startPipeline } from '../../../api'; +import { useState, useEffect } from 'react'; +import { createPipeline, startPipeline, getCompileJobs } from '../../../api'; + +const fmtDur = (s) => { + if (!s) return '0:00'; + const m = Math.floor(s / 60); + const sec = Math.round(s % 60); + return `${m}:${String(sec).padStart(2, '0')}`; +}; export default function PipelineStartModal({ library, initialTrackId, onClose, onCreated }) { + const [inputType, setInputType] = useState('track'); // 'track' | 'compile' const [tid, setTid] = useState(initialTrackId || library?.[0]?.id || ''); + const [cid, setCid] = useState(''); + const [compileJobs, setCompileJobs] = useState([]); + const [advanced, setAdvanced] = useState(false); + const [visualStyle, setVisualStyle] = useState(''); + const [bgMode, setBgMode] = useState(''); + const [bgKeyword, setBgKeyword] = useState(''); const [error, setError] = useState(''); + useEffect(() => { + if (inputType === 'compile') { + getCompileJobs() + .then(r => { + const list = (r.jobs || r || []); + const completed = list.filter(j => j.status === 'done' || j.status === 'succeeded'); + setCompileJobs(completed); + if (completed.length && !cid) setCid(completed[0].id); + }) + .catch(e => setError(String(e))); + } + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [inputType]); + const submit = async () => { try { - const p = await createPipeline(parseInt(tid)); + const payload = {}; + if (inputType === 'track') { + payload.track_id = parseInt(tid); + } else { + if (!cid) { + setError('완료된 Mix를 선택해주세요'); + return; + } + payload.compile_job_id = parseInt(cid); + } + if (visualStyle) payload.visual_style = visualStyle; + if (bgMode) payload.background_mode = bgMode; + if (bgKeyword) payload.background_keyword = bgKeyword; + + const p = await createPipeline(payload); await startPipeline(p.id); onCreated(p); - } catch (e) { setError(String(e)); } + } catch (e) { + setError(e.message || String(e)); + } }; return (