From b6d6ecf0e4b809e11a19bfbca38d6d743b82015f Mon Sep 17 00:00:00 2001 From: gaiborjosue Date: Fri, 10 May 2024 00:36:57 -0400 Subject: [PATCH 1/9] feat(webllm) --- examples/webllm.js | 179 +++++++++++++++++++++++++++++++++++++++++++++ index.html | 4 +- 2 files changed, 181 insertions(+), 2 deletions(-) create mode 100644 examples/webllm.js diff --git a/examples/webllm.js b/examples/webllm.js new file mode 100644 index 00000000..ab4bf709 --- /dev/null +++ b/examples/webllm.js @@ -0,0 +1,179 @@ +script = document.createElement("script"); +script.type = "text/javascript"; +//script.src = "https://boostlet.org/dist/boostlet.min.js"; +script.src = "http://localhost:5500/dist/boostlet.min.js"; + +script.onload = run; +document.head.appendChild(script); +eval(script); + +async function run() { + + // detect visualization framework + Boostlet.init(); + + // Dynamically load the WebLLM script + const webLLMScript = document.createElement("script"); + webLLMScript.src = "https://cdn.jsdelivr.net/npm/@mlc-ai/web-llm@0.2.35/lib/index.min.js"; + document.head.appendChild(webLLMScript); + + webLLMScript.onload = function() { + // Setup the chat UI once the WebLLM is loaded + setupChatUI(); + initializeChat(); + }; +} + +function setupChatUI() { + const style = document.createElement("style"); + style.type = 'text/css'; + style.innerHTML = ` + .chatui { + display: flex; + position: fixed; + bottom: 0; + right: 0; + flex-direction: column; + width: 350px; + height: 600px; + border: 2px solid #ddd; + border-radius: 5px; + background-color: #1F2027; + z-index: 1000; + } + .chatui-select-wrapper { + display: flex; + justify-content: center; + background-color: #1F2027; + padding: 10px 0; + } + #chatui-select { + width: 90%; + background-color: #1F2027; + color: white; + border: none; + } + #chatui-select:focus { + outline: none; + } + .chatui-chat { + flex: 1; + overflow-y: auto; + padding: 10px; + background-color: #1F2027; + } + .chatui-inputarea { + display: flex; + padding: 10px; + border-top: 2px solid transparent; + background-color: #1F2027; + } + .chatui-input { + flex: 1; + background-color: #40414F; + color: white; + border: none; + border-radius: 3px; + padding: 10px; + font-size: 16px; + } + .chatui-send-btn, .chatui-reset-btn { + background-color: #40414F; + color: white; + border: none; + border-radius: 3px; + padding: 10px; + cursor: pointer; + margin-left: 10px; + } + .chatui-send-btn:hover, .chatui-reset-btn:hover { + background-color: #03a33e; + } + .msg-bubble { + background-color: #f0f0f0; + border-radius: 8px; + padding: 16px; + color: black; + width: calc(100% - 20px); + margin: 5px auto; + box-sizing: border-box; + } + .left-msg .msg-bubble { + background-color: #343541; + color: #ececec; + } + .right-msg .msg-bubble { + background-color: #444654; + color: #ececec; + } + `; + document.head.appendChild(style); + + const chatContainer = document.createElement('div'); + chatContainer.className = 'chatui'; + document.body.appendChild(chatContainer); + + chatContainer.innerHTML = ` +
+ +
+
+
+ + + +
+ `; +} + +// Initialize the chat +async function initializeChat() { + const engine = await webllm.Engine.create({modelId: "Llama-3-8B-Instruct-q4f32_1"}); // Use your model ID + const chatUI = new ChatUI(engine); + chatUI.setupEventListeners(); // Set up event listeners after initializing +} + +class ChatUI { + constructor(engine) { + this.engine = engine; + this.uiChat = document.getElementById("chatui-chat"); + this.uiInput = document.getElementById("chatui-input"); + this.uiSendBtn = document.getElementById("chatui-send-btn"); + this.uiResetBtn = document.getElementById("chatui-reset-btn"); + } + + setupEventListeners() { + this.uiSendBtn.addEventListener('click', () => this.sendChatMessage()); + this.uiResetBtn.addEventListener('click', () => this.resetChat()); + } + + sendChatMessage() { + const message = this.uiInput.value.trim(); + if (!message) return; + this.appendMessage('right-msg', message); + this.uiInput.value = ''; + this.processChatMessage(message); + } + + resetChat() { + this.uiChat.innerHTML = ''; + } + + appendMessage(className, text) { + const messageElement = document.createElement('div'); + messageElement.className = 'msg ' + className; + messageElement.innerHTML = `
${text}
`; + this.uiChat.appendChild(messageElement); + this.uiChat.scrollTop = this.uiChat.scrollHeight; + } + + async processChatMessage(message) { + // Integration with WebLLM to send and receive messages + const response = await this.engine.chat.completions.create({ + messages: [{role: "user", content: message}] + }); + this.appendMessage('left-msg', response.choices[0].message.content); // Handle and display response + } +}; \ No newline at end of file diff --git a/index.html b/index.html index 6def4ec6..1599f260 100644 --- a/index.html +++ b/index.html @@ -232,9 +232,9 @@ var examples = ['sobel.js', 'segmentanything.js', 'imageCaptioning.js', 'plotly.js', 'trako.js', 'melanoma.js']; - var baseurl = 'https://raw.githubusercontent.com/mpsych/boostlet/main/examples/'; + // var baseurl = 'https://raw.githubusercontent.com/mpsych/boostlet/main/examples/'; // var baseurl = 'https://raw.githubusercontent.com/gaiborjosue/boostlet/melanoma/examples/'; - // var baseurl = 'http://localhost:5500/examples/'; + var baseurl = 'http://localhost:5500/examples/'; // var baseurl = 'http://localhost:8000/examples/'; // var baseurl = 'https://raw.githubusercontent.com/shrutivarade/boostlet/main/examples/'; From 7ced079a2060a511cf93b8a1bb5ffb809d6430ac Mon Sep 17 00:00:00 2001 From: gaiborjosue Date: Fri, 10 May 2024 18:40:17 -0400 Subject: [PATCH 2/9] feat(web-llm): Improved chat UI and alerts --- examples/webllm.js | 302 +++++++++++++++++++++++++++----------------- gfx/llm_avatar.png | Bin 0 -> 109071 bytes gfx/user_avatar.png | Bin 0 -> 14937 bytes 3 files changed, 188 insertions(+), 114 deletions(-) create mode 100644 gfx/llm_avatar.png create mode 100644 gfx/user_avatar.png diff --git a/examples/webllm.js b/examples/webllm.js index ab4bf709..719d46fd 100644 --- a/examples/webllm.js +++ b/examples/webllm.js @@ -8,104 +8,124 @@ document.head.appendChild(script); eval(script); async function run() { - - // detect visualization framework - Boostlet.init(); - - // Dynamically load the WebLLM script - const webLLMScript = document.createElement("script"); - webLLMScript.src = "https://cdn.jsdelivr.net/npm/@mlc-ai/web-llm@0.2.35/lib/index.min.js"; - document.head.appendChild(webLLMScript); - - webLLMScript.onload = function() { - // Setup the chat UI once the WebLLM is loaded + // detect visualization framework + Boostlet.init(); + + // Dynamically load the WebLLM script + const script = document.createElement('script'); + // It is a module script to avoid conflicts with the global scope + script.type = 'module'; + script.src = 'https://raw.githubusercontent.com/mpsych/cdn/main/web-llm/index.js'; + document.head.appendChild(script); + + // Wait for the script to load + script.onload = async () => { + // Import the module + const module = await import(script.src); + // Now you can use the exported items from the module + // For example, if the module exports a function called `init`, you can call it like this: + console.log(module); + window.webllm = module; setupChatUI(); - initializeChat(); - }; -} + populateModelSelector(); + }; + } function setupChatUI() { const style = document.createElement("style"); style.type = 'text/css'; style.innerHTML = ` - .chatui { - display: flex; - position: fixed; - bottom: 0; - right: 0; - flex-direction: column; - width: 350px; - height: 600px; - border: 2px solid #ddd; - border-radius: 5px; - background-color: #1F2027; - z-index: 1000; - } - .chatui-select-wrapper { - display: flex; - justify-content: center; - background-color: #1F2027; - padding: 10px 0; - } - #chatui-select { - width: 90%; - background-color: #1F2027; - color: white; - border: none; - } - #chatui-select:focus { - outline: none; - } - .chatui-chat { - flex: 1; - overflow-y: auto; - padding: 10px; - background-color: #1F2027; - } - .chatui-inputarea { - display: flex; - padding: 10px; - border-top: 2px solid transparent; - background-color: #1F2027; - } - .chatui-input { - flex: 1; - background-color: #40414F; - color: white; - border: none; - border-radius: 3px; - padding: 10px; - font-size: 16px; - } - .chatui-send-btn, .chatui-reset-btn { - background-color: #40414F; - color: white; - border: none; - border-radius: 3px; - padding: 10px; - cursor: pointer; - margin-left: 10px; - } - .chatui-send-btn:hover, .chatui-reset-btn:hover { - background-color: #03a33e; - } - .msg-bubble { - background-color: #f0f0f0; - border-radius: 8px; - padding: 16px; - color: black; - width: calc(100% - 20px); - margin: 5px auto; - box-sizing: border-box; - } - .left-msg .msg-bubble { - background-color: #343541; - color: #ececec; - } - .right-msg .msg-bubble { - background-color: #444654; - color: #ececec; - } + .chatui { + display: flex; + position: fixed; + bottom: 0; + right: 0; + flex-direction: column; + width: 350px; + height: 600px; + border: 2px solid #ddd; + border-radius: 5px; + background-color: #1F2027; + z-index: 1000; + } + .chatui-select-wrapper { + display: flex; + justify-content: center; + background-color: #1F2027; + padding: 10px 0; + } + #chatui-select { + width: 90%; + background-color: #1F2027; + color: white; + border: none; + } + #chatui-select:focus { + outline: none; + } + .chatui-chat { + flex: 1; + overflow-y: auto; + padding: 10px; + background-color: #1F2027; + } + .chatui-inputarea { + display: flex; + padding: 10px; + border-top: 2px solid transparent; + background-color: #1F2027; + } + .chatui-input { + flex: 1; + background-color: #40414F; + color: white; + border: none; + border-radius: 3px; + padding: 10px; + font-size: 16px; + } + .chatui-send-btn, .chatui-reset-btn { + background-color: #40414F; + color: white; + border: none; + border-radius: 3px; + padding: 10px; + cursor: pointer; + margin-left: 10px; + } + .chatui-send-btn:hover, .chatui-reset-btn:hover { + background-color: #03a33e; + } + .msg-bubble { + display: flex; + align-items: center; + background-color: #f0f0f0; + border-radius: 8px; + padding: 10px; + color: black; + width: calc(100% - 20px); + margin: 5px auto; + box-sizing: border-box; + } + .left-msg .msg-bubble { + background-color: #343541; /* LLM Messages */ + color: #ececec; + } + .right-msg .msg-bubble { + background-color: #444654; /* User Messages */ + color: #ececec; + } + .profile-image { + width: 30px; + height: 30px; + border-radius: 50%; + margin-right: 10px; + } + .message-label { + font-size: 12px; + color: #ccc; + text-align: center; + } `; document.head.appendChild(style); @@ -128,11 +148,49 @@ function setupChatUI() { `; } +function populateModelSelector() { + const modelSelect = document.getElementById('chatui-select'); + webllm.prebuiltAppConfig.model_list.forEach((model, index) => { + let option = new Option(model.model_id, model.model_id); + modelSelect.add(option); + }); + + modelSelect.addEventListener('change', function() { + initializeChat(this.value); + }); +} + // Initialize the chat -async function initializeChat() { - const engine = await webllm.Engine.create({modelId: "Llama-3-8B-Instruct-q4f32_1"}); // Use your model ID - const chatUI = new ChatUI(engine); - chatUI.setupEventListeners(); // Set up event listeners after initializing +async function initializeChat(selectedModel) { + try { + const initProgressCallback = (report) => { + showProgress(report.text); + }; + const engine = await webllm.CreateEngine(selectedModel, { initProgressCallback }); + const chatUI = new ChatUI(engine); + chatUI.setupEventListeners(); + clearProgress(); + } catch (error) { + Boostlet.hint(`Error: ${error.message}`, 3000); + } +} + +function showProgress(text) { + let progressBox = document.getElementById('progress-box'); + if (!progressBox) { + progressBox = document.createElement('div'); + progressBox.id = 'progress-box'; + progressBox.style = 'position: fixed; top: 50%; left: 50%; transform: translate(-50%, -50%); background: rgba(0, 0, 0, 0.75); color: white; padding: 20px; border-radius: 10px; z-index: 1500;'; + document.body.appendChild(progressBox); + } + progressBox.innerText = text; +} + +function clearProgress() { + const progressBox = document.getElementById('progress-box'); + if (progressBox) { + document.body.removeChild(progressBox); + } } class ChatUI { @@ -150,30 +208,46 @@ class ChatUI { } sendChatMessage() { - const message = this.uiInput.value.trim(); - if (!message) return; - this.appendMessage('right-msg', message); - this.uiInput.value = ''; - this.processChatMessage(message); - } + const message = this.uiInput.value.trim(); + if (!message) return; + this.appendMessage('right-msg', message, "User"); + this.uiInput.value = ''; + this.processChatMessage(message); +} resetChat() { this.uiChat.innerHTML = ''; } - appendMessage(className, text) { - const messageElement = document.createElement('div'); - messageElement.className = 'msg ' + className; - messageElement.innerHTML = `
${text}
`; - this.uiChat.appendChild(messageElement); - this.uiChat.scrollTop = this.uiChat.scrollHeight; + appendMessage(className, text, role) { + const chat = this.uiChat; + const messageWrapper = document.createElement('div'); + messageWrapper.className = 'msg ' + className; + + const imgSrc = (role === "User") ? "https://cdn.pixabay.com/photo/2015/10/05/22/37/blank-profile-picture-973460_960_720.png" : "https://en.shiftdelete.net/wp-content/uploads/2023/06/chatbots-unleashed-utilizing-a-gpt-like-mlc-local-chatbot-in-macos-1.jpg"; + const profileImg = `${role}`; + + messageWrapper.innerHTML = ` +
+ ${profileImg} +
+
${text}
+
+
+ `; + + chat.appendChild(messageWrapper); + chat.scrollTop = chat.scrollHeight; } async processChatMessage(message) { - // Integration with WebLLM to send and receive messages - const response = await this.engine.chat.completions.create({ - messages: [{role: "user", content: message}] - }); - this.appendMessage('left-msg', response.choices[0].message.content); // Handle and display response - } + try { + const response = await this.engine.chat.completions.create({ + messages: [{role: "user", content: message}] + }); + this.appendMessage('left-msg', response.choices[0].message.content); + } catch (error) { + Boostlet.hint(`Error: ${error.message}`, 3000); + } + } }; \ No newline at end of file diff --git a/gfx/llm_avatar.png b/gfx/llm_avatar.png new file mode 100644 index 0000000000000000000000000000000000000000..e09bfba3dc165aa78a7677a8d42cdb64cc1c1ea6 GIT binary patch literal 109071 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 literal 0 HcmV?d00001 diff --git a/gfx/user_avatar.png b/gfx/user_avatar.png new file mode 100644 index 0000000000000000000000000000000000000000..bb5c65fa0b7ef4f9f2875fe26667662acf691870 GIT binary patch literal 14937 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 ZI27v1NtMWa#_KPCK2}hdFPF6l`)?haGp_&u literal 0 HcmV?d00001 From fc47d3082cf63f684d24709de994a061bf91d302 Mon Sep 17 00:00:00 2001 From: gaiborjosue Date: Fri, 10 May 2024 19:54:17 -0400 Subject: [PATCH 3/9] feat(web-llm): Beta version working --- examples/webllm.js | 190 ++++++++++++++++++++++++++------------------- 1 file changed, 109 insertions(+), 81 deletions(-) diff --git a/examples/webllm.js b/examples/webllm.js index 719d46fd..472e146d 100644 --- a/examples/webllm.js +++ b/examples/webllm.js @@ -8,32 +8,26 @@ document.head.appendChild(script); eval(script); async function run() { - // detect visualization framework - Boostlet.init(); - - // Dynamically load the WebLLM script - const script = document.createElement('script'); - // It is a module script to avoid conflicts with the global scope - script.type = 'module'; - script.src = 'https://raw.githubusercontent.com/mpsych/cdn/main/web-llm/index.js'; - document.head.appendChild(script); - - // Wait for the script to load - script.onload = async () => { - // Import the module - const module = await import(script.src); - // Now you can use the exported items from the module - // For example, if the module exports a function called `init`, you can call it like this: - console.log(module); - window.webllm = module; - setupChatUI(); - populateModelSelector(); - }; - } + // detect visualization framework + Boostlet.init(); + + const script = document.createElement("script"); + script.type = "module"; + script.src = "https://mpsych.github.io/cdn/web-llm/web-llm-cdn.js"; + document.head.appendChild(script); + + script.onload = async () => { + // Import the module + const module = await import(script.src); + window.webllm = module; + setupChatUI(); + populateModelSelector(); + }; +} function setupChatUI() { const style = document.createElement("style"); - style.type = 'text/css'; + style.type = "text/css"; style.innerHTML = ` .chatui { display: flex; @@ -43,10 +37,11 @@ function setupChatUI() { flex-direction: column; width: 350px; height: 600px; - border: 2px solid #ddd; - border-radius: 5px; + border: 2px solid; + border-radius: 12px; background-color: #1F2027; z-index: 1000; + padding: 10px; } .chatui-select-wrapper { display: flex; @@ -58,7 +53,9 @@ function setupChatUI() { width: 90%; background-color: #1F2027; color: white; - border: none; + height: 70%; + align-self: center; + border-radius: 5px; } #chatui-select:focus { outline: none; @@ -94,7 +91,7 @@ function setupChatUI() { margin-left: 10px; } .chatui-send-btn:hover, .chatui-reset-btn:hover { - background-color: #03a33e; + background-color: #007EC6; } .msg-bubble { display: flex; @@ -126,15 +123,23 @@ function setupChatUI() { color: #ccc; text-align: center; } + .select-label { + font-size: 16px; + color: #ccc; + text-align: left; + } + + `; document.head.appendChild(style); - const chatContainer = document.createElement('div'); - chatContainer.className = 'chatui'; + const chatContainer = document.createElement("div"); + chatContainer.className = "chatui"; document.body.appendChild(chatContainer); chatContainer.innerHTML = `

`; + + const chatInput = document.getElementById("chatui-input"); + chatInput.addEventListener("keypress", function(event) { + if (event.key === "Enter") { + event.preventDefault(); // Prevent the default action to avoid submitting the form + document.getElementById("chatui-send-btn").click(); // Trigger the click event on the send button + } + }); + + Boostlet.hint("Models with “-1k” suffix signify 1024 context length, lowering ~2-3GB VRAM requirement compared to their counterparts. Feel free to start trying with those", 5000); } function populateModelSelector() { - const modelSelect = document.getElementById('chatui-select'); - webllm.prebuiltAppConfig.model_list.forEach((model, index) => { - let option = new Option(model.model_id, model.model_id); - modelSelect.add(option); - }); + const modelSelect = document.getElementById("chatui-select"); + webllm.prebuiltAppConfig.model_list.forEach((model, index) => { + let option = new Option(model.model_id, model.model_id); + modelSelect.add(option); + }); - modelSelect.addEventListener('change', function() { - initializeChat(this.value); - }); + modelSelect.addEventListener("change", function () { + initializeChat(this.value); + }); } // Initialize the chat async function initializeChat(selectedModel) { - try { - const initProgressCallback = (report) => { - showProgress(report.text); - }; - const engine = await webllm.CreateEngine(selectedModel, { initProgressCallback }); - const chatUI = new ChatUI(engine); - chatUI.setupEventListeners(); - clearProgress(); - } catch (error) { - Boostlet.hint(`Error: ${error.message}`, 3000); - } + try { + const initProgressCallback = (report) => { + showProgress(report.text); + }; + const engine = await webllm.CreateEngine(selectedModel, { + initProgressCallback, + }); + window.engine = engine; + + const chatUI = new ChatUI(engine); + chatUI.setupEventListeners(); + clearProgress(); + } catch (error) { + Boostlet.hint(`Error: ${error.message}`, 8000); + } } function showProgress(text) { - let progressBox = document.getElementById('progress-box'); - if (!progressBox) { - progressBox = document.createElement('div'); - progressBox.id = 'progress-box'; - progressBox.style = 'position: fixed; top: 50%; left: 50%; transform: translate(-50%, -50%); background: rgba(0, 0, 0, 0.75); color: white; padding: 20px; border-radius: 10px; z-index: 1500;'; - document.body.appendChild(progressBox); - } - progressBox.innerText = text; + let progressBox = document.getElementById("progress-box"); + if (!progressBox) { + progressBox = document.createElement("div"); + progressBox.id = "progress-box"; + progressBox.style = + "position: fixed; top: 50%; left: 50%; transform: translate(-50%, -50%); background: rgba(0, 0, 0, 0.75); color: white; padding: 20px; border-radius: 10px; z-index: 1500;"; + document.body.appendChild(progressBox); + } + progressBox.innerText = text; } function clearProgress() { - const progressBox = document.getElementById('progress-box'); - if (progressBox) { - document.body.removeChild(progressBox); - } + const progressBox = document.getElementById("progress-box"); + if (progressBox) { + document.body.removeChild(progressBox); + } } class ChatUI { constructor(engine) { - this.engine = engine; - this.uiChat = document.getElementById("chatui-chat"); - this.uiInput = document.getElementById("chatui-input"); - this.uiSendBtn = document.getElementById("chatui-send-btn"); - this.uiResetBtn = document.getElementById("chatui-reset-btn"); + this.engine = engine; + this.uiChat = document.getElementById("chatui-chat"); + this.uiInput = document.getElementById("chatui-input"); + this.uiSendBtn = document.getElementById("chatui-send-btn"); + this.uiResetBtn = document.getElementById("chatui-reset-btn"); } setupEventListeners() { - this.uiSendBtn.addEventListener('click', () => this.sendChatMessage()); - this.uiResetBtn.addEventListener('click', () => this.resetChat()); + this.uiSendBtn.addEventListener("click", () => this.sendChatMessage()); + this.uiResetBtn.addEventListener("click", () => this.resetChat()); } sendChatMessage() { const message = this.uiInput.value.trim(); if (!message) return; - this.appendMessage('right-msg', message, "User"); - this.uiInput.value = ''; + this.appendMessage("right-msg", message, "User"); + this.uiInput.value = ""; this.processChatMessage(message); -} + } resetChat() { - this.uiChat.innerHTML = ''; + this.uiChat.innerHTML = ""; } appendMessage(className, text, role) { const chat = this.uiChat; - const messageWrapper = document.createElement('div'); - messageWrapper.className = 'msg ' + className; + const messageWrapper = document.createElement("div"); + messageWrapper.className = "msg " + className; - const imgSrc = (role === "User") ? "https://cdn.pixabay.com/photo/2015/10/05/22/37/blank-profile-picture-973460_960_720.png" : "https://en.shiftdelete.net/wp-content/uploads/2023/06/chatbots-unleashed-utilizing-a-gpt-like-mlc-local-chatbot-in-macos-1.jpg"; + const imgSrc = + role === "User" + ? "https://raw.githubusercontent.com/gaiborjosue/boostlet/webllm/gfx/user_avatar.png" + : "https://raw.githubusercontent.com/gaiborjosue/boostlet/webllm/gfx/llm_avatar.png"; const profileImg = `${role}`; messageWrapper.innerHTML = ` @@ -242,12 +265,17 @@ class ChatUI { async processChatMessage(message) { try { - const response = await this.engine.chat.completions.create({ - messages: [{role: "user", content: message}] - }); - this.appendMessage('left-msg', response.choices[0].message.content); + const response = await this.engine.chat.completions.create({ + messages: [{ + role: "system", "content": "You are a chatbot that will help users answer questions and your name is BoostBot. Start every message with 'BoostBot: ' to make it clear that you are a chatbot." + },{ + role: "user", content: message + }] + }); + this.appendMessage("left-msg", response.choices[0].message.content); + console.log(await engine.runtimeStatsText()); } catch (error) { - Boostlet.hint(`Error: ${error.message}`, 3000); + Boostlet.hint(`Error: ${error.message}`, 8000); } - } -}; \ No newline at end of file + } +} From 87d7acd2e7a145d36c855728f116ab6590d3482d Mon Sep 17 00:00:00 2001 From: gaiborjosue Date: Fri, 10 May 2024 19:59:30 -0400 Subject: [PATCH 4/9] feat(web-llm): Added boostlet --- dist/boostlet.min.js | 2 +- dist/boostlet.min.js.map | 2 +- index.html | 6 ++++-- 3 files changed, 6 insertions(+), 4 deletions(-) diff --git a/dist/boostlet.min.js b/dist/boostlet.min.js index f33fc3f6..7c2ddb89 100644 --- a/dist/boostlet.min.js +++ b/dist/boostlet.min.js @@ -418,7 +418,7 @@ class $0fabd2f4a96087f5$export$48eecc33595f906d extends (0, $bsgU1.Framework) { select_box(callback) { let scriptBoxCraft = document.createElement("script"); scriptBoxCraft.type = "text/javascript"; - scriptBoostlet.src = "https://boostlet.org/dist/boxcraft.min.js"; + scriptBoxCraft.src = "https://boostlet.org/dist/boxcraft.min.js"; // scriptBoxCraft.src = "https://shrutivarade.github.io/BoxCraft/dist/boxCraft.min.js"; // scriptBoxCraft.src = "https://shrutivarade.github.io/boostlet/dist/boxcraft.min.js"; // scriptBoxCraft.src = "http://localhost:8000/dist/boxcraft.min.js"; diff --git a/dist/boostlet.min.js.map b/dist/boostlet.min.js.map index 2d3fd561..822795b8 100644 --- a/dist/boostlet.min.js.map +++ b/dist/boostlet.min.js.map @@ -1 +1 @@ -{"mappings":"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","sources":["src/util.js","src/framework.js","src/frameworks/cornerstone2d.js","src/frameworks/canvasFallback.js","src/frameworks/niivue.js","src/frameworks/openseadragon.js","src/frameworks/xtk.js","src/frameworks/papaya.js","src/index.js","src/boostlet.js"],"sourcesContent":["import {Framework} from './framework.js';\nimport {Cornerstone2D} from './frameworks/cornerstone2d.js';\nimport {NiiVue} from './frameworks/niivue.js';\nimport { OpenSeaDragon } from './frameworks/openseadragon.js';\nimport { Xtk } from './frameworks/xtk.js';\nimport { Papaya } from './frameworks/papaya.js';\nimport { CanvasFallback } from './frameworks/canvasFallback.js';\n\nexport class Util {\n \n static detect_framework() {\n\n let framework = null;\n\n if (Util.is_defined(window.nv)) {\n \n framework = new NiiVue(window.nv);\n \n } else if (Util.is_defined(window.niivue)) {\n \n framework = new NiiVue(window.niivue);\n\n } else if (Util.is_defined(window.cornerstone)) {\n\n framework = new Cornerstone2D(window.cornerstone);\n\n } else if (Util.is_defined(window.r)) {\n \n framework = new Xtk(window.r);\n \n } else if (Util.is_defined(window.OpenSeadragon)) {\n\n framework = new OpenSeaDragon(window.OpenSeadragon);\n \n } else if (Util.is_defined(window.papayaContainers)) {\n \n framework = new Papaya(window.papayaContainers)\n\n } else {\n // Canvas fallback\n \n console.log(\"No framework detected, falling back to canvas rendering\");\n framework = new CanvasFallback();\n \n }\n\n return framework;\n\n }\n\n static async load_script(url, callback) {\n\n // introducing hack to make it work for openneuro\n window.Object.defineProperty(window.Object.prototype, 'global', {\n get( ){\n return window;\n },\n set(newGlobal) {\n globalThis = newGlobal;\n }\n });\n\n const script = window.document.createElement(\"script\")\n script.type = \"text/javascript\"\n script.src = url;\n\n if (Util.is_defined(callback)) {\n script.onload = callback;\n }\n\n window.document.head.appendChild(script);\n eval(script);\n\n }\n\n static async send_http_post(url, data, callback) {\n\n let xhr = new XMLHttpRequest();\n xhr.open(\"POST\", url);\n xhr.onreadystatechange = function () {\n if (xhr.readyState === 4) {\n // request done\n callback( xhr.response );\n\n return;\n\n }\n }\n\n xhr.send(data)\n\n }\n\n static convert_to_png(uint8array, width, height, flip) {\n\n // we are using an offscreen canvas for this\n let offscreen = window.document.createElement('canvas');\n offscreen.width = width;\n offscreen.height = height;\n\n let offscreen_ctx = offscreen.getContext('2d');\n\n let imgdata = offscreen_ctx.createImageData(offscreen.width, offscreen.height);\n let pxdata = imgdata.data;\n\n for (var i =0; i c.charCodeAt(0));\n\n return pngpixels;\n\n }\n\n static filter(pixels, width, height, kernel) {\n\n const kernelSize = Math.sqrt(kernel.length);\n const halfKernelSize = Math.floor(kernelSize / 2);\n\n const new_pixels = pixels.slice();\n\n for (let y = 0; y < height; y++) {\n for (let x = 0; x < width; x++) {\n const dstIndex = y * width + x;\n\n let newValue = 0;\n for (let ky = 0; ky < kernelSize; ky++) {\n for (let kx = 0; kx < kernelSize; kx++) {\n const srcX = x + kx - halfKernelSize;\n const srcY = y + ky - halfKernelSize;\n const srcIndex = srcY * width + srcX;\n\n const kernelValue = kernel[ky * kernelSize + kx];\n newValue += pixels[srcIndex] * kernelValue;\n }\n }\n\n new_pixels[dstIndex] = newValue;\n\n }\n }\n\n return new_pixels;\n\n }\n\n static grayscale_to_rgba(grayscale) {\n\n const rgba = new Uint8Array(grayscale.length * 4);\n\n for (let i = 0; i < grayscale.length; i++) {\n const g = grayscale[i];\n const index = i * 4;\n\n rgba[index] = g;\n rgba[index + 1] = g;\n rgba[index + 2] = g;\n rgba[index + 3] = 255; \n }\n\n return rgba;\n\n }\n\n static rgba_to_grayscale(rgba) {\n\n const grayscale = new Uint8Array(rgba.length / 4);\n\n for (let i = 0; i < rgba.length; i += 4) {\n\n grayscale[i / 4] = rgba[i];\n\n }\n\n return grayscale;\n\n }\n\n /**\n * Harden a mask into a grayscale pixel array.\n * \n * pixels needs to be RGBA\n * \n * and mask binary.\n * \n * maskcolor is optional and falls back to blue.\n * \n **/\n static harden_mask(pixels, mask, maskcolor) {\n\n // Modified from: https://github.com/facebookresearch/segment-anything/blob/40df6e4046d8b07ab8c4519e083408289eb43032/demo/src/components/helpers/maskUtils.tsx\n // Copyright (c) Meta Platforms, Inc. and affiliates.\n // All rights reserved.\n\n\n let maskcolor_ = [0, 114, 189, 255];\n\n if (Util.is_defined(maskcolor)) {\n \n maskcolor_ = maskcolor;\n \n } \n\n for (var i = 0; i < mask.length; i++) {\n\n if (mask[i] > 0.0) {\n pixels[4 * i + 0] = maskcolor_[0];\n pixels[4 * i + 1] = maskcolor_[1];\n pixels[4 * i + 2] = maskcolor_[2];\n pixels[4 * i + 3] = maskcolor_[3];\n }\n\n }\n\n return pixels;\n\n }\n\n static is_defined(variable) {\n\n return (typeof variable != 'undefined');\n\n }\n \n // \"Boostlet Tooltips\" - This is a hint mechanism that allows to display a message for a certain amount of time (ms).\n static hint(message, duration) {\n\n let hint = window.document.createElement('div');\n hint.id = 'BoostletHint';\n\n hint.style.position = 'fixed';\n hint.style.left = '10px';\n hint.style.top = '10px';\n hint.style.padding = '10px';\n hint.style.background = '#fff';\n hint.style.color = '#000';\n hint.style.zIndex = '100000';\n hint.style.border = '1px solid #007ec6';\n hint.style.borderRadius = '5px';\n hint.style.boxShadow = '0px 0px 20px 5px rgba(0,0,0, 0.3)';\n hint.style.fontSize = '14px';\n hint.style.fontWeight = 'bold';\n hint.style.textAlign = 'center';\n\n hint.innerHTML = message;\n \n window.document.body.appendChild(hint);\n\n if (typeof duration === 'number' && duration > 0) {\n setTimeout(function() {\n hint.remove();\n }, duration);\n }\n\n }\n\n}","import {Util} from './util.js';\n\nexport class Framework {\n\n constructor(instance) {\n\n this.name = 'generic';\n this.instance = instance;\n\n this.flip_on_png = false;\n\n }\n\n get_image(from_canvas) {\n\n throw \"Missing Implementation.\";\n\n }\n\n set_image(new_pixels) {\n\n throw \"Missing Implementation.\";\n\n }\n\n set_mask(new_mask) {\n\n throw \"Missing Implementation.\";\n \n }\n\n select_box(callback) {\n\n throw \"Missing Implementation.\";\n\n }\n\n convert_to_png(uint8array, width, height) {\n\n return Util.convert_to_png(uint8array, width, height, this.flip_on_png);\n\n }\n\n}\n","import {Framework} from '../framework.js';\n\nimport {Util} from '../util.js';\n\nimport {CanvasFallback} from './canvasFallback.js';\n\nexport class Cornerstone2D extends Framework {\n \n constructor(instance) {\n\n super(instance);\n this.name = 'cornerstone2D';\n this.canvasFallback = new CanvasFallback();\n\n this.cornerstonetools_instance = null;\n\n if (typeof window.cornerstoneTools != 'undefined') {\n\n // TODO probably not too robust\n this.cornerstonetools_instance = window.cornerstoneTools;\n\n }\n\n this.flip_on_png = false;\n\n }\n\n get_image(from_canvas) {\n\n let element = this.instance.getEnabledElements()[0];\n let pixels = null;\n let width = null;\n let height = null;\n\n if (typeof from_canvas != 'undefined') {\n\n // TODO this is hacky going through the canvas\n // later should grab the real volume data\n\n let canvas = element.canvas;\n width = canvas.width;\n height = canvas.height;\n\n let ctx = canvas.getContext('2d');\n\n let imagedata = ctx.getImageData(0, 0, width, height);\n pixels = imagedata.data;\n\n } else {\n\n // this is the real image slice data\n let imagedata = element.image;\n pixels = imagedata.getPixelData();\n width = imagedata.width;\n height = imagedata.height;\n\n }\n\n return {'data':pixels, 'width':width, 'height':height};\n\n }\n\n set_image(new_pixels) {\n\n let element = this.instance.getEnabledElements()[0];\n let pixels = element.image.getPixelData();\n\n // Set the new pixel values\n pixels.set(new_pixels);\n\n // Re-render the current slice\n cornerstone.renderGrayscaleImage(element, true);\n\n }\n\n set_mask(new_mask) {\n return this.canvasFallback.set_mask(new_mask);\n \n }\n\n select_box(callback) {\n\n if(this.cornerstonetools_instance.RectangleRoiTool === undefined) {\n console.log(\"Using Boxcraft library to handle box selection.\");\n let element = this.instance.getEnabledElements()[0];\n let canvas = element.canvas;\n\n // Disable the Wwwc tool\n cornerstoneTools.setToolDisabled(\"Wwwc\");\n\n BoxCraft.createDraggableBBox(canvas, function (topleft, bottomright) {\n callback(topleft, bottomright);\n });\n\n BoxCraft.createResizableBBox(canvas, function(topleft, bottomright) {\n console.log(\"Inside Draggable BBox\",topleft, bottomright);\n callback(topleft, bottomright);\n });\n\n // return this.canvasFallback.select_box(callback);\n\n\n }\n else{\n console.log(\"Using Cornerstonetools to handle box selection.\");\n this.cornerstonetools_instance.setToolActive(\"RectangleRoi\", {\n mouseButtonMask: 1,\n });\n\n let element = this.instance.getEnabledElements()[0];\n let canvas = element.canvas;\n\n canvas.onmouseup = function () {\n let state =\n this.cornerstonetools_instance.globalImageIdSpecificToolStateManager.saveToolState();\n\n let topleft =\n state[Object.keys(state).pop()].RectangleRoi.data[0].handles.start;\n let bottomright =\n state[Object.keys(state).pop()].RectangleRoi.data[0].handles.end;\n\n let topleft_c = this.instance.pixelToCanvas(element.element, topleft);\n let bottomright_c = this.instance.pixelToCanvas(\n element.element,\n bottomright\n );\n\n this.cornerstonetools_instance.clearToolState(\n element.element,\n \"RectangleRoi\"\n );\n this.instance.renderGrayscaleImage(element, true);\n callback(topleft_c, bottomright_c);\n }.bind(this);\n }\n\n }\n\n}","import { Framework } from \"../framework.js\";\n\nimport { Util } from \"../util.js\";\n\n\n\nexport class CanvasFallback extends Framework {\n constructor() {\n super();\n this.name = \"canvasFallback\";\n }\n\n get_canvas() {\n let canvases = document.querySelectorAll('canvas');\n let largestCanvas = canvases[0];\n let largestArea = largestCanvas.width * largestCanvas.height;\n\n for (let i = 1; i < canvases.length; i++) {\n let area = canvases[i].width * canvases[i].height;\n if (area > largestArea) {\n largestCanvas = canvases[i];\n largestArea = area;\n }\n }\n\n return largestCanvas;\n }\n\n get_image(from_canvas) {\n\n let canvas = this.get_canvas();\n\n let ctx = canvas.getContext(\"2d\");\n\n let image = ctx.getImageData(0, 0, canvas.width, canvas.height);\n let rgba_image = Util.rgba_to_grayscale(image.data);\n\n if (from_canvas) {\n return { data: image.data, width: image.width, height: image.height };\n } else {\n return { data: rgba_image, width: image.width, height: image.height };\n }\n }\n\n set_image(new_pixels) {\n let originalcanvas = this.get_canvas();\n\n let newcanvas = window.document.createElement(\"canvas\");\n\n newcanvas.width = originalcanvas.width;\n newcanvas.height = originalcanvas.height;\n\n let ctx = newcanvas.getContext(\"2d\");\n\n let newPixelsRgba = Util.grayscale_to_rgba(new_pixels);\n\n let newPixelsClamped = new Uint8ClampedArray(newPixelsRgba);\n\n let newImageData = new ImageData(\n newPixelsClamped,\n newcanvas.width,\n newcanvas.height\n );\n\n // Draw the new image data onto the canvas\n ctx.putImageData(newImageData, 0, 0);\n\n newcanvas.onclick = function () {\n // on click, we will restore the nv canvas\n newcanvas.parentNode.replaceChild(originalcanvas, newcanvas);\n };\n\n // replace nv canvas with new one\n originalcanvas.parentNode.replaceChild(newcanvas, originalcanvas);\n }\n\n set_mask(new_mask) {\n let image = this.get_image(true);\n\n let originalcanvas = this.get_canvas();\n\n let newcanvas = window.document.createElement('canvas');\n\n newcanvas.width = originalcanvas.width;\n newcanvas.height = originalcanvas.height;\n\n let ctx = newcanvas.getContext('2d');\n\n let imageclamped = new Uint8ClampedArray(image.data);\n\n let newImageData = new ImageData(\n imageclamped,\n newcanvas.width,\n newcanvas.height\n );\n\n // Draw the new image data onto the canvas\n ctx.putImageData(newImageData, 0, 0);\n\n image = ctx.getImageData(0, 0, newcanvas.width, newcanvas.height);\n\n let masked_image = Util.harden_mask(image.data, new_mask);\n\n let masked_image_as_imagedata = new ImageData(\n masked_image,\n newcanvas.width,\n newcanvas.height\n );\n\n ctx.putImageData(masked_image_as_imagedata, 0, 0);\n\n originalcanvas.parentNode.replaceChild(newcanvas, originalcanvas);\n }\n\n select_box(callback) {\n let scriptBoxCraft = document.createElement(\"script\");\n scriptBoxCraft.type = \"text/javascript\";\n scriptBoostlet.src = \"https://boostlet.org/dist/boxcraft.min.js\";\n // scriptBoxCraft.src = \"https://shrutivarade.github.io/BoxCraft/dist/boxCraft.min.js\";\n // scriptBoxCraft.src = \"https://shrutivarade.github.io/boostlet/dist/boxcraft.min.js\";\n // scriptBoxCraft.src = \"http://localhost:8000/dist/boxcraft.min.js\";\n let canvas = this.get_canvas();\n document.head.appendChild(scriptBoxCraft);\n\n scriptBoxCraft.onload = function() {\n\n BoxCraft.createDraggableBBox(canvas, function (topleft, bottomright) {\n console.log(\"Inside Draggable BBox\", topleft, bottomright);\n callback(topleft, bottomright);\n });\n }\n\n }\n\n}","import {Framework} from '../framework.js';\n\nimport {Util} from '../util.js';\nimport {CanvasFallback} from './canvasFallback.js';\n\nexport class NiiVue extends Framework {\n \n constructor(instance) {\n\n super(instance);\n this.name = 'niivue';\n this.canvasFallback = new CanvasFallback();\n\n this.flip_on_png = true;\n\n this.onMouseDown = false;\n this.x1 = null;\n this.y1 = null;\n this.x2 = null;\n this.y2 = null;\n\n }\n\n get_image(from_canvas) {\n\n let element = this.instance.canvas;\n let pixels = null;\n let width = null;\n let height = null;\n\n\n // TODO this is hacky going through the canvas\n // later should grab the real volume data\n\n let old_crosshaircolor = this.instance.opts.crosshairColor;\n let old_crosshairwidth = this.instance.opts.crosshairWidth;\n\n this.instance.setCrosshairColor([0,0,0,0]);\n this.instance.opts.crosshairWidth=0;\n this.instance.updateGLVolume();\n\n\n let ctx = this.instance.gl;\n\n \n width = ctx.drawingBufferWidth;\n height = ctx.drawingBufferHeight;\n\n pixels = new Uint8Array(width * height * 4);\n ctx.readPixels(\n 0, \n 0, \n width, \n height, \n ctx.RGBA, \n ctx.UNSIGNED_BYTE, \n pixels);\n\n // restore crosshairs\n this.instance.setCrosshairColor(old_crosshaircolor);\n this.instance.opts.crosshairWidth = old_crosshairwidth;\n\n if (!Util.is_defined(from_canvas)) {\n\n // convert rgba pixels to grayscale\n pixels = Util.rgba_to_grayscale(pixels);\n\n } else {\n\n // TODO\n // not easily possible yet\n // we could hack it using \n // nv.back.get_value(x,y,z)\n // based on the dimensions\n // nv.back.dims.slice(1);\n // but devs promised easy access in the future\n\n }\n\n\n return {'data':pixels, 'width':width, 'height':height};\n\n }\n\n /**\n * Sets the NiiVue.js image.\n * \n * If is_rgba==true, we do *not* convert to RGBA before setting on canvas.\n **/\n set_image(new_pixels, is_rgba, no_flip) {\n\n // TODO this is hacky since we dont work with the real volume yet\n // create new canvas\n // put pixels on canvas\n // show canvas\n // hide on click\n\n let originalcanvas = this.instance.canvas;\n\n let newcanvas = window.document.createElement('canvas');\n newcanvas.width = originalcanvas.width;\n newcanvas.height = originalcanvas.height;\n\n // put new_pixels down\n let ctx = newcanvas.getContext('2d');\n\n let new_pixels_rgba = null;\n\n if (Util.is_defined(is_rgba)) {\n\n new_pixels_rgba = new_pixels;\n\n } else {\n\n new_pixels_rgba = Util.grayscale_to_rgba(new_pixels);\n\n\n }\n\n let new_pixels_clamped = new Uint8ClampedArray(new_pixels_rgba);\n\n let new_image_data = new ImageData(new_pixels_clamped, newcanvas.width, newcanvas.height);\n \n\n ctx.putImageData(new_image_data, 0, 0);\n\n if (!Util.is_defined(no_flip)) {\n // some flipping action\n ctx.save();\n ctx.scale(1, -1);\n ctx.drawImage(newcanvas, 0, -newcanvas.height);\n ctx.restore();\n }\n\n\n newcanvas.onclick = function() {\n\n // on click, we will restore the nv canvas\n newcanvas.parentNode.replaceChild(originalcanvas, newcanvas);\n\n }\n\n // replace nv canvas with new one\n // originalcanvas.parentNode.replaceChild(newcanvas, originalcanvas);\n newcanvas.style.width = originalcanvas.clientWidth+'px';\n newcanvas.style.height = originalcanvas.clientHeight+'px';\n originalcanvas.parentNode.replaceChild(newcanvas, originalcanvas);\n\n }\n\n set_mask(new_mask) {\n\n // merge image + mask\n // and then call set_image with that information\n\n let image = this.get_image(true);\n\n // TODO here we need to flip one more time, this is until\n // we use the official niivue infrastructure for adding\n // a segmentation layer\n let originalcanvas = this.instance.canvas;\n\n let newcanvas = window.document.createElement('canvas');\n newcanvas.width = originalcanvas.width;\n newcanvas.height = originalcanvas.height;\n // put new_pixels down\n let ctx = newcanvas.getContext('2d');\n let imageclamped = new Uint8ClampedArray(image.data);\n let imagedata = new ImageData(imageclamped, image.width, image.height);\n ctx.putImageData(imagedata, 0, 0);\n ctx.save();\n ctx.scale(1, -1);\n ctx.drawImage(newcanvas, 0, -newcanvas.height);\n ctx.restore();\n image = ctx.getImageData(0, 0, newcanvas.width, newcanvas.height);\n // end of flip\n\n let masked_image = Util.harden_mask(image.data, new_mask);\n\n this.set_image(masked_image, true, true); // rgba data, no flip\n\n\n }\n\n select_box(callback) {\n return this.canvasFallback.select_box(callback);\n\n }\n\n}\n","import {Framework} from '../framework.js';\n\nimport {Util} from '../util.js';\n\nimport {CanvasFallback} from './canvasFallback.js';\n\nexport class OpenSeaDragon extends Framework {\n constructor(instance) {\n super(instance);\n this.name = 'opensedragon';\n this.canvasFallback = new CanvasFallback();\n\n }\n\n get_image(from_canvas) {\n return this.canvasFallback.get_image(from_canvas);\n }\n\n set_image(new_pixels) {\n return this.canvasFallback.set_image(new_pixels);\n }\n\n set_mask(new_mask) {\n // return this.canvasFallback.set_mask(new_mask);\n\n let viewer = null;\n let vs = this.instance._viewers;\n vs.forEach(function (e) {\n if (e.id == \"viewer\") {\n viewer = e;\n }\n });\n\n if (!viewer) {\n throw \"OpenSeaDragon viewer not found.\";\n }\n\n let canvas = viewer.canvas.children[0];\n width = canvas.width;\n height = canvas.height;\n\n let ctx = canvas.getContext(\"2d\");\n let imagedata = ctx.getImageData(0, 0, width, height);\n let pixels = imagedata.data;\n\n let masked_image = Util.harden_mask(pixels, new_mask);\n let masked_image_as_imagedata = new ImageData(masked_image, width, height);\n ctx.putImageData(masked_image_as_imagedata, 0, 0);\n }\n\n select_box(callback) {\n\n console.log(\"Using Boxcraft library to handle box selection.\");\n let viewer = null;\n let vs = this.instance._viewers;\n vs.forEach(function (e) {\n if (e.id == \"viewer\") {\n viewer = e;\n }\n });\n\n if (!viewer) {\n throw \"OpenSeaDragon viewer not found.\";\n }\n\n let canvas = viewer.canvas;\n\n BoxCraft.createDraggableBBox(canvas, function (topleft, bottomright) {\n callback(topleft, bottomright);\n });\n\n }\n\n \n\n}\n \n","import { Framework } from \"../framework.js\";\n\nimport { Util } from \"../util.js\";\n\nimport { CanvasFallback } from \"./canvasFallback.js\";\n\nexport class Xtk extends Framework {\n constructor(instance) {\n super(instance);\n this.name = \"xtk\";\n this.canvasFallback = new CanvasFallback();\n }\n\n get_image(from_canvas) {\n return this.canvasFallback.get_image(from_canvas);\n }\n\n set_image(new_pixels) {\n return this.canvasFallback.set_image(new_pixels);\n }\n\n set_mask(new_mask) {\n return this.canvasFallback.set_mask(new_mask);\n }\n\n select_box(callback) {\n return this.canvasFallback.select_box(callback);\n }\n}\n","import { Framework } from \"../framework.js\";\n\nimport { Util } from \"../util.js\";\n\nimport { CanvasFallback } from \"./canvasFallback.js\";\n\nexport class Papaya extends Framework {\n constructor(instance) {\n super(instance);\n this.name = \"papaya\";\n this.canvasFallback = new CanvasFallback();\n }\n\n get_image(from_canvas) {\n return this.canvasFallback.get_image(from_canvas);\n }\n\n set_image(new_pixels) {\n return this.canvasFallback.set_image(new_pixels);\n }\n\n set_mask(new_mask) {\n return this.canvasFallback.set_mask(new_mask);\n }\n\n select_box(callback) {\n return this.canvasFallback.select_box(callback);\n }\n}\n","\nimport {Boostlet} from \"./boostlet.js\"\n\nwindow.console.log('BOOSTLET VERSION 0.1-beta');\n\n// register global namespace with a new boostlet instance\n// later we might want to support multiple active boostlets\nwindow.Boostlet = new Boostlet();\n","import {Util} from './util.js';\n\nimport {Framework} from './framework.js';\n\nexport class Boostlet {\n\n constructor() {\n\n this.framework = null;\n\n }\n\n /**\n * Initializes the Boostlet.\n * \n * This includes several steps such as identifying the \n * visualization/rendering framework that is available. \n * \n * TODO: Later we want to have fallbacks in place if the framework\n * is not detected.\n * \n */\n init(name, instance) {\n\n if (typeof name != 'undefined' && typeof instance != 'undefined') {\n\n console.log('Framework forced by user!');\n throw \"Forced Framework Not Implemented.\";\n // TODO\n\n } else {\n\n this.framework = Util.detect_framework();\n\n }\n\n if (this.framework) {\n\n console.log('Found', this.framework, '!')\n \n } else {\n\n throw \"Framework Not Found.\";\n\n }\n\n }\n\n /**\n * Let's the user select a region of interest box.\n */\n async select_box(callback) {\n\n this.framework.select_box(callback);\n\n }\n\n /**\n * Let's the user select (multiple) seeds.\n */\n async select_seed(howmany) {\n\n throw \"Missing Implementation.\";\n\n }\n\n /**\n * Loads an external javascript file asynchronously. \n */\n async load_script(url, callback) {\n\n Util.load_script(url, callback);\n\n }\n\n /**\n * Sends a HTTP POST request to a url with some data.\n */\n async send_http_post(url, data, callback) {\n\n Util.send_http_post(url, data, callback);\n\n }\n\n /**\n * Gets the current image (2D).\n * \n * TODO: Optional bounding box should be supported.\n * \n */\n get_image(from_canvas) {\n\n return this.framework.get_image(from_canvas);\n\n }\n\n /**\n * Sets the current image (2D).\n * \n */\n set_image(new_pixels) {\n\n return this.framework.set_image(new_pixels);\n\n }\n\n /**\n * Sets the current mask (2D).\n * \n */\n set_mask(new_mask) {\n\n return this.framework.set_mask(new_mask);\n\n }\n\n /**\n * Encode raw image data to PNG.\n */\n convert_to_png(uint8array, width, height) {\n\n return this.framework.convert_to_png(uint8array, width, height);\n\n }\n\n filter(pixels, width, height, kernel) {\n\n return Util.filter(pixels, width, height, kernel);\n\n }\n\n /**\n * Displays a small div located at the top left corner of the screen with message and will disappear after the specified time (ms).\n * \n */\n hint(message, duration) {\n\n return Util.hint(message, duration);\n\n }\n\n}\n"],"names":[],"version":3,"file":"boostlet.min.js.map"} \ No newline at end of file +{"mappings":"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","sources":["src/util.js","src/framework.js","src/frameworks/cornerstone2d.js","src/frameworks/canvasFallback.js","src/frameworks/niivue.js","src/frameworks/openseadragon.js","src/frameworks/xtk.js","src/frameworks/papaya.js","src/index.js","src/boostlet.js"],"sourcesContent":["import {Framework} from './framework.js';\r\nimport {Cornerstone2D} from './frameworks/cornerstone2d.js';\r\nimport {NiiVue} from './frameworks/niivue.js';\r\nimport { OpenSeaDragon } from './frameworks/openseadragon.js';\r\nimport { Xtk } from './frameworks/xtk.js';\r\nimport { Papaya } from './frameworks/papaya.js';\r\nimport { CanvasFallback } from './frameworks/canvasFallback.js';\r\n\r\nexport class Util {\r\n \r\n static detect_framework() {\r\n\r\n let framework = null;\r\n\r\n if (Util.is_defined(window.nv)) {\r\n \r\n framework = new NiiVue(window.nv);\r\n \r\n } else if (Util.is_defined(window.niivue)) {\r\n \r\n framework = new NiiVue(window.niivue);\r\n\r\n } else if (Util.is_defined(window.cornerstone)) {\r\n\r\n framework = new Cornerstone2D(window.cornerstone);\r\n\r\n } else if (Util.is_defined(window.r)) {\r\n \r\n framework = new Xtk(window.r);\r\n \r\n } else if (Util.is_defined(window.OpenSeadragon)) {\r\n\r\n framework = new OpenSeaDragon(window.OpenSeadragon);\r\n \r\n } else if (Util.is_defined(window.papayaContainers)) {\r\n \r\n framework = new Papaya(window.papayaContainers)\r\n\r\n } else {\r\n // Canvas fallback\r\n \r\n console.log(\"No framework detected, falling back to canvas rendering\");\r\n framework = new CanvasFallback();\r\n \r\n }\r\n\r\n return framework;\r\n\r\n }\r\n\r\n static async load_script(url, callback) {\r\n\r\n // introducing hack to make it work for openneuro\r\n window.Object.defineProperty(window.Object.prototype, 'global', {\r\n get( ){\r\n return window;\r\n },\r\n set(newGlobal) {\r\n globalThis = newGlobal;\r\n }\r\n });\r\n\r\n const script = window.document.createElement(\"script\")\r\n script.type = \"text/javascript\"\r\n script.src = url;\r\n\r\n if (Util.is_defined(callback)) {\r\n script.onload = callback;\r\n }\r\n\r\n window.document.head.appendChild(script);\r\n eval(script);\r\n\r\n }\r\n\r\n static async send_http_post(url, data, callback) {\r\n\r\n let xhr = new XMLHttpRequest();\r\n xhr.open(\"POST\", url);\r\n xhr.onreadystatechange = function () {\r\n if (xhr.readyState === 4) {\r\n // request done\r\n callback( xhr.response );\r\n\r\n return;\r\n\r\n }\r\n }\r\n\r\n xhr.send(data)\r\n\r\n }\r\n\r\n static convert_to_png(uint8array, width, height, flip) {\r\n\r\n // we are using an offscreen canvas for this\r\n let offscreen = window.document.createElement('canvas');\r\n offscreen.width = width;\r\n offscreen.height = height;\r\n\r\n let offscreen_ctx = offscreen.getContext('2d');\r\n\r\n let imgdata = offscreen_ctx.createImageData(offscreen.width, offscreen.height);\r\n let pxdata = imgdata.data;\r\n\r\n for (var i =0; i c.charCodeAt(0));\r\n\r\n return pngpixels;\r\n\r\n }\r\n\r\n static filter(pixels, width, height, kernel) {\r\n\r\n const kernelSize = Math.sqrt(kernel.length);\r\n const halfKernelSize = Math.floor(kernelSize / 2);\r\n\r\n const new_pixels = pixels.slice();\r\n\r\n for (let y = 0; y < height; y++) {\r\n for (let x = 0; x < width; x++) {\r\n const dstIndex = y * width + x;\r\n\r\n let newValue = 0;\r\n for (let ky = 0; ky < kernelSize; ky++) {\r\n for (let kx = 0; kx < kernelSize; kx++) {\r\n const srcX = x + kx - halfKernelSize;\r\n const srcY = y + ky - halfKernelSize;\r\n const srcIndex = srcY * width + srcX;\r\n\r\n const kernelValue = kernel[ky * kernelSize + kx];\r\n newValue += pixels[srcIndex] * kernelValue;\r\n }\r\n }\r\n\r\n new_pixels[dstIndex] = newValue;\r\n\r\n }\r\n }\r\n\r\n return new_pixels;\r\n\r\n }\r\n\r\n static grayscale_to_rgba(grayscale) {\r\n\r\n const rgba = new Uint8Array(grayscale.length * 4);\r\n\r\n for (let i = 0; i < grayscale.length; i++) {\r\n const g = grayscale[i];\r\n const index = i * 4;\r\n\r\n rgba[index] = g;\r\n rgba[index + 1] = g;\r\n rgba[index + 2] = g;\r\n rgba[index + 3] = 255; \r\n }\r\n\r\n return rgba;\r\n\r\n }\r\n\r\n static rgba_to_grayscale(rgba) {\r\n\r\n const grayscale = new Uint8Array(rgba.length / 4);\r\n\r\n for (let i = 0; i < rgba.length; i += 4) {\r\n\r\n grayscale[i / 4] = rgba[i];\r\n\r\n }\r\n\r\n return grayscale;\r\n\r\n }\r\n\r\n /**\r\n * Harden a mask into a grayscale pixel array.\r\n * \r\n * pixels needs to be RGBA\r\n * \r\n * and mask binary.\r\n * \r\n * maskcolor is optional and falls back to blue.\r\n * \r\n **/\r\n static harden_mask(pixels, mask, maskcolor) {\r\n\r\n // Modified from: https://github.com/facebookresearch/segment-anything/blob/40df6e4046d8b07ab8c4519e083408289eb43032/demo/src/components/helpers/maskUtils.tsx\r\n // Copyright (c) Meta Platforms, Inc. and affiliates.\r\n // All rights reserved.\r\n\r\n\r\n let maskcolor_ = [0, 114, 189, 255];\r\n\r\n if (Util.is_defined(maskcolor)) {\r\n \r\n maskcolor_ = maskcolor;\r\n \r\n } \r\n\r\n for (var i = 0; i < mask.length; i++) {\r\n\r\n if (mask[i] > 0.0) {\r\n pixels[4 * i + 0] = maskcolor_[0];\r\n pixels[4 * i + 1] = maskcolor_[1];\r\n pixels[4 * i + 2] = maskcolor_[2];\r\n pixels[4 * i + 3] = maskcolor_[3];\r\n }\r\n\r\n }\r\n\r\n return pixels;\r\n\r\n }\r\n\r\n static is_defined(variable) {\r\n\r\n return (typeof variable != 'undefined');\r\n\r\n }\r\n \r\n // \"Boostlet Tooltips\" - This is a hint mechanism that allows to display a message for a certain amount of time (ms).\r\n static hint(message, duration) {\r\n\r\n let hint = window.document.createElement('div');\r\n hint.id = 'BoostletHint';\r\n\r\n hint.style.position = 'fixed';\r\n hint.style.left = '10px';\r\n hint.style.top = '10px';\r\n hint.style.padding = '10px';\r\n hint.style.background = '#fff';\r\n hint.style.color = '#000';\r\n hint.style.zIndex = '100000';\r\n hint.style.border = '1px solid #007ec6';\r\n hint.style.borderRadius = '5px';\r\n hint.style.boxShadow = '0px 0px 20px 5px rgba(0,0,0, 0.3)';\r\n hint.style.fontSize = '14px';\r\n hint.style.fontWeight = 'bold';\r\n hint.style.textAlign = 'center';\r\n\r\n hint.innerHTML = message;\r\n \r\n window.document.body.appendChild(hint);\r\n\r\n if (typeof duration === 'number' && duration > 0) {\r\n setTimeout(function() {\r\n hint.remove();\r\n }, duration);\r\n }\r\n\r\n }\r\n\r\n}","import {Util} from './util.js';\r\n\r\nexport class Framework {\r\n\r\n constructor(instance) {\r\n\r\n this.name = 'generic';\r\n this.instance = instance;\r\n\r\n this.flip_on_png = false;\r\n\r\n }\r\n\r\n get_image(from_canvas) {\r\n\r\n throw \"Missing Implementation.\";\r\n\r\n }\r\n\r\n set_image(new_pixels) {\r\n\r\n throw \"Missing Implementation.\";\r\n\r\n }\r\n\r\n set_mask(new_mask) {\r\n\r\n throw \"Missing Implementation.\";\r\n \r\n }\r\n\r\n select_box(callback) {\r\n\r\n throw \"Missing Implementation.\";\r\n\r\n }\r\n\r\n convert_to_png(uint8array, width, height) {\r\n\r\n return Util.convert_to_png(uint8array, width, height, this.flip_on_png);\r\n\r\n }\r\n\r\n}\r\n","import {Framework} from '../framework.js';\r\n\r\nimport {Util} from '../util.js';\r\n\r\nimport {CanvasFallback} from './canvasFallback.js';\r\n\r\nexport class Cornerstone2D extends Framework {\r\n \r\n constructor(instance) {\r\n\r\n super(instance);\r\n this.name = 'cornerstone2D';\r\n this.canvasFallback = new CanvasFallback();\r\n\r\n this.cornerstonetools_instance = null;\r\n\r\n if (typeof window.cornerstoneTools != 'undefined') {\r\n\r\n // TODO probably not too robust\r\n this.cornerstonetools_instance = window.cornerstoneTools;\r\n\r\n }\r\n\r\n this.flip_on_png = false;\r\n\r\n }\r\n\r\n get_image(from_canvas) {\r\n\r\n let element = this.instance.getEnabledElements()[0];\r\n let pixels = null;\r\n let width = null;\r\n let height = null;\r\n\r\n if (typeof from_canvas != 'undefined') {\r\n\r\n // TODO this is hacky going through the canvas\r\n // later should grab the real volume data\r\n\r\n let canvas = element.canvas;\r\n width = canvas.width;\r\n height = canvas.height;\r\n\r\n let ctx = canvas.getContext('2d');\r\n\r\n let imagedata = ctx.getImageData(0, 0, width, height);\r\n pixels = imagedata.data;\r\n\r\n } else {\r\n\r\n // this is the real image slice data\r\n let imagedata = element.image;\r\n pixels = imagedata.getPixelData();\r\n width = imagedata.width;\r\n height = imagedata.height;\r\n\r\n }\r\n\r\n return {'data':pixels, 'width':width, 'height':height};\r\n\r\n }\r\n\r\n set_image(new_pixels) {\r\n\r\n let element = this.instance.getEnabledElements()[0];\r\n let pixels = element.image.getPixelData();\r\n\r\n // Set the new pixel values\r\n pixels.set(new_pixels);\r\n\r\n // Re-render the current slice\r\n cornerstone.renderGrayscaleImage(element, true);\r\n\r\n }\r\n\r\n set_mask(new_mask) {\r\n return this.canvasFallback.set_mask(new_mask);\r\n \r\n }\r\n\r\n select_box(callback) {\r\n\r\n if(this.cornerstonetools_instance.RectangleRoiTool === undefined) {\r\n console.log(\"Using Boxcraft library to handle box selection.\");\r\n let element = this.instance.getEnabledElements()[0];\r\n let canvas = element.canvas;\r\n\r\n // Disable the Wwwc tool\r\n cornerstoneTools.setToolDisabled(\"Wwwc\");\r\n\r\n BoxCraft.createDraggableBBox(canvas, function (topleft, bottomright) {\r\n callback(topleft, bottomright);\r\n });\r\n\r\n BoxCraft.createResizableBBox(canvas, function(topleft, bottomright) {\r\n console.log(\"Inside Draggable BBox\",topleft, bottomright);\r\n callback(topleft, bottomright);\r\n });\r\n\r\n // return this.canvasFallback.select_box(callback);\r\n\r\n\r\n }\r\n else{\r\n console.log(\"Using Cornerstonetools to handle box selection.\");\r\n this.cornerstonetools_instance.setToolActive(\"RectangleRoi\", {\r\n mouseButtonMask: 1,\r\n });\r\n\r\n let element = this.instance.getEnabledElements()[0];\r\n let canvas = element.canvas;\r\n\r\n canvas.onmouseup = function () {\r\n let state =\r\n this.cornerstonetools_instance.globalImageIdSpecificToolStateManager.saveToolState();\r\n\r\n let topleft =\r\n state[Object.keys(state).pop()].RectangleRoi.data[0].handles.start;\r\n let bottomright =\r\n state[Object.keys(state).pop()].RectangleRoi.data[0].handles.end;\r\n\r\n let topleft_c = this.instance.pixelToCanvas(element.element, topleft);\r\n let bottomright_c = this.instance.pixelToCanvas(\r\n element.element,\r\n bottomright\r\n );\r\n\r\n this.cornerstonetools_instance.clearToolState(\r\n element.element,\r\n \"RectangleRoi\"\r\n );\r\n this.instance.renderGrayscaleImage(element, true);\r\n callback(topleft_c, bottomright_c);\r\n }.bind(this);\r\n }\r\n\r\n }\r\n\r\n}","import { Framework } from \"../framework.js\";\r\n\r\nimport { Util } from \"../util.js\";\r\n\r\n\r\n\r\nexport class CanvasFallback extends Framework {\r\n constructor() {\r\n super();\r\n this.name = \"canvasFallback\";\r\n }\r\n\r\n get_canvas() {\r\n let canvases = document.querySelectorAll('canvas');\r\n let largestCanvas = canvases[0];\r\n let largestArea = largestCanvas.width * largestCanvas.height;\r\n\r\n for (let i = 1; i < canvases.length; i++) {\r\n let area = canvases[i].width * canvases[i].height;\r\n if (area > largestArea) {\r\n largestCanvas = canvases[i];\r\n largestArea = area;\r\n }\r\n }\r\n\r\n return largestCanvas;\r\n }\r\n\r\n get_image(from_canvas) {\r\n\r\n let canvas = this.get_canvas();\r\n\r\n let ctx = canvas.getContext(\"2d\");\r\n\r\n let image = ctx.getImageData(0, 0, canvas.width, canvas.height);\r\n let rgba_image = Util.rgba_to_grayscale(image.data);\r\n\r\n if (from_canvas) {\r\n return { data: image.data, width: image.width, height: image.height };\r\n } else {\r\n return { data: rgba_image, width: image.width, height: image.height };\r\n }\r\n }\r\n\r\n set_image(new_pixels) {\r\n let originalcanvas = this.get_canvas();\r\n\r\n let newcanvas = window.document.createElement(\"canvas\");\r\n\r\n newcanvas.width = originalcanvas.width;\r\n newcanvas.height = originalcanvas.height;\r\n\r\n let ctx = newcanvas.getContext(\"2d\");\r\n\r\n let newPixelsRgba = Util.grayscale_to_rgba(new_pixels);\r\n\r\n let newPixelsClamped = new Uint8ClampedArray(newPixelsRgba);\r\n\r\n let newImageData = new ImageData(\r\n newPixelsClamped,\r\n newcanvas.width,\r\n newcanvas.height\r\n );\r\n\r\n // Draw the new image data onto the canvas\r\n ctx.putImageData(newImageData, 0, 0);\r\n\r\n newcanvas.onclick = function () {\r\n // on click, we will restore the nv canvas\r\n newcanvas.parentNode.replaceChild(originalcanvas, newcanvas);\r\n };\r\n\r\n // replace nv canvas with new one\r\n originalcanvas.parentNode.replaceChild(newcanvas, originalcanvas);\r\n }\r\n\r\n set_mask(new_mask) {\r\n let image = this.get_image(true);\r\n\r\n let originalcanvas = this.get_canvas();\r\n\r\n let newcanvas = window.document.createElement('canvas');\r\n\r\n newcanvas.width = originalcanvas.width;\r\n newcanvas.height = originalcanvas.height;\r\n\r\n let ctx = newcanvas.getContext('2d');\r\n\r\n let imageclamped = new Uint8ClampedArray(image.data);\r\n\r\n let newImageData = new ImageData(\r\n imageclamped,\r\n newcanvas.width,\r\n newcanvas.height\r\n );\r\n\r\n // Draw the new image data onto the canvas\r\n ctx.putImageData(newImageData, 0, 0);\r\n\r\n image = ctx.getImageData(0, 0, newcanvas.width, newcanvas.height);\r\n\r\n let masked_image = Util.harden_mask(image.data, new_mask);\r\n\r\n let masked_image_as_imagedata = new ImageData(\r\n masked_image,\r\n newcanvas.width,\r\n newcanvas.height\r\n );\r\n\r\n ctx.putImageData(masked_image_as_imagedata, 0, 0);\r\n\r\n originalcanvas.parentNode.replaceChild(newcanvas, originalcanvas);\r\n }\r\n\r\n select_box(callback) {\r\n let scriptBoxCraft = document.createElement(\"script\");\r\n scriptBoxCraft.type = \"text/javascript\";\r\n scriptBoxCraft.src = \"https://boostlet.org/dist/boxcraft.min.js\";\r\n // scriptBoxCraft.src = \"https://shrutivarade.github.io/BoxCraft/dist/boxCraft.min.js\";\r\n // scriptBoxCraft.src = \"https://shrutivarade.github.io/boostlet/dist/boxcraft.min.js\";\r\n // scriptBoxCraft.src = \"http://localhost:8000/dist/boxcraft.min.js\";\r\n let canvas = this.get_canvas();\r\n document.head.appendChild(scriptBoxCraft);\r\n\r\n scriptBoxCraft.onload = function() {\r\n\r\n BoxCraft.createDraggableBBox(canvas, function (topleft, bottomright) {\r\n console.log(\"Inside Draggable BBox\", topleft, bottomright);\r\n callback(topleft, bottomright);\r\n });\r\n }\r\n\r\n }\r\n\r\n}\r\n","import {Framework} from '../framework.js';\r\n\r\nimport {Util} from '../util.js';\r\nimport {CanvasFallback} from './canvasFallback.js';\r\n\r\nexport class NiiVue extends Framework {\r\n \r\n constructor(instance) {\r\n\r\n super(instance);\r\n this.name = 'niivue';\r\n this.canvasFallback = new CanvasFallback();\r\n\r\n this.flip_on_png = true;\r\n\r\n this.onMouseDown = false;\r\n this.x1 = null;\r\n this.y1 = null;\r\n this.x2 = null;\r\n this.y2 = null;\r\n\r\n }\r\n\r\n get_image(from_canvas) {\r\n\r\n let element = this.instance.canvas;\r\n let pixels = null;\r\n let width = null;\r\n let height = null;\r\n\r\n\r\n // TODO this is hacky going through the canvas\r\n // later should grab the real volume data\r\n\r\n let old_crosshaircolor = this.instance.opts.crosshairColor;\r\n let old_crosshairwidth = this.instance.opts.crosshairWidth;\r\n\r\n this.instance.setCrosshairColor([0,0,0,0]);\r\n this.instance.opts.crosshairWidth=0;\r\n this.instance.updateGLVolume();\r\n\r\n\r\n let ctx = this.instance.gl;\r\n\r\n \r\n width = ctx.drawingBufferWidth;\r\n height = ctx.drawingBufferHeight;\r\n\r\n pixels = new Uint8Array(width * height * 4);\r\n ctx.readPixels(\r\n 0, \r\n 0, \r\n width, \r\n height, \r\n ctx.RGBA, \r\n ctx.UNSIGNED_BYTE, \r\n pixels);\r\n\r\n // restore crosshairs\r\n this.instance.setCrosshairColor(old_crosshaircolor);\r\n this.instance.opts.crosshairWidth = old_crosshairwidth;\r\n\r\n if (!Util.is_defined(from_canvas)) {\r\n\r\n // convert rgba pixels to grayscale\r\n pixels = Util.rgba_to_grayscale(pixels);\r\n\r\n } else {\r\n\r\n // TODO\r\n // not easily possible yet\r\n // we could hack it using \r\n // nv.back.get_value(x,y,z)\r\n // based on the dimensions\r\n // nv.back.dims.slice(1);\r\n // but devs promised easy access in the future\r\n\r\n }\r\n\r\n\r\n return {'data':pixels, 'width':width, 'height':height};\r\n\r\n }\r\n\r\n /**\r\n * Sets the NiiVue.js image.\r\n * \r\n * If is_rgba==true, we do *not* convert to RGBA before setting on canvas.\r\n **/\r\n set_image(new_pixels, is_rgba, no_flip) {\r\n\r\n // TODO this is hacky since we dont work with the real volume yet\r\n // create new canvas\r\n // put pixels on canvas\r\n // show canvas\r\n // hide on click\r\n\r\n let originalcanvas = this.instance.canvas;\r\n\r\n let newcanvas = window.document.createElement('canvas');\r\n newcanvas.width = originalcanvas.width;\r\n newcanvas.height = originalcanvas.height;\r\n\r\n // put new_pixels down\r\n let ctx = newcanvas.getContext('2d');\r\n\r\n let new_pixels_rgba = null;\r\n\r\n if (Util.is_defined(is_rgba)) {\r\n\r\n new_pixels_rgba = new_pixels;\r\n\r\n } else {\r\n\r\n new_pixels_rgba = Util.grayscale_to_rgba(new_pixels);\r\n\r\n\r\n }\r\n\r\n let new_pixels_clamped = new Uint8ClampedArray(new_pixels_rgba);\r\n\r\n let new_image_data = new ImageData(new_pixels_clamped, newcanvas.width, newcanvas.height);\r\n \r\n\r\n ctx.putImageData(new_image_data, 0, 0);\r\n\r\n if (!Util.is_defined(no_flip)) {\r\n // some flipping action\r\n ctx.save();\r\n ctx.scale(1, -1);\r\n ctx.drawImage(newcanvas, 0, -newcanvas.height);\r\n ctx.restore();\r\n }\r\n\r\n\r\n newcanvas.onclick = function() {\r\n\r\n // on click, we will restore the nv canvas\r\n newcanvas.parentNode.replaceChild(originalcanvas, newcanvas);\r\n\r\n }\r\n\r\n // replace nv canvas with new one\r\n // originalcanvas.parentNode.replaceChild(newcanvas, originalcanvas);\r\n newcanvas.style.width = originalcanvas.clientWidth+'px';\r\n newcanvas.style.height = originalcanvas.clientHeight+'px';\r\n originalcanvas.parentNode.replaceChild(newcanvas, originalcanvas);\r\n\r\n }\r\n\r\n set_mask(new_mask) {\r\n\r\n // merge image + mask\r\n // and then call set_image with that information\r\n\r\n let image = this.get_image(true);\r\n\r\n // TODO here we need to flip one more time, this is until\r\n // we use the official niivue infrastructure for adding\r\n // a segmentation layer\r\n let originalcanvas = this.instance.canvas;\r\n\r\n let newcanvas = window.document.createElement('canvas');\r\n newcanvas.width = originalcanvas.width;\r\n newcanvas.height = originalcanvas.height;\r\n // put new_pixels down\r\n let ctx = newcanvas.getContext('2d');\r\n let imageclamped = new Uint8ClampedArray(image.data);\r\n let imagedata = new ImageData(imageclamped, image.width, image.height);\r\n ctx.putImageData(imagedata, 0, 0);\r\n ctx.save();\r\n ctx.scale(1, -1);\r\n ctx.drawImage(newcanvas, 0, -newcanvas.height);\r\n ctx.restore();\r\n image = ctx.getImageData(0, 0, newcanvas.width, newcanvas.height);\r\n // end of flip\r\n\r\n let masked_image = Util.harden_mask(image.data, new_mask);\r\n\r\n this.set_image(masked_image, true, true); // rgba data, no flip\r\n\r\n\r\n }\r\n\r\n select_box(callback) {\r\n return this.canvasFallback.select_box(callback);\r\n\r\n }\r\n\r\n}\r\n","import {Framework} from '../framework.js';\r\n\r\nimport {Util} from '../util.js';\r\n\r\nimport {CanvasFallback} from './canvasFallback.js';\r\n\r\nexport class OpenSeaDragon extends Framework {\r\n constructor(instance) {\r\n super(instance);\r\n this.name = 'opensedragon';\r\n this.canvasFallback = new CanvasFallback();\r\n\r\n }\r\n\r\n get_image(from_canvas) {\r\n return this.canvasFallback.get_image(from_canvas);\r\n }\r\n\r\n set_image(new_pixels) {\r\n return this.canvasFallback.set_image(new_pixels);\r\n }\r\n\r\n set_mask(new_mask) {\r\n // return this.canvasFallback.set_mask(new_mask);\r\n\r\n let viewer = null;\r\n let vs = this.instance._viewers;\r\n vs.forEach(function (e) {\r\n if (e.id == \"viewer\") {\r\n viewer = e;\r\n }\r\n });\r\n\r\n if (!viewer) {\r\n throw \"OpenSeaDragon viewer not found.\";\r\n }\r\n\r\n let canvas = viewer.canvas.children[0];\r\n width = canvas.width;\r\n height = canvas.height;\r\n\r\n let ctx = canvas.getContext(\"2d\");\r\n let imagedata = ctx.getImageData(0, 0, width, height);\r\n let pixels = imagedata.data;\r\n\r\n let masked_image = Util.harden_mask(pixels, new_mask);\r\n let masked_image_as_imagedata = new ImageData(masked_image, width, height);\r\n ctx.putImageData(masked_image_as_imagedata, 0, 0);\r\n }\r\n\r\n select_box(callback) {\r\n\r\n console.log(\"Using Boxcraft library to handle box selection.\");\r\n let viewer = null;\r\n let vs = this.instance._viewers;\r\n vs.forEach(function (e) {\r\n if (e.id == \"viewer\") {\r\n viewer = e;\r\n }\r\n });\r\n\r\n if (!viewer) {\r\n throw \"OpenSeaDragon viewer not found.\";\r\n }\r\n\r\n let canvas = viewer.canvas;\r\n\r\n BoxCraft.createDraggableBBox(canvas, function (topleft, bottomright) {\r\n callback(topleft, bottomright);\r\n });\r\n\r\n }\r\n\r\n \r\n\r\n}\r\n \r\n","import { Framework } from \"../framework.js\";\r\n\r\nimport { Util } from \"../util.js\";\r\n\r\nimport { CanvasFallback } from \"./canvasFallback.js\";\r\n\r\nexport class Xtk extends Framework {\r\n constructor(instance) {\r\n super(instance);\r\n this.name = \"xtk\";\r\n this.canvasFallback = new CanvasFallback();\r\n }\r\n\r\n get_image(from_canvas) {\r\n return this.canvasFallback.get_image(from_canvas);\r\n }\r\n\r\n set_image(new_pixels) {\r\n return this.canvasFallback.set_image(new_pixels);\r\n }\r\n\r\n set_mask(new_mask) {\r\n return this.canvasFallback.set_mask(new_mask);\r\n }\r\n\r\n select_box(callback) {\r\n return this.canvasFallback.select_box(callback);\r\n }\r\n}\r\n","import { Framework } from \"../framework.js\";\r\n\r\nimport { Util } from \"../util.js\";\r\n\r\nimport { CanvasFallback } from \"./canvasFallback.js\";\r\n\r\nexport class Papaya extends Framework {\r\n constructor(instance) {\r\n super(instance);\r\n this.name = \"papaya\";\r\n this.canvasFallback = new CanvasFallback();\r\n }\r\n\r\n get_image(from_canvas) {\r\n return this.canvasFallback.get_image(from_canvas);\r\n }\r\n\r\n set_image(new_pixels) {\r\n return this.canvasFallback.set_image(new_pixels);\r\n }\r\n\r\n set_mask(new_mask) {\r\n return this.canvasFallback.set_mask(new_mask);\r\n }\r\n\r\n select_box(callback) {\r\n return this.canvasFallback.select_box(callback);\r\n }\r\n}\r\n","\r\nimport {Boostlet} from \"./boostlet.js\"\r\n\r\nwindow.console.log('BOOSTLET VERSION 0.1-beta');\r\n\r\n// register global namespace with a new boostlet instance\r\n// later we might want to support multiple active boostlets\r\nwindow.Boostlet = new Boostlet();\r\n","import {Util} from './util.js';\r\n\r\nimport {Framework} from './framework.js';\r\n\r\nexport class Boostlet {\r\n\r\n constructor() {\r\n\r\n this.framework = null;\r\n\r\n }\r\n\r\n /**\r\n * Initializes the Boostlet.\r\n * \r\n * This includes several steps such as identifying the \r\n * visualization/rendering framework that is available. \r\n * \r\n * TODO: Later we want to have fallbacks in place if the framework\r\n * is not detected.\r\n * \r\n */\r\n init(name, instance) {\r\n\r\n if (typeof name != 'undefined' && typeof instance != 'undefined') {\r\n\r\n console.log('Framework forced by user!');\r\n throw \"Forced Framework Not Implemented.\";\r\n // TODO\r\n\r\n } else {\r\n\r\n this.framework = Util.detect_framework();\r\n\r\n }\r\n\r\n if (this.framework) {\r\n\r\n console.log('Found', this.framework, '!')\r\n \r\n } else {\r\n\r\n throw \"Framework Not Found.\";\r\n\r\n }\r\n\r\n }\r\n\r\n /**\r\n * Let's the user select a region of interest box.\r\n */\r\n async select_box(callback) {\r\n\r\n this.framework.select_box(callback);\r\n\r\n }\r\n\r\n /**\r\n * Let's the user select (multiple) seeds.\r\n */\r\n async select_seed(howmany) {\r\n\r\n throw \"Missing Implementation.\";\r\n\r\n }\r\n\r\n /**\r\n * Loads an external javascript file asynchronously. \r\n */\r\n async load_script(url, callback) {\r\n\r\n Util.load_script(url, callback);\r\n\r\n }\r\n\r\n /**\r\n * Sends a HTTP POST request to a url with some data.\r\n */\r\n async send_http_post(url, data, callback) {\r\n\r\n Util.send_http_post(url, data, callback);\r\n\r\n }\r\n\r\n /**\r\n * Gets the current image (2D).\r\n * \r\n * TODO: Optional bounding box should be supported.\r\n * \r\n */\r\n get_image(from_canvas) {\r\n\r\n return this.framework.get_image(from_canvas);\r\n\r\n }\r\n\r\n /**\r\n * Sets the current image (2D).\r\n * \r\n */\r\n set_image(new_pixels) {\r\n\r\n return this.framework.set_image(new_pixels);\r\n\r\n }\r\n\r\n /**\r\n * Sets the current mask (2D).\r\n * \r\n */\r\n set_mask(new_mask) {\r\n\r\n return this.framework.set_mask(new_mask);\r\n\r\n }\r\n\r\n /**\r\n * Encode raw image data to PNG.\r\n */\r\n convert_to_png(uint8array, width, height) {\r\n\r\n return this.framework.convert_to_png(uint8array, width, height);\r\n\r\n }\r\n\r\n filter(pixels, width, height, kernel) {\r\n\r\n return Util.filter(pixels, width, height, kernel);\r\n\r\n }\r\n\r\n /**\r\n * Displays a small div located at the top left corner of the screen with message and will disappear after the specified time (ms).\r\n * \r\n */\r\n hint(message, duration) {\r\n\r\n return Util.hint(message, duration);\r\n\r\n }\r\n\r\n}\r\n"],"names":[],"version":3,"file":"boostlet.min.js.map"} \ No newline at end of file diff --git a/index.html b/index.html index 1599f260..7ddfe826 100644 --- a/index.html +++ b/index.html @@ -230,7 +230,7 @@ window.onload = function() { - var examples = ['sobel.js', 'segmentanything.js', 'imageCaptioning.js', 'plotly.js', 'trako.js', 'melanoma.js']; + var examples = ['sobel.js', 'segmentanything.js', 'imageCaptioning.js', 'plotly.js', 'trako.js', 'melanoma.js', 'webllm.js']; // var baseurl = 'https://raw.githubusercontent.com/mpsych/boostlet/main/examples/'; // var baseurl = 'https://raw.githubusercontent.com/gaiborjosue/boostlet/melanoma/examples/'; @@ -247,7 +247,7 @@ } - var codeurl = 'https://github.com/mpsych/boostlet/blob/main/examples/'; + var codeurl = 'http://localhost:5500/examples/'; // var codeurl = 'https://github.com/shrutivarade/boostlet/blob/main/examples/'; for (e in examples) { @@ -298,6 +298,8 @@ + + From 9d34c5abf5a25124849ed21f18cc6e6d0301ae57 Mon Sep 17 00:00:00 2001 From: gaiborjosue Date: Fri, 10 May 2024 20:02:23 -0400 Subject: [PATCH 5/9] fix(web-llm): Added public urls --- examples/webllm.js | 3 ++- index.html | 7 ++++--- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/examples/webllm.js b/examples/webllm.js index 472e146d..3b3b56c7 100644 --- a/examples/webllm.js +++ b/examples/webllm.js @@ -1,7 +1,8 @@ script = document.createElement("script"); script.type = "text/javascript"; //script.src = "https://boostlet.org/dist/boostlet.min.js"; -script.src = "http://localhost:5500/dist/boostlet.min.js"; +// script.src = "http://localhost:5500/dist/boostlet.min.js"; +script.src = "https://gaiborjosue.github.io/boostlet/webllm/dist/boostlet.min.js"; script.onload = run; document.head.appendChild(script); diff --git a/index.html b/index.html index 7ddfe826..81fb8258 100644 --- a/index.html +++ b/index.html @@ -233,8 +233,8 @@ var examples = ['sobel.js', 'segmentanything.js', 'imageCaptioning.js', 'plotly.js', 'trako.js', 'melanoma.js', 'webllm.js']; // var baseurl = 'https://raw.githubusercontent.com/mpsych/boostlet/main/examples/'; - // var baseurl = 'https://raw.githubusercontent.com/gaiborjosue/boostlet/melanoma/examples/'; - var baseurl = 'http://localhost:5500/examples/'; + var baseurl = 'https://raw.githubusercontent.com/gaiborjosue/boostlet/webllm/examples/'; + // var baseurl = 'http://localhost:5500/examples/'; // var baseurl = 'http://localhost:8000/examples/'; // var baseurl = 'https://raw.githubusercontent.com/shrutivarade/boostlet/main/examples/'; @@ -247,8 +247,9 @@ } - var codeurl = 'http://localhost:5500/examples/'; + // var codeurl = 'http://localhost:5500/examples/'; // var codeurl = 'https://github.com/shrutivarade/boostlet/blob/main/examples/'; + var codeurl = 'https://github.com/gaiborjosue/boostlet/blob/webllm/examples/'; for (e in examples) { From 10ecbddd4fbab0341bfd0583d37afadf94b24d1e Mon Sep 17 00:00:00 2001 From: gaiborjosue Date: Fri, 10 May 2024 20:06:21 -0400 Subject: [PATCH 6/9] fix(urls) --- examples/webllm.js | 2 +- index.html | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/examples/webllm.js b/examples/webllm.js index 3b3b56c7..ed27cb08 100644 --- a/examples/webllm.js +++ b/examples/webllm.js @@ -2,7 +2,7 @@ script = document.createElement("script"); script.type = "text/javascript"; //script.src = "https://boostlet.org/dist/boostlet.min.js"; // script.src = "http://localhost:5500/dist/boostlet.min.js"; -script.src = "https://gaiborjosue.github.io/boostlet/webllm/dist/boostlet.min.js"; +script.src = "https://gaiborjosue.github.io/boostlet/dist/boostlet.min.js"; script.onload = run; document.head.appendChild(script); diff --git a/index.html b/index.html index 81fb8258..9e6638e7 100644 --- a/index.html +++ b/index.html @@ -233,7 +233,7 @@ var examples = ['sobel.js', 'segmentanything.js', 'imageCaptioning.js', 'plotly.js', 'trako.js', 'melanoma.js', 'webllm.js']; // var baseurl = 'https://raw.githubusercontent.com/mpsych/boostlet/main/examples/'; - var baseurl = 'https://raw.githubusercontent.com/gaiborjosue/boostlet/webllm/examples/'; + var baseurl = 'https://raw.githubusercontent.com/gaiborjosue/boostlet/webllm/ewebllm.xamples/'; // var baseurl = 'http://localhost:5500/examples/'; // var baseurl = 'http://localhost:8000/examples/'; // var baseurl = 'https://raw.githubusercontent.com/shrutivarade/boostlet/main/examples/'; From 5dd57d1bd8ba4b0abd4f23836dc08352b5bff137 Mon Sep 17 00:00:00 2001 From: gaiborjosue Date: Fri, 10 May 2024 21:01:47 -0400 Subject: [PATCH 7/9] fix(index) --- index.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/index.html b/index.html index 9e6638e7..81fb8258 100644 --- a/index.html +++ b/index.html @@ -233,7 +233,7 @@ var examples = ['sobel.js', 'segmentanything.js', 'imageCaptioning.js', 'plotly.js', 'trako.js', 'melanoma.js', 'webllm.js']; // var baseurl = 'https://raw.githubusercontent.com/mpsych/boostlet/main/examples/'; - var baseurl = 'https://raw.githubusercontent.com/gaiborjosue/boostlet/webllm/ewebllm.xamples/'; + var baseurl = 'https://raw.githubusercontent.com/gaiborjosue/boostlet/webllm/examples/'; // var baseurl = 'http://localhost:5500/examples/'; // var baseurl = 'http://localhost:8000/examples/'; // var baseurl = 'https://raw.githubusercontent.com/shrutivarade/boostlet/main/examples/'; From feccace15095879aeac2746990f858c7b9d1998f Mon Sep 17 00:00:00 2001 From: gaiborjosue Date: Fri, 10 May 2024 23:14:59 -0400 Subject: [PATCH 8/9] fix(web-llm): Propagation on typing --- examples/webllm.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/examples/webllm.js b/examples/webllm.js index ed27cb08..1583451d 100644 --- a/examples/webllm.js +++ b/examples/webllm.js @@ -158,7 +158,8 @@ function setupChatUI() { if (event.key === "Enter") { event.preventDefault(); // Prevent the default action to avoid submitting the form document.getElementById("chatui-send-btn").click(); // Trigger the click event on the send button - } + } + event.stopPropagation(); }); Boostlet.hint("Models with “-1k” suffix signify 1024 context length, lowering ~2-3GB VRAM requirement compared to their counterparts. Feel free to start trying with those", 5000); From ebc4c6f296b17a23e3293caa1ef1f728ae356e62 Mon Sep 17 00:00:00 2001 From: gaiborjosue Date: Sun, 12 May 2024 15:48:32 -0400 Subject: [PATCH 9/9] fix(web-llm): Propagation, syntax, select model --- examples/webllm.js | 401 ++++++++++++++++++++++++--------------------- 1 file changed, 211 insertions(+), 190 deletions(-) diff --git a/examples/webllm.js b/examples/webllm.js index 1583451d..2607d218 100644 --- a/examples/webllm.js +++ b/examples/webllm.js @@ -21,207 +21,198 @@ async function run() { // Import the module const module = await import(script.src); window.webllm = module; - setupChatUI(); - populateModelSelector(); + const chatUI = new ChatUI(); + chatUI.setupChatUI(); + chatUI.populateModelSelector(); }; } -function setupChatUI() { - const style = document.createElement("style"); - style.type = "text/css"; - style.innerHTML = ` - .chatui { - display: flex; - position: fixed; - bottom: 0; - right: 0; - flex-direction: column; - width: 350px; - height: 600px; - border: 2px solid; - border-radius: 12px; - background-color: #1F2027; - z-index: 1000; - padding: 10px; - } - .chatui-select-wrapper { - display: flex; - justify-content: center; - background-color: #1F2027; - padding: 10px 0; - } - #chatui-select { - width: 90%; - background-color: #1F2027; - color: white; - height: 70%; - align-self: center; - border-radius: 5px; - } - #chatui-select:focus { - outline: none; - } - .chatui-chat { - flex: 1; - overflow-y: auto; - padding: 10px; - background-color: #1F2027; - } - .chatui-inputarea { - display: flex; - padding: 10px; - border-top: 2px solid transparent; - background-color: #1F2027; - } - .chatui-input { - flex: 1; - background-color: #40414F; - color: white; - border: none; - border-radius: 3px; - padding: 10px; - font-size: 16px; - } - .chatui-send-btn, .chatui-reset-btn { - background-color: #40414F; - color: white; - border: none; - border-radius: 3px; - padding: 10px; - cursor: pointer; - margin-left: 10px; - } - .chatui-send-btn:hover, .chatui-reset-btn:hover { - background-color: #007EC6; - } - .msg-bubble { - display: flex; - align-items: center; - background-color: #f0f0f0; - border-radius: 8px; - padding: 10px; - color: black; - width: calc(100% - 20px); - margin: 5px auto; - box-sizing: border-box; - } - .left-msg .msg-bubble { - background-color: #343541; /* LLM Messages */ - color: #ececec; - } - .right-msg .msg-bubble { - background-color: #444654; /* User Messages */ - color: #ececec; - } - .profile-image { - width: 30px; - height: 30px; - border-radius: 50%; - margin-right: 10px; - } - .message-label { - font-size: 12px; - color: #ccc; - text-align: center; - } - .select-label { - font-size: 16px; - color: #ccc; - text-align: left; - } - - - `; - document.head.appendChild(style); - - const chatContainer = document.createElement("div"); - chatContainer.className = "chatui"; - document.body.appendChild(chatContainer); - - chatContainer.innerHTML = ` -
-

Select a model to start chatting:

- -
-
-
- - - -
- `; - - const chatInput = document.getElementById("chatui-input"); - chatInput.addEventListener("keypress", function(event) { - if (event.key === "Enter") { - event.preventDefault(); // Prevent the default action to avoid submitting the form - document.getElementById("chatui-send-btn").click(); // Trigger the click event on the send button - } - event.stopPropagation(); - }); +class ChatUI { + constructor() { + this.engine = null; + this.uiChat = null; + this.uiInput = null; + this.uiSendBtn = null; + this.uiResetBtn = null; + } - Boostlet.hint("Models with “-1k” suffix signify 1024 context length, lowering ~2-3GB VRAM requirement compared to their counterparts. Feel free to start trying with those", 5000); -} + setupChatUI() { + const style = document.createElement("style"); + style.type = "text/css"; + style.innerHTML = ` + .chatui { + display: flex; + position: fixed; + bottom: 0; + right: 0; + flex-direction: column; + width: 350px; + height: 600px; + border: 2px solid; + border-radius: 12px; + background-color: #1F2027; + z-index: 1000; + padding: 10px; + } + .chatui-select-wrapper { + display: flex; + justify-content: center; + background-color: #1F2027; + padding: 10px 0; + } + #chatui-select { + width: 90%; + background-color: #1F2027; + color: white; + height: 70%; + align-self: center; + border-radius: 5px; + } + #chatui-select:focus { + outline: none; + } + .chatui-chat { + flex: 1; + overflow-y: auto; + padding: 10px; + background-color: #1F2027; + } + .chatui-inputarea { + display: flex; + padding: 10px; + border-top: 2px solid transparent; + background-color: #1F2027; + } + .chatui-input { + flex: 1; + background-color: #40414F; + color: white; + border: none; + border-radius: 3px; + padding: 10px; + font-size: 16px; + } + .chatui-send-btn, .chatui-reset-btn { + background-color: #40414F; + color: white; + border: none; + border-radius: 3px; + padding: 10px; + cursor: pointer; + margin-left: 10px; + } + .chatui-send-btn:hover, .chatui-reset-btn:hover { + background-color: #007EC6; + } + .msg-bubble { + display: flex; + align-items: center; + background-color: #f0f0f0; + border-radius: 8px; + padding: 10px; + color: black; + width: calc(100% - 20px); + margin: 5px auto; + box-sizing: border-box; + } + .left-msg .msg-bubble { + background-color: #343541; /* LLM Messages */ + color: #ececec; + } + .right-msg .msg-bubble { + background-color: #444654; /* User Messages */ + color: #ececec; + } + .profile-image { + width: 30px; + height: 30px; + border-radius: 50%; + margin-right: 10px; + } + .message-label { + font-size: 12px; + color: #ccc; + text-align: center; + } + .select-label { + font-size: 16px; + color: #ccc; + text-align: left; + } + + + `; + document.head.appendChild(style); + + const chatContainer = document.createElement("div"); + chatContainer.className = "chatui"; + document.body.appendChild(chatContainer); + + chatContainer.innerHTML = ` +
+

Select a model to start chatting:

+ +
+
+
+ + + +
+ `; -function populateModelSelector() { - const modelSelect = document.getElementById("chatui-select"); - webllm.prebuiltAppConfig.model_list.forEach((model, index) => { - let option = new Option(model.model_id, model.model_id); - modelSelect.add(option); - }); + this.uiChat = document.getElementById("chatui-chat"); + this.uiInput = document.getElementById("chatui-input"); + this.uiSendBtn = document.getElementById("chatui-send-btn"); + this.uiResetBtn = document.getElementById("chatui-reset-btn"); + + const chatInput = document.getElementById("chatui-input"); + chatInput.addEventListener("keypress", function(event) { + if (event.key === "Enter") { + event.preventDefault(); + document.getElementById("chatui-send-btn").click(); - modelSelect.addEventListener("change", function () { - initializeChat(this.value); - }); -} + // Avoid propagation + event.stopPropagation(); + } + }); + + Boostlet.hint("Models with “-1k” suffix signify 1024 context length, lowering ~2-3GB VRAM requirement compared to their counterparts. Feel free to start trying with those", 5000); + } -// Initialize the chat -async function initializeChat(selectedModel) { - try { - const initProgressCallback = (report) => { - showProgress(report.text); - }; - const engine = await webllm.CreateEngine(selectedModel, { - initProgressCallback, + populateModelSelector() { + const modelSelect = document.getElementById("chatui-select"); + webllm.prebuiltAppConfig.model_list.forEach((model, index) => { + let option = new Option(model.model_id, model.model_id); + modelSelect.add(option); }); - window.engine = engine; - const chatUI = new ChatUI(engine); - chatUI.setupEventListeners(); - clearProgress(); - } catch (error) { - Boostlet.hint(`Error: ${error.message}`, 8000); + modelSelect.addEventListener("change", (event) => { + if (event.target.value !== "Select Model..." && event.target.value !== "") { + this.initializeChat(event.target.value); + } + }); } -} -function showProgress(text) { - let progressBox = document.getElementById("progress-box"); - if (!progressBox) { - progressBox = document.createElement("div"); - progressBox.id = "progress-box"; - progressBox.style = - "position: fixed; top: 50%; left: 50%; transform: translate(-50%, -50%); background: rgba(0, 0, 0, 0.75); color: white; padding: 20px; border-radius: 10px; z-index: 1500;"; - document.body.appendChild(progressBox); - } - progressBox.innerText = text; -} + async initializeChat(selectedModel) { + try { + const initProgressCallback = (report) => { + this.showProgress(report.text); + }; + this.engine = await webllm.CreateEngine(selectedModel, { + initProgressCallback, + useWebWorker: true, + }); -function clearProgress() { - const progressBox = document.getElementById("progress-box"); - if (progressBox) { - document.body.removeChild(progressBox); - } -} + window.engine = this.engine; -class ChatUI { - constructor(engine) { - this.engine = engine; - this.uiChat = document.getElementById("chatui-chat"); - this.uiInput = document.getElementById("chatui-input"); - this.uiSendBtn = document.getElementById("chatui-send-btn"); - this.uiResetBtn = document.getElementById("chatui-reset-btn"); + this.setupEventListeners(); + this.clearProgress(); + } catch (error) { + Boostlet.hint(`Error: ${error.message}`, 8000); + } } setupEventListeners() { @@ -237,8 +228,18 @@ class ChatUI { this.processChatMessage(message); } - resetChat() { - this.uiChat.innerHTML = ""; + async resetChat() { + try { + if (this.engine) { + this.engine.interruptGenerate(); + await this.engine.resetChat(); + } + this.uiChat.innerHTML = ""; + document.getElementById("chatui-select").value = "Select Model..."; + Boostlet.hint("Reset successful, please select a model to start the conversation.", 5000); + } catch (error) { + Boostlet.hint(`Error: ${error.message}`, 8000); + } } appendMessage(className, text, role) { @@ -280,4 +281,24 @@ class ChatUI { Boostlet.hint(`Error: ${error.message}`, 8000); } } + + showProgress(text) { + let progressBox = document.getElementById("progress-box"); + if (!progressBox) { + progressBox = document.createElement("div"); + progressBox.id = "progress-box"; + progressBox.style = + "position: fixed; top: 50%; left: 50%; transform: translate(-50%, -50%); background: rgba(0, 0, 0, 0.75); color: white; padding: 20px; border-radius: 10px; z-index: 1500;"; + document.body.appendChild(progressBox); + } + progressBox.innerText = text; + } + + clearProgress() { + const progressBox = document.getElementById("progress-box"); + if (progressBox) { + document.body.removeChild(progressBox); + } + } } +