EmailAssist / src /streamlit_app.py
Dirk Haupt
start with dear commissioners
e63a72d
import os
import random
import json
import openai
import streamlit as st
from dotenv import load_dotenv
from st_copy import copy_button
from cryptography.fernet import Fernet
# Load environment variables from .env file
load_dotenv()
# Set page config - MUST BE THE FIRST STREAMLIT COMMAND
st.set_page_config(
page_title="Petition Email Generator",
page_icon="πŸ“§",
layout="wide",
)
# Get the password token from environment variables
PASSWORD_TOKEN = os.environ.get("PASSWORD_TOKEN")
# Function to load encrypted email examples
def load_email_examples():
try:
# Get the encryption key from environment variables
encryption_key = os.environ.get("ENCRYPTION_KEY")
if not encryption_key:
st.warning("Encryption key not found in environment variables.")
return None
# Simple path to the encrypted file
file_path = "examples/encrypted_examples.bin"
# Check if the file exists
if not os.path.exists(file_path):
st.warning(f"Encrypted examples file not found at {file_path}")
return None
# Read the encrypted file
with open(file_path, "rb") as file:
encrypted_data = file.read()
# Decrypt the data
fernet = Fernet(encryption_key)
decrypted_data = fernet.decrypt(encrypted_data).decode()
# Parse the JSON data
examples_data = json.loads(decrypted_data)
return examples_data
except Exception as e:
st.error(f"Error loading examples: {str(e)}")
return None
# Initialize authentication state
if 'is_authenticated' not in st.session_state:
st.session_state.is_authenticated = False
# If not authenticated, show login form
if not st.session_state.is_authenticated:
st.title("Petition Email Generator")
st.subheader("Authentication Required")
with st.form("auth_form"):
password = st.text_input("Enter password", type="password")
submitted = st.form_submit_button("Login")
if submitted:
if password == PASSWORD_TOKEN:
st.session_state.is_authenticated = True
st.rerun() # Refresh the page after successful login
else:
st.error("Incorrect password")
else:
# Title and description
st.title("Petition Email Generator")
st.markdown("""
This app helps you create a personalized email to support a Petition.
Follow the steps below to generate your unique email.
""")
# Tips section moved from footer to top
st.markdown("""
### Tips for Effective Emails
- Be polite and respectful
- Clearly state your support for the petition
- Share personal experiences if relevant
- Thank the recipient for their time and consideration
""")
st.markdown("---")
# Initialize session state for storing the generated email
if 'generated_email' not in st.session_state:
st.session_state.generated_email = ""
if 'example_generated' not in st.session_state:
st.session_state.example_generated = False
if 'generated_subject' not in st.session_state:
st.session_state.generated_subject = ""
# Only access environment variables if authenticated
if st.session_state.is_authenticated:
# Set API key from environment variable
openai.api_key = os.environ.get("OPENAI_API_KEY")
# Load email examples
email_examples = load_email_examples()
# Fallback to environment variable if encryption doesn't work
example_email = os.environ.get("EXAMPLE_EMAIL")
# Get recipient email addresses
to_emails = os.environ.get("TO_EMAILS", "")
else:
# If not authenticated, don't set API key
openai.api_key = None
email_examples = None
example_email = "Authentication required to access the example email template."
to_emails = ""
# Function to generate email with subject (only works when authenticated)
def generate_email(name, location, feedback="", model=os.environ.get("MODEL", "gpt-4.1-nano")):
# Check authentication
if not st.session_state.is_authenticated:
st.error("Authentication required. Please use the correct password in the URL.")
return None, None
try:
# If we have loaded examples, use them for a more varied prompt
if email_examples:
# Either pick a random full example email
if random.random() < 0.3: # 30% chance to use a complete example
selected_example = random.choice(email_examples["example_emails"])
example_body = selected_example["full_email"]
# Or compose a new one from modular blocks
else:
blocks = email_examples["modular_blocks"]
selected_blocks = {
category: random.choice(blocks[category])
for category in blocks.keys()
}
# Build a coherent email from the selected blocks
example_body = "\n\n".join([
selected_blocks["intro"],
selected_blocks["concern"],
selected_blocks["argument"],
selected_blocks["support"],
selected_blocks["closing"]
])
# Use the constructed example
base_example = example_body
else:
# Fallback to the environment variable example
base_example = example_email
print("BASE_EXAMPLE:")
print(base_example)
prompt = f"""
Generate a personalized and unique email supporting the Petition based on this example:
ALWAYS start the email with "Dear Commissioners"
{base_example}
Customize it for:
- Name of sender: {name}
- Location of sender: {location}
Additional preferences:
{feedback if feedback else "None provided."}
Return BOTH:
1. A concise, compelling subject line (one line only)
2. The email body text
Format your response exactly as follows:
SUBJECT: [Your generated subject line here]
[Your generated email body here]
Make sure the email is unique, professional, and persuasive.
"""
response = openai.chat.completions.create(
model=model,
messages=[
{"role": "system", "content": "You are a helpful assistant that creates personalized emails."},
{"role": "user", "content": prompt}
],
temperature=0.7,
max_tokens=800
)
content = response.choices[0].message.content
# Extract subject and body from the response
if "SUBJECT:" in content:
parts = content.split("\n\n", 1)
subject = parts[0].replace("SUBJECT:", "").strip()
body = parts[1] if len(parts) > 1 else ""
return subject, body
else:
# Fallback if format wasn't followed
return "Support for the Petition", content
except Exception as e:
st.error(f"Error generating email: {str(e)}")
return None, None
# Main content with two columns
col1, col2 = st.columns([1, 1])
with col1:
st.subheader("πŸ“ Steps to Generate Your Email")
# Step 1: Enter Your Information
info_step = st.expander("Step 1: Enter Your Information", expanded=True)
with info_step:
user_name = st.text_input("Your Name")
location = st.text_input("Your City or County")
if user_name and location:
st.success("Information provided! βœ…")
# Step 2: Additional Preferences (Optional)
pref_step = st.expander("Step 2: Customize Your Email (Optional)", expanded=True)
with pref_step:
additional_feedback = st.text_area(
"Additional details or preferences for your email",
placeholder="E.g., I'd like to emphasize economic benefits, include personal stories, etc."
)
if additional_feedback:
st.success("Customization added! βœ…")
# Step 3: Generate Email
generate_step = st.expander("Step 3: Generate Your Email", expanded=True)
with generate_step:
# Disable button if not authenticated or missing required fields
generate_button = st.button(
"Generate Email",
type="primary",
disabled=(not st.session_state.is_authenticated) or (not user_name or not location)
)
if not st.session_state.is_authenticated:
st.warning("Authentication required to generate emails.")
elif not user_name or not location:
st.warning("Please complete Step 1 (Your Information) first.")
with col2:
st.subheader("πŸ“¨ Your Personalized Email")
# Only attempt to pre-generate example if authenticated
if st.session_state.is_authenticated and not st.session_state.example_generated and not generate_button:
with st.spinner("Generating an example email..."):
example_subject, example_body = generate_email("Jane Smith", "Springfield County")
if example_body:
st.session_state.generated_subject = example_subject
st.session_state.generated_email = example_body
st.session_state.example_generated = True
st.info("Here's an example of a generated email. Customize it by entering your information and preferences in the steps on the left.")
elif not st.session_state.is_authenticated:
st.info("Authentication required to generate example emails.")
# Handle manual generation
if generate_button and user_name and location:
with st.spinner("Generating your personalized email..."):
generated_subject, generated_body = generate_email(user_name, location, additional_feedback)
if generated_body:
st.session_state.generated_subject = generated_subject
st.session_state.generated_email = generated_body
st.success("Your personalized email has been generated! βœ…")
# Display recipient email addresses if available
if to_emails:
st.subheader("πŸ“§ Send your email to:")
email_col, copy_email_col = st.columns([9, 1])
with email_col:
st.code(to_emails, language=None)
with copy_email_col:
st.markdown('<div style="display: flex; align-items: center; height: 100%;">', unsafe_allow_html=True)
copy_button(to_emails)
st.markdown('</div>', unsafe_allow_html=True)
# Display the subject line (if available)
if st.session_state.generated_subject:
subject_container = st.container()
with subject_container:
st.subheader("Subject Line:")
subject_col, copy_subject_col = st.columns([9, 1])
with subject_col:
subject_text = st.text_input(
"",
value=st.session_state.generated_subject,
label_visibility="collapsed"
)
with copy_subject_col:
copy_button(subject_text)
# Display the email body (either pre-generated or manually generated)
email_container = st.container()
with email_container:
st.subheader("Email Body:")
body_col, copy_body_col = st.columns([9, 1])
with body_col:
email_text = st.text_area(
"",
value=st.session_state.generated_email,
height=350,
label_visibility="collapsed"
)
with copy_body_col:
copy_button(st.session_state.generated_email)