Phase 6b: fix dashboard Untitled/WEB bug for transcripts

Two bugs in the Recently Completed table:

1. Title showed "Untitled" for all transcripts because the dashboard
   read documents.book_title (populated by PDF metadata voting) which
   is NULL for transcripts. Fixed by COALESCE(book_title, filename)
   in the SQL query -- falls back to catalogue.filename which holds
   the real video title.

2. Type showed "WEB" for all transcripts because the type CASE
   expression only had web and pdf branches, with web matching any
   http% path -- and transcript paths are PeerTube watch URLs.
   Fixed by adding a transcript branch keyed on catalogue.source =
   stream.echo6.co, evaluated before the web branch.

Also adds badge-transcript CSS (purple) and JS rendering case.
Applied consistently to both the Recently Completed and Sources
table queries.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Matt 2026-04-14 23:05:29 +00:00
commit 70b80cb312
3 changed files with 20 additions and 9 deletions

View file

@ -882,7 +882,11 @@ def _build_knowledge_stats():
sources = conn.execute("""
SELECT
c.source,
CASE WHEN c.path LIKE 'http%' THEN 'web' ELSE 'pdf' END as type,
CASE
WHEN c.source = 'stream.echo6.co' THEN 'transcript'
WHEN c.path LIKE 'http%' THEN 'web'
ELSE 'pdf'
END as type,
COUNT(DISTINCT c.hash) as catalogued,
COUNT(DISTINCT CASE WHEN d.status = 'complete' THEN d.hash END) as complete,
COUNT(DISTINCT CASE WHEN d.status NOT IN ('complete', 'failed') AND d.status IS NOT NULL THEN d.hash END) as in_pipeline,
@ -935,11 +939,17 @@ def _build_knowledge_stats():
""").fetchone()[0]
recent = conn.execute("""
SELECT book_title, status, concepts_extracted, vectors_inserted,
CASE WHEN path LIKE 'http%' THEN 'web' ELSE 'pdf' END as type
FROM documents
WHERE status = 'complete'
ORDER BY embedded_at DESC
SELECT COALESCE(d.book_title, c.filename) as title,
d.status, d.concepts_extracted, d.vectors_inserted,
CASE
WHEN c.source = 'stream.echo6.co' THEN 'transcript'
WHEN d.path LIKE 'http%' THEN 'web'
ELSE 'pdf'
END as type
FROM documents d
JOIN catalogue c ON d.hash = c.hash
WHERE d.status = 'complete'
ORDER BY d.embedded_at DESC
LIMIT 10
""").fetchall()
@ -979,7 +989,7 @@ def _build_knowledge_stats():
'source_types': dict(sorted(source_type_counts.items(), key=lambda x: -x[1])),
'sample_size': sample_size,
'recent_complete': [{
'title': r['book_title'] or 'Untitled',
'title': r['title'] or 'Untitled',
'concepts': r['concepts_extracted'] or 0,
'vectors': r['vectors_inserted'] or 0,
'type': r['type'],

View file

@ -210,6 +210,7 @@ tr:hover { background: var(--bg-secondary); }
}
.badge-web { background: #1e3a5f; color: #60a5fa; padding: 2px 8px; border-radius: var(--radius); font-size: 11px; }
.badge-pdf { background: #2d5a2d; color: #4ade80; padding: 2px 8px; border-radius: var(--radius); font-size: 11px; }
.badge-transcript { background: #3b1f5e; color: #c084fc; padding: 2px 8px; border-radius: var(--radius); font-size: 11px; }
/* ── Trend indicators ── */
.trend { font-size: 11px; margin-left: 6px; }

View file

@ -88,7 +88,7 @@
var pipeCount = s.in_pipeline || 0;
totalCat += catCount; totalComp += compCount; totalPipe += pipeCount;
totalConcepts += s.concepts; totalVectors += s.vectors;
var badge = s.type === 'web' ? '<span class="badge-web">WEB</span>' : '<span class="badge-pdf">PDF</span>';
var badge = s.type === 'transcript' ? '<span class="badge-transcript">TRANSCRIPT</span>' : s.type === 'web' ? '<span class="badge-web">WEB</span>' : '<span class="badge-pdf">PDF</span>';
var compPct = catCount > 0 ? (compCount / catCount * 100) : 0;
var pipePct = catCount > 0 ? (pipeCount / catCount * 100) : 0;
var compColor = compPct >= 100 ? '#00ff41' : compPct > 0 ? '#ffa500' : '#666';
@ -185,7 +185,7 @@
rtb.innerHTML = '<tr><td colspan="4" class="text-dim">None yet</td></tr>';
} else {
rtb.innerHTML = data.recent_complete.map(function(r) {
var badge = r.type === 'web' ? '<span class="badge-web">WEB</span>' : '<span class="badge-pdf">PDF</span>';
var badge = r.type === 'transcript' ? '<span class="badge-transcript">TRANSCRIPT</span>' : r.type === 'web' ? '<span class="badge-web">WEB</span>' : '<span class="badge-pdf">PDF</span>';
return '<tr><td>' + r.title + '</td><td>' + badge + '</td><td>' +
r.concepts + '</td><td>' + r.vectors + '</td></tr>';
}).join('');