Skip to content

Commit bc82424

Browse files
committed
Add settings modal to web ui
1 parent a494bd3 commit bc82424

File tree

5 files changed

+277
-2
lines changed

5 files changed

+277
-2
lines changed

llamafile/server/flagz.cpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ Client::flagz()
3333
json["no_display_prompt"] = FLAG_no_display_prompt;
3434
json["nologo"] = FLAG_nologo;
3535
json["temperature"] = FLAG_temperature;
36+
json["top_p"] = FLAG_top_p;
3637
json["presence_penalty"] = FLAG_presence_penalty;
3738
json["frequency_penalty"] = FLAG_frequency_penalty;
3839
if (FLAG_seed == LLAMA_DEFAULT_SEED) {

llamafile/server/www/chatbot.css

Lines changed: 152 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -263,6 +263,158 @@ ul li:first-child {
263263
display: block;
264264
}
265265

266+
.settings-button {
267+
padding: 0.75rem 1rem;
268+
background: #6c757d;
269+
color: white;
270+
border: none;
271+
border-radius: 6px;
272+
cursor: pointer;
273+
font-size: 1rem;
274+
transition: background-color 0.2s;
275+
}
276+
277+
.settings-button:hover {
278+
background: #5c636a;
279+
}
280+
281+
.settings-modal {
282+
position: fixed;
283+
top: 0;
284+
left: 0;
285+
right: 0;
286+
bottom: 0;
287+
background: rgba(0, 0, 0, 0.5);
288+
z-index: 1000;
289+
display: flex;
290+
align-items: center;
291+
justify-content: center;
292+
}
293+
294+
.settings-panel {
295+
background: white;
296+
padding: 20px;
297+
border-radius: 8px;
298+
max-width: 500px;
299+
width: 90%;
300+
}
301+
302+
.settings-header {
303+
display: flex;
304+
justify-content: space-between;
305+
align-items: center;
306+
margin-bottom: 20px;
307+
}
308+
309+
.settings-header button {
310+
background: none;
311+
border: none;
312+
font-size: 24px;
313+
cursor: pointer;
314+
}
315+
316+
.setting-item {
317+
margin-bottom: 20px;
318+
}
319+
320+
.setting-item label {
321+
display: block;
322+
margin-bottom: 8px;
323+
font-weight: bold;
324+
}
325+
326+
.setting-item input {
327+
width: 100%;
328+
margin-bottom: 8px;
329+
}
330+
331+
.setting-description {
332+
font-size: 0.9em;
333+
color: #666;
334+
margin-top: 4px;
335+
}
336+
337+
.setting-item.disabled label,
338+
.setting-item.disabled input,
339+
.setting-item.disabled .setting-description {
340+
opacity: 0.5;
341+
cursor: not-allowed;
342+
}
343+
344+
.setting-item.disabled input {
345+
pointer-events: none;
346+
}
347+
348+
.setting-item.disabled {
349+
pointer-events: none;
350+
opacity: 0.8;
351+
}
352+
353+
.setting-item.disabled input[type="range"] {
354+
background: #ccc;
355+
}
356+
357+
.setting-item.disabled label,
358+
.setting-item.disabled .setting-description {
359+
color: #999;
360+
}
361+
362+
/* Styling for penalty range inputs */
363+
.penalty-range {
364+
-webkit-appearance: none;
365+
appearance: none;
366+
height: 6px;
367+
border-radius: 8px;
368+
}
369+
370+
/* Chrome track styling */
371+
.penalty-range::-webkit-slider-runnable-track {
372+
width: 100%;
373+
height: 6px;
374+
border-radius: 8px;
375+
background: linear-gradient(to right,
376+
#ff000088 0%,
377+
#00000022 30%,
378+
#00000022 70%,
379+
#ff000088 100%
380+
);
381+
}
382+
383+
/* Firefox track styling */
384+
.penalty-range::-moz-range-track {
385+
width: 100%;
386+
height: 6px;
387+
border-radius: 8px;
388+
background: linear-gradient(to right,
389+
#ff000088 0%,
390+
#00000022 30%,
391+
#00000022 70%,
392+
#ff000088 100%
393+
);
394+
}
395+
396+
/* Chrome thumb styling */
397+
.penalty-range::-webkit-slider-thumb {
398+
-webkit-appearance: none;
399+
appearance: none;
400+
width: 16px;
401+
height: 16px;
402+
border-radius: 50%;
403+
background: #666;
404+
cursor: pointer;
405+
margin-top: -5px; /* Centers the thumb on the track */
406+
}
407+
408+
/* Firefox thumb styling */
409+
.penalty-range::-moz-range-thumb {
410+
width: 16px;
411+
height: 16px;
412+
border: none;
413+
border-radius: 50%;
414+
background: #666;
415+
cursor: pointer;
416+
}
417+
266418
@media print {
267419

268420
html,

llamafile/server/www/chatbot.js

Lines changed: 93 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,13 +27,17 @@ const DEFAULT_FLAGZ = {
2727
"frequency_penalty": 0,
2828
"presence_penalty": 0,
2929
"temperature": 0.8,
30+
"top_p": 0.95,
3031
"seed": null
3132
};
3233

3334
const chatMessages = document.getElementById("chat-messages");
3435
const chatInput = document.getElementById("chat-input");
3536
const sendButton = document.getElementById("send-button");
3637
const stopButton = document.getElementById("stop-button");
38+
const settingsButton = document.getElementById("settings-button");
39+
const settingsModal = document.getElementById("settings-modal");
40+
const closeSettings = document.getElementById("close-settings");
3741

3842
let abortController = null;
3943
let disableAutoScroll = false;
@@ -168,6 +172,7 @@ async function sendMessage() {
168172
// update chat history
169173
chatHistory.push({ role: "user", content: message });
170174

175+
const settings = loadSettings();
171176
try {
172177
const response = await fetch(API_ENDPOINT, {
173178
method: "POST",
@@ -178,7 +183,10 @@ async function sendMessage() {
178183
body: JSON.stringify({
179184
model: "gpt-3.5-turbo",
180185
messages: chatHistory,
181-
temperature: 0.0,
186+
temperature: settings.temperature,
187+
top_p: settings.top_p,
188+
presence_penalty: settings.presence_penalty,
189+
frequency_penalty: settings.frequency_penalty,
182190
stream: true
183191
}),
184192
signal: abortController.signal
@@ -361,9 +369,93 @@ function startChat(history) {
361369
scrollToBottom();
362370
}
363371

372+
function loadSettings() {
373+
const stored = localStorage.getItem('v1.modelSettings');
374+
if (stored) {
375+
return JSON.parse(stored);
376+
}
377+
return {
378+
temperature: flagz.temperature,
379+
top_p: flagz.top_p,
380+
presence_penalty: flagz.presence_penalty,
381+
frequency_penalty: flagz.frequency_penalty,
382+
};
383+
}
384+
385+
function saveSettings(settings) {
386+
localStorage.setItem('v1.modelSettings', JSON.stringify(settings));
387+
}
388+
389+
function formatDoubleWithPlus(x) {
390+
return (x >= 0 ? "+" : "") + x.toFixed(2);
391+
}
392+
393+
function updateSettingsDisplay(settings) {
394+
document.getElementById("temp-value").textContent = settings.temperature ? settings.temperature.toFixed(2) : "0.00 (deterministic)";
395+
document.getElementById("top-p-value").textContent = settings.top_p.toFixed(2);
396+
document.getElementById("presence-value").textContent = formatDoubleWithPlus(settings.presence_penalty);
397+
document.getElementById("frequency-value").textContent = formatDoubleWithPlus(settings.frequency_penalty);
398+
document.getElementById("temperature").value = settings.temperature;
399+
document.getElementById("top-p").value = settings.top_p;
400+
document.getElementById("presence-penalty").value = settings.presence_penalty;
401+
document.getElementById("frequency-penalty").value = settings.frequency_penalty;
402+
403+
// Handle top-p disabling - using a more reliable selector
404+
const topPSettingItem = document.querySelector('.setting-item:has(#top-p)');
405+
if (settings.temperature === 0) {
406+
topPSettingItem.classList.add('disabled');
407+
} else {
408+
topPSettingItem.classList.remove('disabled');
409+
}
410+
411+
// Update top-p description with percentage
412+
const topPDescription = topPSettingItem.querySelector('.setting-description');
413+
if (settings.top_p >= 1) {
414+
topPDescription.textContent = "Disabled. All tokens will be considered by the sampler.";
415+
} else if (settings.top_p > .5) {
416+
const percentage = Math.round((1 - settings.top_p) * 100);
417+
topPDescription.textContent = `The bottom ${percentage}% tokens will be ignored by the sampler.`;
418+
} else {
419+
const percentage = Math.round(settings.top_p * 100);
420+
topPDescription.textContent = `Only the top ${percentage}% tokens will be considered by the sampler.`;
421+
}
422+
}
423+
424+
function setupSettings() {
425+
settingsButton.addEventListener("click", () => {
426+
settingsModal.style.display = "flex";
427+
updateSettingsDisplay(loadSettings());
428+
});
429+
closeSettings.addEventListener("click", () => {
430+
settingsModal.style.display = "none";
431+
});
432+
["temperature", "top-p", "presence-penalty", "frequency-penalty"].forEach(id => {
433+
const element = document.getElementById(id);
434+
element.addEventListener("input", (e) => {
435+
const settings = loadSettings();
436+
const value = parseFloat(e.target.value);
437+
const key = id.replace(/-/g, '_');
438+
settings[key] = value;
439+
saveSettings(settings);
440+
updateSettingsDisplay(settings);
441+
});
442+
});
443+
settingsModal.addEventListener("mousedown", (e) => {
444+
if (e.target === settingsModal) {
445+
settingsModal.style.display = "none";
446+
}
447+
});
448+
document.addEventListener("keydown", (e) => {
449+
if (e.key === "Escape") {
450+
settingsModal.style.display = "none";
451+
}
452+
});
453+
}
454+
364455
async function chatbot() {
365456
flagz = await fetchFlagz();
366457
updateModelInfo();
458+
setupSettings();
367459
startChat([{ role: "system", content: getSystemPrompt() }]);
368460
sendButton.addEventListener("click", sendMessage);
369461
stopButton.addEventListener("click", stopMessage);

llamafile/server/www/highlight.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@ class HighlightDom extends Highlight {
4646
feed(s) {
4747
for (let i = 0; i < s.length; ++i) {
4848
this.text += s[i];
49-
if (isspace(s[i])) {
49+
if (isspace(s[i]) || this.text.length > 50) {
5050
this.flushText();
5151
}
5252
}

llamafile/server/www/index.html

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,36 @@ <h1>
2222
<textarea class="chat-input" id="chat-input" placeholder="Type your message..." rows="1" autocomplete="off"></textarea>
2323
<button class="send-button" id="send-button">Send</button>
2424
<button class="stop-button" id="stop-button" style="display:none">Stop</button>
25+
<button class="settings-button" id="settings-button" title="Settings">⚙️</button>
26+
</div>
27+
</div>
28+
29+
<div id="settings-modal" class="settings-modal" style="display:none">
30+
<div class="settings-panel">
31+
<div class="settings-header">
32+
<h2>Model Settings</h2>
33+
<button id="close-settings">×</button>
34+
</div>
35+
<div class="setting-item">
36+
<label>temperature = <span id="temp-value">0.8</span></label>
37+
<input type="range" id="temperature" min="0" max="2" step="0.01" value="0.8">
38+
<p class="setting-description">How much randomness to use during sampling. Higher values improve improvisation, and lower values result in determinism.</p>
39+
</div>
40+
<div class="setting-item">
41+
<label>top_p = <span id="top-p-value">0.95</span></label>
42+
<input type="range" id="top-p" min="0.01" max="1" step="0.01" value="0.95">
43+
<p class="setting-description">Controls diversity via nucleus sampling. Reduces the probability mass from which to sample.</p>
44+
</div>
45+
<div class="setting-item">
46+
<label>presence_penalty = <span id="presence-value">0</span></label>
47+
<input type="range" id="presence-penalty" class="penalty-range" min="-2" max="2" step="0.1" value="0">
48+
<p class="setting-description">Positive values discourage repetition; negative values encourage it.</p>
49+
</div>
50+
<div class="setting-item">
51+
<label>frequency_penalty = <span id="frequency-value">0</span></label>
52+
<input type="range" id="frequency-penalty" class="penalty-range" min="-2" max="2" step="0.1" value="0">
53+
<p class="setting-description">Penalizes new tokens based on their frequency in the text so far.</p>
54+
</div>
2555
</div>
2656
</div>
2757
<div id="bottom"></div>

0 commit comments

Comments
 (0)