// components/feedback-component.js - Message feedback functionality import { StateManager } from '../services/state-manager.js'; import { ApiService } from '../services/api-service.js'; import { TranslationService } from '../services/translation-service.js'; import { Utils } from '../utils.js'; export const FeedbackComponent = { elements: { feedbackOverlay: null, closeFeedbackBtn: null, cancelFeedbackBtn: null, submitFeedbackBtn: null, feedbackInput: null, feedbackRatingDisplay: null, feedbackMessagePreview: null }, currentFeedback: { messageIndex: null, modelType: null, rating: null, // 'like', 'dislike', 'mixed' messageContent: null }, /** * Initialize the feedback component */ init() { // Get DOM elements this.elements.feedbackOverlay = document.getElementById('feedback-overlay'); this.elements.closeFeedbackBtn = document.getElementById('closeFeedbackBtn'); this.elements.cancelFeedbackBtn = document.getElementById('cancelFeedbackBtn'); this.elements.submitFeedbackBtn = document.getElementById('submitFeedbackBtn'); this.elements.feedbackInput = document.getElementById('feedbackInput'); this.elements.feedbackRatingDisplay = document.getElementById('feedbackRatingDisplay'); this.elements.feedbackMessagePreview = document.getElementById('feedbackMessagePreview'); this.attachOutsideClickListener(); this.attachEventListeners(); }, attachOutsideClickListener() { this.elements.feedbackOverlay.addEventListener('click', (e) => { // Check if click is on the overlay itself (not its children) if (e.target === this.elements.feedbackOverlay) { this.closeModal(); } }); }, /** * Attach event listeners */ attachEventListeners() { this.elements.closeFeedbackBtn.addEventListener('click', () => this.closeModal()); this.elements.cancelFeedbackBtn.addEventListener('click', () => this.closeModal()); this.elements.submitFeedbackBtn.addEventListener('click', () => this.submitFeedback()); // Enter to submit (optional comment) this.elements.feedbackInput.addEventListener('keydown', (e) => { if (e.key === 'Enter' && !e.shiftKey) { e.preventDefault(); this.submitFeedback(); } }); }, /** * Open feedback modal * @param {number} messageIndex - Index of the message * @param {string} modelType - Type of model * @param {string} rating - 'like', 'dislike', or 'mixed' * @param {string} messageContent - Content of the message being rated */ openModal(messageIndex, modelType, rating, messageContent) { this.currentFeedback = { messageIndex, modelType, rating, messageContent }; // Update modal content this.updateModalContent(rating, messageContent); // Show modal this.elements.feedbackOverlay.style.display = ''; // Focus on textarea setTimeout(() => this.elements.feedbackInput.focus(), 100); }, /** * Update modal content based on rating * @param {string} rating - The rating type * @param {string} messageContent - The message content */ updateModalContent(rating, messageContent) { // Update rating display const ratingEmoji = { 'like': '👍', 'dislike': '👎', 'mixed': '~' }; const ratingText = { 'like': 'feedback_like_title', 'dislike': 'feedback_dislike_title', 'mixed': 'feedback_neutral_title' }; this.elements.feedbackRatingDisplay.textContent = ratingEmoji[rating] + ' '; this.elements.feedbackRatingDisplay.dataset.i18n = ratingText[rating]; // Update message preview (truncate if too long) const preview = messageContent.length > 150 ? messageContent.substring(0, 150) + '...' : messageContent; this.elements.feedbackMessagePreview.textContent = preview; // Clear previous comment this.elements.feedbackInput.value = ''; // Apply translations TranslationService.applyTranslation(); }, /** * Close the feedback modal */ closeModal() { this.elements.feedbackOverlay.style.display = 'none'; this.currentFeedback = { messageIndex: null, modelType: null, rating: null, messageContent: null }; }, /** * Submit feedback to the server */ async submitFeedback() { const comment = this.elements.feedbackInput.value.trim(); const feedbackData = { message_index: this.currentFeedback.messageIndex, model_type: this.currentFeedback.modelType, rating: this.currentFeedback.rating, comment: comment || "", // Optional reply_content: this.currentFeedback.messageContent, user_id: Utils.getMachineId(), session_id: StateManager.sessionId, conversation_id: StateManager.getConversationId(this.currentFeedback.modelType) }; try { const result = await ApiService.submitFeedback(feedbackData); if (result.success) { showSnackbar(translations[StateManager.currentLang]["feedback_submitted"], 'success'); // Mark message as rated in state (optional - for UI indication) this.markMessageAsRated( this.currentFeedback.modelType, this.currentFeedback.messageIndex, this.currentFeedback.rating ); this.closeModal(); } else { showSnackbar(translations[StateManager.currentLang]["feedback_failed_server_error"], 'error'); } } catch (err) { showSnackbar(translations[StateManager.currentLang]["feedback_failed_network_error"], 'error'); } }, /** * Mark a message as rated (for UI purposes) * @param {string} modelType - Model type * @param {number} messageIndex - Message index * @param {string} rating - Rating given */ markMessageAsRated(modelType, messageIndex, rating) { const messages = StateManager.getMessages(modelType); if (messages[messageIndex]) { messages[messageIndex].feedback = { rated: true, rating: rating }; window.dispatchEvent(new CustomEvent('feedbackSubmitted', { detail: { modelType, messageIndex, rating } })); } } };