pipeline UI polish: jellyfin deep-links on titles, hover-to-show approve buttons, series approve-up-to
All checks were successful
Build and Push Docker Image / build (push) Successful in 37s

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-03-28 01:14:19 +01:00
parent 7cefd9bf04
commit 9c5a793a47
5 changed files with 151 additions and 88 deletions

View File

@@ -89,6 +89,61 @@ function reanalyze(db: ReturnType<typeof getDb>, itemId: number): void {
}
}
// ─── Pipeline: summary ───────────────────────────────────────────────────────
app.get('/pipeline', (c) => {
const db = getDb();
const jellyfinUrl = getConfig('jellyfin_url') ?? '';
const review = db.prepare(`
SELECT rp.*, mi.name, mi.series_name, mi.series_jellyfin_id,
mi.jellyfin_id,
mi.season_number, mi.episode_number, mi.type, mi.container,
mi.original_language, mi.orig_lang_source, mi.file_path
FROM review_plans rp
JOIN media_items mi ON mi.id = rp.item_id
WHERE rp.status = 'pending' AND rp.is_noop = 0
ORDER BY
CASE rp.confidence WHEN 'high' THEN 0 ELSE 1 END,
COALESCE(mi.series_name, mi.name),
mi.season_number, mi.episode_number
`).all();
const queued = db.prepare(`
SELECT j.*, mi.name, mi.series_name, mi.type,
rp.job_type, rp.apple_compat
FROM jobs j
JOIN media_items mi ON mi.id = j.item_id
JOIN review_plans rp ON rp.item_id = j.item_id
WHERE j.status = 'pending'
ORDER BY j.created_at
`).all();
const processing = db.prepare(`
SELECT j.*, mi.name, mi.series_name, mi.type,
rp.job_type, rp.apple_compat
FROM jobs j
JOIN media_items mi ON mi.id = j.item_id
JOIN review_plans rp ON rp.item_id = j.item_id
WHERE j.status = 'running'
`).all();
const done = db.prepare(`
SELECT j.*, mi.name, mi.series_name, mi.type,
rp.job_type, rp.apple_compat
FROM jobs j
JOIN media_items mi ON mi.id = j.item_id
JOIN review_plans rp ON rp.item_id = j.item_id
WHERE j.status IN ('done', 'error')
ORDER BY j.completed_at DESC
LIMIT 50
`).all();
const noops = db.prepare('SELECT COUNT(*) as count FROM review_plans WHERE is_noop = 1').get() as { count: number };
return c.json({ review, queued, processing, done, noopCount: noops.count, jellyfinUrl });
});
// ─── List ─────────────────────────────────────────────────────────────────────
app.get('/', (c) => {
@@ -448,57 +503,4 @@ app.patch('/series/:seriesKey/language', async (c) => {
return c.json({ updated: items.length });
});
// ─── Pipeline: summary ───────────────────────────────────────────────────────
app.get('/pipeline', (c) => {
const db = getDb();
const review = db.prepare(`
SELECT rp.*, mi.name, mi.series_name, mi.series_jellyfin_id,
mi.season_number, mi.episode_number, mi.type, mi.container,
mi.original_language, mi.orig_lang_source, mi.file_path
FROM review_plans rp
JOIN media_items mi ON mi.id = rp.item_id
WHERE rp.status = 'pending' AND rp.is_noop = 0
ORDER BY
CASE rp.confidence WHEN 'high' THEN 0 ELSE 1 END,
COALESCE(mi.series_name, mi.name),
mi.season_number, mi.episode_number
`).all();
const queued = db.prepare(`
SELECT j.*, mi.name, mi.series_name, mi.type,
rp.job_type, rp.apple_compat
FROM jobs j
JOIN media_items mi ON mi.id = j.item_id
JOIN review_plans rp ON rp.item_id = j.item_id
WHERE j.status = 'pending'
ORDER BY j.created_at
`).all();
const processing = db.prepare(`
SELECT j.*, mi.name, mi.series_name, mi.type,
rp.job_type, rp.apple_compat
FROM jobs j
JOIN media_items mi ON mi.id = j.item_id
JOIN review_plans rp ON rp.item_id = j.item_id
WHERE j.status = 'running'
`).all();
const done = db.prepare(`
SELECT j.*, mi.name, mi.series_name, mi.type,
rp.job_type, rp.apple_compat
FROM jobs j
JOIN media_items mi ON mi.id = j.item_id
JOIN review_plans rp ON rp.item_id = j.item_id
WHERE j.status IN ('done', 'error')
ORDER BY j.completed_at DESC
LIMIT 50
`).all();
const noops = db.prepare('SELECT COUNT(*) as count FROM review_plans WHERE is_noop = 1').get() as { count: number };
return c.json({ review, queued, processing, done, noopCount: noops.count });
});
export default app;