rebase
This commit is contained in:
50
.dockerignore
Normal file
50
.dockerignore
Normal file
@@ -0,0 +1,50 @@
|
|||||||
|
# Python
|
||||||
|
__pycache__/
|
||||||
|
**/*.pyc
|
||||||
|
**/*.pyo
|
||||||
|
**/.pytest_cache/
|
||||||
|
**/.ruff_cache/
|
||||||
|
**/.mypy_cache/
|
||||||
|
.venv/
|
||||||
|
venv/
|
||||||
|
*.egg-info/
|
||||||
|
.coverage
|
||||||
|
.coverage.*
|
||||||
|
htmlcov/
|
||||||
|
|
||||||
|
# Node / Frontend
|
||||||
|
**/node_modules/
|
||||||
|
frontend/dist/
|
||||||
|
frontend/.vite/
|
||||||
|
npm-debug.log*
|
||||||
|
yarn-*
|
||||||
|
|
||||||
|
# Environment & secrets
|
||||||
|
.env
|
||||||
|
.env.local
|
||||||
|
.env.*.local
|
||||||
|
|
||||||
|
# Data & uploads (kept on volumes, not in image)
|
||||||
|
data/
|
||||||
|
!data/.gitkeep
|
||||||
|
*.db
|
||||||
|
*.sqlite3
|
||||||
|
|
||||||
|
# IDE / OS
|
||||||
|
.vscode/
|
||||||
|
.idea/
|
||||||
|
*.swp
|
||||||
|
.DS_Store
|
||||||
|
Thumbs.db
|
||||||
|
|
||||||
|
# Logs
|
||||||
|
*.log
|
||||||
|
logs/
|
||||||
|
|
||||||
|
# Tests artifacts
|
||||||
|
.pytest_cache/
|
||||||
|
tests/.cache/
|
||||||
|
|
||||||
|
# Git
|
||||||
|
.git/
|
||||||
|
.gitignore
|
||||||
40
.env.example
40
.env.example
@@ -1,38 +1,62 @@
|
|||||||
|
# ============================================================
|
||||||
|
# AI-RPG environment configuration
|
||||||
|
# Copy to .env and edit before running `docker compose up`.
|
||||||
|
# ============================================================
|
||||||
|
|
||||||
# === Database ===
|
# === Database ===
|
||||||
POSTGRES_USER=airpg
|
POSTGRES_USER=airpg
|
||||||
POSTGRES_PASSWORD=airpg
|
POSTGRES_PASSWORD=airpg
|
||||||
POSTGRES_DB=airpg
|
POSTGRES_DB=airpg
|
||||||
DATABASE_URL=postgresql+asyncpg://airpg:airpg@db:5432/airpg
|
# Used by backend when running OUTSIDE docker (local dev). Inside docker-compose
|
||||||
DATABASE_URL_SYNC=postgresql+psycopg2://airpg:airpg@db:5432/airpg
|
# this is overridden in docker-compose.yml to point at the `db` service.
|
||||||
|
DATABASE_URL=postgresql+asyncpg://airpg:airpg@localhost:5432/airpg
|
||||||
|
DATABASE_URL_SYNC=postgresql+psycopg2://airpg:airpg@localhost:5432/airpg
|
||||||
|
|
||||||
# === Qdrant ===
|
# === Qdrant ===
|
||||||
QDRANT_URL=http://qdrant:6333
|
QDRANT_URL=http://localhost:6333
|
||||||
QDRANT_API_KEY=
|
QDRANT_API_KEY=
|
||||||
|
|
||||||
# === LLM ===
|
# === LLM (OpenAI-compatible) ===
|
||||||
|
# Examples:
|
||||||
|
# OpenAI: https://api.openai.com/v1
|
||||||
|
# Ollama (host): http://host.docker.internal:11434/v1 ← for docker
|
||||||
|
# Ollama (local): http://localhost:11434/v1 ← for non-docker dev
|
||||||
|
# vLLM: http://localhost:8000/v1
|
||||||
|
# Leave empty to use MockLlmClient (replay-based, for dev/tests).
|
||||||
LLM_API_URL=http://host.docker.internal:11434/v1
|
LLM_API_URL=http://host.docker.internal:11434/v1
|
||||||
LLM_API_KEY=
|
LLM_API_KEY=
|
||||||
LLM_MODEL=qwen2.5-7b-instruct
|
LLM_MODEL=qwen2.5-7b-instruct
|
||||||
LLM_TIMEOUT_SECONDS=60
|
LLM_TIMEOUT_SECONDS=60
|
||||||
|
|
||||||
# === Embeddings ===
|
# === Embeddings ===
|
||||||
|
# Provider: "offline_hash" (deterministic, dev-only, no HTTP) or "openai"
|
||||||
EMBEDDINGS_PROVIDER=offline_hash
|
EMBEDDINGS_PROVIDER=offline_hash
|
||||||
|
# If provider=openai and these are empty, falls back to LLM_API_URL / LLM_API_KEY
|
||||||
EMBEDDINGS_API_URL=
|
EMBEDDINGS_API_URL=
|
||||||
EMBEDDINGS_API_KEY=
|
EMBEDDINGS_API_KEY=
|
||||||
EMBEDDINGS_MODEL=text-embedding-3-small
|
EMBEDDINGS_MODEL=text-embedding-3-small
|
||||||
|
# For offline_hash: 256. For openai text-embedding-3-small: 1536.
|
||||||
|
# Use admin "Probe dimension" button to auto-detect.
|
||||||
EMBEDDINGS_DIMENSION=256
|
EMBEDDINGS_DIMENSION=256
|
||||||
|
|
||||||
# === Application ===
|
# === Application ===
|
||||||
SECRET_KEY=change-me-in-production-32-bytes-long-min
|
# Generate with: python -c "import secrets; print(secrets.token_urlsafe(48))"
|
||||||
|
SECRET_KEY=change-me-to-a-random-32-byte-string-min
|
||||||
DEBUG=false
|
DEBUG=false
|
||||||
LOG_LEVEL=INFO
|
LOG_LEVEL=INFO
|
||||||
CORS_ORIGINS=http://localhost,http://localhost:5173
|
# Comma-separated list of allowed CORS origins (or * for any)
|
||||||
|
CORS_ORIGINS=http://localhost:8080,http://localhost:5173,http://localhost
|
||||||
|
|
||||||
# === Admin setup ===
|
# === Admin setup ===
|
||||||
|
# Leave empty to auto-generate on first startup (will be printed in backend logs).
|
||||||
|
# Or set explicitly to control the URL: /register/admin?token=<ADMIN_SETUP_TOKEN>
|
||||||
ADMIN_SETUP_TOKEN=
|
ADMIN_SETUP_TOKEN=
|
||||||
|
|
||||||
# === Storage ===
|
# === Storage ===
|
||||||
DATA_DIR=/app/data
|
DATA_DIR=/app/data
|
||||||
|
|
||||||
# === Frontend (Vite dev) ===
|
# === Frontend (Vite — ONLY used by `npm run dev`, ignored by docker build) ===
|
||||||
VITE_API_BASE_URL=http://localhost/api
|
# The docker build bakes "/api" (relative) into the bundle so the browser
|
||||||
|
# uses the same origin + nginx proxies /api → backend:8000.
|
||||||
|
# For local `npm run dev` (outside docker), set this to the backend URL.
|
||||||
|
VITE_API_BASE_URL=http://localhost:8000/api
|
||||||
|
|||||||
16
CHANGELOG.md
16
CHANGELOG.md
@@ -4,6 +4,22 @@ All notable changes to AI-RPG are documented here.
|
|||||||
The format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
|
The format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
|
||||||
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
||||||
|
|
||||||
|
## [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` (broke `COPY deploy/nginx.conf` and `COPY frontend/package*.json` in Dockerfile.frontend). Now correctly `.` (project root) with `dockerfile: deploy/Dockerfile.frontend`.
|
||||||
|
- **docker-compose.yml**: fixed frontend port mapping — was `5173:5173` but the frontend container is nginx on port 80. Now `8080:80` so the app is accessible at `http://localhost:8080`.
|
||||||
|
- **docker-compose.yml**: added `extra_hosts: ["host.docker.internal:host-gateway"]` to the backend service — enables `LLM_API_URL=http://host.docker.internal:11434/v1` to work on Linux hosts (not just Docker Desktop on Mac/Windows).
|
||||||
|
- **docker-compose.yml**: added `VITE_API_BASE_URL: /api` build arg for the frontend service — bakes the relative `/api` URL 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` + `ENV` so the build arg actually gets baked into the Vite bundle.
|
||||||
|
- **deploy/nginx.conf**: extended SSE timeouts from 300s → 600s; added `proxy_send_timeout`; added `Upgrade`/`Connection` headers for future WebSocket support; added gzip for static assets.
|
||||||
|
- **.env.example**: clarified `VITE_API_BASE_URL` — only used by local `npm run dev`, ignored by docker build (which uses `/api` relative). Removed the misleading `http://localhost/api` default.
|
||||||
|
- **frontend/src/lib/api.ts**: now respects `VITE_API_BASE_URL` env 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. Native `EventSource` cannot send `Authorization` headers, 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
|
## [1.0.0] — 2026-06-20
|
||||||
|
|
||||||
### Added — Sprint 1: Foundation
|
### Added — Sprint 1: Foundation
|
||||||
|
|||||||
16
README.md
16
README.md
@@ -73,13 +73,15 @@ cp .env.example .env
|
|||||||
# Отредактируйте SECRET_KEY, ADMIN_SETUP_TOKEN, LLM_API_URL, LLM_API_KEY
|
# Отредактируйте SECRET_KEY, ADMIN_SETUP_TOKEN, LLM_API_URL, LLM_API_KEY
|
||||||
|
|
||||||
# 2. Поднять всё
|
# 2. Поднять всё
|
||||||
docker compose up -d
|
docker compose up -d --build
|
||||||
|
|
||||||
# 3. Зайти на http://localhost (frontend через nginx)
|
# 3. Открыть в браузере:
|
||||||
# Или http://localhost:5173 (frontend dev) / http://localhost:8000/api/docs (backend)
|
# - Frontend (nginx + React build): http://localhost:8080
|
||||||
|
# - Backend Swagger docs: http://localhost:8000/api/docs
|
||||||
|
# - Health-check: http://localhost:8000/api/health
|
||||||
```
|
```
|
||||||
|
|
||||||
При первом старте сервер напечатает в лог:
|
При первом старте backend напечатает в лог:
|
||||||
```
|
```
|
||||||
=== AI-RPG Admin Setup ===
|
=== AI-RPG Admin Setup ===
|
||||||
No admin user yet. Open this URL in your browser:
|
No admin user yet. Open this URL in your browser:
|
||||||
@@ -87,7 +89,11 @@ No admin user yet. Open this URL in your browser:
|
|||||||
===========================
|
===========================
|
||||||
```
|
```
|
||||||
|
|
||||||
Откройте `http://localhost/register/admin?token=<...>` и создайте первого админа.
|
Посмотреть лог: `docker compose logs backend | head -20`.
|
||||||
|
|
||||||
|
Откройте `http://localhost:8080/register/admin?token=<...>` и создайте первого админа.
|
||||||
|
|
||||||
|
> **Примечание про LLM:** если у вас Ollama на хосте, используйте `LLM_API_URL=http://host.docker.internal:11434/v1` — backend-контейнер автоматически резолвит `host.docker.internal` через `extra_hosts: host-gateway` (работает на Linux/macOS/Windows).
|
||||||
|
|
||||||
### Опция 2: локальный dev (backend + frontend раздельно)
|
### Опция 2: локальный dev (backend + frontend раздельно)
|
||||||
|
|
||||||
|
|||||||
@@ -1,11 +1,17 @@
|
|||||||
"""Shared API dependencies: current user, admin guard, db session, settings."""
|
"""Shared API dependencies: current user, admin guard, db session, settings.
|
||||||
|
|
||||||
|
Token resolution order:
|
||||||
|
1. `Authorization: Bearer <jwt>` header (preferred).
|
||||||
|
2. `?access_token=<jwt>` query parameter (fallback for SSE — EventSource cannot
|
||||||
|
set custom headers).
|
||||||
|
"""
|
||||||
|
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
import uuid
|
import uuid
|
||||||
from typing import Any
|
from typing import Any
|
||||||
|
|
||||||
from fastapi import Depends, HTTPException, status
|
from fastapi import Depends, HTTPException, Query, status
|
||||||
from fastapi.security import HTTPAuthorizationCredentials, HTTPBearer
|
from fastapi.security import HTTPAuthorizationCredentials, HTTPBearer
|
||||||
from sqlalchemy import select
|
from sqlalchemy import select
|
||||||
from sqlalchemy.ext.asyncio import AsyncSession
|
from sqlalchemy.ext.asyncio import AsyncSession
|
||||||
@@ -20,16 +26,29 @@ _bearer = HTTPBearer(auto_error=False)
|
|||||||
|
|
||||||
async def get_current_user(
|
async def get_current_user(
|
||||||
creds: HTTPAuthorizationCredentials | None = Depends(_bearer),
|
creds: HTTPAuthorizationCredentials | None = Depends(_bearer),
|
||||||
|
access_token: str | None = Query(default=None, description="JWT access token (fallback for SSE)"),
|
||||||
db: AsyncSession = Depends(get_db),
|
db: AsyncSession = Depends(get_db),
|
||||||
) -> User:
|
) -> User:
|
||||||
"""Resolve the JWT bearer token to a User row.
|
"""Resolve the JWT bearer token to a User row.
|
||||||
|
|
||||||
|
Accepts the token either in the `Authorization: Bearer <jwt>` header
|
||||||
|
or as the `?access_token=<jwt>` query parameter (the latter is needed
|
||||||
|
because native `EventSource` cannot send custom headers, and the
|
||||||
|
frontend SSE client passes the token via query string).
|
||||||
|
|
||||||
Raises 401 on missing/invalid/expired token.
|
Raises 401 on missing/invalid/expired token.
|
||||||
"""
|
"""
|
||||||
if creds is None or creds.scheme.lower() != "bearer":
|
raw_token: str | None = None
|
||||||
|
if creds is not None and creds.scheme.lower() == "bearer":
|
||||||
|
raw_token = creds.credentials
|
||||||
|
elif access_token:
|
||||||
|
raw_token = access_token
|
||||||
|
|
||||||
|
if not raw_token:
|
||||||
raise HTTPException(status.HTTP_401_UNAUTHORIZED, "Missing bearer token")
|
raise HTTPException(status.HTTP_401_UNAUTHORIZED, "Missing bearer token")
|
||||||
|
|
||||||
try:
|
try:
|
||||||
payload = decode_token(creds.credentials)
|
payload = decode_token(raw_token)
|
||||||
except Exception as e: # noqa: BLE001
|
except Exception as e: # noqa: BLE001
|
||||||
raise HTTPException(status.HTTP_401_UNAUTHORIZED, f"Invalid token: {e}")
|
raise HTTPException(status.HTTP_401_UNAUTHORIZED, f"Invalid token: {e}")
|
||||||
if payload.get("type") != "access":
|
if payload.get("type") != "access":
|
||||||
|
|||||||
@@ -13,10 +13,10 @@ RUN pip install --no-cache-dir -r requirements.txt
|
|||||||
|
|
||||||
COPY . .
|
COPY . .
|
||||||
|
|
||||||
# Create data directory
|
# Create data directory for assets / uploads
|
||||||
RUN mkdir -p /app/data/assets
|
RUN mkdir -p /app/data/assets
|
||||||
|
|
||||||
EXPOSE 8000
|
EXPOSE 8000
|
||||||
|
|
||||||
# Default: run uvicorn; in dev override with `npm run dev` equivalent
|
# Default: run uvicorn. In dev override via docker-compose volumes for hot reload.
|
||||||
CMD ["uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "8000", "--reload"]
|
CMD ["uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "8000"]
|
||||||
|
|||||||
@@ -1,14 +1,32 @@
|
|||||||
# Build stage
|
# Build stage
|
||||||
FROM node:20-alpine AS build
|
FROM node:20-alpine AS build
|
||||||
WORKDIR /app
|
WORKDIR /app
|
||||||
|
|
||||||
|
# Bake the API base URL into the Vite bundle at build time.
|
||||||
|
# Default is the relative "/api" — works when the app is served from the same
|
||||||
|
# origin as the /api reverse proxy (nginx in front of the backend).
|
||||||
|
ARG VITE_API_BASE_URL=/api
|
||||||
|
ENV VITE_API_BASE_URL=$VITE_API_BASE_URL
|
||||||
|
|
||||||
|
# Copy frontend manifest first for better layer caching
|
||||||
COPY frontend/package*.json ./
|
COPY frontend/package*.json ./
|
||||||
RUN npm install
|
RUN npm install
|
||||||
|
|
||||||
|
# Copy the rest of the frontend source and build
|
||||||
COPY frontend/ .
|
COPY frontend/ .
|
||||||
RUN npm run build
|
RUN npm run build
|
||||||
|
|
||||||
# Serve stage
|
# -------------------------------------------------------------------
|
||||||
|
# Serve stage — nginx
|
||||||
|
# -------------------------------------------------------------------
|
||||||
FROM nginx:1.24-alpine
|
FROM nginx:1.24-alpine
|
||||||
|
|
||||||
|
# Copy built static assets from the build stage
|
||||||
COPY --from=build /app/dist /usr/share/nginx/html
|
COPY --from=build /app/dist /usr/share/nginx/html
|
||||||
|
|
||||||
|
# Copy nginx config (path is relative to the build context, which is the project root)
|
||||||
COPY deploy/nginx.conf /etc/nginx/conf.d/default.conf
|
COPY deploy/nginx.conf /etc/nginx/conf.d/default.conf
|
||||||
|
|
||||||
EXPOSE 80
|
EXPOSE 80
|
||||||
|
|
||||||
CMD ["nginx", "-g", "daemon off;"]
|
CMD ["nginx", "-g", "daemon off;"]
|
||||||
|
|||||||
@@ -5,12 +5,17 @@ server {
|
|||||||
root /usr/share/nginx/html;
|
root /usr/share/nginx/html;
|
||||||
index index.html;
|
index index.html;
|
||||||
|
|
||||||
# SPA fallback
|
# Gzip for text assets
|
||||||
|
gzip on;
|
||||||
|
gzip_types text/plain text/css application/javascript application/json image/svg+xml;
|
||||||
|
gzip_min_length 1024;
|
||||||
|
|
||||||
|
# SPA fallback — must be before /api/ location
|
||||||
location / {
|
location / {
|
||||||
try_files $uri $uri/ /index.html;
|
try_files $uri $uri/ /index.html;
|
||||||
}
|
}
|
||||||
|
|
||||||
# API + SSE
|
# API + SSE — proxy to backend container
|
||||||
location /api/ {
|
location /api/ {
|
||||||
proxy_pass http://backend:8000;
|
proxy_pass http://backend:8000;
|
||||||
proxy_http_version 1.1;
|
proxy_http_version 1.1;
|
||||||
@@ -19,14 +24,27 @@ server {
|
|||||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||||
proxy_set_header X-Forwarded-Proto $scheme;
|
proxy_set_header X-Forwarded-Proto $scheme;
|
||||||
|
|
||||||
# SSE: disable buffering
|
# SSE: disable buffering, extend timeouts for long-running streams
|
||||||
proxy_buffering off;
|
proxy_buffering off;
|
||||||
proxy_cache off;
|
proxy_cache off;
|
||||||
proxy_read_timeout 300s;
|
proxy_read_timeout 600s;
|
||||||
|
proxy_send_timeout 600s;
|
||||||
|
|
||||||
|
# WebSocket support (in case of future upgrade)
|
||||||
|
proxy_set_header Upgrade $http_upgrade;
|
||||||
|
proxy_set_header Connection "upgrade";
|
||||||
}
|
}
|
||||||
|
|
||||||
# Static assets (icons, uploads)
|
# Static assets (icons, uploads) served by backend
|
||||||
location /static/ {
|
location /static/ {
|
||||||
proxy_pass http://backend:8000;
|
proxy_pass http://backend:8000;
|
||||||
|
proxy_set_header Host $host;
|
||||||
|
proxy_set_header X-Real-IP $remote_addr;
|
||||||
|
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||||
|
}
|
||||||
|
|
||||||
|
# Health-check passthrough
|
||||||
|
location = /health {
|
||||||
|
proxy_pass http://backend:8000/api/health;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,3 @@
|
|||||||
version: "3.9"
|
|
||||||
|
|
||||||
services:
|
services:
|
||||||
db:
|
db:
|
||||||
image: postgres:15-alpine
|
image: postgres:15-alpine
|
||||||
@@ -39,8 +37,13 @@ services:
|
|||||||
env_file:
|
env_file:
|
||||||
- .env
|
- .env
|
||||||
environment:
|
environment:
|
||||||
|
# Override .env values with docker-compose service names
|
||||||
DATABASE_URL: postgresql+asyncpg://${POSTGRES_USER:-airpg}:${POSTGRES_PASSWORD:-airpg}@db:5432/${POSTGRES_DB:-airpg}
|
DATABASE_URL: postgresql+asyncpg://${POSTGRES_USER:-airpg}:${POSTGRES_PASSWORD:-airpg}@db:5432/${POSTGRES_DB:-airpg}
|
||||||
QDRANT_URL: http://qdrant:6333
|
QDRANT_URL: http://qdrant:6333
|
||||||
|
# Allow backend to reach host services (e.g. Ollama on host) via host.docker.internal
|
||||||
|
# On Linux this requires the extra_hosts mapping below.
|
||||||
|
extra_hosts:
|
||||||
|
- "host.docker.internal:host-gateway"
|
||||||
volumes:
|
volumes:
|
||||||
- ./data:/app/data
|
- ./data:/app/data
|
||||||
- ./app:/app/app
|
- ./app:/app/app
|
||||||
@@ -53,13 +56,18 @@ services:
|
|||||||
condition: service_healthy
|
condition: service_healthy
|
||||||
|
|
||||||
frontend:
|
frontend:
|
||||||
|
# nginx-served production build of the React app.
|
||||||
|
# Listens on container port 80, mapped to host port 8080.
|
||||||
|
# Access the app at http://localhost:8080
|
||||||
build:
|
build:
|
||||||
context: ./frontend
|
context: .
|
||||||
dockerfile: ../deploy/Dockerfile.frontend
|
dockerfile: deploy/Dockerfile.frontend
|
||||||
environment:
|
args:
|
||||||
|
# Bake the API base URL into the bundle at build time.
|
||||||
|
# Relative "/api" works because nginx proxies /api/* → backend:8000.
|
||||||
VITE_API_BASE_URL: /api
|
VITE_API_BASE_URL: /api
|
||||||
ports:
|
ports:
|
||||||
- "5173:5173"
|
- "8080:80"
|
||||||
depends_on:
|
depends_on:
|
||||||
- backend
|
- backend
|
||||||
|
|
||||||
|
|||||||
@@ -29,7 +29,10 @@ import type {
|
|||||||
WorldPreset,
|
WorldPreset,
|
||||||
} from "@/types";
|
} from "@/types";
|
||||||
|
|
||||||
const BASE_URL = "/api";
|
// Use VITE_API_BASE_URL if set (for local `npm run dev` pointing at a separate
|
||||||
|
// backend); otherwise default to relative "/api" which works behind the nginx
|
||||||
|
// reverse proxy in the Docker deployment.
|
||||||
|
const BASE_URL = (import.meta.env.VITE_API_BASE_URL as string | undefined)?.replace(/\/$/, "") || "/api";
|
||||||
const ACCESS_TOKEN_KEY = "airpg_access_token";
|
const ACCESS_TOKEN_KEY = "airpg_access_token";
|
||||||
const REFRESH_TOKEN_KEY = "airpg_refresh_token";
|
const REFRESH_TOKEN_KEY = "airpg_refresh_token";
|
||||||
|
|
||||||
|
|||||||
9
frontend/src/vite-env.d.ts
vendored
Normal file
9
frontend/src/vite-env.d.ts
vendored
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
/// <reference types="vite/client" />
|
||||||
|
|
||||||
|
interface ImportMetaEnv {
|
||||||
|
readonly VITE_API_BASE_URL?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface ImportMeta {
|
||||||
|
readonly env: ImportMetaEnv;
|
||||||
|
}
|
||||||
@@ -1 +1 @@
|
|||||||
{"root":["./src/App.tsx","./src/main.tsx","./src/components/admin/IconsPanel.tsx","./src/components/admin/LlmLogsTable.tsx","./src/components/admin/SettingsPanel.tsx","./src/components/admin/StatsPanel.tsx","./src/components/admin/TestButtons.tsx","./src/components/admin/UsersTable.tsx","./src/components/auth/ProtectedRoute.tsx","./src/components/sessions/ActionInput.tsx","./src/components/sessions/ChatView.tsx","./src/components/sessions/PhaseProgress.tsx","./src/components/sessions/SseStatus.tsx","./src/components/sessions/ToolCallBubble.tsx","./src/components/ui/Button.tsx","./src/components/ui/Card.tsx","./src/components/ui/Input.tsx","./src/components/ui/JsonEditor.tsx","./src/components/ui/Modal.tsx","./src/components/ui/Navbar.tsx","./src/components/ui/Spinner.tsx","./src/components/ui/Textarea.tsx","./src/components/ui/Toast.tsx","./src/components/worlds/WorldBuilder.tsx","./src/components/worlds/WorldCard.tsx","./src/components/worlds/WorldEditor.tsx","./src/i18n/index.ts","./src/lib/api.ts","./src/lib/cn.ts","./src/lib/sse.ts","./src/pages/AdminPage.tsx","./src/pages/AdminRegisterPage.tsx","./src/pages/LoginPage.tsx","./src/pages/PlayPage.tsx","./src/pages/RegisterPage.tsx","./src/pages/WorldBuilderPage.tsx","./src/pages/WorldEditPage.tsx","./src/pages/WorldsListPage.tsx","./src/stores/authStore.ts","./src/stores/sessionStore.ts","./src/stores/toastStore.ts","./src/stores/uiStore.ts","./src/stores/worldsStore.ts","./src/types/index.ts"],"version":"5.9.3"}
|
{"root":["./src/App.tsx","./src/main.tsx","./src/vite-env.d.ts","./src/components/admin/IconsPanel.tsx","./src/components/admin/LlmLogsTable.tsx","./src/components/admin/SettingsPanel.tsx","./src/components/admin/StatsPanel.tsx","./src/components/admin/TestButtons.tsx","./src/components/admin/UsersTable.tsx","./src/components/auth/ProtectedRoute.tsx","./src/components/sessions/ActionInput.tsx","./src/components/sessions/ChatView.tsx","./src/components/sessions/PhaseProgress.tsx","./src/components/sessions/SseStatus.tsx","./src/components/sessions/ToolCallBubble.tsx","./src/components/ui/Button.tsx","./src/components/ui/Card.tsx","./src/components/ui/Input.tsx","./src/components/ui/JsonEditor.tsx","./src/components/ui/Modal.tsx","./src/components/ui/Navbar.tsx","./src/components/ui/Spinner.tsx","./src/components/ui/Textarea.tsx","./src/components/ui/Toast.tsx","./src/components/worlds/WorldBuilder.tsx","./src/components/worlds/WorldCard.tsx","./src/components/worlds/WorldEditor.tsx","./src/i18n/index.ts","./src/lib/api.ts","./src/lib/cn.ts","./src/lib/sse.ts","./src/pages/AdminPage.tsx","./src/pages/AdminRegisterPage.tsx","./src/pages/LoginPage.tsx","./src/pages/PlayPage.tsx","./src/pages/RegisterPage.tsx","./src/pages/WorldBuilderPage.tsx","./src/pages/WorldEditPage.tsx","./src/pages/WorldsListPage.tsx","./src/stores/authStore.ts","./src/stores/sessionStore.ts","./src/stores/toastStore.ts","./src/stores/uiStore.ts","./src/stores/worldsStore.ts","./src/types/index.ts"],"version":"5.9.3"}
|
||||||
Reference in New Issue
Block a user