const { plugin: { store, scoped }, ui: { openModal, Button, ButtonColors, ButtonLooks, TextBox, TextArea, ModalRoot, ModalHeader, ModalBody, ModalFooter, Header, HeaderTags, Text, TextTags, Divider, showToast, ToastColors, injectCss, }, } = shelter; const UNSLOTH_DEFAULT_URL = "http://localhost:8000"; const UNSLOTH_MODEL = "default"; // ── Default settings ──────────────────────────────────────────── store.apiUrl ??= UNSLOTH_DEFAULT_URL; store.apiKey ??= ""; store.model ??= UNSLOTH_MODEL; store.maxTokens ??= 1024; store.temperature ??= 0.7; // ── Injected CSS for the button ───────────────────────────────── const removeCss = injectCss(` [data-unsloth-chat-btn] { display: flex !important; align-items: center !important; justify-content: center !important; width: 32px !important; height: 32px !important; min-width: 32px !important; border-radius: 8px !important; cursor: pointer !important; font-size: 16px !important; font-weight: 700 !important; color: var(--interactive-normal) !important; background: transparent !important; border: none !important; padding: 0 !important; transition: color 0.15s ease, background 0.15s ease !important; line-height: 1 !important; margin: 0 2px !important; order: 10 !important; } [data-unsloth-chat-btn]:hover { color: var(--interactive-hover) !important; background: var(--background-modifier-hover) !important; } [data-unsloth-chat-btn] svg { width: 20px !important; height: 20px !important; } `); scoped.onDispose(() => removeCss()); // ── Helper: find and fill the message input ───────────────────── function setMessageInput(text) { // Find the Discord chat textarea const textarea = document.querySelector( 'main [class*="channelTextArea"] textarea, ' + 'main [class*="channelTextArea"] [role="textbox"], ' + '[class*="chat"] [class*="channelTextArea"] textarea, ' + '[class*="chat"] [class*="channelTextArea"] [role="textbox"]' ); if (!textarea) return false; // The native textarea (or contenteditable div) inside const nativeInput = textarea.tagName === "TEXTAREA" ? textarea : textarea.querySelector("textarea"); if (!nativeInput) return false; // Set the native value nativeInput.focus(); nativeInput.value = text; // Dispatch an input event so React picks up the change const nativeInputEv = new Event("input", { bubbles: true, cancelable: true }); nativeInput.dispatchEvent(nativeInputEv); // Also dispatch on the outer element if it's a contenteditable if (textarea !== nativeInput && textarea.isContentEditable) { textarea.textContent = text; textarea.dispatchEvent(new Event("input", { bubbles: true })); } return true; } // ── API call ──────────────────────────────────────────────────── async function callUnsloth(prompt) { const url = `${store.apiUrl.replace(/\/+$/, "")}/v1/chat/completions`; const headers = { "Content-Type": "application/json" }; if (store.apiKey) { headers["Authorization"] = `Bearer ${store.apiKey}`; } const body = { model: store.model, messages: [{ role: "user", content: prompt }], stream: false, max_tokens: store.maxTokens, temperature: store.temperature, }; const res = await fetch(url, { method: "POST", headers, body: JSON.stringify(body), }); if (!res.ok) { let detail = `API returned ${res.status}`; try { const err = await res.json(); detail = err.error?.message || err.detail || detail; } catch {} throw new Error(detail); } const data = await res.json(); const text = data?.choices?.[0]?.message?.content || ""; return text; } // ── The prompt modal ──────────────────────────────────────────── function PromptModal(props) { const [prompt, setPrompt] = shelter.solid.createSignal(""); const [loading, setLoading] = shelter.solid.createSignal(false); const [error, setError] = shelter.solid.createSignal(""); let inputRef = undefined; let confirmRef = undefined; shelter.solid.onMount(() => { if (inputRef) inputRef.focus(); }); async function handleSubmit() { const p = prompt().trim(); if (!p) return; setLoading(true); setError(""); try { const result = await callUnsloth(p); if (result) { const ok = setMessageInput(result); if (ok) { showToast({ title: "Unsloth Chat", content: "Response inserted into message input!", color: ToastColors.SUCCESS, }); } else { showToast({ title: "Unsloth Chat", content: "Could not find message input. Response copied to clipboard.", color: ToastColors.WARNING, }); try { await navigator.clipboard.writeText(result); } catch {} } } else { setError("The model returned an empty response."); } props.close(); } catch (e) { setError(e.message || "An error occurred"); } finally { setLoading(false); } } function handleKeyDown(e) { if (e.key === "Enter" && !e.shiftKey) { e.preventDefault(); handleSubmit(); } if (e.key === "Escape") { props.close(); } } return ( Unsloth Studio
Prompt