const { observeDom, ui: { injectCss, Button, openModal, ModalRoot, ModalHeader, ModalBody, ModalFooter, ModalSizes, Text, TextBox, ReactiveRoot, TextArea, ButtonLooks, }, plugin: { store }, util: { getFiber }, } = shelter; let popupButton = null; let unobserve = null; const getMessageHistory = () => { const messageElements = document.querySelectorAll('div[class^="message-"]'); const messages = [...messageElements].map((message) => ({ username: message.querySelector("h3 > span > span")?.textContent, message: message.querySelector("div > div > div").textContent, })); return messages.reduce((acc, message) => { if (message.username) { acc.push(message); } else { acc[acc.length - 1].message += `\n${message.message}`; } return acc; }, []); }; const loadingIndicator = () => ( ); // Credits to yellowsink for this messagebar stuff // https://github.com/yellowsink const appendTextToMessagebar = (text) => { const elem = document.querySelector('[class*="slateContainer"]'); const fiber = getFiber(elem); const editor = fiber.child.pendingProps.editor; editor.insertText(text); }; export function onLoad() { injectCss(` .label-spacing { margin-bottom: .125rem; } .mb-2 { margin-bottom: .5rem; } .pr-2 { padding-right: .5rem; }`); let closeModal = null; const openGenerationModal = async () => { let savedModel = store.model || ""; let model = savedModel; let prompt = ""; closeModal = openModal((p) => ( closeModal()}>Generate Response Model { model = e; }} /> Prompt { prompt = e; }} /> { closeModal(); const myUsername = document.querySelector( "[class^=nameTag] > div" ).textContent; store.model = model; const messages = [ ...getMessageHistory() .slice(-7) .map((message) => ({ role: "user", content: `${message.username}: ${message.message}`, })), { role: "system", content: `generate a response as "${myUsername}" according to the prompt: "${prompt}"`, }, ]; // add loading indicator const messageBar = document.querySelector( '[class*="slateContainer"]' ); // get absolute position of messagebar const { x, y } = messageBar.getBoundingClientRect(); const loadingIndicatorElem = document.body.appendChild( loadingIndicator() ); loadingIndicatorElem.style.position = "absolute"; loadingIndicatorElem.style.left = `${x}px`; loadingIndicatorElem.style.top = `${y + 12}px`; // Unsloth Studio API const apiUrl = `${store.unslothUrl}/v1/chat/completions`; const apiHeaders = { "Content-Type": "application/json", }; if (store.unslothKey) { apiHeaders["Authorization"] = `Bearer ${store.unslothKey}`; } const apiBody = { model, messages, stream: false, }; fetch(apiUrl, { method: "POST", headers: apiHeaders, body: JSON.stringify(apiBody), }) .then((res) => res.json()) .then((res) => { const response = res.choices[0].message.content; appendTextToMessagebar( response .replace(/^(?=.{0,49}:)([\w\s\-]+?[^ ]):/, "") .trim() ); loadingIndicatorElem.remove(); }) .catch((err) => { console.error("API Error:", err); loadingIndicatorElem.remove(); alert(`Error: ${err.message || "Failed to generate response"}`); }); }} > Generate )); }; unobserve = observeDom( '[class^="channelTextArea"] [class^="buttons"]', (node) => { if (document.querySelector("#generate-button")) return; const secondLastChild = node.lastChild.previousSibling; popupButton = node.insertBefore( , node.firstChild ); } ); } export function onUnload() { unobserve(); popupButton?.remove(); } export { default as settings } from "./settings";