# Developer Guide This documents how local development differs from running on the Hugging Face Space, since a few things (persistent storage, secrets) work differently in each environment. ## Provider API tokens (BYOK) - Every provider is **bring-your-own-key**: each user pastes their own API key(s) into the UI. Keys are persisted in the browser's `localStorage` and sent to the server only transiently, as part of each generation request - they are never read from the server environment and never written to disk by this app. - Each `ModelProvider` in [providers.py](providers.py) declares its own `api_token_env` (just a label identifying which key it needs, e.g. `FAL_KEY` or `REPLICATE_API_TOKEN` - not an actual env var read at runtime). `app.py` derives one key input field per distinct `api_token_env` automatically (`TOKEN_ENVS` in [app.py](app.py)), so adding a new provider with a new `api_token_env` adds its input field with no other UI changes needed. Providers that share an `api_token_env` (e.g. the two fal.ai providers both use `FAL_KEY`) share one input field. - `.env` / `load_dotenv()` still exist for non-provider local config (e.g. `LOG_LEVEL`), but no longer carry provider API keys. - Some providers need more than just a key - e.g. Azure resources are deployed per-user, so Sora-2-on-Azure also needs the user's own resource endpoint URL. Declare these via `ModelProvider.extra_config_envs` (a tuple of label names, analogous to `api_token_env`); `app.py` derives one plain (non-password) text input per distinct name automatically (`EXTRA_CONFIG_ENVS`), rendered in the same BYOK section and persisted the same way. Read a value back inside a provider's `call` function with `provider.extra_config("NAME")`. ## Persistent storage (`/data`) - **On the Space**: `/data` is the mounted [persistent storage](https://huggingface.co/docs/hub/spaces-storage) directory (if enabled for the Space). Runs (input image + per-provider output videos + `runs.json` history index) are written there and survive restarts. - **Locally**: `/data` is normally not writable/creatable on a developer machine (e.g. macOS blocks writing to `/data` at the OS level). `runs.ensure_data_dirs()` detects this and falls back to a `./data` directory next to the project files instead. This fallback dir is gitignored. - You don't need to do anything to trigger the fallback - it happens automatically the first time `ensure_data_dirs()` fails to create `/data`. ## Running locally ```bash pip install -r requirements.txt python app.py ``` Then open the printed local URL (e.g. `http://127.0.0.1:7860`). ## Adding a new model provider Add a `ModelProvider(...)` entry to `PROVIDERS` in [providers.py](providers.py). Reuse `fal_queue_call` if the new model is also on fal.ai, `replicate_call` if it's on Replicate, `veo_call` if it's a Veo model on the Gemini API, or write a new `call` function matching the signature `(provider, image_path) -> bytes` for providers with a different API contract (see `generic_sync_call` and `polling_call` for templates of direct-response vs. job-polling contracts). No subclassing is needed - the `call` field is what makes this duck-typed. Give the provider an `api_token_env` label (new or reused) - the UI picks up a BYOK input field for it automatically. If the provider needs more than a key (e.g. a per-resource endpoint URL), also set `extra_config_envs` - see `sora-2-azure` for an example.