final
This commit is contained in:
parent
7f91e5e6df
commit
d6f59d2d94
107
dashboard_api.py
107
dashboard_api.py
@ -41,6 +41,83 @@ class DashboardAPI:
|
||||
for video in fallback_videos:
|
||||
video['like_probability'] = 0.5 # Default probability
|
||||
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()
|
||||
|
||||
@ -124,6 +201,36 @@ def rate_video():
|
||||
'error': str(e)
|
||||
}), 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):
|
||||
if count >= 1000000:
|
||||
return f"{count/1000000:.1f}M views"
|
||||
|
||||
@ -16,16 +16,15 @@ def search_more_videos():
|
||||
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",
|
||||
"python projects for beginners viral",
|
||||
"full stack web development course",
|
||||
"machine learning crash course",
|
||||
"javascript frameworks comparison",
|
||||
@ -35,24 +34,24 @@ def search_more_videos():
|
||||
"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.")
|
||||
|
||||
@ -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]:
|
||||
return [
|
||||
"coding tutorial millions views",
|
||||
"programming challenge viral",
|
||||
"I built app hours",
|
||||
"learn programming beginner",
|
||||
"coding project from scratch",
|
||||
"AI coding tutorial",
|
||||
"web development crash course",
|
||||
"javascript tutorial millions",
|
||||
"python tutorial viral",
|
||||
"coding in 24 hours"
|
||||
# AI focus (shorter terms)
|
||||
"AI coding tools 2024",
|
||||
"ChatGPT API project",
|
||||
"machine learning build",
|
||||
"AI developer workflow",
|
||||
|
||||
# Project-based (concise)
|
||||
"react project build",
|
||||
"python automation script",
|
||||
"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;
|
||||
}
|
||||
|
||||
.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 {
|
||||
text-align: center;
|
||||
padding: 60px;
|
||||
@ -320,7 +353,14 @@
|
||||
<button class="search-button">🔍</button>
|
||||
</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>
|
||||
|
||||
<div class="status-bar" id="statusBar">
|
||||
@ -331,13 +371,28 @@
|
||||
</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 -->
|
||||
<!-- Rating View -->
|
||||
<div class="view-container active" id="ratingView">
|
||||
<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>
|
||||
</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>
|
||||
</main>
|
||||
|
||||
@ -522,11 +577,99 @@
|
||||
}, 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
|
||||
document.addEventListener('DOMContentLoaded', loadRecommendations);
|
||||
|
||||
// Refresh every 5 minutes
|
||||
setInterval(loadRecommendations, 300000);
|
||||
// Refresh current view every 5 minutes
|
||||
setInterval(() => {
|
||||
if (currentView === 'rating') {
|
||||
loadRecommendations();
|
||||
} else if (currentView === 'liked') {
|
||||
loadLikedVideos();
|
||||
}
|
||||
}, 300000);
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
Loading…
x
Reference in New Issue
Block a user