add per-library AI model and prompt customization

- Add library_ai_settings table with migration for per-library overrides
- Extend AiConfig with editable prompt parts for description, tagging,
  extraction, and translation steps; defaults match previous hardcoded values
- Add getEffectiveAiConfig(libraryId) that merges global settings with
  library-level overrides (empty override falls through to global)
- Update all ai-tagger functions to use getEffectiveAiConfig and build
  prompts from configurable parts
- Add GET/PUT /api/ai-settings/library/[id] for per-library overrides
- Update /api/ai-settings GET/PUT to include prompt fields
- Add Prompts section and Library Overrides section to admin UI

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Garret Patti
2026-04-12 20:37:11 -04:00
parent afb9540df2
commit 887cc05901
6 changed files with 588 additions and 30 deletions

View File

@@ -0,0 +1,44 @@
import { NextRequest, NextResponse } from 'next/server'
import { requireAdmin } from '@/lib/auth'
import { getLibraryAiOverrides, setLibraryAiOverrides } from '@/lib/app-settings'
export async function GET(
request: NextRequest,
{ params }: { params: Promise<{ id: string }> }
) {
const auth = await requireAdmin(request)
if (auth instanceof NextResponse) return auth
const { id } = await params
return NextResponse.json(getLibraryAiOverrides(id))
}
export async function PUT(
request: NextRequest,
{ params }: { params: Promise<{ id: string }> }
) {
const auth = await requireAdmin(request)
if (auth instanceof NextResponse) return auth
const { id } = await params
let body: Record<string, unknown>
try {
body = await request.json()
} catch {
return NextResponse.json({ error: 'Invalid JSON body' }, { status: 400 })
}
setLibraryAiOverrides(id, {
modelTagging: typeof body.modelTagging === 'string' ? body.modelTagging : undefined,
modelDescribe: typeof body.modelDescribe === 'string' ? body.modelDescribe : undefined,
modelExtract: typeof body.modelExtract === 'string' ? body.modelExtract : undefined,
modelTranslate: typeof body.modelTranslate === 'string' ? body.modelTranslate : undefined,
promptDescribe: typeof body.promptDescribe === 'string' ? body.promptDescribe : undefined,
promptTagger: typeof body.promptTagger === 'string' ? body.promptTagger : undefined,
promptExtract: typeof body.promptExtract === 'string' ? body.promptExtract : undefined,
promptTranslate: typeof body.promptTranslate === 'string' ? body.promptTranslate : undefined,
})
return NextResponse.json(getLibraryAiOverrides(id))
}

View File

@@ -6,23 +6,40 @@ export async function GET(request: NextRequest) {
const auth = await requireAdmin(request)
if (auth instanceof NextResponse) return auth
const { endpoint, model, modelTagging, modelDescribe, modelExtract, modelTranslate, enabled } = getAiConfig()
const config = getAiConfig()
const preferredLanguage = getPreferredLanguage()
return NextResponse.json({ endpoint, model, modelTagging, modelDescribe, modelExtract, modelTranslate, enabled, preferredLanguage })
return NextResponse.json({ ...config, preferredLanguage })
}
export async function PUT(request: NextRequest) {
const auth = await requireAdmin(request)
if (auth instanceof NextResponse) return auth
let body: { endpoint?: string; model?: string; modelTagging?: string; modelDescribe?: string; modelExtract?: string; modelTranslate?: string; enabled?: boolean; preferredLanguage?: string }
let body: {
endpoint?: string
model?: string
modelTagging?: string
modelDescribe?: string
modelExtract?: string
modelTranslate?: string
enabled?: boolean
preferredLanguage?: string
promptDescribe?: string
promptTagger?: string
promptExtract?: string
promptTranslate?: string
}
try {
body = await request.json()
} catch {
return NextResponse.json({ error: 'Invalid JSON body' }, { status: 400 })
}
const { endpoint, model, enabled, preferredLanguage, modelTagging, modelDescribe, modelExtract, modelTranslate } = body
const {
endpoint, model, enabled, preferredLanguage,
modelTagging, modelDescribe, modelExtract, modelTranslate,
promptDescribe, promptTagger, promptExtract, promptTranslate,
} = body
if (typeof endpoint !== 'string') {
return NextResponse.json({ error: 'endpoint is required' }, { status: 400 })
@@ -42,6 +59,10 @@ export async function PUT(request: NextRequest) {
typeof modelDescribe === 'string' ? modelDescribe : undefined,
typeof modelExtract === 'string' ? modelExtract : undefined,
typeof modelTranslate === 'string' ? modelTranslate : undefined,
typeof promptDescribe === 'string' ? promptDescribe : undefined,
typeof promptTagger === 'string' ? promptTagger : undefined,
typeof promptExtract === 'string' ? promptExtract : undefined,
typeof promptTranslate === 'string' ? promptTranslate : undefined,
)
if (typeof preferredLanguage === 'string' && preferredLanguage.trim()) {