feat(analysis): admin UI — reader context-menu action + dashboard section
Surfaces the scoped re-enqueue and per-page force-analyze in the UI:
- api/admin: reenqueueAnalysis({onlyUnanalyzed, mangaId, chapterId}) and
analyzePage(pageId).
- Reader PageContextMenu gains an admin-only "Queue for analysis" item
(canAnalyze/onAnalyzePage/analyzeState props) wired to the force-analyze
endpoint with inline busy/done/error feedback; reset per page.
- New /admin/analysis dashboard section + nav tab: scope selector
(whole library / one manga / one chapter), id input, and an
"include already-analyzed" toggle (default off), with busy/notice/error.
Tests: vitest for the two clients; Playwright for the context-menu action
(admin vs non-admin) and the admin section (scope + include toggle +
validation). svelte-check + build clean; 262 vitest, 10 new e2e green.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -4,6 +4,7 @@
|
||||
import Tag from '@lucide/svelte/icons/tag';
|
||||
import Download from '@lucide/svelte/icons/download';
|
||||
import Link2 from '@lucide/svelte/icons/link-2';
|
||||
import Sparkles from '@lucide/svelte/icons/sparkles';
|
||||
|
||||
/**
|
||||
* Floating context menu anchored at `(anchor.x, anchor.y)` in
|
||||
@@ -25,7 +26,10 @@
|
||||
onSaveImage,
|
||||
onCopyLink,
|
||||
collectionsCount,
|
||||
tags
|
||||
tags,
|
||||
canAnalyze = false,
|
||||
onAnalyzePage,
|
||||
analyzeState = 'idle'
|
||||
}: {
|
||||
open: boolean;
|
||||
anchor: { x: number; y: number };
|
||||
@@ -41,6 +45,12 @@
|
||||
collectionsCount: number | null;
|
||||
/** May be empty. */
|
||||
tags: string[];
|
||||
/** Admin-only: show the "Queue for analysis" action. */
|
||||
canAnalyze?: boolean;
|
||||
/** Force-enqueue the current page for AI analysis. */
|
||||
onAnalyzePage?: () => void;
|
||||
/** Transient state for the analyze action's label. */
|
||||
analyzeState?: 'idle' | 'busy' | 'done' | 'error';
|
||||
} = $props();
|
||||
|
||||
let menuEl: HTMLDivElement | undefined = $state();
|
||||
@@ -170,6 +180,24 @@
|
||||
<Link2 size={14} aria-hidden="true" />
|
||||
<span>Copy page link</span>
|
||||
</button>
|
||||
{#if canAnalyze}
|
||||
<div class="divider" aria-hidden="true"></div>
|
||||
<button
|
||||
type="button"
|
||||
class="item"
|
||||
onclick={onAnalyzePage}
|
||||
disabled={analyzeState === 'busy'}
|
||||
data-testid="page-context-analyze"
|
||||
>
|
||||
<Sparkles size={14} aria-hidden="true" />
|
||||
<span>
|
||||
{#if analyzeState === 'busy'}Queueing…
|
||||
{:else if analyzeState === 'done'}Queued for analysis ✓
|
||||
{:else if analyzeState === 'error'}Queue failed — retry
|
||||
{:else}Queue for analysis{/if}
|
||||
</span>
|
||||
</button>
|
||||
{/if}
|
||||
<div class="divider" aria-hidden="true"></div>
|
||||
<p class="hint" data-testid="page-context-collections-line">
|
||||
{collectionsLine}
|
||||
|
||||
Reference in New Issue
Block a user