Recipe: AI-powered

Turn free text into structured data with @civility/services. This is the “recipe importer” / “workout generator” shape: prompt + a Zod schema in, a typed object out. See the services guide for modes, grants, and managed credentials — here’s the minimal working call. Assume the singleton app store around this.

// www/utils/import.ts
import { z } from '@zod/zod'
import { generateStructured } from '@civility/services/ai'
import app from '../store/app.ts'

const Recipe = z.object({
  title: z.string(),
  ingredients: z.array(z.string()),
  steps: z.array(z.string()),
  servings: z.number().optional(),
})

export async function importRecipe(pastedText: string) {
  const apiKey = app.settings.value?.aiApiKey
  if (!apiKey) throw new Error('Add an AI key in Settings to use import.')

  const recipe = await generateStructured({
    prompt: 'Extract a structured recipe from the text the user pasted.',
    userDescription: pastedText,
    schema: Recipe, // result is validated + typed
    config: { apiKey, provider: 'claude' },
  })

  await app.recipes.set(crypto.randomUUID(), { ...recipe, source: 'ai-import' })
  return recipe
}

Wire it to a form: a textarea, a “Generate” button that calls importRecipe, and a settings input for the user’s key. Because the parsed result flows straight into a Collection, an AI-imported recipe is indistinguishable from a hand-entered one — it syncs, merges, and exports like any other record.

For the bring-your-own-key vs. host-bundle distinction (and handling services.quota_exceeded), see services §6.


Back to the app development guides.