daemon03 commited on
Commit
240e5bc
·
1 Parent(s): 043433b

inital commit

Browse files
.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
+ ![Version](https://img.shields.io/badge/version-1.0.0-blue.svg)
6
+ ![Python](https://img.shields.io/badge/python-3.9+-green.svg)
7
+ ![License](https://img.shields.io/badge/license-MIT-orange.svg)
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
- import altair as alt
2
- import numpy as np
3
- import pandas as pd
4
- import streamlit as st
5
 
 
 
 
 
 
 
6
  """
7
- # Welcome to Streamlit!
8
 
9
- Edit `/streamlit_app.py` to customize this app to your heart's desire :heart:.
10
- If you have any questions, checkout our [documentation](https://docs.streamlit.io) and [community
11
- forums](https://discuss.streamlit.io).
 
12
 
13
- In the meantime, below is an example of what you can do with just a few lines of code:
14
- """
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
15
 
16
- num_points = st.slider("Number of points in spiral", 1, 10000, 1100)
17
- num_turns = st.slider("Number of turns in spiral", 1, 300, 31)
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")