Save reasoning type with the message - use mes extras property to save where the reasoning came from - update it accordingly on streaming, slash commands and manual add - Modify title tooltip on reasoning header to show the origin where it makes sense, providing the user with a little bit more orientation about the reasoning.

b3688087d56f9373b356aa22c96f3adcfe377c22

Wolfsblvt <wolfsblvt@gmail.com>

1 files changed, +41 -12Ignore whitespace
public/scripts/reasoning.js+41 -12
@@ -3,7 +3,7 @@ import {
3} from '../lib.js';3} from '../lib.js';
4import { chat, closeMessageEditor, event_types, eventSource, main_api, messageFormatting, saveChatConditional, saveSettingsDebounced, substituteParams, updateMessageBlock } from '../script.js';4import { chat, closeMessageEditor, event_types, eventSource, main_api, messageFormatting, saveChatConditional, saveSettingsDebounced, substituteParams, updateMessageBlock } from '../script.js';
5import { getRegexedString, regex_placement } from './extensions/regex/engine.js';5import { getRegexedString, regex_placement } from './extensions/regex/engine.js';
6import { getCurrentLocale, t } from './i18n.js';6import { getCurrentLocale, t, translate } from './i18n.js';
7import { MacrosParser } from './macros.js';7import { MacrosParser } from './macros.js';
8import { chat_completion_sources, getChatCompletionModel, oai_settings } from './openai.js';8import { chat_completion_sources, getChatCompletionModel, oai_settings } from './openai.js';
9import { Popup } from './popup.js';9import { Popup } from './popup.js';
@@ -16,6 +16,18 @@ import { textgen_types, textgenerationwebui_settings } from './textgen-settings.
16import { copyText, escapeRegex, isFalseBoolean, setDatasetProperty, trimSpaces } from './utils.js';16import { copyText, escapeRegex, isFalseBoolean, setDatasetProperty, trimSpaces } from './utils.js';
1717
18/**18/**
19 * Enum representing the type of the reasoning for a message (where it came from)
20 * @enum {string}
21 * @readonly
22 */
23export const ReasoningType = {
24 Model: 'model',
25 Parsed: 'parsed',
26 Manual: 'manual',
27 Edited: 'edited',
28};
29
30/**
19 * Gets a message from a jQuery element.31 * Gets a message from a jQuery element.
20 * @param {Element} element32 * @param {Element} element
21 * @returns {{messageId: number, message: object, messageBlock: JQuery<HTMLElement>}}33 * @returns {{messageId: number, message: object, messageBlock: JQuery<HTMLElement>}}
@@ -142,6 +154,8 @@ export class ReasoningHandler {
142 constructor(timeStarted = null) {154 constructor(timeStarted = null) {
143 /** @type {ReasoningState} The current state of the reasoning process */155 /** @type {ReasoningState} The current state of the reasoning process */
144 this.state = ReasoningState.None;156 this.state = ReasoningState.None;
157 /** @type {ReasoningType?} The type of the reasoning (where it came from) */
158 this.type = null;
145 /** @type {string} The reasoning output */159 /** @type {string} The reasoning output */
146 this.reasoning = '';160 this.reasoning = '';
147 /** @type {Date} When the reasoning started */161 /** @type {Date} When the reasoning started */
@@ -198,6 +212,7 @@ export class ReasoningHandler {
198 this.state = ReasoningState.Hidden;212 this.state = ReasoningState.Hidden;
199 }213 }
200214
215 this.type = extra?.reasoning_type;
201 this.reasoning = extra?.reasoning ?? '';216 this.reasoning = extra?.reasoning ?? '';
202217
203 if (this.state !== ReasoningState.None) {218 if (this.state !== ReasoningState.None) {
@@ -212,6 +227,7 @@ export class ReasoningHandler {
212 // Make sure reset correctly clears all relevant states227 // Make sure reset correctly clears all relevant states
213 if (reset) {228 if (reset) {
214 this.state = this.#isHiddenReasoningModel ? ReasoningState.Thinking : ReasoningState.None;229 this.state = this.#isHiddenReasoningModel ? ReasoningState.Thinking : ReasoningState.None;
230 this.type = null;
215 this.reasoning = '';231 this.reasoning = '';
216 this.initialTime = new Date();232 this.initialTime = new Date();
217 this.startTime = null;233 this.startTime = null;
@@ -264,10 +280,13 @@ export class ReasoningHandler {
264 const reasoningChanged = extra.reasoning !== reasoning;280 const reasoningChanged = extra.reasoning !== reasoning;
265 this.reasoning = getRegexedString(reasoning ?? '', regex_placement.REASONING);281 this.reasoning = getRegexedString(reasoning ?? '', regex_placement.REASONING);
266282
283 this.type = (this.#isParsingReasoning || this.#parsingReasoningMesStartIndex) ? ReasoningType.Parsed : ReasoningType.Model;
284
267 if (persist) {285 if (persist) {
268 // Build and save the reasoning data to message extras286 // Build and save the reasoning data to message extras
269 extra.reasoning = this.reasoning;287 extra.reasoning = this.reasoning;
270 extra.reasoning_duration = this.getDuration();288 extra.reasoning_duration = this.getDuration();
289 extra.reasoning_type = (this.#isParsingReasoning || this.#parsingReasoningMesStartIndex) ? ReasoningType.Parsed : ReasoningType.Model;
271 }290 }
272291
273 return reasoningChanged;292 return reasoningChanged;
@@ -391,6 +410,7 @@ export class ReasoningHandler {
391 // Update states to the relevant DOM elements410 // Update states to the relevant DOM elements
392 setDatasetProperty(this.messageDom, 'reasoningState', this.state !== ReasoningState.None ? this.state : null);411 setDatasetProperty(this.messageDom, 'reasoningState', this.state !== ReasoningState.None ? this.state : null);
393 setDatasetProperty(this.messageReasoningDetailsDom, 'state', this.state);412 setDatasetProperty(this.messageReasoningDetailsDom, 'state', this.state);
413 setDatasetProperty(this.messageReasoningDetailsDom, 'type', this.type);
394414
395 // Update the reasoning message415 // Update the reasoning message
396 const reasoning = trimSpaces(this.reasoning);416 const reasoning = trimSpaces(this.reasoning);
@@ -448,17 +468,14 @@ export class ReasoningHandler {
448 const element = this.messageReasoningHeaderDom;468 const element = this.messageReasoningHeaderDom;
449 const duration = this.getDuration();469 const duration = this.getDuration();
450 let data = null;470 let data = null;
471 let title = '';
451 if (duration) {472 if (duration) {
452 const durationStr = moment.duration(duration).locale(getCurrentLocale()).humanize({ s: 50, ss: 3 });473 const seconds = moment.duration(duration).asSeconds();
453 const secondsStr = moment.duration(duration).asSeconds();
454
455 const span = document.createElement('span');
456 span.title = t`${secondsStr} seconds`;
457 span.textContent = durationStr;
458474
459 element.textContent = t`Thought for `;475 const durationStr = moment.duration(duration).locale(getCurrentLocale()).humanize({ s: 50, ss: 3 });
460 element.appendChild(span);476 element.textContent = t`Thought for ${durationStr}`;
461 data = String(secondsStr);477 data = String(seconds);
478 title = `${seconds} seconds`;
462 } else if ([ReasoningState.Done, ReasoningState.Hidden].includes(this.state)) {479 } else if ([ReasoningState.Done, ReasoningState.Hidden].includes(this.state)) {
463 element.textContent = t`Thought for some time`;480 element.textContent = t`Thought for some time`;
464 data = 'unknown';481 data = 'unknown';
@@ -467,6 +484,12 @@ export class ReasoningHandler {
467 data = null;484 data = null;
468 }485 }
469486
487 if (this.type !== ReasoningType.Model) {
488 title += ` [${translate(this.type)}]`;
489 title = title.trim();
490 }
491 element.title = title;
492
470 setDatasetProperty(this.messageReasoningDetailsDom, 'duration', data);493 setDatasetProperty(this.messageReasoningDetailsDom, 'duration', data);
471 setDatasetProperty(element, 'duration', data);494 setDatasetProperty(element, 'duration', data);
472 }495 }
@@ -628,11 +651,13 @@ function registerReasoningSlashCommands() {
628 callback: async (args, value) => {651 callback: async (args, value) => {
629 const messageId = !isNaN(Number(args.at)) ? Number(args.at) : chat.length - 1;652 const messageId = !isNaN(Number(args.at)) ? Number(args.at) : chat.length - 1;
630 const message = chat[messageId];653 const message = chat[messageId];
631 if (!message?.extra) {654 // Make sure the message has an extra object
632 return '';655 if (!message.extra || typeof message.extra !== 'object') {
656 message.extra = {};
633 }657 }
634658
635 message.extra.reasoning = String(value ?? '');659 message.extra.reasoning = String(value ?? '');
660 message.extra.reasoning_type = ReasoningType.Manual;
636 await saveChatConditional();661 await saveChatConditional();
637662
638 closeMessageEditor('reasoning');663 closeMessageEditor('reasoning');
@@ -775,6 +800,7 @@ function setReasoningEventHandlers() {
775 const textarea = messageBlock.find('.reasoning_edit_textarea');800 const textarea = messageBlock.find('.reasoning_edit_textarea');
776 const reasoning = getRegexedString(String(textarea.val()), regex_placement.REASONING, { isEdit: true });801 const reasoning = getRegexedString(String(textarea.val()), regex_placement.REASONING, { isEdit: true });
777 message.extra.reasoning = reasoning;802 message.extra.reasoning = reasoning;
803 message.extra.reasoning_type = message.extra.reasoning_type ? ReasoningType.Edited : ReasoningType.Manual;
778 await saveChatConditional();804 await saveChatConditional();
779 updateMessageBlock(messageId, message);805 updateMessageBlock(messageId, message);
780 textarea.remove();806 textarea.remove();
@@ -835,6 +861,8 @@ function setReasoningEventHandlers() {
835 return;861 return;
836 }862 }
837 message.extra.reasoning = '';863 message.extra.reasoning = '';
864 delete message.extra.reasoning_type;
865 delete message.extra.reasoning_duration;
838 await saveChatConditional();866 await saveChatConditional();
839 updateMessageBlock(messageId, message);867 updateMessageBlock(messageId, message);
840 const textarea = messageBlock.find('.reasoning_edit_textarea');868 const textarea = messageBlock.find('.reasoning_edit_textarea');
@@ -946,6 +974,7 @@ function registerReasoningAppEvents() {
946 // If reasoning was found, add it to the message974 // If reasoning was found, add it to the message
947 if (parsedReasoning.reasoning) {975 if (parsedReasoning.reasoning) {
948 message.extra.reasoning = getRegexedString(parsedReasoning.reasoning, regex_placement.REASONING);976 message.extra.reasoning = getRegexedString(parsedReasoning.reasoning, regex_placement.REASONING);
977 message.extra.reasoning_type = ReasoningType.Parsed;
949 }978 }
950979
951 // Update the message text if it was changed980 // Update the message text if it was changed