Update app.py
Browse files
app.py
CHANGED
@@ -133,7 +133,7 @@ if 'user_hash' not in st.session_state:
|
|
133 |
def format_timestamp_prefix(username):
|
134 |
central = pytz.timezone('US/Central')
|
135 |
now = datetime.now(central)
|
136 |
-
return f"{now.strftime('%I-%M-%p-ct-%m-%d-%Y')}-by-{username}"
|
137 |
|
138 |
# Compute image hash from binary data
|
139 |
def compute_image_hash(image_data):
|
@@ -385,7 +385,7 @@ async def async_edge_tts_generate(text, voice, rate=0, pitch=0, file_format="mp3
|
|
385 |
return None
|
386 |
|
387 |
# Audio player - tunes blast off! ππ
|
388 |
-
def play_and_download_audio(file_path
|
389 |
if file_path and os.path.exists(file_path):
|
390 |
with open(file_path, "rb") as f:
|
391 |
audio_data = f.read()
|
@@ -394,9 +394,10 @@ def play_and_download_audio(file_path, timestamp):
|
|
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;"
|
398 |
'''
|
399 |
-
|
|
|
400 |
|
401 |
# Image saver - pics preserved with naming! πΈπΎ
|
402 |
async def save_pasted_image(image, username):
|
@@ -535,6 +536,40 @@ def check_query_params():
|
|
535 |
st.session_state.user_id = q_value # Use as user_id if not a valid username
|
536 |
return q_value
|
537 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
538 |
# Main execution - letβs roll! π²π
|
539 |
def main():
|
540 |
NODE_NAME, port = get_node_name()
|
@@ -571,35 +606,45 @@ def main():
|
|
571 |
if not st.session_state.server_task:
|
572 |
st.session_state.server_task = loop.create_task(run_websocket_server())
|
573 |
|
574 |
-
# Unified Chat History at Top
|
575 |
st.subheader(f"{START_ROOM} Chat History π¬")
|
576 |
chat_content = await load_chat()
|
577 |
chat_lines = [line for line in chat_content.split('\n') if line.strip() and not line.startswith('#')]
|
578 |
-
current_entry = None
|
579 |
if chat_lines:
|
580 |
-
|
581 |
-
|
582 |
-
|
583 |
-
|
584 |
-
|
585 |
-
|
586 |
-
|
587 |
-
|
588 |
-
|
589 |
-
|
590 |
-
|
591 |
-
|
592 |
-
|
593 |
-
|
594 |
-
|
595 |
-
|
596 |
-
|
597 |
-
|
598 |
-
|
599 |
-
|
600 |
-
|
601 |
-
|
602 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
603 |
|
604 |
if st.session_state.quote_line:
|
605 |
st.markdown(f"### Quoting: {st.session_state.quote_line}")
|
@@ -614,7 +659,7 @@ def main():
|
|
614 |
filename = await save_pasted_image(paste_result_quote.image_data, st.session_state.username)
|
615 |
if filename:
|
616 |
st.session_state.pasted_image_data = filename
|
617 |
-
await save_chat_entry(st.session_state.username, f"Pasted image: {filename}", quote_line=st.session_state.quote_line)
|
618 |
if st.button("Send Quote π", key="send_quote"):
|
619 |
markdown_response = f"### Quote Response\n- **Original**: {st.session_state.quote_line}\n- **{st.session_state.username} Replies**: {quote_response}"
|
620 |
if st.session_state.pasted_image_data:
|
@@ -669,7 +714,7 @@ def main():
|
|
669 |
useArxivAudio = st.checkbox("Generate Audio File for Research Paper Answers", value=False)
|
670 |
|
671 |
st.subheader("Upload Media π¨πΆππ₯")
|
672 |
-
uploaded_file = st.file_uploader("Upload Media", type=['png', 'jpg', 'mp4', 'mp3', 'wav', 'pdf'])
|
673 |
if uploaded_file:
|
674 |
timestamp = format_timestamp_prefix(st.session_state.username)
|
675 |
username = st.session_state.username
|
@@ -715,36 +760,67 @@ def main():
|
|
715 |
time.sleep(1)
|
716 |
st.rerun()
|
717 |
|
718 |
-
#
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
719 |
st.subheader("Image Gallery πΌ")
|
720 |
-
|
721 |
-
image_cols = st.slider("Image Gallery Columns πΌ", min_value=1, max_value=15, value=5)
|
722 |
cols = st.columns(image_cols)
|
723 |
-
for idx, image_file in enumerate(
|
724 |
with cols[idx % image_cols]:
|
725 |
st.image(image_file, use_container_width=True)
|
726 |
|
727 |
st.subheader("Video Gallery π₯")
|
728 |
-
|
729 |
-
video_cols = st.slider("Video Gallery Columns π¬", min_value=1, max_value=5, value=3)
|
730 |
cols = st.columns(video_cols)
|
731 |
-
for idx, video_file in enumerate(
|
732 |
with cols[idx % video_cols]:
|
733 |
st.markdown(get_video_html(video_file, width="100%"), unsafe_allow_html=True)
|
734 |
|
735 |
st.subheader("Audio Gallery π§")
|
736 |
-
|
737 |
-
audio_cols = st.slider("Audio Gallery Columns πΆ", min_value=1, max_value=15, value=5)
|
738 |
cols = st.columns(audio_cols)
|
739 |
-
for idx, audio_file in enumerate(
|
740 |
with cols[idx % audio_cols]:
|
741 |
st.markdown(await get_audio_html(audio_file, width="100%"), unsafe_allow_html=True)
|
742 |
|
743 |
-
# Full Log at End
|
744 |
st.subheader("Full Chat Log π")
|
745 |
-
with open(
|
746 |
history_content = f.read()
|
747 |
st.markdown(history_content)
|
|
|
748 |
|
749 |
loop.run_until_complete(async_interface())
|
750 |
|
|
|
133 |
def format_timestamp_prefix(username):
|
134 |
central = pytz.timezone('US/Central')
|
135 |
now = datetime.now(central)
|
136 |
+
return f"{now.strftime('%I-%M-%p-ct-%m-%d-%Y')}-by-{username}-{st.session_state.user_id}"
|
137 |
|
138 |
# Compute image hash from binary data
|
139 |
def compute_image_hash(image_data):
|
|
|
385 |
return None
|
386 |
|
387 |
# Audio player - tunes blast off! ππ
|
388 |
+
def play_and_download_audio(file_path):
|
389 |
if file_path and os.path.exists(file_path):
|
390 |
with open(file_path, "rb") as f:
|
391 |
audio_data = f.read()
|
|
|
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>
|
398 |
'''
|
399 |
+
return audio_html
|
400 |
+
return ""
|
401 |
|
402 |
# Image saver - pics preserved with naming! πΈπΎ
|
403 |
async def save_pasted_image(image, username):
|
|
|
536 |
st.session_state.user_id = q_value # Use as user_id if not a valid username
|
537 |
return q_value
|
538 |
|
539 |
+
# Mermaid graph generator
|
540 |
+
def generate_mermaid_graph(chat_lines):
|
541 |
+
mermaid_code = "graph TD\n"
|
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] if ' ' in content else "System"
|
550 |
+
node_id = f"{user}_{i}"
|
551 |
+
nodes[node_id] = f"{user} @ {timestamp}"
|
552 |
+
|
553 |
+
if "Audio:" in line:
|
554 |
+
audio_file = line.split("Audio: ")[-1].strip()
|
555 |
+
nodes[f"audio_{i}"] = f"π΅ {os.path.basename(audio_file)}"
|
556 |
+
edges.append(f"{node_id} --> audio_{i}")
|
557 |
+
elif "Media:" in line:
|
558 |
+
media_file = line.split("Media: ")[-1].strip('![]()')
|
559 |
+
media_type = "πΌ" if media_file.endswith(('.png', '.jpg')) else "π₯" if media_file.endswith('.mp4') else "π"
|
560 |
+
nodes[f"media_{i}"] = f"{media_type} {os.path.basename(media_file)}"
|
561 |
+
edges.append(f"{node_id} --> media_{i}")
|
562 |
+
elif "> " in line: # Quote
|
563 |
+
parent_line = chat_lines[i-1] if i > 0 else None
|
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 f"```mermaid\n{mermaid_code}\n```"
|
572 |
+
|
573 |
# Main execution - letβs roll! π²π
|
574 |
def main():
|
575 |
NODE_NAME, port = get_node_name()
|
|
|
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.subheader(f"{START_ROOM} Chat History π¬")
|
611 |
chat_content = await load_chat()
|
612 |
chat_lines = [line for line in chat_content.split('\n') if line.strip() and not line.startswith('#')]
|
|
|
613 |
if chat_lines:
|
614 |
+
chat_by_minute = {}
|
615 |
+
for line in reversed(chat_lines):
|
616 |
+
timestamp = line.split('] ')[0][1:] if '] ' in line else "Unknown"
|
617 |
+
minute_key = timestamp[:16] # Up to minute
|
618 |
+
if minute_key not in chat_by_minute:
|
619 |
+
chat_by_minute[minute_key] = []
|
620 |
+
chat_by_minute[minute_key].append(line)
|
621 |
+
|
622 |
+
markdown_output = ""
|
623 |
+
for minute, lines in chat_by_minute.items():
|
624 |
+
minute_output = f"### {minute}\n"
|
625 |
+
for line in lines:
|
626 |
+
if ': ' in line and not line.startswith(' '):
|
627 |
+
user_message = line.split(': ', 1)[1]
|
628 |
+
minute_output += f"- π¬ **{user_message.split(' ')[0]}**: {user_message.split(' ', 1)[1] if ' ' in user_message else ''}\n"
|
629 |
+
elif "Audio:" in line:
|
630 |
+
audio_file = line.split("Audio: ")[-1].strip()
|
631 |
+
audio_html = play_and_download_audio(audio_file)
|
632 |
+
minute_output += f" - π΅ {audio_html}\n"
|
633 |
+
elif "Media:" in line:
|
634 |
+
media_file = line.split("Media: ")[-1].strip('![]()')
|
635 |
+
if media_file.endswith(('.png', '.jpg')):
|
636 |
+
minute_output += f" - πΌ <img src='file://{media_file}' width='100'>\n"
|
637 |
+
elif media_file.endswith('.mp4'):
|
638 |
+
minute_output += f" - π₯ <video src='file://{media_file}' width='100' controls autoplay muted loop></video>\n"
|
639 |
+
elif media_file.endswith('.pdf'):
|
640 |
+
minute_output += f" - π {os.path.basename(media_file)}\n"
|
641 |
+
markdown_output += minute_output
|
642 |
+
st.markdown(markdown_output, unsafe_allow_html=True)
|
643 |
+
|
644 |
+
# Mermaid Graph Visualization
|
645 |
+
st.subheader("Chat Relationship Tree π³")
|
646 |
+
mermaid_graph = generate_mermaid_graph(chat_lines)
|
647 |
+
st.markdown(mermaid_graph)
|
648 |
|
649 |
if st.session_state.quote_line:
|
650 |
st.markdown(f"### Quoting: {st.session_state.quote_line}")
|
|
|
659 |
filename = await save_pasted_image(paste_result_quote.image_data, st.session_state.username)
|
660 |
if filename:
|
661 |
st.session_state.pasted_image_data = filename
|
662 |
+
await save_chat_entry(st.session_state.username, f"Pasted image: {filename}", quote_line=st.session_state.quote_line, media_file=filename)
|
663 |
if st.button("Send Quote π", key="send_quote"):
|
664 |
markdown_response = f"### Quote Response\n- **Original**: {st.session_state.quote_line}\n- **{st.session_state.username} Replies**: {quote_response}"
|
665 |
if st.session_state.pasted_image_data:
|
|
|
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
|
|
|
760 |
time.sleep(1)
|
761 |
st.rerun()
|
762 |
|
763 |
+
# Separate Galleries for Own and Shared Files
|
764 |
+
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")
|
765 |
+
own_files = [f for f in all_files if st.session_state.user_id in f]
|
766 |
+
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]]
|
767 |
+
|
768 |
+
st.subheader("Your Files π")
|
769 |
+
st.subheader("Image Gallery πΌ")
|
770 |
+
own_image_files = [f for f in own_files if f.endswith(('.png', '.jpg'))]
|
771 |
+
image_cols = st.slider("Image Gallery Columns πΌ (Own)", min_value=1, max_value=15, value=5)
|
772 |
+
cols = st.columns(image_cols)
|
773 |
+
for idx, image_file in enumerate(own_image_files):
|
774 |
+
with cols[idx % image_cols]:
|
775 |
+
st.image(image_file, use_container_width=True)
|
776 |
+
|
777 |
+
st.subheader("Video Gallery π₯")
|
778 |
+
own_video_files = [f for f in own_files if f.endswith('.mp4')]
|
779 |
+
video_cols = st.slider("Video Gallery Columns π¬ (Own)", min_value=1, max_value=5, value=3)
|
780 |
+
cols = st.columns(video_cols)
|
781 |
+
for idx, video_file in enumerate(own_video_files):
|
782 |
+
with cols[idx % video_cols]:
|
783 |
+
st.markdown(get_video_html(video_file, width="100%"), unsafe_allow_html=True)
|
784 |
+
|
785 |
+
st.subheader("Audio Gallery π§")
|
786 |
+
own_audio_files = [f for f in own_files if f.endswith(('.mp3', '.wav'))]
|
787 |
+
audio_cols = st.slider("Audio Gallery Columns πΆ (Own)", min_value=1, max_value=15, value=5)
|
788 |
+
cols = st.columns(audio_cols)
|
789 |
+
for idx, audio_file in enumerate(own_audio_files):
|
790 |
+
with cols[idx % audio_cols]:
|
791 |
+
st.markdown(await get_audio_html(audio_file, width="100%"), unsafe_allow_html=True)
|
792 |
+
|
793 |
+
st.subheader("Shared Files π€")
|
794 |
st.subheader("Image Gallery πΌ")
|
795 |
+
shared_image_files = [f for f in shared_files if f.endswith(('.png', '.jpg'))]
|
796 |
+
image_cols = st.slider("Image Gallery Columns πΌ (Shared)", min_value=1, max_value=15, value=5)
|
797 |
cols = st.columns(image_cols)
|
798 |
+
for idx, image_file in enumerate(shared_image_files):
|
799 |
with cols[idx % image_cols]:
|
800 |
st.image(image_file, use_container_width=True)
|
801 |
|
802 |
st.subheader("Video Gallery π₯")
|
803 |
+
shared_video_files = [f for f in shared_files if f.endswith('.mp4')]
|
804 |
+
video_cols = st.slider("Video Gallery Columns π¬ (Shared)", min_value=1, max_value=5, value=3)
|
805 |
cols = st.columns(video_cols)
|
806 |
+
for idx, video_file in enumerate(shared_video_files):
|
807 |
with cols[idx % video_cols]:
|
808 |
st.markdown(get_video_html(video_file, width="100%"), unsafe_allow_html=True)
|
809 |
|
810 |
st.subheader("Audio Gallery π§")
|
811 |
+
shared_audio_files = [f for f in shared_files if f.endswith(('.mp3', '.wav'))]
|
812 |
+
audio_cols = st.slider("Audio Gallery Columns πΆ (Shared)", min_value=1, max_value=15, value=5)
|
813 |
cols = st.columns(audio_cols)
|
814 |
+
for idx, audio_file in enumerate(shared_audio_files):
|
815 |
with cols[idx % audio_cols]:
|
816 |
st.markdown(await get_audio_html(audio_file, width="100%"), unsafe_allow_html=True)
|
817 |
|
818 |
+
# Full Log at End with Download
|
819 |
st.subheader("Full Chat Log π")
|
820 |
+
with open(CHAT_FILE, 'r') as f:
|
821 |
history_content = f.read()
|
822 |
st.markdown(history_content)
|
823 |
+
st.download_button("Download Chat Log as .md", history_content, file_name=f"chat_{st.session_state.user_id}.md", mime="text/markdown")
|
824 |
|
825 |
loop.run_until_complete(async_interface())
|
826 |
|