Files
interactive-story/render.yaml
T
parththakkar106andClaude Opus 5 1dd31086c1 Add power-user AI Chat page; centralize demo-key model pinning
AI Chat is a plain scratchpad for talking to a model directly — no story
context, scripts or world state — for poking at models, prompts and endpoints
without starting an adventure. Power users only: the router 404s (rather than
403s) for everyone else and the nav link is hidden. The conversation lives in
localStorage, so there's no new table or migration.

is_power_user() now also returns True in local mode: it's the operator's own
machine and their own key, the same reasoning that makes the provider debug log
local-only.

Alongside that, the rule keeping the shared demo key off paid models now lives
in exactly one place. It had been duplicated into the chat router, which is how
one copy eventually drifts:

- resolve_provider_config() takes an optional model_override and is the only
  place the whitelist is applied, so turns, AI Chat and the connection test all
  inherit it. An override is a per-request preference, never a grant.
- ProviderConfig.__post_init__ refuses to exist when api_key is the demo key
  and the model isn't whitelisted. It keys on the key itself rather than the
  using_demo flag, so a mislabelled config can't slip past, and it raises so a
  future path that bypasses the resolver fails loudly instead of billing.
- The demo branch still pins endpoint_url too — a user-controlled endpoint
  would leak the key itself, which is worse than spending it.

Provider gained chat(messages, ...) beside generate(), both delegating to a
shared _stream(url, body); completion-mode endpoints get the messages flattened
into a labelled transcript. Settings' /models fetch moved to
list_endpoint_models() and is shared with /api/chat/config.

Tests: 10 new in tests/test_chat.py (70 total). These deliberately do not stub
resolve_provider_config — the point is to exercise the real BYOK-vs-demo
decision and assert on what the provider actually received: off-whitelist
override pinned, off-whitelist Settings.model pinned, redirected endpoint
pinned, BYOK passed through untouched.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014FGY1yvzSeKgTtRfeVtDmx
2026-07-26 20:16:56 +05:30

55 lines
2.1 KiB
YAML

# Render Blueprint — https://render.com/docs/blueprint-spec
#
# One web service: the multi-stage Dockerfile builds the React SPA and the
# FastAPI backend into a single image that serves both same-origin. Database
# is external Neon Postgres (set AIDND_DATABASE_URL in the dashboard); the
# free tier has no persistent disk, which is why the DB lives off-box.
#
# First deploy: create a Blueprint from this repo in the Render dashboard,
# then fill the `sync: false` secrets (Neon URL + demo key). Pushes to `main`
# auto-deploy thereafter.
services:
- type: web
name: ai-dnd
runtime: docker
dockerfilePath: ./Dockerfile
dockerContext: .
plan: free # sleeps after ~15 min idle; first wake takes ~30-60s
region: virginia # us-east, closest to the Neon us-east-1 database
healthCheckPath: /api/health
autoDeploy: true
envVars:
# Multi-user hardening on (guest sessions, per-user data, rate limits).
- key: AIDND_MULTI_USER
value: "1"
# Signs session cookies + encrypts stored API keys. Render generates a
# strong value once and keeps it stable across deploys (a regenerated
# secret would log out every user on each deploy).
- key: AIDND_SECRET_KEY
generateValue: true
# Neon Postgres connection string (pooled, sslmode=require). Secret —
# set it in the dashboard; never commit it.
- key: AIDND_DATABASE_URL
sync: false
# --- Shared demo key (optional): lets first-time visitors play without
# bringing their own API key. Set these in the dashboard to enable;
# leave unset to require BYOK. ---
- key: AIDND_DEMO_API_KEY
sync: false
- key: AIDND_DEMO_MODELS
sync: false
- key: AIDND_DEMO_TURNS_PER_DAY
value: "20"
# Comma-separated emails of trusted testers: unmetered demo turns, plus
# the AI Chat page (hidden from everyone else). Set in the dashboard;
# leave unset and nobody gets it. Registered accounts only.
- key: AIDND_POWER_USERS
sync: false
# CORS is unset on purpose: the SPA is served same-origin by FastAPI.