Merge branch 'staging' into sysprompt-divorce

0d294c5371a28bcd79abdf7977ef2c4b5e2f9045

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

14 files changed, +164 -36Showing whitespace changes
Dockerfile+1 -1
@@ -19,7 +19,7 @@ ENV NODE_ENV=production
19COPY package*.json post-install.js ./19COPY package*.json post-install.js ./
20RUN \20RUN \
21 echo "*** Install npm packages ***" && \21 echo "*** Install npm packages ***" && \
22 npm i --no-audit --no-fund --quiet --omit=dev && npm cache clean --force22 npm i --no-audit --no-fund --loglevel=error --no-progress --omit=dev && npm cache clean --force
2323
24# Bundle app source24# Bundle app source
25COPY . ./25COPY . ./
Start.bat+1 -1
@@ -1,7 +1,7 @@
1@echo off1@echo off
2pushd %~dp02pushd %~dp0
3set NODE_ENV=production3set NODE_ENV=production
4call npm install --no-audit --no-fund --quiet --omit=dev4call npm install --no-audit --no-fund --loglevel=error --no-progress --omit=dev
5node server.js %*5node server.js %*
6pause6pause
7popd7popd
UpdateAndStart.bat+1 -1
@@ -12,7 +12,7 @@ if %errorlevel% neq 0 (
12 )12 )
13)13)
14set NODE_ENV=production14set NODE_ENV=production
15call npm install --no-audit --no-fund --quiet --omit=dev15call npm install --no-audit --no-fund --loglevel=error --no-progress --omit=dev
16node server.js %*16node server.js %*
17pause17pause
18popd18popd
UpdateForkAndStart.bat+1 -1
@@ -95,7 +95,7 @@ if %errorlevel% neq 0 (
9595
96echo Installing npm packages and starting server96echo Installing npm packages and starting server
97set NODE_ENV=production97set NODE_ENV=production
98call npm install --no-audit --no-fund --quiet --omit=dev98call npm install --no-audit --no-fund --loglevel=error --no-progress --omit=dev
99node server.js %*99node server.js %*
100100
101:end101:end
public/css/promptmanager.css+12 -0
@@ -316,3 +316,15 @@
316 margin-left: 0.5em;316 margin-left: 0.5em;
317 }317 }
318}318}
319
320.completion_prompt_manager_popup_entry_form_control:has(#completion_prompt_manager_popup_entry_form_prompt:disabled) > div:first-child::after {
321 content: 'The content of this prompt is pulled from elsewhere and cannot be edited here.';
322 display: block;
323 width: 100%;
324 font-weight: 600;
325 text-align: center;
326}
327
328.completion_prompt_manager_popup_entry_form_control #completion_prompt_manager_popup_entry_form_prompt:disabled {
329 visibility: hidden;
330}
public/index.html+2 -1
@@ -3297,7 +3297,8 @@
3297 <option value="16">Command-R</option>3297 <option value="16">Command-R</option>
3298 <option value="4">NerdStash (NovelAI Clio)</option>3298 <option value="4">NerdStash (NovelAI Clio)</option>
3299 <option value="5">NerdStash v2 (NovelAI Kayra)</option>3299 <option value="5">NerdStash v2 (NovelAI Kayra)</option>
3300 <option value="7">Mistral</option>3300 <option value="7">Mistral V1</option>
3301 <option value="17">Mistral Nemo</option>
3301 <option value="8">Yi</option>3302 <option value="8">Yi</option>
3302 <option value="11">Claude 1/2</option>3303 <option value="11">Claude 1/2</option>
3303 <option value="6">API (WebUI / koboldcpp)</option>3304 <option value="6">API (WebUI / koboldcpp)</option>
public/script.js+3 -5
@@ -6923,15 +6923,13 @@ export async function displayPastChats() {
6923 }6923 }
6924 // Check whether `text` {string} includes all of the `fragments` {string[]}.6924 // Check whether `text` {string} includes all of the `fragments` {string[]}.
6925 function matchFragments(fragments, text) {6925 function matchFragments(fragments, text) {
6926 if (!text) {6926 if (!text || !text.toLowerCase) return false;
6927 return false;6927 return fragments.every(item => text.toLowerCase().includes(item));
6928 }
6929 return fragments.every(item => text.includes(item));
6930 }6928 }
6931 const fragments = makeQueryFragments(searchQuery);6929 const fragments = makeQueryFragments(searchQuery);
6932 // At least one chat message must match *all* the fragments.6930 // At least one chat message must match *all* the fragments.
6933 // Currently, this doesn't match if the fragment matches are distributed across several chat messages.6931 // Currently, this doesn't match if the fragment matches are distributed across several chat messages.
6934 return chatContent && Object.values(chatContent).some(message => matchFragments(fragments, message?.mes?.toLowerCase()));6932 return chatContent && Object.values(chatContent).some(message => matchFragments(fragments, message?.mes));
6935 });6933 });
69366934
6937 console.debug(filteredData);6935 console.debug(filteredData);
public/scripts/PromptManager.js+25 -4
@@ -427,12 +427,13 @@ class PromptManager {
427427
428 document.getElementById(this.configuration.prefix + 'prompt_manager_popup_entry_form_name').value = prompt.name;428 document.getElementById(this.configuration.prefix + 'prompt_manager_popup_entry_form_name').value = prompt.name;
429 document.getElementById(this.configuration.prefix + 'prompt_manager_popup_entry_form_role').value = 'system';429 document.getElementById(this.configuration.prefix + 'prompt_manager_popup_entry_form_role').value = 'system';
430 document.getElementById(this.configuration.prefix + 'prompt_manager_popup_entry_form_prompt').value = prompt.content;430 document.getElementById(this.configuration.prefix + 'prompt_manager_popup_entry_form_prompt').value = prompt.content ?? '';
431 document.getElementById(this.configuration.prefix + 'prompt_manager_popup_entry_form_injection_position').value = prompt.injection_position ?? 0;431 document.getElementById(this.configuration.prefix + 'prompt_manager_popup_entry_form_injection_position').value = prompt.injection_position ?? 0;
432 document.getElementById(this.configuration.prefix + 'prompt_manager_popup_entry_form_injection_depth').value = prompt.injection_depth ?? DEFAULT_DEPTH;432 document.getElementById(this.configuration.prefix + 'prompt_manager_popup_entry_form_injection_depth').value = prompt.injection_depth ?? DEFAULT_DEPTH;
433 document.getElementById(this.configuration.prefix + 'prompt_manager_depth_block').style.visibility = prompt.injection_position === INJECTION_POSITION.ABSOLUTE ? 'visible' : 'hidden';433 document.getElementById(this.configuration.prefix + 'prompt_manager_depth_block').style.visibility = prompt.injection_position === INJECTION_POSITION.ABSOLUTE ? 'visible' : 'hidden';
434 document.getElementById(this.configuration.prefix + 'prompt_manager_popup_entry_form_forbid_overrides').checked = prompt.forbid_overrides ?? false;434 document.getElementById(this.configuration.prefix + 'prompt_manager_popup_entry_form_forbid_overrides').checked = prompt.forbid_overrides ?? false;
435 document.getElementById(this.configuration.prefix + 'prompt_manager_forbid_overrides_block').style.visibility = this.overridablePrompts.includes(prompt.identifier) ? 'visible' : 'hidden';435 document.getElementById(this.configuration.prefix + 'prompt_manager_forbid_overrides_block').style.visibility = this.overridablePrompts.includes(prompt.identifier) ? 'visible' : 'hidden';
436 document.getElementById(this.configuration.prefix + 'prompt_manager_popup_entry_form_prompt').disabled = prompt.marker ?? false;
436437
437 if (!this.systemPrompts.includes(promptId)) {438 if (!this.systemPrompts.includes(promptId)) {
438 document.getElementById(this.configuration.prefix + 'prompt_manager_popup_entry_form_injection_position').removeAttribute('disabled');439 document.getElementById(this.configuration.prefix + 'prompt_manager_popup_entry_form_injection_position').removeAttribute('disabled');
@@ -920,7 +921,15 @@ class PromptManager {
920 * @returns {boolean} True if the prompt can be edited, false otherwise.921 * @returns {boolean} True if the prompt can be edited, false otherwise.
921 */922 */
922 isPromptEditAllowed(prompt) {923 isPromptEditAllowed(prompt) {
923 return !prompt.marker;924 const forceEditPrompts = [
925 'charDescription',
926 'charPersonality',
927 'scenario',
928 'personaDescription',
929 'worldInfoBefore',
930 'worldInfoAfter',
931 ];
932 return forceEditPrompts.includes(prompt.identifier) || !prompt.marker;
924 }933 }
925934
926 /**935 /**
@@ -929,7 +938,17 @@ class PromptManager {
929 * @returns {boolean} True if the prompt can be deleted, false otherwise.938 * @returns {boolean} True if the prompt can be deleted, false otherwise.
930 */939 */
931 isPromptToggleAllowed(prompt) {940 isPromptToggleAllowed(prompt) {
932 const forceTogglePrompts = ['charDescription', 'charPersonality', 'scenario', 'personaDescription', 'worldInfoBefore', 'worldInfoAfter', 'main', 'chatHistory', 'dialogueExamples'];941 const forceTogglePrompts = [
942 'charDescription',
943 'charPersonality',
944 'scenario',
945 'personaDescription',
946 'worldInfoBefore',
947 'worldInfoAfter',
948 'main',
949 'chatHistory',
950 'dialogueExamples',
951 ];
933 return prompt.marker && !forceTogglePrompts.includes(prompt.identifier) ? false : !this.configuration.toggleDisabled.includes(prompt.identifier);952 return prompt.marker && !forceTogglePrompts.includes(prompt.identifier) ? false : !this.configuration.toggleDisabled.includes(prompt.identifier);
934 }953 }
935954
@@ -1182,8 +1201,9 @@ class PromptManager {
1182 const forbidOverridesBlock = document.getElementById(this.configuration.prefix + 'prompt_manager_forbid_overrides_block');1201 const forbidOverridesBlock = document.getElementById(this.configuration.prefix + 'prompt_manager_forbid_overrides_block');
11831202
1184 nameField.value = prompt.name ?? '';1203 nameField.value = prompt.name ?? '';
1185 roleField.value = prompt.role ?? '';1204 roleField.value = prompt.role ?? 'system';
1186 promptField.value = prompt.content ?? '';1205 promptField.value = prompt.content ?? '';
1206 promptField.disabled = prompt.marker ?? false;
1187 injectionPositionField.value = prompt.injection_position ?? INJECTION_POSITION.RELATIVE;1207 injectionPositionField.value = prompt.injection_position ?? INJECTION_POSITION.RELATIVE;
1188 injectionDepthField.value = prompt.injection_depth ?? DEFAULT_DEPTH;1208 injectionDepthField.value = prompt.injection_depth ?? DEFAULT_DEPTH;
1189 injectionDepthBlock.style.visibility = prompt.injection_position === INJECTION_POSITION.ABSOLUTE ? 'visible' : 'hidden';1209 injectionDepthBlock.style.visibility = prompt.injection_position === INJECTION_POSITION.ABSOLUTE ? 'visible' : 'hidden';
@@ -1279,6 +1299,7 @@ class PromptManager {
1279 nameField.value = '';1299 nameField.value = '';
1280 roleField.selectedIndex = 0;1300 roleField.selectedIndex = 0;
1281 promptField.value = '';1301 promptField.value = '';
1302 promptField.disabled = false;
1282 injectionPositionField.selectedIndex = 0;1303 injectionPositionField.selectedIndex = 0;
1283 injectionPositionField.removeAttribute('disabled');1304 injectionPositionField.removeAttribute('disabled');
1284 injectionDepthField.value = DEFAULT_DEPTH;1305 injectionDepthField.value = DEFAULT_DEPTH;
public/scripts/openai.js+21 -3
@@ -970,6 +970,12 @@ async function populateChatCompletion(prompts, chatCompletion, { bias, quietProm
970 }970 }
971971
972 const prompt = prompts.get(source);972 const prompt = prompts.get(source);
973
974 if (prompt.injection_position === INJECTION_POSITION.ABSOLUTE) {
975 promptManager.log(`Skipping prompt ${source} because it is an absolute prompt`);
976 return;
977 }
978
973 const index = target ? prompts.index(target) : prompts.index(source);979 const index = target ? prompts.index(target) : prompts.index(source);
974 const collection = new MessageCollection(source);980 const collection = new MessageCollection(source);
975 collection.add(Message.fromPrompt(prompt));981 collection.add(Message.fromPrompt(prompt));
@@ -1014,8 +1020,8 @@ async function populateChatCompletion(prompts, chatCompletion, { bias, quietProm
1014 acc.push(prompt.identifier);1020 acc.push(prompt.identifier);
1015 return acc;1021 return acc;
1016 }, []);1022 }, []);
1017 const userAbsolutePrompts = prompts.collection1023 const absolutePrompts = prompts.collection
1018 .filter((prompt) => false === prompt.system_prompt && prompt.injection_position === INJECTION_POSITION.ABSOLUTE)1024 .filter((prompt) => prompt.injection_position === INJECTION_POSITION.ABSOLUTE)
1019 .reduce((acc, prompt) => {1025 .reduce((acc, prompt) => {
1020 acc.push(prompt);1026 acc.push(prompt);
1021 return acc;1027 return acc;
@@ -1080,7 +1086,7 @@ async function populateChatCompletion(prompts, chatCompletion, { bias, quietProm
1080 }1086 }
10811087
1082 // Add in-chat injections1088 // Add in-chat injections
1083 messages = populationInjectionPrompts(userAbsolutePrompts, messages);1089 messages = populationInjectionPrompts(absolutePrompts, messages);
10841090
1085 // Decide whether dialogue examples should always be added1091 // Decide whether dialogue examples should always be added
1086 if (power_user.pin_examples) {1092 if (power_user.pin_examples) {
@@ -1217,6 +1223,18 @@ function preparePromptsForChatCompletion({ Scenario, charPersonality, name2, wor
12171223
1218 // Merge system prompts with prompt manager prompts1224 // Merge system prompts with prompt manager prompts
1219 systemPrompts.forEach(prompt => {1225 systemPrompts.forEach(prompt => {
1226 const collectionPrompt = prompts.get(prompt.identifier);
1227
1228 // Apply system prompt role/depth overrides if they set in the prompt manager
1229 if (collectionPrompt) {
1230 // In-Chat / Relative
1231 prompt.injection_position = collectionPrompt.injection_position ?? prompt.injection_position;
1232 // Depth for In-Chat
1233 prompt.injection_depth = collectionPrompt.injection_depth ?? prompt.injection_depth;
1234 // Role (system, user, assistant)
1235 prompt.role = collectionPrompt.role ?? prompt.role;
1236 }
1237
1220 const newPrompt = promptManager.preparePrompt(prompt);1238 const newPrompt = promptManager.preparePrompt(prompt);
1221 const markerIndex = prompts.index(prompt.identifier);1239 const markerIndex = prompts.index(prompt.identifier);
12221240
public/scripts/tokenizers.js+11 -0
@@ -30,6 +30,7 @@ export const tokenizers = {
30 JAMBA: 14,30 JAMBA: 14,
31 QWEN2: 15,31 QWEN2: 15,
32 COMMAND_R: 16,32 COMMAND_R: 16,
33 NEMO: 17,
33 BEST_MATCH: 99,34 BEST_MATCH: 99,
34};35};
3536
@@ -43,6 +44,7 @@ export const ENCODE_TOKENIZERS = [
43 tokenizers.JAMBA,44 tokenizers.JAMBA,
44 tokenizers.QWEN2,45 tokenizers.QWEN2,
45 tokenizers.COMMAND_R,46 tokenizers.COMMAND_R,
47 tokenizers.NEMO,
46 // uncomment when NovelAI releases Kayra and Clio weights, lol48 // uncomment when NovelAI releases Kayra and Clio weights, lol
47 //tokenizers.NERD,49 //tokenizers.NERD,
48 //tokenizers.NERD2,50 //tokenizers.NERD2,
@@ -121,6 +123,11 @@ const TOKENIZER_URLS = {
121 decode: '/api/tokenizers/command-r/decode',123 decode: '/api/tokenizers/command-r/decode',
122 count: '/api/tokenizers/command-r/encode',124 count: '/api/tokenizers/command-r/encode',
123 },125 },
126 [tokenizers.NEMO]: {
127 encode: '/api/tokenizers/nemo/encode',
128 decode: '/api/tokenizers/nemo/decode',
129 count: '/api/tokenizers/nemo/encode',
130 },
124 [tokenizers.API_TEXTGENERATIONWEBUI]: {131 [tokenizers.API_TEXTGENERATIONWEBUI]: {
125 encode: '/api/tokenizers/remote/textgenerationwebui/encode',132 encode: '/api/tokenizers/remote/textgenerationwebui/encode',
126 count: '/api/tokenizers/remote/textgenerationwebui/encode',133 count: '/api/tokenizers/remote/textgenerationwebui/encode',
@@ -535,6 +542,7 @@ export function getTokenizerModel() {
535 const jambaTokenizer = 'jamba';542 const jambaTokenizer = 'jamba';
536 const qwen2Tokenizer = 'qwen2';543 const qwen2Tokenizer = 'qwen2';
537 const commandRTokenizer = 'command-r';544 const commandRTokenizer = 'command-r';
545 const nemoTokenizer = 'nemo';
538546
539 // Assuming no one would use it for different models.. right?547 // Assuming no one would use it for different models.. right?
540 if (oai_settings.chat_completion_source == chat_completion_sources.SCALE) {548 if (oai_settings.chat_completion_source == chat_completion_sources.SCALE) {
@@ -628,6 +636,9 @@ export function getTokenizerModel() {
628 }636 }
629637
630 if (oai_settings.chat_completion_source == chat_completion_sources.MISTRALAI) {638 if (oai_settings.chat_completion_source == chat_completion_sources.MISTRALAI) {
639 if (oai_settings.mistralai_model.includes('nemo') || oai_settings.mistralai_model.includes('pixtral')) {
640 return nemoTokenizer;
641 }
631 return mistralTokenizer;642 return mistralTokenizer;
632 }643 }
633644
public/scripts/world-info.js+2 -2
@@ -4134,10 +4134,10 @@ export async function checkWorldInfo(chat, maxContext, isDryRun) {
41344134
4135 switch (entry.position) {4135 switch (entry.position) {
4136 case world_info_position.before:4136 case world_info_position.before:
4137 WIBeforeEntries.unshift(substituteParams(content));4137 WIBeforeEntries.unshift(content);
4138 break;4138 break;
4139 case world_info_position.after:4139 case world_info_position.after:
4140 WIAfterEntries.unshift(substituteParams(content));4140 WIAfterEntries.unshift(content);
4141 break;4141 break;
4142 case world_info_position.EMTop:4142 case world_info_position.EMTop:
4143 EMEntries.unshift(4143 EMEntries.unshift(
src/endpoints/chats.js+59 -16
@@ -92,8 +92,7 @@ function importOobaChat(userName, characterName, jsonData) {
92 }92 }
93 }93 }
9494
95 const chatContent = chat.map(obj => JSON.stringify(obj)).join('\n');95 return chat.map(obj => JSON.stringify(obj)).join('\n');
96 return chatContent;
97}96}
9897
99/**98/**
@@ -121,8 +120,7 @@ function importAgnaiChat(userName, characterName, jsonData) {
121 });120 });
122 }121 }
123122
124 const chatContent = chat.map(obj => JSON.stringify(obj)).join('\n');123 return chat.map(obj => JSON.stringify(obj)).join('\n');
125 return chatContent;
126}124}
127125
128/**126/**
@@ -159,6 +157,37 @@ function importCAIChat(userName, characterName, jsonData) {
159 return newChats;157 return newChats;
160}158}
161159
160/**
161 * Flattens `msg` and `swipes` data from Chub Chat format.
162 * Only changes enough to make it compatible with the standard chat serialization format.
163 * @param {string} userName User name
164 * @param {string} characterName Character name
165 * @param {string[]} lines serialised JSONL data
166 * @returns {string} Converted data
167 */
168function flattenChubChat(userName, characterName, lines) {
169 function flattenSwipe(swipe) {
170 return swipe.message ? swipe.message : swipe;
171 }
172
173 function convert(line) {
174 const lineData = tryParse(line);
175 if (!lineData) return line;
176
177 if (lineData.mes && lineData.mes.message) {
178 lineData.mes = lineData?.mes.message;
179 }
180
181 if (lineData?.swipes && Array.isArray(lineData.swipes)) {
182 lineData.swipes = lineData.swipes.map(swipe => flattenSwipe(swipe));
183 }
184
185 return JSON.stringify(lineData);
186 }
187
188 return (lines ?? []).map(convert).join('\n');
189}
190
162const router = express.Router();191const router = express.Router();
163192
164router.post('/save', jsonParser, function (request, response) {193router.post('/save', jsonParser, function (request, response) {
@@ -273,7 +302,7 @@ router.post('/export', jsonParser, async function (request, response) {
273 }302 }
274 try {303 try {
275 // Short path for JSONL files304 // Short path for JSONL files
276 if (request.body.format == 'jsonl') {305 if (request.body.format === 'jsonl') {
277 try {306 try {
278 const rawFile = fs.readFileSync(filename, 'utf8');307 const rawFile = fs.readFileSync(filename, 'utf8');
279 const successMessage = {308 const successMessage = {
@@ -283,8 +312,7 @@ router.post('/export', jsonParser, async function (request, response) {
283312
284 console.log(`Chat exported as ${exportfilename}`);313 console.log(`Chat exported as ${exportfilename}`);
285 return response.status(200).json(successMessage);314 return response.status(200).json(successMessage);
286 }315 } catch (err) {
287 catch (err) {
288 console.error(err);316 console.error(err);
289 const errorMessage = {317 const errorMessage = {
290 message: `Could not read JSONL file to export. Source chat file: ${filename}.`,318 message: `Could not read JSONL file to export. Source chat file: ${filename}.`,
@@ -319,8 +347,7 @@ router.post('/export', jsonParser, async function (request, response) {
319 console.log(`Chat exported as ${exportfilename}`);347 console.log(`Chat exported as ${exportfilename}`);
320 return response.status(200).json(successMessage);348 return response.status(200).json(successMessage);
321 });349 });
322 }350 } catch (err) {
323 catch (err) {
324 console.log('chat export failed.');351 console.log('chat export failed.');
325 console.log(err);352 console.log(err);
326 return response.sendStatus(400);353 return response.sendStatus(400);
@@ -396,20 +423,36 @@ router.post('/import', urlencodedParser, function (request, response) {
396 }423 }
397424
398 if (format === 'jsonl') {425 if (format === 'jsonl') {
399 const line = data.split('\n')[0];426 let lines = data.split('\n');
427 const header = lines[0];
428
429 const jsonData = JSON.parse(header);
430
431 if (!(jsonData.user_name !== undefined || jsonData.name !== undefined)) {
432 console.log('Incorrect chat format .jsonl');
433 return response.send({ error: true });
434 }
400435
401 const jsonData = JSON.parse(line);436 // Do a tiny bit of work to import Chub Chat data
437 // Processing the entire file is so fast that it's not worth checking if it's a Chub chat first
438 let flattenedChat;
439 try {
440 // flattening is unlikely to break, but it's not worth failing to
441 // import normal chats in an attempt to import a Chub chat
442 flattenedChat = flattenChubChat(userName, characterName, lines);
443 } catch (error) {
444 console.warn('Failed to flatten Chub Chat data: ', error);
445 }
402446
403 if (jsonData.user_name !== undefined || jsonData.name !== undefined) {
404 const fileName = `${characterName} - ${humanizedISO8601DateTime()} imported.jsonl`;447 const fileName = `${characterName} - ${humanizedISO8601DateTime()} imported.jsonl`;
405 const filePath = path.join(request.user.directories.chats, avatarUrl, fileName);448 const filePath = path.join(request.user.directories.chats, avatarUrl, fileName);
449 if (flattenedChat !== data) {
450 writeFileAtomicSync(filePath, flattenedChat, 'utf8');
451 } else {
406 fs.copyFileSync(pathToUpload, filePath);452 fs.copyFileSync(pathToUpload, filePath);
453 }
407 fs.unlinkSync(pathToUpload);454 fs.unlinkSync(pathToUpload);
408 response.send({ res: true });455 response.send({ res: true });
409 } else {
410 console.log('Incorrect chat format .jsonl');
411 return response.send({ error: true });
412 }
413 }456 }
414 } catch (error) {457 } catch (error) {
415 console.error(error);458 console.error(error);
src/endpoints/tokenizers.js+24 -0
@@ -221,6 +221,7 @@ const claude_tokenizer = new WebTokenizer('src/tokenizers/claude.json');
221const llama3_tokenizer = new WebTokenizer('src/tokenizers/llama3.json');221const llama3_tokenizer = new WebTokenizer('src/tokenizers/llama3.json');
222const commandTokenizer = new WebTokenizer('https://github.com/SillyTavern/SillyTavern-Tokenizers/raw/main/command-r.json', 'src/tokenizers/llama3.json');222const commandTokenizer = new WebTokenizer('https://github.com/SillyTavern/SillyTavern-Tokenizers/raw/main/command-r.json', 'src/tokenizers/llama3.json');
223const qwen2Tokenizer = new WebTokenizer('https://github.com/SillyTavern/SillyTavern-Tokenizers/raw/main/qwen2.json', 'src/tokenizers/llama3.json');223const qwen2Tokenizer = new WebTokenizer('https://github.com/SillyTavern/SillyTavern-Tokenizers/raw/main/qwen2.json', 'src/tokenizers/llama3.json');
224const nemoTokenizer = new WebTokenizer('https://github.com/SillyTavern/SillyTavern-Tokenizers/raw/main/nemo.json', 'src/tokenizers/llama3.json');
224225
225const sentencepieceTokenizers = [226const sentencepieceTokenizers = [
226 'llama',227 'llama',
@@ -418,6 +419,10 @@ function getTokenizerModel(requestModel) {
418 return 'command-r';419 return 'command-r';
419 }420 }
420421
422 if (requestModel.includes('nemo')) {
423 return 'nemo';
424 }
425
421 // default426 // default
422 return 'gpt-3.5-turbo';427 return 'gpt-3.5-turbo';
423}428}
@@ -645,6 +650,7 @@ router.post('/claude/encode', jsonParser, createWebTokenizerEncodingHandler(clau
645router.post('/llama3/encode', jsonParser, createWebTokenizerEncodingHandler(llama3_tokenizer));650router.post('/llama3/encode', jsonParser, createWebTokenizerEncodingHandler(llama3_tokenizer));
646router.post('/qwen2/encode', jsonParser, createWebTokenizerEncodingHandler(qwen2Tokenizer));651router.post('/qwen2/encode', jsonParser, createWebTokenizerEncodingHandler(qwen2Tokenizer));
647router.post('/command-r/encode', jsonParser, createWebTokenizerEncodingHandler(commandTokenizer));652router.post('/command-r/encode', jsonParser, createWebTokenizerEncodingHandler(commandTokenizer));
653router.post('/nemo/encode', jsonParser, createWebTokenizerEncodingHandler(nemoTokenizer));
648router.post('/llama/decode', jsonParser, createSentencepieceDecodingHandler(spp_llama));654router.post('/llama/decode', jsonParser, createSentencepieceDecodingHandler(spp_llama));
649router.post('/nerdstash/decode', jsonParser, createSentencepieceDecodingHandler(spp_nerd));655router.post('/nerdstash/decode', jsonParser, createSentencepieceDecodingHandler(spp_nerd));
650router.post('/nerdstash_v2/decode', jsonParser, createSentencepieceDecodingHandler(spp_nerd_v2));656router.post('/nerdstash_v2/decode', jsonParser, createSentencepieceDecodingHandler(spp_nerd_v2));
@@ -657,6 +663,7 @@ router.post('/claude/decode', jsonParser, createWebTokenizerDecodingHandler(clau
657router.post('/llama3/decode', jsonParser, createWebTokenizerDecodingHandler(llama3_tokenizer));663router.post('/llama3/decode', jsonParser, createWebTokenizerDecodingHandler(llama3_tokenizer));
658router.post('/qwen2/decode', jsonParser, createWebTokenizerDecodingHandler(qwen2Tokenizer));664router.post('/qwen2/decode', jsonParser, createWebTokenizerDecodingHandler(qwen2Tokenizer));
659router.post('/command-r/decode', jsonParser, createWebTokenizerDecodingHandler(commandTokenizer));665router.post('/command-r/decode', jsonParser, createWebTokenizerDecodingHandler(commandTokenizer));
666router.post('/nemo/decode', jsonParser, createWebTokenizerDecodingHandler(nemoTokenizer));
660667
661router.post('/openai/encode', jsonParser, async function (req, res) {668router.post('/openai/encode', jsonParser, async function (req, res) {
662 try {669 try {
@@ -707,6 +714,11 @@ router.post('/openai/encode', jsonParser, async function (req, res) {
707 return handler(req, res);714 return handler(req, res);
708 }715 }
709716
717 if (queryModel.includes('nemo')) {
718 const handler = createWebTokenizerEncodingHandler(nemoTokenizer);
719 return handler(req, res);
720 }
721
710 const model = getTokenizerModel(queryModel);722 const model = getTokenizerModel(queryModel);
711 const handler = createTiktokenEncodingHandler(model);723 const handler = createTiktokenEncodingHandler(model);
712 return handler(req, res);724 return handler(req, res);
@@ -765,6 +777,11 @@ router.post('/openai/decode', jsonParser, async function (req, res) {
765 return handler(req, res);777 return handler(req, res);
766 }778 }
767779
780 if (queryModel.includes('nemo')) {
781 const handler = createWebTokenizerDecodingHandler(nemoTokenizer);
782 return handler(req, res);
783 }
784
768 const model = getTokenizerModel(queryModel);785 const model = getTokenizerModel(queryModel);
769 const handler = createTiktokenDecodingHandler(model);786 const handler = createTiktokenDecodingHandler(model);
770 return handler(req, res);787 return handler(req, res);
@@ -835,6 +852,13 @@ router.post('/openai/count', jsonParser, async function (req, res) {
835 return res.send({ 'token_count': num_tokens });852 return res.send({ 'token_count': num_tokens });
836 }853 }
837854
855 if (model === 'nemo') {
856 const instance = await nemoTokenizer.get();
857 if (!instance) throw new Error('Failed to load the Nemo tokenizer');
858 num_tokens = countWebTokenizerTokens(instance, req.body);
859 return res.send({ 'token_count': num_tokens });
860 }
861
838 const tokensPerName = queryModel.includes('gpt-3.5-turbo-0301') ? -1 : 1;862 const tokensPerName = queryModel.includes('gpt-3.5-turbo-0301') ? -1 : 1;
839 const tokensPerMessage = queryModel.includes('gpt-3.5-turbo-0301') ? 4 : 3;863 const tokensPerMessage = queryModel.includes('gpt-3.5-turbo-0301') ? 4 : 3;
840 const tokensPadding = 3;864 const tokensPadding = 3;
start.sh+1 -1
@@ -26,7 +26,7 @@ fi
2626
27echo "Installing Node Modules..."27echo "Installing Node Modules..."
28export NODE_ENV=production28export NODE_ENV=production
29npm i --no-audit --no-fund --quiet --omit=dev29npm i --no-audit --no-fund --loglevel=error --no-progress --omit=dev
3030
31echo "Entering SillyTavern..."31echo "Entering SillyTavern..."
32node "server.js" "$@"32node "server.js" "$@"