|
|
|
@@ -1,4 +1,4 @@
|
|
|
|
|
import { callPopup, cancelTtsPlay, eventSource, event_types, name2, saveSettingsDebounced, substituteParams } from '../../../script.js';
|
|
|
|
|
import { callPopup, cancelTtsPlay, eventSource, event_types, isStreamingEnabled, name2, saveSettingsDebounced, substituteParams } from '../../../script.js';
|
|
|
|
|
import { ModuleWorkerWrapper, doExtrasFetch, extension_settings, getApiUrl, getContext, modules } from '../../extensions.js';
|
|
|
|
|
import { delay, escapeRegex, getBase64Async, getStringHash, onlyUnique } from '../../utils.js';
|
|
|
|
|
import { EdgeTtsProvider } from './edge.js';
|
|
|
|
@@ -18,6 +18,7 @@ import { AzureTtsProvider } from './azure.js';
|
|
|
|
|
import { SlashCommandParser } from '../../slash-commands/SlashCommandParser.js';
|
|
|
|
|
import { SlashCommand } from '../../slash-commands/SlashCommand.js';
|
|
|
|
|
import { ARGUMENT_TYPE, SlashCommandArgument, SlashCommandNamedArgument } from '../../slash-commands/SlashCommandArgument.js';
|
|
|
|
|
import { debounce_timeout } from '../../constants.js';
|
|
|
|
|
export { talkingAnimation };
|
|
|
|
|
|
|
|
|
|
const UPDATE_INTERVAL = 1000;
|
|
|
|
@@ -28,6 +29,8 @@ let talkingHeadState = false;
|
|
|
|
|
let lastChatId = null;
|
|
|
|
|
let lastMessage = null;
|
|
|
|
|
let lastMessageHash = null;
|
|
|
|
|
let periodicMessageGenerationTimer = null;
|
|
|
|
|
let lastPositionOfParagraphEnd = -1;
|
|
|
|
|
|
|
|
|
|
const DEFAULT_VOICE_MARKER = '[Default Voice]';
|
|
|
|
|
const DISABLED_VOICE_MARKER = 'disabled';
|
|
|
|
@@ -109,7 +112,7 @@ async function onNarrateOneMessage() {
|
|
|
|
|
|
|
|
|
|
async function onNarrateText(args, text) {
|
|
|
|
|
if (!text) {
|
|
|
|
|
return;
|
|
|
|
|
return '';
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
audioElement.src = '/sounds/silence.mp3';
|
|
|
|
@@ -135,6 +138,7 @@ async function onNarrateText(args, text) {
|
|
|
|
|
|
|
|
|
|
// Return back to the chat voices
|
|
|
|
|
await initVoiceMap(false);
|
|
|
|
|
return '';
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
async function moduleWorker() {
|
|
|
|
@@ -531,6 +535,7 @@ function loadSettings() {
|
|
|
|
|
$('#tts_narrate_dialogues').prop('checked', extension_settings.tts.narrate_dialogues_only);
|
|
|
|
|
$('#tts_narrate_quoted').prop('checked', extension_settings.tts.narrate_quoted_only);
|
|
|
|
|
$('#tts_auto_generation').prop('checked', extension_settings.tts.auto_generation);
|
|
|
|
|
$('#tts_periodic_auto_generation').prop('checked', extension_settings.tts.periodic_auto_generation);
|
|
|
|
|
$('#tts_narrate_translated_only').prop('checked', extension_settings.tts.narrate_translated_only);
|
|
|
|
|
$('#tts_narrate_user').prop('checked', extension_settings.tts.narrate_user);
|
|
|
|
|
$('#tts_pass_asterisks').prop('checked', extension_settings.tts.pass_asterisks);
|
|
|
|
@@ -594,6 +599,12 @@ function onAutoGenerationClick() {
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
function onPeriodicAutoGenerationClick() {
|
|
|
|
|
extension_settings.tts.periodic_auto_generation = !!$('#tts_periodic_auto_generation').prop('checked');
|
|
|
|
|
saveSettingsDebounced();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
function onNarrateDialoguesClick() {
|
|
|
|
|
extension_settings.tts.narrate_dialogues_only = !!$('#tts_narrate_dialogues').prop('checked');
|
|
|
|
|
saveSettingsDebounced();
|
|
|
|
@@ -679,13 +690,14 @@ export function saveTtsProviderSettings() {
|
|
|
|
|
//###################//
|
|
|
|
|
|
|
|
|
|
async function onChatChanged() {
|
|
|
|
|
await resetTtsPlayback();
|
|
|
|
|
await onGenerationEnded();
|
|
|
|
|
resetTtsPlayback();
|
|
|
|
|
const voiceMapInit = initVoiceMap();
|
|
|
|
|
await Promise.race([voiceMapInit, delay(1000)]);
|
|
|
|
|
await Promise.race([voiceMapInit, delay(debounce_timeout.relaxed)]);
|
|
|
|
|
lastMessage = null;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
async function onMessageEvent(messageId) {
|
|
|
|
|
async function onMessageEvent(messageId, lastCharIndex) {
|
|
|
|
|
// If TTS is disabled, do nothing
|
|
|
|
|
if (!extension_settings.tts.enabled) {
|
|
|
|
|
return;
|
|
|
|
@@ -723,12 +735,17 @@ async function onMessageEvent(messageId) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// if we only want to process part of the message
|
|
|
|
|
if (lastCharIndex) {
|
|
|
|
|
message.mes = message.mes.substring(0, lastCharIndex);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const isLastMessageInCurrent = () =>
|
|
|
|
|
lastMessage &&
|
|
|
|
|
typeof lastMessage === 'object' &&
|
|
|
|
|
message.swipe_id === lastMessage.swipe_id &&
|
|
|
|
|
message.name === lastMessage.name &&
|
|
|
|
|
message.is_user === lastMessage.is_user &&
|
|
|
|
|
message.name === lastMessage.name &&
|
|
|
|
|
message.is_user === lastMessage.is_user &&
|
|
|
|
|
message.mes.indexOf(lastMessage.mes) !== -1;
|
|
|
|
|
|
|
|
|
|
// if last message within current message, message got extended. only send diff to TTS.
|
|
|
|
@@ -781,6 +798,83 @@ async function onMessageDeleted() {
|
|
|
|
|
resetTtsPlayback();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
async function onGenerationStarted(generationType, _args, isDryRun) {
|
|
|
|
|
// If dry running or quiet mode, do nothing
|
|
|
|
|
if (isDryRun || ['quiet', 'impersonate'].includes(generationType)) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// If TTS is disabled, do nothing
|
|
|
|
|
if (!extension_settings.tts.enabled) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Auto generation is disabled
|
|
|
|
|
if (!extension_settings.tts.auto_generation) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Periodic auto generation is disabled
|
|
|
|
|
if (!extension_settings.tts.periodic_auto_generation) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// If the reply is not being streamed
|
|
|
|
|
if (!isStreamingEnabled()) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// start the timer
|
|
|
|
|
if (!periodicMessageGenerationTimer) {
|
|
|
|
|
periodicMessageGenerationTimer = setInterval(onPeriodicMessageGenerationTick, UPDATE_INTERVAL);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
async function onGenerationEnded() {
|
|
|
|
|
if (periodicMessageGenerationTimer) {
|
|
|
|
|
clearInterval(periodicMessageGenerationTimer);
|
|
|
|
|
periodicMessageGenerationTimer = null;
|
|
|
|
|
}
|
|
|
|
|
lastPositionOfParagraphEnd = -1;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
async function onPeriodicMessageGenerationTick() {
|
|
|
|
|
const context = getContext();
|
|
|
|
|
|
|
|
|
|
// no characters or group selected
|
|
|
|
|
if (!context.groupId && context.characterId === undefined) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const lastMessageId = context.chat.length - 1;
|
|
|
|
|
|
|
|
|
|
// the last message was from the user
|
|
|
|
|
if (context.chat[lastMessageId].is_user) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const lastMessage = structuredClone(context.chat[lastMessageId]);
|
|
|
|
|
const lastMessageText = lastMessage?.mes ?? '';
|
|
|
|
|
|
|
|
|
|
// look for double ending lines which should indicate the end of a paragraph
|
|
|
|
|
let newLastPositionOfParagraphEnd = lastMessageText
|
|
|
|
|
.indexOf('\n\n', lastPositionOfParagraphEnd + 1);
|
|
|
|
|
// if not found, look for a single ending line which should indicate the end of a paragraph
|
|
|
|
|
if (newLastPositionOfParagraphEnd === -1) {
|
|
|
|
|
newLastPositionOfParagraphEnd = lastMessageText
|
|
|
|
|
.indexOf('\n', lastPositionOfParagraphEnd + 1);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// send the message to the tts module if we found the new end of a paragraph
|
|
|
|
|
if (newLastPositionOfParagraphEnd > -1) {
|
|
|
|
|
onMessageEvent(lastMessageId, newLastPositionOfParagraphEnd);
|
|
|
|
|
|
|
|
|
|
if (periodicMessageGenerationTimer) {
|
|
|
|
|
lastPositionOfParagraphEnd = newLastPositionOfParagraphEnd;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Get characters in current chat
|
|
|
|
|
* @param {boolean} unrestricted - If true, will include all characters in voiceMapEntries, even if they are not in the current chat.
|
|
|
|
@@ -1010,6 +1104,10 @@ $(document).ready(function () {
|
|
|
|
|
<input type="checkbox" id="tts_auto_generation">
|
|
|
|
|
<small>Auto Generation</small>
|
|
|
|
|
</label>
|
|
|
|
|
<label class="checkbox_label" for="tts_periodic_auto_generation" title="Requires auto generation to be enabled.">
|
|
|
|
|
<input type="checkbox" id="tts_periodic_auto_generation">
|
|
|
|
|
<small>Narrate by paragraphs (when streaming)</small>
|
|
|
|
|
</label>
|
|
|
|
|
<label class="checkbox_label" for="tts_narrate_quoted">
|
|
|
|
|
<input type="checkbox" id="tts_narrate_quoted">
|
|
|
|
|
<small>Only narrate "quotes"</small>
|
|
|
|
@@ -1072,6 +1170,7 @@ $(document).ready(function () {
|
|
|
|
|
$('#tts_skip_tags').on('click', onSkipTagsClick);
|
|
|
|
|
$('#tts_pass_asterisks').on('click', onPassAsterisksClick);
|
|
|
|
|
$('#tts_auto_generation').on('click', onAutoGenerationClick);
|
|
|
|
|
$('#tts_periodic_auto_generation').on('click', onPeriodicAutoGenerationClick);
|
|
|
|
|
$('#tts_narrate_user').on('click', onNarrateUserClick);
|
|
|
|
|
|
|
|
|
|
$('#playback_rate').on('input', function () {
|
|
|
|
@@ -1099,9 +1198,12 @@ $(document).ready(function () {
|
|
|
|
|
eventSource.on(event_types.CHAT_CHANGED, onChatChanged);
|
|
|
|
|
eventSource.on(event_types.MESSAGE_DELETED, onMessageDeleted);
|
|
|
|
|
eventSource.on(event_types.GROUP_UPDATED, onChatChanged);
|
|
|
|
|
eventSource.on(event_types.GENERATION_STARTED, onGenerationStarted);
|
|
|
|
|
eventSource.on(event_types.GENERATION_ENDED, onGenerationEnded);
|
|
|
|
|
eventSource.makeLast(event_types.CHARACTER_MESSAGE_RENDERED, onMessageEvent);
|
|
|
|
|
eventSource.makeLast(event_types.USER_MESSAGE_RENDERED, onMessageEvent);
|
|
|
|
|
SlashCommandParser.addCommandObject(SlashCommand.fromProps({ name: 'speak',
|
|
|
|
|
SlashCommandParser.addCommandObject(SlashCommand.fromProps({
|
|
|
|
|
name: 'speak',
|
|
|
|
|
callback: () => {
|
|
|
|
|
onNarrateText();
|
|
|
|
|
return '';
|
|
|
|
|