All files / web/src/lib/tasks page-spot-image-generate.ts

0% Statements 0/125
0% Branches 0/1
0% Functions 0/1
0% Lines 0/125

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126                                                                                                                                                                                                                                                           
import { createTask } from '../task-manager'
import { getImageProvider } from '../image-providers'
import { generateAndStoreImage } from '../image-generation'
import { imageExists } from '../image-storage'
import type { PageSpotImageGenerateEvent } from './events'
import { recordImageGenUsage } from '../ai-usage/helpers'
import { AiFeature } from '../ai-usage/features'

export { IMAGE_PROVIDERS } from '../image-providers'

export interface PageSpotImageGenerateInput {
  pageId: string
  spotId: string
  provider: 'gemini' | 'openai'
  model: string
  prompt: string
  forceRegenerate?: boolean
  _userId?: string
}

export interface PageSpotImageGenerateOutput {
  status: 'generated' | 'skipped' | 'failed'
  publicUrl?: string
  sizeBytes?: number
  error?: string
}

/**
 * Start a page-spot image generation background task.
 */
export async function startPageSpotImageGeneration(
  input: PageSpotImageGenerateInput
): Promise<string> {
  return createTask<
    PageSpotImageGenerateInput,
    PageSpotImageGenerateOutput,
    PageSpotImageGenerateEvent
  >('page-spot-generate', input, async (handle, config) => {
    const provider = getImageProvider(config.provider)
    if (!provider) {
      handle.fail(`Unknown image provider: ${config.provider}`)
      return
    }

    if (!provider.isAvailable()) {
      const { envKey, envKeyAlt } = provider.meta
      const keys = envKeyAlt ? `${envKey} or ${envKeyAlt}` : envKey
      handle.fail(
        `No API key configured for ${provider.meta.name}. Set ${keys} in your environment.`
      )
      return
    }

    const storageTarget = {
      type: 'static' as const,
      relativePath: `page-spots/${config.pageId}/${config.spotId}.png`,
    }

    // Skip if exists and not force-regenerating
    if (!config.forceRegenerate && imageExists(storageTarget)) {
      handle.emit({
        type: 'spot_skipped',
        pageId: config.pageId,
        spotId: config.spotId,
      })
      handle.complete({
        status: 'skipped',
        publicUrl: `/page-spots/${config.pageId}/${config.spotId}.png`,
      })
      return
    }

    handle.emit({
      type: 'spot_started',
      pageId: config.pageId,
      spotId: config.spotId,
      model: config.model,
      provider: config.provider,
    })

    handle.setProgress(10, 'Generating image...')

    try {
      const result = await generateAndStoreImage({
        provider: config.provider,
        model: config.model,
        prompt: config.prompt,
        storageTarget,
      })

      if (config._userId) {
        recordImageGenUsage(config.provider, config.model, {
          userId: config._userId,
          feature: AiFeature.IMAGE_PAGE_SPOT,
          backgroundTaskId: handle.id,
        })
      }

      handle.emit({
        type: 'spot_complete',
        pageId: config.pageId,
        spotId: config.spotId,
        filePath: result.publicUrl,
        sizeBytes: result.sizeBytes ?? 0,
      })

      handle.complete({
        status: 'generated',
        publicUrl: result.publicUrl,
        sizeBytes: result.sizeBytes,
      })
    } catch (err) {
      const error = err instanceof Error ? err.message : String(err)

      handle.emit({
        type: 'spot_error',
        pageId: config.pageId,
        spotId: config.spotId,
        error,
      })

      handle.fail(error)
    }
  })
}