inital commit
Browse files- .gitignore +2 -0
- requirements.txt +11 -1
- src/README.md +453 -0
- src/config/__init__.py +0 -0
- src/config/__pycache__/__init__.cpython-310.pyc +0 -0
- src/config/__pycache__/settings.cpython-310.pyc +0 -0
- src/config/settings.py +135 -0
- src/streamlit_app.py +777 -35
- src/utils/__init__.py +0 -0
- src/utils/__pycache__/__init__.cpython-310.pyc +0 -0
- src/utils/__pycache__/api_handler.cpython-310.pyc +0 -0
- src/utils/__pycache__/mindmap_generator.cpython-310.pyc +0 -0
- src/utils/api_handler.py +461 -0
- src/utils/mindmap_generator.py +305 -0
.gitignore
ADDED
|
@@ -0,0 +1,2 @@
|
|
|
|
|
|
|
|
|
|
| 1 |
+
src/.env
|
| 2 |
+
env/
|
requirements.txt
CHANGED
|
@@ -1,3 +1,13 @@
|
|
| 1 |
altair
|
| 2 |
pandas
|
| 3 |
-
streamlit
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
altair
|
| 2 |
pandas
|
| 3 |
+
streamlit
|
| 4 |
+
streamlit>=1.28.0
|
| 5 |
+
pyvis>=0.3.2
|
| 6 |
+
networkx>=3.1
|
| 7 |
+
google-generativeai>=0.3.0
|
| 8 |
+
tavily-python>=0.3.0
|
| 9 |
+
google-cloud-enterpriseknowledgegraph>=0.4.0
|
| 10 |
+
aiohttp>=3.9.0
|
| 11 |
+
python-dotenv>=1.0.0
|
| 12 |
+
pydantic>=2.0.0
|
| 13 |
+
requests>=2.31.0
|
src/README.md
ADDED
|
@@ -0,0 +1,453 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# 🧠 Technical Mindmap Generator
|
| 2 |
+
|
| 3 |
+
> An AI-powered Streamlit application that generates interactive radial mindmaps for technical keywords using Gemini, Tavily, and Knowledge Graph APIs.
|
| 4 |
+
|
| 5 |
+

|
| 6 |
+

|
| 7 |
+

|
| 8 |
+
|
| 9 |
+
---
|
| 10 |
+
|
| 11 |
+
## ✨ Features
|
| 12 |
+
|
| 13 |
+
- 🎯 **Interactive Radial Mindmaps**: Beautiful, explorable visualizations with zoom, pan, and hover
|
| 14 |
+
- 🤖 **AI-Powered Analysis**: Leverages three powerful APIs for comprehensive results
|
| 15 |
+
- ⚡ **Optimized Performance**: Sequential API calls with async processing
|
| 16 |
+
- 🎨 **Modern UI**: Clean, responsive design inspired by GPT/Claude/Perplexity
|
| 17 |
+
- 📊 **Query History**: Track and revisit previous mindmaps in the same session
|
| 18 |
+
- 🔍 **Real-time Data**: Fresh insights from authoritative web sources
|
| 19 |
+
|
| 20 |
+
---
|
| 21 |
+
|
| 22 |
+
## 🏗️ Architecture
|
| 23 |
+
|
| 24 |
+
### Sequential API Call Strategy
|
| 25 |
+
|
| 26 |
+
The application uses an optimized 3-step approach for maximum data quality:
|
| 27 |
+
|
| 28 |
+
```
|
| 29 |
+
User Input (Technical Keyword)
|
| 30 |
+
↓
|
| 31 |
+
┌─────────────────────────────────┐
|
| 32 |
+
│ STEP 1: Tavily API │
|
| 33 |
+
│ • Web search & context │
|
| 34 |
+
│ • Related term extraction │
|
| 35 |
+
│ • Source collection │
|
| 36 |
+
└─────────────────────────────────┘
|
| 37 |
+
↓ (context + terms)
|
| 38 |
+
┌─────────────────────────────────┐
|
| 39 |
+
│ STEP 2: Knowledge Graph API │
|
| 40 |
+
│ • Structured entity data │
|
| 41 |
+
│ • Relationship mapping │
|
| 42 |
+
│ • Entity descriptions │
|
| 43 |
+
└─────────────────────────────────┘
|
| 44 |
+
↓ (entities + relationships)
|
| 45 |
+
┌─────────────────────────────────┐
|
| 46 |
+
│ STEP 3: Gemini AI │
|
| 47 |
+
│ • Synthesis of all data │
|
| 48 |
+
│ • Mindmap structure generation │
|
| 49 |
+
│ • Hierarchical organization │
|
| 50 |
+
└─────────────────────────────────┘
|
| 51 |
+
↓
|
| 52 |
+
PyVis Radial Visualization
|
| 53 |
+
```
|
| 54 |
+
|
| 55 |
+
**Why Sequential?**
|
| 56 |
+
Each API call benefits from context gathered in previous steps, maximizing relevance and reducing noise.
|
| 57 |
+
|
| 58 |
+
---
|
| 59 |
+
|
| 60 |
+
## 📦 Installation
|
| 61 |
+
|
| 62 |
+
### Prerequisites
|
| 63 |
+
|
| 64 |
+
- **Python 3.9+**
|
| 65 |
+
- **pip** package manager
|
| 66 |
+
- **API Keys:**
|
| 67 |
+
- [Gemini API Key](https://ai.google.dev/) (Free tier available)
|
| 68 |
+
- [Tavily API Key](https://tavily.com/) (Free tier: 1000 requests/month)
|
| 69 |
+
- [Google Cloud Project](https://console.cloud.google.com/) with Knowledge Graph API enabled
|
| 70 |
+
|
| 71 |
+
### Step-by-Step Setup
|
| 72 |
+
|
| 73 |
+
#### 1. Clone or Download the Project
|
| 74 |
+
|
| 75 |
+
```bash
|
| 76 |
+
# If using git
|
| 77 |
+
git clone <repository-url>
|
| 78 |
+
cd technical-mindmap-generator
|
| 79 |
+
|
| 80 |
+
# Or download and extract the ZIP file
|
| 81 |
+
```
|
| 82 |
+
|
| 83 |
+
#### 2. Create Virtual Environment
|
| 84 |
+
|
| 85 |
+
```bash
|
| 86 |
+
# Create virtual environment
|
| 87 |
+
python -m venv venv
|
| 88 |
+
|
| 89 |
+
# Activate it
|
| 90 |
+
# On macOS/Linux:
|
| 91 |
+
source venv/bin/activate
|
| 92 |
+
|
| 93 |
+
# On Windows:
|
| 94 |
+
venv\Scripts\activate
|
| 95 |
+
```
|
| 96 |
+
|
| 97 |
+
#### 3. Install Dependencies
|
| 98 |
+
|
| 99 |
+
```bash
|
| 100 |
+
pip install -r requirements.txt
|
| 101 |
+
```
|
| 102 |
+
|
| 103 |
+
This will install:
|
| 104 |
+
- streamlit
|
| 105 |
+
- pyvis
|
| 106 |
+
- networkx
|
| 107 |
+
- google-generativeai
|
| 108 |
+
- tavily-python
|
| 109 |
+
- google-cloud-enterpriseknowledgegraph
|
| 110 |
+
- aiohttp
|
| 111 |
+
- python-dotenv
|
| 112 |
+
- pydantic
|
| 113 |
+
- requests
|
| 114 |
+
|
| 115 |
+
#### 4. Configure API Keys
|
| 116 |
+
|
| 117 |
+
Create a `.env` file in the project root:
|
| 118 |
+
|
| 119 |
+
```bash
|
| 120 |
+
# Copy the example file
|
| 121 |
+
cp .env.example .env
|
| 122 |
+
|
| 123 |
+
# Edit with your API keys
|
| 124 |
+
nano .env # or use any text editor
|
| 125 |
+
```
|
| 126 |
+
|
| 127 |
+
Add your API keys to `.env`:
|
| 128 |
+
|
| 129 |
+
```env
|
| 130 |
+
GEMINI_API_KEY=your_actual_gemini_api_key_here
|
| 131 |
+
TAVILY_API_KEY=your_actual_tavily_api_key_here
|
| 132 |
+
GOOGLE_CLOUD_PROJECT_ID=your_google_cloud_project_id_here
|
| 133 |
+
|
| 134 |
+
# Optional settings
|
| 135 |
+
MAX_CONCURRENT_REQUESTS=3
|
| 136 |
+
CACHE_ENABLED=true
|
| 137 |
+
DEBUG_MODE=false
|
| 138 |
+
MAX_NODES=20
|
| 139 |
+
MAX_DEPTH=2
|
| 140 |
+
```
|
| 141 |
+
|
| 142 |
+
#### 5. Get Your API Keys
|
| 143 |
+
|
| 144 |
+
**Gemini API Key:**
|
| 145 |
+
1. Visit https://ai.google.dev/
|
| 146 |
+
2. Click "Get API Key"
|
| 147 |
+
3. Sign in with Google account
|
| 148 |
+
4. Create new API key
|
| 149 |
+
5. Copy the key
|
| 150 |
+
|
| 151 |
+
**Tavily API Key:**
|
| 152 |
+
1. Visit https://tavily.com/
|
| 153 |
+
2. Sign up for free account
|
| 154 |
+
3. Navigate to API Keys section
|
| 155 |
+
4. Copy your API key
|
| 156 |
+
|
| 157 |
+
**Google Cloud Knowledge Graph:**
|
| 158 |
+
1. Visit https://console.cloud.google.com/
|
| 159 |
+
2. Create new project or select existing
|
| 160 |
+
3. Enable "Enterprise Knowledge Graph API"
|
| 161 |
+
4. Copy your project ID
|
| 162 |
+
|
| 163 |
+
#### 6. Run the Application
|
| 164 |
+
|
| 165 |
+
```bash
|
| 166 |
+
streamlit run app.py
|
| 167 |
+
```
|
| 168 |
+
|
| 169 |
+
The application will open automatically in your browser at `http://localhost:8501`
|
| 170 |
+
|
| 171 |
+
---
|
| 172 |
+
|
| 173 |
+
## 🚀 Usage
|
| 174 |
+
|
| 175 |
+
### Basic Workflow
|
| 176 |
+
|
| 177 |
+
1. **Enter Keyword**: Type any technical term (e.g., "Machine Learning", "Kubernetes")
|
| 178 |
+
2. **Generate**: Click the "🚀 Generate Mindmap" button
|
| 179 |
+
3. **Wait**: Processing takes 10-15 seconds as data is fetched from all APIs
|
| 180 |
+
4. **Explore**: Interact with the mindmap:
|
| 181 |
+
- 🔍 **Zoom**: Mouse wheel or pinch gesture
|
| 182 |
+
- 👆 **Pan**: Click and drag
|
| 183 |
+
- ℹ️ **Hover**: View detailed node information
|
| 184 |
+
- 🧭 **Navigate**: Use built-in navigation controls
|
| 185 |
+
5. **View Details**: Expand the metadata section for sources and statistics
|
| 186 |
+
6. **New Query**: Enter another keyword; previous mindmaps saved in history
|
| 187 |
+
|
| 188 |
+
### Example Keywords to Try
|
| 189 |
+
|
| 190 |
+
- **AI/ML**: Machine Learning, Neural Networks, Deep Learning, Transformers
|
| 191 |
+
- **DevOps**: Kubernetes, Docker, CI/CD, Microservices
|
| 192 |
+
- **Blockchain**: Ethereum, Smart Contracts, DeFi, NFTs
|
| 193 |
+
- **Cloud**: AWS, Serverless, Cloud Native, Edge Computing
|
| 194 |
+
- **Programming**: Python, JavaScript, Rust, TypeScript
|
| 195 |
+
|
| 196 |
+
---
|
| 197 |
+
|
| 198 |
+
## 📁 Project Structure
|
| 199 |
+
|
| 200 |
+
```
|
| 201 |
+
technical-mindmap-generator/
|
| 202 |
+
│
|
| 203 |
+
├── app.py # Main Streamlit application
|
| 204 |
+
├── requirements.txt # Python dependencies
|
| 205 |
+
├── .env.example # Environment variables template
|
| 206 |
+
├── .env # Your API keys (create this)
|
| 207 |
+
├── README.md # This file
|
| 208 |
+
│
|
| 209 |
+
├── config/
|
| 210 |
+
│ ├── __init__.py
|
| 211 |
+
│ └── settings.py # Configuration management
|
| 212 |
+
│
|
| 213 |
+
└── utils/
|
| 214 |
+
├── __init__.py
|
| 215 |
+
├── api_handler.py # API integration layer
|
| 216 |
+
└── mindmap_generator.py # PyVis visualization
|
| 217 |
+
```
|
| 218 |
+
|
| 219 |
+
---
|
| 220 |
+
|
| 221 |
+
## ⚙️ Configuration
|
| 222 |
+
|
| 223 |
+
### Environment Variables
|
| 224 |
+
|
| 225 |
+
Edit `.env` to customize behavior:
|
| 226 |
+
|
| 227 |
+
| Variable | Default | Description |
|
| 228 |
+
|----------|---------|-------------|
|
| 229 |
+
| `GEMINI_API_KEY` | Required | Gemini AI API key |
|
| 230 |
+
| `TAVILY_API_KEY` | Required | Tavily search API key |
|
| 231 |
+
| `GOOGLE_CLOUD_PROJECT_ID` | Required | Google Cloud project ID |
|
| 232 |
+
| `MAX_CONCURRENT_REQUESTS` | 3 | Max parallel API calls |
|
| 233 |
+
| `CACHE_ENABLED` | true | Enable result caching |
|
| 234 |
+
| `DEBUG_MODE` | false | Enable debug logging |
|
| 235 |
+
| `MAX_NODES` | 20 | Maximum nodes in mindmap |
|
| 236 |
+
| `MAX_DEPTH` | 2 | Maximum hierarchy depth |
|
| 237 |
+
|
| 238 |
+
### Customization
|
| 239 |
+
|
| 240 |
+
**Mindmap Colors** (in `utils/mindmap_generator.py`):
|
| 241 |
+
```python
|
| 242 |
+
self.level_colors = {
|
| 243 |
+
0: "#ff6b6b", # Center - red/coral
|
| 244 |
+
1: "#4ecdc4", # Primary - teal
|
| 245 |
+
2: "#95e1d3", # Secondary - light teal
|
| 246 |
+
3: "#f9ca24", # Tertiary - yellow
|
| 247 |
+
4: "#a29bfe" # Quaternary - purple
|
| 248 |
+
}
|
| 249 |
+
```
|
| 250 |
+
|
| 251 |
+
**UI Theme** (in `app.py` CSS section):
|
| 252 |
+
- Modify gradient colors
|
| 253 |
+
- Adjust border radius
|
| 254 |
+
- Change font sizes and families
|
| 255 |
+
|
| 256 |
+
---
|
| 257 |
+
|
| 258 |
+
## 🐛 Troubleshooting
|
| 259 |
+
|
| 260 |
+
### Common Issues
|
| 261 |
+
|
| 262 |
+
**Issue: "Missing API Keys" error**
|
| 263 |
+
- **Solution**: Ensure `.env` file exists and contains valid API keys
|
| 264 |
+
- Check that `.env` is in the project root directory
|
| 265 |
+
- Verify no extra spaces around `=` in `.env`
|
| 266 |
+
|
| 267 |
+
**Issue: Mindmap not displaying**
|
| 268 |
+
- **Solution**: Check browser console for JavaScript errors
|
| 269 |
+
- Try refreshing the page (Ctrl+R or Cmd+R)
|
| 270 |
+
- Ensure PyVis generated HTML successfully
|
| 271 |
+
|
| 272 |
+
**Issue: Slow performance**
|
| 273 |
+
- **Solution**:
|
| 274 |
+
- Reduce `MAX_NODES` in `.env`
|
| 275 |
+
- Check internet connection
|
| 276 |
+
- Verify API rate limits not exceeded
|
| 277 |
+
|
| 278 |
+
**Issue: "Import Error" on startup**
|
| 279 |
+
- **Solution**:
|
| 280 |
+
- Ensure virtual environment is activated
|
| 281 |
+
- Run `pip install -r requirements.txt` again
|
| 282 |
+
- Check Python version is 3.9+
|
| 283 |
+
|
| 284 |
+
**Issue: Knowledge Graph API error**
|
| 285 |
+
- **Solution**:
|
| 286 |
+
- Verify Google Cloud project has Knowledge Graph API enabled
|
| 287 |
+
- Check project ID is correct
|
| 288 |
+
- Ensure billing is enabled (free tier available)
|
| 289 |
+
|
| 290 |
+
### Debug Mode
|
| 291 |
+
|
| 292 |
+
Enable debug logging:
|
| 293 |
+
|
| 294 |
+
```bash
|
| 295 |
+
# In .env
|
| 296 |
+
DEBUG_MODE=true
|
| 297 |
+
```
|
| 298 |
+
|
| 299 |
+
Then check console output for detailed error messages.
|
| 300 |
+
|
| 301 |
+
---
|
| 302 |
+
|
| 303 |
+
## 🔧 Development
|
| 304 |
+
|
| 305 |
+
### Adding New Features
|
| 306 |
+
|
| 307 |
+
**Add a new API source:**
|
| 308 |
+
1. Edit `utils/api_handler.py`
|
| 309 |
+
2. Add new async method (e.g., `fetch_xyz_data`)
|
| 310 |
+
3. Integrate into `fetch_all_data()` method
|
| 311 |
+
4. Update documentation
|
| 312 |
+
|
| 313 |
+
**Customize visualization:**
|
| 314 |
+
1. Edit `utils/mindmap_generator.py`
|
| 315 |
+
2. Modify `create_radial_mindmap()` method
|
| 316 |
+
3. Update PyVis options for physics/styling
|
| 317 |
+
|
| 318 |
+
### Testing
|
| 319 |
+
|
| 320 |
+
Run a simple test:
|
| 321 |
+
|
| 322 |
+
```python
|
| 323 |
+
# test_api.py
|
| 324 |
+
from utils.api_handler import fetch_mindmap_data
|
| 325 |
+
from config.settings import settings
|
| 326 |
+
|
| 327 |
+
result = fetch_mindmap_data(
|
| 328 |
+
keyword="Python",
|
| 329 |
+
gemini_key=settings.gemini_api_key,
|
| 330 |
+
tavily_key=settings.tavily_api_key,
|
| 331 |
+
google_project_id=settings.google_cloud_project_id
|
| 332 |
+
)
|
| 333 |
+
|
| 334 |
+
print(f"Nodes: {len(result['mindmap']['nodes'])}")
|
| 335 |
+
print(f"Edges: {len(result['mindmap']['edges'])}")
|
| 336 |
+
```
|
| 337 |
+
|
| 338 |
+
---
|
| 339 |
+
|
| 340 |
+
## 🎯 API Usage & Costs
|
| 341 |
+
|
| 342 |
+
### Free Tier Limits
|
| 343 |
+
|
| 344 |
+
**Gemini API:**
|
| 345 |
+
- 60 requests per minute
|
| 346 |
+
- 1500 requests per day
|
| 347 |
+
- Free for personal use
|
| 348 |
+
|
| 349 |
+
**Tavily API:**
|
| 350 |
+
- 1000 requests per month (free tier)
|
| 351 |
+
- Upgrade available for more
|
| 352 |
+
|
| 353 |
+
**Knowledge Graph API:**
|
| 354 |
+
- 100,000 free calls per day
|
| 355 |
+
- No credit card required for free tier
|
| 356 |
+
|
| 357 |
+
### Cost Optimization Tips
|
| 358 |
+
|
| 359 |
+
1. Enable caching to reduce redundant calls
|
| 360 |
+
2. Use `MAX_NODES` to limit result size
|
| 361 |
+
3. Batch similar queries if possible
|
| 362 |
+
4. Monitor usage in respective dashboards
|
| 363 |
+
|
| 364 |
+
---
|
| 365 |
+
|
| 366 |
+
## 📚 Tech Stack
|
| 367 |
+
|
| 368 |
+
| Technology | Purpose | Version |
|
| 369 |
+
|------------|---------|---------|
|
| 370 |
+
| **Streamlit** | Web framework | 1.28+ |
|
| 371 |
+
| **PyVis** | Graph visualization | 0.3.2+ |
|
| 372 |
+
| **NetworkX** | Graph algorithms | 3.1+ |
|
| 373 |
+
| **Gemini AI** | Language model | Latest |
|
| 374 |
+
| **Tavily** | Web search | Latest |
|
| 375 |
+
| **Knowledge Graph** | Entity data | Latest |
|
| 376 |
+
| **AsyncIO** | Async operations | Built-in |
|
| 377 |
+
| **Pydantic** | Data validation | 2.0+ |
|
| 378 |
+
|
| 379 |
+
---
|
| 380 |
+
|
| 381 |
+
## 🤝 Contributing
|
| 382 |
+
|
| 383 |
+
Contributions are welcome! Here's how:
|
| 384 |
+
|
| 385 |
+
1. Fork the repository
|
| 386 |
+
2. Create feature branch (`git checkout -b feature/amazing-feature`)
|
| 387 |
+
3. Commit changes (`git commit -m 'Add amazing feature'`)
|
| 388 |
+
4. Push to branch (`git push origin feature/amazing-feature`)
|
| 389 |
+
5. Open Pull Request
|
| 390 |
+
|
| 391 |
+
### Development Guidelines
|
| 392 |
+
|
| 393 |
+
- Follow PEP 8 style guide
|
| 394 |
+
- Add docstrings to all functions
|
| 395 |
+
- Test thoroughly before submitting
|
| 396 |
+
- Update documentation as needed
|
| 397 |
+
|
| 398 |
+
---
|
| 399 |
+
|
| 400 |
+
## 📝 License
|
| 401 |
+
|
| 402 |
+
This project is licensed under the MIT License - see LICENSE file for details.
|
| 403 |
+
|
| 404 |
+
---
|
| 405 |
+
|
| 406 |
+
## 🙏 Acknowledgments
|
| 407 |
+
|
| 408 |
+
- [Streamlit](https://streamlit.io/) - Amazing web framework
|
| 409 |
+
- [PyVis](https://pyvis.readthedocs.io/) - Interactive visualizations
|
| 410 |
+
- [Google Gemini](https://ai.google.dev/) - Powerful AI synthesis
|
| 411 |
+
- [Tavily](https://tavily.com/) - Real-time web search
|
| 412 |
+
- [Google Knowledge Graph](https://developers.google.com/knowledge-graph) - Structured data
|
| 413 |
+
|
| 414 |
+
---
|
| 415 |
+
|
| 416 |
+
## 📧 Support
|
| 417 |
+
|
| 418 |
+
For issues, questions, or suggestions:
|
| 419 |
+
- Open an issue on GitHub
|
| 420 |
+
- Email: your.email@example.com
|
| 421 |
+
- Documentation: See inline code comments
|
| 422 |
+
|
| 423 |
+
---
|
| 424 |
+
|
| 425 |
+
## 🚀 Roadmap
|
| 426 |
+
|
| 427 |
+
### Upcoming Features
|
| 428 |
+
|
| 429 |
+
- [ ] Export mindmaps as PNG/SVG/PDF
|
| 430 |
+
- [ ] Persistent storage with database
|
| 431 |
+
- [ ] User authentication and sharing
|
| 432 |
+
- [ ] Custom color themes
|
| 433 |
+
- [ ] Advanced filtering options
|
| 434 |
+
- [ ] Integration with more knowledge sources
|
| 435 |
+
- [ ] Collaborative editing
|
| 436 |
+
- [ ] Mobile app version
|
| 437 |
+
- [ ] API endpoint for programmatic access
|
| 438 |
+
|
| 439 |
+
---
|
| 440 |
+
|
| 441 |
+
**Built with ❤️ using Python, Streamlit, and AI**
|
| 442 |
+
|
| 443 |
+
---
|
| 444 |
+
|
| 445 |
+
## 📸 Screenshots
|
| 446 |
+
|
| 447 |
+
*Add screenshots of your application here*
|
| 448 |
+
|
| 449 |
+
---
|
| 450 |
+
|
| 451 |
+
**Last Updated:** October 2025
|
| 452 |
+
**Version:** 1.0.0
|
| 453 |
+
**Status:** Production Ready
|
src/config/__init__.py
ADDED
|
File without changes
|
src/config/__pycache__/__init__.cpython-310.pyc
ADDED
|
Binary file (150 Bytes). View file
|
|
|
src/config/__pycache__/settings.cpython-310.pyc
ADDED
|
Binary file (4.36 kB). View file
|
|
|
src/config/settings.py
ADDED
|
@@ -0,0 +1,135 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
Configuration management for the Technical Mindmap Generator
|
| 3 |
+
Handles all application settings and API key management
|
| 4 |
+
"""
|
| 5 |
+
import os
|
| 6 |
+
from dotenv import load_dotenv
|
| 7 |
+
from pydantic import BaseModel, Field
|
| 8 |
+
from typing import Optional
|
| 9 |
+
|
| 10 |
+
# Load environment variables from .env file
|
| 11 |
+
load_dotenv()
|
| 12 |
+
|
| 13 |
+
|
| 14 |
+
class Settings(BaseModel):
|
| 15 |
+
"""
|
| 16 |
+
Application settings with validation
|
| 17 |
+
|
| 18 |
+
Attributes:
|
| 19 |
+
gemini_api_key: Google Gemini API key for AI synthesis
|
| 20 |
+
tavily_api_key: Tavily API key for web search
|
| 21 |
+
google_cloud_project_id: Google Cloud project ID for Knowledge Graph
|
| 22 |
+
max_concurrent_requests: Maximum number of parallel API calls
|
| 23 |
+
cache_enabled: Enable/disable result caching
|
| 24 |
+
debug_mode: Enable debug logging
|
| 25 |
+
max_nodes: Maximum nodes in generated mindmap
|
| 26 |
+
max_depth: Maximum depth of node hierarchy
|
| 27 |
+
"""
|
| 28 |
+
|
| 29 |
+
# API Keys
|
| 30 |
+
gemini_api_key: str = Field(
|
| 31 |
+
default_factory=lambda: os.getenv("GEMINI_API_KEY", ""),
|
| 32 |
+
description="Gemini API key for AI-powered synthesis"
|
| 33 |
+
)
|
| 34 |
+
|
| 35 |
+
tavily_api_key: str = Field(
|
| 36 |
+
default_factory=lambda: os.getenv("TAVILY_API_KEY", ""),
|
| 37 |
+
description="Tavily API key for web search"
|
| 38 |
+
)
|
| 39 |
+
|
| 40 |
+
google_cloud_api_key: str = Field(
|
| 41 |
+
default_factory=lambda: os.getenv("GOOGLE_CLOUD_API_KEY", ""),
|
| 42 |
+
description="API key for Google Knowledge Graph REST API"
|
| 43 |
+
)
|
| 44 |
+
|
| 45 |
+
|
| 46 |
+
# Application Settings
|
| 47 |
+
max_concurrent_requests: int = Field(
|
| 48 |
+
default_factory=lambda: int(os.getenv("MAX_CONCURRENT_REQUESTS", "3")),
|
| 49 |
+
ge=1,
|
| 50 |
+
le=10,
|
| 51 |
+
description="Maximum concurrent API requests"
|
| 52 |
+
)
|
| 53 |
+
|
| 54 |
+
cache_enabled: bool = Field(
|
| 55 |
+
default_factory=lambda: os.getenv("CACHE_ENABLED", "true").lower() == "true",
|
| 56 |
+
description="Enable result caching"
|
| 57 |
+
)
|
| 58 |
+
|
| 59 |
+
debug_mode: bool = Field(
|
| 60 |
+
default_factory=lambda: os.getenv("DEBUG_MODE", "false").lower() == "true",
|
| 61 |
+
description="Enable debug mode"
|
| 62 |
+
)
|
| 63 |
+
|
| 64 |
+
# Mindmap Settings
|
| 65 |
+
max_nodes: int = Field(
|
| 66 |
+
default_factory=lambda: int(os.getenv("MAX_NODES", "20")),
|
| 67 |
+
ge=5,
|
| 68 |
+
le=50,
|
| 69 |
+
description="Maximum nodes in mindmap"
|
| 70 |
+
)
|
| 71 |
+
|
| 72 |
+
max_depth: int = Field(
|
| 73 |
+
default_factory=lambda: int(os.getenv("MAX_DEPTH", "2")),
|
| 74 |
+
ge=1,
|
| 75 |
+
le=5,
|
| 76 |
+
description="Maximum depth of node hierarchy"
|
| 77 |
+
)
|
| 78 |
+
|
| 79 |
+
class Config:
|
| 80 |
+
"""Pydantic configuration"""
|
| 81 |
+
env_file = ".env"
|
| 82 |
+
case_sensitive = False
|
| 83 |
+
|
| 84 |
+
def validate_api_keys(self) -> tuple[bool, list[str]]:
|
| 85 |
+
"""
|
| 86 |
+
Validate that all required API keys are present
|
| 87 |
+
|
| 88 |
+
Returns:
|
| 89 |
+
Tuple of (is_valid, missing_keys)
|
| 90 |
+
"""
|
| 91 |
+
missing_keys = []
|
| 92 |
+
|
| 93 |
+
if not self.gemini_api_key:
|
| 94 |
+
missing_keys.append("GEMINI_API_KEY")
|
| 95 |
+
|
| 96 |
+
if not self.tavily_api_key:
|
| 97 |
+
missing_keys.append("TAVILY_API_KEY")
|
| 98 |
+
|
| 99 |
+
if not self.google_cloud_api_key:
|
| 100 |
+
missing_keys.append("GOOGLE_CLOUD_API_KEY")
|
| 101 |
+
|
| 102 |
+
return (len(missing_keys) == 0, missing_keys)
|
| 103 |
+
|
| 104 |
+
|
| 105 |
+
# Create global settings instance
|
| 106 |
+
settings = Settings()
|
| 107 |
+
|
| 108 |
+
|
| 109 |
+
def get_settings() -> Settings:
|
| 110 |
+
"""
|
| 111 |
+
Get the global settings instance
|
| 112 |
+
|
| 113 |
+
Returns:
|
| 114 |
+
Settings object
|
| 115 |
+
"""
|
| 116 |
+
return settings
|
| 117 |
+
|
| 118 |
+
|
| 119 |
+
def display_settings_info():
|
| 120 |
+
"""Print current settings (for debugging)"""
|
| 121 |
+
print("=" * 50)
|
| 122 |
+
print("TECHNICAL MINDMAP GENERATOR - SETTINGS")
|
| 123 |
+
print("=" * 50)
|
| 124 |
+
print(f"Max Concurrent Requests: {settings.max_concurrent_requests}")
|
| 125 |
+
print(f"Cache Enabled: {settings.cache_enabled}")
|
| 126 |
+
print(f"Debug Mode: {settings.debug_mode}")
|
| 127 |
+
print(f"Max Nodes: {settings.max_nodes}")
|
| 128 |
+
print(f"Max Depth: {settings.max_depth}")
|
| 129 |
+
|
| 130 |
+
is_valid, missing = settings.validate_api_keys()
|
| 131 |
+
if is_valid:
|
| 132 |
+
print("✅ All API keys configured")
|
| 133 |
+
else:
|
| 134 |
+
print(f"❌ Missing API keys: {', '.join(missing)}")
|
| 135 |
+
print("=" * 50)
|
src/streamlit_app.py
CHANGED
|
@@ -1,40 +1,782 @@
|
|
| 1 |
-
|
| 2 |
-
|
| 3 |
-
|
| 4 |
-
import streamlit as st
|
| 5 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 6 |
"""
|
| 7 |
-
# Welcome to Streamlit!
|
| 8 |
|
| 9 |
-
|
| 10 |
-
|
| 11 |
-
|
|
|
|
| 12 |
|
| 13 |
-
|
| 14 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 15 |
|
| 16 |
-
|
| 17 |
-
|
| 18 |
-
|
| 19 |
-
indices = np.linspace(0, 1, num_points)
|
| 20 |
-
theta = 2 * np.pi * num_turns * indices
|
| 21 |
-
radius = indices
|
| 22 |
-
|
| 23 |
-
x = radius * np.cos(theta)
|
| 24 |
-
y = radius * np.sin(theta)
|
| 25 |
-
|
| 26 |
-
df = pd.DataFrame({
|
| 27 |
-
"x": x,
|
| 28 |
-
"y": y,
|
| 29 |
-
"idx": indices,
|
| 30 |
-
"rand": np.random.randn(num_points),
|
| 31 |
-
})
|
| 32 |
-
|
| 33 |
-
st.altair_chart(alt.Chart(df, height=700, width=700)
|
| 34 |
-
.mark_point(filled=True)
|
| 35 |
-
.encode(
|
| 36 |
-
x=alt.X("x", axis=None),
|
| 37 |
-
y=alt.Y("y", axis=None),
|
| 38 |
-
color=alt.Color("idx", legend=None, scale=alt.Scale()),
|
| 39 |
-
size=alt.Size("rand", legend=None, scale=alt.Scale(range=[1, 150])),
|
| 40 |
-
))
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
Technical Mindmap Generator - Main Streamlit Application
|
| 3 |
+
Interactive web app for generating radial mindmaps from technical keywords
|
|
|
|
| 4 |
|
| 5 |
+
Features:
|
| 6 |
+
- Modern UI inspired by GPT, Claude, and Perplexity
|
| 7 |
+
- Sequential API calls for optimal data quality
|
| 8 |
+
- Interactive radial mindmaps with PyVis
|
| 9 |
+
- Query history and session management
|
| 10 |
+
- Real-time data from Tavily, Knowledge Graph, and Gemini APIs
|
| 11 |
"""
|
|
|
|
| 12 |
|
| 13 |
+
import streamlit as st
|
| 14 |
+
import sys
|
| 15 |
+
from pathlib import Path
|
| 16 |
+
import time
|
| 17 |
|
| 18 |
+
# Add project root to path
|
| 19 |
+
project_root = Path(__file__).parent
|
| 20 |
+
sys.path.insert(0, str(project_root))
|
| 21 |
+
|
| 22 |
+
# Import project modules
|
| 23 |
+
try:
|
| 24 |
+
from config.settings import settings, display_settings_info
|
| 25 |
+
from utils.api_handler import fetch_mindmap_data
|
| 26 |
+
from utils.mindmap_generator import MindmapGenerator
|
| 27 |
+
except ImportError as e:
|
| 28 |
+
st.error(f"Import Error: {e}")
|
| 29 |
+
st.info("Make sure all project files are in the correct directories")
|
| 30 |
+
st.stop()
|
| 31 |
+
|
| 32 |
+
|
| 33 |
+
# ============================================================================
|
| 34 |
+
# PAGE CONFIGURATION
|
| 35 |
+
# ============================================================================
|
| 36 |
+
|
| 37 |
+
st.set_page_config(
|
| 38 |
+
page_title="Technical Mindmap Generator 🧠",
|
| 39 |
+
page_icon="🧠",
|
| 40 |
+
layout="wide",
|
| 41 |
+
initial_sidebar_state="collapsed",
|
| 42 |
+
menu_items={
|
| 43 |
+
'Get Help': None,
|
| 44 |
+
'Report a bug': None,
|
| 45 |
+
'About': "# Technical Mindmap Generator\nPowered by Gemini, Tavily, and Knowledge Graph APIs"
|
| 46 |
+
}
|
| 47 |
+
)
|
| 48 |
+
|
| 49 |
+
|
| 50 |
+
# ============================================================================
|
| 51 |
+
# CUSTOM CSS STYLING
|
| 52 |
+
# ============================================================================
|
| 53 |
+
|
| 54 |
+
st.markdown("""
|
| 55 |
+
<style>
|
| 56 |
+
/* Import Google Fonts - Claude uses serif fonts */
|
| 57 |
+
@import url('https://fonts.googleapis.com/css2?family=Merriweather:wght@300;400;700&family=Inter:wght@400;500;600;700&display=swap');
|
| 58 |
+
|
| 59 |
+
/* ============================================================
|
| 60 |
+
MAIN CONTAINER - Claude's Warm Aesthetic
|
| 61 |
+
============================================================ */
|
| 62 |
+
.main {
|
| 63 |
+
background: #faf9f5;
|
| 64 |
+
padding: 2rem;
|
| 65 |
+
font-family: 'Inter', -apple-system, BlinkMacSystemFont, sans-serif;
|
| 66 |
+
}
|
| 67 |
+
|
| 68 |
+
/* Remove default padding */
|
| 69 |
+
.block-container {
|
| 70 |
+
padding-top: 2rem;
|
| 71 |
+
padding-bottom: 3rem;
|
| 72 |
+
max-width: 1000px;
|
| 73 |
+
}
|
| 74 |
+
|
| 75 |
+
/* ============================================================
|
| 76 |
+
HEADER SECTION
|
| 77 |
+
============================================================ */
|
| 78 |
+
h1 {
|
| 79 |
+
color: #3d3929 !important;
|
| 80 |
+
text-align: center !important;
|
| 81 |
+
font-size: 3.2rem !important;
|
| 82 |
+
font-weight: 700 !important;
|
| 83 |
+
margin-bottom: 0.5rem !important;
|
| 84 |
+
letter-spacing: -0.5px !important;
|
| 85 |
+
font-family: 'Merriweather', serif !important;
|
| 86 |
+
}
|
| 87 |
+
|
| 88 |
+
/* Subtitle */
|
| 89 |
+
.subtitle {
|
| 90 |
+
color: #6b6656 !important;
|
| 91 |
+
text-align: center !important;
|
| 92 |
+
font-size: 1.2rem !important;
|
| 93 |
+
margin-bottom: 3rem !important;
|
| 94 |
+
font-weight: 400 !important;
|
| 95 |
+
font-family: 'Inter', sans-serif !important;
|
| 96 |
+
line-height: 1.6 !important;
|
| 97 |
+
}
|
| 98 |
+
|
| 99 |
+
/* ============================================================
|
| 100 |
+
INPUT CONTAINER - Claude's Clean Design
|
| 101 |
+
============================================================ */
|
| 102 |
+
.stTextInput > div > div > input {
|
| 103 |
+
background: #ffffff !important;
|
| 104 |
+
color: #3d3929 !important;
|
| 105 |
+
border-radius: 12px !important;
|
| 106 |
+
padding: 18px 24px !important;
|
| 107 |
+
font-size: 16px !important;
|
| 108 |
+
font-weight: 400 !important;
|
| 109 |
+
border: 1.5px solid #ddd9cc !important;
|
| 110 |
+
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.06) !important;
|
| 111 |
+
transition: all 0.2s ease !important;
|
| 112 |
+
font-family: 'Inter', sans-serif !important;
|
| 113 |
+
}
|
| 114 |
+
|
| 115 |
+
.stTextInput > div > div > input:focus {
|
| 116 |
+
border-color: #c15f3c !important;
|
| 117 |
+
box-shadow: 0 0 0 3px rgba(193, 95, 60, 0.1),
|
| 118 |
+
0 1px 3px rgba(0, 0, 0, 0.06) !important;
|
| 119 |
+
outline: none !important;
|
| 120 |
+
}
|
| 121 |
+
|
| 122 |
+
.stTextInput > div > div > input::placeholder {
|
| 123 |
+
color: #9b9688;
|
| 124 |
+
font-weight: 400;
|
| 125 |
+
}
|
| 126 |
+
|
| 127 |
+
/* ============================================================
|
| 128 |
+
BUTTON STYLING - Claude's Orange
|
| 129 |
+
============================================================ */
|
| 130 |
+
.stButton > button {
|
| 131 |
+
background: #c15f3c !important;
|
| 132 |
+
color: #ffffff !important;
|
| 133 |
+
border-radius: 10px !important;
|
| 134 |
+
padding: 14px 32px !important;
|
| 135 |
+
font-size: 15px !important;
|
| 136 |
+
font-weight: 600 !important;
|
| 137 |
+
border: none !important;
|
| 138 |
+
box-shadow: 0 2px 4px rgba(193, 95, 60, 0.2) !important;
|
| 139 |
+
transition: all 0.2s ease !important;
|
| 140 |
+
font-family: 'Inter', sans-serif !important;
|
| 141 |
+
letter-spacing: 0.3px !important;
|
| 142 |
+
}
|
| 143 |
+
|
| 144 |
+
.stButton > button:hover {
|
| 145 |
+
background: #a14a2f !important;
|
| 146 |
+
box-shadow: 0 4px 8px rgba(193, 95, 60, 0.25) !important;
|
| 147 |
+
transform: translateY(-1px) !important;
|
| 148 |
+
}
|
| 149 |
+
|
| 150 |
+
.stButton > button:active {
|
| 151 |
+
transform: translateY(0) !important;
|
| 152 |
+
box-shadow: 0 1px 2px rgba(193, 95, 60, 0.2) !important;
|
| 153 |
+
}
|
| 154 |
+
|
| 155 |
+
/* ============================================================
|
| 156 |
+
MINDMAP CARD - Clean White Container
|
| 157 |
+
============================================================ */
|
| 158 |
+
.mindmap-card {
|
| 159 |
+
background: #ffffff;
|
| 160 |
+
border-radius: 16px;
|
| 161 |
+
padding: 2.5rem;
|
| 162 |
+
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08),
|
| 163 |
+
0 1px 2px rgba(0, 0, 0, 0.04);
|
| 164 |
+
margin: 2rem 0;
|
| 165 |
+
border: 1px solid #ebe9e0;
|
| 166 |
+
}
|
| 167 |
+
|
| 168 |
+
.mindmap-card h3 {
|
| 169 |
+
color: #3d3929 !important;
|
| 170 |
+
font-weight: 700 !important;
|
| 171 |
+
margin-bottom: 1rem !important;
|
| 172 |
+
font-size: 1.6rem !important;
|
| 173 |
+
font-family: 'Merriweather', serif !important;
|
| 174 |
+
}
|
| 175 |
+
|
| 176 |
+
.mindmap-card p {
|
| 177 |
+
color: #6b6656;
|
| 178 |
+
font-size: 1rem;
|
| 179 |
+
margin-bottom: 1.5rem;
|
| 180 |
+
line-height: 1.6;
|
| 181 |
+
}
|
| 182 |
+
|
| 183 |
+
/* ============================================================
|
| 184 |
+
SUCCESS/INFO/ERROR MESSAGES - Claude Style
|
| 185 |
+
============================================================ */
|
| 186 |
+
.stSuccess {
|
| 187 |
+
background: #f0f8f5 !important;
|
| 188 |
+
border-radius: 12px !important;
|
| 189 |
+
padding: 1.2rem !important;
|
| 190 |
+
border-left: 4px solid #4a9d7f !important;
|
| 191 |
+
color: #3d3929 !important;
|
| 192 |
+
font-family: 'Inter', sans-serif !important;
|
| 193 |
+
}
|
| 194 |
+
|
| 195 |
+
.stInfo {
|
| 196 |
+
background: #f5f3ed !important;
|
| 197 |
+
border-radius: 12px !important;
|
| 198 |
+
padding: 1.2rem !important;
|
| 199 |
+
border-left: 4px solid #c15f3c !important;
|
| 200 |
+
color: #3d3929 !important;
|
| 201 |
+
font-family: 'Inter', sans-serif !important;
|
| 202 |
+
}
|
| 203 |
+
|
| 204 |
+
.stError {
|
| 205 |
+
background: #fef2f2 !important;
|
| 206 |
+
border-radius: 12px !important;
|
| 207 |
+
padding: 1.2rem !important;
|
| 208 |
+
border-left: 4px solid #dc2626 !important;
|
| 209 |
+
color: #3d3929 !important;
|
| 210 |
+
font-family: 'Inter', sans-serif !important;
|
| 211 |
+
}
|
| 212 |
+
|
| 213 |
+
.stWarning {
|
| 214 |
+
background: #fef9f3 !important;
|
| 215 |
+
border-radius: 12px !important;
|
| 216 |
+
padding: 1.2rem !important;
|
| 217 |
+
border-left: 4px solid #ea9d3e !important;
|
| 218 |
+
color: #3d3929 !important;
|
| 219 |
+
font-family: 'Inter', sans-serif !important;
|
| 220 |
+
}
|
| 221 |
+
|
| 222 |
+
/* ============================================================
|
| 223 |
+
SPINNER STYLING
|
| 224 |
+
============================================================ */
|
| 225 |
+
.stSpinner > div {
|
| 226 |
+
border-color: #c15f3c transparent #c15f3c transparent !important;
|
| 227 |
+
}
|
| 228 |
+
|
| 229 |
+
/* ============================================================
|
| 230 |
+
INFO BOX - Claude's Warm Design
|
| 231 |
+
============================================================ */
|
| 232 |
+
.info-box {
|
| 233 |
+
background: #ffffff;
|
| 234 |
+
border-left: 4px solid #c15f3c;
|
| 235 |
+
border-radius: 12px;
|
| 236 |
+
padding: 2rem;
|
| 237 |
+
color: #3d3929;
|
| 238 |
+
margin: 2.5rem 0;
|
| 239 |
+
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.06);
|
| 240 |
+
border: 1px solid #ebe9e0;
|
| 241 |
+
border-left: 4px solid #c15f3c;
|
| 242 |
+
}
|
| 243 |
+
|
| 244 |
+
.info-box h3 {
|
| 245 |
+
color: #c15f3c !important;
|
| 246 |
+
margin-bottom: 1.5rem !important;
|
| 247 |
+
font-weight: 700 !important;
|
| 248 |
+
font-family: 'Merriweather', serif !important;
|
| 249 |
+
}
|
| 250 |
+
|
| 251 |
+
.info-box ol {
|
| 252 |
+
margin-left: 1.5rem;
|
| 253 |
+
line-height: 1.8;
|
| 254 |
+
}
|
| 255 |
+
|
| 256 |
+
.info-box li {
|
| 257 |
+
margin: 1rem 0;
|
| 258 |
+
font-size: 1rem;
|
| 259 |
+
color: #3d3929;
|
| 260 |
+
}
|
| 261 |
+
|
| 262 |
+
.info-box p {
|
| 263 |
+
line-height: 1.7;
|
| 264 |
+
font-size: 1rem;
|
| 265 |
+
color: #3d3929;
|
| 266 |
+
}
|
| 267 |
+
|
| 268 |
+
.info-box strong {
|
| 269 |
+
color: #3d3929;
|
| 270 |
+
font-weight: 600;
|
| 271 |
+
}
|
| 272 |
+
|
| 273 |
+
.info-box em {
|
| 274 |
+
color: #6b6656;
|
| 275 |
+
font-style: italic;
|
| 276 |
+
}
|
| 277 |
+
|
| 278 |
+
/* ============================================================
|
| 279 |
+
EXPANDER STYLING
|
| 280 |
+
============================================================ */
|
| 281 |
+
.streamlit-expanderHeader {
|
| 282 |
+
background: #f9f8f4 !important;
|
| 283 |
+
border-radius: 10px !important;
|
| 284 |
+
font-weight: 600 !important;
|
| 285 |
+
padding: 1rem 1.5rem !important;
|
| 286 |
+
border: 1px solid #ebe9e0 !important;
|
| 287 |
+
color: #3d3929 !important;
|
| 288 |
+
font-family: 'Inter', sans-serif !important;
|
| 289 |
+
}
|
| 290 |
+
|
| 291 |
+
.streamlit-expanderHeader:hover {
|
| 292 |
+
background: #f5f3ed !important;
|
| 293 |
+
border-color: #ddd9cc !important;
|
| 294 |
+
}
|
| 295 |
+
|
| 296 |
+
/* ============================================================
|
| 297 |
+
METRICS - Claude Style
|
| 298 |
+
============================================================ */
|
| 299 |
+
[data-testid="stMetricValue"] {
|
| 300 |
+
font-size: 2rem !important;
|
| 301 |
+
color: #c15f3c !important;
|
| 302 |
+
font-weight: 700 !important;
|
| 303 |
+
font-family: 'Inter', sans-serif !important;
|
| 304 |
+
}
|
| 305 |
+
|
| 306 |
+
[data-testid="stMetricLabel"] {
|
| 307 |
+
font-size: 0.9rem !important;
|
| 308 |
+
font-weight: 600 !important;
|
| 309 |
+
color: #6b6656 !important;
|
| 310 |
+
text-transform: uppercase !important;
|
| 311 |
+
letter-spacing: 0.5px !important;
|
| 312 |
+
}
|
| 313 |
+
|
| 314 |
+
/* ============================================================
|
| 315 |
+
SIDEBAR - Warm Neutral
|
| 316 |
+
============================================================ */
|
| 317 |
+
[data-testid="stSidebar"] {
|
| 318 |
+
background: #eeece2;
|
| 319 |
+
border-right: 1px solid #ddd9cc;
|
| 320 |
+
}
|
| 321 |
+
|
| 322 |
+
[data-testid="stSidebar"] .stButton > button {
|
| 323 |
+
background: #ffffff !important;
|
| 324 |
+
border: 1.5px solid #ddd9cc !important;
|
| 325 |
+
color: #3d3929 !important;
|
| 326 |
+
border-radius: 10px !important;
|
| 327 |
+
padding: 10px 16px !important;
|
| 328 |
+
font-size: 0.9rem !important;
|
| 329 |
+
font-weight: 600 !important;
|
| 330 |
+
transition: all 0.2s ease !important;
|
| 331 |
+
font-family: 'Inter', sans-serif !important;
|
| 332 |
+
}
|
| 333 |
+
|
| 334 |
+
[data-testid="stSidebar"] .stButton > button:hover {
|
| 335 |
+
background: #f5f3ed !important;
|
| 336 |
+
border-color: #c15f3c !important;
|
| 337 |
+
color: #c15f3c !important;
|
| 338 |
+
}
|
| 339 |
+
|
| 340 |
+
[data-testid="stSidebar"] h1 {
|
| 341 |
+
font-size: 1.3rem !important;
|
| 342 |
+
color: #3d3929 !important;
|
| 343 |
+
margin-bottom: 1rem !important;
|
| 344 |
+
font-family: 'Merriweather', serif !important;
|
| 345 |
+
}
|
| 346 |
+
|
| 347 |
+
[data-testid="stSidebar"] .stMarkdown {
|
| 348 |
+
color: #3d3929;
|
| 349 |
+
font-family: 'Inter', sans-serif;
|
| 350 |
+
}
|
| 351 |
+
|
| 352 |
+
/* ============================================================
|
| 353 |
+
HORIZONTAL RULE
|
| 354 |
+
============================================================ */
|
| 355 |
+
hr {
|
| 356 |
+
border: none;
|
| 357 |
+
height: 1px;
|
| 358 |
+
background: #ebe9e0;
|
| 359 |
+
margin: 2rem 0;
|
| 360 |
+
}
|
| 361 |
+
|
| 362 |
+
/* ============================================================
|
| 363 |
+
LINKS - Claude Orange
|
| 364 |
+
============================================================ */
|
| 365 |
+
a {
|
| 366 |
+
color: #c15f3c !important;
|
| 367 |
+
text-decoration: none !important;
|
| 368 |
+
font-weight: 500 !important;
|
| 369 |
+
transition: color 0.2s ease !important;
|
| 370 |
+
}
|
| 371 |
+
|
| 372 |
+
a:hover {
|
| 373 |
+
color: #a14a2f !important;
|
| 374 |
+
text-decoration: underline !important;
|
| 375 |
+
}
|
| 376 |
+
|
| 377 |
+
/* ============================================================
|
| 378 |
+
HIDE STREAMLIT BRANDING
|
| 379 |
+
============================================================ */
|
| 380 |
+
#MainMenu {visibility: hidden;}
|
| 381 |
+
footer {visibility: hidden;}
|
| 382 |
+
header {visibility: hidden;}
|
| 383 |
+
|
| 384 |
+
/* ============================================================
|
| 385 |
+
CUSTOM SCROLLBAR - Claude Style
|
| 386 |
+
============================================================ */
|
| 387 |
+
::-webkit-scrollbar {
|
| 388 |
+
width: 8px;
|
| 389 |
+
height: 8px;
|
| 390 |
+
}
|
| 391 |
+
|
| 392 |
+
::-webkit-scrollbar-track {
|
| 393 |
+
background: #f5f3ed;
|
| 394 |
+
}
|
| 395 |
+
|
| 396 |
+
::-webkit-scrollbar-thumb {
|
| 397 |
+
background: #c15f3c;
|
| 398 |
+
border-radius: 4px;
|
| 399 |
+
}
|
| 400 |
+
|
| 401 |
+
::-webkit-scrollbar-thumb:hover {
|
| 402 |
+
background: #a14a2f;
|
| 403 |
+
}
|
| 404 |
+
|
| 405 |
+
/* ============================================================
|
| 406 |
+
TYPOGRAPHY IMPROVEMENTS
|
| 407 |
+
============================================================ */
|
| 408 |
+
body {
|
| 409 |
+
font-family: 'Inter', -apple-system, BlinkMacSystemFont, sans-serif;
|
| 410 |
+
color: #3d3929;
|
| 411 |
+
line-height: 1.6;
|
| 412 |
+
}
|
| 413 |
+
|
| 414 |
+
h2, h3, h4, h5, h6 {
|
| 415 |
+
font-family: 'Merriweather', serif;
|
| 416 |
+
color: #3d3929;
|
| 417 |
+
}
|
| 418 |
+
|
| 419 |
+
/* ============================================================
|
| 420 |
+
RESPONSIVE DESIGN
|
| 421 |
+
============================================================ */
|
| 422 |
+
@media (max-width: 768px) {
|
| 423 |
+
h1 {
|
| 424 |
+
font-size: 2.2rem !important;
|
| 425 |
+
}
|
| 426 |
+
|
| 427 |
+
.subtitle {
|
| 428 |
+
font-size: 1rem !important;
|
| 429 |
+
}
|
| 430 |
+
|
| 431 |
+
.mindmap-card {
|
| 432 |
+
padding: 1.5rem;
|
| 433 |
+
}
|
| 434 |
+
|
| 435 |
+
.stTextInput > div > div > input {
|
| 436 |
+
font-size: 15px !important;
|
| 437 |
+
padding: 14px 18px !important;
|
| 438 |
+
}
|
| 439 |
+
|
| 440 |
+
.stButton > button {
|
| 441 |
+
font-size: 14px !important;
|
| 442 |
+
padding: 12px 24px !important;
|
| 443 |
+
}
|
| 444 |
+
}
|
| 445 |
+
|
| 446 |
+
/* ============================================================
|
| 447 |
+
SMOOTH INTERACTIONS
|
| 448 |
+
============================================================ */
|
| 449 |
+
* {
|
| 450 |
+
transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1);
|
| 451 |
+
}
|
| 452 |
+
|
| 453 |
+
html {
|
| 454 |
+
scroll-behavior: smooth;
|
| 455 |
+
}
|
| 456 |
+
|
| 457 |
+
/* ============================================================
|
| 458 |
+
CLEAN, MINIMAL AESTHETIC
|
| 459 |
+
============================================================ */
|
| 460 |
+
.stApp {
|
| 461 |
+
background: #faf9f5;
|
| 462 |
+
}
|
| 463 |
+
|
| 464 |
+
/* Remove unnecessary decorations */
|
| 465 |
+
.stDeployButton {
|
| 466 |
+
display: none;
|
| 467 |
+
}
|
| 468 |
+
</style>
|
| 469 |
+
""", unsafe_allow_html=True)
|
| 470 |
+
|
| 471 |
+
|
| 472 |
+
|
| 473 |
+
# ============================================================================
|
| 474 |
+
# SESSION STATE MANAGEMENT
|
| 475 |
+
# ============================================================================
|
| 476 |
+
|
| 477 |
+
def initialize_session_state():
|
| 478 |
+
"""Initialize all session state variables"""
|
| 479 |
+
if 'mindmap_history' not in st.session_state:
|
| 480 |
+
st.session_state.mindmap_history = []
|
| 481 |
+
|
| 482 |
+
if 'current_mindmap' not in st.session_state:
|
| 483 |
+
st.session_state.current_mindmap = None
|
| 484 |
+
|
| 485 |
+
if 'query_count' not in st.session_state:
|
| 486 |
+
st.session_state.query_count = 0
|
| 487 |
+
|
| 488 |
+
if 'last_keyword' not in st.session_state:
|
| 489 |
+
st.session_state.last_keyword = ""
|
| 490 |
+
|
| 491 |
+
|
| 492 |
+
# ============================================================================
|
| 493 |
+
# HELPER FUNCTIONS
|
| 494 |
+
# ============================================================================
|
| 495 |
+
|
| 496 |
+
def validate_api_keys() -> tuple[bool, list[str]]:
|
| 497 |
+
"""
|
| 498 |
+
Validate that all required API keys are configured
|
| 499 |
+
|
| 500 |
+
Returns:
|
| 501 |
+
Tuple of (is_valid, missing_keys_list)
|
| 502 |
+
"""
|
| 503 |
+
return settings.validate_api_keys()
|
| 504 |
+
|
| 505 |
+
|
| 506 |
+
def generate_mindmap(keyword: str) -> dict:
|
| 507 |
+
"""
|
| 508 |
+
Generate mindmap for given keyword using API handler
|
| 509 |
+
|
| 510 |
+
Args:
|
| 511 |
+
keyword: Technical keyword to analyze
|
| 512 |
+
|
| 513 |
+
Returns:
|
| 514 |
+
Dictionary with mindmap data and metadata
|
| 515 |
+
"""
|
| 516 |
+
try:
|
| 517 |
+
result = fetch_mindmap_data(
|
| 518 |
+
keyword=keyword,
|
| 519 |
+
gemini_key=settings.gemini_api_key,
|
| 520 |
+
tavily_key=settings.tavily_api_key,
|
| 521 |
+
kg_api_key=settings.google_cloud_api_key
|
| 522 |
+
)
|
| 523 |
+
return result
|
| 524 |
+
except Exception as e:
|
| 525 |
+
st.error(f"Error generating mindmap: {str(e)}")
|
| 526 |
+
return None
|
| 527 |
+
|
| 528 |
+
|
| 529 |
+
# ============================================================================
|
| 530 |
+
# MAIN APPLICATION
|
| 531 |
+
# ============================================================================
|
| 532 |
+
|
| 533 |
+
def main():
|
| 534 |
+
"""Main application logic"""
|
| 535 |
+
|
| 536 |
+
# Initialize session state
|
| 537 |
+
initialize_session_state()
|
| 538 |
+
|
| 539 |
+
# ========================================================================
|
| 540 |
+
# HEADER SECTION
|
| 541 |
+
# ========================================================================
|
| 542 |
+
|
| 543 |
+
st.markdown("<h1>🧠 Technical Mindmap Generator</h1>", unsafe_allow_html=True)
|
| 544 |
+
st.markdown(
|
| 545 |
+
"<p class='subtitle'>Transform technical keywords into interactive visual mindmaps powered by AI</p>",
|
| 546 |
+
unsafe_allow_html=True
|
| 547 |
+
)
|
| 548 |
+
|
| 549 |
+
# ========================================================================
|
| 550 |
+
# API KEY VALIDATION
|
| 551 |
+
# ========================================================================
|
| 552 |
+
|
| 553 |
+
is_valid, missing_keys = validate_api_keys()
|
| 554 |
+
|
| 555 |
+
if not is_valid:
|
| 556 |
+
st.error(f"⚠️ Missing API Keys: {', '.join(missing_keys)}")
|
| 557 |
+
st.info("Please configure your API keys in the `.env` file before using the application.")
|
| 558 |
+
|
| 559 |
+
with st.expander("📝 How to set up API keys"):
|
| 560 |
+
st.markdown("""
|
| 561 |
+
1. Create a `.env` file in the project root
|
| 562 |
+
2. Add your API keys:
|
| 563 |
+
```
|
| 564 |
+
GEMINI_API_KEY=your_gemini_key
|
| 565 |
+
TAVILY_API_KEY=your_tavily_key
|
| 566 |
+
|
| 567 |
+
```
|
| 568 |
+
3. Restart the application
|
| 569 |
+
|
| 570 |
+
**Get API Keys:**
|
| 571 |
+
- Gemini: https://ai.google.dev/
|
| 572 |
+
- Tavily: https://tavily.com/
|
| 573 |
+
- Google Cloud: https://console.cloud.google.com/
|
| 574 |
+
""")
|
| 575 |
+
|
| 576 |
+
st.stop()
|
| 577 |
+
|
| 578 |
+
# ========================================================================
|
| 579 |
+
# INPUT SECTION
|
| 580 |
+
# ========================================================================
|
| 581 |
+
|
| 582 |
+
# Center the input field
|
| 583 |
+
col1, col2, col3 = st.columns([1, 3, 1])
|
| 584 |
+
|
| 585 |
+
with col2:
|
| 586 |
+
keyword = st.text_input(
|
| 587 |
+
"",
|
| 588 |
+
placeholder="e.g., Machine Learning, Blockchain, Kubernetes, Neural Networks...",
|
| 589 |
+
key=f"keyword_input_{st.session_state.query_count}",
|
| 590 |
+
label_visibility="collapsed",
|
| 591 |
+
help="Enter any technical keyword or concept"
|
| 592 |
+
)
|
| 593 |
+
|
| 594 |
+
# Center the button
|
| 595 |
+
btn_col1, btn_col2, btn_col3 = st.columns([1, 1, 1])
|
| 596 |
+
with btn_col2:
|
| 597 |
+
generate_button = st.button(
|
| 598 |
+
"🚀 Generate Mindmap",
|
| 599 |
+
use_container_width=True,
|
| 600 |
+
type="primary"
|
| 601 |
+
)
|
| 602 |
+
|
| 603 |
+
# ========================================================================
|
| 604 |
+
# MINDMAP GENERATION
|
| 605 |
+
# ========================================================================
|
| 606 |
+
|
| 607 |
+
if generate_button and keyword:
|
| 608 |
+
# Validate keyword
|
| 609 |
+
if len(keyword.strip()) < 2:
|
| 610 |
+
st.warning("⚠️ Please enter a longer keyword (at least 2 characters)")
|
| 611 |
+
else:
|
| 612 |
+
# Show loading animation
|
| 613 |
+
with st.spinner(f"🔍 Analyzing '{keyword}'...\n\n⚡ This may take 10-15 seconds..."):
|
| 614 |
+
|
| 615 |
+
# Add progress messages
|
| 616 |
+
progress_placeholder = st.empty()
|
| 617 |
+
|
| 618 |
+
progress_placeholder.info("📡 Step 1/3: Fetching web data from Tavily...")
|
| 619 |
+
time.sleep(1)
|
| 620 |
+
|
| 621 |
+
progress_placeholder.info("🔗 Step 2/3: Querying Knowledge Graph...")
|
| 622 |
+
time.sleep(1)
|
| 623 |
+
|
| 624 |
+
progress_placeholder.info("🤖 Step 3/3: Synthesizing with Gemini AI...")
|
| 625 |
+
|
| 626 |
+
# Generate mindmap
|
| 627 |
+
result = generate_mindmap(keyword)
|
| 628 |
+
|
| 629 |
+
# Clear progress messages
|
| 630 |
+
progress_placeholder.empty()
|
| 631 |
+
|
| 632 |
+
if result:
|
| 633 |
+
# Store in session state
|
| 634 |
+
st.session_state.current_mindmap = result
|
| 635 |
+
st.session_state.mindmap_history.append({
|
| 636 |
+
'keyword': keyword,
|
| 637 |
+
'data': result,
|
| 638 |
+
'timestamp': time.time()
|
| 639 |
+
})
|
| 640 |
+
st.session_state.query_count += 1
|
| 641 |
+
st.session_state.last_keyword = keyword
|
| 642 |
+
|
| 643 |
+
st.success(f"✅ Mindmap generated successfully for '{keyword}'!")
|
| 644 |
+
else:
|
| 645 |
+
st.error("❌ Failed to generate mindmap. Please try again.")
|
| 646 |
+
|
| 647 |
+
# ========================================================================
|
| 648 |
+
# DISPLAY MINDMAP
|
| 649 |
+
# ========================================================================
|
| 650 |
+
|
| 651 |
+
if st.session_state.current_mindmap:
|
| 652 |
+
st.markdown("<div class='mindmap-card'>", unsafe_allow_html=True)
|
| 653 |
+
|
| 654 |
+
# Display mindmap
|
| 655 |
+
mindmap_data = st.session_state.current_mindmap['mindmap']
|
| 656 |
+
|
| 657 |
+
st.markdown(f"### 🗺️ Mindmap: {st.session_state.last_keyword}")
|
| 658 |
+
st.markdown("*Zoom, pan, and hover over nodes for details*")
|
| 659 |
+
|
| 660 |
+
try:
|
| 661 |
+
generator = MindmapGenerator(height="700px", width="100%")
|
| 662 |
+
generator.render_in_streamlit(mindmap_data)
|
| 663 |
+
except Exception as e:
|
| 664 |
+
st.error(f"Error rendering mindmap: {e}")
|
| 665 |
+
|
| 666 |
+
st.markdown("</div>", unsafe_allow_html=True)
|
| 667 |
+
|
| 668 |
+
# ====================================================================
|
| 669 |
+
# METADATA SECTION
|
| 670 |
+
# ====================================================================
|
| 671 |
+
|
| 672 |
+
with st.expander("📊 View Generation Details", expanded=False):
|
| 673 |
+
metadata = st.session_state.current_mindmap['metadata']
|
| 674 |
+
|
| 675 |
+
# Metrics in columns
|
| 676 |
+
metric_col1, metric_col2, metric_col3, metric_col4 = st.columns(4)
|
| 677 |
+
|
| 678 |
+
with metric_col1:
|
| 679 |
+
st.metric("🎯 Keyword", metadata['keyword'])
|
| 680 |
+
|
| 681 |
+
with metric_col2:
|
| 682 |
+
st.metric("🔗 Total Nodes", metadata.get('total_nodes', 0))
|
| 683 |
+
|
| 684 |
+
with metric_col3:
|
| 685 |
+
st.metric("📚 Sources", len(metadata.get('tavily_sources', [])))
|
| 686 |
+
|
| 687 |
+
with metric_col4:
|
| 688 |
+
st.metric("🔍 KG Entities", metadata.get('kg_entities_count', 0))
|
| 689 |
+
|
| 690 |
+
st.markdown("---")
|
| 691 |
+
|
| 692 |
+
# Display sources
|
| 693 |
+
if metadata.get('tavily_sources'):
|
| 694 |
+
st.subheader("📚 Information Sources")
|
| 695 |
+
for i, source in enumerate(metadata['tavily_sources'][:8], 1):
|
| 696 |
+
st.markdown(f"{i}. [{source}]({source})")
|
| 697 |
+
|
| 698 |
+
else:
|
| 699 |
+
# ====================================================================
|
| 700 |
+
# WELCOME / INFO SECTION
|
| 701 |
+
# ====================================================================
|
| 702 |
+
|
| 703 |
+
st.markdown("""
|
| 704 |
+
<div class='info-box'>
|
| 705 |
+
<h3>💡 How it works:</h3>
|
| 706 |
+
<ol>
|
| 707 |
+
<li><strong>Tavily API</strong>: Gathers real-time web context and discovers related terms</li>
|
| 708 |
+
<li><strong>Knowledge Graph API</strong>: Provides structured entity relationships and descriptions</li>
|
| 709 |
+
<li><strong>Gemini AI</strong>: Synthesizes all data into a comprehensive mindmap structure</li>
|
| 710 |
+
</ol>
|
| 711 |
+
<p style='margin-top:1.5rem; font-size:1.1rem;'>
|
| 712 |
+
<strong>Simply enter a technical keyword above and click "Generate Mindmap" to begin!</strong>
|
| 713 |
+
</p>
|
| 714 |
+
<p style='margin-top:1rem; color:#95e1d3;'>
|
| 715 |
+
💡 <em>Tip: Try keywords like "Kubernetes", "Machine Learning", "Quantum Computing", or "Blockchain"</em>
|
| 716 |
+
</p>
|
| 717 |
+
</div>
|
| 718 |
+
""", unsafe_allow_html=True)
|
| 719 |
+
|
| 720 |
+
# ========================================================================
|
| 721 |
+
# SIDEBAR - HISTORY & SETTINGS
|
| 722 |
+
# ========================================================================
|
| 723 |
+
|
| 724 |
+
with st.sidebar:
|
| 725 |
+
st.title("📜 Query History")
|
| 726 |
+
|
| 727 |
+
if st.session_state.mindmap_history:
|
| 728 |
+
st.markdown(f"**Total Queries:** {len(st.session_state.mindmap_history)}")
|
| 729 |
+
st.markdown("---")
|
| 730 |
+
|
| 731 |
+
# Display history in reverse order (most recent first)
|
| 732 |
+
for i, item in enumerate(reversed(st.session_state.mindmap_history)):
|
| 733 |
+
keyword_display = item['keyword']
|
| 734 |
+
|
| 735 |
+
# Create button with emoji
|
| 736 |
+
if st.button(
|
| 737 |
+
f"🔄 {keyword_display}",
|
| 738 |
+
key=f"history_{i}",
|
| 739 |
+
help=f"Load mindmap for '{keyword_display}'"
|
| 740 |
+
):
|
| 741 |
+
st.session_state.current_mindmap = item['data']
|
| 742 |
+
st.session_state.last_keyword = item['keyword']
|
| 743 |
+
st.rerun()
|
| 744 |
+
else:
|
| 745 |
+
st.info("No queries yet.\nGenerate your first mindmap!")
|
| 746 |
+
|
| 747 |
+
st.markdown("---")
|
| 748 |
+
|
| 749 |
+
# Settings section
|
| 750 |
+
st.markdown("### ⚙️ Settings")
|
| 751 |
+
st.markdown(f"**Max Nodes:** {settings.max_nodes}")
|
| 752 |
+
st.markdown(f"**Max Depth:** {settings.max_depth}")
|
| 753 |
+
st.markdown(f"**Caching:** {'Enabled' if settings.cache_enabled else 'Disabled'}")
|
| 754 |
+
|
| 755 |
+
st.markdown("---")
|
| 756 |
+
|
| 757 |
+
# About section
|
| 758 |
+
with st.expander("ℹ️ About"):
|
| 759 |
+
st.markdown("""
|
| 760 |
+
**Technical Mindmap Generator**
|
| 761 |
+
|
| 762 |
+
Version 1.0.0
|
| 763 |
+
|
| 764 |
+
A proof-of-concept application that creates interactive visual mindmaps for technical concepts using AI-powered APIs.
|
| 765 |
+
|
| 766 |
+
**Technologies:**
|
| 767 |
+
- Streamlit
|
| 768 |
+
- PyVis
|
| 769 |
+
- Gemini AI
|
| 770 |
+
- Tavily Search
|
| 771 |
+
- Knowledge Graph
|
| 772 |
+
|
| 773 |
+
**Created by:** Your Name
|
| 774 |
+
""")
|
| 775 |
+
|
| 776 |
+
|
| 777 |
+
# ============================================================================
|
| 778 |
+
# APPLICATION ENTRY POINT
|
| 779 |
+
# ============================================================================
|
| 780 |
|
| 781 |
+
if __name__ == "__main__":
|
| 782 |
+
main()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
src/utils/__init__.py
ADDED
|
File without changes
|
src/utils/__pycache__/__init__.cpython-310.pyc
ADDED
|
Binary file (149 Bytes). View file
|
|
|
src/utils/__pycache__/api_handler.cpython-310.pyc
ADDED
|
Binary file (11.7 kB). View file
|
|
|
src/utils/__pycache__/mindmap_generator.cpython-310.pyc
ADDED
|
Binary file (7.44 kB). View file
|
|
|
src/utils/api_handler.py
ADDED
|
@@ -0,0 +1,461 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
API Handler for Gemini, Tavily, and Knowledge Graph APIs
|
| 3 |
+
Implements optimized sequential async calls for mindmap generation
|
| 4 |
+
|
| 5 |
+
This module orchestrates API calls in the following sequence:
|
| 6 |
+
1. Tavily API: Gather broad web context and related terms
|
| 7 |
+
2. Knowledge Graph API: Get structured entity data using Tavily results
|
| 8 |
+
3. Gemini API: Synthesize comprehensive mindmap structure
|
| 9 |
+
"""
|
| 10 |
+
|
| 11 |
+
import asyncio
|
| 12 |
+
import json
|
| 13 |
+
import re
|
| 14 |
+
from typing import Dict, List, Any, Optional
|
| 15 |
+
from tavily import TavilyClient
|
| 16 |
+
import google.generativeai as genai
|
| 17 |
+
from google.cloud import enterpriseknowledgegraph as ekg
|
| 18 |
+
import logging
|
| 19 |
+
import requests
|
| 20 |
+
|
| 21 |
+
# Configure logging
|
| 22 |
+
logging.basicConfig(level=logging.INFO)
|
| 23 |
+
logger = logging.getLogger(__name__)
|
| 24 |
+
|
| 25 |
+
|
| 26 |
+
class APIHandler:
|
| 27 |
+
"""
|
| 28 |
+
Handles all API interactions with optimized sequential processing
|
| 29 |
+
|
| 30 |
+
This class manages communication with three external APIs:
|
| 31 |
+
- Tavily: Real-time web search and content extraction
|
| 32 |
+
- Knowledge Graph: Structured entity and relationship data
|
| 33 |
+
- Gemini: AI-powered synthesis and structure generation
|
| 34 |
+
"""
|
| 35 |
+
|
| 36 |
+
def __init__(self, gemini_key: str, tavily_key: str, kg_api_key: str):
|
| 37 |
+
"""
|
| 38 |
+
Initialize API clients with provided credentials
|
| 39 |
+
|
| 40 |
+
Args:
|
| 41 |
+
gemini_key: Gemini API key
|
| 42 |
+
tavily_key: Tavily API key
|
| 43 |
+
google_project_id: Google Cloud project ID
|
| 44 |
+
"""
|
| 45 |
+
self.gemini_key = gemini_key
|
| 46 |
+
self.tavily_key = tavily_key
|
| 47 |
+
self.kg_api_key = kg_api_key
|
| 48 |
+
|
| 49 |
+
# Initialize Tavily client
|
| 50 |
+
try:
|
| 51 |
+
self.tavily_client = TavilyClient(api_key=tavily_key)
|
| 52 |
+
logger.info("✅ Tavily client initialized")
|
| 53 |
+
except Exception as e:
|
| 54 |
+
logger.error(f"❌ Tavily initialization failed: {e}")
|
| 55 |
+
self.tavily_client = None
|
| 56 |
+
|
| 57 |
+
# Initialize Gemini
|
| 58 |
+
try:
|
| 59 |
+
genai.configure(api_key=gemini_key)
|
| 60 |
+
self.gemini_model = genai.GenerativeModel('gemini-2.0-flash')
|
| 61 |
+
logger.info("✅ Gemini client initialized")
|
| 62 |
+
except Exception as e:
|
| 63 |
+
logger.error(f"❌ Gemini initialization failed: {e}")
|
| 64 |
+
self.gemini_model = None
|
| 65 |
+
|
| 66 |
+
|
| 67 |
+
|
| 68 |
+
async def fetch_tavily_data(self, keyword: str) -> Dict[str, Any]:
|
| 69 |
+
"""
|
| 70 |
+
Step 1: Fetch related terms and context from Tavily API
|
| 71 |
+
|
| 72 |
+
This method performs a web search to gather:
|
| 73 |
+
- Related technical terms
|
| 74 |
+
- Contextual information
|
| 75 |
+
- Source URLs for reference
|
| 76 |
+
|
| 77 |
+
Args:
|
| 78 |
+
keyword: Technical keyword to search
|
| 79 |
+
|
| 80 |
+
Returns:
|
| 81 |
+
Dictionary containing:
|
| 82 |
+
- key_terms: List of related terms (max 15)
|
| 83 |
+
- context: Aggregated context from top results
|
| 84 |
+
- sources: List of source URLs
|
| 85 |
+
"""
|
| 86 |
+
logger.info(f"🔍 Step 1: Fetching Tavily data for '{keyword}'")
|
| 87 |
+
|
| 88 |
+
if not self.tavily_client:
|
| 89 |
+
logger.warning("Tavily client not available, using fallback")
|
| 90 |
+
return {'key_terms': [], 'context': '', 'sources': []}
|
| 91 |
+
|
| 92 |
+
try:
|
| 93 |
+
# Perform advanced search
|
| 94 |
+
response = self.tavily_client.search(
|
| 95 |
+
query=f"{keyword} technical overview concepts",
|
| 96 |
+
search_depth="advanced",
|
| 97 |
+
max_results=10,
|
| 98 |
+
include_domains=[],
|
| 99 |
+
exclude_domains=[]
|
| 100 |
+
)
|
| 101 |
+
|
| 102 |
+
# Extract key terms and context
|
| 103 |
+
key_terms = set()
|
| 104 |
+
context_parts = []
|
| 105 |
+
sources = []
|
| 106 |
+
|
| 107 |
+
for result in response.get('results', []):
|
| 108 |
+
content = result.get('content', '')
|
| 109 |
+
url = result.get('url', '')
|
| 110 |
+
|
| 111 |
+
# Store context and source
|
| 112 |
+
if content:
|
| 113 |
+
context_parts.append(content)
|
| 114 |
+
if url:
|
| 115 |
+
sources.append(url)
|
| 116 |
+
|
| 117 |
+
# Extract meaningful terms (simple extraction)
|
| 118 |
+
words = re.findall(r'\b[A-Z][a-z]+(?:\s+[A-Z][a-z]+)*\b', content)
|
| 119 |
+
technical_words = [w for w in words if len(w) > 4]
|
| 120 |
+
key_terms.update(technical_words[:20])
|
| 121 |
+
|
| 122 |
+
# Limit and format results
|
| 123 |
+
key_terms_list = list(key_terms)[:15]
|
| 124 |
+
context = ' '.join(context_parts[:3])[:2000] # Limit context length
|
| 125 |
+
|
| 126 |
+
logger.info(f"✅ Tavily: Found {len(key_terms_list)} key terms from {len(sources)} sources")
|
| 127 |
+
|
| 128 |
+
return {
|
| 129 |
+
'key_terms': key_terms_list,
|
| 130 |
+
'context': context,
|
| 131 |
+
'sources': sources
|
| 132 |
+
}
|
| 133 |
+
|
| 134 |
+
except Exception as e:
|
| 135 |
+
logger.error(f"❌ Tavily API error: {e}")
|
| 136 |
+
return {
|
| 137 |
+
'key_terms': [keyword],
|
| 138 |
+
'context': f"Technical information about {keyword}",
|
| 139 |
+
'sources': []
|
| 140 |
+
}
|
| 141 |
+
|
| 142 |
+
async def fetch_knowledge_graph_data(self, keyword: str) -> Dict[str, Any]:
|
| 143 |
+
if not self.kg_api_key:
|
| 144 |
+
print("⚠️ Knowledge Graph skipped (no API key)")
|
| 145 |
+
return {}
|
| 146 |
+
|
| 147 |
+
url = "https://kgsearch.googleapis.com/v1/entities:search"
|
| 148 |
+
params = {
|
| 149 |
+
'query': keyword,
|
| 150 |
+
'limit': 5,
|
| 151 |
+
'key': self.kg_api_key,
|
| 152 |
+
}
|
| 153 |
+
try:
|
| 154 |
+
response = requests.get(url, params=params, timeout=10)
|
| 155 |
+
response.raise_for_status()
|
| 156 |
+
data = response.json()
|
| 157 |
+
|
| 158 |
+
entities = []
|
| 159 |
+
for item in data.get('itemListElement', [])[:5]:
|
| 160 |
+
result = item.get('result', {})
|
| 161 |
+
entities.append({
|
| 162 |
+
'name': result.get('name', ''),
|
| 163 |
+
'description': result.get('description', '')
|
| 164 |
+
})
|
| 165 |
+
|
| 166 |
+
return {'entities': entities, 'relationships': []}
|
| 167 |
+
|
| 168 |
+
except Exception as e:
|
| 169 |
+
print(f" ✗ Error querying Knowledge Graph: {e}")
|
| 170 |
+
return {}
|
| 171 |
+
|
| 172 |
+
|
| 173 |
+
|
| 174 |
+
async def generate_gemini_mindmap(
|
| 175 |
+
self,
|
| 176 |
+
keyword: str,
|
| 177 |
+
tavily_data: Dict[str, Any],
|
| 178 |
+
kg_data: Dict[str, Any]
|
| 179 |
+
) -> Dict[str, Any]:
|
| 180 |
+
"""
|
| 181 |
+
Step 3: Use Gemini to synthesize comprehensive mindmap structure
|
| 182 |
+
|
| 183 |
+
Combines data from Tavily and Knowledge Graph to create a
|
| 184 |
+
well-structured, hierarchical mindmap.
|
| 185 |
+
|
| 186 |
+
Args:
|
| 187 |
+
keyword: Main technical keyword
|
| 188 |
+
tavily_data: Data from Tavily API (context, terms, sources)
|
| 189 |
+
kg_data: Data from Knowledge Graph API (entities, relationships)
|
| 190 |
+
|
| 191 |
+
Returns:
|
| 192 |
+
Dictionary containing complete mindmap structure:
|
| 193 |
+
- center: Central node (keyword)
|
| 194 |
+
- nodes: List of node dictionaries
|
| 195 |
+
- edges: List of edge dictionaries
|
| 196 |
+
"""
|
| 197 |
+
logger.info(f"🔍 Step 3: Generating Gemini mindmap for '{keyword}'")
|
| 198 |
+
|
| 199 |
+
if not self.gemini_model:
|
| 200 |
+
logger.warning("Gemini model not available, using fallback")
|
| 201 |
+
return self._create_fallback_mindmap(keyword, tavily_data, kg_data)
|
| 202 |
+
|
| 203 |
+
try:
|
| 204 |
+
# Prepare context for Gemini
|
| 205 |
+
key_terms_str = ', '.join(tavily_data.get('key_terms', [])[:10])
|
| 206 |
+
|
| 207 |
+
entities_info = []
|
| 208 |
+
for entity in kg_data.get('entities', [])[:5]:
|
| 209 |
+
entities_info.append(
|
| 210 |
+
f"- {entity['name']}: {entity['description']}"
|
| 211 |
+
)
|
| 212 |
+
entities_str = '\n'.join(entities_info) if entities_info else "No entities found"
|
| 213 |
+
|
| 214 |
+
context_snippet = tavily_data.get('context', '')[:1000]
|
| 215 |
+
|
| 216 |
+
# Construct enriched prompt
|
| 217 |
+
prompt = f"""You are a technical knowledge expert creating mindmap structures.
|
| 218 |
+
|
| 219 |
+
Generate a comprehensive radial mindmap structure for: "{keyword}"
|
| 220 |
+
|
| 221 |
+
Web Context:
|
| 222 |
+
{context_snippet}
|
| 223 |
+
|
| 224 |
+
Related Terms Discovered:
|
| 225 |
+
{key_terms_str}
|
| 226 |
+
|
| 227 |
+
Knowledge Graph Entities:
|
| 228 |
+
{entities_str}
|
| 229 |
+
|
| 230 |
+
Create a JSON mindmap with:
|
| 231 |
+
1. Center node: "{keyword}"
|
| 232 |
+
2. Primary nodes (5-7): Major categories/aspects of this technical topic
|
| 233 |
+
3. Secondary nodes (2-3 per primary): Specific concepts, tools, or subtopics
|
| 234 |
+
|
| 235 |
+
Requirements:
|
| 236 |
+
- Each node must have: id, label, level (1=primary, 2=secondary), description
|
| 237 |
+
- Each edge must have: from (node id), to (node id), label (relationship type)
|
| 238 |
+
- Use descriptive labels and meaningful relationships
|
| 239 |
+
- Keep descriptions concise (under 100 chars)
|
| 240 |
+
|
| 241 |
+
Output ONLY valid JSON in this exact format:
|
| 242 |
+
{{
|
| 243 |
+
"center": "{keyword}",
|
| 244 |
+
"nodes": [
|
| 245 |
+
{{"id": "node1", "label": "Category Name", "level": 1, "description": "Brief explanation"}},
|
| 246 |
+
{{"id": "node2", "label": "Subconcept", "level": 2, "description": "Specific detail"}}
|
| 247 |
+
],
|
| 248 |
+
"edges": [
|
| 249 |
+
{{"from": "center", "to": "node1", "label": "includes"}},
|
| 250 |
+
{{"from": "node1", "to": "node2", "label": "contains"}}
|
| 251 |
+
]
|
| 252 |
+
}}
|
| 253 |
+
|
| 254 |
+
Generate the JSON now:"""
|
| 255 |
+
|
| 256 |
+
# Call Gemini API
|
| 257 |
+
response = self.gemini_model.generate_content(prompt)
|
| 258 |
+
response_text = response.text.strip()
|
| 259 |
+
|
| 260 |
+
# Clean response (remove markdown code blocks if present)
|
| 261 |
+
if '```json' in response_text:
|
| 262 |
+
response_text = response_text.split('```json')[1].split('```')[0].strip()
|
| 263 |
+
elif '```' in response_text:
|
| 264 |
+
response_text = response_text.split('```')[1].split('```')[0].strip()
|
| 265 |
+
|
| 266 |
+
# Parse JSON
|
| 267 |
+
mindmap_data = json.loads(response_text)
|
| 268 |
+
|
| 269 |
+
# Validate structure
|
| 270 |
+
if 'center' not in mindmap_data:
|
| 271 |
+
mindmap_data['center'] = keyword
|
| 272 |
+
if 'nodes' not in mindmap_data:
|
| 273 |
+
mindmap_data['nodes'] = []
|
| 274 |
+
if 'edges' not in mindmap_data:
|
| 275 |
+
mindmap_data['edges'] = []
|
| 276 |
+
|
| 277 |
+
logger.info(f"✅ Gemini: Generated mindmap with {len(mindmap_data['nodes'])} nodes")
|
| 278 |
+
|
| 279 |
+
return mindmap_data
|
| 280 |
+
|
| 281 |
+
except json.JSONDecodeError as e:
|
| 282 |
+
logger.error(f"❌ Gemini JSON parse error: {e}")
|
| 283 |
+
return self._create_fallback_mindmap(keyword, tavily_data, kg_data)
|
| 284 |
+
except Exception as e:
|
| 285 |
+
logger.error(f"❌ Gemini API error: {e}")
|
| 286 |
+
return self._create_fallback_mindmap(keyword, tavily_data, kg_data)
|
| 287 |
+
|
| 288 |
+
def _create_fallback_mindmap(
|
| 289 |
+
self,
|
| 290 |
+
keyword: str,
|
| 291 |
+
tavily_data: Dict[str, Any],
|
| 292 |
+
kg_data: Dict[str, Any]
|
| 293 |
+
) -> Dict[str, Any]:
|
| 294 |
+
"""
|
| 295 |
+
Create a basic fallback mindmap when Gemini fails
|
| 296 |
+
|
| 297 |
+
Args:
|
| 298 |
+
keyword: Main keyword
|
| 299 |
+
tavily_data: Tavily results
|
| 300 |
+
kg_data: Knowledge Graph results
|
| 301 |
+
|
| 302 |
+
Returns:
|
| 303 |
+
Basic mindmap structure
|
| 304 |
+
"""
|
| 305 |
+
logger.info("Creating fallback mindmap structure")
|
| 306 |
+
|
| 307 |
+
nodes = []
|
| 308 |
+
edges = []
|
| 309 |
+
|
| 310 |
+
# Add primary nodes from key terms
|
| 311 |
+
key_terms = tavily_data.get('key_terms', [])[:6]
|
| 312 |
+
for i, term in enumerate(key_terms):
|
| 313 |
+
node_id = f"primary_{i}"
|
| 314 |
+
nodes.append({
|
| 315 |
+
'id': node_id,
|
| 316 |
+
'label': term,
|
| 317 |
+
'level': 1,
|
| 318 |
+
'description': f"Related concept to {keyword}"
|
| 319 |
+
})
|
| 320 |
+
edges.append({
|
| 321 |
+
'from': 'center',
|
| 322 |
+
'to': node_id,
|
| 323 |
+
'label': 'related_to'
|
| 324 |
+
})
|
| 325 |
+
|
| 326 |
+
# Add secondary nodes from entities
|
| 327 |
+
entities = kg_data.get('entities', [])[:4]
|
| 328 |
+
for i, entity in enumerate(entities):
|
| 329 |
+
node_id = f"secondary_{i}"
|
| 330 |
+
nodes.append({
|
| 331 |
+
'id': node_id,
|
| 332 |
+
'label': entity['name'],
|
| 333 |
+
'level': 2,
|
| 334 |
+
'description': entity['description'][:100]
|
| 335 |
+
})
|
| 336 |
+
# Connect to first primary node if available
|
| 337 |
+
if key_terms:
|
| 338 |
+
edges.append({
|
| 339 |
+
'from': 'primary_0',
|
| 340 |
+
'to': node_id,
|
| 341 |
+
'label': 'includes'
|
| 342 |
+
})
|
| 343 |
+
|
| 344 |
+
return {
|
| 345 |
+
'center': keyword,
|
| 346 |
+
'nodes': nodes,
|
| 347 |
+
'edges': edges
|
| 348 |
+
}
|
| 349 |
+
|
| 350 |
+
async def fetch_all_data(self, keyword: str) -> Dict[str, Any]:
|
| 351 |
+
"""
|
| 352 |
+
Orchestrate all API calls in optimized sequence
|
| 353 |
+
|
| 354 |
+
This is the main entry point that executes the 3-step process:
|
| 355 |
+
1. Tavily → Get web context and related terms
|
| 356 |
+
2. Knowledge Graph → Get structured entities (using Tavily results)
|
| 357 |
+
3. Gemini → Synthesize comprehensive mindmap (using both)
|
| 358 |
+
|
| 359 |
+
Args:
|
| 360 |
+
keyword: Technical keyword to analyze
|
| 361 |
+
|
| 362 |
+
Returns:
|
| 363 |
+
Complete result dictionary with:
|
| 364 |
+
- mindmap: Full mindmap structure
|
| 365 |
+
- metadata: Additional information (sources, counts, etc.)
|
| 366 |
+
"""
|
| 367 |
+
logger.info(f"\n{'='*60}")
|
| 368 |
+
logger.info(f"Starting mindmap generation for: '{keyword}'")
|
| 369 |
+
logger.info(f"{'='*60}")
|
| 370 |
+
|
| 371 |
+
try:
|
| 372 |
+
# Step 1: Fetch Tavily data (context + terms)
|
| 373 |
+
tavily_data = await self.fetch_tavily_data(keyword)
|
| 374 |
+
|
| 375 |
+
# Step 2: Fetch Knowledge Graph data (using Tavily results)
|
| 376 |
+
kg_data = await self.fetch_knowledge_graph_data(keyword)
|
| 377 |
+
|
| 378 |
+
|
| 379 |
+
|
| 380 |
+
# Step 3: Generate mindmap with Gemini (using both results)
|
| 381 |
+
mindmap_data = await self.generate_gemini_mindmap(
|
| 382 |
+
keyword,
|
| 383 |
+
tavily_data,
|
| 384 |
+
kg_data
|
| 385 |
+
)
|
| 386 |
+
|
| 387 |
+
# Compile metadata
|
| 388 |
+
metadata = {
|
| 389 |
+
'keyword': keyword,
|
| 390 |
+
'tavily_sources': tavily_data.get('sources', []),
|
| 391 |
+
'kg_entities_count': len(kg_data.get('entities', [])),
|
| 392 |
+
'total_nodes': len(mindmap_data.get('nodes', [])),
|
| 393 |
+
'total_edges': len(mindmap_data.get('edges', []))
|
| 394 |
+
}
|
| 395 |
+
|
| 396 |
+
logger.info(f"{'='*60}")
|
| 397 |
+
logger.info(f"✅ Mindmap generation complete!")
|
| 398 |
+
logger.info(f" - Nodes: {metadata['total_nodes']}")
|
| 399 |
+
logger.info(f" - Edges: {metadata['total_edges']}")
|
| 400 |
+
logger.info(f" - Sources: {len(metadata['tavily_sources'])}")
|
| 401 |
+
logger.info(f"{'='*60}\n")
|
| 402 |
+
|
| 403 |
+
return {
|
| 404 |
+
'mindmap': mindmap_data,
|
| 405 |
+
'metadata': metadata
|
| 406 |
+
}
|
| 407 |
+
|
| 408 |
+
except Exception as e:
|
| 409 |
+
logger.error(f"❌ Critical error in fetch_all_data: {e}")
|
| 410 |
+
|
| 411 |
+
# Return minimal fallback
|
| 412 |
+
return {
|
| 413 |
+
'mindmap': {
|
| 414 |
+
'center': keyword,
|
| 415 |
+
'nodes': [{
|
| 416 |
+
'id': 'fallback_1',
|
| 417 |
+
'label': 'Error generating mindmap',
|
| 418 |
+
'level': 1,
|
| 419 |
+
'description': 'Please check API configuration'
|
| 420 |
+
}],
|
| 421 |
+
'edges': [{
|
| 422 |
+
'from': 'center',
|
| 423 |
+
'to': 'fallback_1',
|
| 424 |
+
'label': 'error'
|
| 425 |
+
}]
|
| 426 |
+
},
|
| 427 |
+
'metadata': {
|
| 428 |
+
'keyword': keyword,
|
| 429 |
+
'tavily_sources': [],
|
| 430 |
+
'kg_entities_count': 0,
|
| 431 |
+
'total_nodes': 1,
|
| 432 |
+
'total_edges': 1,
|
| 433 |
+
'error': str(e)
|
| 434 |
+
}
|
| 435 |
+
}
|
| 436 |
+
|
| 437 |
+
|
| 438 |
+
# Synchronous wrapper for Streamlit compatibility
|
| 439 |
+
def fetch_mindmap_data(
|
| 440 |
+
keyword: str,
|
| 441 |
+
gemini_key: str,
|
| 442 |
+
tavily_key: str,
|
| 443 |
+
kg_api_key: str
|
| 444 |
+
) -> Dict[str, Any]:
|
| 445 |
+
"""
|
| 446 |
+
Synchronous wrapper for async API calls (Streamlit-compatible)
|
| 447 |
+
"""
|
| 448 |
+
handler = APIHandler(
|
| 449 |
+
gemini_key=gemini_key,
|
| 450 |
+
tavily_key=tavily_key,
|
| 451 |
+
kg_api_key=kg_api_key
|
| 452 |
+
)
|
| 453 |
+
|
| 454 |
+
return asyncio.run(handler.fetch_all_data(keyword))
|
| 455 |
+
|
| 456 |
+
|
| 457 |
+
|
| 458 |
+
if __name__ == "__main__":
|
| 459 |
+
# Test the API handler
|
| 460 |
+
print("API Handler Module - Ready for import")
|
| 461 |
+
print("Use fetch_mindmap_data() to generate mindmaps")
|
src/utils/mindmap_generator.py
ADDED
|
@@ -0,0 +1,305 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
Mindmap Generator using PyVis for interactive radial visualizations
|
| 3 |
+
Creates beautiful, explorable mindmaps with custom styling
|
| 4 |
+
"""
|
| 5 |
+
|
| 6 |
+
from pyvis.network import Network
|
| 7 |
+
import networkx as nx
|
| 8 |
+
from typing import Dict, Any, Optional
|
| 9 |
+
import streamlit.components.v1 as components
|
| 10 |
+
import tempfile
|
| 11 |
+
import os
|
| 12 |
+
|
| 13 |
+
|
| 14 |
+
class MindmapGenerator:
|
| 15 |
+
"""
|
| 16 |
+
Generate interactive radial mindmaps using PyVis
|
| 17 |
+
|
| 18 |
+
Features:
|
| 19 |
+
- Radial layout with ForceAtlas2 physics
|
| 20 |
+
- Color-coded hierarchy levels
|
| 21 |
+
- Interactive zoom, pan, and hover
|
| 22 |
+
- Customizable styling and dimensions
|
| 23 |
+
"""
|
| 24 |
+
|
| 25 |
+
def __init__(
|
| 26 |
+
self,
|
| 27 |
+
height: str = "650px",
|
| 28 |
+
width: str = "100%",
|
| 29 |
+
bgcolor: str = "#1e1e1e",
|
| 30 |
+
font_color: str = "#ffffff"
|
| 31 |
+
):
|
| 32 |
+
"""
|
| 33 |
+
Initialize mindmap generator with display settings
|
| 34 |
+
|
| 35 |
+
Args:
|
| 36 |
+
height: Height of visualization (CSS format)
|
| 37 |
+
width: Width of visualization (CSS format)
|
| 38 |
+
bgcolor: Background color (hex)
|
| 39 |
+
font_color: Font color (hex)
|
| 40 |
+
"""
|
| 41 |
+
self.height = height
|
| 42 |
+
self.width = width
|
| 43 |
+
self.bgcolor = bgcolor
|
| 44 |
+
self.font_color = font_color
|
| 45 |
+
|
| 46 |
+
# Color scheme for node levels
|
| 47 |
+
self.level_colors = {
|
| 48 |
+
0: "#ff6b6b", # Center - red/coral
|
| 49 |
+
1: "#4ecdc4", # Primary - teal
|
| 50 |
+
2: "#95e1d3", # Secondary - light teal
|
| 51 |
+
3: "#f9ca24", # Tertiary - yellow
|
| 52 |
+
4: "#a29bfe" # Quaternary - purple
|
| 53 |
+
}
|
| 54 |
+
|
| 55 |
+
def create_radial_mindmap(self, mindmap_data: Dict[str, Any]) -> Network:
|
| 56 |
+
"""
|
| 57 |
+
Create interactive radial mindmap from structured data
|
| 58 |
+
|
| 59 |
+
Args:
|
| 60 |
+
mindmap_data: Dictionary with center, nodes, and edges
|
| 61 |
+
|
| 62 |
+
Returns:
|
| 63 |
+
Configured PyVis Network object
|
| 64 |
+
"""
|
| 65 |
+
# Initialize PyVis network
|
| 66 |
+
net = Network(
|
| 67 |
+
height=self.height,
|
| 68 |
+
width=self.width,
|
| 69 |
+
bgcolor=self.bgcolor,
|
| 70 |
+
font_color=self.font_color,
|
| 71 |
+
notebook=False,
|
| 72 |
+
directed=False
|
| 73 |
+
)
|
| 74 |
+
|
| 75 |
+
# Configure physics for radial layout
|
| 76 |
+
net.set_options("""
|
| 77 |
+
{
|
| 78 |
+
"physics": {
|
| 79 |
+
"enabled": true,
|
| 80 |
+
"forceAtlas2Based": {
|
| 81 |
+
"gravitationalConstant": -50,
|
| 82 |
+
"centralGravity": 0.005,
|
| 83 |
+
"springLength": 150,
|
| 84 |
+
"springConstant": 0.08,
|
| 85 |
+
"damping": 0.4,
|
| 86 |
+
"avoidOverlap": 0.5
|
| 87 |
+
},
|
| 88 |
+
"maxVelocity": 50,
|
| 89 |
+
"solver": "forceAtlas2Based",
|
| 90 |
+
"timestep": 0.35,
|
| 91 |
+
"stabilization": {
|
| 92 |
+
"enabled": true,
|
| 93 |
+
"iterations": 1000,
|
| 94 |
+
"updateInterval": 25
|
| 95 |
+
}
|
| 96 |
+
},
|
| 97 |
+
"interaction": {
|
| 98 |
+
"hover": true,
|
| 99 |
+
"hoverConnectedEdges": true,
|
| 100 |
+
"tooltipDelay": 100,
|
| 101 |
+
"navigationButtons": true,
|
| 102 |
+
"keyboard": {
|
| 103 |
+
"enabled": true,
|
| 104 |
+
"speed": {"x": 10, "y": 10, "zoom": 0.02}
|
| 105 |
+
},
|
| 106 |
+
"zoomView": true,
|
| 107 |
+
"dragView": true
|
| 108 |
+
},
|
| 109 |
+
"edges": {
|
| 110 |
+
"smooth": {
|
| 111 |
+
"enabled": true,
|
| 112 |
+
"type": "continuous",
|
| 113 |
+
"roundness": 0.5
|
| 114 |
+
}
|
| 115 |
+
}
|
| 116 |
+
}
|
| 117 |
+
""")
|
| 118 |
+
|
| 119 |
+
# Add center node
|
| 120 |
+
center = mindmap_data.get('center', 'Unknown Topic')
|
| 121 |
+
net.add_node(
|
| 122 |
+
'center',
|
| 123 |
+
label=center,
|
| 124 |
+
title=f"<div style='padding:10px'><b style='font-size:16px'>{center}</b><br><i>Central Topic</i></div>",
|
| 125 |
+
size=45,
|
| 126 |
+
color=self.level_colors[0],
|
| 127 |
+
font={'size': 24, 'color': self.font_color, 'face': 'Arial', 'bold': True},
|
| 128 |
+
borderWidth=3,
|
| 129 |
+
borderWidthSelected=5
|
| 130 |
+
)
|
| 131 |
+
|
| 132 |
+
# Add nodes with level-based styling
|
| 133 |
+
for node in mindmap_data.get('nodes', []):
|
| 134 |
+
node_id = node.get('id', '')
|
| 135 |
+
label = node.get('label', node_id)
|
| 136 |
+
level = node.get('level', 1)
|
| 137 |
+
description = node.get('description', '')
|
| 138 |
+
|
| 139 |
+
# Size decreases with level
|
| 140 |
+
size = max(35 - (level * 7), 15)
|
| 141 |
+
|
| 142 |
+
# Font size decreases with level
|
| 143 |
+
font_size = max(18 - (level * 2), 12)
|
| 144 |
+
|
| 145 |
+
# Get color for this level
|
| 146 |
+
color = self.level_colors.get(level, self.level_colors[2])
|
| 147 |
+
|
| 148 |
+
# Create rich tooltip
|
| 149 |
+
tooltip = f"""
|
| 150 |
+
<div style='padding:12px; max-width:300px;'>
|
| 151 |
+
<b style='font-size:14px; color:#4ecdc4;'>{label}</b><br>
|
| 152 |
+
<span style='color:#888;'>Level {level}</span><br>
|
| 153 |
+
<p style='margin-top:8px; font-size:12px;'>{description}</p>
|
| 154 |
+
</div>
|
| 155 |
+
"""
|
| 156 |
+
|
| 157 |
+
net.add_node(
|
| 158 |
+
node_id,
|
| 159 |
+
label=label,
|
| 160 |
+
title=tooltip,
|
| 161 |
+
size=size,
|
| 162 |
+
color=color,
|
| 163 |
+
font={
|
| 164 |
+
'size': font_size,
|
| 165 |
+
'color': self.font_color,
|
| 166 |
+
'face': 'Arial'
|
| 167 |
+
},
|
| 168 |
+
borderWidth=2,
|
| 169 |
+
borderWidthSelected=4,
|
| 170 |
+
shape='dot'
|
| 171 |
+
)
|
| 172 |
+
|
| 173 |
+
# Add edges with relationship labels
|
| 174 |
+
for edge in mindmap_data.get('edges', []):
|
| 175 |
+
from_node = edge.get('from', '')
|
| 176 |
+
to_node = edge.get('to', '')
|
| 177 |
+
label = edge.get('label', '')
|
| 178 |
+
|
| 179 |
+
# Edge styling
|
| 180 |
+
net.add_edge(
|
| 181 |
+
from_node,
|
| 182 |
+
to_node,
|
| 183 |
+
title=label if label else 'related to',
|
| 184 |
+
label=label if len(label) < 15 else '', # Only show short labels
|
| 185 |
+
color={'color': '#666666', 'opacity': 0.6},
|
| 186 |
+
width=2,
|
| 187 |
+
smooth={'type': 'continuous'}
|
| 188 |
+
)
|
| 189 |
+
|
| 190 |
+
return net
|
| 191 |
+
|
| 192 |
+
def generate_html(self, mindmap_data: Dict[str, Any]) -> str:
|
| 193 |
+
"""
|
| 194 |
+
Generate HTML string for the mindmap
|
| 195 |
+
|
| 196 |
+
Args:
|
| 197 |
+
mindmap_data: Mindmap structure
|
| 198 |
+
|
| 199 |
+
Returns:
|
| 200 |
+
Complete HTML as string
|
| 201 |
+
"""
|
| 202 |
+
net = self.create_radial_mindmap(mindmap_data)
|
| 203 |
+
|
| 204 |
+
# Generate HTML
|
| 205 |
+
html_content = net.generate_html()
|
| 206 |
+
|
| 207 |
+
return html_content
|
| 208 |
+
|
| 209 |
+
def save_to_file(self, mindmap_data: Dict[str, Any], filename: str = "mindmap.html"):
|
| 210 |
+
"""
|
| 211 |
+
Save mindmap to HTML file
|
| 212 |
+
|
| 213 |
+
Args:
|
| 214 |
+
mindmap_data: Mindmap structure
|
| 215 |
+
filename: Output filename
|
| 216 |
+
"""
|
| 217 |
+
net = self.create_radial_mindmap(mindmap_data)
|
| 218 |
+
net.save_graph(filename)
|
| 219 |
+
print(f"✅ Mindmap saved to {filename}")
|
| 220 |
+
|
| 221 |
+
def render_in_streamlit(self, mindmap_data: Dict[str, Any]):
|
| 222 |
+
"""
|
| 223 |
+
Render mindmap directly in Streamlit application
|
| 224 |
+
|
| 225 |
+
Args:
|
| 226 |
+
mindmap_data: Mindmap structure
|
| 227 |
+
"""
|
| 228 |
+
html_content = self.generate_html(mindmap_data)
|
| 229 |
+
|
| 230 |
+
# Display using Streamlit components
|
| 231 |
+
components.html(
|
| 232 |
+
html_content,
|
| 233 |
+
height=int(self.height.replace('px', '')),
|
| 234 |
+
scrolling=False
|
| 235 |
+
)
|
| 236 |
+
|
| 237 |
+
|
| 238 |
+
# Convenience functions for direct usage
|
| 239 |
+
def generate_mindmap_html(mindmap_data: Dict[str, Any]) -> str:
|
| 240 |
+
"""
|
| 241 |
+
Quick function to generate mindmap HTML
|
| 242 |
+
|
| 243 |
+
Args:
|
| 244 |
+
mindmap_data: Mindmap structure
|
| 245 |
+
|
| 246 |
+
Returns:
|
| 247 |
+
HTML string
|
| 248 |
+
"""
|
| 249 |
+
generator = MindmapGenerator()
|
| 250 |
+
return generator.generate_html(mindmap_data)
|
| 251 |
+
|
| 252 |
+
|
| 253 |
+
def create_sample_mindmap() -> Dict[str, Any]:
|
| 254 |
+
"""
|
| 255 |
+
Create a sample mindmap for testing
|
| 256 |
+
|
| 257 |
+
Returns:
|
| 258 |
+
Sample mindmap data structure
|
| 259 |
+
"""
|
| 260 |
+
return {
|
| 261 |
+
'center': 'Machine Learning',
|
| 262 |
+
'nodes': [
|
| 263 |
+
{
|
| 264 |
+
'id': 'supervised',
|
| 265 |
+
'label': 'Supervised Learning',
|
| 266 |
+
'level': 1,
|
| 267 |
+
'description': 'Learning with labeled data'
|
| 268 |
+
},
|
| 269 |
+
{
|
| 270 |
+
'id': 'unsupervised',
|
| 271 |
+
'label': 'Unsupervised Learning',
|
| 272 |
+
'level': 1,
|
| 273 |
+
'description': 'Learning from unlabeled data'
|
| 274 |
+
},
|
| 275 |
+
{
|
| 276 |
+
'id': 'classification',
|
| 277 |
+
'label': 'Classification',
|
| 278 |
+
'level': 2,
|
| 279 |
+
'description': 'Categorizing data into classes'
|
| 280 |
+
},
|
| 281 |
+
{
|
| 282 |
+
'id': 'regression',
|
| 283 |
+
'label': 'Regression',
|
| 284 |
+
'level': 2,
|
| 285 |
+
'description': 'Predicting continuous values'
|
| 286 |
+
}
|
| 287 |
+
],
|
| 288 |
+
'edges': [
|
| 289 |
+
{'from': 'center', 'to': 'supervised', 'label': 'includes'},
|
| 290 |
+
{'from': 'center', 'to': 'unsupervised', 'label': 'includes'},
|
| 291 |
+
{'from': 'supervised', 'to': 'classification', 'label': 'type'},
|
| 292 |
+
{'from': 'supervised', 'to': 'regression', 'label': 'type'}
|
| 293 |
+
]
|
| 294 |
+
}
|
| 295 |
+
|
| 296 |
+
|
| 297 |
+
if __name__ == "__main__":
|
| 298 |
+
# Test the generator
|
| 299 |
+
print("Mindmap Generator Module - Ready for import")
|
| 300 |
+
print("Use MindmapGenerator class to create visualizations")
|
| 301 |
+
|
| 302 |
+
# Create sample
|
| 303 |
+
sample = create_sample_mindmap()
|
| 304 |
+
generator = MindmapGenerator()
|
| 305 |
+
generator.save_to_file(sample, "test_mindmap.html")
|