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 |
|