Added translation to reasoning block (#3617) * Added translate to reasoning block * Added mising reset value * Shortcut nullable type * Added reasoning edited/deleted events, better naming * Fixed async call * Added await to saveChat calls * Exported updateReasoningUI * Removed translated reasoning on edit if auto mode is none * Added new value check before updating reasoning block, fixed an issue that display value stays same when we edit the message. * Translate reasoning before the main message * Fixed auto mode translate for reasoning message * Translate reasoning first. Prevent out of bounds access * Fix translating reasoning on swipe generation --------- Co-authored-by: Cohee <18619528+Cohee1207@users.noreply.github.com>
Signed| @@ -453,6 +453,8 @@ export const event_types = { | |||
| 453 | MESSAGE_DELETED: 'message_deleted', | 453 | MESSAGE_DELETED: 'message_deleted', |
| 454 | MESSAGE_UPDATED: 'message_updated', | 454 | MESSAGE_UPDATED: 'message_updated', |
| 455 | MESSAGE_FILE_EMBEDDED: 'message_file_embedded', | 455 | MESSAGE_FILE_EMBEDDED: 'message_file_embedded', |
| 456 | MESSAGE_REASONING_EDITED: 'message_reasoning_edited', | ||
| 457 | MESSAGE_REASONING_DELETED: 'message_reasoning_deleted', | ||
| 456 | MORE_MESSAGES_LOADED: 'more_messages_loaded', | 458 | MORE_MESSAGES_LOADED: 'more_messages_loaded', |
| 457 | IMPERSONATE_READY: 'impersonate_ready', | 459 | IMPERSONATE_READY: 'impersonate_ready', |
| 458 | CHAT_CHANGED: 'chat_id_changed', | 460 | CHAT_CHANGED: 'chat_id_changed', |
| @@ -11,6 +11,7 @@ import { | |||
| 11 | } from '../../../script.js'; | 11 | } from '../../../script.js'; |
| 12 | import { extension_settings, getContext, renderExtensionTemplateAsync } from '../../extensions.js'; | 12 | import { extension_settings, getContext, renderExtensionTemplateAsync } from '../../extensions.js'; |
| 13 | import { POPUP_RESULT, POPUP_TYPE, callGenericPopup } from '../../popup.js'; | 13 | import { POPUP_RESULT, POPUP_TYPE, callGenericPopup } from '../../popup.js'; |
| 14 | import { updateReasoningUI } from '../../reasoning.js'; | ||
| 14 | import { findSecret, secret_state, writeSecret } from '../../secrets.js'; | 15 | import { findSecret, secret_state, writeSecret } from '../../secrets.js'; |
| 15 | import { SlashCommand } from '../../slash-commands/SlashCommand.js'; | 16 | import { SlashCommand } from '../../slash-commands/SlashCommand.js'; |
| 16 | import { ARGUMENT_TYPE, SlashCommandArgument, SlashCommandNamedArgument } from '../../slash-commands/SlashCommandArgument.js'; | 17 | import { ARGUMENT_TYPE, SlashCommandArgument, SlashCommandNamedArgument } from '../../slash-commands/SlashCommandArgument.js'; |
| @@ -172,21 +173,38 @@ function loadSettings() { | |||
| 172 | showKeysButton(); | 173 | showKeysButton(); |
| 173 | } | 174 | } |
| 174 | 175 | ||
| 176 | /** | ||
| 177 | * Check if the swipe is being generated for a message. | ||
| 178 | * @param {string|number} messageId Message ID | ||
| 179 | * @returns {boolean} Whether the swipe is being generated | ||
| 180 | */ | ||
| 181 | function isGeneratingSwipe(messageId) { | ||
| 182 | return $(`#chat .mes[mesid="${messageId}"] .mes_text`).text() === '...'; | ||
| 183 | } | ||
| 184 | |||
| 175 | async function translateImpersonate(text) { | 185 | async function translateImpersonate(text) { |
| 176 | const translatedText = await translate(text, extension_settings.translate.target_language); | 186 | const translatedText = await translate(text, extension_settings.translate.target_language); |
| 177 | $('#send_textarea').val(translatedText); | 187 | $('#send_textarea').val(translatedText); |
| 178 | } | 188 | } |
| 179 | 189 | ||
| 190 | /** | ||
| 191 | * Translates the contents of an incoming message. | ||
| 192 | * @param {string | number} messageId Message ID | ||
| 193 | * @returns {Promise<void>} | ||
| 194 | */ | ||
| 180 | async function translateIncomingMessage(messageId) { | 195 | async function translateIncomingMessage(messageId) { |
| 181 | const context = getContext(); | 196 | const context = getContext(); |
| 182 | const message = context.chat[messageId]; | 197 | const message = context.chat[messageId]; |
| 183 | 198 | ||
| 199 | if (!message) { | ||
| 200 | return; | ||
| 201 | } | ||
| 202 | |||
| 184 | if (typeof message.extra !== 'object') { | 203 | if (typeof message.extra !== 'object') { |
| 185 | message.extra = {}; | 204 | message.extra = {}; |
| 186 | } | 205 | } |
| 187 | 206 | ||
| 188 | // New swipe is being generated. Don't translate that | 207 | if (isGeneratingSwipe(messageId)) { |
| 189 | if ($(`#chat .mes[mesid="${messageId}"] .mes_text`).text() == '...') { | ||
| 190 | return; | 208 | return; |
| 191 | } | 209 | } |
| 192 | 210 | ||
| @@ -194,7 +212,36 @@ async function translateIncomingMessage(messageId) { | |||
| 194 | const translation = await translate(textToTranslate, extension_settings.translate.target_language); | 212 | const translation = await translate(textToTranslate, extension_settings.translate.target_language); |
| 195 | message.extra.display_text = translation; | 213 | message.extra.display_text = translation; |
| 196 | 214 | ||
| 197 | updateMessageBlock(messageId, message); | 215 | updateMessageBlock(Number(messageId), message); |
| 216 | } | ||
| 217 | |||
| 218 | /** | ||
| 219 | * Translates the reasoning of an incoming message. | ||
| 220 | * @param {string | number} messageId | ||
| 221 | * @returns {Promise<boolean>} translated or not | ||
| 222 | */ | ||
| 223 | async function translateIncomingMessageReasoning(messageId) { | ||
| 224 | const context = getContext(); | ||
| 225 | const message = context.chat[messageId]; | ||
| 226 | |||
| 227 | if (!message) { | ||
| 228 | return false; | ||
| 229 | } | ||
| 230 | |||
| 231 | if (typeof message.extra !== 'object') { | ||
| 232 | message.extra = {}; | ||
| 233 | } | ||
| 234 | |||
| 235 | if (!message.extra.reasoning || isGeneratingSwipe(messageId)) { | ||
| 236 | return false; | ||
| 237 | } | ||
| 238 | |||
| 239 | const textToTranslate = substituteParams(message.extra.reasoning, context.name1, message.name); | ||
| 240 | const translation = await translate(textToTranslate, extension_settings.translate.target_language); | ||
| 241 | message.extra.reasoning_display_text = translation; | ||
| 242 | |||
| 243 | updateReasoningUI(Number(messageId)); | ||
| 244 | return true; | ||
| 198 | } | 245 | } |
| 199 | 246 | ||
| 200 | async function translateProviderOneRing(text, lang) { | 247 | async function translateProviderOneRing(text, lang) { |
| @@ -535,6 +582,7 @@ async function onTranslateChatClick() { | |||
| 535 | toastr.info(`${chat.length} message(s) queued for translation.`, 'Please wait...'); | 582 | toastr.info(`${chat.length} message(s) queued for translation.`, 'Please wait...'); |
| 536 | 583 | ||
| 537 | for (let i = 0; i < chat.length; i++) { | 584 | for (let i = 0; i < chat.length; i++) { |
| 585 | await translateIncomingMessageReasoning(i); | ||
| 538 | await translateIncomingMessage(i); | 586 | await translateIncomingMessage(i); |
| 539 | } | 587 | } |
| 540 | 588 | ||
| @@ -561,6 +609,7 @@ async function onTranslationsClearClick() { | |||
| 561 | for (const mes of chat) { | 609 | for (const mes of chat) { |
| 562 | if (mes.extra) { | 610 | if (mes.extra) { |
| 563 | delete mes.extra.display_text; | 611 | delete mes.extra.display_text; |
| 612 | delete mes.extra.reasoning_display_text; | ||
| 564 | } | 613 | } |
| 565 | } | 614 | } |
| 566 | 615 | ||
| @@ -573,12 +622,47 @@ async function translateMessageEdit(messageId) { | |||
| 573 | const chat = context.chat; | 622 | const chat = context.chat; |
| 574 | const message = chat[messageId]; | 623 | const message = chat[messageId]; |
| 575 | 624 | ||
| 576 | if (message.is_system || extension_settings.translate.auto_mode == autoModeOptions.NONE) { | 625 | let anyChange = false; |
| 577 | return; | 626 | if (message.is_system || (extension_settings.translate.auto_mode == autoModeOptions.NONE && message.extra?.display_text)) { |
| 627 | delete message.extra.display_text; | ||
| 628 | updateMessageBlock(messageId, message); | ||
| 629 | anyChange = true; | ||
| 630 | } else if ((message.is_user && shouldTranslate(outgoingTypes)) || (!message.is_user && shouldTranslate(incomingTypes))) { | ||
| 631 | await translateIncomingMessage(messageId); | ||
| 632 | anyChange = true; | ||
| 578 | } | 633 | } |
| 579 | 634 | ||
| 580 | if ((message.is_user && shouldTranslate(outgoingTypes)) || (!message.is_user && shouldTranslate(incomingTypes))) { | 635 | if (anyChange) { |
| 581 | await translateIncomingMessage(messageId); | 636 | await context.saveChat(); |
| 637 | } | ||
| 638 | } | ||
| 639 | |||
| 640 | async function translateMessageReasoningEdit(messageId) { | ||
| 641 | const context = getContext(); | ||
| 642 | const chat = context.chat; | ||
| 643 | const message = chat[messageId]; | ||
| 644 | |||
| 645 | let anyChange = false; | ||
| 646 | if (message.is_system || (extension_settings.translate.auto_mode == autoModeOptions.NONE && message.extra?.reasoning_display_text)) { | ||
| 647 | delete message.extra.reasoning_display_text; | ||
| 648 | updateReasoningUI(Number(messageId)); | ||
| 649 | anyChange = true; | ||
| 650 | } else if ((message.is_user && shouldTranslate(outgoingTypes)) || (!message.is_user && shouldTranslate(incomingTypes))) { | ||
| 651 | anyChange = await translateIncomingMessageReasoning(messageId); | ||
| 652 | } | ||
| 653 | |||
| 654 | if (anyChange) { | ||
| 655 | await context.saveChat(); | ||
| 656 | } | ||
| 657 | } | ||
| 658 | |||
| 659 | async function removeReasoningDisplayText(messageId) { | ||
| 660 | const context = getContext(); | ||
| 661 | const message = context.chat[messageId]; | ||
| 662 | if (message.extra?.reasoning_display_text) { | ||
| 663 | delete message.extra.reasoning_display_text; | ||
| 664 | updateReasoningUI(Number(messageId)); | ||
| 665 | await context.saveChat(); | ||
| 582 | } | 666 | } |
| 583 | } | 667 | } |
| 584 | 668 | ||
| @@ -588,22 +672,36 @@ async function onMessageTranslateClick() { | |||
| 588 | const message = context.chat[messageId]; | 672 | const message = context.chat[messageId]; |
| 589 | 673 | ||
| 590 | // If the message is already translated, revert it back to the original text | 674 | // If the message is already translated, revert it back to the original text |
| 675 | let alreadyTranslated = false; | ||
| 591 | if (message?.extra?.display_text) { | 676 | if (message?.extra?.display_text) { |
| 592 | delete message.extra.display_text; | 677 | delete message.extra.display_text; |
| 593 | updateMessageBlock(messageId, message); | 678 | updateMessageBlock(Number(messageId), message); |
| 679 | alreadyTranslated = true; | ||
| 594 | } | 680 | } |
| 681 | if (message?.extra?.reasoning_display_text) { | ||
| 682 | delete message.extra.reasoning_display_text; | ||
| 683 | updateReasoningUI(Number(messageId)); | ||
| 684 | alreadyTranslated = true; | ||
| 685 | } | ||
| 686 | |||
| 595 | // If the message is not translated, translate it | 687 | // If the message is not translated, translate it |
| 596 | else { | 688 | if (!alreadyTranslated) { |
| 689 | await translateIncomingMessageReasoning(messageId); | ||
| 597 | await translateIncomingMessage(messageId); | 690 | await translateIncomingMessage(messageId); |
| 598 | } | 691 | } |
| 599 | 692 | ||
| 600 | await context.saveChat(); | 693 | await context.saveChat(); |
| 601 | } | 694 | } |
| 602 | 695 | ||
| 603 | const handleIncomingMessage = createEventHandler(translateIncomingMessage, () => shouldTranslate(incomingTypes)); | 696 | const handleIncomingMessage = createEventHandler(async (messageId) => { |
| 697 | await translateIncomingMessageReasoning(messageId); | ||
| 698 | await translateIncomingMessage(messageId); | ||
| 699 | }, () => shouldTranslate(incomingTypes)); | ||
| 604 | const handleOutgoingMessage = createEventHandler(translateOutgoingMessage, () => shouldTranslate(outgoingTypes)); | 700 | const handleOutgoingMessage = createEventHandler(translateOutgoingMessage, () => shouldTranslate(outgoingTypes)); |
| 605 | const handleImpersonateReady = createEventHandler(translateImpersonate, () => shouldTranslate(incomingTypes)); | 701 | const handleImpersonateReady = createEventHandler(translateImpersonate, () => shouldTranslate(incomingTypes)); |
| 606 | const handleMessageEdit = createEventHandler(translateMessageEdit, () => true); | 702 | const handleMessageEdit = createEventHandler(translateMessageEdit, () => true); |
| 703 | const handleMessageReasoningEdit = createEventHandler(translateMessageReasoningEdit, () => true); | ||
| 704 | const handleMessageReasoningDelete = createEventHandler(removeReasoningDisplayText, () => true); | ||
| 607 | 705 | ||
| 608 | globalThis.translate = translate; | 706 | globalThis.translate = translate; |
| 609 | 707 | ||
| @@ -717,6 +815,8 @@ jQuery(async () => { | |||
| 717 | eventSource.on(event_types.MESSAGE_SWIPED, handleIncomingMessage); | 815 | eventSource.on(event_types.MESSAGE_SWIPED, handleIncomingMessage); |
| 718 | eventSource.on(event_types.IMPERSONATE_READY, handleImpersonateReady); | 816 | eventSource.on(event_types.IMPERSONATE_READY, handleImpersonateReady); |
| 719 | eventSource.on(event_types.MESSAGE_UPDATED, handleMessageEdit); | 817 | eventSource.on(event_types.MESSAGE_UPDATED, handleMessageEdit); |
| 818 | eventSource.on(event_types.MESSAGE_REASONING_EDITED, handleMessageReasoningEdit); | ||
| 819 | eventSource.on(event_types.MESSAGE_REASONING_DELETED, handleMessageReasoningDelete); | ||
| 720 | 820 | ||
| 721 | document.body.classList.add('translate'); | 821 | document.body.classList.add('translate'); |
| 722 | 822 | ||
| @@ -167,6 +167,8 @@ export class ReasoningHandler { | |||
| 167 | this.type = null; | 167 | this.type = null; |
| 168 | /** @type {string} The reasoning output */ | 168 | /** @type {string} The reasoning output */ |
| 169 | this.reasoning = ''; | 169 | this.reasoning = ''; |
| 170 | /** @type {string?} The reasoning output display in case of translate or other */ | ||
| 171 | this.reasoningDisplayText = null; | ||
| 170 | /** @type {Date} When the reasoning started */ | 172 | /** @type {Date} When the reasoning started */ |
| 171 | this.startTime = null; | 173 | this.startTime = null; |
| 172 | /** @type {Date} When the reasoning ended */ | 174 | /** @type {Date} When the reasoning ended */ |
| @@ -234,6 +236,7 @@ export class ReasoningHandler { | |||
| 234 | 236 | ||
| 235 | this.type = extra?.reasoning_type; | 237 | this.type = extra?.reasoning_type; |
| 236 | this.reasoning = extra?.reasoning ?? ''; | 238 | this.reasoning = extra?.reasoning ?? ''; |
| 239 | this.reasoningDisplayText = extra?.reasoning_display_text ?? null; | ||
| 237 | 240 | ||
| 238 | if (this.state !== ReasoningState.None) { | 241 | if (this.state !== ReasoningState.None) { |
| 239 | this.initialTime = new Date(chat[messageId].gen_started); | 242 | this.initialTime = new Date(chat[messageId].gen_started); |
| @@ -249,6 +252,7 @@ export class ReasoningHandler { | |||
| 249 | this.state = this.#isHiddenReasoningModel ? ReasoningState.Thinking : ReasoningState.None; | 252 | this.state = this.#isHiddenReasoningModel ? ReasoningState.Thinking : ReasoningState.None; |
| 250 | this.type = null; | 253 | this.type = null; |
| 251 | this.reasoning = ''; | 254 | this.reasoning = ''; |
| 255 | this.reasoningDisplayText = null; | ||
| 252 | this.initialTime = new Date(); | 256 | this.initialTime = new Date(); |
| 253 | this.startTime = null; | 257 | this.startTime = null; |
| 254 | this.endTime = null; | 258 | this.endTime = null; |
| @@ -434,7 +438,7 @@ export class ReasoningHandler { | |||
| 434 | setDatasetProperty(this.messageReasoningDetailsDom, 'type', this.type); | 438 | setDatasetProperty(this.messageReasoningDetailsDom, 'type', this.type); |
| 435 | 439 | ||
| 436 | // Update the reasoning message | 440 | // Update the reasoning message |
| 437 | const reasoning = trimSpaces(this.reasoning); | 441 | const reasoning = trimSpaces(this.reasoningDisplayText ?? this.reasoning); |
| 438 | const displayReasoning = messageFormatting(reasoning, '', false, false, messageId, {}, true); | 442 | const displayReasoning = messageFormatting(reasoning, '', false, false, messageId, {}, true); |
| 439 | this.messageReasoningContentDom.innerHTML = displayReasoning; | 443 | this.messageReasoningContentDom.innerHTML = displayReasoning; |
| 440 | 444 | ||
| @@ -888,12 +892,17 @@ function setReasoningEventHandlers() { | |||
| 888 | } | 892 | } |
| 889 | 893 | ||
| 890 | const textarea = messageBlock.find('.reasoning_edit_textarea'); | 894 | const textarea = messageBlock.find('.reasoning_edit_textarea'); |
| 891 | updateReasoningFromValue(message, String(textarea.val())); | 895 | const newReasoning = String(textarea.val()); |
| 896 | textarea.remove(); | ||
| 897 | if (newReasoning === message.extra.reasoning) { | ||
| 898 | return; | ||
| 899 | } | ||
| 900 | updateReasoningFromValue(message, newReasoning); | ||
| 892 | await saveChatConditional(); | 901 | await saveChatConditional(); |
| 893 | updateMessageBlock(messageId, message); | 902 | updateMessageBlock(messageId, message); |
| 894 | textarea.remove(); | ||
| 895 | 903 | ||
| 896 | messageBlock.find('.mes_edit_done:visible').trigger('click'); | 904 | messageBlock.find('.mes_edit_done:visible').trigger('click'); |
| 905 | await eventSource.emit(event_types.MESSAGE_REASONING_EDITED, messageId); | ||
| 897 | }); | 906 | }); |
| 898 | 907 | ||
| 899 | $(document).on('click', '.mes_reasoning_edit_cancel', function (e) { | 908 | $(document).on('click', '.mes_reasoning_edit_cancel', function (e) { |
| @@ -955,6 +964,7 @@ function setReasoningEventHandlers() { | |||
| 955 | updateMessageBlock(messageId, message); | 964 | updateMessageBlock(messageId, message); |
| 956 | const textarea = messageBlock.find('.reasoning_edit_textarea'); | 965 | const textarea = messageBlock.find('.reasoning_edit_textarea'); |
| 957 | textarea.remove(); | 966 | textarea.remove(); |
| 967 | await eventSource.emit(event_types.MESSAGE_REASONING_DELETED, messageId); | ||
| 958 | }); | 968 | }); |
| 959 | 969 | ||
| 960 | $(document).on('pointerup', '.mes_reasoning_copy', async function () { | 970 | $(document).on('pointerup', '.mes_reasoning_copy', async function () { |
| @@ -79,6 +79,7 @@ import { timestampToMoment, uuidv4 } from './utils.js'; | |||
| 79 | import { getGlobalVariable, getLocalVariable, setGlobalVariable, setLocalVariable } from './variables.js'; | 79 | import { getGlobalVariable, getLocalVariable, setGlobalVariable, setLocalVariable } from './variables.js'; |
| 80 | import { convertCharacterBook, loadWorldInfo, saveWorldInfo, updateWorldInfoList } from './world-info.js'; | 80 | import { convertCharacterBook, loadWorldInfo, saveWorldInfo, updateWorldInfoList } from './world-info.js'; |
| 81 | import { ChatCompletionService, TextCompletionService } from './custom-request.js'; | 81 | import { ChatCompletionService, TextCompletionService } from './custom-request.js'; |
| 82 | import { updateReasoningUI } from './reasoning.js'; | ||
| 82 | 83 | ||
| 83 | export function getContext() { | 84 | export function getContext() { |
| 84 | return { | 85 | return { |
| @@ -211,6 +212,7 @@ export function getContext() { | |||
| 211 | clearChat, | 212 | clearChat, |
| 212 | ChatCompletionService, | 213 | ChatCompletionService, |
| 213 | TextCompletionService, | 214 | TextCompletionService, |
| 215 | updateReasoningUI, | ||
| 214 | unshallowCharacter, | 216 | unshallowCharacter, |
| 215 | unshallowGroupMembers, | 217 | unshallowGroupMembers, |
| 216 | }; | 218 | }; |