Skip to content

Instantly share code, notes, and snippets.

@lukemilby
Created August 21, 2025 18:55
Show Gist options
  • Select an option

  • Save lukemilby/e29114ead04df3e4aa002f39ede1b07a to your computer and use it in GitHub Desktop.

Select an option

Save lukemilby/e29114ead04df3e4aa002f39ede1b07a to your computer and use it in GitHub Desktop.
htmx
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Chat with AI</title>
<!-- 1. Loading external libraries -->
<!-- htmx for AJAX and WebSocket functionality -->
<script src="https://unpkg.com/htmx.org@1.9.6"></script>
<!-- htmx WebSocket extension -->
<script src="https://unpkg.com/htmx.org/dist/ext/ws.js"></script>
<!-- marked for Markdown parsing -->
<script src="https://cdn.jsdelivr.net/npm/marked/marked.min.js"></script>
<!-- Tailwind CSS for styling -->
<script src="https://cdn.tailwindcss.com"></script>
<!-- highlight.js for code syntax highlighting -->
<script src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.7.0/highlight.min.js"></script>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.7.0/styles/github.min.css">
</head>
<body class="bg-gray-100 min-h-screen flex flex-col">
<!-- 2. Main container with htmx WebSocket connection -->
<div class="container mx-auto p-4 flex-grow flex flex-col" hx-ext="ws" ws-connect="ws://10.10.1.215:8090/ws">
<!-- 3. Chat messages container -->
<div id="chat" class="flex-grow overflow-y-auto bg-white rounded-lg shadow-md p-4 space-y-4"></div>
<form ws-send class="flex space-x-2">
<input type="text" name="text" placeholder="Type a message..." class="flex-grow px-4 py-2 boarder rounded-ld focus:outline-none focus:ring-2 focus:ring-blue-500">
<button type="submit"
class="px-4 py-2 bg-blue-500 text-white rounded-ld hover:bg-blue-600 focus:outline-none focue:ring-2 focus:ring-blue-500">Send</button>
</form>
</div>
<!-- 4. Message input form with htmx WebSocket send attribute -->
<script>
// 5. Variables to track current AI message
let currentAIMessage = null;
let aiMessageContent = '';
// 6. Configure marked library for Markdown parsing
console.log("script is running")
marked.setOptions({
break:true,
gfm: true,
highlight: function (code, lang) {
const language=hljs.getLanguage(lang) ? lang: 'plaintext';
return hljs.highlight(code, {language}).value;
}
});
//https://www.youtube.com/watch?v=bjlVqw7ALls&t=518s
// 7. Function to render Markdown content
function renderMarkdown(content) {
return marked.parse(content);
}
// 8. htmx WebSocket message handler
htmx.on("htmx:wsAfterMessage", (event) => {
console.log("htmx message")
console.log(JSON.parse(event.detail.message).text)
message = JSON.parse(event.detail.message).text
if (message.startsWith("AI: ")) {
if (currentAIMessage) {
const contentDiv = currentAIMessage.querySelector('.markdown-content');
contentDiv.innerHTML = renderMarkdown(aiMessageContent);
}
currentAIMessage = document.createElement('div');
currentAIMessage.className = 'message ai bg-gree-100 rounded-lg p-4';
currentAIMessage.innerHTML = '<strong class="text-green-700">AI:</strong> ';
const contentDiv = document.createElement('div');
contentDiv.className = 'markdown-content mt-2 text-gray-800';
currentAIMessage.appendChild(contentDiv);
chat.appendChild(currentAIMessage);
aiMessageContent = message.substring(4);
} else if (currentAIMessage) {
chat.scrollTop = chat.scrollHeight;
aiMessageContent += message;
}
if (currentAIMessage) {
const contentDiv = currentAIMessage.querySelector('.markdown-content');
contentDiv.innerHTML = renderMarkdown(aiMessageContent);
}
window.scrollTo(0, document.body.scrollHeight);
});
// Sending message tot he screen
document.querySelector('form').addEventListener('submit', (e)=> {
console.log("sending")
const input = e.target.querySelector('input');
const div = document.createElement('div');
div.className = 'message user bg-blue-100 rounded-lg p-4';
div.innerHTML = '<strong class="text-blue-700">User:</strong> ' +
'<span class="text-gray-800">' + input.value + '</span>';
document.getElementById('chat').appendChild(div);
if (currentAIMessage) {
const contentDiv = currentAIMessage.querySelector('.markdown-content');
contentDiv.innerHTML = renderMarkdown(aiMessageContent);
}
currentAIMessage = null;
aiMessageContent = '';
});
</script>
</body>
</html>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment