File size: 8,754 Bytes
b1fe0dd
 
 
 
 
 
 
 
 
e2ffbc8
962f379
b1fe0dd
f9e683f
b1fe0dd
 
 
 
 
 
 
f9e683f
962f379
 
 
 
 
 
 
 
f9e683f
6862189
 
 
f9e683f
b1fe0dd
f9e683f
b1fe0dd
 
 
 
6862189
a58b36c
f9e683f
6862189
f9e683f
e2ffbc8
6862189
 
e2ffbc8
f9e683f
6862189
f9e683f
6862189
e2ffbc8
 
6862189
 
 
e2ffbc8
 
6862189
e2ffbc8
f9e683f
6862189
f9e683f
6862189
 
 
 
 
a58b36c
6862189
 
 
 
 
 
 
 
d597cda
6862189
d597cda
f9e683f
b1fe0dd
f9e683f
b1fe0dd
 
dfdec71
962f379
6862189
962f379
b1fe0dd
 
 
6862189
ca4200f
 
6862189
ca4200f
6862189
ca4200f
 
 
 
 
 
f9e683f
ca4200f
f9e683f
ca4200f
 
 
 
 
 
 
 
 
 
f9e683f
ca4200f
f9e683f
ca4200f
 
 
 
f9e683f
6862189
 
 
 
ca4200f
6862189
f9e683f
6862189
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
f9e683f
6862189
f9e683f
dfdec71
 
f9e683f
dfdec71
ca4200f
dfdec71
6862189
f9e683f
dfdec71
 
 
f9e683f
dfdec71
 
 
 
 
 
 
ca4200f
 
f9e683f
ca4200f
f9e683f
6862189
ca4200f
 
6862189
 
 
 
 
ca4200f
dfdec71
ca4200f
 
 
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
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
import gradio as gr
import asyncio
import websockets
import json
import uuid
import argparse
from datetime import datetime
import logging
import sys
import os
import random

# Configure logging - the gossip queen πŸ“’πŸ‘‘ sets up the chatterbox!
logging.basicConfig(
    level=logging.INFO,
    format='%(asctime)s - %(name)s - %(levelname)s - %(message)s',
    handlers=[logging.StreamHandler(sys.stdout)]
)
logger = logging.getLogger("chat-node")

# Fun usernames with emojis - the VIP list of quirky characters! πŸŽ‰πŸ˜œ
FUN_USERNAMES = [
    "CosmicJester 🌌", "PixelPanda 🐼", "QuantumQuack πŸ¦†", "StellarSquirrel 🐿️",
    "GizmoGuru βš™οΈ", "NebulaNinja 🌠", "ByteBuster πŸ’Ύ", "GalacticGopher 🌍",
    "RocketRaccoon πŸš€", "EchoElf 🧝", "PhantomFox 🦊", "WittyWizard πŸ§™",
    "LunarLlama πŸŒ™", "SolarSloth β˜€οΈ", "AstroAlpaca πŸ¦™", "CyberCoyote 🐺",
    "MysticMoose 🦌", "GlitchGnome 🧚", "VortexViper 🐍", "ChronoChimp πŸ’"
]

# Directory for logs - the secret vault where tales are stashed! πŸ—„οΈπŸ”’
LOG_DIR = "user_logs"
os.makedirs(LOG_DIR, exist_ok=True)

# Node name - the app’s codename generator, sneaky and slick! πŸ•΅οΈβ€β™‚οΈπŸ’Ύ
def get_node_name():
    """🎲 Spins the wheel of fate to name our node - a random alias or user pick! 🏷️"""
    parser = argparse.ArgumentParser(description='Start a chat node with a specific name')
    parser.add_argument('--node-name', type=str, default=None, help='Name for this chat node')
    parser.add_argument('--port', type=int, default=7860, help='Port to run the Gradio interface on')
    args = parser.parse_args()
    return args.node_name or f"node-{uuid.uuid4().hex[:8]}", args.port

# Log file generator - crafts a shiny new logbook with a timestamp twist! πŸ“…βœοΈ
def get_log_file(username):
    """⏰ Stamps the clock and names the log after our witty hero - fresh and ready! πŸ“œ"""
    timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
    safe_username = username.replace(" ", "_").encode('ascii', 'ignore').decode('ascii')
    return os.path.join(LOG_DIR, f"{safe_username}_{timestamp}.md")

# Log saver - the scribe that pens epic log tales into Markdown glory! πŸ–‹οΈπŸ“š
def save_log_entry(username, entry):
    """πŸ–ŒοΈ Scribbles a log entry into a Markdown masterpiece - tales of triumph and woe! 🎭"""
    log_file = get_log_file(username)
    try:
        with open(log_file, 'a') as f:
            f.write(f"```log\n[{entry['timestamp']}] {entry['level']}: {entry['message']}\n```\n")
        logger.info(f"Saved log entry to {log_file}")
        return log_file
    except Exception as e:
        logger.error(f"Error saving log to {log_file}: {e}")
        return None

# Log loader - the treasure hunter digging up the latest log loot! πŸ΄β€β˜ οΈπŸ’°
def load_latest_log(username):
    """πŸ” Hunts down the freshest log file and dresses it up with line numbers - snazzy! ✨"""
    safe_username = username.replace(" ", "_").encode('ascii', 'ignore').decode('ascii')
    log_files = [f for f in os.listdir(LOG_DIR) if f.startswith(safe_username) and f.endswith(".md")]
    if not log_files:
        return "# No logs yet\nStart interacting to generate logs!"
    latest_file = max(log_files, key=lambda f: os.path.getmtime(os.path.join(LOG_DIR, f)))
    try:
        with open(os.path.join(LOG_DIR, latest_file), 'r') as f:
            content = f.read()
        lines = content.strip().split('\n')
        numbered_content = "\n".join(f"{i+1}. {line}" for i, line in enumerate(lines))
        return f"# Log for {username} ({latest_file})\n\n{numbered_content}"
    except Exception as e:
        logger.error(f"Error loading log {latest_file}: {e}")
        return "# Error loading log\nCheck server logs for details."

active_connections = {}

# WebSocket handler - the bouncer at the log party, keeping the vibe alive! πŸŽ‰πŸšͺ
async def websocket_handler(websocket, path):
    """🎧 Listens at the door, letting log fans in and kicking out crashers! 🚨"""
    try:
        client_id = str(uuid.uuid4())
        room_id = "logs"
        active_connections.setdefault(room_id, {})[client_id] = websocket
        logger.info(f"Client {client_id} connected to logs")

        async for message in websocket:
            try:
                data = json.loads(message)
                data["timestamp"] = datetime.now().isoformat()
                await broadcast_message(data, room_id)
            except json.JSONDecodeError:
                await websocket.send(json.dumps({"type": "error", "content": "Invalid JSON", "timestamp": datetime.now().isoformat(), "sender": "system"}))
    except websockets.ConnectionClosed:
        logger.info(f"Client {client_id} disconnected from logs")
    finally:
        if room_id in active_connections and client_id in active_connections[room_id]:
            del active_connections[room_id][client_id]
            if not active_connections[room_id]:
                del active_connections[room_id]

# Broadcaster - the town crier shouting log updates to all who’ll listen! πŸ“£πŸŒ
async def broadcast_message(message, room_id):
    """πŸ“’ Blasts the latest log scoop to every ear in the room - no one misses out! πŸŽ™οΈ"""
    if room_id in active_connections:
        disconnected = []
        for client_id, ws in active_connections[room_id].items():
            try:
                await ws.send(json.dumps(message))
            except websockets.ConnectionClosed:
                disconnected.append(client_id)
        for client_id in disconnected:
            del active_connections[room_id][client_id]

# WebSocket server starter - lights the fuse on the log-streaming rocket! πŸš€πŸ”₯
async def start_websocket_server(host='0.0.0.0', port=8765):
    """🌐 Fires up the WebSocket engine, ready to zip logs at warp speed! ⚑"""
    server = await websockets.serve(websocket_handler, host, port)
    logger.info(f"WebSocket server started on ws://{host}:{port}")
    return server

# Log handler - the sneaky spy logging every move with a witty twist! πŸ•΅οΈβ€β™€οΈπŸ“
class LogBroadcastHandler(logging.Handler):
    def __init__(self, username):
        super().__init__()
        self.username = username

    def emit(self, record):
        """πŸ•΅οΈβ€β™‚οΈ Catches every whisper and scribbles it into the logbook with flair! ✍️"""
        entry = {
            "timestamp": datetime.now().isoformat(),
            "level": record.levelname,
            "message": self.format(record),
            "name": record.name
        }
        log_file = save_log_entry(self.username, entry)
        if log_file:
            asyncio.create_task(broadcast_message({
                "type": "log",
                "content": f"New log entry saved to {log_file}",
                "timestamp": entry["timestamp"],
                "sender": "system",
                "username": self.username
            }, "logs"))

# Interface maker - the artist painting a live log masterpiece! πŸŽ¨πŸ–ΌοΈ
def create_gradio_interface(username):
    """πŸ–ŒοΈ Whips up a snazzy UI canvas that updates live with log magic! 🌟"""
    logger.handlers = [LogBroadcastHandler(username)]
    def update_log_display(dummy_input):
        """🎭 The puppet master pulling fresh log strings for the show! 🎬"""
        return load_latest_log(username)

    with gr.Blocks(title=f"Log Viewer: {NODE_NAME}", css=".code-container { font-family: monospace; background: #1e1e1e; color: #d4d4d4; padding: 10px; border-radius: 5px; }") as interface:
        gr.Markdown(f"# Log Viewer for {username} on Node: {NODE_NAME}")
        gr.Markdown("Logs update live as new entries are written - watch the action unfold! πŸŽ₯")
        dummy_input = gr.Textbox(visible=False, value="trigger", interactive=True)
        log_display = gr.Code(label="Your Latest Log", language="markdown", lines=20, elem_classes=["code-container"])

        # Live interface FTW! πŸš€
        interface = gr.Interface(
            fn=update_log_display,
            inputs=[dummy_input],
            outputs=[log_display],
            live=True,
            title=f"Log Viewer: {NODE_NAME}"
        )
    return interface

# Main event - the ringmaster kicking off the log circus! πŸŽͺ🀑
async def main():
    """🎀 Drops the mic and starts the log party - it’s showtime, folks! πŸŽ‰"""
    global NODE_NAME
    NODE_NAME, port = get_node_name()
    await start_websocket_server()

    username = random.choice(FUN_USERNAMES)
    logger.info(f"Assigned username: {username}")

    interface = create_gradio_interface(username)
    import uvicorn
    await uvicorn.Server(uvicorn.Config(interface, host="0.0.0.0", port=port)).serve()

if __name__ == "__main__":
    asyncio.run(main())