diff --git a/packages/api/src/index.ts b/packages/api/src/index.ts
index a9690a8..66f2852 100644
--- a/packages/api/src/index.ts
+++ b/packages/api/src/index.ts
@@ -69,7 +69,7 @@ app.get("/", (_req, res) => {
app.get("/api", (_req, res) => {
res.json({
name: "Transceiver Intelligence Platform",
- version: "0.2.5",
+ version: "0.2.6",
endpoints: [
"GET /api/transceivers?q=&form_factor=&speed=&category=&fiber_type=&wdm_type=&coherent=",
"GET /api/transceivers/:id",
diff --git a/packages/api/src/routes/blog.ts b/packages/api/src/routes/blog.ts
index 7aa38c6..2365d5c 100644
--- a/packages/api/src/routes/blog.ts
+++ b/packages/api/src/routes/blog.ts
@@ -1291,3 +1291,21 @@ blogRouter.get("/feedback/training-data", async (_req: Request, res: Response) =
res.json({ entries: result.rows, count: result.rowCount });
} catch (err) { res.status(500).json({ error: "Failed" }); }
});
+
+// DELETE /api/blog/:id — Delete a blog draft
+blogRouter.delete("/:id", async (req: Request, res: Response) => {
+ try {
+ // Delete feedback first (FK constraint)
+ await pool.query("DELETE FROM blog_feedback WHERE blog_id = $1::uuid", [req.params.id]);
+ const result = await pool.query(
+ "DELETE FROM blog_drafts WHERE id = $1::uuid RETURNING id, title",
+ [req.params.id]
+ );
+ if (result.rows.length === 0) {
+ return res.status(404).json({ success: false, error: "Draft not found" });
+ }
+ res.json({ success: true, deleted: result.rows[0] });
+ } catch (err) {
+ res.status(500).json({ success: false, error: (err as Error).message });
+ }
+});
diff --git a/packages/api/src/routes/health.ts b/packages/api/src/routes/health.ts
index 82f6f97..8d2d8d7 100644
--- a/packages/api/src/routes/health.ts
+++ b/packages/api/src/routes/health.ts
@@ -14,7 +14,7 @@ healthRouter.get("/", async (_req: Request, res: Response) => {
res.json({
success: true,
status: "healthy",
- version: "0.2.5",
+ version: "0.2.6",
uptime: process.uptime(),
database: {
connected: true,
diff --git a/packages/dashboard/hot-topics.js b/packages/dashboard/hot-topics.js
new file mode 100644
index 0000000..bc7dc18
--- /dev/null
+++ b/packages/dashboard/hot-topics.js
@@ -0,0 +1,176 @@
+/**
+ * Hot Topics + Blog Pipeline UX Enhancement (v0.2.5)
+ * Loaded after main dashboard script.
+ * Overrides generateBlog + pollBlogLlm with improved versions.
+ */
+(function() {
+ var API = window.API || '';
+ var blogPipelineRunning = false;
+
+ 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 blogList = document.getElementById('blog-list');
+ if (blogList) {
+ blogList.innerHTML =
+ '
Discovering hot topics...
';
+
+ fetch((API || '') + '/api/hot-topics').then(function(r) { return r.json(); }).then(function(data) {
+ if (!data.topics || data.topics.length === 0) {
+ grid.innerHTML = '
-
-
Hot Topics (auto-discovered from market data, conferences, research)
-
+
+
Hot Topics auto-discovered from market data + conferences
+
-
-
Loading hot topics...
+
-
+
@@ -1164,7 +1164,7 @@ function goToTab(tabName) {
if (tabName === 'transceivers') searchTransceivers();
if (tabName === 'switches') searchSwitches();
if (tabName === 'news') loadNews();
- if (tabName === 'blog') { loadBlogDrafts(); if (typeof loadHotTopics === 'function') loadHotTopics(); }
+ if (tabName === 'blog') loadBlogDrafts();
}
document.querySelectorAll('.tab').forEach(function(tab) {
@@ -2279,23 +2279,8 @@ function copyBlogContent(id) {
}
// BLOG
-function generateBlog(topic, speed, customTitle, customAngle) {
- // Pipeline lock: prevent multiple simultaneous generations
- if (blogPipelineRunning) {
- showToast('Pipeline Busy', 'A blog is already being generated. Wait for it to finish.', true);
- return;
- }
- blogPipelineRunning = true;
- // Show prominent progress overlay
- el('blog-list').innerHTML = '
' +
- '
🔄 Generating Blog with AI...
' +
- '
Starting 10-step Flexoptix Style pipeline...
' +
- '
Connecting to LLM (qwen2.5:14b on Mac Studio)
' +
- '
' +
- '
0%
' +
- '
';
-
+function generateBlog(topic, speed) {
+ el('blog-list').innerHTML = '
Generating article...
';
var body = { topic: topic };
if (speed) body.speed = speed;
fetch(API + '/api/blog/generate', {
@@ -2304,109 +2289,34 @@ function generateBlog(topic, speed, customTitle, customAngle) {
body: JSON.stringify(body)
}).then(function(r) { return r.json(); }).then(function(data) {
if (data.success) {
- 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) { blogPipelineRunning = false; showToast('Network error', err.message, true); loadBlogDrafts(); });
+ 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 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) {
- blogPipelineRunning = false; showToast('Timeout', 'LLM pipeline took too long. Refresh to check.');
- loadBlogDrafts();
- return;
- }
+ if (attempt > 30) return;
setTimeout(function() {
api('/api/blog/' + id).then(function(data) {
- 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)');
- blogPipelineRunning = false; setTimeout(function() { loadBlogDrafts(); viewBlogDraft(id); }, 2000);
+ if (data.draft && data.draft.generated_by && data.draft.generated_by.includes('llm')) {
+ showToast('LLM Enhanced', data.draft.title + ' — ' + data.draft.word_count + ' words');
+ loadBlogDrafts();
} else {
- // 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() { pollBlogLlm(id, attempt + 1); });
- }, 15000);
+ }).catch(function() {});
+ }, 10000);
}
-// Hot Topics: load dynamically from API
-var blogPipelineRunning = false;
-function loadHotTopics() {
- var grid = el('hot-topics-grid');
- if (!grid) return;
- grid.innerHTML = '
Discovering hot topics...
';
- api('/api/hot-topics').then(function(data) {
- if (!data.topics || data.topics.length === 0) {
- grid.innerHTML = '
Hype Cycle Analysis
800G technology position
';
- return;
- }
- var urgencyColors = { breaking: '#c1121f', hot: '#FF8100', trending: '#e6a800', emerging: '#2d6a4f' };
- grid.innerHTML = data.topics.slice(0, 6).map(function(t) {
- var color = urgencyColors[t.urgency] || '#888';
- return '
' +
- '
' +
- '' + t.urgency + '' +
- '' + t.source_type + '
' +
- '
' + t.title + '
' +
- '
' + (t.suggested_angle || t.description).slice(0, 80) + '...
' +
- '
';
- }).join('');
- }).catch(function() {
- grid.innerHTML = '
';
- });
-}
+// Hot topics loaded dynamically via hot-topics.js
-function generateBlogFromTopic(encodedTopic) {
- var t = JSON.parse(decodeURIComponent(encodedTopic));
- generateBlog(t.blog_type, null, t.title, t.suggested_angle);
-}
-// Load hot topics when blog tab opens
-var origLoadBlog = loadBlogDrafts;
async function loadBlogDrafts() {
var data = await api('/api/blog');
@@ -2550,5 +2460,6 @@ el('compare-overlay').addEventListener('click', function(e) {
// INIT
loadOverview();
+