feat: per-extraction OCR language override

Allow users to specify a Tesseract language string (e.g. jpn+jpn_vert)
on a per-extraction basis, overriding the global OCR language setting.

- Add payload column to ai_jobs table (migration) to carry per-call data
- Thread ocrLanguages payload through enqueueJob → processNextJob → extractItemText
- New GET /api/ai-settings/ocr endpoint (requireAuth) returns { ocrMode, ocrLanguages }
- ImageLightbox fetches OCR settings and shows a language input next to the
  Extract Text button when mode is hybrid or tesseract (hidden for llm-only)
- MixedView fetches OCR settings and passes them down to EntryTile; kebab
  Extract Text on images shows an inline language prompt before dispatching the job

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Garret Patti
2026-04-13 21:55:07 -04:00
parent 96cfb8aae7
commit db2e446ef4
8 changed files with 206 additions and 70 deletions

View File

@@ -0,0 +1,11 @@
import { NextRequest, NextResponse } from 'next/server'
import { requireAuth } from '@/lib/auth'
import { getAiConfig } from '@/lib/app-settings'
export async function GET(request: NextRequest) {
const auth = await requireAuth(request)
if (auth instanceof NextResponse) return auth
const { ocrMode, ocrLanguages } = getAiConfig()
return NextResponse.json({ ocrMode, ocrLanguages })
}

View File

@@ -3,14 +3,14 @@ import { requireLibraryAccess } from '@/lib/auth'
import { enqueueJob } from '@/lib/ai-jobs'
export async function POST(request: NextRequest) {
let body: { itemKey?: string }
let body: { itemKey?: string; ocrLanguages?: string }
try {
body = await request.json()
} catch {
return NextResponse.json({ error: 'Invalid JSON body' }, { status: 400 })
}
const { itemKey } = body
const { itemKey, ocrLanguages } = body
if (!itemKey || typeof itemKey !== 'string') {
return NextResponse.json({ error: 'itemKey is required' }, { status: 400 })
}
@@ -19,6 +19,12 @@ export async function POST(request: NextRequest) {
const auth = await requireLibraryAccess(request, libraryId)
if (auth instanceof NextResponse) return auth
const jobId = enqueueJob(itemKey, 'extract', libraryId)
const jobId = enqueueJob(
itemKey,
'extract',
libraryId,
undefined,
ocrLanguages ? { ocrLanguages } : undefined,
)
return NextResponse.json({ jobId }, { status: 202 })
}