Spaces:
Runtime error
Runtime error
File size: 4,317 Bytes
b040061 37a8fe3 b040061 | 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 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 | 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
@asynccontextmanager
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)
@app.get("/api/run")
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
@app.post("/api/tts")
async def tts(req: TTSRequest):
audio_bytes = generate_speech(req.text)
return Response(content=audio_bytes, media_type="audio/wav")
@app.post("/api/chat")
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())
@app.get("/api/project/{project_id}/sources")
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,
}
@app.get("/api/health")
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")
@app.get("/{full_path:path}")
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")
|