LLM: keep API key in localStorage only, default to muse-spark-1.3

Secrets must never be baked into the static build. Stop reading
VITE_AI_API_KEY from env — key comes only from localStorage via the
⚙️ config dialog (flow-state:llm). Gateway URL / model may still come
from env as non-secret defaults.

- src/llm/config.js: DEFAULTS model → meta/muse-spark-1.3-contributor
  (cheap, per user request), readEnv no longer touches apiKey,
  getLlmConfig resolves key only from stored.apiKey.
- .env.example: drop VITE_AI_API_KEY=, document browser-only key flow.
- src/main.js: dialog copy now says key is browser-only, never baked.

Build + lint:scenes + tests still green. No VITE_AI_API_KEY string
remains in source.

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
Dejvino
2026-09-13 20:08:06 +02:00
co-authored by Claude
parent b7c3537fcb
commit 904ae09486
3 changed files with 17 additions and 16 deletions
+11 -10
View File
@@ -1,14 +1,16 @@
// LLM gateway config — localStorage overrides > VITE env > defaults.
// LLM gateway config — localStorage for the secret, env only for non-secrets.
//
// The app is a static Vite site with no server; the gateway credential lives
// in the browser. Never commit a key — paste it in the panel (→ localStorage)
// or in an untracked .env.local.
// The app is a static Vite site with no server. The API key is a secret and
// must never be baked into the build — it lives only in the browser's
// localStorage (→ ⚙️ dialog). Gateway URL and model are non-secret and may
// come from VITE env as defaults with a localStorage override; the key never
// does.
const STORAGE_KEY = 'flow-state:llm';
const DEFAULTS = {
gatewayUrl: 'https://ai-gateway.vercel.sh/v1',
model: 'openai/gpt-4o-mini',
model: 'meta/muse-spark-1.3-contributor',
apiKey: '',
};
@@ -18,10 +20,10 @@ function readEnv() {
return {
gatewayUrl: env.VITE_AI_GATEWAY_URL || null,
model: env.VITE_AI_MODEL || null,
apiKey: env.VITE_AI_API_KEY || null,
// apiKey intentionally NOT read from env — see module comment.
};
} catch {
return { gatewayUrl: null, model: null, apiKey: null };
return { gatewayUrl: null, model: null };
}
}
@@ -36,14 +38,13 @@ function readStorage() {
}
}
/** Resolved config — localStorage wins, then env, then defaults. */
/** Resolved config — key from localStorage only (never env); URL/model from localStorage > env > defaults. */
export function getLlmConfig() {
const env = readEnv();
const stored = readStorage();
const gatewayUrl = stored.gatewayUrl || env.gatewayUrl || DEFAULTS.gatewayUrl;
const model = stored.model || env.model || DEFAULTS.model;
const apiKey = stored.apiKey != null ? stored.apiKey : (env.apiKey != null ? env.apiKey : DEFAULTS.apiKey);
const key = typeof apiKey === 'string' ? apiKey.trim() : '';
const key = typeof stored.apiKey === 'string' ? stored.apiKey.trim() : '';
return {
gatewayUrl: String(gatewayUrl || DEFAULTS.gatewayUrl).trim() || DEFAULTS.gatewayUrl,
model: String(model || DEFAULTS.model).trim() || DEFAULTS.model,