diff --git a/llamafile/server/flagz.cpp b/llamafile/server/flagz.cpp index 5e6f462e97..2b60751b08 100644 --- a/llamafile/server/flagz.cpp +++ b/llamafile/server/flagz.cpp @@ -33,6 +33,7 @@ Client::flagz() json["no_display_prompt"] = FLAG_no_display_prompt; json["nologo"] = FLAG_nologo; json["temperature"] = FLAG_temperature; + json["top_p"] = FLAG_top_p; json["presence_penalty"] = FLAG_presence_penalty; json["frequency_penalty"] = FLAG_frequency_penalty; if (FLAG_seed == LLAMA_DEFAULT_SEED) { diff --git a/llamafile/server/www/chatbot.css b/llamafile/server/www/chatbot.css index e23fba4dc1..5fc9ced642 100644 --- a/llamafile/server/www/chatbot.css +++ b/llamafile/server/www/chatbot.css @@ -263,6 +263,158 @@ ul li:first-child { display: block; } +.settings-button { + padding: 0.75rem 1rem; + background: #6c757d; + color: white; + border: none; + border-radius: 6px; + cursor: pointer; + font-size: 1rem; + transition: background-color 0.2s; +} + +.settings-button:hover { + background: #5c636a; +} + +.settings-modal { + position: fixed; + top: 0; + left: 0; + right: 0; + bottom: 0; + background: rgba(0, 0, 0, 0.5); + z-index: 1000; + display: flex; + align-items: center; + justify-content: center; +} + +.settings-panel { + background: white; + padding: 20px; + border-radius: 8px; + max-width: 500px; + width: 90%; +} + +.settings-header { + display: flex; + justify-content: space-between; + align-items: center; + margin-bottom: 20px; +} + +.settings-header button { + background: none; + border: none; + font-size: 24px; + cursor: pointer; +} + +.setting-item { + margin-bottom: 20px; +} + +.setting-item label { + display: block; + margin-bottom: 8px; + font-weight: bold; +} + +.setting-item input { + width: 100%; + margin-bottom: 8px; +} + +.setting-description { + font-size: 0.9em; + color: #666; + margin-top: 4px; +} + +.setting-item.disabled label, +.setting-item.disabled input, +.setting-item.disabled .setting-description { + opacity: 0.5; + cursor: not-allowed; +} + +.setting-item.disabled input { + pointer-events: none; +} + +.setting-item.disabled { + pointer-events: none; + opacity: 0.8; +} + +.setting-item.disabled input[type="range"] { + background: #ccc; +} + +.setting-item.disabled label, +.setting-item.disabled .setting-description { + color: #999; +} + +/* Styling for penalty range inputs */ +.penalty-range { + -webkit-appearance: none; + appearance: none; + height: 6px; + border-radius: 8px; +} + +/* Chrome track styling */ +.penalty-range::-webkit-slider-runnable-track { + width: 100%; + height: 6px; + border-radius: 8px; + background: linear-gradient(to right, + #ff000088 0%, + #00000022 30%, + #00000022 70%, + #ff000088 100% + ); +} + +/* Firefox track styling */ +.penalty-range::-moz-range-track { + width: 100%; + height: 6px; + border-radius: 8px; + background: linear-gradient(to right, + #ff000088 0%, + #00000022 30%, + #00000022 70%, + #ff000088 100% + ); +} + +/* Chrome thumb styling */ +.penalty-range::-webkit-slider-thumb { + -webkit-appearance: none; + appearance: none; + width: 16px; + height: 16px; + border-radius: 50%; + background: #666; + cursor: pointer; + margin-top: -5px; /* Centers the thumb on the track */ +} + +/* Firefox thumb styling */ +.penalty-range::-moz-range-thumb { + width: 16px; + height: 16px; + border: none; + border-radius: 50%; + background: #666; + cursor: pointer; +} + @media print { html, diff --git a/llamafile/server/www/chatbot.js b/llamafile/server/www/chatbot.js index 19104f2c44..f04296f3d1 100644 --- a/llamafile/server/www/chatbot.js +++ b/llamafile/server/www/chatbot.js @@ -27,6 +27,7 @@ const DEFAULT_FLAGZ = { "frequency_penalty": 0, "presence_penalty": 0, "temperature": 0.8, + "top_p": 0.95, "seed": null }; @@ -34,6 +35,9 @@ const chatMessages = document.getElementById("chat-messages"); const chatInput = document.getElementById("chat-input"); const sendButton = document.getElementById("send-button"); const stopButton = document.getElementById("stop-button"); +const settingsButton = document.getElementById("settings-button"); +const settingsModal = document.getElementById("settings-modal"); +const closeSettings = document.getElementById("close-settings"); let abortController = null; let disableAutoScroll = false; @@ -168,6 +172,7 @@ async function sendMessage() { // update chat history chatHistory.push({ role: "user", content: message }); + const settings = loadSettings(); try { const response = await fetch(API_ENDPOINT, { method: "POST", @@ -178,7 +183,10 @@ async function sendMessage() { body: JSON.stringify({ model: "gpt-3.5-turbo", messages: chatHistory, - temperature: 0.0, + temperature: settings.temperature, + top_p: settings.top_p, + presence_penalty: settings.presence_penalty, + frequency_penalty: settings.frequency_penalty, stream: true }), signal: abortController.signal @@ -361,9 +369,93 @@ function startChat(history) { scrollToBottom(); } +function loadSettings() { + const stored = localStorage.getItem('v1.modelSettings'); + if (stored) { + return JSON.parse(stored); + } + return { + temperature: flagz.temperature, + top_p: flagz.top_p, + presence_penalty: flagz.presence_penalty, + frequency_penalty: flagz.frequency_penalty, + }; +} + +function saveSettings(settings) { + localStorage.setItem('v1.modelSettings', JSON.stringify(settings)); +} + +function formatDoubleWithPlus(x) { + return (x >= 0 ? "+" : "") + x.toFixed(2); +} + +function updateSettingsDisplay(settings) { + document.getElementById("temp-value").textContent = settings.temperature ? settings.temperature.toFixed(2) : "0.00 (deterministic)"; + document.getElementById("top-p-value").textContent = settings.top_p.toFixed(2); + document.getElementById("presence-value").textContent = formatDoubleWithPlus(settings.presence_penalty); + document.getElementById("frequency-value").textContent = formatDoubleWithPlus(settings.frequency_penalty); + document.getElementById("temperature").value = settings.temperature; + document.getElementById("top-p").value = settings.top_p; + document.getElementById("presence-penalty").value = settings.presence_penalty; + document.getElementById("frequency-penalty").value = settings.frequency_penalty; + + // Handle top-p disabling - using a more reliable selector + const topPSettingItem = document.querySelector('.setting-item:has(#top-p)'); + if (settings.temperature === 0) { + topPSettingItem.classList.add('disabled'); + } else { + topPSettingItem.classList.remove('disabled'); + } + + // Update top-p description with percentage + const topPDescription = topPSettingItem.querySelector('.setting-description'); + if (settings.top_p >= 1) { + topPDescription.textContent = "Disabled. All tokens will be considered by the sampler."; + } else if (settings.top_p > .5) { + const percentage = Math.round((1 - settings.top_p) * 100); + topPDescription.textContent = `The bottom ${percentage}% tokens will be ignored by the sampler.`; + } else { + const percentage = Math.round(settings.top_p * 100); + topPDescription.textContent = `Only the top ${percentage}% tokens will be considered by the sampler.`; + } +} + +function setupSettings() { + settingsButton.addEventListener("click", () => { + settingsModal.style.display = "flex"; + updateSettingsDisplay(loadSettings()); + }); + closeSettings.addEventListener("click", () => { + settingsModal.style.display = "none"; + }); + ["temperature", "top-p", "presence-penalty", "frequency-penalty"].forEach(id => { + const element = document.getElementById(id); + element.addEventListener("input", (e) => { + const settings = loadSettings(); + const value = parseFloat(e.target.value); + const key = id.replace(/-/g, '_'); + settings[key] = value; + saveSettings(settings); + updateSettingsDisplay(settings); + }); + }); + settingsModal.addEventListener("mousedown", (e) => { + if (e.target === settingsModal) { + settingsModal.style.display = "none"; + } + }); + document.addEventListener("keydown", (e) => { + if (e.key === "Escape") { + settingsModal.style.display = "none"; + } + }); +} + async function chatbot() { flagz = await fetchFlagz(); updateModelInfo(); + setupSettings(); startChat([{ role: "system", content: getSystemPrompt() }]); sendButton.addEventListener("click", sendMessage); stopButton.addEventListener("click", stopMessage); diff --git a/llamafile/server/www/highlight.js b/llamafile/server/www/highlight.js index 8354d219e9..92f5214968 100644 --- a/llamafile/server/www/highlight.js +++ b/llamafile/server/www/highlight.js @@ -46,7 +46,7 @@ class HighlightDom extends Highlight { feed(s) { for (let i = 0; i < s.length; ++i) { this.text += s[i]; - if (isspace(s[i])) { + if (isspace(s[i]) || this.text.length > 50) { this.flushText(); } } diff --git a/llamafile/server/www/index.html b/llamafile/server/www/index.html index b01c5fb16e..6b6615f3ad 100644 --- a/llamafile/server/www/index.html +++ b/llamafile/server/www/index.html @@ -22,6 +22,36 @@

+ + + + +