Merge branch 'staging' into staging-3071-v2

842b59605bd5f3fd5bb2f5bf54102171e1b5b151

Eradev <admin@eradev.com>

37 files changed, +749 -323Showing whitespace changes
public/css/toggle-dependent.css+5 -0
@@ -472,6 +472,11 @@ label[for="trim_spaces"]:has(input:checked) i.warning {
472 display: none;472 display: none;
473}473}
474474
475label[for="trim_spaces"]:not(:has(input:checked)) small {
476 color: var(--warning);
477 opacity: 1;
478}
479
475#claude_function_prefill_warning {480#claude_function_prefill_warning {
476 display: none;481 display: none;
477 color: red;482 color: red;
public/index.html+30 -47
@@ -1587,6 +1587,10 @@
1587 <input type="checkbox" id="skip_special_tokens_textgenerationwebui" />1587 <input type="checkbox" id="skip_special_tokens_textgenerationwebui" />
1588 <small data-i18n="Skip Special Tokens">Skip Special Tokens</small>1588 <small data-i18n="Skip Special Tokens">Skip Special Tokens</small>
1589 </label>1589 </label>
1590 <label data-tg-type="openrouter" class="checkbox_label flexGrow flexShrink" for="include_reasoning_textgenerationwebui">
1591 <input type="checkbox" id="include_reasoning_textgenerationwebui" />
1592 <small data-i18n="Request Model Reasoning">Request Model Reasoning</small>
1593 </label>
1590 <label data-tg-type="ooba, aphrodite, tabby" class="checkbox_label flexGrow flexShrink" for="temperature_last_textgenerationwebui">1594 <label data-tg-type="ooba, aphrodite, tabby" class="checkbox_label flexGrow flexShrink" for="temperature_last_textgenerationwebui">
1591 <input type="checkbox" id="temperature_last_textgenerationwebui" />1595 <input type="checkbox" id="temperature_last_textgenerationwebui" />
1592 <label>1596 <label>
@@ -1981,13 +1985,13 @@
1981 <label for="openai_show_thoughts" class="checkbox_label widthFreeExpand">1985 <label for="openai_show_thoughts" class="checkbox_label widthFreeExpand">
1982 <input id="openai_show_thoughts" type="checkbox" />1986 <input id="openai_show_thoughts" type="checkbox" />
1983 <span>1987 <span>
1984 <span data-i18n="Show model reasoning">Show model reasoning</span>1988 <span data-i18n="Request model reasoning">Request model reasoning</span>
1985 <i class="opacity50p fa-solid fa-circle-info" title="Gemini 2.0 Thinking / DeepSeek Reasoner"></i>1989 <i class="opacity50p fa-solid fa-circle-info" title="Gemini 2.0 Thinking / DeepSeek Reasoner"></i>
1986 </span>1990 </span>
1987 </label>1991 </label>
1988 <div class="toggle-description justifyLeft marginBot5">1992 <div class="toggle-description justifyLeft marginBot5">
1989 <span data-i18n="Display the model's internal thoughts in the response.">1993 <span data-i18n="Allows the model to return its thinking process.">
1990 Display the model's internal thoughts in the response.1994 Allows the model to return its thinking process.
1991 </span>1995 </span>
1992 </div>1996 </div>
1993 </div>1997 </div>
@@ -3151,34 +3155,22 @@
3151 </div>3155 </div>
3152 <h4 data-i18n="Groq Model">Groq Model</h4>3156 <h4 data-i18n="Groq Model">Groq Model</h4>
3153 <select id="model_groq_select">3157 <select id="model_groq_select">
3154 <optgroup label="Llama 3.3">3158 <optgroup label="Production Models">
3159 <option value="gemma2-9b-it">gemma2-9b-it</option>
3155 <option value="llama-3.3-70b-versatile">llama-3.3-70b-versatile</option>3160 <option value="llama-3.3-70b-versatile">llama-3.3-70b-versatile</option>
3161 <option value="llama-3.1-8b-instant">llama-3.1-8b-instant</option>
3162 <option value="llama3-70b-8192">llama3-70b-8192</option>
3163 <option value="llama3-8b-8192">llama3-8b-8192</option>
3164 <option value="mixtral-8x7b-32768">mixtral-8x7b-32768</option>
3156 </optgroup>3165 </optgroup>
3157 <optgroup label="Llama 3.2">3166 <optgroup label="Preview Models">
3167 <option value="deepseek-r1-distill-llama-70b">deepseek-r1-distill-llama-70b</option>
3168 <option value="llama-3.3-70b-specdec">llama-3.3-70b-specdec</option>
3158 <option value="llama-3.2-1b-preview">llama-3.2-1b-preview</option>3169 <option value="llama-3.2-1b-preview">llama-3.2-1b-preview</option>
3159 <option value="llama-3.2-3b-preview">llama-3.2-3b-preview</option>3170 <option value="llama-3.2-3b-preview">llama-3.2-3b-preview</option>
3160 <option value="llama-3.2-11b-vision-preview">llama-3.2-11b-vision-preview</option>3171 <option value="llama-3.2-11b-vision-preview">llama-3.2-11b-vision-preview</option>
3161 <option value="llama-3.2-90b-vision-preview">llama-3.2-90b-vision-preview</option>3172 <option value="llama-3.2-90b-vision-preview">llama-3.2-90b-vision-preview</option>
3162 </optgroup>3173 </optgroup>
3163 <optgroup label="Llama 3.1">
3164 <option value="llama-3.1-8b-instant">llama-3.1-8b-instant</option>
3165 <option value="llama-3.1-70b-versatile">llama-3.1-70b-versatile</option>
3166 <option value="llama-3.1-405b-reasoning">llama-3.1-405b-reasoning</option>
3167 </optgroup>
3168 <optgroup label="Llama 3">
3169 <option value="llama3-groq-8b-8192-tool-use-preview">llama3-groq-8b-8192-tool-use-preview</option>
3170 <option value="llama3-groq-70b-8192-tool-use-preview">llama3-groq-70b-8192-tool-use-preview</option>
3171 <option value="llama3-8b-8192">llama3-8b-8192</option>
3172 <option value="llama3-70b-8192">llama3-70b-8192</option>
3173 </optgroup>
3174 <optgroup label="Gemma">
3175 <option value="gemma-7b-it">gemma-7b-it</option>
3176 <option value="gemma2-9b-it">gemma2-9b-it</option>
3177 </optgroup>
3178 <optgroup label="Other">
3179 <option value="mixtral-8x7b-32768">mixtral-8x7b-32768</option>
3180 <option value="llava-v1.5-7b-4096-preview">llava-v1.5-7b-4096-preview</option>
3181 </optgroup>
3182 </select>3174 </select>
3183 </div>3175 </div>
3184 <div id="nanogpt_form" data-source="nanogpt">3176 <div id="nanogpt_form" data-source="nanogpt">
@@ -3227,32 +3219,19 @@
3227 <h4 data-i18n="Perplexity Model">Perplexity Model</h4>3219 <h4 data-i18n="Perplexity Model">Perplexity Model</h4>
3228 <select id="model_perplexity_select">3220 <select id="model_perplexity_select">
3229 <optgroup label="Perplexity Sonar Models">3221 <optgroup label="Perplexity Sonar Models">
3222 <option value="sonar">sonar</option>
3223 <option value="sonar-pro">sonar-pro</option>
3224 <option value="sonar-reasoning">sonar-reasoning</option>
3225 </optgroup>
3226 <optgroup label="Deprecated Models">
3227 <!-- These are scheduled for deprecation after 2/22/2025 -->
3230 <option value="llama-3.1-sonar-small-128k-online">llama-3.1-sonar-small-128k-online</option>3228 <option value="llama-3.1-sonar-small-128k-online">llama-3.1-sonar-small-128k-online</option>
3231 <option value="llama-3.1-sonar-large-128k-online">llama-3.1-sonar-large-128k-online</option>3229 <option value="llama-3.1-sonar-large-128k-online">llama-3.1-sonar-large-128k-online</option>
3232 <option value="llama-3.1-sonar-huge-128k-online">llama-3.1-sonar-huge-128k-online</option>3230 <option value="llama-3.1-sonar-huge-128k-online">llama-3.1-sonar-huge-128k-online</option>
3233 </optgroup>3231 <!-- These are not listed on the site anymore -->
3234 <optgroup label="Perplexity Chat Models">
3235 <option value="llama-3.1-sonar-small-128k-chat">llama-3.1-sonar-small-128k-chat</option>3232 <option value="llama-3.1-sonar-small-128k-chat">llama-3.1-sonar-small-128k-chat</option>
3236 <option value="llama-3.1-sonar-large-128k-chat">llama-3.1-sonar-large-128k-chat</option>3233 <option value="llama-3.1-sonar-large-128k-chat">llama-3.1-sonar-large-128k-chat</option>
3237 </optgroup>3234 </optgroup>
3238 <optgroup label="Open-Source Models">
3239 <option value="llama-3.1-8b-instruct">llama-3.1-8b-instruct</option>
3240 <option value="llama-3.1-70b-instruct">llama-3.1-70b-instruct</option>
3241 </optgroup>
3242 <optgroup label="Deprecated Models">
3243 <option value="llama-3-sonar-small-32k-chat">llama-3-sonar-small-32k-chat</option>
3244 <option value="llama-3-sonar-small-32k-online">llama-3-sonar-small-32k-online</option>
3245 <option value="llama-3-sonar-large-32k-chat">llama-3-sonar-large-32k-chat</option>
3246 <option value="llama-3-sonar-large-32k-online">llama-3-sonar-large-32k-online</option>
3247 <option value="sonar-small-chat">sonar-small-chat</option>
3248 <option value="sonar-small-online">sonar-small-online</option>
3249 <option value="sonar-medium-chat">sonar-medium-chat</option>
3250 <option value="sonar-medium-online">sonar-medium-online</option>
3251 <option value="llama-3-8b-instruct">llama-3-8b-instruct</option>
3252 <option value="llama-3-70b-instruct">llama-3-70b-instruct</option>
3253 <option value="mistral-7b-instruct">mistral-7b-instruct (v0.2)</option>
3254 <option value="mixtral-8x7b-instruct">mixtral-8x7b-instruct</option>
3255 </optgroup>
3256 </select>3235 </select>
3257 </div>3236 </div>
3258 <form id="cohere_form" data-source="cohere" action="javascript:void(null);" method="post" enctype="multipart/form-data">3237 <form id="cohere_form" data-source="cohere" action="javascript:void(null);" method="post" enctype="multipart/form-data">
@@ -3804,6 +3783,12 @@
3804 <span data-i18n="Reasoning">Reasoning</span>3783 <span data-i18n="Reasoning">Reasoning</span>
3805 </h4>3784 </h4>
3806 <div>3785 <div>
3786 <label class="checkbox_label" for="reasoning_auto_parse" title="Automatically parse reasoning blocks from main content between the reasoning prefix/suffix. Both fields must be defined and non-empty." data-i18n="[title]reasoning_auto_parse">
3787 <input id="reasoning_auto_parse" type="checkbox" />
3788 <small data-i18n="Auto-Parse Reasoning">
3789 Auto-Parse Reasoning
3790 </small>
3791 </label>
3807 <label class="checkbox_label" for="reasoning_add_to_prompts" title="Add existing reasoning blocks to prompts. To add a new reasoning block, use the message edit menu." data-i18n="[title]reasoning_add_to_prompts">3792 <label class="checkbox_label" for="reasoning_add_to_prompts" title="Add existing reasoning blocks to prompts. To add a new reasoning block, use the message edit menu." data-i18n="[title]reasoning_add_to_prompts">
3808 <input id="reasoning_add_to_prompts" type="checkbox" />3793 <input id="reasoning_add_to_prompts" type="checkbox" />
3809 <small data-i18n="Add Reasoning to Prompts">3794 <small data-i18n="Add Reasoning to Prompts">
@@ -4845,6 +4830,7 @@
4845 </div>4830 </div>
4846 <div id="extensions_settings" class="flex1 wide50p">4831 <div id="extensions_settings" class="flex1 wide50p">
4847 <div id="assets_container" class="extension_container"></div>4832 <div id="assets_container" class="extension_container"></div>
4833 <div id="typing_indicator_container" class="extension_container"></div>
4848 <div id="expressions_container" class="extension_container"></div>4834 <div id="expressions_container" class="extension_container"></div>
4849 <div id="sd_container" class="extension_container"></div>4835 <div id="sd_container" class="extension_container"></div>
4850 <div id="tts_container" class="extension_container"></div>4836 <div id="tts_container" class="extension_container"></div>
@@ -6488,9 +6474,6 @@
6488 </div>6474 </div>
64896475
6490 <!-- chat and input bar -->6476 <!-- chat and input bar -->
6491 <div id="typing_indicator_template" class="template_element">
6492 <div class="typing_indicator"><span class="typing_indicator_name">CHAR</span> is typing</div>
6493 </div>
6494 <div id="message_file_template" class="template_element">6477 <div id="message_file_template" class="template_element">
6495 <div class="mes_file_container">6478 <div class="mes_file_container">
6496 <div class="fa-lg fa-solid fa-file-alt mes_file_icon"></div>6479 <div class="fa-lg fa-solid fa-file-alt mes_file_icon"></div>
public/locales/fr-fr.json+2 -2
@@ -1385,8 +1385,8 @@
1385 "enable_functions_desc_1": "Autorise l'utilisation",1385 "enable_functions_desc_1": "Autorise l'utilisation",
1386 "enable_functions_desc_2": "outils de fonction",1386 "enable_functions_desc_2": "outils de fonction",
1387 "enable_functions_desc_3": "Peut être utilisé par diverses extensions pour fournir des fonctionnalités supplémentaires.",1387 "enable_functions_desc_3": "Peut être utilisé par diverses extensions pour fournir des fonctionnalités supplémentaires.",
1388 "Show model reasoning": "Afficher les pensées du modèle",1388 "Request model reasoning": "Demander les pensées du modèle",
1389 "Display the model's internal thoughts in the response.": "Afficher les pensées internes du modèle dans la réponse.",1389 "Allows the model to return its thinking process.": "Permet au modèle de retourner son processus de réflexion.",
1390 "Confirm token parsing with": "Confirmer l'analyse des tokens avec",1390 "Confirm token parsing with": "Confirmer l'analyse des tokens avec",
1391 "openai_logit_bias_no_items": "Aucun élément",1391 "openai_logit_bias_no_items": "Aucun élément",
1392 "api_no_connection": "Pas de connection...",1392 "api_no_connection": "Pas de connection...",
public/locales/zh-cn.json+2 -2
@@ -266,8 +266,8 @@
266 "Use system prompt": "使用系统提示词",266 "Use system prompt": "使用系统提示词",
267 "Merges_all_system_messages_desc_1": "合并所有系统消息,直到第一条具有非系统角色的消息,然后通过",267 "Merges_all_system_messages_desc_1": "合并所有系统消息,直到第一条具有非系统角色的消息,然后通过",
268 "Merges_all_system_messages_desc_2": "字段发送。",268 "Merges_all_system_messages_desc_2": "字段发送。",
269 "Show model reasoning": "展示思维链",269 "Request model reasoning": "请求思维链",
270 "Display the model's internal thoughts in the response.": "展示模型在回复时的内部思维链。",270 "Allows the model to return its thinking process.": "允许模型返回其思维过程。",
271 "Assistant Prefill": "AI预填",271 "Assistant Prefill": "AI预填",
272 "Expand the editor": "展开编辑器",272 "Expand the editor": "展开编辑器",
273 "Start Claude's answer with...": "以如下内容开始Claude的回答...",273 "Start Claude's answer with...": "以如下内容开始Claude的回答...",
public/locales/zh-tw.json+2 -2
@@ -2357,8 +2357,8 @@
2357 "Forbid": "禁止",2357 "Forbid": "禁止",
2358 "Aphrodite only. Determines the order of samplers. Skew is always applied post-softmax, so it's not included here.": "僅限 Aphrodite 使用。決定採樣器的順序。偏移總是在 softmax 後應用,因此不包括在此。",2358 "Aphrodite only. Determines the order of samplers. Skew is always applied post-softmax, so it's not included here.": "僅限 Aphrodite 使用。決定採樣器的順序。偏移總是在 softmax 後應用,因此不包括在此。",
2359 "Aphrodite only. Determines the order of samplers.": "僅限 Aphrodite 使用。決定採樣器的順序。",2359 "Aphrodite only. Determines the order of samplers.": "僅限 Aphrodite 使用。決定採樣器的順序。",
2360 "Show model reasoning": "顯示模型思維鏈",2360 "Request model reasoning": "請求模型思維鏈",
2361 "Display the model's internal thoughts in the response.": "在回應中顯示模型的思維鏈(內部思考過程)。",2361 "Allows the model to return its thinking process.": "讓模型回傳其思考過程。",
2362 "Generic (OpenAI-compatible) [LM Studio, LiteLLM, etc.]": "通用(兼容 OpenAI)[LM Studio, LiteLLM 等]",2362 "Generic (OpenAI-compatible) [LM Studio, LiteLLM, etc.]": "通用(兼容 OpenAI)[LM Studio, LiteLLM 等]",
2363 "Model ID (optional)": "模型 ID(可選)",2363 "Model ID (optional)": "模型 ID(可選)",
2364 "DeepSeek API Key": "DeepSeek API 金鑰",2364 "DeepSeek API Key": "DeepSeek API 金鑰",
public/script.js+97 -83
@@ -95,6 +95,7 @@ import {
95 resetMovableStyles,95 resetMovableStyles,
96 forceCharacterEditorTokenize,96 forceCharacterEditorTokenize,
97 applyPowerUserSettings,97 applyPowerUserSettings,
98 generatedTextFiltered,
98} from './scripts/power-user.js';99} from './scripts/power-user.js';
99100
100import {101import {
@@ -169,6 +170,7 @@ import {
169 toggleDrawer,170 toggleDrawer,
170 isElementInViewport,171 isElementInViewport,
171 copyText,172 copyText,
173 escapeHtml,
172} from './scripts/utils.js';174} from './scripts/utils.js';
173import { debounce_timeout } from './scripts/constants.js';175import { debounce_timeout } from './scripts/constants.js';
174176
@@ -1993,14 +1995,15 @@ export async function sendTextareaMessage() {
1993 * @param {boolean} isUser If the message was sent by the user1995 * @param {boolean} isUser If the message was sent by the user
1994 * @param {number} messageId Message index in chat array1996 * @param {number} messageId Message index in chat array
1995 * @param {object} [sanitizerOverrides] DOMPurify sanitizer option overrides1997 * @param {object} [sanitizerOverrides] DOMPurify sanitizer option overrides
1998 * @param {boolean} [isReasoning] If the message is reasoning output
1996 * @returns {string} HTML string1999 * @returns {string} HTML string
1997 */2000 */
1998export function messageFormatting(mes, ch_name, isSystem, isUser, messageId, sanitizerOverrides = {}) {2001export function messageFormatting(mes, ch_name, isSystem, isUser, messageId, sanitizerOverrides = {}, isReasoning = false) {
1999 if (!mes) {2002 if (!mes) {
2000 return '';2003 return '';
2001 }2004 }
20022005
2003 if (Number(messageId) === 0 && !isSystem && !isUser) {2006 if (Number(messageId) === 0 && !isSystem && !isUser && !isReasoning) {
2004 const mesBeforeReplace = mes;2007 const mesBeforeReplace = mes;
2005 const chatMessage = chat[messageId];2008 const chatMessage = chat[messageId];
2006 mes = substituteParams(mes, undefined, ch_name);2009 mes = substituteParams(mes, undefined, ch_name);
@@ -2029,6 +2032,9 @@ export function messageFormatting(mes, ch_name, isSystem, isUser, messageId, san
2029 if (!isSystem) {2032 if (!isSystem) {
2030 function getRegexPlacement() {2033 function getRegexPlacement() {
2031 try {2034 try {
2035 if (isReasoning) {
2036 return regex_placement.REASONING;
2037 }
2032 if (isUser) {2038 if (isUser) {
2033 return regex_placement.USER_INPUT;2039 return regex_placement.USER_INPUT;
2034 } else if (chat[messageId]?.extra?.type === 'narrator') {2040 } else if (chat[messageId]?.extra?.type === 'narrator') {
@@ -2062,6 +2068,17 @@ export function messageFormatting(mes, ch_name, isSystem, isUser, messageId, san
2062 mes = mes.replaceAll('<', '&lt;').replaceAll('>', '&gt;');2068 mes = mes.replaceAll('<', '&lt;').replaceAll('>', '&gt;');
2063 }2069 }
20642070
2071 // Make sure reasoning strings are always shown, even if they include "<" or ">"
2072 [power_user.reasoning.prefix, power_user.reasoning.suffix].forEach((reasoningString) => {
2073 if (!reasoningString || !reasoningString.trim().length) {
2074 return;
2075 }
2076 // Only replace the first occurrence of the reasoning string
2077 if (mes.includes(reasoningString)) {
2078 mes = mes.replace(reasoningString, escapeHtml(reasoningString));
2079 }
2080 });
2081
2065 if (!isSystem) {2082 if (!isSystem) {
2066 // Save double quotes in tags as a special character to prevent them from being encoded2083 // Save double quotes in tags as a special character to prevent them from being encoded
2067 if (!power_user.encode_tags) {2084 if (!power_user.encode_tags) {
@@ -2250,8 +2267,8 @@ function getMessageFromTemplate({
2250export function updateMessageBlock(messageId, message) {2267export function updateMessageBlock(messageId, message) {
2251 const messageElement = $(`#chat [mesid="${messageId}"]`);2268 const messageElement = $(`#chat [mesid="${messageId}"]`);
2252 const text = message?.extra?.display_text ?? message.mes;2269 const text = message?.extra?.display_text ?? message.mes;
2253 messageElement.find('.mes_text').html(messageFormatting(text, message.name, message.is_system, message.is_user, messageId));2270 messageElement.find('.mes_text').html(messageFormatting(text, message.name, message.is_system, message.is_user, messageId, {}, false));
2254 messageElement.find('.mes_reasoning').html(messageFormatting(message.extra?.reasoning ?? '', '', false, false, -1));2271 messageElement.find('.mes_reasoning').html(messageFormatting(message.extra?.reasoning ?? '', '', false, false, messageId, {}, true));
2255 addCopyToCodeBlocks(messageElement);2272 addCopyToCodeBlocks(messageElement);
2256 appendMediaToMessage(message, messageElement);2273 appendMediaToMessage(message, messageElement);
2257}2274}
@@ -2408,9 +2425,10 @@ export function addOneMessage(mes, { type = 'normal', insertAfter = null, scroll
2408 mes.is_user,2425 mes.is_user,
2409 chat.indexOf(mes),2426 chat.indexOf(mes),
2410 sanitizerOverrides,2427 sanitizerOverrides,
2428 false,
2411 );2429 );
2412 const bias = messageFormatting(mes.extra?.bias ?? '', '', false, false, -1);2430 const bias = messageFormatting(mes.extra?.bias ?? '', '', false, false, -1, {}, false);
2413 const reasoning = messageFormatting(mes.extra?.reasoning ?? '', '', false, false, -1);2431 const reasoning = messageFormatting(mes.extra?.reasoning ?? '', '', false, false, chat.indexOf(mes), {}, true);
2414 let bookmarkLink = mes?.extra?.bookmark_link ?? '';2432 let bookmarkLink = mes?.extra?.bookmark_link ?? '';
24152433
2416 let params = {2434 let params = {
@@ -3057,7 +3075,7 @@ export function isStreamingEnabled() {
3057 (main_api == 'openai' &&3075 (main_api == 'openai' &&
3058 oai_settings.stream_openai &&3076 oai_settings.stream_openai &&
3059 !noStreamSources.includes(oai_settings.chat_completion_source) &&3077 !noStreamSources.includes(oai_settings.chat_completion_source) &&
3060 !(oai_settings.chat_completion_source == chat_completion_sources.OPENAI && oai_settings.openai_model.startsWith('o1-')) &&3078 !(oai_settings.chat_completion_source == chat_completion_sources.OPENAI && (oai_settings.openai_model.startsWith('o1') || oai_settings.openai_model.startsWith('o3'))) &&
3061 !(oai_settings.chat_completion_source == chat_completion_sources.MAKERSUITE && oai_settings.google_model.includes('bison')))3079 !(oai_settings.chat_completion_source == chat_completion_sources.MAKERSUITE && oai_settings.google_model.includes('bison')))
3062 || (main_api == 'kobold' && kai_settings.streaming_kobold && kai_flags.can_use_streaming)3080 || (main_api == 'kobold' && kai_settings.streaming_kobold && kai_flags.can_use_streaming)
3063 || (main_api == 'novel' && nai_settings.streaming_novel)3081 || (main_api == 'novel' && nai_settings.streaming_novel)
@@ -3153,7 +3171,7 @@ class StreamingProcessor {
3153 this.sendTextarea.dispatchEvent(new Event('input', { bubbles: true }));3171 this.sendTextarea.dispatchEvent(new Event('input', { bubbles: true }));
3154 }3172 }
3155 else {3173 else {
3156 await saveReply(this.type, text, true);3174 await saveReply(this.type, text, true, '', [], '');
3157 messageId = chat.length - 1;3175 messageId = chat.length - 1;
3158 this.#checkDomElements(messageId);3176 this.#checkDomElements(messageId);
3159 this.showMessageButtons(messageId);3177 this.showMessageButtons(messageId);
@@ -3203,9 +3221,9 @@ class StreamingProcessor {
3203 }3221 }
32043222
3205 if (this.reasoning) {3223 if (this.reasoning) {
3206 chat[messageId]['extra']['reasoning'] = this.reasoning;3224 chat[messageId]['extra']['reasoning'] = power_user.trim_spaces ? this.reasoning.trim() : this.reasoning;
3207 if (this.messageReasoningDom instanceof HTMLElement) {3225 if (this.messageReasoningDom instanceof HTMLElement) {
3208 const formattedReasoning = messageFormatting(this.reasoning, '', false, false, -1);3226 const formattedReasoning = messageFormatting(this.reasoning, '', false, false, messageId, {}, true);
3209 this.messageReasoningDom.innerHTML = formattedReasoning;3227 this.messageReasoningDom.innerHTML = formattedReasoning;
3210 }3228 }
3211 }3229 }
@@ -3232,6 +3250,8 @@ class StreamingProcessor {
3232 chat[messageId].is_system,3250 chat[messageId].is_system,
3233 chat[messageId].is_user,3251 chat[messageId].is_user,
3234 messageId,3252 messageId,
3253 {},
3254 false,
3235 );3255 );
3236 if (this.messageTextDom instanceof HTMLElement) {3256 if (this.messageTextDom instanceof HTMLElement) {
3237 this.messageTextDom.innerHTML = formattedText;3257 this.messageTextDom.innerHTML = formattedText;
@@ -3283,39 +3303,11 @@ class StreamingProcessor {
3283 unblockGeneration();3303 unblockGeneration();
3284 generatedPromptCache = '';3304 generatedPromptCache = '';
32853305
3286 //console.log("Generated text size:", text.length, text)
3287
3288 const isAborted = this.abortController.signal.aborted;3306 const isAborted = this.abortController.signal.aborted;
3289 if (power_user.auto_swipe && !isAborted) {3307 if (!isAborted && power_user.auto_swipe && generatedTextFiltered(text)) {
3290 function containsBlacklistedWords(str, blacklist, threshold) {3308 return swipe_right();
3291 const regex = new RegExp(`\\b(${blacklist.join('|')})\\b`, 'gi');
3292 const matches = str.match(regex) || [];
3293 return matches.length >= threshold;
3294 }3309 }
32953310
3296 const generatedTextFiltered = (text) => {
3297 if (text) {
3298 if (power_user.auto_swipe_minimum_length) {
3299 if (text.length < power_user.auto_swipe_minimum_length && text.length !== 0) {
3300 console.log('Generated text size too small');
3301 return true;
3302 }
3303 }
3304 if (power_user.auto_swipe_blacklist_threshold) {
3305 if (containsBlacklistedWords(text, power_user.auto_swipe_blacklist, power_user.auto_swipe_blacklist_threshold)) {
3306 console.log('Generated text has blacklisted words');
3307 return true;
3308 }
3309 }
3310 }
3311 return false;
3312 };
3313
3314 if (generatedTextFiltered(text)) {
3315 swipe_right();
3316 return;
3317 }
3318 }
3319 playMessageSound();3311 playMessageSound();
3320 }3312 }
33213313
@@ -3373,7 +3365,7 @@ class StreamingProcessor {
3373 const timestamps = [];3365 const timestamps = [];
3374 for await (const { text, swipes, logprobs, toolCalls, state } of this.generator()) {3366 for await (const { text, swipes, logprobs, toolCalls, state } of this.generator()) {
3375 timestamps.push(Date.now());3367 timestamps.push(Date.now());
3376 if (this.isStopped) {3368 if (this.isStopped || this.abortController.signal.aborted) {
3377 return;3369 return;
3378 }3370 }
33793371
@@ -3383,7 +3375,7 @@ class StreamingProcessor {
3383 if (logprobs) {3375 if (logprobs) {
3384 this.messageLogprobs.push(...(Array.isArray(logprobs) ? logprobs : [logprobs]));3376 this.messageLogprobs.push(...(Array.isArray(logprobs) ? logprobs : [logprobs]));
3385 }3377 }
3386 this.reasoning = state?.reasoning ?? '';3378 this.reasoning = getRegexedString(state?.reasoning ?? '', regex_placement.REASONING);
3387 await eventSource.emit(event_types.STREAM_TOKEN_RECEIVED, text);3379 await eventSource.emit(event_types.STREAM_TOKEN_RECEIVED, text);
3388 await sw.tick(() => this.onProgressStreaming(this.messageId, this.continueMessage + text));3380 await sw.tick(() => this.onProgressStreaming(this.messageId, this.continueMessage + text));
3389 }3381 }
@@ -3850,14 +3842,6 @@ export async function Generate(type, { automatic_trigger, force_name2, quiet_pro
3850 coreChat.pop();3842 coreChat.pop();
3851 }3843 }
38523844
3853 const reasoning = new PromptReasoning();
3854 for (let i = coreChat.length - 1; i >= 0; i--) {
3855 if (reasoning.isLimitReached()) {
3856 break;
3857 }
3858 coreChat[i] = { ...coreChat[i], mes: reasoning.addToMessage(coreChat[i].mes, coreChat[i].extra?.reasoning) };
3859 }
3860
3861 coreChat = await Promise.all(coreChat.map(async (chatItem, index) => {3845 coreChat = await Promise.all(coreChat.map(async (chatItem, index) => {
3862 let message = chatItem.mes;3846 let message = chatItem.mes;
3863 let regexType = chatItem.is_user ? regex_placement.USER_INPUT : regex_placement.AI_OUTPUT;3847 let regexType = chatItem.is_user ? regex_placement.USER_INPUT : regex_placement.AI_OUTPUT;
@@ -3877,6 +3861,27 @@ export async function Generate(type, { automatic_trigger, force_name2, quiet_pro
3877 };3861 };
3878 }));3862 }));
38793863
3864 const reasoning = new PromptReasoning();
3865 for (let i = coreChat.length - 1; i >= 0; i--) {
3866 const depth = coreChat.length - i - 1;
3867 const isPrefix = isContinue && i === coreChat.length - 1;
3868 coreChat[i] = {
3869 ...coreChat[i],
3870 mes: reasoning.addToMessage(
3871 coreChat[i].mes,
3872 getRegexedString(
3873 String(coreChat[i].extra?.reasoning ?? ''),
3874 regex_placement.REASONING,
3875 { isPrompt: true, depth: depth },
3876 ),
3877 isPrefix,
3878 ),
3879 };
3880 if (reasoning.isLimitReached()) {
3881 break;
3882 }
3883 }
3884
3880 // Determine token limit3885 // Determine token limit
3881 let this_max_context = getMaxContextSize();3886 let this_max_context = getMaxContextSize();
38823887
@@ -4462,10 +4467,16 @@ export async function Generate(type, { automatic_trigger, force_name2, quiet_pro
4462 // TODO: Make all extension prompts use an array/splice method4467 // TODO: Make all extension prompts use an array/splice method
4463 const lengthDiff = mesSend.length - cfgPrompt.depth;4468 const lengthDiff = mesSend.length - cfgPrompt.depth;
4464 const cfgDepth = lengthDiff >= 0 ? lengthDiff : 0;4469 const cfgDepth = lengthDiff >= 0 ? lengthDiff : 0;
4470 const cfgMessage = finalMesSend[cfgDepth];
4471 if (cfgMessage) {
4472 if (!Array.isArray(finalMesSend[cfgDepth].extensionPrompts)) {
4473 finalMesSend[cfgDepth].extensionPrompts = [];
4474 }
4465 finalMesSend[cfgDepth].extensionPrompts.push(`${cfgPrompt.value}\n`);4475 finalMesSend[cfgDepth].extensionPrompts.push(`${cfgPrompt.value}\n`);
4466 }4476 }
4467 }4477 }
4468 }4478 }
4479 }
44694480
4470 // Add prompt bias after everything else4481 // Add prompt bias after everything else
4471 // Always run with continue4482 // Always run with continue
@@ -4785,6 +4796,11 @@ export async function Generate(type, { automatic_trigger, force_name2, quiet_pro
4785 const swipes = extractMultiSwipes(data, type);4796 const swipes = extractMultiSwipes(data, type);
47864797
4787 messageChunk = cleanUpMessage(getMessage, isImpersonate, isContinue, false);4798 messageChunk = cleanUpMessage(getMessage, isImpersonate, isContinue, false);
4799 reasoning = getRegexedString(reasoning, regex_placement.REASONING);
4800
4801 if (power_user.trim_spaces) {
4802 reasoning = reasoning.trim();
4803 }
47884804
4789 if (isContinue) {4805 if (isContinue) {
4790 getMessage = continue_mag + getMessage;4806 getMessage = continue_mag + getMessage;
@@ -4843,32 +4859,9 @@ export async function Generate(type, { automatic_trigger, force_name2, quiet_pro
4843 }4859 }
48444860
4845 const isAborted = abortController && abortController.signal.aborted;4861 const isAborted = abortController && abortController.signal.aborted;
4846 if (power_user.auto_swipe && !isAborted) {4862 if (!isAborted && power_user.auto_swipe && generatedTextFiltered(getMessage)) {
4847 console.debug('checking for autoswipeblacklist on non-streaming message');
4848 function containsBlacklistedWords(getMessage, blacklist, threshold) {
4849 console.debug('checking blacklisted words');
4850 const regex = new RegExp(`\\b(${blacklist.join('|')})\\b`, 'gi');
4851 const matches = getMessage.match(regex) || [];
4852 return matches.length >= threshold;
4853 }
4854
4855 const generatedTextFiltered = (getMessage) => {
4856 if (power_user.auto_swipe_blacklist_threshold) {
4857 if (containsBlacklistedWords(getMessage, power_user.auto_swipe_blacklist, power_user.auto_swipe_blacklist_threshold)) {
4858 console.debug('Generated text has blacklisted words');
4859 return true;
4860 }
4861 }
4862
4863 return false;
4864 };
4865 if (generatedTextFiltered(getMessage)) {
4866 console.debug('swiping right automatically');
4867 is_send_press = false;4863 is_send_press = false;
4868 swipe_right();4864 return swipe_right();
4869 // TODO: do we want to resolve after an auto-swipe?
4870 return;
4871 }
4872 }4865 }
48734866
4874 console.debug('/api/chats/save called by /Generate');4867 console.debug('/api/chats/save called by /Generate');
@@ -5719,7 +5712,17 @@ function extractMessageFromData(data) {
5719 * @returns {string} Extracted reasoning5712 * @returns {string} Extracted reasoning
5720 */5713 */
5721function extractReasoningFromData(data) {5714function extractReasoningFromData(data) {
5722 if (main_api === 'openai' && oai_settings.show_thoughts) {5715 switch (main_api) {
5716 case 'textgenerationwebui':
5717 switch (textgen_settings.type) {
5718 case textgen_types.OPENROUTER:
5719 return data?.choices?.[0]?.reasoning ?? '';
5720 }
5721 break;
5722
5723 case 'openai':
5724 if (!oai_settings.show_thoughts) break;
5725
5723 switch (oai_settings.chat_completion_source) {5726 switch (oai_settings.chat_completion_source) {
5724 case chat_completion_sources.DEEPSEEK:5727 case chat_completion_sources.DEEPSEEK:
5725 return data?.choices?.[0]?.message?.reasoning_content ?? '';5728 return data?.choices?.[0]?.message?.reasoning_content ?? '';
@@ -5728,6 +5731,7 @@ function extractReasoningFromData(data) {
5728 case chat_completion_sources.MAKERSUITE:5731 case chat_completion_sources.MAKERSUITE:
5729 return data?.responseContent?.parts?.filter(part => part.thought)?.map(part => part.text)?.join('\n\n') ?? '';5732 return data?.responseContent?.parts?.filter(part => part.thought)?.map(part => part.text)?.join('\n\n') ?? '';
5730 }5733 }
5734 break;
5731 }5735 }
57325736
5733 return '';5737 return '';
@@ -5923,6 +5927,15 @@ export async function saveReply(type, getMessage, fromStreaming, title, swipes,
5923 chat[chat.length - 1]['extra'] = {};5927 chat[chat.length - 1]['extra'] = {};
5924 }5928 }
59255929
5930 // Coerce null/undefined to empty string
5931 if (!chat[chat.length - 1]['extra']['reasoning']) {
5932 chat[chat.length - 1]['extra']['reasoning'] = '';
5933 }
5934
5935 if (!reasoning) {
5936 reasoning = '';
5937 }
5938
5926 let oldMessage = '';5939 let oldMessage = '';
5927 const generationFinished = new Date();5940 const generationFinished = new Date();
5928 const img = extractImageFromMessage(getMessage);5941 const img = extractImageFromMessage(getMessage);
@@ -7190,9 +7203,11 @@ function messageEditAuto(div) {
7190 mes.is_system,7203 mes.is_system,
7191 mes.is_user,7204 mes.is_user,
7192 this_edit_mes_id,7205 this_edit_mes_id,
7206 {},
7207 false,
7193 ));7208 ));
7194 mesBlock.find('.mes_bias').empty();7209 mesBlock.find('.mes_bias').empty();
7195 mesBlock.find('.mes_bias').append(messageFormatting(bias, '', false, false, -1));7210 mesBlock.find('.mes_bias').append(messageFormatting(bias, '', false, false, -1, {}, false));
7196 saveChatDebounced();7211 saveChatDebounced();
7197}7212}
71987213
@@ -7214,10 +7229,12 @@ async function messageEditDone(div) {
7214 mes.is_system,7229 mes.is_system,
7215 mes.is_user,7230 mes.is_user,
7216 this_edit_mes_id,7231 this_edit_mes_id,
7232 {},
7233 false,
7217 ),7234 ),
7218 );7235 );
7219 mesBlock.find('.mes_bias').empty();7236 mesBlock.find('.mes_bias').empty();
7220 mesBlock.find('.mes_bias').append(messageFormatting(bias, '', false, false, -1));7237 mesBlock.find('.mes_bias').append(messageFormatting(bias, '', false, false, -1, {}, false));
7221 appendMediaToMessage(mes, div.closest('.mes'));7238 appendMediaToMessage(mes, div.closest('.mes'));
7222 addCopyToCodeBlocks(div.closest('.mes'));7239 addCopyToCodeBlocks(div.closest('.mes'));
72237240
@@ -8721,11 +8738,6 @@ const swipe_right = () => {
8721 easing: animation_easing,8738 easing: animation_easing,
8722 queue: false,8739 queue: false,
8723 complete: async function () {8740 complete: async function () {
8724 /*if (!selected_group) {
8725 var typingIndicator = $("#typing_indicator_template .typing_indicator").clone();
8726 typingIndicator.find(".typing_indicator_name").text(characters[this_chid].name);
8727 } */
8728 /* $("#chat").append(typingIndicator); */
8729 const is_animation_scroll = ($('#chat').scrollTop() >= ($('#chat').prop('scrollHeight') - $('#chat').outerHeight()) - 10);8741 const is_animation_scroll = ($('#chat').scrollTop() >= ($('#chat').prop('scrollHeight') - $('#chat').outerHeight()) - 10);
8730 //console.log(parseInt(chat[chat.length-1]['swipe_id']));8742 //console.log(parseInt(chat[chat.length-1]['swipe_id']));
8731 //console.log(chat[chat.length-1]['swipes'].length);8743 //console.log(chat[chat.length-1]['swipes'].length);
@@ -10860,6 +10872,8 @@ jQuery(async function () {
10860 chat[this_edit_mes_id].is_system,10872 chat[this_edit_mes_id].is_system,
10861 chat[this_edit_mes_id].is_user,10873 chat[this_edit_mes_id].is_user,
10862 this_edit_mes_id,10874 this_edit_mes_id,
10875 {},
10876 false,
10863 ));10877 ));
10864 appendMediaToMessage(chat[this_edit_mes_id], $(this).closest('.mes'));10878 appendMediaToMessage(chat[this_edit_mes_id], $(this).closest('.mes'));
10865 addCopyToCodeBlocks($(this).closest('.mes'));10879 addCopyToCodeBlocks($(this).closest('.mes'));
public/scripts/authors-note.js+1 -1
@@ -566,7 +566,7 @@ export function initAuthorsNote() {
566 namedArgumentList: [],566 namedArgumentList: [],
567 unnamedArgumentList: [567 unnamedArgumentList: [
568 new SlashCommandArgument(568 new SlashCommandArgument(
569 'position', [ARGUMENT_TYPE.STRING], false, false, null, ['system', 'user', 'assistant'],569 'role', [ARGUMENT_TYPE.STRING], false, false, null, ['system', 'user', 'assistant'],
570 ),570 ),
571 ],571 ],
572 helpString: `572 helpString: `
public/scripts/backgrounds.js+15 -5
@@ -96,8 +96,13 @@ function highlightLockedBackground() {
96 });96 });
97}97}
9898
99/**
100 * Locks the background for the current chat
101 * @param {Event} e Click event
102 * @returns {string} Empty string
103 */
99function onLockBackgroundClick(e) {104function onLockBackgroundClick(e) {
100 e.stopPropagation();105 e?.stopPropagation();
101106
102 const chatName = getCurrentChatId();107 const chatName = getCurrentChatId();
103108
@@ -106,7 +111,7 @@ function onLockBackgroundClick(e) {
106 return '';111 return '';
107 }112 }
108113
109 const relativeBgImage = getUrlParameter(this);114 const relativeBgImage = getUrlParameter(this) ?? background_settings.url;
110115
111 saveBackgroundMetadata(relativeBgImage);116 saveBackgroundMetadata(relativeBgImage);
112 setCustomBackground();117 setCustomBackground();
@@ -114,8 +119,13 @@ function onLockBackgroundClick(e) {
114 return '';119 return '';
115}120}
116121
122/**
123 * Locks the background for the current chat
124 * @param {Event} e Click event
125 * @returns {string} Empty string
126 */
117function onUnlockBackgroundClick(e) {127function onUnlockBackgroundClick(e) {
118 e.stopPropagation();128 e?.stopPropagation();
119 removeBackgroundMetadata();129 removeBackgroundMetadata();
120 unsetCustomBackground();130 unsetCustomBackground();
121 highlightLockedBackground();131 highlightLockedBackground();
@@ -513,12 +523,12 @@ export function initBackgrounds() {
513 $('#add_bg_button').on('change', onBackgroundUploadSelected);523 $('#add_bg_button').on('change', onBackgroundUploadSelected);
514 $('#bg-filter').on('input', onBackgroundFilterInput);524 $('#bg-filter').on('input', onBackgroundFilterInput);
515 SlashCommandParser.addCommandObject(SlashCommand.fromProps({ name: 'lockbg',525 SlashCommandParser.addCommandObject(SlashCommand.fromProps({ name: 'lockbg',
516 callback: onLockBackgroundClick,526 callback: () => onLockBackgroundClick(new CustomEvent('click')),
517 aliases: ['bglock'],527 aliases: ['bglock'],
518 helpString: 'Locks a background for the currently selected chat',528 helpString: 'Locks a background for the currently selected chat',
519 }));529 }));
520 SlashCommandParser.addCommandObject(SlashCommand.fromProps({ name: 'unlockbg',530 SlashCommandParser.addCommandObject(SlashCommand.fromProps({ name: 'unlockbg',
521 callback: onUnlockBackgroundClick,531 callback: () => onUnlockBackgroundClick(new CustomEvent('click')),
522 aliases: ['bgunlock'],532 aliases: ['bgunlock'],
523 helpString: 'Unlocks a background for the currently selected chat',533 helpString: 'Unlocks a background for the currently selected chat',
524 }));534 }));
public/scripts/extensions/connection-manager/index.js+4 -0
@@ -30,6 +30,7 @@ const CC_COMMANDS = [
30 'api-url',30 'api-url',
31 'model',31 'model',
32 'proxy',32 'proxy',
33 'stop-strings',
33];34];
3435
35const TC_COMMANDS = [36const TC_COMMANDS = [
@@ -43,6 +44,7 @@ const TC_COMMANDS = [
43 'context',44 'context',
44 'instruct-state',45 'instruct-state',
45 'tokenizer',46 'tokenizer',
47 'stop-strings',
46];48];
4749
48const FANCY_NAMES = {50const FANCY_NAMES = {
@@ -57,6 +59,7 @@ const FANCY_NAMES = {
57 'instruct': 'Instruct Template',59 'instruct': 'Instruct Template',
58 'context': 'Context Template',60 'context': 'Context Template',
59 'tokenizer': 'Tokenizer',61 'tokenizer': 'Tokenizer',
62 'stop-strings': 'Custom Stopping Strings',
60};63};
6164
62/**65/**
@@ -138,6 +141,7 @@ const profilesProvider = () => [
138 * @property {string} [context] Context Template141 * @property {string} [context] Context Template
139 * @property {string} [instruct-state] Instruct Mode142 * @property {string} [instruct-state] Instruct Mode
140 * @property {string} [tokenizer] Tokenizer143 * @property {string} [tokenizer] Tokenizer
144 * @property {string} [stop-strings] Custom Stopping Strings
141 * @property {string[]} [exclude] Commands to exclude145 * @property {string[]} [exclude] Commands to exclude
142 */146 */
143147
public/scripts/extensions/expressions/index.js+0 -1
@@ -2178,7 +2178,6 @@ function migrateSettings() {
2178 typeList: [ARGUMENT_TYPE.STRING],2178 typeList: [ARGUMENT_TYPE.STRING],
2179 isRequired: true,2179 isRequired: true,
2180 enumProvider: commonEnumProviders.characters('character'),2180 enumProvider: commonEnumProviders.characters('character'),
2181 forceEnum: true,
2182 }),2181 }),
2183 ],2182 ],
2184 helpString: 'Returns the last set sprite / expression for the named character.',2183 helpString: 'Returns the last set sprite / expression for the named character.',
public/scripts/extensions/gallery/index.js+0 -1
@@ -441,7 +441,6 @@ SlashCommandParser.addCommandObject(SlashCommand.fromProps({
441 description: 'character name',441 description: 'character name',
442 typeList: [ARGUMENT_TYPE.STRING],442 typeList: [ARGUMENT_TYPE.STRING],
443 enumProvider: commonEnumProviders.characters('character'),443 enumProvider: commonEnumProviders.characters('character'),
444 forceEnum: true,
445 }),444 }),
446 SlashCommandNamedArgument.fromProps({445 SlashCommandNamedArgument.fromProps({
447 name: 'group',446 name: 'group',
public/scripts/extensions/quick-reply/src/SlashCommandHandler.js+4 -0
@@ -883,6 +883,10 @@ export class SlashCommandHandler {
883 }883 }
884 }884 }
885 getQuickReply(args) {885 getQuickReply(args) {
886 if (!args.id && !args.label) {
887 toastr.error('Please provide a valid id or label.');
888 return '';
889 }
886 try {890 try {
887 return JSON.stringify(this.api.getQrByLabel(args.set, args.id !== undefined ? Number(args.id) : args.label));891 return JSON.stringify(this.api.getQrByLabel(args.set, args.id !== undefined ? Number(args.id) : args.label));
888 } catch (ex) {892 } catch (ex) {
public/scripts/extensions/regex/editor.html+6 -0
@@ -94,6 +94,12 @@
94 <span data-i18n="World Info">World Info</span>94 <span data-i18n="World Info">World Info</span>
95 </label>95 </label>
96 </div>96 </div>
97 <div data-i18n="[title]ext_regex_reasoning_desc" title="Reasoning block contents. When 'Only Format Prompt' is checked, it will also affect the reasoning contents added to the prompt.">
98 <label class="checkbox flex-container">
99 <input type="checkbox" name="replace_position" value="6">
100 <span data-i18n="Reasoning">Reasoning</span>
101 </label>
102 </div>
97 <div class="flex-container wide100p marginTop5">103 <div class="flex-container wide100p marginTop5">
98 <div class="flex1 flex-container flexNoGap">104 <div class="flex1 flex-container flexNoGap">
99 <small data-i18n="[title]ext_regex_min_depth_desc" title="When applied to prompts or display, only affect messages that are at least N levels deep. 0 = last message, 1 = penultimate message, etc. Only counts WI entries @Depth and usable messages, i.e. not hidden or system.">105 <small data-i18n="[title]ext_regex_min_depth_desc" title="When applied to prompts or display, only affect messages that are at least N levels deep. 0 = last message, 1 = penultimate message, etc. Only counts WI entries @Depth and usable messages, i.e. not hidden or system.">
public/scripts/extensions/regex/engine.js+2 -1
@@ -20,6 +20,7 @@ const regex_placement = {
20 SLASH_COMMAND: 3,20 SLASH_COMMAND: 3,
21 // 4 - sendAs (legacy)21 // 4 - sendAs (legacy)
22 WORLD_INFO: 5,22 WORLD_INFO: 5,
23 REASONING: 6,
23};24};
2425
25export const substitute_find_regex = {26export const substitute_find_regex = {
@@ -94,7 +95,7 @@ function getRegexedString(rawString, placement, { characterOverride, isMarkdown,
94 // Script applies to Generate and input is Generate95 // Script applies to Generate and input is Generate
95 (script.promptOnly && isPrompt) ||96 (script.promptOnly && isPrompt) ||
96 // Script applies to all cases when neither "only"s are true, but there's no need to do it when `isMarkdown`, the as source (chat history) should already be changed beforehand97 // Script applies to all cases when neither "only"s are true, but there's no need to do it when `isMarkdown`, the as source (chat history) should already be changed beforehand
97 (!script.markdownOnly && !script.promptOnly && !isMarkdown)98 (!script.markdownOnly && !script.promptOnly && !isMarkdown && !isPrompt)
98 ) {99 ) {
99 if (isEdit && !script.runOnEdit) {100 if (isEdit && !script.runOnEdit) {
100 console.debug(`getRegexedString: Skipping script ${script.scriptName} because it does not run on edit`);101 console.debug(`getRegexedString: Skipping script ${script.scriptName} because it does not run on edit`);
public/scripts/extensions/regex/index.js+1 -1
@@ -18,7 +18,7 @@ import { t } from '../../i18n.js';
18 * @property {string} replaceString - The replace string18 * @property {string} replaceString - The replace string
19 * @property {string[]} trimStrings - The trim strings19 * @property {string[]} trimStrings - The trim strings
20 * @property {string?} findRegex - The find regex20 * @property {string?} findRegex - The find regex
21 * @property {string?} substituteRegex - The substitute regex21 * @property {number?} substituteRegex - The substitute regex
22 */22 */
2323
24/**24/**
public/scripts/extensions/tts/alltalk.js+10 -6
@@ -388,7 +388,7 @@ class AllTalkTtsProvider {
388 }388 }
389389
390 async fetchRvcVoiceObjects() {390 async fetchRvcVoiceObjects() {
391 if (this.settings.server_version == 'v2') {391 if (this.settings.server_version == 'v1') {
392 console.log('Skipping RVC voices fetch for V1 server');392 console.log('Skipping RVC voices fetch for V1 server');
393 return [];393 return [];
394 }394 }
@@ -1031,14 +1031,18 @@ class AllTalkTtsProvider {
1031 console.error('fetchTtsGeneration Error Response Text:', errorText);1031 console.error('fetchTtsGeneration Error Response Text:', errorText);
1032 throw new Error(`HTTP ${response.status}: ${errorText}`);1032 throw new Error(`HTTP ${response.status}: ${errorText}`);
1033 }1033 }
1034
1034 const data = await response.json();1035 const data = await response.json();
10351036
1036 // Handle V1/V2 URL differences1037 // V1 returns a complete URL, V2 returns a relative path
1037 const outputUrl = this.settings.server_version === 'v1'1038 if (this.settings.server_version === 'v1') {
1038 ? data.output_file_url // V1 returns full URL1039 // V1: Use the complete URL directly from the response
1039 : `${this.settings.provider_endpoint}${data.output_file_url}`; // V2 returns relative path1040 return data.output_file_url;
1041 } else {
1042 // V2: Combine the endpoint with the relative path
1043 return `${this.settings.provider_endpoint}${data.output_file_url}`;
1044 }
10401045
1041 return outputUrl;
1042 } catch (error) {1046 } catch (error) {
1043 console.error('[fetchTtsGeneration] Exception caught:', error);1047 console.error('[fetchTtsGeneration] Exception caught:', error);
1044 throw error;1048 throw error;
public/scripts/extensions/tts/index.js+47 -4
@@ -30,6 +30,7 @@ import { GoogleTranslateTtsProvider } from './google-translate.js';
30export { talkingAnimation };30export { talkingAnimation };
3131
32const UPDATE_INTERVAL = 1000;32const UPDATE_INTERVAL = 1000;
33const wrapper = new ModuleWorkerWrapper(moduleWorker);
3334
34let voiceMapEntries = [];35let voiceMapEntries = [];
35let voiceMap = {}; // {charName:voiceid, charName2:voiceid2}36let voiceMap = {}; // {charName:voiceid, charName2:voiceid2}
@@ -120,7 +121,7 @@ async function onNarrateOneMessage() {
120 }121 }
121122
122 resetTtsPlayback();123 resetTtsPlayback();
123 ttsJobQueue.push(message);124 processAndQueueTtsMessage(message);
124 moduleWorker();125 moduleWorker();
125}126}
126127
@@ -147,7 +148,7 @@ async function onNarrateText(args, text) {
147 }148 }
148149
149 resetTtsPlayback();150 resetTtsPlayback();
150 ttsJobQueue.push({ mes: text, name: name });151 processAndQueueTtsMessage({ mes: text, name: name });
151 await moduleWorker();152 await moduleWorker();
152153
153 // Return back to the chat voices154 // Return back to the chat voices
@@ -220,6 +221,36 @@ function isTtsProcessing() {
220 return processing;221 return processing;
221}222}
222223
224/**
225 * Splits a message into lines and adds each non-empty line to the TTS job queue.
226 * @param {Object} message - The message object to be processed.
227 * @param {string} message.mes - The text of the message to be split into lines.
228 * @param {string} message.name - The name associated with the message.
229 * @returns {void}
230 */
231function processAndQueueTtsMessage(message) {
232 if (!extension_settings.tts.narrate_by_paragraphs) {
233 ttsJobQueue.push(message);
234 return;
235 }
236
237 const lines = message.mes.split('\n');
238
239 for (let i = 0; i < lines.length; i++) {
240 const line = lines[i];
241
242 if (line.length === 0) {
243 continue;
244 }
245
246 ttsJobQueue.push(
247 Object.assign({}, message, {
248 mes: line,
249 }),
250 );
251 }
252}
253
223function debugTtsPlayback() {254function debugTtsPlayback() {
224 console.log(JSON.stringify(255 console.log(JSON.stringify(
225 {256 {
@@ -350,7 +381,7 @@ function onAudioControlClicked() {
350 talkingAnimation(false);381 talkingAnimation(false);
351 } else {382 } else {
352 // Default play behavior if not processing or playing is to play the last message.383 // Default play behavior if not processing or playing is to play the last message.
353 ttsJobQueue.push(context.chat[context.chat.length - 1]);384 processAndQueueTtsMessage(context.chat[context.chat.length - 1]);
354 }385 }
355 updateUiAudioPlayState();386 updateUiAudioPlayState();
356}387}
@@ -376,6 +407,7 @@ function completeCurrentAudioJob() {
376 currentAudioJob = null;407 currentAudioJob = null;
377 talkingAnimation(false); //stop lip animation408 talkingAnimation(false); //stop lip animation
378 // updateUiPlayState();409 // updateUiPlayState();
410 wrapper.update();
379}411}
380412
381/**413/**
@@ -569,6 +601,7 @@ function loadSettings() {
569 $('#tts_narrate_quoted').prop('checked', extension_settings.tts.narrate_quoted_only);601 $('#tts_narrate_quoted').prop('checked', extension_settings.tts.narrate_quoted_only);
570 $('#tts_auto_generation').prop('checked', extension_settings.tts.auto_generation);602 $('#tts_auto_generation').prop('checked', extension_settings.tts.auto_generation);
571 $('#tts_periodic_auto_generation').prop('checked', extension_settings.tts.periodic_auto_generation);603 $('#tts_periodic_auto_generation').prop('checked', extension_settings.tts.periodic_auto_generation);
604 $('#tts_narrate_by_paragraphs').prop('checked', extension_settings.tts.narrate_by_paragraphs);
572 $('#tts_narrate_translated_only').prop('checked', extension_settings.tts.narrate_translated_only);605 $('#tts_narrate_translated_only').prop('checked', extension_settings.tts.narrate_translated_only);
573 $('#tts_narrate_user').prop('checked', extension_settings.tts.narrate_user);606 $('#tts_narrate_user').prop('checked', extension_settings.tts.narrate_user);
574 $('#tts_pass_asterisks').prop('checked', extension_settings.tts.pass_asterisks);607 $('#tts_pass_asterisks').prop('checked', extension_settings.tts.pass_asterisks);
@@ -638,6 +671,11 @@ function onPeriodicAutoGenerationClick() {
638 saveSettingsDebounced();671 saveSettingsDebounced();
639}672}
640673
674function onNarrateByParagraphsClick() {
675 extension_settings.tts.narrate_by_paragraphs = !!$('#tts_narrate_by_paragraphs').prop('checked');
676 saveSettingsDebounced();
677}
678
641679
642function onNarrateDialoguesClick() {680function onNarrateDialoguesClick() {
643 extension_settings.tts.narrate_dialogues_only = !!$('#tts_narrate_dialogues').prop('checked');681 extension_settings.tts.narrate_dialogues_only = !!$('#tts_narrate_dialogues').prop('checked');
@@ -816,7 +854,12 @@ async function onMessageEvent(messageId, lastCharIndex) {
816 lastChatId = context.chatId;854 lastChatId = context.chatId;
817855
818 console.debug(`Adding message from ${message.name} for TTS processing: "${message.mes}"`);856 console.debug(`Adding message from ${message.name} for TTS processing: "${message.mes}"`);
857
858 if (extension_settings.tts.periodic_auto_generation) {
819 ttsJobQueue.push(message);859 ttsJobQueue.push(message);
860 } else {
861 processAndQueueTtsMessage(message);
862 }
820}863}
821864
822async function onMessageDeleted() {865async function onMessageDeleted() {
@@ -1156,6 +1199,7 @@ jQuery(async function () {
1156 $('#tts_pass_asterisks').on('click', onPassAsterisksClick);1199 $('#tts_pass_asterisks').on('click', onPassAsterisksClick);
1157 $('#tts_auto_generation').on('click', onAutoGenerationClick);1200 $('#tts_auto_generation').on('click', onAutoGenerationClick);
1158 $('#tts_periodic_auto_generation').on('click', onPeriodicAutoGenerationClick);1201 $('#tts_periodic_auto_generation').on('click', onPeriodicAutoGenerationClick);
1202 $('#tts_narrate_by_paragraphs').on('click', onNarrateByParagraphsClick);
1159 $('#tts_narrate_user').on('click', onNarrateUserClick);1203 $('#tts_narrate_user').on('click', onNarrateUserClick);
11601204
1161 $('#playback_rate').on('input', function () {1205 $('#playback_rate').on('input', function () {
@@ -1177,7 +1221,6 @@ jQuery(async function () {
1177 loadSettings(); // Depends on Extension Controls and loadTtsProvider1221 loadSettings(); // Depends on Extension Controls and loadTtsProvider
1178 loadTtsProvider(extension_settings.tts.currentProvider); // No dependencies1222 loadTtsProvider(extension_settings.tts.currentProvider); // No dependencies
1179 addAudioControl(); // Depends on Extension Controls1223 addAudioControl(); // Depends on Extension Controls
1180 const wrapper = new ModuleWorkerWrapper(moduleWorker);
1181 setInterval(wrapper.update.bind(wrapper), UPDATE_INTERVAL); // Init depends on all the things1224 setInterval(wrapper.update.bind(wrapper), UPDATE_INTERVAL); // Init depends on all the things
1182 eventSource.on(event_types.MESSAGE_SWIPED, resetTtsPlayback);1225 eventSource.on(event_types.MESSAGE_SWIPED, resetTtsPlayback);
1183 eventSource.on(event_types.CHAT_CHANGED, onChatChanged);1226 eventSource.on(event_types.CHAT_CHANGED, onChatChanged);
public/scripts/extensions/tts/settings.html+4 -0
@@ -30,6 +30,10 @@
30 <input type="checkbox" id="tts_periodic_auto_generation">30 <input type="checkbox" id="tts_periodic_auto_generation">
31 <small data-i18n="Narrate by paragraphs (when streaming)">Narrate by paragraphs (when streaming)</small>31 <small data-i18n="Narrate by paragraphs (when streaming)">Narrate by paragraphs (when streaming)</small>
32 </label>32 </label>
33 <label class="checkbox_label" for="tts_narrate_by_paragraphs">
34 <input type="checkbox" id="tts_narrate_by_paragraphs">
35 <small data-i18n="Narrate by paragraphs (when not streaming)">Narrate by paragraphs (when not streaming)</small>
36 </label>
33 <label class="checkbox_label" for="tts_narrate_quoted">37 <label class="checkbox_label" for="tts_narrate_quoted">
34 <input type="checkbox" id="tts_narrate_quoted">38 <input type="checkbox" id="tts_narrate_quoted">
35 <small data-i18n="Only narrate quotes">Only narrate "quotes"</small>39 <small data-i18n="Only narrate quotes">Only narrate "quotes"</small>
public/scripts/group-chats.js+27 -33
@@ -292,10 +292,11 @@ export function getGroupNames() {
292292
293/**293/**
294 * Finds the character ID for a group member.294 * Finds the character ID for a group member.
295 * @param {string} arg 0-based member index or character name295 * @param {number|string} arg 0-based member index or character name
296 * @returns {number} 0-based character ID296 * @param {Boolean} full Whether to return a key-value object containing extra data
297 * @returns {number|Object} 0-based character ID or key-value object if full is true
297 */298 */
298export function findGroupMemberId(arg) {299export function findGroupMemberId(arg, full = false) {
299 arg = arg?.trim();300 arg = arg?.trim();
300301
301 if (!arg) {302 if (!arg) {
@@ -311,15 +312,19 @@ export function findGroupMemberId(arg) {
311 }312 }
312313
313 const index = parseInt(arg);314 const index = parseInt(arg);
314 const searchByName = isNaN(index);315 const searchByString = isNaN(index);
315316
316 if (searchByName) {317 if (searchByString) {
317 const memberNames = group.members.map(x => ({ name: characters.find(y => y.avatar === x)?.name, index: characters.findIndex(y => y.avatar === x) }));318 const memberNames = group.members.map(x => ({
318 const fuse = new Fuse(memberNames, { keys: ['name'] });319 avatar: x,
320 name: characters.find(y => y.avatar === x)?.name,
321 index: characters.findIndex(y => y.avatar === x),
322 }));
323 const fuse = new Fuse(memberNames, { keys: ['avatar', 'name'] });
319 const result = fuse.search(arg);324 const result = fuse.search(arg);
320325
321 if (!result.length) {326 if (!result.length) {
322 console.warn(`WARN: No group member found with name ${arg}`);327 console.warn(`WARN: No group member found using string ${arg}`);
323 return;328 return;
324 }329 }
325330
@@ -330,9 +335,11 @@ export function findGroupMemberId(arg) {
330 return;335 return;
331 }336 }
332337
333 console.log(`Triggering group member ${chid} (${arg}) from search result`, result[0]);338 console.log(`Targeting group member ${chid} (${arg}) from search result`, result[0]);
334 return chid;339
335 } else {340 return !full ? chid : { ...{ id: chid }, ...result[0].item };
341 }
342 else {
336 const memberAvatar = group.members[index];343 const memberAvatar = group.members[index];
337344
338 if (memberAvatar === undefined) {345 if (memberAvatar === undefined) {
@@ -347,8 +354,14 @@ export function findGroupMemberId(arg) {
347 return;354 return;
348 }355 }
349356
350 console.log(`Triggering group member ${memberAvatar} at index ${index}`);357 console.log(`Targeting group member ${memberAvatar} at index ${index}`);
351 return chid;358
359 return !full ? chid : {
360 id: chid,
361 avatar: memberAvatar,
362 name: characters.find(y => y.avatar === memberAvatar)?.name,
363 index: index,
364 };
352 }365 }
353}366}
354367
@@ -805,7 +818,6 @@ async function generateGroupWrapper(by_auto_mode, type = null, params = {}) {
805818
806 /** @type {any} Caution: JS war crimes ahead */819 /** @type {any} Caution: JS war crimes ahead */
807 let textResult = '';820 let textResult = '';
808 let typingIndicator = $('#chat .typing_indicator');
809 const group = groups.find((x) => x.id === selected_group);821 const group = groups.find((x) => x.id === selected_group);
810822
811 if (!group || !Array.isArray(group.members) || !group.members.length) {823 if (!group || !Array.isArray(group.members) || !group.members.length) {
@@ -821,14 +833,6 @@ async function generateGroupWrapper(by_auto_mode, type = null, params = {}) {
821 setCharacterId(undefined);833 setCharacterId(undefined);
822 const userInput = String($('#send_textarea').val());834 const userInput = String($('#send_textarea').val());
823835
824 if (typingIndicator.length === 0 && !isStreamingEnabled()) {
825 typingIndicator = $(
826 '#typing_indicator_template .typing_indicator',
827 ).clone();
828 typingIndicator.hide();
829 $('#chat').append(typingIndicator);
830 }
831
832 // id of this specific batch for regeneration purposes836 // id of this specific batch for regeneration purposes
833 group_generation_id = Date.now();837 group_generation_id = Date.now();
834 const lastMessage = chat[chat.length - 1];838 const lastMessage = chat[chat.length - 1];
@@ -906,14 +910,6 @@ async function generateGroupWrapper(by_auto_mode, type = null, params = {}) {
906 }910 }
907 await eventSource.emit(event_types.GROUP_MEMBER_DRAFTED, chId);911 await eventSource.emit(event_types.GROUP_MEMBER_DRAFTED, chId);
908912
909 if (type !== 'swipe' && type !== 'impersonate' && !isStreamingEnabled()) {
910 // update indicator and scroll down
911 typingIndicator
912 .find('.typing_indicator_name')
913 .text(characters[chId].name);
914 typingIndicator.show();
915 }
916
917 // Wait for generation to finish913 // Wait for generation to finish
918 textResult = await Generate(generateType, { automatic_trigger: by_auto_mode, ...(params || {}) });914 textResult = await Generate(generateType, { automatic_trigger: by_auto_mode, ...(params || {}) });
919 let messageChunk = textResult?.messageChunk;915 let messageChunk = textResult?.messageChunk;
@@ -930,8 +926,6 @@ async function generateGroupWrapper(by_auto_mode, type = null, params = {}) {
930 }926 }
931 }927 }
932 } finally {928 } finally {
933 typingIndicator.hide();
934
935 is_group_generating = false;929 is_group_generating = false;
936 setSendButtonState(false);930 setSendButtonState(false);
937 setCharacterId(undefined);931 setCharacterId(undefined);
public/scripts/loader.js+27 -6
@@ -27,21 +27,42 @@ export async function hideLoader() {
27 }27 }
2828
29 return new Promise((resolve) => {29 return new Promise((resolve) => {
30 // Spinner blurs/fades out30 const spinner = $('#load-spinner');
31 $('#load-spinner').on('transitionend webkitTransitionEnd oTransitionEnd MSTransitionEnd', function () {31 if (!spinner.length) {
32 console.warn('Spinner element not found, skipping animation');
33 cleanup();
34 return;
35 }
36
37 // Check if transitions are enabled
38 const transitionDuration = spinner[0] ? getComputedStyle(spinner[0]).transitionDuration : '0s';
39 const hasTransitions = parseFloat(transitionDuration) > 0;
40
41 if (hasTransitions) {
42 Promise.race([
43 new Promise((r) => setTimeout(r, 500)), // Fallback timeout
44 new Promise((r) => spinner.one('transitionend webkitTransitionEnd oTransitionEnd MSTransitionEnd', r)),
45 ]).finally(cleanup);
46 } else {
47 cleanup();
48 }
49
50 function cleanup() {
32 $('#loader').remove();51 $('#loader').remove();
33 // Yoink preloader entirely; it only exists to cover up unstyled content while loading JS52 // Yoink preloader entirely; it only exists to cover up unstyled content while loading JS
34 // If it's present, we remove it once and then it's gone.53 // If it's present, we remove it once and then it's gone.
35 yoinkPreloader();54 yoinkPreloader();
3655
37 loaderPopup.complete(POPUP_RESULT.AFFIRMATIVE).then(() => {56 loaderPopup.complete(POPUP_RESULT.AFFIRMATIVE)
57 .catch((err) => console.error('Error completing loaderPopup:', err))
58 .finally(() => {
38 loaderPopup = null;59 loaderPopup = null;
39 resolve();60 resolve();
40 });61 });
41 });62 }
4263
43 $('#load-spinner')64 // Apply the styles
44 .css({65 spinner.css({
45 'filter': 'blur(15px)',66 'filter': 'blur(15px)',
46 'opacity': '0',67 'opacity': '0',
47 });68 });
public/scripts/openai.js+35 -38
@@ -258,8 +258,8 @@ const default_settings = {
258 ai21_model: 'jamba-1.5-large',258 ai21_model: 'jamba-1.5-large',
259 mistralai_model: 'mistral-large-latest',259 mistralai_model: 'mistral-large-latest',
260 cohere_model: 'command-r-plus',260 cohere_model: 'command-r-plus',
261 perplexity_model: 'llama-3.1-70b-instruct',261 perplexity_model: 'sonar-pro',
262 groq_model: 'llama-3.1-70b-versatile',262 groq_model: 'llama-3.3-70b-versatile',
263 nanogpt_model: 'gpt-4o-mini',263 nanogpt_model: 'gpt-4o-mini',
264 zerooneai_model: 'yi-large',264 zerooneai_model: 'yi-large',
265 blockentropy_model: 'be-70b-base-llama3.1',265 blockentropy_model: 'be-70b-base-llama3.1',
@@ -298,7 +298,7 @@ const default_settings = {
298 names_behavior: character_names_behavior.DEFAULT,298 names_behavior: character_names_behavior.DEFAULT,
299 continue_postfix: continue_postfix_types.SPACE,299 continue_postfix: continue_postfix_types.SPACE,
300 custom_prompt_post_processing: custom_prompt_post_processing_types.NONE,300 custom_prompt_post_processing: custom_prompt_post_processing_types.NONE,
301 show_thoughts: false,301 show_thoughts: true,
302 seed: -1,302 seed: -1,
303 n: 1,303 n: 1,
304};304};
@@ -337,7 +337,7 @@ const oai_settings = {
337 ai21_model: 'jamba-1.5-large',337 ai21_model: 'jamba-1.5-large',
338 mistralai_model: 'mistral-large-latest',338 mistralai_model: 'mistral-large-latest',
339 cohere_model: 'command-r-plus',339 cohere_model: 'command-r-plus',
340 perplexity_model: 'llama-3.1-70b-instruct',340 perplexity_model: 'sonar-pro',
341 groq_model: 'llama-3.1-70b-versatile',341 groq_model: 'llama-3.1-70b-versatile',
342 nanogpt_model: 'gpt-4o-mini',342 nanogpt_model: 'gpt-4o-mini',
343 zerooneai_model: 'yi-large',343 zerooneai_model: 'yi-large',
@@ -377,7 +377,7 @@ const oai_settings = {
377 names_behavior: character_names_behavior.DEFAULT,377 names_behavior: character_names_behavior.DEFAULT,
378 continue_postfix: continue_postfix_types.SPACE,378 continue_postfix: continue_postfix_types.SPACE,
379 custom_prompt_post_processing: custom_prompt_post_processing_types.NONE,379 custom_prompt_post_processing: custom_prompt_post_processing_types.NONE,
380 show_thoughts: false,380 show_thoughts: true,
381 seed: -1,381 seed: -1,
382 n: 1,382 n: 1,
383};383};
@@ -1869,7 +1869,7 @@ async function sendOpenAIRequest(type, messages, signal) {
1869 const isQuiet = type === 'quiet';1869 const isQuiet = type === 'quiet';
1870 const isImpersonate = type === 'impersonate';1870 const isImpersonate = type === 'impersonate';
1871 const isContinue = type === 'continue';1871 const isContinue = type === 'continue';
1872 const stream = oai_settings.stream_openai && !isQuiet && !isScale && !(isGoogle && oai_settings.google_model.includes('bison')) && !(isOAI && oai_settings.openai_model.startsWith('o1-'));1872 const stream = oai_settings.stream_openai && !isQuiet && !isScale && !(isGoogle && oai_settings.google_model.includes('bison')) && !(isOAI && (oai_settings.openai_model.startsWith('o1') || oai_settings.openai_model.startsWith('o3')));
1873 const useLogprobs = !!power_user.request_token_probabilities;1873 const useLogprobs = !!power_user.request_token_probabilities;
1874 const canMultiSwipe = oai_settings.n > 1 && !isContinue && !isImpersonate && !isQuiet && (isOAI || isCustom);1874 const canMultiSwipe = oai_settings.n > 1 && !isContinue && !isImpersonate && !isQuiet && (isOAI || isCustom);
18751875
@@ -1913,7 +1913,7 @@ async function sendOpenAIRequest(type, messages, signal) {
1913 'user_name': name1,1913 'user_name': name1,
1914 'char_name': name2,1914 'char_name': name2,
1915 'group_names': getGroupNames(),1915 'group_names': getGroupNames(),
1916 'show_thoughts': Boolean(oai_settings.show_thoughts),1916 'include_reasoning': Boolean(oai_settings.show_thoughts),
1917 };1917 };
19181918
1919 // Empty array will produce a validation error1919 // Empty array will produce a validation error
@@ -2050,7 +2050,7 @@ async function sendOpenAIRequest(type, messages, signal) {
2050 await ToolManager.registerFunctionToolsOpenAI(generate_data);2050 await ToolManager.registerFunctionToolsOpenAI(generate_data);
2051 }2051 }
20522052
2053 if (isOAI && oai_settings.openai_model.startsWith('o1-')) {2053 if (isOAI && (oai_settings.openai_model.startsWith('o1') || oai_settings.openai_model.startsWith('o3'))) {
2054 generate_data.messages.forEach((msg) => {2054 generate_data.messages.forEach((msg) => {
2055 if (msg.role === 'system') {2055 if (msg.role === 'system') {
2056 msg.role = 'user';2056 msg.role = 'user';
@@ -4027,7 +4027,7 @@ function getMaxContextOpenAI(value) {
4027 if (oai_settings.max_context_unlocked) {4027 if (oai_settings.max_context_unlocked) {
4028 return unlocked_max;4028 return unlocked_max;
4029 }4029 }
4030 else if (value.startsWith('o1-')) {4030 else if (value.startsWith('o1') || value.startsWith('o3')) {
4031 return max_128k;4031 return max_128k;
4032 }4032 }
4033 else if (value.includes('chatgpt-4o-latest') || value.includes('gpt-4-turbo') || value.includes('gpt-4o') || value.includes('gpt-4-1106') || value.includes('gpt-4-0125') || value.includes('gpt-4-vision')) {4033 else if (value.includes('chatgpt-4o-latest') || value.includes('gpt-4-turbo') || value.includes('gpt-4o') || value.includes('gpt-4-1106') || value.includes('gpt-4-0125') || value.includes('gpt-4-vision')) {
@@ -4380,28 +4380,19 @@ async function onModelChange() {
4380 if (oai_settings.max_context_unlocked) {4380 if (oai_settings.max_context_unlocked) {
4381 $('#openai_max_context').attr('max', unlocked_max);4381 $('#openai_max_context').attr('max', unlocked_max);
4382 }4382 }
4383 else if (['sonar', 'sonar-reasoning'].includes(oai_settings.perplexity_model)) {
4384 $('#openai_max_context').attr('max', 127000);
4385 }
4386 else if (['sonar-pro'].includes(oai_settings.perplexity_model)) {
4387 $('#openai_max_context').attr('max', 200000);
4388 }
4383 else if (oai_settings.perplexity_model.includes('llama-3.1')) {4389 else if (oai_settings.perplexity_model.includes('llama-3.1')) {
4384 const isOnline = oai_settings.perplexity_model.includes('online');4390 const isOnline = oai_settings.perplexity_model.includes('online');
4385 const contextSize = isOnline ? 128 * 1024 - 4000 : 128 * 1024;4391 const contextSize = isOnline ? 128 * 1024 - 4000 : 128 * 1024;
4386 $('#openai_max_context').attr('max', contextSize);4392 $('#openai_max_context').attr('max', contextSize);
4387 }4393 }
4388 else if (['llama-3-sonar-small-32k-chat', 'llama-3-sonar-large-32k-chat'].includes(oai_settings.perplexity_model)) {
4389 $('#openai_max_context').attr('max', max_32k);
4390 }
4391 else if (['llama-3-sonar-small-32k-online', 'llama-3-sonar-large-32k-online'].includes(oai_settings.perplexity_model)) {
4392 $('#openai_max_context').attr('max', 28000);
4393 }
4394 else if (['sonar-small-chat', 'sonar-medium-chat', 'codellama-70b-instruct', 'mistral-7b-instruct', 'mixtral-8x7b-instruct', 'mixtral-8x22b-instruct'].includes(oai_settings.perplexity_model)) {
4395 $('#openai_max_context').attr('max', max_16k);
4396 }
4397 else if (['llama-3-8b-instruct', 'llama-3-70b-instruct'].includes(oai_settings.perplexity_model)) {
4398 $('#openai_max_context').attr('max', max_8k);
4399 }
4400 else if (['sonar-small-online', 'sonar-medium-online'].includes(oai_settings.perplexity_model)) {
4401 $('#openai_max_context').attr('max', 12000);
4402 }
4403 else {4394 else {
4404 $('#openai_max_context').attr('max', max_4k);4395 $('#openai_max_context').attr('max', max_128k);
4405 }4396 }
4406 oai_settings.openai_max_context = Math.min(Number($('#openai_max_context').attr('max')), oai_settings.openai_max_context);4397 oai_settings.openai_max_context = Math.min(Number($('#openai_max_context').attr('max')), oai_settings.openai_max_context);
4407 $('#openai_max_context').val(oai_settings.openai_max_context).trigger('input');4398 $('#openai_max_context').val(oai_settings.openai_max_context).trigger('input');
@@ -4412,24 +4403,30 @@ async function onModelChange() {
4412 if (oai_settings.chat_completion_source == chat_completion_sources.GROQ) {4403 if (oai_settings.chat_completion_source == chat_completion_sources.GROQ) {
4413 if (oai_settings.max_context_unlocked) {4404 if (oai_settings.max_context_unlocked) {
4414 $('#openai_max_context').attr('max', unlocked_max);4405 $('#openai_max_context').attr('max', unlocked_max);
4415 }4406 } else if (oai_settings.groq_model.includes('gemma2-9b-it')) {
4416 else if (oai_settings.groq_model.includes('llama-3.2') && oai_settings.groq_model.includes('-preview')) {
4417 $('#openai_max_context').attr('max', max_8k);4407 $('#openai_max_context').attr('max', max_8k);
4418 }4408 } else if (oai_settings.groq_model.includes('llama-3.3-70b-versatile')) {
4419 else if (oai_settings.groq_model.includes('llama-3.3') || oai_settings.groq_model.includes('llama-3.2') || oai_settings.groq_model.includes('llama-3.1')) {
4420 $('#openai_max_context').attr('max', max_128k);4409 $('#openai_max_context').attr('max', max_128k);
4421 }4410 } else if (oai_settings.groq_model.includes('llama-3.1-8b-instant')) {
4422 else if (oai_settings.groq_model.includes('llama3-groq')) {4411 $('#openai_max_context').attr('max', max_128k);
4412 } else if (oai_settings.groq_model.includes('llama3-70b-8192')) {
4423 $('#openai_max_context').attr('max', max_8k);4413 $('#openai_max_context').attr('max', max_8k);
4424 }4414 } else if (oai_settings.groq_model.includes('llama3-8b-8192')) {
4425 else if (['llama3-8b-8192', 'llama3-70b-8192', 'gemma-7b-it', 'gemma2-9b-it'].includes(oai_settings.groq_model)) {
4426 $('#openai_max_context').attr('max', max_8k);4415 $('#openai_max_context').attr('max', max_8k);
4427 }4416 } else if (oai_settings.groq_model.includes('mixtral-8x7b-32768')) {
4428 else if (['mixtral-8x7b-32768'].includes(oai_settings.groq_model)) {
4429 $('#openai_max_context').attr('max', max_32k);4417 $('#openai_max_context').attr('max', max_32k);
4430 }4418 } else if (oai_settings.groq_model.includes('deepseek-r1-distill-llama-70b')) {
4431 else {4419 $('#openai_max_context').attr('max', max_128k);
4432 $('#openai_max_context').attr('max', max_4k);4420 } else if (oai_settings.groq_model.includes('llama-3.3-70b-specdec')) {
4421 $('#openai_max_context').attr('max', max_8k);
4422 } else if (oai_settings.groq_model.includes('llama-3.2-1b-preview')) {
4423 $('#openai_max_context').attr('max', max_128k);
4424 } else if (oai_settings.groq_model.includes('llama-3.2-3b-preview')) {
4425 $('#openai_max_context').attr('max', max_128k);
4426 } else if (oai_settings.groq_model.includes('llama-3.2-11b-vision-preview')) {
4427 $('#openai_max_context').attr('max', max_128k);
4428 } else if (oai_settings.groq_model.includes('llama-3.2-90b-vision-preview')) {
4429 $('#openai_max_context').attr('max', max_128k);
4433 }4430 }
4434 oai_settings.openai_max_context = Math.min(Number($('#openai_max_context').attr('max')), oai_settings.openai_max_context);4431 oai_settings.openai_max_context = Math.min(Number($('#openai_max_context').attr('max')), oai_settings.openai_max_context);
4435 $('#openai_max_context').val(oai_settings.openai_max_context).trigger('input');4432 $('#openai_max_context').val(oai_settings.openai_max_context).trigger('input');
public/scripts/power-user.js+84 -2
@@ -254,6 +254,7 @@ let power_user = {
254 },254 },
255255
256 reasoning: {256 reasoning: {
257 auto_parse: false,
257 add_to_prompts: false,258 add_to_prompts: false,
258 prefix: '<think>\n',259 prefix: '<think>\n',
259 suffix: '\n</think>',260 suffix: '\n</think>',
@@ -2917,6 +2918,46 @@ export function flushEphemeralStoppingStrings() {
2917}2918}
29182919
2919/**2920/**
2921 * Checks if the generated text should be filtered based on the auto-swipe settings.
2922 * @param {string} text The text to check
2923 * @returns {boolean} If the generated text should be filtered
2924 */
2925export function generatedTextFiltered(text) {
2926 /**
2927 * Checks if the given text contains any of the blacklisted words.
2928 * @param {string} text The text to check
2929 * @param {string[]} blacklist The list of blacklisted words
2930 * @param {number} threshold The number of blacklisted words that need to be present to trigger the check
2931 * @returns {boolean} Whether the text contains blacklisted words
2932 */
2933 function containsBlacklistedWords(text, blacklist, threshold) {
2934 const regex = new RegExp(`\\b(${blacklist.join('|')})\\b`, 'gi');
2935 const matches = text.match(regex) || [];
2936 return matches.length >= threshold;
2937 }
2938
2939 // Make sure a generated text is non-empty
2940 // Otherwise we might get in a loop with a broken API
2941 text = text.trim();
2942 if (text.length > 0) {
2943 if (power_user.auto_swipe_minimum_length) {
2944 if (text.length < power_user.auto_swipe_minimum_length) {
2945 console.log('Generated text size too small');
2946 return true;
2947 }
2948 }
2949 if (power_user.auto_swipe_blacklist.length && power_user.auto_swipe_blacklist_threshold) {
2950 if (containsBlacklistedWords(text, power_user.auto_swipe_blacklist, power_user.auto_swipe_blacklist_threshold)) {
2951 console.log('Generated text has blacklisted words');
2952 return true;
2953 }
2954 }
2955 }
2956
2957 return false;
2958}
2959
2960/**
2920 * Gets the custom stopping strings from the power user settings.2961 * Gets the custom stopping strings from the power user settings.
2921 * @param {number | undefined} limit Number of strings to return. If 0 or undefined, returns all strings.2962 * @param {number | undefined} limit Number of strings to return. If 0 or undefined, returns all strings.
2922 * @returns {string[]} An array of custom stopping strings2963 * @returns {string[]} An array of custom stopping strings
@@ -3887,9 +3928,9 @@ $(document).ready(() => {
3887 helpString: 'Start a new chat with a random character. If an argument is provided, only considers characters that have the specified tag.',3928 helpString: 'Start a new chat with a random character. If an argument is provided, only considers characters that have the specified tag.',
3888 }));3929 }));
3889 SlashCommandParser.addCommandObject(SlashCommand.fromProps({3930 SlashCommandParser.addCommandObject(SlashCommand.fromProps({
3890 name: 'delmode',3931 name: 'del',
3891 callback: doDelMode,3932 callback: doDelMode,
3892 aliases: ['del'],3933 aliases: ['delete', 'delmode'],
3893 unnamedArgumentList: [3934 unnamedArgumentList: [
3894 new SlashCommandArgument(3935 new SlashCommandArgument(
3895 'optional number', [ARGUMENT_TYPE.NUMBER], false,3936 'optional number', [ARGUMENT_TYPE.NUMBER], false,
@@ -4072,4 +4113,45 @@ $(document).ready(() => {
4072 ],4113 ],
4073 helpString: 'activates a movingUI preset by name',4114 helpString: 'activates a movingUI preset by name',
4074 }));4115 }));
4116 SlashCommandParser.addCommandObject(SlashCommand.fromProps({
4117 name: 'stop-strings',
4118 aliases: ['stopping-strings', 'custom-stopping-strings', 'custom-stop-strings'],
4119 helpString: `
4120 <div>
4121 Sets a list of custom stopping strings. Gets the list if no value is provided.
4122 </div>
4123 <div>
4124 <strong>Examples:</strong>
4125 </div>
4126 <ul>
4127 <li>Value must be a JSON-serialized array: <pre><code class="language-stscript">/stop-strings ["goodbye", "farewell"]</code></pre></li>
4128 <li>Pipe characters must be escaped with a backslash: <pre><code class="language-stscript">/stop-strings ["left\\|right"]</code></pre></li>
4129 </ul>
4130 `,
4131 returns: ARGUMENT_TYPE.LIST,
4132 unnamedArgumentList: [
4133 SlashCommandArgument.fromProps({
4134 description: 'list of strings',
4135 typeList: [ARGUMENT_TYPE.LIST],
4136 acceptsMultiple: false,
4137 isRequired: false,
4138 }),
4139 ],
4140 callback: (_, value) => {
4141 if (String(value ?? '').trim()) {
4142 const parsedValue = ((x) => { try { return JSON.parse(x.toString()); } catch { return null; } })(value);
4143 if (!parsedValue || !Array.isArray(parsedValue)) {
4144 throw new Error('Invalid list format. The value must be a JSON-serialized array of strings.');
4145 }
4146 parsedValue.forEach((item, index) => {
4147 parsedValue[index] = String(item);
4148 });
4149 power_user.custom_stopping_strings = JSON.stringify(parsedValue);
4150 $('#custom_stopping_strings').val(power_user.custom_stopping_strings);
4151 saveSettingsDebounced();
4152 }
4153
4154 return power_user.custom_stopping_strings;
4155 },
4156 }));
4075});4157});
public/scripts/preset-manager.js+1 -0
@@ -586,6 +586,7 @@ class PresetManager {
586 'tabby_model',586 'tabby_model',
587 'derived',587 'derived',
588 'generic_model',588 'generic_model',
589 'include_reasoning',
589 ];590 ];
590 const settings = Object.assign({}, getSettingsByApiId(this.apiId));591 const settings = Object.assign({}, getSettingsByApiId(this.apiId));
591592
public/scripts/reasoning.js+157 -6
@@ -1,4 +1,5 @@
1import { chat, closeMessageEditor, saveChatConditional, saveSettingsDebounced, substituteParams, updateMessageBlock } from '../script.js';1import { chat, closeMessageEditor, event_types, eventSource, saveChatConditional, saveSettingsDebounced, substituteParams, updateMessageBlock } from '../script.js';
2import { getRegexedString, regex_placement } from './extensions/regex/engine.js';
2import { t } from './i18n.js';3import { t } from './i18n.js';
3import { MacrosParser } from './macros.js';4import { MacrosParser } from './macros.js';
4import { Popup } from './popup.js';5import { Popup } from './popup.js';
@@ -7,7 +8,7 @@ import { SlashCommand } from './slash-commands/SlashCommand.js';
7import { ARGUMENT_TYPE, SlashCommandArgument, SlashCommandNamedArgument } from './slash-commands/SlashCommandArgument.js';8import { ARGUMENT_TYPE, SlashCommandArgument, SlashCommandNamedArgument } from './slash-commands/SlashCommandArgument.js';
8import { commonEnumProviders } from './slash-commands/SlashCommandCommonEnumsProvider.js';9import { commonEnumProviders } from './slash-commands/SlashCommandCommonEnumsProvider.js';
9import { SlashCommandParser } from './slash-commands/SlashCommandParser.js';10import { SlashCommandParser } from './slash-commands/SlashCommandParser.js';
10import { copyText } from './utils.js';11import { copyText, escapeRegex, isFalseBoolean } from './utils.js';
1112
12/**13/**
13 * Gets a message from a jQuery element.14 * Gets a message from a jQuery element.
@@ -49,11 +50,12 @@ export class PromptReasoning {
49 * Add reasoning to a message according to the power user settings.50 * Add reasoning to a message according to the power user settings.
50 * @param {string} content Message content51 * @param {string} content Message content
51 * @param {string} reasoning Message reasoning52 * @param {string} reasoning Message reasoning
53 * @param {boolean} isPrefix Whether this is the last message prefix
52 * @returns {string} Message content with reasoning54 * @returns {string} Message content with reasoning
53 */55 */
54 addToMessage(content, reasoning) {56 addToMessage(content, reasoning, isPrefix) {
55 // Disabled or reached limit of additions57 // Disabled or reached limit of additions
56 if (!power_user.reasoning.add_to_prompts || this.counter >= power_user.reasoning.max_additions) {58 if (!isPrefix && (!power_user.reasoning.add_to_prompts || this.counter >= power_user.reasoning.max_additions)) {
57 return content;59 return content;
58 }60 }
5961
@@ -70,6 +72,11 @@ export class PromptReasoning {
70 const separator = substituteParams(power_user.reasoning.separator || '');72 const separator = substituteParams(power_user.reasoning.separator || '');
71 const suffix = substituteParams(power_user.reasoning.suffix || '');73 const suffix = substituteParams(power_user.reasoning.suffix || '');
7274
75 // Combine parts with reasoning only
76 if (isPrefix && !content) {
77 return `${prefix}${reasoning}`;
78 }
79
73 // Combine parts with reasoning and content80 // Combine parts with reasoning and content
74 return `${prefix}${reasoning}${suffix}${separator}${content}`;81 return `${prefix}${reasoning}${suffix}${separator}${content}`;
75 }82 }
@@ -105,11 +112,18 @@ function loadReasoningSettings() {
105 power_user.reasoning.max_additions = Number($(this).val());112 power_user.reasoning.max_additions = Number($(this).val());
106 saveSettingsDebounced();113 saveSettingsDebounced();
107 });114 });
115
116 $('#reasoning_auto_parse').prop('checked', power_user.reasoning.auto_parse);
117 $('#reasoning_auto_parse').on('change', function () {
118 power_user.reasoning.auto_parse = !!$(this).prop('checked');
119 saveSettingsDebounced();
120 });
108}121}
109122
110function registerReasoningSlashCommands() {123function registerReasoningSlashCommands() {
111 SlashCommandParser.addCommandObject(SlashCommand.fromProps({124 SlashCommandParser.addCommandObject(SlashCommand.fromProps({
112 name: 'reasoning-get',125 name: 'reasoning-get',
126 aliases: ['get-reasoning'],
113 returns: ARGUMENT_TYPE.STRING,127 returns: ARGUMENT_TYPE.STRING,
114 helpString: t`Get the contents of a reasoning block of a message. Returns an empty string if the message does not have a reasoning block.`,128 helpString: t`Get the contents of a reasoning block of a message. Returns an empty string if the message does not have a reasoning block.`,
115 unnamedArgumentList: [129 unnamedArgumentList: [
@@ -129,6 +143,7 @@ function registerReasoningSlashCommands() {
129143
130 SlashCommandParser.addCommandObject(SlashCommand.fromProps({144 SlashCommandParser.addCommandObject(SlashCommand.fromProps({
131 name: 'reasoning-set',145 name: 'reasoning-set',
146 aliases: ['set-reasoning'],
132 returns: ARGUMENT_TYPE.STRING,147 returns: ARGUMENT_TYPE.STRING,
133 helpString: t`Set the reasoning block of a message. Returns the reasoning block content.`,148 helpString: t`Set the reasoning block of a message. Returns the reasoning block content.`,
134 namedArgumentList: [149 namedArgumentList: [
@@ -146,7 +161,7 @@ function registerReasoningSlashCommands() {
146 }),161 }),
147 ],162 ],
148 callback: async (args, value) => {163 callback: async (args, value) => {
149 const messageId = !isNaN(Number(args[0])) ? Number(args[0]) : chat.length - 1;164 const messageId = !isNaN(Number(args.at)) ? Number(args.at) : chat.length - 1;
150 const message = chat[messageId];165 const message = chat[messageId];
151 if (!message?.extra) {166 if (!message?.extra) {
152 return '';167 return '';
@@ -160,6 +175,50 @@ function registerReasoningSlashCommands() {
160 return message.extra.reasoning;175 return message.extra.reasoning;
161 },176 },
162 }));177 }));
178
179 SlashCommandParser.addCommandObject(SlashCommand.fromProps({
180 name: 'reasoning-parse',
181 aliases: ['parse-reasoning'],
182 returns: 'reasoning string',
183 helpString: t`Extracts the reasoning block from a string using the Reasoning Formatting settings.`,
184 namedArgumentList: [
185 SlashCommandNamedArgument.fromProps({
186 name: 'regex',
187 description: 'Whether to apply regex scripts to the reasoning content.',
188 typeList: [ARGUMENT_TYPE.BOOLEAN],
189 defaultValue: 'true',
190 isRequired: false,
191 enumProvider: commonEnumProviders.boolean('trueFalse'),
192 }),
193 ],
194 unnamedArgumentList: [
195 SlashCommandArgument.fromProps({
196 description: 'input string',
197 typeList: [ARGUMENT_TYPE.STRING],
198 }),
199 ],
200 callback: (args, value) => {
201 if (!value) {
202 return '';
203 }
204
205 if (!power_user.reasoning.prefix || !power_user.reasoning.suffix) {
206 toastr.warning(t`Both prefix and suffix must be set in the Reasoning Formatting settings.`);
207 return String(value);
208 }
209
210 const parsedReasoning = parseReasoningFromString(String(value));
211
212 if (!parsedReasoning) {
213 return '';
214 }
215
216 const applyRegex = !isFalseBoolean(String(args.regex ?? ''));
217 return applyRegex
218 ? getRegexedString(parsedReasoning.reasoning, regex_placement.REASONING)
219 : parsedReasoning.reasoning;
220 },
221 }));
163}222}
164223
165function registerReasoningMacros() {224function registerReasoningMacros() {
@@ -224,7 +283,7 @@ function setReasoningEventHandlers(){
224 }283 }
225284
226 const textarea = messageBlock.find('.reasoning_edit_textarea');285 const textarea = messageBlock.find('.reasoning_edit_textarea');
227 const reasoning = String(textarea.val());286 const reasoning = getRegexedString(String(textarea.val()), regex_placement.REASONING, { isEdit: true });
228 message.extra.reasoning = reasoning;287 message.extra.reasoning = reasoning;
229 await saveChatConditional();288 await saveChatConditional();
230 updateMessageBlock(messageId, message);289 updateMessageBlock(messageId, message);
@@ -289,9 +348,101 @@ function setReasoningEventHandlers(){
289 });348 });
290}349}
291350
351/**
352 * Parses reasoning from a string using the power user reasoning settings.
353 * @typedef {Object} ParsedReasoning
354 * @property {string} reasoning Reasoning block
355 * @property {string} content Message content
356 * @param {string} str Content of the message
357 * @returns {ParsedReasoning|null} Parsed reasoning block and message content
358 */
359function parseReasoningFromString(str) {
360 // Both prefix and suffix must be defined
361 if (!power_user.reasoning.prefix || !power_user.reasoning.suffix) {
362 return null;
363 }
364
365 try {
366 const regex = new RegExp(`${escapeRegex(power_user.reasoning.prefix)}(.*?)${escapeRegex(power_user.reasoning.suffix)}`, 's');
367
368 let didReplace = false;
369 let reasoning = '';
370 let content = String(str).replace(regex, (_match, captureGroup) => {
371 didReplace = true;
372 reasoning = captureGroup;
373 return '';
374 });
375
376 if (didReplace && power_user.trim_spaces) {
377 reasoning = reasoning.trim();
378 content = content.trim();
379 }
380
381 return { reasoning, content };
382 } catch (error) {
383 console.error('[Reasoning] Error parsing reasoning block', error);
384 return null;
385 }
386}
387
388function registerReasoningAppEvents() {
389 eventSource.makeFirst(event_types.MESSAGE_RECEIVED, (/** @type {number} */ idx) => {
390 if (!power_user.reasoning.auto_parse) {
391 return;
392 }
393
394 console.debug('[Reasoning] Auto-parsing reasoning block for message', idx);
395 const message = chat[idx];
396
397 if (!message) {
398 console.warn('[Reasoning] Message not found', idx);
399 return null;
400 }
401
402 if (!message.mes || message.mes === '...') {
403 console.debug('[Reasoning] Message content is empty or a placeholder', idx);
404 return null;
405 }
406
407 const parsedReasoning = parseReasoningFromString(message.mes);
408
409 // No reasoning block found
410 if (!parsedReasoning) {
411 return;
412 }
413
414 // Make sure the message has an extra object
415 if (!message.extra || typeof message.extra !== 'object') {
416 message.extra = {};
417 }
418
419 const contentUpdated = !!parsedReasoning.reasoning || parsedReasoning.content !== message.mes;
420
421 // If reasoning was found, add it to the message
422 if (parsedReasoning.reasoning) {
423 message.extra.reasoning = getRegexedString(parsedReasoning.reasoning, regex_placement.REASONING);
424 }
425
426 // Update the message text if it was changed
427 if (parsedReasoning.content !== message.mes) {
428 message.mes = parsedReasoning.content;
429 }
430
431 // Find if a message already exists in DOM and must be updated
432 if (contentUpdated) {
433 const messageRendered = document.querySelector(`.mes[mesid="${idx}"]`) !== null;
434 if (messageRendered) {
435 console.debug('[Reasoning] Updating message block', idx);
436 updateMessageBlock(idx, message);
437 }
438 }
439 });
440}
441
292export function initReasoning() {442export function initReasoning() {
293 loadReasoningSettings();443 loadReasoningSettings();
294 setReasoningEventHandlers();444 setReasoningEventHandlers();
295 registerReasoningSlashCommands();445 registerReasoningSlashCommands();
296 registerReasoningMacros();446 registerReasoningMacros();
447 registerReasoningAppEvents();
297}448}
public/scripts/slash-commands.js+124 -45
@@ -59,7 +59,7 @@ import { autoSelectPersona, retriggerFirstMessageOnEmptyChat, setPersonaLockStat
59import { addEphemeralStoppingString, chat_styles, flushEphemeralStoppingStrings, power_user } from './power-user.js';59import { addEphemeralStoppingString, chat_styles, flushEphemeralStoppingStrings, power_user } from './power-user.js';
60import { SERVER_INPUTS, textgen_types, textgenerationwebui_settings } from './textgen-settings.js';60import { SERVER_INPUTS, textgen_types, textgenerationwebui_settings } from './textgen-settings.js';
61import { decodeTextTokens, getAvailableTokenizers, getFriendlyTokenizerName, getTextTokens, getTokenCountAsync, selectTokenizer } from './tokenizers.js';61import { decodeTextTokens, getAvailableTokenizers, getFriendlyTokenizerName, getTextTokens, getTokenCountAsync, selectTokenizer } from './tokenizers.js';
62import { debounce, delay, equalsIgnoreCaseAndAccents, findChar, getCharIndex, isFalseBoolean, isTrueBoolean, onlyUnique, showFontAwesomePicker, stringToRange, trimToEndSentence, trimToStartSentence, waitUntilCondition } from './utils.js';62import { debounce, delay, equalsIgnoreCaseAndAccents, findChar, getCharIndex, isFalseBoolean, isTrueBoolean, onlyUnique, regexFromString, showFontAwesomePicker, stringToRange, trimToEndSentence, trimToStartSentence, waitUntilCondition } from './utils.js';
63import { registerVariableCommands, resolveVariable } from './variables.js';63import { registerVariableCommands, resolveVariable } from './variables.js';
64import { background_settings } from './backgrounds.js';64import { background_settings } from './backgrounds.js';
65import { SlashCommandClosure } from './slash-commands/SlashCommandClosure.js';65import { SlashCommandClosure } from './slash-commands/SlashCommandClosure.js';
@@ -235,7 +235,6 @@ export function initDefaultSlashCommands() {
235 description: 'Character name - or unique character identifier (avatar key)',235 description: 'Character name - or unique character identifier (avatar key)',
236 typeList: [ARGUMENT_TYPE.STRING],236 typeList: [ARGUMENT_TYPE.STRING],
237 enumProvider: commonEnumProviders.characters('character'),237 enumProvider: commonEnumProviders.characters('character'),
238 forceEnum: false,
239 }),238 }),
240 ],239 ],
241 helpString: `240 helpString: `
@@ -274,7 +273,6 @@ export function initDefaultSlashCommands() {
274 typeList: [ARGUMENT_TYPE.STRING],273 typeList: [ARGUMENT_TYPE.STRING],
275 isRequired: true,274 isRequired: true,
276 enumProvider: commonEnumProviders.characters('character'),275 enumProvider: commonEnumProviders.characters('character'),
277 forceEnum: false,
278 }),276 }),
279 SlashCommandNamedArgument.fromProps({277 SlashCommandNamedArgument.fromProps({
280 name: 'avatar',278 name: 'avatar',
@@ -518,7 +516,6 @@ export function initDefaultSlashCommands() {
518 typeList: [ARGUMENT_TYPE.STRING],516 typeList: [ARGUMENT_TYPE.STRING],
519 isRequired: true,517 isRequired: true,
520 enumProvider: commonEnumProviders.characters('all'),518 enumProvider: commonEnumProviders.characters('all'),
521 forceEnum: true,
522 }),519 }),
523 ],520 ],
524 helpString: 'Opens up a chat with the character or group by its name',521 helpString: 'Opens up a chat with the character or group by its name',
@@ -734,6 +731,57 @@ export function initDefaultSlashCommands() {
734 helpString: 'Unhides a message from the prompt.',731 helpString: 'Unhides a message from the prompt.',
735 }));732 }));
736 SlashCommandParser.addCommandObject(SlashCommand.fromProps({733 SlashCommandParser.addCommandObject(SlashCommand.fromProps({
734 name: 'member-get',
735 aliases: ['getmember', 'memberget'],
736 callback: (async ({ field = 'name' }, arg) => {
737 if (!selected_group) {
738 toastr.warning('Cannot run /member-get command outside of a group chat.');
739 return '';
740 }
741 if (field === '') {
742 toastr.warning('\'/member-get field=\' argument required!');
743 return '';
744 }
745 field = field.toString();
746 arg = arg.toString();
747 if (!['name', 'index', 'id', 'avatar'].includes(field)) {
748 toastr.warning('\'/member-get field=\' argument required!');
749 return '';
750 }
751 const isId = !isNaN(parseInt(arg));
752 const groupMember = findGroupMemberId(arg, true);
753 if (!groupMember) {
754 toastr.warn(`No group member found using ${isId ? 'id' : 'string'} ${arg}`);
755 return '';
756 }
757 return groupMember[field];
758 }),
759 namedArgumentList: [
760 SlashCommandNamedArgument.fromProps({
761 name: 'field',
762 description: 'Whether to retrieve the name, index, id, or avatar.',
763 typeList: [ARGUMENT_TYPE.STRING],
764 isRequired: true,
765 defaultValue: 'name',
766 enumList: [
767 new SlashCommandEnumValue('name', 'Character name'),
768 new SlashCommandEnumValue('index', 'Group member index'),
769 new SlashCommandEnumValue('avatar', 'Character avatar'),
770 new SlashCommandEnumValue('id', 'Character index'),
771 ],
772 }),
773 ],
774 unnamedArgumentList: [
775 SlashCommandArgument.fromProps({
776 description: 'member index (starts with 0), name, or avatar',
777 typeList: [ARGUMENT_TYPE.NUMBER, ARGUMENT_TYPE.STRING],
778 isRequired: true,
779 enumProvider: commonEnumProviders.groupMembers(),
780 }),
781 ],
782 helpString: 'Retrieves a group member\'s name, index, id, or avatar.',
783 }));
784 SlashCommandParser.addCommandObject(SlashCommand.fromProps({
737 name: 'member-disable',785 name: 'member-disable',
738 callback: disableGroupMemberCallback,786 callback: disableGroupMemberCallback,
739 aliases: ['disable', 'disablemember', 'memberdisable'],787 aliases: ['disable', 'disablemember', 'memberdisable'],
@@ -843,7 +891,8 @@ export function initDefaultSlashCommands() {
843 helpString: 'Moves a group member down in the group chat list.',891 helpString: 'Moves a group member down in the group chat list.',
844 }));892 }));
845 SlashCommandParser.addCommandObject(SlashCommand.fromProps({893 SlashCommandParser.addCommandObject(SlashCommand.fromProps({
846 name: 'peek',894 name: 'member-peek',
895 aliases: ['peek', 'memberpeek', 'peekmember'],
847 callback: peekCallback,896 callback: peekCallback,
848 unnamedArgumentList: [897 unnamedArgumentList: [
849 SlashCommandArgument.fromProps({898 SlashCommandArgument.fromProps({
@@ -1009,7 +1058,6 @@ export function initDefaultSlashCommands() {
1009 typeList: [ARGUMENT_TYPE.STRING],1058 typeList: [ARGUMENT_TYPE.STRING],
1010 defaultValue: 'System',1059 defaultValue: 'System',
1011 enumProvider: () => [...commonEnumProviders.characters('character')(), new SlashCommandEnumValue('System', null, enumTypes.enum, enumIcons.assistant)],1060 enumProvider: () => [...commonEnumProviders.characters('character')(), new SlashCommandEnumValue('System', null, enumTypes.enum, enumIcons.assistant)],
1012 forceEnum: false,
1013 }),1061 }),
1014 new SlashCommandNamedArgument(1062 new SlashCommandNamedArgument(
1015 'length', 'API response length in tokens', [ARGUMENT_TYPE.NUMBER], false,1063 'length', 'API response length in tokens', [ARGUMENT_TYPE.NUMBER], false,
@@ -1903,7 +1951,7 @@ export function initDefaultSlashCommands() {
1903 returns: 'uppercase string',1951 returns: 'uppercase string',
1904 unnamedArgumentList: [1952 unnamedArgumentList: [
1905 new SlashCommandArgument(1953 new SlashCommandArgument(
1906 'string', [ARGUMENT_TYPE.STRING], true, false,1954 'text to affect', [ARGUMENT_TYPE.STRING], true, false,
1907 ),1955 ),
1908 ],1956 ],
1909 helpString: 'Converts the provided string to uppercase.',1957 helpString: 'Converts the provided string to uppercase.',
@@ -1915,7 +1963,7 @@ export function initDefaultSlashCommands() {
1915 returns: 'lowercase string',1963 returns: 'lowercase string',
1916 unnamedArgumentList: [1964 unnamedArgumentList: [
1917 new SlashCommandArgument(1965 new SlashCommandArgument(
1918 'string', [ARGUMENT_TYPE.STRING], true, false,1966 'text to affect', [ARGUMENT_TYPE.STRING], true, false,
1919 ),1967 ),
1920 ],1968 ],
1921 helpString: 'Converts the provided string to lowercase.',1969 helpString: 'Converts the provided string to lowercase.',
@@ -1935,7 +1983,7 @@ export function initDefaultSlashCommands() {
1935 ],1983 ],
1936 unnamedArgumentList: [1984 unnamedArgumentList: [
1937 new SlashCommandArgument(1985 new SlashCommandArgument(
1938 'string', [ARGUMENT_TYPE.STRING], true, false,1986 'text to affect', [ARGUMENT_TYPE.STRING], true, false,
1939 ),1987 ),
1940 ],1988 ],
1941 helpString: `1989 helpString: `
@@ -1999,6 +2047,62 @@ export function initDefaultSlashCommands() {
1999 return '';2047 return '';
2000 },2048 },
2001 }));2049 }));
2050 SlashCommandParser.addCommandObject(SlashCommand.fromProps({
2051 name: 'replace',
2052 aliases: ['re'],
2053 callback: (async ({ mode = 'literal', pattern, replacer = '' }, text) => {
2054 if (pattern === '')
2055 throw new Error('Argument of \'pattern=\' cannot be empty');
2056 switch (mode) {
2057 case 'literal':
2058 return text.replaceAll(pattern, replacer);
2059 case 'regex':
2060 return text.replace(regexFromString(pattern), replacer);
2061 default:
2062 throw new Error('Invalid \'/replace mode=\' argument specified!');
2063 }
2064 }),
2065 returns: 'replaced text',
2066 namedArgumentList: [
2067 SlashCommandNamedArgument.fromProps({
2068 name: 'mode',
2069 description: 'Replaces occurrence(s) of a pattern',
2070 typeList: [ARGUMENT_TYPE.STRING],
2071 defaultValue: 'literal',
2072 enumList: ['literal', 'regex'],
2073 }),
2074 new SlashCommandNamedArgument(
2075 'pattern', 'pattern to search with', [ARGUMENT_TYPE.STRING], true, false,
2076 ),
2077 new SlashCommandNamedArgument(
2078 'replacer', 'replacement text for matches', [ARGUMENT_TYPE.STRING], false, false, '',
2079 ),
2080 ],
2081 unnamedArgumentList: [
2082 new SlashCommandArgument(
2083 'text to affect', [ARGUMENT_TYPE.STRING], true, false,
2084 ),
2085 ],
2086 helpString: `
2087 <div>
2088 Replaces text within the provided string based on the pattern.
2089 </div>
2090 <div>
2091 If <code>mode</code> is <code>literal</code> (or omitted), <code>pattern</code> is a literal search string (case-sensitive).<br />
2092 If <code>mode</code> is <code>regex</code>, <code>pattern</code> is parsed as an ECMAScript Regular Expression.<br />
2093 The <code>replacer</code> replaces based on the <code>pattern</code> in the input text.<br />
2094 If <code>replacer</code> is omitted, the replacement(s) will be an empty string.<br />
2095 </div>
2096 <div>
2097 <strong>Example:</strong>
2098 <pre>/let x Blue house and blue car || </pre>
2099 <pre>/replace pattern="blue" {{var::x}} | /echo |/# Blue house and car ||</pre>
2100 <pre>/replace pattern="blue" replacer="red" {{var::x}} | /echo |/# Blue house and red car ||</pre>
2101 <pre>/replace mode=regex pattern="/blue/i" replacer="red" {{var::x}} | /echo |/# red house and blue car ||</pre>
2102 <pre>/replace mode=regex pattern="/blue/gi" replacer="red" {{var::x}} | /echo |/# red house and red car ||</pre>
2103 </div>
2104 `,
2105 }));
20022106
2003 registerVariableCommands();2107 registerVariableCommands();
2004}2108}
@@ -2991,7 +3095,7 @@ function performGroupMemberAction(chid, action) {
29913095
2992async function disableGroupMemberCallback(_, arg) {3096async function disableGroupMemberCallback(_, arg) {
2993 if (!selected_group) {3097 if (!selected_group) {
2994 toastr.warning('Cannot run /disable command outside of a group chat.');3098 toastr.warning('Cannot run /member-disable command outside of a group chat.');
2995 return '';3099 return '';
2996 }3100 }
29973101
@@ -3008,7 +3112,7 @@ async function disableGroupMemberCallback(_, arg) {
30083112
3009async function enableGroupMemberCallback(_, arg) {3113async function enableGroupMemberCallback(_, arg) {
3010 if (!selected_group) {3114 if (!selected_group) {
3011 toastr.warning('Cannot run /enable command outside of a group chat.');3115 toastr.warning('Cannot run /member-enable command outside of a group chat.');
3012 return '';3116 return '';
3013 }3117 }
30143118
@@ -3025,7 +3129,7 @@ async function enableGroupMemberCallback(_, arg) {
30253129
3026async function moveGroupMemberUpCallback(_, arg) {3130async function moveGroupMemberUpCallback(_, arg) {
3027 if (!selected_group) {3131 if (!selected_group) {
3028 toastr.warning('Cannot run /memberup command outside of a group chat.');3132 toastr.warning('Cannot run /member-up command outside of a group chat.');
3029 return '';3133 return '';
3030 }3134 }
30313135
@@ -3042,7 +3146,7 @@ async function moveGroupMemberUpCallback(_, arg) {
30423146
3043async function moveGroupMemberDownCallback(_, arg) {3147async function moveGroupMemberDownCallback(_, arg) {
3044 if (!selected_group) {3148 if (!selected_group) {
3045 toastr.warning('Cannot run /memberdown command outside of a group chat.');3149 toastr.warning('Cannot run /member-down command outside of a group chat.');
3046 return '';3150 return '';
3047 }3151 }
30483152
@@ -3059,12 +3163,12 @@ async function moveGroupMemberDownCallback(_, arg) {
30593163
3060async function peekCallback(_, arg) {3164async function peekCallback(_, arg) {
3061 if (!selected_group) {3165 if (!selected_group) {
3062 toastr.warning('Cannot run /peek command outside of a group chat.');3166 toastr.warning('Cannot run /member-peek command outside of a group chat.');
3063 return '';3167 return '';
3064 }3168 }
30653169
3066 if (is_group_generating) {3170 if (is_group_generating) {
3067 toastr.warning('Cannot run /peek command while the group reply is generating.');3171 toastr.warning('Cannot run /member-peek command while the group reply is generating.');
3068 return '';3172 return '';
3069 }3173 }
30703174
@@ -3081,12 +3185,7 @@ async function peekCallback(_, arg) {
30813185
3082async function removeGroupMemberCallback(_, arg) {3186async function removeGroupMemberCallback(_, arg) {
3083 if (!selected_group) {3187 if (!selected_group) {
3084 toastr.warning('Cannot run /memberremove command outside of a group chat.');3188 toastr.warning('Cannot run /member-remove command outside of a group chat.');
3085 return '';
3086 }
3087
3088 if (is_group_generating) {
3089 toastr.warning('Cannot run /memberremove command while the group reply is generating.');
3090 return '';3189 return '';
3091 }3190 }
30923191
@@ -3194,12 +3293,7 @@ function findPersonaByName(name) {
3194}3293}
31953294
3196async function sendUserMessageCallback(args, text) {3295async function sendUserMessageCallback(args, text) {
3197 if (!text) {3296 text = String(text ?? '').trim();
3198 toastr.warning('You must specify text to send');
3199 return;
3200 }
3201
3202 text = text.trim();
3203 const compact = isTrueBoolean(args?.compact);3297 const compact = isTrueBoolean(args?.compact);
3204 const bias = extractMessageBias(text);3298 const bias = extractMessageBias(text);
32053299
@@ -3508,13 +3602,7 @@ export function getNameAndAvatarForMessage(character, name = null) {
3508}3602}
35093603
3510export async function sendMessageAs(args, text) {3604export async function sendMessageAs(args, text) {
3511 if (!text) {
3512 toastr.warning('You must specify text to send as');
3513 return '';
3514 }
3515
3516 let name = args.name?.trim();3605 let name = args.name?.trim();
3517 let mesText;
35183606
3519 if (!name) {3607 if (!name) {
3520 const namelessWarningKey = 'sendAsNamelessWarningShown';3608 const namelessWarningKey = 'sendAsNamelessWarningShown';
@@ -3525,7 +3613,7 @@ export async function sendMessageAs(args, text) {
3525 name = name2;3613 name = name2;
3526 }3614 }
35273615
3528 mesText = text.trim();3616 let mesText = String(text ?? '').trim();
35293617
3530 // Requires a regex check after the slash command is pushed to output3618 // Requires a regex check after the slash command is pushed to output
3531 mesText = getRegexedString(mesText, regex_placement.SLASH_COMMAND, { characterOverride: name });3619 mesText = getRegexedString(mesText, regex_placement.SLASH_COMMAND, { characterOverride: name });
@@ -3603,11 +3691,7 @@ export async function sendMessageAs(args, text) {
3603}3691}
36043692
3605export async function sendNarratorMessage(args, text) {3693export async function sendNarratorMessage(args, text) {
3606 if (!text) {3694 text = String(text ?? '');
3607 toastr.warning('You must specify text to send');
3608 return '';
3609 }
3610
3611 const name = chat_metadata[NARRATOR_NAME_KEY] || NARRATOR_NAME_DEFAULT;3695 const name = chat_metadata[NARRATOR_NAME_KEY] || NARRATOR_NAME_DEFAULT;
3612 // Messages that do nothing but set bias will be hidden from the context3696 // Messages that do nothing but set bias will be hidden from the context
3613 const bias = extractMessageBias(text);3697 const bias = extractMessageBias(text);
@@ -3698,18 +3782,13 @@ export async function promptQuietForLoudResponse(who, text) {
3698}3782}
36993783
3700async function sendCommentMessage(args, text) {3784async function sendCommentMessage(args, text) {
3701 if (!text) {
3702 toastr.warning('You must specify text to send');
3703 return '';
3704 }
3705
3706 const compact = isTrueBoolean(args?.compact);3785 const compact = isTrueBoolean(args?.compact);
3707 const message = {3786 const message = {
3708 name: COMMENT_NAME_DEFAULT,3787 name: COMMENT_NAME_DEFAULT,
3709 is_user: false,3788 is_user: false,
3710 is_system: true,3789 is_system: true,
3711 send_date: getMessageTimeStamp(),3790 send_date: getMessageTimeStamp(),
3712 mes: substituteParams(text.trim()),3791 mes: substituteParams(String(text ?? '').trim()),
3713 force_avatar: comment_avatar,3792 force_avatar: comment_avatar,
3714 extra: {3793 extra: {
3715 type: system_message_types.COMMENT,3794 type: system_message_types.COMMENT,
public/scripts/st-context.js+11 -0
@@ -69,6 +69,7 @@ import { textgenerationwebui_settings } from './textgen-settings.js';
69import { tokenizers, getTextTokens, getTokenCount, getTokenCountAsync, getTokenizerModel } from './tokenizers.js';69import { tokenizers, getTextTokens, getTokenCount, getTokenCountAsync, getTokenizerModel } from './tokenizers.js';
70import { ToolManager } from './tool-calling.js';70import { ToolManager } from './tool-calling.js';
71import { timestampToMoment, uuidv4 } from './utils.js';71import { timestampToMoment, uuidv4 } from './utils.js';
72import { getGlobalVariable, getLocalVariable, setGlobalVariable, setLocalVariable } from './variables.js';
7273
73export function getContext() {74export function getContext() {
74 return {75 return {
@@ -175,6 +176,16 @@ export function getContext() {
175 humanizedDateTime,176 humanizedDateTime,
176 updateMessageBlock,177 updateMessageBlock,
177 appendMediaToMessage,178 appendMediaToMessage,
179 variables: {
180 local: {
181 get: getLocalVariable,
182 set: setLocalVariable,
183 },
184 global: {
185 get: getGlobalVariable,
186 set: setGlobalVariable,
187 },
188 },
178 };189 };
179}190}
180191
public/scripts/textgen-models.js+6 -0
@@ -54,6 +54,12 @@ const OPENROUTER_PROVIDERS = [
54 'xAI',54 'xAI',
55 'Cloudflare',55 'Cloudflare',
56 'SF Compute',56 'SF Compute',
57 'Minimax',
58 'Nineteen',
59 'Liquid',
60 'Nebius',
61 'Chutes',
62 'Kluster',
57 '01.AI',63 '01.AI',
58 'HuggingFace',64 'HuggingFace',
59 'Mancer',65 'Mancer',
public/scripts/textgen-settings.js+7 -2
@@ -172,6 +172,7 @@ const settings = {
172 //truncation_length: 2048,172 //truncation_length: 2048,
173 ban_eos_token: false,173 ban_eos_token: false,
174 skip_special_tokens: true,174 skip_special_tokens: true,
175 include_reasoning: true,
175 streaming: false,176 streaming: false,
176 mirostat_mode: 0,177 mirostat_mode: 0,
177 mirostat_tau: 5,178 mirostat_tau: 5,
@@ -263,6 +264,7 @@ export const setting_names = [
263 'add_bos_token',264 'add_bos_token',
264 'ban_eos_token',265 'ban_eos_token',
265 'skip_special_tokens',266 'skip_special_tokens',
267 'include_reasoning',
266 'streaming',268 'streaming',
267 'mirostat_mode',269 'mirostat_mode',
268 'mirostat_tau',270 'mirostat_tau',
@@ -501,7 +503,7 @@ export function loadTextGenSettings(data, loadedSettings) {
501 for (const [type, selector] of Object.entries(SERVER_INPUTS)) {503 for (const [type, selector] of Object.entries(SERVER_INPUTS)) {
502 const control = $(selector);504 const control = $(selector);
503 control.val(settings.server_urls[type] ?? '').on('input', function () {505 control.val(settings.server_urls[type] ?? '').on('input', function () {
504 settings.server_urls[type] = String($(this).val());506 settings.server_urls[type] = String($(this).val()).trim();
505 saveSettingsDebounced();507 saveSettingsDebounced();
506 });508 });
507 }509 }
@@ -740,6 +742,7 @@ jQuery(function () {
740 'add_bos_token_textgenerationwebui': true,742 'add_bos_token_textgenerationwebui': true,
741 'temperature_last_textgenerationwebui': true,743 'temperature_last_textgenerationwebui': true,
742 'skip_special_tokens_textgenerationwebui': true,744 'skip_special_tokens_textgenerationwebui': true,
745 'include_reasoning_textgenerationwebui': true,
743 'top_a_textgenerationwebui': 0,746 'top_a_textgenerationwebui': 0,
744 'top_a_counter_textgenerationwebui': 0,747 'top_a_counter_textgenerationwebui': 0,
745 'mirostat_mode_textgenerationwebui': 0,748 'mirostat_mode_textgenerationwebui': 0,
@@ -986,7 +989,7 @@ export async function generateTextGenWithStreaming(generate_data, signal) {
986 let logprobs = null;989 let logprobs = null;
987 const swipes = [];990 const swipes = [];
988 const toolCalls = [];991 const toolCalls = [];
989 const state = {};992 const state = { reasoning: '' };
990 while (true) {993 while (true) {
991 const { done, value } = await reader.read();994 const { done, value } = await reader.read();
992 if (done) return;995 if (done) return;
@@ -1003,6 +1006,7 @@ export async function generateTextGenWithStreaming(generate_data, signal) {
1003 const newText = data?.choices?.[0]?.text || data?.content || '';1006 const newText = data?.choices?.[0]?.text || data?.content || '';
1004 text += newText;1007 text += newText;
1005 logprobs = parseTextgenLogprobs(newText, data.choices?.[0]?.logprobs || data?.completion_probabilities);1008 logprobs = parseTextgenLogprobs(newText, data.choices?.[0]?.logprobs || data?.completion_probabilities);
1009 state.reasoning += data?.choices?.[0]?.reasoning ?? '';
1006 }1010 }
10071011
1008 yield { text, swipes, logprobs, toolCalls, state };1012 yield { text, swipes, logprobs, toolCalls, state };
@@ -1266,6 +1270,7 @@ export function getTextGenGenerationData(finalPrompt, maxTokens, isImpersonate,
1266 'truncation_length': max_context,1270 'truncation_length': max_context,
1267 'ban_eos_token': settings.ban_eos_token,1271 'ban_eos_token': settings.ban_eos_token,
1268 'skip_special_tokens': settings.skip_special_tokens,1272 'skip_special_tokens': settings.skip_special_tokens,
1273 'include_reasoning': settings.include_reasoning,
1269 'top_a': settings.top_a,1274 'top_a': settings.top_a,
1270 'tfs': settings.tfs,1275 'tfs': settings.tfs,
1271 'epsilon_cutoff': [OOBA, MANCER].includes(settings.type) ? settings.epsilon_cutoff : undefined,1276 'epsilon_cutoff': [OOBA, MANCER].includes(settings.type) ? settings.epsilon_cutoff : undefined,
public/scripts/tokenizers.js+3 -0
@@ -679,6 +679,9 @@ export function getTokenizerModel() {
679 }679 }
680680
681 if (oai_settings.chat_completion_source === chat_completion_sources.PERPLEXITY) {681 if (oai_settings.chat_completion_source === chat_completion_sources.PERPLEXITY) {
682 if (oai_settings.perplexity_model.includes('sonar-reasoning')) {
683 return deepseekTokenizer;
684 }
682 if (oai_settings.perplexity_model.includes('llama-3') || oai_settings.perplexity_model.includes('llama3')) {685 if (oai_settings.perplexity_model.includes('llama-3') || oai_settings.perplexity_model.includes('llama3')) {
683 return llama3Tokenizer;686 return llama3Tokenizer;
684 }687 }
public/scripts/utils.js+7 -7
@@ -1733,17 +1733,17 @@ export function hasAnimation(control) {
17331733
1734/**1734/**
1735 * Run an action once an animation on a control ends. If the control has no animation, the action will be executed immediately.1735 * Run an action once an animation on a control ends. If the control has no animation, the action will be executed immediately.
1736 *1736 * The action will be executed after the animation ends or after the timeout, whichever comes first.
1737 * @param {HTMLElement} control - The control element to listen for animation end event1737 * @param {HTMLElement} control - The control element to listen for animation end event
1738 * @param {(control:*?) => void} callback - The callback function to be executed when the animation ends1738 * @param {(control:*?) => void} callback - The callback function to be executed when the animation ends
1739 * @param {number} [timeout=500] - The timeout in milliseconds to wait for the animation to end before executing the callback
1739 */1740 */
1740export function runAfterAnimation(control, callback) {1741export function runAfterAnimation(control, callback, timeout = 500) {
1741 if (hasAnimation(control)) {1742 if (hasAnimation(control)) {
1742 const onAnimationEnd = () => {1743 Promise.race([
1743 control.removeEventListener('animationend', onAnimationEnd);1744 new Promise((r) => setTimeout(r, timeout)), // Fallback timeout
1744 callback(control);1745 new Promise((r) => control.addEventListener('animationend', r, { once: true })),
1745 };1746 ]).finally(() => callback(control));
1746 control.addEventListener('animationend', onAnimationEnd);
1747 } else {1747 } else {
1748 callback(control);1748 callback(control);
1749 }1749 }
public/scripts/variables.js+4 -4
@@ -19,7 +19,7 @@ import { isFalseBoolean, convertValueType, isTrueBoolean } from './utils.js';
1919
20const MAX_LOOPS = 100;20const MAX_LOOPS = 100;
2121
22function getLocalVariable(name, args = {}) {22export function getLocalVariable(name, args = {}) {
23 if (!chat_metadata.variables) {23 if (!chat_metadata.variables) {
24 chat_metadata.variables = {};24 chat_metadata.variables = {};
25 }25 }
@@ -45,7 +45,7 @@ function getLocalVariable(name, args = {}) {
45 return (localVariable?.trim?.() === '' || isNaN(Number(localVariable))) ? (localVariable || '') : Number(localVariable);45 return (localVariable?.trim?.() === '' || isNaN(Number(localVariable))) ? (localVariable || '') : Number(localVariable);
46}46}
4747
48function setLocalVariable(name, value, args = {}) {48export function setLocalVariable(name, value, args = {}) {
49 if (!name) {49 if (!name) {
50 throw new Error('Variable name cannot be empty or undefined.');50 throw new Error('Variable name cannot be empty or undefined.');
51 }51 }
@@ -80,7 +80,7 @@ function setLocalVariable(name, value, args = {}) {
80 return value;80 return value;
81}81}
8282
83function getGlobalVariable(name, args = {}) {83export function getGlobalVariable(name, args = {}) {
84 let globalVariable = extension_settings.variables.global[args.key ?? name];84 let globalVariable = extension_settings.variables.global[args.key ?? name];
85 if (args.index !== undefined) {85 if (args.index !== undefined) {
86 try {86 try {
@@ -102,7 +102,7 @@ function getGlobalVariable(name, args = {}) {
102 return (globalVariable?.trim?.() === '' || isNaN(Number(globalVariable))) ? (globalVariable || '') : Number(globalVariable);102 return (globalVariable?.trim?.() === '' || isNaN(Number(globalVariable))) ? (globalVariable || '') : Number(globalVariable);
103}103}
104104
105function setGlobalVariable(name, value, args = {}) {105export function setGlobalVariable(name, value, args = {}) {
106 if (!name) {106 if (!name) {
107 throw new Error('Variable name cannot be empty or undefined.');107 throw new Error('Variable name cannot be empty or undefined.');
108 }108 }
public/style.css+5 -17
@@ -399,6 +399,11 @@ input[type='checkbox']:focus-visible {
399 color: var(--SmartThemeEmColor);399 color: var(--SmartThemeEmColor);
400}400}
401401
402.mes_text q i,
403.mes_text q em {
404 color: inherit;
405}
406
402.mes_text u,407.mes_text u,
403.mes_reasoning u {408.mes_reasoning u {
404 color: var(--SmartThemeUnderlineColor);409 color: var(--SmartThemeUnderlineColor);
@@ -4597,23 +4602,6 @@ body .ui-widget-content li:hover {
4597 opacity: 1;4602 opacity: 1;
4598}4603}
45994604
4600.typing_indicator {
4601 position: sticky;
4602 bottom: 10px;
4603 margin: 10px;
4604 opacity: 0.85;
4605 text-shadow: 0px 0px calc(var(--shadowWidth) * 1px) var(--SmartThemeShadowColor);
4606 order: 9999;
4607}
4608
4609.typing_indicator:after {
4610 display: inline-block;
4611 vertical-align: bottom;
4612 animation: ellipsis steps(4, end) 1500ms infinite;
4613 content: "";
4614 width: 0px;
4615}
4616
4617#group_avatar_preview .missing-avatar {4605#group_avatar_preview .missing-avatar {
4618 display: inline;4606 display: inline;
4619 vertical-align: middle;4607 vertical-align: middle;
src/constants.js+1 -0
@@ -369,6 +369,7 @@ export const OPENROUTER_KEYS = [
369 'prompt',369 'prompt',
370 'stop',370 'stop',
371 'provider',371 'provider',
372 'include_reasoning',
372];373];
373374
374// https://github.com/vllm-project/vllm/blob/0f8a91401c89ac0a8018def3756829611b57727f/vllm/entrypoints/openai/protocol.py#L220375// https://github.com/vllm-project/vllm/blob/0f8a91401c89ac0a8018def3756829611b57727f/vllm/entrypoints/openai/protocol.py#L220
src/endpoints/backends/chat-completions.js+2 -2
@@ -288,7 +288,7 @@ async function sendMakerSuiteRequest(request, response) {
288288
289 const model = String(request.body.model);289 const model = String(request.body.model);
290 const stream = Boolean(request.body.stream);290 const stream = Boolean(request.body.stream);
291 const showThoughts = Boolean(request.body.show_thoughts);291 const showThoughts = Boolean(request.body.include_reasoning);
292 const isThinking = model.includes('thinking');292 const isThinking = model.includes('thinking');
293293
294 const generationConfig = {294 const generationConfig = {
@@ -1000,7 +1000,7 @@ router.post('/generate', jsonParser, function (request, response) {
1000 bodyParams['route'] = 'fallback';1000 bodyParams['route'] = 'fallback';
1001 }1001 }
10021002
1003 if (request.body.show_thoughts) {1003 if (request.body.include_reasoning) {
1004 bodyParams['include_reasoning'] = true;1004 bodyParams['include_reasoning'] = true;
1005 }1005 }
10061006
src/endpoints/search.js+14 -3
@@ -162,7 +162,7 @@ router.post('/transcript', jsonParser, async (request, response) => {
162162
163router.post('/searxng', jsonParser, async (request, response) => {163router.post('/searxng', jsonParser, async (request, response) => {
164 try {164 try {
165 const { baseUrl, query, preferences } = request.body;165 const { baseUrl, query, preferences, categories } = request.body;
166166
167 if (!baseUrl || !query) {167 if (!baseUrl || !query) {
168 console.error('Missing required parameters for /searxng');168 console.error('Missing required parameters for /searxng');
@@ -193,6 +193,9 @@ router.post('/searxng', jsonParser, async (request, response) => {
193 if (preferences) {193 if (preferences) {
194 searchParams.append('preferences', preferences);194 searchParams.append('preferences', preferences);
195 }195 }
196 if (categories) {
197 searchParams.append('categories', categories);
198 }
196 searchUrl.search = searchParams.toString();199 searchUrl.search = searchParams.toString();
197200
198 const searchResult = await fetch(searchUrl, { headers: visitHeaders });201 const searchResult = await fetch(searchUrl, { headers: visitHeaders });
@@ -220,7 +223,7 @@ router.post('/tavily', jsonParser, async (request, response) => {
220 return response.sendStatus(400);223 return response.sendStatus(400);
221 }224 }
222225
223 const { query } = request.body;226 const { query, include_images } = request.body;
224227
225 const body = {228 const body = {
226 query: query,229 query: query,
@@ -229,7 +232,7 @@ router.post('/tavily', jsonParser, async (request, response) => {
229 topic: 'general',232 topic: 'general',
230 include_answer: true,233 include_answer: true,
231 include_raw_content: false,234 include_raw_content: false,
232 include_images: false,235 include_images: !!include_images,
233 include_image_descriptions: false,236 include_image_descriptions: false,
234 include_domains: [],237 include_domains: [],
235 max_results: 10,238 max_results: 10,
@@ -297,6 +300,7 @@ router.post('/koboldcpp', jsonParser, async (request, response) => {
297router.post('/visit', jsonParser, async (request, response) => {300router.post('/visit', jsonParser, async (request, response) => {
298 try {301 try {
299 const url = request.body.url;302 const url = request.body.url;
303 const html = Boolean(request.body.html ?? true);
300304
301 if (!url) {305 if (!url) {
302 console.error('No url provided for /visit');306 console.error('No url provided for /visit');
@@ -340,6 +344,8 @@ router.post('/visit', jsonParser, async (request, response) => {
340 }344 }
341345
342 const contentType = String(result.headers.get('content-type'));346 const contentType = String(result.headers.get('content-type'));
347
348 if (html) {
343 if (!contentType.includes('text/html')) {349 if (!contentType.includes('text/html')) {
344 console.error(`Visit failed, content-type is ${contentType}, expected text/html`);350 console.error(`Visit failed, content-type is ${contentType}, expected text/html`);
345 return response.sendStatus(500);351 return response.sendStatus(500);
@@ -347,6 +353,11 @@ router.post('/visit', jsonParser, async (request, response) => {
347353
348 const text = await result.text();354 const text = await result.text();
349 return response.send(text);355 return response.send(text);
356 }
357
358 response.setHeader('Content-Type', contentType);
359 const buffer = await result.arrayBuffer();
360 return response.send(Buffer.from(buffer));
350 } catch (error) {361 } catch (error) {
351 console.error(error);362 console.error(error);
352 return response.sendStatus(500);363 return response.sendStatus(500);
src/endpoints/stable-diffusion.js+1 -1
@@ -533,6 +533,7 @@ comfy.post('/delete-workflow', jsonParser, async (request, response) => {
533533
534comfy.post('/generate', jsonParser, async (request, response) => {534comfy.post('/generate', jsonParser, async (request, response) => {
535 try {535 try {
536 let item;
536 const url = new URL(urlJoin(request.body.url, '/prompt'));537 const url = new URL(urlJoin(request.body.url, '/prompt'));
537538
538 const controller = new AbortController();539 const controller = new AbortController();
@@ -557,7 +558,6 @@ comfy.post('/generate', jsonParser, async (request, response) => {
557 /** @type {any} */558 /** @type {any} */
558 const data = await promptResult.json();559 const data = await promptResult.json();
559 const id = data.prompt_id;560 const id = data.prompt_id;
560 let item;
561 const historyUrl = new URL(urlJoin(request.body.url, '/history'));561 const historyUrl = new URL(urlJoin(request.body.url, '/history'));
562 while (true) {562 while (true) {
563 const result = await fetch(historyUrl);563 const result = await fetch(historyUrl);
src/endpoints/tokenizers.js+1 -1
@@ -398,7 +398,7 @@ function getWebTokenizersChunks(tokenizer, ids) {
398 * @returns {string} Tokenizer model to use398 * @returns {string} Tokenizer model to use
399 */399 */
400export function getTokenizerModel(requestModel) {400export function getTokenizerModel(requestModel) {
401 if (requestModel.includes('o1-preview') || requestModel.includes('o1-mini')) {401 if (requestModel.includes('o1-preview') || requestModel.includes('o1-mini') || requestModel.includes('o3-mini')) {
402 return 'gpt-4o';402 return 'gpt-4o';
403 }403 }
404404