/** * Hot Topics + Blog Pipeline UX Enhancement (v0.3.0) * Loaded after main dashboard script. * Overrides generateBlog + pollBlogLlm with improved versions. * * v0.3.0: All fetch() calls now include Authorization Bearer token. */ (function() { var API = window.API || ''; var blogPipelineRunning = false; /** Get auth headers — uses obfuscated token helper from index.html (loadToken) */ function authHeaders(extra) { var token = (window.loadToken ? window.loadToken() : localStorage.getItem('tip_token')) || ''; var h = { 'Authorization': 'Bearer ' + token }; if (extra) Object.assign(h, extra); return h; } var STEP_NAMES = [ 'Topic Expansion', 'Angle Selection', 'Outline Generation', 'Master Draft (writing...)', 'Reality Injection', 'Technical Deepening', 'Opinion Layer', 'Kill AI Tone', 'QA Check', 'Quality Score' ]; // Override generateBlog with pipeline lock + progress UI window.generateBlog = function(topic, speed, customTitle, customAngle) { if (blogPipelineRunning) { if (typeof showToast === 'function') showToast('Pipeline Busy', 'A blog is already being generated. Please wait.'); return; } blogPipelineRunning = true; var pipelineEl = document.getElementById('blog-pipeline-status'); if (pipelineEl) { pipelineEl.innerHTML = '
' + '
Generating Blog with AI...
' + '
Starting 10-step Flexoptix Style pipeline...
' + '
Connecting to LLM (qwen2.5:14b)
' + '
' + '
' + '
0%
' + '
'; } var body = { topic: topic }; if (speed) body.speed = speed; if (customTitle) body.customTitle = customTitle; if (customAngle) body.customAngle = customAngle; fetch(API + '/api/blog/generate', { method: 'POST', headers: authHeaders({ 'Content-Type': 'application/json' }), body: JSON.stringify(body) }).then(function(r) { return r.json(); }).then(function(data) { if (data.success && data.draft) { var s = document.getElementById('bp-status'); if (s) s.textContent = 'Template created. LLM pipeline running (~10 min)...'; var b = document.getElementById('bp-bar'); if (b) b.style.width = '5%'; pollPipeline(data.draft.id, 0); } else { blogPipelineRunning = false; if (typeof showToast === 'function') showToast('Error', (data.error || 'Generation failed'), true); if (typeof loadBlogDrafts === 'function') loadBlogDrafts(); } }).catch(function(err) { blogPipelineRunning = false; if (typeof showToast === 'function') showToast('Network Error', err.message, true); if (typeof loadBlogDrafts === 'function') loadBlogDrafts(); }); }; // Track how many consecutive polls had running=false (stall detection) var _stallCount = 0; function showStallWarning(id) { var status = document.getElementById('bp-status'); var step = document.getElementById('bp-step'); if (status) { status.style.color = '#e6a800'; status.textContent = '⚠ Pipeline nicht aktiv (API-Neustart?) — LLM läuft evtl. noch'; } if (step) { step.innerHTML = 'Status unklar  ·  ' + ''; } } window._resetAndRetry = function(id) { _stallCount = 0; // Reset Ollama queue then regenerate fetch(API + '/api/blog/llm/reset-queue', { method: 'POST', headers: authHeaders() }).catch(function() {}); var step = document.getElementById('bp-step'); var status = document.getElementById('bp-status'); if (status) { status.style.color = '#FF8100'; status.textContent = 'Restarting pipeline…'; } if (step) step.textContent = 'Sending to LLM…'; fetch(API + '/api/blog/' + id + '/regenerate', { method: 'POST', headers: authHeaders({ 'Content-Type': 'application/json' }) }).then(function(r) { return r.json(); }) .then(function(data) { if (data.success) { if (typeof showToast === 'function') showToast('Pipeline gestartet', 'LLM läuft neu — warte auf Ergebnis'); pollPipeline(id, 0); } else { if (typeof showToast === 'function') showToast('Fehler', data.error || 'Regenerierung fehlgeschlagen', true); } }).catch(function(err) { if (typeof showToast === 'function') showToast('Network Error', err.message, true); }); }; function pollPipeline(id, attempt) { if (attempt > 90) { blogPipelineRunning = false; var pipelineEl = document.getElementById('blog-pipeline-status'); if (pipelineEl) pipelineEl.innerHTML = ''; if (typeof showToast === 'function') showToast('Timeout', 'Pipeline took too long. Check the blog list.'); if (typeof loadBlogDrafts === 'function') loadBlogDrafts(); return; } setTimeout(function() { // 1) Fetch real-time progress fetch(API + '/api/blog/' + id + '/progress', { headers: authHeaders() }) .then(function(r) { return r.json(); }) .then(function(prog) { if (prog.running) { _stallCount = 0; var bar = document.getElementById('bp-bar'); var pct = document.getElementById('bp-pct'); var status = document.getElementById('bp-status'); var step = document.getElementById('bp-step'); if (bar) bar.style.width = prog.pct + '%'; if (pct) pct.textContent = prog.pct + '%'; if (status) { status.style.color = '#FF8100'; status.textContent = prog.label || ('Step ' + prog.step + '/10'); } if (step) step.textContent = 'Step ' + prog.step + '/10 · qwen2.5:14b via Ollama'; } else { _stallCount++; // After 5 consecutive non-running polls (~40s), show stall warning if (_stallCount >= 5) showStallWarning(id); } }).catch(function() { _stallCount++; }); // 2) Fetch blog draft to detect completion fetch(API + '/api/blog/' + id, { headers: authHeaders() }) .then(function(r) { return r.json(); }) .then(function(data) { var d = data.draft || data; var gen = d.generated_by || ''; var done = gen && gen !== 'tip-blog-engine-template' && gen.length > 0; if (done) { var bar = document.getElementById('bp-bar'); var pct = document.getElementById('bp-pct'); var status = document.getElementById('bp-status'); var step = document.getElementById('bp-step'); if (bar) bar.style.width = '100%'; if (pct) pct.textContent = '100%'; if (status) { status.textContent = '✓ Blog fertig! ' + (d.word_count || '?') + ' Wörter'; status.style.color = '#2d6a4f'; } if (step) step.textContent = 'Engine: ' + gen; blogPipelineRunning = false; _stallCount = 0; if (typeof showToast === 'function') showToast('Blog Ready!', (d.title || 'Article') + ' — ' + (d.word_count || '?') + ' words'); setTimeout(function() { var pipelineEl = document.getElementById('blog-pipeline-status'); if (pipelineEl) pipelineEl.innerHTML = ''; if (typeof loadBlogDrafts === 'function') loadBlogDrafts(); if (typeof viewBlogDraft === 'function') viewBlogDraft(id); }, 2000); } else { pollPipeline(id, attempt + 1); } }).catch(function() { pollPipeline(id, attempt + 1); }); }, 8000); } // Hot topics loader window.loadHotTopics = function() { var grid = document.getElementById('hot-topics-grid'); var subtitle = document.getElementById('hot-topics-subtitle'); if (!grid) return; grid.innerHTML = '
Discovering hot topics...
'; fetch(API + '/api/hot-topics', { headers: authHeaders() }) .then(function(r) { return r.json(); }) .then(function(data) { if (!data.topics || data.topics.length === 0) { grid.innerHTML = '
Hype Cycle Analysis
'; return; } if (subtitle && data.refreshes_at) { var nextRefresh = new Date(data.refreshes_at); var hoursLeft = Math.round((nextRefresh - new Date()) / 3600000); subtitle.textContent = data.total + ' topics · rotates daily · next refresh in ' + hoursLeft + 'h · sources: ' + (data.sources || []).join(', '); } var colors = { breaking: '#c1121f', hot: '#FF8100', trending: '#e6a800', emerging: '#2d6a4f' }; grid.innerHTML = data.topics.map(function(t) { var c = colors[t.urgency] || '#888'; var cardId = 'ht-' + Math.random().toString(36).slice(2, 8); window['_ht_' + cardId] = t; return '
' + '
' + '' + (t.urgency || '') + '' + '' + (t.source_type || '') + ' · ' + (t.source || '') + '
' + '
' + (t.title || '') + '
' + '
' + (t.suggested_angle || t.description || '').slice(0, 100) + '
' + '
'; }).join(''); }).catch(function(err) { console.error('[HotTopics] fetch error:', err); grid.innerHTML = '
Hype Cycle
' + '
Comparison
' + '
Tutorial
'; }); }; // Generate blog from hot topic card window._generateFromHotTopic = function(cardId) { var t = window['_ht_' + cardId]; if (!t) return; generateBlog(t.blog_type || 'hype_cycle', null, t.title, t.suggested_angle || t.description); }; // Auto-load hot topics when blog tab activates var origActivateTab = window.activateTab; if (origActivateTab) { window.activateTab = function(tabName) { origActivateTab(tabName); if (tabName === 'blog') loadHotTopics(); }; } })(); // Called via data attributes to avoid quote-escaping issues in onclick window.blogDeleteClick = function(el) { var id = el.getAttribute('data-blog-id'); var title = el.getAttribute('data-blog-title'); if (id) window.deleteBlogDraft(id, title); }; // Delete a single blog draft window.deleteBlogDraft = function(id, title) { if (!confirm('Delete "' + title + '"?')) return; var token = (window.loadToken ? window.loadToken() : localStorage.getItem('tip_token')) || ''; fetch((window.API || '') + '/api/blog/' + id, { method: 'DELETE', headers: { 'Authorization': 'Bearer ' + token } }).then(function(r) { return r.json(); }) .then(function(data) { if (data.success) { if (typeof showToast === 'function') showToast('Deleted', title); if (typeof loadBlogDrafts === 'function') loadBlogDrafts(); } }); }; // Delete all template drafts window.deleteAllTemplateDrafts = function() { if (!confirm('Delete ALL template drafts? LLM-generated articles will be kept.')) return; var token = (window.loadToken ? window.loadToken() : localStorage.getItem('tip_token')) || ''; var authH = { 'Authorization': 'Bearer ' + token }; fetch((window.API || '') + '/api/blog', { headers: authH }) .then(function(r) { return r.json(); }) .then(function(data) { var templates = (data.drafts || []).filter(function(d) { return d.generated_by === 'tip-blog-engine-template' || !d.generated_by; }); var count = 0; templates.forEach(function(d) { fetch((window.API || '') + '/api/blog/' + d.id, { method: 'DELETE', headers: authH }) .then(function() { count++; if (count === templates.length && typeof loadBlogDrafts === 'function') loadBlogDrafts(); }); }); if (typeof showToast === 'function') showToast('Cleaning', 'Deleting ' + templates.length + ' template drafts...'); }); };