FocusFlow Assistant commited on
Commit
2847ff4
·
1 Parent(s): 068aa2f

Gracefully disable YouTube in Cloud Mode

Browse files
Files changed (5) hide show
  1. app.py +68 -40
  2. backend/config.py +5 -0
  3. backend/main.py +9 -0
  4. backend/rag_engine.py +7 -0
  5. push_all.sh +23 -0
app.py CHANGED
@@ -411,6 +411,16 @@ if "expanded_topics" not in st.session_state: st.session_state.expanded_topics =
411
  if "show_analytics" not in st.session_state: st.session_state.show_analytics = False
412
  if "topic_scores" not in st.session_state: st.session_state.topic_scores = {} # Track quiz performance by topic_id
413
 
 
 
 
 
 
 
 
 
 
 
414
  # Helper function to add auth headers to all API requests
415
  def get_headers():
416
  """Get auth headers for API requests — uses Firebase token if available."""
@@ -1138,57 +1148,75 @@ if not st.session_state.focus_mode:
1138
 
1139
 
1140
  # URL Input
1141
- with st.expander("+ Add URL / YouTube"):
1142
- url_input = st.text_input("URL", placeholder="https://youtube.com/... or any webpage", label_visibility="collapsed")
1143
- if st.button("Process URL", use_container_width=True):
1144
- if not url_input:
1145
- st.warning("Please enter a URL")
1146
- else:
1147
- import re as re_mod
1148
- is_youtube = "youtube.com" in url_input or "youtu.be" in url_input
1149
-
1150
- if is_youtube:
1151
- # Extract video ID
1152
- vid_match = re_mod.search(r'(?:v=|youtu\.be/|shorts/|embed/)([a-zA-Z0-9_-]{11})', url_input)
1153
- if not vid_match:
1154
- st.error("❌ Invalid YouTube URL format. Supported: youtube.com/watch?v=ID, youtu.be/ID, or youtube.com/shorts/ID")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1155
  else:
1156
- video_id = vid_match.group(1)
1157
- with st.spinner("Fetching transcript via Invidious..."):
1158
  try:
1159
- resp = requests.post(f"{API_URL}/ingest_youtube", json={"video_id": video_id}, headers=get_headers(), timeout=120)
1160
  if resp.status_code == 200:
1161
- st.success("✅ YouTube transcript processed successfully!")
1162
  time.sleep(1)
1163
  st.rerun()
1164
  else:
1165
  error_detail = resp.json().get('detail', resp.text)
1166
- if "No captions available" in str(error_detail):
1167
- st.error("❌ No captions found. Try a video with CC enabled.")
1168
- elif "Could not reach any transcript" in str(error_detail):
1169
- st.error("⚠️ Transcript service unavailable. Try again later.")
1170
- else:
1171
- st.error(f"Failed: {error_detail}")
1172
  except requests.Timeout:
1173
  st.error("⏱️ Request timed out. Please try again.")
1174
  except Exception as e:
1175
  st.error(f"Error: {str(e)}")
1176
- else:
1177
- # Non-YouTube URL: use server-side ingestion
1178
- with st.spinner("Fetching content..."):
1179
- try:
1180
- resp = requests.post(f"{API_URL}/ingest_url", json={"url": url_input}, headers=get_headers(), timeout=120)
1181
- if resp.status_code == 200:
1182
- st.success(f"✅ {resp.json().get('message', 'Content added!')}")
1183
- time.sleep(1)
1184
- st.rerun()
1185
- else:
1186
- error_detail = resp.json().get('detail', resp.text)
1187
- st.error(f"Failed: {error_detail}")
1188
- except requests.Timeout:
1189
- st.error("⏱️ Request timed out. Please try again.")
1190
- except Exception as e:
1191
- st.error(f"Error: {str(e)}")
1192
 
1193
  # --- FOCUS MODE UI ---
1194
  if st.session_state.focus_mode:
 
411
  if "show_analytics" not in st.session_state: st.session_state.show_analytics = False
412
  if "topic_scores" not in st.session_state: st.session_state.topic_scores = {} # Track quiz performance by topic_id
413
 
414
+ if "app_config" not in st.session_state:
415
+ try:
416
+ resp = requests.get(f"{API_URL}/config", timeout=5)
417
+ if resp.status_code == 200:
418
+ st.session_state.app_config = resp.json()
419
+ else:
420
+ st.session_state.app_config = {"youtube_enabled": True}
421
+ except Exception:
422
+ st.session_state.app_config = {"youtube_enabled": True}
423
+
424
  # Helper function to add auth headers to all API requests
425
  def get_headers():
426
  """Get auth headers for API requests — uses Firebase token if available."""
 
1148
 
1149
 
1150
  # URL Input
1151
+ youtube_enabled = st.session_state.get("app_config", {}).get("youtube_enabled", True)
1152
+
1153
+ if youtube_enabled:
1154
+ with st.expander("+ Add URL / YouTube"):
1155
+ url_input = st.text_input("URL", placeholder="https://youtube.com/... or any webpage", label_visibility="collapsed")
1156
+ if st.button("Process URL", use_container_width=True):
1157
+ if not url_input:
1158
+ st.warning("Please enter a URL")
1159
+ else:
1160
+ import re as re_mod
1161
+ is_youtube = "youtube.com" in url_input or "youtu.be" in url_input
1162
+
1163
+ if is_youtube:
1164
+ # Extract video ID
1165
+ vid_match = re_mod.search(r'(?:v=|youtu\.be/|shorts/|embed/)([a-zA-Z0-9_-]{11})', url_input)
1166
+ if not vid_match:
1167
+ st.error("❌ Invalid YouTube URL format. Supported: youtube.com/watch?v=ID, youtu.be/ID, or youtube.com/shorts/ID")
1168
+ else:
1169
+ video_id = vid_match.group(1)
1170
+ with st.spinner("⏳ Fetching transcript via Invidious..."):
1171
+ try:
1172
+ resp = requests.post(f"{API_URL}/ingest_youtube", json={"video_id": video_id}, headers=get_headers(), timeout=120)
1173
+ if resp.status_code == 200:
1174
+ st.success("✅ YouTube transcript processed successfully!")
1175
+ time.sleep(1)
1176
+ st.rerun()
1177
+ else:
1178
+ error_detail = resp.json().get('detail', resp.text)
1179
+ if "No captions available" in str(error_detail):
1180
+ st.error("❌ No captions found. Try a video with CC enabled.")
1181
+ elif "Could not reach any transcript" in str(error_detail):
1182
+ st.error("⚠️ Transcript service unavailable. Try again later.")
1183
+ else:
1184
+ st.error(f"Failed: {error_detail}")
1185
+ except requests.Timeout:
1186
+ st.error("⏱️ Request timed out. Please try again.")
1187
+ except Exception as e:
1188
+ st.error(f"Error: {str(e)}")
1189
  else:
1190
+ # Non-YouTube URL: use server-side ingestion
1191
+ with st.spinner("Fetching content..."):
1192
  try:
1193
+ resp = requests.post(f"{API_URL}/ingest_url", json={"url": url_input}, headers=get_headers(), timeout=120)
1194
  if resp.status_code == 200:
1195
+ st.success(f"✅ {resp.json().get('message', 'Content added!')}")
1196
  time.sleep(1)
1197
  st.rerun()
1198
  else:
1199
  error_detail = resp.json().get('detail', resp.text)
1200
+ st.error(f"Failed: {error_detail}")
 
 
 
 
 
1201
  except requests.Timeout:
1202
  st.error("⏱️ Request timed out. Please try again.")
1203
  except Exception as e:
1204
  st.error(f"Error: {str(e)}")
1205
+ else:
1206
+ with st.expander("▶️ Add YouTube Video"):
1207
+ st.info(
1208
+ "⚠️ **YouTube is only available in local mode.**\n\n"
1209
+ "HuggingFace Spaces blocks outbound network requests "
1210
+ "which prevents YouTube transcript fetching.\n\n"
1211
+ "**Alternatives:**\n"
1212
+ "- 📄 Upload a PDF of your notes instead\n"
1213
+ "- 💻 Run FocusFlow locally to use YouTube\n"
1214
+ "- 📋 Paste text directly (coming soon)"
1215
+ )
1216
+ st.markdown(
1217
+ "[▶️ How to run locally](https://github.com/thesivarohith/focusflow#local-setup)",
1218
+ unsafe_allow_html=False
1219
+ )
 
1220
 
1221
  # --- FOCUS MODE UI ---
1222
  if st.session_state.focus_mode:
backend/config.py CHANGED
@@ -13,6 +13,11 @@ class LLMProvider(Enum):
13
  # Read from environment variable, default to Ollama (local)
14
  USE_PROVIDER = os.getenv("LLM_PROVIDER", "ollama").lower()
15
 
 
 
 
 
 
16
  # Configuration for both providers
17
  CONFIG = {
18
  "llm_provider": LLMProvider.OLLAMA if USE_PROVIDER == "ollama" else LLMProvider.HUGGINGFACE,
 
13
  # Read from environment variable, default to Ollama (local)
14
  USE_PROVIDER = os.getenv("LLM_PROVIDER", "ollama").lower()
15
 
16
+ # Set DEPLOYMENT_MODE=cloud in HuggingFace Spaces secrets
17
+ # Leave unset or set to "local" for local development
18
+ DEPLOYMENT_MODE = os.environ.get("DEPLOYMENT_MODE", "local").lower()
19
+ IS_CLOUD = DEPLOYMENT_MODE == "cloud"
20
+
21
  # Configuration for both providers
22
  CONFIG = {
23
  "llm_provider": LLMProvider.OLLAMA if USE_PROVIDER == "ollama" else LLMProvider.HUGGINGFACE,
backend/main.py CHANGED
@@ -20,6 +20,15 @@ def health_check():
20
  """Health check endpoint"""
21
  return {"status": "healthy"}
22
 
 
 
 
 
 
 
 
 
 
23
  # Dependency
24
  def get_db():
25
  db = SessionLocal()
 
20
  """Health check endpoint"""
21
  return {"status": "healthy"}
22
 
23
+ @app.get("/config")
24
+ async def get_config():
25
+ from backend.config import IS_CLOUD, DEPLOYMENT_MODE
26
+ return {
27
+ "is_cloud": IS_CLOUD,
28
+ "deployment_mode": DEPLOYMENT_MODE,
29
+ "youtube_enabled": not IS_CLOUD
30
+ }
31
+
32
  # Dependency
33
  def get_db():
34
  db = SessionLocal()
backend/rag_engine.py CHANGED
@@ -26,6 +26,13 @@ INVIDIOUS_INSTANCES = [
26
 
27
  def get_youtube_transcript(video_id: str) -> str:
28
  """Fetch YouTube transcripts via the Invidious API to bypass network blocks."""
 
 
 
 
 
 
 
29
  # Step 1: Try each Invidious instance until one works
30
  captions_data = None
31
  last_error = None
 
26
 
27
  def get_youtube_transcript(video_id: str) -> str:
28
  """Fetch YouTube transcripts via the Invidious API to bypass network blocks."""
29
+ from backend.config import IS_CLOUD
30
+ if IS_CLOUD:
31
+ raise ValueError(
32
+ "YouTube is not available in cloud mode. "
33
+ "Please upload a PDF instead."
34
+ )
35
+
36
  # Step 1: Try each Invidious instance until one works
37
  captions_data = None
38
  last_error = None
push_all.sh ADDED
@@ -0,0 +1,23 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/bin/bash
2
+ # A script to easily commit and push to both GitHub and HuggingFace
3
+
4
+ if [ -z "$1" ]; then
5
+ echo "Usage: ./push_all.sh \"Commit message\""
6
+ exit 1
7
+ fi
8
+
9
+ COMMIT_MSG=$1
10
+
11
+ echo "Staging changes..."
12
+ git add .
13
+
14
+ echo "Committing with message: $COMMIT_MSG"
15
+ git commit -m "$COMMIT_MSG"
16
+
17
+ echo "Pushing to HuggingFace (origin)..."
18
+ git push origin main
19
+
20
+ echo "Pushing to GitHub (github)..."
21
+ git push github main
22
+
23
+ echo "All done!"