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?

7845994315e80da00c87c9ba109c938ba509dd22

Cohee <18619528+Cohee1207@users.noreply.github.com>

Signed
2 files changed, +98 -7Showing whitespace changes
public/scripts/extensions/connection-manager/index.js+25 -4
@@ -16,12 +16,19 @@ import { t } from '../../i18n.js';
1616
1717const MODULE_NAME = 'connection-manager';
1818const NONE = '<None>';
19+const EMPTY = '<Empty>';
1920
2021const DEFAULT_SETTINGS = {
2122 profiles: [],
2223 selectedProfile: null,
2324};
2425
26+// Commands that can record an empty value into the profile
27+const ALLOW_EMPTY = [
28+ 'stop-strings',
29+ 'start-reply-with',
30+];
31+
2532const CC_COMMANDS = [
2633 'api',
2734 'preset',
@@ -31,6 +38,7 @@ const CC_COMMANDS = [
3138 'model',
3239 'proxy',
3340 'stop-strings',
41+ 'start-reply-with',
3442];
3543
3644const TC_COMMANDS = [
@@ -45,6 +53,7 @@ const TC_COMMANDS = [
4553 'instruct-state',
4654 'tokenizer',
4755 'stop-strings',
56+ 'start-reply-with',
4857];
4958
5059const FANCY_NAMES = {
@@ -60,6 +69,7 @@ const FANCY_NAMES = {
6069 'context': 'Context Template',
6170 'tokenizer': 'Tokenizer',
6271 'stop-strings': 'Custom Stopping Strings',
72+ 'start-reply-with': 'Start Reply With',
6373};
6474
6575/**
@@ -107,6 +117,7 @@ class ConnectionManagerSpinner {
107117/**
108118 * Get named arguments for the command callback.
109119 * @param {object} [args] Additional named arguments
120+ * @param {string} [args.force] Whether to force setting the value
110121 * @returns {object} Named arguments
111122 */
112123function getNamedArguments(args = {}) {
@@ -142,6 +153,7 @@ const profilesProvider = () => [
142153 * @property {string} [instruct-state] Instruct Mode
143154 * @property {string} [tokenizer] Tokenizer
144155 * @property {string} [stop-strings] Custom Stopping Strings
156+ * @property {string} [start-reply-with] Start Reply With
145157 * @property {string[]} [exclude] Commands to exclude
146158 */
147159
@@ -186,9 +198,10 @@ async function readProfileFromCommands(mode, profile, cleanUp = false) {
186198 continue;
187199 }
188200
201+ const allowEmpty = ALLOW_EMPTY.includes(command);
189202 const args = getNamedArguments();
190203 const result = await SlashCommandParser.commands[command].callback(args, '');
191- if (result) {
204+ if (result || (allowEmpty && result === '')) {
192205 profile[command] = result;
193206 continue;
194207 }
@@ -309,7 +322,14 @@ async function deleteConnectionProfile() {
309322 */
310323function makeFancyProfile(profile) {
311324 return Object.entries(FANCY_NAMES).reduce((acc, [key, value]) => {
312- if (!profile[key]) return acc;
325+ const allowEmpty = ALLOW_EMPTY.includes(key);
326+ if (!profile[key]) {
327+ if (profile[key] === '' && allowEmpty) {
328+ acc[value] = EMPTY;
329+ }
330+ return acc;
331+ }
332+
313333 acc[value] = profile[key];
314334 return acc;
315335 }, {});
@@ -339,11 +359,12 @@ async function applyConnectionProfile(profile) {
339359 }
340360
341361 const argument = profile[command];
342- if (!argument) {
362+ const allowEmpty = ALLOW_EMPTY.includes(command);
363+ if (!argument && !(allowEmpty && argument === '')) {
343364 continue;
344365 }
345366 try {
346367 const args = getNamedArguments(allowEmpty ? { force: 'true' } : {});
347368 await SlashCommandParser.commands[command].callback(args, argument);
348369 } catch (error) {
349370 console.error(`Failed to execute command: ${command} ${argument}`, error);
public/scripts/power-user.js+73 -3
@@ -4134,16 +4134,27 @@ $(document).ready(() => {
41344134 helpString: `
41354135 <div>
41364136 Sets a list of custom stopping strings. Gets the list if no value is provided.
4137+ Use a "force" argument to force set an empty value.
41374138 </div>
41384139 <div>
41394140 <strong>Examples:</strong>
41404141 </div>
41414142 <ul>
4143+ <li>Force set an empty value: <pre><code class="language-stscript">/stop-strings force="true" {{noop}}</code></pre></li>
41424144 <li>Value must be a JSON-serialized array: <pre><code class="language-stscript">/stop-strings ["goodbye", "farewell"]</code></pre></li>
41434145 <li>Pipe characters must be escaped with a backslash: <pre><code class="language-stscript">/stop-strings ["left\\|right"]</code></pre></li>
41444146 </ul>
41454147 `,
41464148 returns: ARGUMENT_TYPE.LIST,
4149+ namedArgumentList: [
4150+ SlashCommandNamedArgument.fromProps({
4151+ name: 'force',
4152+ description: 'force set a value if empty',
4153+ typeList: [ARGUMENT_TYPE.BOOLEAN],
4154+ defaultValue: 'false',
4155+ enumList: commonEnumProviders.boolean('trueFalse')(),
4156+ }),
4157+ ],
41474158 unnamedArgumentList: [
41484159 SlashCommandArgument.fromProps({
41494160 description: 'list of strings',
@@ -4152,8 +4163,20 @@ $(document).ready(() => {
41524163 isRequired: false,
41534164 }),
41544165 ],
41554166 callback: (_args, value) => {
41564167 ifconst force = isTrueBoolean(String(valueargs?.force ?? '').trim(false)) {;
4168+ value = String(value ?? '').trim();
4169+
4170+ // Skip processing if no value and not forced
4171+ if (!force && !value) {
4172+ return power_user.custom_stopping_strings;
4173+ }
4174+
4175+ // Use empty array for forced empty value
4176+ if (force && !value) {
4177+ value = JSON.stringify([]);
4178+ }
4179+
41574180 const parsedValue = ((x) => { try { return JSON.parse(x.toString()); } catch { return null; } })(value);
41584181 if (!parsedValue || !Array.isArray(parsedValue)) {
41594182 throw new Error('Invalid list format. The value must be a JSON-serialized array of strings.');
@@ -4164,9 +4187,56 @@ $(document).ready(() => {
41644187 power_user.custom_stopping_strings = JSON.stringify(parsedValue);
41654188 $('#custom_stopping_strings').val(power_user.custom_stopping_strings);
41664189 saveSettingsDebounced();
4167- }
41684190
41694191 return power_user.custom_stopping_strings;
41704192 },
41714193 }));
4194+ SlashCommandParser.addCommandObject(SlashCommand.fromProps({
4195+ name: 'start-reply-with',
4196+ helpString: `
4197+ <div>
4198+ Sets a "Start Reply With". Gets the current value if no value is provided.
4199+ Use a "force" argument to force set an empty value.
4200+ </div>
4201+ <div>
4202+ <strong>Examples:</strong>
4203+ </div>
4204+ <ul>
4205+ <li>Set the field value: <pre><code class="language-stscript">/start-reply-with Sure!</code></pre></li>
4206+ <li>Force set an empty value: <pre><code class="language-stscript">/start-reply-with force="true" {{noop}}</code></pre></li>
4207+ </ul>
4208+ `,
4209+ namedArgumentList: [
4210+ SlashCommandNamedArgument.fromProps({
4211+ name: 'force',
4212+ description: 'force set a value if empty',
4213+ typeList: [ARGUMENT_TYPE.BOOLEAN],
4214+ defaultValue: 'false',
4215+ enumList: commonEnumProviders.boolean('trueFalse')(),
4216+ }),
4217+ ],
4218+ unnamedArgumentList:[
4219+ SlashCommandArgument.fromProps({
4220+ description: 'value',
4221+ typeList: [ARGUMENT_TYPE.STRING],
4222+ acceptsMultiple: false,
4223+ isRequired: false,
4224+ }),
4225+ ],
4226+ callback: (args, value) => {
4227+ const force = isTrueBoolean(String(args?.force ?? false));
4228+ value = String(value ?? '').trim();
4229+
4230+ // Skip processing if no value and not forced
4231+ if (!force && !value) {
4232+ return power_user.user_prompt_bias;
4233+ }
4234+
4235+ power_user.user_prompt_bias = value;
4236+ $('#start_reply_with').val(power_user.user_prompt_bias);
4237+ saveSettingsDebounced();
4238+
4239+ return power_user.user_prompt_bias;
4240+ },
4241+ }));
41724242});