Refactor BrandStyleManager and MarketingCopywriter to accept dependencies via constructor, enhancing testability. Update main.py to manage campaign data with new routes for saving, editing, and deleting campaigns. Improve index.html with a textarea for editing generated copy and a button for saving changes. Add functionality to view past campaigns.
This commit is contained in:
+8
-21
@@ -8,10 +8,10 @@ from vector_store import VectorStore
|
||||
from config import settings
|
||||
|
||||
class BrandStyleManager:
|
||||
def __init__(self):
|
||||
def __init__(self, embeddings: CohereEmbeddings, vector_store: VectorStore):
|
||||
self.settings = settings
|
||||
self.embeddings = CohereEmbeddings()
|
||||
self.vector_store = VectorStore()
|
||||
self.embeddings = embeddings
|
||||
self.vector_store = vector_store
|
||||
self.brand_voice = self._load_brand_voice()
|
||||
self.sample_campaigns = self._load_sample_campaigns()
|
||||
|
||||
@@ -91,11 +91,11 @@ class BrandStyleManager:
|
||||
results = self.vector_store.search(prompt_embedding, k=k)
|
||||
|
||||
# Optionally rerank results
|
||||
if results:
|
||||
texts = [result["text"] for result in results]
|
||||
reranked = self.embeddings.rerank_results(prompt, texts, top_n=k)
|
||||
# Convert reranked results to the expected format
|
||||
return [{"text": text} for text in reranked]
|
||||
# if results:
|
||||
# texts = [result["text"] for result in results]
|
||||
# reranked = self.embeddings.rerank_results(prompt, texts, top_n=k)
|
||||
# # Convert reranked results to the expected format
|
||||
# return [{"text": text} for text in reranked]
|
||||
|
||||
# If no results, return empty list
|
||||
return []
|
||||
@@ -139,16 +139,3 @@ class BrandStyleManager:
|
||||
else:
|
||||
print(f"No content extracted from {pdf_path}")
|
||||
|
||||
# # Example usage
|
||||
# if __name__ == "__main__":
|
||||
# brand_style_manager = BrandStyleManager()
|
||||
|
||||
# # Example: Get relevant context for a marketing prompt
|
||||
# prompt = "Generate a marketing campaign for an Umbrella company"
|
||||
# context = brand_style_manager.get_relevant_context(prompt)
|
||||
|
||||
# # Print the context in a readable format
|
||||
# print(f"Relevant context for prompt: '{prompt}'")
|
||||
# for i, item in enumerate(context):
|
||||
# print(f"\nReference {i+1}:")
|
||||
# print(item["text"])
|
||||
|
||||
@@ -4,16 +4,14 @@ import json
|
||||
from config import settings
|
||||
from brand_style import BrandStyleManager
|
||||
|
||||
# Initialize brand style manager
|
||||
brand_style_manager = BrandStyleManager()
|
||||
|
||||
class MarketingCopywriter:
|
||||
def __init__(self):
|
||||
def __init__(self, brand_style_manager: BrandStyleManager):
|
||||
self.settings = settings
|
||||
self.api_key = self.settings.DEEPSEEK_API_KEY
|
||||
self.api_url = "https://api.deepseek.com/v1/chat/completions"
|
||||
self.brand_style_manager = brand_style_manager
|
||||
|
||||
def _build_prompt(self, prompt: str, context: List[Dict], content_type: str, tone: str,
|
||||
def _build_prompt(self, prompt: str, context: List[Dict], tone: str,
|
||||
brand_voice: Dict[str, Any], sample_campaigns: List[Dict[str, Any]]) -> str:
|
||||
"""Build a prompt for the LLM using context and parameters."""
|
||||
# Format context from book excerpts
|
||||
@@ -31,7 +29,7 @@ class MarketingCopywriter:
|
||||
sample_campaigns_text += f"Content:\n{campaign.get('content', '')}\n"
|
||||
|
||||
return f"""You are a professional marketing copywriter for {self.settings.BRAND_VOICE}.
|
||||
Your task is to create {content_type} content that matches the following request: {prompt}
|
||||
Your task is to create content that matches the following request: {prompt}
|
||||
|
||||
BRAND VOICE GUIDELINES:
|
||||
{brand_voice_text}
|
||||
@@ -81,10 +79,10 @@ class MarketingCopywriter:
|
||||
|
||||
def generate_marketing_copy(prompt: str) -> str:
|
||||
"""Helper function to generate marketing copy."""
|
||||
copywriter = MarketingCopywriter()
|
||||
brand_style_manager = BrandStyleManager()
|
||||
copywriter = MarketingCopywriter(brand_style_manager)
|
||||
context = brand_style_manager.get_relevant_context(prompt)
|
||||
content_type = "email"
|
||||
tone = "professional and empathetic"
|
||||
brand_voice = brand_style_manager.get_brand_voice()
|
||||
sample_campaigns = brand_style_manager.get_sample_campaigns()
|
||||
return copywriter.generate_copy(prompt, context, content_type, tone, brand_voice, sample_campaigns)
|
||||
return copywriter.generate_copy(prompt, context, tone, brand_voice, sample_campaigns)
|
||||
+70
-2
@@ -1,22 +1,90 @@
|
||||
from flask import Flask, request, jsonify, render_template
|
||||
from flask import Flask, request, jsonify, render_template, redirect, url_for
|
||||
from pydantic import BaseModel
|
||||
from typing import Optional, List, Dict, Any
|
||||
from copywriter import generate_marketing_copy
|
||||
from brand_style import BrandStyleManager
|
||||
from config import settings
|
||||
import os
|
||||
import json
|
||||
import datetime
|
||||
|
||||
app = Flask(__name__)
|
||||
|
||||
# Initialize brand style manager
|
||||
brand_style_manager = BrandStyleManager()
|
||||
data_dir = os.path.abspath(os.path.join(os.path.dirname(__file__), "..", "data"))
|
||||
campaign_prompt = []
|
||||
|
||||
def load_campaigns():
|
||||
campaigns_file = os.path.join(data_dir, "past_campaigns", "campaigns.json")
|
||||
if os.path.exists(campaigns_file):
|
||||
with open(campaigns_file, 'r', encoding='utf-8') as f:
|
||||
data = json.load(f)
|
||||
campaigns = data.get("campaigns", [])
|
||||
return campaigns
|
||||
return []
|
||||
|
||||
def save_campaigns(campaigns):
|
||||
campaigns_file = os.path.join(data_dir, "past_campaigns", "campaigns.json")
|
||||
os.makedirs(os.path.dirname(campaigns_file), exist_ok=True)
|
||||
with open(campaigns_file, 'w', encoding='utf-8') as f:
|
||||
json.dump({"campaigns": campaigns}, f, indent=4)
|
||||
|
||||
@app.route('/', methods=['GET', 'POST'])
|
||||
def root():
|
||||
global prompt
|
||||
if request.method == 'POST':
|
||||
prompt = request.form.get('prompt')
|
||||
campaign_prompt.pop()
|
||||
campaign_prompt.append(prompt)
|
||||
marketing_copy = generate_marketing_copy(prompt)
|
||||
return render_template('index.html', generated_copy=marketing_copy)
|
||||
# generated_copy = generate_marketing_copy("Generate a marketing campaign for our new comers")
|
||||
return render_template('index.html')
|
||||
|
||||
@app.route('/campaigns')
|
||||
def view_campaigns():
|
||||
campaigns = load_campaigns()
|
||||
return render_template('campaigns.html', campaigns=campaigns)
|
||||
|
||||
@app.route('/save-edit', methods=['POST'])
|
||||
def save_edit():
|
||||
edited_copy = request.form.get('editedCopy')
|
||||
global campaign_prompt
|
||||
prompt = campaign_prompt[-1]
|
||||
campaigns = load_campaigns()
|
||||
new_campaign = {
|
||||
"prompt": prompt,
|
||||
"content": edited_copy,
|
||||
"timestamp": datetime.datetime.now().isoformat()
|
||||
}
|
||||
campaigns.append(new_campaign)
|
||||
save_campaigns(campaigns)
|
||||
|
||||
return render_template('index.html', generated_copy="Campaign saved successfully")
|
||||
|
||||
@app.route('/update-campaign', methods=['POST'])
|
||||
def update_campaign():
|
||||
index = int(request.form.get('index'))
|
||||
edited_copy = request.form.get('editedCopy')
|
||||
|
||||
campaigns = load_campaigns()
|
||||
if 0 <= index < len(campaigns):
|
||||
campaigns[index]['content'] = edited_copy
|
||||
campaigns[index]['timestamp'] = datetime.datetime.now().isoformat()
|
||||
save_campaigns(campaigns)
|
||||
|
||||
return redirect(url_for('view_campaigns'))
|
||||
|
||||
@app.route('/delete-campaign', methods=['POST'])
|
||||
def delete_campaign():
|
||||
index = int(request.form.get('index'))
|
||||
|
||||
campaigns = load_campaigns()
|
||||
if 0 <= index < len(campaigns):
|
||||
campaigns.pop(index)
|
||||
save_campaigns(campaigns)
|
||||
|
||||
return redirect(url_for('view_campaigns'))
|
||||
|
||||
if __name__ == "__main__":
|
||||
app.run(host='localhost', port=8000, debug=True)
|
||||
@@ -0,0 +1,151 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Past Campaigns - Marketing Assistant AI</title>
|
||||
<style>
|
||||
body {
|
||||
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
|
||||
max-width: 800px;
|
||||
margin: 0 auto;
|
||||
padding: 20px;
|
||||
background-color: #f5f5f5;
|
||||
}
|
||||
h1 {
|
||||
color: #333;
|
||||
text-align: center;
|
||||
}
|
||||
.container {
|
||||
background-color: white;
|
||||
border-radius: 8px;
|
||||
padding: 20px;
|
||||
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
.campaign {
|
||||
border: 1px solid #ddd;
|
||||
border-radius: 4px;
|
||||
padding: 15px;
|
||||
margin-bottom: 20px;
|
||||
background-color: #f9f9f9;
|
||||
}
|
||||
.campaign-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
.campaign-actions {
|
||||
display: flex;
|
||||
gap: 10px;
|
||||
}
|
||||
.edit-textarea {
|
||||
width: 100%;
|
||||
min-height: 150px;
|
||||
padding: 10px;
|
||||
margin: 10px 0;
|
||||
border: 1px solid #ddd;
|
||||
border-radius: 4px;
|
||||
font-size: 16px;
|
||||
line-height: 1.5;
|
||||
resize: vertical;
|
||||
}
|
||||
.button {
|
||||
padding: 8px 16px;
|
||||
border: none;
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
font-size: 14px;
|
||||
color: white;
|
||||
}
|
||||
.edit-button {
|
||||
background-color: #2196F3;
|
||||
}
|
||||
.delete-button {
|
||||
background-color: #f44336;
|
||||
}
|
||||
.save-button {
|
||||
background-color: #4CAF50;
|
||||
}
|
||||
.button:hover {
|
||||
opacity: 0.9;
|
||||
}
|
||||
.timestamp {
|
||||
color: #666;
|
||||
font-size: 14px;
|
||||
}
|
||||
.nav-link {
|
||||
display: inline-block;
|
||||
margin-bottom: 20px;
|
||||
color: #2196F3;
|
||||
text-decoration: none;
|
||||
}
|
||||
.nav-link:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
.debug-info {
|
||||
background-color: #f8f9fa;
|
||||
padding: 10px;
|
||||
margin: 10px 0;
|
||||
border: 1px solid #ddd;
|
||||
border-radius: 4px;
|
||||
font-family: monospace;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<h1>Past Campaigns</h1>
|
||||
<a href="/" class="nav-link">← Back to Generator</a>
|
||||
|
||||
<div class="container">
|
||||
<!-- Debug information -->
|
||||
<div class="debug-info">
|
||||
Number of campaigns: {{ campaigns|length }}
|
||||
</div>
|
||||
|
||||
{% if campaigns %}
|
||||
{% for campaign in campaigns %}
|
||||
<div class="campaign" id="campaign-{{ loop.index }}">
|
||||
<div class="campaign-header">
|
||||
<h3>Prompt: {{ campaign.prompt }}</h3>
|
||||
<div class="campaign-actions">
|
||||
<button type="button" class="button edit-button" onclick="toggleEdit({{ loop.index }})">Edit</button>
|
||||
<form action="/delete-campaign" method="post" style="display: inline;">
|
||||
<input type="hidden" name="index" value="{{ loop.index0 }}">
|
||||
<button type="submit" class="button delete-button">Delete</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
<div class="timestamp">Created: {{ campaign.timestamp }}</div>
|
||||
<div id="view-{{ loop.index }}">
|
||||
<p>{{ campaign.content }}</p>
|
||||
</div>
|
||||
<div id="edit-{{ loop.index }}" style="display: none;">
|
||||
<form action="/update-campaign" method="post">
|
||||
<input type="hidden" name="index" value="{{ loop.index0 }}">
|
||||
<textarea name="editedCopy" class="edit-textarea">{{ campaign.content }}</textarea>
|
||||
<button type="submit" class="button save-button">Save Changes</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
{% endfor %}
|
||||
{% else %}
|
||||
<p>No campaigns found.</p>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
<script>
|
||||
function toggleEdit(index) {
|
||||
const viewDiv = document.getElementById(`view-${index}`);
|
||||
const editDiv = document.getElementById(`edit-${index}`);
|
||||
if (viewDiv.style.display === 'none') {
|
||||
viewDiv.style.display = 'block';
|
||||
editDiv.style.display = 'none';
|
||||
} else {
|
||||
viewDiv.style.display = 'none';
|
||||
editDiv.style.display = 'block';
|
||||
}
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -63,12 +63,37 @@
|
||||
margin-top: 0;
|
||||
color: #333;
|
||||
}
|
||||
.edit-textarea {
|
||||
width: 100%;
|
||||
min-height: 200px;
|
||||
padding: 10px;
|
||||
margin: 10px 0;
|
||||
border: 1px solid #ddd;
|
||||
border-radius: 4px;
|
||||
font-size: 16px;
|
||||
line-height: 1.5;
|
||||
resize: vertical;
|
||||
}
|
||||
.save-button {
|
||||
background-color: #2196F3;
|
||||
color: white;
|
||||
border: none;
|
||||
padding: 10px 20px;
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
font-size: 14px;
|
||||
margin-top: 10px;
|
||||
}
|
||||
.save-button:hover {
|
||||
background-color: #1976D2;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<h1>Marketing Assistant AI</h1>
|
||||
|
||||
<div class="container">
|
||||
<a href="/campaigns" class="nav-link">View Past Campaigns →</a>
|
||||
<form action="/" method="post">
|
||||
<div class="form-group">
|
||||
<label for="prompt">Enter your marketing prompt:</label>
|
||||
@@ -81,7 +106,11 @@
|
||||
{% if generated_copy %}
|
||||
<div class="response">
|
||||
<h3>Generated Marketing Copy:</h3>
|
||||
<div>{{ generated_copy | safe }}</div>
|
||||
<form id="editForm" action="/save-edit" method="post">
|
||||
<!-- <input type="hidden" name="prompt" value="{{ prompt }}"> -->
|
||||
<textarea id="editedCopy" name="editedCopy" class="edit-textarea">{{ generated_copy }}</textarea>
|
||||
<button type="submit" class="save-button">Save Changes</button>
|
||||
</form>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
@@ -0,0 +1,14 @@
|
||||
{
|
||||
"campaigns": [
|
||||
{
|
||||
"prompt": "",
|
||||
"content": "Subject: Ready to Transform Your Work Journey? \r\n\r\nHi [Name], \r\n\r\nDo you feel stuck in your professional path, unsure how to take the next step? You\u2019re not alone\u2014and the solution is closer than you think. \r\n\r\nIn this exclusive session, you\u2019ll learn how to: \r\n- Unlock your unique strengths and leverage them for success \r\n- Create a clear, actionable plan to achieve your goals \r\n- Build confidence in your professional voice and vision \r\n- Balance ambition with fulfillment\u2014no burnout required \r\n\r\nThis isn\u2019t just about working harder. It\u2019s about working *smarter*, with purpose and clarity. \r\n\r\nJoin us and take the first step toward a career that excites and fulfills you. \r\n\r\nReserve your spot now: [Link] \r\n\r\nWarmly, \r\n[Your Name]",
|
||||
"timestamp": "2025-04-18T19:43:30.474972"
|
||||
},
|
||||
{
|
||||
"prompt": "chair company",
|
||||
"content": "Subject: Elevate Your Workspace with Premium Comfort \r\n\r\nHi [Name], \r\n\r\nYour workspace should inspire productivity and comfort\u2014without compromise. Whether you're building your business from home or leading a team in the office, the right chair can transform.\r\n\r\nHere\u2019s what you deserve: \r\n- **Ergonomic support** to keep you focused and pain-free \r\n- **Timeless design** that complements your professional aesthetic \r\n- **Durable craftsmanship** for long-lasting performance \r\n- **Adjustable features** tailored to your unique needs \r\n\r\nDon\u2019t settle for discomfort. Upgrade to a chair that supports your ambition and reflects your standards. \r\n\r\nExplore your options now: [Link] \r\n\r\nWarmly, \r\n[Your Name] \r\n\r\nP.S. Limited stock available\u2014prioritize your comfort today.",
|
||||
"timestamp": "2025-04-18T19:52:34.117453"
|
||||
}
|
||||
]
|
||||
}
|
||||
Reference in New Issue
Block a user