awacke1 commited on
Commit
9abe9e2
·
verified ·
1 Parent(s): faee485

Create app.py

Browse files
Files changed (1) hide show
  1. app.py +586 -0
app.py ADDED
@@ -0,0 +1,586 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import streamlit as st
2
+ import anthropic, openai, base64, cv2, glob, json, math, os, pytz, random, re, requests, time, zipfile
3
+ import plotly.graph_objects as go
4
+ import streamlit.components.v1 as components
5
+ from datetime import datetime
6
+ from audio_recorder_streamlit import audio_recorder
7
+ from bs4 import BeautifulSoup
8
+ from collections import defaultdict, deque
9
+ from dotenv import load_dotenv
10
+ from gradio_client import Client
11
+ from huggingface_hub import InferenceClient
12
+ from io import BytesIO
13
+ from PIL import Image
14
+ from PyPDF2 import PdfReader
15
+ from urllib.parse import quote
16
+ from xml.etree import ElementTree as ET
17
+ from openai import OpenAI
18
+ import extra_streamlit_components as stx
19
+ from streamlit.runtime.scriptrunner import get_script_run_ctx
20
+ import asyncio
21
+ import edge_tts
22
+
23
+ # 1. Core Configuration & Setup
24
+ st.set_page_config(
25
+ page_title="🚲BikeAI🏆 Research Assistant Pro",
26
+ page_icon="🚲🏆",
27
+ layout="wide",
28
+ initial_sidebar_state="auto",
29
+ menu_items={
30
+ 'Get Help': 'https://huggingface.co/awacke1',
31
+ 'Report a bug': 'https://huggingface.co/spaces/awacke1',
32
+ 'About': "Research Assistant Pro with Voice Search"
33
+ }
34
+ )
35
+ load_dotenv()
36
+
37
+ # 2. API Setup & Clients
38
+ openai_api_key = os.getenv('OPENAI_API_KEY', st.secrets.get('OPENAI_API_KEY', ''))
39
+ anthropic_key = os.getenv('ANTHROPIC_API_KEY_3', st.secrets.get('ANTHROPIC_API_KEY', ''))
40
+ hf_key = os.getenv('HF_KEY', st.secrets.get('HF_KEY', ''))
41
+
42
+ openai_client = OpenAI(api_key=openai_api_key)
43
+ claude_client = anthropic.Anthropic(api_key=anthropic_key)
44
+
45
+ # 3. Session State Management
46
+ if 'transcript_history' not in st.session_state:
47
+ st.session_state['transcript_history'] = []
48
+ if 'chat_history' not in st.session_state:
49
+ st.session_state['chat_history'] = []
50
+ if 'openai_model' not in st.session_state:
51
+ st.session_state['openai_model'] = "gpt-4-vision-preview"
52
+ if 'messages' not in st.session_state:
53
+ st.session_state['messages'] = []
54
+ if 'last_voice_input' not in st.session_state:
55
+ st.session_state['last_voice_input'] = ""
56
+ if 'editing_file' not in st.session_state:
57
+ st.session_state['editing_file'] = None
58
+ if 'current_audio' not in st.session_state:
59
+ st.session_state['current_audio'] = None
60
+ if 'autoplay_audio' not in st.session_state:
61
+ st.session_state['autoplay_audio'] = True
62
+ if 'should_rerun' not in st.session_state:
63
+ st.session_state['should_rerun'] = False
64
+ if 'old_val' not in st.session_state:
65
+ st.session_state['old_val'] = None
66
+
67
+ # 4. Style Definitions
68
+ st.markdown("""
69
+ <style>
70
+ .main { background: linear-gradient(to right, #1a1a1a, #2d2d2d); color: #fff; }
71
+ .stMarkdown { font-family: 'Helvetica Neue', sans-serif; }
72
+ .stButton>button {
73
+ margin-right: 0.5rem;
74
+ background-color: #4CAF50;
75
+ color: white;
76
+ padding: 0.5rem 1rem;
77
+ border-radius: 5px;
78
+ border: none;
79
+ transition: background-color 0.3s;
80
+ }
81
+ .stButton>button:hover {
82
+ background-color: #45a049;
83
+ }
84
+ .audio-player {
85
+ margin: 1rem 0;
86
+ padding: 1rem;
87
+ border-radius: 10px;
88
+ background: white;
89
+ box-shadow: 0 2px 4px rgba(0,0,0,0.1);
90
+ }
91
+ .file-manager {
92
+ padding: 1rem;
93
+ background: white;
94
+ border-radius: 10px;
95
+ margin: 1rem 0;
96
+ }
97
+ </style>
98
+ """, unsafe_allow_html=True)
99
+
100
+ FILE_EMOJIS = {
101
+ "md": "📝",
102
+ "mp3": "🎵",
103
+ "mp4": "🎥",
104
+ "png": "🖼️",
105
+ "jpg": "📸"
106
+ }
107
+
108
+ # 5. Voice Recognition Component
109
+ def create_voice_component():
110
+ """Create auto-starting voice recognition component"""
111
+ return components.html(
112
+ """
113
+ <div style="padding: 20px; border-radius: 10px; background: #f0f2f6;">
114
+ <div id="status">Initializing voice recognition...</div>
115
+ <div id="output" style="margin-top: 10px; padding: 10px; min-height: 100px;
116
+ background: white; border-radius: 5px; white-space: pre-wrap;"></div>
117
+ <script>
118
+ if ('webkitSpeechRecognition' in window) {
119
+ const recognition = new webkitSpeechRecognition();
120
+ recognition.continuous = true;
121
+ recognition.interimResults = true;
122
+
123
+ const status = document.getElementById('status');
124
+ const output = document.getElementById('output');
125
+ let fullTranscript = '';
126
+
127
+ // Auto-start on load
128
+ window.addEventListener('load', () => {
129
+ setTimeout(() => {
130
+ try {
131
+ recognition.start();
132
+ status.textContent = 'Listening...';
133
+ } catch (e) {
134
+ console.error('Start error:', e);
135
+ status.textContent = 'Error starting recognition';
136
+ }
137
+ }, 1000);
138
+ });
139
+
140
+ recognition.onresult = (event) => {
141
+ let interimTranscript = '';
142
+ let finalTranscript = '';
143
+
144
+ for (let i = event.resultIndex; i < event.results.length; i++) {
145
+ const transcript = event.results[i][0].transcript;
146
+ if (event.results[i].isFinal) {
147
+ finalTranscript += transcript + '\\n';
148
+ } else {
149
+ interimTranscript += transcript;
150
+ }
151
+ }
152
+
153
+ if (finalTranscript) {
154
+ fullTranscript += finalTranscript;
155
+ window.parent.postMessage({
156
+ type: 'streamlit:setComponentValue',
157
+ value: fullTranscript,
158
+ dataType: 'json',
159
+ }, '*');
160
+ }
161
+
162
+ output.textContent = fullTranscript + (interimTranscript ? '... ' + interimTranscript : '');
163
+ output.scrollTop = output.scrollHeight;
164
+ };
165
+
166
+ recognition.onend = () => {
167
+ try {
168
+ recognition.start();
169
+ status.textContent = 'Listening...';
170
+ } catch (e) {
171
+ console.error('Restart error:', e);
172
+ status.textContent = 'Recognition stopped. Refresh to restart.';
173
+ }
174
+ };
175
+
176
+ recognition.onerror = (event) => {
177
+ console.error('Recognition error:', event.error);
178
+ status.textContent = 'Error: ' + event.error;
179
+ };
180
+ } else {
181
+ document.getElementById('status').textContent = 'Speech recognition not supported in this browser';
182
+ }
183
+ </script>
184
+ </div>
185
+ """,
186
+ height=200
187
+ )
188
+
189
+ # 6. Audio Processing Functions
190
+ def get_autoplay_audio_html(audio_path, width="100%"):
191
+ """Create HTML for autoplaying audio with controls"""
192
+ try:
193
+ with open(audio_path, "rb") as audio_file:
194
+ audio_bytes = audio_file.read()
195
+ audio_b64 = base64.b64encode(audio_bytes).decode()
196
+ return f'''
197
+ <audio controls autoplay style="width: {width};">
198
+ <source src="data:audio/mpeg;base64,{audio_b64}" type="audio/mpeg">
199
+ Your browser does not support the audio element.
200
+ </audio>
201
+ <div style="margin-top: 5px;">
202
+ <a href="data:audio/mpeg;base64,{audio_b64}"
203
+ download="{os.path.basename(audio_path)}"
204
+ style="text-decoration: none;">
205
+ ⬇️ Download Audio
206
+ </a>
207
+ </div>
208
+ '''
209
+ except Exception as e:
210
+ return f"Error loading audio: {str(e)}"
211
+
212
+ def clean_for_speech(text: str) -> str:
213
+ """Clean text for speech synthesis"""
214
+ text = text.replace("\n", " ")
215
+ text = text.replace("</s>", " ")
216
+ text = text.replace("#", "")
217
+ text = re.sub(r"\(https?:\/\/[^\)]+\)", "", text)
218
+ text = re.sub(r"\s+", " ", text).strip()
219
+ return text
220
+
221
+ async def generate_audio(text, voice="en-US-AriaNeural", rate="+0%", pitch="+0Hz"):
222
+ """Generate audio using Edge TTS"""
223
+ text = clean_for_speech(text)
224
+ if not text.strip():
225
+ return None
226
+
227
+ timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
228
+ output_file = f"response_{timestamp}.mp3"
229
+
230
+ communicate = edge_tts.Communicate(text, voice, rate=rate, pitch=pitch)
231
+ await communicate.save(output_file)
232
+
233
+ return output_file
234
+
235
+ def render_audio_result(audio_file, title="Generated Audio"):
236
+ """Render audio result with autoplay in Streamlit"""
237
+ if audio_file and os.path.exists(audio_file):
238
+ st.markdown(f"### {title}")
239
+ st.markdown(get_autoplay_audio_html(audio_file), unsafe_allow_html=True)
240
+
241
+ # 7. File Operations
242
+ def generate_filename(text, response="", file_type="md"):
243
+ """Generate intelligent filename"""
244
+ timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
245
+ safe_text = re.sub(r'[^\w\s-]', '', text[:50])
246
+ return f"{timestamp}_{safe_text}.{file_type}"
247
+
248
+ def create_file(text, response, file_type="md"):
249
+ """Create file with content"""
250
+ filename = generate_filename(text, response, file_type)
251
+ with open(filename, 'w', encoding='utf-8') as f:
252
+ f.write(f"{text}\n\n{response}")
253
+ return filename
254
+
255
+ def get_download_link(file_path):
256
+ """Generate download link for file"""
257
+ with open(file_path, "rb") as file:
258
+ contents = file.read()
259
+ b64 = base64.b64encode(contents).decode()
260
+ file_name = os.path.basename(file_path)
261
+ return f'<a href="data:file/txt;base64,{b64}" download="{file_name}">⬇️ Download {file_name}</a>'
262
+
263
+ # 8. Search and Process Functions
264
+ def perform_arxiv_search(query, response_type="summary"):
265
+ """Enhanced Arxiv search with voice response"""
266
+ client = Client("awacke1/Arxiv-Paper-Search-And-QA-RAG-Pattern")
267
+
268
+ # Get search results and AI interpretation
269
+ refs = client.predict(
270
+ query, 20, "Semantic Search",
271
+ "mistralai/Mixtral-8x7B-Instruct-v0.1",
272
+ api_name="/update_with_rag_md"
273
+ )[0]
274
+
275
+ summary = client.predict(
276
+ query,
277
+ "mistralai/Mixtral-8x7B-Instruct-v0.1",
278
+ True,
279
+ api_name="/ask_llm"
280
+ )
281
+
282
+ # Format response
283
+ response = f"### 🔎 Search Results for: {query}\n\n{summary}\n\n### 📚 References\n\n{refs}"
284
+
285
+ return response, refs
286
+
287
+ async def process_voice_search(query):
288
+ """Process voice search with automatic audio"""
289
+ response, refs = perform_arxiv_search(query)
290
+
291
+ # Generate audio from response
292
+ audio_file = await generate_audio(response)
293
+
294
+ # Update state
295
+ st.session_state.current_audio = audio_file
296
+
297
+ return response, audio_file
298
+
299
+ def process_with_gpt(text):
300
+ """Process text with GPT-4"""
301
+ if not text:
302
+ return
303
+
304
+ st.session_state.messages.append({"role": "user", "content": text})
305
+
306
+ with st.chat_message("user"):
307
+ st.markdown(text)
308
+
309
+ with st.chat_message("assistant"):
310
+ response = openai_client.chat.completions.create(
311
+ model=st.session_state.openai_model,
312
+ messages=st.session_state.messages,
313
+ stream=False
314
+ )
315
+
316
+ answer = response.choices[0].message.content
317
+ st.write(f"GPT-4: {answer}")
318
+
319
+ # Generate audio response
320
+ audio_file = asyncio.run(generate_audio(answer))
321
+ if audio_file:
322
+ render_audio_result(audio_file, "GPT-4 Response")
323
+
324
+ # Save response
325
+ create_file(text, answer, "md")
326
+ st.session_state.messages.append({"role": "assistant", "content": answer})
327
+
328
+ return answer
329
+
330
+ def process_with_claude(text):
331
+ """Process text with Claude"""
332
+ if not text:
333
+ return
334
+
335
+ with st.chat_message("user"):
336
+ st.markdown(text)
337
+
338
+ with st.chat_message("assistant"):
339
+ response = claude_client.messages.create(
340
+ model="claude-3-sonnet-20240229",
341
+ max_tokens=1000,
342
+ messages=[{"role": "user", "content": text}]
343
+ )
344
+
345
+ answer = response.content[0].text
346
+ st.write(f"Claude-3: {answer}")
347
+
348
+ # Generate audio response
349
+ audio_file = asyncio.run(generate_audio(answer))
350
+ if audio_file:
351
+ render_audio_result(audio_file, "Claude Response")
352
+
353
+ # Save response
354
+ create_file(text, answer, "md")
355
+ st.session_state.chat_history.append({"user": text, "claude": answer})
356
+
357
+ return answer
358
+
359
+ # 9. UI Components
360
+ def render_search_interface():
361
+ """Render main search interface"""
362
+ st.header("🔍 Voice Search")
363
+
364
+ # Voice component with autorun
365
+ voice_component = create_voice_component()
366
+
367
+ if voice_component:
368
+ voice_text = voice_component
369
+ if voice_text and voice_text != st.session_state.get('last_voice_text', ''):
370
+ st.session_state.last_voice_text = voice_text
371
+
372
+ # Process with selected model
373
+ if st.session_state.autoplay_audio:
374
+ response, audio_file = asyncio.run(process_voice_search(voice_text.strip()))
375
+ if response:
376
+ st.markdown(response)
377
+ if audio_file:
378
+ render_audio_result(audio_file, "Search Results")
379
+
380
+ # Manual search option
381
+ with st.expander("📝 Manual Search", expanded=False):
382
+ col1, col2 = st.columns([3, 1])
383
+ with col1:
384
+ query = st.text_input("Enter search query:")
385
+ with col2:
386
+ if st.button("🔍 Search"):
387
+ response, audio_file = asyncio.run(process_voice_search(query))
388
+ if response:
389
+ st.markdown(response)
390
+ if audio_file:
391
+ render_audio_result(audio_file)
392
+
393
+ def display_file_manager():
394
+ """Display file manager with media preview"""
395
+ st.sidebar.title("📁 File Manager")
396
+
397
+ files = {
398
+ 'Documents': glob.glob("*.md"),
399
+ 'Audio': glob.glob("*.mp3"),
400
+ 'Video': glob.glob("*.mp4"),
401
+ 'Images': glob.glob("*.png") + glob.glob("*.jpg")
402
+ }
403
+
404
+ # Top actions
405
+ col1, col2 = st.sidebar.columns(2)
406
+ with col1:
407
+ if st.button("🗑 Delete All"):
408
+ for category in files.values():
409
+ for file in category:
410
+ os.remove(file)
411
+ st.rerun()
412
+
413
+ with col2:
414
+ if st.button("⬇️ Download All"):
415
+ zip_name = f"archive_{datetime.now().strftime('%Y%m%d_%H%M%S')}.zip"
416
+ with zipfile.ZipFile(zip_name, 'w') as zipf:
417
+ for category in files.values():
418
+ for file in category:
419
+ zipf.write(file)
420
+ st.sidebar.markdown(get_download_link(zip_name), unsafe_allow_html=True)
421
+
422
+ # Display files by category
423
+ for category, category_files in files.items():
424
+ if category_files:
425
+ with st.sidebar.expander(f"{FILE_EMOJIS.get(category.lower(), '📄')} {category} ({len(category_files)})", expanded=True):
426
+ for file in sorted(category_files, key=os.path.getmtime, reverse=True):
427
+ col1, col2, col3 = st.columns([3, 1, 1])
428
+ with col1:
429
+ st.markdown(f"**{os.path.basename(file)}**")
430
+ with col2:
431
+ st.markdown(get_download_link(file), unsafe_allow_html=True)
432
+ with col3:
433
+ if st.button("🗑", key=f"del_{file}"):
434
+ os.remove(file)
435
+ st.rerun()
436
+
437
+ def display_media_gallery():
438
+ """Display media files in gallery format"""
439
+ media_tabs = st.tabs(["🎵 Audio", "🎥 Video", "📷 Images"])
440
+
441
+ with media_tabs[0]:
442
+ audio_files = glob.glob("*.mp3")
443
+ if audio_files:
444
+ for audio_file in audio_files:
445
+ st.markdown(get_autoplay_audio_html(audio_file), unsafe_allow_html=True)
446
+ else:
447
+ st.write("No audio files found")
448
+
449
+ with media_tabs[1]:
450
+ video_files = glob.glob("*.mp4")
451
+ if video_files:
452
+ cols = st.columns(2)
453
+ for idx, video_file in enumerate(video_files):
454
+ with cols[idx % 2]:
455
+ st.video(video_file)
456
+ else:
457
+ st.write("No video files found")
458
+
459
+ with media_tabs[2]:
460
+ image_files = glob.glob("*.png") + glob.glob("*.jpg")
461
+ if image_files:
462
+ cols = st.columns(3)
463
+ for idx, image_file in enumerate(image_files):
464
+ with cols[idx % 3]:
465
+ st.image(Image.open(image_file), use_column_width=True)
466
+ if st.button(f"Analyze {os.path.basename(image_file)}", key=f"analyze_{image_file}"):
467
+ with st.spinner("Analyzing image..."):
468
+ analysis = process_with_gpt(f"Analyze this image: {image_file}")
469
+ st.markdown(analysis)
470
+ else:
471
+ st.write("No images found")
472
+
473
+ def display_search_history():
474
+ """Display search history with audio playback"""
475
+ st.header("Search History")
476
+
477
+ history_tabs = st.tabs(["🔍 Voice Searches", "💬 Chat History"])
478
+
479
+ with history_tabs[0]:
480
+ for entry in reversed(st.session_state.transcript_history):
481
+ with st.expander(f"🔍 {entry['timestamp']} - {entry['query'][:50]}...", expanded=False):
482
+ st.markdown(entry['response'])
483
+ if entry.get('audio'):
484
+ render_audio_result(entry['audio'], "Recorded Response")
485
+
486
+ with history_tabs[1]:
487
+ chat_tabs = st.tabs(["Claude History", "GPT-4 History"])
488
+ with chat_tabs[0]:
489
+ for chat in st.session_state.chat_history:
490
+ st.markdown(f"**You:** {chat['user']}")
491
+ st.markdown(f"**Claude:** {chat['claude']}")
492
+ st.markdown("---")
493
+ with chat_tabs[1]:
494
+ for msg in st.session_state.messages:
495
+ with st.chat_message(msg["role"]):
496
+ st.markdown(msg["content"])
497
+
498
+ # Main Application
499
+ def main():
500
+ st.title("🔬 Research Assistant Pro")
501
+
502
+ # Initialize autorun setting
503
+ if 'autorun' not in st.session_state:
504
+ st.session_state.autorun = True
505
+
506
+ # Settings sidebar
507
+ with st.sidebar:
508
+ st.title("⚙️ Settings")
509
+ st.session_state.autorun = st.checkbox("Enable Autorun", value=True)
510
+
511
+ st.subheader("Voice Settings")
512
+ voice_options = [
513
+ "en-US-AriaNeural",
514
+ "en-US-GuyNeural",
515
+ "en-GB-SoniaNeural",
516
+ "en-AU-NatashaNeural"
517
+ ]
518
+ selected_voice = st.selectbox("Select Voice", voice_options)
519
+
520
+ st.subheader("Audio Settings")
521
+ rate = st.slider("Speech Rate", -50, 50, 0, 5)
522
+ pitch = st.slider("Pitch", -50, 50, 0, 5)
523
+
524
+ st.session_state.autoplay_audio = st.checkbox(
525
+ "Autoplay Audio",
526
+ value=True,
527
+ help="Automatically play audio when generated"
528
+ )
529
+
530
+ # Main content tabs
531
+ tabs = st.tabs(["🎤 Voice Search", "📚 History", "🎵 Media", "⚙️ Advanced"])
532
+
533
+ with tabs[0]:
534
+ render_search_interface()
535
+
536
+ with tabs[1]:
537
+ display_search_history()
538
+
539
+ with tabs[2]:
540
+ display_media_gallery()
541
+
542
+ with tabs[3]:
543
+ st.header("Advanced Settings")
544
+
545
+ col1, col2 = st.columns(2)
546
+ with col1:
547
+ st.subheader("Model Settings")
548
+ st.selectbox(
549
+ "Default Search Model",
550
+ ["Claude-3", "GPT-4", "Mixtral-8x7B"],
551
+ key="default_model"
552
+ )
553
+ st.number_input(
554
+ "Max Results",
555
+ min_value=5,
556
+ max_value=50,
557
+ value=20,
558
+ key="max_results"
559
+ )
560
+
561
+ with col2:
562
+ st.subheader("Audio Settings")
563
+ st.slider(
564
+ "Max Audio Duration (seconds)",
565
+ min_value=30,
566
+ max_value=300,
567
+ value=120,
568
+ step=30,
569
+ key="max_audio_duration"
570
+ )
571
+ st.checkbox(
572
+ "High Quality Audio",
573
+ value=True,
574
+ key="high_quality_audio"
575
+ )
576
+
577
+ # File manager sidebar
578
+ display_file_manager()
579
+
580
+ # Handle rerun if needed
581
+ if st.session_state.get('should_rerun', False):
582
+ st.session_state.should_rerun = False
583
+ st.rerun()
584
+
585
+ if __name__ == "__main__":
586
+ main()