File size: 3,500 Bytes
c44fab6
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
0bb4dfa
c44fab6
0bb4dfa
 
c44fab6
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
6a3a7e1
 
 
 
 
c44fab6
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
0bb4dfa
c44fab6
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
0bb4dfa
c44fab6
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
from __future__ import annotations

import logging
import os
import secrets
from contextlib import asynccontextmanager
from pathlib import Path

from fastapi import FastAPI, Request
from fastapi.middleware.cors import CORSMiddleware
from fastapi.responses import JSONResponse
from starlette.middleware.sessions import SessionMiddleware

from app.config import settings
from app.clients.hana_client import hana_client
from app.clients.openai_compat import close_shared_client
from app.api import models, chat, personas
from app.middleware.rate_limit import (
    DAILY_LIMIT, get_oauth_username, is_org_member, get_remaining,
    check_rate_limit, record_conversation,
)

logging.basicConfig(level=logging.INFO)
LOG = logging.getLogger(__name__)

STATIC_DIR = Path(__file__).resolve().parent.parent / "static"


@asynccontextmanager
async def lifespan(app: FastAPI):
    LOG.info("Starting up — authenticating with HANA...")
    try:
        await hana_client.authenticate()
        await hana_client.get_models()
        LOG.info("HANA auth complete, persona cache populated.")
    except Exception as exc:
        LOG.warning("HANA auth failed (Neon models will be unavailable): %s", exc)
    yield
    LOG.info("Shutting down — closing HTTP clients...")
    await hana_client.close()
    await close_shared_client()


app = FastAPI(
    title="Collaborative Conversational AI (CCAI) Demo",
    version="1.0.0",
    lifespan=lifespan,
)

app.add_middleware(
    SessionMiddleware,
    secret_key=os.getenv("SESSION_SECRET", secrets.token_hex(32)),
)

app.add_middleware(
    CORSMiddleware,
    allow_origins=["*"],
    allow_credentials=True,
    allow_methods=["*"],
    allow_headers=["*"],
)

try:
    from huggingface_hub import attach_huggingface_oauth
    attach_huggingface_oauth(app)
    LOG.info("HuggingFace OAuth endpoints registered.")
except Exception as exc:
    LOG.warning("HF OAuth not available (local dev mode): %s", exc)


app.include_router(models.router, prefix="/api")
app.include_router(chat.router, prefix="/api")
app.include_router(personas.router, prefix="/api")


@app.get("/api/health")
async def health():
    return {"status": "ok"}


@app.get("/api/auth/status")
async def auth_status(request: Request):
    username = get_oauth_username(request)
    if username is None:
        remaining = get_remaining(request)
        return {
            "logged_in": False,
            "username": None,
            "is_org_member": False,
            "remaining_conversations": remaining,
        }
    member = is_org_member(request)
    remaining = -1 if member else get_remaining(request)
    return {
        "logged_in": True,
        "username": username,
        "is_org_member": member,
        "remaining_conversations": remaining,
    }


@app.get("/api/rate-limit/status")
async def rate_limit_status(request: Request):
    remaining = get_remaining(request)
    return {"remaining": remaining, "daily_limit": DAILY_LIMIT}


if STATIC_DIR.is_dir():
    from starlette.staticfiles import StaticFiles
    from starlette.responses import FileResponse

    @app.get("/{full_path:path}")
    async def serve_spa(request: Request, full_path: str):
        file_path = STATIC_DIR / full_path
        if file_path.is_file():
            return FileResponse(file_path)
        return FileResponse(STATIC_DIR / "index.html")

    LOG.info("Serving frontend from %s", STATIC_DIR)
else:
    LOG.info("No static directory found at %s — frontend not served.", STATIC_DIR)