| """ |
| Technical Mindmap Generator - Main Streamlit Application |
| Interactive web app for generating radial mindmaps from technical keywords |
| |
| Features: |
| - Modern UI inspired by GPT, Claude, and Perplexity |
| - Sequential API calls for optimal data quality |
| - Interactive radial mindmaps with PyVis |
| - Query history and session management |
| - Real-time data from Tavily, Knowledge Graph, and Gemini APIs |
| """ |
|
|
| import streamlit as st |
| import sys |
| from pathlib import Path |
| import time |
|
|
| |
| project_root = Path(__file__).parent |
| sys.path.insert(0, str(project_root)) |
|
|
| |
| try: |
| from config.settings import settings, display_settings_info |
| from utils.api_handler import fetch_mindmap_data |
| from utils.mindmap_generator import MindmapGenerator |
| except ImportError as e: |
| st.error(f"Import Error: {e}") |
| st.info("Make sure all project files are in the correct directories") |
| st.stop() |
|
|
|
|
| |
| |
| |
|
|
| st.set_page_config( |
| page_title="Technical Mindmap Generator π§ ", |
| page_icon="π§ ", |
| layout="wide", |
| initial_sidebar_state="collapsed", |
| menu_items={ |
| 'Get Help': None, |
| 'Report a bug': None, |
| 'About': "# Technical Mindmap Generator\nPowered by Gemini, Tavily, and Knowledge Graph APIs" |
| } |
| ) |
|
|
|
|
| |
| |
| |
|
|
| st.markdown(""" |
| <style> |
| /* Import Google Fonts - Claude uses serif fonts */ |
| @import url('https://fonts.googleapis.com/css2?family=Merriweather:wght@300;400;700&family=Inter:wght@400;500;600;700&display=swap'); |
| |
| /* ============================================================ |
| MAIN CONTAINER - Claude's Warm Aesthetic |
| ============================================================ */ |
| .main { |
| background: #faf9f5; |
| padding: 2rem; |
| font-family: 'Inter', -apple-system, BlinkMacSystemFont, sans-serif; |
| } |
| |
| /* Remove default padding */ |
| .block-container { |
| padding-top: 2rem; |
| padding-bottom: 3rem; |
| max-width: 1000px; |
| } |
| |
| /* ============================================================ |
| HEADER SECTION |
| ============================================================ */ |
| h1 { |
| color: #3d3929 !important; |
| text-align: center !important; |
| font-size: 3.2rem !important; |
| font-weight: 700 !important; |
| margin-bottom: 0.5rem !important; |
| letter-spacing: -0.5px !important; |
| font-family: 'Merriweather', serif !important; |
| } |
| |
| /* Subtitle */ |
| .subtitle { |
| color: #6b6656 !important; |
| text-align: center !important; |
| font-size: 1.2rem !important; |
| margin-bottom: 3rem !important; |
| font-weight: 400 !important; |
| font-family: 'Inter', sans-serif !important; |
| line-height: 1.6 !important; |
| } |
| |
| /* ============================================================ |
| INPUT CONTAINER - Claude's Clean Design |
| ============================================================ */ |
| .stTextInput > div > div > input { |
| background: #ffffff !important; |
| color: #3d3929 !important; |
| border-radius: 12px !important; |
| padding: 18px 24px !important; |
| font-size: 16px !important; |
| font-weight: 400 !important; |
| border: 1.5px solid #ddd9cc !important; |
| box-shadow: 0 1px 3px rgba(0, 0, 0, 0.06) !important; |
| transition: all 0.2s ease !important; |
| font-family: 'Inter', sans-serif !important; |
| } |
| |
| .stTextInput > div > div > input:focus { |
| border-color: #c15f3c !important; |
| box-shadow: 0 0 0 3px rgba(193, 95, 60, 0.1), |
| 0 1px 3px rgba(0, 0, 0, 0.06) !important; |
| outline: none !important; |
| } |
| |
| .stTextInput > div > div > input::placeholder { |
| color: #9b9688; |
| font-weight: 400; |
| } |
| |
| /* ============================================================ |
| BUTTON STYLING - Claude's Orange |
| ============================================================ */ |
| .stButton > button { |
| background: #c15f3c !important; |
| color: #ffffff !important; |
| border-radius: 10px !important; |
| padding: 14px 32px !important; |
| font-size: 15px !important; |
| font-weight: 600 !important; |
| border: none !important; |
| box-shadow: 0 2px 4px rgba(193, 95, 60, 0.2) !important; |
| transition: all 0.2s ease !important; |
| font-family: 'Inter', sans-serif !important; |
| letter-spacing: 0.3px !important; |
| } |
| |
| .stButton > button:hover { |
| background: #a14a2f !important; |
| box-shadow: 0 4px 8px rgba(193, 95, 60, 0.25) !important; |
| transform: translateY(-1px) !important; |
| } |
| |
| .stButton > button:active { |
| transform: translateY(0) !important; |
| box-shadow: 0 1px 2px rgba(193, 95, 60, 0.2) !important; |
| } |
| |
| /* ============================================================ |
| MINDMAP CARD - Clean White Container |
| ============================================================ */ |
| .mindmap-card { |
| background: #ffffff; |
| border-radius: 16px; |
| padding: 2.5rem; |
| box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08), |
| 0 1px 2px rgba(0, 0, 0, 0.04); |
| margin: 2rem 0; |
| border: 1px solid #ebe9e0; |
| } |
| |
| .mindmap-card h3 { |
| color: #3d3929 !important; |
| font-weight: 700 !important; |
| margin-bottom: 1rem !important; |
| font-size: 1.6rem !important; |
| font-family: 'Merriweather', serif !important; |
| } |
| |
| .mindmap-card p { |
| color: #6b6656; |
| font-size: 1rem; |
| margin-bottom: 1.5rem; |
| line-height: 1.6; |
| } |
| |
| /* ============================================================ |
| SUCCESS/INFO/ERROR MESSAGES - Claude Style |
| ============================================================ */ |
| .stSuccess { |
| background: #f0f8f5 !important; |
| border-radius: 12px !important; |
| padding: 1.2rem !important; |
| border-left: 4px solid #4a9d7f !important; |
| color: #3d3929 !important; |
| font-family: 'Inter', sans-serif !important; |
| } |
| |
| .stInfo { |
| background: #f5f3ed !important; |
| border-radius: 12px !important; |
| padding: 1.2rem !important; |
| border-left: 4px solid #c15f3c !important; |
| color: #3d3929 !important; |
| font-family: 'Inter', sans-serif !important; |
| } |
| |
| .stError { |
| background: #fef2f2 !important; |
| border-radius: 12px !important; |
| padding: 1.2rem !important; |
| border-left: 4px solid #dc2626 !important; |
| color: #3d3929 !important; |
| font-family: 'Inter', sans-serif !important; |
| } |
| |
| .stWarning { |
| background: #fef9f3 !important; |
| border-radius: 12px !important; |
| padding: 1.2rem !important; |
| border-left: 4px solid #ea9d3e !important; |
| color: #3d3929 !important; |
| font-family: 'Inter', sans-serif !important; |
| } |
| |
| /* ============================================================ |
| SPINNER STYLING |
| ============================================================ */ |
| .stSpinner > div { |
| border-color: #c15f3c transparent #c15f3c transparent !important; |
| } |
| |
| /* ============================================================ |
| INFO BOX - Claude's Warm Design |
| ============================================================ */ |
| .info-box { |
| background: #ffffff; |
| border-left: 4px solid #c15f3c; |
| border-radius: 12px; |
| padding: 2rem; |
| color: #3d3929; |
| margin: 2.5rem 0; |
| box-shadow: 0 2px 8px rgba(0, 0, 0, 0.06); |
| border: 1px solid #ebe9e0; |
| border-left: 4px solid #c15f3c; |
| } |
| |
| .info-box h3 { |
| color: #c15f3c !important; |
| margin-bottom: 1.5rem !important; |
| font-weight: 700 !important; |
| font-family: 'Merriweather', serif !important; |
| } |
| |
| .info-box ol { |
| margin-left: 1.5rem; |
| line-height: 1.8; |
| } |
| |
| .info-box li { |
| margin: 1rem 0; |
| font-size: 1rem; |
| color: #3d3929; |
| } |
| |
| .info-box p { |
| line-height: 1.7; |
| font-size: 1rem; |
| color: #3d3929; |
| } |
| |
| .info-box strong { |
| color: #3d3929; |
| font-weight: 600; |
| } |
| |
| .info-box em { |
| color: #6b6656; |
| font-style: italic; |
| } |
| |
| /* ============================================================ |
| EXPANDER STYLING |
| ============================================================ */ |
| .streamlit-expanderHeader { |
| background: #f9f8f4 !important; |
| border-radius: 10px !important; |
| font-weight: 600 !important; |
| padding: 1rem 1.5rem !important; |
| border: 1px solid #ebe9e0 !important; |
| color: #3d3929 !important; |
| font-family: 'Inter', sans-serif !important; |
| } |
| |
| .streamlit-expanderHeader:hover { |
| background: #f5f3ed !important; |
| border-color: #ddd9cc !important; |
| } |
| |
| /* ============================================================ |
| METRICS - Claude Style |
| ============================================================ */ |
| [data-testid="stMetricValue"] { |
| font-size: 2rem !important; |
| color: #c15f3c !important; |
| font-weight: 700 !important; |
| font-family: 'Inter', sans-serif !important; |
| } |
| |
| [data-testid="stMetricLabel"] { |
| font-size: 0.9rem !important; |
| font-weight: 600 !important; |
| color: #6b6656 !important; |
| text-transform: uppercase !important; |
| letter-spacing: 0.5px !important; |
| } |
| |
| /* ============================================================ |
| SIDEBAR - Warm Neutral |
| ============================================================ */ |
| [data-testid="stSidebar"] { |
| background: #eeece2; |
| border-right: 1px solid #ddd9cc; |
| } |
| |
| [data-testid="stSidebar"] .stButton > button { |
| background: #ffffff !important; |
| border: 1.5px solid #ddd9cc !important; |
| color: #3d3929 !important; |
| border-radius: 10px !important; |
| padding: 10px 16px !important; |
| font-size: 0.9rem !important; |
| font-weight: 600 !important; |
| transition: all 0.2s ease !important; |
| font-family: 'Inter', sans-serif !important; |
| } |
| |
| [data-testid="stSidebar"] .stButton > button:hover { |
| background: #f5f3ed !important; |
| border-color: #c15f3c !important; |
| color: #c15f3c !important; |
| } |
| |
| [data-testid="stSidebar"] h1 { |
| font-size: 1.3rem !important; |
| color: #3d3929 !important; |
| margin-bottom: 1rem !important; |
| font-family: 'Merriweather', serif !important; |
| } |
| |
| [data-testid="stSidebar"] .stMarkdown { |
| color: #3d3929; |
| font-family: 'Inter', sans-serif; |
| } |
| |
| /* ============================================================ |
| HORIZONTAL RULE |
| ============================================================ */ |
| hr { |
| border: none; |
| height: 1px; |
| background: #ebe9e0; |
| margin: 2rem 0; |
| } |
| |
| /* ============================================================ |
| LINKS - Claude Orange |
| ============================================================ */ |
| a { |
| color: #c15f3c !important; |
| text-decoration: none !important; |
| font-weight: 500 !important; |
| transition: color 0.2s ease !important; |
| } |
| |
| a:hover { |
| color: #a14a2f !important; |
| text-decoration: underline !important; |
| } |
| |
| /* ============================================================ |
| HIDE STREAMLIT BRANDING |
| ============================================================ */ |
| #MainMenu {visibility: hidden;} |
| footer {visibility: hidden;} |
| header {visibility: hidden;} |
| |
| /* ============================================================ |
| CUSTOM SCROLLBAR - Claude Style |
| ============================================================ */ |
| ::-webkit-scrollbar { |
| width: 8px; |
| height: 8px; |
| } |
| |
| ::-webkit-scrollbar-track { |
| background: #f5f3ed; |
| } |
| |
| ::-webkit-scrollbar-thumb { |
| background: #c15f3c; |
| border-radius: 4px; |
| } |
| |
| ::-webkit-scrollbar-thumb:hover { |
| background: #a14a2f; |
| } |
| |
| /* ============================================================ |
| TYPOGRAPHY IMPROVEMENTS |
| ============================================================ */ |
| body { |
| font-family: 'Inter', -apple-system, BlinkMacSystemFont, sans-serif; |
| color: #3d3929; |
| line-height: 1.6; |
| } |
| |
| h2, h3, h4, h5, h6 { |
| font-family: 'Merriweather', serif; |
| color: #3d3929; |
| } |
| |
| /* ============================================================ |
| RESPONSIVE DESIGN |
| ============================================================ */ |
| @media (max-width: 768px) { |
| h1 { |
| font-size: 2.2rem !important; |
| } |
| |
| .subtitle { |
| font-size: 1rem !important; |
| } |
| |
| .mindmap-card { |
| padding: 1.5rem; |
| } |
| |
| .stTextInput > div > div > input { |
| font-size: 15px !important; |
| padding: 14px 18px !important; |
| } |
| |
| .stButton > button { |
| font-size: 14px !important; |
| padding: 12px 24px !important; |
| } |
| } |
| |
| /* ============================================================ |
| SMOOTH INTERACTIONS |
| ============================================================ */ |
| * { |
| transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1); |
| } |
| |
| html { |
| scroll-behavior: smooth; |
| } |
| |
| /* ============================================================ |
| CLEAN, MINIMAL AESTHETIC |
| ============================================================ */ |
| .stApp { |
| background: #faf9f5; |
| } |
| |
| /* Remove unnecessary decorations */ |
| .stDeployButton { |
| display: none; |
| } |
| </style> |
| """, unsafe_allow_html=True) |
|
|
|
|
|
|
| |
| |
| |
|
|
| def initialize_session_state(): |
| """Initialize all session state variables""" |
| if 'mindmap_history' not in st.session_state: |
| st.session_state.mindmap_history = [] |
|
|
| if 'current_mindmap' not in st.session_state: |
| st.session_state.current_mindmap = None |
|
|
| if 'query_count' not in st.session_state: |
| st.session_state.query_count = 0 |
|
|
| if 'last_keyword' not in st.session_state: |
| st.session_state.last_keyword = "" |
|
|
|
|
| |
| |
| |
|
|
| def validate_api_keys() -> tuple[bool, list[str]]: |
| """ |
| Validate that all required API keys are configured |
| |
| Returns: |
| Tuple of (is_valid, missing_keys_list) |
| """ |
| return settings.validate_api_keys() |
|
|
|
|
| def generate_mindmap(keyword: str) -> dict: |
| """ |
| Generate mindmap for given keyword using API handler |
| |
| Args: |
| keyword: Technical keyword to analyze |
| |
| Returns: |
| Dictionary with mindmap data and metadata |
| """ |
| try: |
| result = fetch_mindmap_data( |
| keyword=keyword, |
| gemini_key=settings.gemini_api_key, |
| tavily_key=settings.tavily_api_key, |
| kg_api_key=settings.google_cloud_api_key |
| ) |
| return result |
| except Exception as e: |
| st.error(f"Error generating mindmap: {str(e)}") |
| return None |
|
|
|
|
| |
| |
| |
|
|
| def main(): |
| """Main application logic""" |
|
|
| |
| initialize_session_state() |
|
|
| |
| |
| |
|
|
| st.markdown("<h1>π§ Technical Mindmap Generator</h1>", unsafe_allow_html=True) |
| st.markdown( |
| "<p class='subtitle'>Transform technical keywords into interactive visual mindmaps powered by AI</p>", |
| unsafe_allow_html=True |
| ) |
|
|
| |
| |
| |
|
|
| is_valid, missing_keys = validate_api_keys() |
|
|
| if not is_valid: |
| st.error(f"β οΈ Missing API Keys: {', '.join(missing_keys)}") |
| st.info("Please configure your API keys in the `.env` file before using the application.") |
|
|
| with st.expander("π How to set up API keys"): |
| st.markdown(""" |
| 1. Create a `.env` file in the project root |
| 2. Add your API keys: |
| ``` |
| GEMINI_API_KEY=your_gemini_key |
| TAVILY_API_KEY=your_tavily_key |
| |
| ``` |
| 3. Restart the application |
| |
| **Get API Keys:** |
| - Gemini: https://ai.google.dev/ |
| - Tavily: https://tavily.com/ |
| - Google Cloud: https://console.cloud.google.com/ |
| """) |
|
|
| st.stop() |
|
|
| |
| |
| |
|
|
| |
| col1, col2, col3 = st.columns([1, 3, 1]) |
|
|
| with col2: |
| keyword = st.text_input( |
| "", |
| placeholder="e.g., Machine Learning, Blockchain, Kubernetes, Neural Networks...", |
| key=f"keyword_input_{st.session_state.query_count}", |
| label_visibility="collapsed", |
| help="Enter any technical keyword or concept" |
| ) |
|
|
| |
| btn_col1, btn_col2, btn_col3 = st.columns([1, 1, 1]) |
| with btn_col2: |
| generate_button = st.button( |
| "π Generate Mindmap", |
| use_container_width=True, |
| type="primary" |
| ) |
|
|
| |
| |
| |
|
|
| if generate_button and keyword: |
| |
| if len(keyword.strip()) < 2: |
| st.warning("β οΈ Please enter a longer keyword (at least 2 characters)") |
| else: |
| |
| with st.spinner(f"π Analyzing '{keyword}'...\n\nβ‘ This may take 10-15 seconds..."): |
|
|
| |
| progress_placeholder = st.empty() |
|
|
| progress_placeholder.info("π‘ Step 1/3: Fetching web data from Tavily...") |
| time.sleep(1) |
|
|
| progress_placeholder.info("π Step 2/3: Querying Knowledge Graph...") |
| time.sleep(1) |
|
|
| progress_placeholder.info("π€ Step 3/3: Synthesizing with Gemini AI...") |
|
|
| |
| result = generate_mindmap(keyword) |
|
|
| |
| progress_placeholder.empty() |
|
|
| if result: |
| |
| st.session_state.current_mindmap = result |
| st.session_state.mindmap_history.append({ |
| 'keyword': keyword, |
| 'data': result, |
| 'timestamp': time.time() |
| }) |
| st.session_state.query_count += 1 |
| st.session_state.last_keyword = keyword |
|
|
| st.success(f"β
Mindmap generated successfully for '{keyword}'!") |
| else: |
| st.error("β Failed to generate mindmap. Please try again.") |
|
|
| |
| |
| |
|
|
| if st.session_state.current_mindmap: |
| st.markdown("<div class='mindmap-card'>", unsafe_allow_html=True) |
|
|
| |
| mindmap_data = st.session_state.current_mindmap['mindmap'] |
|
|
| st.markdown(f"### πΊοΈ Mindmap: {st.session_state.last_keyword}") |
| st.markdown("*Zoom, pan, and hover over nodes for details*") |
|
|
| try: |
| generator = MindmapGenerator(height="700px", width="100%") |
| generator.render_in_streamlit(mindmap_data) |
| except Exception as e: |
| st.error(f"Error rendering mindmap: {e}") |
|
|
| st.markdown("</div>", unsafe_allow_html=True) |
|
|
| |
| |
| |
|
|
| with st.expander("π View Generation Details", expanded=False): |
| metadata = st.session_state.current_mindmap['metadata'] |
|
|
| |
| metric_col1, metric_col2, metric_col3, metric_col4 = st.columns(4) |
|
|
| with metric_col1: |
| st.metric("π― Keyword", metadata['keyword']) |
|
|
| with metric_col2: |
| st.metric("π Total Nodes", metadata.get('total_nodes', 0)) |
|
|
| with metric_col3: |
| st.metric("π Sources", len(metadata.get('tavily_sources', []))) |
|
|
| with metric_col4: |
| st.metric("π KG Entities", metadata.get('kg_entities_count', 0)) |
|
|
| st.markdown("---") |
|
|
| |
| if metadata.get('tavily_sources'): |
| st.subheader("π Information Sources") |
| for i, source in enumerate(metadata['tavily_sources'][:8], 1): |
| st.markdown(f"{i}. [{source}]({source})") |
|
|
| else: |
| |
| |
| |
|
|
| st.markdown(""" |
| <div class='info-box'> |
| <h3>π‘ How it works:</h3> |
| <ol> |
| <li><strong>Tavily API</strong>: Gathers real-time web context and discovers related terms</li> |
| <li><strong>Knowledge Graph API</strong>: Provides structured entity relationships and descriptions</li> |
| <li><strong>Gemini AI</strong>: Synthesizes all data into a comprehensive mindmap structure</li> |
| </ol> |
| <p style='margin-top:1.5rem; font-size:1.1rem;'> |
| <strong>Simply enter a technical keyword above and click "Generate Mindmap" to begin!</strong> |
| </p> |
| <p style='margin-top:1rem; color:#95e1d3;'> |
| π‘ <em>Tip: Try keywords like "Kubernetes", "Machine Learning", "Quantum Computing", or "Blockchain"</em> |
| </p> |
| </div> |
| """, unsafe_allow_html=True) |
|
|
| |
| |
| |
|
|
| with st.sidebar: |
| st.title("π Query History") |
|
|
| if st.session_state.mindmap_history: |
| st.markdown(f"**Total Queries:** {len(st.session_state.mindmap_history)}") |
| st.markdown("---") |
|
|
| |
| for i, item in enumerate(reversed(st.session_state.mindmap_history)): |
| keyword_display = item['keyword'] |
|
|
| |
| if st.button( |
| f"π {keyword_display}", |
| key=f"history_{i}", |
| help=f"Load mindmap for '{keyword_display}'" |
| ): |
| st.session_state.current_mindmap = item['data'] |
| st.session_state.last_keyword = item['keyword'] |
| st.rerun() |
| else: |
| st.info("No queries yet.\nGenerate your first mindmap!") |
|
|
| st.markdown("---") |
|
|
| |
| st.markdown("### βοΈ Settings") |
| st.markdown(f"**Max Nodes:** {settings.max_nodes}") |
| st.markdown(f"**Max Depth:** {settings.max_depth}") |
| st.markdown(f"**Caching:** {'Enabled' if settings.cache_enabled else 'Disabled'}") |
|
|
| st.markdown("---") |
|
|
| |
| with st.expander("βΉοΈ About"): |
| st.markdown(""" |
| **Technical Mindmap Generator** |
| |
| Version 1.0.0 |
| |
| A proof-of-concept application that creates interactive visual mindmaps for technical concepts using AI-powered APIs. |
| |
| **Technologies:** |
| - Streamlit |
| - PyVis |
| - Gemini AI |
| - Tavily Search |
| - Knowledge Graph |
| |
| **Created by:** Your Name |
| """) |
|
|
|
|
| |
| |
| |
|
|
| if __name__ == "__main__": |
| main() |
|
|