feat(v0.2.4): blog generation UX overhaul — live progress bar

When you click Generate:
- Dark overlay with orange progress bar shows pipeline status
- Live step counter: 'Step 3/10: Outline Generation — decision-driven structure'
- Percentage updates every 15 seconds via API polling
- When done: shows word count + QA score, auto-opens the article
- No more silent template dump — user sees the entire pipeline working
This commit is contained in:
Rene Fichtmueller 2026-03-31 09:44:29 +02:00
parent 4233118505
commit 278207078b
3 changed files with 72 additions and 19 deletions

View File

@ -67,7 +67,7 @@ app.get("/", (_req, res) => {
app.get("/api", (_req, res) => {
res.json({
name: "Transceiver Intelligence Platform",
version: "0.2.3",
version: "0.2.4",
endpoints: [
"GET /api/transceivers?q=&form_factor=&speed=&category=&fiber_type=&wdm_type=&coherent=",
"GET /api/transceivers/:id",

View File

@ -14,7 +14,7 @@ healthRouter.get("/", async (_req: Request, res: Response) => {
res.json({
success: true,
status: "healthy",
version: "0.2.3",
version: "0.2.4",
uptime: process.uptime(),
database: {
connected: true,

View File

@ -2287,7 +2287,16 @@ function copyBlogContent(id) {
// BLOG
function generateBlog(topic, speed) {
el('blog-list').innerHTML = '<div class="loading pulse" id="blog-gen-status">Generating template... LLM 10-step pipeline starts in background (~10 min)</div>';
// Show prominent progress overlay instead of just adding to list
el('blog-list').innerHTML = '<div style="background:linear-gradient(135deg,#1a1a1a,#2a2a2a);color:white;padding:2rem;border-radius:12px;text-align:center">' +
'<div style="font-size:1.5rem;font-weight:700;margin-bottom:1rem">🔄 Generating Blog with AI...</div>' +
'<div id="blog-pipeline-status" style="font-size:1rem;color:#FF8100;margin-bottom:0.5rem">Starting 10-step Flexoptix Style pipeline...</div>' +
'<div id="blog-pipeline-step" style="font-size:0.85rem;color:#888">Connecting to LLM (qwen2.5:14b on Mac Studio)</div>' +
'<div style="margin-top:1.5rem;background:#333;border-radius:8px;height:8px;overflow:hidden">' +
'<div id="blog-pipeline-bar" style="width:0%;height:100%;background:#FF8100;transition:width 0.5s ease"></div></div>' +
'<div id="blog-pipeline-pct" style="font-size:0.8rem;color:#666;margin-top:0.5rem">0%</div>' +
'</div>';
var body = { topic: topic };
if (speed) body.speed = speed;
fetch(API + '/api/blog/generate', {
@ -2296,29 +2305,73 @@ function generateBlog(topic, speed) {
body: JSON.stringify(body)
}).then(function(r) { return r.json(); }).then(function(data) {
if (data.success) {
var msg = data.draft.title + ' — ' + data.draft.word_count + ' words';
if (data.draft.llm_enhancing) msg += ' (LLM enhancing in background...)';
showToast('Draft generated', msg);
if (data.draft.llm_enhancing) {
pollBlogLlm(data.draft.id, 0);
}
} else showToast('Failed', data.error || 'Unknown error', true);
loadBlogDrafts();
}).catch(function(err) { showToast('Network error', err.message, true); });
var statusEl = document.getElementById('blog-pipeline-status');
if (statusEl) statusEl.textContent = 'Template created. LLM pipeline running...';
var stepEl = document.getElementById('blog-pipeline-step');
if (stepEl) stepEl.textContent = 'Step 1/10: Topic Expansion — analyzing real-world scenarios...';
var barEl = document.getElementById('blog-pipeline-bar');
if (barEl) barEl.style.width = '5%';
pollBlogLlm(data.draft.id, 0);
} else {
showToast('Failed', data.error || 'Unknown error', true);
loadBlogDrafts();
}
}).catch(function(err) { showToast('Network error', err.message, true); loadBlogDrafts(); });
}
var STEP_NAMES = [
'Topic Expansion — analyzing real-world scenarios',
'Angle Selection — picking the strongest angle',
'Outline Generation — decision-driven structure',
'Master Draft — writing 2500+ word article',
'Reality Injection — adding production failures',
'Technical Deepening — specific optics + numbers',
'Opinion Layer — removing neutrality',
'Kill AI Tone — making it sound human',
'QA Check — fixing weak sections',
'Quality Score — rating 1-10'
];
function pollBlogLlm(id, attempt) {
if (attempt > 60) { showToast("Timeout", "LLM pipeline took too long. Check logs."); return; }
if (attempt > 60) {
showToast('Timeout', 'LLM pipeline took too long. Refresh to check.');
loadBlogDrafts();
return;
}
setTimeout(function() {
api('/api/blog/' + id).then(function(data) {
if (data.draft && data.draft.generated_by && data.draft.generated_by && data.draft.generated_by !== 'tip-blog-engine-template' && data.draft.generated_by !== null) {
showToast('LLM Enhanced', data.draft.title + ' — ' + data.draft.word_count + ' words');
loadBlogDrafts();
var d = data.draft || data;
var done = d.generated_by && d.generated_by !== 'tip-blog-engine-template' && d.generated_by !== null;
var steps = d.pipeline_steps_completed || 0;
if (done) {
// Pipeline finished! Show the result
var pct = document.getElementById('blog-pipeline-pct');
var bar = document.getElementById('blog-pipeline-bar');
var status = document.getElementById('blog-pipeline-status');
if (bar) bar.style.width = '100%';
if (pct) pct.textContent = '100%';
if (status) {
status.textContent = '✅ Blog generated! ' + (d.word_count || '?') + ' words, QA Score: ' + (d.auto_qa_score ? JSON.parse(typeof d.auto_qa_score === 'string' ? d.auto_qa_score : JSON.stringify(d.auto_qa_score)).overall || '?' : '?') + '/10';
status.style.color = '#2d6a4f';
}
showToast('Blog Ready!', d.title + ' — ' + d.word_count + ' words (LLM-generated)');
setTimeout(function() { loadBlogDrafts(); viewBlogDraft(id); }, 2000);
} else {
var steps = data.draft.pipeline_steps_completed || 0; showToast("LLM Step " + steps + "/10", "Pipeline running..."); pollBlogLlm(id, attempt + 1);
// Still processing — update progress
var pctVal = Math.min(95, steps * 10 + 5);
var bar = document.getElementById('blog-pipeline-bar');
var pct = document.getElementById('blog-pipeline-pct');
var stepEl = document.getElementById('blog-pipeline-step');
var status = document.getElementById('blog-pipeline-status');
if (bar) bar.style.width = pctVal + '%';
if (pct) pct.textContent = pctVal + '%';
if (stepEl && steps > 0 && steps <= 10) stepEl.textContent = 'Step ' + steps + '/10: ' + (STEP_NAMES[steps-1] || 'Processing...');
if (status) status.textContent = 'LLM Pipeline: Step ' + steps + '/10';
pollBlogLlm(id, attempt + 1);
}
}).catch(function() {});
}, 20000);
}).catch(function() { pollBlogLlm(id, attempt + 1); });
}, 15000);
}
el('gen-hype').addEventListener('click', function() { generateBlog('hype_cycle', '800G'); });