Blame Raw
Cohee · e3f41666 · · 445 lines (17.3 KB)
1 contributor
1import {
2 main_api,
3 saveSettingsDebounced,
4} from '../script.js';
5//import { BIAS_CACHE, displayLogitBias, getLogitBiasListResult } from './logit-bias.js';
6//import { getEventSourceStream } from './sse-stream.js';
7//import { getSortableDelay, onlyUnique } from './utils.js';
8//import { getCfgPrompt } from './cfg-scale.js';
9import { setting_names as TGsamplerNames, showTGSamplerControls, textgenerationwebui_settings } from './textgen-settings.js';
10import { renderTemplateAsync } from './templates.js';
11import { Popup, POPUP_TYPE } from './popup.js';
12import { localforage } from '../lib.js';
13
14const forcedOnColoring = 'color: #89db35;';
15const forcedOffColoring = 'color: #e84f62;';
16const SELECT_SAMPLER = {
17 DATA: 'selectsampler',
18 SHOWN: 'shown',
19 HIDDEN: 'hidden',
20};
21
22const textGenObjectStore = localforage.createInstance({ name: 'SillyTavern_TextCompletions' });
23let selectedSamplers = {};
24
25// Goal 1: show popup with all samplers for active API
26async function showSamplerSelectPopup() {
27 const html = $(document.createElement('div'));
28 html.attr('id', 'sampler_view_list')
29 .addClass('flex-container flexFlowColumn');
30 html.append(await renderTemplateAsync('samplerSelector'));
31
32 const listContainer = $('<div id="apiSamplersList" class="flex-container flexNoGap"></div>');
33 const APISamplers = await listSamplers(main_api);
34 listContainer.append(APISamplers.toString());
35 html.append(listContainer);
36
37 const showPromise = new Popup(html, POPUP_TYPE.TEXT, null, { wide: true, large: true, allowVerticalScrolling: true }).show();
38
39 setSamplerListListeners();
40
41 $('#resetSelectedSamplers').off('click').on('click', async function () {
42 console.log('saw sampler select reset click');
43
44 if (main_api === 'textgenerationwebui') {
45 $('#prioritizeManuallySelectedSamplers').toggleClass('toggleEnabled', false);
46 await resetApiSelectedSamplers(null, true);
47 }
48
49 await validateDisabledSamplers(true);
50 });
51
52 if (main_api === 'textgenerationwebui') {
53 $('#prioritizeManuallySelectedSamplers').show();
54 $('#prioritizeManuallySelectedSamplers').toggleClass('toggleEnabled', isSamplerManualPriorityEnabled());
55 $('#prioritizeManuallySelectedSamplers').off('click').on('click', function () {
56 $(this).toggleClass('toggleEnabled');
57
58 const isActive = $(this).hasClass('toggleEnabled');
59
60 toggleSamplerManualPriority(isActive);
61 });
62 } else {
63 $('#prioritizeManuallySelectedSamplers').hide();
64 $('#prioritizeManuallySelectedSamplers').off('click');
65 }
66
67 await showPromise;
68 if (main_api === 'textgenerationwebui') await saveApiSelectedSamplers();
69}
70
71function getRelatedDOMElement(samplerName) {
72 let relatedDOMElement = $(`#${samplerName}_${main_api}`).parent();
73 let targetDisplayType = 'flex';
74 let displayname;
75
76 if (samplerName === 'json_schema') {
77 relatedDOMElement = $('#json_schema_block');
78 targetDisplayType = 'block';
79 displayname = 'JSON Schema Block';
80 }
81
82 if (samplerName === 'grammar_string') {
83 relatedDOMElement = $('#grammar_block_ooba');
84 targetDisplayType = 'block';
85 displayname = 'Grammar Block';
86 }
87
88 if (samplerName === 'guidance_scale') {
89 relatedDOMElement = $('#cfg_block_ooba');
90 targetDisplayType = 'block';
91 displayname = 'CFG Block';
92 }
93
94 if (samplerName === 'mirostat_mode') {
95 relatedDOMElement = $('#mirostat_block_ooba');
96 targetDisplayType = 'block';
97 displayname = 'Mirostat Block';
98 }
99
100 if (samplerName === 'dry_multiplier') {
101 relatedDOMElement = $('#dryBlock');
102 targetDisplayType = 'block';
103 displayname = 'DRY Rep Pen Block';
104 }
105
106 if (samplerName === 'xtc_probability') {
107 relatedDOMElement = $('#xtc_block');
108 targetDisplayType = 'block';
109 displayname = 'XTC Block';
110 }
111
112 if (samplerName === 'dynatemp') {
113 relatedDOMElement = $('#dynatemp_block_ooba');
114 targetDisplayType = 'block';
115 displayname = 'DynaTemp Block';
116 }
117
118 if (samplerName === 'banned_tokens') {
119 relatedDOMElement = $('#banned_tokens_block_ooba');
120 targetDisplayType = 'block';
121 }
122
123 if (samplerName === 'sampler_order') { //this is for kcpp sampler order
124 relatedDOMElement = $('#sampler_order_block_kcpp');
125 displayname = 'KCPP Sampler Order Block';
126 }
127
128 if (samplerName === 'samplers') { //this is for lcpp sampler order
129 relatedDOMElement = $('#sampler_order_block_lcpp');
130 displayname = 'LCPP Sampler Order Block';
131 }
132
133 if (samplerName === 'sampler_priority') { //this is for ooba's sampler priority
134 relatedDOMElement = $('#sampler_priority_block_ooba');
135 displayname = 'Ooba Sampler Priority Block';
136 }
137
138 if (samplerName === 'samplers_priorities') { //this is for aphrodite's sampler priority
139 relatedDOMElement = $('#sampler_priority_block_aphrodite');
140 displayname = 'Aphrodite Sampler Priority Block';
141 }
142
143 if (samplerName === 'penalty_alpha') { //contrastive search only has one sampler, does it need its own block?
144 relatedDOMElement = $('#contrastiveSearchBlock');
145 displayname = 'Contrast Search Block';
146 }
147
148 if (samplerName === 'num_beams') { // num_beams is the killswitch for Beam Search
149 relatedDOMElement = $('#beamSearchBlock');
150 targetDisplayType = 'block';
151 displayname = 'Beam Search Block';
152 }
153
154 if (samplerName === 'smoothing_factor') { // num_beams is the killswitch for Beam Search
155 relatedDOMElement = $('#smoothingBlock');
156 targetDisplayType = 'block';
157 displayname = 'Smoothing Block';
158 }
159
160 return { relatedDOMElement, targetDisplayType, displayname };
161}
162
163function setSamplerListListeners() {
164 // Goal 2: hide unchecked samplers from DOM
165 let listContainer = $('#apiSamplersList');
166 listContainer.find('input').off('change').on('change', async function () {
167 const samplerName = this.name.replace('_checkbox', '');
168 const { relatedDOMElement, targetDisplayType } = getRelatedDOMElement(samplerName);
169
170 // Get the current state of the custom data attribute
171 const previousState = relatedDOMElement.data(SELECT_SAMPLER.DATA);
172 const isChecked = $(this).prop('checked');
173 const popupInputLabel = $(this).parent().find('.sampler_name');
174
175 if (isChecked === false) {
176 if (previousState === SELECT_SAMPLER.SHOWN) {
177 console.log('saw previously custom shown sampler => new state:', isChecked, samplerName);
178 relatedDOMElement.removeData(SELECT_SAMPLER.DATA);
179 popupInputLabel.removeAttr('style');
180 } else {
181 console.log('saw previous untouched sampler => new state:', isChecked, samplerName);
182 relatedDOMElement.data(SELECT_SAMPLER.DATA, SELECT_SAMPLER.HIDDEN);
183 popupInputLabel.attr('style', forcedOffColoring);
184 }
185 } else {
186 if (previousState === SELECT_SAMPLER.HIDDEN) {
187 console.log('saw previously custom hidden sampler => new state:', isChecked, samplerName);
188 relatedDOMElement.removeData(SELECT_SAMPLER.DATA);
189 popupInputLabel.removeAttr('style');
190 } else {
191 console.log('saw previous untouched sampler => new state:', isChecked, samplerName);
192 relatedDOMElement.data(SELECT_SAMPLER.DATA, SELECT_SAMPLER.SHOWN);
193 popupInputLabel.attr('style', forcedOnColoring);
194 }
195 }
196
197 await saveSettingsDebounced();
198
199 const shouldDisplay = isChecked ? targetDisplayType : 'none';
200 relatedDOMElement.css('display', shouldDisplay);
201
202 if (main_api === 'textgenerationwebui') setApiSamplersState(samplerName, shouldDisplay !== 'none');
203
204 console.log(samplerName, relatedDOMElement.data(SELECT_SAMPLER.DATA), shouldDisplay);
205 });
206}
207
208function isElementVisibleInDOM(element) {
209 while (element && element !== document.body) {
210 if (window.getComputedStyle(element).display === 'none') {
211 return false;
212 }
213 element = element.parentElement;
214 }
215 return true;
216}
217
218
219async function listSamplers(main_api, arrayOnly = false) {
220 let availableSamplers;
221 if (main_api === 'textgenerationwebui') {
222 availableSamplers = TGsamplerNames;
223 const valuesToRemove = new Set(['streaming', 'bypass_status_check', 'custom_model', 'generic_model', 'openrouter_allow_fallbacks', 'legacy_api', 'extensions']);
224 availableSamplers = availableSamplers.filter(sampler => !valuesToRemove.has(sampler));
225 availableSamplers.sort();
226 }
227
228 if (arrayOnly) {
229 console.debug('returning full samplers array');
230 return availableSamplers;
231 }
232
233 const samplersActivatedManually = (main_api === 'textgenerationwebui') ? getActiveManualApiSamplers() : [];
234 const prioritizeManualSamplerSelect = (main_api === 'textgenerationwebui') ? isSamplerManualPriorityEnabled() : false;
235
236 const samplersListHTML = availableSamplers.reduce((html, sampler) => {
237 let customColor;
238 let { relatedDOMElement, displayname } = getRelatedDOMElement(sampler);
239
240 const isManuallyActivated = samplersActivatedManually.includes(sampler);
241 const displayModified = relatedDOMElement.data(SELECT_SAMPLER.DATA);
242 const isInDefaultState = !displayModified;
243
244 const shouldBeChecked = () => {
245 let finalState = isElementVisibleInDOM(relatedDOMElement[0]);
246
247 if (prioritizeManualSamplerSelect) {
248 finalState = isManuallyActivated;
249 } else if (!isInDefaultState) {
250 finalState = displayModified === SELECT_SAMPLER.SHOWN;
251 customColor = finalState ? forcedOnColoring : forcedOffColoring;
252 }
253
254 return finalState;
255 };
256
257 console.log(sampler, relatedDOMElement.prop('id'), isInDefaultState, shouldBeChecked());
258
259 if (displayname === undefined) displayname = sampler;
260 if (main_api === 'textgenerationwebui') setApiSamplersState(sampler, shouldBeChecked());
261
262 return html + `
263 <label class="sampler_view_list_item wide50p flex-container">
264 <input type="checkbox" name="${sampler}_checkbox" ${shouldBeChecked() ? 'checked' : ''}>
265 <small class="sampler_name" style="${customColor}">${displayname}</small>
266 </label>`;
267 }, '');
268
269 return samplersListHTML;
270}
271
272// Goal 3: make "sampler is hidden/disabled" status persistent (save settings)
273// this runs on initial getSettings as well as after API changes
274
275export async function validateDisabledSamplers(redraw = false) {
276 const APISamplers = await listSamplers(main_api, true);
277
278 if (!Array.isArray(APISamplers)) {
279 return;
280 }
281
282 const samplersActivatedManually = (main_api === 'textgenerationwebui') ? getActiveManualApiSamplers() : [];
283 const prioritizeManualSamplerSelect = (main_api === 'textgenerationwebui') ? isSamplerManualPriorityEnabled() : false;
284
285 for (const sampler of APISamplers) {
286 const { relatedDOMElement, targetDisplayType } = getRelatedDOMElement(sampler);
287
288 if (prioritizeManualSamplerSelect) {
289 const isManuallyActivated = samplersActivatedManually.includes(sampler);
290 relatedDOMElement.css('display', isManuallyActivated ? targetDisplayType : 'none');
291 } else {
292 const selectSamplerData = relatedDOMElement.data(SELECT_SAMPLER.DATA);
293 relatedDOMElement.css('display', selectSamplerData === SELECT_SAMPLER.SHOWN ? targetDisplayType : 'none');
294 }
295
296 relatedDOMElement.removeData(SELECT_SAMPLER.DATA);
297 }
298
299 if (!prioritizeManualSamplerSelect && main_api === 'textgenerationwebui') {
300 showTGSamplerControls();
301 }
302
303 if (redraw) {
304 let samplersHTML = await listSamplers(main_api);
305 $('#apiSamplersList').empty().append(samplersHTML.toString());
306 setSamplerListListeners();
307 }
308
309 await saveSettingsDebounced();
310}
311
312/**
313 * Initializes the configuration object for manually selected samplers.
314 * @returns void
315 */
316export async function loadApiSelectedSamplers() {
317 try {
318 console.debug('Text Completions: loading selected samplers');
319 selectedSamplers = await textGenObjectStore.getItem('selectedSamplers') || {};
320 } catch (error) {
321 console.log('Text Completions: unable to load selected samplers, using default samplers', error);
322 selectedSamplers = {};
323 }
324}
325
326/**
327 * Synchronizes the local forage instance with the selected samplers configuration object.
328 * @returns void
329 */
330export async function saveApiSelectedSamplers() {
331 try {
332 console.debug('Text Completions: saving selected samplers');
333 await textGenObjectStore.setItem('selectedSamplers', selectedSamplers);
334 } catch (error) {
335 console.log('Text Completions: unable to save selected samplers', error);
336 }
337}
338
339/**
340 * Resets the selected samplers configuration object from the local forage instance.
341 * @param {string?} tcApiType Name of the target API Type - It picks the currently active TC API type name by default
342 * @param {boolean} silent Suppresses the toastr message confirming that the data was deleted.
343 * @returns void
344 */
345export async function resetApiSelectedSamplers(tcApiType = '', silent = false) {
346 try {
347 if (!textgenerationwebui_settings?.type && !tcApiType) return;
348 if (!tcApiType) tcApiType = textgenerationwebui_settings.type;
349 if (!selectedSamplers[tcApiType]) return;
350
351 console.debug('Text Completions: resetting selected samplers');
352 delete selectedSamplers[tcApiType];
353 await saveApiSelectedSamplers();
354 if (!silent) toastr.success('Selected samplers cleared.');
355 } catch (error) {
356 console.log('Text Completions: unable to reset selected preset samplers', error);
357 }
358}
359
360/**
361 * Saves the visibility state for selected samplers into the configuration object.
362 * @param {string} samplerName Target sampler key name
363 * @param {string|boolean} state Visibility state of the target sampler
364 * @param {string?} tcApiType Name of the target API Type - It picks the currently active TC API type name by default
365 * @returns void
366 */
367export function setApiSamplersState(samplerName, state, tcApiType = '') {
368 if (!textgenerationwebui_settings?.type && !tcApiType) return;
369 if (!tcApiType) tcApiType = textgenerationwebui_settings.type;
370 if (!selectedSamplers[tcApiType]) selectedSamplers[tcApiType] = {};
371
372 const presetSamplers = selectedSamplers[tcApiType];
373 presetSamplers[samplerName] = String(state) === 'true';
374}
375
376/**
377 * Returns the local forage object belonging to the active/selected TC API Type
378 * @param {string?} tcApiType Name of the target API Type - It picks the currently active TC API type name by default
379 * @returns {object} Full localforage object with manual selections
380 */
381export function getAllManualApiSamplers(tcApiType = '') {
382 if (!textgenerationwebui_settings?.type && !tcApiType) return {};
383 if (!tcApiType) tcApiType = textgenerationwebui_settings.type;
384 if (!selectedSamplers[tcApiType]) selectedSamplers[tcApiType] = {};
385
386 return selectedSamplers[tcApiType];
387}
388
389/**
390 * Returns the key names of all the manually activated API Type samplers.
391 * @param {string?} tcApiType Name of the target API Type - It picks the currently active TC API type name by default
392 * @returns {string[]} Array of sampler key names
393 */
394export function getActiveManualApiSamplers(tcApiType = '') {
395 if (!textgenerationwebui_settings?.type && !tcApiType) return [];
396 if (!tcApiType) tcApiType = textgenerationwebui_settings.type;
397 if (!selectedSamplers[tcApiType]) selectedSamplers[tcApiType] = {};
398
399 try {
400 const presetSamplers = Object.entries(selectedSamplers[tcApiType]);
401
402 return presetSamplers
403 .filter(([key, val]) => val === true && key !== 'st_manual_priority')
404 .map(([key, val]) => key);
405 } catch (error) {
406 console.log('Text Completions: unable to fetch active preset samplers', error);
407 return [];
408 }
409}
410
411/**
412 * @param {string|boolean} state Target state of the feature
413 * @param {string?} tcApiType Name of the target API Type - It picks the currently active TC API type name by default
414 * @returns void
415 */
416export function toggleSamplerManualPriority(state = false, tcApiType = '') {
417 if (!textgenerationwebui_settings?.type && !tcApiType) return;
418 if (!tcApiType) tcApiType = textgenerationwebui_settings.type;
419 if (!selectedSamplers[tcApiType]) selectedSamplers[tcApiType] = {};
420
421 const presetSamplers = selectedSamplers[tcApiType];
422 presetSamplers.st_manual_priority = String(state) === 'true';
423}
424
425/**
426 * @param {string?} tcApiType Name of the target API Type - It picks the currently active TC API type name by default
427 * @returns {boolean}
428 */
429export function isSamplerManualPriorityEnabled(tcApiType = '') {
430 if (!textgenerationwebui_settings?.type && !tcApiType) return false;
431 if (!tcApiType) tcApiType = textgenerationwebui_settings.type;
432 if (!selectedSamplers[tcApiType]) selectedSamplers[tcApiType] = {};
433
434 return selectedSamplers[tcApiType]?.st_manual_priority ?? false;
435}
436
437export async function initCustomSelectedSamplers() {
438 await saveSettingsDebounced();
439 $('#samplerSelectButton').off('click').on('click', showSamplerSelectPopup);
440}
441
442// Goal 4: filter hidden samplers from API output
443
444// Goal 5: allow addition of custom samplers to be displayed
445// Goal 6: send custom sampler values into prompt