Merge branch 'staging' into google-tts

This commit is contained in:
Cohee 2024-10-13 17:16:08 +03:00
commit 83ef5a67f6
14 changed files with 139 additions and 85 deletions

View File

@ -20,6 +20,15 @@ module.exports = {
sourceType: 'module', sourceType: 'module',
}, },
}, },
{
files: ['*.cjs'],
parserOptions: {
sourceType: 'commonjs',
},
env: {
node: true,
},
},
{ {
files: ['src/**/*.mjs'], files: ['src/**/*.mjs'],
parserOptions: { parserOptions: {
@ -74,6 +83,7 @@ module.exports = {
'docker/**', 'docker/**',
'plugins/**', 'plugins/**',
'**/*.min.js', '**/*.min.js',
'public/scripts/extensions/quick-reply/lib/**',
], ],
rules: { rules: {
'no-unused-vars': ['error', { args: 'none' }], 'no-unused-vars': ['error', { args: 'none' }],

View File

@ -12,7 +12,8 @@
"**/dist/**", "**/dist/**",
"**/.git/**", "**/.git/**",
"lib/**", "lib/**",
"**/*.min.js" "**/*.min.js",
"scripts/extensions/quick-reply/lib/**"
], ],
"typeAcquisition": { "typeAcquisition": {
"include": [ "include": [

View File

@ -7,7 +7,6 @@ import {
event_types, event_types,
eventSource, eventSource,
getCharacters, getCharacters,
getPastCharacterChats,
getRequestHeaders, getRequestHeaders,
buildAvatarList, buildAvatarList,
characterToEntity, characterToEntity,

View File

@ -315,7 +315,7 @@ class PromptManager {
*/ */
init(moduleConfiguration, serviceSettings) { init(moduleConfiguration, serviceSettings) {
this.configuration = Object.assign(this.configuration, moduleConfiguration); this.configuration = Object.assign(this.configuration, moduleConfiguration);
this.tokenHandler = this.tokenHandler || new TokenHandler(); this.tokenHandler = this.tokenHandler || new TokenHandler(() => { throw new Error('Token handler not set'); });
this.serviceSettings = serviceSettings; this.serviceSettings = serviceSettings;
this.containerElement = document.getElementById(this.configuration.containerIdentifier); this.containerElement = document.getElementById(this.configuration.containerIdentifier);

View File

@ -148,7 +148,7 @@ export function initDynamicStyles() {
// Start observing the head for any new added stylesheets // Start observing the head for any new added stylesheets
observer.observe(document.head, { observer.observe(document.head, {
childList: true, childList: true,
subtree: true subtree: true,
}); });
// Process all stylesheets on initial load // Process all stylesheets on initial load

View File

@ -1,6 +1,6 @@
import { ensureImageFormatSupported, getBase64Async, isTrueBoolean, saveBase64AsFile } from '../../utils.js'; import { ensureImageFormatSupported, getBase64Async, isTrueBoolean, saveBase64AsFile } from '../../utils.js';
import { getContext, getApiUrl, doExtrasFetch, extension_settings, modules, renderExtensionTemplateAsync } from '../../extensions.js'; import { getContext, getApiUrl, doExtrasFetch, extension_settings, modules, renderExtensionTemplateAsync } from '../../extensions.js';
import { appendMediaToMessage, callPopup, eventSource, event_types, getRequestHeaders, main_api, saveChatConditional, saveSettingsDebounced, substituteParamsExtended } from '../../../script.js'; import { appendMediaToMessage, callPopup, eventSource, event_types, getRequestHeaders, saveChatConditional, saveSettingsDebounced, substituteParamsExtended } from '../../../script.js';
import { getMessageTimeStamp } from '../../RossAscends-mods.js'; import { getMessageTimeStamp } from '../../RossAscends-mods.js';
import { SECRET_KEYS, secret_state } from '../../secrets.js'; import { SECRET_KEYS, secret_state } from '../../secrets.js';
import { getMultimodalCaption } from '../shared.js'; import { getMultimodalCaption } from '../shared.js';

View File

@ -1,8 +1,6 @@
import { import {
saveSettingsDebounced, saveSettingsDebounced,
systemUserName, systemUserName,
hideSwipeButtons,
showSwipeButtons,
getRequestHeaders, getRequestHeaders,
event_types, event_types,
eventSource, eventSource,
@ -2210,7 +2208,7 @@ function processReply(str) {
str = str.replaceAll('“', ''); str = str.replaceAll('“', '');
str = str.replaceAll('\n', ', '); str = str.replaceAll('\n', ', ');
str = str.normalize('NFD'); str = str.normalize('NFD');
str = str.replace(/[^a-zA-Z0-9\.,:_(){}<>[\]\-']+/g, ' '); str = str.replace(/[^a-zA-Z0-9.,:_(){}<>[\]\-']+/g, ' ');
str = str.replace(/\s+/g, ' '); // Collapse multiple whitespaces into one str = str.replace(/\s+/g, ' '); // Collapse multiple whitespaces into one
str = str.trim(); str = str.trim();

View File

@ -183,9 +183,9 @@ function redirectToHome() {
// After a login theres no need to preserve the // After a login theres no need to preserve the
// noauto (if present) // noauto (if present)
const urlParams = new URLSearchParams(window.location.search); const urlParams = new URLSearchParams(window.location.search);
urlParams.delete('noauto'); urlParams.delete('noauto');
window.location.href = '/' + urlParams.toString(); window.location.href = '/' + urlParams.toString();
} }

View File

@ -464,7 +464,7 @@ export function evaluateMacros(content, env) {
content = content.replace(/{{firstIncludedMessageId}}/gi, () => String(getFirstIncludedMessageId() ?? '')); content = content.replace(/{{firstIncludedMessageId}}/gi, () => String(getFirstIncludedMessageId() ?? ''));
content = content.replace(/{{lastSwipeId}}/gi, () => String(getLastSwipeId() ?? '')); content = content.replace(/{{lastSwipeId}}/gi, () => String(getLastSwipeId() ?? ''));
content = content.replace(/{{currentSwipeId}}/gi, () => String(getCurrentSwipeId() ?? '')); content = content.replace(/{{currentSwipeId}}/gi, () => String(getCurrentSwipeId() ?? ''));
content = content.replace(/{{reverse\:(.+?)}}/gi, (_, str) => Array.from(str).reverse().join('')); content = content.replace(/{{reverse:(.+?)}}/gi, (_, str) => Array.from(str).reverse().join(''));
content = content.replace(/\{\{\/\/([\s\S]*?)\}\}/gm, ''); content = content.replace(/\{\{\/\/([\s\S]*?)\}\}/gm, '');

View File

@ -60,7 +60,7 @@ import {
resetScrollHeight, resetScrollHeight,
stringFormat, stringFormat,
} from './utils.js'; } from './utils.js';
import { countTokensOpenAI, getTokenizerModel } from './tokenizers.js'; import { countTokensOpenAIAsync, getTokenizerModel } from './tokenizers.js';
import { isMobile } from './RossAscends-mods.js'; import { isMobile } from './RossAscends-mods.js';
import { saveLogprobsForActiveMessage } from './logprobs.js'; import { saveLogprobsForActiveMessage } from './logprobs.js';
import { SlashCommandParser } from './slash-commands/SlashCommandParser.js'; import { SlashCommandParser } from './slash-commands/SlashCommandParser.js';
@ -671,14 +671,14 @@ async function populateChatHistory(messages, prompts, chatCompletion, type = nul
// Reserve budget for new chat message // Reserve budget for new chat message
const newChat = selected_group ? oai_settings.new_group_chat_prompt : oai_settings.new_chat_prompt; const newChat = selected_group ? oai_settings.new_group_chat_prompt : oai_settings.new_chat_prompt;
const newChatMessage = new Message('system', substituteParams(newChat), 'newMainChat'); const newChatMessage = await Message.createAsync('system', substituteParams(newChat), 'newMainChat');
chatCompletion.reserveBudget(newChatMessage); chatCompletion.reserveBudget(newChatMessage);
// Reserve budget for group nudge // Reserve budget for group nudge
let groupNudgeMessage = null; let groupNudgeMessage = null;
const noGroupNudgeTypes = ['impersonate']; const noGroupNudgeTypes = ['impersonate'];
if (selected_group && prompts.has('groupNudge') && !noGroupNudgeTypes.includes(type)) { if (selected_group && prompts.has('groupNudge') && !noGroupNudgeTypes.includes(type)) {
groupNudgeMessage = Message.fromPrompt(prompts.get('groupNudge')); groupNudgeMessage = await Message.fromPromptAsync(prompts.get('groupNudge'));
chatCompletion.reserveBudget(groupNudgeMessage); chatCompletion.reserveBudget(groupNudgeMessage);
} }
@ -693,12 +693,12 @@ async function populateChatHistory(messages, prompts, chatCompletion, type = nul
}; };
const continuePrompt = new Prompt(promptObject); const continuePrompt = new Prompt(promptObject);
const preparedPrompt = promptManager.preparePrompt(continuePrompt); const preparedPrompt = promptManager.preparePrompt(continuePrompt);
continueMessage = Message.fromPrompt(preparedPrompt); continueMessage = await Message.fromPromptAsync(preparedPrompt);
chatCompletion.reserveBudget(continueMessage); chatCompletion.reserveBudget(continueMessage);
} }
const lastChatPrompt = messages[messages.length - 1]; const lastChatPrompt = messages[messages.length - 1];
const message = new Message('user', oai_settings.send_if_empty, 'emptyUserMessageReplacement'); const message = await Message.createAsync('user', oai_settings.send_if_empty, 'emptyUserMessageReplacement');
if (lastChatPrompt && lastChatPrompt.role === 'assistant' && oai_settings.send_if_empty && chatCompletion.canAfford(message)) { if (lastChatPrompt && lastChatPrompt.role === 'assistant' && oai_settings.send_if_empty && chatCompletion.canAfford(message)) {
chatCompletion.insert(message, 'chatHistory'); chatCompletion.insert(message, 'chatHistory');
} }
@ -715,11 +715,11 @@ async function populateChatHistory(messages, prompts, chatCompletion, type = nul
// We do not want to mutate the prompt // We do not want to mutate the prompt
const prompt = new Prompt(chatPrompt); const prompt = new Prompt(chatPrompt);
prompt.identifier = `chatHistory-${messages.length - index}`; prompt.identifier = `chatHistory-${messages.length - index}`;
const chatMessage = Message.fromPrompt(promptManager.preparePrompt(prompt)); const chatMessage = await Message.fromPromptAsync(promptManager.preparePrompt(prompt));
if (promptManager.serviceSettings.names_behavior === character_names_behavior.COMPLETION && prompt.name) { if (promptManager.serviceSettings.names_behavior === character_names_behavior.COMPLETION && prompt.name) {
const messageName = promptManager.isValidName(prompt.name) ? prompt.name : promptManager.sanitizeName(prompt.name); const messageName = promptManager.isValidName(prompt.name) ? prompt.name : promptManager.sanitizeName(prompt.name);
chatMessage.setName(messageName); await chatMessage.setName(messageName);
} }
if (imageInlining && chatPrompt.image) { if (imageInlining && chatPrompt.image) {
@ -729,9 +729,9 @@ async function populateChatHistory(messages, prompts, chatCompletion, type = nul
if (canUseTools && Array.isArray(chatPrompt.invocations)) { if (canUseTools && Array.isArray(chatPrompt.invocations)) {
/** @type {import('./tool-calling.js').ToolInvocation[]} */ /** @type {import('./tool-calling.js').ToolInvocation[]} */
const invocations = chatPrompt.invocations; const invocations = chatPrompt.invocations;
const toolCallMessage = new Message(chatMessage.role, undefined, 'toolCall-' + chatMessage.identifier); const toolCallMessage = await Message.createAsync(chatMessage.role, undefined, 'toolCall-' + chatMessage.identifier);
const toolResultMessages = invocations.slice().reverse().map((invocation) => new Message('tool', invocation.result || '[No content]', invocation.id)); const toolResultMessages = await Promise.all(invocations.slice().reverse().map((invocation) => Message.createAsync('tool', invocation.result || '[No content]', invocation.id)));
toolCallMessage.setToolCalls(invocations); await toolCallMessage.setToolCalls(invocations);
if (chatCompletion.canAffordAll([toolCallMessage, ...toolResultMessages])) { if (chatCompletion.canAffordAll([toolCallMessage, ...toolResultMessages])) {
for (const resultMessage of toolResultMessages) { for (const resultMessage of toolResultMessages) {
chatCompletion.insertAtStart(resultMessage, 'chatHistory'); chatCompletion.insertAtStart(resultMessage, 'chatHistory');
@ -748,7 +748,8 @@ async function populateChatHistory(messages, prompts, chatCompletion, type = nul
if (type === 'continue' && oai_settings.continue_prefill && chatPrompt === firstNonInjected) { if (type === 'continue' && oai_settings.continue_prefill && chatPrompt === firstNonInjected) {
// in case we are using continue_prefill and the latest message is an assistant message, we want to prepend the users assistant prefill on the message // in case we are using continue_prefill and the latest message is an assistant message, we want to prepend the users assistant prefill on the message
if (chatPrompt.role === 'assistant') { if (chatPrompt.role === 'assistant') {
const collection = new MessageCollection('continuePrefill', new Message(chatMessage.role, substituteParams(oai_settings.assistant_prefill + '\n\n') + chatMessage.content, chatMessage.identifier)); const continueMessage = await Message.createAsync(chatMessage.role, substituteParams(oai_settings.assistant_prefill + '\n\n') + chatMessage.content, chatMessage.identifier);
const collection = new MessageCollection('continuePrefill', continueMessage);
chatCompletion.add(collection, -1); chatCompletion.add(collection, -1);
continue; continue;
} }
@ -787,18 +788,17 @@ async function populateChatHistory(messages, prompts, chatCompletion, type = nul
* @param {ChatCompletion} chatCompletion - An instance of ChatCompletion class that will be populated with the prompts. * @param {ChatCompletion} chatCompletion - An instance of ChatCompletion class that will be populated with the prompts.
* @param {Object[]} messageExamples - Array containing all message examples. * @param {Object[]} messageExamples - Array containing all message examples.
*/ */
function populateDialogueExamples(prompts, chatCompletion, messageExamples) { async function populateDialogueExamples(prompts, chatCompletion, messageExamples) {
if (!prompts.has('dialogueExamples')) { if (!prompts.has('dialogueExamples')) {
return; return;
} }
chatCompletion.add(new MessageCollection('dialogueExamples'), prompts.index('dialogueExamples')); chatCompletion.add(new MessageCollection('dialogueExamples'), prompts.index('dialogueExamples'));
if (Array.isArray(messageExamples) && messageExamples.length) { if (Array.isArray(messageExamples) && messageExamples.length) {
const newExampleChat = new Message('system', substituteParams(oai_settings.new_example_chat_prompt), 'newChat'); const newExampleChat = await Message.createAsync('system', substituteParams(oai_settings.new_example_chat_prompt), 'newChat');
[...messageExamples].forEach((dialogue, dialogueIndex) => { for (const dialogue of [...messageExamples]) {
let examplesAdded = 0; const dialogueIndex = messageExamples.indexOf(dialogue);
const chatMessages = [];
if (chatCompletion.canAfford(newExampleChat)) chatCompletion.insert(newExampleChat, 'dialogueExamples');
for (let promptIndex = 0; promptIndex < dialogue.length; promptIndex++) { for (let promptIndex = 0; promptIndex < dialogue.length; promptIndex++) {
const prompt = dialogue[promptIndex]; const prompt = dialogue[promptIndex];
@ -806,19 +806,20 @@ function populateDialogueExamples(prompts, chatCompletion, messageExamples) {
const content = prompt.content || ''; const content = prompt.content || '';
const identifier = `dialogueExamples ${dialogueIndex}-${promptIndex}`; const identifier = `dialogueExamples ${dialogueIndex}-${promptIndex}`;
const chatMessage = new Message(role, content, identifier); const chatMessage = await Message.createAsync(role, content, identifier);
chatMessage.setName(prompt.name); await chatMessage.setName(prompt.name);
if (!chatCompletion.canAfford(chatMessage)) { chatMessages.push(chatMessage);
break;
}
chatCompletion.insert(chatMessage, 'dialogueExamples');
examplesAdded++;
} }
if (0 === examplesAdded) { if (!chatCompletion.canAffordAll([newExampleChat, ...chatMessages])) {
chatCompletion.removeLastFrom('dialogueExamples'); break;
} }
});
chatCompletion.insert(newExampleChat, 'dialogueExamples');
for (const chatMessage of chatMessages) {
chatCompletion.insert(chatMessage, 'dialogueExamples');
}
}
} }
} }
@ -873,7 +874,7 @@ function getPromptRole(role) {
*/ */
async function populateChatCompletion(prompts, chatCompletion, { bias, quietPrompt, quietImage, type, cyclePrompt, messages, messageExamples }) { async function populateChatCompletion(prompts, chatCompletion, { bias, quietPrompt, quietImage, type, cyclePrompt, messages, messageExamples }) {
// Helper function for preparing a prompt, that already exists within the prompt collection, for completion // Helper function for preparing a prompt, that already exists within the prompt collection, for completion
const addToChatCompletion = (source, target = null) => { const addToChatCompletion = async (source, target = null) => {
// We need the prompts array to determine a position for the source. // We need the prompts array to determine a position for the source.
if (false === prompts.has(source)) return; if (false === prompts.has(source)) return;
@ -891,30 +892,31 @@ async function populateChatCompletion(prompts, chatCompletion, { bias, quietProm
const index = target ? prompts.index(target) : prompts.index(source); const index = target ? prompts.index(target) : prompts.index(source);
const collection = new MessageCollection(source); const collection = new MessageCollection(source);
collection.add(Message.fromPrompt(prompt)); const message = await Message.fromPromptAsync(prompt);
collection.add(message);
chatCompletion.add(collection, index); chatCompletion.add(collection, index);
}; };
chatCompletion.reserveBudget(3); // every reply is primed with <|start|>assistant<|message|> chatCompletion.reserveBudget(3); // every reply is primed with <|start|>assistant<|message|>
// Character and world information // Character and world information
addToChatCompletion('worldInfoBefore'); await addToChatCompletion('worldInfoBefore');
addToChatCompletion('main'); await addToChatCompletion('main');
addToChatCompletion('worldInfoAfter'); await addToChatCompletion('worldInfoAfter');
addToChatCompletion('charDescription'); await addToChatCompletion('charDescription');
addToChatCompletion('charPersonality'); await addToChatCompletion('charPersonality');
addToChatCompletion('scenario'); await addToChatCompletion('scenario');
addToChatCompletion('personaDescription'); await addToChatCompletion('personaDescription');
// Collection of control prompts that will always be positioned last // Collection of control prompts that will always be positioned last
chatCompletion.setOverriddenPrompts(prompts.overriddenPrompts); chatCompletion.setOverriddenPrompts(prompts.overriddenPrompts);
const controlPrompts = new MessageCollection('controlPrompts'); const controlPrompts = new MessageCollection('controlPrompts');
const impersonateMessage = Message.fromPrompt(prompts.get('impersonate')) ?? null; const impersonateMessage = await Message.fromPromptAsync(prompts.get('impersonate')) ?? null;
if (type === 'impersonate') controlPrompts.add(impersonateMessage); if (type === 'impersonate') controlPrompts.add(impersonateMessage);
// Add quiet prompt to control prompts // Add quiet prompt to control prompts
// This should always be last, even in control prompts. Add all further control prompts BEFORE this prompt // This should always be last, even in control prompts. Add all further control prompts BEFORE this prompt
const quietPromptMessage = Message.fromPrompt(prompts.get('quietPrompt')) ?? null; const quietPromptMessage = await Message.fromPromptAsync(prompts.get('quietPrompt')) ?? null;
if (quietPromptMessage && quietPromptMessage.content) { if (quietPromptMessage && quietPromptMessage.content) {
if (isImageInliningSupported() && quietImage) { if (isImageInliningSupported() && quietImage) {
await quietPromptMessage.addImage(quietImage); await quietPromptMessage.addImage(quietImage);
@ -940,20 +942,23 @@ async function populateChatCompletion(prompts, chatCompletion, { bias, quietProm
return acc; return acc;
}, []); }, []);
[...systemPrompts, ...userRelativePrompts].forEach(identifier => addToChatCompletion(identifier)); for (const identifier of [...systemPrompts, ...userRelativePrompts]) {
await addToChatCompletion(identifier);
}
// Add enhance definition instruction // Add enhance definition instruction
if (prompts.has('enhanceDefinitions')) addToChatCompletion('enhanceDefinitions'); if (prompts.has('enhanceDefinitions')) await addToChatCompletion('enhanceDefinitions');
// Bias // Bias
if (bias && bias.trim().length) addToChatCompletion('bias'); if (bias && bias.trim().length) await addToChatCompletion('bias');
// Tavern Extras - Summary // Tavern Extras - Summary
if (prompts.has('summary')) { if (prompts.has('summary')) {
const summary = prompts.get('summary'); const summary = prompts.get('summary');
if (summary.position) { if (summary.position) {
chatCompletion.insert(Message.fromPrompt(summary), 'main', summary.position); const message = await Message.fromPromptAsync(summary);
chatCompletion.insert(message, 'main', summary.position);
} }
} }
@ -962,7 +967,8 @@ async function populateChatCompletion(prompts, chatCompletion, { bias, quietProm
const authorsNote = prompts.get('authorsNote'); const authorsNote = prompts.get('authorsNote');
if (authorsNote.position) { if (authorsNote.position) {
chatCompletion.insert(Message.fromPrompt(authorsNote), 'main', authorsNote.position); const message = await Message.fromPromptAsync(authorsNote);
chatCompletion.insert(message, 'main', authorsNote.position);
} }
} }
@ -971,7 +977,8 @@ async function populateChatCompletion(prompts, chatCompletion, { bias, quietProm
const vectorsMemory = prompts.get('vectorsMemory'); const vectorsMemory = prompts.get('vectorsMemory');
if (vectorsMemory.position) { if (vectorsMemory.position) {
chatCompletion.insert(Message.fromPrompt(vectorsMemory), 'main', vectorsMemory.position); const message = await Message.fromPromptAsync(vectorsMemory);
chatCompletion.insert(message, 'main', vectorsMemory.position);
} }
} }
@ -980,7 +987,8 @@ async function populateChatCompletion(prompts, chatCompletion, { bias, quietProm
const vectorsDataBank = prompts.get('vectorsDataBank'); const vectorsDataBank = prompts.get('vectorsDataBank');
if (vectorsDataBank.position) { if (vectorsDataBank.position) {
chatCompletion.insert(Message.fromPrompt(vectorsDataBank), 'main', vectorsDataBank.position); const message = await Message.fromPromptAsync(vectorsDataBank);
chatCompletion.insert(message, 'main', vectorsDataBank.position);
} }
} }
@ -989,13 +997,15 @@ async function populateChatCompletion(prompts, chatCompletion, { bias, quietProm
const smartContext = prompts.get('smartContext'); const smartContext = prompts.get('smartContext');
if (smartContext.position) { if (smartContext.position) {
chatCompletion.insert(Message.fromPrompt(smartContext), 'main', smartContext.position); const message = await Message.fromPromptAsync(smartContext);
chatCompletion.insert(message, 'main', smartContext.position);
} }
} }
// Other relative extension prompts // Other relative extension prompts
for (const prompt of prompts.collection.filter(p => p.extension && p.position)) { for (const prompt of prompts.collection.filter(p => p.extension && p.position)) {
chatCompletion.insert(Message.fromPrompt(prompt), 'main', prompt.position); const message = await Message.fromPromptAsync(prompt);
chatCompletion.insert(message, 'main', prompt.position);
} }
// Pre-allocation of tokens for tool data // Pre-allocation of tokens for tool data
@ -1003,7 +1013,7 @@ async function populateChatCompletion(prompts, chatCompletion, { bias, quietProm
const toolData = {}; const toolData = {};
await ToolManager.registerFunctionToolsOpenAI(toolData); await ToolManager.registerFunctionToolsOpenAI(toolData);
const toolMessage = [{ role: 'user', content: JSON.stringify(toolData) }]; const toolMessage = [{ role: 'user', content: JSON.stringify(toolData) }];
const toolTokens = tokenHandler.count(toolMessage); const toolTokens = await tokenHandler.countAsync(toolMessage);
chatCompletion.reserveBudget(toolTokens); chatCompletion.reserveBudget(toolTokens);
} }
@ -1012,11 +1022,11 @@ async function populateChatCompletion(prompts, chatCompletion, { bias, quietProm
// Decide whether dialogue examples should always be added // Decide whether dialogue examples should always be added
if (power_user.pin_examples) { if (power_user.pin_examples) {
populateDialogueExamples(prompts, chatCompletion, messageExamples); await populateDialogueExamples(prompts, chatCompletion, messageExamples);
await populateChatHistory(messages, prompts, chatCompletion, type, cyclePrompt); await populateChatHistory(messages, prompts, chatCompletion, type, cyclePrompt);
} else { } else {
await populateChatHistory(messages, prompts, chatCompletion, type, cyclePrompt); await populateChatHistory(messages, prompts, chatCompletion, type, cyclePrompt);
populateDialogueExamples(prompts, chatCompletion, messageExamples); await populateDialogueExamples(prompts, chatCompletion, messageExamples);
} }
chatCompletion.freeBudget(controlPrompts); chatCompletion.freeBudget(controlPrompts);
@ -1281,7 +1291,7 @@ export async function prepareOpenAIMessages({
promptManager.setChatCompletion(chatCompletion); promptManager.setChatCompletion(chatCompletion);
if (oai_settings.squash_system_messages && dryRun == false) { if (oai_settings.squash_system_messages && dryRun == false) {
chatCompletion.squashSystemMessages(); await chatCompletion.squashSystemMessages();
} }
// All information is up-to-date, render. // All information is up-to-date, render.
@ -2127,8 +2137,11 @@ async function calculateLogitBias() {
} }
class TokenHandler { class TokenHandler {
constructor(countTokenFn) { /**
this.countTokenFn = countTokenFn; * @param {(messages: object[] | object, full?: boolean) => Promise<number>} countTokenAsyncFn Function to count tokens
*/
constructor(countTokenAsyncFn) {
this.countTokenAsyncFn = countTokenAsyncFn;
this.counts = { this.counts = {
'start_chat': 0, 'start_chat': 0,
'prompt': 0, 'prompt': 0,
@ -2157,8 +2170,15 @@ class TokenHandler {
this.counts[type] -= value; this.counts[type] -= value;
} }
count(messages, full, type) { /**
const token_count = this.countTokenFn(messages, full); * Count tokens for a message or messages.
* @param {object|any[]} messages Messages to count tokens for
* @param {boolean} [full] Count full tokens
* @param {string} [type] Identifier for the token count
* @returns {Promise<number>} The token count
*/
async countAsync(messages, full, type) {
const token_count = await this.countTokenAsyncFn(messages, full);
this.counts[type] += token_count; this.counts[type] += token_count;
return token_count; return token_count;
@ -2178,7 +2198,7 @@ class TokenHandler {
} }
const tokenHandler = new TokenHandler(countTokensOpenAI); const tokenHandler = new TokenHandler(countTokensOpenAIAsync);
// Thrown by ChatCompletion when a requested prompt couldn't be found. // Thrown by ChatCompletion when a requested prompt couldn't be found.
class IdentifierNotFoundError extends Error { class IdentifierNotFoundError extends Error {
@ -2228,6 +2248,7 @@ class Message {
* @param {string} role - The role of the entity creating the message. * @param {string} role - The role of the entity creating the message.
* @param {string} content - The actual content of the message. * @param {string} content - The actual content of the message.
* @param {string} identifier - A unique identifier for the message. * @param {string} identifier - A unique identifier for the message.
* @private Don't use this constructor directly. Use createAsync instead.
*/ */
constructor(role, content, identifier) { constructor(role, content, identifier) {
this.identifier = identifier; this.identifier = identifier;
@ -2239,18 +2260,32 @@ class Message {
this.role = 'system'; this.role = 'system';
} }
if (typeof this.content === 'string' && this.content.length > 0) { this.tokens = 0;
this.tokens = tokenHandler.count({ role: this.role, content: this.content }); }
} else {
this.tokens = 0; /**
* Create a new Message instance.
* @param {string} role
* @param {string} content
* @param {string} identifier
* @returns {Promise<Message>} Message instance
*/
static async createAsync(role, content, identifier) {
const message = new Message(role, content, identifier);
if (typeof message.content === 'string' && message.content.length > 0) {
message.tokens = await tokenHandler.countAsync({ role: message.role, content: message.content });
} }
return message;
} }
/** /**
* Reconstruct the message from a tool invocation. * Reconstruct the message from a tool invocation.
* @param {import('./tool-calling.js').ToolInvocation[]} invocations * @param {import('./tool-calling.js').ToolInvocation[]} invocations - The tool invocations to reconstruct the message from.
* @returns {Promise<void>}
*/ */
setToolCalls(invocations) { async setToolCalls(invocations) {
this.tool_calls = invocations.map(i => ({ this.tool_calls = invocations.map(i => ({
id: i.id, id: i.id,
type: 'function', type: 'function',
@ -2259,14 +2294,24 @@ class Message {
name: i.name, name: i.name,
}, },
})); }));
this.tokens = tokenHandler.count({ role: this.role, tool_calls: JSON.stringify(this.tool_calls) }); this.tokens = await tokenHandler.countAsync({ role: this.role, tool_calls: JSON.stringify(this.tool_calls) });
} }
setName(name) { /**
* Add a name to the message.
* @param {string} name Name to set for the message.
* @returns {Promise<void>}
*/
async setName(name) {
this.name = name; this.name = name;
this.tokens = tokenHandler.count({ role: this.role, content: this.content, name: this.name }); this.tokens = await tokenHandler.countAsync({ role: this.role, content: this.content, name: this.name });
} }
/**
* Adds an image to the message.
* @param {string} image Image URL or Data URL.
* @returns {Promise<void>}
*/
async addImage(image) { async addImage(image) {
const textContent = this.content; const textContent = this.content;
const isDataUrl = isDataURL(image); const isDataUrl = isDataURL(image);
@ -2356,13 +2401,13 @@ class Message {
} }
/** /**
* Create a new Message instance from a prompt. * Create a new Message instance from a prompt asynchronously.
* @static * @static
* @param {Object} prompt - The prompt object. * @param {Object} prompt - The prompt object.
* @returns {Message} A new instance of Message. * @returns {Promise<Message>} A new instance of Message.
*/ */
static fromPrompt(prompt) { static fromPromptAsync(prompt) {
return new Message(prompt.role, prompt.content, prompt.identifier); return Message.createAsync(prompt.role, prompt.content, prompt.identifier);
} }
/** /**
@ -2488,8 +2533,9 @@ export class ChatCompletion {
/** /**
* Combines consecutive system messages into one if they have no name attached. * Combines consecutive system messages into one if they have no name attached.
* @returns {Promise<void>}
*/ */
squashSystemMessages() { async squashSystemMessages() {
const excludeList = ['newMainChat', 'newChat', 'groupNudge']; const excludeList = ['newMainChat', 'newChat', 'groupNudge'];
this.messages.collection = this.messages.flatten(); this.messages.collection = this.messages.flatten();
@ -2509,7 +2555,7 @@ export class ChatCompletion {
if (shouldSquash(message)) { if (shouldSquash(message)) {
if (lastMessage && shouldSquash(lastMessage)) { if (lastMessage && shouldSquash(lastMessage)) {
lastMessage.content += '\n' + message.content; lastMessage.content += '\n' + message.content;
lastMessage.tokens = tokenHandler.count({ role: lastMessage.role, content: lastMessage.content }); lastMessage.tokens = await tokenHandler.countAsync({ role: lastMessage.role, content: lastMessage.content });
} }
else { else {
squashedMessages.push(message); squashedMessages.push(message);

View File

@ -4,7 +4,6 @@ import { textgenerationwebui_settings as textgen_settings, textgen_types } from
import { tokenizers } from './tokenizers.js'; import { tokenizers } from './tokenizers.js';
import { renderTemplateAsync } from './templates.js'; import { renderTemplateAsync } from './templates.js';
import { POPUP_TYPE, callGenericPopup } from './popup.js'; import { POPUP_TYPE, callGenericPopup } from './popup.js';
import { PAGINATION_TEMPLATE } from './utils.js';
let mancerModels = []; let mancerModels = [];
let togetherModels = []; let togetherModels = [];

View File

@ -1,6 +1,5 @@
import { chat_metadata, getCurrentChatId, saveSettingsDebounced, sendSystemMessage, system_message_types } from '../script.js'; import { chat_metadata, getCurrentChatId, saveSettingsDebounced } from '../script.js';
import { extension_settings, saveMetadataDebounced } from './extensions.js'; import { extension_settings, saveMetadataDebounced } from './extensions.js';
import { callGenericPopup, POPUP_TYPE } from './popup.js';
import { executeSlashCommandsWithOptions } from './slash-commands.js'; import { executeSlashCommandsWithOptions } from './slash-commands.js';
import { SlashCommand } from './slash-commands/SlashCommand.js'; import { SlashCommand } from './slash-commands/SlashCommand.js';
import { SlashCommandAbortController } from './slash-commands/SlashCommandAbortController.js'; import { SlashCommandAbortController } from './slash-commands/SlashCommandAbortController.js';

View File

@ -4565,6 +4565,7 @@ function convertCharacterBook(characterBook) {
sticky: entry.extensions?.sticky ?? null, sticky: entry.extensions?.sticky ?? null,
cooldown: entry.extensions?.cooldown ?? null, cooldown: entry.extensions?.cooldown ?? null,
delay: entry.extensions?.delay ?? null, delay: entry.extensions?.delay ?? null,
extensions: entry.extensions ?? {},
}; };
}); });

View File

@ -468,6 +468,7 @@ function convertWorldInfoToCharacterBook(name, entries) {
position: entry.position == 0 ? 'before_char' : 'after_char', position: entry.position == 0 ? 'before_char' : 'after_char',
use_regex: true, // ST keys are always regex use_regex: true, // ST keys are always regex
extensions: { extensions: {
...entry.extensions,
position: entry.position, position: entry.position,
exclude_recursion: entry.excludeRecursion, exclude_recursion: entry.excludeRecursion,
display_index: entry.displayIndex, display_index: entry.displayIndex,