Spaces:
Sleeping
Sleeping
| # 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. | |