Feature: Enhanced Branch and Checkpoint Naming (#4993) * Add customizable name builder and max tries to getUniqueName utility - Add optional `nameBuilder` parameter to allow custom name formatting - Add `maxTries` parameter to client-side version to prevent infinite loops - Update JSDoc with new optional parameters - Use nullish coalescing to set default name builder function - Default name builder maintains existing "${baseName} (${i})" format * Add startIndex option to getUniqueName utility for flexible name generation * Enhance branch creation to use current chat name and ensure unique names - Import `getCurrentChatDetails` from script.js - Replace hardcoded branch name format with current chat name as base - Add custom `buildBranchName` function to format branch names with suffix - Strip existing " - Branch #N" suffixes before generating new branch name - Use `getUniqueName` utility with `nameBuilder` to ensure unique branch names - Remove unused `mainChat` variable and replace with `mainChatName` * Enhance checkpoint creation to use current chat name and suggest unique names - Import `getCurrentChatDetails` from script.js to get current chat name - Add custom `buildCheckpointName` function to format checkpoint names with suffix - Strip existing " - Checkpoint #N" suffixes before generating new checkpoint name - Use `getUniqueName` utility with `nameBuilder` to ensure unique checkpoint names - Pass `suggestedName` to template and popup input for better UX - Replace manual loop with `getUniqueName` * Remove automatic timestamp suffix from bookmark names * Fix getUniqueName utility to correctly handle maxTries and return null on failure * shut up copilot * Strip legacy bookmark and branch name prefixes when generating unique names - Add removal of old "Checkpoint #N - " prefix format in `buildCheckpointName` - Add removal of old "Branch #N - " prefix format in `buildBranchName` - Change `cleanName` from const to let to allow multiple replacements - Ensures clean base names regardless of legacy or current naming format

491f2a3afdbb5b62f091f59a973760bb9cfd6d51

Wolfsblvt <wolfsblvt@gmail.com>

Signed
3 files changed, +71 -31Showing whitespace changes
public/scripts/bookmarks.js+33 -13
@@ -12,6 +12,7 @@ import {
1212 saveChatConditional,
1313 saveItemizedPrompts,
1414 setActiveGroup,
15+ getCurrentChatDetails,
1516} from '../script.js';
1617import { humanizedDateTime } from './RossAscends-mods.js';
1718import {
@@ -81,24 +82,29 @@ async function getExistingChatNames() {
8182}
8283
8384async function getBookmarkName({ isReplace = false, forceName = null } = {}) {
8485 const chatNamesmainChatName = await getExistingChatNames(getCurrentChatDetails()).sessionName;
8586
86- const body = await renderTemplateAsync('createCheckpoint', { isReplace: isReplace });
87+ function buildCheckpointName(name, i) {
87- let name = forceName ?? await Popup.show.input('Create Checkpoint', body);
88+ // Strip off existing suffixes, then build new name
89+ let cleanName = name.replace(new RegExp(` - ${bookmarkNameToken}\\d+$`), '');
90+ // Strip off legacy old name prefix too
91+ cleanName = name.replace(new RegExp(`^${bookmarkNameToken}\\d+ - `), '');
92+ return `${cleanName} - ${bookmarkNameToken}${i}`;
93+ }
94+ const existingChats = await getExistingChatNames();
95+ const suggestedName = getUniqueName(mainChatName, (x) => existingChats.includes(x), { nameBuilder: buildCheckpointName });
96+
97+ const body = await renderTemplateAsync('createCheckpoint', { isReplace: isReplace, suggestedName: suggestedName });
98+ let name = forceName ?? await Popup.show.input('Create Checkpoint', body, suggestedName);
8899 // Special handling for confirmed empty input (=> auto-generate name)
89100 if (name === '') {
90- for (let i = chatNames.length; i < 1000; i++) {
101+ name = suggestedName;
91- name = bookmarkNameToken + i;
92- if (!chatNames.includes(name)) {
93- break;
94- }
95- }
96102 }
97103 if (!name) {
98104 return null;
99105 }
100106
101- return `${name} - ${humanizedDateTime()}`;
107+ return name;
102108}
103109
104110function getMainChatName() {
@@ -170,9 +176,23 @@ export async function createBranch(mesId) {
170176 }
171177
172178 const lastMes = chat[mesId];
173- const mainChat = selected_group ? groups?.find(x => x.id == selected_group)?.chat_id : characters[this_chid].chat;
179+ const mainChatName = (getCurrentChatDetails()).sessionName;
174180 const newMetadata = { main_chat: mainChatmainChatName };
175- let name = `Branch #${mesId} - ${humanizedDateTime()}`;
181+
182+ function buildBranchName(name, i) {
183+ // Strip off existing suffixes, then build new name
184+ let cleanName = name.replace(/ - Branch #\d+$/, '');
185+ // Strip off legacy old name prefix too
186+ cleanName = name.replace(/^Branch #\d+ - /, '');
187+ return `${cleanName} - Branch #${i}`;
188+ }
189+ const existingChats = await getExistingChatNames();
190+ const name = getUniqueName(mainChatName, (x) => existingChats.includes(x), { nameBuilder: buildBranchName });
191+ if (!name) {
192+ console.error('Could not generate a unique branch name.');
193+ toastr.error('Could not generate a unique branch name.', 'Branch creation failed');
194+ return;
195+ }
176196
177197 if (selected_group) {
178198 await saveGroupBookmarkChat(selected_group, name, newMetadata, mesId);
public/scripts/utils.js+19 -9
@@ -679,18 +679,28 @@ export function isElementInViewport(el) {
679679
680680/**
681681 * Returns a name that is unique among the names that exist.
682682 * @param {string} namebaseName The name to check.
683683 * @param {{ (name: string): boolean; }} exists Function to check if name exists.
684684 * @returnsparam {stringObject} A[options] uniqueThe nameoptions.
685- */
685+ * @param {((baseName: string, i: number) => string)|null} [options.nameBuilder=null] Function to build the name.
686-export function getUniqueName(name, exists) {
686+ * Starts with the index provided by `startIndex` (default is 1). If not provided, uses "${baseName} (${i})".
687- let i = 1;
687+ * @param {number} [options.maxTries=1000] The maximum number of tries to find a unique name. Default is 1000.
688- let baseName = name;
688+ * @param {number} [options.startIndex=1] The index to start with when building the name. Default is 1.
689- while (exists(name)) {
689+ * When set to 0, the intention is to also check if the basename (without applied index) is free.
690- name = `${baseName} (${i})`;
690+ * @returns {string|null} A unique name. Null if no unique name could be found in `maxTries`.
691+ */
692+export function getUniqueName(baseName, exists, { nameBuilder = null, maxTries = 1000, startIndex = 1 } = {}) {
693+ nameBuilder ??= (baseName, i) => i === 0 ? baseName : `${baseName} (${i})`;
694+ let i = startIndex;
695+ let name;
696+ while (i < maxTries + startIndex) {
697+ name = nameBuilder(baseName, i);
698+ if (!exists(name)) {
699+ return name;
700+ }
691701 i++;
692702 }
693703 return namenull;
694704}
695705
696706/**
src/util.js+19 -9
@@ -580,18 +580,28 @@ export function clientRelativePath(root, inputPath) {
580580
581581/**
582582 * Returns a name that is unique among the names that exist.
583583 * @param {string} namebaseName The name to check.
584584 * @param {{ (name: string): boolean; }} exists Function to check if name exists.
585585 * @returnsparam {stringObject} A[options] uniqueThe nameoptions.
586- */
586+ * @param {((baseName: string, i: number) => string)|null} [options.nameBuilder=null] Function to build the name.
587-export function getUniqueName(name, exists) {
587+ * Starts with the index provided by `startIndex` (default is 1). If not provided, uses "${baseName} (${i})".
588- let i = 1;
588+ * @param {number} [options.maxTries=1000] The maximum number of tries to find a unique name. Default is 1000.
589- let baseName = name;
589+ * @param {number} [options.startIndex=1] The index to start with when building the name. Default is 1.
590- while (exists(name)) {
590+ * When set to 0, the intention is to also check if the basename (without applied index) is free.
591- name = `${baseName} (${i})`;
591+ * @returns {string|null} A unique name. Null if no unique name could be found in `maxTries`.
592+ */
593+export function getUniqueName(baseName, exists, { nameBuilder = null, maxTries = 1000, startIndex = 1 } = {}) {
594+ nameBuilder ??= (baseName, i) => i === 0 ? baseName : `${baseName} (${i})`;
595+ let i = startIndex;
596+ let name;
597+ while (i < maxTries + startIndex) {
598+ name = nameBuilder(baseName, i);
599+ if (!exists(name)) {
600+ return name;
601+ }
592602 i++;
593603 }
594604 return namenull;
595605}
596606
597607/**