open-standards/wai/wai-web/src/generate.rs
Transaction Science ce5212b483 wai-web: in-browser deterministic generation (generateSpectral)
The same pure-i64 Rust that generates natively compiles to wasm32 and produces
byte-identical output in the browser — the create-side of "conformance is a hash",
in-page with no ML runtime.

- generate feature added to the wai dep; src/generate.rs binds generateSpectral
  (modelJson, prompt, seed, steps, w, h) -> GeneratedImage and fnv1aHex (the
  portable generation hash). No float, no ONNX.
- Drives the live demo at wai.transaction.science/generate (Astro page + the tiny
  6.4 KB pinned model): interactive prompt/seed/steps generate at 320² in ~41 ms,
  and a conformance line regenerates the pinned inputs and verifies the output hash
  equals the native reference 10b690999f6c5616 byte-for-byte (browser == native).

Co-Authored-By: Claude Opus 4.8 <[email protected]>
2026-06-25 19:08:43 -04:00

55 lines
2 KiB
Rust

//! In-browser **deterministic byte-exact generation** — the create-side of the
//! integer contract, zero JS ML runtime.
//!
//! The same pure-i64 Rust that generates natively compiles straight to wasm32 and
//! produces the IDENTICAL bytes here: a generated image whose hash is portable, so
//! the page can pin "this prompt+seed under this model produces exactly these
//! bytes" and verify it client-side. No float, no ONNX — `wai.generate.spectral`.
use wasm_bindgen::prelude::*;
/// A generated image: RGB row-major, `width * height * 3` bytes.
#[wasm_bindgen]
pub struct GeneratedImage {
rgb: Vec<u8>,
width: u32,
height: u32,
}
#[wasm_bindgen]
impl GeneratedImage {
#[wasm_bindgen(getter)]
pub fn rgb(&self) -> Box<[u8]> {
self.rgb.clone().into_boxed_slice()
}
#[wasm_bindgen(getter)]
pub fn width(&self) -> u32 {
self.width
}
#[wasm_bindgen(getter)]
pub fn height(&self) -> u32 {
self.height
}
}
/// Generate an image deterministically from `(model, prompt, seed, steps)`, in the
/// browser, integer-exact. `model_json` is the pinned model bundle
/// (`generate_spectral_model.json`); the output is byte-identical to the native
/// reference, so its FNV-1a is portable. No float, no ML runtime.
#[wasm_bindgen(js_name = generateSpectral)]
pub fn generate_spectral(
model_json: &[u8], prompt: &str, seed: u32, steps: u32, w: u32, h: u32,
) -> Result<GeneratedImage, JsError> {
let v: serde_json::Value =
serde_json::from_slice(model_json).map_err(|e| JsError::new(&e.to_string()))?;
let m = wai::generate::SpectralModel::from_value(&v);
let rgb = wai::generate::generate(&m, prompt, seed as u64, steps as usize, h as usize, w as usize);
Ok(GeneratedImage { rgb, width: w, height: h })
}
/// FNV-1a 64-bit of `bytes` as lowercase hex — the portable generation hash the
/// page pins and re-checks, the same checksum the native generator emits.
#[wasm_bindgen(js_name = fnv1aHex)]
pub fn fnv1a_hex(bytes: &[u8]) -> String {
format!("{:016x}", wai::generate::fnv1a(bytes))
}