Spaces:
Sleeping
Sleeping
Update app.py
Browse files
app.py
CHANGED
|
@@ -68,6 +68,7 @@ QUOTE_VOTES_FILE = "quote_votes.md"
|
|
| 68 |
MEDIA_VOTES_FILE = "media_votes.md"
|
| 69 |
HISTORY_FILE = "chat_history.md"
|
| 70 |
STATE_FILE = "user_state.txt"
|
|
|
|
| 71 |
|
| 72 |
# Fancy digits - numbers got style! π’π
|
| 73 |
UNICODE_DIGITS = {i: f"{i}\uFE0Fβ£" for i in range(10)}
|
|
@@ -267,6 +268,19 @@ async def save_chat_entry(username, message, is_markdown=False, quote_line=None,
|
|
| 267 |
with open(CHAT_FILE, 'a') as f:
|
| 268 |
f.write(f"{indent}[{timestamp}] Audio: {audio_filename}\n")
|
| 269 |
if media_file:
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 270 |
with open(CHAT_FILE, 'a') as f:
|
| 271 |
f.write(f"{indent}[{timestamp}] Media: \n")
|
| 272 |
with open(user_history_file, 'a') as f:
|
|
@@ -391,7 +405,7 @@ def play_and_download_audio(file_path):
|
|
| 391 |
audio_data = f.read()
|
| 392 |
b64 = base64.b64encode(audio_data).decode()
|
| 393 |
audio_html = f'''
|
| 394 |
-
<audio controls style="display:inline; vertical-align:middle;">
|
| 395 |
<source src="data:audio/mpeg;base64,{b64}" type="audio/mpeg">
|
| 396 |
</audio>
|
| 397 |
<a href="data:audio/mpeg;base64,{b64}" download="{os.path.basename(file_path)}" style="vertical-align:middle;">π΅</a>
|
|
@@ -399,19 +413,42 @@ def play_and_download_audio(file_path):
|
|
| 399 |
return audio_html
|
| 400 |
return ""
|
| 401 |
|
| 402 |
-
# Image saver - pics preserved with naming! πΈπΎ
|
| 403 |
async def save_pasted_image(image, username):
|
| 404 |
await asyncio.to_thread(log_action, username, "πΈπΎ - Image saver - pics preserved!")
|
| 405 |
img_hash = compute_image_hash(image)
|
| 406 |
if img_hash in st.session_state.image_hashes:
|
| 407 |
return None
|
| 408 |
timestamp = format_timestamp_prefix(username)
|
| 409 |
-
|
| 410 |
-
|
| 411 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 412 |
st.session_state.image_hashes.add(img_hash)
|
| 413 |
await save_chat_history_with_image(username, filepath)
|
| 414 |
-
return
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 415 |
|
| 416 |
# PDF saver and audio generator
|
| 417 |
async def save_pdf_and_generate_audio(pdf_file, username, max_pages=10):
|
|
@@ -433,7 +470,7 @@ async def save_pdf_and_generate_audio(pdf_file, username, max_pages=10):
|
|
| 433 |
for i in range(total_pages):
|
| 434 |
text = reader.pages[i].extract_text()
|
| 435 |
texts.append(text)
|
| 436 |
-
audio_filename = f"{timestamp}-page{i+1}-{file_hash}.mp3"
|
| 437 |
audio_data = await audio_processor.create_audio(text, voice, audio_filename)
|
| 438 |
if audio_data:
|
| 439 |
audio_files.append(audio_filename)
|
|
@@ -441,7 +478,7 @@ async def save_pdf_and_generate_audio(pdf_file, username, max_pages=10):
|
|
| 441 |
return pdf_filename, texts, audio_files
|
| 442 |
|
| 443 |
# Video renderer - movies roll with autoplay! π₯π¬
|
| 444 |
-
def get_video_html(video_path, width="
|
| 445 |
video_url = f"data:video/mp4;base64,{base64.b64encode(open(video_path, 'rb').read()).decode()}"
|
| 446 |
return f'''
|
| 447 |
<video width="{width}" controls autoplay muted loop>
|
|
@@ -451,7 +488,7 @@ def get_video_html(video_path, width="100%"):
|
|
| 451 |
'''
|
| 452 |
|
| 453 |
# Audio renderer - sounds soar! πΆβοΈ
|
| 454 |
-
async def get_audio_html(audio_path, width="
|
| 455 |
username = st.session_state.get('username', 'System π')
|
| 456 |
await asyncio.to_thread(log_action, username, "πΆβοΈ - Audio renderer - sounds soar!")
|
| 457 |
audio_url = f"data:audio/mpeg;base64,{base64.b64encode(await asyncio.to_thread(open, audio_path, 'rb').read()).decode()}"
|
|
@@ -510,7 +547,7 @@ async def run_websocket_server():
|
|
| 510 |
|
| 511 |
# Delete all user files function
|
| 512 |
def delete_user_files():
|
| 513 |
-
protected_files = {'app.py', 'requirements.txt', 'README.md', CHAT_FILE, QUOTE_VOTES_FILE, MEDIA_VOTES_FILE, HISTORY_FILE, STATE_FILE}
|
| 514 |
deleted_files = []
|
| 515 |
for file in os.listdir('.'):
|
| 516 |
if file not in protected_files and not file.endswith('_history.md'):
|
|
@@ -519,6 +556,14 @@ def delete_user_files():
|
|
| 519 |
deleted_files.append(file)
|
| 520 |
except Exception as e:
|
| 521 |
st.error(f"Failed to delete {file}: {e}")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 522 |
st.session_state.image_hashes.clear()
|
| 523 |
st.session_state.audio_cache.clear()
|
| 524 |
st.session_state.base64_cache.clear()
|
|
@@ -532,9 +577,10 @@ def check_query_params():
|
|
| 532 |
if q_value and q_value in FUN_USERNAMES:
|
| 533 |
st.session_state.username = q_value
|
| 534 |
st.session_state.voice = FUN_USERNAMES[q_value]
|
|
|
|
| 535 |
elif q_value:
|
| 536 |
st.session_state.user_id = q_value # Use as user_id if not a valid username
|
| 537 |
-
return
|
| 538 |
|
| 539 |
# Mermaid graph generator
|
| 540 |
def generate_mermaid_graph(chat_lines):
|
|
@@ -542,33 +588,30 @@ def generate_mermaid_graph(chat_lines):
|
|
| 542 |
nodes = {}
|
| 543 |
edges = []
|
| 544 |
for i, line in enumerate(chat_lines):
|
| 545 |
-
if line.strip():
|
| 546 |
timestamp = line.split('] ')[0][1:] if '] ' in line else "Unknown"
|
| 547 |
-
minute_key = timestamp[:16] # Up to minute
|
| 548 |
content = line.split(': ', 1)[1] if ': ' in line else line
|
| 549 |
-
user = content.split(' ')[0]
|
|
|
|
| 550 |
node_id = f"{user}_{i}"
|
| 551 |
-
nodes[node_id] = f"{user}
|
| 552 |
-
|
| 553 |
-
|
| 554 |
-
|
| 555 |
-
|
| 556 |
-
|
| 557 |
-
|
| 558 |
-
|
| 559 |
-
|
| 560 |
-
|
| 561 |
-
|
| 562 |
-
|
| 563 |
-
|
| 564 |
-
if parent_line:
|
| 565 |
-
parent_id = f"{parent_line.split(': ')[1].split(' ')[0]}_{i-1}"
|
| 566 |
-
edges.append(f"{parent_id} --> {node_id}")
|
| 567 |
|
| 568 |
for node_id, label in nodes.items():
|
| 569 |
mermaid_code += f" {node_id}[\"{label}\"]\n"
|
| 570 |
mermaid_code += "\n".join(f" {edge}" for edge in edges)
|
| 571 |
-
return
|
| 572 |
|
| 573 |
# Main execution - letβs roll! π²π
|
| 574 |
def main():
|
|
@@ -582,12 +625,13 @@ def main():
|
|
| 582 |
if not st.session_state.user_id:
|
| 583 |
st.session_state.user_id = str(uuid.uuid4())
|
| 584 |
st.session_state.user_hash = await generate_user_hash()
|
| 585 |
-
|
| 586 |
-
|
| 587 |
-
|
| 588 |
-
|
| 589 |
-
|
| 590 |
-
|
|
|
|
| 591 |
st.session_state.voice = FUN_USERNAMES[st.session_state.username]
|
| 592 |
st.markdown(f"**ποΈ Voice Selected**: {st.session_state.voice} π£οΈ for {st.session_state.username}")
|
| 593 |
|
|
@@ -598,229 +642,254 @@ def main():
|
|
| 598 |
st.session_state.displayed_chat_lines = f.read().split('\n')
|
| 599 |
|
| 600 |
user_url = f"/q={st.session_state.username}"
|
| 601 |
-
st.
|
|
|
|
| 602 |
|
| 603 |
-
st.
|
| 604 |
-
|
|
|
|
| 605 |
|
| 606 |
if not st.session_state.server_task:
|
| 607 |
st.session_state.server_task = loop.create_task(run_websocket_server())
|
| 608 |
|
| 609 |
# Unified Chat History at Top with Markdown Emoji Output
|
| 610 |
-
st.
|
| 611 |
-
|
| 612 |
-
|
| 613 |
-
|
| 614 |
-
|
| 615 |
-
|
| 616 |
-
|
| 617 |
-
|
| 618 |
-
|
| 619 |
-
|
| 620 |
-
|
| 621 |
-
|
| 622 |
-
|
| 623 |
-
|
| 624 |
-
|
| 625 |
-
|
| 626 |
-
|
| 627 |
-
|
| 628 |
-
|
| 629 |
-
|
| 630 |
-
|
| 631 |
-
|
| 632 |
-
|
| 633 |
-
|
| 634 |
-
|
| 635 |
-
|
| 636 |
-
|
| 637 |
-
|
| 638 |
-
|
| 639 |
-
|
| 640 |
-
|
| 641 |
-
|
| 642 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 643 |
|
| 644 |
# Mermaid Graph Visualization
|
| 645 |
-
st.
|
| 646 |
-
|
| 647 |
-
|
| 648 |
-
|
| 649 |
-
|
| 650 |
-
|
| 651 |
-
|
| 652 |
-
|
| 653 |
-
|
| 654 |
-
|
| 655 |
-
|
| 656 |
-
|
| 657 |
-
|
| 658 |
-
|
| 659 |
-
|
| 660 |
-
if
|
| 661 |
-
st.session_state.
|
| 662 |
-
|
| 663 |
-
|
| 664 |
-
|
| 665 |
-
|
| 666 |
-
|
| 667 |
-
|
| 668 |
-
|
| 669 |
-
|
| 670 |
-
|
| 671 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 672 |
st.rerun()
|
| 673 |
|
| 674 |
-
|
| 675 |
-
|
| 676 |
-
|
| 677 |
-
|
| 678 |
-
|
| 679 |
-
|
| 680 |
-
|
| 681 |
-
|
| 682 |
-
|
| 683 |
-
# Message input with Send button on the right
|
| 684 |
-
col_input, col_send = st.columns([5, 1])
|
| 685 |
-
with col_input:
|
| 686 |
-
message = st.text_input(f"Message as {st.session_state.username} (Voice: {st.session_state.voice})", key="message_input", value=st.session_state.message_text)
|
| 687 |
-
with col_send:
|
| 688 |
-
if st.button("Send π", key="send_button"):
|
| 689 |
-
if message.strip():
|
| 690 |
-
audio_file = await save_chat_entry(st.session_state.username, message, is_markdown=True)
|
| 691 |
-
if audio_file:
|
| 692 |
-
st.session_state.audio_cache[f"{message}_{FUN_USERNAMES[st.session_state.username]}"] = audio_file
|
| 693 |
-
st.audio(audio_file) # Immediate preview
|
| 694 |
-
if st.session_state.pasted_image_data:
|
| 695 |
-
await save_chat_entry(st.session_state.username, f"Pasted image: {st.session_state.pasted_image_data}", media_file=st.session_state.pasted_image_data)
|
| 696 |
-
st.session_state.pasted_image_data = None
|
| 697 |
-
st.session_state.message_text = ''
|
| 698 |
-
st.rerun()
|
| 699 |
-
|
| 700 |
-
paste_result_msg = paste_image_button("π Paste Image or Text with Message", key="paste_button_msg")
|
| 701 |
-
if paste_result_msg.image_data is not None:
|
| 702 |
-
if isinstance(paste_result_msg.image_data, str):
|
| 703 |
-
st.session_state.message_text = paste_result_msg.image_data
|
| 704 |
-
st.text_input(f"Message as {st.session_state.username} (Voice: {st.session_state.voice})", key="message_input_paste", value=st.session_state.message_text)
|
| 705 |
-
else:
|
| 706 |
-
st.image(paste_result_msg.image_data, caption="Received Image for Message")
|
| 707 |
-
filename = await save_pasted_image(paste_result_msg.image_data, st.session_state.username)
|
| 708 |
-
if filename:
|
| 709 |
-
await save_chat_entry(st.session_state.username, f"Pasted image: {filename}", media_file=filename)
|
| 710 |
-
st.session_state.pasted_image_data = None
|
| 711 |
-
|
| 712 |
-
tab_main = st.radio("Action:", ["πΈ Media", "π ArXiv", "π Editor"], horizontal=True)
|
| 713 |
-
useArxiv = st.checkbox("Search Arxiv for Research Paper Answers", value=True)
|
| 714 |
-
useArxivAudio = st.checkbox("Generate Audio File for Research Paper Answers", value=False)
|
| 715 |
-
|
| 716 |
-
st.subheader("Upload Media π¨πΆππ₯")
|
| 717 |
-
uploaded_file = st.file_uploader("Upload Media", type=['png', 'jpg', 'mp4', 'mp3', 'wav', 'pdf', 'txt', 'md', 'py'])
|
| 718 |
-
if uploaded_file:
|
| 719 |
-
timestamp = format_timestamp_prefix(st.session_state.username)
|
| 720 |
-
username = st.session_state.username
|
| 721 |
-
ext = uploaded_file.name.split('.')[-1]
|
| 722 |
-
file_hash = hashlib.md5(uploaded_file.getbuffer()).hexdigest()[:8]
|
| 723 |
-
if file_hash not in st.session_state.image_hashes:
|
| 724 |
-
filename = f"{timestamp}-{file_hash}.{ext}"
|
| 725 |
-
file_path = filename # Top-level file
|
| 726 |
-
await asyncio.to_thread(lambda: open(file_path, 'wb').write(uploaded_file.getbuffer()))
|
| 727 |
-
st.success(f"Uploaded {filename}")
|
| 728 |
-
if ext == 'pdf':
|
| 729 |
-
pdf_filename, texts, audio_files = await save_pdf_and_generate_audio(uploaded_file, username)
|
| 730 |
-
await save_chat_entry(username, f"Uploaded PDF: {pdf_filename}", media_file=pdf_filename)
|
| 731 |
-
for i, (text, audio_file) in enumerate(zip(texts, audio_files)):
|
| 732 |
if audio_file:
|
| 733 |
-
|
| 734 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 735 |
else:
|
| 736 |
-
|
| 737 |
-
|
| 738 |
-
|
| 739 |
-
|
| 740 |
-
|
| 741 |
-
|
| 742 |
-
|
| 743 |
-
|
| 744 |
-
|
| 745 |
-
|
| 746 |
-
|
| 747 |
-
|
| 748 |
-
|
| 749 |
-
|
| 750 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 751 |
|
| 752 |
-
|
| 753 |
-
|
| 754 |
-
|
| 755 |
-
|
| 756 |
-
|
| 757 |
-
|
| 758 |
-
|
| 759 |
-
|
| 760 |
-
|
| 761 |
-
|
| 762 |
|
| 763 |
# Separate Galleries for Own and Shared Files
|
| 764 |
-
|
| 765 |
-
|
| 766 |
-
|
| 767 |
-
|
| 768 |
-
|
| 769 |
-
|
| 770 |
-
|
| 771 |
-
|
| 772 |
-
|
| 773 |
-
|
| 774 |
-
|
| 775 |
-
|
| 776 |
-
|
| 777 |
-
|
| 778 |
-
|
| 779 |
-
|
| 780 |
-
|
| 781 |
-
|
| 782 |
-
|
| 783 |
-
|
| 784 |
-
|
| 785 |
-
|
| 786 |
-
|
| 787 |
-
|
| 788 |
-
|
| 789 |
-
|
| 790 |
-
|
| 791 |
-
|
| 792 |
-
|
| 793 |
-
|
| 794 |
-
|
| 795 |
-
|
| 796 |
-
|
| 797 |
-
|
| 798 |
-
|
| 799 |
-
|
| 800 |
-
|
| 801 |
-
|
| 802 |
-
|
| 803 |
-
|
| 804 |
-
|
| 805 |
-
|
| 806 |
-
|
| 807 |
-
|
| 808 |
-
|
| 809 |
-
|
| 810 |
-
|
| 811 |
-
|
| 812 |
-
|
| 813 |
-
|
| 814 |
-
|
| 815 |
-
|
| 816 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 817 |
|
| 818 |
# Full Log at End with Download
|
| 819 |
-
st.
|
| 820 |
-
|
| 821 |
-
|
| 822 |
-
|
| 823 |
-
|
|
|
|
| 824 |
|
| 825 |
loop.run_until_complete(async_interface())
|
| 826 |
|
|
|
|
| 68 |
MEDIA_VOTES_FILE = "media_votes.md"
|
| 69 |
HISTORY_FILE = "chat_history.md"
|
| 70 |
STATE_FILE = "user_state.txt"
|
| 71 |
+
MEDIA_DIR = "media_base64"
|
| 72 |
|
| 73 |
# Fancy digits - numbers got style! π’π
|
| 74 |
UNICODE_DIGITS = {i: f"{i}\uFE0Fβ£" for i in range(10)}
|
|
|
|
| 268 |
with open(CHAT_FILE, 'a') as f:
|
| 269 |
f.write(f"{indent}[{timestamp}] Audio: {audio_filename}\n")
|
| 270 |
if media_file:
|
| 271 |
+
if isinstance(media_file, Image.Image):
|
| 272 |
+
img_hash = compute_image_hash(media_file)
|
| 273 |
+
timestamp_prefix = format_timestamp_prefix(username)
|
| 274 |
+
media_filename = f"{timestamp_prefix}-{img_hash}.b64"
|
| 275 |
+
media_path = os.path.join(MEDIA_DIR, media_filename)
|
| 276 |
+
os.makedirs(MEDIA_DIR, exist_ok=True)
|
| 277 |
+
img_byte_arr = io.BytesIO()
|
| 278 |
+
media_file.save(img_byte_arr, format='PNG')
|
| 279 |
+
img_bytes = img_byte_arr.getvalue()
|
| 280 |
+
b64_data = base64.b64encode(img_bytes).decode()
|
| 281 |
+
with open(media_path, 'w') as f:
|
| 282 |
+
f.write(b64_data)
|
| 283 |
+
media_file = media_filename
|
| 284 |
with open(CHAT_FILE, 'a') as f:
|
| 285 |
f.write(f"{indent}[{timestamp}] Media: \n")
|
| 286 |
with open(user_history_file, 'a') as f:
|
|
|
|
| 405 |
audio_data = f.read()
|
| 406 |
b64 = base64.b64encode(audio_data).decode()
|
| 407 |
audio_html = f'''
|
| 408 |
+
<audio controls style="display:inline; vertical-align:middle; width:100px;">
|
| 409 |
<source src="data:audio/mpeg;base64,{b64}" type="audio/mpeg">
|
| 410 |
</audio>
|
| 411 |
<a href="data:audio/mpeg;base64,{b64}" download="{os.path.basename(file_path)}" style="vertical-align:middle;">π΅</a>
|
|
|
|
| 413 |
return audio_html
|
| 414 |
return ""
|
| 415 |
|
| 416 |
+
# Image saver - pics preserved with naming as base64! πΈπΎ
|
| 417 |
async def save_pasted_image(image, username):
|
| 418 |
await asyncio.to_thread(log_action, username, "πΈπΎ - Image saver - pics preserved!")
|
| 419 |
img_hash = compute_image_hash(image)
|
| 420 |
if img_hash in st.session_state.image_hashes:
|
| 421 |
return None
|
| 422 |
timestamp = format_timestamp_prefix(username)
|
| 423 |
+
voice = st.session_state.voice if username == st.session_state.username else FUN_USERNAMES.get(username, "en-US-AriaNeural")
|
| 424 |
+
filename = f"{timestamp}-{img_hash}-voice-{voice}.b64"
|
| 425 |
+
filepath = os.path.join(MEDIA_DIR, filename)
|
| 426 |
+
os.makedirs(MEDIA_DIR, exist_ok=True)
|
| 427 |
+
img_byte_arr = io.BytesIO()
|
| 428 |
+
image.save(img_byte_arr, format='PNG')
|
| 429 |
+
b64_data = base64.b64encode(img_byte_arr.getvalue()).decode()
|
| 430 |
+
with open(filepath, 'w') as f:
|
| 431 |
+
f.write(b64_data)
|
| 432 |
st.session_state.image_hashes.add(img_hash)
|
| 433 |
await save_chat_history_with_image(username, filepath)
|
| 434 |
+
return filename
|
| 435 |
+
|
| 436 |
+
# Display base64 image and audio
|
| 437 |
+
def display_base64_media(media_file, width="100px"):
|
| 438 |
+
if os.path.exists(os.path.join(MEDIA_DIR, media_file)) and media_file.endswith('.b64'):
|
| 439 |
+
with open(os.path.join(MEDIA_DIR, media_file), 'r') as f:
|
| 440 |
+
b64_data = f.read()
|
| 441 |
+
img_data = base64.b64decode(b64_data)
|
| 442 |
+
img = Image.open(io.BytesIO(img_data))
|
| 443 |
+
st.image(img, use_container_width=True)
|
| 444 |
+
|
| 445 |
+
# Find corresponding audio file
|
| 446 |
+
timestamp = media_file.split('-by-')[0] + '-by-' + media_file.split('-by-')[1].split('-')[0]
|
| 447 |
+
voice = media_file.split('-voice-')[1].split('.b64')[0]
|
| 448 |
+
audio_files = glob.glob(f"{timestamp}*-{voice}.mp3")
|
| 449 |
+
if audio_files:
|
| 450 |
+
audio_file = audio_files[0]
|
| 451 |
+
st.audio(audio_file)
|
| 452 |
|
| 453 |
# PDF saver and audio generator
|
| 454 |
async def save_pdf_and_generate_audio(pdf_file, username, max_pages=10):
|
|
|
|
| 470 |
for i in range(total_pages):
|
| 471 |
text = reader.pages[i].extract_text()
|
| 472 |
texts.append(text)
|
| 473 |
+
audio_filename = f"{timestamp}-page{i+1}-{file_hash}-voice-{voice}.mp3"
|
| 474 |
audio_data = await audio_processor.create_audio(text, voice, audio_filename)
|
| 475 |
if audio_data:
|
| 476 |
audio_files.append(audio_filename)
|
|
|
|
| 478 |
return pdf_filename, texts, audio_files
|
| 479 |
|
| 480 |
# Video renderer - movies roll with autoplay! π₯π¬
|
| 481 |
+
def get_video_html(video_path, width="100px"):
|
| 482 |
video_url = f"data:video/mp4;base64,{base64.b64encode(open(video_path, 'rb').read()).decode()}"
|
| 483 |
return f'''
|
| 484 |
<video width="{width}" controls autoplay muted loop>
|
|
|
|
| 488 |
'''
|
| 489 |
|
| 490 |
# Audio renderer - sounds soar! πΆβοΈ
|
| 491 |
+
async def get_audio_html(audio_path, width="100px"):
|
| 492 |
username = st.session_state.get('username', 'System π')
|
| 493 |
await asyncio.to_thread(log_action, username, "πΆβοΈ - Audio renderer - sounds soar!")
|
| 494 |
audio_url = f"data:audio/mpeg;base64,{base64.b64encode(await asyncio.to_thread(open, audio_path, 'rb').read()).decode()}"
|
|
|
|
| 547 |
|
| 548 |
# Delete all user files function
|
| 549 |
def delete_user_files():
|
| 550 |
+
protected_files = {'app.py', 'requirements.txt', 'README.md', CHAT_FILE, QUOTE_VOTES_FILE, MEDIA_VOTES_FILE, HISTORY_FILE, STATE_FILE, MEDIA_DIR}
|
| 551 |
deleted_files = []
|
| 552 |
for file in os.listdir('.'):
|
| 553 |
if file not in protected_files and not file.endswith('_history.md'):
|
|
|
|
| 556 |
deleted_files.append(file)
|
| 557 |
except Exception as e:
|
| 558 |
st.error(f"Failed to delete {file}: {e}")
|
| 559 |
+
for root, dirs, files in os.walk(MEDIA_DIR):
|
| 560 |
+
for file in files:
|
| 561 |
+
file_path = os.path.join(root, file)
|
| 562 |
+
try:
|
| 563 |
+
os.remove(file_path)
|
| 564 |
+
deleted_files.append(file_path)
|
| 565 |
+
except Exception as e:
|
| 566 |
+
st.error(f"Failed to delete {file_path}: {e}")
|
| 567 |
st.session_state.image_hashes.clear()
|
| 568 |
st.session_state.audio_cache.clear()
|
| 569 |
st.session_state.base64_cache.clear()
|
|
|
|
| 577 |
if q_value and q_value in FUN_USERNAMES:
|
| 578 |
st.session_state.username = q_value
|
| 579 |
st.session_state.voice = FUN_USERNAMES[q_value]
|
| 580 |
+
return q_value
|
| 581 |
elif q_value:
|
| 582 |
st.session_state.user_id = q_value # Use as user_id if not a valid username
|
| 583 |
+
return None
|
| 584 |
|
| 585 |
# Mermaid graph generator
|
| 586 |
def generate_mermaid_graph(chat_lines):
|
|
|
|
| 588 |
nodes = {}
|
| 589 |
edges = []
|
| 590 |
for i, line in enumerate(chat_lines):
|
| 591 |
+
if line.strip() and not line.startswith(' '):
|
| 592 |
timestamp = line.split('] ')[0][1:] if '] ' in line else "Unknown"
|
|
|
|
| 593 |
content = line.split(': ', 1)[1] if ': ' in line else line
|
| 594 |
+
user = content.split(' ')[0]
|
| 595 |
+
message = content.split(' ', 1)[1] if ' ' in content else ''
|
| 596 |
node_id = f"{user}_{i}"
|
| 597 |
+
nodes[node_id] = f"{user}: {message}"
|
| 598 |
+
if i + 1 < len(chat_lines) and "Audio:" in chat_lines[i + 1]:
|
| 599 |
+
audio_node = f"audio_{i}"
|
| 600 |
+
nodes[audio_node] = "π΅"
|
| 601 |
+
edges.append(f"{node_id} --> {audio_node}")
|
| 602 |
+
if i + 2 < len(chat_lines) and "Media:" in chat_lines[i + 2]:
|
| 603 |
+
media_node = f"media_{i}"
|
| 604 |
+
nodes[media_node] = "πΌ"
|
| 605 |
+
edges.append(f"{node_id} --> {media_node}")
|
| 606 |
+
if i > 0 and "> " in line:
|
| 607 |
+
parent_user = chat_lines[i-1].split(': ')[1].split(' ')[0]
|
| 608 |
+
parent_id = f"{parent_user}_{i-1}"
|
| 609 |
+
edges.append(f"{parent_id} --> {node_id}")
|
|
|
|
|
|
|
|
|
|
| 610 |
|
| 611 |
for node_id, label in nodes.items():
|
| 612 |
mermaid_code += f" {node_id}[\"{label}\"]\n"
|
| 613 |
mermaid_code += "\n".join(f" {edge}" for edge in edges)
|
| 614 |
+
return mermaid_code
|
| 615 |
|
| 616 |
# Main execution - letβs roll! π²π
|
| 617 |
def main():
|
|
|
|
| 625 |
if not st.session_state.user_id:
|
| 626 |
st.session_state.user_id = str(uuid.uuid4())
|
| 627 |
st.session_state.user_hash = await generate_user_hash()
|
| 628 |
+
|
| 629 |
+
# Check query params first to override username
|
| 630 |
+
q_value = check_query_params()
|
| 631 |
+
if not q_value and 'username' not in st.session_state:
|
| 632 |
+
chat_content = await load_chat()
|
| 633 |
+
available_names = [name for name in FUN_USERNAMES if not any(f"{name} has joined" in line for line in chat_content.split('\n'))]
|
| 634 |
+
st.session_state.username = random.choice(available_names) if available_names else random.choice(list(FUN_USERNAMES.keys()))
|
| 635 |
st.session_state.voice = FUN_USERNAMES[st.session_state.username]
|
| 636 |
st.markdown(f"**ποΈ Voice Selected**: {st.session_state.voice} π£οΈ for {st.session_state.username}")
|
| 637 |
|
|
|
|
| 642 |
st.session_state.displayed_chat_lines = f.read().split('\n')
|
| 643 |
|
| 644 |
user_url = f"/q={st.session_state.username}"
|
| 645 |
+
with st.container():
|
| 646 |
+
st.markdown(f"<small>Your unique URL path: [{user_url}]({user_url})</small>", unsafe_allow_html=True)
|
| 647 |
|
| 648 |
+
with st.container():
|
| 649 |
+
st.markdown(f"#### π€π§ MMO {st.session_state.username}ππ¬")
|
| 650 |
+
st.markdown(f"<small>Welcome to {START_ROOM} - chat, vote, upload, paste images, and enjoy quoting! π User ID: {st.session_state.user_id}</small>", unsafe_allow_html=True)
|
| 651 |
|
| 652 |
if not st.session_state.server_task:
|
| 653 |
st.session_state.server_task = loop.create_task(run_websocket_server())
|
| 654 |
|
| 655 |
# Unified Chat History at Top with Markdown Emoji Output
|
| 656 |
+
with st.container():
|
| 657 |
+
st.markdown(f"##### {START_ROOM} Chat History π¬")
|
| 658 |
+
chat_content = await load_chat()
|
| 659 |
+
chat_lines = [line for line in chat_content.split('\n') if line.strip() and not line.startswith('#')]
|
| 660 |
+
if chat_lines:
|
| 661 |
+
chat_by_minute = {}
|
| 662 |
+
for line in reversed(chat_lines):
|
| 663 |
+
timestamp = line.split('] ')[0][1:] if '] ' in line else "Unknown"
|
| 664 |
+
minute_key = timestamp[:16] # Up to minute
|
| 665 |
+
if minute_key not in chat_by_minute:
|
| 666 |
+
chat_by_minute[minute_key] = []
|
| 667 |
+
chat_by_minute[minute_key].append(line)
|
| 668 |
+
|
| 669 |
+
markdown_output = ""
|
| 670 |
+
for minute, lines in chat_by_minute.items():
|
| 671 |
+
minute_output = f"###### {minute[-5:]}\n" # Show only HH:MM
|
| 672 |
+
for line in lines:
|
| 673 |
+
if ': ' in line and not line.startswith(' '):
|
| 674 |
+
user_message = line.split(': ', 1)[1]
|
| 675 |
+
user = user_message.split(' ')[0]
|
| 676 |
+
msg = user_message.split(' ', 1)[1] if ' ' in user_message else ''
|
| 677 |
+
audio_html = ""
|
| 678 |
+
media_content = ""
|
| 679 |
+
next_lines = chat_lines[chat_lines.index(line)+1:chat_lines.index(line)+3]
|
| 680 |
+
for nl in next_lines:
|
| 681 |
+
if "Audio:" in nl:
|
| 682 |
+
audio_file = nl.split("Audio: ")[-1].strip()
|
| 683 |
+
audio_html = play_and_download_audio(audio_file)
|
| 684 |
+
elif "Media:" in nl:
|
| 685 |
+
media_file = nl.split("Media: ")[-1].strip('![]()')
|
| 686 |
+
if media_file.endswith('.b64'):
|
| 687 |
+
media_content = display_base64_media(media_file, width="100px")
|
| 688 |
+
elif media_file.endswith(('.png', '.jpg')):
|
| 689 |
+
media_content = f"<img src='file://{media_file}' width='100'>"
|
| 690 |
+
elif media_file.endswith('.mp4'):
|
| 691 |
+
media_content = get_video_html(media_file)
|
| 692 |
+
elif media_file.endswith('.pdf'):
|
| 693 |
+
media_content = f"π {os.path.basename(media_file)}"
|
| 694 |
+
minute_output += f"- π¬ **{user}**: {msg} {audio_html} {media_content}\n"
|
| 695 |
+
markdown_output += minute_output
|
| 696 |
+
st.markdown(markdown_output, unsafe_allow_html=True)
|
| 697 |
|
| 698 |
# Mermaid Graph Visualization
|
| 699 |
+
st.markdown("###### Chat Relationship Tree π³")
|
| 700 |
+
mermaid_code = generate_mermaid_graph(chat_lines)
|
| 701 |
+
mermaid_html = f"""
|
| 702 |
+
<script src="https://cdn.jsdelivr.net/npm/mermaid/dist/mermaid.min.js"></script>
|
| 703 |
+
<div class="mermaid" style="height: 200px; overflow: auto;">{mermaid_code}</div>
|
| 704 |
+
<script>mermaid.initialize({{startOnLoad:true}});</script>
|
| 705 |
+
"""
|
| 706 |
+
components.html(mermaid_html, height=250)
|
| 707 |
+
|
| 708 |
+
with st.container():
|
| 709 |
+
if st.session_state.quote_line:
|
| 710 |
+
st.markdown(f"###### Quoting: {st.session_state.quote_line}")
|
| 711 |
+
quote_response = st.text_area("Add your response", key="quote_response", value=st.session_state.message_text)
|
| 712 |
+
paste_result_quote = paste_image_button("π Paste Image or Text with Quote", key="paste_button_quote")
|
| 713 |
+
if paste_result_quote.image_data is not None:
|
| 714 |
+
if isinstance(paste_result_quote.image_data, str):
|
| 715 |
+
st.session_state.message_text = paste_result_quote.image_data
|
| 716 |
+
st.text_area("Add your response", key="quote_response", value=st.session_state.message_text)
|
| 717 |
+
else:
|
| 718 |
+
st.image(paste_result_quote.image_data, caption="Received Image for Quote")
|
| 719 |
+
filename = await save_pasted_image(paste_result_quote.image_data, st.session_state.username)
|
| 720 |
+
if filename:
|
| 721 |
+
st.session_state.pasted_image_data = filename
|
| 722 |
+
await save_chat_entry(st.session_state.username, f"Pasted image: {filename}", quote_line=st.session_state.quote_line, media_file=paste_result_quote.image_data)
|
| 723 |
+
if st.button("Send Quote π", key="send_quote"):
|
| 724 |
+
markdown_response = f"### Quote Response\n- **Original**: {st.session_state.quote_line}\n- **{st.session_state.username} Replies**: {quote_response}"
|
| 725 |
+
if st.session_state.pasted_image_data:
|
| 726 |
+
markdown_response += f"\n- **Image**: "
|
| 727 |
+
await save_chat_entry(st.session_state.username, f"Pasted image: {st.session_state.pasted_image_data}", quote_line=st.session_state.quote_line, media_file=st.session_state.pasted_image_data)
|
| 728 |
+
st.session_state.pasted_image_data = None
|
| 729 |
+
await save_chat_entry(st.session_state.username, markdown_response, is_markdown=True, quote_line=st.session_state.quote_line)
|
| 730 |
+
st.session_state.quote_line = None
|
| 731 |
+
st.session_state.message_text = ''
|
| 732 |
+
st.rerun()
|
| 733 |
+
|
| 734 |
+
current_selection = st.session_state.username if st.session_state.username in FUN_USERNAMES else ""
|
| 735 |
+
new_username = st.selectbox("Change Name and Voice", [""] + list(FUN_USERNAMES.keys()), index=(list(FUN_USERNAMES.keys()).index(current_selection) + 1 if current_selection else 0), format_func=lambda x: f"{x} ({FUN_USERNAMES.get(x, 'No Voice')})" if x else "Select a name")
|
| 736 |
+
if new_username and new_username != st.session_state.username:
|
| 737 |
+
await save_chat_entry("System π", f"{st.session_state.username} changed name to {new_username}")
|
| 738 |
+
st.session_state.username = new_username
|
| 739 |
+
st.session_state.voice = FUN_USERNAMES[new_username]
|
| 740 |
+
st.markdown(f"**ποΈ Voice Changed**: {st.session_state.voice} π£οΈ for {st.session_state.username}")
|
| 741 |
st.rerun()
|
| 742 |
|
| 743 |
+
# Message input with Send button on the right
|
| 744 |
+
col_input, col_send = st.columns([5, 1])
|
| 745 |
+
with col_input:
|
| 746 |
+
message = st.text_input(f"Message as {st.session_state.username} (Voice: {st.session_state.voice})", key="message_input", value=st.session_state.message_text)
|
| 747 |
+
with col_send:
|
| 748 |
+
if st.button("Send π", key="send_button"):
|
| 749 |
+
if message.strip():
|
| 750 |
+
audio_file = await save_chat_entry(st.session_state.username, message, is_markdown=True)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 751 |
if audio_file:
|
| 752 |
+
st.session_state.audio_cache[f"{message}_{FUN_USERNAMES[st.session_state.username]}"] = audio_file
|
| 753 |
+
st.audio(audio_file) # Immediate preview
|
| 754 |
+
if st.session_state.pasted_image_data:
|
| 755 |
+
await save_chat_entry(st.session_state.username, f"Pasted image: {st.session_state.pasted_image_data}", media_file=st.session_state.pasted_image_data)
|
| 756 |
+
st.session_state.pasted_image_data = None
|
| 757 |
+
st.session_state.message_text = ''
|
| 758 |
+
st.rerun()
|
| 759 |
+
|
| 760 |
+
paste_result_msg = paste_image_button("π Paste Image or Text with Message", key="paste_button_msg")
|
| 761 |
+
if paste_result_msg.image_data is not None:
|
| 762 |
+
if isinstance(paste_result_msg.image_data, str):
|
| 763 |
+
st.session_state.message_text = paste_result_msg.image_data
|
| 764 |
+
st.text_input(f"Message as {st.session_state.username} (Voice: {st.session_state.voice})", key="message_input_paste", value=st.session_state.message_text)
|
| 765 |
else:
|
| 766 |
+
st.image(paste_result_msg.image_data, caption="Received Image for Quote")
|
| 767 |
+
filename = await save_pasted_image(paste_result_msg.image_data, st.session_state.username)
|
| 768 |
+
if filename:
|
| 769 |
+
await save_chat_entry(st.session_state.username, f"Pasted image: {filename}", media_file=paste_result_msg.image_data)
|
| 770 |
+
st.session_state.pasted_image_data = None
|
| 771 |
+
|
| 772 |
+
with st.container():
|
| 773 |
+
tab_main = st.radio("Action:", ["πΈ Media", "π ArXiv", "π Editor"], horizontal=True, label_visibility="collapsed")
|
| 774 |
+
useArxiv = st.checkbox("Search Arxiv for Research Paper Answers", value=True, label_visibility="collapsed")
|
| 775 |
+
useArxivAudio = st.checkbox("Generate Audio File for Research Paper Answers", value=False, label_visibility="collapsed")
|
| 776 |
+
|
| 777 |
+
st.markdown("###### Upload Media π¨πΆππ₯")
|
| 778 |
+
uploaded_file = st.file_uploader("Upload Media", type=['png', 'jpg', 'mp4', 'mp3', 'wav', 'pdf', 'txt', 'md', 'py'])
|
| 779 |
+
if uploaded_file:
|
| 780 |
+
timestamp = format_timestamp_prefix(st.session_state.username)
|
| 781 |
+
username = st.session_state.username
|
| 782 |
+
ext = uploaded_file.name.split('.')[-1]
|
| 783 |
+
file_hash = hashlib.md5(uploaded_file.getbuffer()).hexdigest()[:8]
|
| 784 |
+
if file_hash not in st.session_state.image_hashes:
|
| 785 |
+
filename = f"{timestamp}-{file_hash}.{ext}"
|
| 786 |
+
file_path = filename # Top-level file
|
| 787 |
+
await asyncio.to_thread(lambda: open(file_path, 'wb').write(uploaded_file.getbuffer()))
|
| 788 |
+
st.success(f"Uploaded {filename}")
|
| 789 |
+
if ext == 'pdf':
|
| 790 |
+
pdf_filename, texts, audio_files = await save_pdf_and_generate_audio(uploaded_file, username)
|
| 791 |
+
await save_chat_entry(username, f"Uploaded PDF: {pdf_filename}", media_file=pdf_filename)
|
| 792 |
+
for i, (text, audio_file) in enumerate(zip(texts, audio_files)):
|
| 793 |
+
if audio_file:
|
| 794 |
+
with open(CHAT_FILE, 'a') as f:
|
| 795 |
+
f.write(f" [{timestamp}] Page {i+1} Audio: {audio_file}\n")
|
| 796 |
+
else:
|
| 797 |
+
await save_chat_entry(username, f"Uploaded media: {file_path}", media_file=file_path)
|
| 798 |
+
await save_chat_history_with_image(username, file_path)
|
| 799 |
+
st.session_state.image_hashes.add(file_hash)
|
| 800 |
+
if file_path.endswith('.mp4'):
|
| 801 |
+
st.session_state.media_notifications.append(file_path)
|
| 802 |
+
|
| 803 |
+
# Big Red Delete Button
|
| 804 |
+
st.markdown("###### π Danger Zone")
|
| 805 |
+
if st.button("Try Not To Delete It All On Your First Day", key="delete_all", help="Deletes all user-added files!", type="primary", use_container_width=True, label_visibility="collapsed"):
|
| 806 |
+
deleted_files = delete_user_files()
|
| 807 |
+
if deleted_files:
|
| 808 |
+
st.markdown("### ποΈ Deleted Files:\n" + "\n".join([f"- `{file}`" for file in deleted_files]))
|
| 809 |
+
else:
|
| 810 |
+
st.markdown("### ποΈ Nothing to Delete!")
|
| 811 |
+
st.rerun()
|
| 812 |
|
| 813 |
+
st.markdown("###### Refresh β³")
|
| 814 |
+
refresh_rate = st.slider("Refresh Rate", 1, 300, st.session_state.refresh_rate, label_visibility="collapsed")
|
| 815 |
+
st.session_state.refresh_rate = refresh_rate
|
| 816 |
+
timer_placeholder = st.empty()
|
| 817 |
+
for i in range(st.session_state.refresh_rate, -1, -1):
|
| 818 |
+
font_name, font_func = random.choice(UNICODE_FONTS)
|
| 819 |
+
countdown_str = "".join(UNICODE_DIGITS[int(d)] for d in str(i)) if i < 10 else font_func(str(i))
|
| 820 |
+
timer_placeholder.markdown(f"<small>β³ {font_func('Refresh in:')} {countdown_str}</small>", unsafe_allow_html=True)
|
| 821 |
+
time.sleep(1)
|
| 822 |
+
st.rerun()
|
| 823 |
|
| 824 |
# Separate Galleries for Own and Shared Files
|
| 825 |
+
with st.container():
|
| 826 |
+
all_files = glob.glob("*.md") + glob.glob("*.pdf") + glob.glob("*.txt") + glob.glob("*.py") + glob.glob("*.png") + glob.glob("*.jpg") + glob.glob("*.mp3") + glob.glob("*.mp4") + glob.glob(os.path.join(MEDIA_DIR, "*.b64"))
|
| 827 |
+
own_files = [f for f in all_files if st.session_state.user_id in os.path.basename(f)]
|
| 828 |
+
shared_files = [f for f in all_files if f not in own_files and not f in [CHAT_FILE, QUOTE_VOTES_FILE, MEDIA_VOTES_FILE, HISTORY_FILE, STATE_FILE, os.path.join(MEDIA_DIR, "*")]]
|
| 829 |
+
|
| 830 |
+
st.markdown("###### Your Files π")
|
| 831 |
+
st.markdown("###### Image Gallery πΌ")
|
| 832 |
+
own_image_files = [f for f in own_files if f.endswith(('.png', '.jpg', '.b64'))]
|
| 833 |
+
image_cols = st.slider("Image Gallery Columns πΌ (Own)", min_value=1, max_value=15, value=5, label_visibility="collapsed")
|
| 834 |
+
cols = st.columns(image_cols)
|
| 835 |
+
for idx, image_file in enumerate(own_image_files):
|
| 836 |
+
with cols[idx % image_cols]:
|
| 837 |
+
if image_file.endswith('.b64'):
|
| 838 |
+
display_base64_media(os.path.basename(image_file))
|
| 839 |
+
else:
|
| 840 |
+
st.image(image_file, use_container_width=True)
|
| 841 |
+
|
| 842 |
+
st.markdown("###### Video Gallery π₯")
|
| 843 |
+
own_video_files = [f for f in own_files if f.endswith('.mp4')]
|
| 844 |
+
video_cols = st.slider("Video Gallery Columns π¬ (Own)", min_value=1, max_value=5, value=3, label_visibility="collapsed")
|
| 845 |
+
cols = st.columns(video_cols)
|
| 846 |
+
for idx, video_file in enumerate(own_video_files):
|
| 847 |
+
with cols[idx % video_cols]:
|
| 848 |
+
st.markdown(get_video_html(video_file), unsafe_allow_html=True)
|
| 849 |
+
|
| 850 |
+
st.markdown("###### Audio Gallery π§")
|
| 851 |
+
own_audio_files = [f for f in own_files if f.endswith(('.mp3', '.wav'))]
|
| 852 |
+
audio_cols = st.slider("Audio Gallery Columns πΆ (Own)", min_value=1, max_value=15, value=5, label_visibility="collapsed")
|
| 853 |
+
cols = st.columns(audio_cols)
|
| 854 |
+
for idx, audio_file in enumerate(own_audio_files):
|
| 855 |
+
with cols[idx % audio_cols]:
|
| 856 |
+
st.markdown(await get_audio_html(audio_file), unsafe_allow_html=True)
|
| 857 |
+
|
| 858 |
+
st.markdown("###### Shared Files π€")
|
| 859 |
+
st.markdown("###### Image Gallery πΌ")
|
| 860 |
+
shared_image_files = [f for f in shared_files if f.endswith(('.png', '.jpg', '.b64'))]
|
| 861 |
+
image_cols = st.slider("Image Gallery Columns πΌ (Shared)", min_value=1, max_value=15, value=5, label_visibility="collapsed")
|
| 862 |
+
cols = st.columns(image_cols)
|
| 863 |
+
for idx, image_file in enumerate(shared_image_files):
|
| 864 |
+
with cols[idx % image_cols]:
|
| 865 |
+
if image_file.endswith('.b64'):
|
| 866 |
+
display_base64_media(os.path.basename(image_file))
|
| 867 |
+
else:
|
| 868 |
+
st.image(image_file, use_container_width=True)
|
| 869 |
+
|
| 870 |
+
st.markdown("###### Video Gallery π₯")
|
| 871 |
+
shared_video_files = [f for f in shared_files if f.endswith('.mp4')]
|
| 872 |
+
video_cols = st.slider("Video Gallery Columns π¬ (Shared)", min_value=1, max_value=5, value=3, label_visibility="collapsed")
|
| 873 |
+
cols = st.columns(video_cols)
|
| 874 |
+
for idx, video_file in enumerate(shared_video_files):
|
| 875 |
+
with cols[idx % video_cols]:
|
| 876 |
+
st.markdown(get_video_html(video_file), unsafe_allow_html=True)
|
| 877 |
+
|
| 878 |
+
st.markdown("###### Audio Gallery π§")
|
| 879 |
+
shared_audio_files = [f for f in shared_files if f.endswith(('.mp3', '.wav'))]
|
| 880 |
+
audio_cols = st.slider("Audio Gallery Columns πΆ (Shared)", min_value=1, max_value=15, value=5, label_visibility="collapsed")
|
| 881 |
+
cols = st.columns(audio_cols)
|
| 882 |
+
for idx, audio_file in enumerate(shared_audio_files):
|
| 883 |
+
with cols[idx % audio_cols]:
|
| 884 |
+
st.markdown(await get_audio_html(audio_file), unsafe_allow_html=True)
|
| 885 |
|
| 886 |
# Full Log at End with Download
|
| 887 |
+
with st.container():
|
| 888 |
+
st.markdown("###### Full Chat Log π")
|
| 889 |
+
with open(CHAT_FILE, 'r') as f:
|
| 890 |
+
history_content = f.read()
|
| 891 |
+
st.markdown(history_content)
|
| 892 |
+
st.download_button("Download Chat Log as .md", history_content, file_name=f"chat_{st.session_state.user_id}.md", mime="text/markdown", label_visibility="collapsed")
|
| 893 |
|
| 894 |
loop.run_until_complete(async_interface())
|
| 895 |
|