awacke1 commited on
Commit
ae1d609
Β·
verified Β·
1 Parent(s): 1158250

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +291 -320
app.py CHANGED
@@ -9,17 +9,17 @@ import os
9
  import glob
10
  from pathlib import Path
11
  from datetime import datetime
 
 
 
12
  import requests
13
  from collections import defaultdict
14
- import re
 
15
  from urllib.parse import quote
16
  from xml.etree import ElementTree as ET
17
- import base64
18
- from PIL import Image
19
 
20
- # -----------------------------------------
21
- # Session State Initialization
22
- # -----------------------------------------
23
  if 'search_history' not in st.session_state:
24
  st.session_state['search_history'] = []
25
  if 'last_voice_input' not in st.session_state:
@@ -36,185 +36,191 @@ if 'tts_voice' not in st.session_state:
36
  st.session_state['tts_voice'] = "en-US-AriaNeural"
37
  if 'arxiv_last_query' not in st.session_state:
38
  st.session_state['arxiv_last_query'] = ""
39
- if 'old_val' not in st.session_state:
40
- st.session_state['old_val'] = None
41
- if 'current_file' not in st.session_state:
42
- st.session_state['current_file'] = None
43
- if 'file_content' not in st.session_state:
44
- st.session_state['file_content'] = ""
45
-
46
- # -----------------------------------------
47
- # Utility Functions
48
- # -----------------------------------------
49
- def highlight_text(text, query):
50
- """Highlight case-insensitive occurrences of query in text with bold formatting."""
51
- if not query:
52
- return text
53
- pattern = re.compile(re.escape(query), re.IGNORECASE)
54
- return pattern.sub(lambda m: f"**{m.group(0)}**", text)
55
-
56
- @st.cache_data(show_spinner=False)
57
- def fetch_dataset_rows():
58
- """Fetch dataset from Hugging Face API and cache it."""
59
- try:
60
- url = "https://datasets-server.huggingface.co/first-rows?dataset=omegalabsinc%2Fomega-multimodal&config=default&split=train"
61
- response = requests.get(url, timeout=30)
62
- if response.status_code == 200:
63
- data = response.json()
64
- if 'rows' in data:
65
- processed_rows = []
66
- for row_data in data['rows']:
67
- row = row_data.get('row', row_data)
68
- # Convert embed fields from strings to arrays
69
- for key in row:
70
- if any(term in key.lower() for term in ['embed', 'vector', 'encoding']):
71
- if isinstance(row[key], str):
72
- try:
73
- row[key] = [float(x.strip()) for x in row[key].strip('[]').split(',') if x.strip()]
74
- except:
75
- continue
76
- processed_rows.append(row)
77
-
78
- df = pd.DataFrame(processed_rows)
79
- st.session_state['search_columns'] = [col for col in df.columns
80
- if col not in ['video_embed', 'description_embed', 'audio_embed']]
81
- return df
82
- except:
83
- pass
84
- return load_example_data()
85
-
86
- def load_example_data():
87
- """Load example data as fallback."""
88
- example_data = [
89
- {
90
- "video_id": "cd21da96-fcca-4c94-a60f-0b1e4e1e29fc",
91
- "youtube_id": "IO-vwtyicn4",
92
- "description": "This video shows a close-up of an ancient text carved into a surface.",
93
- "views": 45489,
94
- "start_time": 1452,
95
- "end_time": 1458,
96
- "video_embed": [0.014160037972033024, -0.003111184574663639, -0.016604168340563774],
97
- "description_embed": [-0.05835828185081482, 0.02589797042310238, 0.11952091753482819]
98
- }
99
- ]
100
- return pd.DataFrame(example_data)
101
-
102
- @st.cache_data(show_spinner=False)
103
- def load_dataset():
104
- df = fetch_dataset_rows()
105
- return df
106
-
107
- def prepare_features(dataset):
108
- """Prepare embeddings with adaptive field detection."""
109
- try:
110
- embed_cols = [col for col in dataset.columns
111
- if any(term in col.lower() for term in ['embed', 'vector', 'encoding'])]
112
-
113
- embeddings = {}
114
- for col in embed_cols:
115
- try:
116
- data = []
117
- for row in dataset[col]:
118
- if isinstance(row, str):
119
- values = [float(x.strip()) for x in row.strip('[]').split(',') if x.strip()]
120
- elif isinstance(row, list):
121
- values = row
122
- else:
123
- continue
124
- data.append(values)
125
-
126
- if data:
127
- embeddings[col] = np.array(data)
128
- except:
129
- continue
130
-
131
- # Assign default embeddings
132
- video_embeds = embeddings.get('video_embed', None)
133
- text_embeds = embeddings.get('description_embed', None)
134
-
135
- # If missing either, fall back to what is available
136
- if video_embeds is None and embeddings:
137
- video_embeds = next(iter(embeddings.values()))
138
- if text_embeds is None:
139
- text_embeds = video_embeds if video_embeds is not None else np.random.randn(len(dataset), 384)
140
-
141
- if video_embeds is None:
142
- # Fallback to random embeddings if none found
143
- num_rows = len(dataset)
144
- video_embeds = np.random.randn(num_rows, 384)
145
- text_embeds = np.random.randn(num_rows, 384)
146
-
147
- return video_embeds, text_embeds
148
- except:
149
- # Fallback to random embeddings
150
- num_rows = len(dataset)
151
- return np.random.randn(num_rows, 384), np.random.randn(num_rows, 384)
152
 
153
  class VideoSearch:
154
  def __init__(self):
155
  self.text_model = SentenceTransformer('all-MiniLM-L6-v2')
156
- self.dataset = load_dataset()
157
- self.video_embeds, self.text_embeds = prepare_features(self.dataset)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
158
 
159
  def search(self, query, column=None, top_k=20):
160
- # If no query, return all records
161
- if not query.strip():
162
- # Just return all rows as results
163
- results = []
164
- df_copy = self.dataset.copy()
165
- # Add a neutral relevance score (e.g. 1.0)
166
- for row in df_copy.itertuples():
167
- result = {'relevance_score': 1.0}
168
- for col in df_copy.columns:
169
- if col not in ['video_embed', 'description_embed', 'audio_embed']:
170
- result[col] = getattr(row, col)
171
- results.append(result)
172
- return results[:top_k]
173
-
174
- # Semantic search
175
  query_embedding = self.text_model.encode([query])[0]
176
  video_sims = cosine_similarity([query_embedding], self.video_embeds)[0]
177
  text_sims = cosine_similarity([query_embedding], self.text_embeds)[0]
178
  combined_sims = 0.5 * video_sims + 0.5 * text_sims
179
-
180
- # If a column is selected (not All Fields), strictly filter by textual match
181
  if column and column in self.dataset.columns and column != "All Fields":
182
- mask = self.dataset[column].astype(str).str.contains(query, case=False, na=False)
183
- combined_sims = combined_sims[mask]
184
- filtered_dataset = self.dataset[mask].copy()
185
- else:
186
- filtered_dataset = self.dataset.copy()
187
-
188
- # Get top results
189
- top_k = min(top_k, len(combined_sims))
190
- if top_k == 0:
191
- return []
192
  top_indices = np.argsort(combined_sims)[-top_k:][::-1]
193
-
194
  results = []
195
- filtered_dataset = filtered_dataset.iloc[top_indices]
196
- filtered_sims = combined_sims[top_indices]
197
- for idx, row in zip(top_indices, filtered_dataset.itertuples()):
198
- result = {'relevance_score': float(filtered_sims[list(top_indices).index(idx)])}
199
- for col in filtered_dataset.columns:
200
  if col not in ['video_embed', 'description_embed', 'audio_embed']:
201
- result[col] = getattr(row, col)
202
  results.append(result)
203
-
204
  return results
205
 
206
- # -----------------------------------------
207
- # Arxiv Search Functions
208
- # -----------------------------------------
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
209
  def arxiv_search(query, max_results=5):
210
  """Perform a simple Arxiv search using their API and return top results."""
211
- if not query.strip():
212
- return []
213
  base_url = "http://export.arxiv.org/api/query?"
 
214
  search_url = base_url + f"search_query={quote(query)}&start=0&max_results={max_results}"
215
  r = requests.get(search_url)
216
  if r.status_code == 200:
217
  root = ET.fromstring(r.text)
 
218
  ns = {'atom': 'http://www.w3.org/2005/Atom'}
219
  entries = root.findall('atom:entry', ns)
220
  results = []
@@ -242,183 +248,148 @@ def perform_arxiv_lookup(q, vocal_summary=True, titles_summary=True, full_audio=
242
  if link:
243
  st.markdown(f"[View Paper]({link})")
244
 
245
- # -----------------------------------------
246
- # File Manager
247
- # -----------------------------------------
248
- def show_file_manager():
249
- """Display file manager interface for uploading and browsing local files."""
250
- st.subheader("πŸ“‚ File Manager")
251
- col1, col2 = st.columns(2)
252
- with col1:
253
- uploaded_file = st.file_uploader("Upload File", type=['txt', 'md', 'mp3'])
254
- if uploaded_file:
255
- with open(uploaded_file.name, "wb") as f:
256
- f.write(uploaded_file.getvalue())
257
- st.success(f"Uploaded: {uploaded_file.name}")
258
- st.session_state.should_rerun = True
259
-
260
- with col2:
261
- if st.button("πŸ—‘ Clear All Files"):
262
- for f in glob.glob("*.txt") + glob.glob("*.md") + glob.glob("*.mp3"):
263
- os.remove(f)
264
- st.success("All files cleared!")
265
- st.session_state.should_rerun = True
266
-
267
- files = glob.glob("*.txt") + glob.glob("*.md") + glob.glob("*.mp3")
268
- if files:
269
- st.write("### Existing Files")
270
- for f in files:
271
- with st.expander(f"πŸ“„ {os.path.basename(f)}"):
272
- if f.endswith('.mp3'):
273
- st.audio(f)
274
- else:
275
- with open(f, 'r', encoding='utf-8') as file:
276
- st.text_area("Content", file.read(), height=100)
277
- if st.button(f"Delete {os.path.basename(f)}", key=f"del_{f}"):
278
- os.remove(f)
279
- st.session_state.should_rerun = True
280
-
281
- # -----------------------------------------
282
- # Editor: Allow user to select a text file and edit it
283
- # -----------------------------------------
284
- def display_editor():
285
- # Let user pick a file from local directory to edit
286
- text_files = glob.glob("*.txt") + glob.glob("*.md")
287
- selected_file = st.selectbox("Select a file to edit:", ["None"] + text_files)
288
- if selected_file != "None":
289
- with open(selected_file, 'r', encoding='utf-8') as f:
290
- content = f.read()
291
- new_content = st.text_area("✏️ Edit Content:", value=content, height=300)
292
- if st.button("πŸ’Ύ Save"):
293
- with open(selected_file, 'w', encoding='utf-8') as f:
294
- f.write(new_content)
295
- st.success("File saved!")
296
- st.session_state.should_rerun = True
297
-
298
- # -----------------------------------------
299
- # Media (Images & Videos)
300
- # -----------------------------------------
301
- def show_media():
302
- st.header("πŸ“Έ Images & πŸŽ₯ Videos")
303
- tabs = st.tabs(["πŸ–Ό Images", "πŸŽ₯ Video"])
304
- with tabs[0]:
305
- imgs = glob.glob("*.png") + glob.glob("*.jpg") + glob.glob("*.jpeg")
306
- if imgs:
307
- c = st.slider("Columns", 1, 5, 3)
308
- cols = st.columns(c)
309
- for i, f in enumerate(imgs):
310
- with cols[i % c]:
311
- st.image(Image.open(f), use_column_width=True)
312
  else:
313
- st.write("No images found.")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
314
 
315
- with tabs[1]:
316
- vids = glob.glob("*.mp4") + glob.glob("*.webm") + glob.glob("*.mov")
317
- if vids:
318
- for v in vids:
319
- with st.expander(f"πŸŽ₯ {os.path.basename(v)}"):
320
- st.video(v)
321
- else:
322
- st.write("No videos found.")
323
-
324
- # -----------------------------------------
325
- # Video Search
326
- # -----------------------------------------
327
- def display_video_search():
328
- st.subheader("Search Videos")
329
- search_instance = VideoSearch()
330
- col1, col2 = st.columns([3, 1])
331
- with col1:
332
- query = st.text_input("Enter your search query:", value="ancient" if not st.session_state['initial_search_done'] else "")
333
- with col2:
334
- search_column = st.selectbox("Search in field:", ["All Fields"] + st.session_state['search_columns'])
335
-
336
- col3, col4 = st.columns(2)
337
- with col3:
338
- num_results = st.slider("Number of results:", 1, 100, 20)
339
- with col4:
340
- search_button = st.button("πŸ” Search")
341
-
342
- if (search_button or not st.session_state['initial_search_done']) and query is not None:
343
- st.session_state['initial_search_done'] = True
344
- selected_column = None if search_column == "All Fields" else search_column
345
- with st.spinner("Searching..."):
346
- results = search_instance.search(query, selected_column, num_results)
347
-
348
- st.session_state['search_history'].append({
349
- 'query': query,
350
- 'timestamp': datetime.now().strftime("%Y-%m-%d %H:%M:%S"),
351
- 'results': results[:5]
352
- })
353
-
354
- for i, result in enumerate(results, 1):
355
- highlighted_desc = highlight_text(result['description'], query)
356
- with st.expander(f"Result {i}: {result['description'][:100]}...", expanded=(i == 1)):
357
- cols = st.columns([2, 1])
358
- with cols[0]:
359
- st.markdown("**Description:**")
360
- st.write(highlighted_desc)
361
- st.markdown(f"**Time Range:** {result['start_time']}s - {result['end_time']}s")
362
- st.markdown(f"**Views:** {result['views']:,}")
363
-
364
- with cols[1]:
365
- st.markdown(f"**Relevance Score:** {result['relevance_score']:.2%}")
366
- if result.get('youtube_id'):
367
- st.video(f"https://youtube.com/watch?v={result['youtube_id']}&t={result['start_time']}")
368
-
369
- # -----------------------------------------
370
- # Main Application (Integrated)
371
- # -----------------------------------------
372
  def main():
373
- st.sidebar.markdown("### 🚲BikeAIπŸ† Multi-Agent Research")
374
- # We remove the "🎀 Voice" option since voice input is removed
375
- tab_main = st.sidebar.radio("Action:", ["πŸ“Έ Media", "πŸ” ArXiv", "πŸ“ Editor"])
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
376
 
377
- # File manager in the sidebar
378
  with st.sidebar:
379
  st.subheader("βš™οΈ Settings & History")
380
  if st.button("πŸ—‘οΈ Clear History"):
381
  st.session_state['search_history'] = []
382
  st.experimental_rerun()
383
-
384
  st.markdown("### Recent Searches")
385
  for entry in reversed(st.session_state['search_history'][-5:]):
386
  with st.expander(f"{entry['timestamp']}: {entry['query']}"):
387
  for i, result in enumerate(entry['results'], 1):
388
  st.write(f"{i}. {result['description'][:100]}...")
389
 
390
- st.markdown("### TTS Voice (unused)")
391
  st.selectbox("TTS Voice:",
392
  ["en-US-AriaNeural", "en-US-GuyNeural", "en-GB-SoniaNeural"],
393
  key="tts_voice")
394
 
395
- # Main content based on selection
396
- if tab_main == "πŸ“Έ Media":
397
- # Show media and video search combined
398
- show_media()
399
- st.write("---")
400
- display_video_search()
401
-
402
- elif tab_main == "πŸ” ArXiv":
403
- st.subheader("Arxiv Search")
404
- q = st.text_input("Enter your Arxiv search query:", value=st.session_state['arxiv_last_query'])
405
- vocal_summary = st.checkbox("πŸŽ™ Short Audio Summary (Placeholder - no TTS actually)", value=True)
406
- titles_summary = st.checkbox("πŸ”– Titles Only", value=True)
407
- full_audio = st.checkbox("πŸ“š Full Audio Results (Placeholder)", value=False)
408
-
409
- if st.button("πŸ” Arxiv Search"):
410
- st.session_state['arxiv_last_query'] = q
411
- perform_arxiv_lookup(q, vocal_summary=vocal_summary, titles_summary=titles_summary, full_audio=full_audio)
412
-
413
- elif tab_main == "πŸ“ Editor":
414
- show_file_manager()
415
- st.write("---")
416
- display_editor()
417
-
418
- # Rerun if needed
419
- if st.session_state.should_rerun:
420
- st.session_state.should_rerun = False
421
- st.experimental_rerun()
422
-
423
  if __name__ == "__main__":
424
  main()
 
9
  import glob
10
  from pathlib import Path
11
  from datetime import datetime
12
+ import edge_tts
13
+ import asyncio
14
+ import base64
15
  import requests
16
  from collections import defaultdict
17
+ from audio_recorder_streamlit import audio_recorder
18
+ import streamlit.components.v1 as components
19
  from urllib.parse import quote
20
  from xml.etree import ElementTree as ET
 
 
21
 
22
+ # Initialize session state
 
 
23
  if 'search_history' not in st.session_state:
24
  st.session_state['search_history'] = []
25
  if 'last_voice_input' not in st.session_state:
 
36
  st.session_state['tts_voice'] = "en-US-AriaNeural"
37
  if 'arxiv_last_query' not in st.session_state:
38
  st.session_state['arxiv_last_query'] = ""
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
39
 
40
  class VideoSearch:
41
  def __init__(self):
42
  self.text_model = SentenceTransformer('all-MiniLM-L6-v2')
43
+ self.load_dataset()
44
+
45
+ def fetch_dataset_rows(self):
46
+ """Fetch dataset from Hugging Face API"""
47
+ try:
48
+ url = "https://datasets-server.huggingface.co/first-rows?dataset=omegalabsinc%2Fomega-multimodal&config=default&split=train"
49
+ response = requests.get(url, timeout=30)
50
+ if response.status_code == 200:
51
+ data = response.json()
52
+ if 'rows' in data:
53
+ processed_rows = []
54
+ for row_data in data['rows']:
55
+ row = row_data.get('row', row_data)
56
+ for key in row:
57
+ if any(term in key.lower() for term in ['embed', 'vector', 'encoding']):
58
+ if isinstance(row[key], str):
59
+ try:
60
+ row[key] = [float(x.strip()) for x in row[key].strip('[]').split(',') if x.strip()]
61
+ except:
62
+ continue
63
+ processed_rows.append(row)
64
+
65
+ df = pd.DataFrame(processed_rows)
66
+ st.session_state['search_columns'] = [col for col in df.columns
67
+ if col not in ['video_embed', 'description_embed', 'audio_embed']]
68
+ return df
69
+ return self.load_example_data()
70
+ except:
71
+ return self.load_example_data()
72
+
73
+ def prepare_features(self):
74
+ """Prepare embeddings with adaptive field detection"""
75
+ try:
76
+ embed_cols = [col for col in self.dataset.columns
77
+ if any(term in col.lower() for term in ['embed', 'vector', 'encoding'])]
78
+
79
+ embeddings = {}
80
+ for col in embed_cols:
81
+ try:
82
+ data = []
83
+ for row in self.dataset[col]:
84
+ if isinstance(row, str):
85
+ values = [float(x.strip()) for x in row.strip('[]').split(',') if x.strip()]
86
+ elif isinstance(row, list):
87
+ values = row
88
+ else:
89
+ continue
90
+ data.append(values)
91
+
92
+ if data:
93
+ embeddings[col] = np.array(data)
94
+ except:
95
+ continue
96
+
97
+ # Set main embeddings for search
98
+ if 'video_embed' in embeddings:
99
+ self.video_embeds = embeddings['video_embed']
100
+ else:
101
+ self.video_embeds = next(iter(embeddings.values()))
102
+
103
+ if 'description_embed' in embeddings:
104
+ self.text_embeds = embeddings['description_embed']
105
+ else:
106
+ self.text_embeds = self.video_embeds
107
+
108
+ except:
109
+ # Fallback to random embeddings
110
+ num_rows = len(self.dataset)
111
+ self.video_embeds = np.random.randn(num_rows, 384)
112
+ self.text_embeds = np.random.randn(num_rows, 384)
113
+
114
+ def load_example_data(self):
115
+ """Load example data as fallback"""
116
+ example_data = [
117
+ {
118
+ "video_id": "cd21da96-fcca-4c94-a60f-0b1e4e1e29fc",
119
+ "youtube_id": "IO-vwtyicn4",
120
+ "description": "This video shows a close-up of an ancient text carved into a surface.",
121
+ "views": 45489,
122
+ "start_time": 1452,
123
+ "end_time": 1458,
124
+ "video_embed": [0.014160037972033024, -0.003111184574663639, -0.016604168340563774],
125
+ "description_embed": [-0.05835828185081482, 0.02589797042310238, 0.11952091753482819]
126
+ }
127
+ ]
128
+ return pd.DataFrame(example_data)
129
+
130
+ def load_dataset(self):
131
+ self.dataset = self.fetch_dataset_rows()
132
+ self.prepare_features()
133
 
134
  def search(self, query, column=None, top_k=20):
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
135
  query_embedding = self.text_model.encode([query])[0]
136
  video_sims = cosine_similarity([query_embedding], self.video_embeds)[0]
137
  text_sims = cosine_similarity([query_embedding], self.text_embeds)[0]
138
  combined_sims = 0.5 * video_sims + 0.5 * text_sims
139
+
140
+ # Column filtering
141
  if column and column in self.dataset.columns and column != "All Fields":
142
+ mask = self.dataset[column].astype(str).str.contains(query, case=False)
143
+ combined_sims[~mask] *= 0.5
144
+
145
+ top_k = min(top_k, 100)
 
 
 
 
 
 
146
  top_indices = np.argsort(combined_sims)[-top_k:][::-1]
147
+
148
  results = []
149
+ for idx in top_indices:
150
+ result = {'relevance_score': float(combined_sims[idx])}
151
+ for col in self.dataset.columns:
 
 
152
  if col not in ['video_embed', 'description_embed', 'audio_embed']:
153
+ result[col] = self.dataset.iloc[idx][col]
154
  results.append(result)
155
+
156
  return results
157
 
158
+ @st.cache_resource
159
+ def get_speech_model():
160
+ return edge_tts.Communicate
161
+
162
+ async def generate_speech(text, voice=None):
163
+ if not text.strip():
164
+ return None
165
+ if not voice:
166
+ voice = st.session_state['tts_voice']
167
+ try:
168
+ communicate = get_speech_model()(text, voice)
169
+ audio_file = f"speech_{datetime.now().strftime('%Y%m%d_%H%M%S')}.mp3"
170
+ await communicate.save(audio_file)
171
+ return audio_file
172
+ except Exception as e:
173
+ st.error(f"Error generating speech: {e}")
174
+ return None
175
+
176
+ def transcribe_audio(audio_path):
177
+ """Placeholder for ASR transcription (no OpenAI/Anthropic).
178
+ Integrate your own ASR model or API here."""
179
+ # For now, just return a message:
180
+ return "ASR not implemented. Integrate a local model or another service here."
181
+
182
+ def show_file_manager():
183
+ """Display file manager interface"""
184
+ st.subheader("πŸ“‚ File Manager")
185
+ col1, col2 = st.columns(2)
186
+ with col1:
187
+ uploaded_file = st.file_uploader("Upload File", type=['txt', 'md', 'mp3'])
188
+ if uploaded_file:
189
+ with open(uploaded_file.name, "wb") as f:
190
+ f.write(uploaded_file.getvalue())
191
+ st.success(f"Uploaded: {uploaded_file.name}")
192
+ st.experimental_rerun()
193
+
194
+ with col2:
195
+ if st.button("πŸ—‘ Clear All Files"):
196
+ for f in glob.glob("*.txt") + glob.glob("*.md") + glob.glob("*.mp3"):
197
+ os.remove(f)
198
+ st.success("All files cleared!")
199
+ st.experimental_rerun()
200
+
201
+ files = glob.glob("*.txt") + glob.glob("*.md") + glob.glob("*.mp3")
202
+ if files:
203
+ st.write("### Existing Files")
204
+ for f in files:
205
+ with st.expander(f"πŸ“„ {os.path.basename(f)}"):
206
+ if f.endswith('.mp3'):
207
+ st.audio(f)
208
+ else:
209
+ with open(f, 'r', encoding='utf-8') as file:
210
+ st.text_area("Content", file.read(), height=100)
211
+ if st.button(f"Delete {os.path.basename(f)}", key=f"del_{f}"):
212
+ os.remove(f)
213
+ st.experimental_rerun()
214
+
215
  def arxiv_search(query, max_results=5):
216
  """Perform a simple Arxiv search using their API and return top results."""
 
 
217
  base_url = "http://export.arxiv.org/api/query?"
218
+ # Encode the query
219
  search_url = base_url + f"search_query={quote(query)}&start=0&max_results={max_results}"
220
  r = requests.get(search_url)
221
  if r.status_code == 200:
222
  root = ET.fromstring(r.text)
223
+ # Namespace handling
224
  ns = {'atom': 'http://www.w3.org/2005/Atom'}
225
  entries = root.findall('atom:entry', ns)
226
  results = []
 
248
  if link:
249
  st.markdown(f"[View Paper]({link})")
250
 
251
+ # TTS Options
252
+ if vocal_summary:
253
+ spoken_text = f"Here are some Arxiv results for {q}. "
254
+ if titles_summary:
255
+ spoken_text += " Titles: " + ", ".join([res[0] for res in results])
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
256
  else:
257
+ # Just first summary if no titles_summary
258
+ spoken_text += " " + results[0][1][:200]
259
+
260
+ audio_file = asyncio.run(generate_speech(spoken_text))
261
+ if audio_file:
262
+ st.audio(audio_file)
263
+
264
+ if full_audio:
265
+ # Full audio of summaries
266
+ full_text = ""
267
+ for i,(title, summary, _) in enumerate(results, start=1):
268
+ full_text += f"Result {i}: {title}. {summary} "
269
+ audio_file_full = asyncio.run(generate_speech(full_text))
270
+ if audio_file_full:
271
+ st.write("### Full Audio")
272
+ st.audio(audio_file_full)
273
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
274
  def main():
275
+ st.title("πŸŽ₯ Video & Arxiv Search with Voice (No OpenAI/Anthropic)")
276
+
277
+ # Initialize search class
278
+ search = VideoSearch()
279
+
280
+ # Create tabs
281
+ tab1, tab2, tab3, tab4 = st.tabs(["πŸ” Search", "πŸŽ™οΈ Voice Input", "πŸ“š Arxiv", "πŸ“‚ Files"])
282
+
283
+ # ---- Tab 1: Video Search ----
284
+ with tab1:
285
+ st.subheader("Search Videos")
286
+ col1, col2 = st.columns([3, 1])
287
+ with col1:
288
+ query = st.text_input("Enter your search query:",
289
+ value="ancient" if not st.session_state['initial_search_done'] else "")
290
+ with col2:
291
+ search_column = st.selectbox("Search in field:",
292
+ ["All Fields"] + st.session_state['search_columns'])
293
+
294
+ col3, col4 = st.columns(2)
295
+ with col3:
296
+ num_results = st.slider("Number of results:", 1, 100, 20)
297
+ with col4:
298
+ search_button = st.button("πŸ” Search")
299
+
300
+ if (search_button or not st.session_state['initial_search_done']) and query:
301
+ st.session_state['initial_search_done'] = True
302
+ selected_column = None if search_column == "All Fields" else search_column
303
+ with st.spinner("Searching..."):
304
+ results = search.search(query, selected_column, num_results)
305
+
306
+ st.session_state['search_history'].append({
307
+ 'query': query,
308
+ 'timestamp': datetime.now().strftime("%Y-%m-%d %H:%M:%S"),
309
+ 'results': results[:5]
310
+ })
311
+
312
+ for i, result in enumerate(results, 1):
313
+ with st.expander(f"Result {i}: {result['description'][:100]}...", expanded=(i==1)):
314
+ cols = st.columns([2, 1])
315
+ with cols[0]:
316
+ st.markdown("**Description:**")
317
+ st.write(result['description'])
318
+ st.markdown(f"**Time Range:** {result['start_time']}s - {result['end_time']}s")
319
+ st.markdown(f"**Views:** {result['views']:,}")
320
+
321
+ with cols[1]:
322
+ st.markdown(f"**Relevance Score:** {result['relevance_score']:.2%}")
323
+ if result.get('youtube_id'):
324
+ st.video(f"https://youtube.com/watch?v={result['youtube_id']}&t={result['start_time']}")
325
+
326
+ if st.button(f"πŸ”Š Audio Summary", key=f"audio_{i}"):
327
+ summary = f"Video summary: {result['description'][:200]}"
328
+ audio_file = asyncio.run(generate_speech(summary))
329
+ if audio_file:
330
+ st.audio(audio_file)
331
+
332
+ # ---- Tab 2: Voice Input ----
333
+ with tab2:
334
+ st.subheader("Voice Input")
335
+
336
+ st.write("πŸŽ™οΈ Record your voice:")
337
+ audio_bytes = audio_recorder()
338
+ if audio_bytes:
339
+ audio_path = f"temp_audio_{datetime.now().strftime('%Y%m%d_%H%M%S')}.wav"
340
+ with open(audio_path, "wb") as f:
341
+ f.write(audio_bytes)
342
+ st.success("Audio recorded successfully!")
343
+
344
+ voice_query = transcribe_audio(audio_path)
345
+ st.markdown("**Transcribed Text:**")
346
+ st.write(voice_query)
347
+ st.session_state['last_voice_input'] = voice_query
348
+
349
+ if st.button("πŸ” Search from Voice"):
350
+ results = search.search(voice_query, None, 20)
351
+ for i, result in enumerate(results, 1):
352
+ with st.expander(f"Result {i}", expanded=(i==1)):
353
+ st.write(result['description'])
354
+ if result.get('youtube_id'):
355
+ st.video(f"https://youtube.com/watch?v={result['youtube_id']}&t={result.get('start_time', 0)}")
356
+
357
+ if os.path.exists(audio_path):
358
+ os.remove(audio_path)
359
+
360
+ # ---- Tab 3: Arxiv Search ----
361
+ with tab3:
362
+ st.subheader("Arxiv Search")
363
+ q = st.text_input("Enter your Arxiv search query:", value=st.session_state['arxiv_last_query'])
364
+ vocal_summary = st.checkbox("πŸŽ™ Short Audio Summary", value=True)
365
+ titles_summary = st.checkbox("πŸ”– Titles Only", value=True)
366
+ full_audio = st.checkbox("πŸ“š Full Audio Results", value=False)
367
+
368
+ if st.button("πŸ” Arxiv Search"):
369
+ st.session_state['arxiv_last_query'] = q
370
+ perform_arxiv_lookup(q, vocal_summary=vocal_summary, titles_summary=titles_summary, full_audio=full_audio)
371
+
372
+ # ---- Tab 4: File Manager ----
373
+ with tab4:
374
+ show_file_manager()
375
 
376
+ # Sidebar
377
  with st.sidebar:
378
  st.subheader("βš™οΈ Settings & History")
379
  if st.button("πŸ—‘οΈ Clear History"):
380
  st.session_state['search_history'] = []
381
  st.experimental_rerun()
382
+
383
  st.markdown("### Recent Searches")
384
  for entry in reversed(st.session_state['search_history'][-5:]):
385
  with st.expander(f"{entry['timestamp']}: {entry['query']}"):
386
  for i, result in enumerate(entry['results'], 1):
387
  st.write(f"{i}. {result['description'][:100]}...")
388
 
389
+ st.markdown("### Voice Settings")
390
  st.selectbox("TTS Voice:",
391
  ["en-US-AriaNeural", "en-US-GuyNeural", "en-GB-SoniaNeural"],
392
  key="tts_voice")
393
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
394
  if __name__ == "__main__":
395
  main()