final
This commit is contained in:
parent
7f91e5e6df
commit
d6f59d2d94
107
dashboard_api.py
107
dashboard_api.py
@ -42,6 +42,83 @@ class DashboardAPI:
|
|||||||
video['like_probability'] = 0.5 # Default probability
|
video['like_probability'] = 0.5 # Default probability
|
||||||
return fallback_videos
|
return fallback_videos
|
||||||
|
|
||||||
|
def get_liked_videos(self):
|
||||||
|
"""Get videos that user liked, ordered by AI match confidence"""
|
||||||
|
import sqlite3
|
||||||
|
|
||||||
|
try:
|
||||||
|
conn = sqlite3.connect(self.db_path)
|
||||||
|
conn.row_factory = sqlite3.Row
|
||||||
|
cursor = conn.cursor()
|
||||||
|
|
||||||
|
# Get liked videos with features
|
||||||
|
query = """
|
||||||
|
SELECT v.*, vf.*, p.liked
|
||||||
|
FROM videos v
|
||||||
|
JOIN video_features vf ON v.id = vf.video_id
|
||||||
|
JOIN preferences p ON v.id = p.video_id
|
||||||
|
WHERE p.liked = 1
|
||||||
|
ORDER BY v.view_count DESC
|
||||||
|
"""
|
||||||
|
|
||||||
|
cursor.execute(query)
|
||||||
|
results = cursor.fetchall()
|
||||||
|
conn.close()
|
||||||
|
|
||||||
|
liked_videos = []
|
||||||
|
for row in results:
|
||||||
|
video = {
|
||||||
|
'id': row['id'],
|
||||||
|
'title': row['title'],
|
||||||
|
'channel_name': row['channel_name'],
|
||||||
|
'view_count': row['view_count'],
|
||||||
|
'url': f"https://www.youtube.com/watch?v={row['id']}"
|
||||||
|
}
|
||||||
|
liked_videos.append(video)
|
||||||
|
|
||||||
|
# If model is trained, predict confidence for liked videos
|
||||||
|
if self.model_trained and self.model and liked_videos:
|
||||||
|
# Create pandas DataFrame for prediction
|
||||||
|
import pandas as pd
|
||||||
|
|
||||||
|
df_data = []
|
||||||
|
for row in results:
|
||||||
|
row_data = {
|
||||||
|
'id': row['id'],
|
||||||
|
'title': row['title'],
|
||||||
|
'channel_name': row['channel_name'],
|
||||||
|
'view_count': row['view_count'],
|
||||||
|
'title_length': row['title_length'],
|
||||||
|
'description_length': row['description_length'],
|
||||||
|
'view_like_ratio': row['view_like_ratio'],
|
||||||
|
'engagement_score': row['engagement_score'],
|
||||||
|
'title_sentiment': row['title_sentiment'],
|
||||||
|
'has_tutorial_keywords': row['has_tutorial_keywords'],
|
||||||
|
'has_beginner_keywords': row['has_beginner_keywords'],
|
||||||
|
'has_ai_keywords': row['has_ai_keywords'],
|
||||||
|
'has_challenge_keywords': row['has_challenge_keywords'],
|
||||||
|
'has_time_constraint': row['has_time_constraint']
|
||||||
|
}
|
||||||
|
df_data.append(row_data)
|
||||||
|
|
||||||
|
video_features_df = pd.DataFrame(df_data)
|
||||||
|
|
||||||
|
# Get predictions for confidence scores
|
||||||
|
predictions = predict_video_preferences_with_model(self.model, video_features_df)
|
||||||
|
|
||||||
|
# Sort by confidence and return
|
||||||
|
return sorted(predictions, key=lambda x: x.get('like_probability', 0), reverse=True)
|
||||||
|
|
||||||
|
# If no model, return with default confidence
|
||||||
|
for video in liked_videos:
|
||||||
|
video['like_probability'] = 0.8 # High default for liked videos
|
||||||
|
|
||||||
|
return liked_videos
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
print(f"Error getting liked videos: {e}")
|
||||||
|
return []
|
||||||
|
|
||||||
dashboard_api = DashboardAPI()
|
dashboard_api = DashboardAPI()
|
||||||
|
|
||||||
@app.route('/')
|
@app.route('/')
|
||||||
@ -124,6 +201,36 @@ def rate_video():
|
|||||||
'error': str(e)
|
'error': str(e)
|
||||||
}), 500
|
}), 500
|
||||||
|
|
||||||
|
@app.route('/api/liked')
|
||||||
|
def get_liked_videos():
|
||||||
|
try:
|
||||||
|
liked_videos = dashboard_api.get_liked_videos()
|
||||||
|
|
||||||
|
formatted_videos = []
|
||||||
|
for video in liked_videos:
|
||||||
|
formatted_videos.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.8) * 100),
|
||||||
|
'views_formatted': format_view_count(video['view_count'])
|
||||||
|
})
|
||||||
|
|
||||||
|
return jsonify({
|
||||||
|
'success': True,
|
||||||
|
'videos': formatted_videos,
|
||||||
|
'total_liked': len(formatted_videos)
|
||||||
|
})
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
return jsonify({
|
||||||
|
'success': False,
|
||||||
|
'error': str(e)
|
||||||
|
}), 500
|
||||||
|
|
||||||
def format_view_count(count):
|
def format_view_count(count):
|
||||||
if count >= 1000000:
|
if count >= 1000000:
|
||||||
return f"{count/1000000:.1f}M views"
|
return f"{count/1000000:.1f}M views"
|
||||||
|
|||||||
@ -24,7 +24,6 @@ def search_more_videos():
|
|||||||
|
|
||||||
# Use different/additional search queries to find new videos
|
# Use different/additional search queries to find new videos
|
||||||
additional_queries = [
|
additional_queries = [
|
||||||
"react tutorial 2024 millions views",
|
|
||||||
"python projects for beginners viral",
|
"python projects for beginners viral",
|
||||||
"full stack web development course",
|
"full stack web development course",
|
||||||
"machine learning crash course",
|
"machine learning crash course",
|
||||||
|
|||||||
@ -30,14 +30,26 @@ def search_youtube_videos_by_query(api_key: str, query: str, max_results: int) -
|
|||||||
|
|
||||||
def get_coding_search_queries() -> List[str]:
|
def get_coding_search_queries() -> List[str]:
|
||||||
return [
|
return [
|
||||||
"coding tutorial millions views",
|
# AI focus (shorter terms)
|
||||||
"programming challenge viral",
|
"AI coding tools 2024",
|
||||||
"I built app hours",
|
"ChatGPT API project",
|
||||||
"learn programming beginner",
|
"machine learning build",
|
||||||
"coding project from scratch",
|
"AI developer workflow",
|
||||||
"AI coding tutorial",
|
|
||||||
"web development crash course",
|
# Project-based (concise)
|
||||||
"javascript tutorial millions",
|
"react project build",
|
||||||
"python tutorial viral",
|
"python automation script",
|
||||||
"coding in 24 hours"
|
"full stack app build",
|
||||||
|
"javascript project code",
|
||||||
|
|
||||||
|
# Implementation focus (brief)
|
||||||
|
"coding patterns advanced",
|
||||||
|
"developer setup optimization",
|
||||||
|
"API design examples",
|
||||||
|
"database optimization tips",
|
||||||
|
|
||||||
|
# Recent/specialized (short)
|
||||||
|
"modern dev practices",
|
||||||
|
"new coding tools 2024",
|
||||||
|
"framework comparison"
|
||||||
]
|
]
|
||||||
@ -259,6 +259,39 @@
|
|||||||
cursor: not-allowed;
|
cursor: not-allowed;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.nav-buttons {
|
||||||
|
display: flex;
|
||||||
|
gap: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.nav-btn {
|
||||||
|
padding: 8px 16px;
|
||||||
|
border: 1px solid #333;
|
||||||
|
border-radius: 20px;
|
||||||
|
background-color: #222;
|
||||||
|
color: white;
|
||||||
|
cursor: pointer;
|
||||||
|
font-size: 14px;
|
||||||
|
transition: all 0.2s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.nav-btn:hover {
|
||||||
|
background-color: #333;
|
||||||
|
}
|
||||||
|
|
||||||
|
.nav-btn.active {
|
||||||
|
background-color: #ff0000;
|
||||||
|
border-color: #ff0000;
|
||||||
|
}
|
||||||
|
|
||||||
|
.view-container {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.view-container.active {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
.loading {
|
.loading {
|
||||||
text-align: center;
|
text-align: center;
|
||||||
padding: 60px;
|
padding: 60px;
|
||||||
@ -320,7 +353,14 @@
|
|||||||
<button class="search-button">🔍</button>
|
<button class="search-button">🔍</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div style="width: 200px;"></div>
|
<div class="nav-buttons">
|
||||||
|
<button class="nav-btn active" id="ratingBtn" onclick="switchView('rating')">
|
||||||
|
📝 Rate Videos
|
||||||
|
</button>
|
||||||
|
<button class="nav-btn" id="likedBtn" onclick="switchView('liked')">
|
||||||
|
🎯 MyTube
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
</header>
|
</header>
|
||||||
|
|
||||||
<div class="status-bar" id="statusBar">
|
<div class="status-bar" id="statusBar">
|
||||||
@ -331,13 +371,28 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<main class="main-content">
|
<main class="main-content">
|
||||||
<h1 class="section-title" id="sectionTitle" style="display: none;">
|
<!-- Rating View -->
|
||||||
Recommended for you
|
<div class="view-container active" id="ratingView">
|
||||||
<span class="ai-badge">AI POWERED</span>
|
<h1 class="section-title" id="sectionTitle" style="display: none;">
|
||||||
</h1>
|
Recommended for you
|
||||||
|
<span class="ai-badge">AI POWERED</span>
|
||||||
|
</h1>
|
||||||
|
|
||||||
<div class="video-grid" id="videoGrid">
|
<div class="video-grid" id="videoGrid">
|
||||||
<!-- Videos will be loaded here -->
|
<!-- Videos will be loaded here -->
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Liked Videos View -->
|
||||||
|
<div class="view-container" id="likedView">
|
||||||
|
<h1 class="section-title" id="likedSectionTitle" style="display: none;">
|
||||||
|
MyTube - Videos I Know You'll Love
|
||||||
|
<span class="ai-badge">AI CURATED</span>
|
||||||
|
</h1>
|
||||||
|
|
||||||
|
<div class="video-grid" id="likedVideoGrid">
|
||||||
|
<!-- Liked videos will be loaded here -->
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</main>
|
</main>
|
||||||
|
|
||||||
@ -522,11 +577,99 @@
|
|||||||
}, 3000);
|
}, 3000);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function loadLikedVideos() {
|
||||||
|
try {
|
||||||
|
const response = await fetch('/api/liked');
|
||||||
|
const data = await response.json();
|
||||||
|
|
||||||
|
if (!data.success) {
|
||||||
|
throw new Error(data.error || 'Failed to load liked videos');
|
||||||
|
}
|
||||||
|
|
||||||
|
displayLikedVideos(data.videos);
|
||||||
|
updateLikedStatusBar(data.total_liked);
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error loading liked videos:', error);
|
||||||
|
document.getElementById('likedVideoGrid').innerHTML =
|
||||||
|
`<div class="error">Failed to load liked videos: ${error.message}</div>`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function displayLikedVideos(videos) {
|
||||||
|
const likedVideoGrid = document.getElementById('likedVideoGrid');
|
||||||
|
const likedSectionTitle = document.getElementById('likedSectionTitle');
|
||||||
|
|
||||||
|
if (videos.length === 0) {
|
||||||
|
likedVideoGrid.innerHTML = '<div class="loading">No videos curated yet. Rate some videos and I\'ll learn what you love!</div>';
|
||||||
|
likedSectionTitle.style.display = 'none';
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
likedSectionTitle.style.display = 'flex';
|
||||||
|
|
||||||
|
likedVideoGrid.innerHTML = videos.map(video => `
|
||||||
|
<div class="video-card">
|
||||||
|
<div class="video-thumbnail" onclick="openVideo('${video.url}')">
|
||||||
|
<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 confidence-high">
|
||||||
|
${video.confidence}% match
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="video-info">
|
||||||
|
<div class="video-title" onclick="openVideo('${video.url}')" style="cursor: pointer;">${video.title}</div>
|
||||||
|
<div class="video-meta">
|
||||||
|
<span>${video.channel_name}</span>
|
||||||
|
<span>•</span>
|
||||||
|
<span>${video.views_formatted}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
`).join('');
|
||||||
|
}
|
||||||
|
|
||||||
|
function updateLikedStatusBar(totalLiked) {
|
||||||
|
// Update status bar when viewing MyTube videos
|
||||||
|
const statusBar = document.getElementById('statusBar');
|
||||||
|
statusBar.innerHTML = `
|
||||||
|
<div class="status-trained">
|
||||||
|
🎯 MyTube AI Curation • ${totalLiked} videos I know you'll love • Ranked by confidence
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
|
||||||
|
let currentView = 'rating';
|
||||||
|
|
||||||
|
function switchView(view) {
|
||||||
|
currentView = view;
|
||||||
|
|
||||||
|
// Update navigation buttons
|
||||||
|
document.getElementById('ratingBtn').classList.toggle('active', view === 'rating');
|
||||||
|
document.getElementById('likedBtn').classList.toggle('active', view === 'liked');
|
||||||
|
|
||||||
|
// Update view containers
|
||||||
|
document.getElementById('ratingView').classList.toggle('active', view === 'rating');
|
||||||
|
document.getElementById('likedView').classList.toggle('active', view === 'liked');
|
||||||
|
|
||||||
|
// Load appropriate data
|
||||||
|
if (view === 'rating') {
|
||||||
|
loadRecommendations();
|
||||||
|
} else if (view === 'liked') {
|
||||||
|
loadLikedVideos();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Load recommendations when page loads
|
// Load recommendations when page loads
|
||||||
document.addEventListener('DOMContentLoaded', loadRecommendations);
|
document.addEventListener('DOMContentLoaded', loadRecommendations);
|
||||||
|
|
||||||
// Refresh every 5 minutes
|
// Refresh current view every 5 minutes
|
||||||
setInterval(loadRecommendations, 300000);
|
setInterval(() => {
|
||||||
|
if (currentView === 'rating') {
|
||||||
|
loadRecommendations();
|
||||||
|
} else if (currentView === 'liked') {
|
||||||
|
loadLikedVideos();
|
||||||
|
}
|
||||||
|
}, 300000);
|
||||||
</script>
|
</script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
Loading…
x
Reference in New Issue
Block a user