diff --git a/.gitignore b/.gitignore
index 5d1220a..a9a9a5c 100644
--- a/.gitignore
+++ b/.gitignore
@@ -36,8 +36,10 @@ ENV/
# Local data
data/user_queries/*
!data/user_queries/.gitkeep
-backend/data/vector_store/*
-!backend/data/vector_store/.gitkeep
+data/past_campaigns/*
+!data/past_campaigns/.gitkeep
+data/vector_store/*
+data/training_data/*
# Logs
logs/*
diff --git a/backend/main.py b/backend/main.py
index 4aace71..3c76b7c 100644
--- a/backend/main.py
+++ b/backend/main.py
@@ -382,28 +382,36 @@ async def list_user_queries(
page: int = Query(1, ge=1, description="Page number"),
limit: int = Query(10, ge=1, le=100, description="Items per page")
):
- """Retrieve a list of user queries."""
+ """List user queries with pagination."""
try:
- # Get all query files
- query_files = glob.glob(str(Path(config.DATA_DIR) / "user_queries" / "*.json"))
- query_files.sort(reverse=True) # Sort by filename (timestamp) descending
+ # Calculate offset
+ offset = (page - 1) * limit
+
+ # Get files from user_queries directory
+ query_dir = Path(config.DATA_DIR) / "user_queries"
+ query_dir.mkdir(exist_ok=True)
+
+ # List all JSON files and sort by name (timestamp) in descending order
+ files = sorted(query_dir.glob("*.json"), reverse=True)
+ total = len(files)
# Apply pagination
- start_idx = (page - 1) * limit
- end_idx = start_idx + limit
- page_files = query_files[start_idx:end_idx]
+ files = files[offset:offset + limit]
items = []
- for file_path in page_files:
- with open(file_path, 'r') as f:
+ for file in files:
+ with open(file, 'r') as f:
query_data = json.load(f)
items.append(query_data)
return {
"items": items,
- "total": len(query_files),
- "page": page,
- "limit": limit
+ "pagination": {
+ "total": total,
+ "page": page,
+ "limit": limit,
+ "pages": (total + limit - 1) // limit
+ }
}
except Exception as e:
logger.error(f"Error listing user queries: {str(e)}")
diff --git a/data/training_data.db b/data/training_data.db
index 1691c6d..189ca4e 100644
Binary files a/data/training_data.db and b/data/training_data.db differ
diff --git a/data/vector_store/faiss_index.bin b/data/vector_store/faiss_index.bin
index 44c1eb2..ea6344d 100644
Binary files a/data/vector_store/faiss_index.bin and b/data/vector_store/faiss_index.bin differ
diff --git a/data/vector_store/metadata.pkl b/data/vector_store/metadata.pkl
index 2ca0503..78ae91a 100644
Binary files a/data/vector_store/metadata.pkl and b/data/vector_store/metadata.pkl differ
diff --git a/frontend/app.js b/frontend/app.js
index 17e6711..32444ca 100644
--- a/frontend/app.js
+++ b/frontend/app.js
@@ -1,3 +1,4 @@
+
// DOM Elements
document.addEventListener('DOMContentLoaded', function() {
// Navigation
@@ -23,6 +24,11 @@ document.addEventListener('DOMContentLoaded', function() {
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');
@@ -45,6 +51,9 @@ document.addEventListener('DOMContentLoaded', function() {
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';
@@ -62,6 +71,16 @@ document.addEventListener('DOMContentLoaded', function() {
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');
}
@@ -226,8 +245,278 @@ document.addEventListener('DOMContentLoaded', function() {
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
+ // 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 = '
Loading history...
';
+
+ // 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 = 'No history found.
';
+ 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 = `
+ ${getContentTypeLabel(contentType)}
+
+
${getPromptTitle(item.prompt)}
+
${promptPreview}
+
+ ${timestamp}
+
+
+
+
+ `;
+
+ 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 += ``;
+ }
+
+ 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 = 'Error loading history. Please try again.
';
+ });
+ }
+
+ // 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 = `
+
+
+
+ Prompt:
+ ${data.prompt}
+
+
+ Content Type:
+ ${contentType}
+
+
+ Length:
+ ${length}
+
+
+ Include CTA:
+ ${includeCTA}
+
+
+ Date:
+ ${new Date(data.timestamp).toLocaleString()}
+
+
+ `;
+
+ 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';
+ }
+ });
});
}
@@ -408,6 +697,11 @@ document.addEventListener('DOMContentLoaded', function() {
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');
}
@@ -479,8 +773,6 @@ document.addEventListener('DOMContentLoaded', function() {
// 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);
@@ -489,6 +781,257 @@ document.addEventListener('DOMContentLoaded', function() {
});
}
+ // Load Training Data
+ function loadTrainingData(page = 1, contentType = '') {
+ if (!trainingList) return;
+
+ // Show loading state
+ trainingList.innerHTML = 'Loading training data...
';
+
+ // 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 = 'No training data found.
';
+ 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 += `Open Rate: ${(metrics.open_rate * 100).toFixed(1)}%`;
+ }
+ if (metrics.click_rate) {
+ metricsHTML += `Click Rate: ${(metrics.click_rate * 100).toFixed(1)}%`;
+ }
+ if (metrics.conversion_rate) {
+ metricsHTML += `Conversion: ${(metrics.conversion_rate * 100).toFixed(1)}%`;
+ }
+ }
+
+ if (!metricsHTML) {
+ metricsHTML = 'No metrics available';
+ }
+
+ const campaignName = item.metadata?.campaign_name || 'Untitled';
+
+ trainingItem.innerHTML = `
+ ${getContentTypeLabel(item.content_type)}
+
+
${campaignName}
+
Added on: ${new Date(item.added_at).toLocaleDateString()}
+
+ ${metricsHTML}
+
+
+
+
+
+
+
+ `;
+
+ 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 += ``;
+ }
+
+ 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 = 'Error loading training data. Please try again.
';
+ });
+ }
+
+ // 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 += `Open Rate: ${(metrics.open_rate * 100).toFixed(1)}%
`;
+ }
+ if (metrics.click_rate !== undefined) {
+ metricsHTML += `Click Rate: ${(metrics.click_rate * 100).toFixed(1)}%
`;
+ }
+ if (metrics.conversion_rate !== undefined) {
+ metricsHTML += `Conversion Rate: ${(metrics.conversion_rate * 100).toFixed(1)}%
`;
+ }
+ }
+
+ modalContent.innerHTML = `
+
+
+
+ Content Type:
+ ${getContentTypeLabel(data.content_type)}
+
+
+ Added On:
+ ${new Date(data.added_at).toLocaleString()}
+
+ ${metricsHTML}
+
+
Content:
+
${data.content}
+
+
+ `;
+
+ 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 => {
@@ -508,4 +1051,122 @@ document.addEventListener('DOMContentLoaded', function() {
// 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);
});
diff --git a/frontend/index.html b/frontend/index.html
index 76be6f3..d349df1 100644
--- a/frontend/index.html
+++ b/frontend/index.html
@@ -1,3 +1,4 @@
+
@@ -198,9 +199,18 @@
@@ -209,45 +219,7 @@
-
-
Email
-
-
Transformation Masterclass Invitation
-
Subject: Transform Your Potential with Adriana James' Exclusive Workshop...
-
-
Apr 17, 2025
-
-
-
-
-
-
-
-
Social
-
-
3-Step Framework Post
-
BREAKTHROUGH MOMENT ✨ Ever feel stuck in patterns that hold you back...
-
-
Apr 16, 2025
-
-
-
-
-
-
-
-
Blog
-
-
5 Ways to Overcome Limiting Beliefs
-
Are limiting beliefs holding you back from achieving your full potential?...
-
-
Apr 14, 2025
-
-
-
-
-
-
+
@@ -391,7 +363,7 @@