Skip to content

Instantly share code, notes, and snippets.

@mihailik
Last active August 10, 2025 10:28
Show Gist options
  • Select an option

  • Save mihailik/86d363e8e0cc476c3a3f52e934b3e218 to your computer and use it in GitHub Desktop.

Select an option

Save mihailik/86d363e8e0cc476c3a3f52e934b3e218 to your computer and use it in GitHub Desktop.
Hello World: LLM chat in browser
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>TinyLLM In-Browser Chat</title>
<style>
body {
font-family: Arial, sans-serif;
margin: 0;
padding: 0;
background-color: #f0f2f5;
display: flex;
justify-content: center;
align-items: center;
min-height: 100vh;
color: #333;
}
#chat-container {
background-color: #fff;
border-radius: 10px;
box-shadow: 0 4px 15px rgba(0, 0, 0, 0.1);
width: 90%;
max-width: 600px;
display: flex;
flex-direction: column;
overflow: hidden;
min-height: 80vh;
max-height: 95vh;
position: relative;
}
h1 {
text-align: center;
color: #4a4a4a;
padding: 15px;
margin: 0;
border-bottom: 1px solid #eee;
font-size: 1.5em;
}
.status-message {
text-align: center;
padding: 10px;
background-color: #e0f7fa;
color: #00796b;
font-weight: bold;
border-bottom: 1px solid #b2ebf2;
}
#chat-box {
flex-grow: 1;
padding: 20px;
overflow-y: auto;
display: flex;
flex-direction: column;
gap: 10px;
}
.message-container {
display: flex;
flex-direction: column;
max-width: 80%;
}
.message-container.user {
align-self: flex-end;
align-items: flex-end;
}
.message-container.assistant {
align-self: flex-start;
align-items: flex-start;
}
.message-bubble {
padding: 10px 15px;
border-radius: 20px;
line-height: 1.4;
word-wrap: break-word;
white-space: pre-wrap; /* Preserve whitespace and line breaks */
}
or: white;
border-bottom-right-radius: 5px;
}
.message-container.assistant.message-bubble {
background-color: #e9e9eb;
color: #333;
border-bottom-left-radius: 5px;
}
.chat-input-container {
display: flex;
padding: 15px;
border-top: 1px solid #eee;
background-color: #fff;
gap: 10px;
}
#user-input {
flex-grow: 1;
padding: 10px 15px;
border: 1px solid #ddd;
border-radius: 20px;
font-size: 1em;
outline: none;
}
#user-input:focus {
border-color: #007bff;
}
#send {
padding: 10px 20px;
background-color: #007bff;
color: white;
border: none;
border-radius: 20px;
cursor: pointer;
font-size: 1em;
transition: background-color 0.2s;
}
#send:hover:not(:disabled) {
background-color: #0056b3;
}
#send:disabled {
background-color: #a0c9ff;
cursor: not-allowed;
}
.chat-stats {
font-size: 0.8em;
color: #666;
text-align: right;
padding: 5px 20px;
background-color: #f9f9f9;
border-top: 1px solid #eee;
}
.hidden {
display: none!important;
}
</style>
</head>
<body>
<div id="chat-container">
<h1>In-Browser LLM Chat</h1>
<div id="download-status" class="status-message">Loading model...</div>
<div id="chat-box">
</div>
<div id="chat-stats" class="chat-stats hidden"></div>
<div class="chat-input-container">
<input type="text" id="user-input" placeholder="Loading model..." disabled>
<button id="send" disabled>Send</button>
</div>
</div>
<script type="module">
import * as webllm from "https://esm.run/@mlc-ai/web-llm";
/*************** WebLLM Logic ***************/
const messages =;
const chatBox = document.getElementById("chat-box");
const userInput = document.getElementById("user-input");
const sendButton = document.getElementById("send");
const downloadStatus = document.getElementById("download-status");
const chatStats = document.getElementById("chat-stats");
let currentAssistantMessageElement = null; // To update the streaming message
// Callback function for initializing progress.
function updateEngineInitProgressCallback(report) {
console.log("initialize", report.progress, report.text);
downloadStatus.textContent = report.text;
}
// Create engine instance.
const engine = new webllm.MLCEngine();
engine.setInitProgressCallback(updateEngineInitProgressCallback);
async function initializeWebLLMEngine() {
downloadStatus.classList.remove("hidden");
const selectedModel = "TinyLlama-1.1B-Chat-v1.0"; // Recommended small model [1, 2]
const config = {
temperature: 0.7, // Controls randomness [3]
top_p: 0.9, // Controls diversity [3]
};
try {
await engine.reload(selectedModel, config); [4]
downloadStatus.textContent = "Model loaded. Ready to chat!";
sendButton.disabled = false;
userInput.disabled = false;
userInput.setAttribute("placeholder", "Type a message...");
console.log("WebLLM engine initialized successfully.");
} catch (error) {
downloadStatus.textContent = `Error loading model: ${error.message}`;
console.error("Error initializing WebLLM engine:", error);
}
}
/*************** UI Logic ***************/
// Helper function to append messages to the chat box
function appendMessage(message, isStreaming = false) {
const messageContainer = document.createElement("div");
messageContainer.classList.add("message-container", message.role);
const messageBubble = document.createElement("div");
messageBubble.classList.add("message-bubble");
messageBubble.textContent = message.content;
messageContainer.appendChild(messageBubble);
chatBox.appendChild(messageContainer);
chatBox.scrollTop = chatBox.scrollHeight; // Scroll to bottom
if (isStreaming && message.role === "assistant") {
currentAssistantMessageElement = messageBubble;
}
}
// Helper function to update the content of the last assistant message (for streaming)
function updateLastAssistantMessage(newContent) {
if (currentAssistantMessageElement) {
currentAssistantMessageElement.textContent = newContent;
chatBox.scrollTop = chatBox.scrollHeight; // Scroll to bottom
}
}
// Function to handle sending a message
async function onMessageSend() {
const input = userInput.value.trim();
if (input.length === 0) {
return;
}
const userMessage = { content: input, role: "user" }; [5, 6, 3]
messages.push(userMessage);
appendMessage(userMessage);
userInput.value = "";
sendButton.disabled = true;
userInput.setAttribute("placeholder", "Generating response..."); [7]
const aiMessagePlaceholder = { content: "typing...", role: "assistant" }; [7]
appendMessage(aiMessagePlaceholder, true); // Mark as streaming message
let fullAssistantResponse = "";
chatStats.classList.add("hidden"); // Hide stats during generation
try {
const completion = await engine.chat.completions.create({
messages: messages,
stream: true[5, 6, 3]
// Parameters like temperature/top_p can be set here or in engine.reload
});
for await (const chunk of completion) { [5]
const curDelta = chunk.choices?.?.delta.content; [5, 7]
if (curDelta) {
fullAssistantResponse += curDelta;
updateLastAssistantMessage(fullAssistantResponse);
}
}
const finalMessage = await engine.getMessage(); // Get final message from engine [7]
messages.push({ content: finalMessage, role: "assistant" }); // Add final message to history
updateLastAssistantMessage(finalMessage); // Ensure final update
// Display performance stats
const usageText = await engine.runtimeStatsText(); // Get detailed stats string [7]
chatStats.classList.remove("hidden");
chatStats.textContent = usageText;
} catch (error) {
updateLastAssistantMessage(`Error: ${error.message}`);
console.error("Error during LLM inference:", error);
} finally {
sendButton.disabled = false;
userInput.disabled = false;
userInput.setAttribute("placeholder", "Type a message...");
currentAssistantMessageElement = null; // Clear reference
}
}
// Event Listeners
sendButton.addEventListener("click", onMessageSend);
userInput.addEventListener("keypress", (event) => {
if (event.key === "Enter" &&!sendButton.disabled) {
onMessageSend();
}
});
// Initialize the WebLLM engine when the page loads
document.addEventListener("DOMContentLoaded", initializeWebLLMEngine);
</script>
</body>
</html>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>TinyLLM In-Browser Chat</title>
<style>
body {
font-family: Arial, sans-serif;
margin: 0;
padding: 0;
background-color: #f0f2f5;
display: flex;
justify-content: center;
align-items: center;
min-height: 100vh;
color: #333;
}
#chat-container {
background-color: #fff;
border-radius: 10px;
box-shadow: 0 4px 15px rgba(0, 0, 0, 0.1);
width: 90%;
max-width: 600px;
display: flex;
flex-direction: column;
overflow: hidden;
min-height: 80vh;
max-height: 95vh;
position: relative;
}
h1 {
text-align: center;
color: #4a4a4a;
padding: 15px;
margin: 0;
border-bottom: 1px solid #eee;
font-size: 1.5em;
}
.status-message {
text-align: center;
padding: 10px;
background-color: #e0f7fa;
color: #00796b;
font-weight: bold;
border-bottom: 1px solid #b2ebf2;
}
#chat-box {
flex-grow: 1;
padding: 20px;
overflow-y: auto;
display: flex;
flex-direction: column;
gap: 10px;
}
.message-container {
display: flex;
flex-direction: column;
max-width: 80%;
}
.message-container.user {
align-self: flex-end;
align-items: flex-end;
}
.message-container.assistant {
align-self: flex-start;
align-items: flex-start;
}
.message-bubble {
padding: 10px 15px;
border-radius: 20px;
line-height: 1.4;
word-wrap: break-word;
white-space: pre-wrap; /* Preserve whitespace and line breaks */
}
.message-container.user.message-bubble {
background-color: #007bff;
color: white;
border-bottom-right-radius: 5px;
}
.message-container.assistant.message-bubble {
background-color: #e9e9eb;
color: #333;
border-bottom-left-radius: 5px;
}
.chat-input-container {
display: flex;
padding: 15px;
border-top: 1px solid #eee;
background-color: #fff;
gap: 10px;
}
#user-input {
flex-grow: 1;
padding: 10px 15px;
border: 1px solid #ddd;
border-radius: 20px;
font-size: 1em;
outline: none;
}
#user-input:focus {
border-color: #007bff;
}
#send {
padding: 10px 20px;
background-color: #007bff;
color: white;
border: none;
border-radius: 20px;
cursor: pointer;
font-size: 1em;
transition: background-color 0.2s;
}
#send:hover:not(:disabled) {
background-color: #0056b3;
}
#send:disabled {
background-color: #a0c9ff;
cursor: not-allowed;
}
.chat-stats {
font-size: 0.8em;
color: #666;
text-align: right;
padding: 5px 20px;
background-color: #f9f9f9;
border-top: 1px solid #eee;
}
.hidden {
display: none!important;
}
</style>
</head>
<body>
<div id="chat-container">
<h1>In-Browser LLM Chat</h1>
<div id="download-status" class="status-message">Loading model...</div>
<div id="chat-box">
</div>
<div id="chat-stats" class="chat-stats hidden"></div>
<div class="chat-input-container">
<input type="text" id="user-input" placeholder="Loading model..." disabled>
<button id="send" disabled>Send</button>
</div>
</div>
<script type="module">
import * as webllm from "https://esm.run/@mlc-ai/web-llm";
/*************** WebLLM Logic ***************/
const messages = []; // Corrected: Initialized as an empty array
const chatBox = document.getElementById("chat-box");
const userInput = document.getElementById("user-input");
const sendButton = document.getElementById("send");
const downloadStatus = document.getElementById("download-status");
const chatStats = document.getElementById("chat-stats");
let currentAssistantMessageElement = null; // To update the streaming message
// Callback function for initializing progress.
function updateEngineInitProgressCallback(report) {
console.log("initialize", report.progress, report.text);
downloadStatus.textContent = report.text;
}
// Create engine instance.
const engine = new webllm.MLCEngine();
engine.setInitProgressCallback(updateEngineInitProgressCallback);
async function initializeWebLLMEngine() {
downloadStatus.classList.remove("hidden");
const selectedModel = "TinyLlama-1.1B-Chat-v1.0"; // Recommended small model [1, 2]
const config = {
temperature: 0.7, // Controls randomness [3]
top_p: 0.9, // Controls diversity [3]
};
try {
await engine.reload(selectedModel, config); // [4]
downloadStatus.textContent = "Model loaded. Ready to chat!";
sendButton.disabled = false;
userInput.disabled = false;
userInput.setAttribute("placeholder", "Type a message...");
console.log("WebLLM engine initialized successfully.");
} catch (error) {
downloadStatus.textContent = `Error loading model: ${error.message}`;
console.error("Error initializing WebLLM engine:", error);
}
}
/*************** UI Logic ***************/
// Helper function to append messages to the chat box
function appendMessage(message, isStreaming = false) {
const messageContainer = document.createElement("div");
messageContainer.classList.add("message-container", message.role);
const messageBubble = document.createElement("div");
messageBubble.classList.add("message-bubble");
messageBubble.textContent = message.content;
messageContainer.appendChild(messageBubble);
chatBox.appendChild(messageContainer);
chatBox.scrollTop = chatBox.scrollHeight; // Scroll to bottom
if (isStreaming && message.role === "assistant") {
currentAssistantMessageElement = messageBubble;
}
}
// Helper function to update the content of the last assistant message (for streaming)
function updateLastAssistantMessage(newContent) {
if (currentAssistantMessageElement) {
currentAssistantMessageElement.textContent = newContent;
chatBox.scrollTop = chatBox.scrollHeight; // Scroll to bottom
}
}
// Function to handle sending a message
async function onMessageSend() {
const input = userInput.value.trim();
if (input.length === 0) {
return;
}
const userMessage = { content: input, role: "user" }; // [5, 6, 3]
messages.push(userMessage);
appendMessage(userMessage);
userInput.value = "";
sendButton.disabled = true;
userInput.setAttribute("placeholder", "Generating response..."); // [7]
const aiMessagePlaceholder = { content: "typing...", role: "assistant" }; // [7]
appendMessage(aiMessagePlaceholder, true); // Mark as streaming message
let fullAssistantResponse = "";
chatStats.classList.add("hidden"); // Hide stats during generation
try {
const completion = await engine.chat.completions.create({
messages: messages,
stream: true, // [5, 6, 3]
// Parameters like temperature/top_p can be set here or in engine.reload
});
for await (const chunk of completion) { // [5]
const curDelta = chunk.choices?.[0]?.delta.content; // [5, 7]
if (curDelta) {
fullAssistantResponse += curDelta;
updateLastAssistantMessage(fullAssistantResponse);
}
}
const finalMessage = await engine.getMessage(); // Get final message from engine [7]
messages.push({ content: finalMessage, role: "assistant" }); // Add final message to history
updateLastAssistantMessage(finalMessage); // Ensure final update
// Display performance stats
const usageText = await engine.runtimeStatsText(); // Get detailed stats string [7]
chatStats.classList.remove("hidden");
chatStats.textContent = usageText;
} catch (error) {
updateLastAssistantMessage(`Error: ${error.message}`);
console.error("Error during LLM inference:", error);
} finally {
sendButton.disabled = false;
userInput.disabled = false;
userInput.setAttribute("placeholder", "Type a message...");
currentAssistantMessageElement = null; // Clear reference
}
}
// Event Listeners
sendButton.addEventListener("click", onMessageSend);
userInput.addEventListener("keypress", (event) => {
if (event.key === "Enter" && !sendButton.disabled) {
onMessageSend();
}
});
// Initialize the WebLLM engine when the page loads
document.addEventListener("DOMContentLoaded", initializeWebLLMEngine);
</script>
</body>
</html>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>TinyLLM In-Browser Chat</title>
<style>
body {
font-family: Arial, sans-serif;
margin: 0;
padding: 0;
background-color: #f0f2f5;
display: flex;
justify-content: center;
align-items: center;
min-height: 100vh;
color: #333;
}
#chat-container {
background-color: #fff;
border-radius: 10px;
box-shadow: 0 4px 15px rgba(0, 0, 0, 0.1);
width: 90%;
max-width: 600px;
display: flex;
flex-direction: column;
overflow: hidden;
min-height: 80vh;
max-height: 95vh;
position: relative;
}
h1 {
text-align: center;
color: #4a4a4a;
padding: 15px;
margin: 0;
border-bottom: 1px solid #eee;
font-size: 1.5em;
}
.status-message {
text-align: center;
padding: 10px;
background-color: #e0f7fa;
color: #00796b;
font-weight: bold;
border-bottom: 1px solid #b2ebf2;
}
#chat-box {
flex-grow: 1;
padding: 20px;
overflow-y: auto;
display: flex;
flex-direction: column;
gap: 10px;
}
.message-container {
display: flex;
flex-direction: column;
max-width: 80%;
}
.message-container.user {
align-self: flex-end;
align-items: flex-end;
}
.message-container.assistant {
align-self: flex-start;
align-items: flex-start;
}
.message-container.system {
align-self: center; /* Center system messages */
align-items: center;
font-size: 0.85em;
color: #666;
text-align: center;
padding: 5px 10px;
border-radius: 10px;
background-color: #f0f0f0;
margin: 5px 0;
}
.message-bubble {
padding: 10px 15px;
border-radius: 20px;
line-height: 1.4;
word-wrap: break-word;
white-space: pre-wrap; /* Preserve whitespace and line breaks */
}
.message-container.user .message-bubble {
background-color: #007bff;
color: white;
border-bottom-right-radius: 5px;
}
.message-container.assistant .message-bubble {
background-color: #e9e9eb;
color: #333;
border-bottom-left-radius: 5px;
}
.chat-input-container {
display: flex;
padding: 15px;
border-top: 1px solid #eee;
background-color: #fff;
gap: 10px;
}
#user-input {
flex-grow: 1;
padding: 10px 15px;
border: 1px solid #ddd;
border-radius: 20px;
font-size: 1em;
outline: none;
}
#user-input:focus {
border-color: #007bff;
}
#send {
padding: 10px 20px;
background-color: #007bff;
color: white;
border: none;
border-radius: 20px;
cursor: pointer;
font-size: 1em;
transition: background-color 0.2s;
}
#send:hover:not(:disabled) {
background-color: #0056b3;
}
#send:disabled {
background-color: #a0c9ff;
cursor: not-allowed;
}
.chat-stats {
font-size: 0.8em;
color: #666;
text-align: right;
padding: 5px 20px;
background-color: #f9f9f9;
border-top: 1px solid #eee;
}
.hidden {
display: none!important;
}
</style>
</head>
<body>
<div id="chat-container">
<h1>In-Browser LLM Chat</h1>
<div id="download-status" class="status-message">Loading model...</div>
<div id="chat-box">
</div>
<div id="chat-stats" class="chat-stats hidden"></div>
<div class="chat-input-container">
<input type="text" id="user-input" placeholder="Loading model..." disabled>
<button id="send" disabled>Send</button>
</div>
</div>
<script type="module">
import * as webllm from "https://esm.run/@mlc-ai/web-llm";
/*************** WebLLM Logic ***************/
const messages = [
// Initial system message to guide the LLM's behavior
{ role: "system", content: "You are a helpful and concise AI assistant. Keep your responses brief and to the point." }
];
const chatBox = document.getElementById("chat-box");
const userInput = document.getElementById("user-input");
const sendButton = document.getElementById("send");
const downloadStatus = document.getElementById("download-status");
const chatStats = document.getElementById("chat-stats");
let currentAssistantMessageElement = null; // To update the streaming message
// Callback function for initializing progress.
function updateEngineInitProgressCallback(report) {
console.log("initialize", report.progress, report.text);
downloadStatus.textContent = report.text;
}
// Create engine instance.
const engine = new webllm.MLCEngine();
engine.setInitProgressCallback(updateEngineInitProgressCallback);
async function initializeWebLLMEngine() {
downloadStatus.classList.remove("hidden");
let selectedModel = null;
// Define a preferred pattern for the model name (e.g., "TinyLlama", "Gemma", "Phi")
const preferredModelPattern = "TinyLlama";
const availableModels = webllm.prebuiltAppConfig.model_list;
// Try to find a model matching the preferred pattern and suitable quantization
const suitableModels = availableModels.filter(m =>
m.model_id.toLowerCase().includes(preferredModelPattern.toLowerCase()) &&
(m.model_id.includes("q4f16_1") || m.model_id.includes("q4f32_1")) &&
m.model_id.includes("Instruct") // Prioritize instruction-tuned models for chat
);
if (suitableModels.length > 0) {
// Select the first suitable model found (you could add more logic to pick the smallest/best)
selectedModel = suitableModels[0].model_id;
console.log(`Found preferred model matching '${preferredModelPattern}': ${selectedModel}`);
} else {
// Fallback to a known tiny model if preferred not found, or any small instruct model
const fallbackModels = [
"TinyLlama-1.1B-Chat-v1.0-q4f16_1-MLC", // Our original choice, if exact match needed
"Qwen2.5-0.5B-Instruct-q4f16_1-MLC", // Very tiny option
"gemma-2b-it-q4f16_1-MLC", // Another small, instruct-tuned option
"Phi-3.5-mini-instruct-q4f16_1-MLC", // Microsoft's small model
];
for (const fbModelId of fallbackModels) {
const foundFbModel = availableModels.find(m => m.model_id === fbModelId);
if (foundFbModel) {
selectedModel = foundFbModel.model_id;
console.log(`Falling back to model: ${selectedModel}`);
break;
}
}
if (!selectedModel) {
downloadStatus.textContent = "Error: No suitable small chat model found in WebLLM's configuration. Please check your browser console for `webllm.prebuiltAppConfig.model_list` for available models.";
console.error("No suitable small chat model found in available models.");
return; // Stop initialization if no model found
}
}
// Display the initial system message in the chat box
appendMessage({ role: "system", content: messages[0].content });
const config = {
temperature: 0.7, // Controls randomness [3]
top_p: 0.9, // Controls diversity [3]
};
try {
await engine.reload(selectedModel, config); // [4]
downloadStatus.textContent = `Model '${selectedModel}' loaded. Ready to chat!`;
sendButton.disabled = false;
userInput.disabled = false;
userInput.setAttribute("placeholder", "Type a message...");
console.log("WebLLM engine initialized successfully.");
} catch (error) {
downloadStatus.textContent = `Error loading model '${selectedModel}': ${error.message}`;
console.error(`Error initializing WebLLM engine with ${selectedModel}:`, error);
}
}
/*************** UI Logic ***************/
// Helper function to append messages to the chat box
function appendMessage(message, isStreaming = false) {
const messageContainer = document.createElement("div");
messageContainer.classList.add("message-container", message.role);
// Only create a message bubble for user and assistant messages
if (message.role === "user" || message.role === "assistant") {
const messageBubble = document.createElement("div");
messageBubble.classList.add("message-bubble");
messageBubble.textContent = message.content;
messageContainer.appendChild(messageBubble);
} else {
// For system messages, just set the text content directly on the container
messageContainer.textContent = message.content;
}
chatBox.appendChild(messageContainer);
chatBox.scrollTop = chatBox.scrollHeight; // Scroll to bottom
if (isStreaming && message.role === "assistant") {
currentAssistantMessageElement = messageContainer.querySelector(".message-bubble");
// If system message, currentAssistantMessageElement will be null, and that's fine.
}
}
// Helper function to update the content of the last assistant message (for streaming)
function updateLastAssistantMessage(newContent) {
if (currentAssistantMessageElement) {
currentAssistantMessageElement.textContent = newContent;
chatBox.scrollTop = chatBox.scrollHeight; // Scroll to bottom
}
}
// Function to handle sending a message
async function onMessageSend() {
const input = userInput.value.trim();
if (input.length === 0) {
return;
}
const userMessage = { content: input, role: "user" }; // [5, 6, 3]
messages.push(userMessage);
appendMessage(userMessage);
userInput.value = "";
sendButton.disabled = true;
userInput.setAttribute("placeholder", "Generating response..."); // [7]
const aiMessagePlaceholder = { content: "typing...", role: "assistant" }; // [7]
appendMessage(aiMessagePlaceholder, true); // Mark as streaming message
let fullAssistantResponse = "";
chatStats.classList.add("hidden"); // Hide stats during generation
try {
const completion = await engine.chat.completions.create({
messages: messages,
stream: true, // [5, 6, 3]
// Parameters like temperature/top_p can be set here or in engine.reload
});
for await (const chunk of completion) { // [5]
const curDelta = chunk.choices?.[0]?.delta.content; // [5, 7]
if (curDelta) {
fullAssistantResponse += curDelta;
updateLastAssistantMessage(fullAssistantResponse);
}
}
const finalMessage = await engine.getMessage(); // Get final message from engine [7]
messages.push({ content: finalMessage, role: "assistant" }); // Add final message to history
updateLastAssistantMessage(finalMessage); // Ensure final update
// Display performance stats
const usageText = await engine.runtimeStatsText(); // Get detailed stats string [7]
chatStats.classList.remove("hidden");
chatStats.textContent = usageText;
} catch (error) {
updateLastAssistantMessage(`Error: ${error.message}`);
console.error("Error during LLM inference:", error);
} finally {
sendButton.disabled = false;
userInput.disabled = false;
userInput.setAttribute("placeholder", "Type a message...");
currentAssistantMessageElement = null; // Clear reference
}
}
// Event Listeners
sendButton.addEventListener("click", onMessageSend);
userInput.addEventListener("keypress", (event) => {
if (event.key === "Enter" && !sendButton.disabled) {
onMessageSend();
}
});
// Initialize the WebLLM engine when the page loads
document.addEventListener("DOMContentLoaded", initializeWebLLMEngine);
</script>
</body>
</html>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment