""" Streamlit app for AI Project Assistant. """ import streamlit as st from pathlib import Path import os from datetime import datetime from dotenv import load_dotenv from src.rag import ProjectRAG from src.agent import ProjectAgent from src.parsers import load_meetings_from_directory from langchain_huggingface import ChatHuggingFace, HuggingFaceEndpoint from langchain_core.messages import SystemMessage, HumanMessage # Load environment variables load_dotenv() # Page config st.set_page_config( page_title="AI Project Assistant", page_icon="🤖", layout="wide" ) # Custom CSS st.markdown(""" """, unsafe_allow_html=True) # Initialize session state if 'rag' not in st.session_state: st.session_state.rag = None if 'agent' not in st.session_state: st.session_state.agent = None if 'messages' not in st.session_state: st.session_state.messages = [] if 'initialized' not in st.session_state: st.session_state.initialized = False def initialize_system(): """Initialize RAG and Agent systems.""" data_dir = Path("./data") if not data_dir.exists(): data_dir.mkdir(parents=True) st.warning("Created data directory. Please add your meeting notes to 'data/project_name/meetings/'") return False with st.spinner("Loading and indexing meetings..."): st.session_state.rag = ProjectRAG(data_dir) st.session_state.rag.load_and_index() if not st.session_state.rag.meetings: return False st.session_state.agent = ProjectAgent(st.session_state.rag) st.session_state.initialized = True return True def main(): """Main app function.""" # Header st.markdown('
🤖 AI Project Assistant
', unsafe_allow_html=True) st.markdown("Your intelligent assistant for managing multiple projects through meeting summaries") # Sidebar with st.sidebar: st.header("⚙️ Settings") # Project filter if st.session_state.rag and st.session_state.initialized: projects = st.session_state.rag.get_all_projects() selected_project = st.selectbox( "Select Project", options=["All Projects"] + projects, key="selected_project" ) st.session_state.project_filter = None if selected_project == "All Projects" else selected_project if st.button("🔄 Reload Meetings", use_container_width=True): st.session_state.initialized = False st.rerun() st.divider() st.header("📊 Quick Stats") if st.session_state.rag and st.session_state.initialized: current_filter = st.session_state.get("project_filter") if current_filter: st.info(f"Showing: **{current_filter}**") action_items = st.session_state.rag.get_open_action_items(project=current_filter) blockers = st.session_state.rag.get_blockers(project=current_filter) else: projects = st.session_state.rag.get_all_projects() st.metric("Total Projects", len(projects)) action_items = st.session_state.rag.get_open_action_items() blockers = st.session_state.rag.get_blockers() st.metric("Total Meetings", len(st.session_state.rag.meetings)) st.metric("Open Action Items", len(action_items)) st.metric("Current Blockers", len(blockers)) st.divider() st.header("💡 Example Queries") st.markdown(""" - What are the open action items? - What blockers do we have? - What decisions were made? - What should I focus on next? - Summarize the project status """) # Check HF Token (auto-provided on Spaces, optional locally) if not os.getenv("HF_TOKEN"): st.warning("⚠️ HF_TOKEN not found. Running with limited functionality.") st.info("On Spaces: Token is automatically provided") st.info("Locally: Set HF_TOKEN in .env file (optional for free API)") # Initialize system if not st.session_state.initialized: if not initialize_system(): st.warning("No meetings found. Add your meeting notes to get started!") with st.expander("📝 How to add meetings"): st.markdown(""" 1. Create a folder structure: `data/your_project_name/meetings/` 2. Add markdown files with your meeting notes 3. Use this format: ```markdown # Meeting: Project Kickoff Date: 2025-01-15 Participants: Alice, Bob ## Discussion Your meeting notes here ## Decisions - Decision 1 - Decision 2 ## Action Items - [ ] Alice: Task 1 by Jan 20 - [x] Bob: Task 2 (completed) ## Blockers - Waiting for approval ``` """) return st.success(f"Loaded {len(st.session_state.rag.meetings)} meetings!") # Main content tabs - ONLY 2 TABS tab1, tab2 = st.tabs(["💬 Chat", "📤 Upload Meeting"]) with tab1: st.header("💬 Ask Questions About Your Projects") # Project selection BEFORE chat if st.session_state.rag and st.session_state.initialized: projects = st.session_state.rag.get_all_projects() st.markdown("### Select a Project to Chat About") # Create columns for project buttons cols = st.columns(len(projects) + 1) # "All Projects" button with cols[0]: if st.button("🌐 All Projects", use_container_width=True, type="secondary"): st.session_state.selected_chat_project = None st.rerun() # Individual project buttons for i, project in enumerate(projects, 1): with cols[i]: if st.button(f"📁 {project}", use_container_width=True, type="primary"): st.session_state.selected_chat_project = project st.rerun() st.divider() # Show selected project selected_project = st.session_state.get("selected_chat_project") if selected_project: st.success(f"💬 Chatting about: **{selected_project}**") else: st.info("💬 Chatting about: **All Projects**") # Only show chat if a selection has been made if "selected_chat_project" in st.session_state: # Display chat messages for message in st.session_state.messages: with st.chat_message(message["role"]): st.markdown(message["content"]) else: st.warning("👆 Please select a project above to start chatting") return # Chat input (must be outside tabs/columns/expanders) - only show if project selected if tab1 and "selected_chat_project" in st.session_state: prompt = st.chat_input("What would you like to know about your projects?") # Process chat input if prompt: # Add user message st.session_state.messages.append({"role": "user", "content": prompt}) with st.chat_message("user"): st.markdown(prompt) # Get agent response with project filter with st.chat_message("assistant"): with st.spinner("Thinking..."): # Add project context to query if specific project selected selected_project = st.session_state.get("selected_chat_project") if selected_project: enhanced_prompt = f"[Project: {selected_project}] {prompt}" else: enhanced_prompt = prompt response = st.session_state.agent.query(enhanced_prompt) st.markdown(response) st.session_state.messages.append({"role": "assistant", "content": response}) st.rerun() with tab2: st.header("📤 Upload Meeting Notes") st.markdown("Upload plain text meeting notes and let AI structure them for you!") col1, col2 = st.columns(2) with col1: project_name = st.text_input("Project Name", placeholder="e.g., mobile_app_redesign") meeting_date = st.date_input("Meeting Date", value=datetime.now()) meeting_title = st.text_input("Meeting Title", placeholder="e.g., Sprint Planning") with col2: participants = st.text_input("Participants (comma-separated)", placeholder="e.g., Alice, Bob, Charlie") st.markdown("### Paste or Upload Meeting Notes") # Option 1: Text area meeting_text = st.text_area( "Paste your meeting notes here (plain text)", height=300, placeholder="""Example: We discussed the new feature requirements. Alice will implement the login page by next Friday. Bob raised a concern about the database migration. We decided to use PostgreSQL instead of MySQL. Charlie is blocked waiting for API credentials. """ ) # Option 2: File upload uploaded_file = st.file_uploader("Or upload a text file", type=['txt', 'md']) if uploaded_file is not None: meeting_text = uploaded_file.read().decode('utf-8') st.info(f"Loaded {len(meeting_text)} characters from file") if st.button("🤖 Structure Meeting with AI", type="primary", disabled=not meeting_text or not project_name): with st.spinner("AI is structuring your meeting notes..."): try: # Use HF Inference API to structure the meeting endpoint = HuggingFaceEndpoint( repo_id="meta-llama/Llama-3.2-3B-Instruct", temperature=0.3, max_new_tokens=1024, huggingfacehub_api_token=os.getenv("HF_TOKEN") ) llm = ChatHuggingFace(llm=endpoint) system_prompt = """You are a meeting notes structuring assistant. Convert unstructured meeting notes into a well-formatted markdown document with these sections: 1. # Meeting: [title] 2. Date: [date] 3. Participants: [list] 4. ## Discussion (key points discussed) 5. ## Decisions (decisions made) 6. ## Action Items (as checkboxes with assignee and deadline if mentioned) 7. ## Blockers (any blockers or issues raised) Format action items as: - [ ] Person: Task description by deadline or - [ ] Task description (if no person/deadline mentioned) Extract all relevant information from the raw notes.""" user_prompt = f"""Structure these meeting notes: Raw Notes: {meeting_text} Meeting Details: - Title: {meeting_title or 'Meeting'} - Date: {meeting_date} - Participants: {participants or 'Not specified'} """ messages = [ SystemMessage(content=system_prompt), HumanMessage(content=user_prompt) ] response = llm.invoke(messages) structured_md = response.content # Display preview st.success("✅ Meeting structured successfully!") st.markdown("### Preview") st.markdown(structured_md) # Save option st.markdown("### Save Meeting") save_col1, save_col2 = st.columns([3, 1]) with save_col1: filename = st.text_input( "Filename", value=f"{meeting_date.strftime('%Y-%m-%d')}-{meeting_title.lower().replace(' ', '-') if meeting_title else 'meeting'}.md" ) with save_col2: st.markdown("
", unsafe_allow_html=True) if st.button("💾 Save to Project"): # Create project directory if needed project_dir = Path("data") / project_name / "meetings" project_dir.mkdir(parents=True, exist_ok=True) # Save file file_path = project_dir / filename with open(file_path, 'w') as f: f.write(structured_md) st.success(f"✅ Saved to `{file_path}`") st.info("💡 Refresh the page to reload meetings into the RAG system") # Download option st.download_button( label="📥 Download Markdown", data=structured_md, file_name=filename, mime="text/markdown" ) except Exception as e: st.error(f"Error: {str(e)}") if "quota" in str(e).lower() or "rate" in str(e).lower(): st.warning("⚠️ API rate limit reached. Please wait a moment and try again.") if __name__ == "__main__": main()