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
This commit is contained in:
+4
-2
@@ -36,8 +36,10 @@ ENV/
|
|||||||
# Local data
|
# Local data
|
||||||
data/user_queries/*
|
data/user_queries/*
|
||||||
!data/user_queries/.gitkeep
|
!data/user_queries/.gitkeep
|
||||||
backend/data/vector_store/*
|
data/past_campaigns/*
|
||||||
!backend/data/vector_store/.gitkeep
|
!data/past_campaigns/.gitkeep
|
||||||
|
data/vector_store/*
|
||||||
|
data/training_data/*
|
||||||
|
|
||||||
# Logs
|
# Logs
|
||||||
logs/*
|
logs/*
|
||||||
|
|||||||
+20
-12
@@ -382,28 +382,36 @@ async def list_user_queries(
|
|||||||
page: int = Query(1, ge=1, description="Page number"),
|
page: int = Query(1, ge=1, description="Page number"),
|
||||||
limit: int = Query(10, ge=1, le=100, description="Items per page")
|
limit: int = Query(10, ge=1, le=100, description="Items per page")
|
||||||
):
|
):
|
||||||
"""Retrieve a list of user queries."""
|
"""List user queries with pagination."""
|
||||||
try:
|
try:
|
||||||
# Get all query files
|
# Calculate offset
|
||||||
query_files = glob.glob(str(Path(config.DATA_DIR) / "user_queries" / "*.json"))
|
offset = (page - 1) * limit
|
||||||
query_files.sort(reverse=True) # Sort by filename (timestamp) descending
|
|
||||||
|
# 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
|
# Apply pagination
|
||||||
start_idx = (page - 1) * limit
|
files = files[offset:offset + limit]
|
||||||
end_idx = start_idx + limit
|
|
||||||
page_files = query_files[start_idx:end_idx]
|
|
||||||
|
|
||||||
items = []
|
items = []
|
||||||
for file_path in page_files:
|
for file in files:
|
||||||
with open(file_path, 'r') as f:
|
with open(file, 'r') as f:
|
||||||
query_data = json.load(f)
|
query_data = json.load(f)
|
||||||
items.append(query_data)
|
items.append(query_data)
|
||||||
|
|
||||||
return {
|
return {
|
||||||
"items": items,
|
"items": items,
|
||||||
"total": len(query_files),
|
"pagination": {
|
||||||
"page": page,
|
"total": total,
|
||||||
"limit": limit
|
"page": page,
|
||||||
|
"limit": limit,
|
||||||
|
"pages": (total + limit - 1) // limit
|
||||||
|
}
|
||||||
}
|
}
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"Error listing user queries: {str(e)}")
|
logger.error(f"Error listing user queries: {str(e)}")
|
||||||
|
|||||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
+665
-4
@@ -1,3 +1,4 @@
|
|||||||
|
|
||||||
// DOM Elements
|
// DOM Elements
|
||||||
document.addEventListener('DOMContentLoaded', function() {
|
document.addEventListener('DOMContentLoaded', function() {
|
||||||
// Navigation
|
// Navigation
|
||||||
@@ -23,6 +24,11 @@ document.addEventListener('DOMContentLoaded', function() {
|
|||||||
const improvementFeedback = document.getElementById('improvement-feedback');
|
const improvementFeedback = document.getElementById('improvement-feedback');
|
||||||
const submitImprovement = document.getElementById('submit-improvement');
|
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
|
// Brand Style Page
|
||||||
const toneSelector = document.getElementById('tone-selector');
|
const toneSelector = document.getElementById('tone-selector');
|
||||||
const voiceSelector = document.getElementById('voice-selector');
|
const voiceSelector = document.getElementById('voice-selector');
|
||||||
@@ -45,6 +51,9 @@ document.addEventListener('DOMContentLoaded', function() {
|
|||||||
const openRate = document.getElementById('open-rate');
|
const openRate = document.getElementById('open-rate');
|
||||||
const clickRate = document.getElementById('click-rate');
|
const clickRate = document.getElementById('click-rate');
|
||||||
const conversionRate = document.getElementById('conversion-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
|
// API Base URL
|
||||||
const API_URL = 'http://localhost:8000';
|
const API_URL = 'http://localhost:8000';
|
||||||
@@ -62,6 +71,16 @@ document.addEventListener('DOMContentLoaded', function() {
|
|||||||
pages.forEach(page => {
|
pages.forEach(page => {
|
||||||
if (page.id === `${pageName}-page`) {
|
if (page.id === `${pageName}-page`) {
|
||||||
page.classList.add('active');
|
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 {
|
} else {
|
||||||
page.classList.remove('active');
|
page.classList.remove('active');
|
||||||
}
|
}
|
||||||
@@ -226,8 +245,278 @@ document.addEventListener('DOMContentLoaded', function() {
|
|||||||
if (saveBtn) {
|
if (saveBtn) {
|
||||||
saveBtn.addEventListener('click', function() {
|
saveBtn.addEventListener('click', function() {
|
||||||
alert('Content saved to history!');
|
alert('Content saved to history!');
|
||||||
// In a real implementation, you would save this to local storage
|
// Note: The backend automatically saves the query as part of the generate-copy endpoint
|
||||||
// or call an API to save it to the backend
|
// 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';
|
||||||
|
}
|
||||||
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -408,6 +697,11 @@ document.addEventListener('DOMContentLoaded', function() {
|
|||||||
tabContents.forEach(content => {
|
tabContents.forEach(content => {
|
||||||
if (content.id === `${tabName}-tab`) {
|
if (content.id === `${tabName}-tab`) {
|
||||||
content.classList.add('active');
|
content.classList.add('active');
|
||||||
|
|
||||||
|
// Load training data when the View tab is selected
|
||||||
|
if (tabName === 'view-training') {
|
||||||
|
loadTrainingData();
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
content.classList.remove('active');
|
content.classList.remove('active');
|
||||||
}
|
}
|
||||||
@@ -479,8 +773,6 @@ document.addEventListener('DOMContentLoaded', function() {
|
|||||||
|
|
||||||
// Switch to view tab
|
// Switch to view tab
|
||||||
document.querySelector('.tab[data-tab="view-training"]').click();
|
document.querySelector('.tab[data-tab="view-training"]').click();
|
||||||
|
|
||||||
// In a real implementation, you would also refresh the training data list
|
|
||||||
})
|
})
|
||||||
.catch(error => {
|
.catch(error => {
|
||||||
console.error('Error:', 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 = '<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
|
// Load Brand Style on Page Load
|
||||||
fetch(`${API_URL}/brand-style`)
|
fetch(`${API_URL}/brand-style`)
|
||||||
.then(response => {
|
.then(response => {
|
||||||
@@ -508,4 +1051,122 @@ document.addEventListener('DOMContentLoaded', function() {
|
|||||||
// For demonstration purposes, let's create a mocked pre-filled content
|
// For demonstration purposes, let's create a mocked pre-filled content
|
||||||
// In a real implementation, this would be loaded from the backend
|
// In a real implementation, this would be loaded from the backend
|
||||||
document.getElementById('prompt').value = 'Generate an email campaign for a product launch';
|
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);
|
||||||
});
|
});
|
||||||
|
|||||||
+15
-73
@@ -1,3 +1,4 @@
|
|||||||
|
|
||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<html lang="en">
|
<html lang="en">
|
||||||
<head>
|
<head>
|
||||||
@@ -198,9 +199,18 @@
|
|||||||
<select id="history-filter-type">
|
<select id="history-filter-type">
|
||||||
<option value="">All Content Types</option>
|
<option value="">All Content Types</option>
|
||||||
<option value="email_campaign">Email Campaign</option>
|
<option value="email_campaign">Email Campaign</option>
|
||||||
|
<option value="email">Email</option>
|
||||||
<option value="social_media">Social Media</option>
|
<option value="social_media">Social Media</option>
|
||||||
<option value="blog_post">Blog Post</option>
|
<option value="blog_post">Blog Post</option>
|
||||||
<option value="website_copy">Website Copy</option>
|
<option value="website_copy">Website Copy</option>
|
||||||
|
<option value="sales_copy">Sales Copy</option>
|
||||||
|
<option value="ad_copy">Ad Copy</option>
|
||||||
|
<option value="video_script">Video Script</option>
|
||||||
|
<option value="case_study">Case Study</option>
|
||||||
|
<option value="product_description">Product Description</option>
|
||||||
|
<option value="landing_page">Landing Page</option>
|
||||||
|
<option value="press_release">Press Release</option>
|
||||||
|
<option value="newsletter">Newsletter</option>
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
@@ -209,45 +219,7 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="history-list">
|
<div class="history-list">
|
||||||
<div class="history-item">
|
<!-- History items will be loaded dynamically -->
|
||||||
<div class="history-item-type email_campaign">Email</div>
|
|
||||||
<div class="history-item-content">
|
|
||||||
<h4>Transformation Masterclass Invitation</h4>
|
|
||||||
<p>Subject: Transform Your Potential with Adriana James' Exclusive Workshop...</p>
|
|
||||||
</div>
|
|
||||||
<div class="history-item-date">Apr 17, 2025</div>
|
|
||||||
<div class="history-item-actions">
|
|
||||||
<button class="btn btn-icon" title="View content"><i class="fas fa-eye"></i></button>
|
|
||||||
<button class="btn btn-icon" title="Edit content"><i class="fas fa-edit"></i></button>
|
|
||||||
<button class="btn btn-icon" title="Delete content"><i class="fas fa-trash"></i></button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="history-item">
|
|
||||||
<div class="history-item-type social_media">Social</div>
|
|
||||||
<div class="history-item-content">
|
|
||||||
<h4>3-Step Framework Post</h4>
|
|
||||||
<p>BREAKTHROUGH MOMENT ✨ Ever feel stuck in patterns that hold you back...</p>
|
|
||||||
</div>
|
|
||||||
<div class="history-item-date">Apr 16, 2025</div>
|
|
||||||
<div class="history-item-actions">
|
|
||||||
<button class="btn btn-icon" title="View content"><i class="fas fa-eye"></i></button>
|
|
||||||
<button class="btn btn-icon" title="Edit content"><i class="fas fa-edit"></i></button>
|
|
||||||
<button class="btn btn-icon" title="Delete content"><i class="fas fa-trash"></i></button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="history-item">
|
|
||||||
<div class="history-item-type blog_post">Blog</div>
|
|
||||||
<div class="history-item-content">
|
|
||||||
<h4>5 Ways to Overcome Limiting Beliefs</h4>
|
|
||||||
<p>Are limiting beliefs holding you back from achieving your full potential?...</p>
|
|
||||||
</div>
|
|
||||||
<div class="history-item-date">Apr 14, 2025</div>
|
|
||||||
<div class="history-item-actions">
|
|
||||||
<button class="btn btn-icon" title="View content"><i class="fas fa-eye"></i></button>
|
|
||||||
<button class="btn btn-icon" title="Edit content"><i class="fas fa-edit"></i></button>
|
|
||||||
<button class="btn btn-icon" title="Delete content"><i class="fas fa-trash"></i></button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
@@ -391,7 +363,7 @@
|
|||||||
<label for="training-content-type">Content Type</label>
|
<label for="training-content-type">Content Type</label>
|
||||||
<select id="training-content-type">
|
<select id="training-content-type">
|
||||||
<option value="">Select Type</option>
|
<option value="">Select Type</option>
|
||||||
<option value="email_campaign">Email Campaign</option>
|
<option value="email">Email Campaign</option>
|
||||||
<option value="social_media">Social Media</option>
|
<option value="social_media">Social Media</option>
|
||||||
<option value="blog_post">Blog Post</option>
|
<option value="blog_post">Blog Post</option>
|
||||||
<option value="website_copy">Website Copy</option>
|
<option value="website_copy">Website Copy</option>
|
||||||
@@ -434,10 +406,11 @@
|
|||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<select id="training-filter-type">
|
<select id="training-filter-type">
|
||||||
<option value="">All Content Types</option>
|
<option value="">All Content Types</option>
|
||||||
<option value="email_campaign">Email Campaign</option>
|
<option value="email">Email Campaign</option>
|
||||||
<option value="social_media">Social Media</option>
|
<option value="social_media">Social Media</option>
|
||||||
<option value="blog_post">Blog Post</option>
|
<option value="blog_post">Blog Post</option>
|
||||||
<option value="website_copy">Website Copy</option>
|
<option value="website_copy">Website Copy</option>
|
||||||
|
<option value="ad_copy">Ad Copy</option>
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
@@ -446,38 +419,7 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="training-list">
|
<div class="training-list">
|
||||||
<div class="training-item">
|
<!-- Training items will be loaded dynamically -->
|
||||||
<div class="training-item-type email_campaign">Email</div>
|
|
||||||
<div class="training-item-content">
|
|
||||||
<h4>Transformation Masterclass Promotion</h4>
|
|
||||||
<p>Added on: Apr 15, 2025</p>
|
|
||||||
<div class="metrics">
|
|
||||||
<span class="metric">Open Rate: 42%</span>
|
|
||||||
<span class="metric">Click Rate: 18%</span>
|
|
||||||
<span class="metric">Conversion: 8%</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="training-item-actions">
|
|
||||||
<button class="btn btn-icon" title="View content"><i class="fas fa-eye"></i></button>
|
|
||||||
<button class="btn btn-icon" title="Delete content"><i class="fas fa-trash"></i></button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="training-item">
|
|
||||||
<div class="training-item-type social_media">Social</div>
|
|
||||||
<div class="training-item-content">
|
|
||||||
<h4>Breakthrough Framework</h4>
|
|
||||||
<p>Added on: Apr 10, 2025</p>
|
|
||||||
<div class="metrics">
|
|
||||||
<span class="metric">Engagement: 6.4%</span>
|
|
||||||
<span class="metric">Saves: 178</span>
|
|
||||||
<span class="metric">Shares: 92</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="training-item-actions">
|
|
||||||
<button class="btn btn-icon" title="View content"><i class="fas fa-eye"></i></button>
|
|
||||||
<button class="btn btn-icon" title="Delete content"><i class="fas fa-trash"></i></button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|||||||
@@ -882,3 +882,89 @@
|
|||||||
2025-04-18 17:41:55.046 | INFO | copywriter:generate_copy:90 - Generated content with 2070 characters
|
2025-04-18 17:41:55.046 | INFO | copywriter:generate_copy:90 - Generated content with 2070 characters
|
||||||
2025-04-18 17:41:55.458 | INFO | vector_store:add_documents:131 - Added 1 documents to vector store
|
2025-04-18 17:41:55.458 | INFO | vector_store:add_documents:131 - Added 1 documents to vector store
|
||||||
2025-04-18 17:41:55.458 | INFO | vector_store:add_documents:131 - Added 1 documents to vector store
|
2025-04-18 17:41:55.458 | INFO | vector_store:add_documents:131 - Added 1 documents to vector store
|
||||||
|
2025-04-18 18:11:18.236 | INFO | brand_style:update_style_guidelines:178 - Updated brand style guidelines
|
||||||
|
2025-04-18 18:11:18.236 | INFO | brand_style:update_style_guidelines:178 - Updated brand style guidelines
|
||||||
|
2025-04-18 18:11:40.648 | INFO | brand_style:update_style_guidelines:178 - Updated brand style guidelines
|
||||||
|
2025-04-18 18:11:40.648 | INFO | brand_style:update_style_guidelines:178 - Updated brand style guidelines
|
||||||
|
2025-04-18 18:13:01.347 | INFO | brand_style:update_style_guidelines:178 - Updated brand style guidelines
|
||||||
|
2025-04-18 18:13:01.347 | INFO | brand_style:update_style_guidelines:178 - Updated brand style guidelines
|
||||||
|
2025-04-18 18:36:18.449 | ERROR | main:delete_user_query:461 - Error deleting user query: 404: Query with timestamp 20250418164155 not found
|
||||||
|
2025-04-18 18:36:18.449 | ERROR | main:delete_user_query:461 - Error deleting user query: 404: Query with timestamp 20250418164155 not found
|
||||||
|
2025-04-18 18:36:43.730 | ERROR | main:delete_user_query:461 - Error deleting user query: 404: Query with timestamp 20250418161237 not found
|
||||||
|
2025-04-18 18:36:43.730 | ERROR | main:delete_user_query:461 - Error deleting user query: 404: Query with timestamp 20250418161237 not found
|
||||||
|
2025-04-18 18:37:32.259 | ERROR | copywriter:_call_llm_api:139 - Error calling Cohere API:
|
||||||
|
2025-04-18 18:37:32.259 | ERROR | copywriter:_call_llm_api:139 - Error calling Cohere API:
|
||||||
|
2025-04-18 18:38:06.509 | ERROR | copywriter:_call_llm_api:139 - Error calling Cohere API:
|
||||||
|
2025-04-18 18:38:06.509 | ERROR | copywriter:_call_llm_api:139 - Error calling Cohere API:
|
||||||
|
2025-04-18 18:38:48.267 | INFO | copywriter:_generate_headline_suggestions:188 - Generated 3 headline suggestions
|
||||||
|
2025-04-18 18:38:48.267 | INFO | copywriter:_generate_headline_suggestions:188 - Generated 3 headline suggestions
|
||||||
|
2025-04-18 18:38:48.272 | INFO | copywriter:generate_copy:90 - Generated content with 3812 characters
|
||||||
|
2025-04-18 18:38:48.272 | INFO | copywriter:generate_copy:90 - Generated content with 3812 characters
|
||||||
|
2025-04-18 18:38:51.235 | INFO | vector_store:add_documents:131 - Added 1 documents to vector store
|
||||||
|
2025-04-18 18:38:51.235 | INFO | vector_store:add_documents:131 - Added 1 documents to vector store
|
||||||
|
2025-04-18 18:39:59.364 | ERROR | main:get_user_query:437 - Error getting user query: 404: Query with timestamp 20250418173851 not found
|
||||||
|
2025-04-18 18:39:59.364 | ERROR | main:get_user_query:437 - Error getting user query: 404: Query with timestamp 20250418173851 not found
|
||||||
|
2025-04-18 18:40:23.978 | ERROR | main:get_user_query:437 - Error getting user query: 404: Query with timestamp 20250418173851 not found
|
||||||
|
2025-04-18 18:40:23.978 | ERROR | main:get_user_query:437 - Error getting user query: 404: Query with timestamp 20250418173851 not found
|
||||||
|
2025-04-18 18:43:01.011 | INFO | vector_store:add_documents:131 - Added 1 documents to vector store
|
||||||
|
2025-04-18 18:43:01.011 | INFO | vector_store:add_documents:131 - Added 1 documents to vector store
|
||||||
|
2025-04-18 18:43:07.295 | ERROR | main:get_training_data:306 - Error retrieving training data: Column expression, FROM clause, or other columns clause element expected, got [Table('training_data', MetaData(), Column('id', Integer(), table=<training_data>, primary_key=True, nullable=False), Column('content', String(), table=<training_data>, nullable=False), Column('content_type', String(), table=<training_data>, nullable=False), Column('metadata', JSON(), table=<training_data>, nullable=False), Column('added_at', DateTime(), table=<training_data>, nullable=False, default=CallableColumnDefault(<function datetime.utcnow at 0x11abb4fe0>)), Column('is_training_data', Boolean(), table=<training_data>, nullable=False, default=ScalarElementColumnDefault(True)), schema=None)]. Did you mean to say select(Table('training_data', MetaData(), Column('id', Integer(), table=<training_data>, primary_key=True, nullable=False), Column('content', String(), table=<training_data>, nullable=False), Column('content_type', String(), table=<training_data>, nullable=False), Column('metadata', JSON(), table=<training_data>, nullable=False), Column('added_at', DateTime(), table=<training_data>, nullable=False, default=CallableColumnDefault(<function datetime.utcnow at 0x11abb4fe0>)), Column('is_training_data', Boolean(), table=<training_data>, nullable=False, default=ScalarElementColumnDefault(True)), schema=None))?
|
||||||
|
2025-04-18 18:43:07.295 | ERROR | main:get_training_data:306 - Error retrieving training data: Column expression, FROM clause, or other columns clause element expected, got [Table('training_data', MetaData(), Column('id', Integer(), table=<training_data>, primary_key=True, nullable=False), Column('content', String(), table=<training_data>, nullable=False), Column('content_type', String(), table=<training_data>, nullable=False), Column('metadata', JSON(), table=<training_data>, nullable=False), Column('added_at', DateTime(), table=<training_data>, nullable=False, default=CallableColumnDefault(<function datetime.utcnow at 0x11abb4fe0>)), Column('is_training_data', Boolean(), table=<training_data>, nullable=False, default=ScalarElementColumnDefault(True)), schema=None)]. Did you mean to say select(Table('training_data', MetaData(), Column('id', Integer(), table=<training_data>, primary_key=True, nullable=False), Column('content', String(), table=<training_data>, nullable=False), Column('content_type', String(), table=<training_data>, nullable=False), Column('metadata', JSON(), table=<training_data>, nullable=False), Column('added_at', DateTime(), table=<training_data>, nullable=False, default=CallableColumnDefault(<function datetime.utcnow at 0x11abb4fe0>)), Column('is_training_data', Boolean(), table=<training_data>, nullable=False, default=ScalarElementColumnDefault(True)), schema=None))?
|
||||||
|
2025-04-18 18:44:21.955 | INFO | vector_store:add_documents:131 - Added 1 documents to vector store
|
||||||
|
2025-04-18 18:44:21.955 | INFO | vector_store:add_documents:131 - Added 1 documents to vector store
|
||||||
|
2025-04-18 18:44:28.293 | INFO | vector_store:delete_document:256 - Marked document 2 as deleted
|
||||||
|
2025-04-18 18:44:28.293 | INFO | vector_store:delete_document:256 - Marked document 2 as deleted
|
||||||
|
2025-04-18 18:44:32.713 | ERROR | main:get_training_data:306 - Error retrieving training data: Column expression, FROM clause, or other columns clause element expected, got [Table('training_data', MetaData(), Column('id', Integer(), table=<training_data>, primary_key=True, nullable=False), Column('content', String(), table=<training_data>, nullable=False), Column('content_type', String(), table=<training_data>, nullable=False), Column('metadata', JSON(), table=<training_data>, nullable=False), Column('added_at', DateTime(), table=<training_data>, nullable=False, default=CallableColumnDefault(<function datetime.utcnow at 0x11abb4fe0>)), Column('is_training_data', Boolean(), table=<training_data>, nullable=False, default=ScalarElementColumnDefault(True)), schema=None)]. Did you mean to say select(Table('training_data', MetaData(), Column('id', Integer(), table=<training_data>, primary_key=True, nullable=False), Column('content', String(), table=<training_data>, nullable=False), Column('content_type', String(), table=<training_data>, nullable=False), Column('metadata', JSON(), table=<training_data>, nullable=False), Column('added_at', DateTime(), table=<training_data>, nullable=False, default=CallableColumnDefault(<function datetime.utcnow at 0x11abb4fe0>)), Column('is_training_data', Boolean(), table=<training_data>, nullable=False, default=ScalarElementColumnDefault(True)), schema=None))?
|
||||||
|
2025-04-18 18:44:32.713 | ERROR | main:get_training_data:306 - Error retrieving training data: Column expression, FROM clause, or other columns clause element expected, got [Table('training_data', MetaData(), Column('id', Integer(), table=<training_data>, primary_key=True, nullable=False), Column('content', String(), table=<training_data>, nullable=False), Column('content_type', String(), table=<training_data>, nullable=False), Column('metadata', JSON(), table=<training_data>, nullable=False), Column('added_at', DateTime(), table=<training_data>, nullable=False, default=CallableColumnDefault(<function datetime.utcnow at 0x11abb4fe0>)), Column('is_training_data', Boolean(), table=<training_data>, nullable=False, default=ScalarElementColumnDefault(True)), schema=None)]. Did you mean to say select(Table('training_data', MetaData(), Column('id', Integer(), table=<training_data>, primary_key=True, nullable=False), Column('content', String(), table=<training_data>, nullable=False), Column('content_type', String(), table=<training_data>, nullable=False), Column('metadata', JSON(), table=<training_data>, nullable=False), Column('added_at', DateTime(), table=<training_data>, nullable=False, default=CallableColumnDefault(<function datetime.utcnow at 0x11abb4fe0>)), Column('is_training_data', Boolean(), table=<training_data>, nullable=False, default=ScalarElementColumnDefault(True)), schema=None))?
|
||||||
|
2025-04-18 18:47:11.756 | INFO | vector_store:add_documents:131 - Added 1 documents to vector store
|
||||||
|
2025-04-18 18:47:11.756 | INFO | vector_store:add_documents:131 - Added 1 documents to vector store
|
||||||
|
2025-04-18 18:47:12.100 | INFO | vector_store:add_documents:131 - Added 1 documents to vector store
|
||||||
|
2025-04-18 18:47:12.100 | INFO | vector_store:add_documents:131 - Added 1 documents to vector store
|
||||||
|
2025-04-18 18:47:21.198 | ERROR | main:get_training_data:306 - Error retrieving training data: Column expression, FROM clause, or other columns clause element expected, got [Table('training_data', MetaData(), Column('id', Integer(), table=<training_data>, primary_key=True, nullable=False), Column('content', String(), table=<training_data>, nullable=False), Column('content_type', String(), table=<training_data>, nullable=False), Column('metadata', JSON(), table=<training_data>, nullable=False), Column('added_at', DateTime(), table=<training_data>, nullable=False, default=CallableColumnDefault(<function datetime.utcnow at 0x11abb4fe0>)), Column('is_training_data', Boolean(), table=<training_data>, nullable=False, default=ScalarElementColumnDefault(True)), schema=None)]. Did you mean to say select(Table('training_data', MetaData(), Column('id', Integer(), table=<training_data>, primary_key=True, nullable=False), Column('content', String(), table=<training_data>, nullable=False), Column('content_type', String(), table=<training_data>, nullable=False), Column('metadata', JSON(), table=<training_data>, nullable=False), Column('added_at', DateTime(), table=<training_data>, nullable=False, default=CallableColumnDefault(<function datetime.utcnow at 0x11abb4fe0>)), Column('is_training_data', Boolean(), table=<training_data>, nullable=False, default=ScalarElementColumnDefault(True)), schema=None))?
|
||||||
|
2025-04-18 18:47:21.198 | ERROR | main:get_training_data:306 - Error retrieving training data: Column expression, FROM clause, or other columns clause element expected, got [Table('training_data', MetaData(), Column('id', Integer(), table=<training_data>, primary_key=True, nullable=False), Column('content', String(), table=<training_data>, nullable=False), Column('content_type', String(), table=<training_data>, nullable=False), Column('metadata', JSON(), table=<training_data>, nullable=False), Column('added_at', DateTime(), table=<training_data>, nullable=False, default=CallableColumnDefault(<function datetime.utcnow at 0x11abb4fe0>)), Column('is_training_data', Boolean(), table=<training_data>, nullable=False, default=ScalarElementColumnDefault(True)), schema=None)]. Did you mean to say select(Table('training_data', MetaData(), Column('id', Integer(), table=<training_data>, primary_key=True, nullable=False), Column('content', String(), table=<training_data>, nullable=False), Column('content_type', String(), table=<training_data>, nullable=False), Column('metadata', JSON(), table=<training_data>, nullable=False), Column('added_at', DateTime(), table=<training_data>, nullable=False, default=CallableColumnDefault(<function datetime.utcnow at 0x11abb4fe0>)), Column('is_training_data', Boolean(), table=<training_data>, nullable=False, default=ScalarElementColumnDefault(True)), schema=None))?
|
||||||
|
2025-04-18 18:48:22.623 | INFO | copywriter:_generate_headline_suggestions:188 - Generated 3 headline suggestions
|
||||||
|
2025-04-18 18:48:22.623 | INFO | copywriter:_generate_headline_suggestions:188 - Generated 3 headline suggestions
|
||||||
|
2025-04-18 18:48:22.636 | INFO | copywriter:generate_copy:90 - Generated content with 1802 characters
|
||||||
|
2025-04-18 18:48:22.636 | INFO | copywriter:generate_copy:90 - Generated content with 1802 characters
|
||||||
|
2025-04-18 18:48:23.411 | INFO | vector_store:add_documents:131 - Added 1 documents to vector store
|
||||||
|
2025-04-18 18:48:23.411 | INFO | vector_store:add_documents:131 - Added 1 documents to vector store
|
||||||
|
2025-04-18 18:48:46.063 | ERROR | main:delete_user_query:461 - Error deleting user query: 404: Query with timestamp 20250418153242 not found
|
||||||
|
2025-04-18 18:48:46.063 | ERROR | main:delete_user_query:461 - Error deleting user query: 404: Query with timestamp 20250418153242 not found
|
||||||
|
2025-04-18 18:49:03.774 | INFO | vector_store:delete_document:256 - Marked document 3 as deleted
|
||||||
|
2025-04-18 18:49:03.774 | INFO | vector_store:delete_document:256 - Marked document 3 as deleted
|
||||||
|
2025-04-18 18:49:06.850 | INFO | vector_store:delete_document:256 - Marked document 2 as deleted
|
||||||
|
2025-04-18 18:49:06.850 | INFO | vector_store:delete_document:256 - Marked document 2 as deleted
|
||||||
|
2025-04-18 18:49:11.443 | INFO | vector_store:delete_document:256 - Marked document 1 as deleted
|
||||||
|
2025-04-18 18:49:11.443 | INFO | vector_store:delete_document:256 - Marked document 1 as deleted
|
||||||
|
2025-04-18 18:51:53.774 | ERROR | main:add_training_data:214 - Error adding training data: no such table: training_data
|
||||||
|
2025-04-18 18:51:53.774 | ERROR | main:add_training_data:214 - Error adding training data: no such table: training_data
|
||||||
|
2025-04-18 18:52:03.005 | ERROR | main:add_training_data:214 - Error adding training data: no such table: training_data
|
||||||
|
2025-04-18 18:52:03.005 | ERROR | main:add_training_data:214 - Error adding training data: no such table: training_data
|
||||||
|
2025-04-18 18:52:49.739 | INFO | vector_store:add_documents:131 - Added 1 documents to vector store
|
||||||
|
2025-04-18 18:52:49.739 | INFO | vector_store:add_documents:131 - Added 1 documents to vector store
|
||||||
|
2025-04-18 18:52:55.266 | INFO | vector_store:delete_document:256 - Marked document 1 as deleted
|
||||||
|
2025-04-18 18:52:55.266 | INFO | vector_store:delete_document:256 - Marked document 1 as deleted
|
||||||
|
2025-04-18 18:56:30.386 | INFO | copywriter:_generate_headline_suggestions:188 - Generated 3 headline suggestions
|
||||||
|
2025-04-18 18:56:30.386 | INFO | copywriter:_generate_headline_suggestions:188 - Generated 3 headline suggestions
|
||||||
|
2025-04-18 18:56:30.388 | INFO | copywriter:generate_copy:90 - Generated content with 1580 characters
|
||||||
|
2025-04-18 18:56:30.388 | INFO | copywriter:generate_copy:90 - Generated content with 1580 characters
|
||||||
|
2025-04-18 18:56:30.997 | INFO | vector_store:add_documents:131 - Added 1 documents to vector store
|
||||||
|
2025-04-18 18:56:30.997 | INFO | vector_store:add_documents:131 - Added 1 documents to vector store
|
||||||
|
2025-04-18 18:57:15.893 | INFO | vector_store:add_documents:131 - Added 1 documents to vector store
|
||||||
|
2025-04-18 18:57:15.893 | INFO | vector_store:add_documents:131 - Added 1 documents to vector store
|
||||||
|
2025-04-18 18:58:37.976 | INFO | copywriter:_generate_headline_suggestions:188 - Generated 3 headline suggestions
|
||||||
|
2025-04-18 18:58:37.976 | INFO | copywriter:_generate_headline_suggestions:188 - Generated 3 headline suggestions
|
||||||
|
2025-04-18 18:58:37.980 | INFO | copywriter:generate_copy:90 - Generated content with 579 characters
|
||||||
|
2025-04-18 18:58:37.980 | INFO | copywriter:generate_copy:90 - Generated content with 579 characters
|
||||||
|
2025-04-18 18:58:38.798 | INFO | vector_store:add_documents:131 - Added 1 documents to vector store
|
||||||
|
2025-04-18 18:58:38.798 | INFO | vector_store:add_documents:131 - Added 1 documents to vector store
|
||||||
|
2025-04-18 18:59:13.625 | INFO | copywriter:improve_copy:224 - Improved content based on feedback
|
||||||
|
2025-04-18 18:59:13.625 | INFO | copywriter:improve_copy:224 - Improved content based on feedback
|
||||||
|
2025-04-18 18:59:58.642 | INFO | brand_style:update_style_guidelines:178 - Updated brand style guidelines
|
||||||
|
2025-04-18 18:59:58.642 | INFO | brand_style:update_style_guidelines:178 - Updated brand style guidelines
|
||||||
|
2025-04-18 19:00:09.875 | INFO | brand_style:update_style_guidelines:178 - Updated brand style guidelines
|
||||||
|
2025-04-18 19:00:09.875 | INFO | brand_style:update_style_guidelines:178 - Updated brand style guidelines
|
||||||
|
2025-04-18 19:00:20.643 | INFO | brand_style:update_style_guidelines:178 - Updated brand style guidelines
|
||||||
|
2025-04-18 19:00:20.643 | INFO | brand_style:update_style_guidelines:178 - Updated brand style guidelines
|
||||||
|
2025-04-18 19:05:10.093 | INFO | brand_style:update_style_guidelines:178 - Updated brand style guidelines
|
||||||
|
2025-04-18 19:05:10.093 | INFO | brand_style:update_style_guidelines:178 - Updated brand style guidelines
|
||||||
|
|||||||
Reference in New Issue
Block a user