Decisions 2026-08-05 evening (reprioritizing docs/AI-ASSISTANT-CONCEPT.md's original P1/P2 server-first sequencing to local-first, since a real Ollama instance already runs on this Mac with a full model set): - `local` ships free, no entitlement check — always available wherever supportsLocalLlm() is true. - `public` (BYOK) is available too, explicitly unmonitored for now — no seats/metering/consent backend. This reverses the concept doc's decision #1 (server-side-only key custody): the client holds its own key, matching vncmail-native's existing pattern. - `server` (VNC-hosted) stays unwired client-side; that infra is "this MacBook tonight, the dev k8s cluster tomorrow." New: - lib/ai/local-client.ts: listLocalModels/testLocalConnection/chatLocal/ chatPublic, ported near-verbatim from vncmail-native's proven src/api/ai.ts. Direct browser-side fetch, not proxied through this app's own server — a server-side proxy would reach the *server's* loopback, not the user's own laptop, which defeats the point of "local" once this app is hosted remotely. - lib/ai/key-store.ts: client-held BYOK storage (localStorage — this repo's existing convention for client state, no OS keychain reachable from a browser tab). - lib/ai/local-settings.ts: isolated persistence for provider/model/base-URL choices. Deliberately NOT folded into stores/settings-store.ts, which has a hand-maintained export/import enumeration this prototype-scope state doesn't belong in yet. - Retrieval reuses this app's own already-built app/api/offline/search (encrypted SQLite/FTS5 mail index) as context when available, and degrades to unaugmented chat — not an error — when it 404s/503s (no index in this session, e.g. plain browser rather than Electron). Rewrote components/settings/ai-assistant-settings.tsx: provider picker, local runtime config (base URL, model list/refresh, test connection with a CORS-aware diagnostic per the concept doc's own note on the browser row), public BYOK config (base URL, model, key, client-side consent toggle), and a working Ask box. Verified: typecheck clean, lint clean, translations pass, production build succeeds. Live-tested against the real Ollama on this machine (confirmed running: qwen2.5:32b, gemma4, deepseek-r1, llama3.2, hermes3, qwen3) via a local server + demo-mode session — admin flag round-trips correctly, the pane renders both provider options, and the CORS-diagnostic path fires correctly on a real (if here environment-sandboxed, not Ollama-side) connection failure. Full success end-to-end still wants a real, unsandboxed browser tab against this Mac's loopback to close out.
27 lines
1.2 KiB
TypeScript
27 lines
1.2 KiB
TypeScript
// Client-held storage for the user's own public-provider API key (BYOK).
|
|
//
|
|
// Decision 2026-08-05 (reverses docs/AI-ASSISTANT-CONCEPT.md decision #1's
|
|
// server-side-custody design): the user brings and holds their own key,
|
|
// client-side, not VNC. This is the same custody model as
|
|
// vncmail-native's lib/ai-key-store.ts (expo-secure-store there; this repo
|
|
// has no OS keychain access from a browser tab, so localStorage is the
|
|
// honest equivalent here — plain, not hidden behind a false sense of
|
|
// "secure storage"). A fuller Paperclip-style key-management UI (multiple
|
|
// providers, masking, rotation) is good follow-up work, not built tonight.
|
|
const KEY_PREFIX = 'vncmail:ai:key:';
|
|
|
|
export function getAiApiKey(provider: 'public'): string | null {
|
|
if (typeof window === 'undefined') return null;
|
|
return window.localStorage.getItem(KEY_PREFIX + provider);
|
|
}
|
|
|
|
export function setAiApiKey(provider: 'public', key: string): void {
|
|
if (typeof window === 'undefined') return;
|
|
window.localStorage.setItem(KEY_PREFIX + provider, key);
|
|
}
|
|
|
|
export function clearAiApiKey(provider: 'public'): void {
|
|
if (typeof window === 'undefined') return;
|
|
window.localStorage.removeItem(KEY_PREFIX + provider);
|
|
}
|