// Okno postępu przy pisaniu horoskopu. // // Problem: generowanie trwa minutami, a zwykły POST formularza nie daje żadnego // sygnału — aplikacja wygląda na zawieszoną. Zamiast udawanego paska postępu // czytamy strumień RZECZYWISTYCH zdarzeń z serwera (NDJSON) i wypisujemy je // jako log: budowa promptu, limity modelu, każda tura generowania. // // Degradacja: jeśli przeglądarka nie umie strumieniować `fetch`, nie przechwytujemy // wysyłki — formularz idzie klasycznie i wszystko działa jak wcześniej, tylko bez okna. document.addEventListener('DOMContentLoaded', function () { const canStream = typeof fetch === 'function' && typeof ReadableStream === 'function' && typeof TextDecoder === 'function'; const form = document.querySelector('form[action="/interpret"], form[action="/timeline"]'); if (!canStream || !form) return; const profile = form.getAttribute('action') === '/timeline' ? 'period' : 'natal'; const btn = form.querySelector('button[value="horoscope"]'); if (!btn) return; // --- okno --------------------------------------------------------------- const overlay = document.createElement('div'); overlay.className = 'progress-overlay'; overlay.hidden = true; overlay.innerHTML = ''; document.body.appendChild(overlay); const logEl = overlay.querySelector('#progressLog'); const clockEl = overlay.querySelector('#progressClock'); const titleEl = overlay.querySelector('#progressTitle'); const closeEl = overlay.querySelector('#progressClose'); let timer = null; function addLine(text, kind) { const li = document.createElement('li'); if (kind) li.className = 'log-' + kind; const now = new Date(); li.textContent = String(now.getHours()).padStart(2, '0') + ':' + String(now.getMinutes()).padStart(2, '0') + ':' + String(now.getSeconds()).padStart(2, '0') + ' ' + text; logEl.appendChild(li); logEl.scrollTop = logEl.scrollHeight; } function startClock() { const t0 = Date.now(); timer = setInterval(function () { const s = Math.floor((Date.now() - t0) / 1000); clockEl.textContent = Math.floor(s / 60) + ':' + String(s % 60).padStart(2, '0'); }, 1000); } function finish(title, allowClose) { if (timer) { clearInterval(timer); timer = null; } titleEl.textContent = title; overlay.querySelector('.progress-spinner').style.visibility = 'hidden'; if (allowClose) closeEl.hidden = false; } closeEl.addEventListener('click', function () { overlay.hidden = true; }); // --- przechwycenie wysyłki ---------------------------------------------- btn.addEventListener('click', function (event) { event.preventDefault(); if (!form.reportValidity()) return; // te same reguły co przy zwykłej wysyłce const data = new FormData(form); data.set('profile', profile); logEl.innerHTML = ''; closeEl.hidden = true; overlay.querySelector('.progress-spinner').style.visibility = ''; titleEl.textContent = 'Piszę horoskop…'; overlay.hidden = false; startClock(); addLine('Wysyłam żądanie…'); fetch('/horoscope/stream', { method: 'POST', body: data }) .then(function (response) { if (!response.ok || !response.body) throw new Error('HTTP ' + response.status); const reader = response.body.getReader(); const decoder = new TextDecoder(); let buffer = ''; function pump() { return reader.read().then(function (chunk) { if (chunk.done) return; buffer += decoder.decode(chunk.value, { stream: true }); const lines = buffer.split('\n'); buffer = lines.pop(); // ostatni może być niepełny lines.forEach(function (raw) { if (!raw.trim()) return; let ev; try { ev = JSON.parse(raw); } catch (e) { return; } if (ev.type === 'ping') return; // sam heartbeat, nie logujemy if (ev.type === 'result') { addLine(ev.message || 'Gotowe.', 'ok'); if (ev.html) { const host = document.getElementById('promptResult'); if (host) host.innerHTML = ev.html; } finish('Gotowe', true); overlay.hidden = true; const host = document.getElementById('promptResult'); if (host) host.scrollIntoView({ behavior: 'smooth', block: 'start' }); return; } addLine(ev.message || ev.type, ev.type === 'error' ? 'err' : ev.type === 'warn' ? 'warn' : null); if (ev.type === 'error') finish('Nie udało się', true); }); return pump(); }); } return pump(); }) .catch(function (e) { addLine('Połączenie przerwane: ' + e.message, 'err'); addLine('Możesz spróbować ponownie — nic nie zostało utracone.'); finish('Nie udało się', true); }); }); });