Skip to content

AI Image Generator — Ruby source

Turn a text prompt into a 1024×1024 image with FLUX.1 [schnell] on Cloudflare Workers AI. No account, no API key — rate-limited for fair use. Your prompt goes to the model through our Worker; we store no prompts and no images.

This is the Ruby implementation — the same logic the interactive tool runs, in a shareable, citable form.

# frozen_string_literal: true

# AI Image Generator — prompt validation, usage-gate verdicts, and ?prompt=
# URL state for FLUX.1-schnell image generation.
#
# Language: Ruby (3.2, standard library only)
# Source:   CosmoDev polyglot showcase port of the AI Image Generator tool,
#           from src/lib/image-gen.ts (the canonical TypeScript implementation).

require 'erb'
require 'uri'

module ImageGen
  MODEL = '@cf/black-forest-labs/flux-1-schnell'
  STEPS = 4 # flux-1-schnell's design point -> 57.6 neurons/image
  PROMPT_MAX_CHARS = 600 # UX clamp, below the model's 2048 limit
  IP_HOUR_CAP = 10
  DAILY_CAP = 100

  module_function

  # Normalize CRLF/CR to "\n", trim, enforce 1..600 chars, and reject control
  # chars except "\n" (C0 minus \n, DEL, C1). Returns [prompt, nil] or
  # [nil, reason].
  def validate_prompt(raw)
    prompt = raw.gsub(/\r\n?/, "\n").strip
    return [nil, 'empty'] if prompt.empty?
    return [nil, 'tooLong'] if prompt.length > PROMPT_MAX_CHARS
    return [nil, 'controlChars'] if prompt.match?(/[\x00-\x09\x0B-\x1F\x7F-\x9F]/)

    [prompt, nil]
  end

  # Minutes (>= 1) until the next UTC midnight — the daily window reset.
  def minutes_to_utc_midnight(now = Time.now.utc)
    midnight = Time.utc(now.year, now.month, now.day) + 86_400
    [((midnight - now) / 60.0).ceil, 1].max
  end

  # Daily cap wins: the site-global budget resets at UTC midnight. Returns
  # { ok: true } or { ok: false, error:, retryAfterMinutes: }.
  def check_gate(ip_count, daily_count, now = Time.now.utc)
    if daily_count >= DAILY_CAP
      { ok: false, error: 'RATE_LIMITED_DAILY',
        retryAfterMinutes: minutes_to_utc_midnight(now) }
    elsif ip_count >= IP_HOUR_CAP
      { ok: false, error: 'RATE_LIMITED_IP', retryAfterMinutes: 60 }
    else
      { ok: true }
    end
  end

  # Encode the shareable state: '' when there is nothing to share.
  # ERB::Util.url_encode matches encodeURIComponent (%20, not form-style +).
  def encode_url_state(prompt)
    prompt.empty? ? '' : "?prompt=#{ERB::Util.url_encode(prompt)}"
  end

  # Inverse of encode_url_state — a missing param decodes to ''.
  def decode_url_state(search)
    search.sub(/^\?/, '').split('&').each do |pair|
      next unless pair.start_with?('prompt=')

      return URI.decode_www_form_component(pair.delete_prefix('prompt='))
    end
    ''
  end
end

Also available in 13 other languages

Every CosmoDev tool ships its pure logic in TypeScript (web) and Go (CLI), with authored implementations in a dozen-plus languages — the same contract, ported. Compare all languages side by side →