Files
marketing-assistant-ai/frontend/app.js
T
Michael Ikehi 6fd7213076 feat: Initial implementation of Marketing Assistant AI for Adriana James
- Set up FastAPI backend with modular structure:

  - main.py for API routing

  - copywriter.py for AI-powered content generation using Cohere

  - embeddings.py for generating and reranking content embeddings

  - vector_store.py for FAISS-based similarity search

  - brand_style.py for managing brand tone, taboo words, and preferred terms

  - config.py for managing environment and application settings

- Configured RESTful API endpoints: /generate-copy, /brand-style, /training-data, /improve-content, /analyze-content

- Created frontend with vanilla HTML, CSS, and JS (index.html, styles.css, app.js)

- Integrated brand style management for tone, voice, taboo words, and terminology

- Implemented vector search for referencing similar historical content

- Enabled training data input to improve future AI output

- Added environment variable support for API keys and model configs

- Structured data storage with local JSON and DB files

- Added developer documentation, API reference, and project setup instructions

This commit provides the foundation for a full-stack, AI-driven content creation platform that ensures brand consistency, speeds up marketing workflows, and supports iterative improvement over time.
2025-04-17 08:50:12 +01:00

513 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 toneSelect = document.getElementById('tone');
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,
tone: toneSelect.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 = 'Write a social media post about our new coaching program';
});