Spaces:
Running
Running
Commit Β·
6ceb8b5
1
Parent(s): 2fccaae
Fix Video Brain URL handling, add API health check, improve error diagnostics
Browse files- README.md +14 -6
- app/api.py +13 -2
- streamlit_app.py +65 -7
README.md
CHANGED
|
@@ -19,28 +19,36 @@ An AI-powered search and research assistant with multiple modes:
|
|
| 19 |
- **Agentic**: Multi-agent RAG with planning
|
| 20 |
- **Analysis**: Data analysis and insights
|
| 21 |
- **Summarize**: Summarize content and documents
|
|
|
|
|
|
|
| 22 |
|
| 23 |
## Features
|
| 24 |
|
| 25 |
-
- π Real-time web search with Tavily
|
| 26 |
- π Document upload and RAG
|
| 27 |
- π€ LangGraph-powered pipelines
|
| 28 |
- π‘ Follow-up question suggestions
|
| 29 |
- πΌοΈ Image search integration
|
| 30 |
- π Knowledge panels
|
|
|
|
| 31 |
|
| 32 |
-
## Environment Variables
|
| 33 |
|
| 34 |
-
Set these secrets in your Hugging Face Space:
|
| 35 |
|
| 36 |
-
|
| 37 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 38 |
|
| 39 |
## Tech Stack
|
| 40 |
|
| 41 |
- FastAPI backend
|
| 42 |
- Streamlit frontend
|
| 43 |
- LangChain + LangGraph
|
| 44 |
-
- Groq LLM (
|
| 45 |
- FAISS vector store
|
| 46 |
- Sentence Transformers embeddings
|
|
|
|
| 19 |
- **Agentic**: Multi-agent RAG with planning
|
| 20 |
- **Analysis**: Data analysis and insights
|
| 21 |
- **Summarize**: Summarize content and documents
|
| 22 |
+
- **Product MVP**: Generate MVP blueprints from ideas
|
| 23 |
+
- **Video Brain**: Analyze YouTube videos
|
| 24 |
|
| 25 |
## Features
|
| 26 |
|
| 27 |
+
- π Real-time web search with Tavily (with AI answers)
|
| 28 |
- π Document upload and RAG
|
| 29 |
- π€ LangGraph-powered pipelines
|
| 30 |
- π‘ Follow-up question suggestions
|
| 31 |
- πΌοΈ Image search integration
|
| 32 |
- π Knowledge panels
|
| 33 |
+
- π₯ YouTube video analysis
|
| 34 |
|
| 35 |
+
## Environment Variables (Required!)
|
| 36 |
|
| 37 |
+
β οΈ **IMPORTANT**: Set these secrets in your Hugging Face Space settings:
|
| 38 |
|
| 39 |
+
1. Go to your Space Settings β Variables and secrets
|
| 40 |
+
2. Add these as **Secrets** (not Repository secrets):
|
| 41 |
+
|
| 42 |
+
- `GROQ_API_KEY`: Your Groq API key (get from https://console.groq.com)
|
| 43 |
+
- `TAVILY_API_KEY`: Your Tavily API key (get from https://tavily.com)
|
| 44 |
+
|
| 45 |
+
Without these keys, web search and LLM responses will not work!
|
| 46 |
|
| 47 |
## Tech Stack
|
| 48 |
|
| 49 |
- FastAPI backend
|
| 50 |
- Streamlit frontend
|
| 51 |
- LangChain + LangGraph
|
| 52 |
+
- Groq LLM (compound-beta)
|
| 53 |
- FAISS vector store
|
| 54 |
- Sentence Transformers embeddings
|
app/api.py
CHANGED
|
@@ -52,14 +52,25 @@ app.add_middleware(
|
|
| 52 |
allow_credentials=True,
|
| 53 |
)
|
| 54 |
|
|
|
|
| 55 |
|
| 56 |
# =======================================================
|
| 57 |
# Health Check Endpoint
|
| 58 |
# =======================================================
|
| 59 |
@app.get("/health")
|
| 60 |
async def health_check():
|
| 61 |
-
"""Health check endpoint
|
| 62 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 63 |
|
| 64 |
|
| 65 |
# =======================================================
|
|
|
|
| 52 |
allow_credentials=True,
|
| 53 |
)
|
| 54 |
|
| 55 |
+
import os
|
| 56 |
|
| 57 |
# =======================================================
|
| 58 |
# Health Check Endpoint
|
| 59 |
# =======================================================
|
| 60 |
@app.get("/health")
|
| 61 |
async def health_check():
|
| 62 |
+
"""Health check endpoint - shows API key status"""
|
| 63 |
+
groq_key = os.getenv("GROQ_API_KEY", "")
|
| 64 |
+
tavily_key = os.getenv("TAVILY_API_KEY", "")
|
| 65 |
+
|
| 66 |
+
return {
|
| 67 |
+
"status": "healthy",
|
| 68 |
+
"service": "perplexity-clone-api",
|
| 69 |
+
"api_keys": {
|
| 70 |
+
"groq": "configured" if groq_key else "MISSING",
|
| 71 |
+
"tavily": "configured" if tavily_key else "MISSING"
|
| 72 |
+
}
|
| 73 |
+
}
|
| 74 |
|
| 75 |
|
| 76 |
# =======================================================
|
streamlit_app.py
CHANGED
|
@@ -42,6 +42,11 @@ if "product_ideas" not in st.session_state:
|
|
| 42 |
API_URL = os.getenv("BACKEND_URL", "http://127.0.0.1:8000")
|
| 43 |
WORKSPACE = "default"
|
| 44 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 45 |
# MODE MAPPING - All 8 modes with correct backend endpoints
|
| 46 |
MODES = {
|
| 47 |
"Automatic": {
|
|
@@ -456,6 +461,37 @@ def get_css():
|
|
| 456 |
st.markdown(get_css(), unsafe_allow_html=True)
|
| 457 |
|
| 458 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 459 |
# =====================================
|
| 460 |
# HELPER FUNCTIONS
|
| 461 |
# =====================================
|
|
@@ -635,11 +671,16 @@ if st.session_state.mode == "Video Brain" and not st.session_state.current_resul
|
|
| 635 |
<div style="text-align: center; padding: 20px; margin: 20px auto; max-width: 700px;
|
| 636 |
background: linear-gradient(135deg, #3B82F6 0%, #8B5CF6 100%);
|
| 637 |
border-radius: 16px; color: white;">
|
| 638 |
-
<h3 style="margin: 0; font-size: 24px;">π₯ Video Brain β Understand Any YouTube
|
| 639 |
-
<p style="margin: 10px 0 0; opacity: 0.9;">π΅
|
| 640 |
</div>
|
| 641 |
""", unsafe_allow_html=True)
|
| 642 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 643 |
# YouTube URL input - auto-loads on change
|
| 644 |
youtube_url = st.text_input(
|
| 645 |
"YouTube URL",
|
|
@@ -689,26 +730,43 @@ if st.session_state.uploaded_files:
|
|
| 689 |
# =====================================
|
| 690 |
if submit and query.strip():
|
| 691 |
extra_data = None
|
|
|
|
| 692 |
|
| 693 |
# For Video Brain mode, include the YouTube URL
|
| 694 |
if st.session_state.mode == "Video Brain":
|
| 695 |
-
if
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 696 |
extra_data = {"youtube_url": st.session_state.youtube_url}
|
| 697 |
else:
|
| 698 |
-
st.warning("β οΈ Please
|
| 699 |
st.stop()
|
| 700 |
|
| 701 |
# For Product MVP mode, save to ideas history
|
| 702 |
if st.session_state.mode == "Product MVP":
|
| 703 |
st.session_state.product_ideas.append({
|
| 704 |
-
"idea":
|
| 705 |
"time": "just now"
|
| 706 |
})
|
| 707 |
|
| 708 |
with st.spinner(f"π {st.session_state.mode}..."):
|
| 709 |
-
result = call_api(
|
| 710 |
st.session_state.current_result = {
|
| 711 |
-
"query":
|
| 712 |
"mode": st.session_state.mode,
|
| 713 |
"data": result
|
| 714 |
}
|
|
|
|
| 42 |
API_URL = os.getenv("BACKEND_URL", "http://127.0.0.1:8000")
|
| 43 |
WORKSPACE = "default"
|
| 44 |
|
| 45 |
+
# Check API health on startup
|
| 46 |
+
if "api_checked" not in st.session_state:
|
| 47 |
+
st.session_state.api_checked = False
|
| 48 |
+
st.session_state.api_status = None
|
| 49 |
+
|
| 50 |
# MODE MAPPING - All 8 modes with correct backend endpoints
|
| 51 |
MODES = {
|
| 52 |
"Automatic": {
|
|
|
|
| 461 |
st.markdown(get_css(), unsafe_allow_html=True)
|
| 462 |
|
| 463 |
|
| 464 |
+
# =====================================
|
| 465 |
+
# API HEALTH CHECK
|
| 466 |
+
# =====================================
|
| 467 |
+
def check_api_health():
|
| 468 |
+
"""Check if API is healthy and keys are configured."""
|
| 469 |
+
try:
|
| 470 |
+
resp = requests.get(f"{API_URL}/health", timeout=5)
|
| 471 |
+
if resp.ok:
|
| 472 |
+
return resp.json()
|
| 473 |
+
except:
|
| 474 |
+
pass
|
| 475 |
+
return None
|
| 476 |
+
|
| 477 |
+
# Check API on first load
|
| 478 |
+
if not st.session_state.api_checked:
|
| 479 |
+
st.session_state.api_status = check_api_health()
|
| 480 |
+
st.session_state.api_checked = True
|
| 481 |
+
|
| 482 |
+
# Show warnings if API keys are missing
|
| 483 |
+
if st.session_state.api_status:
|
| 484 |
+
api_keys = st.session_state.api_status.get("api_keys", {})
|
| 485 |
+
missing_keys = []
|
| 486 |
+
if api_keys.get("groq") == "MISSING":
|
| 487 |
+
missing_keys.append("GROQ_API_KEY")
|
| 488 |
+
if api_keys.get("tavily") == "MISSING":
|
| 489 |
+
missing_keys.append("TAVILY_API_KEY")
|
| 490 |
+
|
| 491 |
+
if missing_keys:
|
| 492 |
+
st.error(f"β οΈ Missing API keys: {', '.join(missing_keys)}. Please configure them in your Hugging Face Space settings under 'Variables and secrets'.")
|
| 493 |
+
|
| 494 |
+
|
| 495 |
# =====================================
|
| 496 |
# HELPER FUNCTIONS
|
| 497 |
# =====================================
|
|
|
|
| 671 |
<div style="text-align: center; padding: 20px; margin: 20px auto; max-width: 700px;
|
| 672 |
background: linear-gradient(135deg, #3B82F6 0%, #8B5CF6 100%);
|
| 673 |
border-radius: 16px; color: white;">
|
| 674 |
+
<h3 style="margin: 0; font-size: 24px;">π₯ Video Brain β Understand Any YouTube Video</h3>
|
| 675 |
+
<p style="margin: 10px 0 0; opacity: 0.9;">π΅ Two ways to use:</p>
|
| 676 |
</div>
|
| 677 |
""", unsafe_allow_html=True)
|
| 678 |
|
| 679 |
+
st.markdown("""
|
| 680 |
+
**Option 1:** Paste a YouTube URL directly in the search box and click Search
|
| 681 |
+
**Option 2:** Load the URL below first, then ask questions
|
| 682 |
+
""")
|
| 683 |
+
|
| 684 |
# YouTube URL input - auto-loads on change
|
| 685 |
youtube_url = st.text_input(
|
| 686 |
"YouTube URL",
|
|
|
|
| 730 |
# =====================================
|
| 731 |
if submit and query.strip():
|
| 732 |
extra_data = None
|
| 733 |
+
actual_query = query.strip()
|
| 734 |
|
| 735 |
# For Video Brain mode, include the YouTube URL
|
| 736 |
if st.session_state.mode == "Video Brain":
|
| 737 |
+
# Check if query itself contains a YouTube URL
|
| 738 |
+
query_text = query.strip()
|
| 739 |
+
url_in_query = None
|
| 740 |
+
for word in query_text.split():
|
| 741 |
+
if "youtube.com" in word or "youtu.be" in word:
|
| 742 |
+
url_in_query = word
|
| 743 |
+
break
|
| 744 |
+
|
| 745 |
+
if url_in_query:
|
| 746 |
+
# User pasted URL directly in the query
|
| 747 |
+
st.session_state.youtube_url = url_in_query
|
| 748 |
+
st.session_state.video_loaded = True
|
| 749 |
+
extra_data = {"youtube_url": url_in_query}
|
| 750 |
+
# If query is JUST the URL, ask a default question
|
| 751 |
+
if query_text == url_in_query:
|
| 752 |
+
actual_query = "Summarize this video and give me the key takeaways"
|
| 753 |
+
elif st.session_state.video_loaded and st.session_state.youtube_url:
|
| 754 |
extra_data = {"youtube_url": st.session_state.youtube_url}
|
| 755 |
else:
|
| 756 |
+
st.warning("β οΈ Please paste a YouTube URL in the search box or load one above!")
|
| 757 |
st.stop()
|
| 758 |
|
| 759 |
# For Product MVP mode, save to ideas history
|
| 760 |
if st.session_state.mode == "Product MVP":
|
| 761 |
st.session_state.product_ideas.append({
|
| 762 |
+
"idea": actual_query,
|
| 763 |
"time": "just now"
|
| 764 |
})
|
| 765 |
|
| 766 |
with st.spinner(f"π {st.session_state.mode}..."):
|
| 767 |
+
result = call_api(actual_query, st.session_state.mode, extra_data)
|
| 768 |
st.session_state.current_result = {
|
| 769 |
+
"query": actual_query,
|
| 770 |
"mode": st.session_state.mode,
|
| 771 |
"data": result
|
| 772 |
}
|