Files
marketing-assistant-ai/frontend/app.js
T
Michael Ikehi 71ad7b4d26 feat(feedback): Add content improvement feedback system
Frontend (frontend/app.js):

- Add textarea for improvement feedback

- Add submit button with loading state

- Handle API response and display improved content

Backend (backend/copywriter.py):

- Add improve_copy() method using Cohere API

- Integrate retry mechanism for API calls

Backend (backend/main.py):

- Add /improve-content POST endpoint

- Implement error handling and return improved content with metadata

Testing:

- Verified feedback submission flow

- Confirmed improved content generation

- Tested error scenarios and loading states
2025-04-18 19:19:10 +01:00

1173 lines
47 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// DOM Elements
document.addEventListener('DOMContentLoaded', function() {
// Navigation
const menuItems = document.querySelectorAll('.menu li');
const pages = document.querySelectorAll('.page');
// Generate Content Page
const generateBtn = document.getElementById('generate-btn');
const promptInput = document.getElementById('prompt');
const contentTypeSelect = document.getElementById('content-type');
const lengthSelect = document.getElementById('length');
const includeCTACheckbox = document.getElementById('include-cta');
const referenceSimilarCheckbox = document.getElementById('reference-similar');
const resultContainer = document.getElementById('result-container');
const resultContent = document.getElementById('result-content');
const loadingIndicator = document.getElementById('loading-indicator');
const alignmentScore = document.getElementById('alignment-score');
const suggestionsList = document.getElementById('suggestions-list');
const copyBtn = document.getElementById('copy-btn');
const improveBtn = document.getElementById('improve-btn');
const saveBtn = document.getElementById('save-btn');
const improvementPanel = document.getElementById('improvement-panel');
const improvementFeedback = document.getElementById('improvement-feedback');
const submitImprovement = document.getElementById('submit-improvement');
// History Page
const historyFilterType = document.getElementById('history-filter-type');
const historySearch = document.getElementById('history-search');
const historyList = document.querySelector('.history-list');
// Brand Style Page
const toneSelector = document.getElementById('tone-selector');
const voiceSelector = document.getElementById('voice-selector');
const tabooWords = document.getElementById('taboo-words');
const tabooInput = document.getElementById('taboo-input');
const addTabooBtn = document.getElementById('add-taboo-btn');
const avoidTerm = document.getElementById('avoid-term');
const useTerm = document.getElementById('use-term');
const addTermBtn = document.getElementById('add-term-btn');
const saveBrandStyleBtn = document.getElementById('save-brand-style');
const resetBrandStyleBtn = document.getElementById('reset-brand-style');
// Training Page
const trainingTabs = document.querySelectorAll('.training-tabs .tab');
const tabContents = document.querySelectorAll('.tab-content');
const addTrainingBtn = document.getElementById('add-training-btn');
const trainingContentType = document.getElementById('training-content-type');
const campaignName = document.getElementById('campaign-name');
const trainingContent = document.getElementById('training-content');
const openRate = document.getElementById('open-rate');
const clickRate = document.getElementById('click-rate');
const conversionRate = document.getElementById('conversion-rate');
const trainingFilterType = document.getElementById('training-filter-type');
const trainingSearch = document.getElementById('training-search');
const trainingList = document.querySelector('.training-list');
// API Base URL
const API_URL = 'http://localhost:8000';
// Menu Navigation
menuItems.forEach(item => {
item.addEventListener('click', function() {
const pageName = this.getAttribute('data-page');
// Update active menu item
menuItems.forEach(menuItem => menuItem.classList.remove('active'));
this.classList.add('active');
// Show selected page
pages.forEach(page => {
if (page.id === `${pageName}-page`) {
page.classList.add('active');
// Load data for specific pages when they're opened
if (pageName === 'history') {
loadUserQueries();
} else if (pageName === 'training') {
// Check if the view training tab is active
if (document.querySelector('.tab[data-tab="view-training"]').classList.contains('active')) {
loadTrainingData();
}
}
} else {
page.classList.remove('active');
}
});
});
});
// Generate Content
if (generateBtn) {
generateBtn.addEventListener('click', function() {
if (!promptInput.value.trim()) {
alert('Please enter a prompt for content generation.');
return;
}
// Show loading indicator
loadingIndicator.classList.remove('hidden');
resultContainer.classList.add('hidden');
// Prepare request data
const requestData = {
prompt: promptInput.value,
content_type: contentTypeSelect.value || null,
length: lengthSelect.value || null,
include_cta: includeCTACheckbox.checked,
reference_similar_content: referenceSimilarCheckbox.checked
};
// Call the API
fetch(`${API_URL}/generate-copy`, {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify(requestData)
})
.then(response => {
if (!response.ok) {
throw new Error(`HTTP error! Status: ${response.status}`);
}
return response.json();
})
.then(data => {
// Hide loading indicator
loadingIndicator.classList.add('hidden');
// Display result
resultContent.textContent = data.content;
// Set alignment score
const score = data.metadata.alignment_score || 0;
alignmentScore.style.width = `${score}%`;
alignmentScore.textContent = `${Math.round(score)}%`;
if (score < 60) {
alignmentScore.style.backgroundColor = 'var(--danger-color)';
} else if (score < 80) {
alignmentScore.style.backgroundColor = 'var(--warning-color)';
} else {
alignmentScore.style.backgroundColor = 'var(--success-color)';
}
// Display suggestions
if (data.suggestions && data.suggestions.length > 0) {
suggestionsList.innerHTML = '';
data.suggestions.forEach(suggestion => {
const li = document.createElement('li');
li.textContent = suggestion;
li.addEventListener('click', function() {
promptInput.value = suggestion;
});
suggestionsList.appendChild(li);
});
}
// Show result container
resultContainer.classList.remove('hidden');
})
.catch(error => {
console.error('Error:', error);
loadingIndicator.classList.add('hidden');
alert('An error occurred while generating content. Please try again.');
});
});
}
// Copy to Clipboard
if (copyBtn) {
copyBtn.addEventListener('click', function() {
navigator.clipboard.writeText(resultContent.textContent)
.then(() => {
const originalTitle = copyBtn.getAttribute('title');
copyBtn.setAttribute('title', 'Copied!');
setTimeout(() => {
copyBtn.setAttribute('title', originalTitle);
}, 2000);
})
.catch(err => {
console.error('Could not copy text: ', err);
});
});
}
// Toggle Improvement Panel
if (improveBtn) {
improveBtn.addEventListener('click', function() {
improvementPanel.classList.toggle('hidden');
});
}
// Submit Improvement Feedback
if (submitImprovement) {
submitImprovement.addEventListener('click', function() {
if (!improvementFeedback.value.trim()) {
alert('Please enter feedback for improvement.');
return;
}
// Show loading indicator
loadingIndicator.classList.remove('hidden');
// Prepare request data
const requestData = {
content: resultContent.textContent,
feedback: improvementFeedback.value
};
// Call the API
fetch(`${API_URL}/improve-content`, {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify(requestData)
})
.then(response => {
if (!response.ok) {
throw new Error(`HTTP error! Status: ${response.status}`);
}
return response.json();
})
.then(data => {
// Hide loading indicator
loadingIndicator.classList.add('hidden');
// Update result content
resultContent.textContent = data.improved_content;
// Hide improvement panel
improvementPanel.classList.add('hidden');
improvementFeedback.value = '';
})
.catch(error => {
console.error('Error:', error);
loadingIndicator.classList.add('hidden');
alert('An error occurred while improving content. Please try again.');
});
});
}
// Save Content to History
if (saveBtn) {
saveBtn.addEventListener('click', function() {
alert('Content saved to history!');
// Note: The backend automatically saves the query as part of the generate-copy endpoint
// so we don't need to make another API call here
});
}
// Load User Queries (History)
function loadUserQueries(page = 1, contentType = '') {
if (!historyList) return;
// Show loading state
historyList.innerHTML = '<div class="loading-state">Loading history...</div>';
// Build the query parameters
let queryParams = `?page=${page}&limit=10`;
// Call the API
fetch(`${API_URL}/user-queries${queryParams}`)
.then(response => {
if (!response.ok) {
throw new Error(`HTTP error! Status: ${response.status}`);
}
return response.json();
})
.then(data => {
historyList.innerHTML = '';
if (data.items.length === 0) {
historyList.innerHTML = '<div class="empty-state">No history found.</div>';
return;
}
// Filter by content type if provided
let filteredItems = data.items;
if (contentType) {
filteredItems = data.items.filter(item =>
item.parameters && item.parameters.content_type === contentType
);
}
// Create history items
filteredItems.forEach(item => {
const contentType = item.parameters?.content_type || 'general';
const timestamp = item.timestamp ? new Date(item.timestamp).toLocaleDateString() : 'Unknown date';
const promptPreview = item.prompt.length > 80 ? item.prompt.substring(0, 80) + '...' : item.prompt;
const historyItem = document.createElement('div');
historyItem.className = 'history-item';
historyItem.innerHTML = `
<div class="history-item-type ${contentType || 'general'}">${getContentTypeLabel(contentType)}</div>
<div class="history-item-content">
<h4>${getPromptTitle(item.prompt)}</h4>
<p>${promptPreview}</p>
</div>
<div class="history-item-date">${timestamp}</div>
<div class="history-item-actions">
<button class="btn btn-icon view-query" title="View query" data-timestamp="${getTimestampFromISODate(item.timestamp)}">
<i class="fas fa-eye"></i>
</button>
<button class="btn btn-icon delete-query" title="Delete query" data-timestamp="${getTimestampFromISODate(item.timestamp)}">
<i class="fas fa-trash"></i>
</button>
</div>
`;
historyList.appendChild(historyItem);
});
// Add event listeners for view and delete buttons
document.querySelectorAll('.view-query').forEach(btn => {
btn.addEventListener('click', function() {
const timestamp = this.getAttribute('data-timestamp');
viewUserQuery(timestamp);
});
});
document.querySelectorAll('.delete-query').forEach(btn => {
btn.addEventListener('click', function() {
const timestamp = this.getAttribute('data-timestamp');
if (confirm('Are you sure you want to delete this query?')) {
deleteUserQuery(timestamp);
}
});
});
// Add pagination if needed
if (data.pagination && data.pagination.pages > 1) {
// Remove existing pagination if any
const existingPagination = document.querySelector('.pagination');
if (existingPagination) {
existingPagination.remove();
}
const paginationElement = document.createElement('div');
paginationElement.className = 'pagination';
let paginationHTML = '';
for (let i = 1; i <= data.pagination.pages; i++) {
paginationHTML += `<button class="page-btn ${i === page ? 'active' : ''}" data-page="${i}">${i}</button>`;
}
paginationElement.innerHTML = paginationHTML;
historyList.after(paginationElement);
// Add event listeners for pagination buttons
document.querySelectorAll('.page-btn').forEach(btn => {
btn.addEventListener('click', function() {
const pageNum = parseInt(this.getAttribute('data-page'));
loadUserQueries(pageNum, contentType);
});
});
}
})
.catch(error => {
console.error('Error loading user queries:', error);
historyList.innerHTML = '<div class="error-state">Error loading history. Please try again.</div>';
});
}
// Helper function to extract timestamp from ISO date
function getTimestampFromISODate(isoDate) {
if (!isoDate) return '';
const date = new Date(isoDate);
return date.toISOString().replace(/[-:T.]/g, '').slice(0, 14);
}
// Helper function to generate a title from prompt
function getPromptTitle(prompt) {
if (!prompt) return 'Untitled Query';
const words = prompt.split(' ');
if (words.length <= 5) return prompt;
return words.slice(0, 5).join(' ') + '...';
}
// Helper function to get display label for content type
function getContentTypeLabel(contentType) {
if (!contentType) return 'General';
const labels = {
'email': 'Email',
'social_media': 'Social',
'blog_post': 'Blog',
'website_copy': 'Website',
'sales_copy': 'Sales',
'ad_copy': 'Ad',
'video_script': 'Video',
'case_study': 'Case Study',
'product_description': 'Product',
'landing_page': 'Landing',
'press_release': 'Press',
'newsletter': 'Newsletter',
'general': 'General'
};
return labels[contentType] || contentType.charAt(0).toUpperCase() + contentType.slice(1);
}
// View User Query
function viewUserQuery(timestamp) {
fetch(`${API_URL}/user-queries/${timestamp}`)
.then(response => {
if (!response.ok) {
throw new Error(`HTTP error! Status: ${response.status}`);
}
return response.json();
})
.then(data => {
// Create a modal to display the query details
const modal = document.createElement('div');
modal.className = 'modal';
const modalContent = document.createElement('div');
modalContent.className = 'modal-content';
const parameters = data.parameters || {};
const contentType = parameters.content_type || 'Not specified';
const length = parameters.length || 'Not specified';
const includeCTA = parameters.include_cta ? 'Yes' : 'No';
modalContent.innerHTML = `
<div class="modal-header">
<h3>Query Details</h3>
<button class="modal-close">&times;</button>
</div>
<div class="modal-body">
<div class="detail-item">
<span class="detail-label">Prompt:</span>
<span class="detail-value">${data.prompt}</span>
</div>
<div class="detail-item">
<span class="detail-label">Content Type:</span>
<span class="detail-value">${contentType}</span>
</div>
<div class="detail-item">
<span class="detail-label">Length:</span>
<span class="detail-value">${length}</span>
</div>
<div class="detail-item">
<span class="detail-label">Include CTA:</span>
<span class="detail-value">${includeCTA}</span>
</div>
<div class="detail-item">
<span class="detail-label">Date:</span>
<span class="detail-value">${new Date(data.timestamp).toLocaleString()}</span>
</div>
</div>
`;
modal.appendChild(modalContent);
document.body.appendChild(modal);
// Close button functionality
modal.querySelector('.modal-close').addEventListener('click', function() {
document.body.removeChild(modal);
});
// Close when clicking outside the modal
window.addEventListener('click', function(event) {
if (event.target === modal) {
document.body.removeChild(modal);
}
});
})
.catch(error => {
console.error('Error viewing user query:', error);
alert('Error viewing query details. Please try again.');
});
}
// Delete User Query
function deleteUserQuery(timestamp) {
fetch(`${API_URL}/user-queries/${timestamp}`, {
method: 'DELETE'
})
.then(response => {
if (!response.ok) {
throw new Error(`HTTP error! Status: ${response.status}`);
}
return response.json();
})
.then(data => {
alert('Query successfully deleted.');
// Reload the user queries
loadUserQueries();
})
.catch(error => {
console.error('Error deleting user query:', error);
alert('Error deleting query. Please try again.');
});
}
// History Filter Handlers
if (historyFilterType) {
historyFilterType.addEventListener('change', function() {
loadUserQueries(1, this.value);
});
}
if (historySearch) {
historySearch.addEventListener('input', function() {
// Client-side filtering - this would ideally be server-side,
// but we'll implement a simple client-side filter for now
const searchTerm = this.value.toLowerCase();
document.querySelectorAll('.history-item').forEach(item => {
const content = item.querySelector('.history-item-content').textContent.toLowerCase();
if (content.includes(searchTerm)) {
item.style.display = 'flex';
} else {
item.style.display = 'none';
}
});
});
}
// Brand Style Tag Selection
if (toneSelector) {
const tagElements = toneSelector.querySelectorAll('.tag');
tagElements.forEach(tag => {
tag.addEventListener('click', function() {
this.classList.toggle('selected');
});
});
}
if (voiceSelector) {
const tagElements = voiceSelector.querySelectorAll('.tag');
tagElements.forEach(tag => {
tag.addEventListener('click', function() {
this.classList.toggle('selected');
});
});
}
// Add Taboo Word
if (addTabooBtn && tabooInput && tabooWords) {
addTabooBtn.addEventListener('click', function() {
const word = tabooInput.value.trim();
if (word) {
const tagElement = document.createElement('span');
tagElement.classList.add('tag', 'removable');
tagElement.innerHTML = `${word}<i class="fas fa-times"></i>`;
// Add click event to remove the tag
tagElement.querySelector('i').addEventListener('click', function() {
tagElement.remove();
});
tabooWords.appendChild(tagElement);
tabooInput.value = '';
}
});
// Allow pressing Enter to add a word
tabooInput.addEventListener('keypress', function(e) {
if (e.key === 'Enter') {
e.preventDefault();
addTabooBtn.click();
}
});
}
// Add Terminology Term
if (addTermBtn && avoidTerm && useTerm) {
addTermBtn.addEventListener('click', function() {
const avoid = avoidTerm.value.trim();
const use = useTerm.value.trim();
if (avoid && use) {
const tableRow = document.createElement('div');
tableRow.classList.add('terminology-row');
tableRow.innerHTML = `
<div class="term-avoid">${avoid}</div>
<div class="term-use">${use}</div>
<div class="term-actions">
<button class="btn btn-icon"><i class="fas fa-times"></i></button>
</div>
`;
// Add click event to remove the row
tableRow.querySelector('.btn-icon').addEventListener('click', function() {
tableRow.remove();
});
// Insert before the add row
const addRow = document.querySelector('.terminology-row.add-row');
addRow.parentNode.insertBefore(tableRow, addRow);
avoidTerm.value = '';
useTerm.value = '';
}
});
// Allow pressing Enter to add a term
useTerm.addEventListener('keypress', function(e) {
if (e.key === 'Enter') {
e.preventDefault();
addTermBtn.click();
}
});
}
// Save Brand Style
if (saveBrandStyleBtn) {
saveBrandStyleBtn.addEventListener('click', function() {
// Collect tone tags
const selectedTones = [];
toneSelector.querySelectorAll('.tag.selected').forEach(tag => {
selectedTones.push(tag.textContent);
});
// Collect voice characteristics
const selectedVoice = [];
voiceSelector.querySelectorAll('.tag.selected').forEach(tag => {
selectedVoice.push(tag.textContent);
});
// Collect taboo words
const tabooWordsList = [];
tabooWords.querySelectorAll('.tag').forEach(tag => {
// Extract just the text without the 'x' icon
const text = tag.textContent.replace('×', '').trim();
tabooWordsList.push(text);
});
// Collect preferred terms
const preferredTerms = {};
document.querySelectorAll('.terminology-row:not(.add-row):not(.terminology-header)').forEach(row => {
const avoid = row.querySelector('.term-avoid').textContent.trim();
const use = row.querySelector('.term-use').textContent.trim();
if (avoid && use) {
preferredTerms[avoid] = use;
}
});
// Prepare request data
const requestData = {
tone: selectedTones,
voice_characteristics: selectedVoice,
taboo_words: tabooWordsList,
preferred_terms: preferredTerms
};
// Call the API
fetch(`${API_URL}/brand-style`, {
method: 'PUT',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify(requestData)
})
.then(response => {
if (!response.ok) {
throw new Error(`HTTP error! Status: ${response.status}`);
}
return response.json();
})
.then(data => {
alert('Brand style updated successfully!');
})
.catch(error => {
console.error('Error:', error);
alert('An error occurred while updating brand style. Please try again.');
});
});
}
// Reset Brand Style
if (resetBrandStyleBtn) {
resetBrandStyleBtn.addEventListener('click', function() {
if (confirm('Are you sure you want to reset brand style to defaults?')) {
// In a real implementation, you would call an API to reset
// For now, just reload the page
window.location.reload();
}
});
}
// Training Tabs
if (trainingTabs.length > 0) {
trainingTabs.forEach(tab => {
tab.addEventListener('click', function() {
const tabName = this.getAttribute('data-tab');
// Update active tab
trainingTabs.forEach(t => t.classList.remove('active'));
this.classList.add('active');
// Show selected tab content
tabContents.forEach(content => {
if (content.id === `${tabName}-tab`) {
content.classList.add('active');
// Load training data when the View tab is selected
if (tabName === 'view-training') {
loadTrainingData();
}
} else {
content.classList.remove('active');
}
});
});
});
}
// Add Training Data
if (addTrainingBtn) {
addTrainingBtn.addEventListener('click', function() {
if (!trainingContentType.value) {
alert('Please select a content type.');
return;
}
if (!trainingContent.value.trim()) {
alert('Please enter content for training.');
return;
}
// Prepare request data
const requestData = {
content_type: trainingContentType.value,
content: trainingContent.value,
metadata: {
campaign_name: campaignName.value,
performance_metrics: {}
}
};
// Add performance metrics if provided
if (openRate.value) {
requestData.metadata.performance_metrics.open_rate = parseFloat(openRate.value) / 100;
}
if (clickRate.value) {
requestData.metadata.performance_metrics.click_rate = parseFloat(clickRate.value) / 100;
}
if (conversionRate.value) {
requestData.metadata.performance_metrics.conversion_rate = parseFloat(conversionRate.value) / 100;
}
// Call the API
fetch(`${API_URL}/training-data`, {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify(requestData)
})
.then(response => {
if (!response.ok) {
throw new Error(`HTTP error! Status: ${response.status}`);
}
return response.json();
})
.then(data => {
alert('Training data added successfully!');
// Clear form
trainingContentType.value = '';
campaignName.value = '';
trainingContent.value = '';
openRate.value = '';
clickRate.value = '';
conversionRate.value = '';
// Switch to view tab
document.querySelector('.tab[data-tab="view-training"]').click();
})
.catch(error => {
console.error('Error:', error);
alert('An error occurred while adding training data. Please try again.');
});
});
}
// Load Training Data
function loadTrainingData(page = 1, contentType = '') {
if (!trainingList) return;
// Show loading state
trainingList.innerHTML = '<div class="loading-state">Loading training data...</div>';
// Build the query parameters
let queryParams = `?page=${page}&limit=10`;
if (contentType) {
queryParams += `&content_type=${contentType}`;
}
// Call the API
fetch(`${API_URL}/training-data${queryParams}`)
.then(response => {
if (!response.ok) {
throw new Error(`HTTP error! Status: ${response.status}`);
}
return response.json();
})
.then(data => {
trainingList.innerHTML = '';
if (data.items.length === 0) {
trainingList.innerHTML = '<div class="empty-state">No training data found.</div>';
return;
}
// Create training items
data.items.forEach(item => {
const trainingItem = document.createElement('div');
trainingItem.className = 'training-item';
// Generate metrics HTML
let metricsHTML = '';
if (item.metadata && item.metadata.performance_metrics) {
const metrics = item.metadata.performance_metrics;
if (metrics.open_rate) {
metricsHTML += `<span class="metric">Open Rate: ${(metrics.open_rate * 100).toFixed(1)}%</span>`;
}
if (metrics.click_rate) {
metricsHTML += `<span class="metric">Click Rate: ${(metrics.click_rate * 100).toFixed(1)}%</span>`;
}
if (metrics.conversion_rate) {
metricsHTML += `<span class="metric">Conversion: ${(metrics.conversion_rate * 100).toFixed(1)}%</span>`;
}
}
if (!metricsHTML) {
metricsHTML = '<span class="metric">No metrics available</span>';
}
const campaignName = item.metadata?.campaign_name || 'Untitled';
trainingItem.innerHTML = `
<div class="training-item-type ${item.content_type}">${getContentTypeLabel(item.content_type)}</div>
<div class="training-item-content">
<h4>${campaignName}</h4>
<p>Added on: ${new Date(item.added_at).toLocaleDateString()}</p>
<div class="metrics">
${metricsHTML}
</div>
</div>
<div class="training-item-actions">
<button class="btn btn-icon view-training" title="View content" data-id="${item.id}">
<i class="fas fa-eye"></i>
</button>
<button class="btn btn-icon delete-training" title="Delete content" data-id="${item.id}">
<i class="fas fa-trash"></i>
</button>
</div>
`;
trainingList.appendChild(trainingItem);
});
// Add event listeners for view and delete buttons
document.querySelectorAll('.view-training').forEach(btn => {
btn.addEventListener('click', function() {
const id = this.getAttribute('data-id');
viewTrainingData(id);
});
});
document.querySelectorAll('.delete-training').forEach(btn => {
btn.addEventListener('click', function() {
const id = this.getAttribute('data-id');
if (confirm('Are you sure you want to delete this training data?')) {
deleteTrainingData(id);
}
});
});
// Add pagination for training data
if (data.pagination && data.pagination.pages > 1) {
// Remove existing pagination if any
const existingPagination = document.querySelector('.pagination');
if (existingPagination) {
existingPagination.remove();
}
const paginationElement = document.createElement('div');
paginationElement.className = 'pagination';
let paginationHTML = '';
for (let i = 1; i <= data.pagination.pages; i++) {
paginationHTML += `<button class="page-btn ${i === page ? 'active' : ''}" data-page="${i}">${i}</button>`;
}
paginationElement.innerHTML = paginationHTML;
trainingList.after(paginationElement);
// Add event listeners for pagination buttons
document.querySelectorAll('.page-btn').forEach(btn => {
btn.addEventListener('click', function() {
const pageNum = parseInt(this.getAttribute('data-page'));
loadTrainingData(pageNum, contentType);
});
});
}
})
.catch(error => {
console.error('Error loading training data:', error);
trainingList.innerHTML = '<div class="error-state">Error loading training data. Please try again.</div>';
});
}
// View Training Data
function viewTrainingData(id) {
fetch(`${API_URL}/training-data/${id}`)
.then(response => {
if (!response.ok) {
throw new Error(`HTTP error! Status: ${response.status}`);
}
return response.json();
})
.then(data => {
// Create a modal to display the training data details
const modal = document.createElement('div');
modal.className = 'modal';
const modalContent = document.createElement('div');
modalContent.className = 'modal-content';
const campaignName = data.metadata?.campaign_name || 'Untitled';
let metricsHTML = '';
if (data.metadata && data.metadata.performance_metrics) {
const metrics = data.metadata.performance_metrics;
if (metrics.open_rate !== undefined) {
metricsHTML += `<div class="detail-item"><span class="detail-label">Open Rate:</span> <span class="detail-value">${(metrics.open_rate * 100).toFixed(1)}%</span></div>`;
}
if (metrics.click_rate !== undefined) {
metricsHTML += `<div class="detail-item"><span class="detail-label">Click Rate:</span> <span class="detail-value">${(metrics.click_rate * 100).toFixed(1)}%</span></div>`;
}
if (metrics.conversion_rate !== undefined) {
metricsHTML += `<div class="detail-item"><span class="detail-label">Conversion Rate:</span> <span class="detail-value">${(metrics.conversion_rate * 100).toFixed(1)}%</span></div>`;
}
}
modalContent.innerHTML = `
<div class="modal-header">
<h3>${campaignName}</h3>
<button class="modal-close">&times;</button>
</div>
<div class="modal-body">
<div class="detail-item">
<span class="detail-label">Content Type:</span>
<span class="detail-value">${getContentTypeLabel(data.content_type)}</span>
</div>
<div class="detail-item">
<span class="detail-label">Added On:</span>
<span class="detail-value">${new Date(data.added_at).toLocaleString()}</span>
</div>
${metricsHTML}
<div class="detail-item content-preview">
<span class="detail-label">Content:</span>
<div class="detail-value content-box">${data.content}</div>
</div>
</div>
`;
modal.appendChild(modalContent);
document.body.appendChild(modal);
// Close button functionality
modal.querySelector('.modal-close').addEventListener('click', function() {
document.body.removeChild(modal);
});
// Close when clicking outside the modal
window.addEventListener('click', function(event) {
if (event.target === modal) {
document.body.removeChild(modal);
}
});
})
.catch(error => {
console.error('Error viewing training data:', error);
alert('Error viewing training data details. Please try again.');
});
}
// Delete Training Data
function deleteTrainingData(id) {
fetch(`${API_URL}/training-data/${id}`, {
method: 'DELETE'
})
.then(response => {
if (!response.ok) {
throw new Error(`HTTP error! Status: ${response.status}`);
}
return response.json();
})
.then(data => {
alert('Training data successfully deleted.');
// Reload the training data
loadTrainingData();
})
.catch(error => {
console.error('Error deleting training data:', error);
alert('Error deleting training data. Please try again.');
});
}
// Training Filter Handlers
if (trainingFilterType) {
trainingFilterType.addEventListener('change', function() {
loadTrainingData(1, this.value);
});
}
if (trainingSearch) {
trainingSearch.addEventListener('input', function() {
// Client-side filtering
const searchTerm = this.value.toLowerCase();
document.querySelectorAll('.training-item').forEach(item => {
const content = item.querySelector('.training-item-content').textContent.toLowerCase();
if (content.includes(searchTerm)) {
item.style.display = 'flex';
} else {
item.style.display = 'none';
}
});
});
}
// Load Brand Style on Page Load
fetch(`${API_URL}/brand-style`)
.then(response => {
if (!response.ok) {
throw new Error(`HTTP error! Status: ${response.status}`);
}
return response.json();
})
.then(data => {
console.log('Loaded brand style:', data);
// In a real implementation, you would update the UI based on the loaded data
})
.catch(error => {
console.error('Error loading brand style:', error);
});
// For demonstration purposes, let's create a mocked pre-filled content
// In a real implementation, this would be loaded from the backend
document.getElementById('prompt').value = 'Generate an email campaign for a product launch';
// Add CSS for modal
const modalStyle = document.createElement('style');
modalStyle.textContent = `
.modal {
display: block;
position: fixed;
z-index: 1000;
left: 0;
top: 0;
width: 100%;
height: 100%;
background-color: rgba(0, 0, 0, 0.5);
overflow: auto;
}
.modal-content {
background-color: white;
margin: 5% auto;
padding: 0;
border-radius: var(--radius-lg);
box-shadow: var(--shadow-lg);
width: 80%;
max-width: 800px;
animation: modalOpen 0.3s ease-out;
}
@keyframes modalOpen {
from {opacity: 0; transform: translateY(-20px);}
to {opacity: 1; transform: translateY(0);}
}
.modal-header {
padding: 20px 25px;
border-bottom: 1px solid var(--grey-200);
display: flex;
justify-content: space-between;
align-items: center;
}
.modal-header h3 {
margin: 0;
}
.modal-close {
background: transparent;
border: none;
font-size: 24px;
cursor: pointer;
color: var(--grey-600);
}
.modal-close:hover {
color: var(--grey-800);
}
.modal-body {
padding: 25px;
}
.detail-item {
margin-bottom: 15px;
}
.detail-label {
font-weight: 600;
color: var(--grey-700);
display: block;
margin-bottom: 5px;
}
.detail-value {
color: var(--grey-800);
}
.content-preview {
margin-top: 25px;
}
.content-box {
background-color: var(--grey-100);
border: 1px solid var(--grey-200);
border-radius: var(--radius-md);
padding: 15px;
margin-top: 10px;
white-space: pre-wrap;
max-height: 300px;
overflow-y: auto;
}
.loading-state, .empty-state, .error-state {
text-align: center;
padding: 30px;
color: var(--grey-500);
}
.pagination {
display: flex;
justify-content: center;
gap: 5px;
margin-top: 20px;
}
.page-btn {
padding: 8px 12px;
border: 1px solid var(--grey-300);
background-color: white;
border-radius: var(--radius-md);
cursor: pointer;
}
.page-btn.active {
background-color: var(--primary-color);
color: white;
border-color: var(--primary-color);
}
`;
document.head.appendChild(modalStyle);
});