Spaces:
Sleeping
Sleeping
Upload 8 files
Browse files
README.md
CHANGED
|
@@ -20,6 +20,7 @@ A modern web interface for managing Discord bot AI model configurations stored i
|
|
| 20 |
3. Configure environment variables in Space settings:
|
| 21 |
- `FIREBASE_URL`: Your Firebase Realtime Database URL
|
| 22 |
- `FIREBASE_SERVICE_ACCOUNT_B64`: Base64-encoded service account JSON
|
|
|
|
| 23 |
4. The app will build automatically and be available on port 7860
|
| 24 |
|
| 25 |
### Local Development:
|
|
@@ -47,6 +48,7 @@ A modern web interface for managing Discord bot AI model configurations stored i
|
|
| 47 |
|
| 48 |
## ✨ Features
|
| 49 |
|
|
|
|
| 50 |
- **Model Management**: Add, edit, delete AI models by category
|
| 51 |
- **Image Previews**: Thumbnail carousels from Civitai API with metadata
|
| 52 |
- **Advanced Filtering**: Sort by reactions/comments/newest, NSFW levels, time periods
|
|
|
|
| 20 |
3. Configure environment variables in Space settings:
|
| 21 |
- `FIREBASE_URL`: Your Firebase Realtime Database URL
|
| 22 |
- `FIREBASE_SERVICE_ACCOUNT_B64`: Base64-encoded service account JSON
|
| 23 |
+
- `ADMIN_KEY`: Secret admin key for authentication (set this to a secure password)
|
| 24 |
4. The app will build automatically and be available on port 7860
|
| 25 |
|
| 26 |
### Local Development:
|
|
|
|
| 48 |
|
| 49 |
## ✨ Features
|
| 50 |
|
| 51 |
+
- **🔐 Admin Authentication**: Secure login with admin key protection
|
| 52 |
- **Model Management**: Add, edit, delete AI models by category
|
| 53 |
- **Image Previews**: Thumbnail carousels from Civitai API with metadata
|
| 54 |
- **Advanced Filtering**: Sort by reactions/comments/newest, NSFW levels, time periods
|
app.py
CHANGED
|
@@ -1,6 +1,6 @@
|
|
| 1 |
-
from fastapi import FastAPI, HTTPException, Request
|
| 2 |
from fastapi.staticfiles import StaticFiles
|
| 3 |
-
from fastapi.responses import HTMLResponse, FileResponse
|
| 4 |
from pydantic import BaseModel
|
| 5 |
import os
|
| 6 |
import json
|
|
@@ -8,6 +8,8 @@ import firebase_admin
|
|
| 8 |
from firebase_admin import credentials, db
|
| 9 |
import base64
|
| 10 |
import time
|
|
|
|
|
|
|
| 11 |
from dotenv import load_dotenv
|
| 12 |
from typing import List, Dict, Any, Optional
|
| 13 |
|
|
@@ -16,6 +18,33 @@ load_dotenv()
|
|
| 16 |
|
| 17 |
app = FastAPI(title="CatGPT Model Manager", description="Modern web interface for managing Discord bot AI models")
|
| 18 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 19 |
class FirebaseManager:
|
| 20 |
def __init__(self):
|
| 21 |
self.app = None
|
|
@@ -223,12 +252,57 @@ class ModelUpdate(BaseModel):
|
|
| 223 |
field: str
|
| 224 |
value: Any
|
| 225 |
|
| 226 |
-
#
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 227 |
@app.get("/", response_class=HTMLResponse)
|
| 228 |
-
async def read_root():
|
| 229 |
-
"""Serve the main HTML page"""
|
|
|
|
|
|
|
| 230 |
return FileResponse('index.html')
|
| 231 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 232 |
@app.get("/api/models")
|
| 233 |
async def get_all_models():
|
| 234 |
"""Get all models from all categories"""
|
|
@@ -252,7 +326,7 @@ async def get_models_by_category(category: str):
|
|
| 252 |
}
|
| 253 |
|
| 254 |
@app.post("/api/models/{category}")
|
| 255 |
-
async def add_model(category: str, model: ModelCreate):
|
| 256 |
"""Add a new model to a category"""
|
| 257 |
if category not in ["pony", "illustrious", "sdxl"]:
|
| 258 |
raise HTTPException(status_code=400, detail="Invalid category")
|
|
@@ -274,7 +348,7 @@ async def add_model(category: str, model: ModelCreate):
|
|
| 274 |
return {"message": message}
|
| 275 |
|
| 276 |
@app.put("/api/models/{category}/{model_id}")
|
| 277 |
-
async def update_model(category: str, model_id: str, update: ModelUpdate):
|
| 278 |
"""Update a specific field of a model"""
|
| 279 |
if category not in ["pony", "illustrious", "sdxl"]:
|
| 280 |
raise HTTPException(status_code=400, detail="Invalid category")
|
|
@@ -287,7 +361,7 @@ async def update_model(category: str, model_id: str, update: ModelUpdate):
|
|
| 287 |
return {"message": message}
|
| 288 |
|
| 289 |
@app.delete("/api/models/{category}/{model_id}")
|
| 290 |
-
async def delete_model(category: str, model_id: str):
|
| 291 |
"""Delete a model"""
|
| 292 |
if category not in ["pony", "illustrious", "sdxl"]:
|
| 293 |
raise HTTPException(status_code=400, detail="Invalid category")
|
|
@@ -315,6 +389,6 @@ app.add_middleware(
|
|
| 315 |
|
| 316 |
if __name__ == "__main__":
|
| 317 |
import uvicorn
|
| 318 |
-
port = int(os.getenv('PORT',
|
| 319 |
-
print(f"Starting CatGPT Model Manager on http://
|
| 320 |
uvicorn.run(app, host="0.0.0.0", port=port)
|
|
|
|
| 1 |
+
from fastapi import FastAPI, HTTPException, Request, Depends, Cookie
|
| 2 |
from fastapi.staticfiles import StaticFiles
|
| 3 |
+
from fastapi.responses import HTMLResponse, FileResponse, RedirectResponse, JSONResponse
|
| 4 |
from pydantic import BaseModel
|
| 5 |
import os
|
| 6 |
import json
|
|
|
|
| 8 |
from firebase_admin import credentials, db
|
| 9 |
import base64
|
| 10 |
import time
|
| 11 |
+
import hashlib
|
| 12 |
+
import secrets
|
| 13 |
from dotenv import load_dotenv
|
| 14 |
from typing import List, Dict, Any, Optional
|
| 15 |
|
|
|
|
| 18 |
|
| 19 |
app = FastAPI(title="CatGPT Model Manager", description="Modern web interface for managing Discord bot AI models")
|
| 20 |
|
| 21 |
+
# Authentication Configuration
|
| 22 |
+
ADMIN_KEY = os.getenv('ADMIN_KEY')
|
| 23 |
+
if not ADMIN_KEY:
|
| 24 |
+
print("WARNING: ADMIN_KEY not set. Using default key for development.")
|
| 25 |
+
ADMIN_KEY = "dev-admin-key-please-change"
|
| 26 |
+
|
| 27 |
+
# Store active sessions (in production, use Redis or database)
|
| 28 |
+
active_sessions = set()
|
| 29 |
+
|
| 30 |
+
# Pydantic models for authentication
|
| 31 |
+
class LoginRequest(BaseModel):
|
| 32 |
+
admin_key: str
|
| 33 |
+
|
| 34 |
+
def create_session_token() -> str:
|
| 35 |
+
"""Generate a secure session token"""
|
| 36 |
+
return secrets.token_urlsafe(32)
|
| 37 |
+
|
| 38 |
+
def verify_session(session_token: Optional[str] = Cookie(None)) -> bool:
|
| 39 |
+
"""Verify if session token is valid"""
|
| 40 |
+
return session_token in active_sessions
|
| 41 |
+
|
| 42 |
+
def require_auth(authenticated: bool = Depends(verify_session)):
|
| 43 |
+
"""Dependency to require authentication"""
|
| 44 |
+
if not authenticated:
|
| 45 |
+
raise HTTPException(status_code=401, detail="Authentication required")
|
| 46 |
+
return True
|
| 47 |
+
|
| 48 |
class FirebaseManager:
|
| 49 |
def __init__(self):
|
| 50 |
self.app = None
|
|
|
|
| 252 |
field: str
|
| 253 |
value: Any
|
| 254 |
|
| 255 |
+
# Authentication Routes
|
| 256 |
+
@app.post("/api/login")
|
| 257 |
+
async def login(request: LoginRequest):
|
| 258 |
+
"""Authenticate with admin key"""
|
| 259 |
+
if request.admin_key == ADMIN_KEY:
|
| 260 |
+
session_token = create_session_token()
|
| 261 |
+
active_sessions.add(session_token)
|
| 262 |
+
|
| 263 |
+
response = {"success": True, "message": "Authenticated successfully"}
|
| 264 |
+
response_obj = JSONResponse(response)
|
| 265 |
+
response_obj.set_cookie(
|
| 266 |
+
key="session_token",
|
| 267 |
+
value=session_token,
|
| 268 |
+
httponly=True,
|
| 269 |
+
secure=True,
|
| 270 |
+
samesite="lax",
|
| 271 |
+
max_age=86400 # 24 hours
|
| 272 |
+
)
|
| 273 |
+
return response_obj
|
| 274 |
+
else:
|
| 275 |
+
raise HTTPException(status_code=401, detail="Invalid admin key")
|
| 276 |
+
|
| 277 |
+
@app.post("/api/logout")
|
| 278 |
+
async def logout(session_token: Optional[str] = Cookie(None)):
|
| 279 |
+
"""Logout and invalidate session"""
|
| 280 |
+
if session_token and session_token in active_sessions:
|
| 281 |
+
active_sessions.remove(session_token)
|
| 282 |
+
|
| 283 |
+
response = {"success": True, "message": "Logged out successfully"}
|
| 284 |
+
response_obj = JSONResponse(response)
|
| 285 |
+
response_obj.delete_cookie("session_token")
|
| 286 |
+
return response_obj
|
| 287 |
+
|
| 288 |
+
@app.get("/api/auth-status")
|
| 289 |
+
async def auth_status(authenticated: bool = Depends(verify_session)):
|
| 290 |
+
"""Check if user is authenticated"""
|
| 291 |
+
return {"authenticated": authenticated}
|
| 292 |
+
|
| 293 |
+
# Main Routes
|
| 294 |
@app.get("/", response_class=HTMLResponse)
|
| 295 |
+
async def read_root(authenticated: bool = Depends(verify_session)):
|
| 296 |
+
"""Serve the main HTML page or login page"""
|
| 297 |
+
if not authenticated:
|
| 298 |
+
return FileResponse('login.html')
|
| 299 |
return FileResponse('index.html')
|
| 300 |
|
| 301 |
+
@app.get("/login", response_class=HTMLResponse)
|
| 302 |
+
async def login_page():
|
| 303 |
+
"""Serve the login page"""
|
| 304 |
+
return FileResponse('login.html')
|
| 305 |
+
|
| 306 |
@app.get("/api/models")
|
| 307 |
async def get_all_models():
|
| 308 |
"""Get all models from all categories"""
|
|
|
|
| 326 |
}
|
| 327 |
|
| 328 |
@app.post("/api/models/{category}")
|
| 329 |
+
async def add_model(category: str, model: ModelCreate, _: bool = Depends(require_auth)):
|
| 330 |
"""Add a new model to a category"""
|
| 331 |
if category not in ["pony", "illustrious", "sdxl"]:
|
| 332 |
raise HTTPException(status_code=400, detail="Invalid category")
|
|
|
|
| 348 |
return {"message": message}
|
| 349 |
|
| 350 |
@app.put("/api/models/{category}/{model_id}")
|
| 351 |
+
async def update_model(category: str, model_id: str, update: ModelUpdate, _: bool = Depends(require_auth)):
|
| 352 |
"""Update a specific field of a model"""
|
| 353 |
if category not in ["pony", "illustrious", "sdxl"]:
|
| 354 |
raise HTTPException(status_code=400, detail="Invalid category")
|
|
|
|
| 361 |
return {"message": message}
|
| 362 |
|
| 363 |
@app.delete("/api/models/{category}/{model_id}")
|
| 364 |
+
async def delete_model(category: str, model_id: str, _: bool = Depends(require_auth)):
|
| 365 |
"""Delete a model"""
|
| 366 |
if category not in ["pony", "illustrious", "sdxl"]:
|
| 367 |
raise HTTPException(status_code=400, detail="Invalid category")
|
|
|
|
| 389 |
|
| 390 |
if __name__ == "__main__":
|
| 391 |
import uvicorn
|
| 392 |
+
port = int(os.getenv('PORT', 7860))
|
| 393 |
+
print(f"Starting CatGPT Model Manager on http://0.0.0.0:{port}")
|
| 394 |
uvicorn.run(app, host="0.0.0.0", port=port)
|