README / app /backend /main.py
CJGibs's picture
Build Aether Voice Studio Docker Space
703a33a
from __future__ import annotations
import json
import os
from pathlib import Path
from fastapi import FastAPI, HTTPException
from fastapi.middleware.cors import CORSMiddleware
from fastapi.responses import FileResponse
from fastapi.staticfiles import StaticFiles
from pydantic import BaseModel
ROOT_DIR = Path(__file__).resolve().parents[2]
DATA_DIR = ROOT_DIR / "app" / "data"
PUBLIC_DIR = ROOT_DIR / "app" / "public"
DIST_DIR = ROOT_DIR / "app" / "frontend" / "dist"
ALLOWED_DATA_FILES = {
"features.json",
"faq.json",
"route_model_matrix.json",
"docs_nav.json",
"voice_seed_library.json",
"workflow_examples.json",
"architecture_layers.json",
"site_manifest.json",
}
CONTRACT_FILES = {
"voice_registry": "contracts/voice_registry_shape.json",
"route_model_matrix": "contracts/route_model_matrix_shape.json",
"seed_voice_library": "contracts/seed_voice_library_shape.json",
"provider_config": "contracts/provider_config_shape.json",
}
def load_json(relative_path: str) -> dict | list:
with (DATA_DIR / relative_path).open("r", encoding="utf-8") as handle:
return json.load(handle)
class DemoRequest(BaseModel):
workflow_id: str
app = FastAPI(title="Aether Voice Studio Space API", version="1.0.0")
app.add_middleware(
CORSMiddleware,
allow_origins=["*"],
allow_credentials=False,
allow_methods=["*"],
allow_headers=["*"],
)
if DIST_DIR.exists():
assets_dir = DIST_DIR / "assets"
if assets_dir.exists():
app.mount("/assets", StaticFiles(directory=assets_dir), name="assets")
if PUBLIC_DIR.exists():
app.mount("/public", StaticFiles(directory=PUBLIC_DIR), name="public")
@app.get("/api/health")
def health() -> dict:
return {"ok": True, "demo_mode": os.getenv("DEMO_MODE", "true").lower() == "true"}
@app.get("/api/config")
def config() -> dict:
return {
"demoMode": os.getenv("DEMO_MODE", "true").lower() == "true",
"spaceTitle": "Aether Voice Studio",
"singlePort": int(os.getenv("PORT", "7860")),
}
@app.get("/api/content")
def content() -> dict:
return {
"site": load_json("site_manifest.json"),
"features": load_json("features.json"),
"routes": load_json("route_model_matrix.json"),
"docsNav": load_json("docs_nav.json"),
"faq": load_json("faq.json"),
"seedLibrary": load_json("voice_seed_library.json"),
"workflows": load_json("workflow_examples.json"),
"architecture": load_json("architecture_layers.json"),
}
@app.get("/api/data/{filename}")
def data_file(filename: str) -> dict | list:
if filename not in ALLOWED_DATA_FILES:
raise HTTPException(status_code=404, detail="Unknown data file")
return load_json(filename)
@app.get("/api/contracts")
def contracts() -> dict:
return {
name: {
"endpoint": f"/api/contracts/{name}",
"download": f"/api/contracts/{name}?download=true",
}
for name in CONTRACT_FILES
}
@app.get("/api/contracts/{contract_name}")
def contract_file(contract_name: str, download: bool = False):
relative_path = CONTRACT_FILES.get(contract_name)
if not relative_path:
raise HTTPException(status_code=404, detail="Unknown contract")
file_path = DATA_DIR / relative_path
if download:
return FileResponse(file_path, media_type="application/json", filename=file_path.name)
return load_json(relative_path)
@app.post("/api/demo/simulate")
def simulate_demo(request: DemoRequest) -> dict:
payloads = load_json("demo_payloads.json")
workflow_id = request.workflow_id
selected = payloads.get(workflow_id)
if selected is None:
raise HTTPException(status_code=404, detail="Unknown workflow")
return {
"demoMode": os.getenv("DEMO_MODE", "true").lower() == "true",
"workflowId": workflow_id,
"result": selected,
}
@app.get("/{full_path:path}")
def spa_fallback(full_path: str):
requested = DIST_DIR / full_path
if full_path and requested.exists() and requested.is_file():
return FileResponse(requested)
index_file = DIST_DIR / "index.html"
if index_file.exists():
return FileResponse(index_file)
raise HTTPException(status_code=503, detail="Frontend build is unavailable")