|
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 |
|
|
|
|
|
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 = [ |
|
"CosmicJester π", "PixelPanda πΌ", "QuantumQuack π¦", "StellarSquirrel πΏοΈ", |
|
"GizmoGuru βοΈ", "NebulaNinja π ", "ByteBuster πΎ", "GalacticGopher π", |
|
"RocketRaccoon π", "EchoElf π§", "PhantomFox π¦", "WittyWizard π§", |
|
"LunarLlama π", "SolarSloth βοΈ", "AstroAlpaca π¦", "CyberCoyote πΊ", |
|
"MysticMoose π¦", "GlitchGnome π§", "VortexViper π", "ChronoChimp π" |
|
] |
|
|
|
|
|
LOG_DIR = "user_logs" |
|
os.makedirs(LOG_DIR, exist_ok=True) |
|
|
|
|
|
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 |
|
|
|
|
|
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") |
|
|
|
|
|
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 |
|
|
|
|
|
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 = {} |
|
|
|
|
|
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] |
|
|
|
|
|
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] |
|
|
|
|
|
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 |
|
|
|
|
|
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")) |
|
|
|
|
|
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"]) |
|
|
|
|
|
interface = gr.Interface( |
|
fn=update_log_display, |
|
inputs=[dummy_input], |
|
outputs=[log_display], |
|
live=True, |
|
title=f"Log Viewer: {NODE_NAME}" |
|
) |
|
return interface |
|
|
|
|
|
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()) |