awacke1 commited on
Commit
3cbe462
·
verified ·
1 Parent(s): 4c73241

Create app.py

Browse files
Files changed (1) hide show
  1. app.py +789 -0
app.py ADDED
@@ -0,0 +1,789 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import streamlit as st
2
+ import anthropic
3
+ import openai
4
+ import base64
5
+ import cv2
6
+ import glob
7
+ import json
8
+ import math
9
+ import os
10
+ import pytz
11
+ import random
12
+ import re
13
+ import requests
14
+ import textract
15
+ import time
16
+ import zipfile
17
+ import plotly.graph_objects as go
18
+ import streamlit.components.v1 as components
19
+ from datetime import datetime
20
+ from audio_recorder_streamlit import audio_recorder
21
+ from bs4 import BeautifulSoup
22
+ from collections import defaultdict, deque
23
+ from dotenv import load_dotenv
24
+ from gradio_client import Client
25
+ from huggingface_hub import InferenceClient
26
+ from io import BytesIO
27
+ from PIL import Image
28
+ from PyPDF2 import PdfReader
29
+ from urllib.parse import quote
30
+ from xml.etree import ElementTree as ET
31
+ from openai import OpenAI
32
+ import extra_streamlit_components as stx
33
+ from streamlit.runtime.scriptrunner import get_script_run_ctx
34
+ import asyncio
35
+ import edge_tts
36
+
37
+ # 🎯 1. Core Configuration & Setup
38
+ st.set_page_config(
39
+ page_title="🚲BikeAI🏆 Claude/GPT Research",
40
+ page_icon="🚲🏆",
41
+ layout="wide",
42
+ initial_sidebar_state="auto",
43
+ menu_items={
44
+ 'Get Help': 'https://huggingface.co/awacke1',
45
+ 'Report a bug': 'https://huggingface.co/spaces/awacke1',
46
+ 'About': "🚲BikeAI🏆 Claude/GPT Research AI"
47
+ }
48
+ )
49
+ load_dotenv()
50
+
51
+ # 🔑 2. API Setup & Clients
52
+ openai_api_key = os.getenv('OPENAI_API_KEY', "")
53
+ anthropic_key = os.getenv('ANTHROPIC_API_KEY_3', "")
54
+ xai_key = os.getenv('xai',"")
55
+ if 'OPENAI_API_KEY' in st.secrets:
56
+ openai_api_key = st.secrets['OPENAI_API_KEY']
57
+ if 'ANTHROPIC_API_KEY' in st.secrets:
58
+ anthropic_key = st.secrets["ANTHROPIC_API_KEY"]
59
+
60
+ openai.api_key = openai_api_key
61
+ claude_client = anthropic.Anthropic(api_key=anthropic_key)
62
+ openai_client = OpenAI(api_key=openai.api_key, organization=os.getenv('OPENAI_ORG_ID'))
63
+ HF_KEY = os.getenv('HF_KEY')
64
+ API_URL = os.getenv('API_URL')
65
+
66
+ # 📝 3. Session State Management
67
+ if 'transcript_history' not in st.session_state:
68
+ st.session_state['transcript_history'] = []
69
+ if 'chat_history' not in st.session_state:
70
+ st.session_state['chat_history'] = []
71
+ if 'openai_model' not in st.session_state:
72
+ st.session_state['openai_model'] = "gpt-4o-2024-05-13"
73
+ if 'messages' not in st.session_state:
74
+ st.session_state['messages'] = []
75
+ if 'last_voice_input' not in st.session_state:
76
+ st.session_state['last_voice_input'] = ""
77
+ if 'editing_file' not in st.session_state:
78
+ st.session_state['editing_file'] = None
79
+ if 'edit_new_name' not in st.session_state:
80
+ st.session_state['edit_new_name'] = ""
81
+ if 'edit_new_content' not in st.session_state:
82
+ st.session_state['edit_new_content'] = ""
83
+ if 'viewing_prefix' not in st.session_state:
84
+ st.session_state['viewing_prefix'] = None
85
+ if 'should_rerun' not in st.session_state:
86
+ st.session_state['should_rerun'] = False
87
+ if 'old_val' not in st.session_state:
88
+ st.session_state['old_val'] = None
89
+
90
+ # 🎨 4. Custom CSS
91
+ st.markdown("""
92
+ <style>
93
+ .main { background: linear-gradient(to right, #1a1a1a, #2d2d2d); color: #fff; }
94
+ .stMarkdown { font-family: 'Helvetica Neue', sans-serif; }
95
+ .stButton>button {
96
+ margin-right: 0.5rem;
97
+ }
98
+ </style>
99
+ """, unsafe_allow_html=True)
100
+
101
+ FILE_EMOJIS = {
102
+ "md": "📝",
103
+ "mp3": "🎵",
104
+ }
105
+
106
+ # 🧠 5. High-Information Content Extraction
107
+ def get_high_info_terms(text: str) -> list:
108
+ """Extract high-information terms from text, including key phrases."""
109
+ stop_words = set([
110
+ 'the', 'a', 'an', 'and', 'or', 'but', 'in', 'on', 'at', 'to', 'for', 'of', 'with',
111
+ 'by', 'from', 'up', 'about', 'into', 'over', 'after', 'is', 'are', 'was', 'were',
112
+ 'be', 'been', 'being', 'have', 'has', 'had', 'do', 'does', 'did', 'will', 'would',
113
+ 'should', 'could', 'might', 'must', 'shall', 'can', 'may', 'this', 'that', 'these',
114
+ 'those', 'i', 'you', 'he', 'she', 'it', 'we', 'they', 'what', 'which', 'who',
115
+ 'when', 'where', 'why', 'how', 'all', 'any', 'both', 'each', 'few', 'more', 'most',
116
+ 'other', 'some', 'such', 'than', 'too', 'very', 'just', 'there'
117
+ ])
118
+
119
+ key_phrases = [
120
+ 'artificial intelligence', 'machine learning', 'deep learning', 'neural network',
121
+ 'personal assistant', 'natural language', 'computer vision', 'data science',
122
+ 'reinforcement learning', 'knowledge graph', 'semantic search', 'time series',
123
+ 'large language model', 'transformer model', 'attention mechanism',
124
+ 'autonomous system', 'edge computing', 'quantum computing', 'blockchain technology',
125
+ 'cognitive science', 'human computer', 'decision making', 'arxiv search',
126
+ 'research paper', 'scientific study', 'empirical analysis'
127
+ ]
128
+
129
+ # Identify key phrases
130
+ preserved_phrases = []
131
+ lower_text = text.lower()
132
+ for phrase in key_phrases:
133
+ if phrase in lower_text:
134
+ preserved_phrases.append(phrase)
135
+ text = text.replace(phrase, '')
136
+
137
+ # Extract individual words
138
+ words = re.findall(r'\b\w+(?:-\w+)*\b', text)
139
+ high_info_words = [
140
+ word.lower() for word in words
141
+ if len(word) > 3
142
+ and word.lower() not in stop_words
143
+ and not word.isdigit()
144
+ and any(c.isalpha() for c in word)
145
+ ]
146
+
147
+ all_terms = preserved_phrases + high_info_words
148
+ seen = set()
149
+ unique_terms = []
150
+ for term in all_terms:
151
+ if term not in seen:
152
+ seen.add(term)
153
+ unique_terms.append(term)
154
+
155
+ max_terms = 5
156
+ return unique_terms[:max_terms]
157
+
158
+ def clean_text_for_filename(text: str) -> str:
159
+ """Remove punctuation and short filler words, return a compact string."""
160
+ text = text.lower()
161
+ text = re.sub(r'[^\w\s-]', '', text)
162
+ words = text.split()
163
+ stop_short = set(['the','and','for','with','this','that','from','just','very','then','been','only','also','about'])
164
+ filtered = [w for w in words if len(w)>3 and w not in stop_short]
165
+ return '_'.join(filtered)[:200]
166
+
167
+ # 📁 6. File Operations
168
+ def generate_filename(prompt, response, file_type="md"):
169
+ """
170
+ Generate filename with meaningful terms and short dense clips from prompt & response.
171
+ The filename should be about 150 chars total, include high-info terms, and a clipped snippet.
172
+ """
173
+ prefix = datetime.now().strftime("%y%m_%H%M") + "_"
174
+ combined = (prompt + " " + response).strip()
175
+ info_terms = get_high_info_terms(combined)
176
+
177
+ # Include a short snippet from prompt and response
178
+ snippet = (prompt[:100] + " " + response[:100]).strip()
179
+ snippet_cleaned = clean_text_for_filename(snippet)
180
+
181
+ # Combine info terms and snippet
182
+ name_parts = info_terms + [snippet_cleaned]
183
+ full_name = '_'.join(name_parts)
184
+
185
+ # Trim to ~150 chars
186
+ if len(full_name) > 150:
187
+ full_name = full_name[:150]
188
+
189
+ filename = f"{prefix}{full_name}.{file_type}"
190
+ return filename
191
+
192
+ def create_file(prompt, response, file_type="md"):
193
+ """Create file with intelligent naming"""
194
+ filename = generate_filename(prompt.strip(), response.strip(), file_type)
195
+ with open(filename, 'w', encoding='utf-8') as f:
196
+ f.write(prompt + "\n\n" + response)
197
+ return filename
198
+
199
+ def get_download_link(file):
200
+ """Generate download link for file"""
201
+ with open(file, "rb") as f:
202
+ b64 = base64.b64encode(f.read()).decode()
203
+ return f'<a href="data:file/zip;base64,{b64}" download="{os.path.basename(file)}">📂 Download {os.path.basename(file)}</a>'
204
+
205
+ # 🔊 7. Audio Processing
206
+ def clean_for_speech(text: str) -> str:
207
+ """Clean text for speech synthesis"""
208
+ text = text.replace("\n", " ")
209
+ text = text.replace("</s>", " ")
210
+ text = text.replace("#", "")
211
+ text = re.sub(r"\(https?:\/\/[^\)]+\)", "", text)
212
+ text = re.sub(r"\s+", " ", text).strip()
213
+ return text
214
+
215
+ @st.cache_resource
216
+ def speech_synthesis_html(result):
217
+ """Create HTML for speech synthesis"""
218
+ html_code = f"""
219
+ <html><body>
220
+ <script>
221
+ var msg = new SpeechSynthesisUtterance("{result.replace('"', '')}");
222
+ window.speechSynthesis.speak(msg);
223
+ </script>
224
+ </body></html>
225
+ """
226
+ components.html(html_code, height=0)
227
+
228
+ async def edge_tts_generate_audio(text, voice="en-US-AriaNeural", rate=0, pitch=0):
229
+ """Generate audio using Edge TTS"""
230
+ text = clean_for_speech(text)
231
+ if not text.strip():
232
+ return None
233
+ rate_str = f"{rate:+d}%"
234
+ pitch_str = f"{pitch:+d}Hz"
235
+ communicate = edge_tts.Communicate(text, voice, rate=rate_str, pitch=pitch_str)
236
+ out_fn = generate_filename(text, text, "mp3")
237
+ await communicate.save(out_fn)
238
+ return out_fn
239
+
240
+ def speak_with_edge_tts(text, voice="en-US-AriaNeural", rate=0, pitch=0):
241
+ """Wrapper for edge TTS generation"""
242
+ return asyncio.run(edge_tts_generate_audio(text, voice, rate, pitch))
243
+
244
+ def play_and_download_audio(file_path):
245
+ """Play and provide download link for audio"""
246
+ if file_path and os.path.exists(file_path):
247
+ st.audio(file_path)
248
+ dl_link = f'<a href="data:audio/mpeg;base64,{base64.b64encode(open(file_path,"rb").read()).decode()}" download="{os.path.basename(file_path)}">Download {os.path.basename(file_path)}</a>'
249
+ st.markdown(dl_link, unsafe_allow_html=True)
250
+
251
+ # NEW: Helper to embed auto-play audio
252
+ def auto_play_audio(file_path):
253
+ """
254
+ Reads MP3 file as base64, displays an <audio> tag with autoplay + controls + download link.
255
+ Note: Some browsers block audio autoplay if there's no user interaction.
256
+ """
257
+ if not file_path or not os.path.exists(file_path):
258
+ return
259
+ with open(file_path, "rb") as f:
260
+ b64_data = base64.b64encode(f.read()).decode("utf-8")
261
+ filename = os.path.basename(file_path)
262
+ st.markdown(f"""
263
+ <audio controls autoplay>
264
+ <source src="data:audio/mpeg;base64,{b64_data}" type="audio/mpeg">
265
+ Your browser does not support the audio element.
266
+ </audio>
267
+ <br/>
268
+ <a href="data:audio/mpeg;base64,{b64_data}" download="{filename}">
269
+ Download {filename}
270
+ </a>
271
+ """, unsafe_allow_html=True)
272
+
273
+ # NEW: Generate specialized MP3 filename using query + paper metadata
274
+ def generate_audio_filename(query, title, summary):
275
+ combined = (query + " " + title + " " + summary).strip().lower()
276
+ combined = re.sub(r'[^\w\s-]', '', combined) # remove special chars
277
+ combined = "_".join(combined.split())[:80] # max ~80 chars to keep it short
278
+ prefix = datetime.now().strftime("%y%m_%H%M")
279
+ return f"{prefix}_{combined}.mp3"
280
+
281
+ # 🎬 8. Media Processing
282
+ def process_image(image_path, user_prompt):
283
+ """Process image with GPT-4V"""
284
+ with open(image_path, "rb") as imgf:
285
+ image_data = imgf.read()
286
+ b64img = base64.b64encode(image_data).decode("utf-8")
287
+ resp = openai_client.chat.completions.create(
288
+ model=st.session_state["openai_model"],
289
+ messages=[
290
+ {"role": "system", "content": "You are a helpful assistant."},
291
+ {
292
+ "role": "user",
293
+ "content": [
294
+ {"type": "text", "text": user_prompt},
295
+ {"type": "image_url", "image_url": {"url": f"data:image/png;base64,{b64img}"}}
296
+ ]
297
+ }
298
+ ],
299
+ temperature=0.0,
300
+ )
301
+ return resp.choices[0].message.content
302
+
303
+ def process_audio(audio_path):
304
+ """Process audio with Whisper"""
305
+ with open(audio_path, "rb") as f:
306
+ transcription = openai_client.audio.transcriptions.create(model="whisper-1", file=f)
307
+ st.session_state.messages.append({"role": "user", "content": transcription.text})
308
+ return transcription.text
309
+
310
+ def process_video(video_path, seconds_per_frame=1):
311
+ """Extract frames from video"""
312
+ vid = cv2.VideoCapture(video_path)
313
+ total = int(vid.get(cv2.CAP_PROP_FRAME_COUNT))
314
+ fps = vid.get(cv2.CAP_PROP_FPS)
315
+ skip = int(fps * seconds_per_frame)
316
+ frames_b64 = []
317
+ for i in range(0, total, skip):
318
+ vid.set(cv2.CAP_PROP_POS_FRAMES, i)
319
+ ret, frame = vid.read()
320
+ if not ret:
321
+ break
322
+ _, buf = cv2.imencode(".jpg", frame)
323
+ frames_b64.append(base64.b64encode(buf).decode("utf-8"))
324
+ vid.release()
325
+ return frames_b64
326
+
327
+ def process_video_with_gpt(video_path, prompt):
328
+ """Analyze video frames with GPT-4V"""
329
+ frames = process_video(video_path)
330
+ resp = openai_client.chat.completions.create(
331
+ model=st.session_state["openai_model"],
332
+ messages=[
333
+ {"role": "system", "content": "Analyze video frames."},
334
+ {
335
+ "role": "user",
336
+ "content": [
337
+ {"type": "text","text": prompt},
338
+ *[{"type":"image_url","image_url":{"url":f"data:image/jpeg;base64,{fr}"}} for fr in frames]
339
+ ]
340
+ }
341
+ ]
342
+ )
343
+ return resp.choices[0].message.content
344
+
345
+ # 🤖 9. AI Model Integration
346
+
347
+ def save_full_transcript(query, text):
348
+ """Save full transcript of Arxiv results as a file."""
349
+ create_file(query, text, "md")
350
+
351
+ # NEW: parse references with bracketed titles, up to 20
352
+ def parse_arxiv_refs(ref_text: str):
353
+ lines = ref_text.split('\n')
354
+ results = []
355
+ for line in lines:
356
+ line = line.strip()
357
+ if not line:
358
+ continue
359
+ # bracketed title
360
+ title_match = re.search(r"\[([^\]]+)\]", line)
361
+ if title_match:
362
+ raw_title = title_match.group(1).strip()
363
+ else:
364
+ raw_title = "No Title"
365
+ remainder = line.replace(title_match.group(0), "").strip() if title_match else line
366
+ summary = remainder
367
+
368
+ # guess year from either title or summary
369
+ year_match = re.search(r'(20\d{2})', raw_title)
370
+ if not year_match:
371
+ year_match = re.search(r'(20\d{2})', summary)
372
+ year = int(year_match.group(1)) if year_match else None
373
+
374
+ results.append({
375
+ 'title': raw_title,
376
+ 'summary': summary,
377
+ 'year': year
378
+ })
379
+ return results
380
+
381
+
382
+ def perform_ai_lookup(q, vocal_summary=True, extended_refs=False,
383
+ titles_summary=True, full_audio=False):
384
+ """Perform Arxiv search and generate audio summaries."""
385
+ start = time.time()
386
+
387
+ # 1) Query the HF RAG pipeline
388
+ client = Client("awacke1/Arxiv-Paper-Search-And-QA-RAG-Pattern")
389
+ refs = client.predict(q, 20, "Semantic Search","mistralai/Mixtral-8x7B-Instruct-v0.1", api_name="/update_with_rag_md")[0]
390
+ r2 = client.predict(q, "mistralai/Mixtral-8x7B-Instruct-v0.1", True, api_name="/ask_llm")
391
+
392
+ # 2) Combine for final text output
393
+ result = f"### 🔎 {q}\n\n{r2}\n\n{refs}"
394
+ st.markdown(result)
395
+
396
+ # Existing logic for "all-at-once" audio
397
+ if full_audio:
398
+ complete_text = f"Complete response for query: {q}. {clean_for_speech(r2)} {clean_for_speech(refs)}"
399
+ audio_file_full = speak_with_edge_tts(complete_text)
400
+ st.write("### 📚 Full Audio")
401
+ play_and_download_audio(audio_file_full) # or auto_play_audio(audio_file_full)
402
+
403
+ if vocal_summary:
404
+ main_text = clean_for_speech(r2)
405
+ audio_file_main = speak_with_edge_tts(main_text)
406
+ st.write("### 🎙 Short Audio")
407
+ play_and_download_audio(audio_file_main) # or auto_play_audio(audio_file_main)
408
+
409
+ if extended_refs:
410
+ summaries_text = "Extended references: " + refs.replace('"','')
411
+ summaries_text = clean_for_speech(summaries_text)
412
+ audio_file_refs = speak_with_edge_tts(summaries_text)
413
+ st.write("### 📜 Long Refs")
414
+ play_and_download_audio(audio_file_refs) # or auto_play_audio(audio_file_refs)
415
+
416
+ # --------------------------------------
417
+ # Parse references, limit to 20, generate TTS if year==2023 or 2024
418
+ # --------------------------------------
419
+ parsed_refs = parse_arxiv_refs(refs)[:20]
420
+ # Sort by year descending (None -> bottom)
421
+ parsed_refs.sort(key=lambda x: x["year"] if x["year"] else 0, reverse=True)
422
+
423
+ st.write("## Individual Papers (Most Recent First)")
424
+ for idx, paper in enumerate(parsed_refs):
425
+ year_str = paper["year"] if paper["year"] else "Unknown Year"
426
+ st.markdown(f"**{idx+1}. {paper['title']}** \n*Year:* {year_str}")
427
+ st.markdown(f"*Summary:* {paper['summary']}")
428
+ st.write("---")
429
+
430
+ # Only auto-generate TTS if year is 2023 or 2024
431
+ if paper["year"] in [2023, 2024]:
432
+ # Combine Title, Year, Summary in one text
433
+ tts_text = (
434
+ f"Title: {paper['title']}. "
435
+ f"Year: {paper['year']}. "
436
+ f"Summary: {paper['summary']}"
437
+ )
438
+ # Generate specialized MP3 filename
439
+ mp3_filename = generate_audio_filename(q, paper['title'], paper['summary'])
440
+
441
+ # Create TTS as usual
442
+ temp_mp3 = speak_with_edge_tts(tts_text)
443
+ if temp_mp3 and os.path.exists(temp_mp3):
444
+ # rename to a meaningful name
445
+ os.rename(temp_mp3, mp3_filename)
446
+ # Now embed the auto-play audio
447
+ auto_play_audio(mp3_filename)
448
+
449
+ # Titles Only (all-in-one)
450
+ if titles_summary:
451
+ titles = []
452
+ for line in refs.split('\n'):
453
+ m = re.search(r"\[([^\]]+)\]", line)
454
+ if m:
455
+ titles.append(m.group(1))
456
+ if titles:
457
+ titles_text = "Titles: " + ", ".join(titles)
458
+ titles_text = clean_for_speech(titles_text)
459
+ audio_file_titles = speak_with_edge_tts(titles_text)
460
+ st.write("### 🔖 Titles (All-In-One)")
461
+ play_and_download_audio(audio_file_titles)
462
+ # or auto_play_audio(audio_file_titles)
463
+
464
+ elapsed = time.time() - start
465
+ st.write(f"**Total Elapsed:** {elapsed:.2f} s")
466
+
467
+ # Save entire text to file as usual
468
+ create_file(q, result, "md")
469
+
470
+ return result
471
+
472
+
473
+ def process_with_gpt(text):
474
+ """Process text with GPT-4"""
475
+ if not text:
476
+ return
477
+ st.session_state.messages.append({"role":"user","content":text})
478
+ with st.chat_message("user"):
479
+ st.markdown(text)
480
+ with st.chat_message("assistant"):
481
+ c = openai_client.chat.completions.create(
482
+ model=st.session_state["openai_model"],
483
+ messages=st.session_state.messages,
484
+ stream=False
485
+ )
486
+ ans = c.choices[0].message.content
487
+ st.write("GPT-4o: " + ans)
488
+ create_file(text, ans, "md")
489
+ st.session_state.messages.append({"role":"assistant","content":ans})
490
+ return ans
491
+
492
+ def process_with_claude(text):
493
+ """Process text with Claude"""
494
+ if not text:
495
+ return
496
+ with st.chat_message("user"):
497
+ st.markdown(text)
498
+ with st.chat_message("assistant"):
499
+ r = claude_client.messages.create(
500
+ model="claude-3-sonnet-20240229",
501
+ max_tokens=1000,
502
+ messages=[{"role":"user","content":text}]
503
+ )
504
+ ans = r.content[0].text
505
+ st.write("Claude-3.5: " + ans)
506
+ create_file(text, ans, "md")
507
+ st.session_state.chat_history.append({"user":text,"claude":ans})
508
+ return ans
509
+
510
+ # 📂 10. File Management
511
+ def create_zip_of_files(md_files, mp3_files):
512
+ """Create zip with intelligent naming"""
513
+ md_files = [f for f in md_files if os.path.basename(f).lower() != 'readme.md']
514
+ all_files = md_files + mp3_files
515
+ if not all_files:
516
+ return None
517
+
518
+ # Collect content for high-info term extraction
519
+ all_content = []
520
+ for f in all_files:
521
+ if f.endswith('.md'):
522
+ with open(f, 'r', encoding='utf-8') as file:
523
+ all_content.append(file.read())
524
+ elif f.endswith('.mp3'):
525
+ all_content.append(os.path.basename(f))
526
+
527
+ combined_content = " ".join(all_content)
528
+ info_terms = get_high_info_terms(combined_content)
529
+
530
+ timestamp = datetime.now().strftime("%y%m_%H%M")
531
+ name_text = '_'.join(term.replace(' ', '-') for term in info_terms[:3])
532
+ zip_name = f"{timestamp}_{name_text}.zip"
533
+
534
+ with zipfile.ZipFile(zip_name,'w') as z:
535
+ for f in all_files:
536
+ z.write(f)
537
+
538
+ return zip_name
539
+
540
+ def load_files_for_sidebar():
541
+ """Load and group files for sidebar display"""
542
+ md_files = glob.glob("*.md")
543
+ mp3_files = glob.glob("*.mp3")
544
+
545
+ md_files = [f for f in md_files if os.path.basename(f).lower() != 'readme.md']
546
+ all_files = md_files + mp3_files
547
+
548
+ groups = defaultdict(list)
549
+ for f in all_files:
550
+ fname = os.path.basename(f)
551
+ prefix = fname[:10]
552
+ groups[prefix].append(f)
553
+
554
+ for prefix in groups:
555
+ groups[prefix].sort(key=lambda x: os.path.getmtime(x), reverse=True)
556
+
557
+ sorted_prefixes = sorted(groups.keys(),
558
+ key=lambda pre: max(os.path.getmtime(x) for x in groups[pre]),
559
+ reverse=True)
560
+ return groups, sorted_prefixes
561
+
562
+ def extract_keywords_from_md(files):
563
+ """Extract keywords from markdown files"""
564
+ text = ""
565
+ for f in files:
566
+ if f.endswith(".md"):
567
+ c = open(f,'r',encoding='utf-8').read()
568
+ text += " " + c
569
+ return get_high_info_terms(text)
570
+
571
+ def display_file_manager_sidebar(groups, sorted_prefixes):
572
+ """Display file manager in sidebar"""
573
+ st.sidebar.title("🎵 Audio & Docs Manager")
574
+
575
+ all_md = []
576
+ all_mp3 = []
577
+ for prefix in groups:
578
+ for f in groups[prefix]:
579
+ if f.endswith(".md"):
580
+ all_md.append(f)
581
+ elif f.endswith(".mp3"):
582
+ all_mp3.append(f)
583
+
584
+ top_bar = st.sidebar.columns(3)
585
+ with top_bar[0]:
586
+ if st.button("🗑 DelAllMD"):
587
+ for f in all_md:
588
+ os.remove(f)
589
+ st.session_state.should_rerun = True
590
+ with top_bar[1]:
591
+ if st.button("🗑 DelAllMP3"):
592
+ for f in all_mp3:
593
+ os.remove(f)
594
+ st.session_state.should_rerun = True
595
+ with top_bar[2]:
596
+ if st.button("⬇️ ZipAll"):
597
+ z = create_zip_of_files(all_md, all_mp3)
598
+ if z:
599
+ st.sidebar.markdown(get_download_link(z),unsafe_allow_html=True)
600
+
601
+ for prefix in sorted_prefixes:
602
+ files = groups[prefix]
603
+ kw = extract_keywords_from_md(files)
604
+ keywords_str = " ".join(kw) if kw else "No Keywords"
605
+ with st.sidebar.expander(f"{prefix} Files ({len(files)}) - KW: {keywords_str}", expanded=True):
606
+ c1, c2 = st.columns(2)
607
+ with c1:
608
+ if st.button("👀ViewGrp", key="view_group_"+prefix):
609
+ st.session_state.viewing_prefix = prefix
610
+ with c2:
611
+ if st.button("🗑DelGrp", key="del_group_"+prefix):
612
+ for f in files:
613
+ os.remove(f)
614
+ st.success(f"Deleted group {prefix}!")
615
+ st.session_state.should_rerun = True
616
+
617
+ for f in files:
618
+ fname = os.path.basename(f)
619
+ ctime = datetime.fromtimestamp(os.path.getmtime(f)).strftime("%Y-%m-%d %H:%M:%S")
620
+ st.write(f"**{fname}** - {ctime}")
621
+
622
+ # 🎯 11. Main Application
623
+ def main():
624
+ st.sidebar.markdown("### 🚲BikeAI🏆 Multi-Agent Research")
625
+ tab_main = st.radio("Action:", ["🎤 Voice","📸 Media","🔍 ArXiv","📝 Editor"], horizontal=True)
626
+
627
+ # This is your custom React component reference, if used.
628
+ mycomponent = components.declare_component("mycomponent", path="mycomponent")
629
+ val = mycomponent(my_input_value="Hello")
630
+
631
+ # Show input in a text box for editing if detected
632
+ if val:
633
+ val_stripped = val.replace('\n', ' ')
634
+ edited_input = st.text_area("✏️ Edit Input:", value=val_stripped, height=100)
635
+ run_option = st.selectbox("Model:", ["Arxiv", "GPT-4o", "Claude-3.5"])
636
+ col1, col2 = st.columns(2)
637
+ with col1:
638
+ autorun = st.checkbox("⚙ AutoRun", value=True)
639
+ with col2:
640
+ full_audio = st.checkbox("📚FullAudio", value=False,
641
+ help="Generate full audio response")
642
+
643
+ input_changed = (val != st.session_state.old_val)
644
+
645
+ if autorun and input_changed:
646
+ st.session_state.old_val = val
647
+ if run_option == "Arxiv":
648
+ perform_ai_lookup(edited_input,
649
+ vocal_summary=True,
650
+ extended_refs=False,
651
+ titles_summary=True,
652
+ full_audio=full_audio)
653
+ else:
654
+ if run_option == "GPT-4o":
655
+ process_with_gpt(edited_input)
656
+ elif run_option == "Claude-3.5":
657
+ process_with_claude(edited_input)
658
+ else:
659
+ if st.button("▶ Run"):
660
+ st.session_state.old_val = val
661
+ if run_option == "Arxiv":
662
+ perform_ai_lookup(edited_input,
663
+ vocal_summary=True,
664
+ extended_refs=False,
665
+ titles_summary=True,
666
+ full_audio=full_audio)
667
+ else:
668
+ if run_option == "GPT-4o":
669
+ process_with_gpt(edited_input)
670
+ elif run_option == "Claude-3.5":
671
+ process_with_claude(edited_input)
672
+
673
+ if tab_main == "🔍 ArXiv":
674
+ st.subheader("🔍 Query ArXiv")
675
+ q = st.text_input("🔍 Query:")
676
+
677
+ st.markdown("### 🎛 Options")
678
+ vocal_summary = st.checkbox("🎙ShortAudio", value=True)
679
+ extended_refs = st.checkbox("📜LongRefs", value=False)
680
+ titles_summary = st.checkbox("🔖TitlesOnly", value=True)
681
+ full_audio = st.checkbox("📚FullAudio", value=False,
682
+ help="Full audio of results")
683
+ full_transcript = st.checkbox("🧾FullTranscript", value=False,
684
+ help="Generate a full transcript file")
685
+
686
+ if q and st.button("🔍Run"):
687
+ result = perform_ai_lookup(q,
688
+ vocal_summary=vocal_summary,
689
+ extended_refs=extended_refs,
690
+ titles_summary=titles_summary,
691
+ full_audio=full_audio)
692
+ if full_transcript:
693
+ save_full_transcript(q, result)
694
+
695
+ st.markdown("### Change Prompt & Re-Run")
696
+ q_new = st.text_input("🔄 Modify Query:")
697
+ if q_new and st.button("🔄 Re-Run with Modified Query"):
698
+ result = perform_ai_lookup(q_new,
699
+ vocal_summary=vocal_summary,
700
+ extended_refs=extended_refs,
701
+ titles_summary=titles_summary,
702
+ full_audio=full_audio)
703
+ if full_transcript:
704
+ save_full_transcript(q_new, result)
705
+
706
+ elif tab_main == "🎤 Voice":
707
+ st.subheader("🎤 Voice Input")
708
+ user_text = st.text_area("💬 Message:", height=100)
709
+ user_text = user_text.strip().replace('\n', ' ')
710
+ if st.button("📨 Send"):
711
+ process_with_gpt(user_text)
712
+ st.subheader("📜 Chat History")
713
+ t1, t2 = st.tabs(["Claude History","GPT-4o History"])
714
+ with t1:
715
+ for c in st.session_state.chat_history:
716
+ st.write("**You:**", c["user"])
717
+ st.write("**Claude:**", c["claude"])
718
+ with t2:
719
+ for m in st.session_state.messages:
720
+ with st.chat_message(m["role"]):
721
+ st.markdown(m["content"])
722
+
723
+ elif tab_main == "📸 Media":
724
+ st.header("📸 Images & 🎥 Videos")
725
+ tabs = st.tabs(["🖼 Images", "🎥 Video"])
726
+ with tabs[0]:
727
+ imgs = glob.glob("*.png")+glob.glob("*.jpg")
728
+ if imgs:
729
+ c = st.slider("Cols",1,5,3)
730
+ cols = st.columns(c)
731
+ for i, f in enumerate(imgs):
732
+ with cols[i % c]:
733
+ st.image(Image.open(f), use_container_width=True)
734
+ if st.button(f"👀 Analyze {os.path.basename(f)}", key=f"analyze_{f}"):
735
+ a = process_image(f, "Describe this image.")
736
+ st.markdown(a)
737
+ else:
738
+ st.write("No images found.")
739
+ with tabs[1]:
740
+ vids = glob.glob("*.mp4")
741
+ if vids:
742
+ for v in vids:
743
+ with st.expander(f"🎥 {os.path.basename(v)}"):
744
+ st.video(v)
745
+ if st.button(f"Analyze {os.path.basename(v)}", key=f"analyze_{v}"):
746
+ a = process_video_with_gpt(v, "Describe video.")
747
+ st.markdown(a)
748
+ else:
749
+ st.write("No videos found.")
750
+
751
+ elif tab_main == "📝 Editor":
752
+ if getattr(st.session_state,'current_file', None):
753
+ st.subheader(f"Editing: {st.session_state.current_file}")
754
+ new_text = st.text_area("✏️ Content:", st.session_state.file_content, height=300)
755
+ if st.button("💾 Save"):
756
+ with open(st.session_state.current_file, 'w', encoding='utf-8') as f:
757
+ f.write(new_text)
758
+ st.success("Updated!")
759
+ st.session_state.should_rerun = True
760
+ else:
761
+ st.write("Select a file from the sidebar to edit.")
762
+
763
+ groups, sorted_prefixes = load_files_for_sidebar()
764
+ display_file_manager_sidebar(groups, sorted_prefixes)
765
+
766
+ if st.session_state.viewing_prefix and st.session_state.viewing_prefix in groups:
767
+ st.write("---")
768
+ st.write(f"**Viewing Group:** {st.session_state.viewing_prefix}")
769
+ for f in groups[st.session_state.viewing_prefix]:
770
+ fname = os.path.basename(f)
771
+ ext = os.path.splitext(fname)[1].lower().strip('.')
772
+ st.write(f"### {fname}")
773
+ if ext == "md":
774
+ content = open(f, 'r', encoding='utf-8').read()
775
+ st.markdown(content)
776
+ elif ext == "mp3":
777
+ st.audio(f)
778
+ else:
779
+ st.markdown(get_download_link(f), unsafe_allow_html=True)
780
+ if st.button("❌ Close"):
781
+ st.session_state.viewing_prefix = None
782
+
783
+ if st.session_state.should_rerun:
784
+ st.session_state.should_rerun = False
785
+ st.rerun()
786
+
787
+
788
+ if __name__ == "__main__":
789
+ main()