Spaces:
Sleeping
Sleeping
File size: 5,231 Bytes
a83c776 41c1a97 836475d c6b8a39 836475d a83c776 836475d a83c776 836475d a83c776 836475d c6b8a39 836475d a83c776 836475d c6b8a39 a83c776 836475d a83c776 836475d a83c776 836475d a83c776 836475d a83c776 836475d a83c776 836475d a83c776 836475d a83c776 9447266 c6b8a39 a83c776 836475d a83c776 836475d a83c776 836475d a83c776 836475d a83c776 836475d a83c776 836475d a83c776 |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 |
# 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)} |