File size: 3,002 Bytes
448d7a9
cb01390
eaf2b94
cb01390
 
448d7a9
eaf2b94
cb01390
 
eaf2b94
 
448d7a9
eaf2b94
 
448d7a9
eaf2b94
448d7a9
eaf2b94
448d7a9
cb01390
448d7a9
eaf2b94
cb01390
eaf2b94
cb01390
eaf2b94
cb01390
448d7a9
eaf2b94
448d7a9
 
 
cb01390
448d7a9
 
eaf2b94
cb01390
 
 
eaf2b94
cb01390
448d7a9
 
 
cb01390
 
 
 
448d7a9
 
eaf2b94
cb01390
 
eaf2b94
448d7a9
 
cb01390
 
 
 
 
448d7a9
cb01390
 
448d7a9
 
cb01390
 
 
 
 
 
448d7a9
 
 
 
cb01390
 
448d7a9
eaf2b94
cb01390
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
"""
CryptoSentinel AI β€” FastAPI app entry point (no /static mount)
- Fetches live prices from CoinGecko
- Provides real-time sentiment analysis via SSE
- Compatible with Hugging Face Spaces
"""

import json
import asyncio
from pathlib import Path

from fastapi import FastAPI, Request, BackgroundTasks
from fastapi.responses import HTMLResponse, JSONResponse, StreamingResponse
from fastapi.templating import Jinja2Templates
from apscheduler.schedulers.background import BackgroundScheduler

from price_fetcher import fetch_prices, CURRENT_PRICES
from sentiment import SentimentCache

# ────── Setup ───────────────────────────────────────────────────────────────

BASE_DIR = Path(__file__).parent
templates = Jinja2Templates(directory=str(BASE_DIR / "templates"))

app = FastAPI(title="CryptoSentinel AI (CDN-only static)")

# Start background job to refresh prices every 10s
scheduler = BackgroundScheduler(daemon=True)
scheduler.add_job(fetch_prices, trigger="interval", seconds=10)
scheduler.start()

@app.on_event("shutdown")
def shutdown():
    scheduler.shutdown(wait=False)

@app.on_event("startup")
def warmup_model():
    # Preload the sentiment model once on startup
    SentimentCache.compute("The market is pumping πŸš€")

# ────── Routes ──────────────────────────────────────────────────────────────

@app.get("/", response_class=HTMLResponse)
async def index(request: Request):
    """
    Renders index.html which now should reference HTMX via CDN:
      <script src="https://unpkg.com/[email protected]"></script>
    """
    return templates.TemplateResponse("index.html", {"request": request})

@app.get("/prices", response_class=JSONResponse)
async def prices():
    """Return the latest cached crypto prices."""
    return CURRENT_PRICES

@app.post("/sentiment")
async def sentiment(request: Request, background_tasks: BackgroundTasks):
    """
    Queue sentiment analysis for the given text.
    Frontend will pick up results via SSE.
    """
    body = await request.json()
    background_tasks.add_task(SentimentCache.compute, body.get("text", ""))
    return {"status": "queued"}

@app.get("/sentiment/stream")
async def sentiment_stream():
    """
    Server-Sent Events endpoint that pushes new sentiment results
    as they become available in SentimentCache.latest_result.
    """
    async def event_generator():
        last_id = 0
        while True:
            if SentimentCache.latest_id != last_id:
                last_id = SentimentCache.latest_id
                payload = json.dumps(SentimentCache.latest_result)
                yield f"id:{last_id}\ndata:{payload}\n\n"
            await asyncio.sleep(1)

    return StreamingResponse(event_generator(), media_type="text/event-stream")