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 | 3 | } from '../lib.js'; |
| 4 | 4 | import { chat, closeMessageEditor, event_types, eventSource, main_api, messageFormatting, saveChatConditional, saveSettingsDebounced, substituteParams, updateMessageBlock } from '../script.js'; |
| 5 | 5 | import { getRegexedString, regex_placement } from './extensions/regex/engine.js'; |
| 6 | 6 | import { getCurrentLocale, t, translate } from './i18n.js'; |
| 7 | 7 | import { MacrosParser } from './macros.js'; |
| 8 | 8 | import { chat_completion_sources, getChatCompletionModel, oai_settings } from './openai.js'; |
| 9 | 9 | import { Popup } from './popup.js'; |
| @@ -16,6 +16,18 @@ import { textgen_types, textgenerationwebui_settings } from './textgen-settings. | ||
| 16 | 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 | 31 | * Gets a message from a jQuery element. |
| 20 | 32 | * @param {Element} element |
| 21 | 33 | * @returns {{messageId: number, message: object, messageBlock: JQuery<HTMLElement>}} |
| @@ -142,6 +154,8 @@ export class ReasoningHandler { | ||
| 142 | 154 | constructor(timeStarted = null) { |
| 143 | 155 | /** @type {ReasoningState} The current state of the reasoning process */ |
| 144 | 156 | this.state = ReasoningState.None; |
| 157 | + /** @type {ReasoningType?} The type of the reasoning (where it came from) */ | |
| 158 | + this.type = null; | |
| 145 | 159 | /** @type {string} The reasoning output */ |
| 146 | 160 | this.reasoning = ''; |
| 147 | 161 | /** @type {Date} When the reasoning started */ |
| @@ -198,6 +212,7 @@ export class ReasoningHandler { | ||
| 198 | 212 | this.state = ReasoningState.Hidden; |
| 199 | 213 | } |
| 200 | 214 | |
| 215 | + this.type = extra?.reasoning_type; | |
| 201 | 216 | this.reasoning = extra?.reasoning ?? ''; |
| 202 | 217 | |
| 203 | 218 | if (this.state !== ReasoningState.None) { |
| @@ -212,6 +227,7 @@ export class ReasoningHandler { | ||
| 212 | 227 | // Make sure reset correctly clears all relevant states |
| 213 | 228 | if (reset) { |
| 214 | 229 | this.state = this.#isHiddenReasoningModel ? ReasoningState.Thinking : ReasoningState.None; |
| 230 | + this.type = null; | |
| 215 | 231 | this.reasoning = ''; |
| 216 | 232 | this.initialTime = new Date(); |
| 217 | 233 | this.startTime = null; |
| @@ -264,10 +280,13 @@ export class ReasoningHandler { | ||
| 264 | 280 | const reasoningChanged = extra.reasoning !== reasoning; |
| 265 | 281 | this.reasoning = getRegexedString(reasoning ?? '', regex_placement.REASONING); |
| 266 | 282 | |
| 283 | + this.type = (this.#isParsingReasoning || this.#parsingReasoningMesStartIndex) ? ReasoningType.Parsed : ReasoningType.Model; | |
| 284 | + | |
| 267 | 285 | if (persist) { |
| 268 | 286 | // Build and save the reasoning data to message extras |
| 269 | 287 | extra.reasoning = this.reasoning; |
| 270 | 288 | extra.reasoning_duration = this.getDuration(); |
| 289 | + extra.reasoning_type = (this.#isParsingReasoning || this.#parsingReasoningMesStartIndex) ? ReasoningType.Parsed : ReasoningType.Model; | |
| 271 | 290 | } |
| 272 | 291 | |
| 273 | 292 | return reasoningChanged; |
| @@ -391,6 +410,7 @@ export class ReasoningHandler { | ||
| 391 | 410 | // Update states to the relevant DOM elements |
| 392 | 411 | setDatasetProperty(this.messageDom, 'reasoningState', this.state !== ReasoningState.None ? this.state : null); |
| 393 | 412 | setDatasetProperty(this.messageReasoningDetailsDom, 'state', this.state); |
| 413 | + setDatasetProperty(this.messageReasoningDetailsDom, 'type', this.type); | |
| 394 | 414 | |
| 395 | 415 | // Update the reasoning message |
| 396 | 416 | const reasoning = trimSpaces(this.reasoning); |
| @@ -448,17 +468,14 @@ export class ReasoningHandler { | ||
| 448 | 468 | const element = this.messageReasoningHeaderDom; |
| 449 | 469 | const duration = this.getDuration(); |
| 450 | 470 | let data = null; |
| 471 | + let title = ''; | |
| 451 | 472 | if (duration) { |
| 452 | 473 | 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; | |
| 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 | 477 | data = String(secondsStrseconds); |
| 478 | + title = `${seconds} seconds`; | |
| 462 | 479 | } else if ([ReasoningState.Done, ReasoningState.Hidden].includes(this.state)) { |
| 463 | 480 | element.textContent = t`Thought for some time`; |
| 464 | 481 | data = 'unknown'; |
| @@ -467,6 +484,12 @@ export class ReasoningHandler { | ||
| 467 | 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 | 493 | setDatasetProperty(this.messageReasoningDetailsDom, 'duration', data); |
| 471 | 494 | setDatasetProperty(element, 'duration', data); |
| 472 | 495 | } |
| @@ -628,11 +651,13 @@ function registerReasoningSlashCommands() { | ||
| 628 | 651 | callback: async (args, value) => { |
| 629 | 652 | const messageId = !isNaN(Number(args.at)) ? Number(args.at) : chat.length - 1; |
| 630 | 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 | 659 | message.extra.reasoning = String(value ?? ''); |
| 660 | + message.extra.reasoning_type = ReasoningType.Manual; | |
| 636 | 661 | await saveChatConditional(); |
| 637 | 662 | |
| 638 | 663 | closeMessageEditor('reasoning'); |
| @@ -775,6 +800,7 @@ function setReasoningEventHandlers() { | ||
| 775 | 800 | const textarea = messageBlock.find('.reasoning_edit_textarea'); |
| 776 | 801 | const reasoning = getRegexedString(String(textarea.val()), regex_placement.REASONING, { isEdit: true }); |
| 777 | 802 | message.extra.reasoning = reasoning; |
| 803 | + message.extra.reasoning_type = message.extra.reasoning_type ? ReasoningType.Edited : ReasoningType.Manual; | |
| 778 | 804 | await saveChatConditional(); |
| 779 | 805 | updateMessageBlock(messageId, message); |
| 780 | 806 | textarea.remove(); |
| @@ -835,6 +861,8 @@ function setReasoningEventHandlers() { | ||
| 835 | 861 | return; |
| 836 | 862 | } |
| 837 | 863 | message.extra.reasoning = ''; |
| 864 | + delete message.extra.reasoning_type; | |
| 865 | + delete message.extra.reasoning_duration; | |
| 838 | 866 | await saveChatConditional(); |
| 839 | 867 | updateMessageBlock(messageId, message); |
| 840 | 868 | const textarea = messageBlock.find('.reasoning_edit_textarea'); |
| @@ -946,6 +974,7 @@ function registerReasoningAppEvents() { | ||
| 946 | 974 | // If reasoning was found, add it to the message |
| 947 | 975 | if (parsedReasoning.reasoning) { |
| 948 | 976 | message.extra.reasoning = getRegexedString(parsedReasoning.reasoning, regex_placement.REASONING); |
| 977 | + message.extra.reasoning_type = ReasoningType.Parsed; | |
| 949 | 978 | } |
| 950 | 979 | |
| 951 | 980 | // Update the message text if it was changed |