Merge pull request #2683 from eugencowie/tokenizer-slash-command Add slash command to set tokenizer

02d6df3acec2963d3c321a7f6efa335430f1769e

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

Signed
2 files changed, +84 -3Showing whitespace changes
public/scripts/slash-commands.js+44 -1
@@ -52,7 +52,7 @@ import { chat_completion_sources, oai_settings, setupChatCompletionPromptManager
5252import { autoSelectPersona, retriggerFirstMessageOnEmptyChat, setPersonaLockState, togglePersonaLock, user_avatar } from './personas.js';
5353import { addEphemeralStoppingString, chat_styles, flushEphemeralStoppingStrings, power_user } from './power-user.js';
5454import { SERVER_INPUTS, textgen_types, textgenerationwebui_settings } from './textgen-settings.js';
5555import { decodeTextTokens, getAvailableTokenizers, getFriendlyTokenizerName, getTextTokens, getTokenCountAsync, selectTokenizer } from './tokenizers.js';
5656import { debounce, delay, isFalseBoolean, isTrueBoolean, showFontAwesomePicker, stringToRange, trimToEndSentence, trimToStartSentence, waitUntilCondition } from './utils.js';
5757import { registerVariableCommands, resolveVariable } from './variables.js';
5858import { background_settings } from './backgrounds.js';
@@ -1558,6 +1558,28 @@ export function initDefaultSlashCommands() {
15581558 </div>
15591559 `,
15601560 }));
1561+ SlashCommandParser.addCommandObject(SlashCommand.fromProps({
1562+ name: 'tokenizer',
1563+ callback: selectTokenizerCallback,
1564+ returns: 'current tokenizer',
1565+ unnamedArgumentList: [
1566+ SlashCommandArgument.fromProps({
1567+ description: 'tokenizer name',
1568+ typeList: [ARGUMENT_TYPE.STRING],
1569+ enumList: getAvailableTokenizers().map(tokenizer =>
1570+ new SlashCommandEnumValue(tokenizer.tokenizerKey, tokenizer.tokenizerName, enumTypes.enum, enumIcons.default)),
1571+ }),
1572+ ],
1573+ helpString: `
1574+ <div>
1575+ Selects tokenizer by name. Gets the current tokenizer if no name is provided.
1576+ </div>
1577+ <div>
1578+ <strong>Available tokenizers:</strong>
1579+ <pre><code>${getAvailableTokenizers().map(t => t.tokenizerKey).join(', ')}</code></pre>
1580+ </div>
1581+ `,
1582+ }));
15611583
15621584 registerVariableCommands();
15631585}
@@ -3562,6 +3584,27 @@ async function setApiUrlCallback({ api = null, connect = 'true' }, url) {
35623584 return textgenerationwebui_settings.server_urls[type] ?? '';
35633585}
35643586
3587+async function selectTokenizerCallback(_, name) {
3588+ if (!name) {
3589+ return getAvailableTokenizers().find(tokenizer => tokenizer.tokenizerId === power_user.tokenizer)?.tokenizerKey ?? '';
3590+ }
3591+
3592+ const tokenizers = getAvailableTokenizers();
3593+ const fuse = new Fuse(tokenizers, { keys: ['tokenizerKey', 'tokenizerName'] });
3594+ const result = fuse.search(name);
3595+
3596+ if (result.length === 0) {
3597+ toastr.warning(`Tokenizer "${name}" not found`);
3598+ return '';
3599+ }
3600+
3601+ /** @type {import('./tokenizers.js').Tokenizer} */
3602+ const foundTokenizer = result[0].item;
3603+ selectTokenizer(foundTokenizer.tokenizerId);
3604+
3605+ return foundTokenizer.tokenizerKey;
3606+}
3607+
35653608export let isExecutingCommandsFromChatInput = false;
35663609export let commandsFromChatInputAbortController;
35673610
public/scripts/tokenizers.js+40 -2
@@ -148,9 +148,45 @@ async function resetTokenCache() {
148148}
149149
150150/**
151+ * @typedef {object} Tokenizer
152+ * @property {number} tokenizerId - The id of the tokenizer option
153+ * @property {string} tokenizerKey - Internal name/key of the tokenizer
154+ * @property {string} tokenizerName - Human-readable detailed name of the tokenizer (as displayed in the UI)
155+ */
156+
157+/**
158+ * Gets all tokenizers available to the user.
159+ * @returns {Tokenizer[]} Tokenizer info.
160+ */
161+export function getAvailableTokenizers() {
162+ const tokenizerOptions = $('#tokenizer').find('option').toArray();
163+ return tokenizerOptions.map(tokenizerOption => ({
164+ tokenizerId: Number(tokenizerOption.value),
165+ tokenizerKey: Object.entries(tokenizers).find(([_, value]) => value === Number(tokenizerOption.value))[0].toLocaleLowerCase(),
166+ tokenizerName: tokenizerOption.text,
167+ }))
168+}
169+
170+/**
171+ * Selects tokenizer if not already selected.
172+ * @param {number} tokenizerId Tokenizer ID.
173+ */
174+export function selectTokenizer(tokenizerId) {
175+ if (tokenizerId !== power_user.tokenizer) {
176+ const tokenizer = getAvailableTokenizers().find(tokenizer => tokenizer.tokenizerId === tokenizerId);
177+ if (!tokenizer) {
178+ console.warn('Failed to find tokenizer with id', tokenizerId);
179+ return;
180+ }
181+ $('#tokenizer').val(tokenizer.tokenizerId).trigger('change');
182+ toastr.info(`Tokenizer: "${tokenizer.tokenizerName}" selected`);
183+ }
184+}
185+
186+/**
151187 * Gets the friendly name of the current tokenizer.
152188 * @param {string} forApi API to get the tokenizer for. Defaults to the main API.
153189 * @returns { { tokenizerName: string, tokenizerId: number } Tokenizer} Tokenizer info
154190 */
155191export function getFriendlyTokenizerName(forApi) {
156192 if (!forApi) {
@@ -185,7 +221,9 @@ export function getFriendlyTokenizerName(forApi) {
185221 ? tokenizers.OPENAI
186222 : tokenizerId;
187223
188- return { tokenizerName, tokenizerId };
224+ const tokenizerKey = Object.entries(tokenizers).find(([_, value]) => value === tokenizerId)[0].toLocaleLowerCase();
225+
226+ return { tokenizerName, tokenizerKey, tokenizerId };
189227}
190228
191229/**