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 -13Ignore whitespace
public/script.js+2 -0
@@ -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',
public/scripts/extensions/translate/index.js+110 -10
@@ -11,6 +11,7 @@ import {
11} from '../../../script.js';11} from '../../../script.js';
12import { extension_settings, getContext, renderExtensionTemplateAsync } from '../../extensions.js';12import { extension_settings, getContext, renderExtensionTemplateAsync } from '../../extensions.js';
13import { POPUP_RESULT, POPUP_TYPE, callGenericPopup } from '../../popup.js';13import { POPUP_RESULT, POPUP_TYPE, callGenericPopup } from '../../popup.js';
14import { updateReasoningUI } from '../../reasoning.js';
14import { findSecret, secret_state, writeSecret } from '../../secrets.js';15import { findSecret, secret_state, writeSecret } from '../../secrets.js';
15import { SlashCommand } from '../../slash-commands/SlashCommand.js';16import { SlashCommand } from '../../slash-commands/SlashCommand.js';
16import { ARGUMENT_TYPE, SlashCommandArgument, SlashCommandNamedArgument } from '../../slash-commands/SlashCommandArgument.js';17import { ARGUMENT_TYPE, SlashCommandArgument, SlashCommandNamedArgument } from '../../slash-commands/SlashCommandArgument.js';
@@ -172,21 +173,38 @@ function loadSettings() {
172 showKeysButton();173 showKeysButton();
173}174}
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 */
181function isGeneratingSwipe(messageId) {
182 return $(`#chat .mes[mesid="${messageId}"] .mes_text`).text() === '...';
183}
184
175async function translateImpersonate(text) {185async 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}
179189
190/**
191 * Translates the contents of an incoming message.
192 * @param {string | number} messageId Message ID
193 * @returns {Promise<void>}
194 */
180async function translateIncomingMessage(messageId) {195async function translateIncomingMessage(messageId) {
181 const context = getContext();196 const context = getContext();
182 const message = context.chat[messageId];197 const message = context.chat[messageId];
183198
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 }
187206
188 // New swipe is being generated. Don't translate that207 if (isGeneratingSwipe(messageId)) {
189 if ($(`#chat .mes[mesid="${messageId}"] .mes_text`).text() == '...') {
190 return;208 return;
191 }209 }
192210
@@ -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;
196214
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 */
223async 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}
199246
200async function translateProviderOneRing(text, lang) {247async 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...');
536583
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 }
540588
@@ -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 }
566615
@@ -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];
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;
578 }633 }
579634
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
640async 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
659async 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}
584668
@@ -588,22 +672,36 @@ async function onMessageTranslateClick() {
588 const message = context.chat[messageId];672 const message = context.chat[messageId];
589673
590 // If the message is already translated, revert it back to the original text674 // 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;
680 }
681 if (message?.extra?.reasoning_display_text) {
682 delete message.extra.reasoning_display_text;
683 updateReasoningUI(Number(messageId));
684 alreadyTranslated = true;
594 }685 }
686
595 // If the message is not translated, translate it687 // 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 }
599692
600 await context.saveChat();693 await context.saveChat();
601}694}
602695
603const handleIncomingMessage = createEventHandler(translateIncomingMessage, () => shouldTranslate(incomingTypes));696const handleIncomingMessage = createEventHandler(async (messageId) => {
697 await translateIncomingMessageReasoning(messageId);
698 await translateIncomingMessage(messageId);
699}, () => shouldTranslate(incomingTypes));
604const handleOutgoingMessage = createEventHandler(translateOutgoingMessage, () => shouldTranslate(outgoingTypes));700const handleOutgoingMessage = createEventHandler(translateOutgoingMessage, () => shouldTranslate(outgoingTypes));
605const handleImpersonateReady = createEventHandler(translateImpersonate, () => shouldTranslate(incomingTypes));701const handleImpersonateReady = createEventHandler(translateImpersonate, () => shouldTranslate(incomingTypes));
606const handleMessageEdit = createEventHandler(translateMessageEdit, () => true);702const handleMessageEdit = createEventHandler(translateMessageEdit, () => true);
703const handleMessageReasoningEdit = createEventHandler(translateMessageReasoningEdit, () => true);
704const handleMessageReasoningDelete = createEventHandler(removeReasoningDisplayText, () => true);
607705
608globalThis.translate = translate;706globalThis.translate = translate;
609707
@@ -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);
720820
721 document.body.classList.add('translate');821 document.body.classList.add('translate');
722822
public/scripts/reasoning.js+13 -3
@@ -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 {
234236
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;
237240
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);
435439
436 // Update the reasoning message440 // 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;
440444
@@ -888,12 +892,17 @@ function setReasoningEventHandlers() {
888 }892 }
889893
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();
895903
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 });
898907
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 });
959969
960 $(document).on('pointerup', '.mes_reasoning_copy', async function () {970 $(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';
79import { getGlobalVariable, getLocalVariable, setGlobalVariable, setLocalVariable } from './variables.js';79import { getGlobalVariable, getLocalVariable, setGlobalVariable, setLocalVariable } from './variables.js';
80import { convertCharacterBook, loadWorldInfo, saveWorldInfo, updateWorldInfoList } from './world-info.js';80import { convertCharacterBook, loadWorldInfo, saveWorldInfo, updateWorldInfoList } from './world-info.js';
81import { ChatCompletionService, TextCompletionService } from './custom-request.js';81import { ChatCompletionService, TextCompletionService } from './custom-request.js';
82import { updateReasoningUI } from './reasoning.js';
8283
83export function getContext() {84export 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 };