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>

50f1e3f0f25ce859bee2a34c269591f2627465fc

bmen25124 <bmen25124@gmail.com>

Signed
4 files changed, +127 -13Showing whitespace changes
public/script.js+2 -0
@@ -453,6 +453,8 @@ export const event_types = {
453453 MESSAGE_DELETED: 'message_deleted',
454454 MESSAGE_UPDATED: 'message_updated',
455455 MESSAGE_FILE_EMBEDDED: 'message_file_embedded',
456+ MESSAGE_REASONING_EDITED: 'message_reasoning_edited',
457+ MESSAGE_REASONING_DELETED: 'message_reasoning_deleted',
456458 MORE_MESSAGES_LOADED: 'more_messages_loaded',
457459 IMPERSONATE_READY: 'impersonate_ready',
458460 CHAT_CHANGED: 'chat_id_changed',
public/scripts/extensions/translate/index.js+110 -10
@@ -11,6 +11,7 @@ import {
1111} from '../../../script.js';
1212import { extension_settings, getContext, renderExtensionTemplateAsync } from '../../extensions.js';
1313import { POPUP_RESULT, POPUP_TYPE, callGenericPopup } from '../../popup.js';
14+import { updateReasoningUI } from '../../reasoning.js';
1415import { findSecret, secret_state, writeSecret } from '../../secrets.js';
1516import { SlashCommand } from '../../slash-commands/SlashCommand.js';
1617import { ARGUMENT_TYPE, SlashCommandArgument, SlashCommandNamedArgument } from '../../slash-commands/SlashCommandArgument.js';
@@ -172,21 +173,38 @@ function loadSettings() {
172173 showKeysButton();
173174}
174175
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+
175185async function translateImpersonate(text) {
176186 const translatedText = await translate(text, extension_settings.translate.target_language);
177187 $('#send_textarea').val(translatedText);
178188}
179189
190+/**
191+ * Translates the contents of an incoming message.
192+ * @param {string | number} messageId Message ID
193+ * @returns {Promise<void>}
194+ */
180195async function translateIncomingMessage(messageId) {
181196 const context = getContext();
182197 const message = context.chat[messageId];
183198
199+ if (!message) {
200+ return;
201+ }
202+
184203 if (typeof message.extra !== 'object') {
185204 message.extra = {};
186205 }
187206
188- // New swipe is being generated. Don't translate that
207+ if (isGeneratingSwipe(messageId)) {
189- if ($(`#chat .mes[mesid="${messageId}"] .mes_text`).text() == '...') {
190208 return;
191209 }
192210
@@ -194,7 +212,36 @@ async function translateIncomingMessage(messageId) {
194212 const translation = await translate(textToTranslate, extension_settings.translate.target_language);
195213 message.extra.display_text = translation;
196214
197215 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;
198245}
199246
200247async function translateProviderOneRing(text, lang) {
@@ -535,6 +582,7 @@ async function onTranslateChatClick() {
535582 toastr.info(`${chat.length} message(s) queued for translation.`, 'Please wait...');
536583
537584 for (let i = 0; i < chat.length; i++) {
585+ await translateIncomingMessageReasoning(i);
538586 await translateIncomingMessage(i);
539587 }
540588
@@ -561,6 +609,7 @@ async function onTranslationsClearClick() {
561609 for (const mes of chat) {
562610 if (mes.extra) {
563611 delete mes.extra.display_text;
612+ delete mes.extra.reasoning_display_text;
564613 }
565614 }
566615
@@ -573,12 +622,47 @@ async function translateMessageEdit(messageId) {
573622 const chat = context.chat;
574623 const message = chat[messageId];
575624
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;
578633 }
579634
580- if ((message.is_user && shouldTranslate(outgoingTypes)) || (!message.is_user && shouldTranslate(incomingTypes))) {
635+ if (anyChange) {
581636 await translateIncomingMessagecontext.saveChat(messageId);
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();
582666 }
583667}
584668
@@ -588,22 +672,36 @@ async function onMessageTranslateClick() {
588672 const message = context.chat[messageId];
589673
590674 // If the message is already translated, revert it back to the original text
675+ let alreadyTranslated = false;
591676 if (message?.extra?.display_text) {
592677 delete message.extra.display_text;
593678 updateMessageBlock(Number(messageId), message);
679+ alreadyTranslated = true;
594680 }
681+ if (message?.extra?.reasoning_display_text) {
682+ delete message.extra.reasoning_display_text;
683+ updateReasoningUI(Number(messageId));
684+ alreadyTranslated = true;
685+ }
686+
595687 // If the message is not translated, translate it
596- else {
688+ if (!alreadyTranslated) {
689+ await translateIncomingMessageReasoning(messageId);
597690 await translateIncomingMessage(messageId);
598691 }
599692
600693 await context.saveChat();
601694}
602695
603696const handleIncomingMessage = createEventHandler(translateIncomingMessage,async (messageId) => shouldTranslate(incomingTypes));{
697+ await translateIncomingMessageReasoning(messageId);
698+ await translateIncomingMessage(messageId);
699+}, () => shouldTranslate(incomingTypes));
604700const handleOutgoingMessage = createEventHandler(translateOutgoingMessage, () => shouldTranslate(outgoingTypes));
605701const handleImpersonateReady = createEventHandler(translateImpersonate, () => shouldTranslate(incomingTypes));
606702const handleMessageEdit = createEventHandler(translateMessageEdit, () => true);
703+const handleMessageReasoningEdit = createEventHandler(translateMessageReasoningEdit, () => true);
704+const handleMessageReasoningDelete = createEventHandler(removeReasoningDisplayText, () => true);
607705
608706globalThis.translate = translate;
609707
@@ -717,6 +815,8 @@ jQuery(async () => {
717815 eventSource.on(event_types.MESSAGE_SWIPED, handleIncomingMessage);
718816 eventSource.on(event_types.IMPERSONATE_READY, handleImpersonateReady);
719817 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);
720820
721821 document.body.classList.add('translate');
722822
public/scripts/reasoning.js+13 -3
@@ -167,6 +167,8 @@ export class ReasoningHandler {
167167 this.type = null;
168168 /** @type {string} The reasoning output */
169169 this.reasoning = '';
170+ /** @type {string?} The reasoning output display in case of translate or other */
171+ this.reasoningDisplayText = null;
170172 /** @type {Date} When the reasoning started */
171173 this.startTime = null;
172174 /** @type {Date} When the reasoning ended */
@@ -234,6 +236,7 @@ export class ReasoningHandler {
234236
235237 this.type = extra?.reasoning_type;
236238 this.reasoning = extra?.reasoning ?? '';
239+ this.reasoningDisplayText = extra?.reasoning_display_text ?? null;
237240
238241 if (this.state !== ReasoningState.None) {
239242 this.initialTime = new Date(chat[messageId].gen_started);
@@ -249,6 +252,7 @@ export class ReasoningHandler {
249252 this.state = this.#isHiddenReasoningModel ? ReasoningState.Thinking : ReasoningState.None;
250253 this.type = null;
251254 this.reasoning = '';
255+ this.reasoningDisplayText = null;
252256 this.initialTime = new Date();
253257 this.startTime = null;
254258 this.endTime = null;
@@ -434,7 +438,7 @@ export class ReasoningHandler {
434438 setDatasetProperty(this.messageReasoningDetailsDom, 'type', this.type);
435439
436440 // Update the reasoning message
437441 const reasoning = trimSpaces(this.reasoningDisplayText ?? this.reasoning);
438442 const displayReasoning = messageFormatting(reasoning, '', false, false, messageId, {}, true);
439443 this.messageReasoningContentDom.innerHTML = displayReasoning;
440444
@@ -888,12 +892,17 @@ function setReasoningEventHandlers() {
888892 }
889893
890894 const textarea = messageBlock.find('.reasoning_edit_textarea');
891895 updateReasoningFromValue(message,const newReasoning = String(textarea.val()));
896+ textarea.remove();
897+ if (newReasoning === message.extra.reasoning) {
898+ return;
899+ }
900+ updateReasoningFromValue(message, newReasoning);
892901 await saveChatConditional();
893902 updateMessageBlock(messageId, message);
894- textarea.remove();
895903
896904 messageBlock.find('.mes_edit_done:visible').trigger('click');
905+ await eventSource.emit(event_types.MESSAGE_REASONING_EDITED, messageId);
897906 });
898907
899908 $(document).on('click', '.mes_reasoning_edit_cancel', function (e) {
@@ -955,6 +964,7 @@ function setReasoningEventHandlers() {
955964 updateMessageBlock(messageId, message);
956965 const textarea = messageBlock.find('.reasoning_edit_textarea');
957966 textarea.remove();
967+ await eventSource.emit(event_types.MESSAGE_REASONING_DELETED, messageId);
958968 });
959969
960970 $(document).on('pointerup', '.mes_reasoning_copy', async function () {
public/scripts/st-context.js+2 -0
@@ -79,6 +79,7 @@ import { timestampToMoment, uuidv4 } from './utils.js';
7979import { getGlobalVariable, getLocalVariable, setGlobalVariable, setLocalVariable } from './variables.js';
8080import { convertCharacterBook, loadWorldInfo, saveWorldInfo, updateWorldInfoList } from './world-info.js';
8181import { ChatCompletionService, TextCompletionService } from './custom-request.js';
82+import { updateReasoningUI } from './reasoning.js';
8283
8384export function getContext() {
8485 return {
@@ -211,6 +212,7 @@ export function getContext() {
211212 clearChat,
212213 ChatCompletionService,
213214 TextCompletionService,
215+ updateReasoningUI,
214216 unshallowCharacter,
215217 unshallowGroupMembers,
216218 };