diff --git a/chatgpt-suno-tampermonkey.user.js b/chatgpt-suno-tampermonkey.user.js index 21f07c7..713a6b4 100644 --- a/chatgpt-suno-tampermonkey.user.js +++ b/chatgpt-suno-tampermonkey.user.js @@ -1,7 +1,7 @@ // ==UserScript== // @name ChatGPT to Suno Prompt Copier // @namespace local.suno-helper -// @version 0.9.1 +// @version 0.9.3 // @description Copy structured ChatGPT song prompt versions into Suno fields. // @author local // @match https://chatgpt.com/* @@ -30,7 +30,7 @@ (function () { "use strict"; - const SCRIPT_VERSION = "0.9.1"; + const SCRIPT_VERSION = "0.9.3"; const STORAGE_KEY = "chatgptSunoPromptSlots"; const SLOT_COUNT = 2; const isChatGPT = location.hostname.includes("chatgpt.com") || location.hostname.includes("chat.openai.com"); @@ -246,6 +246,28 @@ font-size: 11px; white-space: nowrap; } + .cgs-progress-wrap { + margin-top: 8px; + display: grid; + gap: 4px; + } + .cgs-progress-label { + color: #b7c9ff; + font-size: 12px; + } + .cgs-progress { + height: 10px; + overflow: hidden; + border-radius: 999px; + background: rgba(255, 255, 255, 0.12); + border: 1px solid rgba(255, 255, 255, 0.12); + } + .cgs-progress-fill { + height: 100%; + width: 0%; + background: linear-gradient(90deg, #2f7cf6, #5db6ff); + transition: width 180ms ease; + } `); function createPanel(title, mode = "") { @@ -1412,16 +1434,20 @@ throw new Error(syncText || `${syncResponse.status} ${syncResponse.statusText}`); } const syncData = tryParseJson(syncText) || {}; + const syncTextSummary = showSyncSummary(syncData, tracks.length); const missingTracks = Array.isArray(syncData.tracks) ? syncData.tracks : []; if (!missingTracks.length) { - setStatus(statusTarget, `동기화 완료 상태: ${tracks.length}곡 모두 로컬에 있음`); + setStatus(statusTarget, `동기화 완료. ${syncTextSummary}`); return; } let enqueued = 0; let wavPending = 0; + const syncExisting = Number(syncData.existingRequested ?? syncData.existing ?? 0); + const syncTotal = Number(syncData.requested ?? syncData.received ?? tracks.length); for (let index = 0; index < missingTracks.length; index += 1) { const track = { ...missingTracks[index] }; + updateProgress(syncExisting + enqueued, syncTotal, "WAV 큐 준비"); setStatus(statusTarget, `${index + 1}/${missingTracks.length} 없는 곡 WAV 변환 요청 중: ${track.title || track.id}`); track.wavUrl = await prepareSunoWavCandidate(track); if (!track.wavUrl) { @@ -1445,6 +1471,7 @@ throw new Error(text || `${response.status} ${response.statusText}`); } enqueued += 1; + updateProgress(syncExisting + enqueued, syncTotal, "WAV 큐 준비"); const data = tryParseJson(text) || {}; setStatus(statusTarget, `${enqueued}/${missingTracks.length}곡 큐 추가: ${track.title || track.id} / 대기 ${data.queued ?? "?"}개`); } @@ -1452,7 +1479,7 @@ setStatus(statusTarget, `큐에 넣은 곡 없음. 새 곡 ${missingTracks.length}개 중 ${wavPending}개가 WAV 후보 URL 생성 실패.`); return; } - setStatus(statusTarget, `큐 전송 완료: 새 곡 ${enqueued}개, 보류 ${wavPending}개. 로컬 프로그램이 WAV 준비를 기다리며 처리 중.`); + setStatus(statusTarget, `큐 전송 완료: 새 곡 ${enqueued}개, 보류 ${wavPending}개. ${syncTextSummary}`); } async function uploadTrackToCompanion(track, statusTarget, format = "mp3") { @@ -1471,9 +1498,10 @@ const syncText = await syncResponse.text(); if (!syncResponse.ok) throw new Error(syncText || `${syncResponse.status} ${syncResponse.statusText}`); const syncData = tryParseJson(syncText) || {}; + const syncTextSummary = showSyncSummary(syncData, 1); const missingTracks = Array.isArray(syncData.tracks) ? syncData.tracks : []; if (!missingTracks.length) { - setStatus(statusTarget, `이미 로컬에 있음, SUNO 다운로드 생략: ${track.title || track.id}`); + setStatus(statusTarget, `이미 로컬에 있음, SUNO 다운로드 생략: ${track.title || track.id}. ${syncTextSummary}`); return { ok: true, skipped: true, @@ -1529,17 +1557,21 @@ const syncText = await syncResponse.text(); if (!syncResponse.ok) throw new Error(syncText || `${syncResponse.status} ${syncResponse.statusText}`); const syncData = tryParseJson(syncText) || {}; + const syncTextSummary = showSyncSummary(syncData, tracks.length); const missingTracks = Array.isArray(syncData.tracks) ? syncData.tracks : []; if (!missingTracks.length) { - setStatus(statusTarget, `동기화 완료 상태: ${tracks.length}곡 모두 로컬에 있음`); + setStatus(statusTarget, `동기화 완료. ${syncTextSummary}`); return; } let uploaded = 0; let failed = 0; + const syncExisting = Number(syncData.existingRequested ?? syncData.existing ?? 0); + const syncTotal = Number(syncData.requested ?? syncData.received ?? tracks.length); for (let index = 0; index < missingTracks.length; index += 1) { const track = { ...missingTracks[index] }; try { + updateProgress(syncExisting + uploaded + failed, syncTotal, `${format.toUpperCase()} 다운로드`); setStatus(statusTarget, `${index + 1}/${missingTracks.length} ${format.toUpperCase()} 처리 중: ${track.title || track.id}`); await uploadTrackToCompanion(track, statusTarget, format); uploaded += 1; @@ -1548,13 +1580,14 @@ console.warn("[ChatGPT to Suno] upload failed", track, error); setStatus(statusTarget, `${track.title || track.id} 실패: ${error.message || error}`); } + updateProgress(syncExisting + uploaded + failed, syncTotal, `${format.toUpperCase()} 다운로드`); await new Promise((resolve) => setTimeout(resolve, 400)); } let healthText = ""; try { healthText = ` / ${formatCompanionStatus(await readCompanionHealth())}`; } catch {} - setStatus(statusTarget, `${format.toUpperCase()} 일괄 업로드 완료: ${uploaded}곡 성공, ${failed}곡 실패. 로컬에서 메타데이터 후처리 중${healthText}`); + setStatus(statusTarget, `${format.toUpperCase()} 일괄 업로드 완료: ${uploaded}곡 성공, ${failed}곡 실패. ${syncTextSummary}. 로컬에서 메타데이터 후처리 중${healthText}`); } function trackOptionLabel(track, index) { @@ -1643,6 +1676,11 @@
+ +