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( """
Acknowledgement: This app is based on Mesut Duman's work: CrewAI Essay Writer
""", unsafe_allow_html=True, )