Spaces:
Runtime error
Runtime error
Download backend/main.py from luli331/oppy_google: direct link, hf CLI and curl.
- Browser
- Download file 4.32 kB
-
https://huggingface.co/spaces/luli331/oppy_google/resolve/main/backend/main.py
- Command line
-
hf download hf://spaces/luli331/oppy_google/backend/main.py
-
curl -L -o main.py https://huggingface.co/spaces/luli331/oppy_google/resolve/main/backend/main.py
4.32 kB
| import asyncio | |
| import json | |
| import os | |
| from contextlib import asynccontextmanager | |
| from dotenv import load_dotenv | |
| from pathlib import Path | |
| from fastapi import FastAPI | |
| from fastapi.middleware.cors import CORSMiddleware | |
| from fastapi.responses import FileResponse, Response | |
| from fastapi.staticfiles import StaticFiles | |
| from pydantic import BaseModel | |
| from sse_starlette.sse import EventSourceResponse | |
| from backend.agent import chat_with_operator, create_chat_session, run_operator | |
| from backend.mock_data import MOCK_EMAILS, MOCK_EVENTS, MOCK_SEARCH | |
| from backend.tts import generate_speech | |
| from backend.urgency import load_model | |
| load_dotenv() | |
| # Fail fast if API key is missing | |
| api_key = os.getenv("GOOGLE_API_KEY") | |
| if not api_key or api_key == "your_key_here": | |
| raise RuntimeError("GOOGLE_API_KEY not set. Check backend/.env") | |
| # Persistent chat session (created on first /api/chat call) | |
| _chat_client = None | |
| _chat_session = None | |
| def get_chat_session(): | |
| global _chat_client, _chat_session | |
| if _chat_session is None: | |
| config = load_config() | |
| _chat_client, _chat_session = create_chat_session(config["projects"]) | |
| return _chat_session | |
| async def lifespan(app): | |
| load_model() | |
| yield | |
| app = FastAPI(title="Oppy", lifespan=lifespan) | |
| app.add_middleware( | |
| CORSMiddleware, | |
| allow_origins=["*"], | |
| allow_credentials=True, | |
| allow_methods=["*"], | |
| allow_headers=["*"], | |
| ) | |
| def load_config(): | |
| from pathlib import Path | |
| config_path = Path(__file__).parent / "config.json" | |
| with open(config_path) as f: | |
| return json.load(f) | |
| async def run(): | |
| config = load_config() | |
| queue = asyncio.Queue() | |
| async def on_event(event: dict): | |
| await queue.put(event) | |
| async def generator(): | |
| task = asyncio.create_task(run_operator(config["projects"], on_event)) | |
| while True: | |
| try: | |
| event = await asyncio.wait_for(queue.get(), timeout=1.0) | |
| yield {"event": event["type"], "data": json.dumps(event, ensure_ascii=False)} | |
| if event["type"] == "brief": | |
| break | |
| except asyncio.TimeoutError: | |
| if task.done(): | |
| break | |
| continue | |
| await task | |
| return EventSourceResponse(generator()) | |
| class TTSRequest(BaseModel): | |
| text: str | |
| class ChatRequest(BaseModel): | |
| message: str | |
| async def tts(req: TTSRequest): | |
| audio_bytes = generate_speech(req.text) | |
| return Response(content=audio_bytes, media_type="audio/wav") | |
| async def chat(req: ChatRequest): | |
| session = get_chat_session() | |
| queue = asyncio.Queue() | |
| async def on_event(event: dict): | |
| await queue.put(event) | |
| async def generator(): | |
| task = asyncio.create_task( | |
| chat_with_operator(req.message, session, on_event) | |
| ) | |
| while True: | |
| try: | |
| event = await asyncio.wait_for(queue.get(), timeout=1.0) | |
| yield {"event": event["type"], "data": json.dumps(event, ensure_ascii=False)} | |
| if event["type"] == "chat_reply": | |
| break | |
| except asyncio.TimeoutError: | |
| if task.done(): | |
| break | |
| continue | |
| await task | |
| return EventSourceResponse(generator()) | |
| async def project_sources(project_id: str): | |
| emails = MOCK_EMAILS.get(project_id, []) | |
| events = MOCK_EVENTS.get(project_id, []) | |
| search = MOCK_SEARCH.get(project_id, "") | |
| return { | |
| "emails": emails, | |
| "events": events, | |
| "search": search, | |
| } | |
| async def health(): | |
| return {"status": "ok"} | |
| # --- Serve frontend static files (for Replit / production) --- | |
| _static_dir = Path(__file__).parent / "static" | |
| if _static_dir.exists(): | |
| app.mount("/assets", StaticFiles(directory=_static_dir / "assets"), name="assets") | |
| async def serve_spa(full_path: str): | |
| """Serve the React SPA for any non-API route.""" | |
| file_path = _static_dir / full_path | |
| if file_path.is_file(): | |
| return FileResponse(file_path) | |
| return FileResponse(_static_dir / "index.html") | |