Deploy habadashi_login gateway
Browse files- LOCAL_TESTING.md +119 -0
- README.md +37 -12
- app.py +148 -0
- bootstrap.py +75 -0
- login.py +52 -0
- requirements.txt +18 -0
LOCAL_TESTING.md
ADDED
|
@@ -0,0 +1,119 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Habadashi Login Gateway - Local Testing Guide
|
| 2 |
+
|
| 3 |
+
## Prerequisites
|
| 4 |
+
|
| 5 |
+
1. Environment variables must be set:
|
| 6 |
+
- `SUPABASE_URL`: Your Supabase project URL
|
| 7 |
+
- `SUPABASE_KEY`: Your Supabase anon/service key
|
| 8 |
+
- `HF_TOKEN`: Hugging Face token with access to private `DLPO/habadashi` Space
|
| 9 |
+
- `OPENAI_KEY`: OpenAI API key (required by ver20)
|
| 10 |
+
- `CLIENTPOOL`: Client pool configuration (required by ver20)
|
| 11 |
+
|
| 12 |
+
2. Install dependencies:
|
| 13 |
+
```bash
|
| 14 |
+
pip install -r requirements.txt
|
| 15 |
+
```
|
| 16 |
+
|
| 17 |
+
## Local Testing Steps
|
| 18 |
+
|
| 19 |
+
### Step 1: Set environment variables
|
| 20 |
+
|
| 21 |
+
**PowerShell (Windows):**
|
| 22 |
+
```powershell
|
| 23 |
+
$env:SUPABASE_URL="your-supabase-url"
|
| 24 |
+
$env:SUPABASE_KEY="your-supabase-key"
|
| 25 |
+
$env:HF_TOKEN="your-hf-token"
|
| 26 |
+
$env:OPENAI_KEY="your-openai-key"
|
| 27 |
+
$env:CLIENTPOOL="your-client-pool"
|
| 28 |
+
```
|
| 29 |
+
|
| 30 |
+
**Bash (Linux/Mac):**
|
| 31 |
+
```bash
|
| 32 |
+
export SUPABASE_URL="your-supabase-url"
|
| 33 |
+
export SUPABASE_KEY="your-supabase-key"
|
| 34 |
+
export HF_TOKEN="your-hf-token"
|
| 35 |
+
export OPENAI_KEY="your-openai-key"
|
| 36 |
+
export CLIENTPOOL="your-client-pool"
|
| 37 |
+
```
|
| 38 |
+
|
| 39 |
+
### Step 2: Test bootstrap (download private app)
|
| 40 |
+
|
| 41 |
+
```bash
|
| 42 |
+
python bootstrap.py
|
| 43 |
+
```
|
| 44 |
+
|
| 45 |
+
Expected output:
|
| 46 |
+
- `π Downloading private app from DLPO/habadashi...`
|
| 47 |
+
- `β
Download complete: ./private_app`
|
| 48 |
+
- `π Bootstrap test successful!`
|
| 49 |
+
|
| 50 |
+
This will create a `./private_app/` directory with ver20 files.
|
| 51 |
+
|
| 52 |
+
### Step 3: Start the application
|
| 53 |
+
|
| 54 |
+
```bash
|
| 55 |
+
uvicorn app:app --host 0.0.0.0 --port 7860
|
| 56 |
+
```
|
| 57 |
+
|
| 58 |
+
Or use Gradio's default launch (if app.py is configured to run directly):
|
| 59 |
+
```bash
|
| 60 |
+
python app.py
|
| 61 |
+
```
|
| 62 |
+
|
| 63 |
+
### Step 4: Verify routes
|
| 64 |
+
|
| 65 |
+
1. **Root route**: http://localhost:7860/
|
| 66 |
+
- Should redirect to `/login/` if not authenticated
|
| 67 |
+
- Should redirect to `/app/` if authenticated
|
| 68 |
+
|
| 69 |
+
2. **Login UI**: http://localhost:7860/login/
|
| 70 |
+
- Enter email and password
|
| 71 |
+
- On success, should set cookie and redirect to `/app/`
|
| 72 |
+
|
| 73 |
+
3. **Protected app**: http://localhost:7860/app/
|
| 74 |
+
- Should show ver20 Gradio interface if authenticated
|
| 75 |
+
- Should redirect to login if not authenticated
|
| 76 |
+
|
| 77 |
+
4. **Logout**: http://localhost:7860/logout
|
| 78 |
+
- Should clear cookie and redirect to `/login/`
|
| 79 |
+
|
| 80 |
+
## Troubleshooting
|
| 81 |
+
|
| 82 |
+
### Issue: "HF_TOKEN not found"
|
| 83 |
+
- Make sure `HF_TOKEN` is set in environment variables
|
| 84 |
+
- Verify token has access to private `DLPO/habadashi` Space
|
| 85 |
+
|
| 86 |
+
### Issue: "Failed to import ver20 app"
|
| 87 |
+
- Check that `./private_app/app.py` exists
|
| 88 |
+
- Verify ver20's `app.py` exports `app` variable (Gradio Blocks instance)
|
| 89 |
+
- Check Python path is correctly set
|
| 90 |
+
|
| 91 |
+
### Issue: "SUPABASE_URL and SUPABASE_KEY must be set"
|
| 92 |
+
- Set both environment variables before running the app
|
| 93 |
+
|
| 94 |
+
### Issue: Cookie not persisting (localhost HTTP)
|
| 95 |
+
- For local development, you may need to modify cookie settings in `login.py`
|
| 96 |
+
- Remove `Secure` flag for HTTP testing: `SameSite=Lax;` (remove `Secure;`)
|
| 97 |
+
|
| 98 |
+
## Deployment to HF Space
|
| 99 |
+
|
| 100 |
+
Once local testing is successful, deploy to public HF Space:
|
| 101 |
+
|
| 102 |
+
1. Create new public Space: `DLPO/habadashi_login`
|
| 103 |
+
2. Set secrets in Space settings:
|
| 104 |
+
- `SUPABASE_URL`
|
| 105 |
+
- `SUPABASE_KEY`
|
| 106 |
+
- `HF_TOKEN`
|
| 107 |
+
- `OPENAI_KEY`
|
| 108 |
+
- `CLIENTPOOL`
|
| 109 |
+
3. Upload files:
|
| 110 |
+
- `app.py`
|
| 111 |
+
- `bootstrap.py`
|
| 112 |
+
- `login.py`
|
| 113 |
+
- `requirements.txt`
|
| 114 |
+
- `README.md`
|
| 115 |
+
|
| 116 |
+
The Space will automatically:
|
| 117 |
+
1. Download ver20 from private `DLPO/habadashi` on startup
|
| 118 |
+
2. Mount login UI at `/login/`
|
| 119 |
+
3. Mount ver20 app at `/app/` (protected by auth)
|
README.md
CHANGED
|
@@ -1,12 +1,37 @@
|
|
| 1 |
-
---
|
| 2 |
-
title: Habadashi Login
|
| 3 |
-
emoji:
|
| 4 |
-
colorFrom:
|
| 5 |
-
colorTo: green
|
| 6 |
-
sdk: gradio
|
| 7 |
-
sdk_version:
|
| 8 |
-
app_file: app.py
|
| 9 |
-
pinned: false
|
| 10 |
-
---
|
| 11 |
-
|
| 12 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
---
|
| 2 |
+
title: Habadashi Login Gateway
|
| 3 |
+
emoji: π
|
| 4 |
+
colorFrom: blue
|
| 5 |
+
colorTo: green
|
| 6 |
+
sdk: gradio
|
| 7 |
+
sdk_version: 5.49.1
|
| 8 |
+
app_file: app.py
|
| 9 |
+
pinned: false
|
| 10 |
+
---
|
| 11 |
+
|
| 12 |
+
# Habadashi Login Gateway
|
| 13 |
+
|
| 14 |
+
Public gateway with Supabase authentication that dynamically loads the private ver20 application from `DLPO/habadashi` Space.
|
| 15 |
+
|
| 16 |
+
## Features
|
| 17 |
+
|
| 18 |
+
- Supabase email/password authentication
|
| 19 |
+
- Cookie-based session management
|
| 20 |
+
- Dynamic loading of private application code (source code not exposed in public repo)
|
| 21 |
+
- Role-based access control via profiles table
|
| 22 |
+
|
| 23 |
+
## Environment Variables (Set in HF Space Secrets)
|
| 24 |
+
|
| 25 |
+
- `SUPABASE_URL`: Your Supabase project URL
|
| 26 |
+
- `SUPABASE_KEY`: Your Supabase anon/service key
|
| 27 |
+
- `HF_TOKEN`: Hugging Face token with access to private `DLPO/habadashi` Space
|
| 28 |
+
- `OPENAI_KEY`: OpenAI API key (required by ver20)
|
| 29 |
+
- `CLIENTPOOL`: Client pool configuration (required by ver20)
|
| 30 |
+
|
| 31 |
+
## Architecture
|
| 32 |
+
|
| 33 |
+
1. User visits public Space
|
| 34 |
+
2. Bootstrap downloads ver20 from private `DLPO/habadashi` Space
|
| 35 |
+
3. User logs in with Supabase credentials
|
| 36 |
+
4. On successful login, ver20 Gradio app is mounted at `/app/`
|
| 37 |
+
5. User profile and organization info loaded from `profiles` table
|
app.py
ADDED
|
@@ -0,0 +1,148 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/usr/bin/env python3
|
| 2 |
+
"""
|
| 3 |
+
Habadashi Login Gateway - Main entry point
|
| 4 |
+
Public Space that loads private ver20 app dynamically
|
| 5 |
+
"""
|
| 6 |
+
|
| 7 |
+
import os
|
| 8 |
+
import sys
|
| 9 |
+
from pathlib import Path
|
| 10 |
+
from fastapi import FastAPI, Request, Depends, HTTPException
|
| 11 |
+
from fastapi.responses import RedirectResponse
|
| 12 |
+
import gradio as gr
|
| 13 |
+
from supabase import create_client, Client
|
| 14 |
+
|
| 15 |
+
# Import bootstrap to download private app
|
| 16 |
+
from bootstrap import download_private_app
|
| 17 |
+
from login import create_login_ui
|
| 18 |
+
|
| 19 |
+
# --- Bootstrap: Download private app at startup ---
|
| 20 |
+
print("π Starting Habadashi Login Gateway...")
|
| 21 |
+
try:
|
| 22 |
+
private_app_dir = download_private_app()
|
| 23 |
+
|
| 24 |
+
# Add private app to Python path so we can import it
|
| 25 |
+
private_app_path = str(private_app_dir.resolve())
|
| 26 |
+
if private_app_path not in sys.path:
|
| 27 |
+
sys.path.insert(0, private_app_path)
|
| 28 |
+
print(f"β
Added to Python path: {private_app_path}")
|
| 29 |
+
|
| 30 |
+
except Exception as e:
|
| 31 |
+
print(f"β Failed to bootstrap private app: {e}")
|
| 32 |
+
print("β οΈ Application will start but /app/ route will not work")
|
| 33 |
+
private_app_dir = None
|
| 34 |
+
|
| 35 |
+
# --- Supabase Setup ---
|
| 36 |
+
SUPABASE_URL = os.environ.get("SUPABASE_URL")
|
| 37 |
+
SUPABASE_KEY = os.environ.get("SUPABASE_KEY")
|
| 38 |
+
|
| 39 |
+
if not SUPABASE_URL or not SUPABASE_KEY:
|
| 40 |
+
raise ValueError(
|
| 41 |
+
"SUPABASE_URL and SUPABASE_KEY must be set in environment variables. "
|
| 42 |
+
"Please configure them in HF Space Secrets."
|
| 43 |
+
)
|
| 44 |
+
|
| 45 |
+
supabase: Client = create_client(SUPABASE_URL, SUPABASE_KEY)
|
| 46 |
+
|
| 47 |
+
# --- FastAPI App ---
|
| 48 |
+
app = FastAPI()
|
| 49 |
+
|
| 50 |
+
# --- Authentication Handler (for login UI) ---
|
| 51 |
+
def handle_login(email, password):
|
| 52 |
+
"""Handle login attempt via Supabase"""
|
| 53 |
+
try:
|
| 54 |
+
res = supabase.auth.sign_in_with_password({"email": email, "password": password})
|
| 55 |
+
if res.session:
|
| 56 |
+
return (
|
| 57 |
+
gr.update(visible=False),
|
| 58 |
+
gr.update(visible=True, value=f"### β
γγ°γ€γ³ζε: {email}"),
|
| 59 |
+
res.session.access_token
|
| 60 |
+
)
|
| 61 |
+
except Exception as e:
|
| 62 |
+
return gr.update(), gr.update(value=f"β γ¨γ©γΌ: {str(e)}"), None
|
| 63 |
+
|
| 64 |
+
# --- Authentication Dependency ---
|
| 65 |
+
def get_current_user(request: Request):
|
| 66 |
+
"""Verify token from cookie and fetch user profile"""
|
| 67 |
+
token = request.cookies.get("sb_access_token")
|
| 68 |
+
|
| 69 |
+
print(f"--- Auth Check ---")
|
| 70 |
+
print(f"Token present: {bool(token)}")
|
| 71 |
+
|
| 72 |
+
if not token:
|
| 73 |
+
return None
|
| 74 |
+
|
| 75 |
+
try:
|
| 76 |
+
# Verify token with Supabase
|
| 77 |
+
res = supabase.auth.get_user(token)
|
| 78 |
+
user_id = res.user.id
|
| 79 |
+
|
| 80 |
+
# Fetch profile from profiles table (with organization name)
|
| 81 |
+
profile_res = supabase.from_("profiles").select(
|
| 82 |
+
"email, org_id, role, display_name, organizations(name)"
|
| 83 |
+
).eq("id", user_id).single().execute()
|
| 84 |
+
|
| 85 |
+
profile_data = profile_res.data
|
| 86 |
+
user_dict = {
|
| 87 |
+
"user_id": user_id,
|
| 88 |
+
"email": profile_data.get("email"),
|
| 89 |
+
"display_name": profile_data.get("display_name"),
|
| 90 |
+
"role": profile_data.get("role"),
|
| 91 |
+
"org_name": (profile_data.get("organizations") or {}).get("name")
|
| 92 |
+
}
|
| 93 |
+
|
| 94 |
+
print(f"Auth Success: {user_dict['email']}")
|
| 95 |
+
return user_dict
|
| 96 |
+
|
| 97 |
+
except Exception as e:
|
| 98 |
+
print(f"Auth Error: {e}")
|
| 99 |
+
return None
|
| 100 |
+
|
| 101 |
+
# --- Create UI instances ---
|
| 102 |
+
login_ui = create_login_ui(handle_login)
|
| 103 |
+
|
| 104 |
+
# Import ver20 app (Gradio Blocks)
|
| 105 |
+
ver20_app = None
|
| 106 |
+
if private_app_dir:
|
| 107 |
+
try:
|
| 108 |
+
# Import app module from downloaded private app
|
| 109 |
+
from app import app as ver20_blocks
|
| 110 |
+
ver20_app = ver20_blocks
|
| 111 |
+
print("β
Successfully imported ver20 Gradio app")
|
| 112 |
+
except Exception as e:
|
| 113 |
+
print(f"β Failed to import ver20 app: {e}")
|
| 114 |
+
print(f" Make sure ver20/app.py exports 'app' variable (Gradio Blocks)")
|
| 115 |
+
|
| 116 |
+
# --- Routes ---
|
| 117 |
+
@app.get("/")
|
| 118 |
+
async def root(user=Depends(get_current_user)):
|
| 119 |
+
"""Root route - redirect to login or app based on auth status"""
|
| 120 |
+
if isinstance(user, dict) and user.get("user_id"):
|
| 121 |
+
return RedirectResponse(url="/app/")
|
| 122 |
+
return RedirectResponse(url="/login/")
|
| 123 |
+
|
| 124 |
+
@app.get("/logout")
|
| 125 |
+
async def logout():
|
| 126 |
+
"""Logout route - clear cookie and redirect to login"""
|
| 127 |
+
response = RedirectResponse(url="/login/")
|
| 128 |
+
response.delete_cookie("sb_access_token")
|
| 129 |
+
return response
|
| 130 |
+
|
| 131 |
+
# --- Mount Gradio UIs ---
|
| 132 |
+
# Login UI (public)
|
| 133 |
+
app = gr.mount_gradio_app(app, login_ui, path="/login")
|
| 134 |
+
|
| 135 |
+
# Ver20 App (protected)
|
| 136 |
+
if ver20_app:
|
| 137 |
+
app = gr.mount_gradio_app(app, ver20_app, path="/app", auth_dependency=get_current_user)
|
| 138 |
+
print("β
Mounted ver20 app at /app/ (protected)")
|
| 139 |
+
else:
|
| 140 |
+
# Fallback: simple placeholder if ver20 failed to load
|
| 141 |
+
with gr.Blocks() as fallback_ui:
|
| 142 |
+
gr.Markdown("# β οΈ Application Not Available")
|
| 143 |
+
gr.Markdown("The private application failed to load. Please check logs.")
|
| 144 |
+
|
| 145 |
+
app = gr.mount_gradio_app(app, fallback_ui, path="/app", auth_dependency=get_current_user)
|
| 146 |
+
print("β οΈ Mounted fallback UI at /app/ (ver20 failed to load)")
|
| 147 |
+
|
| 148 |
+
print("π Habadashi Login Gateway ready!")
|
bootstrap.py
ADDED
|
@@ -0,0 +1,75 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/usr/bin/env python3
|
| 2 |
+
"""
|
| 3 |
+
Bootstrap module - Downloads ver20 from private DLPO/habadashi Space
|
| 4 |
+
"""
|
| 5 |
+
|
| 6 |
+
import os
|
| 7 |
+
from pathlib import Path
|
| 8 |
+
from huggingface_hub import snapshot_download
|
| 9 |
+
|
| 10 |
+
def get_hf_token():
|
| 11 |
+
"""Get HF_TOKEN from environment variable"""
|
| 12 |
+
token = os.environ.get("HF_TOKEN")
|
| 13 |
+
if not token:
|
| 14 |
+
raise ValueError(
|
| 15 |
+
"HF_TOKEN not found. Please set HF_TOKEN environment variable "
|
| 16 |
+
"in HF Space Secrets to access private repository."
|
| 17 |
+
)
|
| 18 |
+
return token
|
| 19 |
+
|
| 20 |
+
def download_private_app(force_download: bool = False):
|
| 21 |
+
"""
|
| 22 |
+
Download ver20 application from private DLPO/habadashi Space
|
| 23 |
+
|
| 24 |
+
Args:
|
| 25 |
+
force_download: If True, re-download even if already cached
|
| 26 |
+
|
| 27 |
+
Returns:
|
| 28 |
+
Path: Local directory containing downloaded ver20 files
|
| 29 |
+
"""
|
| 30 |
+
repo_id = "DLPO/habadashi"
|
| 31 |
+
repo_type = "space"
|
| 32 |
+
local_dir = Path("./private_app")
|
| 33 |
+
|
| 34 |
+
# Check if already downloaded (skip if exists and not forced)
|
| 35 |
+
if local_dir.exists() and not force_download:
|
| 36 |
+
# Verify essential files exist
|
| 37 |
+
app_py = local_dir / "app.py"
|
| 38 |
+
if app_py.exists():
|
| 39 |
+
print(f"β
Private app already downloaded: {local_dir}")
|
| 40 |
+
return local_dir
|
| 41 |
+
|
| 42 |
+
print(f"π Downloading private app from {repo_id}...")
|
| 43 |
+
|
| 44 |
+
try:
|
| 45 |
+
token = get_hf_token()
|
| 46 |
+
|
| 47 |
+
# Download entire Space repository
|
| 48 |
+
snapshot_download(
|
| 49 |
+
repo_id=repo_id,
|
| 50 |
+
repo_type=repo_type,
|
| 51 |
+
local_dir=str(local_dir),
|
| 52 |
+
local_dir_use_symlinks=False,
|
| 53 |
+
token=token,
|
| 54 |
+
)
|
| 55 |
+
|
| 56 |
+
print(f"β
Download complete: {local_dir}")
|
| 57 |
+
|
| 58 |
+
# Verify essential structure
|
| 59 |
+
app_py = local_dir / "app.py"
|
| 60 |
+
if not app_py.exists():
|
| 61 |
+
raise FileNotFoundError(
|
| 62 |
+
f"app.py not found in downloaded repository. "
|
| 63 |
+
f"Expected at: {app_py}"
|
| 64 |
+
)
|
| 65 |
+
|
| 66 |
+
return local_dir
|
| 67 |
+
|
| 68 |
+
except Exception as e:
|
| 69 |
+
print(f"β Failed to download private app: {e}")
|
| 70 |
+
raise
|
| 71 |
+
|
| 72 |
+
if __name__ == "__main__":
|
| 73 |
+
# Test download
|
| 74 |
+
download_private_app()
|
| 75 |
+
print("π Bootstrap test successful!")
|
login.py
ADDED
|
@@ -0,0 +1,52 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import gradio as gr
|
| 2 |
+
|
| 3 |
+
def create_login_ui(handle_login_fn):
|
| 4 |
+
"""
|
| 5 |
+
Create Gradio login UI
|
| 6 |
+
|
| 7 |
+
Args:
|
| 8 |
+
handle_login_fn: Function to handle login (email, password) -> (form_update, status_update, token)
|
| 9 |
+
|
| 10 |
+
Returns:
|
| 11 |
+
Gradio Blocks UI for login
|
| 12 |
+
"""
|
| 13 |
+
with gr.Blocks(title="Login") as ui:
|
| 14 |
+
gr.Markdown("# π Habadashi Login")
|
| 15 |
+
|
| 16 |
+
with gr.Column(visible=True) as login_form:
|
| 17 |
+
email_input = gr.Textbox(label="Email")
|
| 18 |
+
pass_input = gr.Textbox(label="Password", type="password")
|
| 19 |
+
login_btn = gr.Button("Login", variant="primary")
|
| 20 |
+
|
| 21 |
+
status_msg = gr.Markdown("")
|
| 22 |
+
|
| 23 |
+
# Hidden textbox to store token and trigger cookie setting via JS
|
| 24 |
+
token_storage = gr.Textbox(visible=False, elem_id="token_storage")
|
| 25 |
+
|
| 26 |
+
# External login handler (from app.py) is bound here
|
| 27 |
+
login_btn.click(
|
| 28 |
+
handle_login_fn,
|
| 29 |
+
inputs=[email_input, pass_input],
|
| 30 |
+
outputs=[login_form, status_msg, token_storage]
|
| 31 |
+
)
|
| 32 |
+
|
| 33 |
+
# When token is set, use JavaScript to save cookie and redirect
|
| 34 |
+
token_storage.change(
|
| 35 |
+
None,
|
| 36 |
+
inputs=[token_storage],
|
| 37 |
+
js="""(token) => {
|
| 38 |
+
if (!token || token === "") return;
|
| 39 |
+
|
| 40 |
+
// Set cookie
|
| 41 |
+
document.cookie = `sb_access_token=${token}; path=/; max-age=3600; SameSite=None; Secure;`;
|
| 42 |
+
|
| 43 |
+
console.log("Cookie set, redirecting...");
|
| 44 |
+
|
| 45 |
+
// Wait briefly then redirect
|
| 46 |
+
setTimeout(() => {
|
| 47 |
+
window.location.href = '/app/';
|
| 48 |
+
}, 200);
|
| 49 |
+
}"""
|
| 50 |
+
)
|
| 51 |
+
|
| 52 |
+
return ui
|
requirements.txt
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Public Gateway dependencies
|
| 2 |
+
fastapi
|
| 3 |
+
uvicorn
|
| 4 |
+
gradio
|
| 5 |
+
supabase
|
| 6 |
+
python-multipart
|
| 7 |
+
huggingface_hub
|
| 8 |
+
|
| 9 |
+
# ver20 dependencies (from ver20/requirements.txt)
|
| 10 |
+
gradio_client
|
| 11 |
+
requests
|
| 12 |
+
openai
|
| 13 |
+
Pillow
|
| 14 |
+
matplotlib
|
| 15 |
+
scipy
|
| 16 |
+
pyyaml
|
| 17 |
+
pytest
|
| 18 |
+
pytest-cov
|