Merge pull request #3090 from kallewoof/202411-auto-templates Derived templates

ba91845ced2f72193b88e6f60e133a49f86f52b8

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

Signed
7 files changed, +205 -21Showing whitespace changes
public/index.html+23 -21
@@ -91,9 +91,6 @@
91 <div class="margin0 title_restorable standoutHeader">91 <div class="margin0 title_restorable standoutHeader">
92 <strong>92 <strong>
93 <span data-i18n="kobldpresets">Kobold Presets</span>93 <span data-i18n="kobldpresets">Kobold Presets</span>
94 <a href="https://docs.sillytavern.app/usage/api-connections/koboldai/" class="notes-link" target="_blank">
95 <span class="fa-solid fa-circle-question note-link-span"></span>
96 </a>
97 </strong>94 </strong>
9895
99 <div class="flex-container gap3px">96 <div class="flex-container gap3px">
@@ -3274,8 +3271,14 @@
3274 </div>3271 </div>
3275 <div id="AdvancedFormatting" class="drawer-content">3272 <div id="AdvancedFormatting" class="drawer-content">
3276 <div class="flex-container alignItemsBaseline">3273 <div class="flex-container alignItemsBaseline">
3277 <h3 class="margin0 flex1" data-i18n="Advanced Formatting">3274 <h3 class="margin0 flex1 flex-container alignItemsBaseline">
3275 <span data-i18n="Advanced Formatting">
3278 Advanced Formatting3276 Advanced Formatting
3277 </span>
3278
3279 <a href="https://docs.sillytavern.app/usage/prompts/" class="notes-link" target="_blank">
3280 <span class="fa-solid fa-circle-question note-link-span"></span>
3281 </a>
3279 </h3>3282 </h3>
3280 <div class="flex-container">3283 <div class="flex-container">
3281 <input id="af_master_import_file" type="file" hidden accept=".json" class="displayNone">3284 <input id="af_master_import_file" type="file" hidden accept=".json" class="displayNone">
@@ -3295,9 +3298,12 @@
3295 <h4 class="standoutHeader title_restorable">3298 <h4 class="standoutHeader title_restorable">
3296 <div>3299 <div>
3297 <span data-i18n="Context Template">Context Template</span>3300 <span data-i18n="Context Template">Context Template</span>
3298 <a href="https://docs.sillytavern.app/usage/core-concepts/advancedformatting/#context-template" class="notes-link" target="_blank">3301 </div>
3299 <span class="fa-solid fa-circle-question note-link-span"></span>3302 <div class="flex-container">
3300 </a>3303 <label for="context_derived" class="checkbox_label flex1" title="Derive from Model Metadata, if possible." data-i18n="[title]context_derived">
3304 <input id="context_derived" type="checkbox" style="display:none;" />
3305 <small><i class="fa-solid fa-bolt menu_button margin0"></i></small>
3306 </label>
3301 </div>3307 </div>
3302 </h4>3308 </h4>
3303 <div class="flex-container" title="Select your current Context Template" data-i18n="[title]Select your current Context Template">3309 <div class="flex-container" title="Select your current Context Template" data-i18n="[title]Select your current Context Template">
@@ -3394,11 +3400,12 @@
3394 <h4 class="standoutHeader title_restorable justifySpaceBetween">3400 <h4 class="standoutHeader title_restorable justifySpaceBetween">
3395 <div class="flex-container">3401 <div class="flex-container">
3396 <span data-i18n="Instruct Template">Instruct Template</span>3402 <span data-i18n="Instruct Template">Instruct Template</span>
3397 <a href="https://docs.sillytavern.app/usage/core-concepts/instructmode/" class="notes-link" target="_blank">
3398 <span class="fa-solid fa-circle-question note-link-span"></span>
3399 </a>
3400 </div>3403 </div>
3401 <div class="flex-container">3404 <div class="flex-container">
3405 <label for="instruct_derived" class="checkbox_label flex1" title="Derive from Model Metadata, if possible." data-i18n="[title]instruct_derived">
3406 <input id="instruct_derived" type="checkbox" style="display:none;" />
3407 <small><i class="fa-solid fa-bolt menu_button margin0"></i></small>
3408 </label>
3402 <label for="instruct_bind_to_context" class="checkbox_label flex1" title="Bind to Context&#10If enabled, Context templates will be automatically selected based on selected Instruct template name or by preference." data-i18n="[title]instruct_bind_to_context">3409 <label for="instruct_bind_to_context" class="checkbox_label flex1" title="Bind to Context&#10If enabled, Context templates will be automatically selected based on selected Instruct template name or by preference." data-i18n="[title]instruct_bind_to_context">
3403 <input id="instruct_bind_to_context" type="checkbox" style="display:none;" />3410 <input id="instruct_bind_to_context" type="checkbox" style="display:none;" />
3404 <small><i class="fa-solid fa-link menu_button margin0"></i></small>3411 <small><i class="fa-solid fa-link menu_button margin0"></i></small>
@@ -3579,9 +3586,6 @@
3579 <h4 class="standoutHeader title_restorable justifySpaceBetween">3586 <h4 class="standoutHeader title_restorable justifySpaceBetween">
3580 <div class="flex-container">3587 <div class="flex-container">
3581 <span data-i18n="System Prompt">System Prompt</span>3588 <span data-i18n="System Prompt">System Prompt</span>
3582 <a href="https://docs.sillytavern.app/usage/core-concepts/advancedformatting/#system-prompt" class="notes-link" target="_blank">
3583 <span class="fa-solid fa-circle-question note-link-span"></span>
3584 </a>
3585 </div>3589 </div>
3586 <div class="flex-container">3590 <div class="flex-container">
3587 <label id="sysprompt_enabled_label" for="sysprompt_enabled" class="checkbox_label flex1" title="Enable System Prompt" data-i18n="[title]sysprompt_enabled">3591 <label id="sysprompt_enabled_label" for="sysprompt_enabled" class="checkbox_label flex1" title="Enable System Prompt" data-i18n="[title]sysprompt_enabled">
@@ -3649,7 +3653,7 @@
3649 <div name="tokenizerSettingsBlock">3653 <div name="tokenizerSettingsBlock">
3650 <div name="tokenizerSelectorBlock">3654 <div name="tokenizerSelectorBlock">
3651 <h4 class="standoutHeader"><span data-i18n="Tokenizer">Tokenizer</span>3655 <h4 class="standoutHeader"><span data-i18n="Tokenizer">Tokenizer</span>
3652 <a href="https://docs.sillytavern.app/usage/core-concepts/advancedformatting/#tokenizer" class="notes-link" target="_blank">3656 <a href="https://docs.sillytavern.app/usage/prompts/tokenizer/" class="notes-link" target="_blank">
3653 <span class="fa-solid fa-circle-question note-link-span"></span>3657 <span class="fa-solid fa-circle-question note-link-span"></span>
3654 </a>3658 </a>
3655 </h4>3659 </h4>
@@ -3675,10 +3679,8 @@
3675 </div>3679 </div>
3676 <div class="range-block flex-container flexnowrap" name="tokenPaddingBlock">3680 <div class="range-block flex-container flexnowrap" name="tokenPaddingBlock">
3677 <div class="range-block-title justifyLeft">3681 <div class="range-block-title justifyLeft">
3678 <small data-i18n="Token Padding" class="flex-container">Token Padding3682 <small data-i18n="Token Padding">
3679 <a href="https://docs.sillytavern.app/usage/core-concepts/advancedformatting/#token-padding" class="notes-link" target="_blank">3683 Token Padding
3680 <span class="fa-solid fa-circle-question note-link-span"></span>
3681 </a>
3682 </small>3684 </small>
3683 </div>3685 </div>
3684 <input id="token_padding" class="text_pole textarea_compact" type="number" min="-2048" max="2048" />3686 <input id="token_padding" class="text_pole textarea_compact" type="number" min="-2048" max="2048" />
@@ -3736,7 +3738,7 @@
3736 </div>3738 </div>
3737 <h3 class="margin0">3739 <h3 class="margin0">
3738 <span data-i18n="Worlds/Lorebooks">Worlds/Lorebooks</span>3740 <span data-i18n="Worlds/Lorebooks">Worlds/Lorebooks</span>
3739 <a href="https://docs.sillytavern.app/usage/core-concepts/worldinfo/" class="notes-link" target="_blank">3741 <a href="https://docs.sillytavern.app/usage/worldinfo/" class="notes-link" target="_blank">
3740 <span class="fa-solid fa-circle-question note-link-span"></span>3742 <span class="fa-solid fa-circle-question note-link-span"></span>
3741 </a>3743 </a>
3742 </h3>3744 </h3>
@@ -4263,7 +4265,7 @@
4263 <audio id="audio_message_sound" src="sounds/message.mp3" hidden></audio>4265 <audio id="audio_message_sound" src="sounds/message.mp3" hidden></audio>
4264 <span>4266 <span>
4265 <small data-i18n="Message Sound">Message Sound</small>4267 <small data-i18n="Message Sound">Message Sound</small>
4266 <a href="https://docs.sillytavern.app/usage/core-concepts/uicustomization/#message-sound" class="notes-link" target="_blank">4268 <a href="https://docs.sillytavern.app/usage/user_settings/uicustomization/#message-sound" class="notes-link" target="_blank">
4267 <span class="fa-solid fa-circle-question note-link-span"></span>4269 <span class="fa-solid fa-circle-question note-link-span"></span>
4268 </a>4270 </a>
4269 </span>4271 </span>
@@ -5795,7 +5797,7 @@
5795 <div class="flex-container justifySpaceBetween">5797 <div class="flex-container justifySpaceBetween">
5796 <small for="group">5798 <small for="group">
5797 <span data-i18n="Inclusion Group">Inclusion Group</span>5799 <span data-i18n="Inclusion Group">Inclusion Group</span>
5798 <a href="https://docs.sillytavern.app/usage/core-concepts/worldinfo/#inclusion-group" class="notes-link" target="_blank" title="Inclusion Groups ensure only one entry from a group is activated at a time, if multiple are triggered.&#13;Supports multiple comma-separated groups.&#13;&#13;Documentation: World Info - Inclusion Group" data-i18n="[title]Inclusion Groups ensure only one entry from a group is activated at a time, if multiple are triggered.Documentation: World Info - Inclusion Group">5800 <a href="https://docs.sillytavern.app/usage/worldinfo/#inclusion-group" class="notes-link" target="_blank" title="Inclusion Groups ensure only one entry from a group is activated at a time, if multiple are triggered.&#13;Supports multiple comma-separated groups.&#13;&#13;Documentation: World Info - Inclusion Group" data-i18n="[title]Inclusion Groups ensure only one entry from a group is activated at a time, if multiple are triggered.Documentation: World Info - Inclusion Group">
5799 <span class="fa-solid fa-circle-question note-link-span"></span>5801 <span class="fa-solid fa-circle-question note-link-span"></span>
5800 </a>5802 </a>
5801 </small>5803 </small>
public/script.js+33 -0
@@ -267,6 +267,7 @@ import { applyBrowserFixes } from './scripts/browser-fixes.js';
267import { initServerHistory } from './scripts/server-history.js';267import { initServerHistory } from './scripts/server-history.js';
268import { initSettingsSearch } from './scripts/setting-search.js';268import { initSettingsSearch } from './scripts/setting-search.js';
269import { initBulkEdit } from './scripts/bulk-edit.js';269import { initBulkEdit } from './scripts/bulk-edit.js';
270import { deriveTemplatesFromChatTemplate } from './scripts/chat-templates.js';
270271
271//exporting functions and vars for mods272//exporting functions and vars for mods
272export {273export {
@@ -1235,6 +1236,38 @@ async function getStatusTextgen() {
1235 const supportsTokenization = response.headers.get('x-supports-tokenization') === 'true';1236 const supportsTokenization = response.headers.get('x-supports-tokenization') === 'true';
1236 supportsTokenization ? sessionStorage.setItem(TOKENIZER_SUPPORTED_KEY, 'true') : sessionStorage.removeItem(TOKENIZER_SUPPORTED_KEY);1237 supportsTokenization ? sessionStorage.setItem(TOKENIZER_SUPPORTED_KEY, 'true') : sessionStorage.removeItem(TOKENIZER_SUPPORTED_KEY);
12371238
1239 const wantsInstructDerivation = (power_user.instruct.enabled && power_user.instruct.derived);
1240 const wantsContextDerivation = power_user.context_derived;
1241 const supportsChatTemplate = [textgen_types.KOBOLDCPP, textgen_types.LLAMACPP].includes(textgen_settings.type);
1242 if (supportsChatTemplate && (wantsInstructDerivation || wantsContextDerivation)) {
1243 const response = await fetch('/api/backends/text-completions/props', {
1244 method: 'POST',
1245 headers: getRequestHeaders(),
1246 body: JSON.stringify({
1247 api_server: endpoint,
1248 api_type: textgen_settings.type,
1249 }),
1250 });
1251
1252 if (response.ok) {
1253 const data = await response.json();
1254 if (data) {
1255 const { chat_template, chat_template_hash } = data;
1256 console.log(`We have chat template ${chat_template.split('\n')[0]}...`);
1257 const templates = await deriveTemplatesFromChatTemplate(chat_template, chat_template_hash);
1258 if (templates) {
1259 const { context, instruct } = templates;
1260 if (wantsContextDerivation) {
1261 selectContextPreset(context, { isAuto: true });
1262 }
1263 if (wantsInstructDerivation) {
1264 selectInstructPreset(instruct, { isAuto: true });
1265 }
1266 }
1267 }
1268 }
1269 }
1270
1238 // We didn't get a 200 status code, but the endpoint has an explanation. Which means it DID connect, but I digress.1271 // We didn't get a 200 status code, but the endpoint has an explanation. Which means it DID connect, but I digress.
1239 if (online_status === 'no_connection' && data.response) {1272 if (online_status === 'no_connection' && data.response) {
1240 toastr.error(data.response, t`API Error`, { timeOut: 5000, preventDuplicates: true });1273 toastr.error(data.response, t`API Error`, { timeOut: 5000, preventDuplicates: true });
public/scripts/chat-templates.js+81 -0
@@ -0,0 +1,81 @@
1// the hash can be obtained from command line e.g. via: MODEL=path_to_model; python -c "import json, hashlib, sys; print(hashlib.sha256(json.load(open('"$MODEL"/tokenizer_config.json'))['chat_template'].encode()).hexdigest())"
2// note that chat templates must be trimmed to match the llama.cpp metadata value
3const hash_derivations = {
4 // Meta
5 'e10ca381b1ccc5cf9db52e371f3b6651576caee0a630b452e2816b2d404d4b65':
6 // Meta-Llama-3.1-8B-Instruct
7 // Meta-Llama-3.1-70B-Instruct
8 'Llama 3 Instruct'
9 ,
10 '5816fce10444e03c2e9ee1ef8a4a1ea61ae7e69e438613f3b17b69d0426223a4':
11 // Llama-3.2-1B-Instruct
12 // Llama-3.2-3B-Instruct
13 'Llama 3 Instruct'
14 ,
15 '73e87b1667d87ab7d7b579107f01151b29ce7f3ccdd1018fdc397e78be76219d':
16 // Nemotron 70B
17 'Llama 3 Instruct'
18 ,
19
20 // Mistral
21 // Mistral Reference: https://github.com/mistralai/mistral-common
22 'e16746b40344d6c5b5265988e0328a0bf7277be86f1c335156eae07e29c82826':
23 // Mistral-Small-Instruct-2409
24 // Mistral-Large-Instruct-2407
25 'Mistral V2 & V3'
26 ,
27 '3c4ad5fa60dd8c7ccdf82fa4225864c903e107728fcaf859fa6052cb80c92ee9':
28 // Mistral-Large-Instruct-2411
29 'Mistral V7' // https://huggingface.co/mistralai/Mistral-Large-Instruct-2411
30 ,
31 'e4676cb56dffea7782fd3e2b577cfaf1e123537e6ef49b3ec7caa6c095c62272':
32 // Mistral-Nemo-Instruct-2407
33 'Mistral V3-Tekken'
34 ,
35 '26a59556925c987317ce5291811ba3b7f32ec4c647c400c6cc7e3a9993007ba7':
36 // Mistral-7B-Instruct-v0.3
37 'Mistral V2 & V3'
38 ,
39
40 // Gemma
41 'ecd6ae513fe103f0eb62e8ab5bfa8d0fe45c1074fa398b089c93a7e70c15cfd6':
42 // gemma-2-9b-it
43 // gemma-2-27b-it
44 'Gemma 2'
45 ,
46 '87fa45af6cdc3d6a9e4dd34a0a6848eceaa73a35dcfe976bd2946a5822a38bf3':
47 // gemma-2-2b-it
48 'Gemma 2'
49 ,
50
51 // Cohere
52 '3b54f5c219ae1caa5c0bb2cdc7c001863ca6807cf888e4240e8739fa7eb9e02e':
53 // command-r-08-2024
54 'Command R'
55 ,
56};
57
58const substr_derivations = {
59 '<|im_start|>': 'ChatML', // qwen2.5, ...
60};
61
62const parse_derivation = derivation => (typeof derivation === 'string') ? {
63 'context': derivation,
64 'instruct': derivation,
65} : derivation;
66
67export async function deriveTemplatesFromChatTemplate(chat_template, hash) {
68 if (hash in hash_derivations) {
69 return parse_derivation(hash_derivations[hash]);
70 }
71
72 // heuristics
73 for (const [substr, derivation] of Object.entries(substr_derivations) ) {
74 if (chat_template.includes(substr)) {
75 return parse_derivation(derivation);
76 }
77 }
78
79 console.log(`Unknown chat template hash: ${hash} for [${chat_template}]`);
80 return null;
81}
public/scripts/instruct-mode.js+18 -0
@@ -39,6 +39,7 @@ const controls = [
39 { id: 'instruct_first_input_sequence', property: 'first_input_sequence', isCheckbox: false },39 { id: 'instruct_first_input_sequence', property: 'first_input_sequence', isCheckbox: false },
40 { id: 'instruct_last_input_sequence', property: 'last_input_sequence', isCheckbox: false },40 { id: 'instruct_last_input_sequence', property: 'last_input_sequence', isCheckbox: false },
41 { id: 'instruct_activation_regex', property: 'activation_regex', isCheckbox: false },41 { id: 'instruct_activation_regex', property: 'activation_regex', isCheckbox: false },
42 { id: 'instruct_derived', property: 'derived', isCheckbox: true },
42 { id: 'instruct_bind_to_context', property: 'bind_to_context', isCheckbox: true },43 { id: 'instruct_bind_to_context', property: 'bind_to_context', isCheckbox: true },
43 { id: 'instruct_skip_examples', property: 'skip_examples', isCheckbox: true },44 { id: 'instruct_skip_examples', property: 'skip_examples', isCheckbox: true },
44 { id: 'instruct_names_behavior', property: 'names_behavior', isCheckbox: false },45 { id: 'instruct_names_behavior', property: 'names_behavior', isCheckbox: false },
@@ -100,6 +101,7 @@ export async function loadInstructMode(data) {
100101
101 $('#instruct_enabled').parent().find('i').toggleClass('toggleEnabled', !!power_user.instruct.enabled);102 $('#instruct_enabled').parent().find('i').toggleClass('toggleEnabled', !!power_user.instruct.enabled);
102 $('#instructSettingsBlock, #InstructSequencesColumn').toggleClass('disabled', !power_user.instruct.enabled);103 $('#instructSettingsBlock, #InstructSequencesColumn').toggleClass('disabled', !power_user.instruct.enabled);
104 $('#instruct_derived').parent().find('i').toggleClass('toggleEnabled', !!power_user.instruct.derived);
103 $('#instruct_bind_to_context').parent().find('i').toggleClass('toggleEnabled', !!power_user.instruct.bind_to_context);105 $('#instruct_bind_to_context').parent().find('i').toggleClass('toggleEnabled', !!power_user.instruct.bind_to_context);
104106
105 controls.forEach(control => {107 controls.forEach(control => {
@@ -146,6 +148,12 @@ export async function loadInstructMode(data) {
146 * @param {boolean} [options.isAuto=false] Is auto-select.148 * @param {boolean} [options.isAuto=false] Is auto-select.
147 */149 */
148export function selectContextPreset(preset, { quiet = false, isAuto = false } = {}) {150export function selectContextPreset(preset, { quiet = false, isAuto = false } = {}) {
151 const presetExists = context_presets.some(x => x.name === preset);
152 if (!presetExists) {
153 console.warn(`Context template "${preset}" not found`);
154 return;
155 }
156
149 // If context template is not already selected, select it157 // If context template is not already selected, select it
150 if (preset !== power_user.context.preset) {158 if (preset !== power_user.context.preset) {
151 $('#context_presets').val(preset).trigger('change');159 $('#context_presets').val(preset).trigger('change');
@@ -163,6 +171,12 @@ export function selectContextPreset(preset, { quiet = false, isAuto = false } =
163 * @param {boolean} [options.isAuto=false] Is auto-select.171 * @param {boolean} [options.isAuto=false] Is auto-select.
164 */172 */
165export function selectInstructPreset(preset, { quiet = false, isAuto = false } = {}) {173export function selectInstructPreset(preset, { quiet = false, isAuto = false } = {}) {
174 const presetExists = instruct_presets.some(x => x.name === preset);
175 if (!presetExists) {
176 console.warn(`Instruct template "${preset}" not found`);
177 return;
178 }
179
166 // If instruct preset is not already selected, select it180 // If instruct preset is not already selected, select it
167 if (preset !== power_user.instruct.preset) {181 if (preset !== power_user.instruct.preset) {
168 $('#instruct_presets').val(preset).trigger('change');182 $('#instruct_presets').val(preset).trigger('change');
@@ -715,6 +729,10 @@ jQuery(() => {
715 }729 }
716 });730 });
717731
732 $('#instruct_derived').on('change', function () {
733 $('#instruct_derived').parent().find('i').toggleClass('toggleEnabled', !!power_user.instruct.derived);
734 });
735
718 $('#instruct_bind_to_context').on('change', function () {736 $('#instruct_bind_to_context').on('change', function () {
719 $('#instruct_bind_to_context').parent().find('i').toggleClass('toggleEnabled', !!power_user.instruct.bind_to_context);737 $('#instruct_bind_to_context').parent().find('i').toggleClass('toggleEnabled', !!power_user.instruct.bind_to_context);
720 });738 });
public/scripts/power-user.js+14 -0
@@ -226,6 +226,7 @@ let power_user = {
226 macro: true,226 macro: true,
227 names_behavior: names_behavior_types.FORCE,227 names_behavior: names_behavior_types.FORCE,
228 activation_regex: '',228 activation_regex: '',
229 derived: false,
229 bind_to_context: false,230 bind_to_context: false,
230 user_alignment_message: '',231 user_alignment_message: '',
231 system_same_as_user: false,232 system_same_as_user: false,
@@ -243,6 +244,8 @@ let power_user = {
243 names_as_stop_strings: true,244 names_as_stop_strings: true,
244 },245 },
245246
247 context_derived: false,
248
246 sysprompt: {249 sysprompt: {
247 enabled: true,250 enabled: true,
248 name: 'Neutral - Chat',251 name: 'Neutral - Chat',
@@ -1472,6 +1475,7 @@ async function loadPowerUserSettings(settings, data) {
1472 $('#encode_tags').prop('checked', power_user.encode_tags);1475 $('#encode_tags').prop('checked', power_user.encode_tags);
1473 $('#example_messages_behavior').val(getExampleMessagesBehavior());1476 $('#example_messages_behavior').val(getExampleMessagesBehavior());
1474 $(`#example_messages_behavior option[value="${getExampleMessagesBehavior()}"]`).prop('selected', true);1477 $(`#example_messages_behavior option[value="${getExampleMessagesBehavior()}"]`).prop('selected', true);
1478 $('#context_derived').parent().find('i').toggleClass('toggleEnabled', !!power_user.context_derived);
14751479
1476 $('#console_log_prompts').prop('checked', power_user.console_log_prompts);1480 $('#console_log_prompts').prop('checked', power_user.console_log_prompts);
1477 $('#request_token_probabilities').prop('checked', power_user.request_token_probabilities);1481 $('#request_token_probabilities').prop('checked', power_user.request_token_probabilities);
@@ -3057,6 +3061,16 @@ $(document).ready(() => {
3057 saveSettingsDebounced();3061 saveSettingsDebounced();
3058 });3062 });
30593063
3064 $('#context_derived').on('input', function () {
3065 const value = !!$(this).prop('checked');
3066 power_user.context_derived = value;
3067 saveSettingsDebounced();
3068 });
3069
3070 $('#context_derived').on('change', function () {
3071 $('#context_derived').parent().find('i').toggleClass('toggleEnabled', !!power_user.context_derived);
3072 });
3073
3060 $('#always-force-name2-checkbox').change(function () {3074 $('#always-force-name2-checkbox').change(function () {
3061 power_user.always_force_name2 = !!$(this).prop('checked');3075 power_user.always_force_name2 = !!$(this).prop('checked');
3062 saveSettingsDebounced();3076 saveSettingsDebounced();
public/scripts/preset-manager.js+1 -0
@@ -584,6 +584,7 @@ class PresetManager {
584 'openrouter_providers',584 'openrouter_providers',
585 'openrouter_allow_fallbacks',585 'openrouter_allow_fallbacks',
586 'tabby_model',586 'tabby_model',
587 'derived',
587 ];588 ];
588 const settings = Object.assign({}, getSettingsByApiId(this.apiId));589 const settings = Object.assign({}, getSettingsByApiId(this.apiId));
589590
src/endpoints/backends/text-completions.js+35 -0
@@ -16,6 +16,7 @@ import {
16} from '../../constants.js';16} from '../../constants.js';
17import { forwardFetchResponse, trimV1, getConfigValue } from '../../util.js';17import { forwardFetchResponse, trimV1, getConfigValue } from '../../util.js';
18import { setAdditionalHeaders } from '../../additional-headers.js';18import { setAdditionalHeaders } from '../../additional-headers.js';
19import { createHash } from 'node:crypto';
1920
20export const router = express.Router();21export const router = express.Router();
2122
@@ -227,6 +228,40 @@ router.post('/status', jsonParser, async function (request, response) {
227 }228 }
228});229});
229230
231router.post('/props', jsonParser, async function (request, response) {
232 if (!request.body.api_server) return response.sendStatus(400);
233
234 try {
235 const baseUrl = trimV1(request.body.api_server);
236 const args = {
237 headers: {},
238 };
239
240 setAdditionalHeaders(request, args, baseUrl);
241
242 const apiType = request.body.api_type;
243 const propsUrl = baseUrl + '/props';
244 const propsReply = await fetch(propsUrl, args);
245
246 if (!propsReply.ok) {
247 return response.status(400);
248 }
249
250 /** @type {any} */
251 const props = await propsReply.json();
252 // TEMPORARY: llama.cpp's /props endpoint has a bug which replaces the last newline with a \0
253 if (apiType === TEXTGEN_TYPES.LLAMACPP && props['chat_template'].endsWith('\u0000')) {
254 props['chat_template'] = props['chat_template'].slice(0, -1) + '\n';
255 }
256 props['chat_template_hash'] = createHash('sha256').update(props['chat_template']).digest('hex');
257 console.log(`Model properties: ${JSON.stringify(props)}`);
258 return response.send(props);
259 } catch (error) {
260 console.error(error);
261 return response.status(500);
262 }
263});
264
230router.post('/generate', jsonParser, async function (request, response) {265router.post('/generate', jsonParser, async function (request, response) {
231 if (!request.body) return response.sendStatus(400);266 if (!request.body) return response.sendStatus(400);
232267