mirror of
https://github.com/SillyTavern/SillyTavern.git
synced 2025-06-05 21:59:27 +02:00
Add 'start-reply-with' to Connection Profiles (#3632)
* Connection Profiles: Add support for 'start-reply-with' command and allow empty values for 'stop-strings' command * Add handling for empty profile values in makeFancyProfile function * Fix application of empty values * Handle undefined values * Improve argument validation * Replace || with && * I got it right this time, swear * Who wrote this?
This commit is contained in:
@@ -16,12 +16,19 @@ import { t } from '../../i18n.js';
|
|||||||
|
|
||||||
const MODULE_NAME = 'connection-manager';
|
const MODULE_NAME = 'connection-manager';
|
||||||
const NONE = '<None>';
|
const NONE = '<None>';
|
||||||
|
const EMPTY = '<Empty>';
|
||||||
|
|
||||||
const DEFAULT_SETTINGS = {
|
const DEFAULT_SETTINGS = {
|
||||||
profiles: [],
|
profiles: [],
|
||||||
selectedProfile: null,
|
selectedProfile: null,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Commands that can record an empty value into the profile
|
||||||
|
const ALLOW_EMPTY = [
|
||||||
|
'stop-strings',
|
||||||
|
'start-reply-with',
|
||||||
|
];
|
||||||
|
|
||||||
const CC_COMMANDS = [
|
const CC_COMMANDS = [
|
||||||
'api',
|
'api',
|
||||||
'preset',
|
'preset',
|
||||||
@@ -31,6 +38,7 @@ const CC_COMMANDS = [
|
|||||||
'model',
|
'model',
|
||||||
'proxy',
|
'proxy',
|
||||||
'stop-strings',
|
'stop-strings',
|
||||||
|
'start-reply-with',
|
||||||
];
|
];
|
||||||
|
|
||||||
const TC_COMMANDS = [
|
const TC_COMMANDS = [
|
||||||
@@ -45,6 +53,7 @@ const TC_COMMANDS = [
|
|||||||
'instruct-state',
|
'instruct-state',
|
||||||
'tokenizer',
|
'tokenizer',
|
||||||
'stop-strings',
|
'stop-strings',
|
||||||
|
'start-reply-with',
|
||||||
];
|
];
|
||||||
|
|
||||||
const FANCY_NAMES = {
|
const FANCY_NAMES = {
|
||||||
@@ -60,6 +69,7 @@ const FANCY_NAMES = {
|
|||||||
'context': 'Context Template',
|
'context': 'Context Template',
|
||||||
'tokenizer': 'Tokenizer',
|
'tokenizer': 'Tokenizer',
|
||||||
'stop-strings': 'Custom Stopping Strings',
|
'stop-strings': 'Custom Stopping Strings',
|
||||||
|
'start-reply-with': 'Start Reply With',
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -107,6 +117,7 @@ class ConnectionManagerSpinner {
|
|||||||
/**
|
/**
|
||||||
* Get named arguments for the command callback.
|
* Get named arguments for the command callback.
|
||||||
* @param {object} [args] Additional named arguments
|
* @param {object} [args] Additional named arguments
|
||||||
|
* @param {string} [args.force] Whether to force setting the value
|
||||||
* @returns {object} Named arguments
|
* @returns {object} Named arguments
|
||||||
*/
|
*/
|
||||||
function getNamedArguments(args = {}) {
|
function getNamedArguments(args = {}) {
|
||||||
@@ -142,6 +153,7 @@ const profilesProvider = () => [
|
|||||||
* @property {string} [instruct-state] Instruct Mode
|
* @property {string} [instruct-state] Instruct Mode
|
||||||
* @property {string} [tokenizer] Tokenizer
|
* @property {string} [tokenizer] Tokenizer
|
||||||
* @property {string} [stop-strings] Custom Stopping Strings
|
* @property {string} [stop-strings] Custom Stopping Strings
|
||||||
|
* @property {string} [start-reply-with] Start Reply With
|
||||||
* @property {string[]} [exclude] Commands to exclude
|
* @property {string[]} [exclude] Commands to exclude
|
||||||
*/
|
*/
|
||||||
|
|
||||||
@@ -186,9 +198,10 @@ async function readProfileFromCommands(mode, profile, cleanUp = false) {
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const allowEmpty = ALLOW_EMPTY.includes(command);
|
||||||
const args = getNamedArguments();
|
const args = getNamedArguments();
|
||||||
const result = await SlashCommandParser.commands[command].callback(args, '');
|
const result = await SlashCommandParser.commands[command].callback(args, '');
|
||||||
if (result) {
|
if (result || (allowEmpty && result === '')) {
|
||||||
profile[command] = result;
|
profile[command] = result;
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
@@ -309,7 +322,14 @@ async function deleteConnectionProfile() {
|
|||||||
*/
|
*/
|
||||||
function makeFancyProfile(profile) {
|
function makeFancyProfile(profile) {
|
||||||
return Object.entries(FANCY_NAMES).reduce((acc, [key, value]) => {
|
return Object.entries(FANCY_NAMES).reduce((acc, [key, value]) => {
|
||||||
if (!profile[key]) return acc;
|
const allowEmpty = ALLOW_EMPTY.includes(key);
|
||||||
|
if (!profile[key]) {
|
||||||
|
if (profile[key] === '' && allowEmpty) {
|
||||||
|
acc[value] = EMPTY;
|
||||||
|
}
|
||||||
|
return acc;
|
||||||
|
}
|
||||||
|
|
||||||
acc[value] = profile[key];
|
acc[value] = profile[key];
|
||||||
return acc;
|
return acc;
|
||||||
}, {});
|
}, {});
|
||||||
@@ -339,11 +359,12 @@ async function applyConnectionProfile(profile) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const argument = profile[command];
|
const argument = profile[command];
|
||||||
if (!argument) {
|
const allowEmpty = ALLOW_EMPTY.includes(command);
|
||||||
|
if (!argument && !(allowEmpty && argument === '')) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
const args = getNamedArguments();
|
const args = getNamedArguments(allowEmpty ? { force: 'true' } : {});
|
||||||
await SlashCommandParser.commands[command].callback(args, argument);
|
await SlashCommandParser.commands[command].callback(args, argument);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error(`Failed to execute command: ${command} ${argument}`, error);
|
console.error(`Failed to execute command: ${command} ${argument}`, error);
|
||||||
|
@@ -4134,16 +4134,27 @@ $(document).ready(() => {
|
|||||||
helpString: `
|
helpString: `
|
||||||
<div>
|
<div>
|
||||||
Sets a list of custom stopping strings. Gets the list if no value is provided.
|
Sets a list of custom stopping strings. Gets the list if no value is provided.
|
||||||
|
Use a "force" argument to force set an empty value.
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<strong>Examples:</strong>
|
<strong>Examples:</strong>
|
||||||
</div>
|
</div>
|
||||||
<ul>
|
<ul>
|
||||||
|
<li>Force set an empty value: <pre><code class="language-stscript">/stop-strings force="true" {{noop}}</code></pre></li>
|
||||||
<li>Value must be a JSON-serialized array: <pre><code class="language-stscript">/stop-strings ["goodbye", "farewell"]</code></pre></li>
|
<li>Value must be a JSON-serialized array: <pre><code class="language-stscript">/stop-strings ["goodbye", "farewell"]</code></pre></li>
|
||||||
<li>Pipe characters must be escaped with a backslash: <pre><code class="language-stscript">/stop-strings ["left\\|right"]</code></pre></li>
|
<li>Pipe characters must be escaped with a backslash: <pre><code class="language-stscript">/stop-strings ["left\\|right"]</code></pre></li>
|
||||||
</ul>
|
</ul>
|
||||||
`,
|
`,
|
||||||
returns: ARGUMENT_TYPE.LIST,
|
returns: ARGUMENT_TYPE.LIST,
|
||||||
|
namedArgumentList: [
|
||||||
|
SlashCommandNamedArgument.fromProps({
|
||||||
|
name: 'force',
|
||||||
|
description: 'force set a value if empty',
|
||||||
|
typeList: [ARGUMENT_TYPE.BOOLEAN],
|
||||||
|
defaultValue: 'false',
|
||||||
|
enumList: commonEnumProviders.boolean('trueFalse')(),
|
||||||
|
}),
|
||||||
|
],
|
||||||
unnamedArgumentList: [
|
unnamedArgumentList: [
|
||||||
SlashCommandArgument.fromProps({
|
SlashCommandArgument.fromProps({
|
||||||
description: 'list of strings',
|
description: 'list of strings',
|
||||||
@@ -4152,21 +4163,80 @@ $(document).ready(() => {
|
|||||||
isRequired: false,
|
isRequired: false,
|
||||||
}),
|
}),
|
||||||
],
|
],
|
||||||
callback: (_, value) => {
|
callback: (args, value) => {
|
||||||
if (String(value ?? '').trim()) {
|
const force = isTrueBoolean(String(args?.force ?? false));
|
||||||
const parsedValue = ((x) => { try { return JSON.parse(x.toString()); } catch { return null; } })(value);
|
value = String(value ?? '').trim();
|
||||||
if (!parsedValue || !Array.isArray(parsedValue)) {
|
|
||||||
throw new Error('Invalid list format. The value must be a JSON-serialized array of strings.');
|
// Skip processing if no value and not forced
|
||||||
}
|
if (!force && !value) {
|
||||||
parsedValue.forEach((item, index) => {
|
return power_user.custom_stopping_strings;
|
||||||
parsedValue[index] = String(item);
|
|
||||||
});
|
|
||||||
power_user.custom_stopping_strings = JSON.stringify(parsedValue);
|
|
||||||
$('#custom_stopping_strings').val(power_user.custom_stopping_strings);
|
|
||||||
saveSettingsDebounced();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Use empty array for forced empty value
|
||||||
|
if (force && !value) {
|
||||||
|
value = JSON.stringify([]);
|
||||||
|
}
|
||||||
|
|
||||||
|
const parsedValue = ((x) => { try { return JSON.parse(x.toString()); } catch { return null; } })(value);
|
||||||
|
if (!parsedValue || !Array.isArray(parsedValue)) {
|
||||||
|
throw new Error('Invalid list format. The value must be a JSON-serialized array of strings.');
|
||||||
|
}
|
||||||
|
parsedValue.forEach((item, index) => {
|
||||||
|
parsedValue[index] = String(item);
|
||||||
|
});
|
||||||
|
power_user.custom_stopping_strings = JSON.stringify(parsedValue);
|
||||||
|
$('#custom_stopping_strings').val(power_user.custom_stopping_strings);
|
||||||
|
saveSettingsDebounced();
|
||||||
|
|
||||||
return power_user.custom_stopping_strings;
|
return power_user.custom_stopping_strings;
|
||||||
},
|
},
|
||||||
}));
|
}));
|
||||||
|
SlashCommandParser.addCommandObject(SlashCommand.fromProps({
|
||||||
|
name: 'start-reply-with',
|
||||||
|
helpString: `
|
||||||
|
<div>
|
||||||
|
Sets a "Start Reply With". Gets the current value if no value is provided.
|
||||||
|
Use a "force" argument to force set an empty value.
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<strong>Examples:</strong>
|
||||||
|
</div>
|
||||||
|
<ul>
|
||||||
|
<li>Set the field value: <pre><code class="language-stscript">/start-reply-with Sure!</code></pre></li>
|
||||||
|
<li>Force set an empty value: <pre><code class="language-stscript">/start-reply-with force="true" {{noop}}</code></pre></li>
|
||||||
|
</ul>
|
||||||
|
`,
|
||||||
|
namedArgumentList: [
|
||||||
|
SlashCommandNamedArgument.fromProps({
|
||||||
|
name: 'force',
|
||||||
|
description: 'force set a value if empty',
|
||||||
|
typeList: [ARGUMENT_TYPE.BOOLEAN],
|
||||||
|
defaultValue: 'false',
|
||||||
|
enumList: commonEnumProviders.boolean('trueFalse')(),
|
||||||
|
}),
|
||||||
|
],
|
||||||
|
unnamedArgumentList:[
|
||||||
|
SlashCommandArgument.fromProps({
|
||||||
|
description: 'value',
|
||||||
|
typeList: [ARGUMENT_TYPE.STRING],
|
||||||
|
acceptsMultiple: false,
|
||||||
|
isRequired: false,
|
||||||
|
}),
|
||||||
|
],
|
||||||
|
callback: (args, value) => {
|
||||||
|
const force = isTrueBoolean(String(args?.force ?? false));
|
||||||
|
value = String(value ?? '').trim();
|
||||||
|
|
||||||
|
// Skip processing if no value and not forced
|
||||||
|
if (!force && !value) {
|
||||||
|
return power_user.user_prompt_bias;
|
||||||
|
}
|
||||||
|
|
||||||
|
power_user.user_prompt_bias = value;
|
||||||
|
$('#start_reply_with').val(power_user.user_prompt_bias);
|
||||||
|
saveSettingsDebounced();
|
||||||
|
|
||||||
|
return power_user.user_prompt_bias;
|
||||||
|
},
|
||||||
|
}));
|
||||||
});
|
});
|
||||||
|
Reference in New Issue
Block a user