-
Notifications
You must be signed in to change notification settings - Fork 6.1k
Expand file tree
/
Copy pathapp.py
More file actions
304 lines (235 loc) · 9.99 KB
/
Copy pathapp.py
File metadata and controls
304 lines (235 loc) · 9.99 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
"""
Meeting Notes Generator
Automatically generates meeting notes from audio files with speaker identification,
summaries, and action items.
"""
import streamlit as st
import tempfile
import os
import gc
from dotenv import load_dotenv
# Load environment variables
load_dotenv()
from src.services.audio_processor import MeetingProcessor
from src.ui.ui_components import (
display_meeting_header,
display_meeting_summary,
display_speaker_analysis,
display_action_items,
display_meeting_transcript
)
def main():
"""Main application function."""
st.set_page_config(
page_title="Multilingual Meeting Notes Generator",
layout="wide"
)
# Initialize session state variables
if "current_result" not in st.session_state:
st.session_state.current_result = None
# Sidebar
with st.sidebar:
# Configuration
st.header("🔧 Configuration")
# API key input fields
assemblyai_key = st.text_input(
"AssemblyAI API Key",
type="password",
help="Enter your AssemblyAI API key for audio transcription"
)
openai_key = st.text_input(
"OpenAI API Key",
type="password",
help="Enter your OpenAI API key for text analysis"
)
# Check if API keys are provided
api_keys_configured = assemblyai_key and openai_key
if api_keys_configured:
st.success("✅ API Keys configured!")
else:
st.warning("⚠️ Please add your API keys to get started")
st.markdown("---")
# Audio input options
st.header("🎤 Audio Input")
# File upload
uploaded_file = st.file_uploader(
"Choose your audio file",
type=['mp3', 'wav', 'm4a', 'mp4', 'webm', 'flac'],
help="Upload an audio file to generate meeting notes"
)
# Process button with enhanced validation
if uploaded_file and api_keys_configured:
if st.button("🚀 Start Processing", type="primary", use_container_width=True):
# Validate API keys format
if not _validate_api_keys(assemblyai_key, openai_key):
st.error("⚠️ Invalid API key format. Please check your keys.")
return
result = _process_uploaded_file(uploaded_file, {
"assemblyai_key": assemblyai_key,
"openai_key": openai_key
})
if result:
st.session_state.current_result = result
st.rerun()
elif uploaded_file and not api_keys_configured:
st.error("⚠️ Please add your API keys first")
# Add reset button
st.markdown("---")
if st.button("🗑️ Reset", help="Clear results and reset session"):
_cleanup_session()
st.success("Session cleared!")
st.rerun()
# Main interface
_display_main_interface()
def _process_uploaded_file(uploaded_file, config: dict):
"""Process the uploaded audio file with enhanced logging and error handling."""
try:
temp_path = _save_uploaded_file(uploaded_file)
processor = MeetingProcessor(
assemblyai_api_key=config["assemblyai_key"],
openai_api_key=config["openai_key"]
)
# Show processing status
status_container = st.empty()
status_container.info("🔄 Processing audio...")
result = processor.process_meeting_audio(
audio_file_path=temp_path
)
_cleanup_temp_file(temp_path)
status_container.success("✅ Processing complete!")
return result
except Exception as e:
error_msg = f"❌ Processing failed: {str(e)}"
st.error(error_msg)
return None
def _save_uploaded_file(uploaded_file) -> str:
"""Save uploaded file to temp location."""
file_extension = uploaded_file.name.split('.')[-1]
with tempfile.NamedTemporaryFile(delete=False, suffix=f".{file_extension}") as temp_file:
temp_file.write(uploaded_file.getvalue())
return temp_file.name
def _cleanup_temp_file(temp_path: str) -> None:
"""Delete temporary file safely and perform garbage collection."""
try:
os.unlink(temp_path)
# Perform garbage collection
gc.collect()
except OSError as e:
print(f"Warning: Could not delete temporary file {temp_path}: {e}")
def _validate_api_keys(assemblyai_key: str, openai_key: str) -> bool:
"""Validate API key formats."""
# Basic validation - keys should be non-empty and have reasonable length
if not assemblyai_key or len(assemblyai_key) < 20:
return False
if not openai_key or len(openai_key) < 20:
return False
return True
def _cleanup_session() -> None:
"""Clean up session state and perform garbage collection."""
# Clear current result
st.session_state.current_result = None
# Perform garbage collection
gc.collect()
def _display_main_interface():
"""Display the main interface."""
# Header with branding - center aligned
st.markdown('''
<div style="text-align: center;">
<h1 style='color: #1C59C3; margin-bottom: 10px;'>
🎤 Multilingual Meeting Notes Generator
</h1>
<div style="display: flex; align-items: center; justify-content: center; gap: 8px; margin-bottom: 20px;">
<span style='color: #A23B72; font-size: 16px;'>Powered by</span>
<div style="display: flex; align-items: center; gap: 20px;">
<a href="https://www.assemblyai.com/" style="display: inline-block; vertical-align: middle;">
<img src="https://cdn.prod.website-files.com/67a08d9d7d19f8fb63692894/67a1038c4a876d1cb37c09aa_AssemblyAI%20Logo.svg"
alt="AssemblyAI" style="height: 32px;">
</a>
</div>
</div>
</div>
''', unsafe_allow_html=True)
# System status - center aligned
if st.session_state.current_result:
st.success("🟢 Meeting processed successfully!")
else:
st.info("🔵 Upload an audio file to get started")
# Display results if available
if st.session_state.current_result:
result = st.session_state.current_result
# Display main results
display_meeting_header(result)
display_meeting_summary(result.summary)
display_speaker_analysis(result.speakers, result.segments)
display_action_items(result.action_items)
# Always show transcript
display_meeting_transcript(result.segments, result.language, result)
# Export options
_display_export_options(result)
# Footer - center aligned
st.markdown("---")
st.markdown(
"<div style='text-align: center;'>"
"<p style='color: #666; font-size: 12px;'>"
"Multilingual Meeting Notes Generator • Powered by AssemblyAI"
"</p>"
"</div>",
unsafe_allow_html=True
)
def _display_export_options(result):
"""Show export options."""
st.subheader("💾 Export Meeting Notes")
markdown_content = _generate_markdown_export(result)
st.download_button(
label="📥 Download as Markdown",
data=markdown_content,
file_name=f"meeting_notes_{result.processed_at.strftime('%Y%m%d_%H%M')}.md",
mime="text/markdown"
)
def _generate_markdown_export(result) -> str:
"""Generate markdown content for export."""
markdown_content = f"""# {result.title}
**Processed on:** {result.processed_at.strftime('%B %d, %Y at %I:%M %p')}
**Duration:** {result.duration // 60}m {result.duration % 60}s
## 📝 Meeting Summary
{result.summary}
## 👥 Speakers
"""
for speaker in result.speakers:
markdown_content += f"### {speaker.name}\n"
markdown_content += f"- **Speaking Time:** {speaker.speaking_time // 60}m {speaker.speaking_time % 60}s\n"
markdown_content += f"- **Words Spoken:** {speaker.word_count}\n\n"
markdown_content += "## ✅ Action Items\n\n"
for i, item in enumerate(result.action_items, 1):
markdown_content += f"### Action {i}\n"
markdown_content += f"**Description:** {item.description}\n"
if item.assignee:
markdown_content += f"**Assigned to:** {item.assignee}\n"
if item.due_date:
markdown_content += f"**Due Date:** {item.due_date}\n"
if item.priority:
markdown_content += f"**Priority:** {item.priority.title()}\n"
markdown_content += "\n"
markdown_content += "## 📄 Full Transcript\n\n"
# Add transcript statistics
total_words = result.total_words
avg_confidence = result.avg_confidence
unique_speakers = result.unique_speakers_count
markdown_content += f"**Transcript Statistics:**\n"
markdown_content += f"- Total Segments: {len(result.segments)}\n"
markdown_content += f"- Total Words: {total_words}\n"
markdown_content += f"- Average Confidence: {avg_confidence:.2f}\n"
markdown_content += f"- Unique Speakers: {unique_speakers}\n\n"
# Add full transcript with enhanced formatting
markdown_content += "### Complete Transcript\n\n"
for i, segment in enumerate(result.segments, 1):
start_minutes = segment.start_time // 1000 // 60
start_seconds = segment.start_time // 1000 % 60
timestamp = f"{start_minutes:02d}:{start_seconds:02d}"
markdown_content += f"#### Segment {i}: Speaker {segment.speaker_id} ({timestamp})\n"
markdown_content += f"*Confidence: {segment.confidence:.2f}*\n\n"
markdown_content += f"{segment.text}\n\n"
markdown_content += "---\n\n"
return markdown_content
if __name__ == "__main__":
main()