diff --git a/package-lock.json b/package-lock.json
index 93c8ed8ee..4cef87f54 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -12,7 +12,6 @@
"dependencies": {
"@agnai/sentencepiece-js": "^1.1.1",
"@agnai/web-tokenizers": "^0.1.3",
- "@dqbd/tiktoken": "^1.0.13",
"@zeldafan0225/ai_horde": "^4.0.1",
"archiver": "^7.0.1",
"bing-translate-api": "^2.9.1",
@@ -46,6 +45,7 @@
"sanitize-filename": "^1.6.3",
"sillytavern-transformers": "^2.14.6",
"simple-git": "^3.19.1",
+ "tiktoken": "^1.0.15",
"vectra": "^0.2.2",
"wavefile": "^11.0.0",
"write-file-atomic": "^5.0.1",
@@ -82,10 +82,6 @@
"version": "0.1.3",
"license": "Apache-2.0"
},
- "node_modules/@dqbd/tiktoken": {
- "version": "1.0.13",
- "license": "MIT"
- },
"node_modules/@eslint-community/eslint-utils": {
"version": "4.4.0",
"dev": true,
@@ -4403,6 +4399,11 @@
"dev": true,
"license": "MIT"
},
+ "node_modules/tiktoken": {
+ "version": "1.0.15",
+ "resolved": "https://registry.npmjs.org/tiktoken/-/tiktoken-1.0.15.tgz",
+ "integrity": "sha512-sCsrq/vMWUSEW29CJLNmPvWxlVp7yh2tlkAjpJltIKqp5CKf98ZNpdeHRmAlPVFlGEbswDc6SmI8vz64W/qErw=="
+ },
"node_modules/timm": {
"version": "1.7.1",
"license": "MIT"
diff --git a/package.json b/package.json
index ba6fb92af..2ab67ede2 100644
--- a/package.json
+++ b/package.json
@@ -2,7 +2,6 @@
"dependencies": {
"@agnai/sentencepiece-js": "^1.1.1",
"@agnai/web-tokenizers": "^0.1.3",
- "@dqbd/tiktoken": "^1.0.13",
"@zeldafan0225/ai_horde": "^4.0.1",
"archiver": "^7.0.1",
"bing-translate-api": "^2.9.1",
@@ -36,6 +35,7 @@
"sanitize-filename": "^1.6.3",
"sillytavern-transformers": "^2.14.6",
"simple-git": "^3.19.1",
+ "tiktoken": "^1.0.15",
"vectra": "^0.2.2",
"wavefile": "^11.0.0",
"write-file-atomic": "^5.0.1",
diff --git a/public/css/world-info.css b/public/css/world-info.css
index 6244d9c2e..0f0a304c8 100644
--- a/public/css/world-info.css
+++ b/public/css/world-info.css
@@ -102,7 +102,7 @@
height: auto;
margin-top: 0;
margin-bottom: 0;
- min-height: calc(var(--mainFontSize) + 13px);
+ min-height: calc(var(--mainFontSize) + 14px);
}
.delete_entry_button {
diff --git a/public/index.html b/public/index.html
index bbf0f8515..da5d8ab11 100644
--- a/public/index.html
+++ b/public/index.html
@@ -5292,6 +5292,12 @@
Prevent further recursion (this entry will not activate others)
+
@@ -5332,7 +5338,7 @@
Inclusion Group
-
+
diff --git a/public/script.js b/public/script.js
index f9e781b85..8dd9b427e 100644
--- a/public/script.js
+++ b/public/script.js
@@ -415,6 +415,7 @@ export const event_types = {
GROUP_MEMBER_DRAFTED: 'group_member_drafted',
WORLD_INFO_ACTIVATED: 'world_info_activated',
TEXT_COMPLETION_SETTINGS_READY: 'text_completion_settings_ready',
+ CHAT_COMPLETION_SETTINGS_READY: 'chat_completion_settings_ready',
CHARACTER_FIRST_MESSAGE_SELECTED: 'character_first_message_selected',
// TODO: Naming convention is inconsistent with other events
CHARACTER_DELETED: 'characterDeleted',
@@ -7566,6 +7567,7 @@ window['SillyTavern'].getContext = function () {
getCurrentChatId: getCurrentChatId,
getRequestHeaders: getRequestHeaders,
reloadCurrentChat: reloadCurrentChat,
+ renameChat: renameChat,
saveSettingsDebounced: saveSettingsDebounced,
onlineStatus: online_status,
maxContext: Number(max_context),
@@ -8288,6 +8290,58 @@ async function doDeleteChat() {
$('#dialogue_popup_ok').trigger('click', { fromSlashCommand: true });
}
+/**
+ * Renames the currently selected chat.
+ * @param {string} oldFileName Old name of the chat (no JSONL extension)
+ * @param {string} newName New name for the chat (no JSONL extension)
+ */
+export async function renameChat(oldFileName, newName) {
+ const body = {
+ is_group: !!selected_group,
+ avatar_url: characters[this_chid]?.avatar,
+ original_file: `${oldFileName}.jsonl`,
+ renamed_file: `${newName}.jsonl`,
+ };
+
+ try {
+ showLoader();
+ const response = await fetch('/api/chats/rename', {
+ method: 'POST',
+ body: JSON.stringify(body),
+ headers: getRequestHeaders(),
+ });
+
+ if (!response.ok) {
+ throw new Error('Unsuccessful request.');
+ }
+
+ const data = await response.json();
+
+ if (data.error) {
+ throw new Error('Server returned an error.');
+ }
+
+ if (selected_group) {
+ await renameGroupChat(selected_group, oldFileName, newName);
+ }
+ else {
+ if (characters[this_chid].chat == oldFileName) {
+ characters[this_chid].chat = newName;
+ $('#selected_chat_pole').val(characters[this_chid].chat);
+ await createOrEditCharacter();
+ }
+ }
+
+ await reloadCurrentChat();
+ } catch {
+ hideLoader();
+ await delay(500);
+ await callPopup('An error has occurred. Chat was not renamed.', 'text');
+ } finally {
+ hideLoader();
+ }
+}
+
/**
* /getchatname` slash command
*/
@@ -8966,69 +9020,26 @@ jQuery(async function () {
$(document).on('click', '.renameChatButton', async function (e) {
e.stopPropagation();
- const old_filenamefull = $(this).closest('.select_chat_block_wrapper').find('.select_chat_block_filename').text();
- const old_filename = old_filenamefull.replace('.jsonl', '');
+ const oldFileNameFull = $(this).closest('.select_chat_block_wrapper').find('.select_chat_block_filename').text();
+ const oldFileName = oldFileNameFull.replace('.jsonl', '');
const popupText = `
Enter the new name for the chat:
!!Using an existing filename will produce an error!!
This will break the link between checkpoint chats.
No need to add '.jsonl' at the end.
`;
- const newName = await callPopup(popupText, 'input', old_filename);
+ const newName = await callPopup(popupText, 'input', oldFileName);
- if (!newName || newName == old_filename) {
+ if (!newName || newName == oldFileName) {
console.log('no new name found, aborting');
return;
}
- const body = {
- is_group: !!selected_group,
- avatar_url: characters[this_chid]?.avatar,
- original_file: `${old_filename}.jsonl`,
- renamed_file: `${newName}.jsonl`,
- };
+ await renameChat(oldFileName, newName);
- try {
- showLoader();
- const response = await fetch('/api/chats/rename', {
- method: 'POST',
- body: JSON.stringify(body),
- headers: getRequestHeaders(),
- });
-
- if (!response.ok) {
- throw new Error('Unsuccessful request.');
- }
-
- const data = await response.json();
-
- if (data.error) {
- throw new Error('Server returned an error.');
- }
-
- if (selected_group) {
- await renameGroupChat(selected_group, old_filename, newName);
- }
- else {
- if (characters[this_chid].chat == old_filename) {
- characters[this_chid].chat = newName;
- $('#selected_chat_pole').val(characters[this_chid].chat);
- await createOrEditCharacter();
- }
- }
-
- await reloadCurrentChat();
-
- await delay(250);
- $('#option_select_chat').trigger('click');
- $('#options').hide();
- } catch {
- hideLoader();
- await delay(500);
- await callPopup('An error has occurred. Chat was not renamed.', 'text');
- } finally {
- hideLoader();
- }
+ await delay(250);
+ $('#option_select_chat').trigger('click');
+ $('#options').hide();
});
$(document).on('click', '.exportChatButton, .exportRawChatButton', async function (e) {
diff --git a/public/scripts/RossAscends-mods.js b/public/scripts/RossAscends-mods.js
index 826f968ea..c08378ee9 100644
--- a/public/scripts/RossAscends-mods.js
+++ b/public/scripts/RossAscends-mods.js
@@ -1133,6 +1133,11 @@ export function initRossMods() {
return;
}
+ if ($('#dialogue_del_mes_cancel').is(':visible')) {
+ $('#dialogue_del_mes_cancel').trigger('click');
+ return;
+ }
+
if ($('.drawer-content')
.not('#WorldInfo')
.not('#left-nav-panel')
diff --git a/public/scripts/char-data.js b/public/scripts/char-data.js
index fa2f03382..20e484f31 100644
--- a/public/scripts/char-data.js
+++ b/public/scripts/char-data.js
@@ -24,6 +24,7 @@
* @property {boolean} group_override - Overrides any existing group assignment for the extension.
* @property {number} group_weight - A value used for prioritizing extensions within the same group.
* @property {boolean} prevent_recursion - Completely disallows recursive application of the extension.
+ * @property {boolean} delay_until_recursion - Will only be checked during recursion.
* @property {number} scan_depth - The maximum depth to search for matches when applying the extension.
* @property {boolean} match_whole_words - Specifies if only entire words should be matched during extension application.
* @property {boolean} use_group_scoring - Indicates if group weight is considered when selecting extensions.
diff --git a/public/scripts/openai.js b/public/scripts/openai.js
index 479df8cb0..2e35ecae7 100644
--- a/public/scripts/openai.js
+++ b/public/scripts/openai.js
@@ -1847,6 +1847,8 @@ async function sendOpenAIRequest(type, messages, signal) {
generate_data['seed'] = oai_settings.seed;
}
+ await eventSource.emit(event_types.CHAT_COMPLETION_SETTINGS_READY, generate_data);
+
const generate_url = '/api/backends/chat-completions/generate';
const response = await fetch(generate_url, {
method: 'POST',
diff --git a/public/scripts/slash-commands/SlashCommandAutoCompleteNameResult.js b/public/scripts/slash-commands/SlashCommandAutoCompleteNameResult.js
index 80ed27a37..ba163fcd5 100644
--- a/public/scripts/slash-commands/SlashCommandAutoCompleteNameResult.js
+++ b/public/scripts/slash-commands/SlashCommandAutoCompleteNameResult.js
@@ -50,6 +50,14 @@ export class SlashCommandAutoCompleteNameResult extends AutoCompleteNameResult {
}
getNamedArgumentAt(text, index, isSelect) {
+ function getSplitRegex() {
+ try {
+ return new RegExp('(?<==)');
+ } catch {
+ // For browsers that don't support lookbehind
+ return new RegExp('=(.*)');
+ }
+ }
const notProvidedNamedArguments = this.executor.command.namedArgumentList.filter(arg=>!this.executor.namedArgumentList.find(it=>it.name == arg.name));
let name;
let value;
@@ -62,7 +70,7 @@ export class SlashCommandAutoCompleteNameResult extends AutoCompleteNameResult {
// cursor is somewhere within the named arguments (including final space)
argAssign = this.executor.namedArgumentList.find(it=>it.start <= index && it.end >= index);
if (argAssign) {
- const [argName, ...v] = text.slice(argAssign.start, index).split(/(?<==)/);
+ const [argName, ...v] = text.slice(argAssign.start, index).split(getSplitRegex());
name = argName;
value = v.join('');
start = argAssign.start;
diff --git a/public/scripts/slash-commands/SlashCommandParser.js b/public/scripts/slash-commands/SlashCommandParser.js
index f1b88b73d..75bb0338d 100644
--- a/public/scripts/slash-commands/SlashCommandParser.js
+++ b/public/scripts/slash-commands/SlashCommandParser.js
@@ -186,6 +186,15 @@ export class SlashCommandParser {
relevance: 0,
};
+ function getQuotedRunRegex() {
+ try {
+ return new RegExp('(".+?(? {
keywordsAndRegexes.push(item.text);
- }
+ };
const { term } = customTokenizer({ _type: 'custom_call', term: input }, undefined, addFindCallback);
const finalTerm = term.trim();
@@ -1501,7 +1502,7 @@ function getWorldEntry(name, data, entry) {
const isRegex = isValidRegex(item.text);
if (isRegex) {
content.html(highlightRegex(item.text));
- content.addClass('regex_item').prepend($('').addClass('regex_icon').text("•*").attr('title', 'Regex'));
+ content.addClass('regex_item').prepend($('').addClass('regex_icon').text('•*').attr('title', 'Regex'));
}
if (searchStyle && item.count) {
@@ -1551,7 +1552,7 @@ function getWorldEntry(name, data, entry) {
if (index > -1) selected.splice(index, 1);
input.val(selected).trigger('change');
// Manually update the cache, that change event is not gonna trigger it
- updateWorldEntryKeyOptionsCache([key], { remove: true })
+ updateWorldEntryKeyOptionsCache([key], { remove: true });
// We need to "hack" the actual text input into the currently open textarea
input.next('span.select2-container').find('textarea')
@@ -1580,10 +1581,10 @@ function getWorldEntry(name, data, entry) {
}
// key
- enableKeysInput("key", "keys");
+ enableKeysInput('key', 'keys');
// keysecondary
- enableKeysInput("keysecondary", "secondary_keys");
+ enableKeysInput('keysecondary', 'secondary_keys');
// draw key input switch button
template.find('.switch_input_type_icon').on('click', function () {
@@ -1871,7 +1872,7 @@ function getWorldEntry(name, data, entry) {
saveWorldInfo(name, data);
});
groupInput.val(entry.group ?? '').trigger('input');
- setTimeout(() => createEntryInputAutocomplete(groupInput, getInclusionGroupCallback(data)), 1);
+ setTimeout(() => createEntryInputAutocomplete(groupInput, getInclusionGroupCallback(data), { allowMultiple: true }), 1);
// inclusion priority
const groupOverrideInput = template.find('input[name="groupOverride"]');
@@ -2143,6 +2144,18 @@ function getWorldEntry(name, data, entry) {
});
preventRecursionInput.prop('checked', entry.preventRecursion).trigger('input');
+ // delay until recursion
+ const delayUntilRecursionInput = template.find('input[name="delay_until_recursion"]');
+ delayUntilRecursionInput.data('uid', entry.uid);
+ delayUntilRecursionInput.on('input', function () {
+ const uid = $(this).data('uid');
+ const value = $(this).prop('checked');
+ data.entries[uid].delayUntilRecursion = value;
+ setOriginalDataValue(data, uid, 'extensions.delay_until_recursion', data.entries[uid].delayUntilRecursion);
+ saveWorldInfo(name, data);
+ });
+ delayUntilRecursionInput.prop('checked', entry.delayUntilRecursion).trigger('input');
+
// duplicate button
const duplicateButton = template.find('.duplicate_entry_button');
duplicateButton.data('uid', entry.uid);
@@ -2281,11 +2294,15 @@ function getWorldEntry(name, data, entry) {
* @returns {(input: any, output: any) => any} Callback function for the autocomplete
*/
function getInclusionGroupCallback(data) {
- return function (input, output) {
+ return function (control, input, output) {
+ const uid = $(control).data('uid');
+ const thisGroups = String($(control).val()).split(/,\s*/).filter(x => x).map(x => x.toLowerCase());
const groups = new Set();
for (const entry of Object.values(data.entries)) {
+ // Skip the groups of this entry, because auto-complete should only suggest the ones that are already available on other entries
+ if (entry.uid == uid) continue;
if (entry.group) {
- groups.add(String(entry.group));
+ entry.group.split(/,\s*/).filter(x => x).forEach(x => groups.add(x));
}
}
@@ -2293,20 +2310,19 @@ function getInclusionGroupCallback(data) {
haystack.sort((a, b) => a.localeCompare(b));
const needle = input.term.toLowerCase();
const hasExactMatch = haystack.findIndex(x => x.toLowerCase() == needle) !== -1;
- const result = haystack.filter(x => x.toLowerCase().includes(needle));
-
- if (input.term && !hasExactMatch) {
- result.unshift(input.term);
- }
+ const result = haystack.filter(x => x.toLowerCase().includes(needle) && (!thisGroups.includes(x) || hasExactMatch && thisGroups.filter(g => g == x).length == 1));
output(result);
};
}
function getAutomationIdCallback(data) {
- return function (input, output) {
+ return function (control, input, output) {
+ const uid = $(control).data('uid');
const ids = new Set();
for (const entry of Object.values(data.entries)) {
+ // Skip automation id of this entry, because auto-complete should only suggest the ones that are already available on other entries
+ if (entry.uid == uid) continue;
if (entry.automationId) {
ids.add(String(entry.automationId));
}
@@ -2322,36 +2338,53 @@ function getAutomationIdCallback(data) {
const haystack = Array.from(ids);
haystack.sort((a, b) => a.localeCompare(b));
const needle = input.term.toLowerCase();
- const hasExactMatch = haystack.findIndex(x => x.toLowerCase() == needle) !== -1;
const result = haystack.filter(x => x.toLowerCase().includes(needle));
- if (input.term && !hasExactMatch) {
- result.unshift(input.term);
- }
-
output(result);
};
}
/**
* Create an autocomplete for the inclusion group.
- * @param {JQuery} input Input element to attach the autocomplete to
- * @param {(input: any, output: any) => any} callback Source data callbacks
+ * @param {JQuery} input - Input element to attach the autocomplete to
+ * @param {(control: JQuery, input: any, output: any) => any} callback - Source data callbacks
+ * @param {object} [options={}] - Optional arguments
+ * @param {boolean} [options.allowMultiple=false] - Whether to allow multiple comma-separated values
*/
-function createEntryInputAutocomplete(input, callback) {
+function createEntryInputAutocomplete(input, callback, { allowMultiple = false } = {}) {
+ const handleSelect = (event, ui) => {
+ // Prevent default autocomplete select, so we can manually set the value
+ event.preventDefault();
+ if (!allowMultiple) {
+ $(input).val(ui.item.value).trigger('input').trigger('blur');
+ } else {
+ var terms = String($(input).val()).split(/,\s*/);
+ terms.pop(); // remove the current input
+ terms.push(ui.item.value); // add the selected item
+ $(input).val(terms.filter(x => x).join(', ')).trigger('input').trigger('blur');
+ }
+ };
+
$(input).autocomplete({
minLength: 0,
- source: callback,
- select: function (_event, ui) {
- $(input).val(ui.item.value).trigger('input').trigger('blur');
+ source: function (request, response) {
+ if (!allowMultiple) {
+ callback(input, request, response);
+ } else {
+ const term = request.term.split(/,\s*/).pop();
+ request.term = term;
+ callback(input, request, response);
+ }
},
+ select: handleSelect,
});
$(input).on('focus click', function () {
- $(input).autocomplete('search', String($(input).val()));
+ $(input).autocomplete('search', allowMultiple ? String($(input).val()).split(/,\s*/).pop() : $(input).val());
});
}
+
/**
* Duplicated a WI entry by copying all of its properties and assigning a new uid
* @param {*} data - The data of the book
@@ -2404,6 +2437,8 @@ const newEntryTemplate = {
position: 0,
disable: false,
excludeRecursion: false,
+ preventRecursion: false,
+ delayUntilRecursion: false,
probability: 100,
useProbability: true,
depth: DEFAULT_DEPTH,
@@ -2771,7 +2806,7 @@ async function checkWorldInfo(chat, maxContext) {
continue;
}
- if (allActivatedEntries.has(entry) || entry.disable == true || (count > 1 && world_info_recursive && entry.excludeRecursion)) {
+ if (allActivatedEntries.has(entry) || entry.disable == true || (count > 1 && world_info_recursive && entry.excludeRecursion) || (count == 1 && entry.delayUntilRecursion)) {
continue;
}
@@ -3044,10 +3079,12 @@ function filterGroupsByScoring(groups, buffer, removeEntry) {
function filterByInclusionGroups(newEntries, allActivatedEntries, buffer) {
console.debug('-- INCLUSION GROUP CHECKS BEGIN --');
const grouped = newEntries.filter(x => x.group).reduce((acc, item) => {
- if (!acc[item.group]) {
- acc[item.group] = [];
- }
- acc[item.group].push(item);
+ item.group.split(/,\s*/).filter(x => x).forEach(group => {
+ if (!acc[group]) {
+ acc[group] = [];
+ }
+ acc[group].push(item);
+ });
return acc;
}, {});
@@ -3139,6 +3176,7 @@ function convertAgnaiMemoryBook(inputObj) {
disable: !entry.enabled,
addMemo: !!entry.name,
excludeRecursion: false,
+ delayUntilRecursion: false,
displayIndex: index,
probability: 100,
useProbability: true,
@@ -3177,6 +3215,7 @@ function convertRisuLorebook(inputObj) {
disable: false,
addMemo: true,
excludeRecursion: false,
+ delayUntilRecursion: false,
displayIndex: index,
probability: entry.activationPercent ?? 100,
useProbability: entry.activationPercent ?? true,
@@ -3220,6 +3259,7 @@ function convertNovelLorebook(inputObj) {
disable: !entry.enabled,
addMemo: addMemo,
excludeRecursion: false,
+ delayUntilRecursion: false,
displayIndex: index,
probability: 100,
useProbability: true,
@@ -3260,6 +3300,7 @@ function convertCharacterBook(characterBook) {
position: entry.extensions?.position ?? (entry.position === 'before_char' ? world_info_position.before : world_info_position.after),
excludeRecursion: entry.extensions?.exclude_recursion ?? false,
preventRecursion: entry.extensions?.prevent_recursion ?? false,
+ delayUntilRecursion: entry.extensions?.delay_until_recursion ?? false,
disable: !entry.enabled,
addMemo: entry.comment ? true : false,
displayIndex: entry.extensions?.display_index ?? index,
diff --git a/public/style.css b/public/style.css
index 9e576a33e..3d44507c4 100644
--- a/public/style.css
+++ b/public/style.css
@@ -129,7 +129,7 @@ body {
height: 100vh;
height: 100svh;
/*defaults as 100%, then reassigned via JS as pixels, will work on PC and Android*/
- height: calc(var(--doc-height) - 1px);
+ /*height: calc(var(--doc-height) - 1px);*/
background-color: var(--greyCAIbg);
background-repeat: no-repeat;
background-attachment: fixed;
@@ -872,7 +872,8 @@ body .panelControlBar {
}
#chat .mes.selected{
- background-color: rgb(from var(--SmartThemeQuoteColor) r g b / .5);
+ /* background-color: rgb(from var(--SmartThemeQuoteColor) r g b / .5); */
+ background-color: rgb(102, 0, 0);
}
.mes q:before,
diff --git a/src/endpoints/characters.js b/src/endpoints/characters.js
index 2e3171880..fc7ae3ef8 100644
--- a/src/endpoints/characters.js
+++ b/src/endpoints/characters.js
@@ -436,6 +436,7 @@ function convertWorldInfoToCharacterBook(name, entries) {
group_override: entry.groupOverride ?? false,
group_weight: entry.groupWeight ?? null,
prevent_recursion: entry.preventRecursion ?? false,
+ delay_until_recursion: entry.delayUntilRecursion ?? false,
scan_depth: entry.scanDepth ?? null,
match_whole_words: entry.matchWholeWords ?? null,
use_group_scoring: entry.useGroupScoring ?? false,
diff --git a/src/endpoints/tokenizers.js b/src/endpoints/tokenizers.js
index 321267d05..82bab1439 100644
--- a/src/endpoints/tokenizers.js
+++ b/src/endpoints/tokenizers.js
@@ -2,7 +2,7 @@ const fs = require('fs');
const path = require('path');
const express = require('express');
const { SentencePieceProcessor } = require('@agnai/sentencepiece-js');
-const tiktoken = require('@dqbd/tiktoken');
+const tiktoken = require('tiktoken');
const { Tokenizer } = require('@agnai/web-tokenizers');
const { convertClaudePrompt, convertGooglePrompt } = require('../prompt-converters');
const { readSecret, SECRET_KEYS } = require('./secrets');
@@ -15,7 +15,7 @@ const { setAdditionalHeaders } = require('../additional-headers');
*/
/**
- * @type {{[key: string]: import("@dqbd/tiktoken").Tiktoken}} Tokenizers cache
+ * @type {{[key: string]: import('tiktoken').Tiktoken}} Tokenizers cache
*/
const tokenizersCache = {};
@@ -262,6 +262,10 @@ function getWebTokenizersChunks(tokenizer, ids) {
* @returns {string} Tokenizer model to use
*/
function getTokenizerModel(requestModel) {
+ if (requestModel.includes('gpt-4o')) {
+ return 'gpt-4o';
+ }
+
if (requestModel.includes('gpt-4-32k')) {
return 'gpt-4-32k';
}