Documentation
Same codebase on your laptop, in Docker, and on Vercel. No provider keys required to start — the offline sandbox keeps everything usable.
Clone, install, run. Open localhost:3000 and the offline Darkroom models work immediately.
git clone github.com/zanni098/moviola
cd moviola
npm install
npm run devCopy .env.example to .env.local and add any of these. Each one lights up more of the model catalog — none are required.
MOVIOLA_REPLICATE_API_TOKENFlux Pro, Kontext, Canvas, Kling, Hailuo, post-processingMOVIOLA_FAL_KEYFlux Swift, Ray FlashMOVIOLA_ANTHROPIC_API_KEYAI prompt enhancerMOVIOLA_ENABLE_MOCKSet to 0 to hide the offline sandbox once real keys existVercel — import the repo, set env vars, deploy. No extra config.
Docker — the standalone image is the exact same build.
docker build -t moviola .
docker run -p 3000:3000 --env-file .env.local moviolasrc/
core/ capability flags, provider contract, presets, cost
providers/ one adapter per provider (replicate, fal, mock)
data/ the model catalog + preset packs (data, not code)
studios/ plugin studios: image, video, storyboard, library
components/ composer, gallery, shell, marketing
app/api/ stateless handlers: generate, jobs, models, proxyServers are stateless proxies; generation records live in the browser (IndexedDB). That local-first design is what makes Vercel and Docker behave identically.
A provider is one file implementing submit and status, plus a catalog entry with capability flags. The UI rebuilds its controls from those flags — zero UI changes.
// src/providers/myprovider.ts
export const myAdapter: ProviderAdapter = {
name: "myprovider",
isConfigured: () => !!process.env.MOVIOLA_MYPROVIDER_KEY,
submit: async (model, input) => { /* returns { providerJobId } */ },
status: async (model, jobId) => { /* returns { state, outputs } */ },
};A studio is a folder under src/studios that registers itself in one array. Adding one requires no changes to core — which is how Lip Sync, VFX and Workflow will arrive.
// src/studios/index.ts
export const STUDIOS = [
{ slug: "image", name: "Image", icon: ImageIcon, component: ImageStudio },
// ...add yours here
];