Update app.py
Browse files
app.py
CHANGED
@@ -100,7 +100,7 @@ UNICODE_FONTS = [
|
|
100 |
("Circled", lambda x: "".join(chr(ord(c) - 0x41 + 0x24B6) if 'A' <= c <= 'Z' else chr(ord(c) - 0x61 + 0x24D0) if 'a' <= c <= 'z' else c for c in x)),
|
101 |
("Squared", lambda x: "".join(chr(ord(c) - 0x41 + 0x1F130) if 'A' <= c <= 'Z' else c for c in x)),
|
102 |
("Negative Circled", lambda x: "".join(chr(ord(c) - 0x41 + 0x1F150) if 'A' <= c <= 'Z' else c for c in x)),
|
103 |
-
("Negative Squared", lambda x: "".join(chr(ord(c) -
|
104 |
("Regional Indicator", lambda x: "".join(chr(ord(c) - 0x41 + 0x1F1E6) if 'A' <= c <= 'Z' else c for c in x)),
|
105 |
]
|
106 |
|
@@ -139,6 +139,8 @@ if 'last_transcript' not in st.session_state:
|
|
139 |
st.session_state.last_transcript = ""
|
140 |
if 'image_hashes' not in st.session_state:
|
141 |
st.session_state.image_hashes = set()
|
|
|
|
|
142 |
|
143 |
# Timestamp wizardry - clock ticks with flair! β°π©
|
144 |
def format_timestamp_prefix(username):
|
@@ -201,11 +203,25 @@ async def save_chat_entry(username, message, is_markdown=False):
|
|
201 |
audio_file = await async_edge_tts_generate(cleaned_message, voice)
|
202 |
if audio_file:
|
203 |
with open(HISTORY_FILE, 'a') as f:
|
204 |
-
f.write(f"[{timestamp}] {username}: Audio generated - {audio_file}\n")
|
205 |
await broadcast_message(f"{username}|{message}", "chat")
|
206 |
st.session_state.last_chat_update = time.time()
|
207 |
return audio_file
|
208 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
209 |
# Chat loader - history unleashed! ππ
|
210 |
async def load_chat():
|
211 |
username = st.session_state.get('username', 'System π')
|
@@ -323,6 +339,7 @@ async def save_pasted_image(image, username):
|
|
323 |
filepath = os.path.join(MEDIA_DIR, filename)
|
324 |
await asyncio.to_thread(image.save, filepath, "PNG")
|
325 |
st.session_state.image_hashes.add(img_hash)
|
|
|
326 |
return filepath
|
327 |
|
328 |
# Video renderer - movies roll with autoplay! π₯π¬
|
@@ -405,6 +422,33 @@ async def perform_ai_lookup(query, vocal_summary=True, extended_refs=False, titl
|
|
405 |
if audio_file:
|
406 |
st.audio(audio_file)
|
407 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
408 |
# ASR Component HTML
|
409 |
ASR_HTML = """
|
410 |
<html>
|
@@ -569,33 +613,6 @@ ASR_HTML = """
|
|
569 |
</html>
|
570 |
"""
|
571 |
|
572 |
-
# Delete all user files function
|
573 |
-
def delete_user_files():
|
574 |
-
protected_files = {'app.py', 'requirements.txt', 'README.md'}
|
575 |
-
deleted_files = []
|
576 |
-
directories = [MEDIA_DIR, AUDIO_DIR, CHAT_DIR, VOTE_DIR, HISTORY_DIR]
|
577 |
-
for directory in directories:
|
578 |
-
if os.path.exists(directory):
|
579 |
-
for root, _, files in os.walk(directory):
|
580 |
-
for file in files:
|
581 |
-
file_path = os.path.join(root, file)
|
582 |
-
if os.path.basename(file_path) not in protected_files:
|
583 |
-
try:
|
584 |
-
os.remove(file_path)
|
585 |
-
deleted_files.append(file_path)
|
586 |
-
except Exception as e:
|
587 |
-
st.error(f"Failed to delete {file_path}: {e}")
|
588 |
-
try:
|
589 |
-
shutil.rmtree(directory, ignore_errors=True)
|
590 |
-
os.makedirs(directory, exist_ok=True)
|
591 |
-
except Exception as e:
|
592 |
-
st.error(f"Failed to remove directory {directory}: {e}")
|
593 |
-
st.session_state.image_hashes.clear()
|
594 |
-
st.session_state.audio_cache.clear()
|
595 |
-
st.session_state.base64_cache.clear()
|
596 |
-
st.session_state.displayed_chat_lines.clear()
|
597 |
-
return deleted_files
|
598 |
-
|
599 |
# Main execution - letβs roll! π²π
|
600 |
def main():
|
601 |
NODE_NAME, port = get_node_name()
|
@@ -633,12 +650,11 @@ def main():
|
|
633 |
st.session_state.last_transcript = transcript
|
634 |
st.rerun()
|
635 |
|
636 |
-
# Chat History
|
637 |
st.subheader(f"{START_ROOM} Chat History π¬")
|
638 |
chat_content = await load_chat()
|
639 |
chat_lines = chat_content.split('\n')
|
640 |
chat_lines = [line for line in chat_lines if line.strip() and ': ' in line and not line.startswith('#')]
|
641 |
-
|
642 |
if chat_lines:
|
643 |
col1, col2 = st.columns([2, 1])
|
644 |
with col1:
|
@@ -664,25 +680,6 @@ def main():
|
|
664 |
audio_file = st.session_state.audio_cache.get(cache_key)
|
665 |
if audio_file:
|
666 |
play_and_download_audio(audio_file)
|
667 |
-
with col2:
|
668 |
-
st.write("### Image Media")
|
669 |
-
media_files = glob.glob(f"{MEDIA_DIR}/*.png") + glob.glob(f"{MEDIA_DIR}/*.jpg") + glob.glob(f"{MEDIA_DIR}/*.mp4")
|
670 |
-
if media_files:
|
671 |
-
media_votes = await load_votes(MEDIA_VOTES_FILE)
|
672 |
-
seen_files = set()
|
673 |
-
for media_file in sorted(media_files, key=os.path.getmtime, reverse=True):
|
674 |
-
if media_file not in seen_files:
|
675 |
-
seen_files.add(media_file)
|
676 |
-
filename = os.path.basename(media_file)
|
677 |
-
vote_count = media_votes.get(media_file, 0)
|
678 |
-
st.markdown(f"**{filename}**")
|
679 |
-
if media_file.endswith(('.png', '.jpg')):
|
680 |
-
st.image(media_file, use_container_width=True)
|
681 |
-
elif media_file.endswith('.mp4'):
|
682 |
-
st.markdown(await get_video_html(media_file), unsafe_allow_html=True)
|
683 |
-
if st.button(f"π {vote_count}", key=f"media_vote_{media_file}"):
|
684 |
-
await save_vote(MEDIA_VOTES_FILE, media_file, await generate_user_hash(), st.session_state.username)
|
685 |
-
st.rerun()
|
686 |
|
687 |
if st.session_state.quote_line:
|
688 |
st.markdown(f"### Quoting: {st.session_state.quote_line}")
|
@@ -762,6 +759,7 @@ def main():
|
|
762 |
await asyncio.to_thread(lambda: open(file_path, 'wb').write(uploaded_file.getbuffer()))
|
763 |
st.success(f"Uploaded {filename}")
|
764 |
await save_chat_entry(username, f"Uploaded media: {file_path}")
|
|
|
765 |
st.session_state.image_hashes.add(file_hash)
|
766 |
if file_path.endswith('.mp4'):
|
767 |
st.session_state.media_notifications.append(file_path)
|
@@ -787,10 +785,37 @@ def main():
|
|
787 |
time.sleep(1)
|
788 |
st.rerun()
|
789 |
|
790 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
791 |
with open(HISTORY_FILE, 'r') as f:
|
792 |
history_content = f.read()
|
793 |
-
st.
|
794 |
|
795 |
loop.run_until_complete(async_interface())
|
796 |
|
|
|
100 |
("Circled", lambda x: "".join(chr(ord(c) - 0x41 + 0x24B6) if 'A' <= c <= 'Z' else chr(ord(c) - 0x61 + 0x24D0) if 'a' <= c <= 'z' else c for c in x)),
|
101 |
("Squared", lambda x: "".join(chr(ord(c) - 0x41 + 0x1F130) if 'A' <= c <= 'Z' else c for c in x)),
|
102 |
("Negative Circled", lambda x: "".join(chr(ord(c) - 0x41 + 0x1F150) if 'A' <= c <= 'Z' else c for c in x)),
|
103 |
+
("Negative Squared", lambda x: "".join(chr(ord(c) - 0x1F170 - 0x41) if 'A' <= c <= 'Z' else c for c in x)),
|
104 |
("Regional Indicator", lambda x: "".join(chr(ord(c) - 0x41 + 0x1F1E6) if 'A' <= c <= 'Z' else c for c in x)),
|
105 |
]
|
106 |
|
|
|
139 |
st.session_state.last_transcript = ""
|
140 |
if 'image_hashes' not in st.session_state:
|
141 |
st.session_state.image_hashes = set()
|
142 |
+
if 'gallery_columns' not in st.session_state:
|
143 |
+
st.session_state.gallery_columns = 1 # Default gallery tiles
|
144 |
|
145 |
# Timestamp wizardry - clock ticks with flair! β°π©
|
146 |
def format_timestamp_prefix(username):
|
|
|
203 |
audio_file = await async_edge_tts_generate(cleaned_message, voice)
|
204 |
if audio_file:
|
205 |
with open(HISTORY_FILE, 'a') as f:
|
206 |
+
f.write(f"[{timestamp}] {username} ({voice}): Audio generated - {audio_file}\n")
|
207 |
await broadcast_message(f"{username}|{message}", "chat")
|
208 |
st.session_state.last_chat_update = time.time()
|
209 |
return audio_file
|
210 |
|
211 |
+
# Save chat history with image
|
212 |
+
async def save_chat_history_with_image(username, image_path):
|
213 |
+
central = pytz.timezone('US/Central')
|
214 |
+
timestamp = datetime.now(central).strftime("%Y-%m-%d_%H-%M-%S")
|
215 |
+
history_filename = f"chat_history_{timestamp}-by-{username}.md"
|
216 |
+
history_filepath = os.path.join(HISTORY_DIR, history_filename)
|
217 |
+
chat_content = await load_chat()
|
218 |
+
voice = FUN_USERNAMES.get(username, "en-US-AriaNeural")
|
219 |
+
with open(history_filepath, 'w') as f:
|
220 |
+
f.write(f"# Chat History at {timestamp} by {username} (Voice: {voice})\n\n")
|
221 |
+
f.write(f"## Image Shared: {os.path.basename(image_path)}\n")
|
222 |
+
f.write(chat_content)
|
223 |
+
return history_filepath
|
224 |
+
|
225 |
# Chat loader - history unleashed! ππ
|
226 |
async def load_chat():
|
227 |
username = st.session_state.get('username', 'System π')
|
|
|
339 |
filepath = os.path.join(MEDIA_DIR, filename)
|
340 |
await asyncio.to_thread(image.save, filepath, "PNG")
|
341 |
st.session_state.image_hashes.add(img_hash)
|
342 |
+
await save_chat_history_with_image(username, filepath) # Save chat history with image
|
343 |
return filepath
|
344 |
|
345 |
# Video renderer - movies roll with autoplay! π₯π¬
|
|
|
422 |
if audio_file:
|
423 |
st.audio(audio_file)
|
424 |
|
425 |
+
# Delete all user files function
|
426 |
+
def delete_user_files():
|
427 |
+
protected_files = {'app.py', 'requirements.txt', 'README.md'}
|
428 |
+
deleted_files = []
|
429 |
+
directories = [MEDIA_DIR, AUDIO_DIR, CHAT_DIR, VOTE_DIR, HISTORY_DIR]
|
430 |
+
for directory in directories:
|
431 |
+
if os.path.exists(directory):
|
432 |
+
for root, _, files in os.walk(directory):
|
433 |
+
for file in files:
|
434 |
+
file_path = os.path.join(root, file)
|
435 |
+
if os.path.basename(file_path) not in protected_files:
|
436 |
+
try:
|
437 |
+
os.remove(file_path)
|
438 |
+
deleted_files.append(file_path)
|
439 |
+
except Exception as e:
|
440 |
+
st.error(f"Failed to delete {file_path}: {e}")
|
441 |
+
try:
|
442 |
+
shutil.rmtree(directory, ignore_errors=True)
|
443 |
+
os.makedirs(directory, exist_ok=True)
|
444 |
+
except Exception as e:
|
445 |
+
st.error(f"Failed to remove directory {directory}: {e}")
|
446 |
+
st.session_state.image_hashes.clear()
|
447 |
+
st.session_state.audio_cache.clear()
|
448 |
+
st.session_state.base64_cache.clear()
|
449 |
+
st.session_state.displayed_chat_lines.clear()
|
450 |
+
return deleted_files
|
451 |
+
|
452 |
# ASR Component HTML
|
453 |
ASR_HTML = """
|
454 |
<html>
|
|
|
613 |
</html>
|
614 |
"""
|
615 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
616 |
# Main execution - letβs roll! π²π
|
617 |
def main():
|
618 |
NODE_NAME, port = get_node_name()
|
|
|
650 |
st.session_state.last_transcript = transcript
|
651 |
st.rerun()
|
652 |
|
653 |
+
# Unified Chat History at Top
|
654 |
st.subheader(f"{START_ROOM} Chat History π¬")
|
655 |
chat_content = await load_chat()
|
656 |
chat_lines = chat_content.split('\n')
|
657 |
chat_lines = [line for line in chat_lines if line.strip() and ': ' in line and not line.startswith('#')]
|
|
|
658 |
if chat_lines:
|
659 |
col1, col2 = st.columns([2, 1])
|
660 |
with col1:
|
|
|
680 |
audio_file = st.session_state.audio_cache.get(cache_key)
|
681 |
if audio_file:
|
682 |
play_and_download_audio(audio_file)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
683 |
|
684 |
if st.session_state.quote_line:
|
685 |
st.markdown(f"### Quoting: {st.session_state.quote_line}")
|
|
|
759 |
await asyncio.to_thread(lambda: open(file_path, 'wb').write(uploaded_file.getbuffer()))
|
760 |
st.success(f"Uploaded {filename}")
|
761 |
await save_chat_entry(username, f"Uploaded media: {file_path}")
|
762 |
+
await save_chat_history_with_image(username, file_path) # Save chat history with upload
|
763 |
st.session_state.image_hashes.add(file_hash)
|
764 |
if file_path.endswith('.mp4'):
|
765 |
st.session_state.media_notifications.append(file_path)
|
|
|
785 |
time.sleep(1)
|
786 |
st.rerun()
|
787 |
|
788 |
+
# Gallery with Adjustable Tiles
|
789 |
+
st.subheader("Media Gallery π¨πΆπ₯")
|
790 |
+
gallery_columns = st.slider("Number of Gallery Tiles", 1, 20, st.session_state.gallery_columns)
|
791 |
+
st.session_state.gallery_columns = gallery_columns
|
792 |
+
media_files = glob.glob(f"{MEDIA_DIR}/*.png") + glob.glob(f"{MEDIA_DIR}/*.jpg") + glob.glob(f"{MEDIA_DIR}/*.mp4")
|
793 |
+
if media_files:
|
794 |
+
media_votes = await load_votes(MEDIA_VOTES_FILE)
|
795 |
+
seen_files = set()
|
796 |
+
cols = st.columns(gallery_columns)
|
797 |
+
col_idx = 0
|
798 |
+
for media_file in sorted(media_files, key=os.path.getmtime, reverse=True):
|
799 |
+
if media_file not in seen_files:
|
800 |
+
seen_files.add(media_file)
|
801 |
+
with cols[col_idx]:
|
802 |
+
filename = os.path.basename(media_file)
|
803 |
+
vote_count = media_votes.get(media_file, 0)
|
804 |
+
st.markdown(f"**{filename}**")
|
805 |
+
if media_file.endswith(('.png', '.jpg')):
|
806 |
+
st.image(media_file, use_container_width=True)
|
807 |
+
elif media_file.endswith('.mp4'):
|
808 |
+
st.markdown(await get_video_html(media_file), unsafe_allow_html=True)
|
809 |
+
if st.button(f"π {vote_count}", key=f"media_vote_{media_file}"):
|
810 |
+
await save_vote(MEDIA_VOTES_FILE, media_file, await generate_user_hash(), st.session_state.username)
|
811 |
+
st.rerun()
|
812 |
+
col_idx = (col_idx + 1) % gallery_columns
|
813 |
+
|
814 |
+
# Full Log at End
|
815 |
+
st.subheader("Full Chat Log π")
|
816 |
with open(HISTORY_FILE, 'r') as f:
|
817 |
history_content = f.read()
|
818 |
+
st.markdown(history_content)
|
819 |
|
820 |
loop.run_until_complete(async_interface())
|
821 |
|