The THONKening

afae8d02be1e837b1dc5abc013fa011ab4c3d067

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

10 files changed, +130 -49Ignore whitespace
public/index.html+1 -0
@@ -6229,6 +6229,7 @@
62296229 <div class="mes_edit_cancel menu_button fa-solid fa-xmark" title="Cancel" data-i18n="[title]Cancel"></div>
62306230 </div>
62316231 </div>
6232+ <div class="mes_reasoning"></div>
62326233 <div class="mes_text"></div>
62336234 <div class="mes_img_container">
62346235 <div class="mes_img_controls">
public/script.js+78 -33
@@ -170,7 +170,7 @@ import {
170170 isElementInViewport,
171171 copyText,
172172} from './scripts/utils.js';
173173import { debounce_timeout, THINK_BREAK } from './scripts/constants.js';
174174
175175import { doDailyExtensionUpdatesCheck, extension_settings, initExtensions, loadExtensionSettings, runGenerationInterceptors, saveMetadataDebounced } from './scripts/extensions.js';
176176import { COMMENT_NAME_DEFAULT, executeSlashCommandsOnChatInput, getSlashCommandsHelp, initDefaultSlashCommands, isExecutingCommandsFromChatInput, pauseScriptExecution, processChatSlashCommands, stopScriptExecution } from './scripts/slash-commands.js';
@@ -2199,6 +2199,7 @@ function getMessageFromTemplate({
21992199 isUser,
22002200 avatarImg,
22012201 bias,
2202+ reasoning,
22022203 isSystem,
22032204 title,
22042205 timerValue,
@@ -2223,6 +2224,7 @@ function getMessageFromTemplate({
22232224 mes.find('.avatar img').attr('src', avatarImg);
22242225 mes.find('.ch_name .name_text').text(characterName);
22252226 mes.find('.mes_bias').html(bias);
2227+ mes.find('.mes_reasoning').html(reasoning);
22262228 mes.find('.timestamp').text(timestamp).attr('title', `${extra?.api ? extra.api + ' - ' : ''}${extra?.model ?? ''}`);
22272229 mes.find('.mesIDDisplay').text(`#${mesId}`);
22282230 tokenCount && mes.find('.tokenCounterDisplay').text(`${tokenCount}t`);
@@ -2241,6 +2243,7 @@ export function updateMessageBlock(messageId, message) {
22412243 const messageElement = $(`#chat [mesid="${messageId}"]`);
22422244 const text = message?.extra?.display_text ?? message.mes;
22432245 messageElement.find('.mes_text').html(messageFormatting(text, message.name, message.is_system, message.is_user, messageId));
2246+ messageElement.find('.mes_reasoning').html(messageFormatting(message.extra?.reasoning ?? '', '', false, false, -1));
22442247 addCopyToCodeBlocks(messageElement);
22452248 appendMediaToMessage(message, messageElement);
22462249}
@@ -2399,6 +2402,7 @@ export function addOneMessage(mes, { type = 'normal', insertAfter = null, scroll
23992402 sanitizerOverrides,
24002403 );
24012404 const bias = messageFormatting(mes.extra?.bias ?? '', '', false, false, -1);
2405+ const reasoning = messageFormatting(mes.extra?.reasoning ?? '', '', false, false, -1);
24022406 let bookmarkLink = mes?.extra?.bookmark_link ?? '';
24032407
24042408 let params = {
@@ -2408,6 +2412,7 @@ export function addOneMessage(mes, { type = 'normal', insertAfter = null, scroll
24082412 isUser: mes.is_user,
24092413 avatarImg: avatarImg,
24102414 bias: bias,
2415+ reasoning: reasoning,
24112416 isSystem: isSystem,
24122417 title: title,
24132418 bookmarkLink: bookmarkLink,
@@ -2467,6 +2472,7 @@ export function addOneMessage(mes, { type = 'normal', insertAfter = null, scroll
24672472 const swipeMessage = chatElement.find(`[mesid="${chat.length - 1}"]`);
24682473 swipeMessage.attr('swipeid', params.swipeId);
24692474 swipeMessage.find('.mes_text').html(messageText).attr('title', title);
2475+ swipeMessage.find('.mes_reasoning').html(reasoning);
24702476 swipeMessage.find('.timestamp').text(timestamp).attr('title', `${params.extra.api} - ${params.extra.model}`);
24712477 appendMediaToMessage(mes, swipeMessage);
24722478 if (power_user.timestamp_model_icon && params.extra?.api) {
@@ -3077,6 +3083,7 @@ class StreamingProcessor {
30773083 this.messageTextDom = null;
30783084 this.messageTimerDom = null;
30793085 this.messageTokenCounterDom = null;
3086+ this.messageReasoningDom = null;
30803087 /** @type {HTMLTextAreaElement} */
30813088 this.sendTextarea = document.querySelector('#send_textarea');
30823089 this.type = type;
@@ -3092,6 +3099,7 @@ class StreamingProcessor {
30923099 /** @type {import('./scripts/logprobs.js').TokenLogprobs[]} */
30933100 this.messageLogprobs = [];
30943101 this.toolCalls = [];
3102+ this.reasoning = '';
30953103 }
30963104
30973105 #checkDomElements(messageId) {
@@ -3100,6 +3108,7 @@ class StreamingProcessor {
31003108 this.messageTextDom = this.messageDom?.querySelector('.mes_text');
31013109 this.messageTimerDom = this.messageDom?.querySelector('.mes_timer');
31023110 this.messageTokenCounterDom = this.messageDom?.querySelector('.tokenCounterDisplay');
3111+ this.messageReasoningDom = this.messageDom?.querySelector('.mes_reasoning');
31033112 }
31043113 }
31053114
@@ -3184,11 +3193,17 @@ class StreamingProcessor {
31843193 chat[messageId]['gen_started'] = this.timeStarted;
31853194 chat[messageId]['gen_finished'] = currentTime;
31863195
31873196 if (currentTokenCount!chat[messageId]['extra']) {
31883197 if (!chat[messageId]['extra']) = {};
3189- chat[messageId]['extra'] = {};
3198+ }
3190- }
31913199
3200+ if (this.reasoning && this.messageReasoningDom instanceof HTMLElement) {
3201+ chat[messageId]['extra']['reasoning'] = this.reasoning;
3202+ const formattedReasoning = messageFormatting(this.reasoning, '', false, false, -1);
3203+ this.messageReasoningDom.innerHTML = formattedReasoning;
3204+ }
3205+
3206+ if (currentTokenCount) {
31923207 chat[messageId]['extra']['token_count'] = currentTokenCount;
31933208 if (this.messageTokenCounterDom instanceof HTMLElement) {
31943209 this.messageTokenCounterDom.textContent = `${currentTokenCount}t`;
@@ -3320,7 +3335,7 @@ class StreamingProcessor {
33203335 }
33213336
33223337 /**
33233338 * @returns {Generator<{ text: string, swipes: string[], logprobs: import('./scripts/logprobs.js').TokenLogprobs, toolCalls: any[], state: any }, void, void>}
33243339 */
33253340 *nullStreamingGeneration() {
33263341 throw new Error('Generation function for streaming is not hooked up');
@@ -3342,7 +3357,7 @@ class StreamingProcessor {
33423357 try {
33433358 const sw = new Stopwatch(1000 / power_user.streaming_fps);
33443359 const timestamps = [];
33453360 for await (const { text, swipes, logprobs, toolCalls, state } of this.generator()) {
33463361 timestamps.push(Date.now());
33473362 if (this.isStopped) {
33483363 return;
@@ -3354,6 +3369,7 @@ class StreamingProcessor {
33543369 if (logprobs) {
33553370 this.messageLogprobs.push(...(Array.isArray(logprobs) ? logprobs : [logprobs]));
33563371 }
3372+ this.reasoning = state?.reasoning ?? '';
33573373 await eventSource.emit(event_types.STREAM_TOKEN_RECEIVED, text);
33583374 await sw.tick(() => this.onProgressStreaming(this.messageId, this.continueMessage + text));
33593375 }
@@ -4741,6 +4757,7 @@ export async function Generate(type, { automatic_trigger, force_name2, quiet_pro
47414757 //const getData = await response.json();
47424758 let getMessage = extractMessageFromData(data);
47434759 let title = extractTitleFromData(data);
4760+ let reasoning = extractReasoningFromData(data);
47444761 kobold_horde_model = title;
47454762
47464763 const swipes = extractMultiSwipes(data, type);
@@ -4767,10 +4784,10 @@ export async function Generate(type, { automatic_trigger, force_name2, quiet_pro
47674784 else {
47684785 // Without streaming we'll be having a full message on continuation. Treat it as a last chunk.
47694786 if (originalType !== 'continue') {
47704787 ({ type, getMessage } = await saveReply(type, getMessage, false, title, swipes, reasoning));
47714788 }
47724789 else {
47734790 ({ type, getMessage } = await saveReply('appendFinal', getMessage, false, title, swipes, reasoning));
47744791 }
47754792
47764793 // This relies on `saveReply` having been called to add the message to the chat, so it must be last.
@@ -5649,43 +5666,66 @@ function parseAndSaveLogprobs(data, continueFrom) {
56495666}
56505667
56515668/**
56525669 * ExtractsGets the messagetext context from the response data.
56535670 * @param {object} data Response JSON data
56545671 * @returns {string} Extracted messagetext
56555672 */
56565673function extractMessageFromDatagetTextContextFromData(data) {
56575674 if (typeof data === 'string') {
56585675 return data;
56595676 }
56605677
56615678 functionswitch getTextContext(main_api) {
5662- switch (main_api) {
5679+ case 'kobold':
5663- case 'kobold':
5680+ return data.results[0].text;
5664- return data.results[0].text;
5681+ case 'koboldhorde':
5665- case 'koboldhorde':
5682+ return data.text;
5666- return data.text;
5683+ case 'textgenerationwebui':
5667- case 'textgenerationwebui':
5684+ return data.choices?.[0]?.text ?? data.content ?? data.response ?? '';
5668- return data.choices?.[0]?.text ?? data.content ?? data.response ?? '';
5685+ case 'novel':
5669- case 'novel':
5686+ return data.output;
5670- return data.output;
5687+ case 'openai':
5671- case 'openai':
5688+ return data?.choices?.[0]?.message?.content ?? data?.choices?.[0]?.text ?? data?.text ?? data?.message?.content?.[0]?.text ?? data?.message?.tool_plan ?? '';
5672- return data?.choices?.[0]?.message?.content ?? data?.choices?.[0]?.text ?? data?.text ?? data?.message?.content?.[0]?.text ?? data?.message?.tool_plan ?? '';
5689+ default:
5673- default:
5690+ return '';
5674- return '';
5675- }
56765691 }
5692+}
56775693
5678- const content = getTextContext();
5694+/**
5695+ * Extracts the message from the response data.
5696+ * @param {object} data Response data
5697+ * @returns {string} Extracted message
5698+ */
5699+function extractMessageFromData(data){
5700+ const content = String(getTextContextFromData(data) ?? '');
56795701
5680- if (main_api === 'openai' && oai_settings.chat_completion_source === chat_completion_sources.DEEPSEEK && oai_settings.show_thoughts) {
5702+ if (content.includes(THINK_BREAK)) {
5681- const thoughts = data?.choices?.[0]?.message?.reasoning_content ?? '';
5703+ return content.split(THINK_BREAK)[1];
5682- return [thoughts, content].filter(x => x).join('\n\n');
56835704 }
56845705
56855706 return content;
56865707}
56875708
56885709/**
5710+ * Extracts the reasoning from the response data.
5711+ * @param {object} data Response data
5712+ * @returns {string} Extracted reasoning
5713+ */
5714+function extractReasoningFromData(data) {
5715+ const content = String(getTextContextFromData(data) ?? '');
5716+
5717+ if (content.includes(THINK_BREAK)) {
5718+ return content.split(THINK_BREAK)[0];
5719+ }
5720+
5721+ if (main_api === 'openai' && oai_settings.chat_completion_source === chat_completion_sources.DEEPSEEK && oai_settings.show_thoughts) {
5722+ return data?.choices?.[0]?.message?.reasoning_content ?? '';
5723+ }
5724+
5725+ return '';
5726+}
5727+
5728+/**
56895729 * Extracts multiswipe swipes from the response data.
56905730 * @param {Object} data Response data
56915731 * @param {string} type Type of generation
@@ -5865,7 +5905,7 @@ export function cleanUpMessage(getMessage, isImpersonate, isContinue, displayInc
58655905 return getMessage;
58665906}
58675907
58685908export async function saveReply(type, getMessage, fromStreaming, title, swipes, reasoning) {
58695909 if (type != 'append' && type != 'continue' && type != 'appendFinal' && chat.length && (chat[chat.length - 1]['swipe_id'] === undefined ||
58705910 chat[chat.length - 1]['is_user'])) {
58715911 type = 'normal';
@@ -5890,6 +5930,7 @@ export async function saveReply(type, getMessage, fromStreaming, title, swipes)
58905930 chat[chat.length - 1]['send_date'] = getMessageTimeStamp();
58915931 chat[chat.length - 1]['extra']['api'] = getGeneratingApi();
58925932 chat[chat.length - 1]['extra']['model'] = getGeneratingModel();
5933+ chat[chat.length - 1]['extra']['reasoning'] = reasoning;
58935934 if (power_user.message_token_count_enabled) {
58945935 chat[chat.length - 1]['extra']['token_count'] = await getTokenCountAsync(chat[chat.length - 1]['mes'], 0);
58955936 }
@@ -5910,6 +5951,7 @@ export async function saveReply(type, getMessage, fromStreaming, title, swipes)
59105951 chat[chat.length - 1]['send_date'] = getMessageTimeStamp();
59115952 chat[chat.length - 1]['extra']['api'] = getGeneratingApi();
59125953 chat[chat.length - 1]['extra']['model'] = getGeneratingModel();
5954+ chat[chat.length - 1]['extra']['reasoning'] += reasoning;
59135955 if (power_user.message_token_count_enabled) {
59145956 chat[chat.length - 1]['extra']['token_count'] = await getTokenCountAsync(chat[chat.length - 1]['mes'], 0);
59155957 }
@@ -5927,6 +5969,7 @@ export async function saveReply(type, getMessage, fromStreaming, title, swipes)
59275969 chat[chat.length - 1]['send_date'] = getMessageTimeStamp();
59285970 chat[chat.length - 1]['extra']['api'] = getGeneratingApi();
59295971 chat[chat.length - 1]['extra']['model'] = getGeneratingModel();
5972+ chat[chat.length - 1]['extra']['reasoning'] += reasoning;
59305973 if (power_user.message_token_count_enabled) {
59315974 chat[chat.length - 1]['extra']['token_count'] = await getTokenCountAsync(chat[chat.length - 1]['mes'], 0);
59325975 }
@@ -5944,6 +5987,7 @@ export async function saveReply(type, getMessage, fromStreaming, title, swipes)
59445987 chat[chat.length - 1]['send_date'] = getMessageTimeStamp();
59455988 chat[chat.length - 1]['extra']['api'] = getGeneratingApi();
59465989 chat[chat.length - 1]['extra']['model'] = getGeneratingModel();
5990+ chat[chat.length - 1]['extra']['reasoning'] = reasoning;
59475991 if (power_user.trim_spaces) {
59485992 getMessage = getMessage.trim();
59495993 }
@@ -8646,6 +8690,7 @@ const swipe_right = () => {
86468690 // resets the timer
86478691 swipeMessage.find('.mes_timer').html('');
86488692 swipeMessage.find('.tokenCounterDisplay').text('');
8693+ swipeMessage.find('.mes_reasoning').html('');
86498694 } else {
86508695 //console.log('showing previously generated swipe candidate, or "..."');
86518696 //console.log('onclick right swipe calling addOneMessage');
public/scripts/constants.js+5 -0
@@ -14,3 +14,8 @@ export const debounce_timeout = {
1414 /** [5 sec] For delayed tasks, like auto-saving or completing batch operations that need a significant pause. */
1515 extended: 5000,
1616};
17+
18+/**
19+ * Custom boundary for splitting the text between the model's reasoning and the actual response.
20+ */
21+export const THINK_BREAK = '##�THINK_BREAK�##';
public/scripts/kai-settings.js+1 -1
@@ -188,7 +188,7 @@ export async function generateKoboldWithStreaming(generate_data, signal) {
188188 if (data?.token) {
189189 text += data.token;
190190 }
191191 yield { text, swipes: [], toolCalls: [], state: {} };
192192 }
193193 };
194194}
public/scripts/nai-settings.js+1 -1
@@ -746,7 +746,7 @@ export async function generateNovelWithStreaming(generate_data, signal) {
746746 text += data.token;
747747 }
748748
749749 yield { text, swipes: [], logprobs: parseNovelAILogprobs(data.logprobs), toolCalls: [], state: {} };
750750 }
751751 };
752752}
public/scripts/openai.js+10 -9
@@ -2095,7 +2095,7 @@ async function sendOpenAIRequest(type, messages, signal) {
20952095 let text = '';
20962096 const swipes = [];
20972097 const toolCalls = [];
20982098 const state = { reasoning: '' };
20992099 while (true) {
21002100 const { done, value } = await reader.read();
21012101 if (done) return;
@@ -2113,7 +2113,7 @@ async function sendOpenAIRequest(type, messages, signal) {
21132113
21142114 ToolManager.parseToolCalls(toolCalls, parsed);
21152115
21162116 yield { text, swipes: swipes, logprobs: parseChatCompletionLogprobs(parsed), toolCalls: toolCalls, state: state };
21172117 }
21182118 };
21192119 }
@@ -2150,16 +2150,17 @@ function getStreamingReply(data, state) {
21502150 if (oai_settings.chat_completion_source === chat_completion_sources.CLAUDE) {
21512151 return data?.delta?.text || '';
21522152 } 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] || '';
21542157 } else if (oai_settings.chat_completion_source === chat_completion_sources.COHERE) {
21552158 return data?.delta?.message?.content?.text || data?.delta?.message?.tool_plan || '';
21562159 } else if (oai_settings.chat_completion_source === chat_completion_sources.DEEPSEEK) {
2157- const hadThoughts = state.hadThoughts;
2160+ if (oai_settings.show_thoughts) {
21582161 const thoughts state.reasoning += (data.choices?.filter(x => oai_settings.show_thoughts || !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' : '';
2162- return [thoughts, separator, content].filter(x => x).join('\n\n');
21632164 } else {
21642165 return data.choices?.[0]?.delta?.content ?? data.choices?.[0]?.message?.content ?? data.choices?.[0]?.text ?? '';
21652166 }
public/scripts/textgen-settings.js+2 -1
@@ -986,6 +986,7 @@ export async function generateTextGenWithStreaming(generate_data, signal) {
986986 let logprobs = null;
987987 const swipes = [];
988988 const toolCalls = [];
989+ const state = {};
989990 while (true) {
990991 const { done, value } = await reader.read();
991992 if (done) return;
@@ -1004,7 +1005,7 @@ export async function generateTextGenWithStreaming(generate_data, signal) {
10041005 logprobs = parseTextgenLogprobs(newText, data.choices?.[0]?.logprobs || data?.completion_probabilities);
10051006 }
10061007
10071008 yield { text, swipes, logprobs, toolCalls, state };
10081009 }
10091010 };
10101011}
public/style.css+25 -3
@@ -332,6 +332,23 @@ input[type='checkbox']:focus-visible {
332332 color: var(--SmartThemeQuoteColor);
333333}
334334
335+.mes_reasoning {
336+ display: block;
337+ border: 1px solid var(--SmartThemeBorderColor);
338+ background-color: var(--black30a);
339+ border-radius: 5px;
340+ padding: 5px;
341+ margin: 5px 0;
342+ overflow-y: auto;
343+ max-height: 100px;
344+}
345+
346+.mes_block:has(.edit_textarea) .mes_reasoning,
347+.mes_bias:empty,
348+.mes_reasoning:empty {
349+ display: none;
350+}
351+
335352.mes_text i,
336353.mes_text em {
337354 color: var(--SmartThemeEmColor);
@@ -1022,6 +1039,7 @@ body .panelControlBar {
10221039 /*only affects bubblechat to make it sit nicely at the bottom*/
10231040}
10241041
1042+.last_mes .mes_reasoning,
10251043.last_mes .mes_text {
10261044 padding-right: 30px;
10271045}
@@ -1235,14 +1253,18 @@ body.swipeAllMessages .mes:not(.last_mes) .swipes-counter {
12351253 overflow-y: clip;
12361254}
12371255
12381256.mes_text {,
1257+.mes_reasoning {
12391258 font-weight: 500;
12401259 line-height: calc(var(--mainFontSize) + .5rem);
1260+ max-width: 100%;
1261+ overflow-wrap: anywhere;
1262+}
1263+
1264+.mes_text {
12411265 padding-left: 0;
12421266 padding-top: 5px;
12431267 padding-bottom: 5px;
1244- max-width: 100%;
1245- overflow-wrap: anywhere;
12461268}
12471269
12481270br {
src/constants.js+5 -0
@@ -413,3 +413,8 @@ export const VLLM_KEYS = [
413413 'guided_decoding_backend',
414414 'guided_whitespace_pattern',
415415];
416+
417+/**
418+ * Custom boundary for splitting the text between the model's reasoning and the actual response.
419+ */
420+export const THINK_BREAK = '##�THINK_BREAK�##';
src/endpoints/backends/chat-completions.js+2 -1
@@ -7,6 +7,7 @@ import {
77 CHAT_COMPLETION_SOURCES,
88 GEMINI_SAFETY,
99 OPENROUTER_HEADERS,
10+ THINK_BREAK,
1011} from '../../constants.js';
1112import {
1213 forwardFetchResponse,
@@ -389,7 +390,7 @@ async function sendMakerSuiteRequest(request, response) {
389390 responseContent.parts = responseContent.parts.filter(part => !part.thought);
390391 }
391392
392393 const responseText = typeof responseContent === 'string' ? responseContent : responseContent?.parts?.map(part => part.text)?.join('\n\n'THINK_BREAK);
393394 if (!responseText) {
394395 let message = 'Google AI Studio Candidate text empty';
395396 console.log(message, generateResponseJson);