import streamlit as st
from graph import EssayWriter, RouteQuery, GraphState
from crew import *
import os
import traceback
import base64
# Install Graphviz if not found
if os.system("which dot") != 0:
os.system("apt-get update && apt-get install -y graphviz")
st.markdown(
"""
Multi-Agent Essay Writing Assistant
""",
unsafe_allow_html=True
)
# Ensure session state variables are initialized properly
if "messages" not in st.session_state:
st.session_state["messages"] = [{"role": "assistant", "content": "Hello! How can I assist you today?"}]
if "app" not in st.session_state:
st.session_state["app"] = None
if "chat_active" not in st.session_state:
st.session_state["chat_active"] = True
# Sidebar with essay settings and user-defined length
# Sidebar with essay settings and user-defined length
with st.sidebar:
st.subheader("About:")
st.info(
"\n\n 1. This app uses the 'gpt-4o-mini-2024-07-18' model."
"\n\n 2. Writing essays may take some time, approximately 1-2 minutes."
)
# API Key Retrieval
openai_key = st.secrets.get("OPENAI_API_KEY", "")
st.divider()
# User-defined essay length selection
st.subheader("📝 Configure Essay Settings:")
essay_length = st.number_input(
"Select Essay Length (words):",
min_value=150,
max_value=500,
value=250,
step=50
)
#st.divider()
#Language Selection
#st.subheader("🌍 Select Language:")
language_options = {
"Arabic (العربية)": "Arabic",
"Bengali (বাংলা)": "Bengali",
"Bulgarian (Български)": "Bulgarian",
"Chinese (中文, Simplified)": "Chinese (Simplified)",
"Chinese (繁體, Traditional)": "Chinese (Traditional)",
"Croatian (Hrvatski)": "Croatian",
"Czech (Čeština)": "Czech",
"Danish (Dansk)": "Danish",
"Dutch (Nederlands)": "Dutch",
"English": "English",
"Filipino (Tagalog)": "Filipino",
"Finnish (Suomi)": "Finnish",
"French (Français)": "French",
"German (Deutsch)": "German",
"Greek (Ελληνικά)": "Greek",
"Gujarati (ગુજરાતી)": "Gujarati",
"Hebrew (עברית)": "Hebrew",
"Hindi (हिन्दी)": "Hindi",
"Hungarian (Magyar)": "Hungarian",
"Indonesian (Bahasa Indonesia)": "Indonesian",
"Italian (Italiano)": "Italian",
"Japanese (日本語)": "Japanese",
"Korean (한국어)": "Korean",
"Malay (Bahasa Melayu)": "Malay",
"Malayalam (മലയാളം)": "Malayalam",
"Marathi (मराठी)": "Marathi",
"Nepali (नेपाली)": "Nepali",
"Norwegian (Norsk)": "Norwegian",
"Persian (فارسی)": "Persian",
"Polish (Polski)": "Polish",
"Portuguese (Português)": "Portuguese",
"Punjabi (ਪੰਜਾਬੀ)": "Punjabi",
"Romanian (Română)": "Romanian",
"Russian (Русский)": "Russian",
"Serbian (Српски)": "Serbian",
"Sinhala (සිංහල)": "Sinhala",
"Slovak (Slovenčina)": "Slovak",
"Spanish (Español)": "Spanish",
"Swahili (Kiswahili)": "Swahili",
"Swedish (Svenska)": "Swedish",
"Tamil (தமிழ்)": "Tamil",
"Telugu (తెలుగు)": "Telugu",
"Thai (ไทย)": "Thai",
"Turkish (Türkçe)": "Turkish",
"Ukrainian (Українська)": "Ukrainian",
"Urdu (اردو)": "Urdu",
"Vietnamese (Tiếng Việt)": "Vietnamese"
}
selected_language = st.selectbox("Choose Language:", sorted(language_options.keys()), index=list(language_options.keys()).index("English"))
st.divider()
# Reference section
st.subheader("📖 References:")
st.markdown(
"[1. Multi-Agent System with CrewAI and LangChain](https://discuss.streamlit.io/t/new-project-i-have-build-a-multi-agent-system-with-crewai-and-langchain/84002)",
unsafe_allow_html=True
)
# Initialize agents function
def initialize_agents():
if not openai_key:
st.error("⚠️ OpenAI API key is missing! Please provide a valid key through Hugging Face Secrets.")
st.session_state["chat_active"] = True
return None
os.environ["OPENAI_API_KEY"] = openai_key
try:
# Prevent re-initialization
if "app" in st.session_state and st.session_state["app"] is not None:
return st.session_state["app"]
# Initialize the full EssayWriter instance
essay_writer = EssayWriter() # Store the full instance
st.session_state["app"] = essay_writer # Now contains `graph`
st.session_state["chat_active"] = False # Enable chat after successful initialization
return essay_writer
except Exception as e:
st.error(f"❌ Error initializing agents: {e}")
st.session_state["chat_active"] = True
return None
# Automatically initialize agents on app load
if st.session_state["app"] is None:
st.session_state["app"] = initialize_agents()
if st.session_state["app"] is None:
st.error("⚠️ Failed to initialize agents. Please check your API key and restart the app.")
app = st.session_state["app"]
# Function to invoke the agent and generate a response
def generate_response(topic, length, selected_language):
if not app or not hasattr(app, "graph"):
st.error("⚠️ Agents are not initialized. Please check the system or restart the app.")
return {"response": "Error: Agents not initialized."}
# Define section allocations dynamically based on length
if length <= 300:
intro_limit = length // 5 # Shorter intro (~20% of total)
body_limit = length // 2 # 2-3 key sections only (~50% of total)
conclusion_limit = length // 5 # Brief closing (~20% of total)
num_sections = 2 # Fewer key points
elif length <= 400:
intro_limit = length // 6 # Slightly shorter intro (~17% of total)
body_limit = length // 1.8 # Allows more depth (~55% of total)
conclusion_limit = length // 6 # Balanced closing (~17% of total)
num_sections = 3 # More sections
else:
intro_limit = length // 7 # Even shorter intro (~15% of total)
body_limit = length // 1.7 # More depth in body (~60% of total)
conclusion_limit = length // 7 # Shorter but strong closing (~15% of total)
num_sections = 4 # Maximum sections for deeper discussion
# Adjusted structured prompt with language enforcement
refined_prompt = f"""
Write a well-structured, engaging, and informative essay on "{topic}" in **{selected_language}** with **EXACTLY {length} words**.
### **Structure:**
- **Title**: A compelling, creative title (max 10 words).
- **Introduction** ({intro_limit} words max):
- Define the topic & its importance concisely.
- Provide a strong thesis statement.
- Briefly mention key themes.
- **Main Body** ({body_limit} words max):
- Cover **{num_sections} key aspects only**.
- Each section must have:
- A clear **subheading**.
- A **topic sentence** & supporting details.
- **Examples, statistics, or historical references** (if relevant).
- Ensure logical transitions between sections.
- **Conclusion** ({conclusion_limit} words max):
- Summarize key insights concisely.
- Reinforce thesis based on discussion.
- End with a **thought-provoking** final statement (question, reflection, or call to action).
### **Important Rules:**
- 🚫 **DO NOT exceed {length} words**.
- ✅ **Avoid redundancy & filler sentences**.
- 🔄 **Merge similar ideas** to keep flow natural.
- ✂ **Use precise, impactful language**.
- 🎯 **Ensure balanced coverage** (not too broad or too specific).
- **STOP when {length} words are reached**.
- Write in **{selected_language}** ONLY.
"""
# Invoke AI model with controlled word limit
response = app.graph.invoke(input={
"topic": topic,
"length": length,
"prompt": refined_prompt,
"max_tokens": length * 1.2 # Ensures generation doesn’t exceed limit
})
return response
# Define Tabs
tab1, tab2 = st.tabs(["📜 Essay Generation", "📊 Workflow Viz"])
# 📜 Tab 1: Essay Generation
with tab1:
# Display chat messages from the session
if "messages" not in st.session_state:
st.session_state["messages"] = [{"role": "assistant", "content": "Hello! How can I assist you today?"}]
for message in st.session_state["messages"]:
with st.chat_message(message["role"]):
st.markdown(message["content"], unsafe_allow_html=True)
# Input
topic = st.text_input("📝 Provide an essay topic:", value="Write an essay on the cultural diversity of India")
# Add spacing
st.write("")
# Generate button
if st.button("🚀 Generate Essay"):
if topic and topic.strip(): # Ensure it's not empty
# Store user message only if it's not already stored
if not any(msg["content"] == topic for msg in st.session_state["messages"]):
st.session_state["messages"].append({"role": "user", "content": topic})
with st.spinner("⏳ Generating your essay..."):
response = None
if app:
response = app.write_essay({"topic": topic})
else:
st.error("⚠️ Agents are not initialized. Please check the system or restart the app.")
# Store and display assistant response
if response and "essay" in response:
essay = response["essay"]
assistant_response = f"Here is your {essay_length}-word essay preview and the download link."
st.session_state["messages"].append({"role": "assistant", "content": assistant_response})
st.chat_message("assistant").markdown(assistant_response)
# Create Two-Column Layout
col1, col2 = st.columns(2)
with col1:
st.markdown(f"### 📝 Essay Preview ({essay_length} words)")
st.markdown(f"#### {essay['header']}")
st.markdown(essay["entry"])
for para in essay["paragraphs"]:
st.markdown(f"**{para['sub_header']}**")
st.markdown(para["paragraph"])
st.markdown("**🖊️ Conclusion:**")
st.markdown(essay["conclusion"])
with col2:
st.markdown("### ✍️ Edit Your Essay:")
# Combine all parts of the essay into one editable text field
full_essay_text = f"## {essay['header']}\n\n{essay['entry']}\n\n"
for para in essay["paragraphs"]:
full_essay_text += f"### {para['sub_header']}\n{para['paragraph']}\n\n"
full_essay_text += f"**Conclusion:**\n{essay['conclusion']}"
# Editable text area for the user
edited_essay = st.text_area("Edit Here:", value=full_essay_text, height=300)
# Save and Download buttons
save_col1, save_col2 = st.columns(2)
with save_col1:
if st.button("💾 Save as TXT"):
with open("edited_essay.txt", "w", encoding="utf-8") as file:
file.write(edited_essay)
with open("edited_essay.txt", "rb") as file:
st.download_button(label="⬇️ Download TXT", data=file, file_name="edited_essay.txt", mime="text/plain")
with save_col2:
if st.button("📄 Save as PDF"):
from fpdf import FPDF
pdf = FPDF()
pdf.set_auto_page_break(auto=True, margin=15)
pdf.add_page()
pdf.set_font("Arial", size=12)
for line in edited_essay.split("\n"):
pdf.cell(200, 10, txt=line, ln=True, align='L')
pdf.output("edited_essay.pdf")
with open("edited_essay.pdf", "rb") as file:
st.download_button(label="⬇️ Download PDF", data=file, file_name="edited_essay.pdf", mime="application/pdf")
# Provide download link for the original PDF
pdf_name = response.get("pdf_name")
if pdf_name and os.path.exists(pdf_name):
with open(pdf_name, "rb") as pdf_file:
b64 = base64.b64encode(pdf_file.read()).decode()
href = f"📄 Click here to download the original PDF"
st.markdown(href, unsafe_allow_html=True)
# Save response in session state
st.session_state["messages"].append(
{"role": "assistant", "content": f"Here is your {essay_length}-word essay preview and the download link."}
)
elif response:
st.markdown(response["response"])
st.session_state["messages"].append({"role": "assistant", "content": response["response"]})
else:
st.error("⚠️ No response received. Please try again.")
# 📊 Tab 2: Workflow Visualization
with tab2:
#st.subheader("📊 Multi-Agent Essay Writer Workflow Viz")
try:
graph_path = "/tmp/graph.png"
if os.path.exists(graph_path):
st.image(graph_path, caption="Multi-Agent Essay Writer Workflow Visualization", use_container_width=True)
else:
st.warning("⚠️ Workflow graph not found. Please run `graph.py` to regenerate `graph.png`.")
except Exception as e:
st.error("❌ An error occurred while generating the workflow visualization.")
st.text_area("Error Details:", traceback.format_exc(), height=500)
# Acknowledgement Section
st.markdown(
"""
""",
unsafe_allow_html=True,
)