AI Image Generator — Swift 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 Swift implementation — the same logic the interactive tool runs, in a shareable, citable form.
// AI Image Generator — prompt validation, usage-gate verdicts, and ?prompt=
// URL state for FLUX.1-schnell image generation.
//
// Language: Swift (5.9, standard library only)
// Source: CosmoDev polyglot showcase port, from src/lib/image-gen.ts.
import Foundation
enum PromptInvalid: String, Error { case empty, tooLong, controlChars }
/// Daily cap wins; the retry hint is in minutes.
enum GateVerdict: Equatable {
case ok
case rateLimitedIP // fixed 60-minute window
case rateLimitedDaily(retryAfterMinutes: Int)
}
enum ImageGen {
static let model = "@cf/black-forest-labs/flux-1-schnell"
static let steps = 4 // flux-1-schnell's design point -> 57.6 neurons/image
static let promptMaxChars = 600 // UX clamp, below the model's 2048 limit
static let ipHourCap = 10
static let dailyCap = 100
/// Normalize CRLF/CR to \n, trim, enforce 1..600 chars, and reject control
/// characters except \n (C0 minus U+000A, DEL, C1) — codepoint-wise.
static func validatePrompt(_ raw: String) -> Result<String, PromptInvalid> {
let prompt = raw
.replacingOccurrences(of: "\r\n", with: "\n")
.replacingOccurrences(of: "\r", with: "\n")
.trimmingCharacters(in: .whitespacesAndNewlines)
if prompt.isEmpty { return .failure(.empty) }
if prompt.count > promptMaxChars { return .failure(.tooLong) }
let hasCtrl = prompt.unicodeScalars.contains {
($0.value < 0x0A || (0x0A < $0.value && $0.value < 0x20)) || (0x7F...0x9F).contains($0.value)
}
if hasCtrl { return .failure(.controlChars) }
return .success(prompt)
}
/// Minutes (>= 1) until the next UTC midnight — the daily window reset.
static func minutesToUtcMidnight(_ now: Date = .init()) -> Int {
var cal = Calendar(identifier: .gregorian)
cal.timeZone = TimeZone(identifier: "UTC")!
let nextMidnight = cal.startOfDay(for: now).addingTimeInterval(86_400)
return max(1, Int(ceil(nextMidnight.timeIntervalSince(now) / 60)))
}
/// Daily cap wins: the site-global budget resets at UTC midnight.
static func checkGate(ipCount: Int, dailyCount: Int, now: Date = .init()) -> GateVerdict {
if dailyCount >= dailyCap {
return .rateLimitedDaily(retryAfterMinutes: minutesToUtcMidnight(now))
}
if ipCount >= ipHourCap { return .rateLimitedIP }
return .ok
}
/// Encode the shareable state: "" when there is nothing to share.
static func encodeUrlState(_ prompt: String) -> String {
guard !prompt.isEmpty else { return "" }
var allowed = CharacterSet.urlQueryAllowed
allowed.remove(charactersIn: "&+=?") // keep the value from breaking the query
return "?prompt=" + prompt.addingPercentEncoding(withAllowedCharacters: allowed)!
}
/// Inverse of encodeUrlState — a missing param decodes to "".
static func decodeUrlState(_ search: String) -> String {
let items = URLComponents(string: "https://cosmolabs.org\(search)")?.queryItems
return items?.first { $0.name == "prompt" }?.value ?? ""
}
}
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 →