File size: 5,803 Bytes
af86b7d
4831588
5dd5427
af86b7d
 
 
3c9c1d8
a918c3e
af86b7d
a918c3e
 
 
53d138c
af86b7d
 
 
 
 
 
 
53d138c
 
af86b7d
 
 
 
 
 
 
 
 
 
5beb699
e4e854a
 
 
5beb699
e4e854a
 
 
 
 
 
af86b7d
 
 
 
 
 
 
 
 
 
 
 
 
 
5dd5427
 
 
4831588
 
 
 
 
53d138c
 
3c9c1d8
 
 
 
 
 
 
 
0787f2d
3c9c1d8
0787f2d
 
793a92d
3c9c1d8
 
 
 
 
 
53d138c
af86b7d
 
 
 
 
 
 
 
e4e854a
 
 
 
 
 
 
af86b7d
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
e4e854a
 
 
af86b7d
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
e4e854a
 
 
af86b7d
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
e4e854a
 
 
af86b7d
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
53d138c
 
 
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
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
from fastapi import FastAPI, HTTPException
from fastapi.responses import HTMLResponse, RedirectResponse
from fastapi.staticfiles import StaticFiles
from fastapi.middleware.cors import CORSMiddleware
from pydantic import BaseModel
from typing import List, Optional
import os
from dotenv import load_dotenv
import logging

# Load environment variables from .env file
load_dotenv()

# Import HF Leaderboard Service
from hf_leaderboard import HFLeaderboardService

# Configure logging
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)

app = FastAPI()

# Add CORS middleware for local development
app.add_middleware(
    CORSMiddleware,
    allow_origins=["*"],
    allow_credentials=True,
    allow_methods=["*"],
    allow_headers=["*"],
)

# Initialize HF Leaderboard Service
# Using dedicated leaderboard Space: milwright/cloze-leaderboard
hf_token = os.getenv("HF_TOKEN")
try:
    hf_leaderboard = HFLeaderboardService(
        repo_id="milwright/cloze-leaderboard",  # Dedicated leaderboard Space
        token=hf_token
    )
except ValueError as e:
    logger.warning(f"Could not initialize HF Leaderboard Service: {e}")
    logger.warning("Leaderboard will use localStorage fallback only")
    hf_leaderboard = None

# Pydantic models for API
class LeaderboardEntry(BaseModel):
    initials: str
    level: int
    round: int
    passagesPassed: int
    date: str

class LeaderboardResponse(BaseModel):
    success: bool
    leaderboard: List[LeaderboardEntry]
    message: Optional[str] = None

# Mount static files
app.mount("/src", StaticFiles(directory="src"), name="src")

@app.get("/icon.png")
async def get_icon():
    # Redirect to GitHub-hosted icon
    return RedirectResponse(url="https://raw.githubusercontent.com/zmuhls/cloze-reader/main/icon.png")

@app.get("/")
async def read_root():
    # Read the HTML file and inject environment variables
    with open("index.html", "r") as f:
        html_content = f.read()
    
    # Inject environment variables as a script
    openrouter_key = os.getenv("OPENROUTER_API_KEY", "")
    hf_key = os.getenv("HF_API_KEY", "")
    
    # Create a CSP-compliant way to inject the keys
    env_script = f"""
    <meta name="openrouter-key" content="{openrouter_key}">
    <meta name="hf-key" content="{hf_key}">
    <script src="./src/init-env.js"></script>
    """
    
    # Insert the script before closing head tag
    html_content = html_content.replace("</head>", env_script + "</head>")
    
    return HTMLResponse(content=html_content)


# ===== LEADERBOARD API ENDPOINTS =====

@app.get("/api/leaderboard", response_model=LeaderboardResponse)
async def get_leaderboard():
    """
    Get current leaderboard data from HF Hub
    """
    if not hf_leaderboard:
        return {
            "success": True,
            "leaderboard": [],
            "message": "HF leaderboard not available (using localStorage only)"
        }

    try:
        leaderboard = hf_leaderboard.get_leaderboard()
        return {
            "success": True,
            "leaderboard": leaderboard,
            "message": f"Retrieved {len(leaderboard)} entries"
        }
    except Exception as e:
        logger.error(f"Error fetching leaderboard: {e}")
        raise HTTPException(status_code=500, detail=str(e))


@app.post("/api/leaderboard/add")
async def add_leaderboard_entry(entry: LeaderboardEntry):
    """
    Add new entry to leaderboard
    """
    if not hf_leaderboard:
        raise HTTPException(status_code=503, detail="HF leaderboard not available")

    try:
        success = hf_leaderboard.add_entry(entry.dict())
        if success:
            return {
                "success": True,
                "message": f"Added {entry.initials} to leaderboard"
            }
        else:
            raise HTTPException(status_code=500, detail="Failed to add entry")
    except ValueError as e:
        raise HTTPException(status_code=403, detail=str(e))
    except Exception as e:
        logger.error(f"Error adding entry: {e}")
        raise HTTPException(status_code=500, detail=str(e))


@app.post("/api/leaderboard/update")
async def update_leaderboard(entries: List[LeaderboardEntry]):
    """
    Update entire leaderboard (replace all data)
    """
    if not hf_leaderboard:
        raise HTTPException(status_code=503, detail="HF leaderboard not available")

    try:
        success = hf_leaderboard.update_leaderboard([e.dict() for e in entries])
        if success:
            return {
                "success": True,
                "message": "Leaderboard updated successfully"
            }
        else:
            raise HTTPException(status_code=500, detail="Failed to update leaderboard")
    except ValueError as e:
        raise HTTPException(status_code=403, detail=str(e))
    except Exception as e:
        logger.error(f"Error updating leaderboard: {e}")
        raise HTTPException(status_code=500, detail=str(e))


@app.delete("/api/leaderboard/clear")
async def clear_leaderboard():
    """
    Clear all leaderboard data (admin only)
    """
    if not hf_leaderboard:
        raise HTTPException(status_code=503, detail="HF leaderboard not available")

    try:
        success = hf_leaderboard.clear_leaderboard()
        if success:
            return {
                "success": True,
                "message": "Leaderboard cleared"
            }
        else:
            raise HTTPException(status_code=500, detail="Failed to clear leaderboard")
    except ValueError as e:
        raise HTTPException(status_code=403, detail=str(e))
    except Exception as e:
        logger.error(f"Error clearing leaderboard: {e}")
        raise HTTPException(status_code=500, detail=str(e))


if __name__ == "__main__":
    import uvicorn
    uvicorn.run(app, host="0.0.0.0", port=7860)