Merge pull request #3382 from SillyTavern/staging Staging into reasoning-regex

9e8fd3f5a060c432692431a10a2610b983b4f24d

Cohee <18619528+Cohee1207@users.noreply.github.com>

Signed
8 files changed, +122 -7Showing whitespace changes
public/script.js+19 -0
@@ -6093,6 +6093,19 @@ export async function saveReply(type, getMessage, fromStreaming, title, swipes,
60936093 return { type, getMessage };
60946094}
60956095
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+
60966109function saveImageToMessage(img, mes) {
60976110 if (mes && img.image) {
60986111 if (!mes.extra || typeof mes.extra !== 'object') {
@@ -8524,6 +8537,9 @@ function swipe_left() { // when we swipe left..but no generation.
85248537 streamingProcessor.onStopStreaming();
85258538 }
85268539
8540+ // Make sure ad-hoc changes to extras are saved before swiping away
8541+ syncCurrentSwipeInfoExtras();
8542+
85278543 const swipe_duration = 120;
85288544 const swipe_range = '700px';
85298545 chat[chat.length - 1]['swipe_id']--;
@@ -8659,6 +8675,9 @@ const swipe_right = () => {
86598675 return unblockGeneration();
86608676 }
86618677
8678+ // Make sure ad-hoc changes to extras are saved before swiping away
8679+ syncCurrentSwipeInfoExtras();
8680+
86628681 const swipe_duration = 200;
86638682 const swipe_range = 700;
86648683 //console.log(swipe_range);
public/scripts/authors-note.js+1 -1
@@ -566,7 +566,7 @@ export function initAuthorsNote() {
566566 namedArgumentList: [],
567567 unnamedArgumentList: [
568568 new SlashCommandArgument(
569569 'positionrole', [ARGUMENT_TYPE.STRING], false, false, null, ['system', 'user', 'assistant'],
570570 ),
571571 ],
572572 helpString: `
public/scripts/extensions/connection-manager/index.js+4 -0
@@ -30,6 +30,7 @@ const CC_COMMANDS = [
3030 'api-url',
3131 'model',
3232 'proxy',
33+ 'stop-strings',
3334];
3435
3536const TC_COMMANDS = [
@@ -43,6 +44,7 @@ const TC_COMMANDS = [
4344 'context',
4445 'instruct-state',
4546 'tokenizer',
47+ 'stop-strings',
4648];
4749
4850const FANCY_NAMES = {
@@ -57,6 +59,7 @@ const FANCY_NAMES = {
5759 'instruct': 'Instruct Template',
5860 'context': 'Context Template',
5961 'tokenizer': 'Tokenizer',
62+ 'stop-strings': 'Custom Stopping Strings',
6063};
6164
6265/**
@@ -138,6 +141,7 @@ const profilesProvider = () => [
138141 * @property {string} [context] Context Template
139142 * @property {string} [instruct-state] Instruct Mode
140143 * @property {string} [tokenizer] Tokenizer
144+ * @property {string} [stop-strings] Custom Stopping Strings
141145 * @property {string[]} [exclude] Commands to exclude
142146 */
143147
public/scripts/extensions/tts/index.js+48 -5
@@ -30,6 +30,7 @@ import { GoogleTranslateTtsProvider } from './google-translate.js';
3030export { talkingAnimation };
3131
3232const UPDATE_INTERVAL = 1000;
33+const wrapper = new ModuleWorkerWrapper(moduleWorker);
3334
3435let voiceMapEntries = [];
3536let voiceMap = {}; // {charName:voiceid, charName2:voiceid2}
@@ -120,7 +121,7 @@ async function onNarrateOneMessage() {
120121 }
121122
122123 resetTtsPlayback();
123124 ttsJobQueue.pushprocessAndQueueTtsMessage(message);
124125 moduleWorker();
125126}
126127
@@ -147,7 +148,7 @@ async function onNarrateText(args, text) {
147148 }
148149
149150 resetTtsPlayback();
150151 ttsJobQueue.pushprocessAndQueueTtsMessage({ mes: text, name: name });
151152 await moduleWorker();
152153
153154 // Return back to the chat voices
@@ -220,6 +221,36 @@ function isTtsProcessing() {
220221 return processing;
221222}
222223
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+
223254function debugTtsPlayback() {
224255 console.log(JSON.stringify(
225256 {
@@ -350,7 +381,7 @@ function onAudioControlClicked() {
350381 talkingAnimation(false);
351382 } else {
352383 // Default play behavior if not processing or playing is to play the last message.
353384 ttsJobQueue.pushprocessAndQueueTtsMessage(context.chat[context.chat.length - 1]);
354385 }
355386 updateUiAudioPlayState();
356387}
@@ -376,6 +407,7 @@ function completeCurrentAudioJob() {
376407 currentAudioJob = null;
377408 talkingAnimation(false); //stop lip animation
378409 // updateUiPlayState();
410+ wrapper.update();
379411}
380412
381413/**
@@ -466,7 +498,7 @@ async function processTtsQueue() {
466498 }
467499
468500 if (extension_settings.tts.skip_tags) {
469501 text = text.replace(/<.*?>.[\s\S]*?<\/.*?>/g, '').trim();
470502 }
471503
472504 if (!extension_settings.tts.pass_asterisks) {
@@ -569,6 +601,7 @@ function loadSettings() {
569601 $('#tts_narrate_quoted').prop('checked', extension_settings.tts.narrate_quoted_only);
570602 $('#tts_auto_generation').prop('checked', extension_settings.tts.auto_generation);
571603 $('#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);
572605 $('#tts_narrate_translated_only').prop('checked', extension_settings.tts.narrate_translated_only);
573606 $('#tts_narrate_user').prop('checked', extension_settings.tts.narrate_user);
574607 $('#tts_pass_asterisks').prop('checked', extension_settings.tts.pass_asterisks);
@@ -638,6 +671,11 @@ function onPeriodicAutoGenerationClick() {
638671 saveSettingsDebounced();
639672}
640673
674+function onNarrateByParagraphsClick() {
675+ extension_settings.tts.narrate_by_paragraphs = !!$('#tts_narrate_by_paragraphs').prop('checked');
676+ saveSettingsDebounced();
677+}
678+
641679
642680function onNarrateDialoguesClick() {
643681 extension_settings.tts.narrate_dialogues_only = !!$('#tts_narrate_dialogues').prop('checked');
@@ -816,7 +854,12 @@ async function onMessageEvent(messageId, lastCharIndex) {
816854 lastChatId = context.chatId;
817855
818856 console.debug(`Adding message from ${message.name} for TTS processing: "${message.mes}"`);
857+
858+ if (extension_settings.tts.periodic_auto_generation) {
819859 ttsJobQueue.push(message);
860+ } else {
861+ processAndQueueTtsMessage(message);
862+ }
820863}
821864
822865async function onMessageDeleted() {
@@ -1156,6 +1199,7 @@ jQuery(async function () {
11561199 $('#tts_pass_asterisks').on('click', onPassAsterisksClick);
11571200 $('#tts_auto_generation').on('click', onAutoGenerationClick);
11581201 $('#tts_periodic_auto_generation').on('click', onPeriodicAutoGenerationClick);
1202+ $('#tts_narrate_by_paragraphs').on('click', onNarrateByParagraphsClick);
11591203 $('#tts_narrate_user').on('click', onNarrateUserClick);
11601204
11611205 $('#playback_rate').on('input', function () {
@@ -1177,7 +1221,6 @@ jQuery(async function () {
11771221 loadSettings(); // Depends on Extension Controls and loadTtsProvider
11781222 loadTtsProvider(extension_settings.tts.currentProvider); // No dependencies
11791223 addAudioControl(); // Depends on Extension Controls
1180- const wrapper = new ModuleWorkerWrapper(moduleWorker);
11811224 setInterval(wrapper.update.bind(wrapper), UPDATE_INTERVAL); // Init depends on all the things
11821225 eventSource.on(event_types.MESSAGE_SWIPED, resetTtsPlayback);
11831226 eventSource.on(event_types.CHAT_CHANGED, onChatChanged);
public/scripts/extensions/tts/settings.html+4 -0
@@ -30,6 +30,10 @@
3030 <input type="checkbox" id="tts_periodic_auto_generation">
3131 <small data-i18n="Narrate by paragraphs (when streaming)">Narrate by paragraphs (when streaming)</small>
3232 </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>
3337 <label class="checkbox_label" for="tts_narrate_quoted">
3438 <input type="checkbox" id="tts_narrate_quoted">
3539 <small data-i18n="Only narrate quotes">Only narrate "quotes"</small>
public/scripts/power-user.js+41 -0
@@ -4072,4 +4072,45 @@ $(document).ready(() => {
40724072 ],
40734073 helpString: 'activates a movingUI preset by name',
40744074 }));
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+ }));
40754116});
public/scripts/reasoning.js+1 -1
@@ -147,7 +147,7 @@ function registerReasoningSlashCommands() {
147147 }),
148148 ],
149149 callback: async (args, value) => {
150150 const messageId = !isNaN(Number(args[0].at)) ? Number(args[0].at) : chat.length - 1;
151151 const message = chat[messageId];
152152 if (!message?.extra) {
153153 return '';
public/scripts/slash-commands.js+4 -0
@@ -42,6 +42,7 @@ import {
4242 showMoreMessages,
4343 stopGeneration,
4444 substituteParams,
45+ syncCurrentSwipeInfoExtras,
4546 system_avatar,
4647 system_message_types,
4748 this_chid,
@@ -2814,8 +2815,11 @@ async function addSwipeCallback(args, value) {
28142815 const newSwipeId = lastMessage.swipes.length - 1;
28152816
28162817 if (isTrueBoolean(args.switch)) {
2818+ // Make sure ad-hoc changes to extras are saved before swiping away
2819+ syncCurrentSwipeInfoExtras();
28172820 lastMessage.swipe_id = newSwipeId;
28182821 lastMessage.mes = lastMessage.swipes[newSwipeId];
2822+ lastMessage.extra = structuredClone(lastMessage.swipe_info?.[newSwipeId]?.extra ?? lastMessage.extra ?? {});
28192823 }
28202824
28212825 await saveChatConditional();