ai starter implementation

This commit is contained in:
Garret Patti
2026-04-12 15:39:48 -04:00
parent 0238dbda7a
commit 732e9134c3
5 changed files with 212 additions and 5 deletions

View File

@@ -6,6 +6,7 @@ import { getAiConfig } from './app-settings'
import { getTags, getCategories, addTagToItem } from './tags'
import { getThumbnailPath } from './thumbnails'
import { findFile } from './media-utils'
import { getLibrary, resolveLibraryRoot } from './libraries'
const BATCH_LIMIT = 50
const REQUEST_TIMEOUT_MS = 30_000
@@ -250,3 +251,59 @@ export async function runAiTagging(library: Library, libraryRoot: string): Promi
console.log(`[ai-tagger] Tagged ${tagged}/${untaggedItems.length} items in library "${library.name}"`)
}
}
/**
* Tag a single item on-demand by itemKey.
* Bypasses the ai_tagged_at check and batch limit — user explicitly requested this.
* Throws descriptive errors so the API route can return appropriate status codes.
*/
export async function tagSingleItem(itemKey: string): Promise<string[]> {
const config = getAiConfig()
if (!config.endpoint || !config.model) {
throw Object.assign(new Error('AI tagging endpoint and model are not configured'), { code: 'NOT_CONFIGURED' })
}
const tags = getTags()
const categories = getCategories()
if (tags.length === 0) {
return []
}
const validTagIds = new Set(tags.map((t) => t.id))
const systemPrompt = buildTagPrompt(tags, categories)
const db = getDb()
const item = db
.prepare('SELECT item_key, item_type, file_path, metadata FROM media_items WHERE item_key = ?')
.get(itemKey) as MediaItemRow | undefined
if (!item) {
throw Object.assign(new Error(`Item not found: ${itemKey}`), { code: 'NOT_FOUND' })
}
const libraryId = itemKey.split(':')[0]
const library = getLibrary(libraryId)
if (!library) {
throw Object.assign(new Error(`Library not found: ${libraryId}`), { code: 'NOT_FOUND' })
}
const libraryRoot = resolveLibraryRoot(library)
const imagePath = resolveItemImage(libraryRoot, item)
if (!imagePath) {
throw Object.assign(new Error('No image available for this item'), { code: 'NO_IMAGE' })
}
const thumbnailPath = await getThumbnailPath(imagePath, libraryId, 'image')
const base64 = fs.readFileSync(thumbnailPath, 'base64')
const suggestedIds = await callVisionApi(config.endpoint, config.model, base64, systemPrompt)
const validIds = suggestedIds.filter((id) => validTagIds.has(id))
for (const tagId of validIds) {
addTagToItem(itemKey, tagId)
}
db.prepare('UPDATE media_items SET ai_tagged_at = ? WHERE item_key = ?').run(Date.now(), itemKey)
return validIds
}