awacke1 commited on
Commit
a180889
·
verified ·
1 Parent(s): dfadfd3

Update app.py

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