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 →