Merge pull request #3382 from SillyTavern/staging Staging into reasoning-regex
Signed| @@ -6093,6 +6093,19 @@ export async function saveReply(type, getMessage, fromStreaming, title, swipes, | ||
| 6093 | 6093 | return { type, getMessage }; |
| 6094 | 6094 | } |
| 6095 | 6095 | |
| 6096 | +export function syncCurrentSwipeInfoExtras() { | |
| 6097 | + if (!chat.length) { | |
| 6098 | + return; | |
| 6099 | + } | |
| 6100 | + const currentMessage = chat[chat.length - 1]; | |
| 6101 | + if (currentMessage && Array.isArray(currentMessage.swipe_info) && typeof currentMessage.swipe_id === 'number') { | |
| 6102 | + const swipeInfo = currentMessage.swipe_info[currentMessage.swipe_id]; | |
| 6103 | + if (swipeInfo && typeof swipeInfo === 'object') { | |
| 6104 | + swipeInfo.extra = structuredClone(currentMessage.extra); | |
| 6105 | + } | |
| 6106 | + } | |
| 6107 | +} | |
| 6108 | + | |
| 6096 | 6109 | function saveImageToMessage(img, mes) { |
| 6097 | 6110 | if (mes && img.image) { |
| 6098 | 6111 | if (!mes.extra || typeof mes.extra !== 'object') { |
| @@ -8524,6 +8537,9 @@ function swipe_left() { // when we swipe left..but no generation. | ||
| 8524 | 8537 | streamingProcessor.onStopStreaming(); |
| 8525 | 8538 | } |
| 8526 | 8539 | |
| 8540 | + // Make sure ad-hoc changes to extras are saved before swiping away | |
| 8541 | + syncCurrentSwipeInfoExtras(); | |
| 8542 | + | |
| 8527 | 8543 | const swipe_duration = 120; |
| 8528 | 8544 | const swipe_range = '700px'; |
| 8529 | 8545 | chat[chat.length - 1]['swipe_id']--; |
| @@ -8659,6 +8675,9 @@ const swipe_right = () => { | ||
| 8659 | 8675 | return unblockGeneration(); |
| 8660 | 8676 | } |
| 8661 | 8677 | |
| 8678 | + // Make sure ad-hoc changes to extras are saved before swiping away | |
| 8679 | + syncCurrentSwipeInfoExtras(); | |
| 8680 | + | |
| 8662 | 8681 | const swipe_duration = 200; |
| 8663 | 8682 | const swipe_range = 700; |
| 8664 | 8683 | //console.log(swipe_range); |
| @@ -566,7 +566,7 @@ export function initAuthorsNote() { | ||
| 566 | 566 | namedArgumentList: [], |
| 567 | 567 | unnamedArgumentList: [ |
| 568 | 568 | new SlashCommandArgument( |
| 569 | 569 | 'positionrole', [ARGUMENT_TYPE.STRING], false, false, null, ['system', 'user', 'assistant'], |
| 570 | 570 | ), |
| 571 | 571 | ], |
| 572 | 572 | helpString: ` |
| @@ -30,6 +30,7 @@ const CC_COMMANDS = [ | ||
| 30 | 30 | 'api-url', |
| 31 | 31 | 'model', |
| 32 | 32 | 'proxy', |
| 33 | + 'stop-strings', | |
| 33 | 34 | ]; |
| 34 | 35 | |
| 35 | 36 | const TC_COMMANDS = [ |
| @@ -43,6 +44,7 @@ const TC_COMMANDS = [ | ||
| 43 | 44 | 'context', |
| 44 | 45 | 'instruct-state', |
| 45 | 46 | 'tokenizer', |
| 47 | + 'stop-strings', | |
| 46 | 48 | ]; |
| 47 | 49 | |
| 48 | 50 | const FANCY_NAMES = { |
| @@ -57,6 +59,7 @@ const FANCY_NAMES = { | ||
| 57 | 59 | 'instruct': 'Instruct Template', |
| 58 | 60 | 'context': 'Context Template', |
| 59 | 61 | 'tokenizer': 'Tokenizer', |
| 62 | + 'stop-strings': 'Custom Stopping Strings', | |
| 60 | 63 | }; |
| 61 | 64 | |
| 62 | 65 | /** |
| @@ -138,6 +141,7 @@ const profilesProvider = () => [ | ||
| 138 | 141 | * @property {string} [context] Context Template |
| 139 | 142 | * @property {string} [instruct-state] Instruct Mode |
| 140 | 143 | * @property {string} [tokenizer] Tokenizer |
| 144 | + * @property {string} [stop-strings] Custom Stopping Strings | |
| 141 | 145 | * @property {string[]} [exclude] Commands to exclude |
| 142 | 146 | */ |
| 143 | 147 | |
| @@ -30,6 +30,7 @@ import { GoogleTranslateTtsProvider } from './google-translate.js'; | ||
| 30 | 30 | export { talkingAnimation }; |
| 31 | 31 | |
| 32 | 32 | const UPDATE_INTERVAL = 1000; |
| 33 | +const wrapper = new ModuleWorkerWrapper(moduleWorker); | |
| 33 | 34 | |
| 34 | 35 | let voiceMapEntries = []; |
| 35 | 36 | let voiceMap = {}; // {charName:voiceid, charName2:voiceid2} |
| @@ -120,7 +121,7 @@ async function onNarrateOneMessage() { | ||
| 120 | 121 | } |
| 121 | 122 | |
| 122 | 123 | resetTtsPlayback(); |
| 123 | 124 | ttsJobQueue.pushprocessAndQueueTtsMessage(message); |
| 124 | 125 | moduleWorker(); |
| 125 | 126 | } |
| 126 | 127 | |
| @@ -147,7 +148,7 @@ async function onNarrateText(args, text) { | ||
| 147 | 148 | } |
| 148 | 149 | |
| 149 | 150 | resetTtsPlayback(); |
| 150 | 151 | ttsJobQueue.pushprocessAndQueueTtsMessage({ mes: text, name: name }); |
| 151 | 152 | await moduleWorker(); |
| 152 | 153 | |
| 153 | 154 | // Return back to the chat voices |
| @@ -220,6 +221,36 @@ function isTtsProcessing() { | ||
| 220 | 221 | return processing; |
| 221 | 222 | } |
| 222 | 223 | |
| 224 | +/** | |
| 225 | + * Splits a message into lines and adds each non-empty line to the TTS job queue. | |
| 226 | + * @param {Object} message - The message object to be processed. | |
| 227 | + * @param {string} message.mes - The text of the message to be split into lines. | |
| 228 | + * @param {string} message.name - The name associated with the message. | |
| 229 | + * @returns {void} | |
| 230 | + */ | |
| 231 | +function processAndQueueTtsMessage(message) { | |
| 232 | + if (!extension_settings.tts.narrate_by_paragraphs) { | |
| 233 | + ttsJobQueue.push(message); | |
| 234 | + return; | |
| 235 | + } | |
| 236 | + | |
| 237 | + const lines = message.mes.split('\n'); | |
| 238 | + | |
| 239 | + for (let i = 0; i < lines.length; i++) { | |
| 240 | + const line = lines[i]; | |
| 241 | + | |
| 242 | + if (line.length === 0) { | |
| 243 | + continue; | |
| 244 | + } | |
| 245 | + | |
| 246 | + ttsJobQueue.push( | |
| 247 | + Object.assign({}, message, { | |
| 248 | + mes: line, | |
| 249 | + }), | |
| 250 | + ); | |
| 251 | + } | |
| 252 | +} | |
| 253 | + | |
| 223 | 254 | function debugTtsPlayback() { |
| 224 | 255 | console.log(JSON.stringify( |
| 225 | 256 | { |
| @@ -350,7 +381,7 @@ function onAudioControlClicked() { | ||
| 350 | 381 | talkingAnimation(false); |
| 351 | 382 | } else { |
| 352 | 383 | // Default play behavior if not processing or playing is to play the last message. |
| 353 | 384 | ttsJobQueue.pushprocessAndQueueTtsMessage(context.chat[context.chat.length - 1]); |
| 354 | 385 | } |
| 355 | 386 | updateUiAudioPlayState(); |
| 356 | 387 | } |
| @@ -376,6 +407,7 @@ function completeCurrentAudioJob() { | ||
| 376 | 407 | currentAudioJob = null; |
| 377 | 408 | talkingAnimation(false); //stop lip animation |
| 378 | 409 | // updateUiPlayState(); |
| 410 | + wrapper.update(); | |
| 379 | 411 | } |
| 380 | 412 | |
| 381 | 413 | /** |
| @@ -466,7 +498,7 @@ async function processTtsQueue() { | ||
| 466 | 498 | } |
| 467 | 499 | |
| 468 | 500 | if (extension_settings.tts.skip_tags) { |
| 469 | 501 | text = text.replace(/<.*?>.[\s\S]*?<\/.*?>/g, '').trim(); |
| 470 | 502 | } |
| 471 | 503 | |
| 472 | 504 | if (!extension_settings.tts.pass_asterisks) { |
| @@ -569,6 +601,7 @@ function loadSettings() { | ||
| 569 | 601 | $('#tts_narrate_quoted').prop('checked', extension_settings.tts.narrate_quoted_only); |
| 570 | 602 | $('#tts_auto_generation').prop('checked', extension_settings.tts.auto_generation); |
| 571 | 603 | $('#tts_periodic_auto_generation').prop('checked', extension_settings.tts.periodic_auto_generation); |
| 604 | + $('#tts_narrate_by_paragraphs').prop('checked', extension_settings.tts.narrate_by_paragraphs); | |
| 572 | 605 | $('#tts_narrate_translated_only').prop('checked', extension_settings.tts.narrate_translated_only); |
| 573 | 606 | $('#tts_narrate_user').prop('checked', extension_settings.tts.narrate_user); |
| 574 | 607 | $('#tts_pass_asterisks').prop('checked', extension_settings.tts.pass_asterisks); |
| @@ -638,6 +671,11 @@ function onPeriodicAutoGenerationClick() { | ||
| 638 | 671 | saveSettingsDebounced(); |
| 639 | 672 | } |
| 640 | 673 | |
| 674 | +function onNarrateByParagraphsClick() { | |
| 675 | + extension_settings.tts.narrate_by_paragraphs = !!$('#tts_narrate_by_paragraphs').prop('checked'); | |
| 676 | + saveSettingsDebounced(); | |
| 677 | +} | |
| 678 | + | |
| 641 | 679 | |
| 642 | 680 | function onNarrateDialoguesClick() { |
| 643 | 681 | extension_settings.tts.narrate_dialogues_only = !!$('#tts_narrate_dialogues').prop('checked'); |
| @@ -816,7 +854,12 @@ async function onMessageEvent(messageId, lastCharIndex) { | ||
| 816 | 854 | lastChatId = context.chatId; |
| 817 | 855 | |
| 818 | 856 | console.debug(`Adding message from ${message.name} for TTS processing: "${message.mes}"`); |
| 857 | + | |
| 858 | + if (extension_settings.tts.periodic_auto_generation) { | |
| 819 | 859 | ttsJobQueue.push(message); |
| 860 | + } else { | |
| 861 | + processAndQueueTtsMessage(message); | |
| 862 | + } | |
| 820 | 863 | } |
| 821 | 864 | |
| 822 | 865 | async function onMessageDeleted() { |
| @@ -1156,6 +1199,7 @@ jQuery(async function () { | ||
| 1156 | 1199 | $('#tts_pass_asterisks').on('click', onPassAsterisksClick); |
| 1157 | 1200 | $('#tts_auto_generation').on('click', onAutoGenerationClick); |
| 1158 | 1201 | $('#tts_periodic_auto_generation').on('click', onPeriodicAutoGenerationClick); |
| 1202 | + $('#tts_narrate_by_paragraphs').on('click', onNarrateByParagraphsClick); | |
| 1159 | 1203 | $('#tts_narrate_user').on('click', onNarrateUserClick); |
| 1160 | 1204 | |
| 1161 | 1205 | $('#playback_rate').on('input', function () { |
| @@ -1177,7 +1221,6 @@ jQuery(async function () { | ||
| 1177 | 1221 | loadSettings(); // Depends on Extension Controls and loadTtsProvider |
| 1178 | 1222 | loadTtsProvider(extension_settings.tts.currentProvider); // No dependencies |
| 1179 | 1223 | addAudioControl(); // Depends on Extension Controls |
| 1180 | - const wrapper = new ModuleWorkerWrapper(moduleWorker); | |
| 1181 | 1224 | setInterval(wrapper.update.bind(wrapper), UPDATE_INTERVAL); // Init depends on all the things |
| 1182 | 1225 | eventSource.on(event_types.MESSAGE_SWIPED, resetTtsPlayback); |
| 1183 | 1226 | eventSource.on(event_types.CHAT_CHANGED, onChatChanged); |
| @@ -30,6 +30,10 @@ | ||
| 30 | 30 | <input type="checkbox" id="tts_periodic_auto_generation"> |
| 31 | 31 | <small data-i18n="Narrate by paragraphs (when streaming)">Narrate by paragraphs (when streaming)</small> |
| 32 | 32 | </label> |
| 33 | + <label class="checkbox_label" for="tts_narrate_by_paragraphs"> | |
| 34 | + <input type="checkbox" id="tts_narrate_by_paragraphs"> | |
| 35 | + <small data-i18n="Narrate by paragraphs (when not streaming)">Narrate by paragraphs (when not streaming)</small> | |
| 36 | + </label> | |
| 33 | 37 | <label class="checkbox_label" for="tts_narrate_quoted"> |
| 34 | 38 | <input type="checkbox" id="tts_narrate_quoted"> |
| 35 | 39 | <small data-i18n="Only narrate quotes">Only narrate "quotes"</small> |
| @@ -4072,4 +4072,45 @@ $(document).ready(() => { | ||
| 4072 | 4072 | ], |
| 4073 | 4073 | helpString: 'activates a movingUI preset by name', |
| 4074 | 4074 | })); |
| 4075 | + SlashCommandParser.addCommandObject(SlashCommand.fromProps({ | |
| 4076 | + name: 'stop-strings', | |
| 4077 | + aliases: ['stopping-strings', 'custom-stopping-strings', 'custom-stop-strings'], | |
| 4078 | + helpString: ` | |
| 4079 | + <div> | |
| 4080 | + Sets a list of custom stopping strings. Gets the list if no value is provided. | |
| 4081 | + </div> | |
| 4082 | + <div> | |
| 4083 | + <strong>Examples:</strong> | |
| 4084 | + </div> | |
| 4085 | + <ul> | |
| 4086 | + <li>Value must be a JSON-serialized array: <pre><code class="language-stscript">/stop-strings ["goodbye", "farewell"]</code></pre></li> | |
| 4087 | + <li>Pipe characters must be escaped with a backslash: <pre><code class="language-stscript">/stop-strings ["left\\|right"]</code></pre></li> | |
| 4088 | + </ul> | |
| 4089 | + `, | |
| 4090 | + returns: ARGUMENT_TYPE.LIST, | |
| 4091 | + unnamedArgumentList: [ | |
| 4092 | + SlashCommandArgument.fromProps({ | |
| 4093 | + description: 'list of strings', | |
| 4094 | + typeList: [ARGUMENT_TYPE.LIST], | |
| 4095 | + acceptsMultiple: false, | |
| 4096 | + isRequired: false, | |
| 4097 | + }), | |
| 4098 | + ], | |
| 4099 | + callback: (_, value) => { | |
| 4100 | + if (String(value ?? '').trim()) { | |
| 4101 | + const parsedValue = ((x) => { try { return JSON.parse(x.toString()); } catch { return null; } })(value); | |
| 4102 | + if (!parsedValue || !Array.isArray(parsedValue)) { | |
| 4103 | + throw new Error('Invalid list format. The value must be a JSON-serialized array of strings.'); | |
| 4104 | + } | |
| 4105 | + parsedValue.forEach((item, index) => { | |
| 4106 | + parsedValue[index] = String(item); | |
| 4107 | + }); | |
| 4108 | + power_user.custom_stopping_strings = JSON.stringify(parsedValue); | |
| 4109 | + $('#custom_stopping_strings').val(power_user.custom_stopping_strings); | |
| 4110 | + saveSettingsDebounced(); | |
| 4111 | + } | |
| 4112 | + | |
| 4113 | + return power_user.custom_stopping_strings; | |
| 4114 | + }, | |
| 4115 | + })); | |
| 4075 | 4116 | }); |
| @@ -147,7 +147,7 @@ function registerReasoningSlashCommands() { | ||
| 147 | 147 | }), |
| 148 | 148 | ], |
| 149 | 149 | callback: async (args, value) => { |
| 150 | 150 | const messageId = !isNaN(Number(args[0].at)) ? Number(args[0].at) : chat.length - 1; |
| 151 | 151 | const message = chat[messageId]; |
| 152 | 152 | if (!message?.extra) { |
| 153 | 153 | return ''; |
| @@ -42,6 +42,7 @@ import { | ||
| 42 | 42 | showMoreMessages, |
| 43 | 43 | stopGeneration, |
| 44 | 44 | substituteParams, |
| 45 | + syncCurrentSwipeInfoExtras, | |
| 45 | 46 | system_avatar, |
| 46 | 47 | system_message_types, |
| 47 | 48 | this_chid, |
| @@ -2814,8 +2815,11 @@ async function addSwipeCallback(args, value) { | ||
| 2814 | 2815 | const newSwipeId = lastMessage.swipes.length - 1; |
| 2815 | 2816 | |
| 2816 | 2817 | if (isTrueBoolean(args.switch)) { |
| 2818 | + // Make sure ad-hoc changes to extras are saved before swiping away | |
| 2819 | + syncCurrentSwipeInfoExtras(); | |
| 2817 | 2820 | lastMessage.swipe_id = newSwipeId; |
| 2818 | 2821 | lastMessage.mes = lastMessage.swipes[newSwipeId]; |
| 2822 | + lastMessage.extra = structuredClone(lastMessage.swipe_info?.[newSwipeId]?.extra ?? lastMessage.extra ?? {}); | |
| 2819 | 2823 | } |
| 2820 | 2824 | |
| 2821 | 2825 | await saveChatConditional(); |