-
-
+
+
';
$('#token_counter_wand_container').append(buttonHtml);
$('#token_counter').on('click', doTokenCounter);
SlashCommandParser.addCommandObject(SlashCommand.fromProps({
diff --git a/public/scripts/extensions/token-counter/window.html b/public/scripts/extensions/token-counter/window.html
new file mode 100644
index 000000000..bcd2e3b0e
--- /dev/null
+++ b/public/scripts/extensions/token-counter/window.html
@@ -0,0 +1,16 @@
+
+
Token Counter
+
+
Type / paste in the box below to see the number of tokens in the text.
+
Selected tokenizer: {{tokenizerName}}
+
Input:
+
+
Tokens: 0
+
+
Tokenized text:
+
—
+
+
Token IDs:
+
+
+
\ No newline at end of file
diff --git a/public/scripts/extensions/vectors/index.js b/public/scripts/extensions/vectors/index.js
index aea42d909..5506bec00 100644
--- a/public/scripts/extensions/vectors/index.js
+++ b/public/scripts/extensions/vectors/index.js
@@ -745,6 +745,44 @@ async function getQueryText(chat, initiator) {
return collapseNewlines(queryText).trim();
}
+/**
+ * Gets common body parameters for vector requests.
+ * @returns {object}
+ */
+function getVectorsRequestBody() {
+ const body = {};
+ switch (settings.source) {
+ case 'extras':
+ body.extrasUrl = extension_settings.apiUrl;
+ body.extrasKey = extension_settings.apiKey;
+ break;
+ case 'togetherai':
+ body.model = extension_settings.vectors.togetherai_model;
+ break;
+ case 'openai':
+ body.model = extension_settings.vectors.openai_model;
+ break;
+ case 'cohere':
+ body.model = extension_settings.vectors.cohere_model;
+ break;
+ case 'ollama':
+ body.model = extension_settings.vectors.ollama_model;
+ body.apiUrl = textgenerationwebui_settings.server_urls[textgen_types.OLLAMA];
+ body.keep = !!extension_settings.vectors.ollama_keep;
+ break;
+ case 'llamacpp':
+ body.apiUrl = textgenerationwebui_settings.server_urls[textgen_types.LLAMACPP];
+ break;
+ case 'vllm':
+ body.apiUrl = textgenerationwebui_settings.server_urls[textgen_types.VLLM];
+ body.model = extension_settings.vectors.vllm_model;
+ break;
+ default:
+ break;
+ }
+ return body;
+}
+
/**
* Gets the saved hashes for a collection
* @param {string} collectionId
@@ -753,8 +791,9 @@ async function getQueryText(chat, initiator) {
async function getSavedHashes(collectionId) {
const response = await fetch('/api/vector/list', {
method: 'POST',
- headers: getVectorHeaders(),
+ headers: getRequestHeaders(),
body: JSON.stringify({
+ ...getVectorsRequestBody(),
collectionId: collectionId,
source: settings.source,
}),
@@ -768,54 +807,6 @@ async function getSavedHashes(collectionId) {
return hashes;
}
-function getVectorHeaders() {
- const headers = getRequestHeaders();
- switch (settings.source) {
- case 'extras':
- Object.assign(headers, {
- 'X-Extras-Url': extension_settings.apiUrl,
- 'X-Extras-Key': extension_settings.apiKey,
- });
- break;
- case 'togetherai':
- Object.assign(headers, {
- 'X-Togetherai-Model': extension_settings.vectors.togetherai_model,
- });
- break;
- case 'openai':
- Object.assign(headers, {
- 'X-OpenAI-Model': extension_settings.vectors.openai_model,
- });
- break;
- case 'cohere':
- Object.assign(headers, {
- 'X-Cohere-Model': extension_settings.vectors.cohere_model,
- });
- break;
- case 'ollama':
- Object.assign(headers, {
- 'X-Ollama-Model': extension_settings.vectors.ollama_model,
- 'X-Ollama-URL': textgenerationwebui_settings.server_urls[textgen_types.OLLAMA],
- 'X-Ollama-Keep': !!extension_settings.vectors.ollama_keep,
- });
- break;
- case 'llamacpp':
- Object.assign(headers, {
- 'X-LlamaCpp-URL': textgenerationwebui_settings.server_urls[textgen_types.LLAMACPP],
- });
- break;
- case 'vllm':
- Object.assign(headers, {
- 'X-Vllm-URL': textgenerationwebui_settings.server_urls[textgen_types.VLLM],
- 'X-Vllm-Model': extension_settings.vectors.vllm_model,
- });
- break;
- default:
- break;
- }
- return headers;
-}
-
/**
* Inserts vector items into a collection
* @param {string} collectionId - The collection to insert into
@@ -825,12 +816,11 @@ function getVectorHeaders() {
async function insertVectorItems(collectionId, items) {
throwIfSourceInvalid();
- const headers = getVectorHeaders();
-
const response = await fetch('/api/vector/insert', {
method: 'POST',
- headers: headers,
+ headers: getRequestHeaders(),
body: JSON.stringify({
+ ...getVectorsRequestBody(),
collectionId: collectionId,
items: items,
source: settings.source,
@@ -879,8 +869,9 @@ function throwIfSourceInvalid() {
async function deleteVectorItems(collectionId, hashes) {
const response = await fetch('/api/vector/delete', {
method: 'POST',
- headers: getVectorHeaders(),
+ headers: getRequestHeaders(),
body: JSON.stringify({
+ ...getVectorsRequestBody(),
collectionId: collectionId,
hashes: hashes,
source: settings.source,
@@ -899,12 +890,11 @@ async function deleteVectorItems(collectionId, hashes) {
* @returns {Promise<{ hashes: number[], metadata: object[]}>} - Hashes of the results
*/
async function queryCollection(collectionId, searchText, topK) {
- const headers = getVectorHeaders();
-
const response = await fetch('/api/vector/query', {
method: 'POST',
- headers: headers,
+ headers: getRequestHeaders(),
body: JSON.stringify({
+ ...getVectorsRequestBody(),
collectionId: collectionId,
searchText: searchText,
topK: topK,
@@ -929,12 +919,11 @@ async function queryCollection(collectionId, searchText, topK) {
* @returns {Promise
>} - Results mapped to collection IDs
*/
async function queryMultipleCollections(collectionIds, searchText, topK, threshold) {
- const headers = getVectorHeaders();
-
const response = await fetch('/api/vector/query-multi', {
method: 'POST',
- headers: headers,
+ headers: getRequestHeaders(),
body: JSON.stringify({
+ ...getVectorsRequestBody(),
collectionIds: collectionIds,
searchText: searchText,
topK: topK,
@@ -965,8 +954,9 @@ async function purgeFileVectorIndex(fileUrl) {
const response = await fetch('/api/vector/purge', {
method: 'POST',
- headers: getVectorHeaders(),
+ headers: getRequestHeaders(),
body: JSON.stringify({
+ ...getVectorsRequestBody(),
collectionId: collectionId,
}),
});
@@ -994,8 +984,9 @@ async function purgeVectorIndex(collectionId) {
const response = await fetch('/api/vector/purge', {
method: 'POST',
- headers: getVectorHeaders(),
+ headers: getRequestHeaders(),
body: JSON.stringify({
+ ...getVectorsRequestBody(),
collectionId: collectionId,
}),
});
@@ -1019,7 +1010,10 @@ async function purgeAllVectorIndexes() {
try {
const response = await fetch('/api/vector/purge-all', {
method: 'POST',
- headers: getVectorHeaders(),
+ headers: getRequestHeaders(),
+ body: JSON.stringify({
+ ...getVectorsRequestBody(),
+ }),
});
if (!response.ok) {
diff --git a/public/scripts/reasoning.js b/public/scripts/reasoning.js
index e5dbce147..44927d83f 100644
--- a/public/scripts/reasoning.js
+++ b/public/scripts/reasoning.js
@@ -3,17 +3,30 @@ import {
} from '../lib.js';
import { chat, closeMessageEditor, event_types, eventSource, main_api, messageFormatting, saveChatConditional, saveSettingsDebounced, substituteParams, updateMessageBlock } from '../script.js';
import { getRegexedString, regex_placement } from './extensions/regex/engine.js';
-import { getCurrentLocale, t } from './i18n.js';
+import { getCurrentLocale, t, translate } from './i18n.js';
import { MacrosParser } from './macros.js';
import { chat_completion_sources, getChatCompletionModel, oai_settings } from './openai.js';
import { Popup } from './popup.js';
import { power_user } from './power-user.js';
import { SlashCommand } from './slash-commands/SlashCommand.js';
import { ARGUMENT_TYPE, SlashCommandArgument, SlashCommandNamedArgument } from './slash-commands/SlashCommandArgument.js';
-import { commonEnumProviders } from './slash-commands/SlashCommandCommonEnumsProvider.js';
+import { commonEnumProviders, enumIcons } from './slash-commands/SlashCommandCommonEnumsProvider.js';
+import { enumTypes, SlashCommandEnumValue } from './slash-commands/SlashCommandEnumValue.js';
import { SlashCommandParser } from './slash-commands/SlashCommandParser.js';
import { textgen_types, textgenerationwebui_settings } from './textgen-settings.js';
-import { copyText, escapeRegex, isFalseBoolean, setDatasetProperty } from './utils.js';
+import { copyText, escapeRegex, isFalseBoolean, setDatasetProperty, trimSpaces } from './utils.js';
+
+/**
+ * Enum representing the type of the reasoning for a message (where it came from)
+ * @enum {string}
+ * @readonly
+ */
+export const ReasoningType = {
+ Model: 'model',
+ Parsed: 'parsed',
+ Manual: 'manual',
+ Edited: 'edited',
+};
/**
* Gets a message from a jQuery element.
@@ -129,7 +142,12 @@ export const ReasoningState = {
* This class is used inside the {@link StreamingProcessor} to manage reasoning states and UI updates.
*/
export class ReasoningHandler {
+ /** @type {boolean} True if the model supports reasoning, but hides the reasoning output */
#isHiddenReasoningModel;
+ /** @type {boolean} True if the handler is currently handling a manual parse of reasoning blocks */
+ #isParsingReasoning = false;
+ /** @type {number?} When reasoning is being parsed manually, and the reasoning has ended, this will be the index at which the actual messages starts */
+ #parsingReasoningMesStartIndex = null;
/**
* @param {Date?} [timeStarted=null] - When the generation started
@@ -137,6 +155,8 @@ export class ReasoningHandler {
constructor(timeStarted = null) {
/** @type {ReasoningState} The current state of the reasoning process */
this.state = ReasoningState.None;
+ /** @type {ReasoningType?} The type of the reasoning (where it came from) */
+ this.type = null;
/** @type {string} The reasoning output */
this.reasoning = '';
/** @type {Date} When the reasoning started */
@@ -147,7 +167,6 @@ export class ReasoningHandler {
/** @type {Date} Initial starting time of the generation */
this.initialTime = timeStarted ?? new Date();
- /** @type {boolean} True if the model supports reasoning, but hides the reasoning output */
this.#isHiddenReasoningModel = isHiddenReasoningModel();
// Cached DOM elements for reasoning
@@ -194,6 +213,7 @@ export class ReasoningHandler {
this.state = ReasoningState.Hidden;
}
+ this.type = extra?.reasoning_type;
this.reasoning = extra?.reasoning ?? '';
if (this.state !== ReasoningState.None) {
@@ -208,6 +228,7 @@ export class ReasoningHandler {
// Make sure reset correctly clears all relevant states
if (reset) {
this.state = this.#isHiddenReasoningModel ? ReasoningState.Thinking : ReasoningState.None;
+ this.type = null;
this.reasoning = '';
this.initialTime = new Date();
this.startTime = null;
@@ -237,18 +258,19 @@ export class ReasoningHandler {
* Updates the reasoning text/string for a message.
*
* @param {number} messageId - The ID of the message to update
- * @param {string?} [reasoning=null] - The reasoning text to update - If null, uses the current reasoning
+ * @param {string?} [reasoning=null] - The reasoning text to update - If null or empty, uses the current reasoning
* @param {Object} [options={}] - Optional arguments
* @param {boolean} [options.persist=false] - Whether to persist the reasoning to the message object
+ * @param {boolean} [options.allowReset=false] - Whether to allow empty reasoning provided to reset the reasoning, instead of just taking the existing one
* @returns {boolean} - Returns true if the reasoning was changed, otherwise false
*/
- updateReasoning(messageId, reasoning = null, { persist = false } = {}) {
+ updateReasoning(messageId, reasoning = null, { persist = false, allowReset = false } = {}) {
if (messageId == -1 || !chat[messageId]) {
return false;
}
- reasoning = reasoning ?? this.reasoning;
- reasoning = power_user.trim_spaces ? reasoning.trim() : reasoning;
+ reasoning = allowReset ? reasoning ?? this.reasoning : reasoning || this.reasoning;
+ reasoning = trimSpaces(reasoning);
// Ensure the chat extra exists
if (!chat[messageId].extra) {
@@ -259,10 +281,13 @@ export class ReasoningHandler {
const reasoningChanged = extra.reasoning !== reasoning;
this.reasoning = getRegexedString(reasoning ?? '', regex_placement.REASONING);
+ this.type = (this.#isParsingReasoning || this.#parsingReasoningMesStartIndex) ? ReasoningType.Parsed : ReasoningType.Model;
+
if (persist) {
// Build and save the reasoning data to message extras
extra.reasoning = this.reasoning;
extra.reasoning_duration = this.getDuration();
+ extra.reasoning_type = (this.#isParsingReasoning || this.#parsingReasoningMesStartIndex) ? ReasoningType.Parsed : ReasoningType.Model;
}
return reasoningChanged;
@@ -279,7 +304,10 @@ export class ReasoningHandler {
* @returns {Promise}
*/
async process(messageId, mesChanged) {
- if (!this.reasoning && !this.#isHiddenReasoningModel) return;
+ mesChanged = this.#autoParseReasoningFromMessage(messageId, mesChanged);
+
+ if (!this.reasoning && !this.#isHiddenReasoningModel)
+ return;
// Ensure reasoning string is updated and regexes are applied correctly
const reasoningChanged = this.updateReasoning(messageId, null, { persist: true });
@@ -294,6 +322,53 @@ export class ReasoningHandler {
}
}
+ #autoParseReasoningFromMessage(messageId, mesChanged) {
+ if (!power_user.reasoning.auto_parse)
+ return;
+ if (!power_user.reasoning.prefix || !power_user.reasoning.suffix)
+ return mesChanged;
+
+ /** @type {{ mes: string, [key: string]: any}} */
+ const message = chat[messageId];
+ if (!message) return mesChanged;
+
+ // If we are done with reasoning parse, we just split the message correctly so the reasoning doesn't show up inside of it.
+ if (this.#parsingReasoningMesStartIndex) {
+ message.mes = trimSpaces(message.mes.slice(this.#parsingReasoningMesStartIndex));
+ return mesChanged;
+ }
+
+ if (this.state === ReasoningState.None) {
+ // If streamed message starts with the opening, cut it out and put all inside reasoning
+ if (message.mes.startsWith(power_user.reasoning.prefix) && message.mes.length > power_user.reasoning.prefix.length) {
+ this.#isParsingReasoning = true;
+
+ // Manually set starting state here, as we might already have received the ending suffix
+ this.state = ReasoningState.Thinking;
+ this.startTime = this.initialTime;
+ }
+ }
+
+ if (!this.#isParsingReasoning)
+ return mesChanged;
+
+ // If we are in manual parsing mode, all currently streaming mes tokens will go the the reasoning block
+ const originalMes = message.mes;
+ this.reasoning = originalMes.slice(power_user.reasoning.prefix.length);
+ message.mes = '';
+
+ // If the reasoning contains the ending suffix, we cut that off and continue as message streaming
+ if (this.reasoning.includes(power_user.reasoning.suffix)) {
+ this.reasoning = this.reasoning.slice(0, this.reasoning.indexOf(power_user.reasoning.suffix));
+ this.#parsingReasoningMesStartIndex = originalMes.indexOf(power_user.reasoning.suffix) + power_user.reasoning.suffix.length;
+ message.mes = trimSpaces(originalMes.slice(this.#parsingReasoningMesStartIndex));
+ this.#isParsingReasoning = false;
+ }
+
+ // Only return the original mesChanged value if we haven't cut off the complete message
+ return message.mes.length ? mesChanged : false;
+ }
+
/**
* Completes the reasoning process for a message.
*
@@ -336,9 +411,10 @@ export class ReasoningHandler {
// Update states to the relevant DOM elements
setDatasetProperty(this.messageDom, 'reasoningState', this.state !== ReasoningState.None ? this.state : null);
setDatasetProperty(this.messageReasoningDetailsDom, 'state', this.state);
+ setDatasetProperty(this.messageReasoningDetailsDom, 'type', this.type);
// Update the reasoning message
- const reasoning = power_user.trim_spaces ? this.reasoning.trim() : this.reasoning;
+ const reasoning = trimSpaces(this.reasoning);
const displayReasoning = messageFormatting(reasoning, '', false, false, messageId, {}, true);
this.messageReasoningContentDom.innerHTML = displayReasoning;
@@ -393,17 +469,14 @@ export class ReasoningHandler {
const element = this.messageReasoningHeaderDom;
const duration = this.getDuration();
let data = null;
+ let title = '';
if (duration) {
+ const seconds = moment.duration(duration).asSeconds();
+
const durationStr = moment.duration(duration).locale(getCurrentLocale()).humanize({ s: 50, ss: 3 });
- const secondsStr = moment.duration(duration).asSeconds();
-
- const span = document.createElement('span');
- span.title = t`${secondsStr} seconds`;
- span.textContent = durationStr;
-
- element.textContent = t`Thought for `;
- element.appendChild(span);
- data = String(secondsStr);
+ element.textContent = t`Thought for ${durationStr}`;
+ data = String(seconds);
+ title = `${seconds} seconds`;
} else if ([ReasoningState.Done, ReasoningState.Hidden].includes(this.state)) {
element.textContent = t`Thought for some time`;
data = 'unknown';
@@ -412,6 +485,12 @@ export class ReasoningHandler {
data = null;
}
+ if (this.type !== ReasoningType.Model) {
+ title += ` [${translate(this.type)}]`;
+ title = title.trim();
+ }
+ element.title = title;
+
setDatasetProperty(this.messageReasoningDetailsDom, 'duration', data);
setDatasetProperty(element, 'duration', data);
}
@@ -573,11 +652,16 @@ function registerReasoningSlashCommands() {
callback: async (args, value) => {
const messageId = !isNaN(Number(args.at)) ? Number(args.at) : chat.length - 1;
const message = chat[messageId];
- if (!message?.extra) {
+ if (!message) {
return '';
}
+ // Make sure the message has an extra object
+ if (!message.extra || typeof message.extra !== 'object') {
+ message.extra = {};
+ }
message.extra.reasoning = String(value ?? '');
+ message.extra.reasoning_type = ReasoningType.Manual;
await saveChatConditional();
closeMessageEditor('reasoning');
@@ -598,7 +682,26 @@ function registerReasoningSlashCommands() {
typeList: [ARGUMENT_TYPE.BOOLEAN],
defaultValue: 'true',
isRequired: false,
- enumProvider: commonEnumProviders.boolean('trueFalse'),
+ enumList: commonEnumProviders.boolean('trueFalse')(),
+ }),
+ SlashCommandNamedArgument.fromProps({
+ name: 'return',
+ description: 'Whether to return the parsed reasoning or the content without reasoning',
+ typeList: [ARGUMENT_TYPE.STRING],
+ defaultValue: 'reasoning',
+ isRequired: false,
+ enumList: [
+ new SlashCommandEnumValue('reasoning', null, enumTypes.enum, enumIcons.reasoning),
+ new SlashCommandEnumValue('content', null, enumTypes.enum, enumIcons.message),
+ ],
+ }),
+ SlashCommandNamedArgument.fromProps({
+ name: 'strict',
+ description: 'Whether to require the reasoning block to be at the beginning of the string (excluding whitespaces).',
+ typeList: [ARGUMENT_TYPE.BOOLEAN],
+ defaultValue: 'true',
+ isRequired: false,
+ enumList: commonEnumProviders.boolean('trueFalse')(),
}),
],
unnamedArgumentList: [
@@ -608,19 +711,27 @@ function registerReasoningSlashCommands() {
}),
],
callback: (args, value) => {
- if (!value) {
+ if (!value || typeof value !== 'string') {
return '';
}
if (!power_user.reasoning.prefix || !power_user.reasoning.suffix) {
- toastr.warning(t`Both prefix and suffix must be set in the Reasoning Formatting settings.`);
- return String(value);
+ toastr.warning(t`Both prefix and suffix must be set in the Reasoning Formatting settings.`, t`Reasoning Parse`);
+ return value;
+ }
+ if (typeof args.return !== 'string' || !['reasoning', 'content'].includes(args.return)) {
+ toastr.warning(t`Invalid return type '${args.return}', defaulting to 'reasoning'.`, t`Reasoning Parse`);
}
- const parsedReasoning = parseReasoningFromString(String(value));
+ const returnMessage = args.return === 'content';
+ const parsedReasoning = parseReasoningFromString(value, { strict: !isFalseBoolean(String(args.strict ?? '')) });
if (!parsedReasoning) {
- return '';
+ return returnMessage ? value : '';
+ }
+
+ if (returnMessage) {
+ return parsedReasoning.content;
}
const applyRegex = !isFalseBoolean(String(args.regex ?? ''));
@@ -720,6 +831,7 @@ function setReasoningEventHandlers() {
const textarea = messageBlock.find('.reasoning_edit_textarea');
const reasoning = getRegexedString(String(textarea.val()), regex_placement.REASONING, { isEdit: true });
message.extra.reasoning = reasoning;
+ message.extra.reasoning_type = message.extra.reasoning_type ? ReasoningType.Edited : ReasoningType.Manual;
await saveChatConditional();
updateMessageBlock(messageId, message);
textarea.remove();
@@ -780,6 +892,8 @@ function setReasoningEventHandlers() {
return;
}
message.extra.reasoning = '';
+ delete message.extra.reasoning_type;
+ delete message.extra.reasoning_duration;
await saveChatConditional();
updateMessageBlock(messageId, message);
const textarea = messageBlock.find('.reasoning_edit_textarea');
@@ -819,16 +933,18 @@ export function removeReasoningFromString(str) {
* @property {string} reasoning Reasoning block
* @property {string} content Message content
* @param {string} str Content of the message
+ * @param {Object} options Optional arguments
+ * @param {boolean} [options.strict=true] Whether the reasoning block **has** to be at the beginning of the provided string (excluding whitespaces), or can be anywhere in it
* @returns {ParsedReasoning|null} Parsed reasoning block and message content
*/
-function parseReasoningFromString(str) {
+function parseReasoningFromString(str, { strict = true } = {}) {
// Both prefix and suffix must be defined
if (!power_user.reasoning.prefix || !power_user.reasoning.suffix) {
return null;
}
try {
- const regex = new RegExp(`${escapeRegex(power_user.reasoning.prefix)}(.*?)${escapeRegex(power_user.reasoning.suffix)}`, 's');
+ const regex = new RegExp(`${(strict ? '^\\s*?' : '')}${escapeRegex(power_user.reasoning.prefix)}(.*?)${escapeRegex(power_user.reasoning.suffix)}`, 's');
let didReplace = false;
let reasoning = '';
@@ -838,9 +954,9 @@ function parseReasoningFromString(str) {
return '';
});
- if (didReplace && power_user.trim_spaces) {
- reasoning = reasoning.trim();
- content = content.trim();
+ if (didReplace) {
+ reasoning = trimSpaces(reasoning);
+ content = trimSpaces(content);
}
return { reasoning, content };
@@ -869,6 +985,11 @@ function registerReasoningAppEvents() {
return null;
}
+ if (message.extra?.reasoning) {
+ console.debug('[Reasoning] Message already has reasoning', idx);
+ return null;
+ }
+
const parsedReasoning = parseReasoningFromString(message.mes);
// No reasoning block found
@@ -886,6 +1007,7 @@ function registerReasoningAppEvents() {
// If reasoning was found, add it to the message
if (parsedReasoning.reasoning) {
message.extra.reasoning = getRegexedString(parsedReasoning.reasoning, regex_placement.REASONING);
+ message.extra.reasoning_type = ReasoningType.Parsed;
}
// Update the message text if it was changed
diff --git a/public/scripts/slash-commands/SlashCommandCommonEnumsProvider.js b/public/scripts/slash-commands/SlashCommandCommonEnumsProvider.js
index 9003cb754..88fe8ffd6 100644
--- a/public/scripts/slash-commands/SlashCommandCommonEnumsProvider.js
+++ b/public/scripts/slash-commands/SlashCommandCommonEnumsProvider.js
@@ -34,6 +34,7 @@ export const enumIcons = {
preset: '⚙️',
file: '📄',
message: '💬',
+ reasoning: '💡',
voice: '🎤',
server: '🖥️',
popup: '🗔',
diff --git a/public/scripts/textgen-settings.js b/public/scripts/textgen-settings.js
index 7fefed1d9..aab6213f4 100644
--- a/public/scripts/textgen-settings.js
+++ b/public/scripts/textgen-settings.js
@@ -311,7 +311,7 @@ export function validateTextGenUrl() {
const formattedUrl = formatTextGenURL(url);
if (!formattedUrl) {
- toastr.error('Enter a valid API URL', 'Text Completion API');
+ toastr.error(t`Enter a valid API URL`, 'Text Completion API');
return;
}
@@ -1187,7 +1187,7 @@ export function getTextGenModel() {
return settings.aphrodite_model;
case OLLAMA:
if (!settings.ollama_model) {
- toastr.error('No Ollama model selected.', 'Text Completion API');
+ toastr.error(t`No Ollama model selected.`, 'Text Completion API');
throw new Error('No Ollama model selected');
}
return settings.ollama_model;
diff --git a/public/scripts/utils.js b/public/scripts/utils.js
index 00304daa6..2006eee23 100644
--- a/public/scripts/utils.js
+++ b/public/scripts/utils.js
@@ -8,7 +8,7 @@ import {
import { getContext } from './extensions.js';
import { characters, getRequestHeaders, this_chid } from '../script.js';
import { isMobile } from './RossAscends-mods.js';
-import { collapseNewlines } from './power-user.js';
+import { collapseNewlines, power_user } from './power-user.js';
import { debounce_timeout } from './constants.js';
import { Popup, POPUP_RESULT, POPUP_TYPE } from './popup.js';
import { SlashCommandClosure } from './slash-commands/SlashCommandClosure.js';
@@ -676,6 +676,19 @@ export function sortByCssOrder(a, b) {
return _a - _b;
}
+/**
+ * Trims leading and trailing whitespace from the input string based on a configuration setting.
+ * @param {string} input - The string to be trimmed
+ * @returns {string} The trimmed string if trimming is enabled; otherwise, returns the original string
+ */
+
+export function trimSpaces(input) {
+ if (!input || typeof input !== 'string') {
+ return input;
+ }
+ return power_user.trim_spaces ? input.trim() : input;
+}
+
/**
* Trims a string to the end of a nearest sentence.
* @param {string} input The string to trim.
diff --git a/src/endpoints/vectors.js b/src/endpoints/vectors.js
index 3cab324d7..abab3b7d1 100644
--- a/src/endpoints/vectors.js
+++ b/src/endpoints/vectors.js
@@ -132,35 +132,35 @@ function getSourceSettings(source, request) {
switch (source) {
case 'togetherai':
return {
- model: String(request.headers['x-togetherai-model']),
+ model: String(request.body.model),
};
case 'openai':
return {
- model: String(request.headers['x-openai-model']),
+ model: String(request.body.model),
};
case 'cohere':
return {
- model: String(request.headers['x-cohere-model']),
+ model: String(request.body.model),
};
case 'llamacpp':
return {
- apiUrl: String(request.headers['x-llamacpp-url']),
+ apiUrl: String(request.body.apiUrl),
};
case 'vllm':
return {
- apiUrl: String(request.headers['x-vllm-url']),
- model: String(request.headers['x-vllm-model']),
+ apiUrl: String(request.body.apiUrl),
+ model: String(request.body.model),
};
case 'ollama':
return {
- apiUrl: String(request.headers['x-ollama-url']),
- model: String(request.headers['x-ollama-model']),
- keep: Boolean(request.headers['x-ollama-keep']),
+ apiUrl: String(request.body.apiUrl),
+ model: String(request.body.model),
+ keep: Boolean(request.body.keep),
};
case 'extras':
return {
- extrasUrl: String(request.headers['x-extras-url']),
- extrasKey: String(request.headers['x-extras-key']),
+ extrasUrl: String(request.body.extrasUrl),
+ extrasKey: String(request.body.extrasKey),
};
case 'transformers':
return {