feat(web-ui): Create 탭 배치 생성 섹션 + BatchProgress 폴링
This commit is contained in:
@@ -15,6 +15,8 @@ import {
|
||||
getTimestampedLyrics,
|
||||
generateStyleBoost,
|
||||
generateVideo,
|
||||
startBatchGen,
|
||||
getBatchJob,
|
||||
} from '../../api';
|
||||
import PullToRefresh from '../../components/PullToRefresh';
|
||||
import FAB from '../../components/FAB';
|
||||
@@ -28,6 +30,7 @@ import StemModal from './components/StemModal';
|
||||
import SyncedLyricsPlayer from './components/SyncedLyricsPlayer';
|
||||
import RemixTab from './components/RemixTab';
|
||||
import YoutubeTab from './components/YoutubeTab';
|
||||
import BatchProgress from './components/BatchProgress';
|
||||
|
||||
/* ─────────────────────────────────────────────
|
||||
데이터 상수
|
||||
@@ -592,6 +595,16 @@ export default function MusicStudio() {
|
||||
const pollRef = useRef(null);
|
||||
const taskIdRef = useRef(null);
|
||||
|
||||
/* ── 배치 생성 상태 ── */
|
||||
const [batchOpen, setBatchOpen] = useState(false);
|
||||
const [batchGenre, setBatchGenre] = useState('lo-fi');
|
||||
const [batchCount, setBatchCount] = useState(10);
|
||||
const [batchDuration, setBatchDuration] = useState(180);
|
||||
const [batchAutoPipe, setBatchAutoPipe] = useState(true);
|
||||
const [currentBatch, setCurrentBatch] = useState(null);
|
||||
const [batchPolling, setBatchPolling] = useState(false);
|
||||
const batchPollRef = useRef(null);
|
||||
|
||||
const activeGenre = GENRES.find((g) => g.id === genre);
|
||||
const accentColor = activeGenre?.color ?? '#f5a623';
|
||||
|
||||
@@ -651,6 +664,43 @@ export default function MusicStudio() {
|
||||
/* ── 언마운트 시 폴링 정리 ── */
|
||||
useEffect(() => () => clearInterval(pollRef.current), []);
|
||||
|
||||
/* ── 배치 생성 시작 ── */
|
||||
const startBatch = async () => {
|
||||
try {
|
||||
const res = await startBatchGen({
|
||||
genre: batchGenre,
|
||||
count: batchCount,
|
||||
target_duration_sec: batchDuration,
|
||||
auto_pipeline: batchAutoPipe,
|
||||
});
|
||||
setCurrentBatch(res);
|
||||
setBatchPolling(true);
|
||||
} catch (e) {
|
||||
alert(`배치 시작 실패: ${e.message || e}`);
|
||||
}
|
||||
};
|
||||
|
||||
/* ── 배치 폴링 ── */
|
||||
useEffect(() => {
|
||||
if (!batchPolling || !currentBatch?.id) return;
|
||||
const tick = async () => {
|
||||
try {
|
||||
const j = await getBatchJob(currentBatch.id);
|
||||
if (j) {
|
||||
setCurrentBatch(j);
|
||||
if (['piped', 'failed', 'cancelled'].includes(j.status)) {
|
||||
setBatchPolling(false);
|
||||
// library 갱신 (새 트랙들 표시되도록)
|
||||
if (typeof loadLibrary === 'function') loadLibrary();
|
||||
}
|
||||
}
|
||||
} catch { /* swallow */ }
|
||||
};
|
||||
batchPollRef.current = setInterval(tick, 5000);
|
||||
return () => clearInterval(batchPollRef.current);
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [batchPolling, currentBatch?.id]);
|
||||
|
||||
/* ── helpers ── */
|
||||
const toggleMood = (id) =>
|
||||
setMoods((prev) =>
|
||||
@@ -1276,6 +1326,43 @@ export default function MusicStudio() {
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Batch Generation Section */}
|
||||
<details className="ms-batch-section" open={batchOpen} onToggle={(e) => setBatchOpen(e.currentTarget.open)}>
|
||||
<summary>🎲 배치 생성 (장르 → 1-10트랙 + 자동 영상)</summary>
|
||||
<div className="ms-batch-form">
|
||||
<label>장르
|
||||
<select value={batchGenre} onChange={e => setBatchGenre(e.target.value)}>
|
||||
<option value="lo-fi">Lo-Fi</option>
|
||||
<option value="phonk">Phonk</option>
|
||||
<option value="ambient">Ambient</option>
|
||||
<option value="pop">Pop</option>
|
||||
</select>
|
||||
</label>
|
||||
<label>트랙 수: <strong>{batchCount}</strong>
|
||||
<input type="range" min={1} max={10} value={batchCount}
|
||||
onChange={e => setBatchCount(parseInt(e.target.value))} />
|
||||
</label>
|
||||
<label>트랙당 길이: <strong>{batchDuration}초</strong>
|
||||
<input type="range" min={60} max={300} step={10} value={batchDuration}
|
||||
onChange={e => setBatchDuration(parseInt(e.target.value))} />
|
||||
</label>
|
||||
<label className="ms-batch-checkbox">
|
||||
<input type="checkbox" checked={batchAutoPipe}
|
||||
onChange={e => setBatchAutoPipe(e.target.checked)} />
|
||||
모든 트랙 생성 후 자동 영상 파이프라인 시작
|
||||
</label>
|
||||
<p className="ms-batch-estimate">
|
||||
예상: 약 {Math.ceil(batchCount * 1.5)}–{batchCount * 2}분 ·
|
||||
{' '}비용 ~${(batchCount * 0.005 + (batchAutoPipe ? 0.05 : 0)).toFixed(2)}
|
||||
</p>
|
||||
<button className="button primary" onClick={startBatch}
|
||||
disabled={batchPolling}>
|
||||
🎵 배치 생성 시작
|
||||
</button>
|
||||
</div>
|
||||
{currentBatch && <BatchProgress batch={currentBatch} />}
|
||||
</details>
|
||||
|
||||
{/* Step 1: Genre */}
|
||||
<section className="ms-section">
|
||||
<div className="ms-section__head">
|
||||
|
||||
Reference in New Issue
Block a user