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:
Cohee
2025-03-09 16:55:49 +02:00
committed by GitHub
parent d94c301b10
commit 7845994315
2 changed files with 107 additions and 16 deletions

View File

@@ -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);

View File

@@ -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;
},
}));
}); });