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.
This commit is contained in:
Wolfsblvt
2025-02-16 05:05:48 +01:00
parent bcea4248c4
commit b3688087d5

View File

@ -3,7 +3,7 @@ import {
} from '../lib.js'; } from '../lib.js';
import { chat, closeMessageEditor, event_types, eventSource, main_api, messageFormatting, saveChatConditional, saveSettingsDebounced, substituteParams, updateMessageBlock } from '../script.js'; import { chat, closeMessageEditor, event_types, eventSource, main_api, messageFormatting, saveChatConditional, saveSettingsDebounced, substituteParams, updateMessageBlock } from '../script.js';
import { getRegexedString, regex_placement } from './extensions/regex/engine.js'; import { getRegexedString, regex_placement } from './extensions/regex/engine.js';
import { getCurrentLocale, t } from './i18n.js'; import { getCurrentLocale, t, translate } from './i18n.js';
import { MacrosParser } from './macros.js'; import { MacrosParser } from './macros.js';
import { chat_completion_sources, getChatCompletionModel, oai_settings } from './openai.js'; import { chat_completion_sources, getChatCompletionModel, oai_settings } from './openai.js';
import { Popup } from './popup.js'; import { Popup } from './popup.js';
@ -15,6 +15,18 @@ import { SlashCommandParser } from './slash-commands/SlashCommandParser.js';
import { textgen_types, textgenerationwebui_settings } from './textgen-settings.js'; import { textgen_types, textgenerationwebui_settings } from './textgen-settings.js';
import { copyText, escapeRegex, isFalseBoolean, setDatasetProperty, trimSpaces } from './utils.js'; import { copyText, escapeRegex, isFalseBoolean, setDatasetProperty, trimSpaces } from './utils.js';
/**
* Enum representing the type of the reasoning for a message (where it came from)
* @enum {string}
* @readonly
*/
export const ReasoningType = {
Model: 'model',
Parsed: 'parsed',
Manual: 'manual',
Edited: 'edited',
};
/** /**
* Gets a message from a jQuery element. * Gets a message from a jQuery element.
* @param {Element} element * @param {Element} element
@ -142,6 +154,8 @@ export class ReasoningHandler {
constructor(timeStarted = null) { constructor(timeStarted = null) {
/** @type {ReasoningState} The current state of the reasoning process */ /** @type {ReasoningState} The current state of the reasoning process */
this.state = ReasoningState.None; this.state = ReasoningState.None;
/** @type {ReasoningType?} The type of the reasoning (where it came from) */
this.type = null;
/** @type {string} The reasoning output */ /** @type {string} The reasoning output */
this.reasoning = ''; this.reasoning = '';
/** @type {Date} When the reasoning started */ /** @type {Date} When the reasoning started */
@ -198,6 +212,7 @@ export class ReasoningHandler {
this.state = ReasoningState.Hidden; this.state = ReasoningState.Hidden;
} }
this.type = extra?.reasoning_type;
this.reasoning = extra?.reasoning ?? ''; this.reasoning = extra?.reasoning ?? '';
if (this.state !== ReasoningState.None) { if (this.state !== ReasoningState.None) {
@ -212,6 +227,7 @@ export class ReasoningHandler {
// Make sure reset correctly clears all relevant states // Make sure reset correctly clears all relevant states
if (reset) { if (reset) {
this.state = this.#isHiddenReasoningModel ? ReasoningState.Thinking : ReasoningState.None; this.state = this.#isHiddenReasoningModel ? ReasoningState.Thinking : ReasoningState.None;
this.type = null;
this.reasoning = ''; this.reasoning = '';
this.initialTime = new Date(); this.initialTime = new Date();
this.startTime = null; this.startTime = null;
@ -264,10 +280,13 @@ export class ReasoningHandler {
const reasoningChanged = extra.reasoning !== reasoning; const reasoningChanged = extra.reasoning !== reasoning;
this.reasoning = getRegexedString(reasoning ?? '', regex_placement.REASONING); this.reasoning = getRegexedString(reasoning ?? '', regex_placement.REASONING);
this.type = (this.#isParsingReasoning || this.#parsingReasoningMesStartIndex) ? ReasoningType.Parsed : ReasoningType.Model;
if (persist) { if (persist) {
// Build and save the reasoning data to message extras // Build and save the reasoning data to message extras
extra.reasoning = this.reasoning; extra.reasoning = this.reasoning;
extra.reasoning_duration = this.getDuration(); extra.reasoning_duration = this.getDuration();
extra.reasoning_type = (this.#isParsingReasoning || this.#parsingReasoningMesStartIndex) ? ReasoningType.Parsed : ReasoningType.Model;
} }
return reasoningChanged; return reasoningChanged;
@ -391,6 +410,7 @@ export class ReasoningHandler {
// Update states to the relevant DOM elements // Update states to the relevant DOM elements
setDatasetProperty(this.messageDom, 'reasoningState', this.state !== ReasoningState.None ? this.state : null); setDatasetProperty(this.messageDom, 'reasoningState', this.state !== ReasoningState.None ? this.state : null);
setDatasetProperty(this.messageReasoningDetailsDom, 'state', this.state); setDatasetProperty(this.messageReasoningDetailsDom, 'state', this.state);
setDatasetProperty(this.messageReasoningDetailsDom, 'type', this.type);
// Update the reasoning message // Update the reasoning message
const reasoning = trimSpaces(this.reasoning); const reasoning = trimSpaces(this.reasoning);
@ -448,17 +468,14 @@ export class ReasoningHandler {
const element = this.messageReasoningHeaderDom; const element = this.messageReasoningHeaderDom;
const duration = this.getDuration(); const duration = this.getDuration();
let data = null; let data = null;
let title = '';
if (duration) { if (duration) {
const seconds = moment.duration(duration).asSeconds();
const durationStr = moment.duration(duration).locale(getCurrentLocale()).humanize({ s: 50, ss: 3 }); const durationStr = moment.duration(duration).locale(getCurrentLocale()).humanize({ s: 50, ss: 3 });
const secondsStr = moment.duration(duration).asSeconds(); element.textContent = t`Thought for ${durationStr}`;
data = String(seconds);
const span = document.createElement('span'); title = `${seconds} seconds`;
span.title = t`${secondsStr} seconds`;
span.textContent = durationStr;
element.textContent = t`Thought for `;
element.appendChild(span);
data = String(secondsStr);
} else if ([ReasoningState.Done, ReasoningState.Hidden].includes(this.state)) { } else if ([ReasoningState.Done, ReasoningState.Hidden].includes(this.state)) {
element.textContent = t`Thought for some time`; element.textContent = t`Thought for some time`;
data = 'unknown'; data = 'unknown';
@ -467,6 +484,12 @@ export class ReasoningHandler {
data = null; data = null;
} }
if (this.type !== ReasoningType.Model) {
title += ` [${translate(this.type)}]`;
title = title.trim();
}
element.title = title;
setDatasetProperty(this.messageReasoningDetailsDom, 'duration', data); setDatasetProperty(this.messageReasoningDetailsDom, 'duration', data);
setDatasetProperty(element, 'duration', data); setDatasetProperty(element, 'duration', data);
} }
@ -628,11 +651,13 @@ function registerReasoningSlashCommands() {
callback: async (args, value) => { callback: async (args, value) => {
const messageId = !isNaN(Number(args.at)) ? Number(args.at) : chat.length - 1; const messageId = !isNaN(Number(args.at)) ? Number(args.at) : chat.length - 1;
const message = chat[messageId]; const message = chat[messageId];
if (!message?.extra) { // Make sure the message has an extra object
return ''; if (!message.extra || typeof message.extra !== 'object') {
message.extra = {};
} }
message.extra.reasoning = String(value ?? ''); message.extra.reasoning = String(value ?? '');
message.extra.reasoning_type = ReasoningType.Manual;
await saveChatConditional(); await saveChatConditional();
closeMessageEditor('reasoning'); closeMessageEditor('reasoning');
@ -775,6 +800,7 @@ function setReasoningEventHandlers() {
const textarea = messageBlock.find('.reasoning_edit_textarea'); const textarea = messageBlock.find('.reasoning_edit_textarea');
const reasoning = getRegexedString(String(textarea.val()), regex_placement.REASONING, { isEdit: true }); const reasoning = getRegexedString(String(textarea.val()), regex_placement.REASONING, { isEdit: true });
message.extra.reasoning = reasoning; message.extra.reasoning = reasoning;
message.extra.reasoning_type = message.extra.reasoning_type ? ReasoningType.Edited : ReasoningType.Manual;
await saveChatConditional(); await saveChatConditional();
updateMessageBlock(messageId, message); updateMessageBlock(messageId, message);
textarea.remove(); textarea.remove();
@ -835,6 +861,8 @@ function setReasoningEventHandlers() {
return; return;
} }
message.extra.reasoning = ''; message.extra.reasoning = '';
delete message.extra.reasoning_type;
delete message.extra.reasoning_duration;
await saveChatConditional(); await saveChatConditional();
updateMessageBlock(messageId, message); updateMessageBlock(messageId, message);
const textarea = messageBlock.find('.reasoning_edit_textarea'); const textarea = messageBlock.find('.reasoning_edit_textarea');
@ -946,6 +974,7 @@ function registerReasoningAppEvents() {
// If reasoning was found, add it to the message // If reasoning was found, add it to the message
if (parsedReasoning.reasoning) { if (parsedReasoning.reasoning) {
message.extra.reasoning = getRegexedString(parsedReasoning.reasoning, regex_placement.REASONING); message.extra.reasoning = getRegexedString(parsedReasoning.reasoning, regex_placement.REASONING);
message.extra.reasoning_type = ReasoningType.Parsed;
} }
// Update the message text if it was changed // Update the message text if it was changed