Merge branch 'staging' into connection-manager
| @@ -95,6 +95,9 @@ requestOverrides: [] | |||
| 95 | enableExtensions: true | 95 | enableExtensions: true |
| 96 | # Automatically update extensions when a release version changes | 96 | # Automatically update extensions when a release version changes |
| 97 | enableExtensionsAutoUpdate: true | 97 | enableExtensionsAutoUpdate: true |
| 98 | # Additional model tokenizers can be downloaded on demand. | ||
| 99 | # Disabling will fallback to another locally available tokenizer. | ||
| 100 | enableDownloadableTokenizers: true | ||
| 98 | # Extension settings | 101 | # Extension settings |
| 99 | extras: | 102 | extras: |
| 100 | # Disables automatic model download from HuggingFace | 103 | # Disables automatic model download from HuggingFace |
| @@ -1820,12 +1820,12 @@ | |||
| 1820 | <span id="claude_assistant_prefill_text" data-i18n="Assistant Prefill">Assistant Prefill</span> | 1820 | <span id="claude_assistant_prefill_text" data-i18n="Assistant Prefill">Assistant Prefill</span> |
| 1821 | <i class="editor_maximize fa-solid fa-maximize right_menu_button" data-for="claude_assistant_prefill" title="Expand the editor" data-i18n="[title]Expand the editor"></i> | 1821 | <i class="editor_maximize fa-solid fa-maximize right_menu_button" data-for="claude_assistant_prefill" title="Expand the editor" data-i18n="[title]Expand the editor"></i> |
| 1822 | </div> | 1822 | </div> |
| 1823 | <textarea id="claude_assistant_prefill" class="text_pole textarea_compact" name="assistant_prefill" rows="6" data-i18n="[placeholder]Start Claude's answer with..." placeholder="Start Claude's answer with..."></textarea> | 1823 | <textarea id="claude_assistant_prefill" class="text_pole textarea_compact autoSetHeight" name="assistant_prefill" rows="2" data-i18n="[placeholder]Start Claude's answer with..." placeholder="Start Claude's answer with..."></textarea> |
| 1824 | <div class="flex-container alignItemsCenter"> | 1824 | <div class="flex-container alignItemsCenter"> |
| 1825 | <span id="claude_assistant_impersonation_text" data-i18n="Assistant Impersonation Prefill">Assistant Impersonation Prefill</span> | 1825 | <span id="claude_assistant_impersonation_text" data-i18n="Assistant Impersonation Prefill">Assistant Impersonation Prefill</span> |
| 1826 | <i class="editor_maximize fa-solid fa-maximize right_menu_button" data-for="claude_assistant_impersonation" title="Expand the editor" data-i18n="[title]Expand the editor"></i> | 1826 | <i class="editor_maximize fa-solid fa-maximize right_menu_button" data-for="claude_assistant_impersonation" title="Expand the editor" data-i18n="[title]Expand the editor"></i> |
| 1827 | </div> | 1827 | </div> |
| 1828 | <textarea id="claude_assistant_impersonation" class="text_pole textarea_compact" name="assistant_impersonation" rows="6" data-i18n="[placeholder]Start Claude's answer with..." placeholder="Start Claude's answer with..."></textarea> | 1828 | <textarea id="claude_assistant_impersonation" class="text_pole textarea_compact autoSetHeight" name="assistant_impersonation" rows="2" data-i18n="[placeholder]Start Claude's answer with..." placeholder="Start Claude's answer with..."></textarea> |
| 1829 | </div> | 1829 | </div> |
| 1830 | <label for="claude_use_sysprompt" class="checkbox_label widthFreeExpand"> | 1830 | <label for="claude_use_sysprompt" class="checkbox_label widthFreeExpand"> |
| 1831 | <input id="claude_use_sysprompt" type="checkbox" /> | 1831 | <input id="claude_use_sysprompt" type="checkbox" /> |
| @@ -1845,7 +1845,7 @@ | |||
| 1845 | <div class="fa-solid fa-clock-rotate-left"></div> | 1845 | <div class="fa-solid fa-clock-rotate-left"></div> |
| 1846 | </div> | 1846 | </div> |
| 1847 | </div> | 1847 | </div> |
| 1848 | <textarea id="claude_human_sysprompt_textarea" class="text_pole textarea_compact" rows="4" data-i18n="[placeholder]Human message" placeholder="Human message, instruction, etc. Adds nothing when empty, i.e. requires a new prompt with the role 'user'."></textarea> | 1848 | <textarea id="claude_human_sysprompt_textarea" class="text_pole textarea_compact autoSetHeight" rows="2" data-i18n="[placeholder]Human message" placeholder="Human message, instruction, etc. Adds nothing when empty, i.e. requires a new prompt with the role 'user'."></textarea> |
| 1849 | </div> | 1849 | </div> |
| 1850 | </div> | 1850 | </div> |
| 1851 | </div> | 1851 | </div> |
| @@ -3289,6 +3289,8 @@ | |||
| 3289 | <option value="12">Llama 3</option> | 3289 | <option value="12">Llama 3</option> |
| 3290 | <option value="13">Gemma / Gemini</option> | 3290 | <option value="13">Gemma / Gemini</option> |
| 3291 | <option value="14">Jamba</option> | 3291 | <option value="14">Jamba</option> |
| 3292 | <option value="15">Qwen2</option> | ||
| 3293 | <option value="16">Command-R</option> | ||
| 3292 | <option value="4">NerdStash (NovelAI Clio)</option> | 3294 | <option value="4">NerdStash (NovelAI Clio)</option> |
| 3293 | <option value="5">NerdStash v2 (NovelAI Kayra)</option> | 3295 | <option value="5">NerdStash v2 (NovelAI Kayra)</option> |
| 3294 | <option value="7">Mistral</option> | 3296 | <option value="7">Mistral</option> |
| @@ -8504,22 +8504,23 @@ for (const chatCompletionSource of Object.values(chat_completion_sources)) { | |||
| 8504 | }; | 8504 | }; |
| 8505 | } | 8505 | } |
| 8506 | 8506 | ||
| 8507 | async function selectContextCallback(_, name) { | 8507 | async function selectContextCallback(args, name) { |
| 8508 | if (!name) { | 8508 | if (!name) { |
| 8509 | return power_user.context.preset; | 8509 | return power_user.context.preset; |
| 8510 | } | 8510 | } |
| 8511 | 8511 | ||
| 8512 | const quiet = isTrueBoolean(args?.quiet); | ||
| 8512 | const contextNames = context_presets.map(preset => preset.name); | 8513 | const contextNames = context_presets.map(preset => preset.name); |
| 8513 | const fuse = new Fuse(contextNames); | 8514 | const fuse = new Fuse(contextNames); |
| 8514 | const result = fuse.search(name); | 8515 | const result = fuse.search(name); |
| 8515 | 8516 | ||
| 8516 | if (result.length === 0) { | 8517 | if (result.length === 0) { |
| 8517 | toastr.warning(`Context template "${name}" not found`); | 8518 | !quiet && toastr.warning(`Context template "${name}" not found`); |
| 8518 | return ''; | 8519 | return ''; |
| 8519 | } | 8520 | } |
| 8520 | 8521 | ||
| 8521 | const foundName = result[0].item; | 8522 | const foundName = result[0].item; |
| 8522 | selectContextPreset(foundName); | 8523 | selectContextPreset(foundName, quiet); |
| 8523 | return foundName; | 8524 | return foundName; |
| 8524 | } | 8525 | } |
| 8525 | 8526 | ||
| @@ -8528,16 +8529,16 @@ async function selectInstructCallback(args, name) { | |||
| 8528 | return power_user.instruct.preset; | 8529 | return power_user.instruct.preset; |
| 8529 | } | 8530 | } |
| 8530 | 8531 | ||
| 8532 | const quiet = isTrueBoolean(args?.quiet); | ||
| 8531 | const instructNames = instruct_presets.map(preset => preset.name); | 8533 | const instructNames = instruct_presets.map(preset => preset.name); |
| 8532 | const fuse = new Fuse(instructNames); | 8534 | const fuse = new Fuse(instructNames); |
| 8533 | const result = fuse.search(name); | 8535 | const result = fuse.search(name); |
| 8534 | 8536 | ||
| 8535 | if (result.length === 0) { | 8537 | if (result.length === 0) { |
| 8536 | toastr.warning(`Instruct template "${name}" not found`); | 8538 | !quiet && toastr.warning(`Instruct template "${name}" not found`); |
| 8537 | return ''; | 8539 | return ''; |
| 8538 | } | 8540 | } |
| 8539 | 8541 | ||
| 8540 | const quiet = isTrueBoolean(args?.quiet); | ||
| 8541 | const foundName = result[0].item; | 8542 | const foundName = result[0].item; |
| 8542 | selectInstructPreset(foundName, quiet); | 8543 | selectInstructPreset(foundName, quiet); |
| 8543 | return foundName; | 8544 | return foundName; |
| @@ -9283,6 +9284,15 @@ jQuery(async function () { | |||
| 9283 | name: 'context', | 9284 | name: 'context', |
| 9284 | callback: selectContextCallback, | 9285 | callback: selectContextCallback, |
| 9285 | returns: 'template name', | 9286 | returns: 'template name', |
| 9287 | namedArgumentList: [ | ||
| 9288 | SlashCommandNamedArgument.fromProps({ | ||
| 9289 | name: 'quiet', | ||
| 9290 | description: 'Suppress the toast message on template change', | ||
| 9291 | typeList: [ARGUMENT_TYPE.BOOLEAN], | ||
| 9292 | defaultValue: 'false', | ||
| 9293 | enumList: commonEnumProviders.boolean('trueFalse')(), | ||
| 9294 | }), | ||
| 9295 | ], | ||
| 9286 | unnamedArgumentList: [ | 9296 | unnamedArgumentList: [ |
| 9287 | SlashCommandArgument.fromProps({ | 9297 | SlashCommandArgument.fromProps({ |
| 9288 | description: 'context template name', | 9298 | description: 'context template name', |
| @@ -724,7 +724,7 @@ function onChatChanged() { | |||
| 724 | } | 724 | } |
| 725 | 725 | ||
| 726 | async function adjustElementScrollHeight() { | 726 | async function adjustElementScrollHeight() { |
| 727 | if (!$('.sd_settings').is(':visible')) { | 727 | if (CSS.supports('field-sizing', 'content') || !$('.sd_settings').is(':visible')) { |
| 728 | return; | 728 | return; |
| 729 | } | 729 | } |
| 730 | 730 | ||
| @@ -737,17 +737,19 @@ async function adjustElementScrollHeight() { | |||
| 737 | async function onCharacterPromptInput() { | 737 | async function onCharacterPromptInput() { |
| 738 | const key = getCharaFilename(this_chid); | 738 | const key = getCharaFilename(this_chid); |
| 739 | extension_settings.sd.character_prompts[key] = $('#sd_character_prompt').val(); | 739 | extension_settings.sd.character_prompts[key] = $('#sd_character_prompt').val(); |
| 740 | await resetScrollHeight($(this)); | ||
| 741 | saveSettingsDebounced(); | 740 | saveSettingsDebounced(); |
| 742 | writePromptFieldsDebounced(this_chid); | 741 | writePromptFieldsDebounced(this_chid); |
| 742 | if (CSS.supports('field-sizing', 'content')) return; | ||
| 743 | await resetScrollHeight($(this)); | ||
| 743 | } | 744 | } |
| 744 | 745 | ||
| 745 | async function onCharacterNegativePromptInput() { | 746 | async function onCharacterNegativePromptInput() { |
| 746 | const key = getCharaFilename(this_chid); | 747 | const key = getCharaFilename(this_chid); |
| 747 | extension_settings.sd.character_negative_prompts[key] = $('#sd_character_negative_prompt').val(); | 748 | extension_settings.sd.character_negative_prompts[key] = $('#sd_character_negative_prompt').val(); |
| 748 | await resetScrollHeight($(this)); | ||
| 749 | saveSettingsDebounced(); | 749 | saveSettingsDebounced(); |
| 750 | writePromptFieldsDebounced(this_chid); | 750 | writePromptFieldsDebounced(this_chid); |
| 751 | if (CSS.supports('field-sizing', 'content')) return; | ||
| 752 | await resetScrollHeight($(this)); | ||
| 751 | } | 753 | } |
| 752 | 754 | ||
| 753 | function getCharacterPrefix() { | 755 | function getCharacterPrefix() { |
| @@ -856,14 +858,16 @@ function onStepsInput() { | |||
| 856 | 858 | ||
| 857 | async function onPromptPrefixInput() { | 859 | async function onPromptPrefixInput() { |
| 858 | extension_settings.sd.prompt_prefix = $('#sd_prompt_prefix').val(); | 860 | extension_settings.sd.prompt_prefix = $('#sd_prompt_prefix').val(); |
| 859 | await resetScrollHeight($(this)); | ||
| 860 | saveSettingsDebounced(); | 861 | saveSettingsDebounced(); |
| 862 | if (CSS.supports('field-sizing', 'content')) return; | ||
| 863 | await resetScrollHeight($(this)); | ||
| 861 | } | 864 | } |
| 862 | 865 | ||
| 863 | async function onNegativePromptInput() { | 866 | async function onNegativePromptInput() { |
| 864 | extension_settings.sd.negative_prompt = $('#sd_negative_prompt').val(); | 867 | extension_settings.sd.negative_prompt = $('#sd_negative_prompt').val(); |
| 865 | await resetScrollHeight($(this)); | ||
| 866 | saveSettingsDebounced(); | 868 | saveSettingsDebounced(); |
| 869 | if (CSS.supports('field-sizing', 'content')) return; | ||
| 870 | await resetScrollHeight($(this)); | ||
| 867 | } | 871 | } |
| 868 | 872 | ||
| 869 | function onSamplerChange() { | 873 | function onSamplerChange() { |
| @@ -3911,12 +3915,14 @@ jQuery(async () => { | |||
| 3911 | $('#sd_stability_style_preset').on('change', onStabilityStylePresetChange); | 3915 | $('#sd_stability_style_preset').on('change', onStabilityStylePresetChange); |
| 3912 | $('#sd_huggingface_model_id').on('input', onHFModelInput); | 3916 | $('#sd_huggingface_model_id').on('input', onHFModelInput); |
| 3913 | 3917 | ||
| 3914 | $('.sd_settings .inline-drawer-toggle').on('click', function () { | 3918 | if (!CSS.supports('field-sizing', 'content')) { |
| 3915 | initScrollHeight($('#sd_prompt_prefix')); | 3919 | $('.sd_settings .inline-drawer-toggle').on('click', function () { |
| 3916 | initScrollHeight($('#sd_negative_prompt')); | 3920 | initScrollHeight($('#sd_prompt_prefix')); |
| 3917 | initScrollHeight($('#sd_character_prompt')); | 3921 | initScrollHeight($('#sd_negative_prompt')); |
| 3918 | initScrollHeight($('#sd_character_negative_prompt')); | 3922 | initScrollHeight($('#sd_character_prompt')); |
| 3919 | }); | 3923 | initScrollHeight($('#sd_character_negative_prompt')); |
| 3924 | }); | ||
| 3925 | } | ||
| 3920 | 3926 | ||
| 3921 | for (const [key, value] of Object.entries(resolutionOptions)) { | 3927 | for (const [key, value] of Object.entries(resolutionOptions)) { |
| 3922 | const option = document.createElement('option'); | 3928 | const option = document.createElement('option'); |
| @@ -408,16 +408,16 @@ | |||
| 408 | </div> | 408 | </div> |
| 409 | </div> | 409 | </div> |
| 410 | <label for="sd_prompt_prefix" data-i18n="Common prompt prefix">Common prompt prefix</label> | 410 | <label for="sd_prompt_prefix" data-i18n="Common prompt prefix">Common prompt prefix</label> |
| 411 | <textarea id="sd_prompt_prefix" class="text_pole textarea_compact" data-i18n="[placeholder]sd_prompt_prefix_placeholder" placeholder="Use {prompt} to specify where the generated prompt will be inserted"></textarea> | 411 | <textarea id="sd_prompt_prefix" class="text_pole textarea_compact autoSetHeight" data-i18n="[placeholder]sd_prompt_prefix_placeholder" placeholder="Use {prompt} to specify where the generated prompt will be inserted"></textarea> |
| 412 | <label for="sd_negative_prompt" data-i18n="Negative common prompt prefix">Negative common prompt prefix</label> | 412 | <label for="sd_negative_prompt" data-i18n="Negative common prompt prefix">Negative common prompt prefix</label> |
| 413 | <textarea id="sd_negative_prompt" class="text_pole textarea_compact"></textarea> | 413 | <textarea id="sd_negative_prompt" class="text_pole textarea_compact autoSetHeight"></textarea> |
| 414 | <div id="sd_character_prompt_block"> | 414 | <div id="sd_character_prompt_block"> |
| 415 | <label for="sd_character_prompt" data-i18n="Character-specific prompt prefix">Character-specific prompt prefix</label> | 415 | <label for="sd_character_prompt" data-i18n="Character-specific prompt prefix">Character-specific prompt prefix</label> |
| 416 | <small data-i18n="Won't be used in groups.">Won't be used in groups.</small> | 416 | <small data-i18n="Won't be used in groups.">Won't be used in groups.</small> |
| 417 | <textarea id="sd_character_prompt" class="text_pole textarea_compact" data-i18n="[placeholder]sd_character_prompt_placeholder" placeholder="Any characteristics that describe the currently selected character. Will be added after a common prompt prefix. Example: female, green eyes, brown hair, pink shirt"></textarea> | 417 | <textarea id="sd_character_prompt" class="text_pole textarea_compact autoSetHeight" data-i18n="[placeholder]sd_character_prompt_placeholder" placeholder="Any characteristics that describe the currently selected character. Will be added after a common prompt prefix. Example: female, green eyes, brown hair, pink shirt"></textarea> |
| 418 | <label for="sd_character_negative_prompt" data-i18n="Character-specific negative prompt prefix">Character-specific negative prompt prefix</label> | 418 | <label for="sd_character_negative_prompt" data-i18n="Character-specific negative prompt prefix">Character-specific negative prompt prefix</label> |
| 419 | <small data-i18n="Won't be used in groups.">Won't be used in groups.</small> | 419 | <small data-i18n="Won't be used in groups.">Won't be used in groups.</small> |
| 420 | <textarea id="sd_character_negative_prompt" class="text_pole textarea_compact" data-i18n="[placeholder]sd_character_negative_prompt_placeholder" placeholder="Any characteristics that should not appear for the selected character. Will be added after a negative common prompt prefix. Example: jewellery, shoes, glasses"></textarea> | 420 | <textarea id="sd_character_negative_prompt" class="text_pole textarea_compact autoSetHeight" data-i18n="[placeholder]sd_character_negative_prompt_placeholder" placeholder="Any characteristics that should not appear for the selected character. Will be added after a negative common prompt prefix. Example: jewellery, shoes, glasses"></textarea> |
| 421 | <label for="sd_character_prompt_share" class="checkbox_label flexWrap marginTop5"> | 421 | <label for="sd_character_prompt_share" class="checkbox_label flexWrap marginTop5"> |
| 422 | <input id="sd_character_prompt_share" type="checkbox" /> | 422 | <input id="sd_character_prompt_share" type="checkbox" /> |
| 423 | <span data-i18n="Shareable"> | 423 | <span data-i18n="Shareable"> |
| @@ -59,8 +59,10 @@ async function doTokenCounter() { | |||
| 59 | $('#tokenized_chunks_display').text('—'); | 59 | $('#tokenized_chunks_display').text('—'); |
| 60 | } | 60 | } |
| 61 | 61 | ||
| 62 | await resetScrollHeight($('#token_counter_textarea')); | 62 | if (!CSS.supports('field-sizing', 'content')) { |
| 63 | await resetScrollHeight($('#token_counter_ids')); | 63 | await resetScrollHeight($('#token_counter_textarea')); |
| 64 | await resetScrollHeight($('#token_counter_ids')); | ||
| 65 | } | ||
| 64 | }, debounce_timeout.relaxed); | 66 | }, debounce_timeout.relaxed); |
| 65 | dialog.find('#token_counter_textarea').on('input', () => countDebounced()); | 67 | dialog.find('#token_counter_textarea').on('input', () => countDebounced()); |
| 66 | 68 | ||
| @@ -4,3 +4,8 @@ | |||
| 4 | padding: 2px; | 4 | padding: 2px; |
| 5 | display: inline-block; | 5 | display: inline-block; |
| 6 | } | 6 | } |
| 7 | |||
| 8 | #token_counter_textarea, | ||
| 9 | #token_counter_ids { | ||
| 10 | field-sizing: content; | ||
| 11 | } | ||
| @@ -1414,8 +1414,11 @@ function toggleHiddenControls(group, generationMode = null) { | |||
| 1414 | const isJoin = [group_generation_mode.APPEND, group_generation_mode.APPEND_DISABLED].includes(generationMode ?? group?.generation_mode); | 1414 | const isJoin = [group_generation_mode.APPEND, group_generation_mode.APPEND_DISABLED].includes(generationMode ?? group?.generation_mode); |
| 1415 | $('#rm_group_generation_mode_join_prefix').parent().toggle(isJoin); | 1415 | $('#rm_group_generation_mode_join_prefix').parent().toggle(isJoin); |
| 1416 | $('#rm_group_generation_mode_join_suffix').parent().toggle(isJoin); | 1416 | $('#rm_group_generation_mode_join_suffix').parent().toggle(isJoin); |
| 1417 | initScrollHeight($('#rm_group_generation_mode_join_prefix')); | 1417 | |
| 1418 | initScrollHeight($('#rm_group_generation_mode_join_suffix')); | 1418 | if (!CSS.supports('field-sizing', 'content')) { |
| 1419 | initScrollHeight($('#rm_group_generation_mode_join_prefix')); | ||
| 1420 | initScrollHeight($('#rm_group_generation_mode_join_suffix')); | ||
| 1421 | } | ||
| 1419 | } | 1422 | } |
| 1420 | 1423 | ||
| 1421 | function select_group_chats(groupId, skipAnimation) { | 1424 | function select_group_chats(groupId, skipAnimation) { |
| @@ -130,19 +130,20 @@ function highlightDefaultPreset() { | |||
| 130 | /** | 130 | /** |
| 131 | * Select context template if not already selected. | 131 | * Select context template if not already selected. |
| 132 | * @param {string} preset Preset name. | 132 | * @param {string} preset Preset name. |
| 133 | * @param {boolean} quiet Suppress info message. | ||
| 133 | */ | 134 | */ |
| 134 | export function selectContextPreset(preset) { | 135 | export function selectContextPreset(preset, quiet) { |
| 135 | // If context template is not already selected, select it | 136 | // If context template is not already selected, select it |
| 136 | if (preset !== power_user.context.preset) { | 137 | if (preset !== power_user.context.preset) { |
| 137 | $('#context_presets').val(preset).trigger('change'); | 138 | $('#context_presets').val(preset).trigger('change'); |
| 138 | toastr.info(`Context Template: preset "${preset}" auto-selected`); | 139 | !quiet && toastr.info(`Context Template: preset "${preset}" auto-selected`); |
| 139 | } | 140 | } |
| 140 | 141 | ||
| 141 | // If instruct mode is disabled, enable it, except for default context template | 142 | // If instruct mode is disabled, enable it, except for default context template |
| 142 | if (!power_user.instruct.enabled && preset !== power_user.default_context) { | 143 | if (!power_user.instruct.enabled && preset !== power_user.default_context) { |
| 143 | power_user.instruct.enabled = true; | 144 | power_user.instruct.enabled = true; |
| 144 | $('#instruct_enabled').prop('checked', true).trigger('change'); | 145 | $('#instruct_enabled').prop('checked', true).trigger('change'); |
| 145 | toastr.info('Instruct Mode enabled'); | 146 | !quiet && toastr.info('Instruct Mode enabled'); |
| 146 | } | 147 | } |
| 147 | 148 | ||
| 148 | saveSettingsDebounced(); | 149 | saveSettingsDebounced(); |
| @@ -590,6 +590,9 @@ function calculateOpenRouterCost() { | |||
| 590 | export function getCurrentOpenRouterModelTokenizer() { | 590 | export function getCurrentOpenRouterModelTokenizer() { |
| 591 | const modelId = textgen_settings.openrouter_model; | 591 | const modelId = textgen_settings.openrouter_model; |
| 592 | const model = openRouterModels.find(x => x.id === modelId); | 592 | const model = openRouterModels.find(x => x.id === modelId); |
| 593 | if (modelId?.includes('jamba')) { | ||
| 594 | return tokenizers.JAMBA; | ||
| 595 | } | ||
| 593 | switch (model?.architecture?.tokenizer) { | 596 | switch (model?.architecture?.tokenizer) { |
| 594 | case 'Llama2': | 597 | case 'Llama2': |
| 595 | return tokenizers.LLAMA; | 598 | return tokenizers.LLAMA; |
| @@ -603,6 +606,10 @@ export function getCurrentOpenRouterModelTokenizer() { | |||
| 603 | return tokenizers.GEMMA; | 606 | return tokenizers.GEMMA; |
| 604 | case 'Claude': | 607 | case 'Claude': |
| 605 | return tokenizers.CLAUDE; | 608 | return tokenizers.CLAUDE; |
| 609 | case 'Cohere': | ||
| 610 | return tokenizers.COMMAND_R; | ||
| 611 | case 'Qwen': | ||
| 612 | return tokenizers.QWEN2; | ||
| 606 | default: | 613 | default: |
| 607 | return tokenizers.OPENAI; | 614 | return tokenizers.OPENAI; |
| 608 | } | 615 | } |
| @@ -28,6 +28,8 @@ export const tokenizers = { | |||
| 28 | LLAMA3: 12, | 28 | LLAMA3: 12, |
| 29 | GEMMA: 13, | 29 | GEMMA: 13, |
| 30 | JAMBA: 14, | 30 | JAMBA: 14, |
| 31 | QWEN2: 15, | ||
| 32 | COMMAND_R: 16, | ||
| 31 | BEST_MATCH: 99, | 33 | BEST_MATCH: 99, |
| 32 | }; | 34 | }; |
| 33 | 35 | ||
| @@ -105,6 +107,16 @@ const TOKENIZER_URLS = { | |||
| 105 | decode: '/api/tokenizers/jamba/decode', | 107 | decode: '/api/tokenizers/jamba/decode', |
| 106 | count: '/api/tokenizers/jamba/encode', | 108 | count: '/api/tokenizers/jamba/encode', |
| 107 | }, | 109 | }, |
| 110 | [tokenizers.QWEN2]: { | ||
| 111 | encode: '/api/tokenizers/qwen2/encode', | ||
| 112 | decode: '/api/tokenizers/qwen2/decode', | ||
| 113 | count: '/api/tokenizers/qwen2/encode', | ||
| 114 | }, | ||
| 115 | [tokenizers.COMMAND_R]: { | ||
| 116 | encode: '/api/tokenizers/command-r/encode', | ||
| 117 | decode: '/api/tokenizers/command-r/decode', | ||
| 118 | count: '/api/tokenizers/command-r/encode', | ||
| 119 | }, | ||
| 108 | [tokenizers.API_TEXTGENERATIONWEBUI]: { | 120 | [tokenizers.API_TEXTGENERATIONWEBUI]: { |
| 109 | encode: '/api/tokenizers/remote/textgenerationwebui/encode', | 121 | encode: '/api/tokenizers/remote/textgenerationwebui/encode', |
| 110 | count: '/api/tokenizers/remote/textgenerationwebui/encode', | 122 | count: '/api/tokenizers/remote/textgenerationwebui/encode', |
| @@ -293,6 +305,12 @@ export function getTokenizerBestMatch(forApi) { | |||
| 293 | if (model.includes('jamba')) { | 305 | if (model.includes('jamba')) { |
| 294 | return tokenizers.JAMBA; | 306 | return tokenizers.JAMBA; |
| 295 | } | 307 | } |
| 308 | if (model.includes('command-r')) { | ||
| 309 | return tokenizers.COMMAND_R; | ||
| 310 | } | ||
| 311 | if (model.includes('qwen2')) { | ||
| 312 | return tokenizers.QWEN2; | ||
| 313 | } | ||
| 296 | } | 314 | } |
| 297 | 315 | ||
| 298 | return tokenizers.LLAMA; | 316 | return tokenizers.LLAMA; |
| @@ -511,6 +529,8 @@ export function getTokenizerModel() { | |||
| 511 | const yiTokenizer = 'yi'; | 529 | const yiTokenizer = 'yi'; |
| 512 | const gemmaTokenizer = 'gemma'; | 530 | const gemmaTokenizer = 'gemma'; |
| 513 | const jambaTokenizer = 'jamba'; | 531 | const jambaTokenizer = 'jamba'; |
| 532 | const qwen2Tokenizer = 'qwen2'; | ||
| 533 | const commandRTokenizer = 'command-r'; | ||
| 514 | 534 | ||
| 515 | // Assuming no one would use it for different models.. right? | 535 | // Assuming no one would use it for different models.. right? |
| 516 | if (oai_settings.chat_completion_source == chat_completion_sources.SCALE) { | 536 | if (oai_settings.chat_completion_source == chat_completion_sources.SCALE) { |
| @@ -558,6 +578,12 @@ export function getTokenizerModel() { | |||
| 558 | else if (model?.architecture?.tokenizer === 'Gemini') { | 578 | else if (model?.architecture?.tokenizer === 'Gemini') { |
| 559 | return gemmaTokenizer; | 579 | return gemmaTokenizer; |
| 560 | } | 580 | } |
| 581 | else if (model?.architecture?.tokenizer === 'Qwen') { | ||
| 582 | return qwen2Tokenizer; | ||
| 583 | } | ||
| 584 | else if (model?.architecture?.tokenizer === 'Cohere') { | ||
| 585 | return commandRTokenizer; | ||
| 586 | } | ||
| 561 | else if (oai_settings.openrouter_model.includes('gpt-4o')) { | 587 | else if (oai_settings.openrouter_model.includes('gpt-4o')) { |
| 562 | return gpt4oTokenizer; | 588 | return gpt4oTokenizer; |
| 563 | } | 589 | } |
| @@ -581,6 +607,10 @@ export function getTokenizerModel() { | |||
| 581 | } | 607 | } |
| 582 | } | 608 | } |
| 583 | 609 | ||
| 610 | if (oai_settings.chat_completion_source == chat_completion_sources.COHERE) { | ||
| 611 | return commandRTokenizer; | ||
| 612 | } | ||
| 613 | |||
| 584 | if (oai_settings.chat_completion_source == chat_completion_sources.MAKERSUITE) { | 614 | if (oai_settings.chat_completion_source == chat_completion_sources.MAKERSUITE) { |
| 585 | return gemmaTokenizer; | 615 | return gemmaTokenizer; |
| 586 | } | 616 | } |
| @@ -182,6 +182,10 @@ body.movingUI ::-webkit-scrollbar-thumb:vertical { | |||
| 182 | min-width: 40px; | 182 | min-width: 40px; |
| 183 | } | 183 | } |
| 184 | 184 | ||
| 185 | ::-webkit-scrollbar-corner { | ||
| 186 | background-color: transparent; | ||
| 187 | } | ||
| 188 | |||
| 185 | table.responsiveTable { | 189 | table.responsiveTable { |
| 186 | width: 100%; | 190 | width: 100%; |
| 187 | margin: 10px 0; | 191 | margin: 10px 0; |
| @@ -4675,10 +4679,6 @@ body:not(.sd) .mes_img_swipes { | |||
| 4675 | width: 100%; | 4679 | width: 100%; |
| 4676 | } | 4680 | } |
| 4677 | 4681 | ||
| 4678 | .img_enlarged_holder::-webkit-scrollbar-corner { | ||
| 4679 | background-color: transparent; | ||
| 4680 | } | ||
| 4681 | |||
| 4682 | .img_enlarged_container pre code { | 4682 | .img_enlarged_container pre code { |
| 4683 | position: relative; | 4683 | position: relative; |
| 4684 | display: block; | 4684 | display: block; |
| @@ -4,13 +4,12 @@ const express = require('express'); | |||
| 4 | const { SentencePieceProcessor } = require('@agnai/sentencepiece-js'); | 4 | const { SentencePieceProcessor } = require('@agnai/sentencepiece-js'); |
| 5 | const tiktoken = require('tiktoken'); | 5 | const tiktoken = require('tiktoken'); |
| 6 | const { Tokenizer } = require('@agnai/web-tokenizers'); | 6 | const { Tokenizer } = require('@agnai/web-tokenizers'); |
| 7 | const { convertClaudePrompt, convertGooglePrompt } = require('../prompt-converters'); | 7 | const { convertClaudePrompt } = require('../prompt-converters'); |
| 8 | const { readSecret, SECRET_KEYS } = require('./secrets'); | ||
| 9 | const { TEXTGEN_TYPES } = require('../constants'); | 8 | const { TEXTGEN_TYPES } = require('../constants'); |
| 10 | const { jsonParser } = require('../express-common'); | 9 | const { jsonParser } = require('../express-common'); |
| 11 | const { setAdditionalHeaders } = require('../additional-headers'); | 10 | const { setAdditionalHeaders } = require('../additional-headers'); |
| 12 | 11 | const { getConfigValue, isValidUrl } = require('../util'); | |
| 13 | const API_MAKERSUITE = 'https://generativelanguage.googleapis.com'; | 12 | const writeFileAtomicSync = require('write-file-atomic').sync; |
| 14 | 13 | ||
| 15 | /** | 14 | /** |
| 16 | * @typedef { (req: import('express').Request, res: import('express').Response) => Promise<any> } TokenizationHandler | 15 | * @typedef { (req: import('express').Request, res: import('express').Response) => Promise<any> } TokenizationHandler |
| @@ -53,6 +52,65 @@ const TEXT_COMPLETION_MODELS = [ | |||
| 53 | ]; | 52 | ]; |
| 54 | 53 | ||
| 55 | const CHARS_PER_TOKEN = 3.35; | 54 | const CHARS_PER_TOKEN = 3.35; |
| 55 | const IS_DOWNLOAD_ALLOWED = getConfigValue('enableDownloadableTokenizers', true); | ||
| 56 | |||
| 57 | /** | ||
| 58 | * Gets a path to the tokenizer model. Downloads the model if it's a URL. | ||
| 59 | * @param {string} model Model URL or path | ||
| 60 | * @param {string|undefined} fallbackModel Fallback model path\ | ||
| 61 | * @returns {Promise<string>} Path to the tokenizer model | ||
| 62 | */ | ||
| 63 | async function getPathToTokenizer(model, fallbackModel) { | ||
| 64 | if (!isValidUrl(model)) { | ||
| 65 | return model; | ||
| 66 | } | ||
| 67 | |||
| 68 | try { | ||
| 69 | const url = new URL(model); | ||
| 70 | |||
| 71 | if (!['https:', 'http:'].includes(url.protocol)) { | ||
| 72 | throw new Error('Invalid URL protocol'); | ||
| 73 | } | ||
| 74 | |||
| 75 | const fileName = url.pathname.split('/').pop(); | ||
| 76 | |||
| 77 | if (!fileName) { | ||
| 78 | throw new Error('Failed to extract the file name from the URL'); | ||
| 79 | } | ||
| 80 | |||
| 81 | const CACHE_PATH = path.join(global.DATA_ROOT, '_cache'); | ||
| 82 | if (!fs.existsSync(CACHE_PATH)) { | ||
| 83 | fs.mkdirSync(CACHE_PATH, { recursive: true }); | ||
| 84 | } | ||
| 85 | |||
| 86 | const cachedFile = path.join(CACHE_PATH, fileName); | ||
| 87 | if (fs.existsSync(cachedFile)) { | ||
| 88 | return cachedFile; | ||
| 89 | } | ||
| 90 | |||
| 91 | if (!IS_DOWNLOAD_ALLOWED) { | ||
| 92 | throw new Error('Downloading tokenizers is disabled, the model is not cached'); | ||
| 93 | } | ||
| 94 | |||
| 95 | console.log('Downloading tokenizer model:', model); | ||
| 96 | const response = await fetch(model); | ||
| 97 | if (!response.ok) { | ||
| 98 | throw new Error(`Failed to fetch the model: ${response.status} ${response.statusText}`); | ||
| 99 | } | ||
| 100 | |||
| 101 | const arrayBuffer = await response.arrayBuffer(); | ||
| 102 | writeFileAtomicSync(cachedFile, Buffer.from(arrayBuffer)); | ||
| 103 | return cachedFile; | ||
| 104 | } catch (error) { | ||
| 105 | const getLastSegment = str => str?.split('/')?.pop() || ''; | ||
| 106 | if (fallbackModel) { | ||
| 107 | console.log(`Could not get a tokenizer from ${getLastSegment(model)}. Reason: ${error.message}. Using a fallback model: ${getLastSegment(fallbackModel)}.`); | ||
| 108 | return fallbackModel; | ||
| 109 | } | ||
| 110 | |||
| 111 | throw new Error(`Failed to instantiate a tokenizer and fallback is not provided. Reason: ${error.message}`); | ||
| 112 | } | ||
| 113 | } | ||
| 56 | 114 | ||
| 57 | /** | 115 | /** |
| 58 | * Sentencepiece tokenizer for tokenizing text. | 116 | * Sentencepiece tokenizer for tokenizing text. |
| @@ -66,13 +124,19 @@ class SentencePieceTokenizer { | |||
| 66 | * @type {string} Path to the tokenizer model | 124 | * @type {string} Path to the tokenizer model |
| 67 | */ | 125 | */ |
| 68 | #model; | 126 | #model; |
| 127 | /** | ||
| 128 | * @type {string|undefined} Path to the fallback model | ||
| 129 | */ | ||
| 130 | #fallbackModel; | ||
| 69 | 131 | ||
| 70 | /** | 132 | /** |
| 71 | * Creates a new Sentencepiece tokenizer. | 133 | * Creates a new Sentencepiece tokenizer. |
| 72 | * @param {string} model Path to the tokenizer model | 134 | * @param {string} model Path to the tokenizer model |
| 135 | * @param {string} [fallbackModel] Path to the fallback model | ||
| 73 | */ | 136 | */ |
| 74 | constructor(model) { | 137 | constructor(model, fallbackModel) { |
| 75 | this.#model = model; | 138 | this.#model = model; |
| 139 | this.#fallbackModel = fallbackModel; | ||
| 76 | } | 140 | } |
| 77 | 141 | ||
| 78 | /** | 142 | /** |
| @@ -85,9 +149,10 @@ class SentencePieceTokenizer { | |||
| 85 | } | 149 | } |
| 86 | 150 | ||
| 87 | try { | 151 | try { |
| 152 | const pathToModel = await getPathToTokenizer(this.#model, this.#fallbackModel); | ||
| 88 | this.#instance = new SentencePieceProcessor(); | 153 | this.#instance = new SentencePieceProcessor(); |
| 89 | await this.#instance.load(this.#model); | 154 | await this.#instance.load(pathToModel); |
| 90 | console.log('Instantiated the tokenizer for', path.parse(this.#model).name); | 155 | console.log('Instantiated the tokenizer for', path.parse(pathToModel).name); |
| 91 | return this.#instance; | 156 | return this.#instance; |
| 92 | } catch (error) { | 157 | } catch (error) { |
| 93 | console.error('Sentencepiece tokenizer failed to load: ' + this.#model, error); | 158 | console.error('Sentencepiece tokenizer failed to load: ' + this.#model, error); |
| @@ -108,13 +173,19 @@ class WebTokenizer { | |||
| 108 | * @type {string} Path to the tokenizer model | 173 | * @type {string} Path to the tokenizer model |
| 109 | */ | 174 | */ |
| 110 | #model; | 175 | #model; |
| 176 | /** | ||
| 177 | * @type {string|undefined} Path to the fallback model | ||
| 178 | */ | ||
| 179 | #fallbackModel; | ||
| 111 | 180 | ||
| 112 | /** | 181 | /** |
| 113 | * Creates a new Web tokenizer. | 182 | * Creates a new Web tokenizer. |
| 114 | * @param {string} model Path to the tokenizer model | 183 | * @param {string} model Path to the tokenizer model |
| 184 | * @param {string} [fallbackModel] Path to the fallback model | ||
| 115 | */ | 185 | */ |
| 116 | constructor(model) { | 186 | constructor(model, fallbackModel) { |
| 117 | this.#model = model; | 187 | this.#model = model; |
| 188 | this.#fallbackModel = fallbackModel; | ||
| 118 | } | 189 | } |
| 119 | 190 | ||
| 120 | /** | 191 | /** |
| @@ -127,9 +198,10 @@ class WebTokenizer { | |||
| 127 | } | 198 | } |
| 128 | 199 | ||
| 129 | try { | 200 | try { |
| 130 | const arrayBuffer = fs.readFileSync(this.#model).buffer; | 201 | const pathToModel = await getPathToTokenizer(this.#model, this.#fallbackModel); |
| 202 | const arrayBuffer = fs.readFileSync(pathToModel).buffer; | ||
| 131 | this.#instance = await Tokenizer.fromJSON(arrayBuffer); | 203 | this.#instance = await Tokenizer.fromJSON(arrayBuffer); |
| 132 | console.log('Instantiated the tokenizer for', path.parse(this.#model).name); | 204 | console.log('Instantiated the tokenizer for', path.parse(pathToModel).name); |
| 133 | return this.#instance; | 205 | return this.#instance; |
| 134 | } catch (error) { | 206 | } catch (error) { |
| 135 | console.error('Web tokenizer failed to load: ' + this.#model, error); | 207 | console.error('Web tokenizer failed to load: ' + this.#model, error); |
| @@ -147,6 +219,8 @@ const spp_gemma = new SentencePieceTokenizer('src/tokenizers/gemma.model'); | |||
| 147 | const spp_jamba = new SentencePieceTokenizer('src/tokenizers/jamba.model'); | 219 | const spp_jamba = new SentencePieceTokenizer('src/tokenizers/jamba.model'); |
| 148 | const claude_tokenizer = new WebTokenizer('src/tokenizers/claude.json'); | 220 | const claude_tokenizer = new WebTokenizer('src/tokenizers/claude.json'); |
| 149 | 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'); | ||
| 223 | const qwen2Tokenizer = new WebTokenizer('https://github.com/SillyTavern/SillyTavern-Tokenizers/raw/main/qwen2.json', 'src/tokenizers/llama3.json'); | ||
| 150 | 224 | ||
| 151 | const sentencepieceTokenizers = [ | 225 | const sentencepieceTokenizers = [ |
| 152 | 'llama', | 226 | 'llama', |
| @@ -332,6 +406,14 @@ function getTokenizerModel(requestModel) { | |||
| 332 | return 'jamba'; | 406 | return 'jamba'; |
| 333 | } | 407 | } |
| 334 | 408 | ||
| 409 | if (requestModel.includes('qwen2')) { | ||
| 410 | return 'qwen2'; | ||
| 411 | } | ||
| 412 | |||
| 413 | if (requestModel.includes('command-r')) { | ||
| 414 | return 'command-r'; | ||
| 415 | } | ||
| 416 | |||
| 335 | // default | 417 | // default |
| 336 | return 'gpt-3.5-turbo'; | 418 | return 'gpt-3.5-turbo'; |
| 337 | } | 419 | } |
| @@ -557,6 +639,8 @@ router.post('/jamba/encode', jsonParser, createSentencepieceEncodingHandler(spp_ | |||
| 557 | router.post('/gpt2/encode', jsonParser, createTiktokenEncodingHandler('gpt2')); | 639 | router.post('/gpt2/encode', jsonParser, createTiktokenEncodingHandler('gpt2')); |
| 558 | router.post('/claude/encode', jsonParser, createWebTokenizerEncodingHandler(claude_tokenizer)); | 640 | router.post('/claude/encode', jsonParser, createWebTokenizerEncodingHandler(claude_tokenizer)); |
| 559 | router.post('/llama3/encode', jsonParser, createWebTokenizerEncodingHandler(llama3_tokenizer)); | 641 | router.post('/llama3/encode', jsonParser, createWebTokenizerEncodingHandler(llama3_tokenizer)); |
| 642 | router.post('/qwen2/encode', jsonParser, createWebTokenizerEncodingHandler(qwen2Tokenizer)); | ||
| 643 | router.post('/command-r/encode', jsonParser, createWebTokenizerEncodingHandler(commandTokenizer)); | ||
| 560 | router.post('/llama/decode', jsonParser, createSentencepieceDecodingHandler(spp_llama)); | 644 | router.post('/llama/decode', jsonParser, createSentencepieceDecodingHandler(spp_llama)); |
| 561 | router.post('/nerdstash/decode', jsonParser, createSentencepieceDecodingHandler(spp_nerd)); | 645 | router.post('/nerdstash/decode', jsonParser, createSentencepieceDecodingHandler(spp_nerd)); |
| 562 | router.post('/nerdstash_v2/decode', jsonParser, createSentencepieceDecodingHandler(spp_nerd_v2)); | 646 | router.post('/nerdstash_v2/decode', jsonParser, createSentencepieceDecodingHandler(spp_nerd_v2)); |
| @@ -567,6 +651,8 @@ router.post('/jamba/decode', jsonParser, createSentencepieceDecodingHandler(spp_ | |||
| 567 | router.post('/gpt2/decode', jsonParser, createTiktokenDecodingHandler('gpt2')); | 651 | router.post('/gpt2/decode', jsonParser, createTiktokenDecodingHandler('gpt2')); |
| 568 | router.post('/claude/decode', jsonParser, createWebTokenizerDecodingHandler(claude_tokenizer)); | 652 | router.post('/claude/decode', jsonParser, createWebTokenizerDecodingHandler(claude_tokenizer)); |
| 569 | router.post('/llama3/decode', jsonParser, createWebTokenizerDecodingHandler(llama3_tokenizer)); | 653 | router.post('/llama3/decode', jsonParser, createWebTokenizerDecodingHandler(llama3_tokenizer)); |
| 654 | router.post('/qwen2/decode', jsonParser, createWebTokenizerDecodingHandler(qwen2Tokenizer)); | ||
| 655 | router.post('/command-r/decode', jsonParser, createWebTokenizerDecodingHandler(commandTokenizer)); | ||
| 570 | 656 | ||
| 571 | router.post('/openai/encode', jsonParser, async function (req, res) { | 657 | router.post('/openai/encode', jsonParser, async function (req, res) { |
| 572 | try { | 658 | try { |
| @@ -607,6 +693,16 @@ router.post('/openai/encode', jsonParser, async function (req, res) { | |||
| 607 | return handler(req, res); | 693 | return handler(req, res); |
| 608 | } | 694 | } |
| 609 | 695 | ||
| 696 | if (queryModel.includes('qwen2')) { | ||
| 697 | const handler = createWebTokenizerEncodingHandler(qwen2Tokenizer); | ||
| 698 | return handler(req, res); | ||
| 699 | } | ||
| 700 | |||
| 701 | if (queryModel.includes('command-r')) { | ||
| 702 | const handler = createWebTokenizerEncodingHandler(commandTokenizer); | ||
| 703 | return handler(req, res); | ||
| 704 | } | ||
| 705 | |||
| 610 | const model = getTokenizerModel(queryModel); | 706 | const model = getTokenizerModel(queryModel); |
| 611 | const handler = createTiktokenEncodingHandler(model); | 707 | const handler = createTiktokenEncodingHandler(model); |
| 612 | return handler(req, res); | 708 | return handler(req, res); |
| @@ -655,6 +751,16 @@ router.post('/openai/decode', jsonParser, async function (req, res) { | |||
| 655 | return handler(req, res); | 751 | return handler(req, res); |
| 656 | } | 752 | } |
| 657 | 753 | ||
| 754 | if (queryModel.includes('qwen2')) { | ||
| 755 | const handler = createWebTokenizerDecodingHandler(qwen2Tokenizer); | ||
| 756 | return handler(req, res); | ||
| 757 | } | ||
| 758 | |||
| 759 | if (queryModel.includes('command-r')) { | ||
| 760 | const handler = createWebTokenizerDecodingHandler(commandTokenizer); | ||
| 761 | return handler(req, res); | ||
| 762 | } | ||
| 763 | |||
| 658 | const model = getTokenizerModel(queryModel); | 764 | const model = getTokenizerModel(queryModel); |
| 659 | const handler = createTiktokenDecodingHandler(model); | 765 | const handler = createTiktokenDecodingHandler(model); |
| 660 | return handler(req, res); | 766 | return handler(req, res); |
| @@ -711,6 +817,20 @@ router.post('/openai/count', jsonParser, async function (req, res) { | |||
| 711 | return res.send({ 'token_count': num_tokens }); | 817 | return res.send({ 'token_count': num_tokens }); |
| 712 | } | 818 | } |
| 713 | 819 | ||
| 820 | if (model === 'qwen2') { | ||
| 821 | const instance = await qwen2Tokenizer.get(); | ||
| 822 | if (!instance) throw new Error('Failed to load the Qwen2 tokenizer'); | ||
| 823 | num_tokens = countWebTokenizerTokens(instance, req.body); | ||
| 824 | return res.send({ 'token_count': num_tokens }); | ||
| 825 | } | ||
| 826 | |||
| 827 | if (model === 'command-r') { | ||
| 828 | const instance = await commandTokenizer.get(); | ||
| 829 | if (!instance) throw new Error('Failed to load the Command-R tokenizer'); | ||
| 830 | num_tokens = countWebTokenizerTokens(instance, req.body); | ||
| 831 | return res.send({ 'token_count': num_tokens }); | ||
| 832 | } | ||
| 833 | |||
| 714 | const tokensPerName = queryModel.includes('gpt-3.5-turbo-0301') ? -1 : 1; | 834 | const tokensPerName = queryModel.includes('gpt-3.5-turbo-0301') ? -1 : 1; |
| 715 | const tokensPerMessage = queryModel.includes('gpt-3.5-turbo-0301') ? 4 : 3; | 835 | const tokensPerMessage = queryModel.includes('gpt-3.5-turbo-0301') ? 4 : 3; |
| 716 | const tokensPadding = 3; | 836 | const tokensPadding = 3; |
| @@ -647,6 +647,20 @@ function getSeparator(n) { | |||
| 647 | return '='.repeat(n); | 647 | return '='.repeat(n); |
| 648 | } | 648 | } |
| 649 | 649 | ||
| 650 | /** | ||
| 651 | * Checks if the string is a valid URL. | ||
| 652 | * @param {string} url String to check | ||
| 653 | * @returns {boolean} If the URL is valid | ||
| 654 | */ | ||
| 655 | function isValidUrl(url) { | ||
| 656 | try { | ||
| 657 | new URL(url); | ||
| 658 | return true; | ||
| 659 | } catch (error) { | ||
| 660 | return false; | ||
| 661 | } | ||
| 662 | } | ||
| 663 | |||
| 650 | module.exports = { | 664 | module.exports = { |
| 651 | getConfig, | 665 | getConfig, |
| 652 | getConfigValue, | 666 | getConfigValue, |
| @@ -676,4 +690,5 @@ module.exports = { | |||
| 676 | makeHttp2Request, | 690 | makeHttp2Request, |
| 677 | removeColorFormatting, | 691 | removeColorFormatting, |
| 678 | getSeparator, | 692 | getSeparator, |
| 693 | isValidUrl, | ||
| 679 | }; | 694 | }; |