File size: 7,637 Bytes
b1fe0dd
 
 
 
 
 
 
 
 
e2ffbc8
962f379
b1fe0dd
 
 
 
 
 
 
 
 
962f379
 
 
 
 
 
 
 
 
6862189
 
 
 
962f379
b1fe0dd
 
 
 
 
6862189
a58b36c
6862189
e2ffbc8
6862189
 
 
e2ffbc8
6862189
 
e2ffbc8
 
6862189
 
 
 
e2ffbc8
 
6862189
e2ffbc8
6862189
 
 
 
 
 
a58b36c
6862189
 
 
 
 
 
 
 
 
d597cda
6862189
d597cda
b1fe0dd
 
 
6862189
962f379
6862189
962f379
b1fe0dd
 
 
6862189
ca4200f
 
6862189
ca4200f
6862189
ca4200f
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
6862189
 
 
 
ca4200f
6862189
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
ca4200f
6862189
ca4200f
6862189
 
 
 
 
 
 
 
ca4200f
 
6862189
 
 
ca4200f
6862189
 
 
 
ca4200f
 
 
 
6862189
ca4200f
 
6862189
 
 
 
 
 
 
ca4200f
6862189
 
 
 
 
ca4200f
 
6862189
 
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
191
192
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
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
FUN_USERNAMES = [
    "CosmicJester 🌌", "PixelPanda 🐼", "QuantumQuack πŸ¦†", "StellarSquirrel 🐿️",
    "GizmoGuru βš™οΈ", "NebulaNinja 🌠", "ByteBuster πŸ’Ύ", "GalacticGopher 🌍",
    "RocketRaccoon πŸš€", "EchoElf 🧝", "PhantomFox 🦊", "WittyWizard πŸ§™",
    "LunarLlama πŸŒ™", "SolarSloth β˜€οΈ", "AstroAlpaca πŸ¦™", "CyberCoyote 🐺",
    "MysticMoose 🦌", "GlitchGnome 🧚", "VortexViper 🐍", "ChronoChimp πŸ’"
]

# Directories
LOG_DIR = "user_logs"
os.makedirs(LOG_DIR, exist_ok=True)

# Node name
def get_node_name():
    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

def get_log_file(username):
    timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
    # Replace spaces and emojis with underscores for valid filenames
    safe_username = username.replace(" ", "_").encode('ascii', 'ignore').decode('ascii')
    return os.path.join(LOG_DIR, f"{safe_username}_{timestamp}.md")

def save_log_entry(username, entry):
    log_file = get_log_file(username)
    try:
        with open(log_file, 'a') as f:
            # Format as Markdown with timestamp
            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

def load_latest_log(username):
    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()
        # Add line numbers to the Markdown content
        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 = {}

async def websocket_handler(websocket, path):
    try:
        client_id = str(uuid.uuid4())
        room_id = "logs"  # Simplified to a single logs room
        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]

async def broadcast_message(message, room_id):
    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]

async def start_websocket_server(host='0.0.0.0', port=8765):
    server = await websockets.serve(websocket_handler, host, port)
    logger.info(f"WebSocket server started on ws://{host}:{port}")
    return server

class LogBroadcastHandler(logging.Handler):
    def __init__(self, username):
        super().__init__()
        self.username = username

    def emit(self, record):
        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:
            # Notify WebSocket clients of new 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"))

def create_gradio_interface(username):
    logger.handlers = [LogBroadcastHandler(username)]  # Replace handlers with user-specific one
    with gr.Blocks(title=f"Log Viewer: {NODE_NAME}") as interface:
        interface.css = """
            .code-container { font-family: monospace; background: #1e1e1e; color: #d4d4d4; padding: 10px; border-radius: 5px; }
        """
        interface.js = f"""
        () => {{
            setInterval(async () => {{
                const response = await fetch('/get_log?username={username}');
                const logContent = await response.text();
                document.getElementById('log-display').innerText = logContent;
            }}, 900); // Refresh every 0.9 seconds
        }}
        """

        gr.Markdown(f"# Log Viewer for {username} on Node: {NODE_NAME}")
        gr.Markdown("Your logs are displayed below, auto-refreshing every 0.9 seconds.")
        log_display = gr.Code(label="Your Latest Log", language="markdown", lines=20, elem_classes=["code-container"], elem_id="log-display")

        # Custom endpoint to fetch log
        def get_log():
            return load_latest_log(username)
        interface.load(get_log, [], [log_display])

    return interface

async def main():
    global NODE_NAME
    NODE_NAME, port = get_node_name()
    await start_websocket_server()

    # Assign a random username on startup
    username = random.choice(FUN_USERNAMES)
    logger.info(f"Assigned username: {username}")

    # Create and run Gradio interface
    interface = create_gradio_interface(username)
    
    from starlette.routing import Route
    from starlette.responses import PlainTextResponse
    
    async def get_log_endpoint(request):
        return PlainTextResponse(load_latest_log(username))
    
    app = gr.routes.App.create_app(interface)
    app.routes.append(Route("/get_log", get_log_endpoint))
    
    import uvicorn
    await uvicorn.Server(uvicorn.Config(app, host="0.0.0.0", port=port)).serve()

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