Introduction:
Meeting Summarizer Pro is a Streamlit app that uses a LLM model to convert meeting transcripts into structured summaries. Powered by the Meta-Llama-3.3-70B-Instruct model via SambaNova Cloud, it offers an efficient and automated solution for meeting documentation.
Features:
- File Upload: Upload meeting transcripts in .txt or .docx format.
- AI-Generated Summaries: Generate detailed summaries including the following key information:
- Meeting title
- Date
- Duration
- Participants
- Action items
- Customizable Summaries: Tailor the summary with specific instructions for the AI to meet your needs.
- Summary Statistics: View additional statistics such as:
- Word count
- Estimated reading time
- Action item count
- Downloadable Formats: Download your summary as either a Markdown or text file.
Usage:
- Upload Transcript: Begin by uploading your meeting transcript in .txt or .docx format.
- Review Summary Details: Check the word count and estimated reading time to gauge the document’s length.
- Generate Summary: Click “Generate Summary” to process the transcript and create the summary.
- Access and Download Summary: Once generated, you can:
- View the summary
- Copy the content
- Download the summary in your preferred format (Markdown or text).
Troubleshooting:
- Summary Generation Issues: If the summary generation fails:
- Try uploading a shorter transcript
- Check your internet connection
- Wait a few minutes and retry the process
- File Upload Problems: Ensure that your file is in .txt or .docx format. If issues persist:
- Try saving your file with UTF-8 encoding
- Check if the file is corrupted
- Download Issues: If you’re unable to download the summary:
- Try using a different browser
- Make sure your browser allows downloads
Configuration:
- API Key: To integrate with the AI model, replace the default API key with your own in
streamlit_app.py
(line 138). - Model Name: If you’d like to use a different model, change the model name in
streamlit_app.py
(line 139).
UI Preview:
Code:
streamlit_app.py
import streamlit as st
import docx
import io
import base64
from openai import OpenAI
import time
import re
import pandas as pd
from datetime import datetime
# App configuration with better defaults for performance
st.set_page_config(
page_title="Meeting Summarizer Pro",
page_icon="📝",
layout="wide",
initial_sidebar_state="expanded",
menu_items={
'Get Help': 'https://www.streamlit.io/community',
'Report a bug': "https://github.com/streamlit/streamlit/issues",
'About': "# Meeting Summarizer Pro\nTransform meeting transcripts into structured summaries with AI."
}
)
# Custom CSS with improved compatibility and performance
st.markdown("""
<style>
/* Base styles with better compatibility */
.stApp {
max-width: 100%;
}
/* Header styles */
.main-header {
font-size: 2.3rem;
color: #1E88E5;
margin-bottom: 0.8rem;
font-weight: 600;
}
.sub-header {
font-size: 1.4rem;
color: #0D47A1;
margin-top: 0.8rem;
margin-bottom: 0.5rem;
font-weight: 500;
}
/* Text styles */
.info-text {
font-size: 1rem;
color: #424242;
line-height: 1.5;
}
/* Content containers */
.highlight {
background-color: #F5F5F5;
padding: 1rem;
border-radius: 0.5rem;
border-left: 0.5rem solid #1E88E5;
margin: 1rem 0;
overflow-wrap: break-word;
}
/* Button styling */
.stButton > button {
background-color: #1E88E5;
color: white;
font-weight: 500;
border: none;
padding: 0.5rem 1rem;
border-radius: 0.3rem;
transition: all 0.3s ease;
}
.stButton > button:hover {
background-color: #1976D2;
box-shadow: 0 2px 5px rgba(0,0,0,0.2);
}
/* Footer styling */
.footer {
margin-top: 2rem;
text-align: center;
color: #9E9E9E;
padding: 1rem;
font-size: 0.9rem;
}
/* Dark mode specific styles */
.dark-mode {
color: #E0E0E0;
}
.dark-mode .highlight {
background-color: #2D2D2D;
border-left: 0.5rem solid #1E88E5;
}
.dark-mode .info-text {
color: #BDBDBD;
}
/* Expander styling */
.streamlit-expanderHeader {
font-weight: 500;
color: #1E88E5;
}
/* Fix for text area overflow */
.stTextArea textarea {
overflow: auto;
}
/* Custom download button */
.download-btn {
display: inline-block;
background-color: #1E88E5;
color: white;
text-decoration: none;
padding: 0.5rem 1rem;
border-radius: 0.3rem;
text-align: center;
font-weight: 500;
margin: 0.5rem 0;
transition: all 0.3s ease;
}
.download-btn:hover {
background-color: #1976D2;
box-shadow: 0 2px 5px rgba(0,0,0,0.2);
}
</style>
""", unsafe_allow_html=True)
# SambaNova API Configuration
SAMBANOVA_API_URL = "https://api.sambanova.ai/v1"
SAMBANOVA_API_KEY = "{Your-api-key}"
MODEL_NAME = "Meta-Llama-3.3-70B-Instruct"
# Initialize OpenAI client with better caching
@st.cache_resource(ttl=3600)
def get_client():
return OpenAI(
base_url=SAMBANOVA_API_URL,
api_key=SAMBANOVA_API_KEY,
)
client = get_client()
# Enhanced prompt for high-quality markdown summary
SYSTEM_PROMPT = """
You are a professional meeting summarization assistant.
Your job is to read a meeting transcript and generate a **comprehensive, human-style summary** in markdown.
The summary should follow this structure:
---
## 📌 Meeting Summary: [Meeting Title]
**Date:** [Insert Date]
**Duration:** ~[Estimate Duration]
**Participants:** [All names mentioned in transcript]
---
### 🔍 Overview
Brief, 2–3 sentence description of the purpose and context of the meeting.
---
### 🚀 Features Demoed / Discussed
- Bullet points explaining what was shown, demoed, or discussed in detail.
---
### 🛠️ Technical Architecture (if applicable)
Summarize the core architecture, tools, languages, and design flow.
---
### 📋 Templates / Use Cases
- List of supported templates, modes, or intended use cases.
---
### 📈 Enhancements Discussed
- 📅 Time-based ideas
- 🤖 AI or automation ideas
- 📧 Alerts or dashboards
- 🔄 Integration ideas
- 🧠 Suggestions from participants
---
### ✅ Action Items
- [ ] Task description – **Owner**, Deadline (if mentioned)
---
### 💬 Quote Highlights
> *"Great insight or technical statement here."* – Speaker Name
> *"Another memorable quote."* – Speaker Name
---
You must:
- Use bullet points where helpful
- Infer names, roles, and actions from the transcript
- Include all participants, even if only briefly involved
- Keep the tone professional yet readable
- Format in clean Markdown
Respond ONLY with the summary.
"""
# Function to call the LLM API with improved error handling and progress tracking
def call_llama(prompt, custom_instructions=""):
try:
# Create a progress bar with smoother updates
progress_placeholder = st.empty()
status_text = st.empty()
progress_bar = progress_placeholder.progress(0)
# Update progress with better timing
status_text.text("⏳ Initializing request...")
for i in range(1, 11):
progress_bar.progress(i * 0.1)
time.sleep(0.1)
# Prepare system prompt with custom instructions if provided
final_system_prompt = SYSTEM_PROMPT
if custom_instructions:
final_system_prompt += f"\n\nAdditional instructions: {custom_instructions}"
status_text.text("🔄 Sending request to AI model...")
for i in range(11, 31):
progress_bar.progress(i * 0.01)
time.sleep(0.05)
# Call the API with timeout handling
try:
completion = client.chat.completions.create(
model=MODEL_NAME,
messages=[
{"role": "system", "content": final_system_prompt},
{"role": "user", "content": prompt}
],
stream=False,
timeout=60 # Add timeout to prevent hanging
)
status_text.text("🧠 Processing response...")
for i in range(31, 71):
progress_bar.progress(i * 0.01)
time.sleep(0.03)
status_text.text("✨ Formatting summary...")
for i in range(71, 100):
progress_bar.progress(i * 0.01)
time.sleep(0.02)
# Complete the progress bar
progress_bar.progress(100)
status_text.text("✅ Summary generated successfully!")
time.sleep(0.5)
# Clear the progress indicators
progress_placeholder.empty()
status_text.empty()
return completion.choices[0].message.content
except TimeoutError:
progress_placeholder.empty()
status_text.empty()
st.error("⏱️ Request timed out. Please try again with a shorter transcript.")
return "⏱️ Error: Request timed out. Please try again with a shorter transcript."
except Exception as e:
# Clear progress indicators and show error
try:
progress_placeholder.empty()
status_text.empty()
except:
pass
error_msg = str(e)
st.error(f"❌ Error: {error_msg}")
# Provide more helpful error messages
if "rate limit" in error_msg.lower():
return "❌ Error: Rate limit exceeded. Please wait a moment before trying again."
elif "timeout" in error_msg.lower():
return "❌ Error: Request timed out. Please try with a shorter transcript."
elif "api key" in error_msg.lower():
return "❌ Error: API authentication issue. Please check your API configuration."
else:
return f"❌ Error: {error_msg}"
# Function to create a download link for text with improved reliability
def get_download_link(text, filename, link_text):
"""Generate a download link for text content"""
# Ensure text is properly encoded
try:
b64 = base64.b64encode(text.encode('utf-8')).decode()
# Create a more reliable download link
href = f'<a href="data:file/txt;base64,{b64}" download="{filename}" class="download-btn">{link_text}</a>'
return href
except Exception as e:
st.warning(f"Could not create download link: {str(e)}")
return f"<p>Download failed: {str(e)}</p>"
# Function to extract meeting title from summary with better error handling
def extract_title(summary):
"""Extract the meeting title from the summary"""
try:
title_match = re.search(r'## 📌 Meeting Summary: (.*)', summary)
if title_match:
return title_match.group(1).strip()
return "Meeting Summary"
except:
return f"Meeting Summary {datetime.now().strftime('%Y-%m-%d')}"
# Sample transcript for demo
SAMPLE_TRANSCRIPT = """
John (Product Manager): Good morning everyone! Thanks for joining our weekly product meeting. Today we'll be discussing the new health check monitoring tool.
Sarah (Developer): Hi John, I've prepared a demo of the current implementation.
John: Great, let's see it.
Sarah: So, this tool allows us to monitor the health of our services in real-time. It checks API endpoints, database connections, and system resources.
Mike (DevOps): I really like the dashboard view. Can we add alerts for when a service goes down?
Sarah: Yes, that's on our roadmap. We're planning to integrate with PagerDuty and Slack for notifications.
Emily (UX Designer): The interface looks clean, but I think we should color-code the status indicators - green for healthy, yellow for warnings, and red for critical issues.
John: That's a good suggestion, Emily. Let's implement that in the next sprint.
Mike: I'd also like to see historical data, so we can track performance over time.
Sarah: We're already storing the data in our time-series database. I can add a graph view to visualize trends.
John: Perfect. What about authentication? Who can access this tool?
Sarah: Currently, it's restricted to the DevOps team, but we can implement role-based access if needed.
John: Yes, let's do that. I think team leads should have access too.
Emily: Should we also consider adding a mobile view? Sometimes people need to check status on the go.
Sarah: That's a good point. We can make the dashboard responsive for mobile devices.
John: Alright, let's summarize the action items:
1. Emily will update the design with color-coded status indicators
2. Sarah will implement the graph view for historical data
3. Mike will define the alert rules for different services
4. I'll talk to the security team about role-based access
Does anyone have any other questions or suggestions?
Mike: Not from me, this looks great!
Emily: I'm good too.
Sarah: I'll send out the updated designs by Friday.
John: Perfect! Thanks everyone for your input. Let's meet again next week to review progress.
"""
# Function to estimate reading time
def estimate_reading_time(text):
"""Estimate reading time in minutes based on word count"""
words = len(text.split())
# Average reading speed: 200-250 words per minute
minutes = max(1, round(words / 225))
return minutes
# Function to count words in text
def count_words(text):
"""Count words in text"""
return len(text.split())
# Function to create a copy button that works without JavaScript
def get_copy_button_html(text_id):
return f"""
<button onclick="
var text = document.getElementById('{text_id}').innerText;
navigator.clipboard.writeText(text)
.then(() => {{
this.innerText = '✓ Copied!';
setTimeout(() => this.innerText = '📋 Copy to Clipboard', 2000);
}})
.catch(err => console.error('Failed to copy: ', err));
" style="
background-color: #1E88E5;
color: white;
border: none;
padding: 0.5rem 1rem;
border-radius: 0.3rem;
cursor: pointer;
font-weight: 500;
width: 100%;
margin: 0.5rem 0;
">
📋 Copy to Clipboard
</button>
"""
# Function to validate and process transcript
def validate_and_process_transcript(transcript_text, max_length):
"""Validate and process transcript text"""
if not transcript_text or transcript_text.strip() == "":
st.error("❌ Empty transcript. Please provide some text.")
return None
# Count words
word_count = count_words(transcript_text)
# Check if transcript exceeds maximum length
if word_count > max_length:
st.warning(f"⚠️ Transcript exceeds maximum length ({word_count} words). Only processing the first {max_length} words.")
words = transcript_text.split()
transcript_text = " ".join(words[:max_length])
# Display word count and estimated reading time
col1, col2 = st.columns(2)
with col1:
st.info(f"📊 Word count: {word_count}")
with col2:
st.info(f"⏱️ Estimated reading time: {estimate_reading_time(transcript_text)} minutes")
return transcript_text
# Function to display summary with actions
def display_summary(summary_markdown, key_prefix=""):
"""Display summary with download and copy options"""
if not summary_markdown or "Error:" in summary_markdown:
return
# Create a unique ID for this summary
summary_id = f"summary_{key_prefix}_{int(time.time())}"
# Display the summary
st.markdown('<div class="highlight">', unsafe_allow_html=True)
st.markdown(f'<div id="{summary_id}">', unsafe_allow_html=True)
st.markdown(summary_markdown, unsafe_allow_html=True)
st.markdown('</div>', unsafe_allow_html=True)
st.markdown('</div>', unsafe_allow_html=True)
# Extract title for filename
title = extract_title(summary_markdown)
filename_base = f"{title.replace(' ', '_')}_{datetime.now().strftime('%Y%m%d')}"
# Actions for the summary
col1, col2, col3 = st.columns(3)
with col1:
# Copy to clipboard button (with fallback)
st.markdown(get_copy_button_html(summary_id), unsafe_allow_html=True)
with col2:
# Download as markdown
st.markdown(
get_download_link(summary_markdown, f"{filename_base}.md", "📥 Download as Markdown"),
unsafe_allow_html=True
)
with col3:
# Download as text
st.markdown(
get_download_link(summary_markdown, f"{filename_base}.txt", "📄 Download as Text"),
unsafe_allow_html=True
)
# Add a divider
st.markdown("---")
# Display summary stats
st.markdown("### 📊 Summary Statistics")
# Create a dataframe with summary statistics
stats_data = {
"Metric": ["Word Count", "Estimated Reading Time", "Action Items"],
"Value": [
count_words(summary_markdown),
f"{estimate_reading_time(summary_markdown)} minutes",
len(re.findall(r'- \[ \]', summary_markdown))
]
}
stats_df = pd.DataFrame(stats_data)
st.dataframe(stats_df, hide_index=True, use_container_width=True)
def main():
# Apply dark mode by default
st.markdown("""
<style>
.stApp {
background-color: #1E1E1E;
color: #E0E0E0;
}
.sidebar .sidebar-content {
background-color: #2D2D2D;
}
h1, h2, h3, h4, h5, h6 {
color: #E0E0E0 !important;
}
.info-text {
color: #BDBDBD !important;
}
.highlight {
background-color: #2D2D2D;
border-left: 0.5rem solid #1E88E5;
}
.stDataFrame {
background-color: #2D2D2D;
}
div[data-testid="stExpander"] {
background-color: #2D2D2D;
}
</style>
""", unsafe_allow_html=True)
# Sidebar for app configuration
with st.sidebar:
# Use local emoji as icon
st.markdown("# 📝")
st.markdown("## App Settings")
# Advanced options
st.markdown("### Advanced Options")
max_length = st.slider("Max transcript length (words)", 1000, 100000, 10000)
# Model settings
st.markdown("### Model Settings")
model_info = st.expander("Model Information", expanded=False)
with model_info:
st.markdown(f"""
**Model**: {MODEL_NAME}
This model is optimized for summarizing meeting transcripts and extracting key information.
""")
# Custom instructions
st.markdown("### Custom Instructions")
custom_instructions = st.text_area(
"Add custom instructions for the AI",
placeholder="E.g., Focus more on technical details, Include more action items, etc.",
height=100
)
# About section
st.markdown("---")
st.markdown("### About")
st.markdown("""
This app uses AI to convert meeting transcripts into structured summaries.
Powered by Meta-Llama-3.3-70B-Instruct via SambaNova.
""")
# Version info
st.markdown("---")
st.markdown("v1.1.0 | 2025")
# Main content area
st.markdown('<h1 class="main-header">📝 Meeting Summarizer Pro</h1>', unsafe_allow_html=True)
st.markdown('<p class="info-text">Transform your meeting transcripts into structured, actionable summaries with AI</p>', unsafe_allow_html=True)
# Create tabs with icons
tab1, tab2, tab3 = st.tabs(["📤 Upload", "📋 Sample Demo", "ℹ️ Help"])
with tab1:
st.markdown('<h2 class="sub-header">Upload Your Transcript</h2>', unsafe_allow_html=True)
# File uploader with better error handling
uploaded_file = st.file_uploader(
"📤 Upload transcript (.txt or .docx)",
type=["txt", "docx"],
help="Upload a meeting transcript file to generate a summary"
)
# Process uploaded file
if uploaded_file:
try:
# Extract text based on file type
if uploaded_file.type == "text/plain":
transcript_text = uploaded_file.read().decode("utf-8")
elif uploaded_file.type == "application/vnd.openxmlformats-officedocument.wordprocessingml.document":
doc = docx.Document(uploaded_file)
transcript_text = "\n".join([p.text for p in doc.paragraphs])
else:
st.error("❌ Unsupported file type.")
transcript_text = None
# Process transcript if valid
if transcript_text:
processed_text = validate_and_process_transcript(transcript_text, max_length)
if processed_text:
# Display transcript in an expander
with st.expander("View Original Transcript", expanded=False):
st.text_area("Transcript Content", processed_text, height=300, disabled=True)
# Generate summary button
if st.button("🔍 Generate Summary", key="generate_upload", use_container_width=True):
with st.spinner("Generating summary..."):
st.markdown('<h2 class="sub-header">📌 Generated Meeting Summary</h2>', unsafe_allow_html=True)
# Call the AI model
summary_markdown = call_llama(processed_text, custom_instructions)
# Display the summary with actions
display_summary(summary_markdown, "upload")
except Exception as e:
st.error(f"❌ Error processing file: {str(e)}")
st.info("💡 Try uploading a different file or check if the file is corrupted.")
with tab2:
st.markdown('<h2 class="sub-header">Try with Sample Transcript</h2>', unsafe_allow_html=True)
st.markdown('<p class="info-text">See how the app works with this sample meeting transcript</p>', unsafe_allow_html=True)
# Display sample transcript in an expander
with st.expander("View Sample Transcript", expanded=False):
st.text_area("Sample Transcript Content", SAMPLE_TRANSCRIPT, height=300, disabled=True)
# Sample transcript stats
st.info(f"📊 Sample transcript: {count_words(SAMPLE_TRANSCRIPT)} words, ~{estimate_reading_time(SAMPLE_TRANSCRIPT)} minute read")
# Generate summary from sample button
if st.button("🔍 Generate Sample Summary", key="generate_sample", use_container_width=True):
with st.spinner("Generating summary..."):
st.markdown('<h2 class="sub-header">📌 Generated Meeting Summary</h2>', unsafe_allow_html=True)
# Call the AI model with the sample transcript
summary_markdown = call_llama(SAMPLE_TRANSCRIPT, custom_instructions)
# Display the summary with actions
display_summary(summary_markdown, "sample")
with tab3:
st.markdown('<h2 class="sub-header">How to Use This App</h2>', unsafe_allow_html=True)
# Create help sections with expanders
with st.expander("📤 Upload Tab", expanded=True):
st.markdown("""
1. Upload your meeting transcript as a .txt or .docx file
2. Review the word count and estimated reading time
3. Click "Generate Summary" to process the transcript
4. View, copy, or download the generated summary
""")
with st.expander("📋 Sample Demo Tab", expanded=False):
st.markdown("""
1. View the sample transcript to understand the expected format
2. Click "Generate Sample Summary" to see how the app works
3. Use this as a reference for your own transcripts
""")
with st.expander("🛠️ Advanced Features", expanded=False):
st.markdown("""
- **Custom Instructions**: Add specific instructions for the AI in the sidebar
- **Max Length**: Adjust the maximum transcript length to process (up to 100,000 words)
- **Summary Statistics**: View word count, reading time, and action item count for your summary
""")
with st.expander("📝 Supported File Formats", expanded=False):
st.markdown("""
- Plain text (.txt)
- Microsoft Word (.docx)
""")
with st.expander("🔍 Summary Structure", expanded=False):
st.markdown("""
The generated summary includes:
- Meeting title, date, duration, and participants
- Overview of the meeting
- Features discussed
- Technical details (if applicable)
- Use cases
- Enhancements discussed
- Action items
- Notable quotes
""")
with st.expander("❓ Troubleshooting", expanded=False):
st.markdown("""
**Common Issues:**
1. **Summary generation fails**
- Try with a shorter transcript
- Check your internet connection
- Wait a few minutes and try again
2. **File upload issues**
- Ensure your file is in .txt or .docx format
- Try saving your file with a different encoding (UTF-8 recommended)
- Check if the file is not corrupted
3. **Download issues**
- Try using a different browser
- Check if your browser allows downloads
""")
# Footer
st.markdown('<div class="footer">Last updated: April 2025</div>', unsafe_allow_html=True)
if __name__ == "__main__":
try:
main()
except Exception as e:
st.error(f"❌ Application Error: {str(e)}")
st.info("💡 Please refresh the page and try again. If the problem persists, contact support.")