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:
Michael Ikehi
2025-04-18 17:57:35 +01:00
parent cc2b230f62
commit 12e0830ba6
11 changed files with 277 additions and 61 deletions
View File
Binary file not shown.
Binary file not shown.
+141 -56
View File
@@ -5,6 +5,7 @@ Provides API endpoints for generating and managing marketing content.
import os import os
import json import json
import glob
from typing import Dict, List, Any, Optional from typing import Dict, List, Any, Optional
from datetime import datetime from datetime import datetime
from pathlib import Path from pathlib import Path
@@ -13,12 +14,15 @@ from fastapi.middleware.cors import CORSMiddleware
from fastapi.responses import JSONResponse from fastapi.responses import JSONResponse
from loguru import logger from loguru import logger
from pydantic import BaseModel, Field from pydantic import BaseModel, Field
from sqlalchemy import select, desc, func
from sqlalchemy.sql import Select
import config import config
from copywriter import copywriter from copywriter import copywriter
from vector_store import vector_store from vector_store import vector_store
from brand_style import brand_style_manager from brand_style import brand_style_manager
from embeddings import embeddings_manager from embeddings import embeddings_manager
from models import database, training_data
# Initialize logging # Initialize logging
logger.add(config.LOG_FILE, level=config.LOG_LEVEL, rotation="10 MB", retention="1 month") logger.add(config.LOG_FILE, level=config.LOG_LEVEL, rotation="10 MB", retention="1 month")
@@ -182,30 +186,29 @@ async def add_training_data(request: TrainingDataRequest):
} }
) )
# Add metadata # Prepare metadata
metadata = request.metadata.copy() metadata = request.metadata.copy()
metadata["content_type"] = request.content_type metadata["content_type"] = request.content_type
metadata["added_at"] = datetime.now().isoformat() metadata["added_at"] = datetime.now().isoformat()
metadata["training_data"] = True metadata["training_data"] = True
# Add to vector store # Add to database
doc_ids = await vector_store.add_documents([request.content], [metadata]) query = training_data.insert().values(
content=request.content,
content_type=request.content_type,
metadata=metadata,
added_at=datetime.now(),
is_training_data=True
)
data_id = await database.execute(query)
# Save to past campaigns # Add to vector store for search functionality
campaign_path = Path(config.DATA_DIR) / "past_campaigns" / f"{datetime.now().strftime('%Y%m%d%H%M%S')}.json" doc_ids = await vector_store.add_documents([request.content], [metadata])
with open(campaign_path, 'w') as f:
json.dump({
"content": request.content,
"content_type": request.content_type,
"metadata": metadata,
"document_id": doc_ids[0] if doc_ids else None,
"timestamp": datetime.now().isoformat()
}, f, indent=2)
return { return {
"status": "success", "status": "success",
"message": "Training data added successfully", "message": "Training data added successfully",
"data_id": doc_ids[0] if doc_ids else None "data_id": data_id
} }
except Exception as e: except Exception as e:
logger.error(f"Error adding training data: {str(e)}") logger.error(f"Error adding training data: {str(e)}")
@@ -222,8 +225,9 @@ async def list_training_data(
): ):
"""Retrieve a list of available training data.""" """Retrieve a list of available training data."""
try: try:
# Build filters # Build base query
filters = {} base_query = select(training_data).where(training_data.c.is_training_data == True)
if content_type: if content_type:
if content_type not in config.CONTENT_TYPES: if content_type not in config.CONTENT_TYPES:
return JSONResponse( return JSONResponse(
@@ -233,38 +237,31 @@ async def list_training_data(
"message": f"Invalid content_type. Must be one of: {', '.join(config.CONTENT_TYPES)}" "message": f"Invalid content_type. Must be one of: {', '.join(config.CONTENT_TYPES)}"
} }
) )
filters["content_type"] = content_type base_query = base_query.where(training_data.c.content_type == content_type)
filters["training_data"] = True # Count total records
count_query = select(func.count()).select_from(training_data).where(training_data.c.is_training_data == True)
if content_type:
count_query = count_query.where(training_data.c.content_type == content_type)
total = await database.fetch_val(count_query)
# Fetch all matching documents first (not efficient for large datasets but works for demo) # Add pagination
all_docs = [] query = base_query.order_by(training_data.c.added_at.desc()) \
for i in range(len(vector_store.metadata)): .offset((page - 1) * limit) \
doc = await vector_store.get_document(i) .limit(limit)
if doc and all(doc["metadata"].get(k) == v for k, v in filters.items()):
all_docs.append(doc)
# Sort by timestamp (newest first) # Execute query
all_docs.sort(key=lambda x: x["metadata"].get("added_at", ""), reverse=True) records = await database.fetch_all(query)
# Paginate # Format response
total = len(all_docs)
pages = (total + limit - 1) // limit if total > 0 else 1
start = (page - 1) * limit
end = start + limit
paginated_docs = all_docs[start:end]
# Format the response
items = [] items = []
for doc in paginated_docs: for record in records:
# Get a preview of the text (first 100 characters) preview = record["content"][:100] + "..." if len(record["content"]) > 100 else record["content"]
preview = doc["text"][:100] + "..." if len(doc["text"]) > 100 else doc["text"]
items.append({ items.append({
"id": doc["document_id"], "id": record["id"],
"content_type": doc["metadata"].get("content_type", "unknown"), "content_type": record["content_type"],
"preview": preview, "preview": preview,
"added_at": doc["metadata"].get("added_at", "") "added_at": record["added_at"].isoformat()
}) })
return { return {
@@ -273,7 +270,7 @@ async def list_training_data(
"total": total, "total": total,
"page": page, "page": page,
"limit": limit, "limit": limit,
"pages": pages "pages": (total + limit - 1) // limit
} }
} }
except Exception as e: except Exception as e:
@@ -283,21 +280,25 @@ async def list_training_data(
detail=f"Failed to list training data: {str(e)}" detail=f"Failed to list training data: {str(e)}"
) )
@app.get("/training-data/{document_id}") @app.get("/training-data/{data_id}")
async def get_training_data(document_id: int): async def get_training_data(data_id: int):
"""Retrieve a specific training document by ID.""" """Retrieve a specific training document by ID."""
try: try:
doc = await vector_store.get_document(document_id) query = select([training_data]).where(training_data.c.id == data_id)
if not doc: record = await database.fetch_one(query)
if not record:
raise HTTPException( raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND, status_code=status.HTTP_404_NOT_FOUND,
detail=f"Document with ID {document_id} not found" detail=f"Document with ID {data_id} not found"
) )
return { return {
"id": doc["document_id"], "id": record["id"],
"content": doc["text"], "content": record["content"],
"metadata": doc["metadata"] "content_type": record["content_type"],
"metadata": record["metadata"],
"added_at": record["added_at"].isoformat()
} }
except HTTPException: except HTTPException:
raise raise
@@ -308,20 +309,25 @@ async def get_training_data(document_id: int):
detail=f"Failed to retrieve training data: {str(e)}" detail=f"Failed to retrieve training data: {str(e)}"
) )
@app.delete("/training-data/{document_id}") @app.delete("/training-data/{data_id}")
async def delete_training_data(document_id: int): async def delete_training_data(data_id: int):
"""Delete a specific training document by ID.""" """Delete a specific training document by ID."""
try: try:
success = await vector_store.delete_document(document_id) query = training_data.delete().where(training_data.c.id == data_id)
if not success: result = await database.execute(query)
if not result:
raise HTTPException( raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND, status_code=status.HTTP_404_NOT_FOUND,
detail=f"Document with ID {document_id} not found or could not be deleted" detail=f"Document with ID {data_id} not found or could not be deleted"
) )
# Also remove from vector store
await vector_store.delete_document(data_id)
return { return {
"status": "success", "status": "success",
"message": f"Document with ID {document_id} successfully deleted" "message": f"Document with ID {data_id} successfully deleted"
} }
except HTTPException: except HTTPException:
raise raise
@@ -371,6 +377,85 @@ async def analyze_content(content: str = Body(..., embed=True)):
detail=f"Failed to analyze content: {str(e)}" detail=f"Failed to analyze content: {str(e)}"
) )
@app.get("/user-queries")
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."""
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
# Apply pagination
start_idx = (page - 1) * limit
end_idx = start_idx + limit
page_files = query_files[start_idx:end_idx]
items = []
for file_path in page_files:
with open(file_path, 'r') as f:
query_data = json.load(f)
items.append(query_data)
return {
"items": items,
"total": len(query_files),
"page": page,
"limit": limit
}
except Exception as e:
logger.error(f"Error listing user queries: {str(e)}")
raise HTTPException(
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
detail=f"Failed to list user queries: {str(e)}"
)
@app.get("/user-queries/{timestamp}")
async def get_user_query(timestamp: str):
"""Retrieve a specific user query by timestamp."""
try:
file_path = Path(config.DATA_DIR) / "user_queries" / f"{timestamp}.json"
if not file_path.exists():
raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND,
detail=f"Query with timestamp {timestamp} not found"
)
with open(file_path, 'r') as f:
return json.load(f)
except Exception as e:
logger.error(f"Error getting user query: {str(e)}")
raise HTTPException(
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
detail=f"Failed to get user query: {str(e)}"
)
@app.delete("/user-queries/{timestamp}")
async def delete_user_query(timestamp: str):
"""Delete a specific user query by timestamp."""
try:
file_path = Path(config.DATA_DIR) / "user_queries" / f"{timestamp}.json"
if not file_path.exists():
raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND,
detail=f"Query with timestamp {timestamp} not found"
)
file_path.unlink() # Delete the file
return {
"status": "success",
"message": f"Query with timestamp {timestamp} successfully deleted"
}
except Exception as e:
logger.error(f"Error deleting user query: {str(e)}")
raise HTTPException(
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
detail=f"Failed to delete user query: {str(e)}"
)
# Run the application # Run the application
if __name__ == "__main__": if __name__ == "__main__":
import uvicorn import uvicorn
+23
View File
@@ -0,0 +1,23 @@
from datetime import datetime
from sqlalchemy import Column, Integer, String, JSON, DateTime, Boolean, MetaData, Table, create_engine
from databases import Database
from config import DATA_DIR
DATABASE_URL = f"sqlite:///{DATA_DIR}/training_data.db"
database = Database(DATABASE_URL)
metadata = MetaData()
training_data = Table(
"training_data",
metadata,
Column("id", Integer, primary_key=True),
Column("content", String, nullable=False),
Column("content_type", String, nullable=False),
Column("metadata", JSON, nullable=False),
Column("added_at", DateTime, nullable=False, default=datetime.utcnow),
Column("is_training_data", Boolean, nullable=False, default=True)
)
# Create tables
engine = create_engine(DATABASE_URL)
metadata.create_all(engine)
Binary file not shown.
Binary file not shown.
Binary file not shown.
+1 -1
View File
@@ -507,5 +507,5 @@ 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 = 'Write a social media post about our new coaching program'; document.getElementById('prompt').value = 'Generate an email campaign for a product launch';
}); });
+3 -3
View File
@@ -48,7 +48,7 @@
<div class="generation-form"> <div class="generation-form">
<div class="form-group"> <div class="form-group">
<label for="prompt">What would you like to create?</label> <label for="prompt">What would you like to create?</label>
<textarea id="prompt" placeholder="e.g., Write a social media post for our new coaching program launch" rows="4"></textarea> <textarea id="prompt" placeholder="e.g., Generate an email campaign for a product launch" rows="4"></textarea>
</div> </div>
<div class="form-row"> <div class="form-row">
@@ -84,12 +84,12 @@
<div class="form-group checkbox-group"> <div class="form-group checkbox-group">
<label class="checkbox"> <label class="checkbox">
<input type="checkbox" id="include-cta" checked> <input type="checkbox" id="include-cta" unchecked>
<span class="checkmark"></span> <span class="checkmark"></span>
Include Call to Action Include Call to Action
</label> </label>
<label class="checkbox"> <label class="checkbox">
<input type="checkbox" id="reference-similar" checked> <input type="checkbox" id="reference-similar" unchecked>
<span class="checkmark"></span> <span class="checkmark"></span>
Reference Similar Content Reference Similar Content
</label> </label>
+108
View File
@@ -774,3 +774,111 @@
2025-04-18 04:22:36.139 | INFO | copywriter:generate_copy:90 - Generated content with 3409 characters 2025-04-18 04:22:36.139 | INFO | copywriter:generate_copy:90 - Generated content with 3409 characters
2025-04-18 04:22:36.945 | INFO | vector_store:add_documents:131 - Added 1 documents to vector store 2025-04-18 04:22:36.945 | INFO | vector_store:add_documents:131 - Added 1 documents to vector store
2025-04-18 04:22:36.945 | INFO | vector_store:add_documents:131 - Added 1 documents to vector store 2025-04-18 04:22:36.945 | INFO | vector_store:add_documents:131 - Added 1 documents to vector store
2025-04-18 16:07:13.340 | ERROR | main:list_training_data:268 - Error listing 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 0x11a56cea0>)), 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 0x11a56cea0>)), Column('is_training_data', Boolean(), table=<training_data>, nullable=False, default=ScalarElementColumnDefault(True)), schema=None))?
2025-04-18 16:07:13.340 | ERROR | main:list_training_data:268 - Error listing 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 0x11a56cea0>)), 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 0x11a56cea0>)), Column('is_training_data', Boolean(), table=<training_data>, nullable=False, default=ScalarElementColumnDefault(True)), schema=None))?
2025-04-18 16:08:07.769 | INFO | copywriter:_generate_headline_suggestions:188 - Generated 3 headline suggestions
2025-04-18 16:08:07.769 | INFO | copywriter:_generate_headline_suggestions:188 - Generated 3 headline suggestions
2025-04-18 16:08:07.772 | INFO | copywriter:generate_copy:90 - Generated content with 651 characters
2025-04-18 16:08:07.772 | INFO | copywriter:generate_copy:90 - Generated content with 651 characters
2025-04-18 16:08:09.329 | INFO | vector_store:add_documents:131 - Added 1 documents to vector store
2025-04-18 16:08:09.329 | INFO | vector_store:add_documents:131 - Added 1 documents to vector store
2025-04-18 16:08:47.520 | ERROR | main:list_training_data:268 - Error listing 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 0x11a56cea0>)), 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 0x11a56cea0>)), Column('is_training_data', Boolean(), table=<training_data>, nullable=False, default=ScalarElementColumnDefault(True)), schema=None))?
2025-04-18 16:08:47.520 | ERROR | main:list_training_data:268 - Error listing 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 0x11a56cea0>)), 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 0x11a56cea0>)), Column('is_training_data', Boolean(), table=<training_data>, nullable=False, default=ScalarElementColumnDefault(True)), schema=None))?
2025-04-18 16:09:56.223 | INFO | vector_store:add_documents:131 - Added 1 documents to vector store
2025-04-18 16:09:56.223 | INFO | vector_store:add_documents:131 - Added 1 documents to vector store
2025-04-18 16:10:00.678 | ERROR | main:list_training_data:268 - Error listing 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 0x11a56cea0>)), 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 0x11a56cea0>)), Column('is_training_data', Boolean(), table=<training_data>, nullable=False, default=ScalarElementColumnDefault(True)), schema=None))?
2025-04-18 16:10:00.678 | ERROR | main:list_training_data:268 - Error listing 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 0x11a56cea0>)), 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 0x11a56cea0>)), Column('is_training_data', Boolean(), table=<training_data>, nullable=False, default=ScalarElementColumnDefault(True)), schema=None))?
2025-04-18 16:14:28.677 | ERROR | main:list_training_data:268 - Error listing 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 0x110e7cea0>)), 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 0x110e7cea0>)), Column('is_training_data', Boolean(), table=<training_data>, nullable=False, default=ScalarElementColumnDefault(True)), schema=None))?
2025-04-18 16:14:28.677 | ERROR | main:list_training_data:268 - Error listing 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 0x110e7cea0>)), 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 0x110e7cea0>)), Column('is_training_data', Boolean(), table=<training_data>, nullable=False, default=ScalarElementColumnDefault(True)), schema=None))?
2025-04-18 16:16:04.245 | ERROR | main:list_training_data:268 - Error listing 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 0x110e7cea0>)), 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 0x110e7cea0>)), Column('is_training_data', Boolean(), table=<training_data>, nullable=False, default=ScalarElementColumnDefault(True)), schema=None))?
2025-04-18 16:16:04.245 | ERROR | main:list_training_data:268 - Error listing 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 0x110e7cea0>)), 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 0x110e7cea0>)), Column('is_training_data', Boolean(), table=<training_data>, nullable=False, default=ScalarElementColumnDefault(True)), schema=None))?
2025-04-18 16:19:37.169 | INFO | vector_store:add_documents:131 - Added 1 documents to vector store
2025-04-18 16:19:37.169 | INFO | vector_store:add_documents:131 - Added 1 documents to vector store
2025-04-18 16:22:09.127 | WARNING | vector_store:delete_document:246 - Invalid document ID: 1
2025-04-18 16:22:09.127 | WARNING | vector_store:delete_document:246 - Invalid document ID: 1
2025-04-18 16:22:27.719 | WARNING | vector_store:delete_document:246 - Invalid document ID: 2
2025-04-18 16:22:27.719 | WARNING | vector_store:delete_document:246 - Invalid document ID: 2
2025-04-18 16:30:22.904 | INFO | vector_store:search:212 - Found 1 matching documents for query
2025-04-18 16:30:22.904 | INFO | vector_store:search:212 - Found 1 matching documents for query
2025-04-18 16:30:31.859 | INFO | copywriter:_generate_headline_suggestions:188 - Generated 3 headline suggestions
2025-04-18 16:30:31.859 | INFO | copywriter:_generate_headline_suggestions:188 - Generated 3 headline suggestions
2025-04-18 16:30:31.859 | INFO | copywriter:generate_copy:90 - Generated content with 604 characters
2025-04-18 16:30:31.859 | INFO | copywriter:generate_copy:90 - Generated content with 604 characters
2025-04-18 16:30:32.289 | INFO | vector_store:add_documents:131 - Added 1 documents to vector store
2025-04-18 16:30:32.289 | INFO | vector_store:add_documents:131 - Added 1 documents to vector store
2025-04-18 16:32:33.304 | INFO | vector_store:search:212 - Found 2 matching documents for query
2025-04-18 16:32:33.304 | INFO | vector_store:search:212 - Found 2 matching documents for query
2025-04-18 16:32:42.281 | INFO | copywriter:_generate_headline_suggestions:188 - Generated 3 headline suggestions
2025-04-18 16:32:42.281 | INFO | copywriter:_generate_headline_suggestions:188 - Generated 3 headline suggestions
2025-04-18 16:32:42.283 | INFO | copywriter:generate_copy:90 - Generated content with 632 characters
2025-04-18 16:32:42.283 | INFO | copywriter:generate_copy:90 - Generated content with 632 characters
2025-04-18 16:32:42.750 | INFO | vector_store:add_documents:131 - Added 1 documents to vector store
2025-04-18 16:32:42.750 | INFO | vector_store:add_documents:131 - Added 1 documents to vector store
2025-04-18 17:12:33.909 | INFO | copywriter:_generate_headline_suggestions:188 - Generated 3 headline suggestions
2025-04-18 17:12:33.909 | INFO | copywriter:_generate_headline_suggestions:188 - Generated 3 headline suggestions
2025-04-18 17:12:33.912 | INFO | copywriter:generate_copy:90 - Generated content with 2740 characters
2025-04-18 17:12:33.912 | INFO | copywriter:generate_copy:90 - Generated content with 2740 characters
2025-04-18 17:12:37.538 | INFO | vector_store:add_documents:131 - Added 1 documents to vector store
2025-04-18 17:12:37.538 | INFO | vector_store:add_documents:131 - Added 1 documents to vector store
2025-04-18 17:13:04.600 | INFO | vector_store:search:212 - Found 3 matching documents for query
2025-04-18 17:13:04.600 | INFO | vector_store:search:212 - Found 3 matching documents for query
2025-04-18 17:13:17.051 | INFO | copywriter:_generate_headline_suggestions:188 - Generated 3 headline suggestions
2025-04-18 17:13:17.051 | INFO | copywriter:_generate_headline_suggestions:188 - Generated 3 headline suggestions
2025-04-18 17:13:17.052 | INFO | copywriter:generate_copy:90 - Generated content with 577 characters
2025-04-18 17:13:17.052 | INFO | copywriter:generate_copy:90 - Generated content with 577 characters
2025-04-18 17:13:17.652 | INFO | vector_store:add_documents:131 - Added 1 documents to vector store
2025-04-18 17:13:17.652 | INFO | vector_store:add_documents:131 - Added 1 documents to vector store
2025-04-18 17:14:08.039 | INFO | vector_store:search:212 - Found 3 matching documents for query
2025-04-18 17:14:08.039 | INFO | vector_store:search:212 - Found 3 matching documents for query
2025-04-18 17:14:33.729 | INFO | copywriter:_generate_headline_suggestions:188 - Generated 3 headline suggestions
2025-04-18 17:14:33.729 | INFO | copywriter:_generate_headline_suggestions:188 - Generated 3 headline suggestions
2025-04-18 17:14:33.741 | INFO | copywriter:generate_copy:90 - Generated content with 1717 characters
2025-04-18 17:14:33.741 | INFO | copywriter:generate_copy:90 - Generated content with 1717 characters
2025-04-18 17:14:34.184 | INFO | vector_store:add_documents:131 - Added 1 documents to vector store
2025-04-18 17:14:34.184 | INFO | vector_store:add_documents:131 - Added 1 documents to vector store
2025-04-18 17:28:38.798 | INFO | copywriter:_generate_headline_suggestions:188 - Generated 3 headline suggestions
2025-04-18 17:28:38.798 | INFO | copywriter:_generate_headline_suggestions:188 - Generated 3 headline suggestions
2025-04-18 17:28:38.800 | INFO | copywriter:generate_copy:90 - Generated content with 1962 characters
2025-04-18 17:28:38.800 | INFO | copywriter:generate_copy:90 - Generated content with 1962 characters
2025-04-18 17:28:39.569 | INFO | vector_store:add_documents:131 - Added 1 documents to vector store
2025-04-18 17:28:39.569 | INFO | vector_store:add_documents:131 - Added 1 documents to vector store
2025-04-18 17:29:13.466 | INFO | vector_store:search:212 - Found 3 matching documents for query
2025-04-18 17:29:13.466 | INFO | vector_store:search:212 - Found 3 matching documents for query
2025-04-18 17:29:30.844 | INFO | copywriter:_generate_headline_suggestions:188 - Generated 3 headline suggestions
2025-04-18 17:29:30.844 | INFO | copywriter:_generate_headline_suggestions:188 - Generated 3 headline suggestions
2025-04-18 17:29:30.845 | INFO | copywriter:generate_copy:90 - Generated content with 1416 characters
2025-04-18 17:29:30.845 | INFO | copywriter:generate_copy:90 - Generated content with 1416 characters
2025-04-18 17:29:31.237 | INFO | vector_store:add_documents:131 - Added 1 documents to vector store
2025-04-18 17:29:31.237 | INFO | vector_store:add_documents:131 - Added 1 documents to vector store
2025-04-18 17:31:18.658 | ERROR | main:get_user_query:429 - Error getting user query: 404: Query with timestamp 2025-04-18T16:32:42.751434 not found
2025-04-18 17:31:18.658 | ERROR | main:get_user_query:429 - Error getting user query: 404: Query with timestamp 2025-04-18T16:32:42.751434 not found
2025-04-18 17:31:40.805 | ERROR | main:get_user_query:429 - Error getting user query: 404: Query with timestamp "2025-04-18T16:32:42.751434" not found
2025-04-18 17:31:40.805 | ERROR | main:get_user_query:429 - Error getting user query: 404: Query with timestamp "2025-04-18T16:32:42.751434" not found
2025-04-18 17:32:24.114 | ERROR | main:get_user_query:429 - Error getting user query: 404: Query with timestamp 2025-04-18T17:12:37.541148 not found
2025-04-18 17:32:24.114 | ERROR | main:get_user_query:429 - Error getting user query: 404: Query with timestamp 2025-04-18T17:12:37.541148 not found
2025-04-18 17:34:18.118 | ERROR | main:get_user_query:429 - Error getting user query: 404: Query with timestamp 20250418T171237541148 not found
2025-04-18 17:34:18.118 | ERROR | main:get_user_query:429 - Error getting user query: 404: Query with timestamp 20250418T171237541148 not found
2025-04-18 17:34:40.614 | ERROR | main:get_user_query:429 - Error getting user query: 404: Query with timestamp 20250418T035226 not found
2025-04-18 17:34:40.614 | ERROR | main:get_user_query:429 - Error getting user query: 404: Query with timestamp 20250418T035226 not found
2025-04-18 17:35:36.594 | ERROR | main:get_user_query:429 - Error getting user query: 404: Query with timestamp 20250418171317. not found
2025-04-18 17:35:36.594 | ERROR | main:get_user_query:429 - Error getting user query: 404: Query with timestamp 20250418171317. not found
2025-04-18 17:36:13.263 | ERROR | main:get_user_query:429 - Error getting user query: 404: Query with timestamp 2025-04-18T17:13:17 not found
2025-04-18 17:36:13.263 | ERROR | main:get_user_query:429 - Error getting user query: 404: Query with timestamp 2025-04-18T17:13:17 not found
2025-04-18 17:36:23.158 | ERROR | main:get_user_query:429 - Error getting user query: 404: Query with timestamp 20250418T171317 not found
2025-04-18 17:36:23.158 | ERROR | main:get_user_query:429 - Error getting user query: 404: Query with timestamp 20250418T171317 not found
2025-04-18 17:40:24.503 | INFO | vector_store:search:212 - Found 3 matching documents for query
2025-04-18 17:40:24.503 | INFO | vector_store:search:212 - Found 3 matching documents for query
2025-04-18 17:40:54.614 | INFO | copywriter:_generate_headline_suggestions:188 - Generated 3 headline suggestions
2025-04-18 17:40:54.614 | INFO | copywriter:_generate_headline_suggestions:188 - Generated 3 headline suggestions
2025-04-18 17:40:54.615 | INFO | copywriter:generate_copy:90 - Generated content with 2080 characters
2025-04-18 17:40:54.615 | INFO | copywriter:generate_copy:90 - Generated content with 2080 characters
2025-04-18 17:40:55.135 | INFO | vector_store:add_documents:131 - Added 1 documents to vector store
2025-04-18 17:40:55.135 | INFO | vector_store:add_documents:131 - Added 1 documents to vector store
2025-04-18 17:41:16.349 | INFO | vector_store:search:212 - Found 3 matching documents for query
2025-04-18 17:41:16.349 | INFO | vector_store:search:212 - Found 3 matching documents for query
2025-04-18 17:41:55.042 | INFO | copywriter:_generate_headline_suggestions:188 - Generated 3 headline suggestions
2025-04-18 17:41:55.042 | INFO | copywriter:_generate_headline_suggestions:188 - Generated 3 headline suggestions
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