/**
* 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;
// Fetch the current active model name so we never show a stale hardcoded version.
var initialModelLabel = (window._activeFoBlogModel) || 'FO_BlogLLM';
fetch(API + '/api/blog/llm/status', { headers: authHeaders() })
.then(function(r) { return r.json(); })
.then(function(data) {
var m = data && data.llm && data.llm.model;
if (m) {
window._activeFoBlogModel = m;
var s = document.getElementById('bp-step');
if (s && s.textContent.indexOf('Connecting to FO_BlogLLM') === 0) {
s.textContent = 'Connecting to FO_BlogLLM (' + m + ')';
}
}
}).catch(function() {});
var pipelineEl = document.getElementById('blog-pipeline-status');
if (pipelineEl) {
pipelineEl.innerHTML =
'
' +
'
Generating Blog with AI...
' +
'
Starting 10-step Flexoptix Style pipeline...
' +
'
Connecting to ' + initialModelLabel + '
' +
'
' +
'
' +
'
0%
' +
'
';
}
var body = { topic: topic };
if (speed) body.speed = speed;
if (customTitle) body.custom_title = customTitle;
if (customAngle) body.additional_context = 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 · ' + (window._activeFoBlogModel || 'fo-blog-v10') + ' via adapter bridge';
} 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 = '