awacke1 commited on
Commit
e7df292
โ€ข
1 Parent(s): 413d49f

Update app.py

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