"gradually replacing property access with a dot operator" (#4965) * "gradually replacing property access with a dot operator" https://github.com/SillyTavern/SillyTavern/pull/4963#discussion_r2663003561 (?<=\w|\])\['([a-zA-Z]\w+)'\] My regex found 593 matches across 47 files. Also, two typos. * Fixed chat[0].chat_metadata type error. https://github.com/SillyTavern/SillyTavern/pull/4965#discussion_r2664275854 * Fixed `swipedElementsDiv[0]?.getAnimations().filter((a) => a.animationName` type error. https://github.com/SillyTavern/SillyTavern/pull/4965#discussion_r2664274593 * Fixed config.MESSAGE_SANITIZE and config.MESSAGE_ALLOW_SYSTEM_UI type errors. https://github.com/SillyTavern/SillyTavern/pull/4965#discussion_r2664266271 * Fixed group.date_last_chat type error. https://github.com/SillyTavern/SillyTavern/pull/4965#discussion_r2664295652 * Reverted SlashCommandParser dot property access. https://github.com/SillyTavern/SillyTavern/pull/4965#discussion_r2664310931 * LLM fixed canUseNegativeLookbehind.result; type error. https://github.com/SillyTavern/SillyTavern/pull/4965#discussion_r2664314288 * Reverted chat-completions.js bodyParams and headers dot property access. https://github.com/SillyTavern/SillyTavern/pull/4965#discussion_r2664317848 https://github.com/SillyTavern/SillyTavern/pull/4965#discussion_r2664320088 https://github.com/SillyTavern/SillyTavern/pull/4965#discussion_r2664324438 * Reverted openai.js data dot property access. https://github.com/SillyTavern/SillyTavern/pull/4965#discussion_r2664326244 * Reverted tests/frontend/MacroEnvBuilder.e2e.js env.dynamicMacros dot property access. https://github.com/SillyTavern/SillyTavern/pull/4965#discussion_r2664330990 * Partially reverted `window` dot property access. * Reverted result.json() and settings dot property access. * Reverted google.js headers dot property access. * Fixed regex: `(?<=\w|\])\['([a-zA-Z]\w*)'\]` * Swapped window to globalThis with dot property access. * LLM fixed canUseNegativeLookbehind type. * Refactor property access * Consistency --------- Co-authored-by: user <user@exmaple.com> Co-authored-by: Cohee <18619528+Cohee1207@users.noreply.github.com>

8372e7bf9df1ff8e2df9f15765cd6f96d8689818

DeclineThyself <235079501+DeclineThyself@users.noreply.github.com>

Signed
44 files changed, +463 -443Ignore whitespace
public/global.d.ts+9 -0
@@ -38,6 +38,7 @@ declare global {
3838 avatar_url?: string;
3939 hideMutedSprites?: boolean;
4040 fav?: boolean;
41+ date_last_chat?: MessageTimestamp;
4142 }
4243
4344 interface ChatFile extends Array<ChatMessage> {
@@ -235,3 +236,11 @@ declare global {
235236
236237 type SwipeEvent = JQuery.TriggeredEvent<any, any, HTMLElement, HTMLElement>;
237238}
239+
240+//Overrides for public/scripts/chats.js
241+declare module 'dompurify' {
242+ interface Config {
243+ MESSAGE_SANITIZE?: boolean;
244+ MESSAGE_ALLOW_SYSTEM_UI?: boolean;
245+ }
246+}
public/script.js+198 -193
@@ -1176,8 +1176,8 @@ export async function getOneCharacter(avatarUrl) {
11761176
11771177 if (response.ok) {
11781178 const getData = await response.json();
11791179 getData['.name'] = DOMPurify.sanitize(getData['.name']);
11801180 getData['.chat'] = String(getData['.chat']);
11811181
11821182 const indexOf = characters.findIndex(x => x.avatar === avatarUrl);
11831183
@@ -1248,14 +1248,14 @@ export async function getCharacters() {
12481248 const getData = await response.json();
12491249 for (let i = 0; i < getData.length; i++) {
12501250 characters[i] = getData[i];
12511251 characters[i]['.name'] = DOMPurify.sanitize(characters[i]['.name']);
12521252
12531253 // For dropped-in cards
12541254 if (!characters[i]['.chat']) {
12551255 characters[i]['.chat'] = `${characters[i]['.name']} - ${humanizedDateTime()}`;
12561256 }
12571257
12581258 characters[i]['.chat'] = String(characters[i]['.chat']);
12591259 }
12601260
12611261 if (previousAvatar) {
@@ -1555,7 +1555,7 @@ export async function deleteMessage(id, swipeDeletionIndex = undefined, askConfi
15551555 chat.splice(id, 1);
15561556 messageElement.remove();
15571557
15581558 chat_metadata['.tainted'] = true;
15591559
15601560 const startIndex = [0, minId].includes(id) ? id : null;
15611561 updateViewMessageIds(startIndex);
@@ -1625,8 +1625,8 @@ export async function sendTextareaMessage() {
16251625 !textareaText &&
16261626 !selected_group &&
16271627 chat.length &&
16281628 !lastMessage['.is_user'] &&
16291629 !lastMessage['.is_system']
16301630 ) {
16311631 generateType = 'continue';
16321632 }
@@ -2406,7 +2406,7 @@ export function addCopyToCodeBlocks(messageElement) {
24062406 * @returns {void}
24072407 */
24082408export function addOneMessage(mes, { type = 'normal', insertAfter = null, scroll = true, insertBefore = null, forceId = null, showSwipes = true } = {}) {
24092409 let messageText = mes['.mes'];
24102410 const momentDate = timestampToMoment(mes.send_date);
24112411 const timestamp = momentDate.isValid() ? momentDate.format('LL LT') : '';
24122412
@@ -2425,8 +2425,8 @@ export function addOneMessage(mes, { type = 'normal', insertAfter = null, scroll
24252425 const isSystem = mes.is_system;
24262426 const title = mes.title;
24272427
24282428 //for non-user mesagesmessages
24292429 if (!mes['.is_user']) {
24302430 if (mes.force_avatar) {
24312431 avatarImg = mes.force_avatar;
24322432 } else if (this_chid === undefined) {
@@ -2442,9 +2442,9 @@ export function addOneMessage(mes, { type = 'normal', insertAfter = null, scroll
24422442 //if messge is from sytem, use the name provided in the message JSONL to proceed,
24432443 //if not system message, use name2 (char's name) to proceed
24442444 //characterName = mes.is_system || mes.force_avatar ? mes.name : name2;
24452445 } else if (mes['.is_user'] && mes['.force_avatar']) {
24462446 // Special case for persona images.
24472447 avatarImg = mes['.force_avatar'];
24482448 }
24492449
24502450 // if mes.extra.uses_system_ui is true, set an override on the sanitizer options
@@ -3265,7 +3265,7 @@ export function getCharacterCardFieldsLazy({ chid = undefined } = {}) {
32653265 persona: () => baseChatReplace(power_user.persona_description?.trim()),
32663266 system: () => {
32673267 if (!character) return '';
32683268 const systemPrompt = chat_metadata['.system_prompt'] || character.data?.system_prompt || '';
32693269 return power_user.prefer_character_prompt ? baseChatReplace(systemPrompt.trim()) : '';
32703270 },
32713271 jailbreak: () => {
@@ -3295,13 +3295,13 @@ export function getCharacterCardFieldsLazy({ chid = undefined } = {}) {
32953295 scenario: () => {
32963296 if (groupCardsLazy) return groupCardsLazy.scenario;
32973297 if (!character) return '';
32983298 const scenarioText = chat_metadata['.scenario'] || character.scenario || '';
32993299 return baseChatReplace(scenarioText.trim());
33003300 },
33013301 mesExamples: () => {
33023302 if (groupCardsLazy) return groupCardsLazy.mesExamples;
33033303 if (!character) return '';
33043304 const exampleDialog = chat_metadata['.mes_example'] || character.mes_example || '';
33053305 return baseChatReplace(exampleDialog.trim());
33063306 },
33073307 };
@@ -3516,39 +3516,39 @@ class StreamingProcessor {
35163516 this.sendTextarea.value = processedText;
35173517 this.sendTextarea.dispatchEvent(new Event('input', { bubbles: true }));
35183518 } else {
35193519 const mesChanged = chat[messageId]['.mes'] !== processedText;
35203520 await this.#checkDomElements(messageId);
35213521 this.#updateMessageBlockVisibility();
35223522 const currentTime = new Date();
35233523 chat[messageId]['.mes'] = processedText;
35243524 chat[messageId]['.gen_started'] = this.timeStarted;
35253525 chat[messageId]['.gen_finished'] = currentTime;
35263526 if (!chat[messageId]['.extra']) {
35273527 chat[messageId]['.extra'] = {};
35283528 }
35293529 chat[messageId]['.extra']['.time_to_first_token'] = this.timeToFirstToken;
35303530
35313531 // Update reasoning
35323532 await this.reasoningHandler.process(messageId, mesChanged, this.promptReasoning);
35333533 processedText = chat[messageId]['.mes'];
35343534
35353535 // Token count update.
35363536 const tokenCountText = this.reasoningHandler.reasoning + processedText;
35373537 const currentTokenCount = isFinal && power_user.message_token_count_enabled ? await getTokenCountAsync(tokenCountText, 0) : 0;
35383538 if (currentTokenCount) {
35393539 chat[messageId]['.extra']['.token_count'] = currentTokenCount;
35403540 if (this.messageTokenCounterDom instanceof HTMLElement) {
35413541 this.messageTokenCounterDom.textContent = `${currentTokenCount}t`;
35423542 }
35433543 }
35443544
35453545 if ((this.type == 'swipe' || this.type === 'continue') && Array.isArray(chat[messageId]['.swipes'])) {
35463546 chat[messageId]['.swipes'][chat[messageId]['.swipe_id']] = processedText;
35473547 chat[messageId]['.swipe_info'][chat[messageId]['.swipe_id']] = {
35483548 'send_date': chat[messageId]['.send_date'],
35493549 'gen_started': chat[messageId]['.gen_started'],
35503550 'gen_finished': chat[messageId]['.gen_finished'],
35513551 'extra': structuredClone(chat[messageId]['.extra']),
35523552 };
35533553 }
35543554
@@ -3657,13 +3657,13 @@ class StreamingProcessor {
36573657
36583658 setFirstSwipe(messageId) {
36593659 if (this.type !== 'swipe' && this.type !== 'impersonate') {
36603660 if (Array.isArray(chat[messageId]['.swipes']) && chat[messageId]['.swipes'].length === 1 && chat[messageId]['.swipe_id'] === 0) {
36613661 chat[messageId]['.swipes'][0] = chat[messageId]['.mes'];
36623662 chat[messageId]['.swipe_info'][0] = {
36633663 'send_date': chat[messageId]['.send_date'],
36643664 'gen_started': chat[messageId]['.gen_started'],
36653665 'gen_finished': chat[messageId]['.gen_finished'],
36663666 'extra': structuredClone(chat[messageId]['.extra']),
36673667 };
36683668 }
36693669 }
@@ -4143,7 +4143,7 @@ export async function Generate(type, { automatic_trigger, force_name2, quiet_pro
41434143 // Hide swipes if not in a dry run.
41444144 hideSwipeButtons();
41454145 // If generated any message, set the flag to indicate it can't be recreated again.
41464146 chat_metadata['.tainted'] = true;
41474147 }
41484148
41494149 if (selected_group && !is_group_generating) {
@@ -4201,7 +4201,7 @@ export async function Generate(type, { automatic_trigger, force_name2, quiet_pro
42014201 $('#send_textarea').val('')[0].dispatchEvent(new Event('input', { bubbles: true }));
42024202 } else {
42034203 textareaText = '';
42044204 if (chat.length && lastMessage['.is_user']) {
42054205 //do nothing? why does this check exist?
42064206 }
42074207 else if (type !== 'quiet' && type !== 'swipe' && !isImpersonate && !dryRun && chat.length) {
@@ -4215,13 +4215,13 @@ export async function Generate(type, { automatic_trigger, force_name2, quiet_pro
42154215
42164216 // Rewrite the generation timer to account for the time passed for all the continuations.
42174217 if (isContinue && chat.length) {
42184218 const prevFinished = lastMessage['.gen_finished'];
42194219 const prevStarted = lastMessage['.gen_started'];
42204220
42214221 if (prevFinished && prevStarted) {
42224222 const timePassed = Number(prevFinished) - Number(prevStarted);
42234223 generation_started = new Date(Date.now() - timePassed);
42244224 lastMessage['.gen_started'] = generation_started;
42254225 }
42264226 }
42274227
@@ -4903,7 +4903,7 @@ export async function Generate(type, { automatic_trigger, force_name2, quiet_pro
49034903 let thisPromptContextSize = await getTokenCountAsync(prompt, power_user.token_padding);
49044904
49054905 if (thisPromptContextSize > this_max_context) { //if the prepared prompt is larger than the max context size...
49064906 if (count_exm_add > 0) { // ..and we have example mesagesmessages..
49074907 count_exm_add--; // remove the example messages...
49084908 await checkPromptSize(); // and try agin...
49094909 } else if (mesSend.length > 0) { // if the chat history is longer than 0
@@ -5631,7 +5631,7 @@ export function getBiasStrings(textareaText, type) {
56315631function formatMessageHistoryItem(chatItem, isInstruct, forceOutputSequence) {
56325632 const isNarratorType = chatItem?.extra?.type === system_message_types.NARRATOR;
56335633 const characterName = chatItem?.name ? chatItem.name : name2;
56345634 const itemName = chatItem.is_user ? chatItem['.name'] : characterName;
56355635 const shouldPrependName = !isNarratorType;
56365636
56375637 // If this symbol flag is set, completely ignore the message.
@@ -5700,7 +5700,7 @@ export async function sendMessageAsUser(messageText, messageBias, insertAt = nul
57005700 await populateFileAttachment(message);
57015701 statMesProcess(message, 'user', characters, this_chid, '');
57025702
57035703 chat_metadata['.tainted'] = true;
57045704
57055705 if (typeof insertAt === 'number' && insertAt >= 0 && insertAt <= chat.length) {
57065706 chat.splice(insertAt, 0, message);
@@ -5851,7 +5851,7 @@ function setInContextMessages(msgInContextCount, type) {
58515851
58525852 // Update last id to chat. No metadata save on purpose, gets hopefully saved via another call
58535853 const lastMessageId = Math.max(0, chat.length - msgInContextCount);
58545854 chat_metadata['.lastInContextMessageId'] = lastMessageId;
58555855}
58565856
58575857/**
@@ -6392,18 +6392,18 @@ export async function saveReply({ type, getMessage, fromStreaming = false, title
63926392
63936393 const lastMessage = chat[chat.length - 1];
63946394
63956395 if (type != 'append' && type != 'continue' && type != 'appendFinal' && chat.length && (lastMessage['.swipe_id'] === undefined ||
63966396 lastMessage['.is_user'])) {
63976397 type = 'normal';
63986398 }
63996399
64006400 if (chat.length && (!lastMessage['.extra'] || typeof lastMessage['.extra'] !== 'object')) {
64016401 lastMessage['.extra'] = {};
64026402 }
64036403
64046404 // Coerce null/undefined to empty string
64056405 if (chat.length && !lastMessage['.extra']['.reasoning']) {
64066406 lastMessage['.extra']['.reasoning'] = '';
64076407 }
64086408
64096409 if (!reasoning) {
@@ -6413,70 +6413,70 @@ export async function saveReply({ type, getMessage, fromStreaming = false, title
64136413 let oldMessage = '';
64146414 const generationFinished = new Date();
64156415 if (type === 'swipe') {
64166416 oldMessage = lastMessage['.mes'];
64176417 lastMessage['.swipes'].length++;
64186418 if (lastMessage['.swipe_id'] === lastMessage['.swipes'].length - 1) {
64196419 lastMessage['.title'] = title;
64206420 lastMessage['.mes'] = getMessage;
64216421 lastMessage['.gen_started'] = generation_started;
64226422 lastMessage['.gen_finished'] = generationFinished;
64236423 lastMessage['.send_date'] = getMessageTimeStamp();
64246424 lastMessage['.extra']['.api'] = getGeneratingApi();
64256425 lastMessage['.extra']['.model'] = getGeneratingModel();
64266426 lastMessage['.extra']['.reasoning'] = reasoning;
64276427 lastMessage['.extra']['.reasoning_duration'] = null;
64286428 lastMessage['.extra']['.reasoning_signature'] = reasoningSignature;
64296429 await processImageAttachment(lastMessage, { imageUrls });
64306430 if (power_user.message_token_count_enabled) {
64316431 const tokenCountText = (reasoning || '') + lastMessage['.mes'];
64326432 lastMessage['.extra']['.token_count'] = await getTokenCountAsync(tokenCountText, 0);
64336433 }
64346434 const chat_id = (chat.length - 1);
64356435 !fromStreaming && await eventSource.emit(event_types.MESSAGE_RECEIVED, chat_id, type);
64366436 addOneMessage(chat[chat_id], { type: 'swipe' });
64376437 !fromStreaming && await eventSource.emit(event_types.CHARACTER_MESSAGE_RENDERED, chat_id, type);
64386438 } else {
64396439 lastMessage['.mes'] = getMessage;
64406440 }
64416441 } else if (type === 'append' || type === 'continue') {
64426442 console.debug('Trying to append.');
64436443 oldMessage = lastMessage['.mes'];
64446444 lastMessage['.title'] = title;
64456445 lastMessage['.mes'] += getMessage;
64466446 lastMessage['.gen_started'] = generation_started;
64476447 lastMessage['.gen_finished'] = generationFinished;
64486448 lastMessage['.send_date'] = getMessageTimeStamp();
64496449 lastMessage['.extra']['.api'] = getGeneratingApi();
64506450 lastMessage['.extra']['.model'] = getGeneratingModel();
64516451 lastMessage['.extra']['.reasoning'] = reasoning;
64526452 lastMessage['.extra']['.reasoning_duration'] = null;
64536453 lastMessage['.extra']['.reasoning_signature'] = reasoningSignature;
64546454 await processImageAttachment(lastMessage, { imageUrls });
64556455 if (power_user.message_token_count_enabled) {
64566456 const tokenCountText = (reasoning || '') + lastMessage['.mes'];
64576457 lastMessage['.extra']['.token_count'] = await getTokenCountAsync(tokenCountText, 0);
64586458 }
64596459 const chat_id = (chat.length - 1);
64606460 !fromStreaming && await eventSource.emit(event_types.MESSAGE_RECEIVED, chat_id, type);
64616461 addOneMessage(chat[chat_id], { type: 'swipe' });
64626462 !fromStreaming && await eventSource.emit(event_types.CHARACTER_MESSAGE_RENDERED, chat_id, type);
64636463 } else if (type === 'appendFinal') {
64646464 oldMessage = lastMessage['.mes'];
64656465 console.debug('Trying to appendFinal.');
64666466 lastMessage['.title'] = title;
64676467 lastMessage['.mes'] = getMessage;
64686468 lastMessage['.gen_started'] = generation_started;
64696469 lastMessage['.gen_finished'] = generationFinished;
64706470 lastMessage['.send_date'] = getMessageTimeStamp();
64716471 lastMessage['.extra']['.api'] = getGeneratingApi();
64726472 lastMessage['.extra']['.model'] = getGeneratingModel();
64736473 lastMessage['.extra']['.reasoning'] += reasoning;
64746474 lastMessage['.extra']['.reasoning_signature'] = reasoningSignature;
64756475 await processImageAttachment(lastMessage, { imageUrls });
64766476 // We don't know if the reasoning duration extended, so we don't update it here on purpose.
64776477 if (power_user.message_token_count_enabled) {
64786478 const tokenCountText = (reasoning || '') + lastMessage['.mes'];
64796479 lastMessage['.extra']['.token_count'] = await getTokenCountAsync(tokenCountText, 0);
64806480 }
64816481 const chat_id = (chat.length - 1);
64826482 !fromStreaming && await eventSource.emit(event_types.MESSAGE_RECEIVED, chat_id, type);
@@ -6487,26 +6487,26 @@ export async function saveReply({ type, getMessage, fromStreaming = false, title
64876487 console.debug('entering chat update routine for non-swipe post');
64886488 const newMessage = {};
64896489 chat.push(newMessage);
64906490 newMessage['.extra'] = {};
64916491 newMessage['.name'] = name2;
64926492 newMessage['.is_user'] = false;
64936493 newMessage['.send_date'] = getMessageTimeStamp();
64946494 newMessage['.extra']['.api'] = getGeneratingApi();
64956495 newMessage['.extra']['.model'] = getGeneratingModel();
64966496 newMessage['.extra']['.reasoning'] = reasoning;
64976497 newMessage['.extra']['.reasoning_duration'] = null;
64986498 newMessage['.extra']['.reasoning_signature'] = reasoningSignature;
64996499 if (power_user.trim_spaces) {
65006500 getMessage = getMessage.trim();
65016501 }
65026502 newMessage['.mes'] = getMessage;
65036503 newMessage['.title'] = title;
65046504 newMessage['.gen_started'] = generation_started;
65056505 newMessage['.gen_finished'] = generationFinished;
65066506
65076507 if (power_user.message_token_count_enabled) {
65086508 const tokenCountText = (reasoning || '') + newMessage['.mes'];
65096509 newMessage['.extra']['.token_count'] = await getTokenCountAsync(tokenCountText, 0);
65106510 }
65116511
65126512 if (selected_group) {
@@ -6515,9 +6515,9 @@ export async function saveReply({ type, getMessage, fromStreaming = false, title
65156515 if (characters[this_chid].avatar != 'none') {
65166516 avatarImg = getThumbnailUrl('avatar', characters[this_chid].avatar);
65176517 }
65186518 newMessage['.force_avatar'] = avatarImg;
65196519 newMessage['.original_avatar'] = characters[this_chid].avatar;
65206520 newMessage['.extra']['.gen_id'] = group_generation_id;
65216521 }
65226522
65236523 await processImageAttachment(newMessage, { imageUrls });
@@ -6529,27 +6529,27 @@ export async function saveReply({ type, getMessage, fromStreaming = false, title
65296529 }
65306530
65316531 const item = chat[chat.length - 1];
65326532 if (item['.swipe_info'] === undefined) {
65336533 item['.swipe_info'] = [];
65346534 }
65356535 if (item['.swipe_id'] !== undefined) {
65366536 const swipeId = item['.swipe_id'];
65376537 item['.swipes'][swipeId] = item['.mes'];
65386538 item['.swipe_info'][swipeId] = {
65396539 send_date: item['.send_date'],
65406540 gen_started: item['.gen_started'],
65416541 gen_finished: item['.gen_finished'],
65426542 extra: structuredClone(item['.extra']),
65436543 };
65446544 } else {
65456545 item['.swipe_id'] = 0;
65466546 item['.swipes'] = [];
65476547 item['.swipes'][0] = item['.mes'];
65486548 item['.swipe_info'][0] = {
65496549 send_date: item['.send_date'],
65506550 gen_started: item['.gen_started'],
65516551 gen_finished: item['.gen_finished'],
65526552 extra: structuredClone(item['.extra']),
65536553 };
65546554 }
65556555
@@ -7155,7 +7155,7 @@ export async function saveChat({ chatName, withMetadata, mesId, force = false }
71557155 return;
71567156 }
71577157
71587158 characters[this_chid]['.date_last_chat'] = Date.now();
71597159
71607160 const trimmedChat = (mesId !== undefined && mesId >= 0 && mesId < chat.length)
71617161 ? chat.slice(0, Number(mesId) + 1)
@@ -7387,15 +7387,19 @@ export async function getChat() {
73877387 dataType: 'json',
73887388 contentType: 'application/json',
73897389 });
7390- if (response[0] !== undefined) {
7390+ if (Array.isArray(response) && response.length > 0) {
7391+ /** @type {ChatHeader} */
7392+ const chatHeader = response.shift();
7393+ chat_metadata = chatHeader?.chat_metadata ?? {};
73917394 chat.splice(0, chat.length, ...response);
7392- chat_metadata = chat[0]['chat_metadata'] ?? {};
7393-
7394- chat.shift();
73957395 chat.forEach(ensureMessageMediaIsArray);
7396+ } else {
7397+ // An empty/corrupted chat file
7398+ chat.splice(0, chat.length);
7399+ chat_metadata = {};
73967400 }
73977401 if (!chat_metadata['.integrity']) {
73987402 chat_metadata['.integrity'] = uuidv4();
73997403 }
74007404 await getChatResult();
74017405 eventSource.emit('chatLoaded', { detail: { id: this_chid, character: characters[this_chid] } });
@@ -7460,9 +7464,9 @@ function getFirstMessage() {
74607464 message.mes = swipes[0];
74617465 }
74627466
74637467 message['.swipe_id'] = 0;
74647468 message['.swipes'] = swipes;
74657469 message['.swipe_info'] = swipes.map(_ => ({
74667470 send_date: message.send_date,
74677471 gen_started: void 0,
74687472 gen_finished: void 0,
@@ -7476,7 +7480,7 @@ function getFirstMessage() {
74767480export async function openCharacterChat(file_name) {
74777481 await waitUntilCondition(() => !isChatSaving, debounce_timeout.extended, 10);
74787482 await clearChat();
74797483 characters[this_chid]['.chat'] = file_name;
74807484 chat.length = 0;
74817485 chat_metadata = {};
74827486 await getChat();
@@ -7862,7 +7866,7 @@ function updateMessage(div) {
78627866 const mes = chat[mesElement.attr('mesid')];
78637867
78647868 // editing old messages
78657869 mes['.extra'] ??= {};
78667870
78677871 let regexPlacement;
78687872 if (mes?.is_user) {
@@ -7893,10 +7897,10 @@ function updateMessage(div) {
78937897 if (bias) {
78947898 text = removeMacros(text);
78957899 }
78967900 mes['.mes'] = text;
78977901 if (mes['.swipe_id'] !== undefined) {
78987902 ensureSwipes(mes);
78997903 mes['.swipes'][mes['.swipe_id']] = text;
79007904 }
79017905
79027906 if (mes?.is_system || mes?.is_user || mes.extra?.type === system_message_types.NARRATOR) {
@@ -7905,7 +7909,7 @@ function updateMessage(div) {
79057909 mes.extra.bias = null;
79067910 }
79077911
79087912 chat_metadata['.tainted'] = true;
79097913
79107914 return { mesBlock, text, mes, bias };
79117915}
@@ -8019,7 +8023,7 @@ export async function messageEdit(editMessageId) {
80198023 * @param {number} [messageId=this_edit_mes_id]
80208024 */
80218025async function messageEditCancel(messageId = this_edit_mes_id) {
80228026 let text = chat[messageId]['.mes'];
80238027 let thisMesDiv;
80248028 // If this is the button then select it's parent. Otherwise, select by messageId.
80258029 if (this?.classList?.contains('mes_edit_cancel')) {
@@ -8167,7 +8171,7 @@ async function messageEditDone(div) {
81678171export async function getChatsFromFiles(data, isGroupChat) {
81688172 const context = getContext();
81698173 let chat_dict = {};
81708174 let chat_list = Object.values(data).sort((a, b) => a['.file_name'].localeCompare(b['.file_name'])).reverse();
81718175
81728176 let chat_promise = chat_list.map(({ file_name }) => {
81738177 return new Promise(async (res, rej) => {
@@ -8244,7 +8248,7 @@ export async function getPastCharacterChats(characterId = null) {
82448248 }
82458249
82468250 const chats = Object.values(data);
82478251 return chats.sort((a, b) => a['.file_name'].localeCompare(b['.file_name'])).reverse();
82488252}
82498253
82508254/**
@@ -8256,9 +8260,9 @@ export function getCurrentChatDetails() {
82568260 }
82578261
82588262 const group = selected_group ? groups.find(x => x.id === selected_group) : null;
82598263 const currentChat = selected_group ? group?.chat_id : characters[this_chid]['.chat'];
82608264 const displayName = selected_group ? group?.name : characters[this_chid].name;
82618265 const avatarImg = selected_group ? group?.avatar_url : getThumbnailUrl('avatar', characters[this_chid]['.avatar']);
82628266 return { sessionName: currentChat, group: group, characterName: displayName, avatarImgURL: avatarImg };
82638267}
82648268
@@ -8722,9 +8726,9 @@ export async function setCharacterSettingsOverrides() {
87228726 return;
87238727 }
87248728
87258729 const scenarioOverrideValue = chat_metadata['.scenario'] || '';
87268730 const exampleMessagesValue = chat_metadata['.mes_example'] || '';
87278731 const systemPromptValue = chat_metadata['.system_prompt'] || '';
87288732 const isGroup = !!selected_group;
87298733
87308734 const $template = $(await renderTemplateAsync('scenarioOverride'));
@@ -8771,9 +8775,9 @@ export async function setCharacterSettingsOverrides() {
87718775 allowVerticalScrolling: true,
87728776 });
87738777
87748778 chat_metadata['.scenario'] = pendingChanges.scenario;
87758779 chat_metadata['.mes_example'] = pendingChanges.examples;
87768780 chat_metadata['.system_prompt'] = pendingChanges.system_prompt;
87778781 await saveMetadata();
87788782}
87798783
@@ -9071,7 +9075,7 @@ export async function deleteSwipe(swipeId = null, messageId = chat.length - 1) {
90719075 // Select the next swipe, or the one before if it was the last one
90729076 const newSwipeId = Math.min(swipeId, message.swipes.length - 1);
90739077
90749078 chat_metadata['.tainted'] = true;
90759079
90769080 messageId = Number(messageId);
90779081 swipeId = Number(swipeId);
@@ -9585,7 +9589,7 @@ export async function createOrEditCharacter(e) {
95859589 !isNewChat &&
95869590 message.mes &&
95879591 !selected_group &&
95889592 !chat_metadata['.tainted'] &&
95899593 (chat.length === 0 || (chat.length === 1 && !chat[0].is_user && !chat[0].is_system));
95909594
95919595 if (shouldRegenerateMessage) {
@@ -9745,7 +9749,7 @@ export async function swipe(event, direction, { source, repeated, message = chat
97459749 }
97469750
97479751 //Clamp Id between swipes.
97489752 let clampedId = clamp(chat[mesId]['.swipe_id'], 0, Math.max(0, chat[mesId]['.swipes'].length - 1));
97499753
97509754 await updateSwipeCounter(mesId);
97519755 //Fallback.
@@ -9837,7 +9841,7 @@ export async function swipe(event, direction, { source, repeated, message = chat
98379841 */
98389842 async function loadFromSwipeId(mesId, newSwipeId) {
98399843 //Update the swipe_id.
98409844 chat[mesId]['.swipe_id'] = newSwipeId;
98419845
98429846 clearMessageData(chat[mesId]);
98439847
@@ -9909,7 +9913,8 @@ export async function swipe(event, direction, { source, repeated, message = chat
99099913 return true;
99109914 };
99119915 //Wait for the animation's end. https://developer.mozilla.org/en-US/docs/Web/API/Animation/finished
99129916 const animationanimations = swipedElementsDiv[0]?.getAnimations().filter((a) => a['animationName'] ==?? 'slide')[0];
9917+ const animation = animations.filter((a) => a instanceof globalThis.CSSAnimation && a.animationName == 'slide')[0];
99139918 try {
99149919 await Promise.race([animation?.finished, createTimeout(duration * 2, `The ${duration}ms swipe animation has not ended after ${duration * 2}ms. It has been skipped.`)].filter(Boolean));
99159920 } catch (error) {
@@ -9997,7 +10002,7 @@ export async function swipe(event, direction, { source, repeated, message = chat
999710002
999810003 const tokenCountText = (chat[mesId]?.extra?.reasoning || '') + chat[mesId].mes;
999910004 const tokenCount = await getTokenCountAsync(tokenCountText, 0);
1000010005 chat[mesId]['.extra']['.token_count'] = tokenCount;
1000110006 thisMesDiv.find('.tokenCounterDisplay').text(`${tokenCount}t`);
1000210007 }
1000310008 }
@@ -10036,20 +10041,20 @@ export async function swipe(event, direction, { source, repeated, message = chat
1003610041 // Make sure ad-hoc changes to extras are saved before swiping away
1003710042 syncMesToSwipe(mesId);
1003810043
1003910044 if (chat[mesId]['.swipe_id'] === undefined) { // if there is no swipe-message in the last spot of the chat array
1004010045 chat[mesId]['.swipe_id'] = 0; // set it to id 0
1004110046 chat[mesId]['.swipes'] = []; // empty the array
1004210047 chat[mesId]['.swipe_info'] = [];
1004310048 chat[mesId]['.swipes'][0] = chat[mesId]['.mes']; //assign swipe array with last chat[mesId] from chat
1004410049 chat[mesId]['.swipe_info'][0] = {
1004510050 'send_date': chat[mesId]['.send_date'],
1004610051 'gen_started': chat[mesId]['.gen_started'],
1004710052 'gen_finished': chat[mesId]['.gen_finished'],
1004810053 'extra': structuredClone(chat[mesId]['.extra']),
1004910054 };
1005010055 }
1005110056 // If the user is holding down the key and we're at the last or first swipe, don't do anything.
1005210057 let isLastSwipe = (direction === SWIPE_DIRECTION.RIGHT) ? (chat[mesId].swipe_id === Math.max(0, chat[mesId]['.swipes'].length - 1)) : chat[mesId].swipe_id === 0;
1005310058 if (source === SWIPE_SOURCE.KEYBOARD && repeated && isLastSwipe) {
1005410059 await endSwipe();
1005510060 return;
@@ -10065,12 +10070,12 @@ export async function swipe(event, direction, { source, repeated, message = chat
1006510070 if (forceSwipeId == null) newSwipeId--;
1006610071 //Loop to last swipe if negative.
1006710072 if (newSwipeId < 0) {
1006810073 newSwipeId = Math.max(0, chat[mesId]['.swipes'].length - 1);
1006910074 }
1007010075 //Limit swipe_id to swipes.
1007110076 if (newSwipeId > chat[mesId]['.swipes'].length - 1) {
1007210077 toastr.warning(`The swipe_id for message #${mesId} was ${newSwipeId}. It has been reset to ${chat[mesId]['.swipes'].length - 1}.`);
1007310078 chat[mesId]['.swipe_id'] = chat[mesId]['.swipes'].length - 1;
1007410079 await endSwipe();
1007510080 return;
1007610081 }
@@ -10085,24 +10090,24 @@ export async function swipe(event, direction, { source, repeated, message = chat
1008510090 //Minimum of zero.
1008610091 if (newSwipeId < 0) {
1008710092 toastr.warning(`The swipe_id for message #${mesId} was ${newSwipeId}. It has been reset to zero.`);
1008810093 chat[mesId]['.swipe_id'] = 0;
1008910094 await endSwipe();
1009010095 return;
1009110096 }
1009210097
1009310098 //If overswiping.
1009410099 if (newSwipeId >= chat[mesId]['.swipes'].length) {
1009510100 newSwipeId = chat[mesId]['.swipes'].length;
1009610101
1009710102 //Update the swipe_id.
1009810103 chat[mesId]['.swipe_id'] = newSwipeId;
1009910104
1010010105 const overswipe = getOverswipeBehavior(mesId);
1010110106
1010210107 //Cancel the generation.
1010310108 if (overswipe == OVERSWIPE_BEHAVIOR.NONE) {
1010410109 //Cancel swipe.
1010510110 chat[mesId]['.swipe_id'] = originalSwipeId;
1010610111 await endSwipe();
1010710112 return;
1010810113 }
@@ -11430,7 +11435,7 @@ jQuery(async function () {
1143011435 chatElement.find(`.mes[mesid="${this_del_mes}"]`).nextAll('div').remove();
1143111436 chatElement.find(`.mes[mesid="${this_del_mes}"]`).remove();
1143211437 chat.length = this_del_mes;
1143311438 chat_metadata['.tainted'] = true;
1143411439 await saveChatConditional();
1143511440 chatElement.scrollTop(chatElement[0].scrollHeight);
1143611441 await eventSource.emit(event_types.MESSAGE_DELETED, chat.length);
@@ -11517,7 +11522,7 @@ jQuery(async function () {
1151711522 if (this_chid !== undefined || selected_group || name2 === neutralCharacterName) {
1151811523 try {
1151911524 const messageId = $(this).closest('.mes').attr('mesid');
1152011525 const text = chat[messageId]['.mes'];
1152111526 await copyText(text);
1152211527 toastr.info('Copied!', '', { timeOut: 2000 });
1152311528 } catch (err) {
@@ -11544,8 +11549,8 @@ jQuery(async function () {
1154411549 let mes_edited = chatElement.find(`[mesid="${this_edit_mes_id}"]`).find('.mes_edit_done');
1154511550 if (Number(edit_mes_id) == chat.length - 1) { //if the generating swipe (...)
1154611551 let run_edit = true;
1154711552 if (chat[edit_mes_id]['.swipe_id'] !== undefined) {
1154811553 if (chat[edit_mes_id]['.swipes'].length === chat[edit_mes_id]['.swipe_id']) {
1154911554 run_edit = false;
1155011555 }
1155111556 }
@@ -11684,8 +11689,8 @@ jQuery(async function () {
1168411689 $(document).on('click', '.mes_edit_delete', async function (event, customData) {
1168511690 const fromSlashCommand = customData?.fromSlashCommand || false;
1168611691 const message = chat[this_edit_mes_id];
1168711692 const selectedSwipe = message['.swipe_id'] ?? undefined;
1168811693 const swipesArray = Array.isArray(message['.swipes']) ? message['.swipes'] : [];
1168911694 const canDeleteSwipe = power_user.confirm_message_delete && !fromSlashCommand && !message.is_user && swipesArray.length > 1 && this_edit_mes_id === chat.length - 1 && selectedSwipe !== undefined;
1169011695 await deleteMessage(Number(this_edit_mes_id), canDeleteSwipe ? selectedSwipe : undefined, power_user.confirm_message_delete && fromSlashCommand !== true);
1169111696 });
@@ -12102,7 +12107,7 @@ jQuery(async function () {
1210212107 });
1210312108
1210412109 // Remember the chat currently selected, so we can reload it after the replacement
1210512110 const currentChatFile = characters[this_chid]['.chat'];
1210612111 async function postReplace() {
1210712112 await openCharacterChat(currentChatFile);
1210812113 }
public/scripts/RossAscends-mods.js+4 -4
@@ -996,7 +996,7 @@ export function initRossMods() {
996996 }
997997
998998 //Enter to send when send_textarea in focus
999999 if (document.activeElement == hotkeyTargets['.send_textarea']) {
10001000 const sendOnEnter = shouldSendOnEnter();
10011001 if (!event.isComposing && !event.shiftKey && !event.ctrlKey && !event.altKey && event.key == 'Enter' && sendOnEnter) {
10021002 event.preventDefault();
@@ -1004,7 +1004,7 @@ export function initRossMods() {
10041004 return;
10051005 }
10061006 }
10071007 if (document.activeElement == hotkeyTargets['.dialogue_popup_input'] && !isMobile()) {
10081008 if (!event.shiftKey && !event.ctrlKey && event.key == 'Enter') {
10091009 event.preventDefault();
10101010 $('#dialogue_popup_ok').trigger('click');
@@ -1139,7 +1139,7 @@ export function initRossMods() {
11391139
11401140 if (event.ctrlKey && event.key == 'ArrowUp') { //edits last USER message if chatbar is empty and focused
11411141 if (
11421142 hotkeyTargets['.send_textarea'].value === '' &&
11431143 chatbarInFocus === true &&
11441144 ($('.swipe_right:last').css('display') === 'flex' || $('.last_mes').attr('is_system') === 'true') &&
11451145 $('#character_popup').css('display') === 'none' &&
@@ -1158,7 +1158,7 @@ export function initRossMods() {
11581158 if (event.key == 'ArrowUp') { //edits last message if chatbar is empty and focused
11591159 console.log('got uparrow input');
11601160 if (
11611161 hotkeyTargets['.send_textarea'].value === '' &&
11621162 chatbarInFocus === true &&
11631163 //$('.swipe_right:last').css('display') === 'flex' &&
11641164 $('.last_mes .mes_buttons').is(':visible') &&
public/scripts/bookmarks.js+9 -9
@@ -103,8 +103,8 @@ async function getBookmarkName({ isReplace = false, forceName = null } = {}) {
103103
104104function getMainChatName() {
105105 if (chat_metadata) {
106106 if (chat_metadata['.main_chat']) {
107107 return chat_metadata['.main_chat'];
108108 }
109109 // groups didn't support bookmarks before chat metadata was introduced
110110 else if (selected_group) {
@@ -112,8 +112,8 @@ function getMainChatName() {
112112 }
113113 else if (characters[this_chid].chat && characters[this_chid].chat.includes(bookmarkNameToken)) {
114114 const tokenIndex = characters[this_chid].chat.lastIndexOf(bookmarkNameToken);
115115 chat_metadata['.main_chat'] = characters[this_chid].chat.substring(0, tokenIndex).trim();
116116 return chat_metadata['.main_chat'];
117117 }
118118 }
119119 return null;
@@ -127,7 +127,7 @@ export function showBookmarksButtons() {
127127 $('#option_convert_to_group').show();
128128 }
129129
130130 if (chat_metadata['.main_chat']) {
131131 // In bookmark chat
132132 $('#option_back_to_main').show();
133133 $('#option_new_bookmark').show();
@@ -184,10 +184,10 @@ export async function createBranch(mesId) {
184184 if (typeof lastMes.extra !== 'object') {
185185 lastMes.extra = {};
186186 }
187187 if (typeof lastMes.extra['.branches'] !== 'object') {
188188 lastMes.extra['.branches'] = [];
189189 }
190190 lastMes.extra['.branches'].push(name);
191191 return name;
192192}
193193
@@ -236,7 +236,7 @@ export async function createNewBookmark(mesId, { forceName = null } = {}) {
236236 await saveChat({ chatName: name, withMetadata: newMetadata, mesId });
237237 }
238238
239239 lastMes.extra['.bookmark_link'] = name;
240240
241241 const mes = $(`.mes[mesid="${mesId}"]`);
242242 updateBookmarkDisplay(mes, name);
public/scripts/cfg-scale.js+14 -14
@@ -42,13 +42,13 @@ function setCharCfg(tempValue, setting) {
4242
4343 switch (setting) {
4444 case settingType.guidance_scale:
4545 tempCharaCfg['.guidance_scale'] = Number(tempValue);
4646 break;
4747 case settingType.negative_prompt:
4848 tempCharaCfg['.negative_prompt'] = tempValue;
4949 break;
5050 case settingType.positive_prompt:
5151 tempCharaCfg['.positive_prompt'] = tempValue;
5252 break;
5353 default:
5454 return false;
@@ -239,31 +239,31 @@ function migrateSettings() {
239239
240240 if (power_user.guidance_scale) {
241241 extension_settings.cfg.global.guidance_scale = power_user.guidance_scale;
242242 delete power_user['.guidance_scale'];
243243 performSettingsSave = true;
244244 }
245245
246246 if (power_user.negative_prompt) {
247247 extension_settings.cfg.global.negative_prompt = power_user.negative_prompt;
248248 delete power_user['.negative_prompt'];
249249 performSettingsSave = true;
250250 }
251251
252252 if (chat_metadata['.cfg_negative_combine']) {
253253 chat_metadata[metadataKeys.prompt_combine] = chat_metadata['.cfg_negative_combine'];
254254 chat_metadata['.cfg_negative_combine'] = undefined;
255255 performMetaSave = true;
256256 }
257257
258258 if (chat_metadata['.cfg_negative_insertion_depth']) {
259259 chat_metadata[metadataKeys.prompt_insertion_depth] = chat_metadata['.cfg_negative_insertion_depth'];
260260 chat_metadata['.cfg_negative_insertion_depth'] = undefined;
261261 performMetaSave = true;
262262 }
263263
264264 if (chat_metadata['.cfg_negative_separator']) {
265265 chat_metadata[metadataKeys.prompt_separator] = chat_metadata['.cfg_negative_separator'];
266266 chat_metadata['.cfg_negative_separator'] = undefined;
267267 performMetaSave = true;
268268 }
269269
public/scripts/chat-templates.js+6 -6
@@ -148,8 +148,8 @@ export async function bindModelTemplates(power_user, online_status) {
148148 ?? power_user.model_templates_mappings[chatTemplateHash]
149149 ?? {};
150150 const bindingsMatch = bindModelTemplates
151151 && power_user.context.preset == bindModelTemplates['.context']
152152 && (!power_user.instruct.enabled || power_user.instruct.preset === bindModelTemplates['.instruct']);
153153
154154 const bound = [];
155155
@@ -160,21 +160,21 @@ export async function bindModelTemplates(power_user, online_status) {
160160 toastr.info(t`Context preset for ${online_status} will use defaults when loaded the next time.`);
161161 } else {
162162 if (power_user.context_derived) {
163163 if (power_user.context.preset !== bindModelTemplates['.context']) {
164164 bound.push(`${power_user.context.preset} context preset`);
165165 // toastr.info(`Bound ${power_user.context.preset} preset to currently loaded model and all models that share its chat template.`);
166166
167167 // map current preset to current chat template hash
168168 bindModelTemplates['.context'] = power_user.context.preset;
169169 }
170170 } else {
171171 toastr.warning(t`Note: Context derivation is disabled. Not including context preset.`);
172172 }
173173 if (power_user.instruct.enabled) {
174174 if (power_user.instruct_derived) {
175175 if (power_user.instruct.preset !== bindModelTemplates['.instruct']) {
176176 bound.push(`${power_user.instruct.preset} instruct preset`);
177177 bindModelTemplates['.instruct'] = power_user.instruct.preset;
178178 }
179179 } else {
180180 toastr.warning(t`Note: Instruct derivation is disabled. Not including instruct preset.`);
public/scripts/chats.js+4 -4
@@ -685,7 +685,7 @@ export function formatCreatorNotes(text, avatarId) {
685685 const preference = new StylesPreference(avatarId);
686686 const sanitizeStyles = !preference.get();
687687 const decodeStyleParam = { prefix: sanitizeStyles ? '#creator_notes_spoiler ' : '' };
688688 /** @type {import('dompurify').Config & { MESSAGE_SANITIZE: boolean }} */
689689 const config = {
690690 RETURN_DOM: false,
691691 RETURN_DOM_FRAGMENT: false,
@@ -1911,13 +1911,13 @@ export function addDOMPurifyHooks() {
19111911 });
19121912
19131913 DOMPurify.addHook('uponSanitizeAttribute', (node, data, config) => {
19141914 if (!config['.MESSAGE_SANITIZE']) {
19151915 return;
19161916 }
19171917
19181918 /* Retain the classes on UI elements of messages that interact with the main UI */
19191919 const permittedNodeTypes = ['BUTTON', 'DIV'];
19201920 if (config['.MESSAGE_ALLOW_SYSTEM_UI'] && node.classList.contains('menu_button') && permittedNodeTypes.includes(node.nodeName)) {
19211921 return;
19221922 }
19231923
@@ -1938,7 +1938,7 @@ export function addDOMPurifyHooks() {
19381938 });
19391939
19401940 DOMPurify.addHook('uponSanitizeElement', (node, _, config) => {
19411941 if (!config['.MESSAGE_SANITIZE']) {
19421942 return;
19431943 }
19441944
public/scripts/extensions/assets/index.js+15 -15
@@ -103,10 +103,10 @@ async function downloadAssetsList(url) {
103103
104104 for (const i of json) {
105105 //console.log(DEBUG_PREFIX,i)
106106 if (availableAssets[i['.type']] === undefined)
107107 availableAssets[i['.type']] = [];
108108
109109 availableAssets[i['.type']].push(i);
110110 }
111111
112112 console.debug(DEBUG_PREFIX, 'Updated available assets to', availableAssets);
@@ -139,7 +139,7 @@ async function downloadAssetsList(url) {
139139 assetTypeMenu.append(await renderExtensionTemplateAsync('assets', 'installation'));
140140 }
141141
142142 for (const asset of availableAssets[assetType].sort((a, b) => a?.name && b?.name && a['.name'].localeCompare(b['.name']))) {
143143 const i = availableAssets[assetType].indexOf(asset);
144144 const elemId = `assets_install_${assetType}_${i}`;
145145 let element = $('<div />', { id: elemId, class: 'asset-download-button right_menu_button' });
@@ -149,13 +149,13 @@ async function downloadAssetsList(url) {
149149 //if (DEBUG_TONY_SAMA_FORK_MODE)
150150 // asset["url"] = asset["url"].replace("https://github.com/SillyTavern/","https://github.com/Tony-sama/"); // DBG
151151
152152 console.debug(DEBUG_PREFIX, 'Checking asset', asset['.id'], asset['.url']);
153153
154154 const assetInstall = async function () {
155155 element.off('click');
156156 label.removeClass('fa-download');
157157 this.classList.add('asset-download-button-loading');
158158 await installAsset(asset['.url'], assetType, asset['.id']);
159159 label.addClass('fa-check');
160160 this.classList.remove('asset-download-button-loading');
161161 element.on('click', assetDelete);
@@ -173,11 +173,11 @@ async function downloadAssetsList(url) {
173173 const assetDelete = async function () {
174174 if (assetType === 'character') {
175175 toastr.error('Go to the characters menu to delete a character.', 'Character deletion not supported');
176176 await executeSlashCommandsWithOptions(`/go ${asset['.id']}`);
177177 return;
178178 }
179179 element.off('click');
180180 await deleteAsset(assetType, asset['.id']);
181181 label.removeClass('fa-check');
182182 label.removeClass('redOverlayGlow');
183183 label.removeClass('fa-trash');
@@ -186,7 +186,7 @@ async function downloadAssetsList(url) {
186186 element.on('click', assetInstall);
187187 };
188188
189189 if (isAssetInstalled(assetType, asset['.id'])) {
190190 console.debug(DEBUG_PREFIX, 'installed, checked');
191191 label.toggleClass('fa-download');
192192 label.toggleClass('fa-check');
@@ -207,14 +207,14 @@ async function downloadAssetsList(url) {
207207 element.on('click', assetInstall);
208208 }
209209
210210 console.debug(DEBUG_PREFIX, 'Created element for ', asset['.id']);
211211
212212 const displayName = DOMPurify.sanitize(asset['.name'] || asset['.id']);
213213 const description = DOMPurify.sanitize(asset['.description'] || '');
214214 const url = isValidUrl(asset['.url']) ? asset['.url'] : '';
215215 const title = assetType === 'extension' ? t`Extension repo/guide:` + ` ${url}` : t`Preview in browser`;
216216 const previewIcon = (assetType === 'extension' || assetType === 'character') ? 'fa-arrow-up-right-from-square' : 'fa-headphones-simple';
217217 const toolTag = assetType === 'extension' && asset['.tool'];
218218 const author = url && assetType === 'extension' ? getAuthorFromUrl(url) : EMPTY_AUTHOR;
219219
220220 const assetBlock = $('<i></i>')
@@ -246,7 +246,7 @@ async function downloadAssetsList(url) {
246246 if (asset.highlight) {
247247 assetBlock.find('.asset-name').append('<i class="fa-solid fa-sm fa-trophy"></i>');
248248 }
249249 assetBlock.find('.asset-name').prepend(`<div class="avatar"><img src="${asset['.url']}" alt="${displayName}"></div>`);
250250 }
251251
252252 assetBlock.addClass('asset-block');
public/scripts/extensions/caption/index.js+1 -1
@@ -204,7 +204,7 @@ async function sendCaptionedMessage(caption, image, mimeType) {
204204 inline_image: !!extension_settings.caption.show_in_chat,
205205 },
206206 };
207207 chat_metadata['.tainted'] = true;
208208 context.chat.push(message);
209209 const messageId = context.chat.length - 1;
210210 await eventSource.emit(event_types.MESSAGE_SENT, messageId);
public/scripts/extensions/memory/index.js+1 -1
@@ -456,7 +456,7 @@ async function onChatEvent() {
456456 .catch(console.error)
457457 .finally(() => {
458458 lastMessageId = context.chat?.length ?? null;
459459 lastMessageHash = getStringHash((context.chat.length && context.chat[context.chat.length - 1]['.mes']) ?? '');
460460 });
461461}
462462
public/scripts/extensions/quick-reply/index.js+1 -1
@@ -185,7 +185,7 @@ const init = async () => {
185185 buttons.show();
186186 settings.onSave = ()=>buttons.refresh();
187187
188188 window['globalThis.executeQuickReplyByName'] = async(name, args = {}, options = {}) => {
189189 let qr = [
190190 ...settings.config.setList,
191191 ...(settings.chatConfig?.setList ?? []),
public/scripts/extensions/quick-reply/src/SlashCommandHandler.js+1 -1
@@ -77,7 +77,7 @@ export class SlashCommandHandler {
7777 },
7878 };
7979
8080 window['globalThis.qrEnumProviderExecutables'] = localEnumProviders.qrExecutables;
8181
8282 SlashCommandParser.addCommandObject(SlashCommand.fromProps({ name: 'qr',
8383 callback: (_, value) => this.executeQuickReplyByIndex(Number(value)),
public/scripts/extensions/stable-diffusion/index.js+1 -1
@@ -426,7 +426,7 @@ function processTriggers(chat, _, abort, type) {
426426 }
427427}
428428
429429window['globalThis.SD_ProcessTriggers'] = processTriggers;
430430
431431function getSdRequestBody() {
432432 switch (extension_settings.sd.source) {
public/scripts/extensions/tts/coqui.js+10 -10
@@ -207,13 +207,13 @@ class CoquiTtsProvider {
207207 this.settings.customVoices = {};
208208 for (let voiceName in this.settings.voiceMapDict) {
209209 const voiceId = this.settings.voiceMapDict[voiceName];
210210 this.settings.customVoices[voiceName] = voiceId['.model_id'];
211211
212212 if (voiceId['.model_language'] != null)
213213 this.settings.customVoices[voiceName] += '[' + voiceId['.model_language'] + ']';
214214
215215 if (voiceId['.model_speaker'] != null)
216216 this.settings.customVoices[voiceName] += '[' + voiceId['.model_speaker'] + ']';
217217 }
218218
219219 // Update UI select list with voices
@@ -493,8 +493,8 @@ class CoquiTtsProvider {
493493 .append('<option value="none">Select language</option>')
494494 .val('none');
495495
496496 for (let i = 0; i < model_settings['.languages'].length; i++) {
497497 const language_label = JSON.stringify(model_settings['.languages'][i]).replaceAll('"', '');
498498 $('#coqui_api_model_settings_language').append(new Option(language_label, i));
499499 }
500500 }
@@ -512,8 +512,8 @@ class CoquiTtsProvider {
512512 .append('<option value="none">Select speaker</option>')
513513 .val('none');
514514
515515 for (let i = 0; i < model_settings['.speakers'].length; i++) {
516516 const speaker_label = JSON.stringify(model_settings['.speakers'][i]).replaceAll('"', '');
517517 $('#coqui_api_model_settings_speaker').append(new Option(speaker_label, i));
518518 }
519519 }
@@ -525,7 +525,7 @@ class CoquiTtsProvider {
525525 $('#coqui_api_model_install_status').show();
526526
527527 // Check if already installed and propose to do it otherwise
528528 const model_id = modelDict[model_language][model_dataset][model_name]['.id'];
529529 console.debug(DEBUG_PREFIX,'Check if model is already installed',model_id);
530530 let result = await CoquiTtsProvider.checkmodel_state(model_id);
531531 result = await result.json();
public/scripts/extensions/tts/cosyvoice.js+1 -1
@@ -175,7 +175,7 @@ class CosyVoiceProvider {
175175 };
176176
177177 if (streaming) {
178178 params['.streaming'] = 1;
179179 }
180180
181181 const url = `${this.settings.provider_endpoint}/`;
public/scripts/extensions/tts/elevenlabs.js+3 -3
@@ -126,16 +126,16 @@ class ElevenLabsTtsProvider {
126126 this.settings = this.defaultSettings;
127127
128128 // Migrate old settings
129129 if (settings['.multilingual'] !== undefined) {
130130 settings.model = settings.multilingual ? 'eleven_multilingual_v1' : 'eleven_monolingual_v1';
131131 delete settings['.multilingual'];
132132 }
133133
134134 if (Object.hasOwn(settings, 'apiKey')) {
135135 if (settings.apiKey && !secret_state[SECRET_KEYS.ELEVENLABS]){
136136 await writeSecret(SECRET_KEYS.ELEVENLABS, settings.apiKey);
137137 }
138138 delete settings['.apiKey'];
139139 }
140140
141141 $('#elevenlabs_tts_key').toggleClass('success', !!secret_state[SECRET_KEYS.ELEVENLABS]);
public/scripts/extensions/tts/index.js+7 -7
@@ -284,7 +284,7 @@ function debugTtsPlayback() {
284284 },
285285 ));
286286}
287287window['globalThis.debugTtsPlayback'] = debugTtsPlayback;
288288
289289//##################//
290290// Audio Control //
@@ -321,8 +321,8 @@ async function playAudioData(audioJob) {
321321 const srcUrl = await getBase64Async(audioBlob);
322322
323323 // VRM lip sync
324324 if (extension_settings.vrm?.enabled && typeof window['globalThis.vrmLipSync'] === 'function') {
325325 await window['globalThis.vrmLipSync'](audioBlob, char);
326326 }
327327
328328 audioElement.src = srcUrl;
@@ -339,7 +339,7 @@ async function playAudioData(audioJob) {
339339 });
340340}
341341
342342window['globalThis.tts_preview'] = function (id) {
343343 const audio = document.getElementById(id);
344344
345345 if (audio instanceof HTMLAudioElement && !$(audio).data('disabled')) {
@@ -474,8 +474,8 @@ function completeTtsJob() {
474474async function tts(text, voiceId, char, voiceMapKey = null) {
475475 async function processResponse(response) {
476476 // RVC injection
477477 if (typeof window['globalThis.rvcVoiceConversion'] === 'function' && extension_settings.rvc.enabled)
478478 response = await window['globalThis.rvcVoiceConversion'](response, char, text);
479479
480480 await addAudioJob(response, char);
481481 }
@@ -806,7 +806,7 @@ async function playFullConversation() {
806806 ttsJobQueue = chat;
807807}
808808
809809window['globalThis.playFullConversation'] = playFullConversation;
810810
811811//#############################//
812812// Extension UI and Settings //
public/scripts/extensions/vectors/index.js+2 -2
@@ -746,7 +746,7 @@ function overlapChunks(chunk, index, chunks, overlapSize) {
746746 return overlappedChunk;
747747}
748748
749749window['globalThis.vectors_rearrangeChat'] = rearrangeChat;
750750
751751const onChatEvent = debounce(async () => await moduleWorker.update(), debounce_timeout.relaxed);
752752
@@ -1620,7 +1620,7 @@ jQuery(async () => {
16201620 }
16211621
16221622 // Migrate from old settings
16231623 if (settings['.enabled']) {
16241624 settings.enabled_chats = true;
16251625 }
16261626
public/scripts/group-chats.js+14 -14
@@ -274,8 +274,8 @@ export async function getGroupChat(groupId, reload = false) {
274274 }
275275
276276 // Add integrity slug if missing
277277 if (!metadata['.integrity']) {
278278 metadata['.integrity'] = uuidv4();
279279 }
280280
281281 await loadItemizedPrompts(getCurrentChatId());
@@ -559,8 +559,8 @@ export function getGroupCharacterCardsLazy(groupId, characterId) {
559559 return values.filter(x => x.length).join('\n');
560560 }
561561
562562 const scenarioOverride = String(chat_metadata['.scenario'] || '');
563563 const mesExamplesOverride = String(chat_metadata['.mes_example'] || '');
564564
565565 return createLazyFields({
566566 description: () => collectField('Description', c => c.description),
@@ -593,16 +593,16 @@ async function getFirstCharacterMessage(character) {
593593 }
594594
595595 const mes = {};
596596 mes['.is_user'] = false;
597597 mes['.is_system'] = false;
598598 mes['.name'] = character.name;
599599 mes['.send_date'] = getMessageTimeStamp();
600600 mes['.original_avatar'] = character.avatar;
601601 mes['.extra'] = { 'gen_id': Date.now() * Math.random() * 1000000 };
602602 mes['.mes'] = messageText
603603 ? substituteParams(messageText.trim(), { name2Override: character.name })
604604 : '';
605605 mes['.force_avatar'] =
606606 character.avatar != 'none'
607607 ? getThumbnailUrl('avatar', character.avatar)
608608 : default_avatar;
@@ -628,7 +628,7 @@ async function saveGroupChat(groupId, shouldSaveGroup, force = false) {
628628 return;
629629 }
630630 const chatId = group.chat_id;
631631 group['.date_last_chat'] = Date.now();
632632 /** @type {ChatHeader} */
633633 const chatHeader = {
634634 chat_metadata: { ...chat_metadata },
@@ -2179,7 +2179,7 @@ export async function openGroupChat(groupId, chatId) {
21792179 await clearChat();
21802180 chat.length = 0;
21812181 group.chat_id = chatId;
21822182 group['.date_last_chat'] = Date.now();
21832183 updateChatMetadata({}, true);
21842184
21852185 await editGroup(groupId, true, false);
public/scripts/horde.js+5 -5
@@ -205,11 +205,11 @@ export async function generateHorde(prompt, params, signal, reportProgress) {
205205 delete params.prompt;
206206
207207 // No idea what these do
208208 params['.n'] = 1;
209209 params['.frmtadsnsp'] = false;
210210 params['.frmtrmblln'] = false;
211211 params['.frmtrmspch'] = false;
212212 params['.frmttriminc'] = false;
213213
214214 const payload = {
215215 'prompt': prompt,
public/scripts/i18n.js+2 -2
@@ -258,8 +258,8 @@ function addLanguagesToDropdown() {
258258 const uiLanguageSelects = $('#ui_language_select, #onboarding_ui_language_select');
259259 for (const langObj of langs) { // Set the value to the language code
260260 const option = document.createElement('option');
261261 option.value = langObj['.lang']; // Set the value to the language code
262262 option.innerText = langObj['.display']; // Set the display text to the language name
263263 uiLanguageSelects.append(option);
264264 }
265265
public/scripts/instruct-mode.js+1 -1
@@ -161,7 +161,7 @@ export async function loadInstructMode(data) {
161161 */
162162export function updateBindModelTemplatesState() {
163163 const bindModelTemplates = power_user.model_templates_mappings[online_status] ?? power_user.model_templates_mappings[power_user.chat_template_hash];
164164 const bindingsMatch = (bindModelTemplates && power_user.context.preset === bindModelTemplates['.context'] && (!power_user.instruct.enabled || power_user.instruct.preset === bindModelTemplates['.instruct'])) ?? false;
165165 const currentState = $('#bind_model_templates').prop('checked');
166166 if (bindingsMatch === currentState) {
167167 // No change needed
public/scripts/macros.js+4 -4
@@ -312,14 +312,14 @@ export class MacrosParser {
312312 * @returns {number} The hashed chat id
313313 */
314314function getChatIdHash() {
315315 const cachedIdHash = chat_metadata['.chat_id_hash'];
316316
317317 // If chat_id_hash is not already set, calculate it
318318 if (!cachedIdHash) {
319319 // Use the main_chat if it's available, otherwise get the current chat ID
320320 const chatId = chat_metadata['.main_chat'] ?? getCurrentChatId();
321321 const chatIdHash = getStringHash(chatId);
322322 chat_metadata['.chat_id_hash'] = chatIdHash;
323323 return chatIdHash;
324324 }
325325
@@ -361,7 +361,7 @@ export function getLastMessageId({ exclude_swipe_in_propress = true, filter = nu
361361 * @returns {number|null} The ID of the first message in the context
362362 */
363363function getFirstIncludedMessageId() {
364364 return chat_metadata['.lastInContextMessageId'];
365365}
366366
367367/**
public/scripts/macros/definitions/chat-macros.js+1 -1
@@ -104,7 +104,7 @@ function getLastCharMessage() {
104104}
105105
106106function getFirstIncludedMessageId() {
107107 const value = chat_metadata['.lastInContextMessageId'];
108108 return typeof value === 'number' ? value : null;
109109}
110110
public/scripts/macros/definitions/core-macros.js+3 -3
@@ -433,13 +433,13 @@ export function registerCoreMacros() {
433433}
434434
435435function getChatIdHash() {
436436 const cachedIdHash = chat_metadata['.chat_id_hash'];
437437 if (typeof cachedIdHash === 'number') {
438438 return cachedIdHash;
439439 }
440440
441441 const chatId = chat_metadata['.main_chat'] ?? getCurrentChatId();
442442 const chatIdHash = getStringHash(chatId);
443443 chat_metadata['.chat_id_hash'] = chatIdHash;
444444 return chatIdHash;
445445}
public/scripts/openai.js+53 -53
@@ -529,8 +529,8 @@ function setOpenAIMessages(chat) {
529529 const currentModel = getChatCompletionModel();
530530
531531 for (let i = chat.length - 1; i >= 0; i--) {
532532 let role = chat[j]['.is_user'] ? 'user' : 'assistant';
533533 let content = chat[j]['.mes'];
534534
535535 // If this symbol flag is set, completely ignore the message.
536536 // This can be used to hide messages without affecting the number of messages in the chat.
@@ -567,7 +567,7 @@ function setOpenAIMessages(chat) {
567567 // remove caret return (waste of tokens)
568568 content = content.replace(/\r/gm, '');
569569
570570 const name = chat[j]['.name'];
571571 const media = chat[j]?.extra?.media;
572572 const mediaDisplay = getMediaDisplay(chat[j]);
573573 const mediaIndex = getMediaIndex(chat[j]);
@@ -2588,14 +2588,14 @@ export async function createGenerationParameters(settings, model, type, messages
25882588
25892589 if (settings.reverse_proxy && proxySupportedSources.includes(settings.chat_completion_source)) {
25902590 await validateReverseProxy();
25912591 generate_data['.reverse_proxy'] = settings.reverse_proxy;
25922592 generate_data['.proxy_password'] = settings.proxy_password;
25932593 }
25942594
25952595 // Add logprobs request (max 5 per OpenAI docs)
25962596 const useLogprobs = !!power_user.request_token_probabilities;
25972597 if (useLogprobs && logprobsSupportedSources.includes(settings.chat_completion_source)) {
25982598 generate_data['.logprobs'] = 5;
25992599 }
26002600
26012601 // Remove logit bias/logprobs/stop-strings if not supported by the model
@@ -2610,67 +2610,67 @@ export async function createGenerationParameters(settings, model, type, messages
26102610 }
26112611
26122612 if (settings.chat_completion_source === chat_completion_sources.CLAUDE) {
26132613 generate_data['.top_k'] = Number(settings.top_k_openai);
26142614 generate_data['.use_sysprompt'] = settings.use_sysprompt;
26152615 generate_data['.stop'] = getCustomStoppingStrings(); // Claude shouldn't have limits on stop strings.
26162616 // Don't add a prefill on quiet gens (summarization) and when using continue prefill.
26172617 if (type !== 'quiet' && !(type === 'continue' && settings.continue_prefill)) {
26182618 generate_data['.assistant_prefill'] = type === 'impersonate'
26192619 ? substituteParams(settings.assistant_impersonation)
26202620 : substituteParams(settings.assistant_prefill);
26212621 }
26222622 }
26232623
26242624 if (settings.chat_completion_source === chat_completion_sources.OPENROUTER) {
26252625 generate_data['.top_k'] = Number(settings.top_k_openai);
26262626 generate_data['.min_p'] = Number(settings.min_p_openai);
26272627 generate_data['.repetition_penalty'] = Number(settings.repetition_penalty_openai);
26282628 generate_data['.top_a'] = Number(settings.top_a_openai);
26292629 generate_data['.use_fallback'] = settings.openrouter_use_fallback;
26302630 generate_data['.provider'] = settings.openrouter_providers;
26312631 generate_data['.allow_fallbacks'] = settings.openrouter_allow_fallbacks;
26322632 generate_data['.middleout'] = settings.openrouter_middleout;
26332633 }
26342634
26352635 if ([chat_completion_sources.MAKERSUITE, chat_completion_sources.VERTEXAI].includes(settings.chat_completion_source)) {
26362636 const stopStringsLimit = 5;
26372637 generate_data['.top_k'] = Number(settings.top_k_openai);
26382638 generate_data['.stop'] = getCustomStoppingStrings(stopStringsLimit).slice(0, stopStringsLimit).filter(x => x.length >= 1 && x.length <= 16);
26392639 generate_data['.use_sysprompt'] = settings.use_sysprompt;
26402640 if (settings.chat_completion_source === chat_completion_sources.VERTEXAI) {
26412641 generate_data['.vertexai_auth_mode'] = settings.vertexai_auth_mode;
26422642 generate_data['.vertexai_region'] = settings.vertexai_region;
26432643 generate_data['.vertexai_express_project_id'] = settings.vertexai_express_project_id;
26442644 }
26452645 }
26462646
26472647 if (settings.chat_completion_source === chat_completion_sources.MISTRALAI) {
26482648 generate_data['.safe_prompt'] = false; // already defaults to false, but just incase they change that in the future.
26492649 generate_data['.stop'] = getCustomStoppingStrings(); // Mistral shouldn't have limits on stop strings.
26502650 }
26512651
26522652 if (settings.chat_completion_source === chat_completion_sources.CUSTOM) {
26532653 generate_data['.custom_url'] = settings.custom_url;
26542654 generate_data['.custom_include_body'] = settings.custom_include_body;
26552655 generate_data['.custom_exclude_body'] = settings.custom_exclude_body;
26562656 generate_data['.custom_include_headers'] = settings.custom_include_headers;
26572657 }
26582658
26592659 if (settings.chat_completion_source === chat_completion_sources.COHERE) {
26602660 // Clamp to 0.01 -> 0.99
26612661 generate_data['.top_p'] = Math.min(Math.max(Number(settings.top_p_openai), 0.01), 0.99);
26622662 generate_data['.top_k'] = Number(settings.top_k_openai);
26632663 // Clamp to 0 -> 1
26642664 generate_data['.frequency_penalty'] = Math.min(Math.max(Number(settings.freq_pen_openai), 0), 1);
26652665 generate_data['.presence_penalty'] = Math.min(Math.max(Number(settings.pres_pen_openai), 0), 1);
26662666 generate_data['.stop'] = getCustomStoppingStrings(5);
26672667 }
26682668
26692669 if (settings.chat_completion_source === chat_completion_sources.PERPLEXITY) {
26702670 generate_data['.top_k'] = Number(settings.top_k_openai);
26712671 generate_data['.frequency_penalty'] = Number(settings.freq_pen_openai);
26722672 generate_data['.presence_penalty'] = Number(settings.pres_pen_openai);
26732673 delete generate_data['.stop'];
26742674 }
26752675
26762676 // https://console.groq.com/docs/openai
@@ -2713,35 +2713,35 @@ export async function createGenerationParameters(settings, model, type, messages
27132713
27142714 // https://docs.electronhub.ai/api-reference/chat/completions
27152715 if (settings.chat_completion_source === chat_completion_sources.ELECTRONHUB) {
27162716 generate_data['.top_k'] = Number(settings.top_k_openai);
27172717 }
27182718
27192719 if (settings.chat_completion_source === chat_completion_sources.CHUTES) {
27202720 generate_data['.min_p'] = Number(settings.min_p_openai);
27212721 generate_data['.top_k'] = settings.top_k_openai > 0 ? Number(settings.top_k_openai) : undefined;
27222722 generate_data['.repetition_penalty'] = Number(settings.repetition_penalty_openai);
27232723 generate_data['.stop'] = getCustomStoppingStrings();
27242724 }
27252725
27262726 // https://docs.z.ai/api-reference/llm/chat-completion
27272727 if (settings.chat_completion_source === chat_completion_sources.ZAI) {
27282728 generate_data['.top_p'] = generate_data.top_p || 0.01;
27292729 generate_data['.stop'] = getCustomStoppingStrings(1);
27302730 generate_data['.zai_endpoint'] = settings.zai_endpoint || ZAI_ENDPOINT.COMMON;
27312731 delete generate_data.presence_penalty;
27322732 delete generate_data.frequency_penalty;
27332733 }
27342734
27352735 // https://docs.nano-gpt.com/api-reference/endpoint/chat-completion#temperature-&-nucleus
27362736 if (settings.chat_completion_source === chat_completion_sources.NANOGPT) {
27372737 generate_data['.top_k'] = Number(settings.top_k_openai);
27382738 generate_data['.min_p'] = Number(settings.min_p_openai);
27392739 generate_data['.repetition_penalty'] = Number(settings.repetition_penalty_openai);
27402740 generate_data['.top_a'] = Number(settings.top_a_openai);
27412741 }
27422742
27432743 if (seedSupportedSources.includes(settings.chat_completion_source) && settings.seed >= 0) {
27442744 generate_data['.seed'] = settings.seed;
27452745 }
27462746
27472747 if ([chat_completion_sources.OPENAI, chat_completion_sources.AZURE_OPENAI].includes(settings.chat_completion_source) && /^(o1|o3|o4)/.test(model) ||
@@ -6162,7 +6162,7 @@ async function onVertexAIValidateServiceAccount() {
61626162 }
61636163
61646164 // Save to backend secret storage
61656165 const keyLabel = serviceAccount['.client_email'] || '';
61666166 await writeSecret(SECRET_KEYS.VERTEXAI_SERVICE_ACCOUNT, jsonContent, keyLabel);
61676167
61686168 // Show success status
public/scripts/personas.js+20 -20
@@ -835,7 +835,7 @@ async function renamePersona(avatarId) {
835835async function selectCurrentPersona({ toastPersonaNameChange = true } = {}) {
836836 const personaName = power_user.personas[user_avatar];
837837 if (personaName) {
838838 const shouldAutoLock = power_user.persona_auto_lock && user_avatar !== chat_metadata['.persona'];
839839
840840 if (personaName !== name1) {
841841 console.log(`Auto-updating user name to ${personaName}`);
@@ -871,7 +871,7 @@ async function selectCurrentPersona({ toastPersonaNameChange = true } = {}) {
871871
872872 // Update the locked persona if setting is enabled
873873 if (shouldAutoLock) {
874874 chat_metadata['.persona'] = user_avatar;
875875 console.log(`Auto locked persona to ${user_avatar}`);
876876 if (toastPersonaNameChange && power_user.persona_show_notifications) {
877877 toastr.success(t`Persona ${personaName} selected and auto-locked to current chat`, t`Persona Selected`);
@@ -914,7 +914,7 @@ export function isPersonaLocked(type = 'chat') {
914914 case 'default':
915915 return power_user.default_persona === user_avatar;
916916 case 'chat':
917917 return chat_metadata['.persona'] == user_avatar;
918918 case 'character': {
919919 return !!power_user.persona_descriptions[user_avatar]?.connections?.some(isPersonaConnectionLocked);
920920 }
@@ -960,9 +960,9 @@ async function unlockPersona(type = 'chat') {
960960 break;
961961 }
962962 case 'chat': {
963963 if (chat_metadata['.persona']) {
964964 console.log(`Unlocking persona ${user_avatar} from this chat`);
965965 delete chat_metadata['.persona'];
966966 await saveMetadata();
967967 if (power_user.persona_show_notifications && !isPersonaPanelOpen()) {
968968 toastr.info(t`Persona ${name1} is now unlocked from this chat.`, t`Persona Unlocked`);
@@ -1021,7 +1021,7 @@ async function lockPersona(type = 'chat') {
10211021 }
10221022 case 'chat': {
10231023 console.log(`Locking persona ${user_avatar} to this chat`);
10241024 chat_metadata['.persona'] = user_avatar;
10251025 saveMetadataDebounced();
10261026 if (power_user.persona_show_notifications && !isPersonaPanelOpen()) {
10271027 toastr.success(t`User persona ${name1} is locked to ${name2} in this chat`, t`Persona Locked`);
@@ -1105,9 +1105,9 @@ async function deleteUserAvatar() {
11051105 power_user.default_persona = null;
11061106 }
11071107
11081108 if (avatarId === chat_metadata['.persona']) {
11091109 toastr.warning(t`The locked persona was deleted. You will need to set a new persona for this chat.`, t`Persona Deleted`);
11101110 delete chat_metadata['.persona'];
11111111 await saveMetadata();
11121112 }
11131113
@@ -1317,7 +1317,7 @@ async function toggleDefaultPersona(avatarId, { quiet = false } = {}) {
13171317 */
13181318function getPersonaStates(avatarId) {
13191319 const isDefaultPersona = power_user.default_persona === avatarId;
13201320 const hasChatLock = chat_metadata['.persona'] == avatarId;
13211321
13221322 /** @type {PersonaConnection[]} */
13231323 const connections = power_user.persona_descriptions[avatarId]?.connections;
@@ -1413,13 +1413,13 @@ function updatePersonaUIStates({ navigateToCurrent = false } = {}) {
14131413 * @returns {PersonaLockInfo} An object containing flags and a message describing the persona lock status.
14141414 */
14151415function getPersonaTemporaryLockInfo() {
14161416 const hasDifferentChatLock = !!chat_metadata['.persona'] && chat_metadata['.persona'] !== user_avatar;
14171417 const hasDifferentDefaultLock = power_user.default_persona && power_user.default_persona !== user_avatar;
14181418 const isTemporary = hasDifferentChatLock || (!chat_metadata['.persona'] && hasDifferentDefaultLock);
14191419 const info = isTemporary ? t`A different persona is locked to this chat, or you have a different default persona set. The currently selected persona will only be temporary, and resets on reload. Consider locking this persona to the chat if you want to permanently use it.`
14201420 + '\n\n'
14211421 + t`Current Persona: ${power_user.personas[user_avatar]}`
14221422 + (hasDifferentChatLock ? '\n' + t`Chat persona: ${power_user.personas[chat_metadata['.persona']]}` : '')
14231423 + (hasDifferentDefaultLock ? '\n' + t`Default persona: ${power_user.personas[power_user.default_persona]}` : '') : '';
14241424
14251425 return {
@@ -1458,14 +1458,14 @@ async function loadPersonaForCurrentChat({ doRender = false } = {}) {
14581458 let connectType = null;
14591459
14601460 // If persona is locked in chat metadata, select it
14611461 if (chat_metadata['.persona']) {
14621462 console.log(`Using locked persona ${chat_metadata['.persona']}`);
14631463 chatPersona = chat_metadata['.persona'];
14641464
14651465 // Verify it exists
14661466 if (!userAvatars.includes(chatPersona)) {
14671467 console.warn('Chat-locked persona avatar not found, unlocking persona');
14681468 delete chat_metadata['.persona'];
14691469 saveSettingsDebounced();
14701470 chatPersona = '';
14711471 }
@@ -1528,9 +1528,9 @@ async function loadPersonaForCurrentChat({ doRender = false } = {}) {
15281528 }
15291529
15301530 // Whatever way we selected a persona, if it doesn't exist, unlock this chat
15311531 if (chat_metadata['.persona'] && !userAvatars.includes(chat_metadata['.persona'])) {
15321532 console.warn('Persona avatar not found, unlocking persona');
15331533 delete chat_metadata['.persona'];
15341534 }
15351535
15361536 // Default persona missing
@@ -1542,7 +1542,7 @@ async function loadPersonaForCurrentChat({ doRender = false } = {}) {
15421542
15431543 // Persona avatar found, select it
15441544 if (chatPersona && user_avatar !== chatPersona) {
15451545 const willAutoLock = power_user.persona_auto_lock && user_avatar !== chat_metadata['.persona'];
15461546 await setUserAvatar(chatPersona, { toastPersonaNameChange: false, navigateToCurrent: true });
15471547
15481548 if (power_user.persona_show_notifications) {
@@ -1554,7 +1554,7 @@ async function loadPersonaForCurrentChat({ doRender = false } = {}) {
15541554 }
15551555 }
15561556 // Even if it's the same persona, we still might need to auto-lock to chat if that's enabled
15571557 else if (chatPersona && power_user.persona_auto_lock && !chat_metadata['.persona']) {
15581558 await lockPersona('chat');
15591559 }
15601560
public/scripts/preset-manager.js+6 -6
@@ -163,7 +163,7 @@ class PresetManager {
163163 const manager = getPresetManager('textgenerationwebui');
164164 const name = manager.getSelectedPresetName();
165165 const data = manager.getPresetSettings(name);
166166 data['.name'] = name;
167167 return data;
168168 },
169169 setData: (data) => {
@@ -652,22 +652,22 @@ class PresetManager {
652652 return textgen_settings;
653653 case 'context': {
654654 const context_preset = getContextSettings();
655655 context_preset['.name'] = name || power_user.context.preset;
656656 return context_preset;
657657 }
658658 case 'instruct': {
659659 const instruct_preset = structuredClone(power_user.instruct);
660660 instruct_preset['.name'] = name || power_user.instruct.preset;
661661 return instruct_preset;
662662 }
663663 case 'sysprompt': {
664664 const sysprompt_preset = structuredClone(power_user.sysprompt);
665665 sysprompt_preset['.name'] = name || power_user.sysprompt.preset;
666666 return sysprompt_preset;
667667 }
668668 case 'reasoning': {
669669 const reasoning_preset = structuredClone(power_user.reasoning);
670670 reasoning_preset['.name'] = name || power_user.reasoning.preset;
671671 return reasoning_preset;
672672 }
673673 default:
@@ -1115,7 +1115,7 @@ export async function initPresetManager() {
11151115 const fileName = file.name.replace('.json', '').replace('.settings', '');
11161116 const data = await parseJsonFile(file);
11171117 const name = data?.name ?? fileName;
11181118 data['.name'] = name;
11191119
11201120 await presetManager.savePreset(name, data);
11211121 const successToast = !presetManager.isAdvancedFormatting() ? t`Preset imported` : t`Template imported`;
public/scripts/slash-commands.js+7 -7
@@ -2120,7 +2120,7 @@ export function initDefaultSlashCommands() {
21202120 isRequired: true,
21212121 enumProvider: (executor, scope) => [
21222122 ...commonEnumProviders.variables('scope')(executor, scope),
21232123 ...(typeof window['globalThis.qrEnumProviderExecutables'] === 'function') ? window['globalThis.qrEnumProviderExecutables']() : [],
21242124 ],
21252125 }),
21262126 ],
@@ -3572,7 +3572,7 @@ async function runCallback(args, name) {
35723572 return result.pipe;
35733573 }
35743574
35753575 if (typeof window['globalThis.executeQuickReplyByName'] !== 'function') {
35763576 throw new Error(t`Quick Reply extension is not loaded`);
35773577 }
35783578
@@ -3583,7 +3583,7 @@ async function runCallback(args, name) {
35833583 abortController: args._abortController,
35843584 debugController: args._debugController,
35853585 };
35863586 return await window['globalThis.executeQuickReplyByName'](name, args, options);
35873587 } catch (error) {
35883588 throw new Error(t`Error running Quick Reply "${name}": ${error.message}`);
35893589 }
@@ -4788,7 +4788,7 @@ export async function sendMessageAs(args, text) {
47884788 insertAt = chat.length + insertAt;
47894789 }
47904790
47914791 chat_metadata['.tainted'] = true;
47924792
47934793 if (!isNaN(insertAt) && insertAt >= 0 && insertAt <= chat.length) {
47944794 chat.splice(insertAt, 0, message);
@@ -4840,7 +4840,7 @@ export async function sendNarratorMessage(args, text) {
48404840 insertAt = chat.length + insertAt;
48414841 }
48424842
48434843 chat_metadata['.tainted'] = true;
48444844
48454845 if (!isNaN(insertAt) && insertAt >= 0 && insertAt <= chat.length) {
48464846 chat.splice(insertAt, 0, message);
@@ -4892,7 +4892,7 @@ export async function promptQuietForLoudResponse(who, text) {
48924892 },
48934893 };
48944894
48954895 chat_metadata['.tainted'] = true;
48964896
48974897 chat.push(message);
48984898 await eventSource.emit(event_types.MESSAGE_SENT, (chat.length - 1));
@@ -4928,7 +4928,7 @@ async function sendCommentMessage(args, text) {
49284928 insertAt = chat.length + insertAt;
49294929 }
49304930
49314931 chat_metadata['.tainted'] = true;
49324932
49334933 if (!isNaN(insertAt) && insertAt >= 0 && insertAt <= chat.length) {
49344934 chat.splice(insertAt, 0, message);
public/scripts/textgen-models.js+2 -2
@@ -880,8 +880,8 @@ async function downloadTabbyModel() {
880880 }
881881
882882 // Params for the server side of ST
883883 params['.api_server'] = serverUrl;
884884 params['.api_type'] = textgen_settings.type;
885885
886886 toastr.info('Downloading. Check the Tabby console for progress reports.');
887887
public/scripts/textgen-settings.js+5 -5
@@ -755,7 +755,7 @@ async function getStatusTextgen() {
755755 power_user.chat_template_hash = chat_template_hash;
756756
757757 if (wantsContextSize && 'default_generation_settings' in data) {
758758 const backend_max_context = data['.default_generation_settings']['.n_ctx'];
759759 if (backend_max_context && typeof backend_max_context === 'number') {
760760 const old_value = max_context;
761761 if (max_context !== backend_max_context) {
@@ -913,16 +913,16 @@ export function initTextGenSettings() {
913913 $('#ban_eos_token_textgenerationwebui').prop('checked', false); //Aphro should not ban EOS, just ignore it; 'add token '2' to ban list do to this'
914914 //special handling for vLLM/Aphrodite topK -1 disable state
915915 $('#top_k_textgenerationwebui').attr('min', -1);
916916 if ($('#top_k_textgenerationwebui').val() === '0' || textgenerationwebui_settings['.top_k'] === 0) {
917917 textgenerationwebui_settings['.top_k'] = -1;
918918 $('#top_k_textgenerationwebui').val('-1').trigger('input');
919919 }
920920 } else {
921921 $('#mirostat_mode_textgenerationwebui').attr('step', 1);
922922 //undo special vLLM/Aphrodite setup for topK
923923 $('#top_k_textgenerationwebui').attr('min', 0);
924924 if ($('#top_k_textgenerationwebui').val() === '-1' || textgenerationwebui_settings['.top_k'] === -1) {
925925 textgenerationwebui_settings['.top_k'] = 0;
926926 $('#top_k_textgenerationwebui').val('0').trigger('input');
927927 }
928928 }
public/scripts/tool-calling.js+2 -2
@@ -410,8 +410,8 @@ export class ToolManager {
410410 if (tools.length) {
411411 console.log('[ToolManager] Registered function tools:', tools);
412412
413413 data['.tools'] = tools;
414414 data['.tool_choice'] = 'auto';
415415 }
416416 }
417417
public/scripts/utils.js+10 -4
@@ -36,10 +36,16 @@ export const localizePagination = function (container) {
3636
3737/**
3838 * Checks if the current environment supports negative lookbehind in regular expressions.
39+ * @type {{ (): boolean; result?: boolean }} Defines the function as a memoized object with a cached result.
3940 * @returns {boolean} True if negative lookbehind is supported, false otherwise.
4041 */
4142export function canUseNegativeLookbehind() {
42- let result = canUseNegativeLookbehind['result'];
43+ /**
44+ * A reference to the function itself, typed as a callable object with a cache property.
45+ * @type {{ (): boolean; result?: boolean }}
46+ */
47+ const fn = canUseNegativeLookbehind;
48+ let result = fn.result;
4349 if (typeof result !== 'boolean') {
4450 try {
4551 new RegExp('(?<!_)');
@@ -47,7 +53,7 @@ export function canUseNegativeLookbehind() {
4753 } catch (e) {
4854 result = false;
4955 }
5056 canUseNegativeLookbehind['fn.result'] = result;
5157 }
5258 return result;
5359}
@@ -2440,8 +2446,8 @@ export async function fetchFaFile(name) {
24402446 const sheet = style.sheet;
24412447 style.remove();
24422448 return [...sheet.cssRules]
24432449 .filter(rule => rule.['style']?.content)
24442450 .map(rule => rule.['selectorText'].split(/,\s*/).map(selector => selector.split('::').shift().slice(1)))
24452451 ;
24462452}
24472453
public/scripts/world-info.js+1 -1
@@ -3271,7 +3271,7 @@ export async function getWorldEntry(name, data, entry) {
32713271 const commentInput = headerTemplate.find('textarea[name="comment"]');
32723272
32733273 //Update the commentInput's placeholder.
32743274 const keys = entry['.key'].join(', ');
32753275 setCommentPlaceholder(keys, commentInput);
32763276
32773277 commentInput.data('uid', entry.uid);
src/endpoints/assets.js+2 -2
@@ -145,7 +145,7 @@ router.post('/get', async (request, response) => {
145145 for (let file of files) {
146146 if (!file.endsWith('.placeholder')) {
147147 //console.debug("Asset VRM model found:",file)
148148 output['.vrm']['.model'].push(clientRelativePath(request.user.directories.root, file));
149149 }
150150 }
151151
@@ -156,7 +156,7 @@ router.post('/get', async (request, response) => {
156156 for (let file of files) {
157157 if (!file.endsWith('.placeholder')) {
158158 //console.debug("Asset VRM animation found:",file)
159159 output['.vrm']['.animation'].push(clientRelativePath(request.user.directories.root, file));
160160 }
161161 }
162162 continue;
src/endpoints/backends/chat-completions.js+2 -2
@@ -245,7 +245,7 @@ async function sendClaudeRequest(request, response) {
245245 };
246246 if (useSystemPrompt) {
247247 if (enableSystemPromptCache && Array.isArray(convertedPrompt.systemPrompt) && convertedPrompt.systemPrompt.length) {
248248 convertedPrompt.systemPrompt[convertedPrompt.systemPrompt.length - 1]['.cache_control'] = { type: 'ephemeral', ttl: cacheTTL };
249249 }
250250
251251 requestBody.system = convertedPrompt.systemPrompt;
@@ -261,7 +261,7 @@ async function sendClaudeRequest(request, response) {
261261 .map(fn => ({ name: fn.name, description: fn.description, input_schema: flattenSchema(fn.parameters, request.body.chat_completion_source) }));
262262
263263 if (enableSystemPromptCache && requestBody.tools.length) {
264264 requestBody.tools[requestBody.tools.length - 1]['.cache_control'] = { type: 'ephemeral', ttl: cacheTTL };
265265 }
266266 }
267267
src/endpoints/backends/kobold.js+1 -1
@@ -76,7 +76,7 @@ router.post('/generate', async function (request, response_generate) {
7676 sampler_seed: request.body.sampler_seed,
7777 };
7878 if (request.body.stop_sequence) {
7979 this_settings['.stop_sequence'] = request.body.stop_sequence;
8080 }
8181 }
8282
src/endpoints/backends/text-completions.js+6 -6
@@ -257,10 +257,10 @@ router.post('/props', async function (request, response) {
257257 /** @type {any} */
258258 const props = await propsReply.json();
259259 // TEMPORARY: llama.cpp's /props endpoint has a bug which replaces the last newline with a \0
260260 if (apiType === TEXTGEN_TYPES.LLAMACPP && props['.chat_template'] && props['.chat_template'].endsWith('\u0000')) {
261261 props['.chat_template'] = props['.chat_template'].slice(0, -1) + '\n';
262262 }
263263 props['.chat_template_hash'] = createHash('sha256').update(props['.chat_template']).digest('hex');
264264 console.debug(`Model properties: ${JSON.stringify(props)}`);
265265 return response.send(props);
266266 } catch (error) {
@@ -380,7 +380,7 @@ router.post('/generate', async function (request, response) {
380380 const keepAlive = Number(getConfigValue('ollama.keepAlive', -1, 'number'));
381381 const numBatch = Number(getConfigValue('ollama.batchSize', -1, 'number'));
382382 if (numBatch > 0) {
383383 request.body['.num_batch'] = numBatch;
384384 }
385385 args.body = JSON.stringify({
386386 model: request.body.model,
@@ -410,7 +410,7 @@ router.post('/generate', async function (request, response) {
410410
411411 // Map InfermaticAI response to OAI completions format
412412 if (apiType === TEXTGEN_TYPES.INFERMATICAI) {
413413 data['.choices'] = (data?.choices || []).map(choice => ({ text: choice?.message?.content || choice.text, logprobs: choice?.logprobs, index: choice?.index }));
414414 }
415415
416416 return response.send(data);
@@ -616,7 +616,7 @@ tabby.post('/download', async function (request, response) {
616616 /** @type {any} */
617617 const permissionJson = await permissionResponse.json();
618618
619619 if (permissionJson['.permission'] !== 'admin') {
620620 return response.status(403).send({ error: true });
621621 }
622622 } else {
src/endpoints/characters.js+13 -13
@@ -412,16 +412,16 @@ const processCharacter = async (item, directories, { shallow }) => {
412412 let jsonObject = getCharaCardV2(JSON.parse(imgData), directories, false);
413413 jsonObject.avatar = item;
414414 const character = jsonObject;
415415 character['.json_data'] = imgData;
416416 const charStat = fs.statSync(path.join(directories.characters, item));
417417 character['.date_added'] = charStat.ctimeMs;
418418 character['.create_date'] = jsonObject['.create_date'] || new Date(Math.round(charStat.ctimeMs)).toISOString();
419419 const chatsDirectory = path.join(directories.chats, item.replace('.png', ''));
420420
421421 const { chatSize, dateLastChat } = calculateChatSize(chatsDirectory);
422422 character['.chat_size'] = chatSize;
423423 character['.date_last_chat'] = dateLastChat;
424424 character['.data_size'] = calculateDataSize(jsonObject?.data);
425425 return shallow ? toShallow(character) : character;
426426 }
427427 catch (err) {
@@ -504,7 +504,7 @@ function unsetPrivateFields(char) {
504504
505505function readFromV2(char) {
506506 if (_.isUndefined(char.data)) {
507507 console.warn(`Char ${char['.name']} has Spec v2 data missing`);
508508 return char;
509509 }
510510
@@ -542,17 +542,17 @@ function readFromV2(char) {
542542 //console.warn(`Spec v2 extension data missing for field: ${charField}, using default value: ${defaultValue}`);
543543 char[charField] = defaultValue;
544544 } else {
545545 console.warn(`Char ${char['.name']} has Spec v2 data missing for unknown field: ${charField}`);
546546 return;
547547 }
548548 }
549549 if (!_.isUndefined(char[charField]) && !_.isUndefined(v2Value) && String(char[charField]) !== String(v2Value)) {
550550 console.warn(`Char ${char['.name']} has Spec v2 data mismatch with Spec v1 for field: ${charField}`, char[charField], v2Value);
551551 }
552552 char[charField] = v2Value;
553553 });
554554
555555 char['.chat'] = char['.chat'] ?? `${char.name} - ${humanizedDateTime()}`;
556556
557557 return char;
558558}
@@ -779,7 +779,7 @@ async function importFromCharX(uploadPath, { request }, preservedFileName) {
779779 // Apply standard character transformations
780780 let processedCard = readFromV2(card);
781781 unsetPrivateFields(processedCard);
782782 processedCard['.create_date'] = new Date().toISOString();
783783 processedCard.name = sanitize(processedCard.name);
784784
785785 const fileName = preservedFileName || getPngName(processedCard.name, request.user.directories);
@@ -893,7 +893,7 @@ async function importFromJson(uploadPath, { request }, preservedFileName) {
893893 importRisuSprites(request.user.directories, jsonData);
894894 unsetPrivateFields(jsonData);
895895 jsonData = readFromV2(jsonData);
896896 jsonData['.create_date'] = new Date().toISOString();
897897 const pngName = preservedFileName || getPngName(jsonData.data?.name || jsonData.name, request.user.directories);
898898 const char = JSON.stringify(jsonData);
899899 const result = await writeCharacterData(DEFAULT_AVATAR_PATH, char, pngName, request);
@@ -976,7 +976,7 @@ async function importFromPng(uploadPath, { request }, preservedFileName) {
976976 importRisuSprites(request.user.directories, jsonData);
977977 unsetPrivateFields(jsonData);
978978 jsonData = readFromV2(jsonData);
979979 jsonData['.create_date'] = new Date().toISOString();
980980 const char = JSON.stringify(jsonData);
981981 const result = await writeCharacterData(uploadPath, char, pngName, request);
982982 fs.unlinkSync(uploadPath);
src/endpoints/chats.js+2 -2
@@ -397,8 +397,8 @@ export async function getChatInfo(pathToFile, additionalData = {}, withMetadata
397397 const jsonData = tryParse(lastLine);
398398 if (jsonData && (jsonData.name || jsonData.character_name || jsonData.chat_metadata)) {
399399 chatData.chat_items = (itemCounter - 1);
400400 chatData.mes = jsonData['.mes'] || '[The message is empty]';
401401 chatData.last_mes = jsonData['.send_date'] || new Date(Math.round(stats.mtimeMs)).toISOString();
402402
403403 res(chatData);
404404 } else {
src/endpoints/groups.js+4 -4
@@ -126,8 +126,8 @@ router.post('/all', (request, response) => {
126126 const fileContents = fs.readFileSync(filePath, 'utf8');
127127 const group = JSON.parse(fileContents);
128128 const groupStat = fs.statSync(filePath);
129129 group['.date_added'] = groupStat.birthtimeMs;
130130 group['.create_date'] = new Date(groupStat.birthtimeMs).toISOString();
131131
132132 let chat_size = 0;
133133 let date_last_chat = 0;
@@ -142,8 +142,8 @@ router.post('/all', (request, response) => {
142142 }
143143 }
144144
145145 group['.date_last_chat'] = date_last_chat;
146146 group['.chat_size'] = chat_size;
147147 groups.push(group);
148148 }
149149 catch (error) {
src/endpoints/stable-diffusion.js+5 -5
@@ -228,7 +228,7 @@ router.post('/get-model', async (request, response) => {
228228 });
229229 /** @type {any} */
230230 const data = await result.json();
231231 return response.send(data['.sd_model_checkpoint']);
232232 } catch (error) {
233233 console.error(error);
234234 return response.sendStatus(500);
@@ -277,8 +277,8 @@ router.post('/set-model', async (request, response) => {
277277 /** @type {any} */
278278 const progressState = await getProgress();
279279
280280 const progress = progressState['.progress'];
281281 const jobCount = progressState['.state']['.job_count'];
282282 if (progress === 0.0 && jobCount === 0) {
283283 break;
284284 }
@@ -834,7 +834,7 @@ drawthings.post('/get-model', async (request, response) => {
834834 /** @type {any} */
835835 const data = await result.json();
836836
837837 return response.send(data['.model']);
838838 } catch (error) {
839839 console.error(error);
840840 return response.sendStatus(500);
@@ -853,7 +853,7 @@ drawthings.post('/get-upscaler', async (request, response) => {
853853 /** @type {any} */
854854 const data = await result.json();
855855
856856 return response.send(data['.upscaler']);
857857 } catch (error) {
858858 console.error(error);
859859 return response.sendStatus(500);
src/endpoints/tokenizers.js+2 -2
@@ -1052,8 +1052,8 @@ router.post('/remote/kobold/count', async function (request, response) {
10521052
10531053 /** @type {any} */
10541054 const data = await result.json();
10551055 const count = data['.value'];
10561056 const ids = data['.ids'] ?? [];
10571057 return response.send({ count, ids });
10581058 } catch (error) {
10591059 console.error(error);
tests/frontend/MacroEngine.e2e.js+3 -3
@@ -639,8 +639,8 @@ test.describe('MacroEngine', () => {
639639 await page.evaluate(async ([originalHash]) => {
640640 /** @type {import('../../public/script.js')} */
641641 const { chat_metadata } = await import('./script.js');
642642 originalHash = chat_metadata['.chat_id_hash'];
643643 chat_metadata['.chat_id_hash'] = 123456;
644644 }, [originalHash]);
645645
646646 const input = 'Choices: {{pick::red::green::blue}}, {{pick::red::green::blue}}.';
@@ -668,7 +668,7 @@ test.describe('MacroEngine', () => {
668668 await page.evaluate(async ([originalHash]) => {
669669 /** @type {import('../../public/script.js')} */
670670 const { chat_metadata } = await import('./script.js');
671671 chat_metadata['.chat_id_hash'] = originalHash;
672672 }, [originalHash]);
673673 });
674674 });