Merge pull request #3484 from SillyTavern/reasoning-parsing-streaming Implement reasoning parsing during streaming, based on provided prefix/suffix
Signed| @@ -3223,6 +3223,7 @@ class StreamingProcessor { | |||
| 3223 | 3223 | ||
| 3224 | // Update reasoning | 3224 | // Update reasoning |
| 3225 | await this.reasoningHandler.process(messageId, mesChanged); | 3225 | await this.reasoningHandler.process(messageId, mesChanged); |
| 3226 | processedText = chat[messageId]['mes']; | ||
| 3226 | 3227 | ||
| 3227 | // Token count update. | 3228 | // Token count update. |
| 3228 | const tokenCountText = this.reasoningHandler.reasoning + processedText; | 3229 | const tokenCountText = this.reasoningHandler.reasoning + processedText; |
| @@ -3373,7 +3374,7 @@ class StreamingProcessor { | |||
| 3373 | this.messageLogprobs.push(...(Array.isArray(logprobs) ? logprobs : [logprobs])); | 3374 | this.messageLogprobs.push(...(Array.isArray(logprobs) ? logprobs : [logprobs])); |
| 3374 | } | 3375 | } |
| 3375 | // Get the updated reasoning string into the handler | 3376 | // Get the updated reasoning string into the handler |
| 3376 | this.reasoningHandler.updateReasoning(this.messageId, state?.reasoning ?? ''); | 3377 | this.reasoningHandler.updateReasoning(this.messageId, state?.reasoning); |
| 3377 | await eventSource.emit(event_types.STREAM_TOKEN_RECEIVED, text); | 3378 | await eventSource.emit(event_types.STREAM_TOKEN_RECEIVED, text); |
| 3378 | await sw.tick(async () => await this.onProgressStreaming(this.messageId, this.continueMessage + text)); | 3379 | await sw.tick(async () => await this.onProgressStreaming(this.messageId, this.continueMessage + text)); |
| 3379 | } | 3380 | } |
| @@ -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'; |
| @@ -14,7 +14,19 @@ import { commonEnumProviders, enumIcons } from './slash-commands/SlashCommandCom | |||
| 14 | import { enumTypes, SlashCommandEnumValue } from './slash-commands/SlashCommandEnumValue.js'; | 14 | import { enumTypes, SlashCommandEnumValue } from './slash-commands/SlashCommandEnumValue.js'; |
| 15 | import { SlashCommandParser } from './slash-commands/SlashCommandParser.js'; | 15 | import { SlashCommandParser } from './slash-commands/SlashCommandParser.js'; |
| 16 | import { textgen_types, textgenerationwebui_settings } from './textgen-settings.js'; | 16 | import { textgen_types, textgenerationwebui_settings } from './textgen-settings.js'; |
| 17 | import { copyText, escapeRegex, isFalseBoolean, setDatasetProperty } from './utils.js'; | 17 | import { copyText, escapeRegex, isFalseBoolean, setDatasetProperty, trimSpaces } from './utils.js'; |
| 18 | |||
| 19 | /** | ||
| 20 | * Enum representing the type of the reasoning for a message (where it came from) | ||
| 21 | * @enum {string} | ||
| 22 | * @readonly | ||
| 23 | */ | ||
| 24 | export const ReasoningType = { | ||
| 25 | Model: 'model', | ||
| 26 | Parsed: 'parsed', | ||
| 27 | Manual: 'manual', | ||
| 28 | Edited: 'edited', | ||
| 29 | }; | ||
| 18 | 30 | ||
| 19 | /** | 31 | /** |
| 20 | * Gets a message from a jQuery element. | 32 | * Gets a message from a jQuery element. |
| @@ -130,7 +142,12 @@ export const ReasoningState = { | |||
| 130 | * This class is used inside the {@link StreamingProcessor} to manage reasoning states and UI updates. | 142 | * This class is used inside the {@link StreamingProcessor} to manage reasoning states and UI updates. |
| 131 | */ | 143 | */ |
| 132 | export class ReasoningHandler { | 144 | export class ReasoningHandler { |
| 145 | /** @type {boolean} True if the model supports reasoning, but hides the reasoning output */ | ||
| 133 | #isHiddenReasoningModel; | 146 | #isHiddenReasoningModel; |
| 147 | /** @type {boolean} True if the handler is currently handling a manual parse of reasoning blocks */ | ||
| 148 | #isParsingReasoning = false; | ||
| 149 | /** @type {number?} When reasoning is being parsed manually, and the reasoning has ended, this will be the index at which the actual messages starts */ | ||
| 150 | #parsingReasoningMesStartIndex = null; | ||
| 134 | 151 | ||
| 135 | /** | 152 | /** |
| 136 | * @param {Date?} [timeStarted=null] - When the generation started | 153 | * @param {Date?} [timeStarted=null] - When the generation started |
| @@ -138,6 +155,8 @@ export class ReasoningHandler { | |||
| 138 | constructor(timeStarted = null) { | 155 | constructor(timeStarted = null) { |
| 139 | /** @type {ReasoningState} The current state of the reasoning process */ | 156 | /** @type {ReasoningState} The current state of the reasoning process */ |
| 140 | this.state = ReasoningState.None; | 157 | this.state = ReasoningState.None; |
| 158 | /** @type {ReasoningType?} The type of the reasoning (where it came from) */ | ||
| 159 | this.type = null; | ||
| 141 | /** @type {string} The reasoning output */ | 160 | /** @type {string} The reasoning output */ |
| 142 | this.reasoning = ''; | 161 | this.reasoning = ''; |
| 143 | /** @type {Date} When the reasoning started */ | 162 | /** @type {Date} When the reasoning started */ |
| @@ -148,7 +167,6 @@ export class ReasoningHandler { | |||
| 148 | /** @type {Date} Initial starting time of the generation */ | 167 | /** @type {Date} Initial starting time of the generation */ |
| 149 | this.initialTime = timeStarted ?? new Date(); | 168 | this.initialTime = timeStarted ?? new Date(); |
| 150 | 169 | ||
| 151 | /** @type {boolean} True if the model supports reasoning, but hides the reasoning output */ | ||
| 152 | this.#isHiddenReasoningModel = isHiddenReasoningModel(); | 170 | this.#isHiddenReasoningModel = isHiddenReasoningModel(); |
| 153 | 171 | ||
| 154 | // Cached DOM elements for reasoning | 172 | // Cached DOM elements for reasoning |
| @@ -195,6 +213,7 @@ export class ReasoningHandler { | |||
| 195 | this.state = ReasoningState.Hidden; | 213 | this.state = ReasoningState.Hidden; |
| 196 | } | 214 | } |
| 197 | 215 | ||
| 216 | this.type = extra?.reasoning_type; | ||
| 198 | this.reasoning = extra?.reasoning ?? ''; | 217 | this.reasoning = extra?.reasoning ?? ''; |
| 199 | 218 | ||
| 200 | if (this.state !== ReasoningState.None) { | 219 | if (this.state !== ReasoningState.None) { |
| @@ -209,6 +228,7 @@ export class ReasoningHandler { | |||
| 209 | // Make sure reset correctly clears all relevant states | 228 | // Make sure reset correctly clears all relevant states |
| 210 | if (reset) { | 229 | if (reset) { |
| 211 | this.state = this.#isHiddenReasoningModel ? ReasoningState.Thinking : ReasoningState.None; | 230 | this.state = this.#isHiddenReasoningModel ? ReasoningState.Thinking : ReasoningState.None; |
| 231 | this.type = null; | ||
| 212 | this.reasoning = ''; | 232 | this.reasoning = ''; |
| 213 | this.initialTime = new Date(); | 233 | this.initialTime = new Date(); |
| 214 | this.startTime = null; | 234 | this.startTime = null; |
| @@ -238,18 +258,19 @@ export class ReasoningHandler { | |||
| 238 | * Updates the reasoning text/string for a message. | 258 | * Updates the reasoning text/string for a message. |
| 239 | * | 259 | * |
| 240 | * @param {number} messageId - The ID of the message to update | 260 | * @param {number} messageId - The ID of the message to update |
| 241 | * @param {string?} [reasoning=null] - The reasoning text to update - If null, uses the current reasoning | 261 | * @param {string?} [reasoning=null] - The reasoning text to update - If null or empty, uses the current reasoning |
| 242 | * @param {Object} [options={}] - Optional arguments | 262 | * @param {Object} [options={}] - Optional arguments |
| 243 | * @param {boolean} [options.persist=false] - Whether to persist the reasoning to the message object | 263 | * @param {boolean} [options.persist=false] - Whether to persist the reasoning to the message object |
| 264 | * @param {boolean} [options.allowReset=false] - Whether to allow empty reasoning provided to reset the reasoning, instead of just taking the existing one | ||
| 244 | * @returns {boolean} - Returns true if the reasoning was changed, otherwise false | 265 | * @returns {boolean} - Returns true if the reasoning was changed, otherwise false |
| 245 | */ | 266 | */ |
| 246 | updateReasoning(messageId, reasoning = null, { persist = false } = {}) { | 267 | updateReasoning(messageId, reasoning = null, { persist = false, allowReset = false } = {}) { |
| 247 | if (messageId == -1 || !chat[messageId]) { | 268 | if (messageId == -1 || !chat[messageId]) { |
| 248 | return false; | 269 | return false; |
| 249 | } | 270 | } |
| 250 | 271 | ||
| 251 | reasoning = reasoning ?? this.reasoning; | 272 | reasoning = allowReset ? reasoning ?? this.reasoning : reasoning || this.reasoning; |
| 252 | reasoning = power_user.trim_spaces ? reasoning.trim() : reasoning; | 273 | reasoning = trimSpaces(reasoning); |
| 253 | 274 | ||
| 254 | // Ensure the chat extra exists | 275 | // Ensure the chat extra exists |
| 255 | if (!chat[messageId].extra) { | 276 | if (!chat[messageId].extra) { |
| @@ -260,10 +281,13 @@ export class ReasoningHandler { | |||
| 260 | const reasoningChanged = extra.reasoning !== reasoning; | 281 | const reasoningChanged = extra.reasoning !== reasoning; |
| 261 | this.reasoning = getRegexedString(reasoning ?? '', regex_placement.REASONING); | 282 | this.reasoning = getRegexedString(reasoning ?? '', regex_placement.REASONING); |
| 262 | 283 | ||
| 284 | this.type = (this.#isParsingReasoning || this.#parsingReasoningMesStartIndex) ? ReasoningType.Parsed : ReasoningType.Model; | ||
| 285 | |||
| 263 | if (persist) { | 286 | if (persist) { |
| 264 | // Build and save the reasoning data to message extras | 287 | // Build and save the reasoning data to message extras |
| 265 | extra.reasoning = this.reasoning; | 288 | extra.reasoning = this.reasoning; |
| 266 | extra.reasoning_duration = this.getDuration(); | 289 | extra.reasoning_duration = this.getDuration(); |
| 290 | extra.reasoning_type = (this.#isParsingReasoning || this.#parsingReasoningMesStartIndex) ? ReasoningType.Parsed : ReasoningType.Model; | ||
| 267 | } | 291 | } |
| 268 | 292 | ||
| 269 | return reasoningChanged; | 293 | return reasoningChanged; |
| @@ -280,7 +304,10 @@ export class ReasoningHandler { | |||
| 280 | * @returns {Promise<void>} | 304 | * @returns {Promise<void>} |
| 281 | */ | 305 | */ |
| 282 | async process(messageId, mesChanged) { | 306 | async process(messageId, mesChanged) { |
| 283 | if (!this.reasoning && !this.#isHiddenReasoningModel) return; | 307 | mesChanged = this.#autoParseReasoningFromMessage(messageId, mesChanged); |
| 308 | |||
| 309 | if (!this.reasoning && !this.#isHiddenReasoningModel) | ||
| 310 | return; | ||
| 284 | 311 | ||
| 285 | // Ensure reasoning string is updated and regexes are applied correctly | 312 | // Ensure reasoning string is updated and regexes are applied correctly |
| 286 | const reasoningChanged = this.updateReasoning(messageId, null, { persist: true }); | 313 | const reasoningChanged = this.updateReasoning(messageId, null, { persist: true }); |
| @@ -295,6 +322,53 @@ export class ReasoningHandler { | |||
| 295 | } | 322 | } |
| 296 | } | 323 | } |
| 297 | 324 | ||
| 325 | #autoParseReasoningFromMessage(messageId, mesChanged) { | ||
| 326 | if (!power_user.reasoning.auto_parse) | ||
| 327 | return; | ||
| 328 | if (!power_user.reasoning.prefix || !power_user.reasoning.suffix) | ||
| 329 | return mesChanged; | ||
| 330 | |||
| 331 | /** @type {{ mes: string, [key: string]: any}} */ | ||
| 332 | const message = chat[messageId]; | ||
| 333 | if (!message) return mesChanged; | ||
| 334 | |||
| 335 | // If we are done with reasoning parse, we just split the message correctly so the reasoning doesn't show up inside of it. | ||
| 336 | if (this.#parsingReasoningMesStartIndex) { | ||
| 337 | message.mes = trimSpaces(message.mes.slice(this.#parsingReasoningMesStartIndex)); | ||
| 338 | return mesChanged; | ||
| 339 | } | ||
| 340 | |||
| 341 | if (this.state === ReasoningState.None) { | ||
| 342 | // If streamed message starts with the opening, cut it out and put all inside reasoning | ||
| 343 | if (message.mes.startsWith(power_user.reasoning.prefix) && message.mes.length > power_user.reasoning.prefix.length) { | ||
| 344 | this.#isParsingReasoning = true; | ||
| 345 | |||
| 346 | // Manually set starting state here, as we might already have received the ending suffix | ||
| 347 | this.state = ReasoningState.Thinking; | ||
| 348 | this.startTime = this.initialTime; | ||
| 349 | } | ||
| 350 | } | ||
| 351 | |||
| 352 | if (!this.#isParsingReasoning) | ||
| 353 | return mesChanged; | ||
| 354 | |||
| 355 | // If we are in manual parsing mode, all currently streaming mes tokens will go the the reasoning block | ||
| 356 | const originalMes = message.mes; | ||
| 357 | this.reasoning = originalMes.slice(power_user.reasoning.prefix.length); | ||
| 358 | message.mes = ''; | ||
| 359 | |||
| 360 | // If the reasoning contains the ending suffix, we cut that off and continue as message streaming | ||
| 361 | if (this.reasoning.includes(power_user.reasoning.suffix)) { | ||
| 362 | this.reasoning = this.reasoning.slice(0, this.reasoning.indexOf(power_user.reasoning.suffix)); | ||
| 363 | this.#parsingReasoningMesStartIndex = originalMes.indexOf(power_user.reasoning.suffix) + power_user.reasoning.suffix.length; | ||
| 364 | message.mes = trimSpaces(originalMes.slice(this.#parsingReasoningMesStartIndex)); | ||
| 365 | this.#isParsingReasoning = false; | ||
| 366 | } | ||
| 367 | |||
| 368 | // Only return the original mesChanged value if we haven't cut off the complete message | ||
| 369 | return message.mes.length ? mesChanged : false; | ||
| 370 | } | ||
| 371 | |||
| 298 | /** | 372 | /** |
| 299 | * Completes the reasoning process for a message. | 373 | * Completes the reasoning process for a message. |
| 300 | * | 374 | * |
| @@ -337,9 +411,10 @@ export class ReasoningHandler { | |||
| 337 | // Update states to the relevant DOM elements | 411 | // Update states to the relevant DOM elements |
| 338 | setDatasetProperty(this.messageDom, 'reasoningState', this.state !== ReasoningState.None ? this.state : null); | 412 | setDatasetProperty(this.messageDom, 'reasoningState', this.state !== ReasoningState.None ? this.state : null); |
| 339 | setDatasetProperty(this.messageReasoningDetailsDom, 'state', this.state); | 413 | setDatasetProperty(this.messageReasoningDetailsDom, 'state', this.state); |
| 414 | setDatasetProperty(this.messageReasoningDetailsDom, 'type', this.type); | ||
| 340 | 415 | ||
| 341 | // Update the reasoning message | 416 | // Update the reasoning message |
| 342 | const reasoning = power_user.trim_spaces ? this.reasoning.trim() : this.reasoning; | 417 | const reasoning = trimSpaces(this.reasoning); |
| 343 | const displayReasoning = messageFormatting(reasoning, '', false, false, messageId, {}, true); | 418 | const displayReasoning = messageFormatting(reasoning, '', false, false, messageId, {}, true); |
| 344 | this.messageReasoningContentDom.innerHTML = displayReasoning; | 419 | this.messageReasoningContentDom.innerHTML = displayReasoning; |
| 345 | 420 | ||
| @@ -394,17 +469,14 @@ export class ReasoningHandler { | |||
| 394 | const element = this.messageReasoningHeaderDom; | 469 | const element = this.messageReasoningHeaderDom; |
| 395 | const duration = this.getDuration(); | 470 | const duration = this.getDuration(); |
| 396 | let data = null; | 471 | let data = null; |
| 472 | let title = ''; | ||
| 397 | if (duration) { | 473 | if (duration) { |
| 398 | const durationStr = moment.duration(duration).locale(getCurrentLocale()).humanize({ s: 50, ss: 3 }); | 474 | const seconds = moment.duration(duration).asSeconds(); |
| 399 | const secondsStr = moment.duration(duration).asSeconds(); | ||
| 400 | |||
| 401 | const span = document.createElement('span'); | ||
| 402 | span.title = t`${secondsStr} seconds`; | ||
| 403 | span.textContent = durationStr; | ||
| 404 | 475 | ||
| 405 | element.textContent = t`Thought for `; | 476 | const durationStr = moment.duration(duration).locale(getCurrentLocale()).humanize({ s: 50, ss: 3 }); |
| 406 | element.appendChild(span); | 477 | element.textContent = t`Thought for ${durationStr}`; |
| 407 | data = String(secondsStr); | 478 | data = String(seconds); |
| 479 | title = `${seconds} seconds`; | ||
| 408 | } else if ([ReasoningState.Done, ReasoningState.Hidden].includes(this.state)) { | 480 | } else if ([ReasoningState.Done, ReasoningState.Hidden].includes(this.state)) { |
| 409 | element.textContent = t`Thought for some time`; | 481 | element.textContent = t`Thought for some time`; |
| 410 | data = 'unknown'; | 482 | data = 'unknown'; |
| @@ -413,6 +485,12 @@ export class ReasoningHandler { | |||
| 413 | data = null; | 485 | data = null; |
| 414 | } | 486 | } |
| 415 | 487 | ||
| 488 | if (this.type !== ReasoningType.Model) { | ||
| 489 | title += ` [${translate(this.type)}]`; | ||
| 490 | title = title.trim(); | ||
| 491 | } | ||
| 492 | element.title = title; | ||
| 493 | |||
| 416 | setDatasetProperty(this.messageReasoningDetailsDom, 'duration', data); | 494 | setDatasetProperty(this.messageReasoningDetailsDom, 'duration', data); |
| 417 | setDatasetProperty(element, 'duration', data); | 495 | setDatasetProperty(element, 'duration', data); |
| 418 | } | 496 | } |
| @@ -574,11 +652,16 @@ function registerReasoningSlashCommands() { | |||
| 574 | callback: async (args, value) => { | 652 | callback: async (args, value) => { |
| 575 | const messageId = !isNaN(Number(args.at)) ? Number(args.at) : chat.length - 1; | 653 | const messageId = !isNaN(Number(args.at)) ? Number(args.at) : chat.length - 1; |
| 576 | const message = chat[messageId]; | 654 | const message = chat[messageId]; |
| 577 | if (!message?.extra) { | 655 | if (!message) { |
| 578 | return ''; | 656 | return ''; |
| 579 | } | 657 | } |
| 658 | // Make sure the message has an extra object | ||
| 659 | if (!message.extra || typeof message.extra !== 'object') { | ||
| 660 | message.extra = {}; | ||
| 661 | } | ||
| 580 | 662 | ||
| 581 | message.extra.reasoning = String(value ?? ''); | 663 | message.extra.reasoning = String(value ?? ''); |
| 664 | message.extra.reasoning_type = ReasoningType.Manual; | ||
| 582 | await saveChatConditional(); | 665 | await saveChatConditional(); |
| 583 | 666 | ||
| 584 | closeMessageEditor('reasoning'); | 667 | closeMessageEditor('reasoning'); |
| @@ -748,6 +831,7 @@ function setReasoningEventHandlers() { | |||
| 748 | const textarea = messageBlock.find('.reasoning_edit_textarea'); | 831 | const textarea = messageBlock.find('.reasoning_edit_textarea'); |
| 749 | const reasoning = getRegexedString(String(textarea.val()), regex_placement.REASONING, { isEdit: true }); | 832 | const reasoning = getRegexedString(String(textarea.val()), regex_placement.REASONING, { isEdit: true }); |
| 750 | message.extra.reasoning = reasoning; | 833 | message.extra.reasoning = reasoning; |
| 834 | message.extra.reasoning_type = message.extra.reasoning_type ? ReasoningType.Edited : ReasoningType.Manual; | ||
| 751 | await saveChatConditional(); | 835 | await saveChatConditional(); |
| 752 | updateMessageBlock(messageId, message); | 836 | updateMessageBlock(messageId, message); |
| 753 | textarea.remove(); | 837 | textarea.remove(); |
| @@ -808,6 +892,8 @@ function setReasoningEventHandlers() { | |||
| 808 | return; | 892 | return; |
| 809 | } | 893 | } |
| 810 | message.extra.reasoning = ''; | 894 | message.extra.reasoning = ''; |
| 895 | delete message.extra.reasoning_type; | ||
| 896 | delete message.extra.reasoning_duration; | ||
| 811 | await saveChatConditional(); | 897 | await saveChatConditional(); |
| 812 | updateMessageBlock(messageId, message); | 898 | updateMessageBlock(messageId, message); |
| 813 | const textarea = messageBlock.find('.reasoning_edit_textarea'); | 899 | const textarea = messageBlock.find('.reasoning_edit_textarea'); |
| @@ -868,9 +954,9 @@ function parseReasoningFromString(str, { strict = true } = {}) { | |||
| 868 | return ''; | 954 | return ''; |
| 869 | }); | 955 | }); |
| 870 | 956 | ||
| 871 | if (didReplace && power_user.trim_spaces) { | 957 | if (didReplace) { |
| 872 | reasoning = reasoning.trim(); | 958 | reasoning = trimSpaces(reasoning); |
| 873 | content = content.trim(); | 959 | content = trimSpaces(content); |
| 874 | } | 960 | } |
| 875 | 961 | ||
| 876 | return { reasoning, content }; | 962 | return { reasoning, content }; |
| @@ -899,6 +985,11 @@ function registerReasoningAppEvents() { | |||
| 899 | return null; | 985 | return null; |
| 900 | } | 986 | } |
| 901 | 987 | ||
| 988 | if (message.extra?.reasoning) { | ||
| 989 | console.debug('[Reasoning] Message already has reasoning', idx); | ||
| 990 | return null; | ||
| 991 | } | ||
| 992 | |||
| 902 | const parsedReasoning = parseReasoningFromString(message.mes); | 993 | const parsedReasoning = parseReasoningFromString(message.mes); |
| 903 | 994 | ||
| 904 | // No reasoning block found | 995 | // No reasoning block found |
| @@ -916,6 +1007,7 @@ function registerReasoningAppEvents() { | |||
| 916 | // If reasoning was found, add it to the message | 1007 | // If reasoning was found, add it to the message |
| 917 | if (parsedReasoning.reasoning) { | 1008 | if (parsedReasoning.reasoning) { |
| 918 | message.extra.reasoning = getRegexedString(parsedReasoning.reasoning, regex_placement.REASONING); | 1009 | message.extra.reasoning = getRegexedString(parsedReasoning.reasoning, regex_placement.REASONING); |
| 1010 | message.extra.reasoning_type = ReasoningType.Parsed; | ||
| 919 | } | 1011 | } |
| 920 | 1012 | ||
| 921 | // Update the message text if it was changed | 1013 | // Update the message text if it was changed |
| @@ -8,7 +8,7 @@ import { | |||
| 8 | import { getContext } from './extensions.js'; | 8 | import { getContext } from './extensions.js'; |
| 9 | import { characters, getRequestHeaders, this_chid } from '../script.js'; | 9 | import { characters, getRequestHeaders, this_chid } from '../script.js'; |
| 10 | import { isMobile } from './RossAscends-mods.js'; | 10 | import { isMobile } from './RossAscends-mods.js'; |
| 11 | import { collapseNewlines } from './power-user.js'; | 11 | import { collapseNewlines, power_user } from './power-user.js'; |
| 12 | import { debounce_timeout } from './constants.js'; | 12 | import { debounce_timeout } from './constants.js'; |
| 13 | import { Popup, POPUP_RESULT, POPUP_TYPE } from './popup.js'; | 13 | import { Popup, POPUP_RESULT, POPUP_TYPE } from './popup.js'; |
| 14 | import { SlashCommandClosure } from './slash-commands/SlashCommandClosure.js'; | 14 | import { SlashCommandClosure } from './slash-commands/SlashCommandClosure.js'; |
| @@ -677,6 +677,19 @@ export function sortByCssOrder(a, b) { | |||
| 677 | } | 677 | } |
| 678 | 678 | ||
| 679 | /** | 679 | /** |
| 680 | * Trims leading and trailing whitespace from the input string based on a configuration setting. | ||
| 681 | * @param {string} input - The string to be trimmed | ||
| 682 | * @returns {string} The trimmed string if trimming is enabled; otherwise, returns the original string | ||
| 683 | */ | ||
| 684 | |||
| 685 | export function trimSpaces(input) { | ||
| 686 | if (!input || typeof input !== 'string') { | ||
| 687 | return input; | ||
| 688 | } | ||
| 689 | return power_user.trim_spaces ? input.trim() : input; | ||
| 690 | } | ||
| 691 | |||
| 692 | /** | ||
| 680 | * Trims a string to the end of a nearest sentence. | 693 | * Trims a string to the end of a nearest sentence. |
| 681 | * @param {string} input The string to trim. | 694 | * @param {string} input The string to trim. |
| 682 | * @returns {string} The trimmed string. | 695 | * @returns {string} The trimmed string. |