ability to have dashboard
This commit is contained in:
parent
5eb977dee6
commit
46409b62e3
92
README.md
Normal file
92
README.md
Normal 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
91
dashboard_api.py
Normal 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
64
run_dashboard.py
Executable 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
61
search_more_videos.py
Normal 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()
|
||||
91
setup.sh
91
setup.sh
@ -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
|
||||
@ -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
382
templates/dashboard.html
Normal 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>
|
||||
Loading…
x
Reference in New Issue
Block a user