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 {
33} from '../lib.js';
44import { chat, closeMessageEditor, event_types, eventSource, main_api, messageFormatting, saveChatConditional, saveSettingsDebounced, substituteParams, updateMessageBlock } from '../script.js';
55import { getRegexedString, regex_placement } from './extensions/regex/engine.js';
66import { getCurrentLocale, t, translate } from './i18n.js';
77import { MacrosParser } from './macros.js';
88import { chat_completion_sources, getChatCompletionModel, oai_settings } from './openai.js';
99import { Popup } from './popup.js';
@@ -16,6 +16,18 @@ import { textgen_types, textgenerationwebui_settings } from './textgen-settings.
1616import { copyText, escapeRegex, isFalseBoolean, setDatasetProperty, trimSpaces } from './utils.js';
1717
1818/**
19+ * Enum representing the type of the reasoning for a message (where it came from)
20+ * @enum {string}
21+ * @readonly
22+ */
23+export const ReasoningType = {
24+ Model: 'model',
25+ Parsed: 'parsed',
26+ Manual: 'manual',
27+ Edited: 'edited',
28+};
29+
30+/**
1931 * Gets a message from a jQuery element.
2032 * @param {Element} element
2133 * @returns {{messageId: number, message: object, messageBlock: JQuery<HTMLElement>}}
@@ -142,6 +154,8 @@ export class ReasoningHandler {
142154 constructor(timeStarted = null) {
143155 /** @type {ReasoningState} The current state of the reasoning process */
144156 this.state = ReasoningState.None;
157+ /** @type {ReasoningType?} The type of the reasoning (where it came from) */
158+ this.type = null;
145159 /** @type {string} The reasoning output */
146160 this.reasoning = '';
147161 /** @type {Date} When the reasoning started */
@@ -198,6 +212,7 @@ export class ReasoningHandler {
198212 this.state = ReasoningState.Hidden;
199213 }
200214
215+ this.type = extra?.reasoning_type;
201216 this.reasoning = extra?.reasoning ?? '';
202217
203218 if (this.state !== ReasoningState.None) {
@@ -212,6 +227,7 @@ export class ReasoningHandler {
212227 // Make sure reset correctly clears all relevant states
213228 if (reset) {
214229 this.state = this.#isHiddenReasoningModel ? ReasoningState.Thinking : ReasoningState.None;
230+ this.type = null;
215231 this.reasoning = '';
216232 this.initialTime = new Date();
217233 this.startTime = null;
@@ -264,10 +280,13 @@ export class ReasoningHandler {
264280 const reasoningChanged = extra.reasoning !== reasoning;
265281 this.reasoning = getRegexedString(reasoning ?? '', regex_placement.REASONING);
266282
283+ this.type = (this.#isParsingReasoning || this.#parsingReasoningMesStartIndex) ? ReasoningType.Parsed : ReasoningType.Model;
284+
267285 if (persist) {
268286 // Build and save the reasoning data to message extras
269287 extra.reasoning = this.reasoning;
270288 extra.reasoning_duration = this.getDuration();
289+ extra.reasoning_type = (this.#isParsingReasoning || this.#parsingReasoningMesStartIndex) ? ReasoningType.Parsed : ReasoningType.Model;
271290 }
272291
273292 return reasoningChanged;
@@ -391,6 +410,7 @@ export class ReasoningHandler {
391410 // Update states to the relevant DOM elements
392411 setDatasetProperty(this.messageDom, 'reasoningState', this.state !== ReasoningState.None ? this.state : null);
393412 setDatasetProperty(this.messageReasoningDetailsDom, 'state', this.state);
413+ setDatasetProperty(this.messageReasoningDetailsDom, 'type', this.type);
394414
395415 // Update the reasoning message
396416 const reasoning = trimSpaces(this.reasoning);
@@ -448,17 +468,14 @@ export class ReasoningHandler {
448468 const element = this.messageReasoningHeaderDom;
449469 const duration = this.getDuration();
450470 let data = null;
471+ let title = '';
451472 if (duration) {
452473 const durationStrseconds = moment.duration(duration).locale(getCurrentLocale()).humanizeasSeconds({ s: 50, ss: 3 });
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}`;
461477 data = String(secondsStrseconds);
478+ title = `${seconds} seconds`;
462479 } else if ([ReasoningState.Done, ReasoningState.Hidden].includes(this.state)) {
463480 element.textContent = t`Thought for some time`;
464481 data = 'unknown';
@@ -467,6 +484,12 @@ export class ReasoningHandler {
467484 data = null;
468485 }
469486
487+ if (this.type !== ReasoningType.Model) {
488+ title += ` [${translate(this.type)}]`;
489+ title = title.trim();
490+ }
491+ element.title = title;
492+
470493 setDatasetProperty(this.messageReasoningDetailsDom, 'duration', data);
471494 setDatasetProperty(element, 'duration', data);
472495 }
@@ -628,11 +651,13 @@ function registerReasoningSlashCommands() {
628651 callback: async (args, value) => {
629652 const messageId = !isNaN(Number(args.at)) ? Number(args.at) : chat.length - 1;
630653 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 = {};
633657 }
634658
635659 message.extra.reasoning = String(value ?? '');
660+ message.extra.reasoning_type = ReasoningType.Manual;
636661 await saveChatConditional();
637662
638663 closeMessageEditor('reasoning');
@@ -775,6 +800,7 @@ function setReasoningEventHandlers() {
775800 const textarea = messageBlock.find('.reasoning_edit_textarea');
776801 const reasoning = getRegexedString(String(textarea.val()), regex_placement.REASONING, { isEdit: true });
777802 message.extra.reasoning = reasoning;
803+ message.extra.reasoning_type = message.extra.reasoning_type ? ReasoningType.Edited : ReasoningType.Manual;
778804 await saveChatConditional();
779805 updateMessageBlock(messageId, message);
780806 textarea.remove();
@@ -835,6 +861,8 @@ function setReasoningEventHandlers() {
835861 return;
836862 }
837863 message.extra.reasoning = '';
864+ delete message.extra.reasoning_type;
865+ delete message.extra.reasoning_duration;
838866 await saveChatConditional();
839867 updateMessageBlock(messageId, message);
840868 const textarea = messageBlock.find('.reasoning_edit_textarea');
@@ -946,6 +974,7 @@ function registerReasoningAppEvents() {
946974 // If reasoning was found, add it to the message
947975 if (parsedReasoning.reasoning) {
948976 message.extra.reasoning = getRegexedString(parsedReasoning.reasoning, regex_placement.REASONING);
977+ message.extra.reasoning_type = ReasoningType.Parsed;
949978 }
950979
951980 // Update the message text if it was changed