From 17155b68108df5313ffb54024ba48745eb6063ed Mon Sep 17 00:00:00 2001 From: gpatti Date: Mon, 13 Apr 2026 17:03:27 +0000 Subject: [PATCH] revert ce6803b0e0669ecf5312e2626c0b8ac7fc07de90 revert Merge branch 'main' of http://gitea.lan/gpatti/MediaLore --- src/app/api/ai-jobs/route.ts | 63 ---- src/app/api/ai-settings/route.ts | 13 +- src/app/api/ai-tagging/describe-bulk/route.ts | 25 +- src/app/api/ai-tagging/describe/route.ts | 21 +- .../api/ai-tagging/extract-text-bulk/route.ts | 23 +- src/app/api/ai-tagging/extract-text/route.ts | 21 +- src/app/api/ai-tagging/fields/route.ts | 26 +- src/app/api/ai-tagging/route.ts | 21 +- src/app/api/ai-tagging/translate/route.ts | 22 +- src/app/manage/ai-tagging/page.tsx | 304 +-------------- src/components/DoomScrollView.tsx | 5 - src/components/ManageSubNav.tsx | 2 +- src/components/mixed/ImageLightbox.tsx | 146 +++----- src/components/tags/TagSelector.tsx | 5 - src/instrumentation.ts | 3 - src/lib/ai-jobs.ts | 351 ------------------ src/lib/ai-tagger.ts | 151 ++++---- src/lib/app-settings.ts | 12 - src/lib/db.ts | 24 -- 19 files changed, 223 insertions(+), 1015 deletions(-) delete mode 100644 src/app/api/ai-jobs/route.ts diff --git a/src/app/api/ai-jobs/route.ts b/src/app/api/ai-jobs/route.ts deleted file mode 100644 index 79c2810..0000000 --- a/src/app/api/ai-jobs/route.ts +++ /dev/null @@ -1,63 +0,0 @@ -import { NextRequest, NextResponse } from 'next/server' -import { requireAdmin } from '@/lib/auth' -import { getJobQueue, getJobHistory, retryJob, cancelJob, cancelAllQueued, clearJobHistory } from '@/lib/ai-jobs' - -export async function GET(request: NextRequest) { - const auth = await requireAdmin(request) - if (auth instanceof NextResponse) return auth - - const queue = getJobQueue() - const history = getJobHistory(50) - return NextResponse.json({ queue, history }) -} - -export async function POST(request: NextRequest) { - const auth = await requireAdmin(request) - if (auth instanceof NextResponse) return auth - - let body: { action?: string; jobId?: string } - try { - body = await request.json() - } catch { - return NextResponse.json({ error: 'Invalid JSON body' }, { status: 400 }) - } - - const { action, jobId } = body - - switch (action) { - case 'retry': { - if (!jobId || typeof jobId !== 'string') { - return NextResponse.json({ error: 'jobId is required' }, { status: 400 }) - } - const ok = retryJob(jobId) - if (!ok) { - return NextResponse.json({ error: 'Job not found or not in failed state' }, { status: 404 }) - } - return NextResponse.json({ ok: true }) - } - - case 'cancel': { - if (!jobId || typeof jobId !== 'string') { - return NextResponse.json({ error: 'jobId is required' }, { status: 400 }) - } - const ok = cancelJob(jobId) - if (!ok) { - return NextResponse.json({ error: 'Job not found or not in queued state' }, { status: 404 }) - } - return NextResponse.json({ ok: true }) - } - - case 'cancel-all': { - const cancelled = cancelAllQueued() - return NextResponse.json({ cancelled }) - } - - case 'clear-history': { - const cleared = clearJobHistory() - return NextResponse.json({ cleared }) - } - - default: - return NextResponse.json({ error: 'Unknown action' }, { status: 400 }) - } -} diff --git a/src/app/api/ai-settings/route.ts b/src/app/api/ai-settings/route.ts index 219e559..414ddbb 100644 --- a/src/app/api/ai-settings/route.ts +++ b/src/app/api/ai-settings/route.ts @@ -1,6 +1,6 @@ import { NextRequest, NextResponse } from 'next/server' import { requireAdmin } from '@/lib/auth' -import { getAiConfig, updateAiConfig, getPreferredLanguage, setPreferredLanguage, getAiMaxRetries, setAiMaxRetries } from '@/lib/app-settings' +import { getAiConfig, updateAiConfig, getPreferredLanguage, setPreferredLanguage } from '@/lib/app-settings' export async function GET(request: NextRequest) { const auth = await requireAdmin(request) @@ -8,8 +8,7 @@ export async function GET(request: NextRequest) { const config = getAiConfig() const preferredLanguage = getPreferredLanguage() - const maxRetries = getAiMaxRetries() - return NextResponse.json({ ...config, preferredLanguage, maxRetries }) + return NextResponse.json({ ...config, preferredLanguage }) } export async function PUT(request: NextRequest) { @@ -29,7 +28,6 @@ export async function PUT(request: NextRequest) { promptTagger?: string promptExtract?: string promptTranslate?: string - maxRetries?: number } try { body = await request.json() @@ -41,7 +39,6 @@ export async function PUT(request: NextRequest) { endpoint, model, enabled, preferredLanguage, modelTagging, modelDescribe, modelExtract, modelTranslate, promptDescribe, promptTagger, promptExtract, promptTranslate, - maxRetries, } = body if (typeof endpoint !== 'string') { @@ -72,10 +69,6 @@ export async function PUT(request: NextRequest) { setPreferredLanguage(preferredLanguage.trim()) } - if (typeof maxRetries === 'number' && Number.isFinite(maxRetries)) { - setAiMaxRetries(maxRetries) - } - const config = getAiConfig() - return NextResponse.json({ ...config, preferredLanguage: getPreferredLanguage(), maxRetries: getAiMaxRetries() }) + return NextResponse.json({ ...config, preferredLanguage: getPreferredLanguage() }) } diff --git a/src/app/api/ai-tagging/describe-bulk/route.ts b/src/app/api/ai-tagging/describe-bulk/route.ts index 35fc2ba..fb9d05e 100644 --- a/src/app/api/ai-tagging/describe-bulk/route.ts +++ b/src/app/api/ai-tagging/describe-bulk/route.ts @@ -1,10 +1,6 @@ import { NextRequest, NextResponse } from 'next/server' import { requireLibraryAccess } from '@/lib/auth' -import { enqueueBulkJobs } from '@/lib/ai-jobs' - -const IMAGE_EXTENSIONS = new Set(['.jpg', '.jpeg', '.png', '.gif', '.webp', '.bmp', '.tiff', '.tif']) -const VIDEO_EXTENSIONS = new Set(['.mp4', '.mkv', '.avi', '.mov', '.wmv', '.m4v', '.webm', '.flv', '.ts', '.mpg', '.mpeg']) -const MEDIA_EXTENSIONS = new Set([...IMAGE_EXTENSIONS, ...VIDEO_EXTENSIONS]) +import { describeDirectoryItems } from '@/lib/ai-tagger' export async function POST(request: NextRequest) { let body: { libraryId?: string; path?: string } @@ -22,6 +18,21 @@ export async function POST(request: NextRequest) { const auth = await requireLibraryAccess(request, libraryId) if (auth instanceof NextResponse) return auth - const jobIds = enqueueBulkJobs(libraryId, dirPath ?? '', 'describe', 'mixed_file', MEDIA_EXTENSIONS) - return NextResponse.json({ jobIds, queued: jobIds.length }, { status: 202 }) + try { + const processed = await describeDirectoryItems(libraryId, dirPath ?? '') + return NextResponse.json({ processed }) + } catch (err) { + const error = err as Error & { code?: string } + if (error.code === 'NOT_CONFIGURED') { + return NextResponse.json({ error: error.message }, { status: 400 }) + } + if (error.code === 'NOT_FOUND') { + return NextResponse.json({ error: error.message }, { status: 404 }) + } + if (error.code === 'INVALID_TYPE') { + return NextResponse.json({ error: error.message }, { status: 400 }) + } + console.error('[ai-tagging/describe-bulk] Error:', error) + return NextResponse.json({ error: 'Failed to generate descriptions' }, { status: 502 }) + } } diff --git a/src/app/api/ai-tagging/describe/route.ts b/src/app/api/ai-tagging/describe/route.ts index ba6e508..5121c65 100644 --- a/src/app/api/ai-tagging/describe/route.ts +++ b/src/app/api/ai-tagging/describe/route.ts @@ -1,6 +1,6 @@ import { NextRequest, NextResponse } from 'next/server' import { requireLibraryAccess } from '@/lib/auth' -import { enqueueJob } from '@/lib/ai-jobs' +import { generateItemDescription } from '@/lib/ai-tagger' export async function POST(request: NextRequest) { let body: { itemKey?: string } @@ -19,6 +19,21 @@ export async function POST(request: NextRequest) { const auth = await requireLibraryAccess(request, libraryId) if (auth instanceof NextResponse) return auth - const jobId = enqueueJob(itemKey, 'describe', libraryId) - return NextResponse.json({ jobId }, { status: 202 }) + try { + const description = await generateItemDescription(itemKey) + return NextResponse.json({ description }) + } catch (err) { + const error = err as Error & { code?: string } + if (error.code === 'NOT_CONFIGURED') { + return NextResponse.json({ error: error.message }, { status: 400 }) + } + if (error.code === 'NOT_FOUND') { + return NextResponse.json({ error: error.message }, { status: 404 }) + } + if (error.code === 'NO_IMAGE') { + return NextResponse.json({ error: error.message }, { status: 404 }) + } + console.error('[ai-tagging/describe] Error:', error) + return NextResponse.json({ error: 'Failed to generate description' }, { status: 502 }) + } } diff --git a/src/app/api/ai-tagging/extract-text-bulk/route.ts b/src/app/api/ai-tagging/extract-text-bulk/route.ts index 3004abd..196ca19 100644 --- a/src/app/api/ai-tagging/extract-text-bulk/route.ts +++ b/src/app/api/ai-tagging/extract-text-bulk/route.ts @@ -1,8 +1,6 @@ import { NextRequest, NextResponse } from 'next/server' import { requireLibraryAccess } from '@/lib/auth' -import { enqueueBulkJobs } from '@/lib/ai-jobs' - -const IMAGE_EXTENSIONS = new Set(['.jpg', '.jpeg', '.png', '.gif', '.webp', '.bmp', '.tiff', '.tif']) +import { extractDirectoryText } from '@/lib/ai-tagger' export async function POST(request: NextRequest) { let body: { libraryId?: string; path?: string } @@ -20,6 +18,21 @@ export async function POST(request: NextRequest) { const auth = await requireLibraryAccess(request, libraryId) if (auth instanceof NextResponse) return auth - const jobIds = enqueueBulkJobs(libraryId, dirPath ?? '', 'extract', 'mixed_file', IMAGE_EXTENSIONS) - return NextResponse.json({ jobIds, queued: jobIds.length }, { status: 202 }) + try { + const processed = await extractDirectoryText(libraryId, dirPath ?? '') + return NextResponse.json({ processed }) + } catch (err) { + const error = err as Error & { code?: string } + if (error.code === 'NOT_CONFIGURED') { + return NextResponse.json({ error: error.message }, { status: 400 }) + } + if (error.code === 'NOT_FOUND') { + return NextResponse.json({ error: error.message }, { status: 404 }) + } + if (error.code === 'INVALID_TYPE') { + return NextResponse.json({ error: error.message }, { status: 400 }) + } + console.error('[ai-tagging/extract-text-bulk] Error:', error) + return NextResponse.json({ error: 'Failed to extract text' }, { status: 502 }) + } } diff --git a/src/app/api/ai-tagging/extract-text/route.ts b/src/app/api/ai-tagging/extract-text/route.ts index 5b6ad22..58de630 100644 --- a/src/app/api/ai-tagging/extract-text/route.ts +++ b/src/app/api/ai-tagging/extract-text/route.ts @@ -1,6 +1,6 @@ import { NextRequest, NextResponse } from 'next/server' import { requireLibraryAccess } from '@/lib/auth' -import { enqueueJob } from '@/lib/ai-jobs' +import { extractItemText } from '@/lib/ai-tagger' export async function POST(request: NextRequest) { let body: { itemKey?: string } @@ -19,6 +19,21 @@ export async function POST(request: NextRequest) { const auth = await requireLibraryAccess(request, libraryId) if (auth instanceof NextResponse) return auth - const jobId = enqueueJob(itemKey, 'extract', libraryId) - return NextResponse.json({ jobId }, { status: 202 }) + try { + const result = await extractItemText(itemKey) + return NextResponse.json(result) + } catch (err) { + const error = err as Error & { code?: string } + if (error.code === 'NOT_CONFIGURED') { + return NextResponse.json({ error: error.message }, { status: 400 }) + } + if (error.code === 'NOT_FOUND') { + return NextResponse.json({ error: error.message }, { status: 404 }) + } + if (error.code === 'NO_IMAGE' || error.code === 'INVALID_TYPE') { + return NextResponse.json({ error: error.message }, { status: 400 }) + } + console.error('[ai-tagging/extract-text] Error:', error) + return NextResponse.json({ error: 'Failed to extract text' }, { status: 502 }) + } } diff --git a/src/app/api/ai-tagging/fields/route.ts b/src/app/api/ai-tagging/fields/route.ts index c4155b1..ee647aa 100644 --- a/src/app/api/ai-tagging/fields/route.ts +++ b/src/app/api/ai-tagging/fields/route.ts @@ -1,6 +1,6 @@ import { NextRequest, NextResponse } from 'next/server' import { requireLibraryAccess } from '@/lib/auth' -import { getAiFields, updateExtractedText } from '@/lib/ai-tagger' +import { getAiFields } from '@/lib/ai-tagger' export async function GET(request: NextRequest) { const { searchParams } = request.nextUrl @@ -17,27 +17,3 @@ export async function GET(request: NextRequest) { const fields = getAiFields(itemKey) return NextResponse.json(fields) } - -export async function PATCH(request: NextRequest) { - let body: { itemKey?: string; extractedText?: string } - try { - body = await request.json() - } catch { - return NextResponse.json({ error: 'Invalid JSON body' }, { status: 400 }) - } - - const { itemKey, extractedText } = body - if (!itemKey || typeof itemKey !== 'string') { - return NextResponse.json({ error: 'itemKey is required' }, { status: 400 }) - } - if (typeof extractedText !== 'string') { - return NextResponse.json({ error: 'extractedText is required' }, { status: 400 }) - } - - const libraryId = itemKey.split(':')[0] - const auth = await requireLibraryAccess(request, libraryId) - if (auth instanceof NextResponse) return auth - - updateExtractedText(itemKey, extractedText) - return NextResponse.json({ ok: true }) -} diff --git a/src/app/api/ai-tagging/route.ts b/src/app/api/ai-tagging/route.ts index 428b701..f248a14 100644 --- a/src/app/api/ai-tagging/route.ts +++ b/src/app/api/ai-tagging/route.ts @@ -1,6 +1,6 @@ import { NextRequest, NextResponse } from 'next/server' import { requireLibraryAccess } from '@/lib/auth' -import { enqueueJob } from '@/lib/ai-jobs' +import { tagSingleItem } from '@/lib/ai-tagger' export async function POST(request: NextRequest) { let body: { itemKey?: string } @@ -19,6 +19,21 @@ export async function POST(request: NextRequest) { const auth = await requireLibraryAccess(request, libraryId) if (auth instanceof NextResponse) return auth - const jobId = enqueueJob(itemKey, 'tag', libraryId) - return NextResponse.json({ jobId }, { status: 202 }) + try { + const tagIds = await tagSingleItem(itemKey) + return NextResponse.json({ tagIds }) + } catch (err) { + const error = err as Error & { code?: string } + if (error.code === 'NOT_CONFIGURED') { + return NextResponse.json({ error: error.message }, { status: 400 }) + } + if (error.code === 'NOT_FOUND') { + return NextResponse.json({ error: error.message }, { status: 404 }) + } + if (error.code === 'NO_IMAGE') { + return NextResponse.json({ error: error.message }, { status: 404 }) + } + console.error('[ai-tagging] Error tagging item:', error) + return NextResponse.json({ error: 'AI tagging failed' }, { status: 502 }) + } } diff --git a/src/app/api/ai-tagging/translate/route.ts b/src/app/api/ai-tagging/translate/route.ts index 27ee8dd..740d9fb 100644 --- a/src/app/api/ai-tagging/translate/route.ts +++ b/src/app/api/ai-tagging/translate/route.ts @@ -1,16 +1,16 @@ import { NextRequest, NextResponse } from 'next/server' import { requireLibraryAccess } from '@/lib/auth' -import { enqueueJob } from '@/lib/ai-jobs' +import { translateItemText } from '@/lib/ai-tagger' export async function POST(request: NextRequest) { - let body: { itemKey?: string; sourceLanguage?: string } + let body: { itemKey?: string } try { body = await request.json() } catch { return NextResponse.json({ error: 'Invalid JSON body' }, { status: 400 }) } - const { itemKey, sourceLanguage } = body + const { itemKey } = body if (!itemKey || typeof itemKey !== 'string') { return NextResponse.json({ error: 'itemKey is required' }, { status: 400 }) } @@ -19,6 +19,18 @@ export async function POST(request: NextRequest) { const auth = await requireLibraryAccess(request, libraryId) if (auth instanceof NextResponse) return auth - const jobId = enqueueJob(itemKey, 'translate', libraryId, sourceLanguage || undefined) - return NextResponse.json({ jobId }, { status: 202 }) + try { + const translatedText = await translateItemText(itemKey) + return NextResponse.json({ translatedText }) + } catch (err) { + const error = err as Error & { code?: string } + if (error.code === 'NOT_CONFIGURED') { + return NextResponse.json({ error: error.message }, { status: 400 }) + } + if (error.code === 'NOT_FOUND') { + return NextResponse.json({ error: error.message }, { status: 404 }) + } + console.error('[ai-tagging/translate] Error:', error) + return NextResponse.json({ error: 'Failed to translate text' }, { status: 502 }) + } } diff --git a/src/app/manage/ai-tagging/page.tsx b/src/app/manage/ai-tagging/page.tsx index e45b65c..5807cd7 100644 --- a/src/app/manage/ai-tagging/page.tsx +++ b/src/app/manage/ai-tagging/page.tsx @@ -1,6 +1,6 @@ 'use client' -import { useEffect, useState, useCallback, useRef } from 'react' +import { useEffect, useState, useCallback } from 'react' interface AiSettings { endpoint: string @@ -15,22 +15,6 @@ interface AiSettings { promptTagger: string promptExtract: string promptTranslate: string - maxRetries: number -} - -interface AiJob { - id: string - itemKey: string - libraryId: string - jobType: string - status: string - error: string | null - attempt: number - maxRetries: number - createdAt: number - startedAt: number | null - completedAt: number | null - itemTitle: string | null } interface Library { @@ -49,24 +33,11 @@ interface LibraryOverride { promptTranslate: string } -function formatElapsed(startedAt: number): string { - const seconds = Math.floor((Date.now() - startedAt) / 1000) - if (seconds < 60) return `${seconds}s` - const m = Math.floor(seconds / 60) - const s = seconds % 60 - return `${m}m ${s}s` -} - -function formatDate(ts: number): string { - return new Date(ts).toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' }) -} - export default function AiTaggingPage() { const [settings, setSettings] = useState({ endpoint: '', model: '', modelTagging: '', modelDescribe: '', modelExtract: '', modelTranslate: '', enabled: false, preferredLanguage: 'English', promptDescribe: '', promptTagger: '', promptExtract: '', promptTranslate: '', - maxRetries: 3, }) const [loading, setLoading] = useState(true) const [saving, setSaving] = useState(false) @@ -83,11 +54,6 @@ export default function AiTaggingPage() { const [librarySaving, setLibrarySaving] = useState>({}) const [librarySaveResult, setLibrarySaveResult] = useState>({}) - // Job queue state - const [jobQueue, setJobQueue] = useState([]) - const [jobHistory, setJobHistory] = useState([]) - const [historyExpanded, setHistoryExpanded] = useState(false) - const jobPollRef = useRef | null>(null) const fetchSettings = useCallback(async () => { try { const [settingsRes, librariesRes] = await Promise.all([ @@ -113,77 +79,6 @@ export default function AiTaggingPage() { fetchSettings() }, [fetchSettings]) - // ─── Job queue polling ─────────────────────────────────────────────────────── - - const fetchJobs = useCallback(async () => { - try { - const res = await fetch('/api/ai-jobs') - if (res.ok) { - const data: { queue: AiJob[]; history: AiJob[] } = await res.json() - setJobQueue(data.queue) - setJobHistory(data.history) - } - } catch { - // ignore - } - }, []) - - useEffect(() => { - fetchJobs() - }, [fetchJobs]) - - // Poll every 2s while there are active jobs - useEffect(() => { - const hasActive = jobQueue.length > 0 - if (hasActive) { - jobPollRef.current = setInterval(fetchJobs, 2000) - } else { - if (jobPollRef.current) { - clearInterval(jobPollRef.current) - jobPollRef.current = null - } - } - return () => { - if (jobPollRef.current) clearInterval(jobPollRef.current) - } - }, [jobQueue.length, fetchJobs]) - - const handleRetryJob = async (jobId: string) => { - await fetch('/api/ai-jobs', { - method: 'POST', - headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify({ action: 'retry', jobId }), - }) - fetchJobs() - } - - const handleCancelJob = async (jobId: string) => { - await fetch('/api/ai-jobs', { - method: 'POST', - headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify({ action: 'cancel', jobId }), - }) - fetchJobs() - } - - const handleCancelAll = async () => { - await fetch('/api/ai-jobs', { - method: 'POST', - headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify({ action: 'cancel-all' }), - }) - fetchJobs() - } - - const handleClearHistory = async () => { - await fetch('/api/ai-jobs', { - method: 'POST', - headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify({ action: 'clear-history' }), - }) - fetchJobs() - } - const fetchLibraryOverrides = useCallback(async (libraryId: string) => { try { const res = await fetch(`/api/ai-settings/library/${libraryId}`) @@ -306,182 +201,12 @@ export default function AiTaggingPage() { return (

- AI Integrations + AI Tagging

- Manage AI-powered tagging, descriptions, and text extraction. + Automatically tag media using a vision-capable LLM on your network.

- {/* ─── Job Queue ─────────────────────────────────────────────────────── */} -
- {(() => { - const running = jobQueue.filter((j) => j.status === 'running') - const queued = jobQueue.filter((j) => j.status === 'queued') - if (running.length === 0 && queued.length === 0) { - return ( -

- No active jobs. -

- ) - } - return ( -
-
-

- {running.length > 0 && {running.length} running} - {running.length > 0 && queued.length > 0 && ', '} - {queued.length > 0 && {queued.length} queued} -

- {queued.length > 0 && ( - - )} -
-
- {running.map((job) => ( -
- - Running - - - {job.itemTitle || job.itemKey} - - - {job.jobType} - - {job.startedAt && ( - - {formatElapsed(job.startedAt)} - - )} -
- ))} - {queued.map((job) => ( -
- - Queued - - - {job.itemTitle || job.itemKey} - - - {job.jobType} - - -
- ))} -
-
- ) - })()} -
- - {/* ─── Job History ───────────────────────────────────────────────────── */} -
- {jobHistory.length === 0 ? ( -

- No recent jobs. -

- ) : ( -
- - {historyExpanded && ( - <> -
- {jobHistory.map((job) => ( -
- - {job.status === 'completed' ? 'Done' : 'Failed'} - -
- - {job.itemTitle || job.itemKey} - - {job.status === 'failed' && job.error && ( - - {job.error} - - )} -
- - {job.jobType} - - - {job.completedAt ? formatDate(job.completedAt) : ''} - - {job.status === 'failed' && ( - - )} -
- ))} -
-
- -
- - )} -
- )} -
-
{loading ? ( @@ -640,29 +365,6 @@ export default function AiTaggingPage() {

- - - setSettings((s) => ({ ...s, maxRetries: Math.max(0, Math.min(10, parseInt(e.target.value) || 0)) })) - } - className="w-24 rounded-lg px-3 py-2 text-sm outline-none focus:ring-2" - style={{ - backgroundColor: 'var(--background)', - border: '1px solid var(--border)', - color: 'var(--text-primary)', - }} - onFocus={(e) => ((e.currentTarget as HTMLElement).style.borderColor = 'var(--accent)')} - onBlur={(e) => ((e.currentTarget as HTMLElement).style.borderColor = 'var(--border)')} - /> -

- Number of times to automatically retry a failed AI job before marking it as failed (0–10). -

-
- {saveError && (

({})) throw new Error((data as { error?: string }).error ?? 'Extraction failed') } - if (res.status === 202) { - setExtractError('Queued — check AI Integrations for progress') - setTimeout(() => setExtractError(null), 4000) - return - } const result = await res.json() setExtractedText(result.extractedText || null) setTranslatedText(result.translatedText || null) diff --git a/src/components/ManageSubNav.tsx b/src/components/ManageSubNav.tsx index 0195c31..a57531f 100644 --- a/src/components/ManageSubNav.tsx +++ b/src/components/ManageSubNav.tsx @@ -8,7 +8,7 @@ const TABS = [ { href: '/manage/tags', label: 'Tags' }, { href: '/manage/users', label: 'Users' }, { href: '/manage/scanning', label: 'Scanning' }, - { href: '/manage/ai-tagging', label: 'AI Integrations' }, + { href: '/manage/ai-tagging', label: 'AI Tagging' }, ] export default function ManageSubNav() { diff --git a/src/components/mixed/ImageLightbox.tsx b/src/components/mixed/ImageLightbox.tsx index 92e4072..02e600d 100644 --- a/src/components/mixed/ImageLightbox.tsx +++ b/src/components/mixed/ImageLightbox.tsx @@ -27,9 +27,6 @@ export default function ImageLightbox({ url, name, onClose, onPrev, onNext, item const [extracting, setExtracting] = useState(false) const [extractError, setExtractError] = useState(null) const [retranslating, setRetranslating] = useState(false) - const [editedExtractedText, setEditedExtractedText] = useState('') - const [savingText, setSavingText] = useState(false) - const [sourceLanguage, setSourceLanguage] = useState('') // Text overlay state const [showTextOverlay, setShowTextOverlay] = useState(false) @@ -48,7 +45,6 @@ export default function ImageLightbox({ url, name, onClose, onPrev, onNext, item .then((r) => r.json()) .then((data: { extractedText: string | null; extractedTextTranslated: string | null }) => { setExtractedText(data.extractedText) - setEditedExtractedText(data.extractedText ?? '') setTranslatedText(data.extractedTextTranslated) }) .catch(() => {}) @@ -183,14 +179,14 @@ export default function ImageLightbox({ url, name, onClose, onPrev, onNext, item

{showTags ? ( -
+
{/* Image */} -
+
{/* eslint-disable-next-line @next/next/no-img-element */} {name} e.stopPropagation()} /> {onPrev && ( @@ -269,14 +265,8 @@ export default function ImageLightbox({ url, name, onClose, onPrev, onNext, item const data = await res.json().catch(() => ({})) throw new Error((data as { error?: string }).error ?? 'Failed to extract text') } - if (res.status === 202) { - setExtractError('Queued — check AI Integrations for progress') - setTimeout(() => setExtractError(null), 4000) - return - } const result = await res.json() setExtractedText(result.extractedText || null) - setEditedExtractedText(result.extractedText || '') setTranslatedText(result.translatedText || null) } catch (err) { setExtractError(err instanceof Error ? err.message : 'Failed to extract text') @@ -312,41 +302,12 @@ export default function ImageLightbox({ url, name, onClose, onPrev, onNext, item

Extracted Text

-