feat(admin): present OCR-only analysis UI, hide vision surfaces
The analysis admin now reflects the OCR backend instead of the vision LLM. Backend response types are unchanged (vision fields ride along, ignored) so this is fully reversible. - Settings → Analysis: keep only the OCR-relevant knobs (workers, job timeout, max image bytes) plus a read-only "Backend: OCR (ocrs)" line; drop the endpoint/model/API-key, sampling, slicing, and prompt fieldsets. - Analysis tab: reword the lede to OCR text extraction; the page-detail modal drops the NSFW/content-warning, scene, and auto-tag sections and shows only OCR text (the per-line kind label renders only when set, since ocrs leaves it blank). - History table: remove the NSFW-only filter and the per-row NSFW tag, and stop sending the `nsfw` query param. - Metrics panel: remove the by-model table (OCR has a single engine); keep the tiles and trend charts. Add component tests asserting the history table omits the NSFW filter and never tags a row, and the metrics panel renders tiles without the by-model table. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,64 @@
|
||||
import { describe, it, expect, vi, afterEach } from 'vitest';
|
||||
import { render, screen, cleanup, waitFor } from '@testing-library/svelte';
|
||||
|
||||
// Mock the admin API so the component's onMount load() is deterministic.
|
||||
const listAnalysisHistory = vi.fn();
|
||||
vi.mock('$lib/api/admin', () => ({
|
||||
listAnalysisHistory: (...args: unknown[]) => listAnalysisHistory(...args)
|
||||
}));
|
||||
|
||||
import AnalysisHistoryTable from './AnalysisHistoryTable.svelte';
|
||||
|
||||
afterEach(() => {
|
||||
cleanup();
|
||||
listAnalysisHistory.mockReset();
|
||||
});
|
||||
|
||||
function row(over: Record<string, unknown> = {}) {
|
||||
return {
|
||||
page_id: 'p1',
|
||||
page_number: 1,
|
||||
chapter_id: 'c1',
|
||||
chapter_number: 5,
|
||||
manga_id: 'm1',
|
||||
manga_title: 'Berserk',
|
||||
status: 'done',
|
||||
is_nsfw: true, // even when the backend reports nsfw, the OCR UI must not surface it
|
||||
model: 'ocrs',
|
||||
error: null,
|
||||
analyzed_at: '2026-01-02T00:00:00Z',
|
||||
duration_ms: 1234,
|
||||
...over
|
||||
};
|
||||
}
|
||||
|
||||
function resolveOnce(rows: ReturnType<typeof row>[]) {
|
||||
listAnalysisHistory.mockResolvedValue({
|
||||
items: rows,
|
||||
page: { total: rows.length }
|
||||
});
|
||||
}
|
||||
|
||||
describe('AnalysisHistoryTable (OCR)', () => {
|
||||
it('does not render the NSFW-only filter', async () => {
|
||||
resolveOnce([row()]);
|
||||
render(AnalysisHistoryTable, { props: { onOpenDetail: () => {} } });
|
||||
await waitFor(() => expect(listAnalysisHistory).toHaveBeenCalled());
|
||||
expect(screen.queryByTestId('analysis-history-nsfw')).toBeNull();
|
||||
});
|
||||
|
||||
it('does not pass an nsfw filter to the history query', async () => {
|
||||
resolveOnce([row()]);
|
||||
render(AnalysisHistoryTable, { props: { onOpenDetail: () => {} } });
|
||||
await waitFor(() => expect(listAnalysisHistory).toHaveBeenCalled());
|
||||
const arg = listAnalysisHistory.mock.calls[0][0] as Record<string, unknown>;
|
||||
expect('nsfw' in arg).toBe(false);
|
||||
});
|
||||
|
||||
it('never shows an NSFW tag on a row, even if the row is flagged', async () => {
|
||||
resolveOnce([row({ is_nsfw: true })]);
|
||||
render(AnalysisHistoryTable, { props: { onOpenDetail: () => {} } });
|
||||
await waitFor(() => expect(screen.getByText(/Berserk/)).toBeTruthy());
|
||||
expect(screen.queryByText(/NSFW/i)).toBeNull();
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user