Spaces:
Paused
Paused
| import { | |
| getRequestHeaders, | |
| saveSettingsDebounced, | |
| getStoppingStrings, | |
| substituteParams, | |
| api_server, | |
| } from '../script.js'; | |
| import { | |
| power_user, | |
| } from './power-user.js'; | |
| import { getEventSourceStream } from './sse-stream.js'; | |
| import { getSortableDelay } from './utils.js'; | |
| export const kai_settings = { | |
| temp: 1, | |
| rep_pen: 1, | |
| rep_pen_range: 0, | |
| top_p: 1, | |
| min_p: 0, | |
| top_a: 1, | |
| top_k: 0, | |
| typical: 1, | |
| tfs: 1, | |
| rep_pen_slope: 0.9, | |
| streaming_kobold: false, | |
| sampler_order: [0, 1, 2, 3, 4, 5, 6], | |
| mirostat: 0, | |
| mirostat_tau: 5.0, | |
| mirostat_eta: 0.1, | |
| use_default_badwordsids: false, | |
| grammar: '', | |
| seed: -1, | |
| }; | |
| /** | |
| * Stable version of KoboldAI has a nasty payload validation. | |
| * It will reject any payload that has a key that is not in the whitelist. | |
| * @typedef {Object.<string, boolean>} kai_flags | |
| */ | |
| export const kai_flags = { | |
| can_use_tokenization: false, | |
| can_use_stop_sequence: false, | |
| can_use_streaming: false, | |
| can_use_default_badwordsids: false, | |
| can_use_mirostat: false, | |
| can_use_grammar: false, | |
| can_use_min_p: false, | |
| }; | |
| const defaultValues = Object.freeze(structuredClone(kai_settings)); | |
| const MIN_STOP_SEQUENCE_VERSION = '1.2.2'; | |
| const MIN_UNBAN_VERSION = '1.2.4'; | |
| const MIN_STREAMING_KCPPVERSION = '1.30'; | |
| const MIN_TOKENIZATION_KCPPVERSION = '1.41'; | |
| const MIN_MIROSTAT_KCPPVERSION = '1.35'; | |
| const MIN_GRAMMAR_KCPPVERSION = '1.44'; | |
| const MIN_MIN_P_KCPPVERSION = '1.48'; | |
| const KOBOLDCPP_ORDER = [6, 0, 1, 3, 4, 2, 5]; | |
| export function formatKoboldUrl(value) { | |
| try { | |
| const url = new URL(value); | |
| if (!power_user.relaxed_api_urls) { | |
| url.pathname = '/api'; | |
| } | |
| return url.toString(); | |
| } catch { | |
| // Just using URL as a validation check | |
| } | |
| return null; | |
| } | |
| export function loadKoboldSettings(preset) { | |
| for (const name of Object.keys(kai_settings)) { | |
| const value = preset[name] ?? defaultValues[name]; | |
| const slider = sliders.find(x => x.name === name); | |
| if (!slider) { | |
| continue; | |
| } | |
| const formattedValue = slider.format(value); | |
| slider.setValue(value); | |
| $(slider.sliderId).val(value); | |
| $(slider.counterId).val(formattedValue); | |
| } | |
| if (Object.hasOwn(preset, 'streaming_kobold')) { | |
| kai_settings.streaming_kobold = preset.streaming_kobold; | |
| $('#streaming_kobold').prop('checked', kai_settings.streaming_kobold); | |
| } | |
| if (Object.hasOwn(preset, 'use_default_badwordsids')) { | |
| kai_settings.use_default_badwordsids = preset.use_default_badwordsids; | |
| $('#use_default_badwordsids').prop('checked', kai_settings.use_default_badwordsids); | |
| } | |
| } | |
| /** | |
| * Gets the Kobold generation data. | |
| * @param {string} finalPrompt Final text prompt. | |
| * @param {object} settings Settings preset object. | |
| * @param {number} maxLength Maximum length. | |
| * @param {number} maxContextLength Maximum context length. | |
| * @param {boolean} isHorde True if the generation is for a horde, false otherwise. | |
| * @param {string} type Generation type. | |
| * @returns {object} Kobold generation data. | |
| */ | |
| export function getKoboldGenerationData(finalPrompt, settings, maxLength, maxContextLength, isHorde, type) { | |
| const isImpersonate = type === 'impersonate'; | |
| const isContinue = type === 'continue'; | |
| const sampler_order = kai_settings.sampler_order || settings.sampler_order; | |
| let generate_data = { | |
| prompt: finalPrompt, | |
| gui_settings: false, | |
| sampler_order: sampler_order, | |
| max_context_length: Number(maxContextLength), | |
| max_length: maxLength, | |
| rep_pen: Number(kai_settings.rep_pen), | |
| rep_pen_range: Number(kai_settings.rep_pen_range), | |
| rep_pen_slope: kai_settings.rep_pen_slope, | |
| temperature: Number(kai_settings.temp), | |
| tfs: kai_settings.tfs, | |
| top_a: kai_settings.top_a, | |
| top_k: kai_settings.top_k, | |
| top_p: kai_settings.top_p, | |
| min_p: (kai_flags.can_use_min_p || isHorde) ? kai_settings.min_p : undefined, | |
| typical: kai_settings.typical, | |
| use_world_info: false, | |
| singleline: false, | |
| stop_sequence: (kai_flags.can_use_stop_sequence || isHorde) ? getStoppingStrings(isImpersonate, isContinue) : undefined, | |
| streaming: kai_settings.streaming_kobold && kai_flags.can_use_streaming && type !== 'quiet', | |
| can_abort: kai_flags.can_use_streaming, | |
| mirostat: (kai_flags.can_use_mirostat || isHorde) ? kai_settings.mirostat : undefined, | |
| mirostat_tau: (kai_flags.can_use_mirostat || isHorde) ? kai_settings.mirostat_tau : undefined, | |
| mirostat_eta: (kai_flags.can_use_mirostat || isHorde) ? kai_settings.mirostat_eta : undefined, | |
| use_default_badwordsids: (kai_flags.can_use_default_badwordsids || isHorde) ? kai_settings.use_default_badwordsids : undefined, | |
| grammar: (kai_flags.can_use_grammar || isHorde) ? substituteParams(kai_settings.grammar) : undefined, | |
| sampler_seed: kai_settings.seed >= 0 ? kai_settings.seed : undefined, | |
| api_server, | |
| }; | |
| return generate_data; | |
| } | |
| function tryParseStreamingError(response, decoded) { | |
| try { | |
| const data = JSON.parse(decoded); | |
| if (!data) { | |
| return; | |
| } | |
| if (data.error) { | |
| toastr.error(data.error.message || response.statusText, 'KoboldAI API'); | |
| throw new Error(data); | |
| } | |
| } | |
| catch { | |
| // No JSON. Do nothing. | |
| } | |
| } | |
| export async function generateKoboldWithStreaming(generate_data, signal) { | |
| const response = await fetch('/api/backends/kobold/generate', { | |
| headers: getRequestHeaders(), | |
| body: JSON.stringify(generate_data), | |
| method: 'POST', | |
| signal: signal, | |
| }); | |
| if (!response.ok) { | |
| tryParseStreamingError(response, await response.text()); | |
| throw new Error(`Got response status ${response.status}`); | |
| } | |
| const eventStream = getEventSourceStream(); | |
| response.body.pipeThrough(eventStream); | |
| const reader = eventStream.readable.getReader(); | |
| return async function* streamData() { | |
| let text = ''; | |
| while (true) { | |
| const { done, value } = await reader.read(); | |
| if (done) return; | |
| const data = JSON.parse(value.data); | |
| if (data?.token) { | |
| text += data.token; | |
| } | |
| yield { text, swipes: [], toolCalls: [], state: {} }; | |
| } | |
| }; | |
| } | |
| const sliders = [ | |
| { | |
| name: 'temp', | |
| sliderId: '#temp', | |
| counterId: '#temp_counter', | |
| format: (val) => Number(val).toFixed(2), | |
| setValue: (val) => { kai_settings.temp = Number(val); }, | |
| }, | |
| { | |
| name: 'rep_pen', | |
| sliderId: '#rep_pen', | |
| counterId: '#rep_pen_counter', | |
| format: (val) => Number(val).toFixed(2), | |
| setValue: (val) => { kai_settings.rep_pen = Number(val); }, | |
| }, | |
| { | |
| name: 'rep_pen_range', | |
| sliderId: '#rep_pen_range', | |
| counterId: '#rep_pen_range_counter', | |
| format: (val) => val, | |
| setValue: (val) => { kai_settings.rep_pen_range = Number(val); }, | |
| }, | |
| { | |
| name: 'top_p', | |
| sliderId: '#top_p', | |
| counterId: '#top_p_counter', | |
| format: (val) => val, | |
| setValue: (val) => { kai_settings.top_p = Number(val); }, | |
| }, | |
| { | |
| name: 'min_p', | |
| sliderId: '#min_p', | |
| counterId: '#min_p_counter', | |
| format: (val) => val, | |
| setValue: (val) => { kai_settings.min_p = Number(val); }, | |
| }, | |
| { | |
| name: 'top_a', | |
| sliderId: '#top_a', | |
| counterId: '#top_a_counter', | |
| format: (val) => val, | |
| setValue: (val) => { kai_settings.top_a = Number(val); }, | |
| }, | |
| { | |
| name: 'top_k', | |
| sliderId: '#top_k', | |
| counterId: '#top_k_counter', | |
| format: (val) => val, | |
| setValue: (val) => { kai_settings.top_k = Number(val); }, | |
| }, | |
| { | |
| name: 'typical', | |
| sliderId: '#typical_p', | |
| counterId: '#typical_p_counter', | |
| format: (val) => val, | |
| setValue: (val) => { kai_settings.typical = Number(val); }, | |
| }, | |
| { | |
| name: 'tfs', | |
| sliderId: '#tfs', | |
| counterId: '#tfs_counter', | |
| format: (val) => val, | |
| setValue: (val) => { kai_settings.tfs = Number(val); }, | |
| }, | |
| { | |
| name: 'rep_pen_slope', | |
| sliderId: '#rep_pen_slope', | |
| counterId: '#rep_pen_slope_counter', | |
| format: (val) => val, | |
| setValue: (val) => { kai_settings.rep_pen_slope = Number(val); }, | |
| }, | |
| { | |
| name: 'sampler_order', | |
| sliderId: '#no_op_selector', | |
| counterId: '#no_op_selector', | |
| format: (val) => val, | |
| setValue: (val) => { sortItemsByOrder(val); kai_settings.sampler_order = val; }, | |
| }, | |
| { | |
| name: 'mirostat', | |
| sliderId: '#mirostat_mode_kobold', | |
| counterId: '#mirostat_mode_counter_kobold', | |
| format: (val) => val, | |
| setValue: (val) => { kai_settings.mirostat = Number(val); }, | |
| }, | |
| { | |
| name: 'mirostat_tau', | |
| sliderId: '#mirostat_tau_kobold', | |
| counterId: '#mirostat_tau_counter_kobold', | |
| format: (val) => val, | |
| setValue: (val) => { kai_settings.mirostat_tau = Number(val); }, | |
| }, | |
| { | |
| name: 'mirostat_eta', | |
| sliderId: '#mirostat_eta_kobold', | |
| counterId: '#mirostat_eta_counter_kobold', | |
| format: (val) => val, | |
| setValue: (val) => { kai_settings.mirostat_eta = Number(val); }, | |
| }, | |
| { | |
| name: 'grammar', | |
| sliderId: '#grammar', | |
| counterId: '#grammar_counter_kobold', | |
| format: (val) => val, | |
| setValue: (val) => { kai_settings.grammar = val; }, | |
| }, | |
| { | |
| name: 'seed', | |
| sliderId: '#seed_kobold', | |
| counterId: '#seed_counter_kobold', | |
| format: (val) => val, | |
| setValue: (val) => { kai_settings.seed = Number(val); }, | |
| }, | |
| ]; | |
| /** | |
| * Sets the supported feature flags for the KoboldAI backend. | |
| * @param {string} koboldUnitedVersion Kobold United version | |
| * @param {string} koboldCppVersion KoboldCPP version | |
| */ | |
| export function setKoboldFlags(koboldUnitedVersion, koboldCppVersion) { | |
| kai_flags.can_use_stop_sequence = versionCompare(koboldUnitedVersion, MIN_STOP_SEQUENCE_VERSION); | |
| kai_flags.can_use_streaming = versionCompare(koboldCppVersion, MIN_STREAMING_KCPPVERSION); | |
| kai_flags.can_use_tokenization = versionCompare(koboldCppVersion, MIN_TOKENIZATION_KCPPVERSION); | |
| kai_flags.can_use_default_badwordsids = versionCompare(koboldUnitedVersion, MIN_UNBAN_VERSION); | |
| kai_flags.can_use_mirostat = versionCompare(koboldCppVersion, MIN_MIROSTAT_KCPPVERSION); | |
| kai_flags.can_use_grammar = versionCompare(koboldCppVersion, MIN_GRAMMAR_KCPPVERSION); | |
| kai_flags.can_use_min_p = versionCompare(koboldCppVersion, MIN_MIN_P_KCPPVERSION); | |
| const isKoboldCpp = versionCompare(koboldCppVersion, '1.0.0'); | |
| $('#koboldcpp_hint').toggleClass('displayNone', !isKoboldCpp); | |
| } | |
| /** | |
| * Compares two version numbers, returning true if srcVersion >= minVersion | |
| * @param {string} srcVersion The current version. | |
| * @param {string} minVersion The target version number to test against | |
| * @returns {boolean} True if srcVersion >= minVersion, false if not | |
| */ | |
| function versionCompare(srcVersion, minVersion) { | |
| return (srcVersion || '0.0.0').localeCompare(minVersion, undefined, { numeric: true, sensitivity: 'base' }) > -1; | |
| } | |
| /** | |
| * Sorts the sampler items by the given order. | |
| * @param {any[]} orderArray Sampler order array. | |
| */ | |
| function sortItemsByOrder(orderArray) { | |
| console.debug('Preset samplers order: ' + orderArray); | |
| const $draggableItems = $('#kobold_order'); | |
| for (let i = 0; i < orderArray.length; i++) { | |
| const index = orderArray[i]; | |
| const $item = $draggableItems.find(`[data-id="${index}"]`).detach(); | |
| $draggableItems.append($item); | |
| } | |
| } | |
| jQuery(function () { | |
| sliders.forEach(slider => { | |
| $(document).on('input', slider.sliderId, function () { | |
| const value = $(this).val(); | |
| const formattedValue = slider.format(value); | |
| slider.setValue(value); | |
| $(slider.counterId).val(formattedValue); | |
| saveSettingsDebounced(); | |
| }); | |
| }); | |
| $('#streaming_kobold').on('input', function () { | |
| const value = !!$(this).prop('checked'); | |
| kai_settings.streaming_kobold = value; | |
| saveSettingsDebounced(); | |
| }); | |
| $('#use_default_badwordsids').on('input', function () { | |
| const value = !!$(this).prop('checked'); | |
| kai_settings.use_default_badwordsids = value; | |
| saveSettingsDebounced(); | |
| }); | |
| $('#kobold_order').sortable({ | |
| delay: getSortableDelay(), | |
| stop: function () { | |
| const order = []; | |
| $('#kobold_order').children().each(function () { | |
| order.push($(this).data('id')); | |
| }); | |
| kai_settings.sampler_order = order; | |
| console.log('Samplers reordered:', kai_settings.sampler_order); | |
| saveSettingsDebounced(); | |
| }, | |
| }); | |
| $('#samplers_order_recommended').on('click', function () { | |
| kai_settings.sampler_order = KOBOLDCPP_ORDER; | |
| sortItemsByOrder(kai_settings.sampler_order); | |
| saveSettingsDebounced(); | |
| }); | |
| }); | |