diff --git a/src/main/resources/templates/reply.html b/src/main/resources/templates/reply.html index b8a9d57..b7fa7e8 100644 --- a/src/main/resources/templates/reply.html +++ b/src/main/resources/templates/reply.html @@ -14,6 +14,9 @@

내 영상에 달린 댓글 중 아직 답 안 한 것만 모아, 영상 내용까지 보고 초안을 씁니다.

+ @@ -36,6 +39,22 @@
+ + +
@@ -299,9 +318,21 @@ } hideState(); document.getElementById('list').innerHTML = items.map(cardHtml).join(''); + updateBatchBtn(); if(window.lucide) lucide.createIcons(); } + /** 일괄 생성 버튼에 남은 건수를 붙이고, 할 게 없으면 끈다. */ + function updateBatchBtn(){ + const btn = document.getElementById('batchBtn'); + if(!btn || btn.disabled) return; // 실행 중이면 건드리지 않는다 + const n = items.filter(c => c.status !== 'DONE' && !c.draftA && !c.draftB).length; + btn.innerHTML = ` 전체 초안 생성${n ? ` (${n})` : ''}`; + btn.classList.toggle('btn-secondary', true); + btn.style.opacity = n ? '' : '.5'; + btn.style.pointerEvents = n ? '' : 'none'; + } + // ---------- 액션 ---------- async function collect(){ const btn = document.getElementById('collectBtn'); @@ -343,6 +374,68 @@ } } + // ---------- 일괄 생성 ---------- + // 서버에서 통짜로 돌리면 몇 분짜리 요청이 되어 프록시에서 끊긴다. + // 한 건씩 순차 호출해 진행률을 보여주고, 중단해도 앞서 만든 초안은 남게 한다. + let batchAbort = false; + + function setBatchUI(on, done, total, label){ + const bar = document.getElementById('batchBar'); + bar.classList.toggle('hidden', !on); + document.getElementById('batchBtn').disabled = on; + document.getElementById('collectBtn').disabled = on; + if(on){ + document.getElementById('batchCount').textContent = `${done} / ${total}`; + document.getElementById('batchFill').style.width = total ? (done/total*100)+'%' : '0%'; + if(label) document.getElementById('batchLabel').textContent = label; + if(window.lucide) lucide.createIcons(); + } + } + + function cancelBatch(){ + batchAbort = true; + document.getElementById('batchLabel').textContent = '중단하는 중... (진행 중인 1건은 끝냅니다)'; + } + + async function draftAll(){ + const targets = items.filter(c => c.status !== 'DONE' && !c.draftA && !c.draftB); + if(targets.length === 0){ + toast('초안이 없는 댓글이 없습니다'); + return; + } + // 실측 건당 26~32초(영상을 처음 읽는 비용). 예상 시간을 알려주고 시작한다. + const mins = Math.max(1, Math.round(targets.length * 30 / 60)); + if(!confirm(`초안 없는 댓글 ${targets.length}건에 초안을 만듭니다.\n` + + `건당 30초 안팎이라 약 ${mins}분 걸립니다.\n\n` + + `창을 켜둔 채로 두시면 되고, 만든 초안은 그때그때 저장됩니다.\n진행할까요?`)) return; + + batchAbort = false; + let done = 0, failed = 0; + setBatchUI(true, 0, targets.length, '초안 생성 중...'); + + for(const c of targets){ + if(batchAbort) break; + try { + const updated = await api(`${API}/${c.id}/draft?refreshContext=false`, { method:'POST' }); + const i = items.findIndex(v => v.id === c.id); + if(i >= 0) items[i] = updated; + } catch(e){ + failed++; + console.warn('초안 실패', c.id, e.message); + } + done++; + setBatchUI(true, done, targets.length); + render(); // 완성되는 대로 카드에 반영 + } + + setBatchUI(false); + loadStats(); + const msg = batchAbort + ? `중단했습니다 — ${done - failed}건 생성` + : `${done - failed}건 생성 완료`; + toast(failed ? `${msg} · ${failed}건 실패` : msg, failed > 0); + } + async function copyDraft(btn, id, key){ const c = items.find(v => v.id === id); if(!c) return;