12 KiB
12 KiB
Changelog
All notable changes to AI-RPG are documented here. The format follows Keep a Changelog, and this project adheres to Semantic Versioning.
[1.0.4] — 2026-06-20
Fixed (proper fix for CORS env parsing)
- app/config.py: replaced the broken
Annotated[list[str], NoDecode]approach with a simpler, more robust one:cors_originsis now declared as a plainstr(comma-separated, e.g."http://a,http://b"or"*").- Added a
cors_origins_listproperty that splits the string into alist[str]on demand. - This sidesteps the entire
EnvSettingsSource.decode_complex_value/ JSON-parsing codepath — pydantic-settings sees astrfield, takes the env value as-is, no JSON parsing attempted.
- app/main.py: updated
CORSMiddleware(allow_origins=cfg.cors_origins_list)to use the new property. - Why v1.0.3 didn't work: in pydantic-settings 2.7.0,
NoDecodeis importable but_annotation_is_complex()doesn't actually check for it (only checks forJson). The marker was added to the package surface but the inner logic was only wired up in a later version. Switching to a plainstrfield is the most reliable fix and works across all pydantic-settings 2.x versions. - All 68 unit tests still pass.
[1.0.3] — 2026-06-20
Fixed
- app/config.py: fixed
SettingsError: error parsing value for field "cors_origins" from source "EnvSettingsSource"that crashed the backend on startup whenCORS_ORIGINSwas set as a comma-separated string (e.g.http://localhost:8080,http://localhost:5173,http://localhost).- Root cause: pydantic-settings v2 by default tries to JSON-parse complex-typed env vars before applying field validators. The comma-separated string isn't valid JSON, so parsing failed before our
@field_validator(mode="before")could split it. - Fix: declared
cors_origins: Annotated[list[str], NoDecode]—NoDecodeis a pydantic-settings marker that disables JSON pre-parsing, so the raw string reaches our_split_corsvalidator unchanged.
- Root cause: pydantic-settings v2 by default tries to JSON-parse complex-typed env vars before applying field validators. The comma-separated string isn't valid JSON, so parsing failed before our
- requirements.txt: bumped
pydantic2.7.1 → 2.9.2 andpydantic-settings2.2.1 → 2.7.0. TheNoDecodeannotation was only introduced in pydantic-settings 2.6+, so the older versions couldn't support the fix above. All 68 unit tests still pass with the new versions.
[1.0.2] — 2026-06-20
Fixed
- requirements.txt: added missing
email-validator==2.2.0dependency. Pydantic'sEmailStrtype (used inRegisterRequest,AdminRegisterRequest,LoginRequest.user.email,UserPublic.email,TokenResponse.user.email) requires this package at runtime, but it's not bundled with pydantic itself. Without it the backend crashed at startup withImportError: email-validator is not installed, run pip install pydantic[email]. Also removed a duplicatehttpx==0.27.0line.
[1.0.1] — 2026-06-20
Fixed
- docker-compose.yml: removed obsolete
version: "3.9"(caused warning in modern Docker Compose). - docker-compose.yml: fixed frontend build context — was
./frontend(brokeCOPY deploy/nginx.confandCOPY frontend/package*.jsonin Dockerfile.frontend). Now correctly.(project root) withdockerfile: deploy/Dockerfile.frontend. - docker-compose.yml: fixed frontend port mapping — was
5173:5173but the frontend container is nginx on port 80. Now8080:80so the app is accessible athttp://localhost:8080. - docker-compose.yml: added
extra_hosts: ["host.docker.internal:host-gateway"]to the backend service — enablesLLM_API_URL=http://host.docker.internal:11434/v1to work on Linux hosts (not just Docker Desktop on Mac/Windows). - docker-compose.yml: added
VITE_API_BASE_URL: /apibuild arg for the frontend service — bakes the relative/apiURL into the Vite bundle so the browser uses the same origin + nginx proxies/api→backend:8000. - deploy/Dockerfile.frontend: added
ARG VITE_API_BASE_URL=/api+ENVso the build arg actually gets baked into the Vite bundle. - deploy/nginx.conf: extended SSE timeouts from 300s → 600s; added
proxy_send_timeout; addedUpgrade/Connectionheaders for future WebSocket support; added gzip for static assets. - .env.example: clarified
VITE_API_BASE_URL— only used by localnpm run dev, ignored by docker build (which uses/apirelative). Removed the misleadinghttp://localhost/apidefault. - frontend/src/lib/api.ts: now respects
VITE_API_BASE_URLenv var with fallback to relative/api. Works both for local dev (point at separate backend) and Docker (nginx proxy). - frontend/src/vite-env.d.ts: added Vite env type declarations so TypeScript knows about
import.meta.env.VITE_API_BASE_URL. - app/api/deps.py: added
?access_token=<jwt>query parameter fallback for SSE endpoints. NativeEventSourcecannot sendAuthorizationheaders, so the frontend SSE client passes the token via query string. Without this fix, all SSE endpoints (/iterate/stream,/builder/stream,/editor/stream) returned 401. - .dockerignore: added at project root — excludes
node_modules/,__pycache__/,.venv/,data/,.git/, etc. from Docker build contexts (faster builds, smaller context transfer).
[1.0.0] — 2026-06-20
Added — Sprint 1: Foundation
- docker-compose with 4 services: db (PostgreSQL 15), qdrant (1.9), backend (FastAPI/uvicorn), frontend (Vite dev / nginx prod).
- SQLAlchemy 2.x async models for all 10 tables:
users,settings,world_presets,worlds,entities,steps,step_tool_calls,deferred_triggers,story_entries,llm_call_logs. - Alembic-equivalent idempotent migration
001_initial_schema.py(creates all tables, no pgvector). init_qdrant.pycreates collectionsentitiesandstory_entrieswith payload indexes onworld_id,entity_type,entry_type,deleted,created_at.- Settings seed: 33 default keys (llm., embeddings., context., qdrant., game., ui., admin.setup_token).
- 2 builtin presets: Classic Fantasy, Deep Space Outpost.
GET /api/healthreturns{status, db, qdrant, llm, embeddings, version}.
Added — Sprint 2: Auth + Admin
POST /api/register(only if at least one admin exists),POST /api/register/admin?token=,POST /api/auth/login(email or username),POST /api/auth/refresh,POST /api/auth/logout,GET /api/auth/me.- JWT (access + refresh) with python-jose, password hashing with passlib[bcrypt].
- Password strength validation: ≥8 chars, ≥1 letter, ≥1 digit, blacklist of trivial passwords.
- Anti-enumeration: same
401 invalid_credentialsfor "user not found" and "wrong password". - Admin setup token: auto-generated on first startup, printed in logs.
GET/PATCH /api/admin/settingswith secret masking.GET /api/admin/llm-logswith filters (world_id, stage, status, pagination) + detail view.GET /api/admin/users,PATCH /api/admin/users/{id}(admin/active toggle).GET /api/admin/stats(users, worlds, steps, avg LLM latency).- Diagnostic endpoints:
POST /api/admin/test/llm,/test/llm-tools,/test/embeddings,/test/embeddings/probe-dimension,/embeddings/recreate-collections. POST /api/admin/upload-icon(multipart, favicon/logo/og_image, ≤1MB, PNG/SVG/JPG/WebP/ICO).
Added — Sprint 3: World Builder
LlmClient— OpenAI-compatible chat completions client with retry (3 attempts, exp backoff), streaming, tool-calling, separate-transaction logging.MockLlmClient— replay-based mock for tests/dev when no LLM configured.app/prompts/— 11 stage prompts in English (world_builder_schema/env/entities, world_editor, orchestrator_phase1/2/3_summary/3_suggest, intro_scene, subagent, summary).POST /api/worldscreates draft world + returns SSE URL.GET /api/sessions/worlds/{id}/builder/streamruns the 4-step builder flow: schemas → environment → entities (tool-calling loop) → intro scene (with submit_step + suggest_actions).- If preset_id provided, schemas/environment copied from preset (skip first LLM call).
Added — Sprint 4: World Editor
POST /api/worlds/{id}/editbody{instruction}→ SSE URL.GET /api/sessions/worlds/{id}/editor/stream?instruction=runs LLM with world_editor tools.propose_changestool returns diff,ask_useremits clarification event,comment_to_userfor chat.- Optimistic locking via
updated_atonPATCH /api/worlds/{id}(409 state_conflict on mismatch). PATCH /api/worlds/{id}for direct JSON edits.DELETE /api/worlds/{id}soft-delete (status='archived').
Added — Sprint 5: Orchestrator
POST /api/sessions/worlds/{id}/iteratebody{action, action_source}creates new step + returns SSE URL.GET /api/sessions/worlds/{id}/iterate/stream?step_id=runs the 3-phase orchestrator:- Phase 1: tool-calling loop with
submit_planterminator, max 8 substeps (configurable). - Phase 2: writer LLM call with
submit_step, streamsscene_chunkevents. - Phase 3: persist + deferred triggers + summary (if history > threshold) + suggest_actions.
- Phase 1: tool-calling loop with
POST /api/sessions/worlds/{id}/retrysoft-deletes last step + creates new one with same action.POST /api/sessions/worlds/{id}/rollbacksoft-deletes last step.- 17 game tools: entity_create/get/list/update/delete, env_update/get, update_plot_rails, advance_time, schedule_trigger, calc (with dice), random_choice (deterministic), rag_query/add, run_subagent, submit_plan, submit_step, suggest_actions.
- 4 schema tools: schema_add_type/add_field/remove_field/modify_field.
Added — Sprint 6: Frontend polish
- React 18 + Vite 5 + TypeScript 5 + Tailwind CSS 3 + zustand 4 + react-i18next 14.
- Dark theme by default (Tailwind
dark:class on<html>). - 8 pages: Login, Register, AdminRegister, WorldsList, WorldBuilder, WorldEdit, Play, Admin.
- 23 components: 9 UI primitives, 5 session, 3 worlds, 6 admin.
- 5 zustand stores: auth, ui, worlds, session, toast.
- i18n bundles in English + Russian (complete).
- Native EventSource SSE client with reconnect + Last-Event-ID.
- Mobile-first responsive layout.
- TypeScript strict mode, 0 type errors,
npm run buildsucceeds (340 KB JS / 22 KB CSS).
Added — Sprint 7: RAG + Triggers + Summary + Context
app/core/rag.py— two-stage retrieval: Qdrant vector search → PostgreSQL hydration.HashEmbedder(offline, deterministic) andOpenAIEmbedder(OpenAI-compatible API) implementations.- Embedder provider selection via
embeddings.providersetting (offline_hash|openai). - Auto-fallback:
embeddings.api_urlfalls back tollm.api_urlif empty (and same for api_key). - Embedder cache with
reset_embedder_cache()(called on settings update). rag_queryreturns empty list on embedder/Qdrant failure (non-blocking).rag_addsaves story entry withembedding_status='pending'if embedding fails (background indexer can retry).index_entityhelper for entity vectors.- World isolation via Qdrant payload filter
world_id. _cleanup_qdrant(world_id)deletes all points for a world on world delete (best-effort).- Deferred triggers: Phase 3.1 fires triggers where
fire_at <= current_time, appends summary to scene_text, marksis_fired=true. - Summary: Phase 3.2 generates summary when
len(recent_steps) > compression_threshold_messages, stores as StoryEntry withmetadata.type=summary. - Context manager: builds messages with system prompt + optional summary + last N guaranteed messages + current action.
Added — Sprint 8: Production
README.mdwith quickstart, architecture diagram, API overview, testing instructions.pytest.ini+ 65+ unit tests across 8 test files.CHANGELOG.md(this file).docker-compose.ymlwith healthchecks for db and qdrant.deploy/Dockerfile.backend,deploy/Dockerfile.frontend,deploy/nginx.conf..env.examplewith all 24 env vars documented..gitignorefor Python, Node, env, IDE, data dirs.- Frontend production build verified (
npm run buildproducesdist/).