Merge branch 'staging' into sysprompt-divorce
| @@ -19,7 +19,7 @@ ENV NODE_ENV=production | |||
| 19 | COPY package*.json post-install.js ./ | 19 | COPY package*.json post-install.js ./ |
| 20 | RUN \ | 20 | RUN \ |
| 21 | echo "*** Install npm packages ***" && \ | 21 | echo "*** Install npm packages ***" && \ |
| 22 | npm i --no-audit --no-fund --quiet --omit=dev && npm cache clean --force | 22 | npm i --no-audit --no-fund --loglevel=error --no-progress --omit=dev && npm cache clean --force |
| 23 | 23 | ||
| 24 | # Bundle app source | 24 | # Bundle app source |
| 25 | COPY . ./ | 25 | COPY . ./ |
| @@ -1,7 +1,7 @@ | |||
| 1 | @echo off | 1 | @echo off |
| 2 | pushd %~dp0 | 2 | pushd %~dp0 |
| 3 | set NODE_ENV=production | 3 | set NODE_ENV=production |
| 4 | call npm install --no-audit --no-fund --quiet --omit=dev | 4 | call npm install --no-audit --no-fund --loglevel=error --no-progress --omit=dev |
| 5 | node server.js %* | 5 | node server.js %* |
| 6 | pause | 6 | pause |
| 7 | popd | 7 | popd |
| @@ -12,7 +12,7 @@ if %errorlevel% neq 0 ( | |||
| 12 | ) | 12 | ) |
| 13 | ) | 13 | ) |
| 14 | set NODE_ENV=production | 14 | set NODE_ENV=production |
| 15 | call npm install --no-audit --no-fund --quiet --omit=dev | 15 | call npm install --no-audit --no-fund --loglevel=error --no-progress --omit=dev |
| 16 | node server.js %* | 16 | node server.js %* |
| 17 | pause | 17 | pause |
| 18 | popd | 18 | popd |
| @@ -95,7 +95,7 @@ if %errorlevel% neq 0 ( | |||
| 95 | 95 | ||
| 96 | echo Installing npm packages and starting server | 96 | echo Installing npm packages and starting server |
| 97 | set NODE_ENV=production | 97 | set NODE_ENV=production |
| 98 | call npm install --no-audit --no-fund --quiet --omit=dev | 98 | call npm install --no-audit --no-fund --loglevel=error --no-progress --omit=dev |
| 99 | node server.js %* | 99 | node server.js %* |
| 100 | 100 | ||
| 101 | :end | 101 | :end |
| @@ -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 | } | ||
| @@ -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> |
| @@ -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 | }); |
| 6936 | 6934 | ||
| 6937 | console.debug(filteredData); | 6935 | console.debug(filteredData); |
| @@ -427,12 +427,13 @@ class PromptManager { | |||
| 427 | 427 | ||
| 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; | ||
| 436 | 437 | ||
| 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 | } |
| 925 | 934 | ||
| 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 | } |
| 935 | 954 | ||
| @@ -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'); |
| 1183 | 1202 | ||
| 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; |
| @@ -970,6 +970,12 @@ async function populateChatCompletion(prompts, chatCompletion, { bias, quietProm | |||
| 970 | } | 970 | } |
| 971 | 971 | ||
| 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.collection | 1023 | 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 | } |
| 1081 | 1087 | ||
| 1082 | // Add in-chat injections | 1088 | // Add in-chat injections |
| 1083 | messages = populationInjectionPrompts(userAbsolutePrompts, messages); | 1089 | messages = populationInjectionPrompts(absolutePrompts, messages); |
| 1084 | 1090 | ||
| 1085 | // Decide whether dialogue examples should always be added | 1091 | // 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 | |||
| 1217 | 1223 | ||
| 1218 | // Merge system prompts with prompt manager prompts | 1224 | // 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); |
| 1222 | 1240 | ||
| @@ -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 | }; |
| 35 | 36 | ||
| @@ -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, lol | 48 | // 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'; | ||
| 538 | 546 | ||
| 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 | } |
| 629 | 637 | ||
| 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 | } |
| 633 | 644 | ||
| @@ -4134,10 +4134,10 @@ export async function checkWorldInfo(chat, maxContext, isDryRun) { | |||
| 4134 | 4134 | ||
| 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( |
| @@ -92,8 +92,7 @@ function importOobaChat(userName, characterName, jsonData) { | |||
| 92 | } | 92 | } |
| 93 | } | 93 | } |
| 94 | 94 | ||
| 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 | } |
| 98 | 97 | ||
| 99 | /** | 98 | /** |
| @@ -121,8 +120,7 @@ function importAgnaiChat(userName, characterName, jsonData) { | |||
| 121 | }); | 120 | }); |
| 122 | } | 121 | } |
| 123 | 122 | ||
| 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 | } |
| 127 | 125 | ||
| 128 | /** | 126 | /** |
| @@ -159,6 +157,37 @@ function importCAIChat(userName, characterName, jsonData) { | |||
| 159 | return newChats; | 157 | return newChats; |
| 160 | } | 158 | } |
| 161 | 159 | ||
| 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 | */ | ||
| 168 | function 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 | |||
| 162 | const router = express.Router(); | 191 | const router = express.Router(); |
| 163 | 192 | ||
| 164 | router.post('/save', jsonParser, function (request, response) { | 193 | router.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 files | 304 | // 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) { | |||
| 283 | 312 | ||
| 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 | } |
| 397 | 424 | ||
| 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 | } | ||
| 400 | 435 | ||
| 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 | } | ||
| 402 | 446 | ||
| 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); |
| @@ -221,6 +221,7 @@ const claude_tokenizer = new WebTokenizer('src/tokenizers/claude.json'); | |||
| 221 | const llama3_tokenizer = new WebTokenizer('src/tokenizers/llama3.json'); | 221 | const llama3_tokenizer = new WebTokenizer('src/tokenizers/llama3.json'); |
| 222 | const commandTokenizer = new WebTokenizer('https://github.com/SillyTavern/SillyTavern-Tokenizers/raw/main/command-r.json', 'src/tokenizers/llama3.json'); | 222 | const commandTokenizer = new WebTokenizer('https://github.com/SillyTavern/SillyTavern-Tokenizers/raw/main/command-r.json', 'src/tokenizers/llama3.json'); |
| 223 | const qwen2Tokenizer = new WebTokenizer('https://github.com/SillyTavern/SillyTavern-Tokenizers/raw/main/qwen2.json', 'src/tokenizers/llama3.json'); | 223 | const qwen2Tokenizer = new WebTokenizer('https://github.com/SillyTavern/SillyTavern-Tokenizers/raw/main/qwen2.json', 'src/tokenizers/llama3.json'); |
| 224 | const nemoTokenizer = new WebTokenizer('https://github.com/SillyTavern/SillyTavern-Tokenizers/raw/main/nemo.json', 'src/tokenizers/llama3.json'); | ||
| 224 | 225 | ||
| 225 | const sentencepieceTokenizers = [ | 226 | const sentencepieceTokenizers = [ |
| 226 | 'llama', | 227 | 'llama', |
| @@ -418,6 +419,10 @@ function getTokenizerModel(requestModel) { | |||
| 418 | return 'command-r'; | 419 | return 'command-r'; |
| 419 | } | 420 | } |
| 420 | 421 | ||
| 422 | if (requestModel.includes('nemo')) { | ||
| 423 | return 'nemo'; | ||
| 424 | } | ||
| 425 | |||
| 421 | // default | 426 | // 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 | |||
| 645 | router.post('/llama3/encode', jsonParser, createWebTokenizerEncodingHandler(llama3_tokenizer)); | 650 | router.post('/llama3/encode', jsonParser, createWebTokenizerEncodingHandler(llama3_tokenizer)); |
| 646 | router.post('/qwen2/encode', jsonParser, createWebTokenizerEncodingHandler(qwen2Tokenizer)); | 651 | router.post('/qwen2/encode', jsonParser, createWebTokenizerEncodingHandler(qwen2Tokenizer)); |
| 647 | router.post('/command-r/encode', jsonParser, createWebTokenizerEncodingHandler(commandTokenizer)); | 652 | router.post('/command-r/encode', jsonParser, createWebTokenizerEncodingHandler(commandTokenizer)); |
| 653 | router.post('/nemo/encode', jsonParser, createWebTokenizerEncodingHandler(nemoTokenizer)); | ||
| 648 | router.post('/llama/decode', jsonParser, createSentencepieceDecodingHandler(spp_llama)); | 654 | router.post('/llama/decode', jsonParser, createSentencepieceDecodingHandler(spp_llama)); |
| 649 | router.post('/nerdstash/decode', jsonParser, createSentencepieceDecodingHandler(spp_nerd)); | 655 | router.post('/nerdstash/decode', jsonParser, createSentencepieceDecodingHandler(spp_nerd)); |
| 650 | router.post('/nerdstash_v2/decode', jsonParser, createSentencepieceDecodingHandler(spp_nerd_v2)); | 656 | router.post('/nerdstash_v2/decode', jsonParser, createSentencepieceDecodingHandler(spp_nerd_v2)); |
| @@ -657,6 +663,7 @@ router.post('/claude/decode', jsonParser, createWebTokenizerDecodingHandler(clau | |||
| 657 | router.post('/llama3/decode', jsonParser, createWebTokenizerDecodingHandler(llama3_tokenizer)); | 663 | router.post('/llama3/decode', jsonParser, createWebTokenizerDecodingHandler(llama3_tokenizer)); |
| 658 | router.post('/qwen2/decode', jsonParser, createWebTokenizerDecodingHandler(qwen2Tokenizer)); | 664 | router.post('/qwen2/decode', jsonParser, createWebTokenizerDecodingHandler(qwen2Tokenizer)); |
| 659 | router.post('/command-r/decode', jsonParser, createWebTokenizerDecodingHandler(commandTokenizer)); | 665 | router.post('/command-r/decode', jsonParser, createWebTokenizerDecodingHandler(commandTokenizer)); |
| 666 | router.post('/nemo/decode', jsonParser, createWebTokenizerDecodingHandler(nemoTokenizer)); | ||
| 660 | 667 | ||
| 661 | router.post('/openai/encode', jsonParser, async function (req, res) { | 668 | router.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 | } |
| 709 | 716 | ||
| 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 | } |
| 767 | 779 | ||
| 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 | } |
| 837 | 854 | ||
| 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; |
| @@ -26,7 +26,7 @@ fi | |||
| 26 | 26 | ||
| 27 | echo "Installing Node Modules..." | 27 | echo "Installing Node Modules..." |
| 28 | export NODE_ENV=production | 28 | export NODE_ENV=production |
| 29 | npm i --no-audit --no-fund --quiet --omit=dev | 29 | npm i --no-audit --no-fund --loglevel=error --no-progress --omit=dev |
| 30 | 30 | ||
| 31 | echo "Entering SillyTavern..." | 31 | echo "Entering SillyTavern..." |
| 32 | node "server.js" "$@" | 32 | node "server.js" "$@" |