AI Backlinker, Google Ads Generator, Letter Writer - WIP
This commit is contained in:
@@ -7,6 +7,7 @@ well-researched FAQs from various content sources with customizable options.
|
||||
|
||||
import sys
|
||||
import json
|
||||
import re
|
||||
from typing import Dict, List, Optional, Union
|
||||
from pathlib import Path
|
||||
from enum import Enum
|
||||
@@ -15,12 +16,12 @@ from loguru import logger
|
||||
|
||||
from lib.gpt_providers.text_generation.main_text_generation import llm_text_gen
|
||||
from lib.ai_web_researcher.google_serp_search import google_search
|
||||
from lib.ai_web_researcher.tavily_ai_search import tavily_search
|
||||
from lib.ai_web_researcher.tavily_ai_search import do_tavily_ai_search
|
||||
from lib.ai_web_researcher.metaphor_basic_neural_web_search import metaphor_search_articles
|
||||
|
||||
logger.remove()
|
||||
logger.add(sys.stdout,
|
||||
colorize=True,
|
||||
colorize=True,
|
||||
format="<level>{level}</level>|<green>{file}:{line}:{function}</green>| {message}")
|
||||
|
||||
class TargetAudience(Enum):
|
||||
@@ -51,6 +52,7 @@ class FAQConfig:
|
||||
time_range: str = "last_6_months"
|
||||
exclude_domains: List[str] = None
|
||||
language: str = "English"
|
||||
selected_search_queries: List[str] = None
|
||||
|
||||
@dataclass
|
||||
class FAQItem:
|
||||
@@ -71,26 +73,77 @@ class FAQGenerator:
|
||||
self.config = config or FAQConfig()
|
||||
self.faqs: List[FAQItem] = []
|
||||
self.research_results = {}
|
||||
self.search_queries = []
|
||||
|
||||
async def generate_faqs(self, content: str, content_type: str = "general") -> List[FAQItem]:
|
||||
def generate_search_queries(self, content: str) -> List[str]:
|
||||
"""Generate search queries based on the content."""
|
||||
try:
|
||||
prompt = f"""Based on the following content, generate 5 specific search queries that would help create comprehensive FAQs.
|
||||
Content: {content}
|
||||
|
||||
Guidelines for search queries:
|
||||
1. Focus on key concepts and terms
|
||||
2. Include common questions users might have
|
||||
3. Cover technical aspects that need clarification
|
||||
4. Include best practices and recommendations
|
||||
5. Make queries specific and focused
|
||||
|
||||
Please provide exactly 5 search queries, one per line.
|
||||
Do not include numbers or bullet points in the queries.
|
||||
"""
|
||||
|
||||
response = llm_text_gen(prompt)
|
||||
# Clean up the queries by removing numbers and extra spaces
|
||||
queries = []
|
||||
for line in response.split('\n'):
|
||||
# Remove any leading numbers, dots, or spaces
|
||||
cleaned = re.sub(r'^\d+\.\s*', '', line.strip())
|
||||
if cleaned:
|
||||
queries.append(cleaned)
|
||||
|
||||
self.search_queries = queries[:5] # Ensure we only get 5 queries
|
||||
return self.search_queries
|
||||
|
||||
except Exception as err:
|
||||
logger.error(f"Failed to generate search queries: {err}")
|
||||
return []
|
||||
|
||||
def _clean_search_query(self, query: str) -> str:
|
||||
"""Clean up a search query by removing numbers and extra formatting."""
|
||||
# Remove any leading numbers, dots, or spaces
|
||||
cleaned = re.sub(r'^\d+\.\s*', '', query.strip())
|
||||
# Remove any quotes
|
||||
cleaned = cleaned.replace('"', '').replace("'", '')
|
||||
# Remove any extra spaces
|
||||
cleaned = ' '.join(cleaned.split())
|
||||
return cleaned
|
||||
|
||||
def generate_faqs(self, content: str, content_type: str = "general") -> List[FAQItem]:
|
||||
"""Generate FAQs from the given content with research integration."""
|
||||
try:
|
||||
# Step 1: Research the topic
|
||||
research_results = await self._conduct_research(content)
|
||||
if not self.config.selected_search_queries:
|
||||
raise ValueError("No search queries selected. Please select queries to proceed.")
|
||||
|
||||
# Clean up selected queries
|
||||
cleaned_queries = [self._clean_search_query(q) for q in self.config.selected_search_queries]
|
||||
self.config.selected_search_queries = cleaned_queries
|
||||
|
||||
# Step 1: Research the topic using selected queries
|
||||
research_results = self._conduct_research(content)
|
||||
|
||||
# Step 2: Generate initial FAQs
|
||||
initial_faqs = await self._generate_initial_faqs(content, research_results)
|
||||
initial_faqs = self._generate_initial_faqs(content, research_results)
|
||||
|
||||
# Step 3: Enhance FAQs with research
|
||||
enhanced_faqs = await self._enhance_faqs_with_research(initial_faqs, research_results)
|
||||
enhanced_faqs = self._enhance_faqs_with_research(initial_faqs, research_results)
|
||||
|
||||
# Step 4: Add code examples if requested
|
||||
if self.config.include_code_examples:
|
||||
enhanced_faqs = await self._add_code_examples(enhanced_faqs)
|
||||
enhanced_faqs = self._add_code_examples(enhanced_faqs)
|
||||
|
||||
# Step 5: Add references if requested
|
||||
if self.config.include_references:
|
||||
enhanced_faqs = await self._add_references(enhanced_faqs, research_results)
|
||||
enhanced_faqs = self._add_references(enhanced_faqs, research_results)
|
||||
|
||||
self.faqs = enhanced_faqs
|
||||
return enhanced_faqs
|
||||
@@ -99,38 +152,34 @@ class FAQGenerator:
|
||||
logger.error(f"Failed to generate FAQs: {err}")
|
||||
raise
|
||||
|
||||
async def _conduct_research(self, content: str) -> Dict:
|
||||
"""Conduct online research based on the content."""
|
||||
def _conduct_research(self, content: str) -> Dict:
|
||||
"""Conduct online research based on the selected search queries."""
|
||||
try:
|
||||
research_prompt = f"""Based on the following content, identify key topics and questions for research:
|
||||
{content}
|
||||
|
||||
Please provide a list of research topics and questions that would help create comprehensive FAQs.
|
||||
Focus on:
|
||||
1. Key concepts and terms
|
||||
2. Common questions users might have
|
||||
3. Technical aspects that need clarification
|
||||
4. Best practices and recommendations
|
||||
"""
|
||||
|
||||
research_topics = await llm_text_gen(research_prompt)
|
||||
|
||||
# Conduct research for each topic
|
||||
research_results = {}
|
||||
for topic in research_topics.split('\n'):
|
||||
if topic.strip():
|
||||
|
||||
for query in self.config.selected_search_queries:
|
||||
try:
|
||||
# Clean the query before searching
|
||||
cleaned_query = self._clean_search_query(query)
|
||||
logger.info(f"Researching query: {cleaned_query}")
|
||||
|
||||
# Select search function based on search depth
|
||||
if self.config.search_depth == SearchDepth.BASIC:
|
||||
results = await google_search(topic.strip())
|
||||
results = google_search(cleaned_query)
|
||||
elif self.config.search_depth == SearchDepth.COMPREHENSIVE:
|
||||
results = await tavily_search(topic.strip())
|
||||
results = do_tavily_ai_search(cleaned_query)
|
||||
elif self.config.search_depth == SearchDepth.EXPERT:
|
||||
results = await metaphor_search_articles(topic.strip())
|
||||
results = metaphor_search_articles(cleaned_query)
|
||||
else:
|
||||
logger.warning(f"Unknown search depth: {self.config.search_depth}, defaulting to Google search")
|
||||
results = await google_search(topic.strip())
|
||||
results = google_search(cleaned_query)
|
||||
|
||||
research_results[topic.strip()] = results
|
||||
research_results[query] = results
|
||||
logger.info(f"Research completed for query: {query}")
|
||||
|
||||
except Exception as err:
|
||||
logger.error(f"Failed to research query '{query}': {err}")
|
||||
continue
|
||||
|
||||
return research_results
|
||||
|
||||
@@ -138,7 +187,7 @@ class FAQGenerator:
|
||||
logger.error(f"Failed to conduct research: {err}")
|
||||
return {}
|
||||
|
||||
async def _generate_initial_faqs(self, content: str, research_results: Dict) -> List[FAQItem]:
|
||||
def _generate_initial_faqs(self, content: str, research_results: Dict) -> List[FAQItem]:
|
||||
"""Generate initial FAQs using LLM."""
|
||||
try:
|
||||
system_prompt = f"""You are an expert FAQ generator with deep knowledge in content creation and technical writing.
|
||||
@@ -159,6 +208,13 @@ class FAQGenerator:
|
||||
- Based on the provided research
|
||||
- Relevant to the target audience
|
||||
- Written in the specified style
|
||||
|
||||
Format each FAQ exactly as follows:
|
||||
Q: [Your question here]
|
||||
A: [Your detailed answer here]
|
||||
Category: [Category name]
|
||||
Confidence: [Score between 0 and 1]
|
||||
---
|
||||
"""
|
||||
|
||||
prompt = f"""Content to generate FAQs from:
|
||||
@@ -168,22 +224,26 @@ class FAQGenerator:
|
||||
{json.dumps(research_results, indent=2)}
|
||||
|
||||
Please generate {self.config.num_faqs} FAQs following the guidelines above.
|
||||
Format each FAQ with:
|
||||
- Question
|
||||
- Detailed answer
|
||||
- Category
|
||||
- Confidence score (0-1)
|
||||
Each FAQ must be separated by '---' and include all required fields.
|
||||
"""
|
||||
|
||||
response = await llm_text_gen(prompt, system_prompt=system_prompt)
|
||||
response = llm_text_gen(prompt, system_prompt=system_prompt)
|
||||
logger.info(f"LLM Response: {response}")
|
||||
|
||||
# Parse the response into FAQItem objects
|
||||
faqs = []
|
||||
current_faq = None
|
||||
|
||||
for line in response.split('\n'):
|
||||
line = line.strip()
|
||||
if not line or line == '---':
|
||||
if current_faq and current_faq.question and current_faq.answer:
|
||||
faqs.append(current_faq)
|
||||
current_faq = None
|
||||
continue
|
||||
|
||||
if line.startswith('Q:'):
|
||||
if current_faq:
|
||||
if current_faq and current_faq.question and current_faq.answer:
|
||||
faqs.append(current_faq)
|
||||
current_faq = FAQItem(question=line[2:].strip(), answer="", category="")
|
||||
elif line.startswith('A:'):
|
||||
@@ -194,18 +254,23 @@ class FAQGenerator:
|
||||
current_faq.category = line[9:].strip()
|
||||
elif line.startswith('Confidence:'):
|
||||
if current_faq:
|
||||
current_faq.confidence_score = float(line[11:].strip())
|
||||
try:
|
||||
current_faq.confidence_score = float(line[11:].strip())
|
||||
except ValueError:
|
||||
current_faq.confidence_score = 0.5
|
||||
|
||||
if current_faq:
|
||||
# Add the last FAQ if it exists and is complete
|
||||
if current_faq and current_faq.question and current_faq.answer:
|
||||
faqs.append(current_faq)
|
||||
|
||||
logger.info(f"Generated {len(faqs)} FAQs")
|
||||
return faqs
|
||||
|
||||
except Exception as err:
|
||||
logger.error(f"Failed to generate initial FAQs: {err}")
|
||||
raise
|
||||
|
||||
async def _enhance_faqs_with_research(self, faqs: List[FAQItem], research_results: Dict) -> List[FAQItem]:
|
||||
def _enhance_faqs_with_research(self, faqs: List[FAQItem], research_results: Dict) -> List[FAQItem]:
|
||||
"""Enhance FAQs with research findings."""
|
||||
try:
|
||||
enhanced_faqs = []
|
||||
@@ -231,7 +296,7 @@ class FAQGenerator:
|
||||
4. Keeping the answer concise and clear
|
||||
"""
|
||||
|
||||
enhanced_answer = await llm_text_gen(enhancement_prompt)
|
||||
enhanced_answer = llm_text_gen(enhancement_prompt)
|
||||
faq.answer = enhanced_answer
|
||||
|
||||
enhanced_faqs.append(faq)
|
||||
@@ -242,24 +307,20 @@ class FAQGenerator:
|
||||
logger.error(f"Failed to enhance FAQs with research: {err}")
|
||||
return faqs
|
||||
|
||||
async def _add_code_examples(self, faqs: List[FAQItem]) -> List[FAQItem]:
|
||||
def _add_code_examples(self, faqs: List[FAQItem]) -> List[FAQItem]:
|
||||
"""Add code examples to FAQs where applicable."""
|
||||
try:
|
||||
for faq in faqs:
|
||||
if self._is_technical_question(faq.question):
|
||||
code_prompt = f"""Generate a code example for the following FAQ:
|
||||
|
||||
Question: {faq.question}
|
||||
Answer: {faq.answer}
|
||||
|
||||
Please provide a relevant code example that:
|
||||
1. Illustrates the answer clearly
|
||||
2. Includes comments and explanations
|
||||
3. Follows best practices
|
||||
4. Is easy to understand
|
||||
Please provide a relevant code example that demonstrates the concept.
|
||||
Include comments and explanations where necessary.
|
||||
"""
|
||||
|
||||
code_example = await llm_text_gen(code_prompt)
|
||||
code_example = llm_text_gen(code_prompt)
|
||||
faq.code_example = code_example
|
||||
|
||||
return faqs
|
||||
@@ -268,21 +329,19 @@ class FAQGenerator:
|
||||
logger.error(f"Failed to add code examples: {err}")
|
||||
return faqs
|
||||
|
||||
async def _add_references(self, faqs: List[FAQItem], research_results: Dict) -> List[FAQItem]:
|
||||
"""Add references to FAQs."""
|
||||
def _add_references(self, faqs: List[FAQItem], research_results: Dict) -> List[FAQItem]:
|
||||
"""Add references to FAQs based on research results."""
|
||||
try:
|
||||
for faq in faqs:
|
||||
relevant_research = self._find_relevant_research(faq, research_results)
|
||||
if relevant_research:
|
||||
faq.references = [
|
||||
{
|
||||
"title": ref.get("title", ""),
|
||||
"url": ref.get("url", ""),
|
||||
"source": ref.get("source", ""),
|
||||
"date": ref.get("date", "")
|
||||
}
|
||||
for ref in relevant_research.get("references", [])
|
||||
]
|
||||
references = []
|
||||
for source, content in relevant_research.items():
|
||||
references.append({
|
||||
"source": source,
|
||||
"content": content
|
||||
})
|
||||
faq.references = references
|
||||
|
||||
return faqs
|
||||
|
||||
@@ -291,8 +350,7 @@ class FAQGenerator:
|
||||
return faqs
|
||||
|
||||
def _find_relevant_research(self, faq: FAQItem, research_results: Dict) -> Dict:
|
||||
"""Find research relevant to a specific FAQ."""
|
||||
# Simple keyword matching for now - can be enhanced with semantic search
|
||||
"""Find research results relevant to a specific FAQ."""
|
||||
relevant_research = {}
|
||||
for topic, results in research_results.items():
|
||||
if any(keyword in faq.question.lower() for keyword in topic.lower().split()):
|
||||
@@ -308,8 +366,8 @@ class FAQGenerator:
|
||||
"""Convert FAQs to markdown format."""
|
||||
markdown = "# Frequently Asked Questions\n\n"
|
||||
|
||||
for i, faq in enumerate(self.faqs, 1):
|
||||
markdown += f"## {i}. {faq.question}\n\n"
|
||||
for faq in self.faqs:
|
||||
markdown += f"## {faq.question}\n\n"
|
||||
markdown += f"{faq.answer}\n\n"
|
||||
|
||||
if faq.code_example:
|
||||
@@ -320,7 +378,7 @@ class FAQGenerator:
|
||||
if faq.references:
|
||||
markdown += "### References\n"
|
||||
for ref in faq.references:
|
||||
markdown += f"- [{ref['title']}]({ref['url']}) - {ref['source']} ({ref['date']})\n"
|
||||
markdown += f"- {ref['source']}\n"
|
||||
markdown += "\n"
|
||||
|
||||
return markdown
|
||||
@@ -333,52 +391,52 @@ class FAQGenerator:
|
||||
<head>
|
||||
<title>Frequently Asked Questions</title>
|
||||
<style>
|
||||
.faq-container { max-width: 800px; margin: 0 auto; }
|
||||
.faq-item { margin-bottom: 2em; }
|
||||
.question { font-weight: bold; font-size: 1.2em; }
|
||||
.answer { margin: 1em 0; }
|
||||
.code-example { background: #f5f5f5; padding: 1em; }
|
||||
.references { margin-top: 1em; font-size: 0.9em; }
|
||||
body { font-family: Arial, sans-serif; max-width: 800px; margin: 0 auto; padding: 20px; }
|
||||
.faq { margin-bottom: 30px; }
|
||||
.question { font-weight: bold; font-size: 1.2em; color: #2c3e50; }
|
||||
.answer { margin: 10px 0; }
|
||||
.code-example { background: #f8f9fa; padding: 15px; border-radius: 4px; }
|
||||
.references { margin-top: 15px; font-size: 0.9em; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="faq-container">
|
||||
<h1>Frequently Asked Questions</h1>
|
||||
<h1>Frequently Asked Questions</h1>
|
||||
"""
|
||||
|
||||
for i, faq in enumerate(self.faqs, 1):
|
||||
for faq in self.faqs:
|
||||
html += f"""
|
||||
<div class="faq-item">
|
||||
<div class="question">{i}. {faq.question}</div>
|
||||
<div class="answer">{faq.answer}</div>
|
||||
<div class="faq">
|
||||
<div class="question">{faq.question}</div>
|
||||
<div class="answer">{faq.answer}</div>
|
||||
"""
|
||||
|
||||
if faq.code_example:
|
||||
html += f"""
|
||||
<pre class="code-example">{faq.code_example}</pre>
|
||||
<div class="code-example">
|
||||
<pre><code>{faq.code_example}</code></pre>
|
||||
</div>
|
||||
"""
|
||||
|
||||
if faq.references:
|
||||
html += """
|
||||
<div class="references">
|
||||
<h3>References</h3>
|
||||
<ul>
|
||||
<div class="references">
|
||||
<h3>References</h3>
|
||||
<ul>
|
||||
"""
|
||||
for ref in faq.references:
|
||||
html += f"""
|
||||
<li><a href="{ref['url']}">{ref['title']}</a> - {ref['source']} ({ref['date']})</li>
|
||||
<li>{ref['source']}</li>
|
||||
"""
|
||||
html += """
|
||||
</ul>
|
||||
</div>
|
||||
</ul>
|
||||
</div>
|
||||
"""
|
||||
|
||||
html += """
|
||||
</div>
|
||||
</div>
|
||||
"""
|
||||
|
||||
html += """
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
"""
|
||||
|
||||
@@ -5,15 +5,27 @@ This module provides a user-friendly interface for generating FAQs from various
|
||||
"""
|
||||
|
||||
import streamlit as st
|
||||
import asyncio
|
||||
from pathlib import Path
|
||||
from typing import Optional
|
||||
import json
|
||||
import requests
|
||||
from bs4 import BeautifulSoup
|
||||
import logging
|
||||
import pyperclip
|
||||
|
||||
from .faqs_generator_blog import FAQGenerator, FAQConfig, TargetAudience, FAQStyle, SearchDepth
|
||||
|
||||
# Set up logging
|
||||
logging.basicConfig(level=logging.INFO)
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
def copy_to_clipboard(text: str) -> None:
|
||||
"""Copy text to clipboard and show success message."""
|
||||
try:
|
||||
pyperclip.copy(text)
|
||||
st.success("Copied to clipboard!")
|
||||
except Exception as e:
|
||||
st.error(f"Failed to copy to clipboard: {str(e)}")
|
||||
|
||||
def fetch_url_content(url):
|
||||
"""Fetch and extract content from a URL."""
|
||||
@@ -42,15 +54,27 @@ def fetch_url_content(url):
|
||||
return None
|
||||
|
||||
def main():
|
||||
st.set_page_config(
|
||||
page_title="FAQ Generator",
|
||||
page_icon="❓",
|
||||
layout="wide"
|
||||
)
|
||||
|
||||
st.title("FAQ Generator")
|
||||
st.markdown("Generate comprehensive FAQs from your content with research integration.")
|
||||
|
||||
# Initialize session state variables if they don't exist
|
||||
if 'search_queries' not in st.session_state:
|
||||
st.session_state.search_queries = []
|
||||
if 'selected_queries' not in st.session_state:
|
||||
st.session_state.selected_queries = []
|
||||
if 'research_completed' not in st.session_state:
|
||||
st.session_state.research_completed = False
|
||||
if 'research_results' not in st.session_state:
|
||||
st.session_state.research_results = {}
|
||||
if 'faq_config' not in st.session_state:
|
||||
st.session_state.faq_config = None
|
||||
if 'generator' not in st.session_state:
|
||||
st.session_state.generator = FAQGenerator()
|
||||
if 'generated_faqs' not in st.session_state:
|
||||
st.session_state.generated_faqs = None
|
||||
if 'output_format' not in st.session_state:
|
||||
st.session_state.output_format = "Preview"
|
||||
|
||||
# Sidebar for configuration
|
||||
with st.sidebar:
|
||||
st.header("Configuration")
|
||||
@@ -99,40 +123,137 @@ def main():
|
||||
if content:
|
||||
st.text_area("Extracted Content", content, height=300)
|
||||
|
||||
# Generate button
|
||||
if st.button("Generate FAQs") and content:
|
||||
try:
|
||||
# Create config
|
||||
config = FAQConfig(
|
||||
num_faqs=num_faqs,
|
||||
target_audience=TargetAudience(target_audience),
|
||||
faq_style=FAQStyle(faq_style),
|
||||
include_emojis=include_emojis,
|
||||
include_code_examples=include_code_examples,
|
||||
include_references=include_references,
|
||||
search_depth=SearchDepth(search_depth),
|
||||
time_range=time_range,
|
||||
language=language
|
||||
)
|
||||
|
||||
# Initialize generator
|
||||
generator = FAQGenerator(config)
|
||||
|
||||
# Generate FAQs
|
||||
with st.spinner("Generating FAQs..."):
|
||||
faqs = asyncio.run(generator.generate_faqs(content))
|
||||
|
||||
# Display results
|
||||
st.success("FAQs generated successfully!")
|
||||
# Step 1: Generate search queries
|
||||
if content and not st.session_state.search_queries:
|
||||
if st.button("Generate Search Queries"):
|
||||
with st.spinner("Generating search queries..."):
|
||||
search_queries = st.session_state.generator.generate_search_queries(content)
|
||||
if search_queries:
|
||||
st.session_state.search_queries = search_queries
|
||||
st.session_state.selected_queries = [] # Reset selected queries
|
||||
st.session_state.research_completed = False # Reset research status
|
||||
st.session_state.research_results = {} # Reset research results
|
||||
st.session_state.faq_config = None # Reset config
|
||||
st.session_state.generated_faqs = None # Reset generated FAQs
|
||||
st.success("Search queries generated successfully!")
|
||||
|
||||
# Step 2: Display and select search queries
|
||||
if st.session_state.search_queries:
|
||||
st.subheader("Select Search Queries")
|
||||
st.info("Select the queries you want to use for web research. You can select multiple queries.")
|
||||
|
||||
# Create checkboxes for each search query
|
||||
selected_queries = []
|
||||
for query in st.session_state.search_queries:
|
||||
if st.checkbox(query, key=f"query_{query}", value=query in st.session_state.selected_queries):
|
||||
selected_queries.append(query)
|
||||
|
||||
# Update selected queries in session state
|
||||
st.session_state.selected_queries = selected_queries
|
||||
|
||||
# Step 3: Do web research
|
||||
if st.session_state.selected_queries and not st.session_state.research_completed:
|
||||
if st.button("Do Web Research"):
|
||||
try:
|
||||
# Create config with selected queries
|
||||
config = FAQConfig(
|
||||
num_faqs=num_faqs,
|
||||
target_audience=TargetAudience(target_audience),
|
||||
faq_style=FAQStyle(faq_style),
|
||||
include_emojis=include_emojis,
|
||||
include_code_examples=include_code_examples,
|
||||
include_references=include_references,
|
||||
search_depth=SearchDepth(search_depth),
|
||||
time_range=time_range,
|
||||
language=language,
|
||||
selected_search_queries=selected_queries
|
||||
)
|
||||
|
||||
# Store config in session state
|
||||
st.session_state.faq_config = config
|
||||
|
||||
# Update generator with config
|
||||
st.session_state.generator.config = config
|
||||
|
||||
# Do research
|
||||
with st.spinner("Conducting web research..."):
|
||||
research_results = st.session_state.generator._conduct_research(content)
|
||||
st.session_state.research_completed = True
|
||||
st.session_state.research_results = research_results
|
||||
st.success("Web research completed successfully!")
|
||||
|
||||
# Display research results
|
||||
st.subheader("Research Results")
|
||||
for query, results in research_results.items():
|
||||
with st.expander(f"Results for: {query}"):
|
||||
if isinstance(results, dict):
|
||||
st.json(results)
|
||||
else:
|
||||
st.text(results)
|
||||
|
||||
except Exception as e:
|
||||
st.error(f"Error during web research: {str(e)}")
|
||||
st.error("Please try again with different search queries or adjust the search depth.")
|
||||
|
||||
# Step 4: Generate FAQs
|
||||
if st.session_state.research_completed and st.session_state.research_results and st.session_state.faq_config:
|
||||
if st.button("Generate FAQs"):
|
||||
try:
|
||||
# Update generator with stored config
|
||||
st.session_state.generator.config = st.session_state.faq_config
|
||||
|
||||
# Generate FAQs
|
||||
with st.spinner("Generating FAQs..."):
|
||||
logger.info("Starting FAQ generation...")
|
||||
faqs = st.session_state.generator.generate_faqs(content)
|
||||
logger.info(f"Generated {len(faqs) if faqs else 0} FAQs")
|
||||
|
||||
if not faqs:
|
||||
st.error("No FAQs were generated. Please try again.")
|
||||
return
|
||||
|
||||
st.session_state.generated_faqs = faqs
|
||||
st.success("FAQs generated successfully!")
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Error generating FAQs: {str(e)}")
|
||||
st.error(f"Error generating FAQs: {str(e)}")
|
||||
st.error("Please try again or adjust your settings.")
|
||||
|
||||
# Display generated FAQs if they exist
|
||||
if st.session_state.generated_faqs:
|
||||
st.subheader("Generated FAQs")
|
||||
|
||||
# Output format selection
|
||||
output_format = st.radio(
|
||||
"Output Format",
|
||||
["Preview", "Markdown", "HTML", "JSON"]
|
||||
["Preview", "Markdown", "HTML", "JSON"],
|
||||
key="output_format"
|
||||
)
|
||||
|
||||
# Create columns for copy and download buttons
|
||||
col1, col2 = st.columns(2)
|
||||
|
||||
if output_format == "Preview":
|
||||
for i, faq in enumerate(faqs, 1):
|
||||
# Create a formatted text for copying
|
||||
preview_text = ""
|
||||
for i, faq in enumerate(st.session_state.generated_faqs, 1):
|
||||
preview_text += f"{i}. {faq.question}\n"
|
||||
preview_text += f"{faq.answer}\n\n"
|
||||
if faq.code_example:
|
||||
preview_text += f"Code Example:\n{faq.code_example}\n\n"
|
||||
if faq.references:
|
||||
preview_text += "References:\n"
|
||||
for ref in faq.references:
|
||||
preview_text += f"- {ref['source']}\n"
|
||||
preview_text += "\n"
|
||||
|
||||
with col1:
|
||||
if st.button("Copy to Clipboard", key="copy_preview"):
|
||||
copy_to_clipboard(preview_text)
|
||||
|
||||
# Display the FAQs
|
||||
for i, faq in enumerate(st.session_state.generated_faqs, 1):
|
||||
with st.expander(f"{i}. {faq.question}"):
|
||||
st.markdown(faq.answer)
|
||||
if faq.code_example:
|
||||
@@ -140,38 +261,52 @@ def main():
|
||||
if faq.references:
|
||||
st.markdown("**References:**")
|
||||
for ref in faq.references:
|
||||
st.markdown(f"- [{ref['title']}]({ref['url']}) - {ref['source']} ({ref['date']})")
|
||||
st.markdown(f"- {ref['source']}")
|
||||
|
||||
elif output_format == "Markdown":
|
||||
st.code(generator.to_markdown(), language="markdown")
|
||||
st.download_button(
|
||||
"Download Markdown",
|
||||
generator.to_markdown(),
|
||||
file_name="faqs.md",
|
||||
mime="text/markdown"
|
||||
)
|
||||
markdown_output = st.session_state.generator.to_markdown()
|
||||
st.code(markdown_output, language="markdown")
|
||||
|
||||
with col1:
|
||||
if st.button("Copy to Clipboard", key="copy_markdown"):
|
||||
copy_to_clipboard(markdown_output)
|
||||
with col2:
|
||||
st.download_button(
|
||||
"Download Markdown",
|
||||
markdown_output,
|
||||
file_name="faqs.md",
|
||||
mime="text/markdown"
|
||||
)
|
||||
|
||||
elif output_format == "HTML":
|
||||
st.code(generator.to_html(), language="html")
|
||||
st.download_button(
|
||||
"Download HTML",
|
||||
generator.to_html(),
|
||||
file_name="faqs.html",
|
||||
mime="text/html"
|
||||
)
|
||||
html_output = st.session_state.generator.to_html()
|
||||
st.code(html_output, language="html")
|
||||
|
||||
with col1:
|
||||
if st.button("Copy to Clipboard", key="copy_html"):
|
||||
copy_to_clipboard(html_output)
|
||||
with col2:
|
||||
st.download_button(
|
||||
"Download HTML",
|
||||
html_output,
|
||||
file_name="faqs.html",
|
||||
mime="text/html"
|
||||
)
|
||||
|
||||
elif output_format == "JSON":
|
||||
json_output = json.dumps([faq.__dict__ for faq in faqs], indent=2)
|
||||
json_output = json.dumps([faq.__dict__ for faq in st.session_state.generated_faqs], indent=2)
|
||||
st.code(json_output, language="json")
|
||||
st.download_button(
|
||||
"Download JSON",
|
||||
json_output,
|
||||
file_name="faqs.json",
|
||||
mime="application/json"
|
||||
)
|
||||
|
||||
except Exception as e:
|
||||
st.error(f"Error generating FAQs: {str(e)}")
|
||||
|
||||
with col1:
|
||||
if st.button("Copy to Clipboard", key="copy_json"):
|
||||
copy_to_clipboard(json_output)
|
||||
with col2:
|
||||
st.download_button(
|
||||
"Download JSON",
|
||||
json_output,
|
||||
file_name="faqs.json",
|
||||
mime="application/json"
|
||||
)
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
1271
lib/ai_writers/ai_letter_writer/business_letters.py
Normal file
1271
lib/ai_writers/ai_letter_writer/business_letters.py
Normal file
File diff suppressed because it is too large
Load Diff
1135
lib/ai_writers/ai_letter_writer/cover_letters.py
Normal file
1135
lib/ai_writers/ai_letter_writer/cover_letters.py
Normal file
File diff suppressed because it is too large
Load Diff
1184
lib/ai_writers/ai_letter_writer/formal_letters.py
Normal file
1184
lib/ai_writers/ai_letter_writer/formal_letters.py
Normal file
File diff suppressed because it is too large
Load Diff
758
lib/ai_writers/ai_letter_writer/letter_templates.py
Normal file
758
lib/ai_writers/ai_letter_writer/letter_templates.py
Normal file
@@ -0,0 +1,758 @@
|
||||
"""
|
||||
Letter Templates Module
|
||||
|
||||
This module provides structured templates and guidance for generating
|
||||
different types and subtypes of letters.
|
||||
Templates are defined as a nested dictionary containing 'structure' (list of sections)
|
||||
and 'guidance' (a string) for each letter type and subtype.
|
||||
"""
|
||||
|
||||
from typing import Dict, Any, List
|
||||
|
||||
# Define letter templates using a nested dictionary structure for better organization and lookup.
|
||||
# The structure is {letter_type: {subtype: {template_details}}}
|
||||
# 'default' subtype is used as a fallback if a specific subtype isn't found for a given type.
|
||||
TEMPLATES: Dict[str, Dict[str, Dict[str, Any]]] = {
|
||||
"personal": {
|
||||
"congratulations": {
|
||||
"structure": [
|
||||
"Greeting",
|
||||
"Express congratulations",
|
||||
"Acknowledge the achievement",
|
||||
"Share personal thoughts/memory (optional)",
|
||||
"Look to the future/well wishes",
|
||||
"Closing"
|
||||
],
|
||||
"guidance": "Be warm, sincere, and specific about the achievement. Express genuine happiness for the recipient. Keep the tone personal and friendly."
|
||||
},
|
||||
"thank_you": {
|
||||
"structure": [
|
||||
"Greeting",
|
||||
"Express gratitude clearly",
|
||||
"Specify what you are thankful for",
|
||||
"Explain the impact or how you used it (optional)",
|
||||
"Share a personal thought or memory (optional)",
|
||||
"Offer reciprocation or look to the future",
|
||||
"Closing"
|
||||
],
|
||||
"guidance": "Be specific about what you're thankful for and how it affected you. Express sincere appreciation. Personalize the message."
|
||||
},
|
||||
"sympathy": {
|
||||
"structure": [
|
||||
"Greeting",
|
||||
"Express sympathy for the loss",
|
||||
"Acknowledge the significance of the person/situation",
|
||||
"Share a positive memory or quality (optional)",
|
||||
"Offer specific support (optional)",
|
||||
"Closing with comforting words"
|
||||
],
|
||||
"guidance": "Be gentle, compassionate, and sincere. Avoid clichés. Focus on offering genuine comfort and acknowledging the recipient's feelings."
|
||||
},
|
||||
"apology": {
|
||||
"structure": [
|
||||
"Greeting",
|
||||
"Clearly state your apology",
|
||||
"Acknowledge the specific mistake or action",
|
||||
"Express understanding of the impact on the other person",
|
||||
"Explain (briefly, without making excuses) what happened (optional)",
|
||||
"Offer amends or suggest how to make things right",
|
||||
"Assure it won't happen again",
|
||||
"Closing"
|
||||
],
|
||||
"guidance": "Be sincere, take full responsibility for your actions, and focus on making things right. Avoid making excuses or blaming others."
|
||||
},
|
||||
"invitation": {
|
||||
"structure": [
|
||||
"Greeting",
|
||||
"Clearly state the invitation",
|
||||
"Provide full event details (What, When, Where)",
|
||||
"Explain the significance or purpose (optional)",
|
||||
"Mention who else might be there (optional)",
|
||||
"Request RSVP (date and contact method)",
|
||||
"Express anticipation",
|
||||
"Closing"
|
||||
],
|
||||
"guidance": "Be clear and specific about the details (what, when, where, why). Make it easy for the person to respond."
|
||||
},
|
||||
"friendship": {
|
||||
"structure": [
|
||||
"Greeting",
|
||||
"Express appreciation for the friendship",
|
||||
"Share a recent memory or anecdote",
|
||||
"Acknowledge the value of the relationship",
|
||||
"Check in on them or share updates",
|
||||
"Look to the future (getting together, etc.)",
|
||||
"Closing"
|
||||
],
|
||||
"guidance": "Be warm, personal, and specific about what you value in the friendship. Share updates and show genuine interest."
|
||||
},
|
||||
"love": {
|
||||
"structure": [
|
||||
"Greeting (Terms of endearment)",
|
||||
"Express depth of feelings",
|
||||
"Share a cherished memory or moment",
|
||||
"Describe specific qualities you love and appreciate",
|
||||
"Reaffirm commitment or future hopes",
|
||||
"Closing (Terms of endearment)"
|
||||
],
|
||||
"guidance": "Be sincere, personal, and specific about your feelings. Use sensory details and emotional language appropriate for your relationship."
|
||||
},
|
||||
"encouragement": {
|
||||
"structure": [
|
||||
"Greeting",
|
||||
"Acknowledge the situation or challenge they face",
|
||||
"Express belief in their abilities/strength",
|
||||
"Offer specific words of encouragement or support",
|
||||
"Remind them of past successes (optional)",
|
||||
"Offer practical help (optional)",
|
||||
"Look to the future with hope",
|
||||
"Closing with support"
|
||||
],
|
||||
"guidance": "Be positive, supportive, and specific about the person's strengths and abilities. Offer genuine encouragement and belief in them."
|
||||
},
|
||||
"farewell": {
|
||||
"structure": [
|
||||
"Greeting",
|
||||
"State the purpose (saying goodbye)",
|
||||
"Express feelings about their departure (sadness, happiness for them)",
|
||||
"Share a positive memory or highlight their contribution",
|
||||
"Express good wishes for their future endeavors",
|
||||
"Look to staying in touch (optional)",
|
||||
"Closing"
|
||||
],
|
||||
"guidance": "Be warm, reflective, and forward-looking. Focus on positive memories and express genuine good wishes for their next steps."
|
||||
},
|
||||
# Default personal letter template if subtype is not found
|
||||
"default": {
|
||||
"structure": [
|
||||
"Greeting",
|
||||
"Introduction",
|
||||
"Main content paragraphs",
|
||||
"Closing thoughts",
|
||||
"Signature"
|
||||
],
|
||||
"guidance": "Be personal, authentic, and appropriate for your relationship with the recipient. The tone is typically informal to semi-formal."
|
||||
}
|
||||
},
|
||||
"formal": {
|
||||
"application": {
|
||||
"structure": [
|
||||
"Sender's contact information",
|
||||
"Date",
|
||||
"Recipient's contact information (if known)",
|
||||
"Subject line (Clear and concise)",
|
||||
"Salutation (Formal)",
|
||||
"Introduction (State position applied for and where you saw it)",
|
||||
"Body paragraphs (Highlight relevant skills and experience)",
|
||||
"Closing paragraph (Reiterate interest, mention enclosed resume, call to action)",
|
||||
"Complimentary close (Formal)",
|
||||
"Signature (Typed name)",
|
||||
"Enclosures (Mention if attaching resume/portfolio)"
|
||||
],
|
||||
"guidance": "Be professional, concise, and specific about your qualifications and genuine interest in the position. Tailor it to the specific job description."
|
||||
},
|
||||
"complaint": {
|
||||
"structure": [
|
||||
"Sender's contact information",
|
||||
"Date",
|
||||
"Recipient's contact information",
|
||||
"Subject line (Clearly state it's a complaint)",
|
||||
"Salutation (Formal)",
|
||||
"Introduction (State the purpose: complaint about X service/product)",
|
||||
"Problem description (Provide specific details: date, time, location, product details, names if applicable)",
|
||||
"Impact statement (Explain how the problem affected you)",
|
||||
"Requested resolution (Clearly state what you want: refund, replacement, action)",
|
||||
"Closing paragraph (Reference attached documents, state expectation for response)",
|
||||
"Complimentary close (Formal)",
|
||||
"Signature (Typed name)"
|
||||
],
|
||||
"guidance": "Be clear, factual, and specific about the issue and your desired resolution. Maintain a respectful but firm tone. Include all relevant details."
|
||||
},
|
||||
"request": {
|
||||
"structure": [
|
||||
"Sender's contact information",
|
||||
"Date",
|
||||
"Recipient's contact information",
|
||||
"Subject line (Clearly state the request)",
|
||||
"Salutation (Formal)",
|
||||
"Introduction (State the purpose: making a request)",
|
||||
"Request details (Clearly explain what you are requesting)",
|
||||
"Justification (Explain why the request is necessary or beneficial)",
|
||||
"Provide supporting information (optional)",
|
||||
"Closing paragraph (Express gratitude for consideration, reiterate call to action)",
|
||||
"Complimentary close (Formal)",
|
||||
"Signature (Typed name)"
|
||||
],
|
||||
"guidance": "Be clear, specific, and courteous about your request. Explain why it's important or beneficial to the recipient or organization."
|
||||
},
|
||||
"recommendation": {
|
||||
"structure": [
|
||||
"Sender's contact information",
|
||||
"Date",
|
||||
"Recipient's contact information",
|
||||
"Subject line (Letter of Recommendation for [Name])",
|
||||
"Salutation (Formal)",
|
||||
"Introduction (State your name, title, relationship to the recommendee, and for what purpose the letter is written)",
|
||||
"Body paragraphs (Describe the recommendee's qualifications, skills, and achievements with specific examples)",
|
||||
"Highlight relevant experiences and contributions",
|
||||
"Closing recommendation (Summarize endorsement, strongly recommend the person)",
|
||||
"Complimentary close (Formal)",
|
||||
"Signature (Typed name and title)"
|
||||
],
|
||||
"guidance": "Be specific, positive, and credible. Use concrete examples and anecdotes to support your recommendation. Tailor it to the specific role/opportunity."
|
||||
},
|
||||
"resignation": {
|
||||
"structure": [
|
||||
"Sender's contact information",
|
||||
"Date",
|
||||
"Recipient's contact information (Immediate supervisor/HR)",
|
||||
"Subject line (Letter of Resignation - [Your Name])",
|
||||
"Salutation (Formal)",
|
||||
"Statement of resignation (Clearly state you are resigning)",
|
||||
"Last day of employment (Specify the date)",
|
||||
"Gratitude and reflection (Optional: Express thanks for the opportunity/experience)",
|
||||
"Transition plan/Offer of assistance (Optional: Suggest how to ensure a smooth handover)",
|
||||
"Closing paragraph (Express good wishes for the company's future)",
|
||||
"Complimentary close (Formal)",
|
||||
"Signature (Typed name)"
|
||||
],
|
||||
"guidance": "Be professional, positive (if possible), and clear about your departure and last day. Maintain a good relationship."
|
||||
},
|
||||
"inquiry": {
|
||||
"structure": [
|
||||
"Sender's contact information",
|
||||
"Date",
|
||||
"Recipient's contact information",
|
||||
"Subject line (Clearly state the nature of the inquiry)",
|
||||
"Salutation (Formal)",
|
||||
"Introduction (State your purpose for writing - making an inquiry)",
|
||||
"Inquiry details (Provide necessary context or background)",
|
||||
"Specific questions (List your questions clearly, perhaps numbered)",
|
||||
"Closing paragraph (Express gratitude for assistance, indicate when you need a response)",
|
||||
"Complimentary close (Formal)",
|
||||
"Signature (Typed name)"
|
||||
],
|
||||
"guidance": "Be clear, specific, and courteous about your inquiry. Organize your questions logically for easy answering."
|
||||
},
|
||||
"authorization": {
|
||||
"structure": [
|
||||
"Sender's contact information (The grantor of authority)",
|
||||
"Date",
|
||||
"Recipient's contact information (The person/entity receiving the letter)",
|
||||
"Subject line (Letter of Authorization)",
|
||||
"Salutation (Formal)",
|
||||
"Statement of authorization (Clearly state who is authorized)",
|
||||
"Authorized person's details (Full name, ID if applicable)",
|
||||
"Scope of authority (Precisely define what they are authorized to do)",
|
||||
"Limitations (Specify any restrictions or conditions)",
|
||||
"Duration of authorization (Start and end dates, if applicable)",
|
||||
"Closing paragraph (State responsibility, express confidence)",
|
||||
"Complimentary close (Formal)",
|
||||
"Signature (Typed name and title)"
|
||||
],
|
||||
"guidance": "Be clear, specific, and precise about who is authorized, what they can do, for how long, and under what conditions. This is a legal document."
|
||||
},
|
||||
"appeal": {
|
||||
"structure": [
|
||||
"Sender's contact information",
|
||||
"Date",
|
||||
"Recipient's contact information (Appeals committee/relevant authority)",
|
||||
"Subject line (Letter of Appeal - [Your Name] - [Subject of Appeal])",
|
||||
"Salutation (Formal)",
|
||||
"Introduction (State your name, the decision being appealed, and the date of the decision)",
|
||||
"Grounds for appeal (Clearly state the reasons why you believe the decision is incorrect)",
|
||||
"Provide supporting evidence (Reference attached documents: records, photos, etc.)",
|
||||
"Explain mitigating circumstances (Optional)",
|
||||
"Requested outcome (Clearly state what resolution you seek)",
|
||||
"Closing paragraph (Express hope for reconsideration, gratitude for time)",
|
||||
"Complimentary close (Formal)",
|
||||
"Signature (Typed name)"
|
||||
],
|
||||
"guidance": "Be respectful, factual, and persuasive. Focus on valid grounds for appeal and provide clear, supporting evidence. Maintain a formal tone."
|
||||
},
|
||||
"introduction": {
|
||||
"structure": [
|
||||
"Sender's contact information",
|
||||
"Date",
|
||||
"Recipient's contact information",
|
||||
"Subject line (Introduction - [Your Name])",
|
||||
"Salutation (Formal)",
|
||||
"Introduction (Introduce yourself and the purpose of the letter)",
|
||||
"Background information (Briefly describe your relevant background or expertise)",
|
||||
"Reason for reaching out (Explain why you are introducing yourself to this specific person/entity)",
|
||||
"Potential areas of collaboration or shared interest (Optional)",
|
||||
"Call to action (Suggest a meeting, call, or further communication)",
|
||||
"Closing paragraph (Express enthusiasm for potential connection)",
|
||||
"Complimentary close (Formal)",
|
||||
"Signature (Typed name)"
|
||||
],
|
||||
"guidance": "Be professional, informative, and engaging. Clearly explain who you are, your expertise, and why you're reaching out to them specifically."
|
||||
},
|
||||
# Default formal letter template if subtype is not found
|
||||
"default": {
|
||||
"structure": [
|
||||
"Sender's address",
|
||||
"Date",
|
||||
"Recipient's address",
|
||||
"Subject line",
|
||||
"Salutation",
|
||||
"Introduction",
|
||||
"Body paragraphs",
|
||||
"Closing paragraph",
|
||||
"Complimentary close",
|
||||
"Signature"
|
||||
],
|
||||
"guidance": "Be professional, clear, and concise. Use formal language and structure. The tone is typically formal."
|
||||
}
|
||||
},
|
||||
"business": {
|
||||
"sales": {
|
||||
"structure": [
|
||||
"Letterhead",
|
||||
"Date",
|
||||
"Recipient's address",
|
||||
"Subject line (Benefit-oriented)",
|
||||
"Salutation",
|
||||
"Attention-grabbing opening (Address a pain point or introduce a benefit)",
|
||||
"Problem statement (Briefly describe the challenge the recipient faces)",
|
||||
"Solution presentation (Introduce your product/service as the solution)",
|
||||
"Benefits and features (Explain how your solution helps, focusing on benefits)",
|
||||
"Social proof (Optional: Testimonials, case studies, data)",
|
||||
"Call to action (Clearly state what you want them to do next)",
|
||||
"Closing paragraph (Reiterate benefit, create urgency/incentive)",
|
||||
"Complimentary close (Professional)",
|
||||
"Signature (Typed name and title)",
|
||||
"Enclosures (Optional: Brochure, pricing)"
|
||||
],
|
||||
"guidance": "Be persuasive, customer-focused, and clear about the value proposition. Focus on benefits, not just features. Make the call to action obvious."
|
||||
},
|
||||
"proposal": {
|
||||
"structure": [
|
||||
"Letterhead",
|
||||
"Date",
|
||||
"Recipient's address",
|
||||
"Subject line (Clear and descriptive)",
|
||||
"Salutation",
|
||||
"Introduction (State purpose: submitting a proposal)",
|
||||
"Problem statement/Needs assessment (Demonstrate understanding of client's needs)",
|
||||
"Proposed solution (Describe your solution in detail)",
|
||||
"Implementation plan (Outline steps and timeline)",
|
||||
"Costs and investment (Clearly state pricing and payment terms)",
|
||||
"Benefits and ROI (Explain the value the client will receive)",
|
||||
"Call to action (Suggest next steps: meeting, discussion)",
|
||||
"Closing paragraph (Express enthusiasm, availability for questions)",
|
||||
"Complimentary close (Professional)",
|
||||
"Signature (Typed name and title)",
|
||||
"Enclosures (Proposal document, appendix)"
|
||||
],
|
||||
"guidance": "Be clear, specific, and persuasive about your solution. Focus on the client's needs and the value you provide. Structure it logically."
|
||||
},
|
||||
"order": {
|
||||
"structure": [
|
||||
"Letterhead (Your company)",
|
||||
"Date",
|
||||
"Recipient's address (Supplier)",
|
||||
"Subject line (Purchase Order - [PO Number])",
|
||||
"Salutation",
|
||||
"Introduction (Reference quote/agreement, state purpose: placing an order)",
|
||||
"Order details (Item list with quantities, descriptions, unit prices, total)",
|
||||
"Delivery requirements (Shipping address, requested delivery date, shipping method)",
|
||||
"Payment terms (Reference agreed terms)",
|
||||
"Closing paragraph (Express expectation for timely delivery)",
|
||||
"Complimentary close (Professional)",
|
||||
"Signature (Typed name and title)"
|
||||
],
|
||||
"guidance": "Be clear, specific, and detailed about what you're ordering, quantities, delivery requirements, and payment terms. Include a purchase order number."
|
||||
},
|
||||
"quotation": {
|
||||
"structure": [
|
||||
"Letterhead (Your company)",
|
||||
"Date",
|
||||
"Recipient's address (Customer)",
|
||||
"Subject line (Quotation for [Product/Service])",
|
||||
"Salutation",
|
||||
"Introduction (Reference inquiry, state purpose: providing a quotation)",
|
||||
"Quotation details (List items/services, descriptions, unit prices, quantities, line totals)",
|
||||
"Pricing breakdown (Mention taxes, discounts, fees separately)",
|
||||
"Terms and conditions (Payment terms, delivery terms, warranty)",
|
||||
"Validity period (State how long the quote is valid)",
|
||||
"Next steps (How they can place an order)",
|
||||
"Closing paragraph (Express hope to do business, offer further assistance)",
|
||||
"Complimentary close (Professional)",
|
||||
"Signature (Typed name and title)"
|
||||
],
|
||||
"guidance": "Be clear, specific, and transparent about pricing, terms, and what's included or excluded. Make it easy for the customer to understand and accept."
|
||||
},
|
||||
"acknowledgment": {
|
||||
"structure": [
|
||||
"Letterhead",
|
||||
"Date",
|
||||
"Recipient's address",
|
||||
"Subject line (Acknowledgment of [Received Item/Request])",
|
||||
"Salutation",
|
||||
"Acknowledgment statement (Clearly state what you have received or are acknowledging)",
|
||||
"Details of what's being acknowledged (Reference number, date, brief description)",
|
||||
"Confirm understanding (Optional: Briefly restate the request/issue to show understanding)",
|
||||
"Next steps (Outline what will happen next, e.g., processing order, investigating issue)",
|
||||
"Timeline (Provide an estimated timeframe if possible)",
|
||||
"Closing paragraph (Express gratitude, offer further assistance)",
|
||||
"Complimentary close (Professional)",
|
||||
"Signature (Typed name and title)"
|
||||
],
|
||||
"guidance": "Be prompt, clear, and specific about what you're acknowledging. Set clear expectations for next steps and timelines."
|
||||
},
|
||||
"collection": {
|
||||
"structure": [
|
||||
"Letterhead",
|
||||
"Date",
|
||||
"Recipient's address",
|
||||
"Subject line (Invoice [Invoice Number] - Payment Due)",
|
||||
"Salutation",
|
||||
"Introduction (Reference invoice number and due date)",
|
||||
"Account status (Clearly state the outstanding amount)",
|
||||
"Payment request (Politely request payment)",
|
||||
"Payment options (Remind them how to pay)",
|
||||
"Consequences of non-payment (Optional: Briefly mention late fees or further action, depending on letter stage)",
|
||||
"Call to action (Request payment by a specific date)",
|
||||
"Closing paragraph (Express hope for prompt payment, offer to discuss)",
|
||||
"Complimentary close (Professional)",
|
||||
"Signature (Typed name and title)"
|
||||
],
|
||||
"guidance": "Be firm but professional. Clearly state the amount due, due date, and payment options. The tone may vary depending on how overdue the payment is."
|
||||
},
|
||||
"adjustment": {
|
||||
"structure": [
|
||||
"Letterhead",
|
||||
"Date",
|
||||
"Recipient's address (Customer who made a complaint)",
|
||||
"Subject line (Response to your inquiry - [Reference Number])",
|
||||
"Salutation",
|
||||
"Acknowledgment of complaint (Reference their communication and the issue)",
|
||||
"Investigation findings (Explain the outcome of your investigation)",
|
||||
"Adjustment offered (Clearly state the resolution: refund, replacement, credit, etc.)",
|
||||
"Apology (Optional: Express regret for the inconvenience)",
|
||||
"Preventive measures (Optional: Explain steps taken to prevent recurrence)",
|
||||
"Closing paragraph (Express hope for continued business, offer further assistance)",
|
||||
"Complimentary close (Professional)",
|
||||
"Signature (Typed name and title)"
|
||||
],
|
||||
"guidance": "Be responsive, empathetic, and solution-oriented. Clearly explain the adjustment and any preventive measures taken."
|
||||
},
|
||||
"credit": {
|
||||
"structure": [
|
||||
"Letterhead",
|
||||
"Date",
|
||||
"Recipient's address (Applicant)",
|
||||
"Subject line (Credit Application Status - [Applicant Name])",
|
||||
"Salutation",
|
||||
"Introduction (Reference their credit application and the purpose of the letter)",
|
||||
"Credit decision (Clearly state if credit is approved or denied)",
|
||||
"If approved: Credit terms (Credit limit, payment terms, interest rates)",
|
||||
"If denied: Reason for decision (Provide specific, compliant reasons)",
|
||||
"Requirements (If approved: any further steps or documents needed)",
|
||||
"Closing paragraph (If approved: Express welcome; If denied: Offer alternative options or appeals process)",
|
||||
"Complimentary close (Professional)",
|
||||
"Signature (Typed name and title)"
|
||||
],
|
||||
"guidance": "Be clear, specific, and transparent about the credit decision, terms, limits, or reasons for denial. Ensure compliance with regulations if denying credit."
|
||||
},
|
||||
"follow_up": {
|
||||
"structure": [
|
||||
"Letterhead",
|
||||
"Date",
|
||||
"Recipient's address",
|
||||
"Subject line (Following up on [Previous Communication/Meeting])",
|
||||
"Salutation",
|
||||
"Reference to previous communication (Mention date, topic, or meeting)",
|
||||
"Purpose of follow-up (Clearly state why you are writing again)",
|
||||
"Action items/Next steps (Remind of agreed-upon actions or propose next steps)",
|
||||
"Provide additional information (Optional)",
|
||||
"Call to action (If applicable, e.g., request a response, schedule a meeting)",
|
||||
"Closing paragraph (Reiterate interest, express anticipation)",
|
||||
"Complimentary close (Professional)",
|
||||
"Signature (Typed name and title)"
|
||||
],
|
||||
"guidance": "Be clear, specific, and action-oriented. Reference previous communication and clearly state the purpose of your follow-up and desired outcome."
|
||||
},
|
||||
# Default business letter template if subtype is not found
|
||||
"default": {
|
||||
"structure": [
|
||||
"Letterhead",
|
||||
"Date",
|
||||
"Recipient's address",
|
||||
"Subject line",
|
||||
"Salutation",
|
||||
"Introduction",
|
||||
"Body paragraphs",
|
||||
"Closing paragraph",
|
||||
"Complimentary close",
|
||||
"Signature"
|
||||
],
|
||||
"guidance": "Be professional, clear, and concise. Focus on the business purpose of your letter. The tone is typically formal to semi-formal."
|
||||
}
|
||||
},
|
||||
"cover": {
|
||||
"standard": {
|
||||
"structure": [
|
||||
"Your contact information",
|
||||
"Date",
|
||||
"Hiring Manager contact information (if known)",
|
||||
"Subject line (Job Application - [Your Name] - [Job Title])",
|
||||
"Salutation (Formal)",
|
||||
"Introduction (State the position you are applying for, where you saw the advertisement, and a brief statement of enthusiasm)",
|
||||
"Body paragraph 1 (Highlight skills and experience directly relevant to the job description - often 1-2 key qualifications)",
|
||||
"Body paragraph 2 (Provide a specific example or anecdote demonstrating your abilities)",
|
||||
"Body paragraph 3 (Connect your passion/goals to the company's mission/values - optional but effective)",
|
||||
"Closing paragraph (Reiterate interest, mention enclosed resume, call to action)",
|
||||
"Complimentary close (Formal)",
|
||||
"Signature (Typed name)"
|
||||
],
|
||||
"guidance": "Be professional, specific about your most relevant qualifications, and clear about your interest in the position. Tailor every cover letter to the specific job and company."
|
||||
},
|
||||
"career_change": {
|
||||
"structure": [
|
||||
"Your contact information",
|
||||
"Date",
|
||||
"Hiring Manager contact information",
|
||||
"Subject line (Job Application - [Your Name] - [Job Title])",
|
||||
"Salutation",
|
||||
"Introduction (State the position and acknowledge your career transition)",
|
||||
"Body paragraph 1 (Highlight transferable skills from previous roles)",
|
||||
"Body paragraph 2 (Explain your motivation for the career change and how your skills apply)",
|
||||
"Body paragraph 3 (Demonstrate understanding of the new industry/role)",
|
||||
"Closing paragraph (Reiterate enthusiasm, mention enclosed resume, call to action)",
|
||||
"Complimentary close",
|
||||
"Signature"
|
||||
],
|
||||
"guidance": "Focus on transferable skills and explain your career transition. Connect your past experience and new skills directly to the requirements of the target role."
|
||||
},
|
||||
"entry_level": {
|
||||
"structure": [
|
||||
"Your contact information",
|
||||
"Date",
|
||||
"Hiring Manager contact information",
|
||||
"Subject line (Job Application - [Your Name] - [Job Title])",
|
||||
"Salutation",
|
||||
"Introduction (State the position and your enthusiasm for the opportunity as a recent graduate/entrant)",
|
||||
"Body paragraph 1 (Highlight relevant education, coursework, GPA if strong)",
|
||||
"Body paragraph 2 (Describe relevant internships, projects, or volunteer experience)",
|
||||
"Body paragraph 3 (Showcase soft skills: teamwork, communication, eagerness to learn)",
|
||||
"Closing paragraph (Reiterate interest, mention attached resume, express availability for interview)",
|
||||
"Complimentary close",
|
||||
"Signature"
|
||||
],
|
||||
"guidance": "Emphasize education, relevant internships/projects, and transferable skills gained through academic or extracurricular activities. Show strong potential and enthusiasm."
|
||||
},
|
||||
"executive": {
|
||||
"structure": [
|
||||
"Your contact information",
|
||||
"Date",
|
||||
"Recipient's contact information (Senior Executive/Board Member)",
|
||||
"Subject line (Executive Application - [Your Name] - [Position])",
|
||||
"Salutation (Formal)",
|
||||
"Introduction (State position applying for, brief summary of executive profile)",
|
||||
"Body paragraph 1 (Highlight strategic leadership experience and key achievements)",
|
||||
"Body paragraph 2 (Discuss relevant industry expertise and market insights)",
|
||||
"Body paragraph 3 (Describe experience in driving growth, managing teams, achieving results)",
|
||||
"Closing paragraph (Reiterate interest, express desire to discuss contribution to the organization)",
|
||||
"Complimentary close (Formal)",
|
||||
"Signature"
|
||||
],
|
||||
"guidance": "Emphasize strategic leadership experience, significant achievements with measurable results, and industry expertise. Use a confident, authoritative, and forward-looking tone."
|
||||
},
|
||||
"creative": {
|
||||
"structure": [
|
||||
"Your contact information",
|
||||
"Date",
|
||||
"Hiring Manager contact information",
|
||||
"Subject line (Application - [Your Name] - [Creative Role])",
|
||||
"Salutation",
|
||||
"Creative introduction (Engaging hook related to the role or your passion)",
|
||||
"Body paragraph 1 (Highlight relevant creative experience and skills)",
|
||||
"Body paragraph 2 (Reference specific portfolio pieces or projects that showcase your style/abilities)",
|
||||
"Body paragraph 3 (Describe your creative process or approach)",
|
||||
"Closing paragraph (Reiterate enthusiasm, mention attached resume/portfolio link, call to action)",
|
||||
"Complimentary close",
|
||||
"Signature"
|
||||
],
|
||||
"guidance": "Use a more engaging and expressive style appropriate for a creative role while maintaining professionalism. Highlight specific creative achievements and link to your portfolio."
|
||||
},
|
||||
"technical": {
|
||||
"structure": [
|
||||
"Your contact information",
|
||||
"Date",
|
||||
"Hiring Manager contact information",
|
||||
"Subject line (Application - [Your Name] - [Technical Role])",
|
||||
"Salutation (Formal)",
|
||||
"Introduction (State position, source, and brief technical interest)",
|
||||
"Body paragraph 1 (Highlight specific technical skills and proficiencies relevant to the job description)",
|
||||
"Body paragraph 2 (Describe relevant technical projects or challenges you've solved)",
|
||||
"Body paragraph 3 (Discuss problem-solving abilities and experience with relevant technologies)",
|
||||
"Closing paragraph (Reiterate interest, mention attached resume, express availability for technical discussion/interview)",
|
||||
"Complimentary close (Formal)",
|
||||
"Signature"
|
||||
],
|
||||
"guidance": "Focus on technical skills, relevant projects, and problem-solving abilities. Use appropriate technical terminology accurately."
|
||||
},
|
||||
"academic": {
|
||||
"structure": [
|
||||
"Your contact information",
|
||||
"Date",
|
||||
"Recipient's contact information (Search Committee Chair)",
|
||||
"Subject line (Application for [Position] - [Your Name])",
|
||||
"Salutation (Formal)",
|
||||
"Introduction (State the position, the department, and express your strong interest)",
|
||||
"Body paragraph 1 (Discuss your research experience, focus on key projects and contributions)",
|
||||
"Body paragraph 2 (Describe your teaching philosophy and relevant teaching experience)",
|
||||
"Body paragraph 3 (Mention publications, presentations, grants, and other scholarly contributions)",
|
||||
"Closing paragraph (Reiterate enthusiasm for joining the faculty, express availability for interview/presentation)",
|
||||
"Complimentary close (Formal)",
|
||||
"Signature (Typed name)"
|
||||
],
|
||||
"guidance": "Focus on research experience, teaching philosophy, publications, and contributions to the field. Use a scholarly and professional tone suitable for academia."
|
||||
},
|
||||
"remote": {
|
||||
"structure": [
|
||||
"Your contact information",
|
||||
"Date",
|
||||
"Hiring Manager contact information",
|
||||
"Subject line (Remote Application - [Your Name] - [Job Title])",
|
||||
"Salutation",
|
||||
"Introduction (State the remote position, source, and enthusiasm for remote work)",
|
||||
"Body paragraph 1 (Highlight experience working remotely or independently)",
|
||||
"Body paragraph 2 (Emphasize self-management, time management, and organizational skills required for remote work)",
|
||||
"Body paragraph 3 (Describe strong written and verbal communication skills, essential for remote collaboration)",
|
||||
"Closing paragraph (Reiterate interest in the remote role, mention attached resume, express availability for video interview)",
|
||||
"Complimentary close",
|
||||
"Signature"
|
||||
],
|
||||
"guidance": "Emphasize self-motivation, excellent communication skills (especially written), time management, and any prior experience working independently or in remote teams."
|
||||
},
|
||||
"referral": {
|
||||
"structure": [
|
||||
"Your contact information",
|
||||
"Date",
|
||||
"Hiring Manager contact information",
|
||||
"Subject line (Referral Application - [Your Name] - [Job Title] - Referred by [Referrer's Name])",
|
||||
"Salutation",
|
||||
"Referral introduction (Immediately state who referred you and for what position)",
|
||||
"Body paragraph 1 (Briefly explain your connection to the referrer and how you learned about the role)",
|
||||
"Body paragraph 2 (Highlight key qualifications relevant to the job description)",
|
||||
"Body paragraph 3 (Express strong interest in the position and the company)",
|
||||
"Closing paragraph (Reiterate enthusiasm, mention attached resume, express availability for interview)",
|
||||
"Complimentary close",
|
||||
"Signature"
|
||||
],
|
||||
"guidance": "Mention the referral prominently and early. Explain your connection to the referrer and how it aligns with your interest in the role. Still, ensure you highlight your own qualifications."
|
||||
},
|
||||
# Default cover letter template if subtype is not found
|
||||
"default": {
|
||||
"structure": [
|
||||
"Contact information",
|
||||
"Date",
|
||||
"Recipient's information",
|
||||
"Salutation",
|
||||
"Introduction",
|
||||
"Body paragraphs",
|
||||
"Closing paragraph",
|
||||
"Complimentary close",
|
||||
"Signature"
|
||||
],
|
||||
"guidance": "Be professional, specific about your qualifications, and clear about your interest in the position. Tailor your letter to the specific job and company."
|
||||
}
|
||||
},
|
||||
# Overall default template if letter type is not recognized
|
||||
"default": {
|
||||
"structure": [
|
||||
"Introduction",
|
||||
"Body",
|
||||
"Conclusion"
|
||||
],
|
||||
"guidance": "Be clear, concise, and appropriate for your audience and purpose. This is a generic structure."
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
def get_template_by_type(letter_type: str, subtype: str = "default") -> Dict[str, Any]:
|
||||
"""
|
||||
Get a template for a specific letter type and subtype using a dictionary lookup.
|
||||
|
||||
Args:
|
||||
letter_type: Type of letter (e.g., "personal", "formal", "business", "cover").
|
||||
subtype: Subtype of letter (e.g., "congratulations", "application", "sales").
|
||||
Defaults to "default" if no subtype is specified.
|
||||
|
||||
Returns:
|
||||
Template dictionary with 'structure' (List[str]) and 'guidance' (str).
|
||||
Returns the default template if the letter type or subtype is not found,
|
||||
ensuring the return structure is always consistent.
|
||||
"""
|
||||
# Get templates for the specific letter type, or the overall default templates
|
||||
# .get() method is used for safe dictionary access with a default fallback
|
||||
type_templates = TEMPLATES.get(letter_type, TEMPLATES["default"])
|
||||
|
||||
# Get the template for the specific subtype, or the default for that letter type
|
||||
# Chain .get() calls to handle cases where subtype or the type's default is missing
|
||||
template = type_templates.get(subtype, type_templates.get("default", TEMPLATES["default"]))
|
||||
|
||||
# Ensure the returned template always has 'structure' (as a list) and 'guidance' (as a string) keys.
|
||||
# This adds robustness in case a template definition is incomplete.
|
||||
if "structure" not in template or not isinstance(template["structure"], list):
|
||||
# Fallback structure if missing or incorrect type
|
||||
template["structure"] = ["Introduction", "Body", "Conclusion"]
|
||||
# Update guidance to reflect that the structure was defaulted
|
||||
template["guidance"] = "Generic template structure applied due to missing or invalid definition."
|
||||
|
||||
if "guidance" not in template or not isinstance(template["guidance"], str):
|
||||
# Fallback guidance if missing or incorrect type
|
||||
template["guidance"] = "Generic guidance applied due to missing or invalid definition."
|
||||
|
||||
|
||||
return template
|
||||
|
||||
# Example usage (for testing purposes)
|
||||
if __name__ == '__main__':
|
||||
# Test cases to demonstrate functionality and default handling
|
||||
print("--- Testing Letter Templates Module ---")
|
||||
|
||||
# Test a known personal letter subtype
|
||||
personal_congrats = get_template_by_type("personal", "congratulations")
|
||||
print("\nPersonal Congratulations Template:")
|
||||
print(f"Structure: {personal_congrats['structure']}")
|
||||
print(f"Guidance: {personal_congrats['guidance']}")
|
||||
|
||||
# Test a known formal letter subtype
|
||||
formal_complaint = get_template_by_type("formal", "complaint")
|
||||
print("\nFormal Complaint Template:")
|
||||
print(f"Structure: {formal_complaint['structure']}")
|
||||
print(f"Guidance: {formal_complaint['guidance']}")
|
||||
|
||||
# Test a known business letter subtype
|
||||
business_sales = get_template_by_type("business", "sales")
|
||||
print("\nBusiness Sales Template:")
|
||||
print(f"Structure: {business_sales['structure']}")
|
||||
print(f"Guidance: {business_sales['guidance']}")
|
||||
|
||||
# Test a known cover letter subtype
|
||||
cover_entry_level = get_template_by_type("cover", "entry_level")
|
||||
print("\nCover Entry Level Template:")
|
||||
print(f"Structure: {cover_entry_level['structure']}")
|
||||
print(f"Guidance: {cover_entry_level['guidance']}")
|
||||
|
||||
# Test an unknown letter type (should fallback to overall default)
|
||||
unknown_type = get_template_by_type("unknown_type", "some_subtype")
|
||||
print("\nUnknown Type Template (Should be Overall Default):")
|
||||
print(f"Structure: {unknown_type['structure']}")
|
||||
print(f"Guidance: {unknown_type['guidance']}")
|
||||
|
||||
# Test a known letter type but unknown subtype (should fallback to type's default)
|
||||
personal_unknown_subtype = get_template_by_type("personal", "unknown_subtype")
|
||||
print("\nPersonal Unknown Subtype Template (Should be Personal Default):")
|
||||
print(f"Structure: {personal_unknown_subtype['structure']}")
|
||||
print(f"Guidance: {personal_unknown_subtype['guidance']}")
|
||||
|
||||
# Test with only letter type (should use type's default)
|
||||
formal_default = get_template_by_type("formal")
|
||||
print("\nFormal Default Template (No Subtype Specified):")
|
||||
print(f"Structure: {formal_default['structure']}")
|
||||
print(f"Guidance: {formal_default['guidance']}")
|
||||
236
lib/ai_writers/ai_letter_writer/main.py
Normal file
236
lib/ai_writers/ai_letter_writer/main.py
Normal file
@@ -0,0 +1,236 @@
|
||||
"""
|
||||
AI Letter Writer - Main Module
|
||||
|
||||
This module provides a comprehensive interface for generating various types of letters
|
||||
using AI assistance. It supports multiple letter formats, styles, and use cases.
|
||||
It uses Streamlit for the user interface.
|
||||
"""
|
||||
|
||||
import streamlit as st
|
||||
# Assuming these modules exist in a package structure
|
||||
from .letter_types import (
|
||||
business_letters,
|
||||
personal_letters,
|
||||
formal_letters,
|
||||
cover_letters,
|
||||
recommendation_letters,
|
||||
complaint_letters,
|
||||
thank_you_letters,
|
||||
invitation_letters
|
||||
)
|
||||
# Assuming these utility functions exist
|
||||
from .utils.letter_formatter import format_letter
|
||||
from .utils.letter_analyzer import analyze_letter_tone, check_formality
|
||||
from .utils.letter_templates import get_template_by_type
|
||||
|
||||
# Define the letter types and their properties
|
||||
LETTER_TYPES_CONFIG = [
|
||||
{
|
||||
"id": "business",
|
||||
"name": "Business Letters",
|
||||
"icon": "💼",
|
||||
"description": "Professional correspondence for business contexts.",
|
||||
"color": "#1E88E5", # Blue 600
|
||||
"module": business_letters
|
||||
},
|
||||
{
|
||||
"id": "personal",
|
||||
"name": "Personal Letters",
|
||||
"icon": "💌",
|
||||
"description": "Heartfelt messages for friends and family.",
|
||||
"color": "#43A047", # Green 600
|
||||
"module": personal_letters
|
||||
},
|
||||
{
|
||||
"id": "formal",
|
||||
"name": "Formal Letters",
|
||||
"icon": "📜",
|
||||
"description": "Official correspondence for institutions and authorities.",
|
||||
"color": "#5E35B1", # Deep Purple 600
|
||||
"module": formal_letters
|
||||
},
|
||||
{
|
||||
"id": "cover",
|
||||
"name": "Cover Letters",
|
||||
"icon": "📋",
|
||||
"description": "Job application letters to showcase your qualifications.",
|
||||
"color": "#FB8C00", # Orange 600
|
||||
"module": cover_letters
|
||||
},
|
||||
{
|
||||
"id": "recommendation",
|
||||
"name": "Recommendation Letters",
|
||||
"icon": "👍",
|
||||
"description": "Endorse colleagues, students, or employees.",
|
||||
"color": "#00ACC1", # Cyan 600
|
||||
"module": recommendation_letters
|
||||
},
|
||||
{
|
||||
"id": "complaint",
|
||||
"name": "Complaint Letters",
|
||||
"icon": "⚠️",
|
||||
"description": "Address issues with products, services, or situations.",
|
||||
"color": "#E53935", # Red 600
|
||||
"module": complaint_letters
|
||||
},
|
||||
{
|
||||
"id": "thank_you",
|
||||
"name": "Thank You Letters",
|
||||
"icon": "🙏",
|
||||
"description": "Express gratitude for various occasions.",
|
||||
"color": "#8E24AA", # Purple 600
|
||||
"module": thank_you_letters
|
||||
},
|
||||
{
|
||||
"id": "invitation",
|
||||
"name": "Invitation Letters",
|
||||
"icon": "🎉",
|
||||
"description": "Invite people to events, interviews, or gatherings.",
|
||||
"color": "#FFB300", # Amber 600
|
||||
"module": invitation_letters
|
||||
}
|
||||
]
|
||||
|
||||
# Map letter type IDs to their modules for easy access
|
||||
LETTER_MODULES_MAP = {config["id"]: config["module"] for config in LETTER_TYPES_CONFIG}
|
||||
|
||||
|
||||
def initialize_session_state() -> None:
|
||||
"""Initializes necessary Streamlit session state variables."""
|
||||
if "letter_type" not in st.session_state:
|
||||
st.session_state.letter_type = None
|
||||
if "letter_subtype" not in st.session_state:
|
||||
st.session_state.letter_subtype = None # Useful if a letter type has subtypes
|
||||
if "generated_letter" not in st.session_state:
|
||||
st.session_state.generated_letter = None
|
||||
if "letter_metadata" not in st.session_state:
|
||||
# Store information like sender, recipient, date, subject, tone, etc.
|
||||
st.session_state.letter_metadata = {}
|
||||
if "letter_input_data" not in st.session_state:
|
||||
# Store user inputs for letter generation
|
||||
st.session_state.letter_input_data = {}
|
||||
|
||||
|
||||
def display_letter_type_selection() -> None:
|
||||
"""Displays the letter type selection interface using a grid of styled containers with buttons."""
|
||||
|
||||
st.markdown("## Select Letter Type")
|
||||
|
||||
# Create a grid layout for the cards (3 columns)
|
||||
cols = st.columns(3)
|
||||
|
||||
# Display each letter type as a card with a button below it
|
||||
for i, letter_type_config in enumerate(LETTER_TYPES_CONFIG):
|
||||
with cols[i % 3]:
|
||||
# Use markdown to create a styled container for the card appearance
|
||||
st.markdown(
|
||||
f"""
|
||||
<div style="
|
||||
background-color: {letter_type_config['color']};
|
||||
padding: 20px;
|
||||
border-radius: 10px;
|
||||
margin-bottom: 10px; /* Space between card content and button */
|
||||
color: white;
|
||||
min-height: 180px; /* Ensure consistent minimum height */
|
||||
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.2);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: space-between; /* Distribute space within the card */
|
||||
">
|
||||
<h3 style="margin-top: 0; color: white;">{letter_type_config['icon']} {letter_type_config['name']}</h3>
|
||||
<p style="color: white;">{letter_type_config['description']}</p>
|
||||
</div>
|
||||
""",
|
||||
unsafe_allow_html=True
|
||||
)
|
||||
|
||||
# Place the Streamlit button below the styled container
|
||||
# Make the button expand to the width of the column for better alignment with the card
|
||||
if st.button(
|
||||
f"Select {letter_type_config['name']}",
|
||||
key=f"btn_select_{letter_type_config['id']}", # Unique key for each button
|
||||
use_container_width=True
|
||||
):
|
||||
st.session_state.letter_type = letter_type_config['id']
|
||||
# Clear previous state data when selecting a new type
|
||||
st.session_state.letter_subtype = None
|
||||
st.session_state.generated_letter = None
|
||||
st.session_state.letter_metadata = {}
|
||||
st.session_state.letter_input_data = {}
|
||||
st.rerun()
|
||||
|
||||
|
||||
def display_letter_interface(letter_type_id: str) -> None:
|
||||
"""
|
||||
Displays the interface for the selected letter type by calling the
|
||||
appropriate module's write function.
|
||||
|
||||
Args:
|
||||
letter_type_id: The ID string of the selected letter type.
|
||||
"""
|
||||
module = LETTER_MODULES_MAP.get(letter_type_id)
|
||||
|
||||
if module:
|
||||
try:
|
||||
# Call the main function (e.g., write_letter or main) from the selected module
|
||||
# Assuming the module has a function that renders its UI and handles generation
|
||||
module.write_letter() # Assuming the function is named 'write_letter'
|
||||
except AttributeError:
|
||||
st.error(f"Module for '{letter_type_id}' does not have a 'write_letter' function.")
|
||||
except Exception as e:
|
||||
st.error(f"An error occurred while loading the interface for '{letter_type_id}': {e}")
|
||||
else:
|
||||
st.error(f"Letter type module '{letter_type_id}' not found in map.")
|
||||
|
||||
|
||||
def write_letter() -> None:
|
||||
"""Main function for the AI Letter Writer interface."""
|
||||
|
||||
# Page title and description
|
||||
st.title("✉️ AI Letter Writer")
|
||||
st.markdown("""
|
||||
Create professional, personalized letters for any occasion. Select a letter type below to get started.
|
||||
Our AI will help you craft the perfect letter with the right tone, structure, and content.
|
||||
""")
|
||||
|
||||
# Initialize session state on first run
|
||||
initialize_session_state()
|
||||
|
||||
# Back button logic - only show if a letter type is selected
|
||||
if st.session_state.letter_type is not None:
|
||||
if st.button("← Back to Letter Types"):
|
||||
# Reset session state to return to selection
|
||||
st.session_state.letter_type = None
|
||||
st.session_state.letter_subtype = None
|
||||
st.session_state.generated_letter = None
|
||||
st.session_state.letter_metadata = {}
|
||||
st.session_state.letter_input_data = {}
|
||||
st.rerun() # Rerun to show the selection page
|
||||
|
||||
# Main navigation logic
|
||||
if st.session_state.letter_type is None:
|
||||
# Display letter type selection if no type is selected
|
||||
display_letter_type_selection()
|
||||
else:
|
||||
# Display the interface for the selected letter type
|
||||
display_letter_interface(st.session_state.letter_type)
|
||||
|
||||
# --- Placeholder for displaying generated letter and actions ---
|
||||
# This part would typically be handled within the specific letter type modules
|
||||
# after the letter is generated. However, if a common display is needed
|
||||
# after returning from the module function, it would go here, but this
|
||||
# requires the module function to somehow signal completion or store
|
||||
# the generated letter in session state. The current structure expects
|
||||
# the module's write_letter() to handle its entire lifecycle.
|
||||
|
||||
# Example of potentially displaying a generated letter after returning
|
||||
# (This assumes the module updates st.session_state.generated_letter)
|
||||
# if st.session_state.generated_letter:
|
||||
# st.subheader("Generated Letter Preview")
|
||||
# st.text_area("Your Letter", st.session_state.generated_letter, height=400)
|
||||
# # Add options like copy, download, analyze, edit, etc.
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
# Run the main letter writing function when the script is executed
|
||||
write_letter()
|
||||
1121
lib/ai_writers/ai_letter_writer/personal_letter.py
Normal file
1121
lib/ai_writers/ai_letter_writer/personal_letter.py
Normal file
File diff suppressed because it is too large
Load Diff
493
lib/ai_writers/ai_letter_writer/utils/letter_analyzer.py
Normal file
493
lib/ai_writers/ai_letter_writer/utils/letter_analyzer.py
Normal file
@@ -0,0 +1,493 @@
|
||||
"""
|
||||
Letter Analyzer Utility
|
||||
|
||||
This module provides functions for analyzing letter content, including tone,
|
||||
formality, readability, and offering basic suggestions for improvement.
|
||||
Note: The analysis methods provided here are simplified rule-based and
|
||||
keyword-based approaches. For more sophisticated analysis in a production
|
||||
environment, consider using advanced Natural Language Processing (NLP)
|
||||
libraries and models.
|
||||
"""
|
||||
|
||||
import re
|
||||
from typing import Dict, Any, Tuple, List
|
||||
|
||||
def analyze_letter_tone(content: str) -> Dict[str, float]:
|
||||
"""
|
||||
Analyze the tone of a letter based on the presence of specific keywords
|
||||
and phrases.
|
||||
|
||||
Args:
|
||||
content: The letter content to analyze.
|
||||
|
||||
Returns:
|
||||
Dictionary with tone scores (formal, friendly, assertive, etc.).
|
||||
Scores are based on the frequency of matching patterns and capped at 1.0.
|
||||
"""
|
||||
# This is a simplified version using keyword matching.
|
||||
# A more sophisticated approach would involve NLP libraries for sentiment and tone analysis.
|
||||
|
||||
# Initialize tone scores
|
||||
# Scores are arbitrary counts normalized in a simple way
|
||||
tone_scores = {
|
||||
"formal": 0.0,
|
||||
"friendly": 0.0,
|
||||
"assertive": 0.0,
|
||||
"respectful": 0.0,
|
||||
"urgent": 0.0,
|
||||
"apologetic": 0.0
|
||||
}
|
||||
|
||||
# Define patterns for different tones (case-insensitive)
|
||||
formal_patterns = [
|
||||
r"\bI am writing to\b",
|
||||
r"\bI would like to\b",
|
||||
r"\bplease find\b",
|
||||
r"\bregarding\b",
|
||||
r"\bpursuant to\b",
|
||||
r"\bhereby\b",
|
||||
r"\bthus\b",
|
||||
r"\btherefore\b",
|
||||
r"\bfurthermore\b",
|
||||
r"\bconsequently\b",
|
||||
r"\bnevertheless\b",
|
||||
r"\bmoreover\b",
|
||||
r"\benclosed\b", # Added common formal word
|
||||
r"\bherewith\b" # Added common formal word
|
||||
]
|
||||
|
||||
friendly_patterns = [
|
||||
r"\bhope you're well\b",
|
||||
r"\bhope this finds you well\b",
|
||||
r"\bgreat to hear\b",
|
||||
r"\blooking forward\b",
|
||||
r"\bthanks\b",
|
||||
r"\bappreciate\b",
|
||||
r"!", # Exclamation points often indicate friendly or excited tone
|
||||
r"\bexcited\b",
|
||||
r"\bgreat\b", # Common friendly adjective
|
||||
r"\bnice\b" # Common friendly adjective
|
||||
]
|
||||
|
||||
assertive_patterns = [
|
||||
r"\brequire\b",
|
||||
r"\bmust\b",
|
||||
r"\bneed\b",
|
||||
r"\bexpect\b",
|
||||
r"\bdemand\b",
|
||||
r"\binsist\b",
|
||||
r"\bimmediately\b",
|
||||
r"\baction\b", # Often used in assertive contexts
|
||||
r"\bresolution\b" # Can imply assertion
|
||||
]
|
||||
|
||||
respectful_patterns = [
|
||||
r"\brespectfully\b",
|
||||
r"\bhonored\b",
|
||||
r"\bplease\b",
|
||||
r"\bkindly\b",
|
||||
r"\bgrateful\b",
|
||||
r"\bthank you\b",
|
||||
r"\bappreciate\b",
|
||||
r"\bhumbly\b", # Added respectful word
|
||||
r"\bapologies\b" # Can show respect for impact
|
||||
]
|
||||
|
||||
urgent_patterns = [
|
||||
r"\burgent\b",
|
||||
r"\bas soon as possible\b",
|
||||
r"\bASAP\b",
|
||||
r"\bimmediately\b",
|
||||
r"\bpressing\b",
|
||||
r"\bcritical\b",
|
||||
r"\bdeadline\b",
|
||||
r"\bexpedite\b", # Added urgent word
|
||||
r"\bpromptly\b" # Added urgent word
|
||||
]
|
||||
|
||||
apologetic_patterns = [
|
||||
r"\bapologize\b",
|
||||
r"\bsorry\b",
|
||||
r"\bregret\b",
|
||||
r"\bmistake\b",
|
||||
r"\berror\b",
|
||||
r"\binconvenience\b",
|
||||
r"\bfault\b", # Added apologetic word
|
||||
r"\boversight\b" # Added apologetic word
|
||||
]
|
||||
|
||||
# Count pattern matches and update scores (arbitrary weighting)
|
||||
# A simple count multiplied by a factor acts as a basic indicator
|
||||
for pattern in formal_patterns:
|
||||
tone_scores["formal"] += len(re.findall(pattern, content, re.IGNORECASE)) * 0.2
|
||||
|
||||
for pattern in friendly_patterns:
|
||||
tone_scores["friendly"] += len(re.findall(pattern, content, re.IGNORECASE)) * 0.2
|
||||
|
||||
for pattern in assertive_patterns:
|
||||
tone_scores["assertive"] += len(re.findall(pattern, content, re.IGNORECASE)) * 0.2
|
||||
|
||||
for pattern in respectful_patterns:
|
||||
tone_scores["respectful"] += len(re.findall(pattern, content, re.IGNORECASE)) * 0.2
|
||||
|
||||
for pattern in urgent_patterns:
|
||||
tone_scores["urgent"] += len(re.findall(pattern, content, re.IGNORECASE)) * 0.2
|
||||
|
||||
for pattern in apologetic_patterns:
|
||||
tone_scores["apologetic"] += len(re.findall(pattern, content, re.IGNORECASE)) * 0.2
|
||||
|
||||
# Cap scores at 1.0 (arbitrary capping)
|
||||
# A more meaningful score might be relative frequency or use a proper model
|
||||
for tone in tone_scores:
|
||||
tone_scores[tone] = min(tone_scores[tone], 1.0)
|
||||
|
||||
return tone_scores
|
||||
|
||||
def check_formality(content: str) -> float:
|
||||
"""
|
||||
Check the formality level of a letter based on the presence of formal
|
||||
vs. informal indicators and contractions.
|
||||
|
||||
Args:
|
||||
content: The letter content to analyze.
|
||||
|
||||
Returns:
|
||||
Formality score between 0.0 (very informal) and 1.0 (very formal).
|
||||
Calculated as formal_count / (formal_count + informal_count).
|
||||
"""
|
||||
# This is a simplified version based on keyword counting.
|
||||
# More accurate formality analysis would require advanced NLP techniques.
|
||||
|
||||
# Define formal and informal indicators (case-insensitive)
|
||||
formal_indicators = [
|
||||
r"\bDear\b",
|
||||
r"\bSincerely\b",
|
||||
r"\bRegards\b",
|
||||
r"\bRespectfully\b",
|
||||
r"\bI am writing to\b",
|
||||
r"\bI would like to\b",
|
||||
r"\bplease find\b",
|
||||
r"\bregarding\b",
|
||||
r"\bpursuant to\b",
|
||||
r"\bhereby\b",
|
||||
r"\bthus\b",
|
||||
r"\btherefore\b",
|
||||
r"\bfurthermore\b",
|
||||
r"\bconsequently\b",
|
||||
r"\bnevertheless\b",
|
||||
r"\bmoreover\b",
|
||||
r"\benclosed\b",
|
||||
r"\bherewith\b",
|
||||
r"\bsincerely yours\b", # Added
|
||||
r"\bto whom it may concern\b" # Added
|
||||
]
|
||||
|
||||
informal_indicators = [
|
||||
r"\bHey\b",
|
||||
r"\bHi\b",
|
||||
r"\bWhat's up\b",
|
||||
r"\bCheers\b",
|
||||
r"\bThanks\b", # 'Thank you' is formal, 'Thanks' is informal
|
||||
r"\bTake care\b",
|
||||
r"\bSee you\b",
|
||||
r"\bLater\b",
|
||||
r"\bBye\b",
|
||||
r"\bLove\b", # As a closing
|
||||
r"\bXO\b",
|
||||
r"!+", # Multiple exclamation points
|
||||
r"\bawesome\b",
|
||||
r"\bcool\b",
|
||||
r"\bgreat\b",
|
||||
r"\bnice\b",
|
||||
r"\bbtw\b", # By the way
|
||||
r"\bimo\b", # In my opinion
|
||||
r"\blol\b" # Laugh out loud
|
||||
]
|
||||
|
||||
# Define common contractions (case-insensitive)
|
||||
contractions = [
|
||||
r"\bdon't\b", r"\bcan't\b", r"\bwon't\b", r"\bshouldn't\b",
|
||||
r"\bcouldn't\b", r"\bwouldn't\b", r"\bhasn't\b", r"\bhaven't\b",
|
||||
r"\bisn't\b", r"\baren't\b", r"\bwasn't\b", r"\bweren't\b",
|
||||
r"\bi'm\b", r"\byou're\b", r"\bhe's\b", r"\bshe's\b", r"\bit's\b",
|
||||
r"\bwe're\b", r"\bthey're\b", r"\bi've\b", r"\byou've\b",
|
||||
r"\bwe've\b", r"\bthey've\b", r"\bi'd\b", r"\byou'd\b",
|
||||
r"\bhe'd\b", r"\bshe'd\b", r"\bit'd\b", r"\bwe'd\b", r"\bthey'd\b",
|
||||
r"\bi'll\b", r"\byou'll\b", r"\bhe'll\b", r"\bshe'll\b", r"\bit'll\b",
|
||||
r"\bwe'll\b", r"\bthey'll\b"
|
||||
]
|
||||
|
||||
formal_count = 0
|
||||
for pattern in formal_indicators:
|
||||
formal_count += len(re.findall(pattern, content, re.IGNORECASE))
|
||||
|
||||
informal_count = 0
|
||||
for pattern in informal_indicators:
|
||||
informal_count += len(re.findall(pattern, content, re.IGNORECASE))
|
||||
|
||||
# Count contractions as informal indicators
|
||||
for pattern in contractions:
|
||||
informal_count += len(re.findall(pattern, content, re.IGNORECASE))
|
||||
|
||||
# Calculate formality score
|
||||
total_indicators = formal_count + informal_count
|
||||
if total_indicators == 0:
|
||||
# If no indicators found, return a neutral score
|
||||
return 0.5
|
||||
|
||||
# Score is the proportion of formal indicators
|
||||
formality_score = formal_count / total_indicators
|
||||
return formality_score
|
||||
|
||||
def count_syllables_simple(word: str) -> int:
|
||||
"""
|
||||
Counts syllables in a word using a simplified heuristic.
|
||||
This method is not linguistically perfect but provides a basic estimate
|
||||
for readability formulas.
|
||||
|
||||
Args:
|
||||
word: The word string.
|
||||
|
||||
Returns:
|
||||
Estimated syllable count.
|
||||
"""
|
||||
word = word.lower()
|
||||
if len(word) <= 3:
|
||||
# Assume short words have one syllable
|
||||
return 1
|
||||
|
||||
# Remove common silent endings like 'e', 'es', 'ed'
|
||||
if word.endswith(('es', 'ed')):
|
||||
word = word[:-2]
|
||||
elif word.endswith('e'):
|
||||
word = word[:-1]
|
||||
|
||||
# Count vowel groups (consecutive vowels count as one syllable)
|
||||
vowels = 'aeiouy'
|
||||
count = 0
|
||||
prev_is_vowel = False
|
||||
|
||||
for char in word:
|
||||
is_vowel = char in vowels
|
||||
if is_vowel and not prev_is_vowel:
|
||||
count += 1
|
||||
prev_is_vowel = is_vowel
|
||||
|
||||
# Ensure at least one syllable is counted
|
||||
return max(1, count)
|
||||
|
||||
|
||||
def get_readability_metrics(content: str) -> Dict[str, Any]:
|
||||
"""
|
||||
Calculate readability metrics for a letter using simplified methods
|
||||
like Flesch Reading Ease.
|
||||
|
||||
Args:
|
||||
content: The letter content to analyze.
|
||||
|
||||
Returns:
|
||||
Dictionary with readability metrics: word_count, sentence_count,
|
||||
avg_words_per_sentence, flesch_reading_ease, reading_level.
|
||||
"""
|
||||
# Split content into words and sentences using simple regex
|
||||
words = re.findall(r'\b\w+\b', content)
|
||||
# Split by common sentence terminators, handling potential multiple marks
|
||||
sentences = re.split(r'[.!?]+\s*', content)
|
||||
# Filter out empty strings resulting from the split (e.g., trailing punctuation)
|
||||
sentences = [s for s in sentences if s.strip()]
|
||||
|
||||
word_count = len(words)
|
||||
sentence_count = len(sentences)
|
||||
syllable_count = sum(count_syllables_simple(word) for word in words)
|
||||
|
||||
if word_count == 0 or sentence_count == 0:
|
||||
return {
|
||||
"word_count": word_count,
|
||||
"sentence_count": sentence_count,
|
||||
"avg_words_per_sentence": 0.0,
|
||||
"flesch_reading_ease": 0.0,
|
||||
"reading_level": "N/A"
|
||||
}
|
||||
|
||||
# Calculate average words per sentence
|
||||
avg_words_per_sentence = word_count / sentence_count
|
||||
|
||||
# Calculate Flesch Reading Ease Score
|
||||
# Formula: 206.835 - (1.015 * AvgWordsPerSentence) - (84.6 * AvgSyllablesPerWord)
|
||||
# AvgSyllablesPerWord = syllable_count / word_count
|
||||
avg_syllables_per_word = syllable_count / word_count if word_count > 0 else 0
|
||||
|
||||
flesch = 206.835 - (1.015 * avg_words_per_sentence) - (84.6 * avg_syllables_per_word)
|
||||
# Clamp score between 0 and 100
|
||||
flesch = max(0.0, min(100.0, flesch))
|
||||
|
||||
# Determine reading level based on Flesch score ranges
|
||||
if flesch >= 90:
|
||||
reading_level = "Very Easy (5th grade)"
|
||||
elif flesch >= 80:
|
||||
reading_level = "Easy (6th grade)"
|
||||
elif flesch >= 70:
|
||||
reading_level = "Fairly Easy (7th grade)"
|
||||
elif flesch >= 60:
|
||||
reading_level = "Standard (8th-9th grade)"
|
||||
elif flesch >= 50:
|
||||
reading_level = "Fairly Difficult (10th-12th grade)"
|
||||
elif flesch >= 30:
|
||||
reading_level = "Difficult (College)"
|
||||
else:
|
||||
reading_level = "Very Difficult (Graduate)"
|
||||
|
||||
return {
|
||||
"word_count": word_count,
|
||||
"sentence_count": sentence_count,
|
||||
"avg_words_per_sentence": round(avg_words_per_sentence, 2), # Rounded for display
|
||||
"flesch_reading_ease": round(flesch, 2), # Rounded for display
|
||||
"reading_level": reading_level
|
||||
}
|
||||
|
||||
def suggest_improvements(content: str, letter_type: str) -> List[str]:
|
||||
"""
|
||||
Suggest improvements for a letter based on its content, basic analysis,
|
||||
and target letter type.
|
||||
|
||||
Args:
|
||||
content: The letter content to analyze.
|
||||
letter_type: The type of letter (e.g., "business", "cover", "personal").
|
||||
|
||||
Returns:
|
||||
List of improvement suggestions strings.
|
||||
"""
|
||||
suggestions = []
|
||||
|
||||
words = re.findall(r'\b\w+\b', content)
|
||||
word_count = len(words)
|
||||
|
||||
# Basic length check based on letter type
|
||||
if letter_type in ["business", "formal"]:
|
||||
if word_count < 100 and word_count > 10: # Avoid suggesting for very short placeholders
|
||||
suggestions.append("Consider adding more details to make your letter more comprehensive.")
|
||||
elif word_count > 600: # Increased max length slightly
|
||||
suggestions.append("Your letter is quite long. Consider condensing it for better readability and focus.")
|
||||
elif letter_type == "cover":
|
||||
if word_count < 150 and word_count > 10: # Avoid suggesting for very short placeholders
|
||||
suggestions.append("Your cover letter may be too brief. Consider highlighting more of your relevant qualifications.")
|
||||
elif word_count > 500: # Increased max length slightly
|
||||
suggestions.append("Your cover letter is quite long. Consider focusing on your most relevant qualifications and experiences.")
|
||||
elif letter_type == "recommendation":
|
||||
if word_count < 150 and word_count > 10:
|
||||
suggestions.append("Consider adding more specific examples or anecdotes to strengthen the recommendation.")
|
||||
elif word_count > 600:
|
||||
suggestions.append("Your recommendation letter is quite long. Ensure it remains focused and impactful.")
|
||||
|
||||
|
||||
# Check for overuse of "I" (simple count-based heuristic)
|
||||
# Count "I" as a standalone word
|
||||
i_count = len(re.findall(r"\bI\b", content))
|
||||
# Avoid suggestion for very short content or content with few sentences
|
||||
sentence_count = len(re.split(r'[.!?]+\s*', content.strip()))
|
||||
if sentence_count > 2 and word_count > 50 and i_count > sentence_count * 1.5: # Suggest if 'I' count is significantly higher than sentence count
|
||||
suggestions.append("Your letter contains many uses of 'I'. Consider rephrasing some sentences to focus more on the recipient or the subject matter.")
|
||||
|
||||
|
||||
# Check for expression of gratitude (using common phrases)
|
||||
gratitude_patterns = [r"\bthank you\b", r"\bgrateful\b", r"\bappreciate\b"]
|
||||
has_gratitude = any(re.search(pattern, content, re.IGNORECASE) for pattern in gratitude_patterns)
|
||||
# Suggest adding gratitude, but avoid for letter types where it might be less common (e.g., some complaint letters)
|
||||
if not has_gratitude and letter_type not in ["complaint", "urgent"]:
|
||||
suggestions.append("Consider expressing gratitude or appreciation somewhere in your letter.")
|
||||
|
||||
# Check for clear call to action (using common phrases)
|
||||
# Phrases indicating desired action or next step
|
||||
action_phrases = [
|
||||
"look forward to", "please", "would appreciate", "request",
|
||||
"hope to", "call me", "email me", "contact me", "schedule",
|
||||
"arrange", "require action", "next steps"
|
||||
]
|
||||
has_call_to_action = any(phrase in content.lower() for phrase in action_phrases)
|
||||
# Suggest adding a call to action for relevant letter types
|
||||
if not has_call_to_action and letter_type in ["business", "cover", "complaint", "invitation"]:
|
||||
suggestions.append("Consider adding a clear call to action or outlining the desired next steps.")
|
||||
|
||||
# Check for proper closing (using common phrases)
|
||||
closing_patterns = [
|
||||
r"\bSincerely\b", r"\bRegards\b", r"\bThank you\b", r"\bBest regards\b",
|
||||
r"\bYours sincerely\b", r"\bYours faithfully\b", r"\bRespectfully\b",
|
||||
r"\bBest wishes\b", r"\bKind regards\b"
|
||||
]
|
||||
# Check if any standard closing phrase is present, typically near the end
|
||||
# A more robust check might look specifically at the last paragraph/lines
|
||||
has_proper_closing = any(re.search(pattern, content[-200:], re.IGNORECASE) for pattern in closing_patterns) # Check last 200 chars
|
||||
|
||||
if not has_proper_closing and word_count > 20: # Avoid suggesting for very short snippets
|
||||
suggestions.append("Consider adding a proper closing phrase (e.g., Sincerely, Regards) followed by your name.")
|
||||
|
||||
return suggestions
|
||||
|
||||
# Example usage (for testing purposes, not part of the module's core functionality)
|
||||
if __name__ == '__main__':
|
||||
sample_formal_letter = """
|
||||
Dear Mr. Smith,
|
||||
|
||||
I am writing to follow up regarding the project proposal submitted on October 26, 2023.
|
||||
We believe the proposed solution aligns well with your stated requirements.
|
||||
Please find the revised budget document attached for your review.
|
||||
We look forward to your feedback at your earliest convenience.
|
||||
|
||||
Sincerely,
|
||||
Jane Doe
|
||||
"""
|
||||
|
||||
sample_informal_letter = """
|
||||
Hey John,
|
||||
|
||||
Hope you're doing well! Just wanted to quickly touch base about the party next week.
|
||||
Excited to catch up with everyone! Let me know if you need any help setting up.
|
||||
Thanks!
|
||||
|
||||
Best,
|
||||
Alex
|
||||
"""
|
||||
|
||||
sample_complaint_letter = """
|
||||
To Whom It May Concern,
|
||||
|
||||
I am writing to complain about the faulty product I received on November 1, 2023 (Order #12345).
|
||||
The device stopped working after only two days of use. I require a full refund or replacement immediately.
|
||||
I expect a prompt response regarding this issue.
|
||||
|
||||
Sincerely,
|
||||
Concerned Customer
|
||||
"""
|
||||
|
||||
print("--- Analyzing Formal Letter ---")
|
||||
tone = analyze_letter_tone(sample_formal_letter)
|
||||
formality = check_formality(sample_formal_letter)
|
||||
readability = get_readability_metrics(sample_formal_letter)
|
||||
suggestions = suggest_improvements(sample_formal_letter, "business")
|
||||
|
||||
print(f"Tone: {tone}")
|
||||
print(f"Formality: {formality:.2f}")
|
||||
print(f"Readability: {readability}")
|
||||
print(f"Suggestions: {suggestions}")
|
||||
|
||||
print("\n--- Analyzing Informal Letter ---")
|
||||
tone = analyze_letter_tone(sample_informal_letter)
|
||||
formality = check_formality(sample_informal_letter)
|
||||
readability = get_readability_metrics(sample_informal_letter)
|
||||
suggestions = suggest_improvements(sample_informal_letter, "personal")
|
||||
|
||||
print(f"Tone: {tone}")
|
||||
print(f"Formality: {formality:.2f}")
|
||||
print(f"Readability: {readability}")
|
||||
print(f"Suggestions: {suggestions}")
|
||||
|
||||
print("\n--- Analyzing Complaint Letter ---")
|
||||
tone = analyze_letter_tone(sample_complaint_letter)
|
||||
formality = check_formality(sample_complaint_letter)
|
||||
readability = get_readability_metrics(sample_complaint_letter)
|
||||
suggestions = suggest_improvements(sample_complaint_letter, "complaint")
|
||||
|
||||
print(f"Tone: {tone}")
|
||||
print(f"Formality: {formality:.2f}")
|
||||
print(f"Readability: {readability}")
|
||||
print(f"Suggestions: {suggestions}")
|
||||
545
lib/ai_writers/ai_letter_writer/utils/letter_formatter.py
Normal file
545
lib/ai_writers/ai_letter_writer/utils/letter_formatter.py
Normal file
@@ -0,0 +1,545 @@
|
||||
"""
|
||||
Letter Formatter Module
|
||||
|
||||
This module provides utilities for formatting letters and generating HTML
|
||||
previews in different styles (Personal, Formal, Business, Cover).
|
||||
The formatting functions here are primarily focused on generating HTML
|
||||
for preview purposes, applying standard layout conventions for each letter type
|
||||
using inline CSS styles.
|
||||
"""
|
||||
|
||||
import re
|
||||
from typing import Dict, Any
|
||||
|
||||
def format_letter(content: str, metadata: Dict[str, Any], letter_type: str = "personal") -> str:
|
||||
"""
|
||||
Format a letter with basic structure (paragraphs).
|
||||
|
||||
Args:
|
||||
content: The raw letter content (string).
|
||||
metadata: Dictionary containing metadata (currently not used for formatting in this placeholder).
|
||||
letter_type: Type of letter (personal, formal, business, cover).
|
||||
|
||||
Returns:
|
||||
Formatted letter content (currently just returns the input content).
|
||||
This is a placeholder and would be expanded to apply specific
|
||||
formatting rules (e.g., indentation, spacing) based on letter type
|
||||
and metadata in a full implementation before generating HTML.
|
||||
For this module, we primarily rely on the HTML generation functions
|
||||
to handle the visual formatting.
|
||||
"""
|
||||
# This is a basic placeholder. In a real implementation, this function
|
||||
# might process the raw text content to add indentation, adjust line breaks,
|
||||
# or handle specific markdown-like syntax before it's passed to the
|
||||
# HTML generation functions.
|
||||
# For now, we assume the input `content` uses double newlines for paragraphs.
|
||||
return content
|
||||
|
||||
def get_letter_preview_html(content: str, metadata: Dict[str, Any], letter_type: str = "personal") -> str:
|
||||
"""
|
||||
Generate HTML for letter preview based on letter type and metadata.
|
||||
This function acts as a dispatcher to the specific HTML generation functions.
|
||||
|
||||
Args:
|
||||
content: The letter content string.
|
||||
metadata: Dictionary containing metadata like sender/recipient info, date, etc.
|
||||
letter_type: Type of letter ("personal", "formal", "business", "cover").
|
||||
Defaults to "personal".
|
||||
|
||||
Returns:
|
||||
HTML string for letter preview, styled appropriately for the type.
|
||||
Includes basic styling for a printable letter appearance.
|
||||
"""
|
||||
# Dispatch to the appropriate HTML generation function based on letter type
|
||||
# Pass the content and metadata to the specific functions
|
||||
if letter_type == "personal":
|
||||
return get_personal_letter_html(content, metadata)
|
||||
elif letter_type == "formal":
|
||||
return get_formal_letter_html(content, metadata)
|
||||
elif letter_type == "business":
|
||||
return get_business_letter_html(content, metadata)
|
||||
elif letter_type == "cover":
|
||||
return get_cover_letter_html(content, metadata)
|
||||
else:
|
||||
# Fallback for unrecognized types, displaying raw content in a styled box
|
||||
return f"""
|
||||
<div style="max-width: 800px; margin: 20px auto; padding: 20px; border: 1px solid #ccc; font-family: sans-serif; line-height: 1.6; background-color: #fff8f8; color: #333; border-radius: 8px;">
|
||||
<h3 style="color: #e53935; margin-top: 0;">Preview Unavailable for Unknown Letter Type</h3>
|
||||
<p>The letter type '{letter_type}' is not recognized. Displaying raw content:</p>
|
||||
<pre style="white-space: pre-wrap; word-wrap: break-word; background-color: #f8f8f8; padding: 15px; border: 1px solid #ddd; border-radius: 4px; overflow-x: auto;">{content}</pre>
|
||||
</div>
|
||||
"""
|
||||
|
||||
def get_personal_letter_html(content: str, metadata: Dict[str, Any]) -> str:
|
||||
"""
|
||||
Generate HTML for personal letter preview with basic styling.
|
||||
Uses a more informal layout and font style.
|
||||
|
||||
Args:
|
||||
content: The letter content string.
|
||||
metadata: Dictionary containing personal letter metadata (sender_name, date).
|
||||
|
||||
Returns:
|
||||
HTML string for personal letter preview.
|
||||
"""
|
||||
# Extract metadata with default empty strings for robustness
|
||||
sender_name = metadata.get("sender_name", "")
|
||||
# recipient_name = metadata.get("recipient_name", "") # Less common in personal body, but could be used in greeting
|
||||
date = metadata.get("date", "")
|
||||
|
||||
# Split content into paragraphs based on double newlines
|
||||
# Use list comprehension to strip whitespace and filter out empty strings
|
||||
paragraphs = [p.strip() for p in content.split("\n\n") if p.strip()]
|
||||
|
||||
# Format paragraphs as HTML <p> tags with bottom margin
|
||||
formatted_paragraphs = "".join(f"<p style='margin-bottom: 1em;'>{paragraph}</p>" for paragraph in paragraphs)
|
||||
|
||||
# Basic HTML structure with inline styles for a personal letter feel
|
||||
# Styles aim for a warm, readable appearance
|
||||
html = f"""
|
||||
<div style="max-width: 700px; margin: 20px auto; padding: 30px; border: 1px solid #e0e0e0; border-radius: 8px; background-color: #ffffff; font-family: 'Georgia', serif; line-height: 1.7; color: #333; box-shadow: 0 2px 4px rgba(0,0,0,0.1);">
|
||||
<div style="text-align: right; margin-bottom: 30px; font-size: 0.9em; color: #555;">
|
||||
{date if date else "[Date]"}
|
||||
</div>
|
||||
|
||||
<div style="margin-bottom: 30px;">
|
||||
{formatted_paragraphs if formatted_paragraphs else "<p style='color: #888;'>Letter content goes here...</p>"}
|
||||
</div>
|
||||
|
||||
<div style="margin-top: 40px;">
|
||||
<p style="margin-bottom: 0.5em;">Sincerely,</p>
|
||||
<p style="font-weight: bold; margin-top: 0;">{sender_name if sender_name else "[Sender Name]"}</p>
|
||||
</div>
|
||||
</div>
|
||||
"""
|
||||
return html
|
||||
|
||||
def get_formal_letter_html(content: str, metadata: Dict[str, Any]) -> str:
|
||||
"""
|
||||
Generate HTML for formal letter preview with standard formal structure and styling.
|
||||
Uses a more professional layout and font style (Arial/sans-serif).
|
||||
|
||||
Args:
|
||||
content: The letter content string.
|
||||
metadata: Dictionary containing formal letter metadata.
|
||||
|
||||
Returns:
|
||||
HTML string for formal letter preview.
|
||||
"""
|
||||
# Extract metadata with default empty strings
|
||||
sender_name = metadata.get("sender_name", "")
|
||||
sender_title = metadata.get("sender_title", "")
|
||||
sender_organization = metadata.get("sender_organization", "")
|
||||
# Replace newlines in address for HTML display
|
||||
sender_address = metadata.get("sender_address", "").replace("\n", "<br>")
|
||||
sender_phone = metadata.get("sender_phone", "")
|
||||
sender_email = metadata.get("sender_email", "")
|
||||
|
||||
recipient_name = metadata.get("recipient_name", "")
|
||||
recipient_title = metadata.get("recipient_title", "")
|
||||
recipient_organization = metadata.get("recipient_organization", "")
|
||||
# Replace newlines in address for HTML display
|
||||
recipient_address = metadata.get("recipient_address", "").replace("\n", "<br>")
|
||||
|
||||
date = metadata.get("date", "")
|
||||
subject = metadata.get("subject", "") # Added subject line
|
||||
salutation = metadata.get("salutation", "Dear Sir/Madam,") # Added salutation
|
||||
complimentary_close = metadata.get("complimentary_close", "Sincerely,") # Added close
|
||||
|
||||
# Determine alignment based on letter format (simplified)
|
||||
# Full Block: All aligned left
|
||||
# Modified Block: Sender address block, date, closing, and signature are right-aligned
|
||||
letter_format = metadata.get("letter_format", "Full Block")
|
||||
sender_address_align = "left"
|
||||
date_align = "left"
|
||||
closing_align = "left"
|
||||
|
||||
if letter_format == "Modified Block":
|
||||
sender_address_align = "right"
|
||||
date_align = "right"
|
||||
closing_align = "right"
|
||||
|
||||
# Split content into paragraphs based on double newlines
|
||||
paragraphs = [p.strip() for p in content.split("\n\n") if p.strip()]
|
||||
|
||||
# Format paragraphs as HTML <p> tags with bottom margin
|
||||
formatted_paragraphs = "".join(f"<p style='margin-bottom: 1em;'>{paragraph}</p>" for paragraph in paragraphs)
|
||||
|
||||
# Basic HTML structure with inline styles for a formal letter
|
||||
html = f"""
|
||||
<div style="max-width: 800px; margin: 20px auto; padding: 30px; border: 1px solid #d0d0d0; border-radius: 8px; background-color: #ffffff; font-family: 'Arial', sans-serif; line-height: 1.6; color: #333; box-shadow: 0 2px 4px rgba(0,0,0,0.1);">
|
||||
|
||||
<div style="text-align: {sender_address_align}; margin-bottom: 20px; font-size: 0.9em;">
|
||||
<p style="margin: 0;">{sender_name if sender_name else "[Sender Name]"}{', ' + sender_title if sender_title else ''}</p>
|
||||
<p style="margin: 0;">{sender_organization if sender_organization else "[Sender Organization]"}</p>
|
||||
<p style="margin: 0;">{sender_address if sender_address else "[Sender Address]"}</p>
|
||||
<p style="margin: 0;">{sender_phone}</p>
|
||||
<p style="margin: 0;">{sender_email}</p>
|
||||
</div>
|
||||
|
||||
<div style="text-align: {date_align}; margin-bottom: 20px;">
|
||||
<p style="margin: 0;">{date if date else "[Date]"}</p>
|
||||
</div>
|
||||
|
||||
<div style="margin-bottom: 20px; font-size: 0.9em;">
|
||||
<p style="margin: 0;">{recipient_name if recipient_name else "[Recipient Name]"}{', ' + recipient_title if recipient_title else ''}</p>
|
||||
<p style="margin: 0;">{recipient_organization if recipient_organization else "[Recipient Organization]"}</p>
|
||||
<p style="margin: 0;">{recipient_address if recipient_address else "[Recipient Address]"}</p>
|
||||
</div>
|
||||
|
||||
<div style="margin-bottom: 20px;">
|
||||
<p style="margin: 0; font-weight: bold;">Subject: {subject if subject else "[Subject Line]"}</p>
|
||||
</div>
|
||||
|
||||
<div style="margin-bottom: 20px;">
|
||||
<p style="margin: 0;">{salutation}</p>
|
||||
</div>
|
||||
|
||||
<div style="margin-bottom: 20px;">
|
||||
{formatted_paragraphs if formatted_paragraphs else "<p style='color: #888;'>Letter content goes here...</p>"}
|
||||
</div>
|
||||
|
||||
<div style="margin-top: 40px; text-align: {closing_align};">
|
||||
<p style="margin-bottom: 0.5em;">{complimentary_close}</p>
|
||||
<p style="font-weight: bold; margin: 0;">{sender_name}</p>
|
||||
<p style="margin: 0; font-size: 0.9em;">{sender_title}</p>
|
||||
<p style="margin: 0; font-size: 0.9em;">{sender_organization}</p>
|
||||
</div>
|
||||
</div>
|
||||
"""
|
||||
return html
|
||||
|
||||
def get_business_letter_html(content: str, metadata: Dict[str, Any]) -> str:
|
||||
"""
|
||||
Generate HTML for business letter preview with standard business structure and styling.
|
||||
Includes optional letterhead.
|
||||
|
||||
Args:
|
||||
content: The letter content string.
|
||||
metadata: Dictionary containing business letter metadata.
|
||||
|
||||
Returns:
|
||||
HTML string for business letter preview.
|
||||
"""
|
||||
# Extract metadata with default empty strings
|
||||
sender_company = metadata.get("sender_company", "")
|
||||
sender_name = metadata.get("sender_name", "")
|
||||
sender_title = metadata.get("sender_title", "")
|
||||
sender_address = metadata.get("sender_address", "").replace("\n", "<br>")
|
||||
sender_phone = metadata.get("sender_phone", "")
|
||||
sender_email = metadata.get("sender_email", "")
|
||||
sender_website = metadata.get("sender_website", "")
|
||||
|
||||
recipient_company = metadata.get("recipient_company", "")
|
||||
recipient_name = metadata.get("recipient_name", "")
|
||||
recipient_title = metadata.get("recipient_title", "")
|
||||
recipient_address = metadata.get("recipient_address", "").replace("\n", "<br>")
|
||||
|
||||
date = metadata.get("date", "")
|
||||
subject = metadata.get("subject", "") # Added subject line
|
||||
salutation = metadata.get("salutation", "Dear Sir/Madam,") # Added salutation
|
||||
complimentary_close = metadata.get("complimentary_close", "Sincerely,") # Added close
|
||||
|
||||
# Determine alignment based on letter format (simplified)
|
||||
letter_format = metadata.get("letter_format", "Full Block")
|
||||
sender_info_align = "left"
|
||||
date_align = "left"
|
||||
closing_align = "left"
|
||||
|
||||
if letter_format == "Modified Block":
|
||||
sender_info_align = "right"
|
||||
date_align = "right"
|
||||
closing_align = "right"
|
||||
|
||||
# Include letterhead logic
|
||||
include_letterhead = metadata.get("include_letterhead", True)
|
||||
|
||||
# Split content into paragraphs based on double newlines
|
||||
paragraphs = [p.strip() for p in content.split("\n\n") if p.strip()]
|
||||
|
||||
# Format paragraphs as HTML <p> tags with bottom margin
|
||||
formatted_paragraphs = "".join(f"<p style='margin-bottom: 1em;'>{paragraph}</p>" for paragraph in paragraphs)
|
||||
|
||||
# Create letterhead HTML if included and company name is provided
|
||||
letterhead_html = ""
|
||||
if include_letterhead and sender_company:
|
||||
letterhead_html = f"""
|
||||
<div style="padding-bottom: 15px; margin-bottom: 20px; border-bottom: 1px solid #eee;">
|
||||
<h2 style="margin: 0; color: #333; font-size: 1.5em;">{sender_company}</h2>
|
||||
<p style="margin: 5px 0 0 0; font-size: 0.9em; color: #555;">
|
||||
{sender_address.replace('<br>', ', ') if sender_address else ''}
|
||||
{' | ' + sender_phone if sender_phone else ''}
|
||||
{' | ' + sender_email if sender_email else ''}
|
||||
{' | ' + sender_website if sender_website else ''}
|
||||
</p>
|
||||
</div>
|
||||
"""
|
||||
|
||||
# Basic HTML structure with inline styles for a business letter
|
||||
html = f"""
|
||||
<div style="max-width: 800px; margin: 20px auto; padding: 30px; border: 1px solid #d0d0d0; border-radius: 8px; background-color: #ffffff; font-family: 'Arial', sans-serif; line-height: 1.6; color: #333; box-shadow: 0 2px 4px rgba(0,0,0,0.1);">
|
||||
{letterhead_html}
|
||||
|
||||
<div style="text-align: {date_align}; margin-bottom: 20px;">
|
||||
<p style="margin: 0;">{date if date else "[Date]"}</p>
|
||||
</div>
|
||||
|
||||
<div style="margin-bottom: 20px; font-size: 0.9em;">
|
||||
<p style="margin: 0;">{recipient_name if recipient_name else "[Recipient Name]"}{', ' + recipient_title if recipient_title else ''}</p>
|
||||
<p style="margin: 0;">{recipient_company if recipient_company else "[Recipient Company]"}</p>
|
||||
<p style="margin: 0;">{recipient_address if recipient_address else "[Recipient Address]"}</p>
|
||||
</div>
|
||||
|
||||
<div style="margin-bottom: 20px;">
|
||||
<p style="margin: 0; font-weight: bold;">Subject: {subject if subject else "[Subject Line]"}</p>
|
||||
</div>
|
||||
|
||||
<div style="margin-bottom: 20px;">
|
||||
<p style="margin: 0;">{salutation}</p>
|
||||
</div>
|
||||
|
||||
<div style="margin-bottom: 20px;">
|
||||
{formatted_paragraphs if formatted_paragraphs else "<p style='color: #888;'>Letter content goes here...</p>"}
|
||||
</div>
|
||||
|
||||
<div style="margin-top: 40px; text-align: {closing_align};">
|
||||
<p style="margin-bottom: 0.5em;">{complimentary_close}</p>
|
||||
<p style="font-weight: bold; margin: 0;">{sender_name if sender_name else "[Sender Name]"}</p>
|
||||
<p style="margin: 0; font-size: 0.9em;">{sender_title}</p>
|
||||
<p style="margin: 0; font-size: 0.9em;">{sender_company}</p>
|
||||
</div>
|
||||
</div>
|
||||
"""
|
||||
return html
|
||||
|
||||
def get_cover_letter_html(content: str, metadata: Dict[str, Any]) -> str:
|
||||
"""
|
||||
Generate HTML for cover letter preview with standard cover letter structure and styling.
|
||||
Includes sender contact block and optional online links.
|
||||
|
||||
Args:
|
||||
content: The letter content string.
|
||||
metadata: Dictionary containing cover letter metadata.
|
||||
|
||||
Returns:
|
||||
HTML string for cover letter preview.
|
||||
"""
|
||||
# Extract metadata with default empty strings
|
||||
sender_name = metadata.get("sender_name", "")
|
||||
sender_email = metadata.get("sender_email", "")
|
||||
sender_phone = metadata.get("sender_phone", "")
|
||||
sender_location = metadata.get("sender_location", "")
|
||||
sender_linkedin = metadata.get("sender_linkedin", "")
|
||||
sender_portfolio = metadata.get("sender_portfolio", "")
|
||||
|
||||
recipient_name = metadata.get("recipient_name", "")
|
||||
recipient_title = metadata.get("recipient_title", "") # Added recipient title
|
||||
recipient_company = metadata.get("recipient_company", "")
|
||||
recipient_department = metadata.get("recipient_department", "") # Added department
|
||||
recipient_address = metadata.get("recipient_address", "").replace("\n", "<br>") # Added recipient address
|
||||
|
||||
date = metadata.get("date", "")
|
||||
job_title = metadata.get("job_title", "") # Added job title for subject
|
||||
salutation = metadata.get("salutation", "Dear Hiring Manager,") # Added salutation
|
||||
complimentary_close = metadata.get("complimentary_close", "Sincerely,") # Added close
|
||||
|
||||
|
||||
# Split content into paragraphs based on double newlines
|
||||
paragraphs = [p.strip() for p in content.split("\n\n") if p.strip()]
|
||||
|
||||
# Format paragraphs as HTML <p> tags with bottom margin
|
||||
formatted_paragraphs = "".join(f"<p style='margin-bottom: 1em;'>{paragraph}</p>" for paragraph in paragraphs)
|
||||
|
||||
# Construct sender contact line, only including fields that have values
|
||||
sender_contact_parts = [sender_location, sender_phone, sender_email]
|
||||
sender_contact_line = " | ".join(filter(None, sender_contact_parts))
|
||||
|
||||
# Construct sender online links line, only including fields that have values
|
||||
sender_online_parts = []
|
||||
if sender_linkedin:
|
||||
# Add basic styling for links
|
||||
sender_online_parts.append(f'<a href="{sender_linkedin}" style="color: #0077b5; text-decoration: none;">LinkedIn</a>')
|
||||
if sender_portfolio:
|
||||
# Add basic styling for links
|
||||
sender_online_parts.append(f'<a href="{sender_portfolio}" style="color: #0077b5; text-decoration: none;">Portfolio</a>')
|
||||
|
||||
sender_online_line = " | ".join(filter(None, sender_online_parts))
|
||||
|
||||
|
||||
# Basic HTML structure with inline styles for a cover letter
|
||||
# Styles aim for a clean, professional look
|
||||
html = f"""
|
||||
<div style="max-width: 800px; margin: 20px auto; padding: 30px; border: 1px solid #d0d0d0; border-radius: 8px; background-color: #ffffff; font-family: 'Arial', sans-serif; line-height: 1.6; color: #333; box-shadow: 0 2px 4px rgba(0,0,0,0.1);">
|
||||
|
||||
<div style="text-align: left; margin-bottom: 30px; padding-bottom: 15px; border-bottom: 1px solid #eee;">
|
||||
<h2 style="margin: 0; color: #333; font-size: 1.5em;">{sender_name if sender_name else "[Your Name]"}</h2>
|
||||
{'<p style="margin: 5px 0 0 0; font-size: 0.9em; color: #555;">' + sender_contact_line + '</p>' if sender_contact_line else ''}
|
||||
{'<p style="margin: 2px 0 0 0; font-size: 0.9em;">' + sender_online_line + '</p>' if sender_online_line else ''}
|
||||
</div>
|
||||
|
||||
<div style="margin-bottom: 20px;">
|
||||
<p style="margin: 0;">{date if date else "[Date]"}</p>
|
||||
</div>
|
||||
|
||||
<div style="margin-bottom: 20px; font-size: 0.9em;">
|
||||
<p style="margin: 0;">{recipient_name if recipient_name else "[Recipient Name]"}{', ' + recipient_title if recipient_title else ''}</p>
|
||||
<p style="margin: 0;">{recipient_department}</p>
|
||||
<p style="margin: 0;">{recipient_company if recipient_company else "[Recipient Company]"}</p>
|
||||
<p style="margin: 0;">{recipient_address if recipient_address else "[Recipient Address]"}</p>
|
||||
</div>
|
||||
|
||||
<div style="margin-bottom: 20px;">
|
||||
<p style="margin: 0; font-weight: bold;">Subject: Application for {job_title if job_title else '[Job Title]'} Position</p>
|
||||
</div>
|
||||
|
||||
<div style="margin-bottom: 20px;">
|
||||
<p style="margin: 0;">{salutation}</p>
|
||||
</div>
|
||||
|
||||
<div style="margin-bottom: 20px;">
|
||||
{formatted_paragraphs if formatted_paragraphs else "<p style='color: #888;'>Letter content goes here...</p>"}
|
||||
</div>
|
||||
|
||||
<div style="margin-top: 40px;">
|
||||
<p style="margin-bottom: 0.5em;">{complimentary_close}</p>
|
||||
<p style="font-weight: bold; margin: 0;">{sender_name}</p>
|
||||
</div>
|
||||
</div>
|
||||
"""
|
||||
return html
|
||||
|
||||
# Example usage (for testing purposes)
|
||||
if __name__ == '__main__':
|
||||
sample_personal_content = """
|
||||
Hi Sarah,
|
||||
|
||||
Hope you're doing well!
|
||||
|
||||
Just wanted to send a quick note to say how much I enjoyed catching up last week. It was great hearing about your trip to Italy.
|
||||
|
||||
Let's try to do it again soon!
|
||||
|
||||
Best,
|
||||
Emily
|
||||
"""
|
||||
sample_personal_metadata = {
|
||||
"sender_name": "Emily Davis",
|
||||
"recipient_name": "Sarah Johnson",
|
||||
"date": "November 5, 2023"
|
||||
}
|
||||
|
||||
sample_formal_content = """
|
||||
I am writing to formally request a copy of my academic transcript.
|
||||
|
||||
I require this document for a graduate school application. The deadline for submission is December 15, 2023.
|
||||
|
||||
Please let me know if there are any fees associated with this request or if any further information is needed from my end.
|
||||
|
||||
Thank you for your time and assistance.
|
||||
"""
|
||||
sample_formal_metadata_full_block = {
|
||||
"sender_name": "John Smith",
|
||||
"sender_title": "Student",
|
||||
"sender_organization": "University of Example",
|
||||
"sender_address": "123 University Ave\nAnytown, CA 91234",
|
||||
"sender_phone": "(555) 123-4567",
|
||||
"sender_email": "john.smith@example.com",
|
||||
"recipient_name": "Registrar's Office",
|
||||
"recipient_organization": "University of Example",
|
||||
"recipient_address": "456 Admin Building\nAnytown, CA 91234",
|
||||
"date": "November 5, 2023",
|
||||
"subject": "Request for Academic Transcript",
|
||||
"salutation": "To the Registrar's Office,",
|
||||
"complimentary_close": "Sincerely,",
|
||||
"letter_format": "Full Block"
|
||||
}
|
||||
|
||||
sample_formal_metadata_modified_block = sample_formal_metadata_full_block.copy()
|
||||
sample_formal_metadata_modified_block["letter_format"] = "Modified Block"
|
||||
|
||||
|
||||
sample_business_content = """
|
||||
This letter confirms the details of Purchase Order #PO-7890.
|
||||
|
||||
We are ordering 50 units of Model X widgets at the agreed-upon price of $100 per unit, totaling $5,000.
|
||||
|
||||
Please ensure delivery to our warehouse by November 20, 2023. Payment will be made within 30 days of receipt of invoice.
|
||||
|
||||
Thank you for your prompt processing of this order.
|
||||
"""
|
||||
sample_business_metadata_full_block = {
|
||||
"sender_company": "Acme Corp",
|
||||
"sender_name": "Alice Brown",
|
||||
"sender_title": "Procurement Manager",
|
||||
"sender_address": "789 Business Rd\nMetropolis, NY 10001",
|
||||
"sender_phone": "(555) 987-6543",
|
||||
"sender_email": "alice.brown@acmecorp.com",
|
||||
"sender_website": "www.acmecorp.com",
|
||||
"recipient_company": "Supplier Co.",
|
||||
"recipient_name": "Sales Department",
|
||||
"recipient_title": "",
|
||||
"recipient_address": "101 Vendor Lane\nIndustriatown, TX 75001",
|
||||
"date": "November 5, 2023",
|
||||
"subject": "Purchase Order Confirmation - PO-7890",
|
||||
"salutation": "To the Sales Department,",
|
||||
"complimentary_close": "Sincerely,",
|
||||
"letter_format": "Full Block",
|
||||
"include_letterhead": True
|
||||
}
|
||||
|
||||
sample_business_metadata_modified_block = sample_business_metadata_full_block.copy()
|
||||
sample_business_metadata_modified_block["letter_format"] = "Modified Block"
|
||||
sample_business_metadata_no_letterhead = sample_business_metadata_full_block.copy()
|
||||
sample_business_metadata_no_letterhead["include_letterhead"] = False
|
||||
|
||||
|
||||
sample_cover_letter_content = """
|
||||
I am writing to express my enthusiastic interest in the Marketing Specialist position advertised on LinkedIn.
|
||||
|
||||
With three years of experience in digital marketing and a proven track record in content creation and social media management, I am confident in my ability to contribute to your team. My skills in [Specific Skill 1] and [Specific Skill 2] align perfectly with the requirements outlined in the job description.
|
||||
|
||||
In my previous role at [Previous Company], I successfully managed social media campaigns that resulted in a 25% increase in engagement. I am particularly drawn to [Company Name]'s innovative approach to [Industry Trend] and believe my creative problem-solving skills would be a valuable asset.
|
||||
|
||||
Thank you for considering my application. I have attached my resume for your review and welcome the opportunity to discuss how my background and skills can benefit [Company Name].
|
||||
"""
|
||||
sample_cover_letter_metadata = {
|
||||
"sender_name": "Jane Doe",
|
||||
"sender_email": "jane.doe@email.com",
|
||||
"sender_phone": "(123) 456-7890",
|
||||
"sender_location": "San Francisco, CA",
|
||||
"sender_linkedin": "https://linkedin.com/in/janedoe",
|
||||
"sender_portfolio": "https://janedoeportfolio.com",
|
||||
"recipient_name": "Hiring Manager",
|
||||
"recipient_title": "", # Example with no recipient title
|
||||
"recipient_company": "Innovative Solutions Inc.",
|
||||
"recipient_department": "Marketing Department",
|
||||
"recipient_address": "456 Tech Way\nSilicon Valley, CA 95001",
|
||||
"date": "November 5, 2023",
|
||||
"job_title": "Marketing Specialist",
|
||||
"salutation": "Dear Hiring Manager,",
|
||||
"complimentary_close": "Sincerely,"
|
||||
}
|
||||
|
||||
print("--- Personal Letter HTML Preview ---")
|
||||
print(get_letter_preview_html(sample_personal_content, sample_personal_metadata, letter_type="personal"))
|
||||
|
||||
print("\n--- Formal Letter HTML Preview (Full Block) ---")
|
||||
print(get_letter_preview_html(sample_formal_content, sample_formal_metadata_full_block, letter_type="formal"))
|
||||
|
||||
print("\n--- Formal Letter HTML Preview (Modified Block) ---")
|
||||
print(get_letter_preview_html(sample_formal_content, sample_formal_metadata_modified_block, letter_type="formal"))
|
||||
|
||||
print("\n--- Business Letter HTML Preview (Full Block, with Letterhead) ---")
|
||||
print(get_letter_preview_html(sample_business_content, sample_business_metadata_full_block, letter_type="business"))
|
||||
|
||||
print("\n--- Business Letter HTML Preview (Modified Block, with Letterhead) ---")
|
||||
print(get_letter_preview_html(sample_business_content, sample_business_metadata_modified_block, letter_type="business"))
|
||||
|
||||
print("\n--- Business Letter HTML Preview (Full Block, no Letterhead) ---")
|
||||
print(get_letter_preview_html(sample_business_content, sample_business_metadata_no_letterhead, letter_type="business"))
|
||||
|
||||
print("\n--- Cover Letter HTML Preview ---")
|
||||
print(get_letter_preview_html(sample_cover_letter_content, sample_cover_letter_metadata, letter_type="cover"))
|
||||
|
||||
print("\n--- Unknown Type HTML Preview ---")
|
||||
print(get_letter_preview_html("Some random content.", {}, letter_type="unknown"))
|
||||
988
lib/ai_writers/ai_letter_writer/utils/letter_templates.py
Normal file
988
lib/ai_writers/ai_letter_writer/utils/letter_templates.py
Normal file
@@ -0,0 +1,988 @@
|
||||
"""
|
||||
Letter Templates Module
|
||||
|
||||
This module provides structured templates and guidance for generating
|
||||
different types and subtypes of letters.
|
||||
Templates are defined as dictionaries containing a 'structure' (list of sections)
|
||||
and 'guidance' (a string).
|
||||
"""
|
||||
|
||||
from typing import Dict, Any, List
|
||||
|
||||
# Define letter templates using a nested dictionary structure for easier management
|
||||
TEMPLATES: Dict[str, Dict[str, Dict[str, Any]]] = {
|
||||
"personal": {
|
||||
"congratulations": {
|
||||
"structure": [
|
||||
"Greeting",
|
||||
"Express congratulations",
|
||||
"Acknowledge the achievement",
|
||||
"Share personal thoughts/memory (optional)",
|
||||
"Look to the future/well wishes",
|
||||
"Closing"
|
||||
],
|
||||
"guidance": "Be warm, sincere, and specific about the achievement. Express genuine happiness for the recipient. Keep the tone personal and friendly."
|
||||
},
|
||||
"thank_you": {
|
||||
"structure": [
|
||||
"Greeting",
|
||||
"Express gratitude clearly",
|
||||
"Specify what you are thankful for",
|
||||
"Explain the impact or how you used it (optional)",
|
||||
"Share a personal thought or memory (optional)",
|
||||
"Offer reciprocation or look to the future",
|
||||
"Closing"
|
||||
],
|
||||
"guidance": "Be specific about what you're thankful for and how it affected you. Express sincere appreciation. Personalize the message."
|
||||
},
|
||||
"sympathy": {
|
||||
"structure": [
|
||||
"Greeting",
|
||||
"Express sympathy for the loss",
|
||||
"Acknowledge the significance of the person/situation",
|
||||
"Share a positive memory or quality (optional)",
|
||||
"Offer specific support (optional)",
|
||||
"Closing with comforting words"
|
||||
],
|
||||
"guidance": "Be gentle, compassionate, and sincere. Avoid clichés. Focus on offering genuine comfort and acknowledging the recipient's feelings."
|
||||
},
|
||||
"apology": {
|
||||
"structure": [
|
||||
"Greeting",
|
||||
"Clearly state your apology",
|
||||
"Acknowledge the specific mistake or action",
|
||||
"Express understanding of the impact on the other person",
|
||||
"Explain (briefly, without making excuses) what happened (optional)",
|
||||
"Offer amends or suggest how to make things right",
|
||||
"Assure it won't happen again",
|
||||
"Closing"
|
||||
],
|
||||
"guidance": "Be sincere, take full responsibility for your actions, and focus on making things right. Avoid making excuses or blaming others."
|
||||
},
|
||||
"invitation": {
|
||||
"structure": [
|
||||
"Greeting",
|
||||
"Clearly state the invitation",
|
||||
"Provide full event details (What, When, Where)",
|
||||
"Explain the significance or purpose (optional)",
|
||||
"Mention who else might be there (optional)",
|
||||
"Request RSVP (date and contact method)",
|
||||
"Express anticipation",
|
||||
"Closing"
|
||||
],
|
||||
"guidance": "Be clear and specific about the details (what, when, where, why). Make it easy for the person to respond."
|
||||
},
|
||||
"friendship": {
|
||||
"structure": [
|
||||
"Greeting",
|
||||
"Express appreciation for the friendship",
|
||||
"Share a recent memory or anecdote",
|
||||
"Acknowledge the value of the relationship",
|
||||
"Check in on them or share updates",
|
||||
"Look to the future (getting together, etc.)",
|
||||
"Closing"
|
||||
],
|
||||
"guidance": "Be warm, personal, and specific about what you value in the friendship. Share updates and show genuine interest."
|
||||
},
|
||||
"love": {
|
||||
"structure": [
|
||||
"Greeting (Terms of endearment)",
|
||||
"Express depth of feelings",
|
||||
"Share a cherished memory or moment",
|
||||
"Describe specific qualities you love and appreciate",
|
||||
"Reaffirm commitment or future hopes",
|
||||
"Closing (Terms of endearment)"
|
||||
],
|
||||
"guidance": "Be sincere, personal, and specific about your feelings. Use sensory details and emotional language appropriate for your relationship."
|
||||
},
|
||||
"encouragement": {
|
||||
"structure": [
|
||||
"Greeting",
|
||||
"Acknowledge the situation or challenge they face",
|
||||
"Express belief in their abilities/strength",
|
||||
"Offer specific words of encouragement or support",
|
||||
"Remind them of past successes (optional)",
|
||||
"Offer practical help (optional)",
|
||||
"Look to the future with hope",
|
||||
"Closing with support"
|
||||
],
|
||||
"guidance": "Be positive, supportive, and specific about the person's strengths and abilities. Offer genuine encouragement and belief in them."
|
||||
},
|
||||
"farewell": {
|
||||
"structure": [
|
||||
"Greeting",
|
||||
"State the purpose (saying goodbye)",
|
||||
"Express feelings about their departure (sadness, happiness for them)",
|
||||
"Share a positive memory or highlight their contribution",
|
||||
"Express good wishes for their future endeavors",
|
||||
"Look to staying in touch (optional)",
|
||||
"Closing"
|
||||
],
|
||||
"guidance": "Be warm, reflective, and forward-looking. Focus on positive memories and express genuine good wishes for their next steps."
|
||||
},
|
||||
# Default personal letter template if subtype is not found
|
||||
"default": {
|
||||
"structure": [
|
||||
"Greeting",
|
||||
"Introduction",
|
||||
"Main content paragraphs",
|
||||
"Closing thoughts",
|
||||
"Signature"
|
||||
],
|
||||
"guidance": "Be personal, authentic, and appropriate for your relationship with the recipient. The tone is typically informal to semi-formal."
|
||||
}
|
||||
},
|
||||
"formal": {
|
||||
"application": {
|
||||
"structure": [
|
||||
"Sender's contact information",
|
||||
"Date",
|
||||
"Recipient's contact information (if known)",
|
||||
"Subject line (Clear and concise)",
|
||||
"Salutation (Formal)",
|
||||
"Introduction (State position applied for and where you saw it)",
|
||||
"Body paragraphs (Highlight relevant skills and experience)",
|
||||
"Closing paragraph (Reiterate interest, mention enclosed resume, call to action)",
|
||||
"Complimentary close (Formal)",
|
||||
"Signature (Typed name)",
|
||||
"Enclosures (Mention if attaching resume/portfolio)"
|
||||
],
|
||||
"guidance": "Be professional, concise, and specific about your qualifications and genuine interest in the position. Tailor it to the specific job description."
|
||||
},
|
||||
"complaint": {
|
||||
"structure": [
|
||||
"Sender's contact information",
|
||||
"Date",
|
||||
"Recipient's contact information",
|
||||
"Subject line (Clearly state it's a complaint)",
|
||||
"Salutation (Formal)",
|
||||
"Introduction (State the purpose: complaint about X service/product)",
|
||||
"Problem description (Provide specific details: date, time, location, product details, names if applicable)",
|
||||
"Impact statement (Explain how the problem affected you)",
|
||||
"Requested resolution (Clearly state what you want: refund, replacement, action)",
|
||||
"Closing paragraph (Reference attached documents, state expectation for response)",
|
||||
"Complimentary close (Formal)",
|
||||
"Signature (Typed name)"
|
||||
],
|
||||
"guidance": "Be clear, factual, and specific about the issue and your desired resolution. Maintain a respectful but firm tone. Include all relevant details."
|
||||
},
|
||||
"request": {
|
||||
"structure": [
|
||||
"Sender's contact information",
|
||||
"Date",
|
||||
"Recipient's contact information",
|
||||
"Subject line (Clearly state the request)",
|
||||
"Salutation (Formal)",
|
||||
"Introduction (State the purpose: making a request)",
|
||||
"Request details (Clearly explain what you are requesting)",
|
||||
"Justification (Explain why the request is necessary or beneficial)",
|
||||
"Provide supporting information (optional)",
|
||||
"Closing paragraph (Express gratitude for consideration, reiterate call to action)",
|
||||
"Complimentary close (Formal)",
|
||||
"Signature (Typed name)"
|
||||
],
|
||||
"guidance": "Be clear, specific, and courteous about your request. Explain why it's important or beneficial to the recipient or organization."
|
||||
},
|
||||
"recommendation": {
|
||||
"structure": [
|
||||
"Sender's contact information",
|
||||
"Date",
|
||||
"Recipient's contact information",
|
||||
"Subject line (Letter of Recommendation for [Name])",
|
||||
"Salutation (Formal)",
|
||||
"Introduction (State your name, title, relationship to the recommendee, and for what purpose the letter is written)",
|
||||
"Body paragraphs (Describe the recommendee's qualifications, skills, and achievements with specific examples)",
|
||||
"Highlight relevant experiences and contributions",
|
||||
"Closing recommendation (Summarize endorsement, strongly recommend the person)",
|
||||
"Complimentary close (Formal)",
|
||||
"Signature (Typed name and title)"
|
||||
],
|
||||
"guidance": "Be specific, positive, and credible. Use concrete examples and anecdotes to support your recommendation. Tailor it to the specific role/opportunity."
|
||||
},
|
||||
"resignation": {
|
||||
"structure": [
|
||||
"Sender's contact information",
|
||||
"Date",
|
||||
"Recipient's contact information (Immediate supervisor/HR)",
|
||||
"Subject line (Letter of Resignation - [Your Name])",
|
||||
"Salutation (Formal)",
|
||||
"Statement of resignation (Clearly state you are resigning)",
|
||||
"Last day of employment (Specify the date)",
|
||||
"Gratitude and reflection (Optional: Express thanks for the opportunity/experience)",
|
||||
"Transition plan/Offer of assistance (Optional: Suggest how to ensure a smooth handover)",
|
||||
"Closing paragraph (Express good wishes for the company's future)",
|
||||
"Complimentary close (Formal)",
|
||||
"Signature (Typed name)"
|
||||
],
|
||||
"guidance": "Be professional, positive (if possible), and clear about your departure and last day. Maintain a good relationship."
|
||||
},
|
||||
"inquiry": {
|
||||
"structure": [
|
||||
"Sender's contact information",
|
||||
"Date",
|
||||
"Recipient's contact information",
|
||||
"Subject line (Clearly state the nature of the inquiry)",
|
||||
"Salutation (Formal)",
|
||||
"Introduction (State your purpose for writing - making an inquiry)",
|
||||
"Inquiry details (Provide necessary context or background)",
|
||||
"Specific questions (List your questions clearly, perhaps numbered)",
|
||||
"Closing paragraph (Express gratitude for assistance, indicate when you need a response)",
|
||||
"Complimentary close (Formal)",
|
||||
"Signature (Typed name)"
|
||||
],
|
||||
"guidance": "Be clear, specific, and courteous about your inquiry. Organize your questions logically for easy answering."
|
||||
},
|
||||
"authorization": {
|
||||
"structure": [
|
||||
"Sender's contact information (The grantor of authority)",
|
||||
"Date",
|
||||
"Recipient's contact information (The person/entity receiving the letter)",
|
||||
"Subject line (Letter of Authorization)",
|
||||
"Salutation (Formal)",
|
||||
"Statement of authorization (Clearly state who is authorized)",
|
||||
"Authorized person's details (Full name, ID if applicable)",
|
||||
"Scope of authority (Precisely define what they are authorized to do)",
|
||||
"Limitations (Specify any restrictions or conditions)",
|
||||
"Duration of authorization (Start and end dates, if applicable)",
|
||||
"Closing paragraph (State responsibility, express confidence)",
|
||||
"Complimentary close (Formal)",
|
||||
"Signature (Typed name and title)"
|
||||
],
|
||||
"guidance": "Be clear, specific, and precise about who is authorized, what they can do, for how long, and under what conditions. This is a legal document."
|
||||
},
|
||||
"appeal": {
|
||||
"structure": [
|
||||
"Sender's contact information",
|
||||
"Date",
|
||||
"Recipient's contact information (Appeals committee/relevant authority)",
|
||||
"Subject line (Letter of Appeal - [Your Name] - [Subject of Appeal])",
|
||||
"Salutation (Formal)",
|
||||
"Introduction (State your name, the decision being appealed, and the date of the decision)",
|
||||
"Grounds for appeal (Clearly state the reasons why you believe the decision is incorrect)",
|
||||
"Provide supporting evidence (Reference attached documents: records, photos, etc.)",
|
||||
"Explain mitigating circumstances (Optional)",
|
||||
"Requested outcome (Clearly state what resolution you seek)",
|
||||
"Closing paragraph (Express hope for reconsideration, gratitude for time)",
|
||||
"Complimentary close (Formal)",
|
||||
"Signature (Typed name)"
|
||||
],
|
||||
"guidance": "Be respectful, factual, and persuasive. Focus on valid grounds for appeal and provide clear, supporting evidence. Maintain a formal tone."
|
||||
},
|
||||
"introduction": {
|
||||
"structure": [
|
||||
"Sender's contact information",
|
||||
"Date",
|
||||
"Recipient's contact information",
|
||||
"Subject line (Introduction - [Your Name])",
|
||||
"Salutation (Formal)",
|
||||
"Introduction (Introduce yourself and the purpose of the letter)",
|
||||
"Background information (Briefly describe your relevant background or expertise)",
|
||||
"Reason for reaching out (Explain why you are introducing yourself to this specific person/entity)",
|
||||
"Potential areas of collaboration or shared interest (Optional)",
|
||||
"Call to action (Suggest a meeting, call, or further communication)",
|
||||
"Closing paragraph (Express enthusiasm for potential connection)",
|
||||
"Complimentary close (Formal)",
|
||||
"Signature (Typed name)"
|
||||
],
|
||||
"guidance": "Be professional, informative, and engaging. Clearly explain who you are, your expertise, and why you're reaching out to them specifically."
|
||||
},
|
||||
# Default formal letter template if subtype is not found
|
||||
"default": {
|
||||
"structure": [
|
||||
"Sender's address",
|
||||
"Date",
|
||||
"Recipient's address",
|
||||
"Subject line",
|
||||
"Salutation",
|
||||
"Introduction",
|
||||
"Body paragraphs",
|
||||
"Closing paragraph",
|
||||
"Complimentary close",
|
||||
"Signature"
|
||||
],
|
||||
"guidance": "Be professional, clear, and concise. Use formal language and structure. The tone is typically formal."
|
||||
}
|
||||
},
|
||||
"business": {
|
||||
"sales": {
|
||||
"structure": [
|
||||
"Letterhead",
|
||||
"Date",
|
||||
"Recipient's address",
|
||||
"Subject line (Benefit-oriented)",
|
||||
"Salutation",
|
||||
"Attention-grabbing opening (Address a pain point or introduce a benefit)",
|
||||
"Problem statement (Briefly describe the challenge the recipient faces)",
|
||||
"Solution presentation (Introduce your product/service as the solution)",
|
||||
"Benefits and features (Explain how your solution helps, focusing on benefits)",
|
||||
"Social proof (Optional: Testimonials, case studies, data)",
|
||||
"Call to action (Clearly state what you want them to do next)",
|
||||
"Closing paragraph (Reiterate benefit, create urgency/incentive)",
|
||||
"Complimentary close (Professional)",
|
||||
"Signature (Typed name and title)",
|
||||
"Enclosures (Optional: Brochure, pricing)"
|
||||
],
|
||||
"guidance": "Be persuasive, customer-focused, and clear about the value proposition. Focus on benefits, not just features. Make the call to action obvious."
|
||||
},
|
||||
"proposal": {
|
||||
"structure": [
|
||||
"Letterhead",
|
||||
"Date",
|
||||
"Recipient's address",
|
||||
"Subject line (Clear and descriptive)",
|
||||
"Salutation",
|
||||
"Introduction (State purpose: submitting a proposal)",
|
||||
"Problem statement/Needs assessment (Demonstrate understanding of client's needs)",
|
||||
"Proposed solution (Describe your solution in detail)",
|
||||
"Implementation plan (Outline steps and timeline)",
|
||||
"Costs and investment (Clearly state pricing and payment terms)",
|
||||
"Benefits and ROI (Explain the value the client will receive)",
|
||||
"Call to action (Suggest next steps: meeting, discussion)",
|
||||
"Closing paragraph (Express enthusiasm, availability for questions)",
|
||||
"Complimentary close (Professional)",
|
||||
"Signature (Typed name and title)",
|
||||
"Enclosures (Proposal document, appendix)"
|
||||
],
|
||||
"guidance": "Be clear, specific, and persuasive about your solution. Focus on the client's needs and the value you provide. Structure it logically."
|
||||
},
|
||||
"order": {
|
||||
"structure": [
|
||||
"Letterhead (Your company)",
|
||||
"Date",
|
||||
"Recipient's address (Supplier)",
|
||||
"Subject line (Purchase Order - [PO Number])",
|
||||
"Salutation",
|
||||
"Introduction (Reference quote/agreement, state purpose: placing an order)",
|
||||
"Order details (Item list with quantities, descriptions, unit prices, total)",
|
||||
"Delivery requirements (Shipping address, requested delivery date, shipping method)",
|
||||
"Payment terms (Reference agreed terms)",
|
||||
"Closing paragraph (Express expectation for timely delivery)",
|
||||
"Complimentary close (Professional)",
|
||||
"Signature (Typed name and title)"
|
||||
],
|
||||
"guidance": "Be clear, specific, and detailed about what you're ordering, quantities, delivery requirements, and payment terms. Include a purchase order number."
|
||||
},
|
||||
"quotation": {
|
||||
"structure": [
|
||||
"Letterhead (Your company)",
|
||||
"Date",
|
||||
"Recipient's address (Customer)",
|
||||
"Subject line (Quotation for [Product/Service])",
|
||||
"Salutation",
|
||||
"Introduction (Reference inquiry, state purpose: providing a quotation)",
|
||||
"Quotation details (List items/services, descriptions, unit prices, quantities, line totals)",
|
||||
"Pricing breakdown (Mention taxes, discounts, fees separately)",
|
||||
"Terms and conditions (Payment terms, delivery terms, warranty)",
|
||||
"Validity period (State how long the quote is valid)",
|
||||
"Next steps (How they can place an order)",
|
||||
"Closing paragraph (Express hope to do business, offer further assistance)",
|
||||
"Complimentary close (Professional)",
|
||||
"Signature (Typed name and title)"
|
||||
],
|
||||
"guidance": "Be clear, specific, and transparent about pricing, terms, and what's included or excluded. Make it easy for the customer to understand and accept."
|
||||
},
|
||||
"acknowledgment": {
|
||||
"structure": [
|
||||
"Letterhead",
|
||||
"Date",
|
||||
"Recipient's address",
|
||||
"Subject line (Acknowledgment of [Received Item/Request])",
|
||||
"Salutation",
|
||||
"Acknowledgment statement (Clearly state what you have received or are acknowledging)",
|
||||
"Details of what's being acknowledged (Reference number, date, brief description)",
|
||||
"Confirm understanding (Optional: Briefly restate the request/issue to show understanding)",
|
||||
"Next steps (Outline what will happen next, e.g., processing order, investigating issue)",
|
||||
"Timeline (Provide an estimated timeframe if possible)",
|
||||
"Closing paragraph (Express gratitude, offer further assistance)",
|
||||
"Complimentary close (Professional)",
|
||||
"Signature (Typed name and title)"
|
||||
],
|
||||
"guidance": "Be prompt, clear, and specific about what you're acknowledging. Set clear expectations for next steps and timelines."
|
||||
},
|
||||
"collection": {
|
||||
"structure": [
|
||||
"Letterhead",
|
||||
"Date",
|
||||
"Recipient's address",
|
||||
"Subject line (Invoice [Invoice Number] - Payment Due)",
|
||||
"Salutation",
|
||||
"Introduction (Reference invoice number and due date)",
|
||||
"Account status (Clearly state the outstanding amount)",
|
||||
"Payment request (Politely request payment)",
|
||||
"Payment options (Remind them how to pay)",
|
||||
"Consequences of non-payment (Optional: Briefly mention late fees or further action, depending on letter stage)",
|
||||
"Call to action (Request payment by a specific date)",
|
||||
"Closing paragraph (Express hope for prompt payment, offer to discuss)",
|
||||
"Complimentary close (Professional)",
|
||||
"Signature (Typed name and title)"
|
||||
],
|
||||
"guidance": "Be firm but professional. Clearly state the amount due, due date, and payment options. The tone may vary depending on how overdue the payment is."
|
||||
},
|
||||
"adjustment": {
|
||||
"structure": [
|
||||
"Letterhead",
|
||||
"Date",
|
||||
"Recipient's address (Customer who made a complaint)",
|
||||
"Subject line (Response to your inquiry - [Reference Number])",
|
||||
"Salutation",
|
||||
"Acknowledgment of complaint (Reference their communication and the issue)",
|
||||
"Investigation findings (Explain the outcome of your investigation)",
|
||||
"Adjustment offered (Clearly state the resolution: refund, replacement, credit, etc.)",
|
||||
"Apology (Optional: Express regret for the inconvenience)",
|
||||
"Preventive measures (Optional: Explain steps taken to prevent recurrence)",
|
||||
"Closing paragraph (Express hope for continued business, offer further assistance)",
|
||||
"Complimentary close (Professional)",
|
||||
"Signature (Typed name and title)"
|
||||
],
|
||||
"guidance": "Be responsive, empathetic, and solution-oriented. Clearly explain the adjustment and any preventive measures taken."
|
||||
},
|
||||
"credit": {
|
||||
"structure": [
|
||||
"Letterhead",
|
||||
"Date",
|
||||
"Recipient's address (Applicant)",
|
||||
"Subject line (Credit Application Status - [Applicant Name])",
|
||||
"Salutation",
|
||||
"Introduction (Reference their credit application and the purpose of the letter)",
|
||||
"Credit decision (Clearly state if credit is approved or denied)",
|
||||
"If approved: Credit terms (Credit limit, payment terms, interest rates)",
|
||||
"If denied: Reason for decision (Provide specific, compliant reasons)",
|
||||
"Requirements (If approved: any further steps or documents needed)",
|
||||
"Closing paragraph (If approved: Express welcome; If denied: Offer alternative options or appeals process)",
|
||||
"Complimentary close (Professional)",
|
||||
"Signature (Typed name and title)"
|
||||
],
|
||||
"guidance": "Be clear, specific, and transparent about the credit decision, terms, limits, or reasons for denial. Ensure compliance with regulations if denying credit."
|
||||
},
|
||||
"follow_up": {
|
||||
"structure": [
|
||||
"Letterhead",
|
||||
"Date",
|
||||
"Recipient's address",
|
||||
"Subject line (Following up on [Previous Communication/Meeting])",
|
||||
"Salutation",
|
||||
"Reference to previous communication (Mention date, topic, or meeting)",
|
||||
"Purpose of follow-up (Clearly state why you are writing again)",
|
||||
"Action items/Next steps (Remind of agreed-upon actions or propose next steps)",
|
||||
"Provide additional information (Optional)",
|
||||
"Call to action (If applicable, e.g., request a response, schedule a meeting)",
|
||||
"Closing paragraph (Reiterate interest, express anticipation)",
|
||||
"Complimentary close (Professional)",
|
||||
"Signature (Typed name and title)"
|
||||
],
|
||||
"guidance": "Be clear, specific, and action-oriented. Reference previous communication and clearly state the purpose of your follow-up and desired outcome."
|
||||
},
|
||||
# Default business letter template if subtype is not found
|
||||
"default": {
|
||||
"structure": [
|
||||
"Letterhead",
|
||||
"Date",
|
||||
"Recipient's address",
|
||||
"Subject line",
|
||||
"Salutation",
|
||||
"Introduction",
|
||||
"Body paragraphs",
|
||||
"Closing paragraph",
|
||||
"Complimentary close",
|
||||
"Signature"
|
||||
],
|
||||
"guidance": "Be professional, clear, and concise. Focus on the business purpose of your letter. The tone is typically formal to semi-formal."
|
||||
}
|
||||
},
|
||||
"cover": {
|
||||
"standard": {
|
||||
"structure": [
|
||||
"Your contact information",
|
||||
"Date",
|
||||
"Hiring Manager contact information (if known)",
|
||||
"Subject line (Job Application - [Your Name] - [Job Title])",
|
||||
"Salutation (Formal)",
|
||||
"Introduction (State the position you are applying for, where you saw the advertisement, and a brief statement of enthusiasm)",
|
||||
"Body paragraph 1 (Highlight skills and experience directly relevant to the job description - often 1-2 key qualifications)",
|
||||
"Body paragraph 2 (Provide a specific example or anecdote demonstrating your abilities)",
|
||||
"Body paragraph 3 (Connect your passion/goals to the company's mission/values - optional but effective)",
|
||||
"Closing paragraph (Reiterate interest, mention attached resume, express availability for interview)",
|
||||
"Complimentary close (Formal)",
|
||||
"Signature (Typed name)"
|
||||
],
|
||||
"guidance": "Be professional, specific about your most relevant qualifications, and clear about your interest in the position. Tailor every cover letter to the specific job and company."
|
||||
},
|
||||
"career_change": {
|
||||
"structure": [
|
||||
"Your contact information",
|
||||
"Date",
|
||||
"Hiring Manager contact information",
|
||||
"Subject line (Job Application - [Your Name] - [Job Title])",
|
||||
"Salutation",
|
||||
"Introduction (State the position and acknowledge your career transition)",
|
||||
"Body paragraph 1 (Highlight transferable skills from previous roles)",
|
||||
"Body paragraph 2 (Explain your motivation for the career change and how your skills apply)",
|
||||
"Body paragraph 3 (Demonstrate understanding of the new industry/role)",
|
||||
"Closing paragraph (Reiterate enthusiasm, mention enclosed resume, call to action)",
|
||||
"Complimentary close",
|
||||
"Signature"
|
||||
],
|
||||
"guidance": "Focus on transferable skills and explain your career transition. Connect your past experience and new skills directly to the requirements of the target role."
|
||||
},
|
||||
"entry_level": {
|
||||
"structure": [
|
||||
"Your contact information",
|
||||
"Date",
|
||||
"Hiring Manager contact information",
|
||||
"Subject line (Job Application - [Your Name] - [Job Title])",
|
||||
"Salutation",
|
||||
"Introduction (State the position and your enthusiasm for the opportunity as a recent graduate/entrant)",
|
||||
"Body paragraph 1 (Highlight relevant education, coursework, GPA if strong)",
|
||||
"Body paragraph 2 (Describe relevant internships, projects, or volunteer experience)",
|
||||
"Body paragraph 3 (Showcase soft skills: teamwork, communication, eagerness to learn)",
|
||||
"Closing paragraph (Reiterate interest, mention attached resume, express availability for interview)",
|
||||
"Complimentary close",
|
||||
"Signature"
|
||||
],
|
||||
"guidance": "Emphasize education, relevant internships/projects, and transferable skills gained through academic or extracurricular activities. Show strong potential and enthusiasm."
|
||||
},
|
||||
"executive": {
|
||||
"structure": [
|
||||
"Your contact information",
|
||||
"Date",
|
||||
"Recipient's contact information (Senior Executive/Board Member)",
|
||||
"Subject line (Executive Application - [Your Name] - [Position])",
|
||||
"Salutation (Formal)",
|
||||
"Introduction (State position applying for, brief summary of executive profile)",
|
||||
"Body paragraph 1 (Highlight strategic leadership experience and key achievements)",
|
||||
"Body paragraph 2 (Discuss relevant industry expertise and market insights)",
|
||||
"Body paragraph 3 (Describe experience in driving growth, managing teams, achieving results)",
|
||||
"Closing paragraph (Reiterate interest, express desire to discuss contribution to the organization)",
|
||||
"Complimentary close (Formal)",
|
||||
"Signature"
|
||||
],
|
||||
"guidance": "Emphasize strategic leadership experience, significant achievements with measurable results, and industry expertise. Use a confident, authoritative, and forward-looking tone."
|
||||
},
|
||||
"creative": {
|
||||
"structure": [
|
||||
"Your contact information",
|
||||
"Date",
|
||||
"Hiring Manager contact information",
|
||||
"Subject line (Application - [Your Name] - [Creative Role])",
|
||||
"Salutation",
|
||||
"Creative introduction (Engaging hook related to the role or your passion)",
|
||||
"Body paragraph 1 (Highlight relevant creative experience and skills)",
|
||||
"Body paragraph 2 (Reference specific portfolio pieces or projects that showcase your style/abilities)",
|
||||
"Body paragraph 3 (Describe your creative process or approach)",
|
||||
"Closing paragraph (Reiterate enthusiasm, mention attached resume/portfolio link, call to action)",
|
||||
"Complimentary close",
|
||||
"Signature"
|
||||
],
|
||||
"guidance": "Use a more engaging and expressive style appropriate for a creative role while maintaining professionalism. Highlight specific creative achievements and link to your portfolio."
|
||||
},
|
||||
"technical": {
|
||||
"structure": [
|
||||
"Your contact information",
|
||||
"Date",
|
||||
"Hiring Manager contact information",
|
||||
"Subject line (Application - [Your Name] - [Technical Role])",
|
||||
"Salutation (Formal)",
|
||||
"Introduction (State position, source, and brief technical interest)",
|
||||
"Body paragraph 1 (Highlight specific technical skills and proficiencies relevant to the job description)",
|
||||
"Body paragraph 2 (Describe relevant technical projects or challenges you've solved)",
|
||||
"Body paragraph 3 (Discuss problem-solving abilities and experience with relevant technologies)",
|
||||
"Closing paragraph (Reiterate interest, mention attached resume, express availability for technical discussion/interview)",
|
||||
"Complimentary close (Formal)",
|
||||
"Signature"
|
||||
],
|
||||
"guidance": "Focus on technical skills, relevant projects, and problem-solving abilities. Use appropriate technical terminology accurately."
|
||||
},
|
||||
"academic": {
|
||||
"structure": [
|
||||
"Your contact information",
|
||||
"Date",
|
||||
"Recipient's contact information (Search Committee Chair)",
|
||||
"Subject line (Application for [Position] - [Your Name])",
|
||||
"Salutation (Formal)",
|
||||
"Introduction (State the position, the department, and express your strong interest)",
|
||||
"Body paragraph 1 (Discuss your research experience, focus on key projects and contributions)",
|
||||
"Body paragraph 2 (Describe your teaching philosophy and relevant teaching experience)",
|
||||
"Body paragraph 3 (Mention publications, presentations, grants, and other scholarly contributions)",
|
||||
"Closing paragraph (Reiterate enthusiasm for joining the faculty, express availability for interview/presentation)",
|
||||
"Complimentary close (Formal)",
|
||||
"Signature (Typed name)"
|
||||
],
|
||||
"guidance": "Focus on research experience, teaching philosophy, publications, and contributions to the field. Use a scholarly and professional tone suitable for academia."
|
||||
},
|
||||
"remote": {
|
||||
"structure": [
|
||||
"Your contact information",
|
||||
"Date",
|
||||
"Hiring Manager contact information",
|
||||
"Subject line (Remote Application - [Your Name] - [Job Title])",
|
||||
"Salutation",
|
||||
"Introduction (State the remote position, source, and enthusiasm for remote work)",
|
||||
"Body paragraph 1 (Highlight experience working remotely or independently)",
|
||||
"Body paragraph 2 (Emphasize self-management, time management, and organizational skills required for remote work)",
|
||||
"Body paragraph 3 (Describe strong written and verbal communication skills, essential for remote collaboration)",
|
||||
"Closing paragraph (Reiterate interest in the remote role, mention attached resume, express availability for video interview)",
|
||||
"Complimentary close",
|
||||
"Signature"
|
||||
],
|
||||
"guidance": "Emphasize self-motivation, excellent communication skills (especially written), time management, and any prior experience working independently or in remote teams."
|
||||
},
|
||||
"referral": {
|
||||
"structure": [
|
||||
"Your contact information",
|
||||
"Date",
|
||||
"Hiring Manager contact information",
|
||||
"Subject line (Referral Application - [Your Name] - [Job Title] - Referred by [Referrer's Name])",
|
||||
"Salutation",
|
||||
"Referral introduction (Immediately state who referred you and for what position)",
|
||||
"Body paragraph 1 (Briefly explain your connection to the referrer and how you learned about the role)",
|
||||
"Body paragraph 2 (Highlight key qualifications relevant to the job description)",
|
||||
"Body paragraph 3 (Express strong interest in the position and the company)",
|
||||
"Closing paragraph (Reiterate enthusiasm, mention attached resume, express availability for interview)",
|
||||
"Complimentary close",
|
||||
"Signature"
|
||||
],
|
||||
"guidance": "Mention the referral prominently and early. Explain your connection to the referrer and how it aligns with your interest in the role. Still, ensure you highlight your own qualifications."
|
||||
},
|
||||
# Default cover letter template if subtype is not found
|
||||
"default": {
|
||||
"structure": [
|
||||
"Contact information",
|
||||
"Date",
|
||||
"Recipient's information",
|
||||
"Subject line",
|
||||
"Salutation",
|
||||
"Introduction",
|
||||
"Body paragraphs",
|
||||
"Closing paragraph",
|
||||
"Complimentary close",
|
||||
"Signature"
|
||||
],
|
||||
"guidance": "Be professional, specific about your qualifications, and clear about your interest in the position. Tailor your letter to the specific job and company."
|
||||
}
|
||||
},
|
||||
"recommendation": {
|
||||
# Recommendation letters are often considered a subtype of Formal,
|
||||
# but can be a top-level type in some systems. Keeping the structure
|
||||
# consistent with the original request, but noting this potential overlap.
|
||||
"standard": {
|
||||
"structure": [
|
||||
"Sender's contact information",
|
||||
"Date",
|
||||
"Recipient's contact information (e.g., Admissions Committee, Hiring Manager)",
|
||||
"Subject line (Letter of Recommendation for [Name])",
|
||||
"Salutation (Formal)",
|
||||
"Introduction (State your name, title, relationship to the recommendee, how long you've known them, and for what opportunity the letter is written)",
|
||||
"Body paragraph 1 (Describe their relevant skills and qualities, providing specific examples)",
|
||||
"Body paragraph 2 (Discuss their achievements or contributions, with context and impact)",
|
||||
"Body paragraph 3 (Optional: Mention character traits, teamwork, or specific anecdotes)",
|
||||
"Overall Endorsement (Summarize your strong recommendation and why they are a good fit)",
|
||||
"Closing paragraph (Offer to provide further information)",
|
||||
"Complimentary close (Formal)",
|
||||
"Signature (Typed name and title)"
|
||||
],
|
||||
"guidance": "Be specific, positive, and credible. Use concrete examples and anecdotes to support your recommendation. Clearly state your relationship with the person and for what opportunity you are recommending them."
|
||||
},
|
||||
# Default recommendation letter template if subtype is not found
|
||||
"default": {
|
||||
"structure": [
|
||||
"Sender's contact information",
|
||||
"Date",
|
||||
"Recipient's contact information",
|
||||
"Subject line (Letter of Recommendation for [Name])",
|
||||
"Salutation",
|
||||
"Introduction",
|
||||
"Body paragraphs describing qualifications and experiences",
|
||||
"Specific examples and anecdotes",
|
||||
"Overall endorsement and recommendation",
|
||||
"Closing and offer for further information",
|
||||
"Complimentary close",
|
||||
"Signature"
|
||||
],
|
||||
"guidance": "Provide a strong, positive, and specific endorsement based on your professional or academic relationship with the individual."
|
||||
}
|
||||
},
|
||||
"complaint": {
|
||||
# Complaint letters are often considered a subtype of Formal or Business,
|
||||
# but can be a top-level type. Keeping the structure consistent.
|
||||
"product": {
|
||||
"structure": [
|
||||
"Your contact information",
|
||||
"Date",
|
||||
"Company contact information",
|
||||
"Subject line (Complaint Regarding [Product Name/Model])",
|
||||
"Salutation (Formal)",
|
||||
"Introduction (State purpose: complaining about a product, include product name, model, date/place of purchase)",
|
||||
"Problem description (Explain the specific defect or issue with the product in detail)",
|
||||
"History of the problem (Mention if you've tried fixing it, contacted support, etc.)",
|
||||
"Desired resolution (Clearly state if you want a refund, replacement, repair)",
|
||||
"Call to action (State what you expect the company to do and by when)",
|
||||
"Closing paragraph (Reference attached documents like receipt, express expectation for resolution)",
|
||||
"Complimentary close (Formal)",
|
||||
"Signature"
|
||||
],
|
||||
"guidance": "Be clear, factual, and specific about the product issue and your desired resolution. Include all relevant details like model number, date of purchase, and copies of receipts. Maintain a firm but professional tone."
|
||||
},
|
||||
"service": {
|
||||
"structure": [
|
||||
"Your contact information",
|
||||
"Date",
|
||||
"Company/Service Provider contact information",
|
||||
"Subject line (Complaint Regarding [Service Type/Issue])",
|
||||
"Salutation (Formal)",
|
||||
"Introduction (State purpose: complaining about a service received, include date/time/location of service)",
|
||||
"Problem description (Explain the specific issue with the service provided in detail)",
|
||||
"Impact of the issue (Explain how this problem affected you)",
|
||||
"Desired resolution (Clearly state what you want: refund, re-performance of service, compensation)",
|
||||
"Call to action (State what you expect the company to do and by when)",
|
||||
"Closing paragraph (Reference any relevant documents, express expectation for resolution)",
|
||||
"Complimentary close (Formal)",
|
||||
"Signature"
|
||||
],
|
||||
"guidance": "Be clear, factual, and specific about the service issue and your desired resolution. Include details like dates, times, and names of service providers if possible. Maintain a firm but professional tone."
|
||||
},
|
||||
"billing": {
|
||||
"structure": [
|
||||
"Your contact information",
|
||||
"Date",
|
||||
"Company contact information",
|
||||
"Subject line (Complaint Regarding Billing Error - Account #[Your Account Number])",
|
||||
"Salutation (Formal)",
|
||||
"Introduction (State purpose: complaining about a billing error, include account number and invoice number)",
|
||||
"Problem description (Explain the specific error on the bill: incorrect charge, double billing, etc.)",
|
||||
"Provide supporting evidence (Reference payments made, attach relevant statements)",
|
||||
"Desired resolution (Clearly state what you want: correction of bill, refund, credit)",
|
||||
"Call to action (State what you expect the company to do and by when)",
|
||||
"Closing paragraph (Reference attached documents, express expectation for resolution)",
|
||||
"Complimentary close (Formal)",
|
||||
"Signature"
|
||||
],
|
||||
"guidance": "Be clear, factual, and specific about the billing error. Provide supporting documentation like invoices or payment records. Clearly state the desired correction."
|
||||
},
|
||||
# Default complaint letter template if subtype is not found
|
||||
"default": {
|
||||
"structure": [
|
||||
"Your contact information",
|
||||
"Date",
|
||||
"Recipient's contact information",
|
||||
"Subject line (Complaint Regarding [Issue Summary])",
|
||||
"Salutation",
|
||||
"Introduction (State the purpose of the letter - to complain)",
|
||||
"Detailed description of the problem",
|
||||
"Explanation of the impact",
|
||||
"Desired resolution",
|
||||
"Call to action",
|
||||
"Closing",
|
||||
"Signature"
|
||||
],
|
||||
"guidance": "Be clear, factual, and specific about the issue and your desired resolution. Maintain a respectful but firm tone and provide relevant details."
|
||||
}
|
||||
},
|
||||
"thank_you": {
|
||||
# Thank You letters are often considered a subtype of Personal or Business,
|
||||
# but can be a top-level type. Keeping the structure consistent.
|
||||
"personal": {
|
||||
"structure": [
|
||||
"Greeting",
|
||||
"Express gratitude clearly and sincerely",
|
||||
"Specify what you are thankful for (gift, favor, support)",
|
||||
"Explain the impact it had on you or how you used it",
|
||||
"Share a personal thought or memory related to it (optional)",
|
||||
"Look to the future or express continued appreciation",
|
||||
"Closing"
|
||||
],
|
||||
"guidance": "Be warm, sincere, and specific about what you are thankful for. Personalize the message and explain the impact of their action or gift."
|
||||
},
|
||||
"professional": {
|
||||
"structure": [
|
||||
"Sender's contact information",
|
||||
"Date",
|
||||
"Recipient's contact information",
|
||||
"Subject line (Thank You - [Your Name])",
|
||||
"Salutation (Formal/Semi-formal)",
|
||||
"Express gratitude clearly (e.g., Thank you for the interview, thank you for your help)",
|
||||
"Specify what you are thankful for (Meeting date/topic, specific assistance)",
|
||||
"Reiterate interest or connection (e.g., Reiterate interest in the job, mention something discussed)",
|
||||
"Express appreciation for their time or effort",
|
||||
"Closing paragraph (Optional: look to future interaction)",
|
||||
"Complimentary close (Formal/Semi-formal)",
|
||||
"Signature"
|
||||
],
|
||||
"guidance": "Be prompt, professional, and specific. Reiterate your interest or key points discussed. Send within 24 hours for interviews."
|
||||
},
|
||||
"after_interview": {
|
||||
"structure": [
|
||||
"Your contact information",
|
||||
"Date",
|
||||
"Interviewer's contact information",
|
||||
"Subject line (Thank You - [Your Name] - [Job Title])",
|
||||
"Salutation (Formal)",
|
||||
"Express sincere thanks for the interview opportunity",
|
||||
"Mention the specific position and date of the interview",
|
||||
"Reiterate your strong interest in the role and the company",
|
||||
"Reference a specific point discussed during the interview to show engagement",
|
||||
"Briefly highlight how your skills/experience align with a need discussed",
|
||||
"Express enthusiasm for next steps",
|
||||
"Complimentary close (Formal)",
|
||||
"Signature"
|
||||
],
|
||||
"guidance": "Send within 24 hours of the interview. Be specific, professional, and reiterate your key strengths and interest. Proofread carefully."
|
||||
},
|
||||
# Default thank you letter template if subtype is not found
|
||||
"default": {
|
||||
"structure": [
|
||||
"Greeting",
|
||||
"Express thanks",
|
||||
"Specify reason for thanks",
|
||||
"Closing"
|
||||
],
|
||||
"guidance": "Be sincere and specific about what you are thankful for."
|
||||
}
|
||||
},
|
||||
"invitation": {
|
||||
# Invitation letters are often considered a subtype of Personal or Formal,
|
||||
# but can be a top-level type. Keeping the structure consistent.
|
||||
"event": { # e.g., party, gathering, wedding
|
||||
"structure": [
|
||||
"Greeting",
|
||||
"State the purpose: extending an invitation",
|
||||
"Event details (Type of event, Host)",
|
||||
"Date and Time",
|
||||
"Location (Full address)",
|
||||
"Purpose/Theme (Optional)",
|
||||
"Special instructions (Dress code, what to bring, etc. - optional)",
|
||||
"RSVP information (Date, Contact method)",
|
||||
"Express anticipation",
|
||||
"Closing"
|
||||
],
|
||||
"guidance": "Be clear about all the event details (What, When, Where). Make it easy for guests to RSVP. Tone can be formal or informal depending on the event."
|
||||
},
|
||||
"interview": {
|
||||
"structure": [
|
||||
"Company Letterhead",
|
||||
"Date",
|
||||
"Candidate's contact information",
|
||||
"Subject line (Interview Invitation - [Job Title] - [Your Name])",
|
||||
"Salutation (Formal)",
|
||||
"State the purpose: inviting them for an interview",
|
||||
"Specify the position applied for",
|
||||
"Propose date(s) and time(s) for the interview",
|
||||
"Provide location details (Address, or link for virtual)",
|
||||
"Mention who they will meet with (Names and titles)",
|
||||
"Explain the interview format/duration (Optional)",
|
||||
"Instructions (What to bring, who to contact with questions)",
|
||||
"Call to action (Request confirmation or scheduling)",
|
||||
"Closing paragraph (Express anticipation)",
|
||||
"Complimentary close (Formal)",
|
||||
"Signature (Interviewer/HR Contact Name and Title)"
|
||||
],
|
||||
"guidance": "Be professional, clear, and provide all necessary details for the candidate. Make the scheduling process straightforward."
|
||||
},
|
||||
"meeting": {
|
||||
"structure": [
|
||||
"Sender's contact information",
|
||||
"Date",
|
||||
"Recipient's contact information",
|
||||
"Subject line (Invitation to Meeting - [Meeting Topic])",
|
||||
"Salutation",
|
||||
"State the purpose: inviting them to a meeting",
|
||||
"Meeting details (Date, Time, Location/Virtual link)",
|
||||
"Purpose/Agenda (Clearly state what the meeting is about)",
|
||||
"Expected duration (Optional)",
|
||||
"Preparation required (Optional: Documents to review)",
|
||||
"RSVP information (Optional)",
|
||||
"Closing paragraph",
|
||||
"Complimentary close",
|
||||
"Signature"
|
||||
],
|
||||
"guidance": "Be clear about the purpose, date, time, and location. Provide an agenda so attendees can prepare. The tone can be formal or informal depending on the context."
|
||||
},
|
||||
# Default invitation letter template if subtype is not found
|
||||
"default": {
|
||||
"structure": [
|
||||
"Greeting",
|
||||
"Invitation statement",
|
||||
"Event/Meeting details (What, When, Where)",
|
||||
"Purpose (Optional)",
|
||||
"RSVP information",
|
||||
"Closing"
|
||||
],
|
||||
"guidance": "Be clear and specific about the details of the event or meeting."
|
||||
}
|
||||
},
|
||||
# Overall default template if letter type is not recognized
|
||||
"default": {
|
||||
"structure": [
|
||||
"Introduction",
|
||||
"Body paragraphs",
|
||||
"Conclusion"
|
||||
],
|
||||
"guidance": "Be clear, concise, and appropriate for your audience and purpose. This is a generic structure."
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
def get_template_by_type(letter_type: str, subtype: str = "default") -> Dict[str, Any]:
|
||||
"""
|
||||
Get a template for a specific letter type and subtype using a dictionary lookup.
|
||||
|
||||
Args:
|
||||
letter_type: Type of letter (e.g., "personal", "formal", "business", "cover").
|
||||
subtype: Subtype of letter (e.g., "congratulations", "application", "sales").
|
||||
Defaults to "default" if no subtype is specified.
|
||||
|
||||
Returns:
|
||||
Template dictionary with 'structure' (List[str]) and 'guidance' (str).
|
||||
Returns the default template if the letter type or subtype is not found.
|
||||
"""
|
||||
# Get templates for the specific letter type, or the overall default templates
|
||||
type_templates = TEMPLATES.get(letter_type, TEMPLATES["default"])
|
||||
|
||||
# Get the template for the specific subtype, or the default for that letter type
|
||||
template = type_templates.get(subtype, type_templates.get("default", TEMPLATES["default"])) # Fallback to overall default
|
||||
|
||||
# Ensure the returned template always has 'structure' and 'guidance' keys
|
||||
# This handles cases where an incomplete template might have been defined (error tolerance)
|
||||
if "structure" not in template or not isinstance(template["structure"], list):
|
||||
template["structure"] = ["Introduction", "Body", "Conclusion"]
|
||||
template["guidance"] = "Generic template: structure or guidance missing."
|
||||
|
||||
if "guidance" not in template or not isinstance(template["guidance"], str):
|
||||
template["guidance"] = "Generic guidance: structure or guidance missing."
|
||||
|
||||
|
||||
return template
|
||||
|
||||
# Example usage (for testing purposes)
|
||||
if __name__ == '__main__':
|
||||
# Test cases
|
||||
print("--- Testing Letter Templates ---")
|
||||
|
||||
personal_congrats = get_template_by_type("personal", "congratulations")
|
||||
print("\nPersonal Congratulations Template:")
|
||||
print(f"Structure: {personal_congrats['structure']}")
|
||||
print(f"Guidance: {personal_congrats['guidance']}")
|
||||
|
||||
formal_complaint = get_template_by_type("formal", "complaint")
|
||||
print("\nFormal Complaint Template:")
|
||||
print(f"Structure: {formal_complaint['structure']}")
|
||||
print(f"Guidance: {formal_complaint['guidance']}")
|
||||
|
||||
business_sales = get_template_by_type("business", "sales")
|
||||
print("\nBusiness Sales Template:")
|
||||
print(f"Structure: {business_sales['structure']}")
|
||||
print(f"Guidance: {business_sales['guidance']}")
|
||||
|
||||
cover_entry_level = get_template_by_type("cover", "entry_level")
|
||||
print("\nCover Entry Level Template:")
|
||||
print(f"Structure: {cover_entry_level['structure']}")
|
||||
print(f"Guidance: {cover_entry_level['guidance']}")
|
||||
|
||||
unknown_type = get_template_by_type("unknown_type", "some_subtype")
|
||||
print("\nUnknown Type Template (Should be Default):")
|
||||
print(f"Structure: {unknown_type['structure']}")
|
||||
print(f"Guidance: {unknown_type['guidance']}")
|
||||
|
||||
personal_unknown_subtype = get_template_by_type("personal", "unknown_subtype")
|
||||
print("\nPersonal Unknown Subtype Template (Should be Personal Default):")
|
||||
print(f"Structure: {personal_unknown_subtype['structure']}")
|
||||
print(f"Guidance: {personal_unknown_subtype['guidance']}")
|
||||
557
lib/ai_writers/ai_outline_writer/README.md
Normal file
557
lib/ai_writers/ai_outline_writer/README.md
Normal file
@@ -0,0 +1,557 @@
|
||||
# Blog Outline Generator
|
||||
|
||||
A powerful AI-powered tool for generating comprehensive blog outlines with advanced editing capabilities, content generation, and image integration.
|
||||
|
||||
## 🛠 Technical Architecture
|
||||
|
||||
### Core Components
|
||||
- **Backend**: Python-based implementation using Streamlit for UI
|
||||
- **AI Integration**:
|
||||
- Text Generation: Integration with multiple LLM providers (Gemini, OpenAI, Anthropic)
|
||||
- Image Generation: Support for multiple image generation APIs (Gemini-AI, Dalle3, Stability-AI)
|
||||
- **Data Structures**:
|
||||
```python
|
||||
class OutlineConfig:
|
||||
content_type: ContentType
|
||||
content_depth: ContentDepth
|
||||
outline_style: OutlineStyle
|
||||
target_word_count: int
|
||||
num_main_sections: int
|
||||
num_subsections_per_section: int
|
||||
include_images: bool
|
||||
image_style: str
|
||||
image_engine: str
|
||||
```
|
||||
|
||||
### Key Technologies
|
||||
- **Streamlit**: Web application framework
|
||||
- **Asyncio**: Asynchronous operations for AI calls
|
||||
- **Loguru**: Advanced logging system
|
||||
- **BeautifulSoup**: Web content parsing
|
||||
- **Pydantic**: Data validation
|
||||
- **Markdown**: Content formatting
|
||||
|
||||
## 🌟 Features with Examples
|
||||
|
||||
### 1. Content Generation
|
||||
- **AI-Powered Content Creation**:
|
||||
```python
|
||||
# Example prompt for content generation
|
||||
prompt = f"""
|
||||
Generate content for a {content_type} article about {topic}.
|
||||
Target audience: {target_audience}
|
||||
Word count: {target_word_count}
|
||||
Style: {outline_style}
|
||||
"""
|
||||
content = await llm_text_gen(prompt)
|
||||
```
|
||||
|
||||
- **Multiple Content Types**:
|
||||
```python
|
||||
# Example configuration for different content types
|
||||
config = OutlineConfig(
|
||||
content_type=ContentType.TUTORIAL,
|
||||
content_depth=ContentDepth.INTERMEDIATE,
|
||||
target_word_count=2000
|
||||
)
|
||||
```
|
||||
|
||||
### 2. Outline Structure
|
||||
- **Flexible Section Management**:
|
||||
```python
|
||||
# Example section generation
|
||||
async def generate_sections(self, topic: str) -> List[str]:
|
||||
sections = []
|
||||
for i in range(self.config.num_main_sections):
|
||||
section = await self._generate_section(topic, i)
|
||||
sections.append(section)
|
||||
return sections
|
||||
```
|
||||
|
||||
- **Optional Components**:
|
||||
```python
|
||||
# Example FAQ generation
|
||||
async def generate_faqs(self, topic: str) -> List[str]:
|
||||
prompt = f"""
|
||||
Generate 5 common questions about {topic}
|
||||
Content type: {self.config.content_type}
|
||||
Target audience: {self.config.target_audience}
|
||||
"""
|
||||
return await llm_text_gen(prompt)
|
||||
```
|
||||
|
||||
### 3. Advanced Editing Capabilities
|
||||
- **Section Content Editor**:
|
||||
```python
|
||||
# Example content editing interface
|
||||
def edit_section_content(self, section: str, content: str) -> str:
|
||||
edited_content = st.text_area(
|
||||
"Edit Content",
|
||||
value=content,
|
||||
height=300,
|
||||
key=f"content_edit_{section}"
|
||||
)
|
||||
return edited_content
|
||||
```
|
||||
|
||||
- **Subsection Management**:
|
||||
```python
|
||||
# Example subsection reordering
|
||||
def reorder_subsections(self, section: str, subsections: List[str]) -> List[str]:
|
||||
for i, subsection in enumerate(subsections):
|
||||
if st.button("↑", key=f"move_up_{section}_{i}"):
|
||||
subsections[i], subsections[i-1] = subsections[i-1], subsections[i]
|
||||
return subsections
|
||||
```
|
||||
|
||||
### 4. Image Generation
|
||||
- **AI Image Generation**:
|
||||
```python
|
||||
# Example image generation
|
||||
async def generate_image(self, prompt: str, style: str) -> str:
|
||||
image_prompt = f"""
|
||||
Create a {style} image for: {prompt}
|
||||
Style: {self.config.image_style}
|
||||
"""
|
||||
return await generate_image(image_prompt)
|
||||
```
|
||||
|
||||
### 5. Content Optimization
|
||||
- **SEO Features**:
|
||||
```python
|
||||
# Example SEO optimization
|
||||
def optimize_content(self, content: str, keywords: List[str]) -> str:
|
||||
for keyword in keywords:
|
||||
content = self._naturally_insert_keyword(content, keyword)
|
||||
return content
|
||||
```
|
||||
|
||||
## 📊 Technical Implementation Details
|
||||
|
||||
### 1. Content Generation Pipeline
|
||||
```python
|
||||
async def generate_content(self, topic: str) -> Dict:
|
||||
# 1. Generate outline structure
|
||||
outline = await self.generate_outline(topic)
|
||||
|
||||
# 2. Generate content for each section
|
||||
for section in outline:
|
||||
content = await self.generate_section_content(section)
|
||||
outline[section]['content'] = content
|
||||
|
||||
# 3. Generate images if enabled
|
||||
if self.config.include_images:
|
||||
for section in outline:
|
||||
image = await self.generate_section_image(section)
|
||||
outline[section]['image'] = image
|
||||
|
||||
return outline
|
||||
```
|
||||
|
||||
### 2. AI Integration
|
||||
```python
|
||||
class AIIntegration:
|
||||
def __init__(self, provider: str):
|
||||
self.provider = provider
|
||||
self.model = self._initialize_model()
|
||||
|
||||
async def generate_text(self, prompt: str) -> str:
|
||||
if self.provider == "gemini":
|
||||
return await gemini_text_response(prompt)
|
||||
elif self.provider == "openai":
|
||||
return await openai_chatgpt(prompt)
|
||||
```
|
||||
|
||||
### 3. Image Processing
|
||||
```python
|
||||
class ImageProcessor:
|
||||
def __init__(self, engine: str):
|
||||
self.engine = engine
|
||||
|
||||
async def generate_image(self, prompt: str) -> str:
|
||||
if self.engine == "Gemini-AI":
|
||||
return await generate_gemini_image(prompt)
|
||||
elif self.engine == "Dalle3":
|
||||
return await generate_dalle3_images(prompt)
|
||||
```
|
||||
|
||||
## 🔧 Configuration Examples
|
||||
|
||||
### 1. Basic Configuration
|
||||
```python
|
||||
config = OutlineConfig(
|
||||
content_type=ContentType.GUIDE,
|
||||
content_depth=ContentDepth.INTERMEDIATE,
|
||||
target_word_count=2000,
|
||||
num_main_sections=5,
|
||||
num_subsections_per_section=3
|
||||
)
|
||||
```
|
||||
|
||||
### 2. Advanced Configuration
|
||||
```python
|
||||
config = OutlineConfig(
|
||||
content_type=ContentType.TUTORIAL,
|
||||
content_depth=ContentDepth.ADVANCED,
|
||||
outline_style=OutlineStyle.MODERN,
|
||||
target_word_count=3000,
|
||||
include_images=True,
|
||||
image_style="realistic",
|
||||
image_engine="Gemini-AI",
|
||||
target_audience="developers",
|
||||
language="English",
|
||||
keywords=["python", "tutorial", "advanced"]
|
||||
)
|
||||
```
|
||||
|
||||
## 📝 Usage Examples
|
||||
|
||||
### 1. Basic Usage
|
||||
```python
|
||||
# Initialize generator
|
||||
generator = BlogOutlineGenerator()
|
||||
|
||||
# Generate outline
|
||||
outline = await generator.generate_outline("Python Programming Basics")
|
||||
|
||||
# Export to markdown
|
||||
markdown = generator.to_markdown()
|
||||
```
|
||||
|
||||
### 2. Advanced Usage
|
||||
```python
|
||||
# Custom configuration
|
||||
config = OutlineConfig(
|
||||
content_type=ContentType.TUTORIAL,
|
||||
content_depth=ContentDepth.ADVANCED,
|
||||
include_images=True
|
||||
)
|
||||
|
||||
# Initialize with config
|
||||
generator = BlogOutlineGenerator(config)
|
||||
|
||||
# Generate with custom settings
|
||||
outline = await generator.generate_outline(
|
||||
"Advanced Python Decorators",
|
||||
keywords=["python", "decorators", "advanced"]
|
||||
)
|
||||
|
||||
# Export to multiple formats
|
||||
markdown = generator.to_markdown()
|
||||
json_output = generator.to_json()
|
||||
html_output = generator.to_html()
|
||||
```
|
||||
|
||||
## 🔍 Technical Considerations
|
||||
|
||||
### 1. Performance Optimization
|
||||
- Asynchronous operations for AI calls
|
||||
- Caching of generated content
|
||||
- Batch processing for images
|
||||
- Memory management for large documents
|
||||
|
||||
### 2. Error Handling
|
||||
```python
|
||||
try:
|
||||
content = await llm_text_gen(prompt)
|
||||
except Exception as e:
|
||||
logger.error(f"Content generation failed: {e}")
|
||||
return None
|
||||
```
|
||||
|
||||
### 3. Data Validation
|
||||
```python
|
||||
from pydantic import BaseModel, validator
|
||||
|
||||
class SectionContent(BaseModel):
|
||||
title: str
|
||||
content: str
|
||||
image_path: Optional[str]
|
||||
|
||||
@validator('content')
|
||||
def validate_content_length(cls, v):
|
||||
if len(v.split()) < 100:
|
||||
raise ValueError("Content too short")
|
||||
return v
|
||||
```
|
||||
|
||||
## 🌟 Features
|
||||
|
||||
### 1. Content Generation
|
||||
- **AI-Powered Content Creation**: Generate high-quality content for each section using advanced language models
|
||||
- **Multiple Content Types**: Support for various content formats including:
|
||||
- How-to guides
|
||||
- Tutorials
|
||||
- Listicles
|
||||
- Comparisons
|
||||
- Case studies
|
||||
- Opinion pieces
|
||||
- News articles
|
||||
- Reviews
|
||||
- General guides
|
||||
- **Customizable Content Depth**:
|
||||
- Basic: Simple, easy-to-understand content
|
||||
- Intermediate: Balanced depth with practical examples
|
||||
- Advanced: Detailed technical content
|
||||
- Expert: In-depth analysis and advanced concepts
|
||||
|
||||
### 2. Outline Structure
|
||||
- **Flexible Section Management**:
|
||||
- Customizable number of main sections
|
||||
- Configurable subsections per section
|
||||
- Dynamic section reordering
|
||||
- Easy addition/removal of sections
|
||||
- **Optional Components**:
|
||||
- Introduction section
|
||||
- Conclusion section
|
||||
- FAQ section
|
||||
- Additional resources section
|
||||
|
||||
### 3. Advanced Editing Capabilities
|
||||
- **Section Content Editor**:
|
||||
- Rich text editing interface
|
||||
- Real-time word count tracking
|
||||
- Formatting options (Bold, Italic, Lists, Code Blocks, Links)
|
||||
- AI-powered content enhancement
|
||||
- **Subsection Management**:
|
||||
- Drag-and-drop reordering
|
||||
- Individual subsection editing
|
||||
- Add/remove subsection functionality
|
||||
- Bulk editing capabilities
|
||||
- **Metadata Editing**:
|
||||
- Section-specific settings
|
||||
- Content depth adjustment
|
||||
- Target word count configuration
|
||||
- Image settings customization
|
||||
|
||||
### 4. Image Generation
|
||||
- **AI Image Generation**:
|
||||
- Multiple image styles (realistic, illustration, minimalist, photographic, artistic)
|
||||
- Support for multiple image engines (Gemini-AI, Dalle3, Stability-AI)
|
||||
- Custom image prompts
|
||||
- Image regeneration capability
|
||||
- **Image Integration**:
|
||||
- Automatic image placement
|
||||
- Image preview and editing
|
||||
- Image prompt viewing and editing
|
||||
- Image style customization
|
||||
|
||||
### 5. Content Optimization
|
||||
- **SEO Features**:
|
||||
- Keyword integration
|
||||
- Content structure optimization
|
||||
- Meta description generation
|
||||
- SEO-friendly formatting
|
||||
- **Audience Targeting**:
|
||||
- Customizable target audience
|
||||
- Language selection
|
||||
- Content tone adjustment
|
||||
- Reading level optimization
|
||||
|
||||
### 6. Export Options
|
||||
- **Multiple Formats**:
|
||||
- Markdown export
|
||||
- JSON export
|
||||
- HTML export
|
||||
- Custom formatting options
|
||||
- **Download Capabilities**:
|
||||
- One-click download
|
||||
- Format-specific styling
|
||||
- Custom file naming
|
||||
- Batch export options
|
||||
|
||||
### 7. User Interface
|
||||
- **Intuitive Design**:
|
||||
- Clean, modern interface
|
||||
- Responsive layout
|
||||
- Easy navigation
|
||||
- Clear visual hierarchy
|
||||
- **Interactive Features**:
|
||||
- Real-time preview
|
||||
- Drag-and-drop functionality
|
||||
- Quick edit options
|
||||
- Contextual help
|
||||
|
||||
### 8. Statistics and Analytics
|
||||
- **Content Metrics**:
|
||||
- Word count tracking
|
||||
- Section statistics
|
||||
- Subsection counts
|
||||
- Content depth analysis
|
||||
- **Progress Tracking**:
|
||||
- Generation progress
|
||||
- Edit history
|
||||
- Version comparison
|
||||
- Performance metrics
|
||||
|
||||
## 🚀 Getting Started
|
||||
|
||||
### Installation
|
||||
```bash
|
||||
pip install -r requirements.txt
|
||||
```
|
||||
|
||||
### Usage
|
||||
1. Launch the application:
|
||||
```bash
|
||||
streamlit run lib/ai_writers/ai_outline_writer/outline_ui.py
|
||||
```
|
||||
|
||||
2. Configure your outline:
|
||||
- Enter your blog topic
|
||||
- Select content type and depth
|
||||
- Choose outline style
|
||||
- Set target word count
|
||||
- Configure sections and subsections
|
||||
|
||||
3. Generate and edit:
|
||||
- Click "Generate Outline"
|
||||
- Review and edit sections
|
||||
- Customize content and images
|
||||
- Export in your preferred format
|
||||
|
||||
## 🔧 Configuration Options
|
||||
|
||||
### Basic Settings
|
||||
- **Blog Topic**: Main subject of your content
|
||||
- **Content Type**: Type of content to generate
|
||||
- **Content Depth**: Level of detail and complexity
|
||||
- **Outline Style**: Structure and formatting style
|
||||
|
||||
### Advanced Settings
|
||||
- **Target Word Count**: Desired length of the content
|
||||
- **Number of Sections**: Customize main sections
|
||||
- **Subsections**: Configure subsections per section
|
||||
- **Image Settings**: Customize image generation
|
||||
- **Target Audience**: Define your audience
|
||||
- **Language**: Select content language
|
||||
- **Keywords**: Add SEO keywords
|
||||
- **Excluded Topics**: Specify topics to avoid
|
||||
|
||||
## 📊 Output Formats
|
||||
|
||||
### 1. Preview Mode
|
||||
- Interactive preview of the entire outline
|
||||
- Real-time editing capabilities
|
||||
- Image preview and management
|
||||
- Content statistics
|
||||
|
||||
### 2. Markdown Export
|
||||
- Clean markdown formatting
|
||||
- Proper heading hierarchy
|
||||
- Image embedding
|
||||
- Code block formatting
|
||||
|
||||
### 3. JSON Export
|
||||
- Structured data format
|
||||
- Complete outline information
|
||||
- Content and image metadata
|
||||
- Configuration details
|
||||
|
||||
### 4. HTML Export
|
||||
- Styled HTML output
|
||||
- Responsive design
|
||||
- Image integration
|
||||
- Custom CSS support
|
||||
|
||||
## 💡 Best Practices
|
||||
|
||||
### Content Generation
|
||||
1. Start with a clear topic and target audience
|
||||
2. Choose appropriate content type and depth
|
||||
3. Use relevant keywords for SEO
|
||||
4. Review and edit generated content
|
||||
5. Add personal insights and examples
|
||||
|
||||
### Outline Structure
|
||||
1. Maintain logical flow between sections
|
||||
2. Balance section lengths
|
||||
3. Include relevant subsections
|
||||
4. Add appropriate transitions
|
||||
5. Ensure comprehensive coverage
|
||||
|
||||
### Image Usage
|
||||
1. Choose appropriate image styles
|
||||
2. Generate relevant images
|
||||
3. Optimize image placement
|
||||
4. Review image prompts
|
||||
5. Consider image licensing
|
||||
|
||||
## 🔄 Workflow
|
||||
|
||||
1. **Initial Setup**
|
||||
- Configure basic settings
|
||||
- Set content parameters
|
||||
- Define target audience
|
||||
|
||||
2. **Generation**
|
||||
- Generate initial outline
|
||||
- Review structure
|
||||
- Generate content
|
||||
- Create images
|
||||
|
||||
3. **Editing**
|
||||
- Review and edit content
|
||||
- Adjust structure
|
||||
- Customize images
|
||||
- Optimize for SEO
|
||||
|
||||
4. **Export**
|
||||
- Choose export format
|
||||
- Review final output
|
||||
- Download content
|
||||
- Save configuration
|
||||
|
||||
## 📝 Tips and Tricks
|
||||
|
||||
### Content Generation
|
||||
- Use specific keywords for better results
|
||||
- Provide clear context for the AI
|
||||
- Review and refine generated content
|
||||
- Add personal expertise
|
||||
|
||||
### Structure Optimization
|
||||
- Maintain consistent section lengths
|
||||
- Use clear subsection hierarchies
|
||||
- Include relevant examples
|
||||
- Add practical applications
|
||||
|
||||
### Image Enhancement
|
||||
- Use descriptive image prompts
|
||||
- Experiment with different styles
|
||||
- Consider image placement
|
||||
- Review image relevance
|
||||
|
||||
## 🤝 Contributing
|
||||
|
||||
We welcome contributions! Please follow these steps:
|
||||
1. Fork the repository
|
||||
2. Create a feature branch
|
||||
3. Make your changes
|
||||
4. Submit a pull request
|
||||
|
||||
## 📄 License
|
||||
|
||||
This project is licensed under the MIT License - see the LICENSE file for details.
|
||||
|
||||
## 📞 Support
|
||||
|
||||
For support, please:
|
||||
1. Check the documentation
|
||||
2. Review existing issues
|
||||
3. Create a new issue if needed
|
||||
4. Contact the maintainers
|
||||
|
||||
## 🔮 Future Enhancements
|
||||
|
||||
Planned features:
|
||||
- Multi-language support
|
||||
- Advanced AI models
|
||||
- More export formats
|
||||
- Enhanced editing tools
|
||||
- Collaboration features
|
||||
- Version control integration
|
||||
- Analytics dashboard
|
||||
- Custom templates
|
||||
- API integration
|
||||
- Mobile optimization
|
||||
336
lib/ai_writers/ai_outline_writer/get_blog_outline.py
Normal file
336
lib/ai_writers/ai_outline_writer/get_blog_outline.py
Normal file
@@ -0,0 +1,336 @@
|
||||
"""
|
||||
Enhanced Blog Outline Generator
|
||||
|
||||
This module provides a sophisticated outline generation system that creates detailed,
|
||||
well-structured outlines for blog posts based on user preferences and content requirements.
|
||||
"""
|
||||
|
||||
import sys
|
||||
from typing import Dict, List, Optional
|
||||
from enum import Enum
|
||||
from dataclasses import dataclass
|
||||
from loguru import logger
|
||||
|
||||
from lib.gpt_providers.text_generation.main_text_generation import llm_text_gen
|
||||
from lib.gpt_providers.text_to_image_generation.main_generate_image_from_prompt import generate_image
|
||||
|
||||
logger.remove()
|
||||
logger.add(sys.stdout,
|
||||
colorize=True,
|
||||
format="<level>{level}</level>|<green>{file}:{line}:{function}</green>| {message}")
|
||||
|
||||
class ContentType(Enum):
|
||||
"""Types of content that can be generated."""
|
||||
HOW_TO = "how-to"
|
||||
TUTORIAL = "tutorial"
|
||||
LISTICLE = "listicle"
|
||||
COMPARISON = "comparison"
|
||||
CASE_STUDY = "case-study"
|
||||
OPINION = "opinion"
|
||||
NEWS = "news"
|
||||
REVIEW = "review"
|
||||
GUIDE = "guide"
|
||||
|
||||
class ContentDepth(Enum):
|
||||
"""Depth levels for content coverage."""
|
||||
BASIC = "basic"
|
||||
INTERMEDIATE = "intermediate"
|
||||
ADVANCED = "advanced"
|
||||
EXPERT = "expert"
|
||||
|
||||
class OutlineStyle(Enum):
|
||||
"""Styles for outline structure."""
|
||||
TRADITIONAL = "traditional"
|
||||
MODERN = "modern"
|
||||
CONVERSATIONAL = "conversational"
|
||||
ACADEMIC = "academic"
|
||||
SEO_OPTIMIZED = "seo-optimized"
|
||||
|
||||
@dataclass
|
||||
class OutlineConfig:
|
||||
"""Configuration for outline generation."""
|
||||
content_type: ContentType = ContentType.GUIDE
|
||||
content_depth: ContentDepth = ContentDepth.INTERMEDIATE
|
||||
outline_style: OutlineStyle = OutlineStyle.MODERN
|
||||
target_word_count: int = 2000
|
||||
num_main_sections: int = 5
|
||||
num_subsections_per_section: int = 3
|
||||
include_introduction: bool = True
|
||||
include_conclusion: bool = True
|
||||
include_faqs: bool = True
|
||||
include_resources: bool = True
|
||||
target_audience: str = "general"
|
||||
language: str = "English"
|
||||
keywords: List[str] = None
|
||||
exclude_topics: List[str] = None
|
||||
include_images: bool = True
|
||||
image_style: str = "realistic"
|
||||
image_engine: str = "Gemini-AI"
|
||||
|
||||
@dataclass
|
||||
class SectionContent:
|
||||
"""Content for a section including text and image."""
|
||||
title: str
|
||||
content: str
|
||||
image_prompt: Optional[str] = None
|
||||
image_path: Optional[str] = None
|
||||
|
||||
class BlogOutlineGenerator:
|
||||
"""Enhanced blog outline generator with comprehensive controls."""
|
||||
|
||||
def __init__(self, config: Optional[OutlineConfig] = None):
|
||||
"""Initialize the outline generator with optional configuration."""
|
||||
self.config = config or OutlineConfig()
|
||||
self.outline = {}
|
||||
self.section_contents = {}
|
||||
|
||||
async def generate_outline(self, topic: str) -> Dict:
|
||||
"""Generate a comprehensive outline based on the topic and configuration."""
|
||||
try:
|
||||
# Step 1: Generate main sections
|
||||
main_sections = await self._generate_main_sections(topic)
|
||||
|
||||
# Step 2: Generate subsections for each main section
|
||||
detailed_sections = await self._generate_subsections(main_sections)
|
||||
|
||||
# Step 3: Add introduction and conclusion if requested
|
||||
if self.config.include_introduction:
|
||||
detailed_sections["Introduction"] = await self._generate_introduction(topic)
|
||||
|
||||
if self.config.include_conclusion:
|
||||
detailed_sections["Conclusion"] = await self._generate_conclusion(topic)
|
||||
|
||||
# Step 4: Add FAQs if requested
|
||||
if self.config.include_faqs:
|
||||
detailed_sections["FAQs"] = await self._generate_faqs(topic)
|
||||
|
||||
# Step 5: Add resources if requested
|
||||
if self.config.include_resources:
|
||||
detailed_sections["Additional Resources"] = await self._generate_resources(topic)
|
||||
|
||||
self.outline = detailed_sections
|
||||
|
||||
# Step 6: Generate content for each section
|
||||
await self._generate_section_contents(topic)
|
||||
|
||||
return self.outline
|
||||
|
||||
except Exception as err:
|
||||
logger.error(f"Failed to generate outline: {err}")
|
||||
raise
|
||||
|
||||
async def _generate_main_sections(self, topic: str) -> List[str]:
|
||||
"""Generate main sections for the outline."""
|
||||
prompt = f"""Generate {self.config.num_main_sections} main sections for a {self.config.content_type.value}
|
||||
article about {topic} with the following characteristics:
|
||||
|
||||
Content Type: {self.config.content_type.value}
|
||||
Content Depth: {self.config.content_depth.value}
|
||||
Target Word Count: {self.config.target_word_count}
|
||||
Target Audience: {self.config.target_audience}
|
||||
Style: {self.config.outline_style.value}
|
||||
|
||||
Additional Requirements:
|
||||
- Each section should contribute to the overall word count goal
|
||||
- Sections should flow logically
|
||||
- Include key concepts and important points
|
||||
- Consider SEO optimization
|
||||
- Keywords to include: {', '.join(self.config.keywords or [])}
|
||||
- Topics to exclude: {', '.join(self.config.exclude_topics or [])}
|
||||
|
||||
Please provide only the section titles, one per line."""
|
||||
|
||||
response = await llm_text_gen(prompt)
|
||||
return [section.strip() for section in response.split('\n') if section.strip()]
|
||||
|
||||
async def _generate_subsections(self, main_sections: List[str]) -> Dict[str, List[str]]:
|
||||
"""Generate subsections for each main section."""
|
||||
detailed_sections = {}
|
||||
|
||||
for section in main_sections:
|
||||
prompt = f"""Generate {self.config.num_subsections_per_section} subsections for the following section:
|
||||
{section}
|
||||
|
||||
Content Type: {self.config.content_type.value}
|
||||
Content Depth: {self.config.content_depth.value}
|
||||
Style: {self.config.outline_style.value}
|
||||
|
||||
Each subsection should:
|
||||
- Be specific and focused
|
||||
- Support the main section's topic
|
||||
- Include key points to cover
|
||||
- Consider SEO optimization
|
||||
|
||||
Please provide only the subsection titles, one per line."""
|
||||
|
||||
response = await llm_text_gen(prompt)
|
||||
detailed_sections[section] = [sub.strip() for sub in response.split('\n') if sub.strip()]
|
||||
|
||||
return detailed_sections
|
||||
|
||||
async def _generate_introduction(self, topic: str) -> List[str]:
|
||||
"""Generate introduction subsections."""
|
||||
prompt = f"""Generate introduction subsections for an article about {topic}.
|
||||
|
||||
Content Type: {self.config.content_type.value}
|
||||
Content Depth: {self.config.content_depth.value}
|
||||
Style: {self.config.outline_style.value}
|
||||
|
||||
The introduction should:
|
||||
- Hook the reader
|
||||
- Present the main topic
|
||||
- Outline what's to come
|
||||
- Set the tone for the article
|
||||
|
||||
Please provide only the subsection titles, one per line."""
|
||||
|
||||
response = await llm_text_gen(prompt)
|
||||
return [sub.strip() for sub in response.split('\n') if sub.strip()]
|
||||
|
||||
async def _generate_conclusion(self, topic: str) -> List[str]:
|
||||
"""Generate conclusion subsections."""
|
||||
prompt = f"""Generate conclusion subsections for an article about {topic}.
|
||||
|
||||
Content Type: {self.config.content_type.value}
|
||||
Content Depth: {self.config.content_depth.value}
|
||||
Style: {self.config.outline_style.value}
|
||||
|
||||
The conclusion should:
|
||||
- Summarize key points
|
||||
- Provide final thoughts
|
||||
- Include a call to action
|
||||
- Leave a lasting impression
|
||||
|
||||
Please provide only the subsection titles, one per line."""
|
||||
|
||||
response = await llm_text_gen(prompt)
|
||||
return [sub.strip() for sub in response.split('\n') if sub.strip()]
|
||||
|
||||
async def _generate_faqs(self, topic: str) -> List[str]:
|
||||
"""Generate FAQ subsections."""
|
||||
prompt = f"""Generate FAQ subsections for an article about {topic}.
|
||||
|
||||
Content Type: {self.config.content_type.value}
|
||||
Content Depth: {self.config.content_depth.value}
|
||||
Style: {self.config.outline_style.value}
|
||||
|
||||
The FAQs should:
|
||||
- Address common questions
|
||||
- Cover important aspects
|
||||
- Be relevant to the target audience
|
||||
- Include both basic and advanced questions
|
||||
|
||||
Please provide only the FAQ questions, one per line."""
|
||||
|
||||
response = await llm_text_gen(prompt)
|
||||
return [sub.strip() for sub in response.split('\n') if sub.strip()]
|
||||
|
||||
async def _generate_resources(self, topic: str) -> List[str]:
|
||||
"""Generate resource subsections."""
|
||||
prompt = f"""Generate resource subsections for an article about {topic}.
|
||||
|
||||
Content Type: {self.config.content_type.value}
|
||||
Content Depth: {self.config.content_depth.value}
|
||||
Style: {self.config.outline_style.value}
|
||||
|
||||
The resources should:
|
||||
- Include relevant links
|
||||
- Suggest further reading
|
||||
- Provide tools or references
|
||||
- Include related materials
|
||||
|
||||
Please provide only the resource categories, one per line."""
|
||||
|
||||
response = await llm_text_gen(prompt)
|
||||
return [sub.strip() for sub in response.split('\n') if sub.strip()]
|
||||
|
||||
async def _generate_section_contents(self, topic: str):
|
||||
"""Generate content and images for each section."""
|
||||
for section, subsections in self.outline.items():
|
||||
if section not in ["Introduction", "Conclusion", "FAQs", "Additional Resources"]:
|
||||
# Generate content for the main section
|
||||
content_prompt = f"""Write a detailed section for a blog post about {topic}.
|
||||
Section Title: {section}
|
||||
Content Type: {self.config.content_type.value}
|
||||
Content Depth: {self.config.content_depth.value}
|
||||
Style: {self.config.outline_style.value}
|
||||
Target Word Count: {self.config.target_word_count // self.config.num_main_sections}
|
||||
|
||||
Include:
|
||||
- Clear explanation of the main points
|
||||
- Examples and illustrations
|
||||
- Key takeaways
|
||||
- Relevant data or statistics
|
||||
"""
|
||||
|
||||
content = await llm_text_gen(content_prompt)
|
||||
|
||||
# Generate image prompt if images are enabled
|
||||
image_prompt = None
|
||||
image_path = None
|
||||
|
||||
if self.config.include_images:
|
||||
image_prompt = f"""Create a detailed image prompt for a blog section about {topic}.
|
||||
Section: {section}
|
||||
Content: {content[:200]}...
|
||||
Style: {self.config.image_style}
|
||||
"""
|
||||
|
||||
image_prompt = await llm_text_gen(image_prompt)
|
||||
try:
|
||||
image_path = generate_image(
|
||||
image_prompt,
|
||||
title=section,
|
||||
description=content[:100],
|
||||
tags=self.config.keywords
|
||||
)
|
||||
except Exception as err:
|
||||
logger.warning(f"Failed to generate image for section {section}: {err}")
|
||||
|
||||
self.section_contents[section] = SectionContent(
|
||||
title=section,
|
||||
content=content,
|
||||
image_prompt=image_prompt,
|
||||
image_path=image_path
|
||||
)
|
||||
|
||||
def to_markdown(self) -> str:
|
||||
"""Convert outline to markdown format with content and images."""
|
||||
markdown = f"# {self.outline.get('Introduction', [''])[0]}\n\n"
|
||||
|
||||
for section, subsections in self.outline.items():
|
||||
if section not in ["Introduction", "Conclusion", "FAQs", "Additional Resources"]:
|
||||
markdown += f"## {section}\n\n"
|
||||
|
||||
# Add section content if available
|
||||
if section in self.section_contents:
|
||||
content = self.section_contents[section]
|
||||
markdown += f"{content.content}\n\n"
|
||||
|
||||
# Add image if available
|
||||
if content.image_path:
|
||||
markdown += f"\n\n"
|
||||
|
||||
# Add subsections
|
||||
for subsection in subsections:
|
||||
markdown += f"- {subsection}\n"
|
||||
markdown += "\n"
|
||||
|
||||
if "Conclusion" in self.outline:
|
||||
markdown += "## Conclusion\n\n"
|
||||
for subsection in self.outline["Conclusion"]:
|
||||
markdown += f"- {subsection}\n"
|
||||
markdown += "\n"
|
||||
|
||||
if "FAQs" in self.outline:
|
||||
markdown += "## Frequently Asked Questions\n\n"
|
||||
for faq in self.outline["FAQs"]:
|
||||
markdown += f"- {faq}\n"
|
||||
markdown += "\n"
|
||||
|
||||
if "Additional Resources" in self.outline:
|
||||
markdown += "## Additional Resources\n\n"
|
||||
for resource in self.outline["Additional Resources"]:
|
||||
markdown += f"- {resource}\n"
|
||||
|
||||
return markdown
|
||||
489
lib/ai_writers/ai_outline_writer/outline_ui.py
Normal file
489
lib/ai_writers/ai_outline_writer/outline_ui.py
Normal file
@@ -0,0 +1,489 @@
|
||||
"""
|
||||
Streamlit UI for Enhanced Blog Outline Generator
|
||||
|
||||
This module provides a user-friendly interface for generating comprehensive blog outlines
|
||||
with AI-powered content and image generation capabilities.
|
||||
"""
|
||||
|
||||
import streamlit as st
|
||||
import asyncio
|
||||
from pathlib import Path
|
||||
from typing import Optional, Dict, List
|
||||
import json
|
||||
import time
|
||||
from datetime import datetime
|
||||
|
||||
from .get_blog_outline import (
|
||||
BlogOutlineGenerator,
|
||||
OutlineConfig,
|
||||
ContentType,
|
||||
ContentDepth,
|
||||
OutlineStyle
|
||||
)
|
||||
|
||||
# Custom CSS for better styling
|
||||
st.markdown("""
|
||||
<style>
|
||||
.main {
|
||||
background-color: #f5f5f5;
|
||||
}
|
||||
.stButton>button {
|
||||
background-color: #4CAF50;
|
||||
color: white;
|
||||
padding: 10px 24px;
|
||||
border-radius: 4px;
|
||||
border: none;
|
||||
font-weight: bold;
|
||||
}
|
||||
.stButton>button:hover {
|
||||
background-color: #45a049;
|
||||
}
|
||||
.section-card {
|
||||
background-color: white;
|
||||
padding: 20px;
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
.content-preview {
|
||||
background-color: #f8f9fa;
|
||||
padding: 15px;
|
||||
border-radius: 4px;
|
||||
margin: 10px 0;
|
||||
}
|
||||
.image-container {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
margin: 20px 0;
|
||||
}
|
||||
.stats-card {
|
||||
background-color: #e8f5e9;
|
||||
padding: 15px;
|
||||
border-radius: 8px;
|
||||
margin: 10px 0;
|
||||
}
|
||||
.edit-section {
|
||||
background-color: #e3f2fd;
|
||||
padding: 15px;
|
||||
border-radius: 4px;
|
||||
margin: 10px 0;
|
||||
}
|
||||
.subsection-list {
|
||||
margin-left: 20px;
|
||||
}
|
||||
</style>
|
||||
""", unsafe_allow_html=True)
|
||||
|
||||
def edit_section_content(section: str, content: str) -> str:
|
||||
"""Edit section content with advanced options."""
|
||||
st.markdown('<div class="edit-section">', unsafe_allow_html=True)
|
||||
|
||||
# Content editing
|
||||
edited_content = st.text_area(
|
||||
"Edit Content",
|
||||
value=content,
|
||||
height=300,
|
||||
key=f"content_edit_{section}"
|
||||
)
|
||||
|
||||
# Word count and formatting
|
||||
col1, col2 = st.columns(2)
|
||||
with col1:
|
||||
word_count = len(edited_content.split())
|
||||
st.info(f"Word Count: {word_count}")
|
||||
|
||||
with col2:
|
||||
formatting = st.multiselect(
|
||||
"Formatting Options",
|
||||
["Bold", "Italic", "Lists", "Code Blocks", "Links"],
|
||||
key=f"format_{section}"
|
||||
)
|
||||
|
||||
# AI enhancement options
|
||||
with st.expander("AI Enhancement Options"):
|
||||
enhance_options = st.multiselect(
|
||||
"Select Enhancements",
|
||||
["Improve Clarity", "Add Examples", "Expand Details", "Add Statistics", "Improve SEO"],
|
||||
key=f"enhance_{section}"
|
||||
)
|
||||
|
||||
if st.button("Apply Enhancements", key=f"apply_enhance_{section}"):
|
||||
with st.spinner("Applying enhancements..."):
|
||||
# TODO: Implement AI enhancement logic
|
||||
st.success("Enhancements applied!")
|
||||
|
||||
st.markdown('</div>', unsafe_allow_html=True)
|
||||
return edited_content
|
||||
|
||||
def edit_subsections(section: str, subsections: List[str]) -> List[str]:
|
||||
"""Edit subsections with reordering and editing capabilities."""
|
||||
st.markdown('<div class="edit-section">', unsafe_allow_html=True)
|
||||
|
||||
# Reorder subsections
|
||||
st.markdown("### Reorder Subsections")
|
||||
for i, subsection in enumerate(subsections):
|
||||
col1, col2 = st.columns([4, 1])
|
||||
with col1:
|
||||
subsections[i] = st.text_input(
|
||||
f"Subsection {i+1}",
|
||||
value=subsection,
|
||||
key=f"subsection_{section}_{i}"
|
||||
)
|
||||
with col2:
|
||||
if st.button("↑", key=f"move_up_{section}_{i}") and i > 0:
|
||||
subsections[i], subsections[i-1] = subsections[i-1], subsections[i]
|
||||
st.experimental_rerun()
|
||||
if st.button("↓", key=f"move_down_{section}_{i}") and i < len(subsections)-1:
|
||||
subsections[i], subsections[i+1] = subsections[i+1], subsections[i]
|
||||
st.experimental_rerun()
|
||||
|
||||
# Add/remove subsections
|
||||
col1, col2 = st.columns(2)
|
||||
with col1:
|
||||
if st.button("Add Subsection", key=f"add_sub_{section}"):
|
||||
subsections.append("New Subsection")
|
||||
st.experimental_rerun()
|
||||
with col2:
|
||||
if st.button("Remove Last Subsection", key=f"remove_sub_{section}"):
|
||||
if subsections:
|
||||
subsections.pop()
|
||||
st.experimental_rerun()
|
||||
|
||||
st.markdown('</div>', unsafe_allow_html=True)
|
||||
return subsections
|
||||
|
||||
def edit_section_metadata(section: str, generator: BlogOutlineGenerator):
|
||||
"""Edit section metadata and settings."""
|
||||
st.markdown('<div class="edit-section">', unsafe_allow_html=True)
|
||||
|
||||
# Section settings
|
||||
st.markdown("### Section Settings")
|
||||
|
||||
# Image settings
|
||||
if generator.config.include_images:
|
||||
col1, col2 = st.columns(2)
|
||||
with col1:
|
||||
new_image_style = st.selectbox(
|
||||
"Image Style",
|
||||
["realistic", "illustration", "minimalist", "photographic", "artistic"],
|
||||
key=f"img_style_{section}"
|
||||
)
|
||||
with col2:
|
||||
new_image_engine = st.selectbox(
|
||||
"Image Engine",
|
||||
["Gemini-AI", "Dalle3", "Stability-AI"],
|
||||
key=f"img_engine_{section}"
|
||||
)
|
||||
|
||||
if st.button("Regenerate Image", key=f"regen_img_{section}"):
|
||||
with st.spinner("Regenerating image..."):
|
||||
# TODO: Implement image regeneration logic
|
||||
st.success("Image regenerated!")
|
||||
|
||||
# Content settings
|
||||
st.markdown("### Content Settings")
|
||||
col1, col2 = st.columns(2)
|
||||
with col1:
|
||||
target_word_count = st.number_input(
|
||||
"Target Word Count",
|
||||
min_value=100,
|
||||
max_value=2000,
|
||||
value=500,
|
||||
step=100,
|
||||
key=f"word_count_{section}"
|
||||
)
|
||||
with col2:
|
||||
content_depth = st.selectbox(
|
||||
"Content Depth",
|
||||
[depth.value for depth in ContentDepth],
|
||||
key=f"depth_{section}"
|
||||
)
|
||||
|
||||
st.markdown('</div>', unsafe_allow_html=True)
|
||||
|
||||
def display_section(section: str, subsections: List[str], content: Optional[Dict] = None, generator: Optional[BlogOutlineGenerator] = None):
|
||||
"""Display a section with its content and subsections."""
|
||||
st.markdown(f"""
|
||||
<div class="section-card">
|
||||
<h2>{section}</h2>
|
||||
""", unsafe_allow_html=True)
|
||||
|
||||
# Section editing controls
|
||||
col1, col2 = st.columns([4, 1])
|
||||
with col1:
|
||||
st.markdown(f"### {section}")
|
||||
with col2:
|
||||
edit_mode = st.checkbox("Edit Mode", key=f"edit_mode_{section}")
|
||||
|
||||
if content:
|
||||
# Display content with word count
|
||||
word_count = len(content.content.split())
|
||||
st.markdown(f"""
|
||||
<div class="content-preview">
|
||||
<p><strong>Content Preview</strong> ({word_count} words)</p>
|
||||
{content.content[:500]}...
|
||||
</div>
|
||||
""", unsafe_allow_html=True)
|
||||
|
||||
# Display image if available
|
||||
if content.image_path:
|
||||
st.markdown('<div class="image-container">', unsafe_allow_html=True)
|
||||
st.image(content.image_path, caption=section, use_column_width=True)
|
||||
st.markdown('</div>', unsafe_allow_html=True)
|
||||
|
||||
# Display image prompt in expander
|
||||
if content.image_prompt:
|
||||
with st.expander("View Image Prompt"):
|
||||
st.code(content.image_prompt, language="text")
|
||||
|
||||
# Edit mode controls
|
||||
if edit_mode:
|
||||
# Edit content
|
||||
edited_content = edit_section_content(section, content.content)
|
||||
content.content = edited_content
|
||||
|
||||
# Edit subsections
|
||||
edited_subsections = edit_subsections(section, subsections)
|
||||
subsections[:] = edited_subsections
|
||||
|
||||
# Edit metadata
|
||||
if generator:
|
||||
edit_section_metadata(section, generator)
|
||||
|
||||
# Display subsections
|
||||
st.markdown("### Subsections")
|
||||
st.markdown('<div class="subsection-list">', unsafe_allow_html=True)
|
||||
for subsection in subsections:
|
||||
st.markdown(f"- {subsection}")
|
||||
st.markdown('</div>', unsafe_allow_html=True)
|
||||
|
||||
st.markdown("</div>", unsafe_allow_html=True)
|
||||
|
||||
def display_stats(generator, outline):
|
||||
"""Display statistics about the generated outline."""
|
||||
total_sections = len(outline)
|
||||
total_subsections = sum(len(subsections) for subsections in outline.values())
|
||||
total_content = sum(len(content.content.split()) for content in generator.section_contents.values())
|
||||
|
||||
col1, col2, col3 = st.columns(3)
|
||||
with col1:
|
||||
st.markdown(f"""
|
||||
<div class="stats-card">
|
||||
<h3>📊 Statistics</h3>
|
||||
<p>Total Sections: {total_sections}</p>
|
||||
<p>Total Subsections: {total_subsections}</p>
|
||||
<p>Estimated Word Count: {total_content}</p>
|
||||
</div>
|
||||
""", unsafe_allow_html=True)
|
||||
|
||||
with col2:
|
||||
st.markdown(f"""
|
||||
<div class="stats-card">
|
||||
<h3>🎯 Target</h3>
|
||||
<p>Target Word Count: {generator.config.target_word_count}</p>
|
||||
<p>Content Depth: {generator.config.content_depth.value}</p>
|
||||
<p>Style: {generator.config.outline_style.value}</p>
|
||||
</div>
|
||||
""", unsafe_allow_html=True)
|
||||
|
||||
with col3:
|
||||
st.markdown(f"""
|
||||
<div class="stats-card">
|
||||
<h3>📝 Content Type</h3>
|
||||
<p>Type: {generator.config.content_type.value}</p>
|
||||
<p>Audience: {generator.config.target_audience}</p>
|
||||
<p>Language: {generator.config.language}</p>
|
||||
</div>
|
||||
""", unsafe_allow_html=True)
|
||||
|
||||
def main():
|
||||
st.set_page_config(
|
||||
page_title="Blog Outline Generator",
|
||||
page_icon="📝",
|
||||
layout="wide",
|
||||
initial_sidebar_state="expanded"
|
||||
)
|
||||
|
||||
# Header with description
|
||||
st.title("Blog Outline Generator")
|
||||
st.markdown("""
|
||||
Generate comprehensive blog outlines with AI-powered content and images.
|
||||
Customize your outline with various options and get detailed content for each section.
|
||||
""")
|
||||
|
||||
# Sidebar for configuration
|
||||
with st.sidebar:
|
||||
st.header("Configuration")
|
||||
|
||||
# Basic settings
|
||||
topic = st.text_input("Blog Topic", placeholder="Enter your blog topic")
|
||||
content_type = st.selectbox(
|
||||
"Content Type",
|
||||
[type.value for type in ContentType]
|
||||
)
|
||||
content_depth = st.selectbox(
|
||||
"Content Depth",
|
||||
[depth.value for depth in ContentDepth]
|
||||
)
|
||||
outline_style = st.selectbox(
|
||||
"Outline Style",
|
||||
[style.value for style in OutlineStyle]
|
||||
)
|
||||
|
||||
# Content structure
|
||||
st.subheader("Content Structure")
|
||||
target_word_count = st.slider("Target Word Count", 500, 5000, 2000, 100)
|
||||
num_main_sections = st.slider("Number of Main Sections", 3, 10, 5)
|
||||
num_subsections = st.slider("Subsections per Section", 2, 5, 3)
|
||||
|
||||
# Advanced settings
|
||||
with st.expander("Advanced Settings"):
|
||||
include_intro = st.checkbox("Include Introduction", value=True)
|
||||
include_conclusion = st.checkbox("Include Conclusion", value=True)
|
||||
include_faqs = st.checkbox("Include FAQs", value=True)
|
||||
include_resources = st.checkbox("Include Resources", value=True)
|
||||
|
||||
# Image settings
|
||||
st.subheader("Image Settings")
|
||||
include_images = st.checkbox("Include Images", value=True)
|
||||
if include_images:
|
||||
image_style = st.selectbox(
|
||||
"Image Style",
|
||||
["realistic", "illustration", "minimalist", "photographic", "artistic"]
|
||||
)
|
||||
image_engine = st.selectbox(
|
||||
"Image Engine",
|
||||
["Gemini-AI", "Dalle3", "Stability-AI"]
|
||||
)
|
||||
|
||||
# Target audience and language
|
||||
st.subheader("Target Audience")
|
||||
target_audience = st.text_input("Target Audience", value="general")
|
||||
language = st.text_input("Language", value="English")
|
||||
|
||||
# Keywords and exclusions
|
||||
st.subheader("Content Optimization")
|
||||
keywords = st.text_area("Keywords (comma-separated)")
|
||||
exclude_topics = st.text_area("Topics to Exclude (comma-separated)")
|
||||
|
||||
# Main content area
|
||||
if topic:
|
||||
# Create configuration
|
||||
config = OutlineConfig(
|
||||
content_type=ContentType(content_type),
|
||||
content_depth=ContentDepth(content_depth),
|
||||
outline_style=OutlineStyle(outline_style),
|
||||
target_word_count=target_word_count,
|
||||
num_main_sections=num_main_sections,
|
||||
num_subsections_per_section=num_subsections,
|
||||
include_introduction=include_intro,
|
||||
include_conclusion=include_conclusion,
|
||||
include_faqs=include_faqs,
|
||||
include_resources=include_resources,
|
||||
include_images=include_images,
|
||||
image_style=image_style if include_images else "realistic",
|
||||
image_engine=image_engine if include_images else "Gemini-AI",
|
||||
target_audience=target_audience,
|
||||
language=language,
|
||||
keywords=[k.strip() for k in keywords.split(',')] if keywords else None,
|
||||
exclude_topics=[t.strip() for t in exclude_topics.split(',')] if exclude_topics else None
|
||||
)
|
||||
|
||||
# Initialize generator
|
||||
generator = BlogOutlineGenerator(config)
|
||||
|
||||
# Generate outline
|
||||
if st.button("Generate Outline"):
|
||||
with st.spinner("Generating outline and content..."):
|
||||
try:
|
||||
# Add progress bar
|
||||
progress_bar = st.progress(0)
|
||||
for i in range(100):
|
||||
time.sleep(0.01)
|
||||
progress_bar.progress(i + 1)
|
||||
|
||||
outline = asyncio.run(generator.generate_outline(topic))
|
||||
|
||||
# Display results
|
||||
st.success("Outline generated successfully!")
|
||||
|
||||
# Display statistics
|
||||
display_stats(generator, outline)
|
||||
|
||||
# Output format selection
|
||||
output_format = st.radio(
|
||||
"Output Format",
|
||||
["Preview", "Markdown", "JSON", "HTML"]
|
||||
)
|
||||
|
||||
if output_format == "Preview":
|
||||
# Display outline with content and images
|
||||
for section, subsections in outline.items():
|
||||
content = generator.section_contents.get(section)
|
||||
display_section(section, subsections, content)
|
||||
|
||||
elif output_format == "Markdown":
|
||||
st.code(generator.to_markdown(), language="markdown")
|
||||
st.download_button(
|
||||
"Download Markdown",
|
||||
generator.to_markdown(),
|
||||
file_name="blog_outline.md",
|
||||
mime="text/markdown"
|
||||
)
|
||||
|
||||
elif output_format == "JSON":
|
||||
json_output = json.dumps({
|
||||
"outline": outline,
|
||||
"contents": {
|
||||
section: {
|
||||
"title": content.title,
|
||||
"content": content.content,
|
||||
"image_prompt": content.image_prompt,
|
||||
"image_path": content.image_path
|
||||
}
|
||||
for section, content in generator.section_contents.items()
|
||||
}
|
||||
}, indent=2)
|
||||
st.code(json_output, language="json")
|
||||
st.download_button(
|
||||
"Download JSON",
|
||||
json_output,
|
||||
file_name="blog_outline.json",
|
||||
mime="application/json"
|
||||
)
|
||||
|
||||
elif output_format == "HTML":
|
||||
# Add HTML export functionality
|
||||
html_output = f"""
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>{topic} - Blog Outline</title>
|
||||
<style>
|
||||
body {{ font-family: Arial, sans-serif; max-width: 800px; margin: 0 auto; padding: 20px; }}
|
||||
.section {{ margin-bottom: 30px; }}
|
||||
.content {{ background: #f8f9fa; padding: 15px; border-radius: 4px; }}
|
||||
img {{ max-width: 100%; height: auto; }}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<h1>{topic}</h1>
|
||||
{generator.to_markdown().replace('#', '##')}
|
||||
</body>
|
||||
</html>
|
||||
"""
|
||||
st.code(html_output, language="html")
|
||||
st.download_button(
|
||||
"Download HTML",
|
||||
html_output,
|
||||
file_name="blog_outline.html",
|
||||
mime="text/html"
|
||||
)
|
||||
|
||||
except Exception as e:
|
||||
st.error(f"Error generating outline: {str(e)}")
|
||||
else:
|
||||
st.info("Please enter a blog topic to get started.")
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -8,6 +8,7 @@ from lib.ai_writers.linkedin_writer import LinkedInAIWriter
|
||||
from lib.ai_writers.blog_rewriter_updater.ai_blog_rewriter import write_blog_rewriter
|
||||
from lib.ai_writers.ai_blog_faqs_writer.faqs_ui import main as faqs_generator
|
||||
from lib.ai_writers.ai_blog_writer.ai_blog_generator import ai_blog_writer_page
|
||||
from lib.ai_writers.ai_outline_writer.outline_ui import main as outline_generator
|
||||
from loguru import logger
|
||||
|
||||
def list_ai_writers():
|
||||
@@ -92,6 +93,14 @@ def list_ai_writers():
|
||||
"category": "Content Creation",
|
||||
"function": faqs_generator,
|
||||
"path": "faqs_generator"
|
||||
},
|
||||
{
|
||||
"name": "Blog Outline Generator",
|
||||
"icon": "📋",
|
||||
"description": "Create detailed blog outlines with AI-powered content generation and image integration",
|
||||
"category": "Content Creation",
|
||||
"function": outline_generator,
|
||||
"path": "outline_generator"
|
||||
}
|
||||
]
|
||||
|
||||
|
||||
Reference in New Issue
Block a user