its working

This commit is contained in:
Waylon S. Walker 2025-03-16 20:32:02 -05:00
parent 3410f98d7e
commit 30d01b82b0
2 changed files with 119 additions and 41 deletions

View file

@ -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,17 +150,30 @@ 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:
@ -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

View file

@ -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>