Customizing
Swapping models, adding languages, adding tools — how to move through the codebase.
Code layout
src/
├── lib/
│ ├── ort/ ONNX Runtime layer shared by both tools
│ │ ├── runtime.ts runtime setup (wasm paths, thread count)
│ │ └── modelCache.ts weight downloads + Cache Storage
│ ├── upscaler/ AI upscaling: model table, session, tensors, tiling
│ ├── cutout/ AI cutout: model table, preprocessing, session, compositing
│ ├── models.ts upscaler model registry
│ └── types.ts shared types
├── components/
│ ├── compress/ compression workbench
│ ├── upscale/ upscale workbench
│ └── cutout/ cutout workbench
├── hooks/ useUpscaler / useCutout state machines
└── i18n/ Chinese and English copy
One deliberate design choice: the inference layer never contains user-facing copy.
Progress is emitted as structured { phase, ratio, detail }, errors are thrown as objects
carrying a code, and result notes are structured data. All wording is rendered by i18n —
so adding a language never means touching algorithm code.
Swapping in another upscaler
Add an entry in src/lib/models.ts and drop the .onnx file into public/models/:
{
id: 'my-model',
label: 'Display name',
url: '/models/my-model.onnx',
scale: 4,
approxBytes: 0,
inputRange: 'unit', // 'unit' = 0–1, 'byte' = 0–255
inputName: 'image', // input tensor name in the ONNX graph
outputName: 'upscaled_image',
fixedInputSize: 128, // fixed input edge; null for dynamic shapes
license: 'BSD-3-Clause',
}
Keep any single file under 25 MiB or Cloudflare will reject the deploy.
The model description copy goes under upscaleModels in i18n, keyed by id.
Swapping in another cutout model
Add an entry in src/lib/cutout/models.ts:
{
id: 'my-matting-model',
label: 'Display name',
variants: {
webgpu: { url: 'https://…/model_fp16.onnx', approxBytes: 0, dtype: 'fp16' },
wasm: { url: 'https://…/model.onnx', approxBytes: 0, dtype: 'fp32' },
},
inputSize: 512,
inputName: 'input_image',
outputName: 'output_image', // a typo here triggers tensor-missing
outputIsLogits: true, // does the output need a sigmoid?
license: 'MIT',
}
inputSize and the preprocessing
follows. But the ImageNet mean/std in the preprocessing is hard-coded (as IMAGE_MEAN /
IMAGE_STD in models.ts) — check it whenever you swap models.
The output tensor name is fault-tolerant. If the registry name doesn't match, it falls
back to the sole output; with multiple outputs it picks the one that looks most like a mask (the spatial
tensor with the smallest last dimension). Only if nothing can be identified does it throw
tensor-missing, listing the output names actually present in the graph.
Adding a language
-
Create a locale file under
src/i18n/locales/, usingen.tsas the template. The type isTranslation = typeof en, so a missing translation is a compile-time error. - Register the language in
src/i18n/index.ts. -
Copy the HTML entries: one language-prefixed copy each of
index.html,upscale/index.htmlandcutout/index.html, withcanonicalandhreflangupdated. - Add the new entries to
rollupOptions.inputinvite.config.ts.
Adding a new tool
-
Build an inference layer under
src/lib/if the tool needs a model. Reusesrc/lib/ort/for the runtime and cache — do not pull in a second copy of ONNX Runtime. - Write the state machine under
src/hooks/, following the same rule: no user-facing copy. - Write the components under
src/components/<tool>/. -
Add it in
src/App.tsx— theTooltype, thePATHSmap and theTOOLSlist. Tabs and routing follow automatically. - Create the HTML entries (including the English version) and add them to
DIR_INDEXandinputinvite.config.ts. - Add the i18n copy, the sitemap entries, and the matching documentation page.
Known limits
| Limit | Details |
|---|---|
| Upscale output capped at 64 megapixels | At 4× that is roughly a 2000×2000 source. Anything larger must be cropped or downscaled first. |
| Upscale and cutout are one image at a time | Compression supports batches; these two are heavy compute jobs where you check each result. |
| Cutout mask capped at 512×512 | For larger sources, edge accuracy is limited by mask resolution. The result panel adds a note. |
| A machine without WebGPU has a limited experience | It falls back to WASM, and cutout downloads 192 MB instead of 94 MB. |
| Cutout only finds the most salient subject | Multi-object images get only the most prominent one; a pure landscape may be judged "no subject" and error out. |
Local development
npm run dev # dev server (with COOP/COEP headers)
npm run build # production build
npm run typecheck # types only
npm run lint # ESLint
npm run preview # preview the build output
server.headers and
preview.headers in vite.config.ts share the same constant), so local and
production behaviour match — otherwise multi-threaded WASM would be untestable locally.