Merge branch 'staging' into persona-improvements

6081b9f0cee884abc95c8d0d1f0eb78dadba12fb

Wolfsblvt <wolfsblvt@gmail.com>

19 files changed, +664 -100Ignore whitespace
.github/readme.md+1 -1
@@ -274,7 +274,7 @@ You will need two mandatory directory mappings and a port mapping to allow Silly
2741. Open your Command Line2741. Open your Command Line
2752. Run the following command2752. Run the following command
276276
277`docker create --name='sillytavern' --net='[DockerNet]' -p '8000:8000/tcp' -v '[plugins]':'/home/node/app/plugins':'rw' -v '[config]':'/home/node/app/config':'rw' -v '[data]':'/home/node/app/data':'rw' -v '[extensions]':'/home/node/app/public/scripts/extensions/third-party':'rw' 'ghcr.io/sillytavern/sillytavern:[version]'`277`docker run --name='sillytavern' --net='[DockerNet]' -p '8000:8000/tcp' -v '[plugins]':'/home/node/app/plugins':'rw' -v '[config]':'/home/node/app/config':'rw' -v '[data]':'/home/node/app/data':'rw' -v '[extensions]':'/home/node/app/public/scripts/extensions/third-party':'rw' 'ghcr.io/sillytavern/sillytavern:[version]'`
278278
279> Note that 8000 is a default listening port. Don't forget to use an appropriate port if you change it in the config.279> Note that 8000 is a default listening port. Don't forget to use an appropriate port if you change it in the config.
280280
public/index.html+51 -6
@@ -1977,11 +1977,11 @@
1977 </span>1977 </span>
1978 </div>1978 </div>
1979 </div>1979 </div>
1980 <div class="range-block" data-source="makersuite,deepseek">1980 <div class="range-block" data-source="makersuite,deepseek,openrouter">
1981 <label for="openai_show_thoughts" class="checkbox_label widthFreeExpand">1981 <label for="openai_show_thoughts" class="checkbox_label widthFreeExpand">
1982 <input id="openai_show_thoughts" type="checkbox" />1982 <input id="openai_show_thoughts" type="checkbox" />
1983 <span>1983 <span>
1984 <span data-i18n="Show model thoughts">Show model thoughts</span>1984 <span data-i18n="Show model reasoning">Show model reasoning</span>
1985 <i class="opacity50p fa-solid fa-circle-info" title="Gemini 2.0 Thinking / DeepSeek Reasoner"></i>1985 <i class="opacity50p fa-solid fa-circle-info" title="Gemini 2.0 Thinking / DeepSeek Reasoner"></i>
1986 </span>1986 </span>
1987 </label>1987 </label>
@@ -3800,6 +3800,39 @@
3800 </div>3800 </div>
3801 </div>3801 </div>
3802 <div>3802 <div>
3803 <h4 class="standoutHeader">
3804 <span data-i18n="Reasoning">Reasoning</span>
3805 </h4>
3806 <div>
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">
3808 <input id="reasoning_add_to_prompts" type="checkbox" />
3809 <small data-i18n="Add Reasoning to Prompts">
3810 Add Reasoning to Prompts
3811 </small>
3812 </label>
3813 <div class="flex-container">
3814 <div class="flex1" title="Inserted before the reasoning content." data-i18n="[title]reasoning_prefix">
3815 <small data-i18n="Prefix">Prefix</small>
3816 <textarea id="reasoning_prefix" class="text_pole textarea_compact autoSetHeight"></textarea>
3817 </div>
3818 <div class="flex1" title="Inserted after the reasoning content." data-i18n="[title]reasoning_suffix">
3819 <small data-i18n="Suffix">Suffix</small>
3820 <textarea id="reasoning_suffix" class="text_pole textarea_compact autoSetHeight"></textarea>
3821 </div>
3822 </div>
3823 <div class="flex-container">
3824 <div class="flex1" title="Inserted between the reasoning and the message content." data-i18n="[title]reasoning_separator">
3825 <small data-i18n="Separator">Separator</small>
3826 <textarea id="reasoning_separator" class="text_pole textarea_compact autoSetHeight"></textarea>
3827 </div>
3828 <div class="flex1" title="Maximum number of reasoning blocks to be added per prompt, counting from the last message." data-i18n="[title]reasoning_max_additions">
3829 <small data-i18n="Max Additions">Max Additions</small>
3830 <input id="reasoning_max_additions" class="text_pole textarea_compact" type="number" min="0" max="999"></textarea>
3831 </div>
3832 </div>
3833 </div>
3834 </div>
3835 <div>
3803 <h4 class="standoutHeader" data-i18n="Miscellaneous">Miscellaneous</h4>3836 <h4 class="standoutHeader" data-i18n="Miscellaneous">Miscellaneous</h4>
3804 <div>3837 <div>
3805 <small>3838 <small>
@@ -6265,14 +6298,26 @@
6265 <div class="mes_edit_buttons">6298 <div class="mes_edit_buttons">
6266 <div class="mes_edit_done menu_button fa-solid fa-check" title="Confirm" data-i18n="[title]Confirm"></div>6299 <div class="mes_edit_done menu_button fa-solid fa-check" title="Confirm" data-i18n="[title]Confirm"></div>
6267 <div class="mes_edit_copy menu_button fa-solid fa-copy" title="Copy this message" data-i18n="[title]Copy this message"></div>6300 <div class="mes_edit_copy menu_button fa-solid fa-copy" title="Copy this message" data-i18n="[title]Copy this message"></div>
6268 <div class="mes_edit_delete menu_button fa-solid fa-trash-can" title="Delete this message" data-i18n="[title]Delete this message">6301 <div class="mes_edit_add_reasoning menu_button fa-solid fa-lightbulb" title="Add a reasoning block" data-i18n="[title]Add a reasoning block"></div>
6269 </div>6302 <div class="mes_edit_delete menu_button fa-solid fa-trash-can" title="Delete this message" data-i18n="[title]Delete this message"></div>
6270 <div class="mes_edit_up menu_button fa-solid fa-chevron-up " title="Move message up" data-i18n="[title]Move message up"></div>6303 <div class="mes_edit_up menu_button fa-solid fa-chevron-up " title="Move message up" data-i18n="[title]Move message up"></div>
6271 <div class="mes_edit_down menu_button fa-solid fa-chevron-down" title="Move message down" data-i18n="[title]Move message down">6304 <div class="mes_edit_down menu_button fa-solid fa-chevron-down" title="Move message down" data-i18n="[title]Move message down"></div>
6272 </div>
6273 <div class="mes_edit_cancel menu_button fa-solid fa-xmark" title="Cancel" data-i18n="[title]Cancel"></div>6305 <div class="mes_edit_cancel menu_button fa-solid fa-xmark" title="Cancel" data-i18n="[title]Cancel"></div>
6274 </div>6306 </div>
6275 </div>6307 </div>
6308 <details class="mes_reasoning_details">
6309 <summary class="mes_reasoning_summary">
6310 <span data-i18n="Reasoning">Reasoning</span>
6311 <div class="mes_reasoning_actions">
6312 <div class="mes_reasoning_edit_done mes_button fa-solid fa-check" title="Confirm" data-i18n="[title]Confirmedit"></div>
6313 <div class="mes_reasoning_edit_cancel mes_button fa-solid fa-xmark" title="Cancel edit" data-i18n="[title]Cancel edit"></div>
6314 <div class="mes_reasoning_edit mes_button fa-solid fa-pencil" title="Edit reasoning" data-i18n="[title]Edit reasoning"></div>
6315 <div class="mes_reasoning_copy mes_button fa-solid fa-copy" title="Copy reasoning" data-i18n="[title]Copy reasoning"></div>
6316 <div class="mes_reasoning_delete mes_button fa-solid fa-trash-can" title="Remove reasoning" data-i18n="[title]Remove reasoning"></div>
6317 </div>
6318 </summary>
6319 <div class="mes_reasoning"></div>
6320 </details>
6276 <div class="mes_text"></div>6321 <div class="mes_text"></div>
6277 <div class="mes_img_container">6322 <div class="mes_img_container">
6278 <div class="mes_img_controls">6323 <div class="mes_img_controls">
public/locales/fr-fr.json+1 -1
@@ -1385,7 +1385,7 @@
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 thoughts": "Afficher les pensées du modèle",1388 "Show model reasoning": "Afficher 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 "Display the model's internal thoughts in the response.": "Afficher les pensées internes du modèle dans la réponse.",
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",
public/locales/zh-cn.json+1 -1
@@ -266,7 +266,7 @@
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 thoughts": "展示思维链",269 "Show model reasoning": "展示思维链",
270 "Display the model's internal thoughts in the response.": "展示模型在回复时的内部思维链。",270 "Display the model's internal thoughts in the response.": "展示模型在回复时的内部思维链。",
271 "Assistant Prefill": "AI预填",271 "Assistant Prefill": "AI预填",
272 "Expand the editor": "展开编辑器",272 "Expand the editor": "展开编辑器",
public/locales/zh-tw.json+1 -1
@@ -2357,7 +2357,7 @@
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 thoughts": "顯示模型思維鏈",2360 "Show model reasoning": "顯示模型思維鏈",
2361 "Display the model's internal thoughts in the response.": "在回應中顯示模型的思維鏈(內部思考過程)。",2361 "Display the model's internal thoughts in the response.": "在回應中顯示模型的思維鏈(內部思考過程)。",
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(可選)",
public/script.js+137 -44
@@ -271,6 +271,7 @@ import { initSettingsSearch } from './scripts/setting-search.js';
271import { initBulkEdit } from './scripts/bulk-edit.js';271import { initBulkEdit } from './scripts/bulk-edit.js';
272import { deriveTemplatesFromChatTemplate } from './scripts/chat-templates.js';272import { deriveTemplatesFromChatTemplate } from './scripts/chat-templates.js';
273import { getContext } from './scripts/st-context.js';273import { getContext } from './scripts/st-context.js';
274import { initReasoning, PromptReasoning } from './scripts/reasoning.js';
274275
275// API OBJECT FOR EXTERNAL WIRING276// API OBJECT FOR EXTERNAL WIRING
276globalThis.SillyTavern = {277globalThis.SillyTavern = {
@@ -986,6 +987,7 @@ async function firstLoadInit() {
986 initServerHistory();987 initServerHistory();
987 initSettingsSearch();988 initSettingsSearch();
988 initBulkEdit();989 initBulkEdit();
990 initReasoning();
989 await initScrapers();991 await initScrapers();
990 doDailyExtensionUpdatesCheck();992 doDailyExtensionUpdatesCheck();
991 await hideLoader();993 await hideLoader();
@@ -2213,6 +2215,7 @@ function getMessageFromTemplate({
2213 isUser,2215 isUser,
2214 avatarImg,2216 avatarImg,
2215 bias,2217 bias,
2218 reasoning,
2216 isSystem,2219 isSystem,
2217 title,2220 title,
2218 timerValue,2221 timerValue,
@@ -2237,6 +2240,7 @@ function getMessageFromTemplate({
2237 mes.find('.avatar img').attr('src', avatarImg);2240 mes.find('.avatar img').attr('src', avatarImg);
2238 mes.find('.ch_name .name_text').text(characterName);2241 mes.find('.ch_name .name_text').text(characterName);
2239 mes.find('.mes_bias').html(bias);2242 mes.find('.mes_bias').html(bias);
2243 mes.find('.mes_reasoning').html(reasoning);
2240 mes.find('.timestamp').text(timestamp).attr('title', `${extra?.api ? extra.api + ' - ' : ''}${extra?.model ?? ''}`);2244 mes.find('.timestamp').text(timestamp).attr('title', `${extra?.api ? extra.api + ' - ' : ''}${extra?.model ?? ''}`);
2241 mes.find('.mesIDDisplay').text(`#${mesId}`);2245 mes.find('.mesIDDisplay').text(`#${mesId}`);
2242 tokenCount && mes.find('.tokenCounterDisplay').text(`${tokenCount}t`);2246 tokenCount && mes.find('.tokenCounterDisplay').text(`${tokenCount}t`);
@@ -2251,10 +2255,16 @@ function getMessageFromTemplate({
2251 return mes;2255 return mes;
2252}2256}
22532257
2258/**
2259 * Re-renders a message block with updated content.
2260 * @param {number} messageId Message ID
2261 * @param {object} message Message object
2262 */
2254export function updateMessageBlock(messageId, message) {2263export function updateMessageBlock(messageId, message) {
2255 const messageElement = $(`#chat [mesid="${messageId}"]`);2264 const messageElement = $(`#chat [mesid="${messageId}"]`);
2256 const text = message?.extra?.display_text ?? message.mes;2265 const text = message?.extra?.display_text ?? message.mes;
2257 messageElement.find('.mes_text').html(messageFormatting(text, message.name, message.is_system, message.is_user, messageId));2266 messageElement.find('.mes_text').html(messageFormatting(text, message.name, message.is_system, message.is_user, messageId));
2267 messageElement.find('.mes_reasoning').html(messageFormatting(message.extra?.reasoning ?? '', '', false, false, -1));
2258 addCopyToCodeBlocks(messageElement);2268 addCopyToCodeBlocks(messageElement);
2259 appendMediaToMessage(message, messageElement);2269 appendMediaToMessage(message, messageElement);
2260}2270}
@@ -2413,6 +2423,7 @@ export function addOneMessage(mes, { type = 'normal', insertAfter = null, scroll
2413 sanitizerOverrides,2423 sanitizerOverrides,
2414 );2424 );
2415 const bias = messageFormatting(mes.extra?.bias ?? '', '', false, false, -1);2425 const bias = messageFormatting(mes.extra?.bias ?? '', '', false, false, -1);
2426 const reasoning = messageFormatting(mes.extra?.reasoning ?? '', '', false, false, -1);
2416 let bookmarkLink = mes?.extra?.bookmark_link ?? '';2427 let bookmarkLink = mes?.extra?.bookmark_link ?? '';
24172428
2418 let params = {2429 let params = {
@@ -2422,6 +2433,7 @@ export function addOneMessage(mes, { type = 'normal', insertAfter = null, scroll
2422 isUser: mes.is_user,2433 isUser: mes.is_user,
2423 avatarImg: avatarImg,2434 avatarImg: avatarImg,
2424 bias: bias,2435 bias: bias,
2436 reasoning: reasoning,
2425 isSystem: isSystem,2437 isSystem: isSystem,
2426 title: title,2438 title: title,
2427 bookmarkLink: bookmarkLink,2439 bookmarkLink: bookmarkLink,
@@ -2481,6 +2493,7 @@ export function addOneMessage(mes, { type = 'normal', insertAfter = null, scroll
2481 const swipeMessage = chatElement.find(`[mesid="${chat.length - 1}"]`);2493 const swipeMessage = chatElement.find(`[mesid="${chat.length - 1}"]`);
2482 swipeMessage.attr('swipeid', params.swipeId);2494 swipeMessage.attr('swipeid', params.swipeId);
2483 swipeMessage.find('.mes_text').html(messageText).attr('title', title);2495 swipeMessage.find('.mes_text').html(messageText).attr('title', title);
2496 swipeMessage.find('.mes_reasoning').html(reasoning);
2484 swipeMessage.find('.timestamp').text(timestamp).attr('title', `${params.extra.api} - ${params.extra.model}`);2497 swipeMessage.find('.timestamp').text(timestamp).attr('title', `${params.extra.api} - ${params.extra.model}`);
2485 appendMediaToMessage(mes, swipeMessage);2498 appendMediaToMessage(mes, swipeMessage);
2486 if (power_user.timestamp_model_icon && params.extra?.api) {2499 if (power_user.timestamp_model_icon && params.extra?.api) {
@@ -3091,6 +3104,7 @@ class StreamingProcessor {
3091 this.messageTextDom = null;3104 this.messageTextDom = null;
3092 this.messageTimerDom = null;3105 this.messageTimerDom = null;
3093 this.messageTokenCounterDom = null;3106 this.messageTokenCounterDom = null;
3107 this.messageReasoningDom = null;
3094 /** @type {HTMLTextAreaElement} */3108 /** @type {HTMLTextAreaElement} */
3095 this.sendTextarea = document.querySelector('#send_textarea');3109 this.sendTextarea = document.querySelector('#send_textarea');
3096 this.type = type;3110 this.type = type;
@@ -3106,6 +3120,7 @@ class StreamingProcessor {
3106 /** @type {import('./scripts/logprobs.js').TokenLogprobs[]} */3120 /** @type {import('./scripts/logprobs.js').TokenLogprobs[]} */
3107 this.messageLogprobs = [];3121 this.messageLogprobs = [];
3108 this.toolCalls = [];3122 this.toolCalls = [];
3123 this.reasoning = '';
3109 }3124 }
31103125
3111 #checkDomElements(messageId) {3126 #checkDomElements(messageId) {
@@ -3114,6 +3129,7 @@ class StreamingProcessor {
3114 this.messageTextDom = this.messageDom?.querySelector('.mes_text');3129 this.messageTextDom = this.messageDom?.querySelector('.mes_text');
3115 this.messageTimerDom = this.messageDom?.querySelector('.mes_timer');3130 this.messageTimerDom = this.messageDom?.querySelector('.mes_timer');
3116 this.messageTokenCounterDom = this.messageDom?.querySelector('.tokenCounterDisplay');3131 this.messageTokenCounterDom = this.messageDom?.querySelector('.tokenCounterDisplay');
3132 this.messageReasoningDom = this.messageDom?.querySelector('.mes_reasoning');
3117 }3133 }
3118 }3134 }
31193135
@@ -3191,18 +3207,27 @@ class StreamingProcessor {
3191 this.#checkDomElements(messageId);3207 this.#checkDomElements(messageId);
3192 this.#updateMessageBlockVisibility();3208 this.#updateMessageBlockVisibility();
3193 const currentTime = new Date();3209 const currentTime = new Date();
3194 // Don't waste time calculating token count for streaming
3195 const currentTokenCount = isFinal && power_user.message_token_count_enabled ? getTokenCount(processedText, 0) : 0;
3196 const timePassed = formatGenerationTimer(this.timeStarted, currentTime, currentTokenCount);
3197 chat[messageId]['mes'] = processedText;3210 chat[messageId]['mes'] = processedText;
3198 chat[messageId]['gen_started'] = this.timeStarted;3211 chat[messageId]['gen_started'] = this.timeStarted;
3199 chat[messageId]['gen_finished'] = currentTime;3212 chat[messageId]['gen_finished'] = currentTime;
32003213
3201 if (currentTokenCount) {3214 if (!chat[messageId]['extra']) {
3202 if (!chat[messageId]['extra']) {3215 chat[messageId]['extra'] = {};
3203 chat[messageId]['extra'] = {};3216 }
3217
3218 if (this.reasoning) {
3219 chat[messageId]['extra']['reasoning'] = this.reasoning;
3220 if (this.messageReasoningDom instanceof HTMLElement) {
3221 const formattedReasoning = messageFormatting(this.reasoning, '', false, false, -1);
3222 this.messageReasoningDom.innerHTML = formattedReasoning;
3204 }3223 }
3224 }
3225
3226 // Don't waste time calculating token count for streaming
3227 const tokenCountText = (this.reasoning || '') + processedText;
3228 const currentTokenCount = isFinal && power_user.message_token_count_enabled ? getTokenCount(tokenCountText, 0) : 0;
32053229
3230 if (currentTokenCount) {
3206 chat[messageId]['extra']['token_count'] = currentTokenCount;3231 chat[messageId]['extra']['token_count'] = currentTokenCount;
3207 if (this.messageTokenCounterDom instanceof HTMLElement) {3232 if (this.messageTokenCounterDom instanceof HTMLElement) {
3208 this.messageTokenCounterDom.textContent = `${currentTokenCount}t`;3233 this.messageTokenCounterDom.textContent = `${currentTokenCount}t`;
@@ -3224,10 +3249,13 @@ class StreamingProcessor {
3224 if (this.messageTextDom instanceof HTMLElement) {3249 if (this.messageTextDom instanceof HTMLElement) {
3225 this.messageTextDom.innerHTML = formattedText;3250 this.messageTextDom.innerHTML = formattedText;
3226 }3251 }
3252
3253 const timePassed = formatGenerationTimer(this.timeStarted, currentTime, currentTokenCount);
3227 if (this.messageTimerDom instanceof HTMLElement) {3254 if (this.messageTimerDom instanceof HTMLElement) {
3228 this.messageTimerDom.textContent = timePassed.timerValue;3255 this.messageTimerDom.textContent = timePassed.timerValue;
3229 this.messageTimerDom.title = timePassed.timerTitle;3256 this.messageTimerDom.title = timePassed.timerTitle;
3230 }3257 }
3258
3231 this.setFirstSwipe(messageId);3259 this.setFirstSwipe(messageId);
3232 }3260 }
32333261
@@ -3334,7 +3362,7 @@ class StreamingProcessor {
3334 }3362 }
33353363
3336 /**3364 /**
3337 * @returns {Generator<{ text: string, swipes: string[], logprobs: import('./scripts/logprobs.js').TokenLogprobs, toolCalls: any[] }, void, void>}3365 * @returns {Generator<{ text: string, swipes: string[], logprobs: import('./scripts/logprobs.js').TokenLogprobs, toolCalls: any[], state: any }, void, void>}
3338 */3366 */
3339 *nullStreamingGeneration() {3367 *nullStreamingGeneration() {
3340 throw new Error('Generation function for streaming is not hooked up');3368 throw new Error('Generation function for streaming is not hooked up');
@@ -3356,7 +3384,7 @@ class StreamingProcessor {
3356 try {3384 try {
3357 const sw = new Stopwatch(1000 / power_user.streaming_fps);3385 const sw = new Stopwatch(1000 / power_user.streaming_fps);
3358 const timestamps = [];3386 const timestamps = [];
3359 for await (const { text, swipes, logprobs, toolCalls } of this.generator()) {3387 for await (const { text, swipes, logprobs, toolCalls, state } of this.generator()) {
3360 timestamps.push(Date.now());3388 timestamps.push(Date.now());
3361 if (this.isStopped) {3389 if (this.isStopped) {
3362 return;3390 return;
@@ -3368,6 +3396,7 @@ class StreamingProcessor {
3368 if (logprobs) {3396 if (logprobs) {
3369 this.messageLogprobs.push(...(Array.isArray(logprobs) ? logprobs : [logprobs]));3397 this.messageLogprobs.push(...(Array.isArray(logprobs) ? logprobs : [logprobs]));
3370 }3398 }
3399 this.reasoning = state?.reasoning ?? '';
3371 await eventSource.emit(event_types.STREAM_TOKEN_RECEIVED, text);3400 await eventSource.emit(event_types.STREAM_TOKEN_RECEIVED, text);
3372 await sw.tick(() => this.onProgressStreaming(this.messageId, this.continueMessage + text));3401 await sw.tick(() => this.onProgressStreaming(this.messageId, this.continueMessage + text));
3373 }3402 }
@@ -3834,6 +3863,14 @@ export async function Generate(type, { automatic_trigger, force_name2, quiet_pro
3834 coreChat.pop();3863 coreChat.pop();
3835 }3864 }
38363865
3866 const reasoning = new PromptReasoning();
3867 for (let i = coreChat.length - 1; i >= 0; i--) {
3868 if (reasoning.isLimitReached()) {
3869 break;
3870 }
3871 coreChat[i] = { ...coreChat[i], mes: reasoning.addToMessage(coreChat[i].mes, coreChat[i].extra?.reasoning) };
3872 }
3873
3837 coreChat = await Promise.all(coreChat.map(async (chatItem, index) => {3874 coreChat = await Promise.all(coreChat.map(async (chatItem, index) => {
3838 let message = chatItem.mes;3875 let message = chatItem.mes;
3839 let regexType = chatItem.is_user ? regex_placement.USER_INPUT : regex_placement.AI_OUTPUT;3876 let regexType = chatItem.is_user ? regex_placement.USER_INPUT : regex_placement.AI_OUTPUT;
@@ -4755,6 +4792,7 @@ export async function Generate(type, { automatic_trigger, force_name2, quiet_pro
4755 //const getData = await response.json();4792 //const getData = await response.json();
4756 let getMessage = extractMessageFromData(data);4793 let getMessage = extractMessageFromData(data);
4757 let title = extractTitleFromData(data);4794 let title = extractTitleFromData(data);
4795 let reasoning = extractReasoningFromData(data);
4758 kobold_horde_model = title;4796 kobold_horde_model = title;
47594797
4760 const swipes = extractMultiSwipes(data, type);4798 const swipes = extractMultiSwipes(data, type);
@@ -4781,10 +4819,10 @@ export async function Generate(type, { automatic_trigger, force_name2, quiet_pro
4781 else {4819 else {
4782 // Without streaming we'll be having a full message on continuation. Treat it as a last chunk.4820 // Without streaming we'll be having a full message on continuation. Treat it as a last chunk.
4783 if (originalType !== 'continue') {4821 if (originalType !== 'continue') {
4784 ({ type, getMessage } = await saveReply(type, getMessage, false, title, swipes));4822 ({ type, getMessage } = await saveReply(type, getMessage, false, title, swipes, reasoning));
4785 }4823 }
4786 else {4824 else {
4787 ({ type, getMessage } = await saveReply('appendFinal', getMessage, false, title, swipes));4825 ({ type, getMessage } = await saveReply('appendFinal', getMessage, false, title, swipes, reasoning));
4788 }4826 }
47894827
4790 // This relies on `saveReply` having been called to add the message to the chat, so it must be last.4828 // This relies on `saveReply` having been called to add the message to the chat, so it must be last.
@@ -5672,31 +5710,40 @@ function extractMessageFromData(data) {
5672 return data;5710 return data;
5673 }5711 }
56745712
5675 function getTextContext() {5713 switch (main_api) {
5676 switch (main_api) {5714 case 'kobold':
5677 case 'kobold':5715 return data.results[0].text;
5678 return data.results[0].text;5716 case 'koboldhorde':
5679 case 'koboldhorde':5717 return data.text;
5680 return data.text;5718 case 'textgenerationwebui':
5681 case 'textgenerationwebui':5719 return data.choices?.[0]?.text ?? data.content ?? data.response ?? '';
5682 return data.choices?.[0]?.text ?? data.content ?? data.response ?? '';5720 case 'novel':
5683 case 'novel':5721 return data.output;
5684 return data.output;5722 case 'openai':
5685 case 'openai':5723 return data?.choices?.[0]?.message?.content ?? data?.choices?.[0]?.text ?? data?.text ?? data?.message?.content?.[0]?.text ?? data?.message?.tool_plan ?? '';
5686 return data?.choices?.[0]?.message?.content ?? data?.choices?.[0]?.text ?? data?.text ?? data?.message?.content?.[0]?.text ?? data?.message?.tool_plan ?? '';5724 default:
5687 default:5725 return '';
5688 return '';
5689 }
5690 }5726 }
5727}
56915728
5692 const content = getTextContext();5729/**
56935730 * Extracts the reasoning from the response data.
5694 if (main_api === 'openai' && oai_settings.chat_completion_source === chat_completion_sources.DEEPSEEK && oai_settings.show_thoughts) {5731 * @param {object} data Response data
5695 const thoughts = data?.choices?.[0]?.message?.reasoning_content ?? '';5732 * @returns {string} Extracted reasoning
5696 return [thoughts, content].filter(x => x).join('\n\n');5733 */
5734function extractReasoningFromData(data) {
5735 if (main_api === 'openai' && oai_settings.show_thoughts) {
5736 switch (oai_settings.chat_completion_source) {
5737 case chat_completion_sources.DEEPSEEK:
5738 return data?.choices?.[0]?.message?.reasoning_content ?? '';
5739 case chat_completion_sources.OPENROUTER:
5740 return data?.choices?.[0]?.message?.reasoning ?? '';
5741 case chat_completion_sources.MAKERSUITE:
5742 return data?.responseContent?.parts?.filter(part => part.thought)?.map(part => part.text)?.join('\n\n') ?? '';
5743 }
5697 }5744 }
56985745
5699 return content;5746 return '';
5700}5747}
57015748
5702/**5749/**
@@ -5879,7 +5926,7 @@ export function cleanUpMessage(getMessage, isImpersonate, isContinue, displayInc
5879 return getMessage;5926 return getMessage;
5880}5927}
58815928
5882export async function saveReply(type, getMessage, fromStreaming, title, swipes) {5929export async function saveReply(type, getMessage, fromStreaming, title, swipes, reasoning) {
5883 if (type != 'append' && type != 'continue' && type != 'appendFinal' && chat.length && (chat[chat.length - 1]['swipe_id'] === undefined ||5930 if (type != 'append' && type != 'continue' && type != 'appendFinal' && chat.length && (chat[chat.length - 1]['swipe_id'] === undefined ||
5884 chat[chat.length - 1]['is_user'])) {5931 chat[chat.length - 1]['is_user'])) {
5885 type = 'normal';5932 type = 'normal';
@@ -5904,8 +5951,10 @@ export async function saveReply(type, getMessage, fromStreaming, title, swipes)
5904 chat[chat.length - 1]['send_date'] = getMessageTimeStamp();5951 chat[chat.length - 1]['send_date'] = getMessageTimeStamp();
5905 chat[chat.length - 1]['extra']['api'] = getGeneratingApi();5952 chat[chat.length - 1]['extra']['api'] = getGeneratingApi();
5906 chat[chat.length - 1]['extra']['model'] = getGeneratingModel();5953 chat[chat.length - 1]['extra']['model'] = getGeneratingModel();
5954 chat[chat.length - 1]['extra']['reasoning'] = reasoning;
5907 if (power_user.message_token_count_enabled) {5955 if (power_user.message_token_count_enabled) {
5908 chat[chat.length - 1]['extra']['token_count'] = await getTokenCountAsync(chat[chat.length - 1]['mes'], 0);5956 const tokenCountText = (reasoning || '') + chat[chat.length - 1]['mes'];
5957 chat[chat.length - 1]['extra']['token_count'] = await getTokenCountAsync(tokenCountText, 0);
5909 }5958 }
5910 const chat_id = (chat.length - 1);5959 const chat_id = (chat.length - 1);
5911 await eventSource.emit(event_types.MESSAGE_RECEIVED, chat_id);5960 await eventSource.emit(event_types.MESSAGE_RECEIVED, chat_id);
@@ -5924,8 +5973,10 @@ export async function saveReply(type, getMessage, fromStreaming, title, swipes)
5924 chat[chat.length - 1]['send_date'] = getMessageTimeStamp();5973 chat[chat.length - 1]['send_date'] = getMessageTimeStamp();
5925 chat[chat.length - 1]['extra']['api'] = getGeneratingApi();5974 chat[chat.length - 1]['extra']['api'] = getGeneratingApi();
5926 chat[chat.length - 1]['extra']['model'] = getGeneratingModel();5975 chat[chat.length - 1]['extra']['model'] = getGeneratingModel();
5976 chat[chat.length - 1]['extra']['reasoning'] += reasoning;
5927 if (power_user.message_token_count_enabled) {5977 if (power_user.message_token_count_enabled) {
5928 chat[chat.length - 1]['extra']['token_count'] = await getTokenCountAsync(chat[chat.length - 1]['mes'], 0);5978 const tokenCountText = (reasoning || '') + chat[chat.length - 1]['mes'];
5979 chat[chat.length - 1]['extra']['token_count'] = await getTokenCountAsync(tokenCountText, 0);
5929 }5980 }
5930 const chat_id = (chat.length - 1);5981 const chat_id = (chat.length - 1);
5931 await eventSource.emit(event_types.MESSAGE_RECEIVED, chat_id);5982 await eventSource.emit(event_types.MESSAGE_RECEIVED, chat_id);
@@ -5941,8 +5992,10 @@ export async function saveReply(type, getMessage, fromStreaming, title, swipes)
5941 chat[chat.length - 1]['send_date'] = getMessageTimeStamp();5992 chat[chat.length - 1]['send_date'] = getMessageTimeStamp();
5942 chat[chat.length - 1]['extra']['api'] = getGeneratingApi();5993 chat[chat.length - 1]['extra']['api'] = getGeneratingApi();
5943 chat[chat.length - 1]['extra']['model'] = getGeneratingModel();5994 chat[chat.length - 1]['extra']['model'] = getGeneratingModel();
5995 chat[chat.length - 1]['extra']['reasoning'] += reasoning;
5944 if (power_user.message_token_count_enabled) {5996 if (power_user.message_token_count_enabled) {
5945 chat[chat.length - 1]['extra']['token_count'] = await getTokenCountAsync(chat[chat.length - 1]['mes'], 0);5997 const tokenCountText = (reasoning || '') + chat[chat.length - 1]['mes'];
5998 chat[chat.length - 1]['extra']['token_count'] = await getTokenCountAsync(tokenCountText, 0);
5946 }5999 }
5947 const chat_id = (chat.length - 1);6000 const chat_id = (chat.length - 1);
5948 await eventSource.emit(event_types.MESSAGE_RECEIVED, chat_id);6001 await eventSource.emit(event_types.MESSAGE_RECEIVED, chat_id);
@@ -5958,6 +6011,7 @@ export async function saveReply(type, getMessage, fromStreaming, title, swipes)
5958 chat[chat.length - 1]['send_date'] = getMessageTimeStamp();6011 chat[chat.length - 1]['send_date'] = getMessageTimeStamp();
5959 chat[chat.length - 1]['extra']['api'] = getGeneratingApi();6012 chat[chat.length - 1]['extra']['api'] = getGeneratingApi();
5960 chat[chat.length - 1]['extra']['model'] = getGeneratingModel();6013 chat[chat.length - 1]['extra']['model'] = getGeneratingModel();
6014 chat[chat.length - 1]['extra']['reasoning'] = reasoning;
5961 if (power_user.trim_spaces) {6015 if (power_user.trim_spaces) {
5962 getMessage = getMessage.trim();6016 getMessage = getMessage.trim();
5963 }6017 }
@@ -5967,7 +6021,8 @@ export async function saveReply(type, getMessage, fromStreaming, title, swipes)
5967 chat[chat.length - 1]['gen_finished'] = generationFinished;6021 chat[chat.length - 1]['gen_finished'] = generationFinished;
59686022
5969 if (power_user.message_token_count_enabled) {6023 if (power_user.message_token_count_enabled) {
5970 chat[chat.length - 1]['extra']['token_count'] = await getTokenCountAsync(chat[chat.length - 1]['mes'], 0);6024 const tokenCountText = (reasoning || '') + chat[chat.length - 1]['mes'];
6025 chat[chat.length - 1]['extra']['token_count'] = await getTokenCountAsync(tokenCountText, 0);
5971 }6026 }
59726027
5973 if (selected_group) {6028 if (selected_group) {
@@ -6032,6 +6087,19 @@ export async function saveReply(type, getMessage, fromStreaming, title, swipes)
6032 return { type, getMessage };6087 return { type, getMessage };
6033}6088}
60346089
6090export function syncCurrentSwipeInfoExtras() {
6091 if (!chat.length) {
6092 return;
6093 }
6094 const currentMessage = chat[chat.length - 1];
6095 if (currentMessage && Array.isArray(currentMessage.swipe_info) && typeof currentMessage.swipe_id === 'number') {
6096 const swipeInfo = currentMessage.swipe_info[currentMessage.swipe_id];
6097 if (swipeInfo && typeof swipeInfo === 'object') {
6098 swipeInfo.extra = structuredClone(currentMessage.extra);
6099 }
6100 }
6101}
6102
6035function saveImageToMessage(img, mes) {6103function saveImageToMessage(img, mes) {
6036 if (mes && img.image) {6104 if (mes && img.image) {
6037 if (!mes.extra || typeof mes.extra !== 'object') {6105 if (!mes.extra || typeof mes.extra !== 'object') {
@@ -8016,9 +8084,23 @@ function updateEditArrowClasses() {
8016 }8084 }
8017}8085}
80188086
8019function closeMessageEditor() {8087/**
8020 if (this_edit_mes_id) {8088 * Closes the message editor.
8021 $(`#chat .mes[mesid="${this_edit_mes_id}"] .mes_edit_cancel`).click();8089 * @param {'message'|'reasoning'|'all'} what What to close. Default is 'all'.
8090 */
8091export function closeMessageEditor(what = 'all') {
8092 if (what === 'message' || what === 'all') {
8093 if (this_edit_mes_id) {
8094 $(`#chat .mes[mesid="${this_edit_mes_id}"] .mes_edit_cancel`).click();
8095 }
8096 }
8097 if (what === 'reasoning' || what === 'all') {
8098 document.querySelectorAll('.reasoning_edit_textarea').forEach((el) => {
8099 const cancelButton = el.closest('.mes')?.querySelector('.mes_reasoning_edit_cancel');
8100 if (cancelButton instanceof HTMLElement) {
8101 cancelButton.click();
8102 }
8103 });
8022 }8104 }
8023}8105}
80248106
@@ -8451,6 +8533,9 @@ function swipe_left() { // when we swipe left..but no generation.
8451 streamingProcessor.onStopStreaming();8533 streamingProcessor.onStopStreaming();
8452 }8534 }
84538535
8536 // Make sure ad-hoc changes to extras are saved before swiping away
8537 syncCurrentSwipeInfoExtras();
8538
8454 const swipe_duration = 120;8539 const swipe_duration = 120;
8455 const swipe_range = '700px';8540 const swipe_range = '700px';
8456 chat[chat.length - 1]['swipe_id']--;8541 chat[chat.length - 1]['swipe_id']--;
@@ -8502,7 +8587,8 @@ function swipe_left() { // when we swipe left..but no generation.
8502 }8587 }
85038588
8504 const swipeMessage = $('#chat').find(`[mesid="${chat.length - 1}"]`);8589 const swipeMessage = $('#chat').find(`[mesid="${chat.length - 1}"]`);
8505 const tokenCount = await getTokenCountAsync(chat[chat.length - 1].mes, 0);8590 const tokenCountText = (chat[chat.length - 1]?.extra?.reasoning || '') + chat[chat.length - 1].mes;
8591 const tokenCount = await getTokenCountAsync(tokenCountText, 0);
8506 chat[chat.length - 1]['extra']['token_count'] = tokenCount;8592 chat[chat.length - 1]['extra']['token_count'] = tokenCount;
8507 swipeMessage.find('.tokenCounterDisplay').text(`${tokenCount}t`);8593 swipeMessage.find('.tokenCounterDisplay').text(`${tokenCount}t`);
8508 }8594 }
@@ -8585,6 +8671,9 @@ const swipe_right = () => {
8585 return unblockGeneration();8671 return unblockGeneration();
8586 }8672 }
85878673
8674 // Make sure ad-hoc changes to extras are saved before swiping away
8675 syncCurrentSwipeInfoExtras();
8676
8588 const swipe_duration = 200;8677 const swipe_duration = 200;
8589 const swipe_range = 700;8678 const swipe_range = 700;
8590 //console.log(swipe_range);8679 //console.log(swipe_range);
@@ -8666,6 +8755,7 @@ const swipe_right = () => {
8666 // resets the timer8755 // resets the timer
8667 swipeMessage.find('.mes_timer').html('');8756 swipeMessage.find('.mes_timer').html('');
8668 swipeMessage.find('.tokenCounterDisplay').text('');8757 swipeMessage.find('.tokenCounterDisplay').text('');
8758 swipeMessage.find('.mes_reasoning').html('');
8669 } else {8759 } else {
8670 //console.log('showing previously generated swipe candidate, or "..."');8760 //console.log('showing previously generated swipe candidate, or "..."');
8671 //console.log('onclick right swipe calling addOneMessage');8761 //console.log('onclick right swipe calling addOneMessage');
@@ -8676,7 +8766,8 @@ const swipe_right = () => {
8676 chat[chat.length - 1].extra = {};8766 chat[chat.length - 1].extra = {};
8677 }8767 }
86788768
8679 const tokenCount = await getTokenCountAsync(chat[chat.length - 1].mes, 0);8769 const tokenCountText = (chat[chat.length - 1]?.extra?.reasoning || '') + chat[chat.length - 1].mes;
8770 const tokenCount = await getTokenCountAsync(tokenCountText, 0);
8680 chat[chat.length - 1]['extra']['token_count'] = tokenCount;8771 chat[chat.length - 1]['extra']['token_count'] = tokenCount;
8681 swipeMessage.find('.tokenCounterDisplay').text(`${tokenCount}t`);8772 swipeMessage.find('.tokenCounterDisplay').text(`${tokenCount}t`);
8682 }8773 }
@@ -9478,7 +9569,8 @@ function addDebugFunctions() {
9478 message.extra = {};9569 message.extra = {};
9479 }9570 }
94809571
9481 message.extra.token_count = await getTokenCountAsync(message.mes, 0);9572 const tokenCountText = (message?.extra?.reasoning || '') + message.mes;
9573 message.extra.token_count = await getTokenCountAsync(tokenCountText, 0);
9482 }9574 }
94839575
9484 await saveChatConditional();9576 await saveChatConditional();
@@ -11290,14 +11382,15 @@ jQuery(async function () {
1129011382
11291 $(document).keyup(function (e) {11383 $(document).keyup(function (e) {
11292 if (e.key === 'Escape') {11384 if (e.key === 'Escape') {
11293 const isEditVisible = $('#curEditTextarea').is(':visible');11385 const isEditVisible = $('#curEditTextarea').is(':visible') || $('.reasoning_edit_textarea').length > 0;
11294 if (isEditVisible && power_user.auto_save_msg_edits === false) {11386 if (isEditVisible && power_user.auto_save_msg_edits === false) {
11295 closeMessageEditor();11387 closeMessageEditor('all');
11296 $('#send_textarea').focus();11388 $('#send_textarea').focus();
11297 return;11389 return;
11298 }11390 }
11299 if (isEditVisible && power_user.auto_save_msg_edits === true) {11391 if (isEditVisible && power_user.auto_save_msg_edits === true) {
11300 $(`#chat .mes[mesid="${this_edit_mes_id}"] .mes_edit_done`).click();11392 $(`#chat .mes[mesid="${this_edit_mes_id}"] .mes_edit_done`).click();
11393 closeMessageEditor('reasoning');
11301 $('#send_textarea').focus();11394 $('#send_textarea').focus();
11302 return;11395 return;
11303 }11396 }
public/scripts/backgrounds.js+4 -4
@@ -482,10 +482,10 @@ function highlightNewBackground(bg) {
482 */482 */
483function setFittingClass(fitting) {483function setFittingClass(fitting) {
484 const backgrounds = $('#bg1, #bg_custom');484 const backgrounds = $('#bg1, #bg_custom');
485 backgrounds.toggleClass('cover', fitting === 'cover');485 for (const option of ['cover', 'contain', 'stretch', 'center']) {
486 backgrounds.toggleClass('contain', fitting === 'contain');486 backgrounds.toggleClass(option, option === fitting);
487 backgrounds.toggleClass('stretch', fitting === 'stretch');487 }
488 backgrounds.toggleClass('center', fitting === 'center');488 background_settings.fitting = fitting;
489}489}
490490
491function onBackgroundFilterInput() {491function onBackgroundFilterInput() {
public/scripts/extensions/tts/index.js+1 -1
@@ -466,7 +466,7 @@ async function processTtsQueue() {
466 }466 }
467467
468 if (extension_settings.tts.skip_tags) {468 if (extension_settings.tts.skip_tags) {
469 text = text.replace(/<.*?>.*?<\/.*?>/g, '').trim();469 text = text.replace(/<.*?>[\s\S]*?<\/.*?>/g, '').trim();
470 }470 }
471471
472 if (!extension_settings.tts.pass_asterisks) {472 if (!extension_settings.tts.pass_asterisks) {
public/scripts/kai-settings.js+1 -1
@@ -188,7 +188,7 @@ export async function generateKoboldWithStreaming(generate_data, signal) {
188 if (data?.token) {188 if (data?.token) {
189 text += data.token;189 text += data.token;
190 }190 }
191 yield { text, swipes: [], toolCalls: [] };191 yield { text, swipes: [], toolCalls: [], state: {} };
192 }192 }
193 };193 };
194}194}
public/scripts/nai-settings.js+1 -1
@@ -746,7 +746,7 @@ export async function generateNovelWithStreaming(generate_data, signal) {
746 text += data.token;746 text += data.token;
747 }747 }
748748
749 yield { text, swipes: [], logprobs: parseNovelAILogprobs(data.logprobs), toolCalls: [] };749 yield { text, swipes: [], logprobs: parseNovelAILogprobs(data.logprobs), toolCalls: [], state: {} };
750 }750 }
751 };751 };
752}752}
public/scripts/openai.js+16 -10
@@ -1096,8 +1096,8 @@ async function preparePromptsForChatCompletion({ Scenario, charPersonality, name
1096 // Unordered prompts without marker1096 // Unordered prompts without marker
1097 { role: 'system', content: impersonationPrompt, identifier: 'impersonate' },1097 { role: 'system', content: impersonationPrompt, identifier: 'impersonate' },
1098 { role: 'system', content: quietPrompt, identifier: 'quietPrompt' },1098 { role: 'system', content: quietPrompt, identifier: 'quietPrompt' },
1099 { role: 'system', content: bias, identifier: 'bias' },
1100 { role: 'system', content: groupNudge, identifier: 'groupNudge' },1099 { role: 'system', content: groupNudge, identifier: 'groupNudge' },
1100 { role: 'assistant', content: bias, identifier: 'bias' },
1101 ];1101 ];
11021102
1103 // Tavern Extras - Summary1103 // Tavern Extras - Summary
@@ -2095,7 +2095,7 @@ async function sendOpenAIRequest(type, messages, signal) {
2095 let text = '';2095 let text = '';
2096 const swipes = [];2096 const swipes = [];
2097 const toolCalls = [];2097 const toolCalls = [];
2098 const state = {};2098 const state = { reasoning: '' };
2099 while (true) {2099 while (true) {
2100 const { done, value } = await reader.read();2100 const { done, value } = await reader.read();
2101 if (done) return;2101 if (done) return;
@@ -2113,7 +2113,7 @@ async function sendOpenAIRequest(type, messages, signal) {
21132113
2114 ToolManager.parseToolCalls(toolCalls, parsed);2114 ToolManager.parseToolCalls(toolCalls, parsed);
21152115
2116 yield { text, swipes: swipes, logprobs: parseChatCompletionLogprobs(parsed), toolCalls: toolCalls };2116 yield { text, swipes: swipes, logprobs: parseChatCompletionLogprobs(parsed), toolCalls: toolCalls, state: state };
2117 }2117 }
2118 };2118 };
2119 }2119 }
@@ -2150,16 +2150,22 @@ function getStreamingReply(data, state) {
2150 if (oai_settings.chat_completion_source === chat_completion_sources.CLAUDE) {2150 if (oai_settings.chat_completion_source === chat_completion_sources.CLAUDE) {
2151 return data?.delta?.text || '';2151 return data?.delta?.text || '';
2152 } else if (oai_settings.chat_completion_source === chat_completion_sources.MAKERSUITE) {2152 } else if (oai_settings.chat_completion_source === chat_completion_sources.MAKERSUITE) {
2153 return data?.candidates?.[0]?.content?.parts?.filter(x => oai_settings.show_thoughts || !x.thought)?.map(x => x.text)?.filter(x => x)?.join('\n\n') || '';2153 if (oai_settings.show_thoughts) {
2154 state.reasoning += (data?.candidates?.[0]?.content?.parts?.filter(x => x.thought)?.map(x => x.text)?.[0] || '');
2155 }
2156 return data?.candidates?.[0]?.content?.parts?.filter(x => !x.thought)?.map(x => x.text)?.[0] || '';
2154 } else if (oai_settings.chat_completion_source === chat_completion_sources.COHERE) {2157 } else if (oai_settings.chat_completion_source === chat_completion_sources.COHERE) {
2155 return data?.delta?.message?.content?.text || data?.delta?.message?.tool_plan || '';2158 return data?.delta?.message?.content?.text || data?.delta?.message?.tool_plan || '';
2156 } else if (oai_settings.chat_completion_source === chat_completion_sources.DEEPSEEK) {2159 } else if (oai_settings.chat_completion_source === chat_completion_sources.DEEPSEEK) {
2157 const hadThoughts = state.hadThoughts;2160 if (oai_settings.show_thoughts) {
2158 const thoughts = data.choices?.filter(x => oai_settings.show_thoughts || !x?.delta?.reasoning_content)?.[0]?.delta?.reasoning_content || '';2161 state.reasoning += (data.choices?.filter(x => x?.delta?.reasoning_content)?.[0]?.delta?.reasoning_content || '');
2159 const content = data.choices?.[0]?.delta?.content || '';2162 }
2160 state.hadThoughts = !!thoughts;2163 return data.choices?.[0]?.delta?.content || '';
2161 const separator = hadThoughts && !thoughts ? '\n\n' : '';2164 } else if (oai_settings.chat_completion_source === chat_completion_sources.OPENROUTER) {
2162 return [thoughts, separator, content].filter(x => x).join('\n\n');2165 if (oai_settings.show_thoughts) {
2166 state.reasoning += (data.choices?.filter(x => x?.delta?.reasoning)?.[0]?.delta?.reasoning || '');
2167 }
2168 return data.choices?.[0]?.delta?.content ?? data.choices?.[0]?.message?.content ?? data.choices?.[0]?.text ?? '';
2163 } else {2169 } else {
2164 return data.choices?.[0]?.delta?.content ?? data.choices?.[0]?.message?.content ?? data.choices?.[0]?.text ?? '';2170 return data.choices?.[0]?.delta?.content ?? data.choices?.[0]?.message?.content ?? data.choices?.[0]?.text ?? '';
2165 }2171 }
public/scripts/power-user.js+8 -0
@@ -253,6 +253,14 @@ let power_user = {
253 content: 'Write {{char}}\'s next reply in a fictional chat between {{char}} and {{user}}.',253 content: 'Write {{char}}\'s next reply in a fictional chat between {{char}} and {{user}}.',
254 },254 },
255255
256 reasoning: {
257 add_to_prompts: false,
258 prefix: '<think>\n',
259 suffix: '\n</think>',
260 separator: '\n\n',
261 max_additions: 1,
262 },
263
256 personas: {},264 personas: {},
257 default_persona: null,265 default_persona: null,
258 persona_descriptions: {},266 persona_descriptions: {},
public/scripts/reasoning.js+297 -0
@@ -0,0 +1,297 @@
1import { chat, closeMessageEditor, saveChatConditional, saveSettingsDebounced, substituteParams, updateMessageBlock } from '../script.js';
2import { t } from './i18n.js';
3import { MacrosParser } from './macros.js';
4import { Popup } from './popup.js';
5import { power_user } from './power-user.js';
6import { SlashCommand } from './slash-commands/SlashCommand.js';
7import { ARGUMENT_TYPE, SlashCommandArgument, SlashCommandNamedArgument } from './slash-commands/SlashCommandArgument.js';
8import { commonEnumProviders } from './slash-commands/SlashCommandCommonEnumsProvider.js';
9import { SlashCommandParser } from './slash-commands/SlashCommandParser.js';
10import { copyText } from './utils.js';
11
12/**
13 * Gets a message from a jQuery element.
14 * @param {Element} element
15 * @returns {{messageId: number, message: object, messageBlock: JQuery<HTMLElement>}}
16 */
17function getMessageFromJquery(element) {
18 const messageBlock = $(element).closest('.mes');
19 const messageId = Number(messageBlock.attr('mesid'));
20 const message = chat[messageId];
21 return { messageId: messageId, message, messageBlock };
22}
23
24/**
25 * Helper class for adding reasoning to messages.
26 * Keeps track of the number of reasoning additions.
27 */
28export class PromptReasoning {
29 static REASONING_PLACEHOLDER = '\u200B';
30 static REASONING_PLACEHOLDER_REGEX = new RegExp(`${PromptReasoning.REASONING_PLACEHOLDER}$`);
31
32 constructor() {
33 this.counter = 0;
34 }
35
36 /**
37 * Checks if the limit of reasoning additions has been reached.
38 * @returns {boolean} True if the limit of reasoning additions has been reached, false otherwise.
39 */
40 isLimitReached() {
41 if (!power_user.reasoning.add_to_prompts) {
42 return true;
43 }
44
45 return this.counter >= power_user.reasoning.max_additions;
46 }
47
48 /**
49 * Add reasoning to a message according to the power user settings.
50 * @param {string} content Message content
51 * @param {string} reasoning Message reasoning
52 * @returns {string} Message content with reasoning
53 */
54 addToMessage(content, reasoning) {
55 // Disabled or reached limit of additions
56 if (!power_user.reasoning.add_to_prompts || this.counter >= power_user.reasoning.max_additions) {
57 return content;
58 }
59
60 // No reasoning provided or a placeholder
61 if (!reasoning || reasoning === PromptReasoning.REASONING_PLACEHOLDER) {
62 return content;
63 }
64
65 // Increment the counter
66 this.counter++;
67
68 // Substitute macros in variable parts
69 const prefix = substituteParams(power_user.reasoning.prefix || '');
70 const separator = substituteParams(power_user.reasoning.separator || '');
71 const suffix = substituteParams(power_user.reasoning.suffix || '');
72
73 // Combine parts with reasoning and content
74 return `${prefix}${reasoning}${suffix}${separator}${content}`;
75 }
76}
77
78function loadReasoningSettings() {
79 $('#reasoning_add_to_prompts').prop('checked', power_user.reasoning.add_to_prompts);
80 $('#reasoning_add_to_prompts').on('change', function () {
81 power_user.reasoning.add_to_prompts = !!$(this).prop('checked');
82 saveSettingsDebounced();
83 });
84
85 $('#reasoning_prefix').val(power_user.reasoning.prefix);
86 $('#reasoning_prefix').on('input', function () {
87 power_user.reasoning.prefix = String($(this).val());
88 saveSettingsDebounced();
89 });
90
91 $('#reasoning_suffix').val(power_user.reasoning.suffix);
92 $('#reasoning_suffix').on('input', function () {
93 power_user.reasoning.suffix = String($(this).val());
94 saveSettingsDebounced();
95 });
96
97 $('#reasoning_separator').val(power_user.reasoning.separator);
98 $('#reasoning_separator').on('input', function () {
99 power_user.reasoning.separator = String($(this).val());
100 saveSettingsDebounced();
101 });
102
103 $('#reasoning_max_additions').val(power_user.reasoning.max_additions);
104 $('#reasoning_max_additions').on('input', function () {
105 power_user.reasoning.max_additions = Number($(this).val());
106 saveSettingsDebounced();
107 });
108}
109
110function registerReasoningSlashCommands() {
111 SlashCommandParser.addCommandObject(SlashCommand.fromProps({
112 name: 'reasoning-get',
113 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.`,
115 unnamedArgumentList: [
116 SlashCommandArgument.fromProps({
117 description: 'Message ID. If not provided, the message ID of the last message is used.',
118 typeList: ARGUMENT_TYPE.NUMBER,
119 enumProvider: commonEnumProviders.messages(),
120 }),
121 ],
122 callback: (_args, value) => {
123 const messageId = !isNaN(Number(value)) ? Number(value) : chat.length - 1;
124 const message = chat[messageId];
125 const reasoning = String(message?.extra?.reasoning ?? '');
126 return reasoning.replace(PromptReasoning.REASONING_PLACEHOLDER_REGEX, '');
127 },
128 }));
129
130 SlashCommandParser.addCommandObject(SlashCommand.fromProps({
131 name: 'reasoning-set',
132 returns: ARGUMENT_TYPE.STRING,
133 helpString: t`Set the reasoning block of a message. Returns the reasoning block content.`,
134 namedArgumentList: [
135 SlashCommandNamedArgument.fromProps({
136 name: 'at',
137 description: 'Message ID. If not provided, the message ID of the last message is used.',
138 typeList: ARGUMENT_TYPE.NUMBER,
139 enumProvider: commonEnumProviders.messages(),
140 }),
141 ],
142 unnamedArgumentList: [
143 SlashCommandArgument.fromProps({
144 description: 'Reasoning block content.',
145 typeList: ARGUMENT_TYPE.STRING,
146 }),
147 ],
148 callback: async (args, value) => {
149 const messageId = !isNaN(Number(args[0])) ? Number(args[0]) : chat.length - 1;
150 const message = chat[messageId];
151 if (!message?.extra) {
152 return '';
153 }
154
155 message.extra.reasoning = String(value ?? '');
156 await saveChatConditional();
157
158 closeMessageEditor('reasoning');
159 updateMessageBlock(messageId, message);
160 return message.extra.reasoning;
161 },
162 }));
163}
164
165function registerReasoningMacros() {
166 MacrosParser.registerMacro('reasoningPrefix', () => power_user.reasoning.prefix, t`Reasoning Prefix`);
167 MacrosParser.registerMacro('reasoningSuffix', () => power_user.reasoning.suffix, t`Reasoning Suffix`);
168 MacrosParser.registerMacro('reasoningSeparator', () => power_user.reasoning.separator, t`Reasoning Separator`);
169}
170
171function setReasoningEventHandlers(){
172 $(document).on('click', '.mes_reasoning_copy', (e) => {
173 e.stopPropagation();
174 e.preventDefault();
175 });
176
177 $(document).on('click', '.mes_reasoning_edit', function (e) {
178 e.stopPropagation();
179 e.preventDefault();
180 const { message, messageBlock } = getMessageFromJquery(this);
181 if (!message?.extra) {
182 return;
183 }
184
185 const reasoning = String(message?.extra?.reasoning ?? '');
186 const chatElement = document.getElementById('chat');
187 const textarea = document.createElement('textarea');
188 const reasoningBlock = messageBlock.find('.mes_reasoning');
189 textarea.classList.add('reasoning_edit_textarea');
190 textarea.value = reasoning.replace(PromptReasoning.REASONING_PLACEHOLDER_REGEX, '');
191 $(textarea).insertBefore(reasoningBlock);
192
193 if (!CSS.supports('field-sizing', 'content')) {
194 const resetHeight = function () {
195 const scrollTop = chatElement.scrollTop;
196 textarea.style.height = '0px';
197 textarea.style.height = `${textarea.scrollHeight}px`;
198 chatElement.scrollTop = scrollTop;
199 };
200
201 textarea.addEventListener('input', resetHeight);
202 resetHeight();
203 }
204
205 textarea.focus();
206 textarea.setSelectionRange(textarea.value.length, textarea.value.length);
207
208 const textareaRect = textarea.getBoundingClientRect();
209 const chatRect = chatElement.getBoundingClientRect();
210
211 // Scroll if textarea bottom is below visible area
212 if (textareaRect.bottom > chatRect.bottom) {
213 const scrollOffset = textareaRect.bottom - chatRect.bottom;
214 chatElement.scrollTop += scrollOffset;
215 }
216 });
217
218 $(document).on('click', '.mes_reasoning_edit_done', async function (e) {
219 e.stopPropagation();
220 e.preventDefault();
221 const { message, messageId, messageBlock } = getMessageFromJquery(this);
222 if (!message?.extra) {
223 return;
224 }
225
226 const textarea = messageBlock.find('.reasoning_edit_textarea');
227 const reasoning = String(textarea.val());
228 message.extra.reasoning = reasoning;
229 await saveChatConditional();
230 updateMessageBlock(messageId, message);
231 textarea.remove();
232 });
233
234 $(document).on('click', '.mes_reasoning_edit_cancel', function (e) {
235 e.stopPropagation();
236 e.preventDefault();
237
238 const { messageBlock } = getMessageFromJquery(this);
239 const textarea = messageBlock.find('.reasoning_edit_textarea');
240 textarea.remove();
241 });
242
243 $(document).on('click', '.mes_edit_add_reasoning', async function () {
244 const { message, messageId } = getMessageFromJquery(this);
245 if (!message?.extra) {
246 return;
247 }
248
249 if (message.extra.reasoning) {
250 toastr.info(t`Reasoning already exists.`, t`Edit Message`);
251 return;
252 }
253
254 message.extra.reasoning = PromptReasoning.REASONING_PLACEHOLDER;
255 await saveChatConditional();
256 closeMessageEditor();
257 updateMessageBlock(messageId, message);
258 });
259
260 $(document).on('click', '.mes_reasoning_delete', async function (e) {
261 e.stopPropagation();
262 e.preventDefault();
263
264 const confirm = await Popup.show.confirm(t`Are you sure you want to clear the reasoning?`, t`Visible message contents will stay intact.`);
265
266 if (!confirm) {
267 return;
268 }
269
270 const { message, messageId } = getMessageFromJquery(this);
271 if (!message?.extra) {
272 return;
273 }
274 message.extra.reasoning = '';
275 await saveChatConditional();
276 updateMessageBlock(messageId, message);
277 });
278
279 $(document).on('pointerup', '.mes_reasoning_copy', async function () {
280 const { message } = getMessageFromJquery(this);
281 const reasoning = String(message?.extra?.reasoning ?? '').replace(PromptReasoning.REASONING_PLACEHOLDER_REGEX, '');
282
283 if (!reasoning) {
284 return;
285 }
286
287 await copyText(reasoning);
288 toastr.info(t`Copied!`, '', { timeOut: 2000 });
289 });
290}
291
292export function initReasoning() {
293 loadReasoningSettings();
294 setReasoningEventHandlers();
295 registerReasoningSlashCommands();
296 registerReasoningMacros();
297}
public/scripts/slash-commands.js+4 -0
@@ -42,6 +42,7 @@ import {
42 showMoreMessages,42 showMoreMessages,
43 stopGeneration,43 stopGeneration,
44 substituteParams,44 substituteParams,
45 syncCurrentSwipeInfoExtras,
45 system_avatar,46 system_avatar,
46 system_message_types,47 system_message_types,
47 this_chid,48 this_chid,
@@ -2862,8 +2863,11 @@ async function addSwipeCallback(args, value) {
2862 const newSwipeId = lastMessage.swipes.length - 1;2863 const newSwipeId = lastMessage.swipes.length - 1;
28632864
2864 if (isTrueBoolean(args.switch)) {2865 if (isTrueBoolean(args.switch)) {
2866 // Make sure ad-hoc changes to extras are saved before swiping away
2867 syncCurrentSwipeInfoExtras();
2865 lastMessage.swipe_id = newSwipeId;2868 lastMessage.swipe_id = newSwipeId;
2866 lastMessage.mes = lastMessage.swipes[newSwipeId];2869 lastMessage.mes = lastMessage.swipes[newSwipeId];
2870 lastMessage.extra = structuredClone(lastMessage.swipe_info?.[newSwipeId]?.extra ?? lastMessage.extra ?? {});
2867 }2871 }
28682872
2869 await saveChatConditional();2873 await saveChatConditional();
public/scripts/sse-stream.js+15 -0
@@ -235,6 +235,21 @@ async function* parseStreamData(json) {
235 }235 }
236 return;236 return;
237 }237 }
238 else if (typeof json.choices[0].delta.reasoning === 'string' && json.choices[0].delta.reasoning.length > 0) {
239 for (let j = 0; j < json.choices[0].delta.reasoning.length; j++) {
240 const str = json.choices[0].delta.reasoning[j];
241 const isLastSymbol = j === json.choices[0].delta.reasoning.length - 1;
242 const choiceClone = structuredClone(json.choices[0]);
243 choiceClone.delta.reasoning = str;
244 choiceClone.delta.content = isLastSymbol ? choiceClone.delta.content : '';
245 const choices = [choiceClone];
246 yield {
247 data: { ...json, choices },
248 chunk: str,
249 };
250 }
251 return;
252 }
238 else if (typeof json.choices[0].delta.content === 'string' && json.choices[0].delta.content.length > 0) {253 else if (typeof json.choices[0].delta.content === 'string' && json.choices[0].delta.content.length > 0) {
239 for (let j = 0; j < json.choices[0].delta.content.length; j++) {254 for (let j = 0; j < json.choices[0].delta.content.length; j++) {
240 const str = json.choices[0].delta.content[j];255 const str = json.choices[0].delta.content[j];
public/scripts/st-context.js+4 -0
@@ -1,6 +1,7 @@
1import {1import {
2 activateSendButtons,2 activateSendButtons,
3 addOneMessage,3 addOneMessage,
4 appendMediaToMessage,
4 callPopup,5 callPopup,
5 characters,6 characters,
6 chat,7 chat,
@@ -41,6 +42,7 @@ import {
41 substituteParamsExtended,42 substituteParamsExtended,
42 this_chid,43 this_chid,
43 updateChatMetadata,44 updateChatMetadata,
45 updateMessageBlock,
44} from '../script.js';46} from '../script.js';
45import {47import {
46 extension_settings,48 extension_settings,
@@ -171,6 +173,8 @@ export function getContext() {
171 getCharacters,173 getCharacters,
172 uuidv4,174 uuidv4,
173 humanizedDateTime,175 humanizedDateTime,
176 updateMessageBlock,
177 appendMediaToMessage,
174 };178 };
175}179}
176180
public/scripts/textgen-settings.js+2 -1
@@ -986,6 +986,7 @@ export async function generateTextGenWithStreaming(generate_data, signal) {
986 let logprobs = null;986 let logprobs = null;
987 const swipes = [];987 const swipes = [];
988 const toolCalls = [];988 const toolCalls = [];
989 const state = {};
989 while (true) {990 while (true) {
990 const { done, value } = await reader.read();991 const { done, value } = await reader.read();
991 if (done) return;992 if (done) return;
@@ -1004,7 +1005,7 @@ export async function generateTextGenWithStreaming(generate_data, signal) {
1004 logprobs = parseTextgenLogprobs(newText, data.choices?.[0]?.logprobs || data?.completion_probabilities);1005 logprobs = parseTextgenLogprobs(newText, data.choices?.[0]?.logprobs || data?.completion_probabilities);
1005 }1006 }
10061007
1007 yield { text, swipes, logprobs, toolCalls };1008 yield { text, swipes, logprobs, toolCalls, state };
1008 }1009 }
1009 };1010 };
1010}1011}
public/style.css+106 -21
@@ -292,36 +292,44 @@ input[type='checkbox']:focus-visible {
292 filter: grayscale(25%);292 filter: grayscale(25%);
293}293}
294294
295.mes_text table {295.mes_text table,
296.mes_reasoning table {
296 border-spacing: 0;297 border-spacing: 0;
297 border-collapse: collapse;298 border-collapse: collapse;
298 margin-bottom: 10px;299 margin-bottom: 10px;
299}300}
300301
301.mes_text td,302.mes_text td,
302.mes_text th {303.mes_text th,
304.mes_reasoning td,
305.mes_reasoning th {
303 border: 1px solid;306 border: 1px solid;
304 border-collapse: collapse;307 border-collapse: collapse;
305 padding: 0.25em;308 padding: 0.25em;
306}309}
307310
308.mes_text p {311.mes_text p,
312.mes_reasoning p {
309 margin-top: 0;313 margin-top: 0;
310 margin-bottom: 10px;314 margin-bottom: 10px;
311}315}
312316
313.mes_text li tt {317.mes_text li tt,
318.mes_reasoning li tt {
314 display: inline-block;319 display: inline-block;
315}320}
316321
317.mes_text ol,322.mes_text ol,
318.mes_text ul {323.mes_text ul,
324.mes_reasoning ol,
325.mes_reasoning ul {
319 margin-top: 5px;326 margin-top: 5px;
320 margin-bottom: 5px;327 margin-bottom: 5px;
321}328}
322329
323.mes_text br,330.mes_text br,
324.mes_bias br {331.mes_bias br,
332.mes_reasoning br {
325 content: ' ';333 content: ' ';
326}334}
327335
@@ -332,25 +340,83 @@ input[type='checkbox']:focus-visible {
332 color: var(--SmartThemeQuoteColor);340 color: var(--SmartThemeQuoteColor);
333}341}
334342
343.mes_reasoning {
344 display: block;
345 border: 1px solid var(--SmartThemeBorderColor);
346 background-color: var(--black30a);
347 border-radius: 5px;
348 padding: 5px;
349 margin: 5px 0;
350 overflow-y: auto;
351}
352
353.mes_reasoning_summary {
354 cursor: pointer;
355 position: relative;
356 margin: 2px;
357}
358
359@supports not selector(:has(*)) {
360 .mes_reasoning_details {
361 display: none !important;
362 }
363}
364
365.mes_bias:empty,
366.mes_reasoning:empty,
367.mes_reasoning_details:has(.mes_reasoning:empty),
368.mes_block:has(.edit_textarea) .mes_reasoning_details,
369.mes_reasoning_details:not([open]) .mes_reasoning_actions,
370.mes_reasoning_details:has(.reasoning_edit_textarea) .mes_reasoning,
371.mes_reasoning_details:not(:has(.reasoning_edit_textarea)) .mes_reasoning_actions .mes_button.mes_reasoning_edit_done,
372.mes_reasoning_details:not(:has(.reasoning_edit_textarea)) .mes_reasoning_actions .mes_button.mes_reasoning_edit_cancel,
373.mes_reasoning_details:has(.reasoning_edit_textarea) .mes_reasoning_actions .mes_button:not(.mes_reasoning_edit_done, .mes_reasoning_edit_cancel) {
374 display: none;
375}
376
377.mes_reasoning_actions {
378 position: absolute;
379 right: 0;
380 top: 0;
381
382 display: flex;
383 gap: 4px;
384 flex-wrap: nowrap;
385 justify-content: flex-end;
386 transition: all 200ms;
387 overflow-x: hidden;
388 padding: 1px;
389}
390
391.mes_reasoning_summary>span {
392 margin-left: 0.5em;
393}
394
335.mes_text i,395.mes_text i,
336.mes_text em {396.mes_text em,
397.mes_reasoning i,
398.mes_reasoning em {
337 color: var(--SmartThemeEmColor);399 color: var(--SmartThemeEmColor);
338}400}
339401
340.mes_text u {402.mes_text u,
403.mes_reasoning u {
341 color: var(--SmartThemeUnderlineColor);404 color: var(--SmartThemeUnderlineColor);
342}405}
343406
344.mes_text q {407.mes_text q,
408.mes_reasoning q {
345 color: var(--SmartThemeQuoteColor);409 color: var(--SmartThemeQuoteColor);
346}410}
347411
348.mes_text font[color] em,412.mes_text font[color] em,
349.mes_text font[color] i {413.mes_text font[color] i,
350 color: inherit;414.mes_text font[color] u,
351}415.mes_text font[color] q,
352416.mes_reasoning font[color] em,
353.mes_text font[color] q {417.mes_reasoning font[color] i,
418.mes_reasoning font[color] u,
419.mes_reasoning font[color] q {
354 color: inherit;420 color: inherit;
355}421}
356422
@@ -358,7 +424,8 @@ input[type='checkbox']:focus-visible {
358 display: block;424 display: block;
359}425}
360426
361.mes_text blockquote {427.mes_text blockquote,
428.mes_reasoning blockquote {
362 border-left: 3px solid var(--SmartThemeQuoteColor);429 border-left: 3px solid var(--SmartThemeQuoteColor);
363 padding-left: 10px;430 padding-left: 10px;
364 background-color: var(--black30a);431 background-color: var(--black30a);
@@ -368,18 +435,24 @@ input[type='checkbox']:focus-visible {
368.mes_text strong em,435.mes_text strong em,
369.mes_text strong,436.mes_text strong,
370.mes_text h2,437.mes_text h2,
371.mes_text h1 {438.mes_text h1,
439.mes_reasoning strong em,
440.mes_reasoning strong,
441.mes_reasoning h2,
442.mes_reasoning h1 {
372 font-weight: bold;443 font-weight: bold;
373}444}
374445
375.mes_text pre code {446.mes_text pre code,
447.mes_reasoning pre code {
376 position: relative;448 position: relative;
377 display: block;449 display: block;
378 overflow-x: auto;450 overflow-x: auto;
379 padding: 1em;451 padding: 1em;
380}452}
381453
382.mes_text img:not(.mes_img) {454.mes_text img:not(.mes_img),
455.mes_reasoning img:not(.mes_img) {
383 max-width: 100%;456 max-width: 100%;
384 max-height: var(--doc-height);457 max-height: var(--doc-height);
385}458}
@@ -1022,6 +1095,11 @@ body .panelControlBar {
1022 /*only affects bubblechat to make it sit nicely at the bottom*/1095 /*only affects bubblechat to make it sit nicely at the bottom*/
1023}1096}
10241097
1098.last_mes:has(.mes_text:empty):has(.mes_reasoning_details[open]) .mes_reasoning:not(:empty) {
1099 margin-bottom: 30px;
1100}
1101
1102.last_mes .mes_reasoning,
1025.last_mes .mes_text {1103.last_mes .mes_text {
1026 padding-right: 30px;1104 padding-right: 30px;
1027}1105}
@@ -1242,14 +1320,18 @@ body.swipeAllMessages .mes:not(.last_mes) .swipes-counter {
1242 overflow-y: clip;1320 overflow-y: clip;
1243}1321}
12441322
1245.mes_text {1323.mes_text,
1324.mes_reasoning {
1246 font-weight: 500;1325 font-weight: 500;
1247 line-height: calc(var(--mainFontSize) + .5rem);1326 line-height: calc(var(--mainFontSize) + .5rem);
1327 max-width: 100%;
1328 overflow-wrap: anywhere;
1329}
1330
1331.mes_text {
1248 padding-left: 0;1332 padding-left: 0;
1249 padding-top: 5px;1333 padding-top: 5px;
1250 padding-bottom: 5px;1334 padding-bottom: 5px;
1251 max-width: 100%;
1252 overflow-wrap: anywhere;
1253}1335}
12541336
1255br {1337br {
@@ -4169,10 +4251,12 @@ input[type="range"]::-webkit-slider-thumb {
4169 align-items: center;4251 align-items: center;
4170}4252}
41714253
4254.mes_reasoning_edit_cancel,
4172.mes_edit_cancel.menu_button {4255.mes_edit_cancel.menu_button {
4173 background-color: var(--crimson70a);4256 background-color: var(--crimson70a);
4174}4257}
41754258
4259.mes_reasoning_edit_done,
4176.mes_edit_done.menu_button {4260.mes_edit_done.menu_button {
4177 background-color: var(--okGreen70a);4261 background-color: var(--okGreen70a);
4178}4262}
@@ -4181,6 +4265,7 @@ input[type="range"]::-webkit-slider-thumb {
4181 opacity: 1;4265 opacity: 1;
4182}4266}
41834267
4268.reasoning_edit_textarea,
4184.edit_textarea {4269.edit_textarea {
4185 padding: 5px;4270 padding: 5px;
4186 margin: 0;4271 margin: 0;
src/endpoints/backends/chat-completions.js+13 -7
@@ -289,6 +289,7 @@ async function sendMakerSuiteRequest(request, response) {
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.show_thoughts);
292 const isThinking = model.includes('thinking');
292293
293 const generationConfig = {294 const generationConfig = {
294 stopSequences: request.body.stop,295 stopSequences: request.body.stop,
@@ -329,6 +330,12 @@ async function sendMakerSuiteRequest(request, response) {
329 body.systemInstruction = prompt.system_instruction;330 body.systemInstruction = prompt.system_instruction;
330 }331 }
331332
333 if (isThinking && showThoughts) {
334 generationConfig.thinkingConfig = {
335 includeThoughts: true,
336 };
337 }
338
332 return body;339 return body;
333 }340 }
334341
@@ -342,7 +349,6 @@ async function sendMakerSuiteRequest(request, response) {
342 controller.abort();349 controller.abort();
343 });350 });
344351
345 const isThinking = model.includes('thinking');
346 const apiVersion = isThinking ? 'v1alpha' : 'v1beta';352 const apiVersion = isThinking ? 'v1alpha' : 'v1beta';
347 const responseType = (stream ? 'streamGenerateContent' : 'generateContent');353 const responseType = (stream ? 'streamGenerateContent' : 'generateContent');
348354
@@ -387,11 +393,7 @@ async function sendMakerSuiteRequest(request, response) {
387 const responseContent = candidates[0].content ?? candidates[0].output;393 const responseContent = candidates[0].content ?? candidates[0].output;
388 console.log('Google AI Studio response:', responseContent);394 console.log('Google AI Studio response:', responseContent);
389395
390 if (Array.isArray(responseContent?.parts) && isThinking && !showThoughts) {396 const responseText = typeof responseContent === 'string' ? responseContent : responseContent?.parts?.filter(part => !part.thought)?.map(part => part.text)?.join('\n\n');
391 responseContent.parts = responseContent.parts.filter(part => !part.thought);
392 }
393
394 const responseText = typeof responseContent === 'string' ? responseContent : responseContent?.parts?.map(part => part.text)?.join('\n\n');
395 if (!responseText) {397 if (!responseText) {
396 let message = 'Google AI Studio Candidate text empty';398 let message = 'Google AI Studio Candidate text empty';
397 console.log(message, generateResponseJson);399 console.log(message, generateResponseJson);
@@ -399,7 +401,7 @@ async function sendMakerSuiteRequest(request, response) {
399 }401 }
400402
401 // Wrap it back to OAI format403 // Wrap it back to OAI format
402 const reply = { choices: [{ 'message': { 'content': responseText } }] };404 const reply = { choices: [{ 'message': { 'content': responseText } }], responseContent };
403 return response.send(reply);405 return response.send(reply);
404 }406 }
405 } catch (error) {407 } catch (error) {
@@ -996,6 +998,10 @@ router.post('/generate', jsonParser, function (request, response) {
996 bodyParams['route'] = 'fallback';998 bodyParams['route'] = 'fallback';
997 }999 }
9981000
1001 if (request.body.show_thoughts) {
1002 bodyParams['include_reasoning'] = true;
1003 }
1004
999 let cachingAtDepth = getConfigValue('claude.cachingAtDepth', -1);1005 let cachingAtDepth = getConfigValue('claude.cachingAtDepth', -1);
1000 if (Number.isInteger(cachingAtDepth) && cachingAtDepth >= 0 && request.body.model?.startsWith('anthropic/claude-3')) {1006 if (Number.isInteger(cachingAtDepth) && cachingAtDepth >= 0 && request.body.model?.startsWith('anthropic/claude-3')) {
1001 cachingAtDepthForOpenRouterClaude(request.body.messages, cachingAtDepth);1007 cachingAtDepthForOpenRouterClaude(request.body.messages, cachingAtDepth);