able to rate on dashboard
This commit is contained in:
parent
46409b62e3
commit
7f91e5e6df
@ -1,10 +1,10 @@
|
|||||||
import os
|
import os
|
||||||
from flask import Flask, jsonify, render_template
|
from flask import Flask, jsonify, render_template, request
|
||||||
from flask_cors import CORS
|
from flask_cors import CORS
|
||||||
from dotenv import load_dotenv
|
from dotenv import load_dotenv
|
||||||
|
|
||||||
from src.database.manager import setup_database_tables
|
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.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.model_training import create_recommendation_model, train_model_on_user_preferences
|
||||||
from src.ml.predictions import predict_video_preferences_with_model
|
from src.ml.predictions import predict_video_preferences_with_model
|
||||||
@ -79,6 +79,51 @@ def get_recommendations():
|
|||||||
'error': str(e)
|
'error': str(e)
|
||||||
}), 500
|
}), 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):
|
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"
|
||||||
|
|||||||
@ -209,6 +209,56 @@
|
|||||||
font-size: 14px;
|
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 {
|
.loading {
|
||||||
text-align: center;
|
text-align: center;
|
||||||
padding: 60px;
|
padding: 60px;
|
||||||
@ -343,20 +393,28 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
videoGrid.innerHTML = videos.map(video => `
|
videoGrid.innerHTML = videos.map(video => `
|
||||||
<div class="video-card" onclick="openVideo('${video.url}')">
|
<div class="video-card">
|
||||||
<div class="video-thumbnail">
|
<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>'">
|
<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)}">
|
<div class="confidence-badge ${getConfidenceClass(video.confidence)}">
|
||||||
${video.confidence}% match
|
${video.confidence}% match
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="video-info">
|
<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">
|
<div class="video-meta">
|
||||||
<span>${video.channel_name}</span>
|
<span>${video.channel_name}</span>
|
||||||
<span>•</span>
|
<span>•</span>
|
||||||
<span>${video.views_formatted}</span>
|
<span>${video.views_formatted}</span>
|
||||||
</div>
|
</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>
|
||||||
</div>
|
</div>
|
||||||
`).join('');
|
`).join('');
|
||||||
@ -372,6 +430,98 @@
|
|||||||
window.open(url, '_blank');
|
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
|
// Load recommendations when page loads
|
||||||
document.addEventListener('DOMContentLoaded', loadRecommendations);
|
document.addEventListener('DOMContentLoaded', loadRecommendations);
|
||||||
|
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user