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
This commit is contained in:
parththakkar106
2026-07-06 16:08:19 +05:30
co-authored by Claude Fable 5
commit db9f904222
57 changed files with 7804 additions and 0 deletions
+114
View File
@@ -0,0 +1,114 @@
from fastapi import APIRouter, Body, Depends, HTTPException
from sqlalchemy.orm import Session
from .. import models, schemas
from ..database import get_db
from ..scripting import run_hook
router = APIRouter(prefix="/api/scripts", tags=["scripts"])
HOOK_FIELDS = {"input": "input_js", "context": "context_js", "output": "output_js"}
def get_script_or_404(script_id: int, db: Session) -> models.Script:
script = db.get(models.Script, script_id)
if script is None:
raise HTTPException(404, "Script not found")
return script
@router.get("", response_model=list[schemas.ScriptOut])
def list_scripts(db: Session = Depends(get_db)):
return db.query(models.Script).order_by(models.Script.updated_at.desc()).all()
@router.post("", response_model=schemas.ScriptOut, status_code=201)
def create_script(payload: schemas.ScriptCreate, db: Session = Depends(get_db)):
script = models.Script(**payload.model_dump())
db.add(script)
db.commit()
return script
@router.get("/{script_id}", response_model=schemas.ScriptOut)
def get_script(script_id: int, db: Session = Depends(get_db)):
return get_script_or_404(script_id, db)
@router.patch("/{script_id}", response_model=schemas.ScriptOut)
def update_script(script_id: int, payload: schemas.ScriptUpdate, db: Session = Depends(get_db)):
script = get_script_or_404(script_id, db)
for field, value in payload.model_dump(exclude_unset=True).items():
setattr(script, field, value)
db.commit()
return script
@router.delete("/{script_id}", status_code=204)
def delete_script(script_id: int, db: Session = Depends(get_db)):
db.delete(get_script_or_404(script_id, db))
db.commit()
@router.post("/{script_id}/test")
def test_script(
script_id: int, payload: schemas.ScriptTestRequest, db: Session = Depends(get_db)
):
"""Dry-run one hook against sample text — no AI call, no persistence."""
script = get_script_or_404(script_id, db)
result = run_hook(
script.library_js,
getattr(script, HOOK_FIELDS[payload.hook]),
payload.text,
payload.state,
history=[],
story_cards=[],
info={"actionCount": 0, "characterNames": [], "memoryLength": 0, "maxChars": 0},
)
return {
"text": result.text,
"stop": result.stop,
"state": result.state,
"storyCards": result.story_cards,
"logs": result.logs,
"error": result.error,
}
# ---------- Import / Export ----------
@router.get("/{script_id}/export")
def export_script(script_id: int, db: Session = Depends(get_db)):
"""JSON bundle matching how AI Dungeon scripts circulate."""
script = get_script_or_404(script_id, db)
return {
"name": script.name,
"description": script.description,
"library": script.library_js,
"input": script.input_js,
"context": script.context_js,
"output": script.output_js,
}
@router.post("/import", response_model=schemas.ScriptOut, status_code=201)
def import_script(bundle: dict = Body(...), db: Session = Depends(get_db)):
"""Accepts our export bundle; tolerates *_js key names too."""
def pick(*keys: str) -> str:
for key in keys:
value = bundle.get(key)
if isinstance(value, str):
return value
return ""
script = models.Script(
name=pick("name") or "Imported Script",
description=pick("description"),
library_js=pick("library", "library_js", "sharedLibrary"),
input_js=pick("input", "input_js", "onInput"),
context_js=pick("context", "context_js", "onModelContext"),
output_js=pick("output", "output_js", "onOutput"),
)
db.add(script)
db.commit()
return script