Overview

aSaaSin's AI feature uses OpenAI server actions with structured JSON output — the model returns a typed object your UI renders directly. The Branding Generator is the included example, but the same pattern works for any generative feature.

Prompt anatomy

A good structured-output prompt has four parts:

  1. Role — tell the model what it is ("You are a brand naming expert").
  2. Context — the user's inputs, concisely summarized.
  3. Constraints — what you want and what to avoid.
  4. Output format — describe the exact JSON shape or use response_format with a Zod schema.
// services/ai/generateBranding.ts (pattern)
import { openai } from '@/lib/openai'
import { z } from 'zod'
import { zodResponseFormat } from 'openai/helpers/zod'

const BrandingSchema = z.object({
  names: z.array(z.string()).length(5),
  taglines: z.array(z.string()).length(3),
  colors: z.array(z.string()).length(4), // hex strings
})

export async function generateBranding(input: {
  industry: string
  keywords: string
  tone: string
}) {
  const completion = await openai.beta.chat.completions.parse({
    model: 'gpt-4o-mini',
    messages: [
      {
        role: 'system',
        content: `You are a brand identity expert. Return only valid JSON.`,
      },
      {
        role: 'user',
        content: `Industry: ${input.industry}\nKeywords: ${input.keywords}\nTone: ${input.tone}\n\nGenerate 5 brand names, 3 taglines, and 4 brand colors.`,
      },
    ],
    response_format: zodResponseFormat(BrandingSchema, 'branding'),
  })
  return completion.choices[0].message.parsed
}

Prompt tips for structured output

  • Be specific about counts — "generate exactly 5 names" is clearer than "generate some names".
  • Name the output fields in the prompt — mention names, taglines, colors so the model maps its response to your schema.
  • Keep the system prompt short — one sentence on role and one on format. Long system prompts dilute focus.
  • Validate before renderingzodResponseFormat parses the response; if it throws, surface a user-friendly error rather than crashing.
  • One concern per action — branding names in one action, color palettes in another. Mixing concerns makes prompts harder to tune.

Extending the Branding Generator

The Branding Generator (app/dashboard/playground/branding/) is a self-contained example. To add a new AI feature:

  1. Define a Zod schema for the output in types/schemas.ts.
  2. Write a service function in services/ai/ that calls OpenAI and returns the parsed schema.
  3. Create a server action in app/actions.ts (or a feature-specific actions file) that validates input, calls the service, and returns ActionResponse.
  4. Build a client component that collects inputs, calls the action with useTransition, and renders the result with apply/reset controls.

Keep OPENAI_API_KEY server-only. Never import it in client components or expose it via NEXT_PUBLIC_ prefix.