|
|
""" |
|
|
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_dotenv() |
|
|
|
|
|
|
|
|
st.set_page_config( |
|
|
page_title="AI Project Assistant", |
|
|
page_icon="🤖", |
|
|
layout="wide" |
|
|
) |
|
|
|
|
|
|
|
|
st.markdown(""" |
|
|
<style> |
|
|
.main-header { |
|
|
font-size: 2.5rem; |
|
|
font-weight: bold; |
|
|
margin-bottom: 1rem; |
|
|
} |
|
|
.project-card { |
|
|
padding: 1rem; |
|
|
border-radius: 0.5rem; |
|
|
background-color: #f0f2f6; |
|
|
margin: 0.5rem 0; |
|
|
} |
|
|
.action-item { |
|
|
padding: 0.5rem; |
|
|
margin: 0.25rem 0; |
|
|
border-left: 3px solid #1f77b4; |
|
|
background-color: #e8f4f8; |
|
|
} |
|
|
.blocker { |
|
|
padding: 0.5rem; |
|
|
margin: 0.25rem 0; |
|
|
border-left: 3px solid #d62728; |
|
|
background-color: #ffe8e8; |
|
|
} |
|
|
</style> |
|
|
""", unsafe_allow_html=True) |
|
|
|
|
|
|
|
|
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.""" |
|
|
|
|
|
|
|
|
st.markdown('<div class="main-header">🤖 AI Project Assistant</div>', unsafe_allow_html=True) |
|
|
st.markdown("Your intelligent assistant for managing multiple projects through meeting summaries") |
|
|
|
|
|
|
|
|
with st.sidebar: |
|
|
st.header("⚙️ Settings") |
|
|
|
|
|
|
|
|
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 |
|
|
""") |
|
|
|
|
|
|
|
|
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)") |
|
|
|
|
|
|
|
|
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!") |
|
|
|
|
|
|
|
|
tab1, tab2 = st.tabs(["💬 Chat", "📤 Upload Meeting"]) |
|
|
|
|
|
with tab1: |
|
|
st.header("💬 Ask Questions About Your Projects") |
|
|
|
|
|
|
|
|
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") |
|
|
|
|
|
|
|
|
cols = st.columns(len(projects) + 1) |
|
|
|
|
|
|
|
|
with cols[0]: |
|
|
if st.button("🌐 All Projects", use_container_width=True, type="secondary"): |
|
|
st.session_state.selected_chat_project = None |
|
|
st.rerun() |
|
|
|
|
|
|
|
|
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() |
|
|
|
|
|
|
|
|
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**") |
|
|
|
|
|
|
|
|
if "selected_chat_project" in st.session_state: |
|
|
|
|
|
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 |
|
|
|
|
|
|
|
|
if tab1 and "selected_chat_project" in st.session_state: |
|
|
prompt = st.chat_input("What would you like to know about your projects?") |
|
|
|
|
|
|
|
|
if prompt: |
|
|
|
|
|
st.session_state.messages.append({"role": "user", "content": prompt}) |
|
|
with st.chat_message("user"): |
|
|
st.markdown(prompt) |
|
|
|
|
|
|
|
|
with st.chat_message("assistant"): |
|
|
with st.spinner("Thinking..."): |
|
|
|
|
|
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") |
|
|
|
|
|
|
|
|
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. |
|
|
""" |
|
|
) |
|
|
|
|
|
|
|
|
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: |
|
|
|
|
|
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 |
|
|
|
|
|
|
|
|
st.success("✅ Meeting structured successfully!") |
|
|
st.markdown("### Preview") |
|
|
st.markdown(structured_md) |
|
|
|
|
|
|
|
|
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("<br>", unsafe_allow_html=True) |
|
|
if st.button("💾 Save to Project"): |
|
|
|
|
|
project_dir = Path("data") / project_name / "meetings" |
|
|
project_dir.mkdir(parents=True, exist_ok=True) |
|
|
|
|
|
|
|
|
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") |
|
|
|
|
|
|
|
|
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() |