feat: move base URL, API key, and model to server .env

- BASE_URL, API_KEY, MODEL now read from process.env (Bun auto-loads .env)
- requireEnv() fails fast at startup if any is missing
- request body simplifies to { prompt, size, referenceImages? }
- client drops the three fields from form and localStorage
- add .env.example as the variable-name source of truth
- AGENTS.md notes the 0.0.0.0 bind now exposes the upstream quota to
  anyone reachable on the network
This commit is contained in:
2026-05-18 23:28:12 +08:00
parent 4ef2f1ba2b
commit d5bbc14c8d
5 changed files with 64 additions and 62 deletions
+25 -4
View File
@@ -12,6 +12,26 @@ proxy-read timeout.
`dotenv` — Bun loads `.env` automatically.
- Bun version baseline: `1.3.13` (per `README.md`).
## Config
Required env vars (validated at startup via `requireEnv`; the process exits
if any is missing):
| Var | Example | Purpose |
|---|---|---|
| `BASE_URL` | `https://api.openai.com/v1` | OpenAI-compatible base URL |
| `API_KEY` | `sk-…` | Bearer token sent to upstream |
| `MODEL` | `gpt-image-2` | Model name forwarded to upstream |
`.env.example` is the source of truth for variable names. The real `.env`
is gitignored. Restart the server after changing env vars — they are read
once at module load.
These secrets stay **server-side**. The browser only sends `prompt`,
`size`, and `referenceImages`. Combined with the `0.0.0.0` bind, this
means anyone reachable on the network can spend your upstream quota —
bind to `127.0.0.1` or put auth in front if that matters.
## Commands
- Install: `bun install`
@@ -41,7 +61,9 @@ Three files do everything:
connections before the first keepalive can fire. The symptom is an
empty EventStream in DevTools and `request timed out after 10 seconds`
in the log.
- `POST /api/generate` uses `streamSSE` from `hono/streaming`. Emits:
- `POST /api/generate` uses `streamSSE` from `hono/streaming`. Accepts
`{ prompt, size, referenceImages? }``BASE_URL`, `API_KEY`, and
`MODEL` come from env, not the request. Emits:
- `event: partial``{ image: dataUrl, index }` for each
`image_generation.partial_image` / `image_edit.partial_image`.
- `event: final``{ image: dataUrl }` for `*.completed`.
@@ -80,9 +102,8 @@ Three files do everything:
`signal`, and the `onopen` / `onmessage` / `onerror` callbacks.
- On `done`, the client calls `abort.abort()` to terminate the
`fetchEventSource` loop cleanly — otherwise it would retry forever.
- Text fields (`baseURL`, `apiKey`, `model`, `size`, `prompt`) persist
in `localStorage` under the `aip:<field>` prefix. Reference images
stay in-memory only.
- Text fields (`size`, `prompt`) persist in `localStorage` under the
`aip:<field>` prefix. Reference images stay in-memory only.
- `index.html` — markup + inline CSS only. No JS lives here.
No router, no DB, no auth, no AI SDK. API key is supplied per-request by the