mirror of
https://github.com/SillyTavern/SillyTavern.git
synced 2025-06-05 21:59:27 +02:00
Merge remote-tracking branch 'upstream/staging' into improve-bulk-edit-and-fixes
This commit is contained in:
@@ -47,6 +47,7 @@ const sources = {
|
||||
openai: 'openai',
|
||||
comfy: 'comfy',
|
||||
togetherai: 'togetherai',
|
||||
drawthings: 'drawthings',
|
||||
};
|
||||
|
||||
const generationMode = {
|
||||
@@ -217,6 +218,9 @@ const defaultSettings = {
|
||||
vlad_url: 'http://localhost:7860',
|
||||
vlad_auth: '',
|
||||
|
||||
drawthings_url: 'http://localhost:7860',
|
||||
drawthings_auth: '',
|
||||
|
||||
hr_upscaler: 'Latent',
|
||||
hr_scale: 2.0,
|
||||
hr_scale_min: 1.0,
|
||||
@@ -314,6 +318,8 @@ function getSdRequestBody() {
|
||||
return { url: extension_settings.sd.vlad_url, auth: extension_settings.sd.vlad_auth };
|
||||
case sources.auto:
|
||||
return { url: extension_settings.sd.auto_url, auth: extension_settings.sd.auto_auth };
|
||||
case sources.drawthings:
|
||||
return { url: extension_settings.sd.drawthings_url, auth: extension_settings.sd.drawthings_auth };
|
||||
default:
|
||||
throw new Error('Invalid SD source.');
|
||||
}
|
||||
@@ -390,6 +396,8 @@ async function loadSettings() {
|
||||
$('#sd_auto_auth').val(extension_settings.sd.auto_auth);
|
||||
$('#sd_vlad_url').val(extension_settings.sd.vlad_url);
|
||||
$('#sd_vlad_auth').val(extension_settings.sd.vlad_auth);
|
||||
$('#sd_drawthings_url').val(extension_settings.sd.drawthings_url);
|
||||
$('#sd_drawthings_auth').val(extension_settings.sd.drawthings_auth);
|
||||
$('#sd_interactive_mode').prop('checked', extension_settings.sd.interactive_mode);
|
||||
$('#sd_openai_style').val(extension_settings.sd.openai_style);
|
||||
$('#sd_openai_quality').val(extension_settings.sd.openai_quality);
|
||||
@@ -865,6 +873,16 @@ function onVladAuthInput() {
|
||||
saveSettingsDebounced();
|
||||
}
|
||||
|
||||
function onDrawthingsUrlInput() {
|
||||
extension_settings.sd.drawthings_url = $('#sd_drawthings_url').val();
|
||||
saveSettingsDebounced();
|
||||
}
|
||||
|
||||
function onDrawthingsAuthInput() {
|
||||
extension_settings.sd.drawthings_auth = $('#sd_drawthings_auth').val();
|
||||
saveSettingsDebounced();
|
||||
}
|
||||
|
||||
function onHrUpscalerChange() {
|
||||
extension_settings.sd.hr_upscaler = $('#sd_hr_upscaler').find(':selected').val();
|
||||
saveSettingsDebounced();
|
||||
@@ -931,6 +949,29 @@ async function validateAutoUrl() {
|
||||
}
|
||||
}
|
||||
|
||||
async function validateDrawthingsUrl() {
|
||||
try {
|
||||
if (!extension_settings.sd.drawthings_url) {
|
||||
throw new Error('URL is not set.');
|
||||
}
|
||||
|
||||
const result = await fetch('/api/sd/drawthings/ping', {
|
||||
method: 'POST',
|
||||
headers: getRequestHeaders(),
|
||||
body: JSON.stringify(getSdRequestBody()),
|
||||
});
|
||||
|
||||
if (!result.ok) {
|
||||
throw new Error('SD Drawthings returned an error.');
|
||||
}
|
||||
|
||||
await loadSettingOptions();
|
||||
toastr.success('SD Drawthings API connected.');
|
||||
} catch (error) {
|
||||
toastr.error(`Could not validate SD Drawthings API: ${error.message}`);
|
||||
}
|
||||
}
|
||||
|
||||
async function validateVladUrl() {
|
||||
try {
|
||||
if (!extension_settings.sd.vlad_url) {
|
||||
@@ -1018,6 +1059,27 @@ async function getAutoRemoteModel() {
|
||||
}
|
||||
}
|
||||
|
||||
async function getDrawthingsRemoteModel() {
|
||||
try {
|
||||
const result = await fetch('/api/sd/drawthings/get-model', {
|
||||
method: 'POST',
|
||||
headers: getRequestHeaders(),
|
||||
body: JSON.stringify(getSdRequestBody()),
|
||||
});
|
||||
|
||||
if (!result.ok) {
|
||||
throw new Error('SD DrawThings API returned an error.');
|
||||
}
|
||||
|
||||
const data = await result.text();
|
||||
|
||||
return data;
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
async function onVaeChange() {
|
||||
extension_settings.sd.vae = $('#sd_vae').find(':selected').val();
|
||||
}
|
||||
@@ -1108,6 +1170,9 @@ async function loadSamplers() {
|
||||
case sources.auto:
|
||||
samplers = await loadAutoSamplers();
|
||||
break;
|
||||
case sources.drawthings:
|
||||
samplers = await loadDrawthingsSamplers();
|
||||
break;
|
||||
case sources.novel:
|
||||
samplers = await loadNovelSamplers();
|
||||
break;
|
||||
@@ -1193,6 +1258,11 @@ async function loadAutoSamplers() {
|
||||
}
|
||||
}
|
||||
|
||||
async function loadDrawthingsSamplers() {
|
||||
// The app developer doesn't provide an API to get these yet
|
||||
return ["UniPC", "DPM++ 2M Karras", "Euler a", "DPM++ SDE Karras", "PLMS", "DDIM", "LCM", "Euler A Substep", "DPM++ SDE Substep", "TCD"];
|
||||
}
|
||||
|
||||
async function loadVladSamplers() {
|
||||
if (!extension_settings.sd.vlad_url) {
|
||||
return [];
|
||||
@@ -1269,6 +1339,9 @@ async function loadModels() {
|
||||
case sources.auto:
|
||||
models = await loadAutoModels();
|
||||
break;
|
||||
case sources.drawthings:
|
||||
models = await loadDrawthingsModels();
|
||||
break;
|
||||
case sources.novel:
|
||||
models = await loadNovelModels();
|
||||
break;
|
||||
@@ -1405,6 +1478,27 @@ async function loadAutoModels() {
|
||||
}
|
||||
}
|
||||
|
||||
async function loadDrawthingsModels() {
|
||||
if (!extension_settings.sd.drawthings_url) {
|
||||
return [];
|
||||
}
|
||||
|
||||
try {
|
||||
const currentModel = await getDrawthingsRemoteModel();
|
||||
|
||||
if (currentModel) {
|
||||
extension_settings.sd.model = currentModel;
|
||||
}
|
||||
|
||||
const data = [{value: currentModel, text: currentModel}];
|
||||
|
||||
return data;
|
||||
} catch (error) {
|
||||
console.log("Error loading DrawThings API models:", error);
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
async function loadOpenAiModels() {
|
||||
return [
|
||||
{ value: 'dall-e-3', text: 'DALL-E 3' },
|
||||
@@ -1527,6 +1621,9 @@ async function loadSchedulers() {
|
||||
case sources.vlad:
|
||||
schedulers = ['N/A'];
|
||||
break;
|
||||
case sources.drawthings:
|
||||
schedulers = ['N/A'];
|
||||
break;
|
||||
case sources.openai:
|
||||
schedulers = ['N/A'];
|
||||
break;
|
||||
@@ -1589,6 +1686,9 @@ async function loadVaes() {
|
||||
case sources.vlad:
|
||||
vaes = ['N/A'];
|
||||
break;
|
||||
case sources.drawthings:
|
||||
vaes = ['N/A'];
|
||||
break;
|
||||
case sources.openai:
|
||||
vaes = ['N/A'];
|
||||
break;
|
||||
@@ -1996,6 +2096,9 @@ async function sendGenerationRequest(generationType, prompt, characterName = nul
|
||||
case sources.vlad:
|
||||
result = await generateAutoImage(prefixedPrompt, negativePrompt);
|
||||
break;
|
||||
case sources.drawthings:
|
||||
result = await generateDrawthingsImage(prefixedPrompt, negativePrompt);
|
||||
break;
|
||||
case sources.auto:
|
||||
result = await generateAutoImage(prefixedPrompt, negativePrompt);
|
||||
break;
|
||||
@@ -2178,6 +2281,42 @@ async function generateAutoImage(prompt, negativePrompt) {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates an image in Drawthings API using the provided prompt and configuration settings.
|
||||
*
|
||||
* @param {string} prompt - The main instruction used to guide the image generation.
|
||||
* @param {string} negativePrompt - The instruction used to restrict the image generation.
|
||||
* @returns {Promise<{format: string, data: string}>} - A promise that resolves when the image generation and processing are complete.
|
||||
*/
|
||||
async function generateDrawthingsImage(prompt, negativePrompt) {
|
||||
const result = await fetch('/api/sd/drawthings/generate', {
|
||||
method: 'POST',
|
||||
headers: getRequestHeaders(),
|
||||
body: JSON.stringify({
|
||||
...getSdRequestBody(),
|
||||
prompt: prompt,
|
||||
negative_prompt: negativePrompt,
|
||||
sampler_name: extension_settings.sd.sampler,
|
||||
steps: extension_settings.sd.steps,
|
||||
cfg_scale: extension_settings.sd.scale,
|
||||
width: extension_settings.sd.width,
|
||||
height: extension_settings.sd.height,
|
||||
restore_faces: !!extension_settings.sd.restore_faces,
|
||||
enable_hr: !!extension_settings.sd.enable_hr,
|
||||
denoising_strength: extension_settings.sd.denoising_strength,
|
||||
// TODO: advanced API parameters: hr, upscaler
|
||||
}),
|
||||
});
|
||||
|
||||
if (result.ok) {
|
||||
const data = await result.json();
|
||||
return { format: 'png', data: data.images[0] };
|
||||
} else {
|
||||
const text = await result.text();
|
||||
throw new Error(text);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates an image in NovelAI API using the provided prompt and configuration settings.
|
||||
*
|
||||
@@ -2603,6 +2742,8 @@ function isValidState() {
|
||||
return true;
|
||||
case sources.auto:
|
||||
return !!extension_settings.sd.auto_url;
|
||||
case sources.drawthings:
|
||||
return !!extension_settings.sd.drawthings_url;
|
||||
case sources.vlad:
|
||||
return !!extension_settings.sd.vlad_url;
|
||||
case sources.novel:
|
||||
@@ -2745,6 +2886,9 @@ jQuery(async () => {
|
||||
$('#sd_auto_validate').on('click', validateAutoUrl);
|
||||
$('#sd_auto_url').on('input', onAutoUrlInput);
|
||||
$('#sd_auto_auth').on('input', onAutoAuthInput);
|
||||
$('#sd_drawthings_validate').on('click', validateDrawthingsUrl);
|
||||
$('#sd_drawthings_url').on('input', onDrawthingsUrlInput);
|
||||
$('#sd_drawthings_auth').on('input', onDrawthingsAuthInput);
|
||||
$('#sd_vlad_validate').on('click', validateVladUrl);
|
||||
$('#sd_vlad_url').on('input', onVladUrlInput);
|
||||
$('#sd_vlad_auth').on('input', onVladAuthInput);
|
||||
|
@@ -36,6 +36,7 @@
|
||||
<option value="horde">Stable Horde</option>
|
||||
<option value="auto">Stable Diffusion Web UI (AUTOMATIC1111)</option>
|
||||
<option value="vlad">SD.Next (vladmandic)</option>
|
||||
<option value="drawthings">DrawThings HTTP API</option>
|
||||
<option value="novel">NovelAI Diffusion</option>
|
||||
<option value="openai">OpenAI (DALL-E)</option>
|
||||
<option value="comfy">ComfyUI</option>
|
||||
@@ -56,6 +57,21 @@
|
||||
<input id="sd_auto_auth" type="text" class="text_pole" placeholder="Example: username:password" value="" />
|
||||
<i><b>Important:</b> run SD Web UI with the <tt>--api</tt> flag! The server must be accessible from the SillyTavern host machine.</i>
|
||||
</div>
|
||||
<div data-sd-source="drawthings">
|
||||
<label for="sd_drawthings_url">DrawThings API URL</label>
|
||||
<div class="flex-container flexnowrap">
|
||||
<input id="sd_drawthings_url" type="text" class="text_pole" placeholder="Example: {{drawthings_url}}" value="{{drawthings_url}}" />
|
||||
<div id="sd_drawthings_validate" class="menu_button menu_button_icon">
|
||||
<i class="fa-solid fa-check"></i>
|
||||
<span data-i18n="Connect">
|
||||
Connect
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<label for="sd_drawthings_auth">Authentication (optional)</label>
|
||||
<input id="sd_drawthings_auth" type="text" class="text_pole" placeholder="Example: username:password" value="" />
|
||||
<i><b>Important:</b> run DrawThings app with HTTP API switch enabled in the UI! The server must be accessible from the SillyTavern host machine.</i>
|
||||
</div>
|
||||
<div data-sd-source="vlad">
|
||||
<label for="sd_vlad_url">SD.Next API URL</label>
|
||||
<div class="flex-container flexnowrap">
|
||||
|
@@ -33,7 +33,7 @@ async function doTokenCounter() {
|
||||
<div id="tokenized_chunks_display" class="wide100p">—</div>
|
||||
<hr>
|
||||
<div>Token IDs:</div>
|
||||
<textarea id="token_counter_ids" class="wide100p textarea_compact" disabled rows="1">—</textarea>
|
||||
<textarea id="token_counter_ids" class="wide100p textarea_compact" readonly rows="1">—</textarea>
|
||||
</div>
|
||||
</div>`;
|
||||
|
||||
|
@@ -1,7 +1,8 @@
|
||||
'use strict';
|
||||
|
||||
import { saveSettingsDebounced, substituteParams } from '../script.js';
|
||||
import { name1, name2, saveSettingsDebounced, substituteParams } from '../script.js';
|
||||
import { selected_group } from './group-chats.js';
|
||||
import { parseExampleIntoIndividual } from './openai.js';
|
||||
import {
|
||||
power_user,
|
||||
context_presets,
|
||||
@@ -19,9 +20,13 @@ const controls = [
|
||||
{ id: 'instruct_system_prompt', property: 'system_prompt', isCheckbox: false },
|
||||
{ id: 'instruct_system_sequence_prefix', property: 'system_sequence_prefix', isCheckbox: false },
|
||||
{ id: 'instruct_system_sequence_suffix', property: 'system_sequence_suffix', isCheckbox: false },
|
||||
{ id: 'instruct_separator_sequence', property: 'separator_sequence', isCheckbox: false },
|
||||
{ id: 'instruct_input_sequence', property: 'input_sequence', isCheckbox: false },
|
||||
{ id: 'instruct_input_suffix', property: 'input_suffix', isCheckbox: false },
|
||||
{ id: 'instruct_output_sequence', property: 'output_sequence', isCheckbox: false },
|
||||
{ id: 'instruct_output_suffix', property: 'output_suffix', isCheckbox: false },
|
||||
{ id: 'instruct_system_sequence', property: 'system_sequence', isCheckbox: false },
|
||||
{ id: 'instruct_system_suffix', property: 'system_suffix', isCheckbox: false },
|
||||
{ id: 'instruct_user_alignment_message', property: 'user_alignment_message', isCheckbox: false },
|
||||
{ id: 'instruct_stop_sequence', property: 'stop_sequence', isCheckbox: false },
|
||||
{ id: 'instruct_names', property: 'names', isCheckbox: true },
|
||||
{ id: 'instruct_macro', property: 'macro', isCheckbox: true },
|
||||
@@ -31,8 +36,38 @@ const controls = [
|
||||
{ id: 'instruct_activation_regex', property: 'activation_regex', isCheckbox: false },
|
||||
{ id: 'instruct_bind_to_context', property: 'bind_to_context', isCheckbox: true },
|
||||
{ id: 'instruct_skip_examples', property: 'skip_examples', isCheckbox: true },
|
||||
{ id: 'instruct_system_same_as_user', property: 'system_same_as_user', isCheckbox: true, trigger: true },
|
||||
];
|
||||
|
||||
/**
|
||||
* Migrates instruct mode settings into the evergreen format.
|
||||
* @param {object} settings Instruct mode settings.
|
||||
* @returns {void}
|
||||
*/
|
||||
function migrateInstructModeSettings(settings) {
|
||||
// Separator sequence => Output suffix
|
||||
if (settings.separator_sequence !== undefined) {
|
||||
settings.output_suffix = settings.separator_sequence || '';
|
||||
delete settings.separator_sequence;
|
||||
}
|
||||
|
||||
const defaults = {
|
||||
input_suffix: '',
|
||||
system_sequence: '',
|
||||
system_suffix: '',
|
||||
user_alignment_message: '',
|
||||
names_force_groups: true,
|
||||
skip_examples: false,
|
||||
system_same_as_user: false,
|
||||
};
|
||||
|
||||
for (let key in defaults) {
|
||||
if (settings[key] === undefined) {
|
||||
settings[key] = defaults[key];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads instruct mode settings from the given data object.
|
||||
* @param {object} data Settings data object.
|
||||
@@ -42,13 +77,7 @@ export function loadInstructMode(data) {
|
||||
instruct_presets = data.instruct;
|
||||
}
|
||||
|
||||
if (power_user.instruct.names_force_groups === undefined) {
|
||||
power_user.instruct.names_force_groups = true;
|
||||
}
|
||||
|
||||
if (power_user.instruct.skip_examples === undefined) {
|
||||
power_user.instruct.skip_examples = false;
|
||||
}
|
||||
migrateInstructModeSettings(power_user.instruct);
|
||||
|
||||
controls.forEach(control => {
|
||||
const $element = $(`#${control.id}`);
|
||||
@@ -66,6 +95,10 @@ export function loadInstructMode(data) {
|
||||
resetScrollHeight($element);
|
||||
}
|
||||
});
|
||||
|
||||
if (control.trigger) {
|
||||
$element.trigger('input');
|
||||
}
|
||||
});
|
||||
|
||||
instruct_presets.forEach((preset) => {
|
||||
@@ -210,12 +243,14 @@ export function getInstructStoppingSequences() {
|
||||
const result = [];
|
||||
|
||||
if (power_user.instruct.enabled) {
|
||||
const input_sequence = power_user.instruct.input_sequence;
|
||||
const output_sequence = power_user.instruct.output_sequence;
|
||||
const first_output_sequence = power_user.instruct.first_output_sequence;
|
||||
const last_output_sequence = power_user.instruct.last_output_sequence;
|
||||
const stop_sequence = power_user.instruct.stop_sequence;
|
||||
const input_sequence = power_user.instruct.input_sequence.replace(/{{name}}/gi, name1);
|
||||
const output_sequence = power_user.instruct.output_sequence.replace(/{{name}}/gi, name2);
|
||||
const first_output_sequence = power_user.instruct.first_output_sequence.replace(/{{name}}/gi, name2);
|
||||
const last_output_sequence = power_user.instruct.last_output_sequence.replace(/{{name}}/gi, name2);
|
||||
const system_sequence = power_user.instruct.system_sequence.replace(/{{name}}/gi, 'System');
|
||||
|
||||
const combined_sequence = `${input_sequence}\n${output_sequence}\n${first_output_sequence}\n${last_output_sequence}`;
|
||||
const combined_sequence = `${stop_sequence}\n${input_sequence}\n${output_sequence}\n${first_output_sequence}\n${last_output_sequence}\n${system_sequence}`;
|
||||
|
||||
combined_sequence.split('\n').filter((line, index, self) => self.indexOf(line) === index).forEach(addInstructSequence);
|
||||
}
|
||||
@@ -257,26 +292,48 @@ export function formatInstructModeChat(name, mes, isUser, isNarrator, forceAvata
|
||||
includeNames = true;
|
||||
}
|
||||
|
||||
let sequence = (isUser || isNarrator) ? power_user.instruct.input_sequence : power_user.instruct.output_sequence;
|
||||
|
||||
if (forceOutputSequence && sequence === power_user.instruct.output_sequence) {
|
||||
if (forceOutputSequence === force_output_sequence.FIRST && power_user.instruct.first_output_sequence) {
|
||||
sequence = power_user.instruct.first_output_sequence;
|
||||
} else if (forceOutputSequence === force_output_sequence.LAST && power_user.instruct.last_output_sequence) {
|
||||
sequence = power_user.instruct.last_output_sequence;
|
||||
function getPrefix() {
|
||||
if (isNarrator) {
|
||||
return power_user.instruct.system_same_as_user ? power_user.instruct.input_sequence : power_user.instruct.system_sequence;
|
||||
}
|
||||
|
||||
if (isUser) {
|
||||
return power_user.instruct.input_sequence;
|
||||
}
|
||||
|
||||
if (forceOutputSequence === force_output_sequence.FIRST) {
|
||||
return power_user.instruct.first_output_sequence || power_user.instruct.output_sequence;
|
||||
}
|
||||
|
||||
if (forceOutputSequence === force_output_sequence.LAST) {
|
||||
return power_user.instruct.last_output_sequence || power_user.instruct.output_sequence;
|
||||
}
|
||||
|
||||
return power_user.instruct.output_sequence;
|
||||
}
|
||||
|
||||
function getSuffix() {
|
||||
if (isNarrator) {
|
||||
return power_user.instruct.system_same_as_user ? power_user.instruct.input_suffix : power_user.instruct.system_suffix;
|
||||
}
|
||||
|
||||
if (isUser) {
|
||||
return power_user.instruct.input_suffix;
|
||||
}
|
||||
|
||||
return power_user.instruct.output_suffix;
|
||||
}
|
||||
|
||||
let prefix = getPrefix() || '';
|
||||
let suffix = getSuffix() || '';
|
||||
|
||||
if (power_user.instruct.macro) {
|
||||
sequence = substituteParams(sequence, name1, name2);
|
||||
sequence = sequence.replace(/{{name}}/gi, name || 'System');
|
||||
prefix = substituteParams(prefix, name1, name2);
|
||||
prefix = prefix.replace(/{{name}}/gi, name || 'System');
|
||||
}
|
||||
|
||||
const separator = power_user.instruct.wrap ? '\n' : '';
|
||||
const separatorSequence = power_user.instruct.separator_sequence && !isUser
|
||||
? power_user.instruct.separator_sequence
|
||||
: separator;
|
||||
const textArray = includeNames ? [sequence, `${name}: ${mes}` + separatorSequence] : [sequence, mes + separatorSequence];
|
||||
const textArray = includeNames ? [prefix, `${name}: ${mes}` + suffix] : [prefix, mes + suffix];
|
||||
const text = textArray.filter(x => x).join(separator);
|
||||
return text;
|
||||
}
|
||||
@@ -286,7 +343,7 @@ export function formatInstructModeChat(name, mes, isUser, isNarrator, forceAvata
|
||||
* @param {string} systemPrompt System prompt string.
|
||||
* @returns {string} Formatted instruct mode system prompt.
|
||||
*/
|
||||
export function formatInstructModeSystemPrompt(systemPrompt){
|
||||
export function formatInstructModeSystemPrompt(systemPrompt) {
|
||||
const separator = power_user.instruct.wrap ? '\n' : '';
|
||||
|
||||
if (power_user.instruct.system_sequence_prefix) {
|
||||
@@ -302,33 +359,59 @@ export function formatInstructModeSystemPrompt(systemPrompt){
|
||||
|
||||
/**
|
||||
* Formats example messages according to instruct mode settings.
|
||||
* @param {string} mesExamples Example messages string.
|
||||
* @param {string[]} mesExamplesArray Example messages array.
|
||||
* @param {string} name1 User name.
|
||||
* @param {string} name2 Character name.
|
||||
* @returns {string} Formatted example messages string.
|
||||
* @returns {string[]} Formatted example messages string.
|
||||
*/
|
||||
export function formatInstructModeExamples(mesExamples, name1, name2) {
|
||||
export function formatInstructModeExamples(mesExamplesArray, name1, name2) {
|
||||
if (power_user.instruct.skip_examples) {
|
||||
return mesExamples;
|
||||
return mesExamplesArray.map(x => x.replace(/<START>\n/i, ''));
|
||||
}
|
||||
|
||||
const includeNames = power_user.instruct.names || (!!selected_group && power_user.instruct.names_force_groups);
|
||||
|
||||
let inputSequence = power_user.instruct.input_sequence;
|
||||
let outputSequence = power_user.instruct.output_sequence;
|
||||
let inputPrefix = power_user.instruct.input_sequence || '';
|
||||
let outputPrefix = power_user.instruct.output_sequence || '';
|
||||
let inputSuffix = power_user.instruct.input_suffix || '';
|
||||
let outputSuffix = power_user.instruct.output_suffix || '';
|
||||
|
||||
if (power_user.instruct.macro) {
|
||||
inputSequence = substituteParams(inputSequence, name1, name2);
|
||||
outputSequence = substituteParams(outputSequence, name1, name2);
|
||||
inputPrefix = substituteParams(inputPrefix, name1, name2);
|
||||
outputPrefix = substituteParams(outputPrefix, name1, name2);
|
||||
inputSuffix = substituteParams(inputSuffix, name1, name2);
|
||||
outputSuffix = substituteParams(outputSuffix, name1, name2);
|
||||
|
||||
inputPrefix = inputPrefix.replace(/{{name}}/gi, name1);
|
||||
outputPrefix = outputPrefix.replace(/{{name}}/gi, name2);
|
||||
}
|
||||
|
||||
const separator = power_user.instruct.wrap ? '\n' : '';
|
||||
const separatorSequence = power_user.instruct.separator_sequence ? power_user.instruct.separator_sequence : separator;
|
||||
const parsedExamples = [];
|
||||
|
||||
mesExamples = mesExamples.replace(new RegExp(`\n${name1}: `, 'gm'), separatorSequence + inputSequence + separator + (includeNames ? `${name1}: ` : ''));
|
||||
mesExamples = mesExamples.replace(new RegExp(`\n${name2}: `, 'gm'), separator + outputSequence + separator + (includeNames ? `${name2}: ` : ''));
|
||||
for (const item of mesExamplesArray) {
|
||||
const cleanedItem = item.replace(/<START>/i, '{Example Dialogue:}').replace(/\r/gm, '');
|
||||
const blockExamples = parseExampleIntoIndividual(cleanedItem);
|
||||
parsedExamples.push(...blockExamples);
|
||||
}
|
||||
|
||||
return mesExamples;
|
||||
// Not something we can parse, return as is
|
||||
if (!Array.isArray(parsedExamples) || parsedExamples.length === 0) {
|
||||
return mesExamplesArray;
|
||||
}
|
||||
|
||||
const formattedExamples = [];
|
||||
|
||||
for (const example of parsedExamples) {
|
||||
const prefix = example.name == 'example_user' ? inputPrefix : outputPrefix;
|
||||
const suffix = example.name == 'example_user' ? inputSuffix : outputSuffix;
|
||||
const name = example.name == 'example_user' ? name1 : name2;
|
||||
const messageContent = includeNames ? `${name}: ${example.content}` : example.content;
|
||||
const formattedMessage = [prefix, messageContent + suffix].filter(x => x).join(separator);
|
||||
formattedExamples.push(formattedMessage);
|
||||
}
|
||||
|
||||
return formattedExamples;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -338,12 +421,34 @@ export function formatInstructModeExamples(mesExamples, name1, name2) {
|
||||
* @param {string} promptBias Prompt bias string.
|
||||
* @param {string} name1 User name.
|
||||
* @param {string} name2 Character name.
|
||||
* @param {boolean} isQuiet Is quiet mode generation.
|
||||
* @param {boolean} isQuietToLoud Is quiet to loud generation.
|
||||
* @returns {string} Formatted instruct mode last prompt line.
|
||||
*/
|
||||
export function formatInstructModePrompt(name, isImpersonate, promptBias, name1, name2) {
|
||||
const includeNames = name && (power_user.instruct.names || (!!selected_group && power_user.instruct.names_force_groups));
|
||||
const getOutputSequence = () => power_user.instruct.last_output_sequence || power_user.instruct.output_sequence;
|
||||
let sequence = isImpersonate ? power_user.instruct.input_sequence : getOutputSequence();
|
||||
export function formatInstructModePrompt(name, isImpersonate, promptBias, name1, name2, isQuiet, isQuietToLoud) {
|
||||
const includeNames = name && (power_user.instruct.names || (!!selected_group && power_user.instruct.names_force_groups)) && !(isQuiet && !isQuietToLoud);
|
||||
|
||||
function getSequence() {
|
||||
// User impersonation prompt
|
||||
if (isImpersonate) {
|
||||
return power_user.instruct.input_sequence;
|
||||
}
|
||||
|
||||
// Neutral / system prompt
|
||||
if (isQuiet && !isQuietToLoud) {
|
||||
return power_user.instruct.output_sequence;
|
||||
}
|
||||
|
||||
// Quiet in-character prompt
|
||||
if (isQuiet && isQuietToLoud) {
|
||||
return power_user.instruct.last_output_sequence || power_user.instruct.output_sequence;
|
||||
}
|
||||
|
||||
// Default AI response
|
||||
return power_user.instruct.last_output_sequence || power_user.instruct.output_sequence;
|
||||
}
|
||||
|
||||
let sequence = getSequence() || '';
|
||||
|
||||
if (power_user.instruct.macro) {
|
||||
sequence = substituteParams(sequence, name1, name2);
|
||||
@@ -353,6 +458,11 @@ export function formatInstructModePrompt(name, isImpersonate, promptBias, name1,
|
||||
const separator = power_user.instruct.wrap ? '\n' : '';
|
||||
let text = includeNames ? (separator + sequence + separator + `${name}:`) : (separator + sequence);
|
||||
|
||||
// Quiet prompt already has a newline at the end
|
||||
if (isQuiet && separator) {
|
||||
text = text.slice(separator.length);
|
||||
}
|
||||
|
||||
if (!isImpersonate && promptBias) {
|
||||
text += (includeNames ? promptBias : (separator + promptBias.trimStart()));
|
||||
}
|
||||
@@ -390,15 +500,19 @@ export function replaceInstructMacros(input) {
|
||||
return '';
|
||||
}
|
||||
|
||||
input = input.replace(/{{instructSystem}}/gi, power_user.instruct.enabled ? power_user.instruct.system_prompt : '');
|
||||
input = input.replace(/{{instructSystemPrefix}}/gi, power_user.instruct.enabled ? power_user.instruct.system_sequence_prefix : '');
|
||||
input = input.replace(/{{instructSystemSuffix}}/gi, power_user.instruct.enabled ? power_user.instruct.system_sequence_suffix : '');
|
||||
input = input.replace(/{{instructInput}}/gi, power_user.instruct.enabled ? power_user.instruct.input_sequence : '');
|
||||
input = input.replace(/{{instructOutput}}/gi, power_user.instruct.enabled ? power_user.instruct.output_sequence : '');
|
||||
input = input.replace(/{{instructFirstOutput}}/gi, power_user.instruct.enabled ? (power_user.instruct.first_output_sequence || power_user.instruct.output_sequence) : '');
|
||||
input = input.replace(/{{instructLastOutput}}/gi, power_user.instruct.enabled ? (power_user.instruct.last_output_sequence || power_user.instruct.output_sequence) : '');
|
||||
input = input.replace(/{{instructSeparator}}/gi, power_user.instruct.enabled ? power_user.instruct.separator_sequence : '');
|
||||
input = input.replace(/{{(instructSystem|instructSystemPrompt)}}/gi, power_user.instruct.enabled ? power_user.instruct.system_prompt : '');
|
||||
input = input.replace(/{{instructSystemPromptPrefix}}/gi, power_user.instruct.enabled ? power_user.instruct.system_sequence_prefix : '');
|
||||
input = input.replace(/{{instructSystemPromptSuffix}}/gi, power_user.instruct.enabled ? power_user.instruct.system_sequence_suffix : '');
|
||||
input = input.replace(/{{(instructInput|instructUserPrefix)}}/gi, power_user.instruct.enabled ? power_user.instruct.input_sequence : '');
|
||||
input = input.replace(/{{instructUserSuffix}}/gi, power_user.instruct.enabled ? power_user.instruct.input_suffix : '');
|
||||
input = input.replace(/{{(instructOutput|instructAssistantPrefix)}}/gi, power_user.instruct.enabled ? power_user.instruct.output_sequence : '');
|
||||
input = input.replace(/{{(instructSeparator|instructAssistantSuffix)}}/gi, power_user.instruct.enabled ? power_user.instruct.output_suffix : '');
|
||||
input = input.replace(/{{instructSystemPrefix}}/gi, power_user.instruct.enabled ? power_user.instruct.system_sequence : '');
|
||||
input = input.replace(/{{instructSystemSuffix}}/gi, power_user.instruct.enabled ? power_user.instruct.system_suffix : '');
|
||||
input = input.replace(/{{(instructFirstOutput|instructFirstAssistantPrefix)}}/gi, power_user.instruct.enabled ? (power_user.instruct.first_output_sequence || power_user.instruct.output_sequence) : '');
|
||||
input = input.replace(/{{(instructLastOutput|instructLastAssistantPrefix)}}/gi, power_user.instruct.enabled ? (power_user.instruct.last_output_sequence || power_user.instruct.output_sequence) : '');
|
||||
input = input.replace(/{{instructStop}}/gi, power_user.instruct.enabled ? power_user.instruct.stop_sequence : '');
|
||||
input = input.replace(/{{instructUserFiller}}/gi, power_user.instruct.enabled ? power_user.instruct.user_alignment_message : '');
|
||||
input = input.replace(/{{exampleSeparator}}/gi, power_user.context.example_separator);
|
||||
input = input.replace(/{{chatStart}}/gi, power_user.context.chat_start);
|
||||
|
||||
@@ -420,6 +534,12 @@ jQuery(() => {
|
||||
saveSettingsDebounced();
|
||||
});
|
||||
|
||||
$('#instruct_system_same_as_user').on('input', function () {
|
||||
const state = !!$(this).prop('checked');
|
||||
$('#instruct_system_sequence').prop('disabled', state);
|
||||
$('#instruct_system_suffix').prop('disabled', state);
|
||||
});
|
||||
|
||||
$('#instruct_enabled').on('change', function () {
|
||||
if (!power_user.instruct.bind_to_context) {
|
||||
return;
|
||||
@@ -428,8 +548,8 @@ jQuery(() => {
|
||||
// When instruct mode gets enabled, select context template matching selected instruct preset
|
||||
if (power_user.instruct.enabled) {
|
||||
selectMatchingContextTemplate(power_user.instruct.preset);
|
||||
// When instruct mode gets disabled, select default context preset
|
||||
} else {
|
||||
// When instruct mode gets disabled, select default context preset
|
||||
selectContextPreset(power_user.default_context);
|
||||
}
|
||||
});
|
||||
@@ -442,6 +562,8 @@ jQuery(() => {
|
||||
return;
|
||||
}
|
||||
|
||||
migrateInstructModeSettings(preset);
|
||||
|
||||
power_user.instruct.preset = String(name);
|
||||
controls.forEach(control => {
|
||||
if (preset[control.property] !== undefined) {
|
||||
|
@@ -4,6 +4,9 @@ import { textgenerationwebui_banned_in_macros } from './textgen-settings.js';
|
||||
import { replaceInstructMacros } from './instruct-mode.js';
|
||||
import { replaceVariableMacros } from './variables.js';
|
||||
|
||||
// Register any macro that you want to leave in the compiled story string
|
||||
Handlebars.registerHelper('trim', () => '{{trim}}');
|
||||
|
||||
/**
|
||||
* Returns the ID of the last message in the chat.
|
||||
* @returns {string} The ID of the last message in the chat.
|
||||
@@ -257,6 +260,7 @@ export function evaluateMacros(content, env) {
|
||||
content = replaceInstructMacros(content);
|
||||
content = replaceVariableMacros(content);
|
||||
content = content.replace(/{{newline}}/gi, '\n');
|
||||
content = content.replace(/\n*{{trim}}\n*/gi, '');
|
||||
content = content.replace(/{{input}}/gi, () => String($('#send_textarea').val()));
|
||||
|
||||
// Substitute passed-in variables
|
||||
|
@@ -448,8 +448,10 @@ function convertChatCompletionToInstruct(messages, type) {
|
||||
|
||||
const isImpersonate = type === 'impersonate';
|
||||
const isContinue = type === 'continue';
|
||||
const isQuiet = type === 'quiet';
|
||||
const isQuietToLoud = false; // Quiet to loud not implemented for Chat Completion
|
||||
const promptName = isImpersonate ? name1 : name2;
|
||||
const promptLine = isContinue ? '' : formatInstructModePrompt(promptName, isImpersonate, '', name1, name2).trimStart();
|
||||
const promptLine = isContinue ? '' : formatInstructModePrompt(promptName, isImpersonate, '', name1, name2, isQuiet, isQuietToLoud).trimStart();
|
||||
|
||||
let prompt = [systemPromptText, examplesText, chatMessagesText, promptLine]
|
||||
.filter(x => x)
|
||||
@@ -523,7 +525,7 @@ function setOpenAIMessageExamples(mesExamplesArray) {
|
||||
for (let item of mesExamplesArray) {
|
||||
// remove <START> {Example Dialogue:} and replace \r\n with just \n
|
||||
let replaced = item.replace(/<START>/i, '{Example Dialogue:}').replace(/\r/gm, '');
|
||||
let parsed = parseExampleIntoIndividual(replaced);
|
||||
let parsed = parseExampleIntoIndividual(replaced, true);
|
||||
// add to the example message blocks array
|
||||
examples.push(parsed);
|
||||
}
|
||||
@@ -584,7 +586,13 @@ function setupChatCompletionPromptManager(openAiSettings) {
|
||||
return promptManager;
|
||||
}
|
||||
|
||||
function parseExampleIntoIndividual(messageExampleString) {
|
||||
/**
|
||||
* Parses the example messages into individual messages.
|
||||
* @param {string} messageExampleString - The string containing the example messages
|
||||
* @param {boolean} appendNamesForGroup - Whether to append the character name for group chats
|
||||
* @returns {Message[]} Array of message objects
|
||||
*/
|
||||
export function parseExampleIntoIndividual(messageExampleString, appendNamesForGroup = true) {
|
||||
let result = []; // array of msgs
|
||||
let tmp = messageExampleString.split('\n');
|
||||
let cur_msg_lines = [];
|
||||
@@ -597,7 +605,7 @@ function parseExampleIntoIndividual(messageExampleString) {
|
||||
// strip to remove extra spaces
|
||||
let parsed_msg = cur_msg_lines.join('\n').replace(name + ':', '').trim();
|
||||
|
||||
if (selected_group && ['example_user', 'example_assistant'].includes(system_name)) {
|
||||
if (appendNamesForGroup && selected_group && ['example_user', 'example_assistant'].includes(system_name)) {
|
||||
parsed_msg = `${name}: ${parsed_msg}`;
|
||||
}
|
||||
|
||||
|
@@ -197,19 +197,26 @@ let power_user = {
|
||||
preset: 'Alpaca',
|
||||
system_prompt: 'Below is an instruction that describes a task. Write a response that appropriately completes the request.\n\nWrite {{char}}\'s next reply in a fictional roleplay chat between {{user}} and {{char}}.\n',
|
||||
input_sequence: '### Instruction:',
|
||||
input_suffix: '',
|
||||
output_sequence: '### Response:',
|
||||
output_suffix: '',
|
||||
system_sequence: '',
|
||||
system_suffix: '',
|
||||
first_output_sequence: '',
|
||||
last_output_sequence: '',
|
||||
system_sequence_prefix: '',
|
||||
system_sequence_suffix: '',
|
||||
stop_sequence: '',
|
||||
separator_sequence: '',
|
||||
wrap: true,
|
||||
macro: true,
|
||||
names: false,
|
||||
names_force_groups: true,
|
||||
activation_regex: '',
|
||||
bind_to_context: false,
|
||||
user_alignment_message: '',
|
||||
system_same_as_user: false,
|
||||
/** @deprecated Use output_suffix instead */
|
||||
separator_sequence: '',
|
||||
},
|
||||
|
||||
default_context: 'Default',
|
||||
|
@@ -230,8 +230,8 @@ parser.addCommand('peek', peekCallback, [], '<span class="monospace">(message in
|
||||
parser.addCommand('delswipe', deleteSwipeCallback, ['swipedel'], '<span class="monospace">(optional 1-based id)</span> – deletes a swipe from the last chat message. If swipe id not provided - deletes the current swipe.', true, true);
|
||||
parser.addCommand('echo', echoCallback, [], '<span class="monospace">(title=string severity=info/warning/error/success [text])</span> – echoes the text to toast message. Useful for pipes debugging.', true, true);
|
||||
//parser.addCommand('#', (_, value) => '', [], ' – a comment, does nothing, e.g. <tt>/# the next three commands switch variables a and b</tt>', true, true);
|
||||
parser.addCommand('gen', generateCallback, [], '<span class="monospace">(lock=on/off name="System" [prompt])</span> – generates text using the provided prompt and passes it to the next command through the pipe, optionally locking user input while generating and allowing to configure the in-prompt name for instruct mode (default = "System").', true, true);
|
||||
parser.addCommand('genraw', generateRawCallback, [], '<span class="monospace">(lock=on/off [prompt])</span> – generates text using the provided prompt and passes it to the next command through the pipe, optionally locking user input while generating. Does not include chat history or character card. Use instruct=off to skip instruct formatting, e.g. <tt>/genraw instruct=off Why is the sky blue?</tt>. Use stop=... with a JSON-serialized array to add one-time custom stop strings, e.g. <tt>/genraw stop=["\\n"] Say hi</tt>', true, true);
|
||||
parser.addCommand('gen', generateCallback, [], '<span class="monospace">(lock=on/off name="System" [prompt])</span> – generates text using the provided prompt and passes it to the next command through the pipe, optionally locking user input while generating and allowing to configure the in-prompt name for instruct mode (default = "System"). "as" argument controls the role of the output prompt: system (default) or char.', true, true);
|
||||
parser.addCommand('genraw', generateRawCallback, [], '<span class="monospace">(lock=on/off instruct=on/off stop=[] as=system/char [prompt])</span> – generates text using the provided prompt and passes it to the next command through the pipe, optionally locking user input while generating. Does not include chat history or character card. Use instruct=off to skip instruct formatting, e.g. <tt>/genraw instruct=off Why is the sky blue?</tt>. Use stop=... with a JSON-serialized array to add one-time custom stop strings, e.g. <tt>/genraw stop=["\\n"] Say hi</tt>. "as" argument controls the role of the output prompt: system (default) or char.', true, true);
|
||||
parser.addCommand('addswipe', addSwipeCallback, ['swipeadd'], '<span class="monospace">(text)</span> – adds a swipe to the last chat message.', true, true);
|
||||
parser.addCommand('abort', abortCallback, [], ' – aborts the slash command batch execution', true, true);
|
||||
parser.addCommand('fuzzy', fuzzyCallback, [], 'list=["a","b","c"] threshold=0.4 (text to search) – performs a fuzzy match of each items of list within the text to search. If any item matches then its name is returned. If no item list matches the text to search then no value is returned. The optional threshold (default is 0.4) allows some control over the matching. A low value (min 0.0) means the match is very strict. At 1.0 (max) the match is very loose and probably matches anything. The returned value passes to the next command through the pipe.', true, true); parser.addCommand('pass', (_, arg) => arg, ['return'], '<span class="monospace">(text)</span> – passes the text to the next command through the pipe.', true, true);
|
||||
@@ -659,6 +659,8 @@ async function generateRawCallback(args, value) {
|
||||
// Prevent generate recursion
|
||||
$('#send_textarea').val('').trigger('input');
|
||||
const lock = isTrueBoolean(args?.lock);
|
||||
const as = args?.as || 'system';
|
||||
const quietToLoud = as === 'char';
|
||||
|
||||
try {
|
||||
if (lock) {
|
||||
@@ -666,7 +668,7 @@ async function generateRawCallback(args, value) {
|
||||
}
|
||||
|
||||
setEphemeralStopStrings(resolveVariable(args?.stop));
|
||||
const result = await generateRaw(value, '', isFalseBoolean(args?.instruct));
|
||||
const result = await generateRaw(value, '', isFalseBoolean(args?.instruct), quietToLoud);
|
||||
return result;
|
||||
} finally {
|
||||
if (lock) {
|
||||
@@ -685,6 +687,8 @@ async function generateCallback(args, value) {
|
||||
// Prevent generate recursion
|
||||
$('#send_textarea').val('').trigger('input');
|
||||
const lock = isTrueBoolean(args?.lock);
|
||||
const as = args?.as || 'system';
|
||||
const quietToLoud = as === 'char';
|
||||
|
||||
try {
|
||||
if (lock) {
|
||||
@@ -693,7 +697,7 @@ async function generateCallback(args, value) {
|
||||
|
||||
setEphemeralStopStrings(resolveVariable(args?.stop));
|
||||
const name = args?.name;
|
||||
const result = await generateQuietPrompt(value, false, false, '', name);
|
||||
const result = await generateQuietPrompt(value, quietToLoud, false, '', name);
|
||||
return result;
|
||||
} finally {
|
||||
if (lock) {
|
||||
|
@@ -48,14 +48,18 @@
|
||||
<li><tt>{{maxPrompt}}</tt> – max allowed prompt length in tokens = (context size - response length)</li>
|
||||
<li><tt>{{exampleSeparator}}</tt> – context template example dialogues separator</li>
|
||||
<li><tt>{{chatStart}}</tt> – context template chat start line</li>
|
||||
<li><tt>{{instructSystem}}</tt> – instruct system prompt</li>
|
||||
<li><tt>{{instructSystemPrefix}}</tt> – instruct system prompt prefix sequence</li>
|
||||
<li><tt>{{instructSystemSuffix}}</tt> – instruct system prompt suffix sequence</li>
|
||||
<li><tt>{{instructInput}}</tt> – instruct user input sequence</li>
|
||||
<li><tt>{{instructOutput}}</tt> – instruct assistant output sequence</li>
|
||||
<li><tt>{{instructFirstOutput}}</tt> – instruct assistant first output sequence</li>
|
||||
<li><tt>{{instructLastOutput}}</tt> – instruct assistant last output sequence</li>
|
||||
<li><tt>{{instructSeparator}}</tt> – instruct turn separator sequence</li>
|
||||
<li><tt>{{instructSystemPrompt}}</tt> – instruct system prompt</li>
|
||||
<li><tt>{{instructSystemPromptPrefix}}</tt> – instruct system prompt prefix sequence</li>
|
||||
<li><tt>{{instructSystemPromptSuffix}}</tt> – instruct system prompt suffix sequence</li>
|
||||
<li><tt>{{instructUserPrefix}}</tt> – instruct user prefix sequence</li>
|
||||
<li><tt>{{instructUserSuffix}}</tt> – instruct user suffix sequence</li>
|
||||
<li><tt>{{instructAssistantPrefix}}</tt> – instruct assistant prefix sequence</li>
|
||||
<li><tt>{{instructAssistantSuffix}}</tt> – instruct assistant suffix sequence</li>
|
||||
<li><tt>{{instructFirstAssistantPrefix}}</tt> – instruct assistant first output sequence</li>
|
||||
<li><tt>{{instructLastAssistantPrefix}}</tt> – instruct assistant last output sequence</li>
|
||||
<li><tt>{{instructSystemPrefix}}</tt> – instruct system message prefix sequence</li>
|
||||
<li><tt>{{instructSystemSuffix}}</tt> – instruct system message suffix sequence</li>
|
||||
<li><tt>{{instructUserFiller}}</tt> – instruct first user message filler</li>
|
||||
<li><tt>{{instructStop}}</tt> – instruct stop sequence</li>
|
||||
</ul>
|
||||
<div>
|
||||
|
@@ -38,7 +38,7 @@ export const textgen_types = {
|
||||
OPENROUTER: 'openrouter',
|
||||
};
|
||||
|
||||
const { MANCER, APHRODITE, TABBY, TOGETHERAI, OOBA, OLLAMA, LLAMACPP, INFERMATICAI, DREAMGEN, OPENROUTER } = textgen_types;
|
||||
const { MANCER, APHRODITE, TABBY, TOGETHERAI, OOBA, OLLAMA, LLAMACPP, INFERMATICAI, DREAMGEN, OPENROUTER, KOBOLDCPP } = textgen_types;
|
||||
|
||||
const LLAMACPP_DEFAULT_ORDER = [
|
||||
'top_k',
|
||||
@@ -1047,6 +1047,10 @@ export function getTextGenGenerationData(finalPrompt, maxTokens, isImpersonate,
|
||||
//'prompt_logprobs': settings.prompt_log_probs_aphrodite,
|
||||
};
|
||||
|
||||
if (settings.type === KOBOLDCPP) {
|
||||
params.grammar = settings.grammar_string;
|
||||
}
|
||||
|
||||
if (settings.type === MANCER) {
|
||||
params.n = canMultiSwipe ? settings.n : 1;
|
||||
params.epsilon_cutoff /= 1000;
|
||||
|
Reference in New Issue
Block a user