auth button fixed
Browse files- app.py +43 -0
- assets/app.js +34 -1
- assets/index.html +6 -2
- assets/server.css +4 -0
app.py
CHANGED
|
@@ -7,12 +7,18 @@ import subprocess
|
|
| 7 |
# Python port (7861). Stale Node workers from prior restarts can block it.
|
| 8 |
os.environ["GRADIO_NUM_PORTS"] = "20"
|
| 9 |
|
|
|
|
| 10 |
from pathlib import Path
|
|
|
|
| 11 |
|
| 12 |
import gradio as gr
|
|
|
|
| 13 |
from fastapi.responses import HTMLResponse
|
| 14 |
from fastapi.staticfiles import StaticFiles
|
| 15 |
from gradio import Server
|
|
|
|
|
|
|
|
|
|
| 16 |
|
| 17 |
from ui import server_api
|
| 18 |
|
|
@@ -27,6 +33,29 @@ async def homepage() -> str:
|
|
| 27 |
return (ASSETS_DIR / "index.html").read_text(encoding="utf-8")
|
| 28 |
|
| 29 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 30 |
@app.api(name="get_intake_choices")
|
| 31 |
def api_get_intake_choices() -> dict:
|
| 32 |
return server_api.get_intake_choices()
|
|
@@ -95,6 +124,20 @@ def _cleanup_stale_gradio_nodes() -> None:
|
|
| 95 |
pass
|
| 96 |
|
| 97 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 98 |
if __name__ == "__main__":
|
| 99 |
_cleanup_stale_gradio_nodes()
|
| 100 |
app.launch(show_error=True)
|
|
|
|
| 7 |
# Python port (7861). Stale Node workers from prior restarts can block it.
|
| 8 |
os.environ["GRADIO_NUM_PORTS"] = "20"
|
| 9 |
|
| 10 |
+
import time
|
| 11 |
from pathlib import Path
|
| 12 |
+
from typing import Any
|
| 13 |
|
| 14 |
import gradio as gr
|
| 15 |
+
from fastapi import Request
|
| 16 |
from fastapi.responses import HTMLResponse
|
| 17 |
from fastapi.staticfiles import StaticFiles
|
| 18 |
from gradio import Server
|
| 19 |
+
from gradio.blocks import Blocks
|
| 20 |
+
from gradio.components import LoginButton
|
| 21 |
+
from gradio.events import api as gr_api
|
| 22 |
|
| 23 |
from ui import server_api
|
| 24 |
|
|
|
|
| 33 |
return (ASSETS_DIR / "index.html").read_text(encoding="utf-8")
|
| 34 |
|
| 35 |
|
| 36 |
+
@app.get("/api/auth/status")
|
| 37 |
+
async def auth_status(request: Request) -> dict[str, Any]:
|
| 38 |
+
session = getattr(request, "session", None)
|
| 39 |
+
if session is None:
|
| 40 |
+
return {"logged_in": False}
|
| 41 |
+
|
| 42 |
+
oauth_info = session.get("oauth_info")
|
| 43 |
+
if not oauth_info:
|
| 44 |
+
return {"logged_in": False}
|
| 45 |
+
|
| 46 |
+
expires_at = oauth_info.get("expires_at")
|
| 47 |
+
if expires_at is not None and expires_at <= time.time():
|
| 48 |
+
session.pop("oauth_info", None)
|
| 49 |
+
return {"logged_in": False}
|
| 50 |
+
|
| 51 |
+
userinfo = oauth_info.get("userinfo") or {}
|
| 52 |
+
return {
|
| 53 |
+
"logged_in": True,
|
| 54 |
+
"username": userinfo.get("preferred_username"),
|
| 55 |
+
"name": userinfo.get("name"),
|
| 56 |
+
}
|
| 57 |
+
|
| 58 |
+
|
| 59 |
@app.api(name="get_intake_choices")
|
| 60 |
def api_get_intake_choices() -> dict:
|
| 61 |
return server_api.get_intake_choices()
|
|
|
|
| 124 |
pass
|
| 125 |
|
| 126 |
|
| 127 |
+
def _launch_with_oauth(**kwargs: Any):
|
| 128 |
+
"""Launch with a hidden LoginButton so HF OAuth routes are registered."""
|
| 129 |
+
with Blocks() as blocks:
|
| 130 |
+
LoginButton(visible=False)
|
| 131 |
+
for fn, api_kwargs in app._deferred_apis:
|
| 132 |
+
gr_api(fn=fn, **api_kwargs)
|
| 133 |
+
|
| 134 |
+
os.environ["GRADIO_SERVER_MODE_ENABLED"] = "1"
|
| 135 |
+
return blocks.launch(_app=app, **kwargs)
|
| 136 |
+
|
| 137 |
+
|
| 138 |
+
app.launch = _launch_with_oauth # type: ignore[method-assign]
|
| 139 |
+
|
| 140 |
+
|
| 141 |
if __name__ == "__main__":
|
| 142 |
_cleanup_stale_gradio_nodes()
|
| 143 |
app.launch(show_error=True)
|
assets/app.js
CHANGED
|
@@ -40,8 +40,41 @@ const els = {
|
|
| 40 |
intakeForm: document.getElementById("intake-form"),
|
| 41 |
createPrompt: document.getElementById("create-prompt"),
|
| 42 |
personaList: document.getElementById("persona-list"),
|
|
|
|
|
|
|
| 43 |
};
|
| 44 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 45 |
function activeStatusBanner() {
|
| 46 |
return state.view === "form" ? els.formStatus : els.statusBanner;
|
| 47 |
}
|
|
@@ -314,5 +347,5 @@ els.chatInput.addEventListener("keydown", (event) => {
|
|
| 314 |
}
|
| 315 |
});
|
| 316 |
|
| 317 |
-
await loadChoices();
|
| 318 |
renderMessages();
|
|
|
|
| 40 |
intakeForm: document.getElementById("intake-form"),
|
| 41 |
createPrompt: document.getElementById("create-prompt"),
|
| 42 |
personaList: document.getElementById("persona-list"),
|
| 43 |
+
authLogin: document.getElementById("auth-login"),
|
| 44 |
+
authLogout: document.getElementById("auth-logout"),
|
| 45 |
};
|
| 46 |
|
| 47 |
+
function authRedirectTarget() {
|
| 48 |
+
return encodeURIComponent(window.location.pathname + window.location.search);
|
| 49 |
+
}
|
| 50 |
+
|
| 51 |
+
function updateAuthUI({ logged_in: loggedIn, username }) {
|
| 52 |
+
const target = authRedirectTarget();
|
| 53 |
+
if (loggedIn) {
|
| 54 |
+
els.authLogin.hidden = true;
|
| 55 |
+
els.authLogout.hidden = false;
|
| 56 |
+
els.authLogout.textContent = username ? `Log out (${username})` : "Log out";
|
| 57 |
+
els.authLogout.href = `/logout?_target_url=${target}`;
|
| 58 |
+
} else {
|
| 59 |
+
els.authLogin.hidden = false;
|
| 60 |
+
els.authLogout.hidden = true;
|
| 61 |
+
els.authLogin.href = `/login/huggingface?_target_url=${target}`;
|
| 62 |
+
}
|
| 63 |
+
}
|
| 64 |
+
|
| 65 |
+
async function loadAuthStatus() {
|
| 66 |
+
try {
|
| 67 |
+
const response = await fetch("/api/auth/status", { credentials: "include" });
|
| 68 |
+
if (!response.ok) {
|
| 69 |
+
updateAuthUI({ logged_in: false });
|
| 70 |
+
return;
|
| 71 |
+
}
|
| 72 |
+
updateAuthUI(await response.json());
|
| 73 |
+
} catch {
|
| 74 |
+
updateAuthUI({ logged_in: false });
|
| 75 |
+
}
|
| 76 |
+
}
|
| 77 |
+
|
| 78 |
function activeStatusBanner() {
|
| 79 |
return state.view === "form" ? els.formStatus : els.statusBanner;
|
| 80 |
}
|
|
|
|
| 347 |
}
|
| 348 |
});
|
| 349 |
|
| 350 |
+
await Promise.all([loadChoices(), loadAuthStatus()]);
|
| 351 |
renderMessages();
|
assets/index.html
CHANGED
|
@@ -21,8 +21,12 @@
|
|
| 21 |
</p>
|
| 22 |
</div>
|
| 23 |
<div class="auth-actions">
|
| 24 |
-
<a href="/login/huggingface"
|
| 25 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 26 |
</div>
|
| 27 |
</header>
|
| 28 |
|
|
|
|
| 21 |
</p>
|
| 22 |
</div>
|
| 23 |
<div class="auth-actions">
|
| 24 |
+
<a id="auth-login" class="auth-button" href="/login/huggingface" hidden>
|
| 25 |
+
Log in with Hugging Face
|
| 26 |
+
</a>
|
| 27 |
+
<a id="auth-logout" class="auth-button" href="/logout" hidden>
|
| 28 |
+
Log out
|
| 29 |
+
</a>
|
| 30 |
</div>
|
| 31 |
</header>
|
| 32 |
|
assets/server.css
CHANGED
|
@@ -46,6 +46,10 @@ a {
|
|
| 46 |
align-items: center;
|
| 47 |
}
|
| 48 |
|
|
|
|
|
|
|
|
|
|
|
|
|
| 49 |
.auth-actions a,
|
| 50 |
button {
|
| 51 |
border-radius: 8px;
|
|
|
|
| 46 |
align-items: center;
|
| 47 |
}
|
| 48 |
|
| 49 |
+
.auth-actions [hidden] {
|
| 50 |
+
display: none !important;
|
| 51 |
+
}
|
| 52 |
+
|
| 53 |
.auth-actions a,
|
| 54 |
button {
|
| 55 |
border-radius: 8px;
|