able to rate on dashboard

This commit is contained in:
Gauri Joshi 2025-08-24 15:07:41 +02:00
parent 46409b62e3
commit 7f91e5e6df
2 changed files with 200 additions and 5 deletions

View File

@ -1,10 +1,10 @@
import os
from flask import Flask, jsonify, render_template
from flask import Flask, jsonify, render_template, request
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.preference_operations import get_training_data_from_database, get_unrated_videos_with_features_from_database, get_rated_count_from_database, save_video_rating_to_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
@ -79,6 +79,51 @@ def get_recommendations():
'error': str(e)
}), 500
@app.route('/api/rate', methods=['POST'])
def rate_video():
try:
data = request.json
video_id = data.get('video_id')
liked = data.get('liked')
if not video_id or liked is None:
return jsonify({
'success': False,
'error': 'Missing video_id or liked parameter'
}), 400
# Save the rating
save_video_rating_to_database(video_id, liked, "", dashboard_api.db_path)
# Check if we should retrain the model
model_retrained = False
rated_count = get_rated_count_from_database(dashboard_api.db_path)
if rated_count >= 10: # Minimum ratings needed for training
# Retrain the model with new data
if not dashboard_api.model:
dashboard_api.model = create_recommendation_model()
training_data = get_training_data_from_database(dashboard_api.db_path)
success = train_model_on_user_preferences(dashboard_api.model, training_data)
if success:
dashboard_api.model_trained = True
model_retrained = True
return jsonify({
'success': True,
'message': 'Rating saved successfully',
'model_retrained': model_retrained,
'total_ratings': rated_count
})
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"

View File

@ -209,6 +209,56 @@
font-size: 14px;
}
.rating-buttons {
display: flex;
gap: 8px;
margin-top: 8px;
justify-content: center;
}
.rating-btn {
padding: 6px 12px;
border: 1px solid #333;
border-radius: 20px;
background-color: #222;
color: white;
cursor: pointer;
font-size: 14px;
display: flex;
align-items: center;
gap: 4px;
transition: all 0.2s ease;
}
.rating-btn:hover {
transform: scale(1.05);
}
.rating-btn.like-btn:hover {
background-color: #00d400;
border-color: #00d400;
}
.rating-btn.dislike-btn:hover {
background-color: #ff4444;
border-color: #ff4444;
}
.rating-btn.liked {
background-color: #00d400;
border-color: #00d400;
}
.rating-btn.disliked {
background-color: #ff4444;
border-color: #ff4444;
}
.rating-btn:disabled {
opacity: 0.6;
cursor: not-allowed;
}
.loading {
text-align: center;
padding: 60px;
@ -343,20 +393,28 @@
}
videoGrid.innerHTML = videos.map(video => `
<div class="video-card" onclick="openVideo('${video.url}')">
<div class="video-thumbnail">
<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 ${getConfidenceClass(video.confidence)}">
${video.confidence}% match
</div>
</div>
<div class="video-info">
<div class="video-title">${video.title}</div>
<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 class="rating-buttons">
<button class="rating-btn like-btn" onclick="rateVideo('${video.id}', true)" id="like-${video.id}">
👍 Like
</button>
<button class="rating-btn dislike-btn" onclick="rateVideo('${video.id}', false)" id="dislike-${video.id}">
👎 Dislike
</button>
</div>
</div>
</div>
`).join('');
@ -372,6 +430,98 @@
window.open(url, '_blank');
}
async function rateVideo(videoId, liked) {
const likeBtn = document.getElementById(`like-${videoId}`);
const dislikeBtn = document.getElementById(`dislike-${videoId}`);
// Disable buttons during request
likeBtn.disabled = true;
dislikeBtn.disabled = true;
try {
const response = await fetch('/api/rate', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
video_id: videoId,
liked: liked
})
});
const result = await response.json();
if (result.success) {
// Update button states
likeBtn.classList.remove('liked');
dislikeBtn.classList.remove('disliked');
if (liked) {
likeBtn.classList.add('liked');
likeBtn.textContent = '👍 Liked';
} else {
dislikeBtn.classList.add('disliked');
dislikeBtn.textContent = '👎 Disliked';
}
// Show feedback
showNotification(liked ? 'Video liked! 👍' : 'Video disliked! 👎');
// Refresh recommendations after rating if model was retrained
if (result.model_retrained) {
setTimeout(() => {
showNotification('🤖 AI model updated with your feedback!');
loadRecommendations();
}, 1500);
}
} else {
throw new Error(result.error || 'Failed to rate video');
}
} catch (error) {
console.error('Error rating video:', error);
showNotification('Failed to rate video: ' + error.message);
}
// Re-enable buttons
likeBtn.disabled = false;
dislikeBtn.disabled = false;
}
function showNotification(message) {
// Create notification element
const notification = document.createElement('div');
notification.style.cssText = `
position: fixed;
top: 20px;
right: 20px;
background-color: #333;
color: white;
padding: 12px 16px;
border-radius: 8px;
box-shadow: 0 4px 12px rgba(0,0,0,0.4);
z-index: 1000;
transition: all 0.3s ease;
transform: translateX(100%);
`;
notification.textContent = message;
document.body.appendChild(notification);
// Animate in
setTimeout(() => {
notification.style.transform = 'translateX(0)';
}, 100);
// Remove after 3 seconds
setTimeout(() => {
notification.style.transform = 'translateX(100%)';
setTimeout(() => {
document.body.removeChild(notification);
}, 300);
}, 3000);
}
// Load recommendations when page loads
document.addEventListener('DOMContentLoaded', loadRecommendations);