Skip to content

feat: keyless demo mode (canned first-run answers instead of a 401) - #28

Open
amal66 wants to merge 1 commit into
upstream-pr/provider-registryfrom
upstream-pr/demo-mode
Open

feat: keyless demo mode (canned first-run answers instead of a 401)#28
amal66 wants to merge 1 commit into
upstream-pr/provider-registryfrom
upstream-pr/demo-mode

Conversation

@amal66

@amal66 amal66 commented Jul 17, 2026

Copy link
Copy Markdown
Owner

Stacked PR: this branch is based on upstream-pr/provider-registry (#27) and consumes its provider registry. Merge #27 first; this PR's own diff is just the demo-mode commit.

Summary

Adds a built-in, keyless "demo" provider so a brand-new instance answers its first question with a canned, context-aware placeholder — plus a nudge to configure a key — instead of a raw provider 401. First-run users and zero-setup demos work with no API keys at all.

Changes

Backend:

  • backend/src/lib/llm/providers/demo.ts (new): demo provider registered via the registry (registerProvider + registerApiKeyProvider("demo", [])). Builds a deterministic, clearly-labelled reply that echoes the user's question, names any document filenames it can spot in the prompt, explains what a real model would do, and points at Settings → API Keys. No network call — works offline.
  • backend/src/lib/llm/models.ts: DEMO_MODEL = "mike-demo".
  • backend/src/lib/llm/index.ts: setupDemo() registered alongside the built-in providers.
  • backend/src/routes/chat.ts: resolveDemoFallback() — if the requested model's provider has no env or per-user key, the chat stream runs on DEMO_MODEL instead of failing; an explicitly selected demo model or a keyed provider is returned unchanged.

Frontend:

  • ModelToggle.tsx: "Demo (no key needed)" entry (new Demo group) in the model dropdown.
  • ChatInput.tsx: sending with an unavailable model silently falls back to the demo model instead of opening the blocking ApiKeyMissingPopup (the popup remains in use for tabular reviews).
  • components/shared/ApiKeyBanner.tsx (new) + (pages)/layout.tsx: dismissible global banner ("No AI provider key is set up… answering in demo mode") shown while no model provider key is configured; hidden on /account pages; dismissal is per tab session.
  • lib/modelAvailability.ts: the demo model is always available; the Demo group maps to no key provider.

Why

Today a fresh install (or a self-hoster who hasn't added a key yet) hits a hard error on the very first message. With demo mode, first-run users and journalist demos work with zero API keys: they get an immediate, clearly-labelled placeholder answer and two consistent nudges (the demo reply itself and the banner) toward Settings → API Keys. Documents never leave the workspace until a real key is added.

Testing

  • npm run build --prefix backend — passes (tsc, no errors).
  • npm run build --prefix frontend — passes (next build; .env.local created from .env.local.example).
  • npx vitest run src/lib/llm/__tests__ — 20 tests passing (registry + models, from refactor: table-driven LLM provider registry #27).
  • Module-level smoke test (tsx, uncommitted): providerForModel("mike-demo") === "demo"; streamChatWithTools({ model: DEMO_MODEL, ... }) streams the full reply through onContentDelta, labels it Demo mode, and names nda.pdf from the prompt; completeText returns the truncated question for title generation.
  • Upstream has no test runner yet, so vitest was installed temporarily and uncommitted (npm install --no-save vitest); tests are ready to run atop a test-harness PR once one lands.

Provenance

All changes are mechanical ports of code in amal66/mike@origin/main (commit b3166dd); exceptions:

  • One comment path rewritten for the upstream layout (chat.routes.tsroutes/chat.ts in demo.ts; apps/api/...backend/... in ModelToggle.tsx).
  • The fork's resolveEffectiveTabularModel addition to modelAvailability.ts and its FullScreenLoader change in layout.tsx belong to unrelated fork features and were not ported.
  • The fork has no dedicated tests for the demo provider, so none are added here (matching the fork exactly).

Credits & prior art

  • @pkbtran (pkbtran/marketingOS) — independently parallels this work: their fork shipped a demo mode that bypasses the API-key modal so the app is usable without keys. This PR reaches the same goal via a keyless registry-backed demo provider.

🤖 Generated with Claude Code

https://claude.ai/code/session_01CEguyEgXa9JjCciXCcVemC

Add a built-in keyless "demo" provider (lib/llm/providers/demo.ts,
DEMO_MODEL = "mike-demo") that answers with a canned, context-aware
placeholder plus a nudge to configure a key. The chat stream route
falls back to it automatically when the chosen model's provider has
no env or per-user key (resolveDemoFallback in routes/chat.ts).

Frontend: "Demo (no key needed)" entry in the model dropdown, silent
demo fallback on send in ChatInput (replaces the blocking
ApiKeyMissingPopup), and a dismissible global ApiKeyBanner while no
provider key is configured.

Ported from the amal66/mike monorepo fork (origin/main, b3166dd);
mechanical translation into the backend/ + frontend/ layout.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CEguyEgXa9JjCciXCcVemC
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant