Skip to content

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 →