Files
marketing-assistant-ai/frontend/app.js
T
Michael Ikehi 12e0830ba6 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 17:57:35 +01:00

512 lines
20 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');
// 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');
// 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');
} 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!');
// In a real implementation, you would save this to local storage
// or call an API to save it to the backend
});
}
// 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');
} 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();
// In a real implementation, you would also refresh the training data list
})
.catch(error => {
console.error('Error:', error);
alert('An error occurred while adding training data. Please try again.');
});
});
}
// 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';
});