CoolFace
Apppublic

luli331/oppy_google

sourceHugging Facemitupdated 6mo agoView on Hugging Face
0likes
main.py165 linesDownload Raw Back to backend
1import asyncio2import json3import os4from contextlib import asynccontextmanager5 6from dotenv import load_dotenv7from pathlib import Path8 9from fastapi import FastAPI10from fastapi.middleware.cors import CORSMiddleware11from fastapi.responses import FileResponse, Response12from fastapi.staticfiles import StaticFiles13from pydantic import BaseModel14from sse_starlette.sse import EventSourceResponse15 16from backend.agent import chat_with_operator, create_chat_session, run_operator17from backend.mock_data import MOCK_EMAILS, MOCK_EVENTS, MOCK_SEARCH18from backend.tts import generate_speech19from backend.urgency import load_model20 21load_dotenv()22 23# Fail fast if API key is missing24api_key = os.getenv("GOOGLE_API_KEY")25if not api_key or api_key == "your_key_here":26    raise RuntimeError("GOOGLE_API_KEY not set. Check backend/.env")27 28 29# Persistent chat session (created on first /api/chat call)30_chat_client = None31_chat_session = None32 33 34def get_chat_session():35    global _chat_client, _chat_session36    if _chat_session is None:37        config = load_config()38        _chat_client, _chat_session = create_chat_session(config["projects"])39    return _chat_session40 41 42@asynccontextmanager43async def lifespan(app):44    load_model()45    yield46 47 48app = FastAPI(title="Oppy", lifespan=lifespan)49 50app.add_middleware(51    CORSMiddleware,52    allow_origins=["*"],53    allow_credentials=True,54    allow_methods=["*"],55    allow_headers=["*"],56)57 58 59def load_config():60    from pathlib import Path61    config_path = Path(__file__).parent / "config.json"62    with open(config_path) as f:63        return json.load(f)64 65 66@app.get("/api/run")67async def run():68    config = load_config()69    queue = asyncio.Queue()70 71    async def on_event(event: dict):72        await queue.put(event)73 74    async def generator():75        task = asyncio.create_task(run_operator(config["projects"], on_event))76 77        while True:78            try:79                event = await asyncio.wait_for(queue.get(), timeout=1.0)80                yield {"event": event["type"], "data": json.dumps(event, ensure_ascii=False)}81                if event["type"] == "brief":82                    break83            except asyncio.TimeoutError:84                if task.done():85                    break86                continue87 88        await task89 90    return EventSourceResponse(generator())91 92 93class TTSRequest(BaseModel):94    text: str95 96 97class ChatRequest(BaseModel):98    message: str99 100 101@app.post("/api/tts")102async def tts(req: TTSRequest):103    audio_bytes = generate_speech(req.text)104    return Response(content=audio_bytes, media_type="audio/wav")105 106 107@app.post("/api/chat")108async def chat(req: ChatRequest):109    session = get_chat_session()110    queue = asyncio.Queue()111 112    async def on_event(event: dict):113        await queue.put(event)114 115    async def generator():116        task = asyncio.create_task(117            chat_with_operator(req.message, session, on_event)118        )119 120        while True:121            try:122                event = await asyncio.wait_for(queue.get(), timeout=1.0)123                yield {"event": event["type"], "data": json.dumps(event, ensure_ascii=False)}124                if event["type"] == "chat_reply":125                    break126            except asyncio.TimeoutError:127                if task.done():128                    break129                continue130 131        await task132 133    return EventSourceResponse(generator())134 135 136@app.get("/api/project/{project_id}/sources")137async def project_sources(project_id: str):138    emails = MOCK_EMAILS.get(project_id, [])139    events = MOCK_EVENTS.get(project_id, [])140    search = MOCK_SEARCH.get(project_id, "")141    return {142        "emails": emails,143        "events": events,144        "search": search,145    }146 147 148@app.get("/api/health")149async def health():150    return {"status": "ok"}151 152 153# --- Serve frontend static files (for Replit / production) ---154_static_dir = Path(__file__).parent / "static"155if _static_dir.exists():156    app.mount("/assets", StaticFiles(directory=_static_dir / "assets"), name="assets")157 158    @app.get("/{full_path:path}")159    async def serve_spa(full_path: str):160        """Serve the React SPA for any non-API route."""161        file_path = _static_dir / full_path162        if file_path.is_file():163            return FileResponse(file_path)164        return FileResponse(_static_dir / "index.html")165