its working
This commit is contained in:
parent
3410f98d7e
commit
30d01b82b0
2 changed files with 119 additions and 41 deletions
90
server.py
90
server.py
|
|
@ -12,6 +12,7 @@
|
||||||
|
|
||||||
import asyncio
|
import asyncio
|
||||||
from fastapi import FastAPI, Request, WebSocket, WebSocketDisconnect
|
from fastapi import FastAPI, Request, WebSocket, WebSocketDisconnect
|
||||||
|
from fastapi.responses import HTMLResponse
|
||||||
import json
|
import json
|
||||||
from redis import asyncio as redis
|
from redis import asyncio as redis
|
||||||
|
|
||||||
|
|
@ -24,6 +25,43 @@ templates = Jinja2Templates(directory="templates")
|
||||||
# Redis connection
|
# Redis connection
|
||||||
redis_client = redis.Redis(host="localhost", port=6379, decode_responses=True)
|
redis_client = redis.Redis(host="localhost", port=6379, decode_responses=True)
|
||||||
|
|
||||||
|
new_button = """
|
||||||
|
|
||||||
|
<div id='after_chat_wrapper'>
|
||||||
|
<div
|
||||||
|
id='after_chat'
|
||||||
|
class='h-12'
|
||||||
|
_="
|
||||||
|
on load
|
||||||
|
wait 10ms
|
||||||
|
on intersection(intersecting) having threshold 0.5
|
||||||
|
if #go_to_bottom exists and intersecting
|
||||||
|
then remove #go_to_bottom
|
||||||
|
|
||||||
|
|
||||||
|
"
|
||||||
|
>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div id="after_form" class='mb-24'>
|
||||||
|
<button
|
||||||
|
id="go_to_bottom"
|
||||||
|
class="fixed bottom-0 right-0 px-2 py-1 rounded font-bold m-4 invisible"
|
||||||
|
hx-get="/null"
|
||||||
|
hx-swap="outerHTML"
|
||||||
|
hx-trigger="intersect from:#after_chat"
|
||||||
|
_="
|
||||||
|
on load
|
||||||
|
wait 10ms
|
||||||
|
remove .invisible
|
||||||
|
on click
|
||||||
|
go to bottom of the body
|
||||||
|
"
|
||||||
|
>
|
||||||
|
go to bottom
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
"""
|
||||||
# Constants
|
# Constants
|
||||||
REDIS_MESSAGE_LIST = "chat_messages"
|
REDIS_MESSAGE_LIST = "chat_messages"
|
||||||
WEBSOCKET_CHANNEL = "websocket_channel"
|
WEBSOCKET_CHANNEL = "websocket_channel"
|
||||||
|
|
@ -41,6 +79,7 @@ async def listen_to_redis():
|
||||||
await pubsub.subscribe(WEBSOCKET_CHANNEL)
|
await pubsub.subscribe(WEBSOCKET_CHANNEL)
|
||||||
|
|
||||||
async for message in pubsub.listen():
|
async for message in pubsub.listen():
|
||||||
|
print(message)
|
||||||
if message["type"] == "message":
|
if message["type"] == "message":
|
||||||
data = message["data"]
|
data = message["data"]
|
||||||
data = json.loads(data)
|
data = json.loads(data)
|
||||||
|
|
@ -52,11 +91,16 @@ async def listen_to_redis():
|
||||||
color_level = color_levels[level]
|
color_level = color_levels[level]
|
||||||
# Send message to all connected clients
|
# Send message to all connected clients
|
||||||
for name, websocket in connected_clients.items():
|
for name, websocket in connected_clients.items():
|
||||||
if data.get("name") == name:
|
html_data = ""
|
||||||
html_data = f'<div id="chat_room" hx-swap-oob="beforeend"><li class="px-4 py-2 h-12 w-64 rounded bg-green-{color_level} ml-8 my-4">{data.get("name")}: {data.get("chat_message")}</li></div>'
|
|
||||||
else:
|
if "chat_message" in data:
|
||||||
html_data = f'<div id="chat_room" hx-swap-oob="beforeend"><li class="px-4 py-2 h-12 w-64 rounded bg-blue-{color_level} my-4">{data.get("name")}: {data.get("chat_message")}</li></div>'
|
if data.get("name") == name:
|
||||||
await websocket.send_text(html_data)
|
html_data += f'<div id="chat_room" hx-swap-oob="beforeend" hx-swap="scroll:bottom"><li class="px-4 py-2 h-12 w-124 rounded bg-green-{color_level} ml-8 my-4">{data.get("name")}: {data.get("chat_message")}</li></div>'
|
||||||
|
else:
|
||||||
|
html_data += f'<div id="chat_room" hx-swap-oob="beforeend" hx-swap="scroll:bottom"><li class="px-4 py-2 h-12 w-124 rounded bg-blue-{color_level} my-4">{data.get("name")}: {data.get("chat_message")}</li></div>'
|
||||||
|
if "notification" in data:
|
||||||
|
html_data += f'<div id="notifications" hx-swap-oob="beforeend"><li class="px-4 py-2 h-12 w-64 rounded bg-blue-{color_level} my-4" _="on click remove me on load wait 3s remove me" hx-trigger="click, load delay:3s" hx-swap="outerHTML">{data.get("notification")}</li></div>'
|
||||||
|
await websocket.send_text(html_data + new_button)
|
||||||
|
|
||||||
|
|
||||||
@app.on_event("startup")
|
@app.on_event("startup")
|
||||||
|
|
@ -106,28 +150,41 @@ async def websocket_endpoint(websocket: WebSocket):
|
||||||
|
|
||||||
name = random.choice(chat_names)
|
name = random.choice(chat_names)
|
||||||
connected_clients[name] = websocket
|
connected_clients[name] = websocket
|
||||||
|
data = {
|
||||||
|
"name": name,
|
||||||
|
"notification": f"{name} joined the chat",
|
||||||
|
}
|
||||||
|
|
||||||
|
data_str = json.dumps(data)
|
||||||
|
|
||||||
|
await redis_client.publish(WEBSOCKET_CHANNEL, data_str)
|
||||||
|
|
||||||
# Send previous messages to the new client
|
# Send previous messages to the new client
|
||||||
messages = await redis_client.lrange(REDIS_MESSAGE_LIST, 0, -1)
|
messages = await redis_client.lrange(REDIS_MESSAGE_LIST, 0, -1)
|
||||||
for msg in messages:
|
for msg in messages:
|
||||||
msg_data = json.loads(msg)
|
msg_data = json.loads(msg)
|
||||||
color_level = color_levels[0] # Use first color level for old messages
|
color_level = color_levels[0] # Use first color level for old messages
|
||||||
if msg_data.get("name") == name:
|
html_data = ""
|
||||||
html_data = f'<div id="chat_room" hx-swap-oob="beforeend"><li class="px-4 py-2 h-12 w-64 rounded bg-green-{color_level} ml-8 my-4">{msg_data.get("name")}: {msg_data.get("chat_message")}</li></div>'
|
if "chat_message" in msg_data:
|
||||||
else:
|
if msg_data.get("name") == name:
|
||||||
html_data = f'<div id="chat_room" hx-swap-oob="beforeend"><li class="px-4 py-2 h-12 w-64 rounded bg-blue-{color_level} my-4">{msg_data.get("name")}: {msg_data.get("chat_message")}</li></div>'
|
html_data += f'<div id="chat_room" hx-swap-oob="beforeend"><li class="px-4 py-2 h-12 w-124 rounded bg-green-{color_level} ml-8 my-4" _="">{msg_data.get("name")}: {msg_data.get("chat_message")}</li></div>'
|
||||||
await websocket.send_text(html_data)
|
|
||||||
|
else:
|
||||||
|
html_data += f'<div id="chat_room" hx-swap-oob="beforeend"><li class="px-4 py-2 h-12 w-124 rounded bg-blue-{color_level} my-4" _="">{msg_data.get("name")}: {msg_data.get("chat_message")}</li></div>'
|
||||||
|
if "notification" in msg_data:
|
||||||
|
html_data += f'<div id="notifications" hx-swap-oob="beforeend"><li class="px-4 py-2 h-12 w-64 rounded bg-blue-{color_level} my-4">{msg_data.get("notification")}</li></div>'
|
||||||
|
await websocket.send_text(html_data + new_button)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
while True:
|
while True:
|
||||||
data = await websocket.receive_text()
|
data = await websocket.receive_text()
|
||||||
data = json.loads(data)
|
data = json.loads(data)
|
||||||
data["name"] = name
|
data["name"] = name
|
||||||
data_str = json.dumps(data)
|
data_str = json.dumps(data)
|
||||||
|
|
||||||
# Store message in Redis list
|
# Store message in Redis list
|
||||||
await redis_client.rpush(REDIS_MESSAGE_LIST, data_str)
|
await redis_client.rpush(REDIS_MESSAGE_LIST, data_str)
|
||||||
|
|
||||||
# Publish message to Redis
|
# Publish message to Redis
|
||||||
await redis_client.publish(WEBSOCKET_CHANNEL, data_str)
|
await redis_client.publish(WEBSOCKET_CHANNEL, data_str)
|
||||||
except WebSocketDisconnect:
|
except WebSocketDisconnect:
|
||||||
|
|
@ -139,6 +196,11 @@ async def root(request: Request):
|
||||||
return templates.TemplateResponse("index.html", {"request": request})
|
return templates.TemplateResponse("index.html", {"request": request})
|
||||||
|
|
||||||
|
|
||||||
|
@app.get("/null")
|
||||||
|
async def root(request: Request):
|
||||||
|
return HTMLResponse("")
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
import uvicorn
|
import uvicorn
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -7,62 +7,78 @@
|
||||||
<script src="https://unpkg.com/htmx.org@1.9.12"></script>
|
<script src="https://unpkg.com/htmx.org@1.9.12"></script>
|
||||||
<script src="https://unpkg.com/htmx.org@1.9.12/dist/ext/ws.js"></script>
|
<script src="https://unpkg.com/htmx.org@1.9.12/dist/ext/ws.js"></script>
|
||||||
<script src="https://cdn.jsdelivr.net/npm/@tailwindcss/browser@4"></script>
|
<script src="https://cdn.jsdelivr.net/npm/@tailwindcss/browser@4"></script>
|
||||||
<style>
|
<script src="https://unpkg.com/hyperscript.org@0.9.14"></script>
|
||||||
#ws-status {
|
|
||||||
position: fixed;
|
|
||||||
top: 10px;
|
|
||||||
right: 10px;
|
|
||||||
padding: 5px 10px;
|
|
||||||
border-radius: 5px;
|
|
||||||
font-weight: bold;
|
|
||||||
}
|
|
||||||
.connecting { background-color: orange; color: white; }
|
|
||||||
.connected { background-color: green; color: white; }
|
|
||||||
.disconnected { background-color: red; color: white; }
|
|
||||||
.error { background-color: darkred; color: white; }
|
|
||||||
</style>
|
|
||||||
<script>
|
<script>
|
||||||
window.onload = () => {
|
window.onload = () => {
|
||||||
document.getElementById("chatInput").focus();
|
document.getElementById("chatInput").focus();
|
||||||
const messagesContainer = document.getElementById("chat_room");
|
|
||||||
messagesContainer.scrollTop = messagesContainer.scrollHeight;
|
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body onload="window.scrollTo(0, document.body.scrollHeight);">
|
||||||
|
|
||||||
|
|
||||||
<div class="flex flex-col gap-2 justify-center items-center" hx-ext="ws" ws-connect="/ws">
|
<div class="flex flex-col gap-2 justify-center items-center" hx-ext="ws" ws-connect="/ws">
|
||||||
<h2>WebSocket HTML Example</h2>
|
<h2>WebSocket HTML Example</h2>
|
||||||
<div id="ws-status" class="disconnected">Disconnected</div>
|
<div id="ws-status"
|
||||||
<div id="notifications"></div>
|
class="disconnected fixed top-0 right-0 px-2 py-1 rounded font-bold m-4"
|
||||||
<ul id="chat_room" hx-swap-oob='beforeend'>
|
>Disconnected</div>
|
||||||
|
<ul
|
||||||
|
id="notifications"
|
||||||
|
class="fixed top-0 left-0 px-2 py-1 rounded font-bold m-4"
|
||||||
|
></ul>
|
||||||
|
<ul id="chat_room"
|
||||||
|
hx-swap-oob='beforeend'
|
||||||
|
>
|
||||||
</ul>
|
</ul>
|
||||||
<form id="form" ws-send class="flex gap-2 fixed bottom-0">
|
|
||||||
<input id="chatInput" name="chat_message" class='px-2 py-1 bg-gray-800 border border-gray-600 rounded' type="text">
|
<div id='after_chat_wrapper'>
|
||||||
|
</div>
|
||||||
|
<form id="form" ws-send class="flex gap-2 fixed bottom-0 w-full p-4 bg-gray-900">
|
||||||
|
<input id="chatInput" name="chat_message" class='w-124 min-h-24 rounded-lg px-2 py-1 mx-auto bg-gray-800 border border-gray-600 rounded' type="text">
|
||||||
</form>
|
</form>
|
||||||
|
<div id="after_form" class='mb-24'>
|
||||||
|
|
||||||
|
<button
|
||||||
|
id="go_to_bottom"
|
||||||
|
class="fixed bottom-0 right-0 px-2 py-1 rounded font-bold m-4 invisible"
|
||||||
|
hx-get="/null"
|
||||||
|
hx-swap="outerHTML"
|
||||||
|
hx-trigger="intersect from:#after_chat"
|
||||||
|
_="
|
||||||
|
on load
|
||||||
|
wait 10ms
|
||||||
|
remove .invisible
|
||||||
|
on click
|
||||||
|
go to bottom of the body
|
||||||
|
"
|
||||||
|
>
|
||||||
|
go to bottom
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</body>
|
</body>
|
||||||
<script>
|
<script>
|
||||||
document.addEventListener("htmx:wsConnecting", function() {
|
document.addEventListener("htmx:wsConnecting", function() {
|
||||||
updateStatus("Connecting...", "connecting");
|
updateStatus("Connecting...", "bg-orange-500");
|
||||||
});
|
});
|
||||||
|
|
||||||
document.addEventListener("htmx:wsOpen", function() {
|
document.addEventListener("htmx:wsOpen", function() {
|
||||||
updateStatus("Connected", "connected");
|
updateStatus("Connected", "bg-green-500");
|
||||||
});
|
});
|
||||||
|
|
||||||
document.addEventListener("htmx:wsClose", function() {
|
document.addEventListener("htmx:wsClose", function() {
|
||||||
updateStatus("Disconnected", "disconnected");
|
updateStatus("Disconnected", "bg-red-500");
|
||||||
});
|
});
|
||||||
|
|
||||||
document.addEventListener("htmx:wsError", function() {
|
document.addEventListener("htmx:wsError", function() {
|
||||||
updateStatus("Error!", "error");
|
updateStatus("Error!", "bg-red-500");
|
||||||
});
|
});
|
||||||
|
|
||||||
function updateStatus(text, statusClass) {
|
function updateStatus(text, statusClass) {
|
||||||
let statusElement = document.getElementById("ws-status");
|
let statusElement = document.getElementById("ws-status");
|
||||||
statusElement.textContent = text;
|
statusElement.textContent = text;
|
||||||
statusElement.className = statusClass;
|
statusElement.classList.remove('bg-orange-500', 'bg-green-500', 'bg-red-500');
|
||||||
|
statusElement.classList.add(statusClass);
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
</html>
|
</html>
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue