Commit Graph
75 Commits
Author SHA1 Message Date
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
parththakkar106andClaude Opus 5 57c24a07d8 Redesign the library UI and add scenario cover art
Rework the home page into a single landing surface and give the app a
consistent visual language, chosen as "illuminated tome" over two other
pitched directions because it builds on the existing Cinzel + gold identity
instead of replacing it.

Home is now Continue (up to 4 in-progress stories, each showing where you
left off) over a scenario shelf, each section with a "See all" link. The
full adventure list moves to /adventures.

Scenario cover art has three tiers, in precedence order: an uploaded picture
(downscaled client-side to 400px WebP before storing), an emoji, or gradient
art generated from a hash of the title so no card is ever an empty box.
Adventures inherit their scenario's art. Images live in the row rather than
on disk because Render's free tier has no persistent volume, and it keeps
export bundles self-contained; list responses carry a cacheable
/api/scenarios/{id}/image URL rather than the base64.

Also: ambient drifting motes behind the app, loading skeletons, staggered
card entrance, ornamental scene breaks and a drop cap in the story, a
"Weaving" thinking indicator, and a toast system replacing every alert().

Two fixes found along the way:

- Importing a scenario bundle with no "tags" key returned a 500. Column
  defaults are not applied until flush, so the attribute was still None
  when the width clamp sliced it.
- Anything meaning "the story's latest narration" was missing action type
  "start", which is the only text a freshly created adventure has, so new
  adventures looked empty. Collected as NARRATION_TYPES.

Migrations 30 and 31 add scenarios.image and scenarios.icon; both are
additive with a '' default and were verified against a database stamped at
29. vite.config.js now reads AIDND_API_PORT so the recurring port-8000
clash with another local app needs no file edit.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014FGY1yvzSeKgTtRfeVtDmx
2026-07-26 13:17:46 +05:30
parththakkar106andClaude Opus 4.8 8fc6d4d2e0 Add responsive mobile layout for the Play screen and nav
The stylesheet had no real mobile design (one media query that only hid a
nav hint), so on phones the Play screen's four side-by-side columns
overflowed horizontally. Add a max-width:720px layer that collapses the
Play layout to a single story column, turns the World/Script State rails
into left-edge tabs that open as slide-in overlays, and makes the
Plot/Memory/Scripts/Insights side-panel a full-screen overlay. Also add a
hamburger dropdown for the top nav, switch the play-layout heights from vh
to dvh (fixes the mobile address-bar jump), and bump inputs to 16px to stop
iOS zoom-on-focus.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UWVyFKvqJGjfbXdibLgkMe
2026-07-25 11:42:42 +05:30
parththakkar106andClaude Sonnet 5 141eec7c64 Add a world-state edit mode for overriding live RPG values
Players/authors can now directly correct the live values of stats the
schema already defines (health, trust, flags, milestones, etc.) without
waiting for the AI to emit a delta. New apply_override() sets values
absolutely rather than adding deltas, and — unlike the AI-facing
apply_delta() — bypasses cooldown/max_delta_per_turn and lets
milestones be un-set, since this is a deliberate correction rather
than a turn to police. Exposed via PUT /adventures/{id}/world-state
and an edit toggle in the World State drawer. Also fixes the
schema-editor stat-kind dropdown and free-text initial-value input
to size consistently with the numeric fields next to them.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-21 18:19:31 +05:30
parththakkar106andClaude Sonnet 5 8c02fe30d7 Fix stat kind dropdown sizing to match number inputs
The new kind <select> fell through to the generic input/select CSS
rule instead of the compact .se-num sizing, making it visually
inconsistent with the min/max/initial number boxes next to it.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-21 17:40:22 +05:30
parththakkar106andClaude Sonnet 5 42073d5d89 Add free-text stat type to the world-state RPG layer
Numeric stats couldn't represent things like worn armor or held items.
A stat can now be marked type: "text" — the AI reports the new full
value instead of a delta, with no clamping/bands (cooldown still
applies). Updated the scenario editor's stat kind selector, the
World State drawer's stat display, and the emit-rule prompt; added
tests and an outfit stat to the RPG demo scenario.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-21 17:35:57 +05:30
parththakkar106andClaude Opus 4.8 279110f75b Show per-turn stat changes under each AI message
Each AI action now carries a compact world_changes summary (derived from its
stored snapshot), rendered as small chips beneath the message: numeric stats
show a signed delta (green up / red down), flags show on/off, milestones show
a check. Gives an at-a-glance "what changed" without opening Insights.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-21 16:48:35 +05:30
parththakkar106andClaude Opus 4.8 3b03bf737a Show raw AI output in Insights per turn
Store the model's literal reply (before the world-state block is stripped)
in each turn's snapshot, and render it in the Insights panel when inspecting
a past AI message (the 🔍 button). Makes it possible to see exactly what the
model emitted — including whether it sent a state delta and with what paths —
which is the fastest way to debug under-reported or misrouted stat changes.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-21 16:34:38 +05:30
parththakkar106andClaude Opus 4.8 80fb5b0c89 Add explicit Save button to the schema JSON editor
The JSON view no longer parses/saves on every keystroke — you type freely
and click "Save JSON" to apply. Invalid JSON (or a non-object) shows the
parse error inline instead of auto-rejecting; valid JSON is pretty-printed,
applied to the form/preview, and saved. The form editor still auto-saves.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-21 16:01:42 +05:30
parththakkar106andClaude Opus 4.8 67fbe17cee Add form-based editor for the RPG world-state schema
Scenario editor now has a "World State (RPG)" section with an Editor/JSON
toggle. The Editor is a form UI to add/edit/remove world & player stats
(min/max/initial/±per-turn/cooldown/counter/desc/bands), NPCs (name, keys,
description, and their own stats), flags, and milestones — no JSON required.
Both views edit the same parsed schema and stay in sync; empty clears the
RPG layer. Keeps the read-only structured preview under the JSON view.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-21 15:52:09 +05:30
parththakkar106andClaude Opus 4.8 cf464a48b0 Make NPCs a dedicated section with per-NPC stats
Replace the single shared `npc` stat template (+ npc_card_types) with an
`npcs` section: each NPC keyed by a stable id, carrying its own name,
description, trigger keys, and its OWN stats block. The AI addresses NPCs
as npc.<id>.<stat> (id shown in context), which also fixes the old
card-id-guessing problem. On adventure creation each NPC auto-creates a
story card (name/keys/desc) for lore + in-scene detection, unless a
same-name card already exists. All NPCs instantiate up front.

- engine: npcs instantiate/apply/render/reference, npc_name/npc_triggers
- builder: _visible_npcs matches each NPC's own keys
- create_adventure: auto-create story cards from npcs
- WorldStateDrawer: render defined NPCs with their own stats + desc tooltip
- demo seed: Gwen (health/trust) + Bandit Leader (health/aggression)
- tests updated (34 pass); no new migration (npcs lives in stat_schema JSON)

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-21 15:17:47 +05:30
parththakkar106andClaude Opus 4.8 4dac445f90 Add Phase 12: native RPG world state
Structured world/player/NPC stats, two-way flags, and sticky milestones
per scenario (stat_schema). The AI proposes a per-turn delta; a Python
engine referees it (clamp to min/max, per-turn cap, cooldown, counters).
Band word-labels plus a fixed stat guide (descriptions + full ranges)
keep the model grounded. World State drawer + Insights delta report;
undo/retry roll it back via the Phase 11 snapshot pattern.

- migrations 26-28 (scenarios.stat_schema, adventures.world_state,
  actions.world_state_before); all nullable, additive, safe on existing rows
- migration 29 raises the default context budget 4096 -> 16384
  (custom values preserved)
- seeded demo scenario 04-rpg-world-state.json (Bandit Camp)
- 19 new tests (33 total pass)

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-21 14:47:29 +05:30
parththakkar106andClaude Opus 4.8 92044a53c4 Make the action input auto-grow up to ~4 lines
Swap the single-line action <input> for a <textarea> that grows with its
content (capped at ~4 lines, then scrolls) and shrinks back after send.
Enter still sends; Shift+Enter inserts a newline. Buttons stay bottom-aligned
as it grows.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TEpWMjnfPqzZ13nPMoGhs5
2026-07-21 01:41:00 +05:30
parththakkar106andClaude Opus 4.8 8a1650c0d4 Collapse nested state trees by default, keep top level expanded
Only depth 0 opens automatically; deeper objects/arrays start collapsed
and expand on click, so deep state stays readable.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TEpWMjnfPqzZ13nPMoGhs5
2026-07-21 01:23:59 +05:30
parththakkar106andClaude Opus 4.8 e74566c5d4 Render script state as a nested tree, not a flat JSON blob
The State drawer stringified non-string values with JSON.stringify, so
nested objects/arrays showed as raw JSON. Add a recursive StateValue /
StateTree renderer: primitives are typed and coloured, objects/arrays are
collapsible and indented so deep state shows its structure.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TEpWMjnfPqzZ13nPMoGhs5
2026-07-21 01:22:33 +05:30
parththakkar106andClaude Opus 4.8 328ae8942d Add per-adventure "Sync from library" for scripts
Adventure scripts are frozen snapshots taken at creation. Add an opt-in
way to pull the latest library code into a live adventure:

- AdventureScript.source_script_id links a copy to its library Script
  (migration 24; set at adventure creation)
- list endpoint flags out_of_date by diffing copy vs library
- POST /adventures/{id}/scripts/{sid}/sync overwrites the copy's code,
  preserving enabled/position/script_state
- legacy copies with no link fall back to a name match, then adopt the link
- Play page shows a Sync button only when a copy differs from its library

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TEpWMjnfPqzZ13nPMoGhs5
2026-07-21 00:56:01 +05:30
parththakkar106andClaude Opus 4.8 36413acd3e Add Download button to in-play Scripts panel
Each script in the panel can now be downloaded as an import-compatible
JSON bundle (matching the /scripts export format), so demo/server-owned
scripts can be forked into a user's own library via the Scripts page's
Import.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TEpWMjnfPqzZ13nPMoGhs5
2026-07-20 19:30:13 +05:30
parththakkar106andClaude Opus 4.8 73f43efbe7 Let the in-play Scripts panel show each script's code
The panel only showed a name + on/off toggle, so demo (server-owned)
scripts — which never appear on the personal Scripts page — had no
in-app way to inspect their code. AdventureScriptOut already ships the
hook sources, so add a "View code" expander that renders the non-empty
hooks (library / onInput / onModelContext / onOutput) read-only.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TEpWMjnfPqzZ13nPMoGhs5
2026-07-20 19:11:39 +05:30
parththakkar106andClaude Opus 4.8 f067ddb766 Add collapsible left "Script State" drawer on the play screen
Exposes the scripting `state` object (every variable scripts read/write
via state.x, persisted per-adventure) in a collapsible left rail that
refreshes after each turn. New owner-scoped GET
/api/adventures/{id}/script-state endpoint; the drawer only fetches
while open and shows an empty-state hint until a script sets a variable.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TEpWMjnfPqzZ13nPMoGhs5
2026-07-20 17:59:16 +05:30
parththakkar106andClaude Opus 4.8 fdb13a2c19 Bulk import/export of story cards (AI Dungeon world-info format)
Add GET /api/story-cards/export and POST /api/story-cards/import for a
scenario or adventure, using the AI Dungeon world-info array shape
(keys/value/type/title/description/useForCharacterCreation) mapped to our
columns. Import accepts a bare array or {cards|storyCards|worldInfo:[...]},
enforces the per-owner cap (existing + incoming) and import rate limit.
Export/Import buttons added to the scenario editor and the in-play Plot
panel.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TEpWMjnfPqzZ13nPMoGhs5
2026-07-20 17:25:00 +05:30
parththakkar106andClaude Opus 4.8 8188ed3999 Play screen UX: sticky header + fix streaming autoscroll
- Pin the play header (Plot/Memory/Scripts/Insights toggles) just below
  the top nav so they stay reachable without scrolling back up to the
  first message.
- Fix streaming autoscroll: snap to the real document bottom instantly
  instead of smooth-scrolling to storyEndRef. That ref sits above the
  sticky composer, so block:'end' stopped short — hiding the latest line
  behind the input bar and yanking the reader back up when they scrolled
  down. Smooth behavior also never settled at per-token speed.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TEpWMjnfPqzZ13nPMoGhs5
2026-07-20 16:57:16 +05:30
parththakkar106andClaude Fable 5 de4db373f2 Phase 8: optional accounts, per-user data, shared demo key
Guest-first multi-user mode behind AIDND_MULTI_USER (local installs
unchanged): signed-cookie guest sessions bootstrapped by /api/auth/me,
register upgrades the guest in place, login/logout, per-IP rate limits.
Every router scoped by user_id; Settings become per-user with the API
key Fernet-encrypted at rest and write-only through the API. Users
without a key get a server-funded demo key (OpenRouter free models,
20 turns/day, memory bank disabled on demo turns). Public read-only
demo scenarios (seed_demo.py); debug log restricted to local mode.
Frontend: auth modal + guest nudge, 401 re-establish/retry, demo
banner and key management in Settings.

Migrations 13-23 adopt existing data under a local user and encrypt
stored keys. Verified: migration on a copy of real data.db, two-session
isolation + register/login via curl and Chrome, demo cap 429, live
OpenRouter turn through the encrypted-key path, vite build + oxlint.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KFsGHju9szibJJa2YJcdbg
2026-07-06 23:04:03 +05:30
parththakkar106andClaude Fable 5 253b533d3b Fix remaining code-review findings (15 bugs; #11 skipped as AID-compatible)
Backend:
- provider: fall back to parsing a plain JSON body when a server ignores
  stream=true (was: silent empty turn); error if response has no text (#5)
- memorybank: clamp cursors after undo/retry shrinks the action list, and
  translate summary_cursor (list position) to an Action.index boundary
  before comparing with Memory.source_end (#7)
- memorybank: pinned memories now count toward the top_k budget (#8)
- memorybank: cosine() returns 0.0 on dimension mismatch; changing the
  embedding model clears stored vectors so they re-embed (#9)
- scripting: MAX_STORY_CARDS cap now counts cards inserted during the
  hook, so a script can't add unbounded cards in one turn (#10)
- settings: /test tolerates non-dict JSON from /models (#13)
- scenarios: import accepts worldInformation as a story-card source (#14)

Frontend:
- per-key debounce timers in PlotPanel and ScenarioEditor — editing two
  things within 600ms no longer drops the first save (#15, #16)
- Continue button no longer discards typed input (#17)
- failed retry resyncs actions from the server instead of leaving the
  removed action missing (#18)
- Settings save/test surface errors instead of hanging on Testing… (#19)
- InsightsPanel ignores stale responses from superseded requests (#20)
- placeholder scan includes story-card trigger keys (#21)

addStoryCard returning the 0-based index (falsy for the first card) matches
real AI Dungeon per the scripting guidebook — kept, documented (#11).
Statuses updated in CODE_REVIEW_FINDINGS.md; stale entries for previously
fixed items (#1-4, #6, #12) corrected.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KFsGHju9szibJJa2YJcdbg
2026-07-06 17:23:12 +05:30
parththakkar106andClaude Fable 5 3fb2fe1a9e Regenerate package-lock.json with npm 11.18
npm 11.6 failed to record top-level entries for @emnapi/core and
@emnapi/runtime (optional deps of @napi-rs/wasm-runtime), which made
'npm ci' fail on any other machine.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KFsGHju9szibJJa2YJcdbg
2026-07-06 17:03:42 +05:30
parththakkar106andClaude Fable 5 db9f904222 Initial commit: AI Dungeon clone (FastAPI backend + React frontend)
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KFsGHju9szibJJa2YJcdbg
2026-07-06 16:08:19 +05:30