Moviola

Documentation

Running in two minutes.

Same codebase on your laptop, in Docker, and on Vercel. No provider keys required to start — the offline sandbox keeps everything usable.

Quickstart

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 dev

Provider keys

Copy .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-processing
MOVIOLA_FAL_KEYFlux Swift, Ray Flash
MOVIOLA_ANTHROPIC_API_KEYAI prompt enhancer
MOVIOLA_ENABLE_MOCKSet to 0 to hide the offline sandbox once real keys exist

Deploy

Vercel — 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 moviola

Architecture

src/
  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, proxy

Servers are stateless proxies; generation records live in the browser (IndexedDB). That local-first design is what makes Vercel and Docker behave identically.

Add a provider

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 } */ },
};

Add a studio

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
];