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.
| @@ -3,7 +3,7 @@ import { | |||
| 3 | } from '../lib.js'; | 3 | } from '../lib.js'; |
| 4 | import { chat, closeMessageEditor, event_types, eventSource, main_api, messageFormatting, saveChatConditional, saveSettingsDebounced, substituteParams, updateMessageBlock } from '../script.js'; | 4 | import { chat, closeMessageEditor, event_types, eventSource, main_api, messageFormatting, saveChatConditional, saveSettingsDebounced, substituteParams, updateMessageBlock } from '../script.js'; |
| 5 | import { getRegexedString, regex_placement } from './extensions/regex/engine.js'; | 5 | import { getRegexedString, regex_placement } from './extensions/regex/engine.js'; |
| 6 | import { getCurrentLocale, t } from './i18n.js'; | 6 | import { getCurrentLocale, t, translate } from './i18n.js'; |
| 7 | import { MacrosParser } from './macros.js'; | 7 | import { MacrosParser } from './macros.js'; |
| 8 | import { chat_completion_sources, getChatCompletionModel, oai_settings } from './openai.js'; | 8 | import { chat_completion_sources, getChatCompletionModel, oai_settings } from './openai.js'; |
| 9 | import { Popup } from './popup.js'; | 9 | import { Popup } from './popup.js'; |
| @@ -16,6 +16,18 @@ import { textgen_types, textgenerationwebui_settings } from './textgen-settings. | |||
| 16 | import { copyText, escapeRegex, isFalseBoolean, setDatasetProperty, trimSpaces } from './utils.js'; | 16 | import { copyText, escapeRegex, isFalseBoolean, setDatasetProperty, trimSpaces } from './utils.js'; |
| 17 | 17 | ||
| 18 | /** | 18 | /** |
| 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 | /** | ||
| 19 | * Gets a message from a jQuery element. | 31 | * Gets a message from a jQuery element. |
| 20 | * @param {Element} element | 32 | * @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 | } |
| 200 | 214 | ||
| 215 | this.type = extra?.reasoning_type; | ||
| 201 | this.reasoning = extra?.reasoning ?? ''; | 216 | this.reasoning = extra?.reasoning ?? ''; |
| 202 | 217 | ||
| 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 states | 227 | // 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); |
| 266 | 282 | ||
| 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 extras | 286 | // 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 | } |
| 272 | 291 | ||
| 273 | return reasoningChanged; | 292 | return reasoningChanged; |
| @@ -391,6 +410,7 @@ export class ReasoningHandler { | |||
| 391 | // Update states to the relevant DOM elements | 410 | // 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); | ||
| 394 | 414 | ||
| 395 | // Update the reasoning message | 415 | // 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; | ||
| 458 | 474 | ||
| 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 | } |
| 469 | 486 | ||
| 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 | } |
| 634 | 658 | ||
| 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(); |
| 637 | 662 | ||
| 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 message | 974 | // 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 | } |
| 950 | 979 | ||
| 951 | // Update the message text if it was changed | 980 | // Update the message text if it was changed |