Files
interactive-story/backend/app/routers/settings.py
T
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

67 lines
2.3 KiB
Python

import httpx
from fastapi import APIRouter, Depends
from sqlalchemy.orm import Session
from .. import models, schemas
from ..database import get_db
router = APIRouter(prefix="/api/settings", tags=["settings"])
def get_settings(db: Session) -> models.Settings:
settings = db.get(models.Settings, 1)
if settings is None:
settings = models.Settings(id=1)
db.add(settings)
db.commit()
return settings
@router.get("", response_model=schemas.SettingsOut)
def read_settings(db: Session = Depends(get_db)):
return get_settings(db)
@router.put("", response_model=schemas.SettingsOut)
def update_settings(payload: schemas.SettingsUpdate, db: Session = Depends(get_db)):
settings = get_settings(db)
fields = payload.model_dump(exclude_unset=True)
embedding_model_changed = (
"embedding_model" in fields
and fields["embedding_model"] != settings.embedding_model
)
for field, value in fields.items():
setattr(settings, field, value)
if embedding_model_changed:
# Vectors from the old model have a different dimensionality/space;
# clear them so the post-turn task re-embeds with the new model.
db.query(models.Memory).update({"embedding": None})
db.commit()
return settings
@router.post("/test")
async def test_connection(db: Session = Depends(get_db)):
"""Hit the endpoint's /models listing as a cheap connectivity check."""
settings = get_settings(db)
url = settings.endpoint_url.rstrip("/") + "/models"
headers = {}
if settings.api_key:
headers["Authorization"] = f"Bearer {settings.api_key}"
try:
async with httpx.AsyncClient(timeout=10) as client:
resp = await client.get(url, headers=headers)
except httpx.HTTPError as exc:
return {"ok": False, "detail": f"Connection failed: {exc}"}
if resp.status_code != 200:
return {"ok": False, "detail": f"HTTP {resp.status_code}: {resp.text[:300]}"}
models_available: list[str] = []
try:
data = resp.json()
models_available = [m.get("id", "?") for m in data.get("data", [])]
except (ValueError, AttributeError, TypeError):
pass # non-JSON or unexpected shape — connectivity is still confirmed
return {"ok": True, "models": models_available}