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