Add SSE endpoint and dream notify — /api/events and /api/events/notify
This commit is contained in:
@@ -26,6 +26,8 @@ from fastapi.responses import FileResponse, JSONResponse
|
||||
from fastapi.staticfiles import StaticFiles
|
||||
from fastapi.middleware.cors import CORSMiddleware
|
||||
import uvicorn
|
||||
import asyncio
|
||||
from fastapi.responses import StreamingResponse
|
||||
|
||||
load_dotenv(Path.home() / "aaronai" / ".env")
|
||||
|
||||
@@ -767,5 +769,51 @@ async def clear_all_conversations(auth: str = Depends(require_auth)):
|
||||
return JSONResponse({"cleared": True})
|
||||
|
||||
|
||||
# SSE client registry
|
||||
sse_clients: list[asyncio.Queue] = []
|
||||
|
||||
async def sse_generator(queue: asyncio.Queue):
|
||||
try:
|
||||
yield 'data: {"type": "connected"}\n\n'
|
||||
while True:
|
||||
try:
|
||||
event = await asyncio.wait_for(queue.get(), timeout=30.0)
|
||||
import json as _json
|
||||
yield 'data: ' + _json.dumps(event) + '\n\n'
|
||||
except asyncio.TimeoutError:
|
||||
yield 'data: {"type": "heartbeat"}\n\n'
|
||||
|
||||
except asyncio.CancelledError:
|
||||
pass
|
||||
finally:
|
||||
if queue in sse_clients:
|
||||
sse_clients.remove(queue)
|
||||
|
||||
@app.get("/api/events")
|
||||
async def sse_endpoint(request: Request, auth: str = Depends(require_auth)):
|
||||
queue: asyncio.Queue = asyncio.Queue()
|
||||
sse_clients.append(queue)
|
||||
return StreamingResponse(
|
||||
sse_generator(queue),
|
||||
media_type="text/event-stream",
|
||||
headers={
|
||||
"Cache-Control": "no-cache",
|
||||
"X-Accel-Buffering": "no",
|
||||
"Connection": "keep-alive",
|
||||
}
|
||||
)
|
||||
|
||||
@app.post("/api/events/notify")
|
||||
async def notify_clients(request: Request):
|
||||
"""Internal endpoint — called by dream.py when a dream is delivered"""
|
||||
# Only allow from localhost
|
||||
client_host = request.client.host if request.client else ""
|
||||
if client_host not in ("127.0.0.1", "::1", "localhost"):
|
||||
raise HTTPException(status_code=403, detail="Internal only")
|
||||
data = await request.json()
|
||||
for queue in sse_clients:
|
||||
await queue.put(data)
|
||||
return JSONResponse({"notified": len(sse_clients)})
|
||||
|
||||
if __name__ == "__main__":
|
||||
uvicorn.run(app, host="0.0.0.0", port=8000)
|
||||
|
||||
@@ -289,6 +289,18 @@ def deliver(dream_text, mode, task=None):
|
||||
|
||||
print(f"Dream written to Nextcloud: Journal/Dreams/{filename}")
|
||||
|
||||
# Notify any open browser connections via SSE
|
||||
try:
|
||||
import requests as _req
|
||||
_req.post("http://localhost:8000/api/events/notify", json={
|
||||
"type": "dream",
|
||||
"mode": mode,
|
||||
"filename": filename,
|
||||
"timestamp": datetime.now().strftime("%Y-%m-%d %H:%M"),
|
||||
}, timeout=3)
|
||||
except Exception as _e:
|
||||
print(f"SSE notify failed (non-critical): {_e}")
|
||||
|
||||
state = load_dreamer_state()
|
||||
state["last_dream_timestamp"] = datetime.now().timestamp()
|
||||
state["last_dream_mode"] = mode
|
||||
|
||||
Reference in New Issue
Block a user