diff --git a/public/scripts/extensions/connection-manager/index.js b/public/scripts/extensions/connection-manager/index.js index f31e4b495..d56073b4c 100644 --- a/public/scripts/extensions/connection-manager/index.js +++ b/public/scripts/extensions/connection-manager/index.js @@ -16,12 +16,19 @@ import { t } from '../../i18n.js'; const MODULE_NAME = 'connection-manager'; const NONE = ''; +const EMPTY = ''; const DEFAULT_SETTINGS = { profiles: [], selectedProfile: null, }; +// Commands that can record an empty value into the profile +const ALLOW_EMPTY = [ + 'stop-strings', + 'start-reply-with', +]; + const CC_COMMANDS = [ 'api', 'preset', @@ -31,6 +38,7 @@ const CC_COMMANDS = [ 'model', 'proxy', 'stop-strings', + 'start-reply-with', ]; const TC_COMMANDS = [ @@ -45,6 +53,7 @@ const TC_COMMANDS = [ 'instruct-state', 'tokenizer', 'stop-strings', + 'start-reply-with', ]; const FANCY_NAMES = { @@ -60,6 +69,7 @@ const FANCY_NAMES = { 'context': 'Context Template', 'tokenizer': 'Tokenizer', 'stop-strings': 'Custom Stopping Strings', + 'start-reply-with': 'Start Reply With', }; /** @@ -107,6 +117,7 @@ class ConnectionManagerSpinner { /** * Get named arguments for the command callback. * @param {object} [args] Additional named arguments + * @param {string} [args.force] Whether to force setting the value * @returns {object} Named arguments */ function getNamedArguments(args = {}) { @@ -142,6 +153,7 @@ const profilesProvider = () => [ * @property {string} [instruct-state] Instruct Mode * @property {string} [tokenizer] Tokenizer * @property {string} [stop-strings] Custom Stopping Strings + * @property {string} [start-reply-with] Start Reply With * @property {string[]} [exclude] Commands to exclude */ @@ -186,9 +198,10 @@ async function readProfileFromCommands(mode, profile, cleanUp = false) { continue; } + const allowEmpty = ALLOW_EMPTY.includes(command); const args = getNamedArguments(); const result = await SlashCommandParser.commands[command].callback(args, ''); - if (result) { + if (result || (allowEmpty && result === '')) { profile[command] = result; continue; } @@ -309,7 +322,14 @@ async function deleteConnectionProfile() { */ function makeFancyProfile(profile) { 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]; return acc; }, {}); @@ -339,11 +359,12 @@ async function applyConnectionProfile(profile) { } const argument = profile[command]; - if (!argument) { + const allowEmpty = ALLOW_EMPTY.includes(command); + if (!argument && !(allowEmpty && argument === '')) { continue; } try { - const args = getNamedArguments(); + const args = getNamedArguments(allowEmpty ? { force: 'true' } : {}); await SlashCommandParser.commands[command].callback(args, argument); } catch (error) { console.error(`Failed to execute command: ${command} ${argument}`, error); diff --git a/public/scripts/power-user.js b/public/scripts/power-user.js index f114e6254..521868663 100644 --- a/public/scripts/power-user.js +++ b/public/scripts/power-user.js @@ -4134,16 +4134,27 @@ $(document).ready(() => { helpString: `
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.
Examples:
`, 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: [ SlashCommandArgument.fromProps({ description: 'list of strings', @@ -4152,21 +4163,80 @@ $(document).ready(() => { isRequired: false, }), ], - callback: (_, value) => { - if (String(value ?? '').trim()) { - 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(); + 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.custom_stopping_strings; } + // 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; }, })); + SlashCommandParser.addCommandObject(SlashCommand.fromProps({ + name: 'start-reply-with', + helpString: ` +
+ Sets a "Start Reply With". Gets the current value if no value is provided. + Use a "force" argument to force set an empty value. +
+
+ Examples: +
+ + `, + 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; + }, + })); });