midlajvalappil commited on
Commit
7086bdb
·
verified ·
1 Parent(s): 1697da9

Update src/streamlit_app.py

Browse files
Files changed (1) hide show
  1. src/streamlit_app.py +597 -35
src/streamlit_app.py CHANGED
@@ -1,40 +1,602 @@
1
- import altair as alt
2
- import numpy as np
3
- import pandas as pd
 
 
 
 
4
  import streamlit as st
 
 
5
 
6
- """
7
- # Welcome to Streamlit!
8
 
9
- Edit `/streamlit_app.py` to customize this app to your heart's desire :heart:.
10
- If you have any questions, checkout our [documentation](https://docs.streamlit.io) and [community
11
- forums](https://discuss.streamlit.io).
 
 
 
 
12
 
13
- In the meantime, below is an example of what you can do with just a few lines of code:
14
- """
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
15
 
16
- num_points = st.slider("Number of points in spiral", 1, 10000, 1100)
17
- num_turns = st.slider("Number of turns in spiral", 1, 300, 31)
18
-
19
- indices = np.linspace(0, 1, num_points)
20
- theta = 2 * np.pi * num_turns * indices
21
- radius = indices
22
-
23
- x = radius * np.cos(theta)
24
- y = radius * np.sin(theta)
25
-
26
- df = pd.DataFrame({
27
- "x": x,
28
- "y": y,
29
- "idx": indices,
30
- "rand": np.random.randn(num_points),
31
- })
32
-
33
- st.altair_chart(alt.Chart(df, height=700, width=700)
34
- .mark_point(filled=True)
35
- .encode(
36
- x=alt.X("x", axis=None),
37
- y=alt.Y("y", axis=None),
38
- color=alt.Color("idx", legend=None, scale=alt.Scale()),
39
- size=alt.Size("rand", legend=None, scale=alt.Scale(range=[1, 150])),
40
- ))
 
1
+ """
2
+ AI-Powered YouTube Transcript Tutor - Main Streamlit Application
3
+ Enhanced version with modern UI, error handling, and extended functionality.
4
+ """
5
+
6
+ import os
7
+ import sys
8
  import streamlit as st
9
+ from datetime import datetime
10
+ import time
11
 
12
+ # Add src to path for imports
13
+ sys.path.insert(0, os.path.join(os.path.dirname(__file__), 'src'))
14
 
15
+ # Import custom modules
16
+ from src.utils.youtube_handler import YouTubeHandler
17
+ from src.utils.text_processor import TextProcessor
18
+ from src.utils.session_manager import SessionManager
19
+ from src.utils.export_utils import ExportUtils
20
+ from src.utils.logger import setup_logging
21
+ from config.settings import settings
22
 
23
+ # Load environment variables
24
+ from dotenv import load_dotenv
25
+ load_dotenv()
26
+
27
+ # Setup logging
28
+ logger = setup_logging(
29
+ log_level=settings.get('logging.level', 'INFO'),
30
+ log_file=settings.get('logging.file')
31
+ )
32
+
33
+ class YouTubeChatbotApp:
34
+ """Main application class for YouTube Transcript Chatbot."""
35
+
36
+ def __init__(self):
37
+ """Initialize the application."""
38
+ self.setup_page_config()
39
+ self.load_custom_css()
40
+ self.initialize_components()
41
+
42
+ def setup_page_config(self):
43
+ """Configure Streamlit page settings."""
44
+ app_config = settings.get_app_config()
45
+ st.set_page_config(
46
+ page_title=app_config.get('title', 'YouTube Transcript Tutor'),
47
+ page_icon="🎓",
48
+ layout="wide",
49
+ initial_sidebar_state="expanded"
50
+ )
51
+
52
+ def load_custom_css(self):
53
+ """Load custom CSS styling with dark theme."""
54
+ try:
55
+ with open('static/style.css', 'r') as f:
56
+ css_content = f.read()
57
+ st.markdown(f'<style>{css_content}</style>', unsafe_allow_html=True)
58
+ logger.info("Custom dark theme CSS loaded successfully")
59
+ except FileNotFoundError:
60
+ logger.warning("Custom CSS file not found, using fallback dark theme")
61
+ # Fallback CSS for dark theme
62
+ fallback_css = """
63
+ <style>
64
+ .stApp {
65
+ background-color: #1a1a1a !important;
66
+ color: #e9ecef !important;
67
+ }
68
+ .stMarkdown, .stText {
69
+ color: #e9ecef !important;
70
+ }
71
+ .stButton > button {
72
+ background-color: #667eea !important;
73
+ color: white !important;
74
+ }
75
+ body, html {
76
+ background-color: #1a1a1a !important;
77
+ color: #e9ecef !important;
78
+ }
79
+ div[data-testid="stSidebar"] {
80
+ background-color: #2d3748 !important;
81
+ }
82
+ </style>
83
+ """
84
+ st.markdown(fallback_css, unsafe_allow_html=True)
85
+
86
+ def initialize_components(self):
87
+ """Initialize application components."""
88
+ # Check for OpenAI API key
89
+ self.openai_api_key = settings.get_openai_api_key()
90
+ if not self.openai_api_key:
91
+ st.error("⚠️ OpenAI API key not found. Please set OPENAI_API_KEY environment variable.")
92
+ st.stop()
93
+
94
+ # Initialize components
95
+ self.youtube_handler = YouTubeHandler()
96
+ self.text_processor = TextProcessor(self.openai_api_key)
97
+ self.session_manager = SessionManager()
98
+ self.export_utils = ExportUtils()
99
+
100
+ def render_header(self):
101
+ """Render application header."""
102
+ app_config = settings.get_app_config()
103
+
104
+ st.markdown(f"""
105
+ <div class="app-header">
106
+ <h1>🎓 {app_config.get('title', 'AI-Powered YouTube Transcript Tutor')}</h1>
107
+ <p>{app_config.get('description', 'Ask questions from YouTube lecture transcripts using AI')}</p>
108
+ </div>
109
+ """, unsafe_allow_html=True)
110
+
111
+ def render_sidebar(self):
112
+ """Render sidebar with navigation and controls."""
113
+ with st.sidebar:
114
+ st.markdown("### 📋 Navigation")
115
+
116
+ # Session statistics
117
+ stats = self.session_manager.get_session_stats()
118
+ st.markdown(f"""
119
+ <div class="sidebar-content">
120
+ <h4>📊 Session Stats</h4>
121
+ <div class="metadata-item">
122
+ <span class="metadata-label">Questions Asked:</span>
123
+ <span class="metadata-value">{stats['total_questions']}</span>
124
+ </div>
125
+ <div class="metadata-item">
126
+ <span class="metadata-label">Videos Processed:</span>
127
+ <span class="metadata-value">{stats['processed_videos']}</span>
128
+ </div>
129
+ </div>
130
+ """, unsafe_allow_html=True)
131
+
132
+ # Processed videos
133
+ processed_videos = self.session_manager.get_processed_videos()
134
+ if processed_videos:
135
+ st.markdown("### 📹 Processed Videos")
136
+ for video_id, video_info in processed_videos.items():
137
+ title = video_info['metadata'].get('title', 'Unknown Title')[:50] + "..."
138
+ if st.button(f"📺 {title}", key=f"video_{video_id}"):
139
+ self.session_manager.switch_to_video(video_id)
140
+ st.rerun()
141
+
142
+ # Export options
143
+ if st.session_state.chat_history:
144
+ st.markdown("### 📤 Export Options")
145
+ export_format = st.selectbox(
146
+ "Export Format",
147
+ ["PDF", "Text", "JSON"],
148
+ key="export_format"
149
+ )
150
+
151
+ if st.button("📥 Export Chat History"):
152
+ self.export_chat_history(export_format.lower())
153
+
154
+ # Settings
155
+ st.markdown("### ⚙️ Settings")
156
+
157
+ # Language selection
158
+ processing_config = settings.get_processing_config()
159
+ supported_languages = processing_config.get('supported_languages', ['en'])
160
+ default_language = processing_config.get('default_language', 'en')
161
+
162
+ selected_language = st.selectbox(
163
+ "Transcript Language",
164
+ supported_languages,
165
+ index=supported_languages.index(default_language) if default_language in supported_languages else 0,
166
+ key="transcript_language"
167
+ )
168
+
169
+ # Clear history button
170
+ if st.button("🗑️ Clear Chat History", type="secondary"):
171
+ self.session_manager.clear_chat_history()
172
+ st.success("Chat history cleared!")
173
+ st.rerun()
174
+
175
+ # Working video examples
176
+ st.markdown("### 🎯 Example Videos")
177
+ st.markdown("Try these videos that usually work:")
178
+
179
+ example_videos = {
180
+ "🧮 Neural Networks": "https://www.youtube.com/watch?v=aircAruvnKk",
181
+ "📚 Khan Academy": "https://www.youtube.com/watch?v=WUvTyaaNkzM",
182
+ "🎓 TED-Ed": "https://www.youtube.com/watch?v=kBdfcR-8hEY"
183
+ }
184
+
185
+ for title, url in example_videos.items():
186
+ if st.button(title, key=f"example_{title}"):
187
+ st.session_state.video_url = url
188
+ st.rerun()
189
+
190
+ # Troubleshooting section
191
+ st.markdown("### 🔧 Troubleshooting")
192
+ with st.expander("Common Issues & Solutions"):
193
+ st.markdown("""
194
+ **"Could not retrieve transcript":**
195
+ - Video may be region-restricted
196
+ - Try videos from educational channels
197
+ - Ensure video has captions enabled
198
+
199
+ **"No transcript available":**
200
+ - Video doesn't have captions
201
+ - Try auto-generated captions videos
202
+ - Look for educational content
203
+
204
+ **"Video unavailable":**
205
+ - Video may be private/deleted
206
+ - Check the URL is correct
207
+ - Try a different video
208
+ """)
209
+
210
+ def render_video_input_section(self):
211
+ """Render video input and processing section."""
212
+ st.markdown("### 🎬 Video Processing")
213
+
214
+ col1, col2 = st.columns([3, 1])
215
+
216
+ with col1:
217
+ video_url = st.text_input(
218
+ "Enter YouTube Video URL",
219
+ placeholder="https://www.youtube.com/watch?v=...",
220
+ help="Paste a YouTube video URL to extract and process its transcript"
221
+ )
222
+
223
+ with col2:
224
+ st.markdown("<br>", unsafe_allow_html=True) # Add spacing
225
+ process_button = st.button("🚀 Process Video", type="primary")
226
+
227
+ if process_button and video_url:
228
+ self.process_video(video_url)
229
+ elif process_button and not video_url:
230
+ st.warning("⚠️ Please enter a valid YouTube URL.")
231
+
232
+ def process_video(self, video_url: str):
233
+ """
234
+ Process YouTube video and create QA chain.
235
+
236
+ Args:
237
+ video_url (str): YouTube video URL
238
+ """
239
+ # Validate URL
240
+ if not self.youtube_handler.validate_youtube_url(video_url):
241
+ st.error("❌ Invalid YouTube URL format. Please check the URL and try again.")
242
+ return
243
+
244
+ # Create progress indicators
245
+ progress_bar = st.progress(0)
246
+ status_text = st.empty()
247
+
248
+ try:
249
+ # Step 1: Extract transcript
250
+ status_text.text("🔍 Extracting video transcript...")
251
+ progress_bar.progress(25)
252
+
253
+ language = st.session_state.get('transcript_language', 'en')
254
+ transcript_result = self.youtube_handler.get_youtube_transcript(video_url, language)
255
+
256
+ if not transcript_result['success']:
257
+ error_msg = transcript_result['error']
258
+ st.error(f"❌ {error_msg}")
259
+
260
+ # Provide specific suggestions based on error type
261
+ if "ip blocked" in error_msg.lower() or "cloud provider" in error_msg.lower():
262
+ st.warning("🚫 **YouTube has temporarily blocked your IP address**")
263
+ st.info("💡 **How to fix this:**")
264
+ st.markdown("""
265
+ **Immediate solutions:**
266
+ - ⏰ **Wait 10-15 minutes** before trying again
267
+ - 🌐 **Try a different network** (mobile hotspot, different WiFi)
268
+ - 🔄 **Restart your router** to get a new IP address
269
+
270
+ **Why this happens:**
271
+ - Too many requests to YouTube in a short time
272
+ - Using cloud services (AWS, Google Cloud, etc.)
273
+ - YouTube's anti-bot protection
274
+
275
+ **Prevention:**
276
+ - Wait between video processing attempts
277
+ - Don't process multiple videos rapidly
278
+ """)
279
+
280
+ # Show a countdown timer suggestion
281
+ st.info("⏱️ **Recommended:** Wait 15 minutes, then try one of the example videos below.")
282
+
283
+ elif "rate limited" in error_msg.lower() or "too many requests" in error_msg.lower():
284
+ st.warning("⚡ **Rate Limited: Too many requests**")
285
+ st.info("💡 **Solution:** Wait 5-10 minutes before trying again.")
286
+
287
+ elif "region" in error_msg.lower():
288
+ st.info("💡 **Suggestions to fix this issue:**")
289
+ st.markdown("""
290
+ - Try a different video that's available in your region
291
+ - Look for videos from creators in your country
292
+ - Try educational channels like Khan Academy, Coursera, or TED-Ed
293
+ - Some videos may work better than others depending on regional settings
294
+ """)
295
+ elif "private" in error_msg.lower():
296
+ st.info("💡 **This video is private.** Try a public video instead.")
297
+ elif "disabled" in error_msg.lower():
298
+ st.info("💡 **Captions are disabled for this video.** Try finding a video with captions enabled.")
299
+ elif "unavailable" in error_msg.lower():
300
+ st.info("💡 **This video is unavailable.** It may have been deleted or made private.")
301
+ else:
302
+ st.info("💡 **Try these alternatives:**")
303
+ st.markdown("""
304
+ - Make sure the video is public and has captions
305
+ - Try a different YouTube video
306
+ - Look for educational content which usually has transcripts
307
+ - Check if the video URL is correct
308
+ """)
309
+
310
+ # Show some example working videos
311
+ st.markdown("### 🎯 **Try these example videos that usually work:**")
312
+ example_videos = [
313
+ "https://www.youtube.com/watch?v=aircAruvnKk", # 3Blue1Brown
314
+ "https://www.youtube.com/watch?v=WUvTyaaNkzM", # Khan Academy
315
+ "https://www.youtube.com/watch?v=kBdfcR-8hEY", # TED-Ed
316
+ ]
317
+
318
+ for i, example_url in enumerate(example_videos, 1):
319
+ if st.button(f"📺 Try Example Video {i}", key=f"example_{i}"):
320
+ st.session_state.video_url = example_url
321
+ st.rerun()
322
+
323
+ return
324
+
325
+ # Step 2: Display video metadata
326
+ progress_bar.progress(50)
327
+ status_text.text("📊 Processing video metadata...")
328
+
329
+ metadata = transcript_result['metadata']
330
+ if metadata:
331
+ self.display_video_metadata(metadata)
332
+
333
+ # Step 3: Process transcript
334
+ progress_bar.progress(75)
335
+ status_text.text("🧠 Creating AI knowledge base...")
336
+
337
+ processing_result = self.text_processor.process_transcript(
338
+ transcript_result['transcript'],
339
+ metadata
340
+ )
341
+
342
+ if not processing_result['success']:
343
+ st.error(f"❌ {processing_result['error']}")
344
+ return
345
+
346
+ # Step 4: Save to session
347
+ progress_bar.progress(100)
348
+ status_text.text("✅ Video processed successfully!")
349
+
350
+ video_id = metadata.get('video_id', 'unknown')
351
+ self.session_manager.save_processed_video(
352
+ video_url,
353
+ video_id,
354
+ metadata,
355
+ transcript_result['transcript'],
356
+ processing_result['qa_chain'],
357
+ processing_result['vectorstore']
358
+ )
359
+
360
+ # Success message
361
+ st.success("🎉 Video processed successfully! You can now ask questions about the content.")
362
+
363
+ # Show transcript download option
364
+ if st.button("📥 Download Transcript"):
365
+ self.download_transcript(transcript_result['transcript'], metadata)
366
+
367
+ except Exception as e:
368
+ logger.error(f"Error processing video: {e}")
369
+ st.error(f"❌ An unexpected error occurred: {str(e)}")
370
+
371
+ finally:
372
+ progress_bar.empty()
373
+ status_text.empty()
374
+
375
+ def display_video_metadata(self, metadata: dict):
376
+ """
377
+ Display video metadata in a formatted card.
378
+
379
+ Args:
380
+ metadata (dict): Video metadata
381
+ """
382
+ st.markdown(f"""
383
+ <div class="video-metadata" style="background-color: #2d3748 !important; border: 1px solid #4a5568 !important; color: #e9ecef !important;">
384
+ <h4 style="color: #e9ecef !important;">📹 Video Information</h4>
385
+ <div class="metadata-item">
386
+ <span class="metadata-label" style="color: #a0aec0 !important;">Title:</span>
387
+ <span class="metadata-value" style="color: #e9ecef !important;">{metadata.get('title', 'N/A')}</span>
388
+ </div>
389
+ <div class="metadata-item">
390
+ <span class="metadata-label" style="color: #a0aec0 !important;">Author:</span>
391
+ <span class="metadata-value" style="color: #e9ecef !important;">{metadata.get('author', 'N/A')}</span>
392
+ </div>
393
+ <div class="metadata-item">
394
+ <span class="metadata-label" style="color: #a0aec0 !important;">Duration:</span>
395
+ <span class="metadata-value" style="color: #e9ecef !important;">{self.format_duration(metadata.get('length', 0))}</span>
396
+ </div>
397
+ <div class="metadata-item">
398
+ <span class="metadata-label" style="color: #a0aec0 !important;">Views:</span>
399
+ <span class="metadata-value" style="color: #e9ecef !important;">{metadata.get('views', 'N/A'):,}</span>
400
+ </div>
401
+ </div>
402
+ """, unsafe_allow_html=True)
403
+
404
+ def format_duration(self, seconds: int) -> str:
405
+ """Format duration from seconds to HH:MM:SS."""
406
+ if not seconds:
407
+ return "N/A"
408
+
409
+ hours = seconds // 3600
410
+ minutes = (seconds % 3600) // 60
411
+ seconds = seconds % 60
412
+
413
+ if hours > 0:
414
+ return f"{hours:02d}:{minutes:02d}:{seconds:02d}"
415
+ else:
416
+ return f"{minutes:02d}:{seconds:02d}"
417
+
418
+ def render_qa_section(self):
419
+ """Render question and answer section."""
420
+ if 'qa_chain' not in st.session_state or st.session_state.qa_chain is None:
421
+ st.info("👆 Please process a YouTube video first to start asking questions.")
422
+ return
423
+
424
+ st.markdown("### 💬 Ask Questions")
425
+
426
+ # Question input
427
+ col1, col2 = st.columns([4, 1])
428
+
429
+ with col1:
430
+ user_question = st.text_input(
431
+ "Your Question",
432
+ placeholder="Ask anything about the video content...",
433
+ key="user_question"
434
+ )
435
+
436
+ with col2:
437
+ st.markdown("<br>", unsafe_allow_html=True)
438
+ ask_button = st.button("🤔 Ask", type="primary")
439
+
440
+ if ask_button and user_question:
441
+ self.process_question(user_question)
442
+ elif ask_button and not user_question:
443
+ st.warning("⚠️ Please enter a question.")
444
+
445
+ def process_question(self, question: str):
446
+ """
447
+ Process user question and generate answer.
448
+
449
+ Args:
450
+ question (str): User question
451
+ """
452
+ with st.spinner("🤔 Thinking..."):
453
+ try:
454
+ result = self.text_processor.ask_question(st.session_state.qa_chain, question)
455
+
456
+ if result['success']:
457
+ # Display answer with dark theme
458
+ st.markdown("### 💡 Answer")
459
+ st.markdown(f"""
460
+ <div class="info-card" style="background: #2d3748 !important; border: 1px solid #4a5568 !important; color: #e9ecef !important;">
461
+ <p style="color: #e9ecef !important; margin: 0 !important; line-height: 1.6 !important;">{result['answer']}</p>
462
+ </div>
463
+ """, unsafe_allow_html=True)
464
+
465
+ # Add to chat history
466
+ video_id = st.session_state.get('current_video')
467
+ self.session_manager.add_to_chat_history(
468
+ question,
469
+ result['answer'],
470
+ video_id,
471
+ result.get('source_documents', [])
472
+ )
473
+
474
+ # Show source documents if available
475
+ if result.get('source_documents'):
476
+ with st.expander("📚 Source References"):
477
+ for i, doc in enumerate(result['source_documents'], 1):
478
+ st.markdown(f"**Reference {i}:**")
479
+ st.text(doc.page_content[:300] + "..." if len(doc.page_content) > 300 else doc.page_content)
480
+
481
+ else:
482
+ st.error(f"❌ {result['error']}")
483
+
484
+ except Exception as e:
485
+ logger.error(f"Error processing question: {e}")
486
+ st.error(f"❌ An error occurred while processing your question: {str(e)}")
487
+
488
+ def render_chat_history(self):
489
+ """Render chat history section."""
490
+ chat_history = self.session_manager.get_chat_history()
491
+
492
+ if not chat_history:
493
+ return
494
+
495
+ st.markdown("### 📜 Chat History")
496
+
497
+ # Limit displayed history
498
+ ui_config = settings.get_ui_config()
499
+ max_display = ui_config.get('max_chat_history_display', 50)
500
+ recent_history = chat_history[-max_display:] if len(chat_history) > max_display else chat_history
501
+
502
+ for entry in reversed(recent_history):
503
+ with st.expander(f"Q: {entry['question'][:50]}..." if len(entry['question']) > 50 else f"Q: {entry['question']}"):
504
+ st.markdown(f"**Question:** {entry['question']}")
505
+ st.markdown(f"**Answer:** {entry['answer']}")
506
+ st.markdown(f"**Time:** {datetime.fromisoformat(entry['timestamp']).strftime('%Y-%m-%d %H:%M:%S')}")
507
+
508
+ def export_chat_history(self, format: str):
509
+ """
510
+ Export chat history in specified format.
511
+
512
+ Args:
513
+ format (str): Export format (pdf, txt, json)
514
+ """
515
+ try:
516
+ chat_history = self.session_manager.get_chat_history()
517
+ video_metadata = st.session_state.get('video_metadata', {})
518
+
519
+ if format == 'pdf':
520
+ pdf_data = self.export_utils.export_to_pdf(chat_history, video_metadata)
521
+ if pdf_data:
522
+ st.download_button(
523
+ label="📥 Download PDF",
524
+ data=pdf_data,
525
+ file_name=f"chat_history_{datetime.now().strftime('%Y%m%d_%H%M%S')}.pdf",
526
+ mime="application/pdf"
527
+ )
528
+
529
+ elif format == 'txt':
530
+ text_data = self.export_utils.export_to_text(chat_history, video_metadata)
531
+ if text_data:
532
+ st.download_button(
533
+ label="📥 Download Text",
534
+ data=text_data,
535
+ file_name=f"chat_history_{datetime.now().strftime('%Y%m%d_%H%M%S')}.txt",
536
+ mime="text/plain"
537
+ )
538
+
539
+ elif format == 'json':
540
+ json_data = self.export_utils.export_to_json(chat_history, video_metadata)
541
+ if json_data:
542
+ st.download_button(
543
+ label="📥 Download JSON",
544
+ data=json_data,
545
+ file_name=f"chat_history_{datetime.now().strftime('%Y%m%d_%H%M%S')}.json",
546
+ mime="application/json"
547
+ )
548
+
549
+ except Exception as e:
550
+ logger.error(f"Error exporting chat history: {e}")
551
+ st.error(f"❌ Error exporting chat history: {str(e)}")
552
+
553
+ def download_transcript(self, transcript_text: str, metadata: dict):
554
+ """
555
+ Provide transcript download functionality.
556
+
557
+ Args:
558
+ transcript_text (str): Transcript text
559
+ metadata (dict): Video metadata
560
+ """
561
+ try:
562
+ transcript_export = self.export_utils.export_transcript(transcript_text, metadata, 'txt')
563
+
564
+ st.download_button(
565
+ label="📥 Download Transcript",
566
+ data=transcript_export,
567
+ file_name=f"transcript_{metadata.get('video_id', 'unknown')}_{datetime.now().strftime('%Y%m%d_%H%M%S')}.txt",
568
+ mime="text/plain"
569
+ )
570
+
571
+ except Exception as e:
572
+ logger.error(f"Error preparing transcript download: {e}")
573
+ st.error(f"❌ Error preparing transcript download: {str(e)}")
574
+
575
+ def run(self):
576
+ """Run the main application."""
577
+ try:
578
+ self.render_header()
579
+ self.render_sidebar()
580
+
581
+ # Main content area
582
+ self.render_video_input_section()
583
+
584
+ st.markdown("---")
585
+
586
+ self.render_qa_section()
587
+
588
+ st.markdown("---")
589
+
590
+ self.render_chat_history()
591
+
592
+ except Exception as e:
593
+ logger.error(f"Application error: {e}")
594
+ st.error(f"❌ An application error occurred: {str(e)}")
595
+
596
+ def main():
597
+ """Main function to run the application."""
598
+ app = YouTubeChatbotApp()
599
+ app.run()
600
 
601
+ if __name__ == "__main__":
602
+ main()