Spaces:
Sleeping
Sleeping
github-actions[bot] commited on
Commit ·
dde0c6d
1
Parent(s): 627c009
Sync from GitHub 907b7448edad59db074f2417e42629ba5c3f1cc7
Browse files- README.md +1 -2
- frontend/app.py +96 -20
README.md
CHANGED
|
@@ -273,8 +273,7 @@ This repo now includes:
|
|
| 273 |
4. For HF OAuth mode, also set:
|
| 274 |
- `HF_OAUTH_CLIENT_ID`
|
| 275 |
- `HF_OAUTH_CLIENT_SECRET`
|
| 276 |
-
- `HF_OAUTH_REDIRECT_URI` (
|
| 277 |
- `AUTH_SUCCESS_REDIRECT_URL` (your Space URL)
|
| 278 |
-
- `BACKEND_PUBLIC_URL=https://<space>.hf.space/proxy/8000` (used for browser OAuth links)
|
| 279 |
|
| 280 |
The container exposes Streamlit on port `7860` and points the frontend to the internal backend via `BACKEND_URL=http://127.0.0.1:8000`.
|
|
|
|
| 273 |
4. For HF OAuth mode, also set:
|
| 274 |
- `HF_OAUTH_CLIENT_ID`
|
| 275 |
- `HF_OAUTH_CLIENT_SECRET`
|
| 276 |
+
- `HF_OAUTH_REDIRECT_URI` (Space URL registered in your HF Connected App, e.g. `https://<space>.hf.space/`)
|
| 277 |
- `AUTH_SUCCESS_REDIRECT_URL` (your Space URL)
|
|
|
|
| 278 |
|
| 279 |
The container exposes Streamlit on port `7860` and points the frontend to the internal backend via `BACKEND_URL=http://127.0.0.1:8000`.
|
frontend/app.py
CHANGED
|
@@ -9,7 +9,6 @@ import streamlit as st
|
|
| 9 |
st.set_page_config(page_title="NotebookLM Clone", page_icon="📚", layout="wide")
|
| 10 |
|
| 11 |
BACKEND_URL = os.getenv("BACKEND_URL", "http://127.0.0.1:8000")
|
| 12 |
-
BACKEND_PUBLIC_URL = os.getenv("BACKEND_PUBLIC_URL", "").strip()
|
| 13 |
REQUEST_TIMEOUT_SECONDS = 30
|
| 14 |
|
| 15 |
STATUS_LABELS = {
|
|
@@ -115,21 +114,58 @@ def api_post_multipart(
|
|
| 115 |
return api_request("POST", path, data=data, files=files)
|
| 116 |
|
| 117 |
|
| 118 |
-
def
|
| 119 |
-
|
| 120 |
-
|
| 121 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 122 |
|
| 123 |
|
| 124 |
-
def
|
| 125 |
-
|
| 126 |
-
if
|
| 127 |
-
return
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 128 |
|
| 129 |
-
|
| 130 |
-
if not
|
| 131 |
-
|
| 132 |
-
|
|
|
|
|
|
|
| 133 |
|
| 134 |
|
| 135 |
def fetch_notebooks() -> tuple[bool, list[dict] | str]:
|
|
@@ -718,6 +754,10 @@ if "bridge_error" not in st.session_state:
|
|
| 718 |
st.session_state["bridge_error"] = None
|
| 719 |
if "processed_bridge_token" not in st.session_state:
|
| 720 |
st.session_state["processed_bridge_token"] = None
|
|
|
|
|
|
|
|
|
|
|
|
|
| 721 |
|
| 722 |
bridge_token = get_query_param("auth_bridge")
|
| 723 |
if bridge_token and bridge_token != st.session_state["processed_bridge_token"]:
|
|
@@ -730,6 +770,34 @@ if bridge_token and bridge_token != st.session_state["processed_bridge_token"]:
|
|
| 730 |
st.session_state["bridge_error"] = str(bridge_result)
|
| 731 |
st.rerun()
|
| 732 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 733 |
with st.sidebar:
|
| 734 |
st.markdown("### Workspace")
|
| 735 |
st.caption("Connected client settings")
|
|
@@ -763,6 +831,7 @@ with st.sidebar:
|
|
| 763 |
st.session_state.pop("selected_notebook_id", None)
|
| 764 |
st.session_state.pop("selected_notebook_title", None)
|
| 765 |
st.session_state.pop("selected_thread_id", None)
|
|
|
|
| 766 |
st.rerun()
|
| 767 |
elif auth_mode == "dev":
|
| 768 |
with st.form("dev_login_form"):
|
|
@@ -781,14 +850,21 @@ with st.sidebar:
|
|
| 781 |
st.rerun()
|
| 782 |
st.error(f"Login failed: {login_result}")
|
| 783 |
elif auth_mode == "hf_oauth":
|
| 784 |
-
|
| 785 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 786 |
st.caption("After provider login, you will be redirected back and signed in automatically.")
|
| 787 |
-
if not BACKEND_PUBLIC_URL and ("127.0.0.1" in login_url or "localhost" in login_url):
|
| 788 |
-
st.info(
|
| 789 |
-
"If this app is hosted remotely, set BACKEND_PUBLIC_URL "
|
| 790 |
-
"(example: https://<space>.hf.space/proxy/8000)."
|
| 791 |
-
)
|
| 792 |
if st.button("Refresh auth"):
|
| 793 |
st.rerun()
|
| 794 |
else:
|
|
|
|
| 9 |
st.set_page_config(page_title="NotebookLM Clone", page_icon="📚", layout="wide")
|
| 10 |
|
| 11 |
BACKEND_URL = os.getenv("BACKEND_URL", "http://127.0.0.1:8000")
|
|
|
|
| 12 |
REQUEST_TIMEOUT_SECONDS = 30
|
| 13 |
|
| 14 |
STATUS_LABELS = {
|
|
|
|
| 114 |
return api_request("POST", path, data=data, files=files)
|
| 115 |
|
| 116 |
|
| 117 |
+
def request_backend_get_raw(
|
| 118 |
+
path: str,
|
| 119 |
+
*,
|
| 120 |
+
params: dict | None = None,
|
| 121 |
+
allow_redirects: bool = True,
|
| 122 |
+
) -> tuple[bool, requests.Response | str]:
|
| 123 |
+
base_url = st.session_state.get("backend_url", BACKEND_URL).rstrip("/")
|
| 124 |
+
url = f"{base_url}{path}"
|
| 125 |
+
try:
|
| 126 |
+
response = get_http_session().request(
|
| 127 |
+
method="GET",
|
| 128 |
+
url=url,
|
| 129 |
+
params=params,
|
| 130 |
+
timeout=REQUEST_TIMEOUT_SECONDS,
|
| 131 |
+
allow_redirects=allow_redirects,
|
| 132 |
+
)
|
| 133 |
+
return True, response
|
| 134 |
+
except requests.RequestException as exc:
|
| 135 |
+
return False, str(exc)
|
| 136 |
|
| 137 |
|
| 138 |
+
def begin_hf_oauth() -> tuple[bool, str]:
|
| 139 |
+
ok, response_or_error = request_backend_get_raw("/auth/login", allow_redirects=False)
|
| 140 |
+
if not ok:
|
| 141 |
+
return False, str(response_or_error)
|
| 142 |
+
|
| 143 |
+
response = response_or_error
|
| 144 |
+
if not isinstance(response, requests.Response):
|
| 145 |
+
return False, "Unexpected OAuth start response."
|
| 146 |
+
if response.status_code not in {301, 302, 303, 307, 308}:
|
| 147 |
+
return False, f"OAuth start failed with HTTP {response.status_code}: {response.text}"
|
| 148 |
+
location = response.headers.get("location", "").strip()
|
| 149 |
+
if not location:
|
| 150 |
+
return False, "OAuth start failed: missing redirect location."
|
| 151 |
+
return True, location
|
| 152 |
+
|
| 153 |
+
|
| 154 |
+
def complete_hf_oauth(code: str, state: str) -> tuple[bool, str | None]:
|
| 155 |
+
ok, response_or_error = request_backend_get_raw(
|
| 156 |
+
"/auth/callback",
|
| 157 |
+
params={"code": code, "state": state},
|
| 158 |
+
allow_redirects=False,
|
| 159 |
+
)
|
| 160 |
+
if not ok:
|
| 161 |
+
return False, str(response_or_error)
|
| 162 |
|
| 163 |
+
response = response_or_error
|
| 164 |
+
if not isinstance(response, requests.Response):
|
| 165 |
+
return False, "Unexpected OAuth callback response."
|
| 166 |
+
if response.status_code in {301, 302, 303, 307, 308}:
|
| 167 |
+
return True, None
|
| 168 |
+
return False, f"OAuth callback failed with HTTP {response.status_code}: {response.text}"
|
| 169 |
|
| 170 |
|
| 171 |
def fetch_notebooks() -> tuple[bool, list[dict] | str]:
|
|
|
|
| 754 |
st.session_state["bridge_error"] = None
|
| 755 |
if "processed_bridge_token" not in st.session_state:
|
| 756 |
st.session_state["processed_bridge_token"] = None
|
| 757 |
+
if "processed_oauth_callback" not in st.session_state:
|
| 758 |
+
st.session_state["processed_oauth_callback"] = None
|
| 759 |
+
if "oauth_login_url" not in st.session_state:
|
| 760 |
+
st.session_state["oauth_login_url"] = None
|
| 761 |
|
| 762 |
bridge_token = get_query_param("auth_bridge")
|
| 763 |
if bridge_token and bridge_token != st.session_state["processed_bridge_token"]:
|
|
|
|
| 770 |
st.session_state["bridge_error"] = str(bridge_result)
|
| 771 |
st.rerun()
|
| 772 |
|
| 773 |
+
oauth_error = get_query_param("error")
|
| 774 |
+
oauth_error_description = get_query_param("error_description")
|
| 775 |
+
oauth_code = get_query_param("code")
|
| 776 |
+
oauth_state = get_query_param("state")
|
| 777 |
+
if oauth_error:
|
| 778 |
+
st.session_state["bridge_error"] = (
|
| 779 |
+
f"OAuth provider error: {oauth_error} ({oauth_error_description})"
|
| 780 |
+
if oauth_error_description
|
| 781 |
+
else f"OAuth provider error: {oauth_error}"
|
| 782 |
+
)
|
| 783 |
+
for key in ("error", "error_description", "code", "state"):
|
| 784 |
+
remove_query_param(key)
|
| 785 |
+
st.rerun()
|
| 786 |
+
|
| 787 |
+
if oauth_code and oauth_state:
|
| 788 |
+
callback_key = f"{oauth_code}:{oauth_state}"
|
| 789 |
+
if callback_key != st.session_state["processed_oauth_callback"]:
|
| 790 |
+
st.session_state["processed_oauth_callback"] = callback_key
|
| 791 |
+
callback_ok, callback_error = complete_hf_oauth(code=oauth_code, state=oauth_state)
|
| 792 |
+
for key in ("code", "state", "auth_bridge"):
|
| 793 |
+
remove_query_param(key)
|
| 794 |
+
if callback_ok:
|
| 795 |
+
st.session_state["bridge_error"] = None
|
| 796 |
+
st.session_state["oauth_login_url"] = None
|
| 797 |
+
st.rerun()
|
| 798 |
+
st.session_state["bridge_error"] = str(callback_error)
|
| 799 |
+
st.rerun()
|
| 800 |
+
|
| 801 |
with st.sidebar:
|
| 802 |
st.markdown("### Workspace")
|
| 803 |
st.caption("Connected client settings")
|
|
|
|
| 831 |
st.session_state.pop("selected_notebook_id", None)
|
| 832 |
st.session_state.pop("selected_notebook_title", None)
|
| 833 |
st.session_state.pop("selected_thread_id", None)
|
| 834 |
+
st.session_state["oauth_login_url"] = None
|
| 835 |
st.rerun()
|
| 836 |
elif auth_mode == "dev":
|
| 837 |
with st.form("dev_login_form"):
|
|
|
|
| 850 |
st.rerun()
|
| 851 |
st.error(f"Login failed: {login_result}")
|
| 852 |
elif auth_mode == "hf_oauth":
|
| 853 |
+
if not st.session_state.get("oauth_login_url"):
|
| 854 |
+
oauth_ok, oauth_result = begin_hf_oauth()
|
| 855 |
+
if oauth_ok:
|
| 856 |
+
st.session_state["oauth_login_url"] = oauth_result
|
| 857 |
+
else:
|
| 858 |
+
st.session_state["bridge_error"] = str(oauth_result)
|
| 859 |
+
login_url = st.session_state.get("oauth_login_url")
|
| 860 |
+
if login_url:
|
| 861 |
+
st.markdown(f"[Sign in with Hugging Face]({login_url})")
|
| 862 |
+
else:
|
| 863 |
+
st.warning("Unable to start OAuth. Check backend auth settings and retry.")
|
| 864 |
+
if st.button("Refresh login link"):
|
| 865 |
+
st.session_state["oauth_login_url"] = None
|
| 866 |
+
st.rerun()
|
| 867 |
st.caption("After provider login, you will be redirected back and signed in automatically.")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 868 |
if st.button("Refresh auth"):
|
| 869 |
st.rerun()
|
| 870 |
else:
|