'; var win = window.open('', '_blank'); win.document.write(html); win.document.close(); setTimeout(function() { win.print(); }, 500); } async function editTale() { const input = document.getElementById('editPromptInput'); const status = document.getElementById('editStatus'); const prompt = input?.value?.trim(); if (!prompt || !state.tale) return; status.style.display = 'block'; status.textContent = '✍️ Переписываем...'; input.disabled = true; try { const resp = await fetch('/api/edit-tale', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ tale: state.tale, prompt: prompt, tale_id: state.taleId }) }); const data = await resp.json(); if (data.tale) { state.tale = data.tale; input.value = ''; status.textContent = '✓ Готово!'; setTimeout(() => { status.style.display = 'none'; }, 2000); renderResult(document.getElementById('content'), true); } else { status.textContent = '⚠ ' + (data.error || 'Ошибка'); } } catch (e) { status.textContent = '⚠ Ошибка соединения'; } input.disabled = false; } function newTaleKeepHeroes() { // Keep child info, reset tale const keepName = state.childName; const keepAge = state.childAge; const keepDesc = state.childDescription; const keepPhoto = state.childPhotoDescription; const keepGender = state.gender; const keepAgeGroup = state.ageGroup; resetAll(); state.childName = keepName; state.childAge = keepAge; state.childDescription = keepDesc; state.childPhotoDescription = keepPhoto; state.gender = keepGender; state.ageGroup = keepAgeGroup; state.step = 2; // Skip to theme selection render(); } function copyTale() { navigator.clipboard.writeText(state.tale).then(() => { const btn = event.target; btn.textContent = "Скопировано!"; setTimeout(() => { btn.textContent = "Скопировать текст"; }, 2000); }); } // Simple Russian genitive case for first names function toGenitive(name) { if (!name) return name; const n = name.trim(); // Female names ending in -а → -ы, -я → -и, -ия → -ии if (/ия$/i.test(n)) return n.slice(0, -1) + 'и'; if (/[кгхжшщч]а$/i.test(n)) return n.slice(0, -1) + 'и'; // Мика→Мики, Ольга→Ольги if (/а$/i.test(n)) return n.slice(0, -1) + 'ы'; if (/я$/i.test(n)) return n.slice(0, -1) + 'и'; // Male names ending in consonant → +а, -й → -я, -ь → -я if (/й$/i.test(n)) return n.slice(0, -1) + 'я'; if (/ь$/i.test(n)) return n.slice(0, -1) + 'я'; if (/[бвгджзклмнпрстфхцчшщ]$/i.test(n)) return n + 'а'; return n; // fallback — return unchanged } function escapeHtml(str) { const div = document.createElement("div"); div.textContent = str; return div.innerHTML; } function escapeAttr(str) { return str.replace(/&/g,'&').replace(/"/g,'"').replace(/'/g,''').replace(/
Загружаем сказку...
`; try { const resp = await fetch(`/api/tales/${taleId}`); if (!resp.ok) throw new Error("Сказка не найдена"); const tale = await resp.json(); if (tale.error) throw new Error(tale.error); // Restore state from saved tale state.taleId = taleId; state.tale = tale.tale_text; state.childName = tale.child_name || ""; state.childAge = ""; state.ageGroup = tale.age_group || ""; state.gender = tale.gender || ""; state.theme = tale.theme || ""; state.customTheme = tale.custom_theme || ""; state.world = tale.world || ""; state.customWorld = tale.custom_world || ""; state.depth = tale.depth || ""; state.characters = tale.characters || ""; state.details = tale.details || ""; state.usage = { input_tokens: tale.input_tokens || 0, output_tokens: tale.output_tokens || 0 }; state.illustrationPrompts = tale.illustration_prompts || null; state.taleTitle = tale.title || ""; state.taleSummary = tale.summary || ""; state.step = 5; // Load illustrations const illResp = await fetch(`/api/illustrations/${taleId}`); const illData = await illResp.json(); state.illustrations = illData; render(); // Apply loaded illustrations to DOM if (state.illustrations) updateIllustrations(state.illustrations); // Preload illustration images into browser cache if (illData.scenes) { Object.values(illData.scenes).forEach(function(s) { if (s.ready && s.url) { var img = new Image(); img.src = s.url; } }); } if (illData.characters) { Object.values(illData.characters).forEach(function(c) { if (c.ready && c.url) { var img = new Image(); img.src = c.url; } }); } // If still generating, keep polling if (illData.status !== "complete" && illData.status !== "not_found") { pollIllustrations(); } } catch (err) { el.innerHTML = `
Ошибка

${err.message}

`; } } // ── Init ─────────────────────────────────────────────────────────── const hashMatch = location.hash.match(/^#tale\/(.+)$/); const quizMatch = location.hash.match(/^#quiz\/(.+)$/); if (hashMatch) { loadExistingTale(hashMatch[1]); } else if (quizMatch) { // Parse quiz params — pre-fill all settings from quiz const qp = new URLSearchParams(quizMatch[1]); state.gender = qp.get('gender') || 'girl'; state.ageGroup = qp.get('age') || '5-7'; state.theme = qp.get('theme') || ''; state.childName = qp.get('name') || ''; state.customTheme = qp.get('customTheme') || ''; state.depth = qp.get('depth') || 'moderate'; state.storyLength = qp.get('storyLength') || 'medium'; // World if (qp.get('world')) { state.world = qp.get('world'); state.customWorld = qp.get('customWorld') || ''; } // Details (description, wishes, siblings) if (qp.get('details')) { state.details = qp.get('details'); } // Second child for siblings if (qp.get('name2')) { var sibInfo = 'Второй ребёнок: ' + qp.get('name2'); if (qp.get('age2')) sibInfo += ', ' + qp.get('age2') + ' лет'; if (qp.get('gender2')) sibInfo += ' (' + (qp.get('gender2') === 'girl' ? 'девочка' : 'мальчик') + ')'; state.details = (state.details ? state.details + '. ' : '') + sibInfo; } // Characters (family members) from quiz if (qp.get('characters')) { try { var famChars = JSON.parse(qp.get('characters')); var roleLabels = {brother:'Брат',sister:'Сестра',mother:'Мама',father:'Папа',grandmother:'Бабушка',grandfather:'Дедушка',friend:'Друг',pet:'Питомец',other:'Персонаж'}; famChars.forEach(function(c) { if (state.characters.length < 5) { state.characters.push({ role: c.role, name: c.name || roleLabels[c.role] || '', photoFile: null, photoDescription: '', photoAnalyzing: false }); } }); } catch(e) {} } // Analyze photos from sessionStorage function analyzeStoredPhoto(storageKey, callback) { var data = sessionStorage.getItem(storageKey); if (!data) return; sessionStorage.removeItem(storageKey); fetch(data).then(function(r){return r.blob()}).then(function(blob) { var formData = new FormData(); formData.append("data", blob, "photo.jpg"); fetch("/api/analyze-photo", {method:"POST", body:formData}) .then(function(r){return r.json()}) .then(function(result) { if (result.description) callback(result.description); }) .catch(function(){}); }).catch(function(){}); } // Child photo analyzeStoredPhoto('quiz_photo', function(desc) { state.childPhotoDescription = desc; }); if (sessionStorage.getItem('quiz_photo')) { state.childPhotoDescription = '(анализируем фото...)'; } // Second child photo analyzeStoredPhoto('quiz_photo2', function(desc) { state.details = (state.details ? state.details + '. ' : '') + 'Фото второго ребёнка: ' + desc; }); // Family member photos var famCount = parseInt(sessionStorage.getItem('quiz_fam_count') || '0'); for (var fi = 0; fi < famCount; fi++) { (function(idx) { analyzeStoredPhoto('quiz_fam_photo_' + idx, function(desc) { if (state.characters[idx]) { state.characters[idx].photoDescription = desc; } }); })(fi); } sessionStorage.removeItem('quiz_fam_count'); history.replaceState(null, '', location.pathname); // Quiz provides all settings — go straight to generation if (state.theme && state.world) { generate(); } else if (state.theme) { state.step = 3; // world selection render(); } else { state.step = 2; // theme selection render(); } } else { render(); }