Files
web-page/src/pages/music/components/PipelineStartModal.jsx

137 lines
5.9 KiB
JavaScript

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 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(e.message || String(e));
}
};
return (
<div className="modal-overlay" onClick={onClose}>
<div className="modal-body" onClick={e => e.stopPropagation()}>
<h3> 파이프라인 시작</h3>
<fieldset className="psm-input-radio">
<legend>입력</legend>
<label>
<input type="radio" checked={inputType === 'track'}
onChange={() => setInputType('track')} />
{' '}단일 트랙
</label>
<label style={{ marginLeft: 12 }}>
<input type="radio" checked={inputType === 'compile'}
onChange={() => setInputType('compile')} />
{' '}Mix (컴파일 결과)
</label>
</fieldset>
{inputType === 'track' ? (
<select value={tid} onChange={e => setTid(e.target.value)}>
{(library || []).map(t => (
<option key={t.id} value={t.id}>{t.title} ({t.genre})</option>
))}
</select>
) : (
<select value={cid} onChange={e => setCid(e.target.value)}>
{compileJobs.length === 0 && <option value="">완료된 Mix가 없습니다</option>}
{compileJobs.map(j => (
<option key={j.id} value={j.id}>
{j.title || `Mix #${j.id}`}
{' '}({fmtDur(j.duration_sec || 0)},{' '}
{j.tracks_count || (j.track_ids && j.track_ids.length) || '?'})
</option>
))}
</select>
)}
<details className="psm-advanced" open={advanced}>
<summary onClick={(e) => { e.preventDefault(); setAdvanced(!advanced); }}>
고급 옵션
</summary>
<label>
시각 스타일
<select value={visualStyle} onChange={e => setVisualStyle(e.target.value)}>
<option value="">기본 (구성 default)</option>
<option value="essential">essential (배경 + 중앙 비주얼)</option>
<option value="single">single (커버 + 가장자리 파형)</option>
</select>
</label>
<label>
배경 모드
<select value={bgMode} onChange={e => setBgMode(e.target.value)}>
<option value="">기본 (구성 default)</option>
<option value="static">정적 사진</option>
<option value="video_loop">영상 루프 (Pexels)</option>
</select>
</label>
<label>
배경 키워드 (Pexels 검색용)
<input value={bgKeyword} onChange={e => setBgKeyword(e.target.value)}
placeholder="rainy window, lofi cafe ..." />
</label>
</details>
{error && <div className="ms-error">{error}</div>}
<div className="modal-actions">
<button onClick={onClose}>취소</button>
<button className="button primary" onClick={submit}
disabled={inputType === 'compile' && !cid}>
시작
</button>
</div>
</div>
</div>
);
}