71ad7b4d26
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
1173 lines
47 KiB
JavaScript
1173 lines
47 KiB
JavaScript
|
||
// 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">×</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">×</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);
|
||
});
|