awacke1's picture
Update app.py
a83c776 verified
raw
history blame
5.23 kB
# app.py (Merged Version - World Versioning)
import streamlit as st
import asyncio
import websockets
import uuid
from datetime import datetime
import os
import random
import time
import hashlib
# from PIL import Image
import glob
import base64
import io
import streamlit.components.v1 as components
import edge_tts
# from audio_recorder_streamlit import audio_recorder
import nest_asyncio
import re
import pytz
import shutil
# import anthropic
# import openai
from PyPDF2 import PdfReader
import threading
import json
import zipfile
# from gradio_client import Client
from dotenv import load_dotenv
from streamlit_marquee import streamlit_marquee
from collections import defaultdict, Counter
import pandas as pd
from streamlit_js_eval import streamlit_js_eval
from PIL import Image # Needed for paste_image_component
# πŸ› οΈ Patch asyncio for nesting
nest_asyncio.apply()
# 🎨 Page Config
st.set_page_config(
page_title="πŸ€–πŸ—οΈ Shared World Builder πŸ†",
page_icon="πŸ—οΈ",
layout="wide",
initial_sidebar_state="expanded"
)
# --- Constants ---
# General
icons = 'πŸ€–πŸ—οΈπŸ—£οΈπŸ’Ύ'
Site_Name = 'πŸ€–πŸ—οΈ Shared World Builder πŸ—£οΈ'
START_ROOM = "World Lobby 🌍"
MEDIA_DIR = "." # Base directory for general files
STATE_FILE = "user_state.txt" # For remembering username
# User/Chat
FUN_USERNAMES = {
"BuilderBot πŸ€–": "en-US-AriaNeural", "WorldWeaver πŸ•ΈοΈ": "en-US-JennyNeural",
"Terraformer 🌱": "en-GB-SoniaNeural", "SkyArchitect ☁️": "en-AU-NatashaNeural",
"PixelPainter 🎨": "en-CA-ClaraNeural", "VoxelVortex πŸŒͺ️": "en-US-GuyNeural",
"CosmicCrafter ✨": "en-GB-RyanNeural", "GeoGuru πŸ—ΊοΈ": "en-AU-WilliamNeural",
"BlockBard 🧱": "en-CA-LiamNeural", "SoundSculptor πŸ”Š": "en-US-AnaNeural",
}
EDGE_TTS_VOICES = list(set(FUN_USERNAMES.values()))
CHAT_DIR = "chat_logs"
# Audio
AUDIO_CACHE_DIR = "audio_cache"
AUDIO_DIR = "audio_logs"
# World Builder
SAVED_WORLDS_DIR = "saved_worlds" # Directory for MD world files
PLOT_WIDTH = 50.0 # Needed for JS injection
PLOT_DEPTH = 50.0 # Needed for JS injection
# CSV_COLUMNS = [...] # No longer needed if not using CSVs
# File Emojis
FILE_EMOJIS = {"md": "πŸ“", "mp3": "🎡", "png": "πŸ–ΌοΈ", "mp4": "πŸŽ₯", "zip": "πŸ“¦", "json": "πŸ“„"}
# --- Directories ---
for d in [CHAT_DIR, AUDIO_DIR, AUDIO_CACHE_DIR, SAVED_WORLDS_DIR]:
os.makedirs(d, exist_ok=True)
# --- API Keys (Placeholder) ---
load_dotenv()
# --- Global State & Lock ---
world_objects_lock = threading.Lock()
world_objects = defaultdict(dict) # In-memory world state {obj_id: data}
connected_clients = set() # Holds client_id strings
# --- Helper Functions ---
def get_current_time_str(tz='US/Central'):
"""Gets formatted timestamp string."""
try:
timezone = pytz.timezone(tz)
except pytz.UnknownTimeZoneError:
timezone = pytz.utc
return datetime.now(timezone).strftime('%Y%m%d_%H%M%S')
def clean_filename_part(text, max_len=30):
"""Cleans a string part for use in a filename."""
text = re.sub(r'\s+', '_', text) # Replace spaces
text = re.sub(r'[^\w\-.]', '', text) # Keep word chars, hyphen, period
return text[:max_len]
def generate_world_save_filename(name="World"):
"""Generates a filename for saving world state MD files."""
timestamp = get_current_time_str()
clean_name = clean_filename_part(name)
# Add short random hash to prevent collisions further
rand_hash = hashlib.md5(str(time.time()).encode()).hexdigest()[:6]
return f"🌍_{clean_name}_{timestamp}_{rand_hash}.md"
def parse_world_filename(filename):
"""Extracts info from filename if possible, otherwise returns defaults."""
basename = os.path.basename(filename)
parts = basename.split('_')
if len(parts) >= 4 and parts[0] == '🌍' and parts[-1].endswith('.md'):
name = " ".join(parts[1:-2]) # Join parts between emoji and date
timestamp_str = parts[-2]
try:
# Attempt to parse timestamp for sorting/display
dt_obj = datetime.strptime(timestamp_str, '%Y%m%d_%H%M%S')
except ValueError:
dt_obj = None # Could not parse timestamp
return {"name": name, "timestamp": timestamp_str, "dt": dt_obj, "filename": filename}
else:
# Fallback for unknown format
return {"name": basename.replace('.md',''), "timestamp": "Unknown", "dt": None, "filename": filename}
# --- World State MD File Handling ---
def save_world_state_to_md(target_filename):
"""Saves the current in-memory world state to a specific MD file."""
global world_objects
save_path = os.path.join(SAVED_WORLDS_DIR, target_filename)
print(f"Saving {len(world_objects)} objects to MD file: {save_path}...")
# Prepare JSON data (acquire lock)
with world_objects_lock:
world_data_dict = dict(world_objects)
# Format Markdown content
parsed_info = parse_world_filename(target_filename)
timestamp = get_current_time_str() # Current time for header
md_content = f"""# World State: {parsed_info['name']}
* **Saved:** {timestamp}
* **Original Timestamp:** {parsed_info['timestamp']}
* **Objects:** {len(world_data_dict)}
```json
{json.dumps(world_data_dict, indent=2)}