ability to have dashboard

This commit is contained in:
Gauri Joshi 2025-08-23 23:56:19 +02:00
parent 5eb977dee6
commit 46409b62e3
7 changed files with 780 additions and 5 deletions

92
README.md Normal file
View File

@ -0,0 +1,92 @@
# Video Inspiration Finder 🎯
AI-powered YouTube video recommendation system that learns your preferences to suggest coding videos you'll love.
## Features
- 🔍 **Smart Search**: Finds viral coding videos from YouTube
- 🤖 **Machine Learning**: RandomForest model learns your preferences
- 📊 **Web Dashboard**: YouTube-like interface showing personalized recommendations
- 🔒 **Privacy First**: All data stored locally in SQLite
- ⚡ **Fast Setup**: One command installation and execution
## Quick Start
### Option 1: Command Line Interface
```bash
./setup.sh
```
This will:
1. Create virtual environment
2. Install dependencies
3. Search for videos
4. Start interactive rating session
### Option 2: Web Dashboard
```bash
./setup.sh # First time setup
python run_dashboard.py
```
Opens a YouTube-like dashboard at `http://localhost:5000`
## How It Works
1. **Search Phase**: Queries YouTube API for trending coding videos
2. **Rating Phase**: You rate videos as like/dislike with optional notes
3. **Learning Phase**: After 10+ ratings, ML model activates
4. **Recommendation Phase**: AI suggests videos based on your preferences
## Project Structure
```
src/
├── database/ # SQLite operations
├── youtube/ # YouTube API integration
├── ml/ # Machine learning pipeline
└── rating/ # Interactive rating system
main.py # CLI application
dashboard_api.py # Web API server
templates/ # Dashboard HTML/CSS/JS
```
## ML Pipeline
- **Features**: 11 extracted features (keywords, engagement, sentiment)
- **Model**: RandomForest with 100 trees
- **Training**: Incremental learning after each rating
- **Prediction**: Confidence scores for video recommendations
## Requirements
- Python 3.7+
- YouTube Data API v3 key
- SQLite (included with Python)
## Setup
1. Get YouTube API key from [Google Cloud Console](https://console.cloud.google.com/)
2. Create `.env` file:
```
YOUTUBE_API_KEY=your_api_key_here
```
3. Run `./setup.sh`
## Dashboard Features
- 📱 Responsive YouTube-like design
- 🎯 AI confidence scores for each recommendation
- 🔄 Real-time model status (learning vs trained)
- 🖱️ Click videos to open in YouTube
- 📊 Visual feedback on model training progress
## Commands
```bash
./setup.sh # Full setup and CLI
python main.py # CLI only
python run_dashboard.py # Web dashboard
python dashboard_api.py # API server only
```
Built with Python, Flask, scikit-learn, and YouTube Data API v3.

91
dashboard_api.py Normal file
View File

@ -0,0 +1,91 @@
import os
from flask import Flask, jsonify, render_template
from flask_cors import CORS
from dotenv import load_dotenv
from src.database.manager import setup_database_tables
from src.database.preference_operations import get_training_data_from_database, get_unrated_videos_with_features_from_database, get_rated_count_from_database
from src.database.video_operations import get_unrated_videos_from_database
from src.ml.model_training import create_recommendation_model, train_model_on_user_preferences
from src.ml.predictions import predict_video_preferences_with_model
load_dotenv()
app = Flask(__name__)
CORS(app)
class DashboardAPI:
def __init__(self):
self.db_path = "video_inspiration.db"
self.model = None
self.model_trained = False
setup_database_tables(self.db_path)
self._initialize_model()
def _initialize_model(self):
rated_count = get_rated_count_from_database(self.db_path)
if rated_count >= 10:
self.model = create_recommendation_model()
training_data = get_training_data_from_database(self.db_path)
success = train_model_on_user_preferences(self.model, training_data)
if success:
self.model_trained = True
def get_recommendations(self):
if self.model_trained and self.model:
video_features = get_unrated_videos_with_features_from_database(self.db_path)
recommendations = predict_video_preferences_with_model(self.model, video_features)
return recommendations[:12] # Return 12 videos for dashboard
else:
fallback_videos = get_unrated_videos_from_database(12, self.db_path)
for video in fallback_videos:
video['like_probability'] = 0.5 # Default probability
return fallback_videos
dashboard_api = DashboardAPI()
@app.route('/')
def dashboard():
return render_template('dashboard.html')
@app.route('/api/recommendations')
def get_recommendations():
try:
recommendations = dashboard_api.get_recommendations()
formatted_recommendations = []
for video in recommendations:
formatted_recommendations.append({
'id': video['id'],
'title': video['title'],
'channel_name': video['channel_name'],
'view_count': video['view_count'],
'url': video['url'],
'thumbnail': f"https://img.youtube.com/vi/{video['id']}/hqdefault.jpg",
'confidence': round(video.get('like_probability', 0.5) * 100),
'views_formatted': format_view_count(video['view_count'])
})
return jsonify({
'success': True,
'videos': formatted_recommendations,
'model_trained': dashboard_api.model_trained,
'total_ratings': get_rated_count_from_database(dashboard_api.db_path)
})
except Exception as e:
return jsonify({
'success': False,
'error': str(e)
}), 500
def format_view_count(count):
if count >= 1000000:
return f"{count/1000000:.1f}M views"
elif count >= 1000:
return f"{count/1000:.1f}K views"
else:
return f"{count} views"
if __name__ == '__main__':
app.run(debug=True, port=5001)

64
run_dashboard.py Executable file
View File

@ -0,0 +1,64 @@
#!/usr/bin/env python3
import os
import subprocess
import sys
import time
from pathlib import Path
def check_database_exists():
return Path("video_inspiration.db").exists()
def check_has_videos():
if not check_database_exists():
return False
import sqlite3
try:
conn = sqlite3.connect("video_inspiration.db")
cursor = conn.cursor()
cursor.execute("SELECT COUNT(*) FROM videos")
count = cursor.fetchone()[0]
conn.close()
return count > 0
except:
return False
def main():
print("🚀 Video Inspiration Dashboard")
print("=" * 40)
# Check if database and videos exist
if not check_has_videos():
print("⚠️ No videos found in database!")
print("\nOptions:")
print("1. Run main application first to search and rate videos")
print("2. Continue with empty dashboard (demo mode)")
choice = input("\nEnter choice (1/2): ").strip()
if choice == "1":
print("\n🔍 Running main application first...")
try:
subprocess.run([sys.executable, "main.py"], check=True)
except KeyboardInterrupt:
print("\n👋 Switching to dashboard...")
time.sleep(1)
except Exception as e:
print(f"Error running main app: {e}")
return
# Start dashboard
print("\n🌐 Starting dashboard server...")
print("📱 Dashboard will be available at: http://localhost:5000")
print("🛑 Press Ctrl+C to stop the server")
print("-" * 40)
try:
subprocess.run([sys.executable, "dashboard_api.py"], check=True)
except KeyboardInterrupt:
print("\n👋 Dashboard stopped!")
except Exception as e:
print(f"Error starting dashboard: {e}")
if __name__ == "__main__":
main()

61
search_more_videos.py Normal file
View File

@ -0,0 +1,61 @@
#!/usr/bin/env python3
import os
from dotenv import load_dotenv
from src.database.manager import setup_database_tables
from src.database.video_operations import save_videos_to_database, save_video_features_to_database
from src.youtube.search import search_youtube_videos_by_query, get_coding_search_queries
from src.youtube.details import get_video_details_from_youtube
from src.youtube.utils import remove_duplicate_videos
from src.ml.feature_extraction import extract_all_features_from_video
load_dotenv()
def search_more_videos():
api_key = os.getenv('YOUTUBE_API_KEY')
if not api_key:
print("Error: YOUTUBE_API_KEY not found in environment variables")
return
db_path = "video_inspiration.db"
setup_database_tables(db_path)
print("🔍 Searching for more coding videos...")
# Use different/additional search queries to find new videos
additional_queries = [
"react tutorial 2024 millions views",
"python projects for beginners viral",
"full stack web development course",
"machine learning crash course",
"javascript frameworks comparison",
"coding interview preparation",
"docker tutorial for developers",
"git and github tutorial",
"database design tutorial",
"API development tutorial"
]
all_videos = []
for query in additional_queries:
print(f" Searching: {query}")
video_ids = search_youtube_videos_by_query(api_key, query, 10)
videos = get_video_details_from_youtube(api_key, video_ids)
all_videos.extend(videos)
unique_videos = remove_duplicate_videos(all_videos)
if unique_videos:
save_videos_to_database(unique_videos, db_path)
for video in unique_videos:
features = extract_all_features_from_video(video)
save_video_features_to_database(video['id'], features, db_path)
print(f"✅ Found and saved {len(unique_videos)} new videos!")
else:
print("❌ No new videos found.")
if __name__ == "__main__":
search_more_videos()

View File

@ -14,10 +14,93 @@ source venv/bin/activate
# Install dependencies
echo "📚 Installing dependencies..."
pip install requests pandas scikit-learn numpy python-dotenv
pip install requests pandas scikit-learn numpy python-dotenv flask flask-cors
echo "✅ Setup complete!"
echo "🚀 Running Video Inspiration Finder..."
# Run the main script
python main.py
# Function to check if database has videos
check_videos() {
if [ -f "video_inspiration.db" ]; then
video_count=$(sqlite3 video_inspiration.db "SELECT COUNT(*) FROM videos;" 2>/dev/null || echo "0")
echo $video_count
else
echo "0"
fi
}
# Function to check unrated videos count
check_unrated_videos() {
if [ -f "video_inspiration.db" ]; then
unrated_count=$(sqlite3 video_inspiration.db "SELECT COUNT(*) FROM videos v LEFT JOIN preferences p ON v.id = p.video_id WHERE p.video_id IS NULL;" 2>/dev/null || echo "0")
echo $unrated_count
else
echo "0"
fi
}
# Check current state
video_count=$(check_videos)
unrated_count=$(check_unrated_videos)
echo ""
echo "📊 Current Status:"
echo " Total videos: $video_count"
echo " Unrated videos: $unrated_count"
echo ""
# Main menu
echo "Choose what you want to do:"
echo "1. 🌐 Launch Dashboard (recommended)"
echo "2. 📱 Interactive CLI Rating Session"
echo "3. 🔍 Search for More Videos"
echo "4. 🛠️ Full Setup (Search + Rate + Dashboard)"
echo ""
read -p "Enter choice (1-4): " choice
case $choice in
1)
echo ""
echo "🌐 Launching Dashboard..."
if [ "$unrated_count" -eq "0" ] && [ "$video_count" -gt "0" ]; then
echo "⚠️ All videos are rated. Searching for more videos first..."
python search_more_videos.py
elif [ "$video_count" -eq "0" ]; then
echo "⚠️ No videos found. Searching for videos first..."
python main.py --search-only 2>/dev/null || python search_more_videos.py
fi
echo ""
echo "📱 Dashboard will be available at: http://localhost:5001"
echo "🛑 Press Ctrl+C to stop the server"
echo "----------------------------------------"
python dashboard_api.py
;;
2)
echo ""
echo "📱 Starting Interactive Rating Session..."
python main.py
;;
3)
echo ""
echo "🔍 Searching for more videos..."
python search_more_videos.py
echo ""
echo "✅ Search complete! You can now:"
echo " • Run './setup.sh' again and choose option 1 for Dashboard"
echo " • Run 'python dashboard_api.py' directly"
;;
4)
echo ""
echo "🛠️ Running Full Setup..."
echo "🔍 Step 1: Searching for videos..."
python main.py --search-only 2>/dev/null || python search_more_videos.py
echo ""
echo "📱 Step 2: Starting rating session..."
echo "💡 Tip: Rate at least 10 videos to activate AI recommendations"
echo " (You can press 'q' anytime to skip to dashboard)"
python main.py
;;
*)
echo "❌ Invalid choice. Please run './setup.sh' again."
exit 1
;;
esac

View File

@ -16,5 +16,7 @@ def display_session_type_message(is_ml_ready: bool, rated_count: int) -> str:
if is_ml_ready:
return "📊 ML Recommendations based on your preferences:"
else:
remaining_needed = 10 - rated_count
remaining_needed = max(0, 10 - rated_count)
if remaining_needed == 0:
return "🎓 Ready to train ML model!"
return f"📹 Unrated videos (need {remaining_needed} more to train ML):"

382
templates/dashboard.html Normal file
View File

@ -0,0 +1,382 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Video Inspiration Dashboard</title>
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: 'Roboto', sans-serif;
background-color: #0f0f0f;
color: #ffffff;
overflow-x: hidden;
}
.header {
background-color: #212121;
padding: 12px 16px;
display: flex;
align-items: center;
justify-content: space-between;
border-bottom: 1px solid #333;
position: sticky;
top: 0;
z-index: 100;
}
.logo-section {
display: flex;
align-items: center;
gap: 24px;
}
.logo {
display: flex;
align-items: center;
gap: 8px;
text-decoration: none;
color: white;
}
.logo-icon {
width: 32px;
height: 32px;
background: linear-gradient(45deg, #ff0000, #cc0000);
border-radius: 6px;
display: flex;
align-items: center;
justify-content: center;
font-weight: bold;
font-size: 18px;
}
.logo-text {
font-size: 20px;
font-weight: 500;
}
.search-container {
flex-grow: 1;
max-width: 640px;
margin: 0 40px;
display: flex;
}
.search-box {
flex-grow: 1;
padding: 12px 16px;
border: 1px solid #333;
border-radius: 40px 0 0 40px;
background-color: #121212;
color: white;
font-size: 16px;
outline: none;
}
.search-box:focus {
border-color: #3ea6ff;
}
.search-button {
padding: 12px 20px;
border: 1px solid #333;
border-left: none;
border-radius: 0 40px 40px 0;
background-color: #222;
cursor: pointer;
color: white;
}
.status-bar {
background-color: #1a1a1a;
padding: 16px;
text-align: center;
border-bottom: 1px solid #333;
}
.status-trained {
color: #00d400;
}
.status-learning {
color: #ffaa00;
}
.main-content {
padding: 24px;
}
.section-title {
font-size: 20px;
font-weight: 500;
margin-bottom: 16px;
display: flex;
align-items: center;
gap: 12px;
}
.ai-badge {
background: linear-gradient(45deg, #00d4aa, #007acc);
padding: 4px 8px;
border-radius: 12px;
font-size: 12px;
font-weight: 600;
}
.video-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(320px, 1fr));
gap: 24px;
margin-top: 24px;
}
.video-card {
background-color: transparent;
border-radius: 12px;
overflow: hidden;
cursor: pointer;
transition: transform 0.2s ease;
}
.video-card:hover {
transform: scale(1.02);
}
.video-thumbnail {
position: relative;
width: 100%;
height: 180px;
background-color: #333;
border-radius: 12px;
overflow: hidden;
}
.video-thumbnail img {
width: 100%;
height: 100%;
object-fit: cover;
}
.confidence-badge {
position: absolute;
top: 8px;
right: 8px;
background: rgba(0, 0, 0, 0.8);
padding: 4px 8px;
border-radius: 12px;
font-size: 12px;
font-weight: 600;
}
.confidence-high {
color: #00d400;
}
.confidence-medium {
color: #ffaa00;
}
.confidence-low {
color: #ff6b6b;
}
.video-info {
padding: 12px 0;
}
.video-title {
font-size: 16px;
font-weight: 500;
line-height: 1.3;
margin-bottom: 4px;
display: -webkit-box;
-webkit-line-clamp: 2;
-webkit-box-orient: vertical;
overflow: hidden;
}
.video-meta {
display: flex;
align-items: center;
gap: 8px;
color: #aaa;
font-size: 14px;
}
.loading {
text-align: center;
padding: 60px;
color: #aaa;
}
.loading-spinner {
width: 40px;
height: 40px;
border: 4px solid #333;
border-top: 4px solid #ff0000;
border-radius: 50%;
animation: spin 1s linear infinite;
margin: 0 auto 16px;
}
@keyframes spin {
0% { transform: rotate(0deg); }
100% { transform: rotate(360deg); }
}
.error {
background-color: #ff4444;
color: white;
padding: 16px;
border-radius: 8px;
margin: 20px;
text-align: center;
}
@media (max-width: 768px) {
.search-container {
margin: 0 16px;
max-width: none;
}
.video-grid {
grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
gap: 16px;
}
.main-content {
padding: 16px;
}
}
</style>
</head>
<body>
<header class="header">
<div class="logo-section">
<a href="/" class="logo">
<div class="logo-icon">VI</div>
<span class="logo-text">Video Inspiration</span>
</a>
</div>
<div class="search-container">
<input type="text" class="search-box" placeholder="Search coding videos..." disabled>
<button class="search-button">🔍</button>
</div>
<div style="width: 200px;"></div>
</header>
<div class="status-bar" id="statusBar">
<div class="loading">
<div class="loading-spinner"></div>
Loading your personalized recommendations...
</div>
</div>
<main class="main-content">
<h1 class="section-title" id="sectionTitle" style="display: none;">
Recommended for you
<span class="ai-badge">AI POWERED</span>
</h1>
<div class="video-grid" id="videoGrid">
<!-- Videos will be loaded here -->
</div>
</main>
<script>
async function loadRecommendations() {
try {
const response = await fetch('/api/recommendations');
const data = await response.json();
if (!data.success) {
throw new Error(data.error || 'Failed to load recommendations');
}
updateStatusBar(data.model_trained, data.total_ratings);
displayVideos(data.videos);
} catch (error) {
console.error('Error loading recommendations:', error);
document.getElementById('videoGrid').innerHTML =
`<div class="error">Failed to load recommendations: ${error.message}</div>`;
}
}
function updateStatusBar(modelTrained, totalRatings) {
const statusBar = document.getElementById('statusBar');
const sectionTitle = document.getElementById('sectionTitle');
if (modelTrained) {
statusBar.innerHTML = `
<div class="status-trained">
🤖 AI Model Active • Trained on ${totalRatings} video ratings • Showing personalized recommendations
</div>
`;
sectionTitle.innerHTML = 'AI Recommendations for You <span class="ai-badge">PERSONALIZED</span>';
} else {
statusBar.innerHTML = `
<div class="status-learning">
📚 Learning Mode • Need ${10 - totalRatings} more ratings to activate AI recommendations
</div>
`;
sectionTitle.innerHTML = 'Popular Coding Videos <span class="ai-badge">TRENDING</span>';
}
sectionTitle.style.display = 'flex';
}
function displayVideos(videos) {
const videoGrid = document.getElementById('videoGrid');
if (videos.length === 0) {
videoGrid.innerHTML = '<div class="loading">No videos available. Please run the main application to search for videos.</div>';
return;
}
videoGrid.innerHTML = videos.map(video => `
<div class="video-card" onclick="openVideo('${video.url}')">
<div class="video-thumbnail">
<img src="${video.thumbnail}" alt="${video.title}" onerror="this.src='data:image/svg+xml,<svg xmlns=%22http://www.w3.org/2000/svg%22 width=%22320%22 height=%22180%22><rect width=%22100%%22 height=%22100%%22 fill=%22%23333%22/><text x=%2250%%22 y=%2250%%22 text-anchor=%22middle%22 dy=%22.3em%22 fill=%22%23999%22>No Image</text></svg>'">
<div class="confidence-badge ${getConfidenceClass(video.confidence)}">
${video.confidence}% match
</div>
</div>
<div class="video-info">
<div class="video-title">${video.title}</div>
<div class="video-meta">
<span>${video.channel_name}</span>
<span></span>
<span>${video.views_formatted}</span>
</div>
</div>
</div>
`).join('');
}
function getConfidenceClass(confidence) {
if (confidence >= 70) return 'confidence-high';
if (confidence >= 50) return 'confidence-medium';
return 'confidence-low';
}
function openVideo(url) {
window.open(url, '_blank');
}
// Load recommendations when page loads
document.addEventListener('DOMContentLoaded', loadRecommendations);
// Refresh every 5 minutes
setInterval(loadRecommendations, 300000);
</script>
</body>
</html>