@happyvertical/smrt-images 0.30.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (75) hide show
  1. package/AGENTS.md +48 -0
  2. package/CLAUDE.md +1 -0
  3. package/LICENSE +7 -0
  4. package/README.md +92 -0
  5. package/dist/__smrt-register__.d.ts +2 -0
  6. package/dist/__smrt-register__.d.ts.map +1 -0
  7. package/dist/categorizer.d.ts +26 -0
  8. package/dist/categorizer.d.ts.map +1 -0
  9. package/dist/deriver.d.ts +33 -0
  10. package/dist/deriver.d.ts.map +1 -0
  11. package/dist/editor.d.ts +72 -0
  12. package/dist/editor.d.ts.map +1 -0
  13. package/dist/image.d.ts +53 -0
  14. package/dist/image.d.ts.map +1 -0
  15. package/dist/images.d.ts +80 -0
  16. package/dist/images.d.ts.map +1 -0
  17. package/dist/index.d.ts +12 -0
  18. package/dist/index.d.ts.map +1 -0
  19. package/dist/index.js +839 -0
  20. package/dist/index.js.map +1 -0
  21. package/dist/manifest.json +1179 -0
  22. package/dist/media-bundle-persistence.d.ts +15 -0
  23. package/dist/media-bundle-persistence.d.ts.map +1 -0
  24. package/dist/metadata.d.ts +19 -0
  25. package/dist/metadata.d.ts.map +1 -0
  26. package/dist/playground.d.ts +2 -0
  27. package/dist/playground.d.ts.map +1 -0
  28. package/dist/playground.js +140 -0
  29. package/dist/playground.js.map +1 -0
  30. package/dist/prompts.d.ts +8 -0
  31. package/dist/prompts.d.ts.map +1 -0
  32. package/dist/search.d.ts +42 -0
  33. package/dist/search.d.ts.map +1 -0
  34. package/dist/smrt-knowledge.json +561 -0
  35. package/dist/svelte/components/AssetsGallery.svelte +436 -0
  36. package/dist/svelte/components/AssetsGallery.svelte.d.ts +11 -0
  37. package/dist/svelte/components/AssetsGallery.svelte.d.ts.map +1 -0
  38. package/dist/svelte/components/ImageEditor.svelte +485 -0
  39. package/dist/svelte/components/ImageEditor.svelte.d.ts +12 -0
  40. package/dist/svelte/components/ImageEditor.svelte.d.ts.map +1 -0
  41. package/dist/svelte/components/ImageUploader.svelte +922 -0
  42. package/dist/svelte/components/ImageUploader.svelte.d.ts +15 -0
  43. package/dist/svelte/components/ImageUploader.svelte.d.ts.map +1 -0
  44. package/dist/svelte/i18n.d.ts +42 -0
  45. package/dist/svelte/i18n.d.ts.map +1 -0
  46. package/dist/svelte/i18n.js +46 -0
  47. package/dist/svelte/image-clients.d.ts +45 -0
  48. package/dist/svelte/image-clients.d.ts.map +1 -0
  49. package/dist/svelte/image-clients.js +1 -0
  50. package/dist/svelte/index.d.ts +14 -0
  51. package/dist/svelte/index.d.ts.map +1 -0
  52. package/dist/svelte/index.js +21 -0
  53. package/dist/svelte/playground.d.ts +74 -0
  54. package/dist/svelte/playground.d.ts.map +1 -0
  55. package/dist/svelte/playground.js +105 -0
  56. package/dist/svelte/routes/ImageStudioRoute.svelte +194 -0
  57. package/dist/svelte/routes/ImageStudioRoute.svelte.d.ts +7 -0
  58. package/dist/svelte/routes/ImageStudioRoute.svelte.d.ts.map +1 -0
  59. package/dist/svelte/routes/index.d.ts +2 -0
  60. package/dist/svelte/routes/index.d.ts.map +1 -0
  61. package/dist/svelte/routes/index.js +1 -0
  62. package/dist/svelte/routes/shared.d.ts +25 -0
  63. package/dist/svelte/routes/shared.d.ts.map +1 -0
  64. package/dist/svelte/routes/shared.js +31 -0
  65. package/dist/types.d.ts +51 -0
  66. package/dist/types.d.ts.map +1 -0
  67. package/dist/types.js +2 -0
  68. package/dist/types.js.map +1 -0
  69. package/dist/ui.d.ts +10 -0
  70. package/dist/ui.d.ts.map +1 -0
  71. package/dist/ui.js +42 -0
  72. package/dist/ui.js.map +1 -0
  73. package/dist/upstream.d.ts +65 -0
  74. package/dist/upstream.d.ts.map +1 -0
  75. package/package.json +95 -0
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sources":["../src/__smrt-register__.ts","../src/prompts.ts","../src/categorizer.ts","../src/deriver.ts","../src/editor.ts","../src/image.ts","../src/images.ts","../src/metadata.ts","../src/search.ts","../src/upstream.ts"],"sourcesContent":["/**\n * Self-registers this package's build-time manifest before any @smrt() decorator\n * in the package fires. Fixes issue #1132: in consumer runtimes (tsx, SvelteKit\n * SSR, plain `vite dev`) the decorator's synchronous manifest lookup previously\n * missed because no step populated the global manifest cache — classes got\n * registered with zero fields and `save()` / `toJSON()` silently dropped every\n * declared property.\n *\n * Import this module as the first statement in `src/index.ts` so its top-level\n * side effect runs ahead of any class module's @smrt() decorator.\n *\n * Silent no-op in dev/test, where the vitest plugin already populates manifests\n * via a different path. Only needs to succeed in the published dist output.\n *\n * @see https://github.com/happyvertical/smrt/issues/1132\n */\nimport { ObjectRegistry } from '@happyvertical/smrt-core';\n\n// `new URL('./manifest.json', import.meta.url)` resolves at runtime to the\n// manifest sitting next to this module's compiled output. Vite warns at build\n// time that it cannot pre-resolve the URL; that is the intended behavior —\n// the URL must resolve to dist/manifest.json at runtime, not be inlined.\nObjectRegistry.registerPackageManifest(\n new URL('./manifest.json', import.meta.url),\n);\n","/**\n * Prompt registrations for the @happyvertical/smrt-images package.\n *\n * Prompts are registered at module-load time via `definePrompt()` so that\n * tenant-aware overrides can be applied at call time via `resolvePrompt()`.\n *\n * Mirrors the pattern used by `@happyvertical/smrt-profiles` (see\n * `prompts.ts`) and `@happyvertical/smrt-properties` (see `prompts.ts`).\n */\n\nimport {\n definePrompt,\n type ResolvedPromptAI,\n} from '@happyvertical/smrt-prompts';\n\n// Alt text generation only uses non-PII metadata describing the image\n// itself (name and description). Source URIs, internal foreign-key\n// fields (e.g. sourceAssetId), and the extensible `metadata` blob are\n// intentionally NOT passed to the AI provider — source URIs may embed\n// signed/private bucket paths, and metadata may contain EXIF GPS data\n// or tenant-private configuration. If a downstream tenant needs richer\n// context they can override the template via PromptOverride.\nexport const smrtImagesGenerateAltTextPrompt = definePrompt({\n key: 'smrtImages.image.generateAltText',\n template: `Generate concise accessibility alt text for this image.\nConsider: subject matter, key visual elements, context.\nKeep it under 125 characters for screen reader compatibility.\n\nImage name: {imageName}\nImage description: {imageDescription}\n\nReturn only the alt text, with no commentary or surrounding quotation marks.`,\n editable: {\n template: true,\n profile: true,\n model: true,\n params: true,\n },\n});\n\nexport function promptMessageOptions(ai: ResolvedPromptAI) {\n return {\n ...(ai.params || {}),\n ...(ai.model ? { model: ai.model } : {}),\n ...(typeof ai.temperature === 'number'\n ? { temperature: ai.temperature }\n : {}),\n ...(typeof ai.maxTokens === 'number' ? { maxTokens: ai.maxTokens } : {}),\n };\n}\n","/**\n * ImageCategorizer - AI-powered image categorization\n *\n * Uses @happyvertical/ai to analyze image content and suggest\n * tags, descriptions, and subject classifications.\n */\n\nimport type { AIClientOptions } from '@happyvertical/ai';\nimport type { AssetCollection } from '@happyvertical/smrt-assets';\nimport type { Image } from './image';\nimport type { CategoryResult } from './types';\n\nexport class ImageCategorizer {\n constructor(private readonly options: { ai: AIClientOptions }) {}\n\n /**\n * Categorize an image using AI vision analysis\n *\n * @param image - The Image instance to categorize\n * @param buffer - Optional raw image data for vision analysis\n * @returns Categorization results with tags, description, and subjects\n */\n async categorize(image: Image, buffer?: Buffer): Promise<CategoryResult> {\n const { getAI } = await import('@happyvertical/ai');\n const ai = await getAI(this.options.ai);\n\n // TODO: When AI vision API is available, pass buffer for visual analysis\n void buffer;\n\n const prompt = `Analyze this image and provide categorization.\nImage name: ${image.name}\nImage description: ${image.description}\nMIME type: ${image.mimeType}\nDimensions: ${image.width}x${image.height}\n\nRespond in JSON format:\n{\n \"tags\": [\"tag1\", \"tag2\", ...],\n \"description\": \"Brief description of the image content\",\n \"confidence\": 0.0-1.0,\n \"subjects\": [\"subject1\", \"subject2\", ...]\n}`;\n\n const response = await ai.chat([{ role: 'user', content: prompt }]);\n const text = response.content;\n\n try {\n const jsonMatch = text.match(/\\{[\\s\\S]*\\}/);\n if (jsonMatch) {\n return JSON.parse(jsonMatch[0]) as CategoryResult;\n }\n } catch {\n // Fall through to default\n }\n\n return {\n tags: [],\n description: image.description || image.name,\n confidence: 0,\n subjects: [],\n };\n }\n\n /**\n * Run categorization and apply results to the image\n *\n * @param image - The Image to categorize and update\n * @param assetCollection - AssetCollection for tag management\n */\n async autoTag(image: Image, assetCollection: AssetCollection): Promise<void> {\n const result = await this.categorize(image);\n\n if (result.description && !image.description) {\n image.description = result.description;\n }\n\n if (!image.alt && result.description) {\n image.alt = result.description.slice(0, 125);\n }\n\n await image.save();\n\n // Add tags via the asset collection\n for (const tag of result.tags) {\n await assetCollection.addTag(image.id!, tag);\n }\n }\n}\n","/**\n * ImageDeriver - Creates new images from source images + AI prompts\n *\n * Combines multiple source images with creative prompts to generate\n * new derivative images, tracking provenance via AssetAssociation.\n */\n\nimport type { AIClientOptions } from '@happyvertical/ai';\nimport type {\n AssetAssociationCollection,\n AssetStore,\n} from '@happyvertical/smrt-assets';\nimport type { Image } from './image';\nimport type { ImageCollection } from './images';\nimport type { DeriveOptions } from './types';\n\nexport class ImageDeriver {\n constructor(\n private readonly store: AssetStore,\n private readonly collection: ImageCollection,\n private readonly options: { ai: AIClientOptions },\n ) {}\n\n /**\n * Derive new images from source images and a creative prompt\n *\n * @param sources - One or more source images\n * @param prompt - Creative instructions for generation\n * @param deriveOptions - Generation options (count, size, style)\n * @returns Array of newly created derivative Images\n */\n async derive(\n sources: Image[],\n prompt: string,\n deriveOptions: DeriveOptions = {},\n ): Promise<Image[]> {\n if (sources.length === 0) {\n throw new Error('At least one source image is required');\n }\n\n const { getAI } = await import('@happyvertical/ai');\n const ai = await getAI(this.options.ai);\n\n const count = deriveOptions.count ?? 1;\n const results: Image[] = [];\n\n const fullPrompt = [\n prompt,\n deriveOptions.style ? `Style: ${deriveOptions.style}` : '',\n deriveOptions.size ? `Output size: ${deriveOptions.size}` : '',\n `Source images: ${sources.map((s) => s.name).join(', ')}`,\n ]\n .filter(Boolean)\n .join('\\n');\n\n for (let i = 0; i < count; i++) {\n const response = await ai.generateImage(fullPrompt, {\n size: deriveOptions.size,\n });\n\n const imageData = response.images[0]?.data;\n if (!imageData || !(imageData instanceof Buffer)) {\n throw new Error('AI did not return image data as Buffer');\n }\n const result = imageData;\n\n const derived = (await this.collection.create({\n name: `derived-${sources[0].name}-${i + 1}`,\n mimeType: 'image/png',\n sourceUri: '',\n sourceAssetId: sources[0].id,\n typeSlug: 'image',\n description: `Derived: ${prompt}`,\n })) as Image;\n\n const sourceUri = await this.store.storeFile(derived, result, {\n mimeType: 'image/png',\n typeSlug: 'image',\n });\n derived.sourceUri = sourceUri;\n await derived.save();\n\n results.push(derived);\n }\n\n return results;\n }\n\n /**\n * Derive images and link all sources via AssetAssociation\n *\n * @param sources - Source images\n * @param prompt - Creative instructions\n * @param associations - AssetAssociationCollection for linking\n * @param deriveOptions - Generation options\n * @returns Array of newly created derivative Images\n */\n async deriveWithAssociations(\n sources: Image[],\n prompt: string,\n associations: AssetAssociationCollection,\n deriveOptions: DeriveOptions = {},\n ): Promise<Image[]> {\n const results = await this.derive(sources, prompt, deriveOptions);\n\n // Link all source images to each derived image\n for (const derived of results) {\n for (const source of sources) {\n await associations.attach('Image', derived.id!, source.id!, {\n role: 'derivation-source',\n });\n }\n }\n\n return results;\n }\n}\n","/**\n * ImageEditor - Standard and AI-powered image editing\n *\n * Creates derivative assets for each edit operation, preserving\n * the original image and linking via sourceAssetId (the derivation\n * pointer; renamed from `parentId` in R3-D).\n *\n * Standard operations use @happyvertical/images (file-based API).\n * AI operations use @happyvertical/ai.\n */\n\nimport { randomUUID } from 'node:crypto';\nimport { readFile, unlink, writeFile } from 'node:fs/promises';\nimport { tmpdir } from 'node:os';\nimport { join } from 'node:path';\nimport type { AIClientOptions } from '@happyvertical/ai';\nimport type { ImageFormat } from '@happyvertical/images';\nimport type { AssetStore } from '@happyvertical/smrt-assets';\nimport type { Image } from './image';\nimport type { ImageCollection } from './images';\n\n/**\n * Output formats `convert()` accepts. Mirrors the `ImageFormat` union from\n * `@happyvertical/images`. Used as a runtime allowlist so a caller-supplied\n * `format` string can never escape this set.\n */\nconst ALLOWED_CONVERT_FORMATS = new Set<ImageFormat>([\n 'jpeg',\n 'png',\n 'webp',\n 'avif',\n 'gif',\n 'tiff',\n]);\n\nexport class ImageEditor {\n constructor(\n private readonly store: AssetStore,\n private readonly collection: ImageCollection,\n private readonly options: { ai?: AIClientOptions } = {},\n ) {}\n\n /**\n * Resize an image to the specified dimensions\n *\n * @param image - Source image\n * @param width - Target width\n * @param height - Target height\n * @returns New derivative Image\n */\n async resize(image: Image, width: number, height: number): Promise<Image> {\n const { resizeImage } = await import('@happyvertical/images');\n const sourceData = await this.store.read(image);\n\n const inputPath = join(tmpdir(), `smrt-edit-in-${randomUUID()}.bin`);\n const outputPath = join(tmpdir(), `smrt-edit-out-${randomUUID()}.bin`);\n\n try {\n await writeFile(inputPath, sourceData);\n await resizeImage(inputPath, outputPath, { width, height });\n const resized = await readFile(outputPath);\n\n return this.createDerivative(image, resized, {\n name: `${image.name}-${width}x${height}`,\n width,\n height,\n description: `Resized from ${image.width}x${image.height} to ${width}x${height}`,\n });\n } finally {\n await unlink(inputPath).catch(() => {});\n await unlink(outputPath).catch(() => {});\n }\n }\n\n /**\n * Crop an image to the specified region\n *\n * @param image - Source image\n * @param x - Left offset\n * @param y - Top offset\n * @param w - Crop width\n * @param h - Crop height\n * @returns New derivative Image\n */\n async crop(\n image: Image,\n x: number,\n y: number,\n w: number,\n h: number,\n ): Promise<Image> {\n const { getImageProcessor } = await import('@happyvertical/images');\n const processor = await getImageProcessor();\n const sourceData = await this.store.read(image);\n\n const inputPath = join(tmpdir(), `smrt-crop-in-${randomUUID()}.bin`);\n const outputPath = join(tmpdir(), `smrt-crop-out-${randomUUID()}.bin`);\n\n try {\n await writeFile(inputPath, sourceData);\n // TODO: @happyvertical/images does not yet expose a direct x,y extract API.\n // The x/y offset params are accepted but unused — this is a resize-with-cover-fit,\n // not a true x,y crop. When the `extract(x, y, w, h)` API is available, replace this.\n // Tracked in: https://github.com/happyvertical/smrt/issues/TODO\n await processor.resize(inputPath, outputPath, {\n width: w,\n height: h,\n fit: 'cover',\n });\n const cropped = await readFile(outputPath);\n\n return this.createDerivative(image, cropped, {\n name: `${image.name}-crop`,\n width: w,\n height: h,\n description: `Cropped region ${x},${y} ${w}x${h}`,\n });\n } finally {\n await unlink(inputPath).catch(() => {});\n await unlink(outputPath).catch(() => {});\n }\n }\n\n /**\n * Convert an image to a different format\n *\n * @param image - Source image\n * @param format - Target format (e.g., 'webp', 'png', 'jpeg')\n * @returns New derivative Image\n */\n async convert(image: Image, format: string): Promise<Image> {\n // Validate against a fixed allowlist BEFORE the value is interpolated into\n // a filesystem path. `format` is caller-controlled (e.g. an HTTP request\n // body via `ImageConvertRequest`) and is appended as the output temp\n // file's extension; an unchecked value like `../../../etc/cron.d/x` would\n // escape `tmpdir()` and let an attacker write the converted bytes to an\n // arbitrary location (path-traversal write). The static `as ImageFormat`\n // cast below gives zero runtime protection, so guard explicitly here.\n const normalizedFormat = format.trim().toLowerCase();\n if (!ALLOWED_CONVERT_FORMATS.has(normalizedFormat as ImageFormat)) {\n throw new Error(\n `Unsupported image format: ${JSON.stringify(format)}. ` +\n `Allowed formats: ${[...ALLOWED_CONVERT_FORMATS].join(', ')}`,\n );\n }\n const safeFormat = normalizedFormat as ImageFormat;\n\n const { convertFormat } = await import('@happyvertical/images');\n const sourceData = await this.store.read(image);\n const mimeType = `image/${safeFormat}`;\n\n const inputPath = join(tmpdir(), `smrt-conv-in-${randomUUID()}.bin`);\n const outputPath = join(\n tmpdir(),\n `smrt-conv-out-${randomUUID()}.${safeFormat}`,\n );\n\n try {\n await writeFile(inputPath, sourceData);\n await convertFormat(inputPath, outputPath, {\n format: safeFormat,\n });\n const converted = await readFile(outputPath);\n\n return this.createDerivative(image, converted, {\n name: `${image.name}.${safeFormat}`,\n mimeType,\n description: `Converted from ${image.mimeType} to ${mimeType}`,\n });\n } finally {\n await unlink(inputPath).catch(() => {});\n await unlink(outputPath).catch(() => {});\n }\n }\n\n /**\n * Generate a square thumbnail of the specified size\n *\n * @param image - Source image\n * @param size - Thumbnail dimension (square)\n * @returns New derivative Image\n */\n async thumbnail(image: Image, size: number): Promise<Image> {\n const { generateThumbnail } = await import('@happyvertical/images');\n const sourceData = await this.store.read(image);\n\n const inputPath = join(tmpdir(), `smrt-thumb-in-${randomUUID()}.bin`);\n const outputPath = join(tmpdir(), `smrt-thumb-out-${randomUUID()}.bin`);\n\n try {\n await writeFile(inputPath, sourceData);\n await generateThumbnail(inputPath, outputPath, {\n maxWidth: size,\n maxHeight: size,\n });\n const thumbData = await readFile(outputPath);\n\n return this.createDerivative(image, thumbData, {\n name: `${image.name}-thumb-${size}`,\n width: size,\n height: size,\n description: `Thumbnail ${size}x${size}`,\n });\n } finally {\n await unlink(inputPath).catch(() => {});\n await unlink(outputPath).catch(() => {});\n }\n }\n\n /**\n * AI-powered image generation based on a prompt (creates derivative linked to source)\n *\n * @param image - Source image (used for metadata, linked as parent)\n * @param prompt - Generation instructions (e.g., \"similar image with sunset colors\")\n * @returns New derivative Image\n */\n async edit(image: Image, prompt: string): Promise<Image> {\n if (!this.options.ai) {\n throw new Error('AI options required for AI-powered editing');\n }\n\n const { getAI } = await import('@happyvertical/ai');\n const ai = await getAI(this.options.ai);\n\n const response = await ai.generateImage(prompt, {\n size: `${image.width}x${image.height}`,\n });\n\n const imageData = response.images[0]?.data;\n if (!imageData || !(imageData instanceof Buffer)) {\n throw new Error('AI did not return image data as Buffer');\n }\n\n return this.createDerivative(image, imageData, {\n name: `${image.name}-edited`,\n description: `AI edit: ${prompt}`,\n });\n }\n\n /**\n * Generate variations of an image using AI\n *\n * @param image - Source image\n * @param prompt - Variation instructions\n * @param options - Number of variations to generate\n * @returns Array of new derivative Images\n */\n async generateVariation(\n image: Image,\n prompt: string,\n options: { count?: number } = {},\n ): Promise<Image[]> {\n const count = options.count ?? 1;\n const results: Image[] = [];\n\n for (let i = 0; i < count; i++) {\n const variation = await this.edit(\n image,\n `${prompt} (variation ${i + 1} of ${count})`,\n );\n results.push(variation);\n }\n\n return results;\n }\n\n /**\n * Helper: Create a derivative Image from processed buffer data\n */\n private async createDerivative(\n source: Image,\n data: Buffer,\n overrides: {\n name: string;\n width?: number;\n height?: number;\n mimeType?: string;\n description?: string;\n },\n ): Promise<Image> {\n const mimeType = overrides.mimeType ?? source.mimeType;\n const typeSlug = source.typeSlug || 'image';\n\n // Create only the Image record (not a plain Asset via store.store())\n const derivative = (await this.collection.create({\n name: overrides.name,\n sourceUri: '',\n mimeType,\n width: overrides.width ?? source.width,\n height: overrides.height ?? source.height,\n alt: source.alt,\n sourceAssetId: source.id,\n typeSlug,\n description: overrides.description ?? '',\n })) as Image;\n\n // Write file data for the existing record\n const sourceUri = await this.store.storeFile(derivative, data, {\n mimeType,\n typeSlug,\n });\n derivative.sourceUri = sourceUri;\n await derivative.save();\n\n return derivative;\n }\n}\n","/**\n * Image model - Asset subclass for image files\n *\n * Represents an image asset with dimensions and accessibility text.\n * Uses STI (Single Table Inheritance) - stored in the assets table with _meta_type='Image'.\n *\n * For semantic search on images, use the centralized embedding system:\n * @smrt({ embeddings: { fields: ['alt', 'description'] } })\n */\n\nimport { Asset } from '@happyvertical/smrt-assets';\nimport { field, smrt } from '@happyvertical/smrt-core';\nimport { resolvePrompt } from '@happyvertical/smrt-prompts';\nimport {\n promptMessageOptions,\n smrtImagesGenerateAltTextPrompt,\n} from './prompts';\nimport type { ImageOptions } from './types';\n\n@smrt({\n api: { include: ['list', 'get', 'create', 'update', 'delete'] },\n mcp: { include: ['list', 'get', 'create', 'update', 'generateAltText'] },\n cli: true,\n})\nexport class Image extends Asset {\n // Core image dimensions (regular columns for querying)\n @field()\n width: number = 0;\n @field()\n height: number = 0;\n\n // Accessibility text\n @field()\n alt: string = '';\n\n constructor(options: ImageOptions = {}) {\n super(options);\n if (options.width !== undefined) this.width = options.width;\n if (options.height !== undefined) this.height = options.height;\n if (options.alt !== undefined) this.alt = options.alt;\n }\n\n /**\n * Calculate aspect ratio from dimensions\n */\n get aspectRatio(): number {\n if (this.height === 0) return 0;\n return this.width / this.height;\n }\n\n /**\n * Helper to get URL from sourceUri for frontend components\n */\n get url(): string {\n return this.sourceUri;\n }\n\n /**\n * Check if dimensions indicate landscape orientation\n */\n get isLandscape(): boolean {\n return this.width > this.height;\n }\n\n /**\n * Check if dimensions indicate portrait orientation\n */\n get isPortrait(): boolean {\n return this.height > this.width;\n }\n\n /**\n * Check if dimensions indicate square aspect ratio\n */\n get isSquare(): boolean {\n return this.width === this.height && this.width > 0;\n }\n\n /**\n * Validate that the asset is an image based on MIME type\n */\n isValidImageFormat(): boolean {\n return this.mimeType.startsWith('image/');\n }\n\n /**\n * Check if this image is high resolution (4K+)\n */\n isHighResolution(): boolean {\n return this.width >= 3840 || this.height >= 2160;\n }\n\n /**\n * AI-powered: Generate accessibility alt text for this image.\n *\n * Uses the `smrtImages.image.generateAltText` prompt registered via\n * `@happyvertical/smrt-prompts`, allowing tenant- or instance-level\n * overrides of the template, model, and parameters at runtime.\n *\n * Only non-PII metadata fields (name, description) are sent to the AI\n * provider. Source URIs, internal foreign-key fields, and the\n * extensible `metadata` blob are intentionally excluded — source URIs\n * may embed signed/private bucket paths and metadata may contain EXIF\n * GPS data or tenant-private configuration.\n *\n * @returns AI-generated alt text describing the image\n */\n async generateAltText(): Promise<string> {\n // Resolve `db` from either the canonical `db` option or its `persistence`\n // alias. SmrtClass maps `persistence → db` lazily during `initialize()`,\n // so on a freshly-constructed Image that has not yet been initialized,\n // `this.options.db` may be undefined while `this.options.persistence` is\n // set. Falling back here ensures stored app- and tenant-level prompt\n // overrides in `_smrt_prompt_overrides` are honored on the first call —\n // before `getAiClient()` triggers full initialization further below.\n const db = this.options.db ?? this.options.persistence;\n\n const resolvedPrompt = await resolvePrompt(\n smrtImagesGenerateAltTextPrompt.key,\n {\n db,\n tenantId: this.tenantId,\n variables: {\n imageName: this.name || '',\n imageDescription: this.description || '',\n },\n },\n );\n\n const ai = await this.getAiClient();\n const response = await ai.message(\n resolvedPrompt.text,\n promptMessageOptions(resolvedPrompt.ai),\n );\n\n const altText = response.trim();\n this.alt = altText;\n return altText;\n }\n}\n","/**\n * ImageCollection - Collection manager for Image instances\n *\n * Provides image-specific query operations, tenant-aware queries, and bulk operations\n */\n\nimport { SmrtCollection } from '@happyvertical/smrt-core';\nimport { Image } from './image';\n\nexport class ImageCollection extends SmrtCollection<Image> {\n static readonly _itemClass = Image;\n\n // ─────────────────────────────────────────────────────────────────────────────\n // Tenant-Aware Query Methods\n // ─────────────────────────────────────────────────────────────────────────────\n\n /**\n * Find all images belonging to a specific tenant\n *\n * @param tenantId - The tenant ID to filter by\n * @returns Array of images belonging to this tenant\n */\n async findByTenant(tenantId: string): Promise<Image[]> {\n return (await this.list({ where: { tenantId } })) as Image[];\n }\n\n /**\n * Find all global images (images without a tenant)\n *\n * @returns Array of global images\n */\n async findGlobal(): Promise<Image[]> {\n return (await this.list({ where: { tenantId: null } })) as Image[];\n }\n\n /**\n * Find images belonging to a tenant plus all global images\n *\n * @param tenantId - The tenant ID to include\n * @returns Array of tenant-specific and global images\n */\n async findWithGlobals(tenantId: string): Promise<Image[]> {\n return (await this.query(\n `SELECT * FROM ${this.tableName} WHERE tenant_id = ? OR tenant_id IS NULL`,\n [tenantId],\n )) as Image[];\n }\n\n /**\n * Get images by minimum dimensions\n *\n * @param minWidth - Minimum width in pixels\n * @param minHeight - Minimum height in pixels\n * @returns Array of images meeting minimum dimension requirements\n */\n async getByMinDimensions(\n minWidth: number,\n minHeight: number,\n ): Promise<Image[]> {\n return (await this.list({\n where: {\n 'width >=': minWidth,\n 'height >=': minHeight,\n },\n })) as Image[];\n }\n\n /**\n * Get images by maximum dimensions\n *\n * @param maxWidth - Maximum width in pixels\n * @param maxHeight - Maximum height in pixels\n * @returns Array of images within maximum dimension limits\n */\n async getByMaxDimensions(\n maxWidth: number,\n maxHeight: number,\n ): Promise<Image[]> {\n return (await this.list({\n where: {\n 'width <=': maxWidth,\n 'height <=': maxHeight,\n },\n })) as Image[];\n }\n\n /**\n * Get landscape images (width > height)\n *\n * @returns Array of landscape-oriented images\n */\n async getLandscape(): Promise<Image[]> {\n return (await this.query(\n `SELECT * FROM ${this.tableName} WHERE width > height`,\n )) as Image[];\n }\n\n /**\n * Get portrait images (height > width)\n *\n * @returns Array of portrait-oriented images\n */\n async getPortrait(): Promise<Image[]> {\n return (await this.query(\n `SELECT * FROM ${this.tableName} WHERE height > width`,\n )) as Image[];\n }\n\n /**\n * Get square images (width === height)\n *\n * @returns Array of square images\n */\n async getSquare(): Promise<Image[]> {\n return (await this.query(\n `SELECT * FROM ${this.tableName} WHERE width = height AND width > 0`,\n )) as Image[];\n }\n\n /**\n * Get images missing alt text\n *\n * @returns Array of images without accessibility text\n */\n async getMissingAltText(): Promise<Image[]> {\n return (await this.list({\n where: { alt: '' },\n })) as Image[];\n }\n\n /**\n * Get high resolution images (4K+)\n *\n * @returns Array of high resolution images\n */\n async getHighResolution(): Promise<Image[]> {\n return (await this.query(\n `SELECT * FROM ${this.tableName} WHERE width >= 3840 OR height >= 2160`,\n )) as Image[];\n }\n\n /**\n * Get images by aspect ratio range\n *\n * @param minRatio - Minimum aspect ratio (width/height)\n * @param maxRatio - Maximum aspect ratio (width/height)\n * @returns Array of images within the aspect ratio range\n */\n async getByAspectRatio(minRatio: number, maxRatio: number): Promise<Image[]> {\n // Use computed ratio in SQL: (CAST(width AS REAL) / NULLIF(height, 0))\n return (await this.query(\n `SELECT * FROM ${this.tableName}\n WHERE height > 0\n AND (CAST(width AS REAL) / height) >= ?\n AND (CAST(width AS REAL) / height) <= ?`,\n [minRatio, maxRatio],\n )) as Image[];\n }\n}\n","/**\n * ImageMetadataExtractor - Extracts metadata from image buffers\n *\n * Uses @happyvertical/images for dimension and format extraction.\n */\n\nimport type { Image } from './image';\nimport type { ImageMetadataResult } from './types';\n\nexport class ImageMetadataExtractor {\n /**\n * Extract metadata from an image buffer\n *\n * @param buffer - Raw image data\n * @returns Extracted metadata including dimensions and format\n */\n async extract(buffer: Buffer): Promise<ImageMetadataResult> {\n const { getDimensions, getImageMetadata } = await import(\n '@happyvertical/images'\n );\n\n const dimensions = await getDimensions(buffer);\n const metadata = await getImageMetadata(buffer);\n\n return {\n width: dimensions.width,\n height: dimensions.height,\n format: metadata.format ?? '',\n mimeType: metadata.format ? `image/${metadata.format}` : 'image/unknown',\n exif: metadata.exif as Record<string, unknown> | undefined,\n };\n }\n\n /**\n * Extract metadata and apply it to an Image instance\n *\n * @param image - The Image instance to update\n * @param buffer - Raw image data\n */\n async extractAndApply(image: Image, buffer: Buffer): Promise<void> {\n const result = await this.extract(buffer);\n image.width = result.width;\n image.height = result.height;\n if (result.mimeType) image.mimeType = result.mimeType;\n }\n}\n","/**\n * ImageSearch - AI-powered image search\n *\n * Provides text-based and semantic search across image collections\n * with optional AI-powered similarity matching.\n */\n\nimport type { AIClientOptions } from '@happyvertical/ai';\nimport type { Image } from './image';\nimport type { ImageCollection } from './images';\nimport type { ImageSearchOptions } from './types';\n\nexport class ImageSearch {\n constructor(\n private readonly collection: ImageCollection,\n readonly _options: { ai?: AIClientOptions } = {},\n ) {}\n\n /**\n * Search images by text query with optional dimension/orientation filters\n *\n * @param query - Text search query\n * @param searchOptions - Optional filters for dimensions, orientation, etc.\n * @returns Matching images\n */\n async search(\n query: string,\n searchOptions: ImageSearchOptions = {},\n ): Promise<Image[]> {\n // Build where clause from dimension filters\n const where: Record<string, unknown> = {};\n\n if (searchOptions.minWidth) where['width >='] = searchOptions.minWidth;\n if (searchOptions.minHeight) where['height >='] = searchOptions.minHeight;\n\n // Single DB query with dimension filters; text matching done in-memory\n // to avoid multiple queries and dedup issues\n const fetchLimit = query\n ? (searchOptions.limit ?? 100) * 3\n : searchOptions.limit;\n\n let results = (await this.collection.list({\n where,\n limit: fetchLimit,\n offset: searchOptions.offset,\n })) as Image[];\n\n // Text filter across name, description, and alt\n if (query) {\n const lowerQuery = query.toLowerCase();\n results = results.filter(\n (img) =>\n img.name.toLowerCase().includes(lowerQuery) ||\n img.description?.toLowerCase().includes(lowerQuery) ||\n img.alt?.toLowerCase().includes(lowerQuery),\n );\n }\n\n // Apply orientation filter\n if (searchOptions.orientation) {\n results = results.filter((img) => {\n switch (searchOptions.orientation) {\n case 'landscape':\n return img.isLandscape;\n case 'portrait':\n return img.isPortrait;\n case 'square':\n return img.isSquare;\n default:\n return true;\n }\n });\n }\n\n // Apply final limit\n if (searchOptions.limit && results.length > searchOptions.limit) {\n results = results.slice(0, searchOptions.limit);\n }\n\n return results;\n }\n\n /**\n * Find images similar to a given image\n *\n * @param image - The reference image\n * @param options - Search options\n * @returns Similar images\n */\n async findSimilar(\n image: Image,\n options: { limit?: number } = {},\n ): Promise<Image[]> {\n const limit = options.limit ?? 10;\n\n // Use basic heuristic: same aspect ratio range + similar dimensions\n const ratio = image.aspectRatio;\n const minRatio = ratio * 0.8;\n const maxRatio = ratio * 1.2;\n\n const candidates = await this.collection.getByAspectRatio(\n minRatio,\n maxRatio,\n );\n\n // Exclude the source image and limit results\n return candidates.filter((c) => c.id !== image.id).slice(0, limit);\n }\n\n /**\n * Find images matching a natural language prompt\n *\n * @param prompt - Natural language description of desired images\n * @param options - Search options\n * @returns Matching images\n */\n async findByPrompt(\n prompt: string,\n options: { limit?: number } = {},\n ): Promise<Image[]> {\n // Default to text search; AI enhancement can be added later\n return this.search(prompt, { limit: options.limit });\n }\n}\n","/**\n * UpstreamManager - Manages importing images from upstream sources\n *\n * Searches across configured upstream source adapters and imports\n * assets into the local store with provenance tracking.\n */\n\nimport type { AssetStore } from '@happyvertical/smrt-assets';\nimport type { Image } from './image';\nimport type { ImageCollection } from './images';\n\n/**\n * Minimal adapter interface for upstream asset sources.\n * Full implementation lives in @happyvertical/assets (SDK package).\n */\nexport interface AssetSourceAdapter {\n readonly name: string;\n readonly capabilities: {\n search: boolean;\n download: boolean;\n browse: boolean;\n };\n search(\n query: string,\n options?: { limit?: number; offset?: number },\n ): Promise<SourceAsset[]>;\n get(externalId: string): Promise<SourceAsset | null>;\n download(\n externalId: string,\n ): Promise<{ data: Buffer; metadata: SourceAssetMetadata }>;\n}\n\nexport interface SourceAsset {\n externalId: string;\n sourceName: string;\n name: string;\n mimeType: string;\n previewUrl?: string;\n thumbnailUrl?: string;\n metadata: SourceAssetMetadata;\n}\n\nexport interface SourceAssetMetadata {\n width?: number;\n height?: number;\n description?: string;\n tags?: string[];\n license?: string;\n attribution?: string;\n}\n\nexport class UpstreamManager {\n constructor(\n private readonly sources: AssetSourceAdapter[],\n private readonly store: AssetStore,\n private readonly collection: ImageCollection,\n ) {}\n\n /**\n * Search across all configured upstream sources\n *\n * @param query - Search query\n * @param options - Search options\n * @returns Merged and ranked results from all sources\n */\n async search(\n query: string,\n options: { limit?: number } = {},\n ): Promise<SourceAsset[]> {\n const limit = options.limit ?? 20;\n const allResults: SourceAsset[] = [];\n\n const searches = this.sources\n .filter((s) => s.capabilities.search)\n .map((source) =>\n source.search(query, { limit }).catch(() => [] as SourceAsset[]),\n );\n\n const results = await Promise.all(searches);\n for (const sourceResults of results) {\n allResults.push(...sourceResults);\n }\n\n return allResults.slice(0, limit);\n }\n\n /**\n * Import an asset from an upstream source into the local store\n *\n * @param sourceAsset - The upstream asset to import\n * @returns Locally stored Image with provenance\n */\n async import(sourceAsset: SourceAsset): Promise<Image> {\n // Find the source adapter\n const adapter = this.sources.find((s) => s.name === sourceAsset.sourceName);\n if (!adapter) {\n throw new Error(`No adapter found for source: ${sourceAsset.sourceName}`);\n }\n\n // Download from upstream\n const { data, metadata } = await adapter.download(sourceAsset.externalId);\n\n // Create the Image record with provenance\n const image = (await this.collection.create({\n name: sourceAsset.name,\n sourceUri: '',\n mimeType: sourceAsset.mimeType,\n width: metadata.width ?? 0,\n height: metadata.height ?? 0,\n alt: metadata.description ?? '',\n description: metadata.attribution\n ? `${metadata.description ?? ''} (${metadata.attribution})`\n : (metadata.description ?? ''),\n sourceType: sourceAsset.sourceName,\n externalId: sourceAsset.externalId,\n typeSlug: 'image',\n })) as Image;\n\n // Write file data for the existing record\n const sourceUri = await this.store.storeFile(image, data, {\n mimeType: sourceAsset.mimeType,\n typeSlug: 'image',\n });\n image.sourceUri = sourceUri;\n await image.save();\n\n return image;\n }\n}\n"],"names":[],"mappings":"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"}