@happyvertical/smrt-images

Image asset management with AI-powered categorization, editing, metadata extraction, and cross-package STI extending Asset.

v0.20.44AI CategorizationImage EditingSTI Asset

Overview

smrt-images extends the base Asset model from smrt-assets via cross-package STI, adding image-specific dimensions, AI-powered categorization, editing operations, and metadata extraction. Each editing operation creates a new derivative Image linked to its source via parentId.

Installation

bash
npm install @happyvertical/smrt-images

Quick Start

typescript
import {
  Image, ImageCollection,
  ImageCategorizer, ImageEditor, ImageDeriver,
  ImageMetadataExtractor, ImageSearch, UpstreamManager,
} from '@happyvertical/smrt-images';

// Create and query images
const images = new ImageCollection(db);
const image = await images.create({
  name: 'hero.jpg',
  url: 'https://cdn.example.com/hero.jpg',
  mimeType: 'image/jpeg',
  width: 1920,
  height: 1080,
});
await image.save();

// Computed properties from dimensions
image.isLandscape;  // true
image.aspectRatio;  // 1.778
image.isHighResolution();  // false (below 4K)

// AI-powered categorization
const categorizer = new ImageCategorizer({ ai: aiConfig });
const result = await categorizer.categorize(image);
// Auto-tag: applies tags and sets description/alt text
await categorizer.autoTag(image, assetCollection);

// AI alt text generation via this.do()
const altText = await image.generateAltText();

// Standard editing (each creates a derivative Image)
const editor = new ImageEditor(store, images, { ai: aiConfig });
const thumb = await editor.thumbnail(image, 256);
const resized = await editor.resize(image, 800, 600);
const webp = await editor.convert(image, 'webp');

// AI-powered editing and variation generation
const edited = await editor.edit(image, 'add warm sunset tones');
const variations = await editor.generateVariation(
  image, 'winter theme', { count: 3 }
);

Core Models

Image (STI subclass of Asset)

typescript
class Image extends Asset {
  width: number
  height: number
  alt?: string

  // Computed properties
  get isLandscape(): boolean
  get isPortrait(): boolean
  get isSquare(): boolean
  get aspectRatio(): number
  isHighResolution(): boolean
  generateAltText(): Promise<string>  // AI via this.do()
}

ImageCollection

typescript
class ImageCollection extends SmrtCollection<Image> {
  getByMinDimensions(width: number, height: number): Promise<Image[]>
  getByAspectRatio(ratio: number, tolerance?: number): Promise<Image[]>
  getLandscape(): Promise<Image[]>
  getPortrait(): Promise<Image[]>
  getSquare(): Promise<Image[]>
  getHighResolution(): Promise<Image[]>
  getMissingAltText(): Promise<Image[]>
}

Services

typescript
// ImageCategorizer: AI vision analysis
const categorizer = new ImageCategorizer({ ai: aiConfig });
const result = await categorizer.categorize(image);
// Returns: { tags, description, confidence, subjects }

// ImageEditor: resize/crop/convert + AI editing
// Each operation creates a new Image linked via parentId
const editor = new ImageEditor(store, images, { ai: aiConfig });

// ImageMetadataExtractor: dimensions, format, EXIF
const extractor = new ImageMetadataExtractor();
const meta = await extractor.extract(imageBuffer);

// ImageSearch: text search with orientation filters
const search = new ImageSearch(images);
const results = await search.find({ query: 'sunset', orientation: 'landscape' });

// UpstreamManager: import from external providers
const upstream = new UpstreamManager(images, store);
await upstream.importFromSource(sourceAdapter, { provenance: true });

Best Practices

DOs

  • Use generateAltText() to ensure accessibility for all images
  • Use autoTag() for consistent AI categorization
  • Create derivatives through ImageEditor (maintains parentId chain)
  • Use dimension-based queries for responsive image selection
  • Track provenance when importing from external sources

DON'Ts

  • Don't modify the Asset schema without considering cross-package STI impact
  • Don't create Image instances directly (use ImageEditor for derivatives)
  • Don't assume orientation filtering is indexed (it's in-memory)
  • Don't skip the 3 DB calls for derivative creation (create + store + save)

Related Modules