Function that claude suggested to fix warnings in the chat-server.py that duck.ai made for me...
def make_msg(msg_type, **kwargs):
    return json.dumps({"type": msg_type, **kwargs})

# usage:
await websocket.send(make_msg("echo", text=f"echo: {message}"))
await websocket.send(make_msg("error", text="Server error"))
await websocket.send(make_msg("chat", from_id=client_ids[websocket], ip=ip, msg=message))

Other pertinent bits:
import itertools
id_counter = itertools.count(1)
client_ids = {}  # websocket -> id

# on connect:
client_ids[websocket] = next(id_counter)

# when broadcasting:
data = json.dumps({"from": client_ids[websocket], "ip": ip, "msg": message})
websockets.broadcast(others, data)

Also, some js changes:

ws.addEventListener("message", (e) => {
    let msg;
    try {
        msg = JSON.parse(e.data);
    } catch (err) {
        console.warn("Non-JSON message:", e.data);
        return;
    }

    switch (msg.type) {
        case "chat":
            document.getElementById("from").textContent = msg.from_id;
            document.getElementById("msgDiv").textContent = msg.msg;
            break;
        case "echo":
        case "error":
            console.log(msg.type, msg.text);
            break;
        default:
            console.warn("Unknown message type:", msg);
    }
});