File size: 12,035 Bytes
77a41a5 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 | // π‘ [μμ ] νλ‘κ·Έλ¨ μ€ν μ κΈ°λ³ΈμΌλ‘ 1λ¨κ³κ° μ‘΄μ¬νλλ‘ μ΄κΈ°κ° μ€μ
let currentScript = [{
credit: "μΆμ² μ
λ ₯\n@username",
idiom: "β ν΅μ¬ νν",
meaning: "; λ»μ μ
λ ₯νμΈμ",
en: "",
ko: "",
pron: "",
prompt_top: "stand-up comedian, highly detailed",
prompt_bottom: "audience reacting, cinematic lighting"
}];
let assetStates = {};
let currentPreviewTurn = 0;
// π¨ μνΌ μλ§ νμ΄ν¬κ·ΈλνΌ κΈλ‘λ² μ€μ (λ³ν ν¨κ³Ό ν¬ν¨)
let styleConfig = {
english: {
font: 'SUIT', size: 80, baseColor: '#FFFFFF', bgColor: '#000000', bgOpacity: 0.85,
highlights: {
1: { color: '#FFF200', desc: 'μ£Όμ (λ
Έλ)' },
2: { color: '#00E5FF', desc: 'ν¨ν΄(μμ)' },
3: { color: '#FF3B30', desc: 'μ£Όμ(λΉ¨κ°)' },
4: { color: '#4CD964', desc: 'μ΄ν(μ΄λ‘)' },
5: { color: '#FF2D55', desc: 'λμμ€(νν¬)' }
}
},
korean: { font: 'SeoulNamsanM', size: 55, color: '#FFFFFF', outlineColor: '#000000', outlineWidth: 3 },
pronunciation: { font: 'SchoolSafeDictation', size: 65, color: '#FF3B30', outlineColor: '#000000', outlineWidth: 2 }
};
// β¨ [μμ§] λ³ν κ°μ κΈ°λ° λ©ν° μ€νμΌ μλ§ λ³ν ν¨μ
function parseAsteriskToHTML(text) {
if (!text) return "";
let html = text;
html = html.replace(/\*{5}(.*?)\*{5}/g, `<span class="font-bold" style="color: ${styleConfig.english.highlights[5].color};">$1</span>`);
html = html.replace(/\*{4}(.*?)\*{4}/g, `<span class="font-bold" style="color: ${styleConfig.english.highlights[4].color};">$1</span>`);
html = html.replace(/\*{3}(.*?)\*{3}/g, `<span class="font-bold underline" style="color: ${styleConfig.english.highlights[3].color};">$1</span>`);
html = html.replace(/\*{2}(.*?)\*{2}/g, `<span class="font-bold" style="color: ${styleConfig.english.highlights[2].color};">$1</span>`);
html = html.replace(/\*(.*?)\*/g, `<span class="font-bold" style="color: ${styleConfig.english.highlights[1].color};">$1</span>`);
return html;
}
// [ν¨λ 1] AI λλ³Έ μμ± μμ§
async function generateScript() {
const topic = document.getElementById('topicInput').value;
if(!topic) { alert("μ£Όμ λ₯Ό μ
λ ₯ν΄μ£ΌμΈμ."); return; }
const btn = event.currentTarget;
btn.innerHTML = '<i class="fa-solid fa-circle-notch fa-spin"></i> μμ± μ€...';
btn.disabled = true;
try {
const response = await fetch('/api/studio/generate-script', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ topic: topic })
});
if (!response.ok) throw new Error(`μλ² μ€λ₯: ${response.status}`);
const data = await response.json();
currentScript = data.dialogue.map((turn) => ({
credit: turn.credit || "μ§λ―Έ μΉ΄\n@jimmycarr",
idiom: turn.idiom || "β ν΅μ¬ νν",
meaning: turn.meaning || "; λ»μ μ
λ ₯νμΈμ",
en: turn.en || "",
ko: turn.ko || "",
pron: turn.ko_pron || turn.pron || "",
prompt_top: turn.image_prompt_top || turn.image_prompt || "stand-up comedian, highly detailed",
prompt_bottom: turn.image_prompt_bottom || "audience reacting, cinematic lighting"
}));
renderScriptPanel();
renderAssetPanel();
} catch (error) {
console.error("λλ³Έ μμ± μλ¬:", error);
// π‘ [μμ ] AI μμ± μ€ν¨ μ(Mock λ°μ΄ν°) κΈ°λ³Έ 4λ¨κ³ κΈ°νμ λ§μΆ° μΆλ ₯
currentScript = [
{
credit: "μ§λ―Έ μΉ΄\n@jimmycarr", idiom: "β test the water(s)", meaning: "; λΆμκΈ°λ₯Ό μ΄νΌλ€ / λ°μμ λ 보λ€",
en: "I'll just, I'll *test the water* first.", ko: "κ·Έλ₯ λΆμκΈ° ν λ² λ³Όκ²", pron: "μμμ μ€, μμν
μ€λ μλ΄ νΌμ€λ",
prompt_top: "stand-up comedian holding a microphone", prompt_bottom: "crowd of people smiling"
},
{
credit: "μ§λ―Έ μΉ΄\n@jimmycarr", idiom: "β make a move", meaning: "; νλμ μ·¨νλ€ / λ€κ°κ°λ€",
en: "Before I actually **make a move**, you know?", ko: "본격μ μΌλ‘ νλνκΈ° μ μ λ§μ΄μΌ.", pron: "λΉν¬ μμ΄ μ‘μΈμΌλ¦¬ λ©μ΄μ»€ 무λΈ, μ λ
Έ?",
prompt_top: "man looking thoughtful, cinematic lighting", prompt_bottom: "close up of a person's eyes"
},
{
credit: "μ§λ―Έ μΉ΄\n@jimmycarr", idiom: "β play it safe", meaning: "; μμ νκ² κ°λ€ / 무리νμ§ μλ€",
en: "It's always better to ***play it safe***.", ko: "μΈμ λ μμ νκ² κ°λ κ² λ«μ§.", pron: "μ΄μΈ μ¬μ¨μ΄μ¦ λ² λ¬ ν¬ νλ μ΄ μ μΈμ΄ν.",
prompt_top: "a cautious person, dark background", prompt_bottom: "audience nodding in agreement"
},
{
credit: "μ§λ―Έ μΉ΄\n@jimmycarr", idiom: "β go all in", meaning: "; μ¬μΈνλ€ / λ€ κ±Έλ€",
en: "Then I'll just ****go all in****.", ko: "κ·Έλ¬κ³ λμ μΉ λ€ κ±Έμ΄λ²λ¦΄ κ±°μΌ.", pron: "λ΄ μμμ μ€ κ³ μ¬ μΈ.",
prompt_top: "casino chips, aggressive lighting", prompt_bottom: "people cheering loudly"
}
];
renderScriptPanel();
renderAssetPanel();
} finally {
btn.innerHTML = '<i class="fa-solid fa-wand-magic-sparkles"></i> AI μμ±';
btn.disabled = false;
}
}
// [ν¨λ 1] λλ³Έ μλν° UI λ λλ§
function renderScriptPanel() {
const container = document.getElementById('scriptContainer');
container.innerHTML = "";
currentScript.forEach((turn, idx) => {
// π‘ [μμ ] μΉ΄λ μ°μΈ‘ μλ¨μ X(μμ ) λ²νΌ μΆκ°
const html = `
<div class="bg-neutral-800 rounded-xl p-4 border border-neutral-700 shadow-md relative space-y-3">
<div class="absolute -left-2 -top-2 bg-neutral-700 text-white text-xs w-6 h-6 flex items-center justify-center rounded-full font-mono font-bold shadow-md">${idx+1}</div>
<button onclick="deleteScriptTurn(${idx})" class="absolute -right-2 -top-2 bg-neutral-900 border border-neutral-700 text-neutral-400 hover:bg-red-600 hover:text-white hover:border-red-500 w-6 h-6 flex items-center justify-center rounded-full transition-colors shadow-md text-[10px]" title="μ΄ λ¨κ³ μμ ">
<i class="fa-solid fa-xmark"></i>
</button>
<div class="grid grid-cols-2 gap-2 pt-1">
<div>
<label class="block text-[10px] text-gray-500 font-bold mb-1">π·οΈ μλ¨ μΆμ² νκΈ° (Credit)</label>
<textarea onchange="updateScriptData(${idx}, 'credit', this.value)" class="w-full bg-neutral-950 border border-neutral-700 rounded p-1.5 text-xs text-gray-300 font-mono h-12 leading-tight resize-none">${turn.credit}</textarea>
</div>
<div class="space-y-1.5">
<div>
<label class="block text-[10px] text-yellow-500 font-bold">β ν΅μ¬ νν (Idiom)</label>
<input onchange="updateScriptData(${idx}, 'idiom', this.value)" type="text" class="w-full bg-neutral-950 border border-neutral-700 rounded px-2 py-1 text-xs text-white font-bold" value="${turn.idiom}">
</div>
<div>
<label class="block text-[10px] text-neutral-400 font-bold">π¬ νν λ» (Meaning)</label>
<input onchange="updateScriptData(${idx}, 'meaning', this.value)" type="text" class="w-full bg-neutral-950 border border-neutral-700 rounded px-2 py-1 text-xs text-neutral-400" value="${turn.meaning}">
</div>
</div>
</div>
<div class="space-y-2 border-t border-neutral-700/60 pt-2">
<div>
<label class="block text-[10px] text-blue-400 font-bold mb-0.5">πΊπΈ μμ΄ λ¬Έμ₯ (Sentence)</label>
<textarea id="textarea_en_${idx}" onchange="updateScriptData(${idx}, 'en', this.value)" class="w-full bg-neutral-950 border border-neutral-700 rounded p-2 text-xs text-white resize-none font-medium" rows="2">${turn.en}</textarea>
</div>
<div>
<label class="block text-[10px] text-emerald-400 font-bold mb-0.5">π°π· νκ΅μ΄ ν΄μ (Translation)</label>
<input id="textarea_ko_${idx}" onchange="updateScriptData(${idx}, 'ko', this.value)" type="text" class="w-full bg-neutral-950 border border-neutral-700 rounded px-2 py-1.5 text-xs text-gray-300" value="${turn.ko}">
</div>
<div>
<label class="block text-[10px] text-purple-400 font-bold mb-0.5">π£οΈ νκΈ λ°μ νκΈ° (Pronunciation)</label>
<input id="textarea_pron_${idx}" onchange="updateScriptData(${idx}, 'pron', this.value)" type="text" class="w-full bg-neutral-950 border border-neutral-700 rounded px-2 py-1.5 text-xs text-purple-300 font-mono" value="${turn.pron}">
</div>
</div>
</div>
`;
container.insertAdjacentHTML('beforeend', html);
});
}
// ποΈ [μ κ· μΆκ°] μλμΌλ‘ νΉμ λ¨κ³(Turn) μμ ν¨μ
function deleteScriptTurn(idx) {
if (currentScript.length <= 1) {
alert("μ΅μ 1κ°μ λ¨κ³λ μ μ§ν΄μΌ ν©λλ€.");
return;
}
if (confirm(`μ λ§λ‘ ${idx + 1}λ¨κ³λ₯Ό μμ νμκ² μ΅λκΉ?`)) {
// ν΄λΉ μΈλ±μ€μ λλ³Έ λ° μμ
λ°μ΄ν° μμ
currentScript.splice(idx, 1);
// μμ
λ°μ΄ν°λ λκΈ°ννμ¬ μμ νκ³ μΈλ±μ€ μ¬μ λ ¬
const newAssetStates = {};
currentScript.forEach((_, newIdx) => {
newAssetStates[newIdx] = assetStates[newIdx >= idx ? newIdx + 1 : newIdx];
});
assetStates = newAssetStates;
// ν리뷰 μΈλ±μ€κ° λ²μλ₯Ό λ²μ΄λλ©΄ μ‘°μ
if (currentPreviewTurn >= currentScript.length) {
currentPreviewTurn = Math.max(0, currentScript.length - 1);
}
localStorage.setItem('studio_current_script', JSON.stringify(currentScript));
renderScriptPanel();
renderAssetPanel();
updatePreviewScreen();
}
}
function updateScriptData(idx, field, value) {
currentScript[idx][field] = value;
localStorage.setItem('studio_current_script', JSON.stringify(currentScript));
updatePreviewScreen();
}
// π [ν¨λ 1] μλμΌλ‘ μλ‘μ΄ λλ³Έ λ¨κ³(Turn) μΆκ° ν¨μ
function addScriptTurn() {
// 1. λΉ λ°μ΄ν° λ°°μ΄μ λ°μ΄λ£κΈ°
currentScript.push({
credit: "μΆμ² μ
λ ₯\n@username",
idiom: "β ν΅μ¬ νν",
meaning: "; λ»μ μ
λ ₯νμΈμ",
en: "",
ko: "",
pron: "",
prompt_top: "stand-up comedian, highly detailed",
prompt_bottom: "audience reacting, cinematic lighting"
});
// 2. ν¨λ λ€μ 그리기 (1λ², 2λ² ν¨λ λμ λκΈ°ν)
renderScriptPanel();
renderAssetPanel();
// 3. μ¬μ©μκ° λ°©κΈ μΆκ°ν 맨 μλμͺ½μΌλ‘ μ€ν¬λ‘€ λΆλλ½κ² μ΄λ
setTimeout(() => {
const container = document.getElementById('scriptContainer');
if (container) {
container.scrollTo({ top: container.scrollHeight, behavior: 'smooth' });
}
}, 50);
}
// π¨ ν
λ§ ν리μ
μ μ© ν¨μ
function applyThemePreset(theme) {
if (theme === 'variety') {
// μλ₯ν: κ°λ ¬ν ν°μ + λκΊΌμ΄ κ²μ ν
λ리, λ°μμ λΉ¨κ°μ
styleConfig.korean.color = '#FFFFFF';
styleConfig.korean.outlineColor = '#000000';
styleConfig.pronunciation.color = '#FF3B30';
styleConfig.pronunciation.outlineColor = '#000000';
} else if (theme === 'docu') {
// λ€νν: μ°¨λΆν μ°νμ + ν
λ리 μμ, λ°μμ μ΄λμ΄ νμ
styleConfig.korean.color = '#E5E5E5';
styleConfig.korean.outlineColor = 'transparent';
styleConfig.pronunciation.color = '#A3A3A3';
styleConfig.pronunciation.outlineColor = 'transparent';
} else if (theme === 'trendy') {
// νΈλ λν: λ€μ¨ λλμ μμμ + μ΄λμ΄ λ³΄λΌμ ν
λ리
styleConfig.korean.color = '#00E5FF';
styleConfig.korean.outlineColor = '#1A0B2E';
styleConfig.pronunciation.color = '#FF2D55';
styleConfig.pronunciation.outlineColor = '#1A0B2E';
}
// λ³κ²½λ μ€μ μ 미리보기 νλ©΄μ μ¦μ λ λλ§
updatePreviewScreen();
} |