mirror of
https://github.com/SillyTavern/SillyTavern.git
synced 2025-06-05 21:59:27 +02:00
Merge branches 'staging' and 'staging' of https://github.com/SillyTavern/SillyTavern into staging
This commit is contained in:
@@ -328,9 +328,7 @@ body.movingUI .drawer-content,
|
|||||||
body.movingUI #expression-holder,
|
body.movingUI #expression-holder,
|
||||||
body.movingUI .zoomed_avatar,
|
body.movingUI .zoomed_avatar,
|
||||||
body.movingUI .draggable,
|
body.movingUI .draggable,
|
||||||
body.movingUI #floatingPrompt,
|
body.movingUI #floatingPrompt {
|
||||||
body.movingUI #groupMemberListPopout,
|
|
||||||
body.movingUI #summaryExtensionPopout {
|
|
||||||
resize: both;
|
resize: both;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -122,11 +122,11 @@
|
|||||||
<div class="flex-container" id="ai_response_configuration">
|
<div class="flex-container" id="ai_response_configuration">
|
||||||
<div id="respective-presets-block" class="width100p">
|
<div id="respective-presets-block" class="width100p">
|
||||||
<div id="kobold_api-presets">
|
<div id="kobold_api-presets">
|
||||||
<h3><span data-i18n="kobldpresets">Kobold Presets</span>
|
<h4 class="margin0"><span data-i18n="kobldpresets">Kobold Presets</span>
|
||||||
<a href="https://docs.sillytavern.app/usage/api-connections/koboldai/" class="notes-link" target="_blank">
|
<a href="https://docs.sillytavern.app/usage/api-connections/koboldai/" class="notes-link" target="_blank">
|
||||||
<span class="note-link-span">?</span>
|
<span class="note-link-span">?</span>
|
||||||
</a>
|
</a>
|
||||||
</h3>
|
</h4>
|
||||||
<div class="preset_buttons">
|
<div class="preset_buttons">
|
||||||
<select id="settings_perset" data-preset-manager-for="kobold">
|
<select id="settings_perset" data-preset-manager-for="kobold">
|
||||||
<option value="gui" data-i18n="guikoboldaisettings">GUI KoboldAI Settings</option>
|
<option value="gui" data-i18n="guikoboldaisettings">GUI KoboldAI Settings</option>
|
||||||
@@ -140,12 +140,12 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div id="novel_api-presets">
|
<div id="novel_api-presets">
|
||||||
<h3>
|
<h4 class="margin0">
|
||||||
<span data-i18n="novelaipreserts">NovelAI Presets</span>
|
<span data-i18n="novelaipreserts">NovelAI Presets</span>
|
||||||
<a href="https://docs.sillytavern.app/usage/api-connections/novelai/" class="notes-link" target="_blank">
|
<a href="https://docs.sillytavern.app/usage/api-connections/novelai/" class="notes-link" target="_blank">
|
||||||
<span class="note-link-span">?</span>
|
<span class="note-link-span">?</span>
|
||||||
</a>
|
</a>
|
||||||
</h3>
|
</h4>
|
||||||
<div class="preset_buttons">
|
<div class="preset_buttons">
|
||||||
<select id="settings_perset_novel" data-preset-manager-for="novel">
|
<select id="settings_perset_novel" data-preset-manager-for="novel">
|
||||||
<option value="gui" data-i18n="default">Default</option>
|
<option value="gui" data-i18n="default">Default</option>
|
||||||
@@ -160,7 +160,7 @@
|
|||||||
</div>
|
</div>
|
||||||
<div id="openai_api-presets">
|
<div id="openai_api-presets">
|
||||||
<div>
|
<div>
|
||||||
<h4><span data-i18n="openaipresets">Chat Completion Presets</span></h4>
|
<h4 class="margin0"><span data-i18n="openaipresets">Chat Completion Presets</span></h4>
|
||||||
<div class="openai_preset_buttons">
|
<div class="openai_preset_buttons">
|
||||||
<select id="settings_perset_openai">
|
<select id="settings_perset_openai">
|
||||||
<option value="gui" data-i18n="default">Default</option>
|
<option value="gui" data-i18n="default">Default</option>
|
||||||
@@ -175,8 +175,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div id="textgenerationwebui_api-presets">
|
<div id="textgenerationwebui_api-presets">
|
||||||
<h3><span data-i18n="Text Gen WebUI (ooba/Mancer) presets">Text Gen WebUI (ooba/Mancer) presets</span>
|
<h4 class="margin0"><span data-i18n="Text Gen WebUI (ooba/Mancer) presets">Text Gen WebUI (ooba/Mancer) presets</span></h4>
|
||||||
</h3>
|
|
||||||
<div class="preset_buttons">
|
<div class="preset_buttons">
|
||||||
<select id="settings_preset_textgenerationwebui" data-preset-manager-for="textgenerationwebui">
|
<select id="settings_preset_textgenerationwebui" data-preset-manager-for="textgenerationwebui">
|
||||||
</select>
|
</select>
|
||||||
@@ -1755,7 +1754,7 @@
|
|||||||
<div id="API-status-top" class="drawer-icon fa-solid fa-plug-circle-exclamation closedIcon" title="API Connections" data-i18n="[title]API Connections"></div>
|
<div id="API-status-top" class="drawer-icon fa-solid fa-plug-circle-exclamation closedIcon" title="API Connections" data-i18n="[title]API Connections"></div>
|
||||||
</div>
|
</div>
|
||||||
<div id="rm_api_block" class="drawer-content closedDrawer">
|
<div id="rm_api_block" class="drawer-content closedDrawer">
|
||||||
<h3 id="title_api">API</h3>
|
<h3 class="margin0" id="title_api">API</h3>
|
||||||
<div class="flex-container flexFlowColumn">
|
<div class="flex-container flexFlowColumn">
|
||||||
<div id="main-API-selector-block">
|
<div id="main-API-selector-block">
|
||||||
<select id="main_api">
|
<select id="main_api">
|
||||||
@@ -1967,9 +1966,9 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div id="openai_api" style="display: none;position: relative;">
|
<div id="openai_api" style="display: none;position: relative;">
|
||||||
<h3 data-i18n="Chat Completion Source">
|
<h4 class="margin0" data-i18n="Chat Completion Source">
|
||||||
Chat Completion Source
|
Chat Completion Source
|
||||||
</h3>
|
</h4>
|
||||||
<select id="chat_completion_source">
|
<select id="chat_completion_source">
|
||||||
<option value="openai">OpenAI</option>
|
<option value="openai">OpenAI</option>
|
||||||
<option value="windowai">Window AI</option>
|
<option value="windowai">Window AI</option>
|
||||||
@@ -2237,7 +2236,7 @@
|
|||||||
<div class="drawer-icon fa-solid fa-font closedIcon" title="AI Response Formatting" data-i18n="[title]AI Response Formatting"></div>
|
<div class="drawer-icon fa-solid fa-font closedIcon" title="AI Response Formatting" data-i18n="[title]AI Response Formatting"></div>
|
||||||
</div>
|
</div>
|
||||||
<div class="drawer-content">
|
<div class="drawer-content">
|
||||||
<h3 data-i18n="Advanced Formatting">Advanced Formatting
|
<h3 class="margin0" data-i18n="Advanced Formatting">Advanced Formatting
|
||||||
<a href="https://docs.sillytavern.app/usage/core-concepts/advancedformatting/" class="notes-link" target="_blank">
|
<a href="https://docs.sillytavern.app/usage/core-concepts/advancedformatting/" class="notes-link" target="_blank">
|
||||||
<span class="note-link-span">?</span>
|
<span class="note-link-span">?</span>
|
||||||
</a>
|
</a>
|
||||||
@@ -2620,7 +2619,7 @@
|
|||||||
<div class="unchecked fa-solid fa-unlock "></div>
|
<div class="unchecked fa-solid fa-unlock "></div>
|
||||||
<div class="checked fa-solid fa-lock "></div>
|
<div class="checked fa-solid fa-lock "></div>
|
||||||
</label>
|
</label>
|
||||||
<h3>
|
<h3 class="margin0">
|
||||||
<span data-i18n="Worlds/Lorebooks">Worlds/Lorebooks</span>
|
<span data-i18n="Worlds/Lorebooks">Worlds/Lorebooks</span>
|
||||||
<a href="https://docs.sillytavern.app/usage/core-concepts/worldinfo/" class="notes-link" target="_blank">
|
<a href="https://docs.sillytavern.app/usage/core-concepts/worldinfo/" class="notes-link" target="_blank">
|
||||||
<span class="note-link-span">?</span>
|
<span class="note-link-span">?</span>
|
||||||
@@ -2797,7 +2796,7 @@
|
|||||||
<div name="userSettingsRowOne" class="flex-container flexFlowRow alignitemscenter spaceBetween">
|
<div name="userSettingsRowOne" class="flex-container flexFlowRow alignitemscenter spaceBetween">
|
||||||
<div class="flex-container">
|
<div class="flex-container">
|
||||||
<div class="flex-container flexnowrap alignitemscenter">
|
<div class="flex-container flexnowrap alignitemscenter">
|
||||||
<h3><span data-i18n="User Settings">User Settings</span></h3>
|
<h3 class="margin0"><span data-i18n="User Settings">User Settings</span></h3>
|
||||||
<select id="ui_mode_select" class="margin0 widthNatural">
|
<select id="ui_mode_select" class="margin0 widthNatural">
|
||||||
<option value="0" data-i18n="Simple">Simple</option>
|
<option value="0" data-i18n="Simple">Simple</option>
|
||||||
<option value="1" data-i18n="Advanced">Advanced</option>
|
<option value="1" data-i18n="Advanced">Advanced</option>
|
||||||
@@ -3273,7 +3272,7 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="drawer-content closedDrawer">
|
<div class="drawer-content closedDrawer">
|
||||||
<div class="flex-container wide100p alignitemscenter spaceBetween">
|
<div class="flex-container wide100p alignitemscenter spaceBetween">
|
||||||
<h3><span data-i18n="Persona Management">Persona Management</span></h3>
|
<h3 class="margin0"><span data-i18n="Persona Management">Persona Management</span></h3>
|
||||||
<a href="https://docs.sillytavern.app/usage/core-concepts/personas/" target="_blank" data-i18n="How do I use this?">How do I use this?</a>
|
<a href="https://docs.sillytavern.app/usage/core-concepts/personas/" target="_blank" data-i18n="How do I use this?">How do I use this?</a>
|
||||||
<div id="persona-management-block" class="flex-container wide100p">
|
<div id="persona-management-block" class="flex-container wide100p">
|
||||||
<div class="flex1">
|
<div class="flex1">
|
||||||
@@ -3674,7 +3673,7 @@
|
|||||||
<div id="dialogue_popup">
|
<div id="dialogue_popup">
|
||||||
<div id="dialogue_popup_holder">
|
<div id="dialogue_popup_holder">
|
||||||
<div id="dialogue_popup_text">
|
<div id="dialogue_popup_text">
|
||||||
<h3>text</h3>
|
<h3 class="margin0">text</h3>
|
||||||
</div>
|
</div>
|
||||||
<textarea id="dialogue_popup_input" class="text_pole" rows="1"></textarea>
|
<textarea id="dialogue_popup_input" class="text_pole" rows="1"></textarea>
|
||||||
<div id="dialogue_popup_controls">
|
<div id="dialogue_popup_controls">
|
||||||
@@ -3687,7 +3686,7 @@
|
|||||||
<div id="character_popup" class="flex-container flexFlowColumn flexNoGap">
|
<div id="character_popup" class="flex-container flexFlowColumn flexNoGap">
|
||||||
|
|
||||||
<div id="character_popup_text">
|
<div id="character_popup_text">
|
||||||
<h3 id="character_popup_text_h3"></h3> <span data-i18n="Advanced Defininitions">- Advanced
|
<h3 id="character_popup_text_h3" class="margin0"></h3> <span data-i18n="Advanced Defininitions">- Advanced
|
||||||
Definitions</span>
|
Definitions</span>
|
||||||
</div>
|
</div>
|
||||||
<hr class="margin-bot-10px">
|
<hr class="margin-bot-10px">
|
||||||
@@ -3868,7 +3867,7 @@
|
|||||||
<div id="scenario_override_template" class="template_element">
|
<div id="scenario_override_template" class="template_element">
|
||||||
<div class="scenario_override range-block flexFlowColumn flex-container">
|
<div class="scenario_override range-block flexFlowColumn flex-container">
|
||||||
<div class="range-block-title title_restorable">
|
<div class="range-block-title title_restorable">
|
||||||
<h3><span data-i18n="Chat Scenario Override">Chat Scenario Override</span></h3>
|
<h3><span data-i18n="Chat Scenario Override" class="margin0">Chat Scenario Override</span></h3>
|
||||||
<div title="Remove" data-i18n="[title]Remove" class="menu_button fa-solid fa-trash-can remove_scenario_override"></div>
|
<div title="Remove" data-i18n="[title]Remove" class="menu_button fa-solid fa-trash-can remove_scenario_override"></div>
|
||||||
</div>
|
</div>
|
||||||
<div class="range-block-counter justifyLeft flex-container flexFlowColumn">
|
<div class="range-block-counter justifyLeft flex-container flexFlowColumn">
|
||||||
@@ -4580,6 +4579,11 @@
|
|||||||
Tokens: <span id="extension_floating_prompt_token_counter">0</span>
|
Tokens: <span id="extension_floating_prompt_token_counter">0</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<label class="checkbox_label" for="extension_floating_allow_wi_scan">
|
||||||
|
<input id="extension_floating_allow_wi_scan" type="checkbox" />
|
||||||
|
<span data-i18n="Include in World Info Scanning">Include in World Info Scanning</span>
|
||||||
|
</label>
|
||||||
|
|
||||||
<div class="floating_prompt_radio_group">
|
<div class="floating_prompt_radio_group">
|
||||||
<label>
|
<label>
|
||||||
<input type="radio" name="extension_floating_position" value="2" />
|
<input type="radio" name="extension_floating_position" value="2" />
|
||||||
@@ -4776,6 +4780,7 @@
|
|||||||
|
|
||||||
<template id="generic_draggable_template">
|
<template id="generic_draggable_template">
|
||||||
<div class="draggable">
|
<div class="draggable">
|
||||||
|
<div class="dragTitle"></div>
|
||||||
<div class="panelControlBar flex-container">
|
<div class="panelControlBar flex-container">
|
||||||
<div class="fa-solid fa-grip drag-grabber"></div>
|
<div class="fa-solid fa-grip drag-grabber"></div>
|
||||||
<div class="fa-solid fa-circle-xmark dragClose"></div>
|
<div class="fa-solid fa-circle-xmark dragClose"></div>
|
||||||
@@ -4833,4 +4838,4 @@
|
|||||||
</script>
|
</script>
|
||||||
</body>
|
</body>
|
||||||
|
|
||||||
</html>
|
</html>
|
@@ -212,6 +212,7 @@ export {
|
|||||||
saveChat,
|
saveChat,
|
||||||
messageFormatting,
|
messageFormatting,
|
||||||
getExtensionPrompt,
|
getExtensionPrompt,
|
||||||
|
getExtensionPromptByName,
|
||||||
showSwipeButtons,
|
showSwipeButtons,
|
||||||
hideSwipeButtons,
|
hideSwipeButtons,
|
||||||
changeMainAPI,
|
changeMainAPI,
|
||||||
@@ -2069,6 +2070,15 @@ function getAllExtensionPrompts() {
|
|||||||
return value.length ? substituteParams(value) : '';
|
return value.length ? substituteParams(value) : '';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Wrapper to fetch extension prompts by module name
|
||||||
|
function getExtensionPromptByName(moduleName) {
|
||||||
|
if (moduleName) {
|
||||||
|
return substituteParams(extension_prompts[moduleName]?.value);
|
||||||
|
} else {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function getExtensionPrompt(position = 0, depth = undefined, separator = "\n") {
|
function getExtensionPrompt(position = 0, depth = undefined, separator = "\n") {
|
||||||
let extension_prompt = Object.keys(extension_prompts)
|
let extension_prompt = Object.keys(extension_prompts)
|
||||||
.sort()
|
.sort()
|
||||||
|
@@ -235,6 +235,7 @@ function loadSettings() {
|
|||||||
chat_metadata[metadata_keys.depth] = chat_metadata[metadata_keys.depth] ?? extension_settings.note.defaultDepth ?? DEFAULT_DEPTH;
|
chat_metadata[metadata_keys.depth] = chat_metadata[metadata_keys.depth] ?? extension_settings.note.defaultDepth ?? DEFAULT_DEPTH;
|
||||||
$('#extension_floating_prompt').val(chat_metadata[metadata_keys.prompt]);
|
$('#extension_floating_prompt').val(chat_metadata[metadata_keys.prompt]);
|
||||||
$('#extension_floating_interval').val(chat_metadata[metadata_keys.interval]);
|
$('#extension_floating_interval').val(chat_metadata[metadata_keys.interval]);
|
||||||
|
$('#extension_floating_allow_wi_scan').prop('checked', extension_settings.note.allowWIScan ?? false);
|
||||||
$('#extension_floating_depth').val(chat_metadata[metadata_keys.depth]);
|
$('#extension_floating_depth').val(chat_metadata[metadata_keys.depth]);
|
||||||
$(`input[name="extension_floating_position"][value="${chat_metadata[metadata_keys.position]}"]`).prop('checked', true);
|
$(`input[name="extension_floating_position"][value="${chat_metadata[metadata_keys.position]}"]`).prop('checked', true);
|
||||||
|
|
||||||
@@ -389,6 +390,11 @@ function onChatChanged() {
|
|||||||
$('#extension_floating_default_token_counter').text(tokenCounter3);
|
$('#extension_floating_default_token_counter').text(tokenCounter3);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function onAllowWIScanCheckboxChanged() {
|
||||||
|
extension_settings.note.allowWIScan = !!$(this).prop('checked');
|
||||||
|
updateSettings();
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Inject author's note options and setup event listeners.
|
* Inject author's note options and setup event listeners.
|
||||||
*/
|
*/
|
||||||
@@ -402,6 +408,7 @@ export function initAuthorsNote() {
|
|||||||
$('#extension_floating_default').on('input', onExtensionFloatingDefaultInput);
|
$('#extension_floating_default').on('input', onExtensionFloatingDefaultInput);
|
||||||
$('#extension_default_depth').on('input', onDefaultDepthInput);
|
$('#extension_default_depth').on('input', onDefaultDepthInput);
|
||||||
$('#extension_default_interval').on('input', onDefaultIntervalInput);
|
$('#extension_default_interval').on('input', onDefaultIntervalInput);
|
||||||
|
$('#extension_floating_allow_wi_scan').on('input', onAllowWIScanCheckboxChanged);
|
||||||
$('input[name="extension_floating_position"]').on('change', onExtensionFloatingPositionInput);
|
$('input[name="extension_floating_position"]').on('change', onExtensionFloatingPositionInput);
|
||||||
$('input[name="extension_default_position"]').on('change', onDefaultPositionInput);
|
$('input[name="extension_default_position"]').on('change', onDefaultPositionInput);
|
||||||
$('input[name="extension_floating_char_position"]').on('change', onExtensionFloatingCharPositionInput);
|
$('input[name="extension_floating_char_position"]').on('change', onExtensionFloatingCharPositionInput);
|
||||||
|
@@ -123,8 +123,7 @@ function downloadAssetsList(url) {
|
|||||||
|
|
||||||
$(`<i></i>`)
|
$(`<i></i>`)
|
||||||
.append(element)
|
.append(element)
|
||||||
.append(`<span>${displayName}</span>`)
|
.append(`<div class="flex-container flexFlowColumn"><span>${displayName}</span><span>${description}</span></div>`)
|
||||||
.append(`<span>${description}</span>`)
|
|
||||||
.appendTo(assetTypeMenu);
|
.appendTo(assetTypeMenu);
|
||||||
}
|
}
|
||||||
assetTypeMenu.appendTo("#assets_menu");
|
assetTypeMenu.appendTo("#assets_menu");
|
||||||
|
@@ -5,7 +5,7 @@ import {
|
|||||||
getRequestHeaders,
|
getRequestHeaders,
|
||||||
} from "../../../script.js";
|
} from "../../../script.js";
|
||||||
import { selected_group } from "../../group-chats.js";
|
import { selected_group } from "../../group-chats.js";
|
||||||
import { loadFileToDocument } from "../../utils.js";
|
import { loadFileToDocument, delay } from "../../utils.js";
|
||||||
import { loadMovingUIState } from '../../power-user.js';
|
import { loadMovingUIState } from '../../power-user.js';
|
||||||
import { dragElement } from '../../RossAscends-mods.js';
|
import { dragElement } from '../../RossAscends-mods.js';
|
||||||
import { registerSlashCommand } from "../../slash-commands.js";
|
import { registerSlashCommand } from "../../slash-commands.js";
|
||||||
@@ -109,6 +109,13 @@ async function initGallery(items, url) {
|
|||||||
let file = e.originalEvent.dataTransfer.files[0];
|
let file = e.originalEvent.dataTransfer.files[0];
|
||||||
uploadFile(file, url); // Added url parameter to know where to upload
|
uploadFile(file, url); // Added url parameter to know where to upload
|
||||||
});
|
});
|
||||||
|
|
||||||
|
//let images populate first
|
||||||
|
await delay(100)
|
||||||
|
//unset the height (which must be getting set by the gallery library at some point)
|
||||||
|
$("#dragGallery").css('height', 'unset');
|
||||||
|
//force a resize to make images display correctly
|
||||||
|
jQuery("#dragGallery").nanogallery2('resize');
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -247,14 +254,16 @@ $(document).ready(function () {
|
|||||||
* The cloned element has its attributes set, a new child div appended, and is made visible on the body.
|
* The cloned element has its attributes set, a new child div appended, and is made visible on the body.
|
||||||
* Additionally, it sets up the element to prevent dragging on its images.
|
* Additionally, it sets up the element to prevent dragging on its images.
|
||||||
*/
|
*/
|
||||||
function makeMovable(id="gallery"){
|
function makeMovable(id = "gallery") {
|
||||||
|
|
||||||
console.debug('making new container from template')
|
console.debug('making new container from template')
|
||||||
const template = $('#generic_draggable_template').html();
|
const template = $('#generic_draggable_template').html();
|
||||||
const newElement = $(template);
|
const newElement = $(template);
|
||||||
|
newElement.css('background-color', 'var(--SmartThemeBlurTintColor)');
|
||||||
newElement.attr('forChar', id);
|
newElement.attr('forChar', id);
|
||||||
newElement.attr('id', `${id}`);
|
newElement.attr('id', `${id}`);
|
||||||
newElement.find('.drag-grabber').attr('id', `${id}header`);
|
newElement.find('.drag-grabber').attr('id', `${id}header`);
|
||||||
|
newElement.find('.dragTitle').text('Image Gallery')
|
||||||
//add a div for the gallery
|
//add a div for the gallery
|
||||||
newElement.append(`<div id="dragGallery"></div>`);
|
newElement.append(`<div id="dragGallery"></div>`);
|
||||||
// add no-scrollbar class to this element
|
// add no-scrollbar class to this element
|
||||||
@@ -326,6 +335,8 @@ function makeDragImg(id, url) {
|
|||||||
|
|
||||||
// Ensure that the newly added element is displayed as block
|
// Ensure that the newly added element is displayed as block
|
||||||
draggableElem.style.display = 'block';
|
draggableElem.style.display = 'block';
|
||||||
|
//and has no padding unlike other non-zoomed-avatar draggables
|
||||||
|
draggableElem.style.padding = '0';
|
||||||
|
|
||||||
// Add an id to the close button
|
// Add an id to the close button
|
||||||
// If the close button exists, set related-id
|
// If the close button exists, set related-id
|
||||||
@@ -375,11 +386,11 @@ function makeDragImg(id, url) {
|
|||||||
* @param {string} id - The ID to be sanitized.
|
* @param {string} id - The ID to be sanitized.
|
||||||
* @returns {string} - The sanitized ID.
|
* @returns {string} - The sanitized ID.
|
||||||
*/
|
*/
|
||||||
function sanitizeHTMLId(id){
|
function sanitizeHTMLId(id) {
|
||||||
// Replace spaces and non-word characters
|
// Replace spaces and non-word characters
|
||||||
id = id.replace(/\s+/g, '-')
|
id = id.replace(/\s+/g, '-')
|
||||||
.replace(/[^\x00-\x7F]/g, '-')
|
.replace(/[^\x00-\x7F]/g, '-')
|
||||||
.replace(/\W/g, '');
|
.replace(/\W/g, '');
|
||||||
|
|
||||||
return id;
|
return id;
|
||||||
}
|
}
|
||||||
|
@@ -571,12 +571,13 @@ function doPopout(e) {
|
|||||||
const template = $('#zoomed_avatar_template').html();
|
const template = $('#zoomed_avatar_template').html();
|
||||||
const controlBarHtml = `<div class="panelControlBar flex-container">
|
const controlBarHtml = `<div class="panelControlBar flex-container">
|
||||||
<div id="summaryExtensionPopoutheader" class="fa-solid fa-grip drag-grabber hoverglow"></div>
|
<div id="summaryExtensionPopoutheader" class="fa-solid fa-grip drag-grabber hoverglow"></div>
|
||||||
<div id="summaryExtensionPopoutClose" class="fa-solid fa-circle-xmark hoverglow"></div>
|
<div id="summaryExtensionPopoutClose" class="fa-solid fa-circle-xmark hoverglow dragClose"></div>
|
||||||
</div>`
|
</div>`
|
||||||
const newElement = $(template);
|
const newElement = $(template);
|
||||||
newElement.attr('id', 'summaryExtensionPopout');
|
newElement.attr('id', 'summaryExtensionPopout')
|
||||||
newElement.removeClass('zoomed_avatar')
|
.removeClass('zoomed_avatar')
|
||||||
newElement.empty()
|
.addClass('draggable')
|
||||||
|
.empty()
|
||||||
const prevSummaryBoxContents = $('#memory_contents').val(); //copy summary box before emptying
|
const prevSummaryBoxContents = $('#memory_contents').val(); //copy summary box before emptying
|
||||||
originalElement.empty();
|
originalElement.empty();
|
||||||
originalElement.html(`<div class="flex-container alignitemscenter justifyCenter wide100p"><small>Currently popped out</small></div>`)
|
originalElement.html(`<div class="flex-container alignitemscenter justifyCenter wide100p"><small>Currently popped out</small></div>`)
|
||||||
|
@@ -1,233 +0,0 @@
|
|||||||
// Borrowed from Agnai (AGPLv3)
|
|
||||||
// https://github.com/agnaistic/agnai/blob/dev/web/pages/Chat/components/SpeechRecognitionRecorder.tsx
|
|
||||||
// First version by Cohee#1207
|
|
||||||
// Adapted by Tony-sama
|
|
||||||
|
|
||||||
export { BrowserSttProvider }
|
|
||||||
|
|
||||||
const DEBUG_PREFIX = "<Speech Recognition module (Browser)> "
|
|
||||||
|
|
||||||
class BrowserSttProvider {
|
|
||||||
//########//
|
|
||||||
// Config //
|
|
||||||
//########//
|
|
||||||
|
|
||||||
settings = {
|
|
||||||
language: ""
|
|
||||||
}
|
|
||||||
|
|
||||||
defaultSettings = {
|
|
||||||
language: "en-US",
|
|
||||||
}
|
|
||||||
|
|
||||||
processTranscriptFunction = null;
|
|
||||||
|
|
||||||
get settingsHtml() {
|
|
||||||
let html = ' \
|
|
||||||
<span>Language</span> </br> \
|
|
||||||
<select id="speech_recognition_browser_provider_language"> \
|
|
||||||
<option value="ar-SA">ar-SA: Arabic (Saudi Arabia)</option> \
|
|
||||||
<option value="bn-BD">bn-BD: Bangla (Bangladesh)</option> \
|
|
||||||
<option value="bn-IN">bn-IN: Bangla (India)</option> \
|
|
||||||
<option value="cs-CZ">cs-CZ: Czech (Czech Republic)</option> \
|
|
||||||
<option value="da-DK">da-DK: Danish (Denmark)</option> \
|
|
||||||
<option value="de-AT">de-AT: German (Austria)</option> \
|
|
||||||
<option value="de-CH">de-CH: German (Switzerland)</option> \
|
|
||||||
<option value="de-DE">de-DE: German (Germany)</option> \
|
|
||||||
<option value="el-GR">el-GR: Greek (Greece)</option> \
|
|
||||||
<option value="en-AU">en-AU: English (Australia)</option> \
|
|
||||||
<option value="en-CA">en-CA: English (Canada)</option> \
|
|
||||||
<option value="en-GB">en-GB: English (United Kingdom)</option> \
|
|
||||||
<option value="en-IE">en-IE: English (Ireland)</option> \
|
|
||||||
<option value="en-IN">en-IN: English (India)</option> \
|
|
||||||
<option value="en-NZ">en-NZ: English (New Zealand)</option> \
|
|
||||||
<option value="en-US">en-US: English (United States)</option> \
|
|
||||||
<option value="en-ZA">en-ZA: English (South Africa)</option> \
|
|
||||||
<option value="es-AR">es-AR: Spanish (Argentina)</option> \
|
|
||||||
<option value="es-CL">es-CL: Spanish (Chile)</option> \
|
|
||||||
<option value="es-CO">es-CO: Spanish (Columbia)</option> \
|
|
||||||
<option value="es-ES">es-ES: Spanish (Spain)</option> \
|
|
||||||
<option value="es-MX">es-MX: Spanish (Mexico)</option> \
|
|
||||||
<option value="es-US">es-US: Spanish (United States)</option> \
|
|
||||||
<option value="fi-FI">fi-FI: Finnish (Finland)</option> \
|
|
||||||
<option value="fr-BE">fr-BE: French (Belgium)</option> \
|
|
||||||
<option value="fr-CA">fr-CA: French (Canada)</option> \
|
|
||||||
<option value="fr-CH">fr-CH: French (Switzerland)</option> \
|
|
||||||
<option value="fr-FR">fr-FR: French (France)</option> \
|
|
||||||
<option value="he-IL">he-IL: Hebrew (Israel)</option> \
|
|
||||||
<option value="hi-IN">hi-IN: Hindi (India)</option> \
|
|
||||||
<option value="hu-HU">hu-HU: Hungarian (Hungary)</option> \
|
|
||||||
<option value="id-ID">id-ID: Indonesian (Indonesia)</option> \
|
|
||||||
<option value="it-CH">it-CH: Italian (Switzerland)</option> \
|
|
||||||
<option value="it-IT">it-IT: Italian (Italy)</option> \
|
|
||||||
<option value="ja-JP">ja-JP: Japanese (Japan)</option> \
|
|
||||||
<option value="ko-KR">ko-KR: Korean (Republic of Korea)</option> \
|
|
||||||
<option value="nl-BE">nl-BE: Dutch (Belgium)</option> \
|
|
||||||
<option value="nl-NL">nl-NL: Dutch (The Netherlands)</option> \
|
|
||||||
<option value="no-NO">no-NO: Norwegian (Norway)</option> \
|
|
||||||
<option value="pl-PL">pl-PL: Polish (Poland)</option> \
|
|
||||||
<option value="pt-BR">pt-BR: Portugese (Brazil)</option> \
|
|
||||||
<option value="pt-PT">pt-PT: Portugese (Portugal)</option> \
|
|
||||||
<option value="ro-RO">ro-RO: Romanian (Romania)</option> \
|
|
||||||
<option value="ru-RU">ru-RU: Russian (Russian Federation)</option> \
|
|
||||||
<option value="sk-SK">sk-SK: Slovak (Slovakia)</option> \
|
|
||||||
<option value="sv-SE">sv-SE: Swedish (Sweden)</option> \
|
|
||||||
<option value="ta-IN">ta-IN: Tamil (India)</option> \
|
|
||||||
<option value="ta-LK">ta-LK: Tamil (Sri Lanka)</option> \
|
|
||||||
<option value="th-TH">th-TH: Thai (Thailand)</option> \
|
|
||||||
<option value="tr-TR">tr-TR: Turkish (Turkey)</option> \
|
|
||||||
<option value="zh-CN">zh-CN: Chinese (China)</option> \
|
|
||||||
<option value="zh-HK">zh-HK: Chinese (Hond Kong)</option> \
|
|
||||||
<option value="zh-TW">zh-TW: Chinese (Taiwan)</option> \
|
|
||||||
</select> \
|
|
||||||
'
|
|
||||||
return html
|
|
||||||
}
|
|
||||||
|
|
||||||
onSettingsChange() {
|
|
||||||
// Used when provider settings are updated from UI
|
|
||||||
this.settings.language = $("#speech_recognition_browser_provider_language").val();
|
|
||||||
console.debug(DEBUG_PREFIX+"Change language to",this.settings.language);
|
|
||||||
this.loadSettings(this.settings);
|
|
||||||
}
|
|
||||||
|
|
||||||
static capitalizeInterim(interimTranscript) {
|
|
||||||
let capitalizeIndex = -1;
|
|
||||||
if (interimTranscript.length > 2 && interimTranscript[0] === ' ') capitalizeIndex = 1;
|
|
||||||
else if (interimTranscript.length > 1) capitalizeIndex = 0;
|
|
||||||
if (capitalizeIndex > -1) {
|
|
||||||
const spacing = capitalizeIndex > 0 ? ' '.repeat(capitalizeIndex - 1) : '';
|
|
||||||
const capitalized = interimTranscript[capitalizeIndex].toLocaleUpperCase();
|
|
||||||
const rest = interimTranscript.substring(capitalizeIndex + 1);
|
|
||||||
interimTranscript = spacing + capitalized + rest;
|
|
||||||
}
|
|
||||||
return interimTranscript;
|
|
||||||
}
|
|
||||||
|
|
||||||
static composeValues(previous, interim) {
|
|
||||||
let spacing = '';
|
|
||||||
if (previous.endsWith('.')) spacing = ' ';
|
|
||||||
return previous + spacing + interim;
|
|
||||||
}
|
|
||||||
|
|
||||||
loadSettings(settings) {
|
|
||||||
const processTranscript = this.processTranscriptFunction;
|
|
||||||
|
|
||||||
// Populate Provider UI given input settings
|
|
||||||
if (Object.keys(settings).length == 0) {
|
|
||||||
console.debug(DEBUG_PREFIX+"Using default browser STT settings")
|
|
||||||
}
|
|
||||||
|
|
||||||
// Initialise as defaultSettings
|
|
||||||
this.settings = this.defaultSettings;
|
|
||||||
|
|
||||||
for (const key in settings){
|
|
||||||
if (key in this.settings){
|
|
||||||
this.settings[key] = settings[key]
|
|
||||||
} else {
|
|
||||||
throw `Invalid setting passed to Speech recogniton extension (browser): ${key}`
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
$("#speech_recognition_browser_provider_language").val(this.settings.language);
|
|
||||||
|
|
||||||
const speechRecognitionSettings = $.extend({
|
|
||||||
grammar: '' // Custom grammar
|
|
||||||
}, options);
|
|
||||||
|
|
||||||
const speechRecognition = window.SpeechRecognition || window.webkitSpeechRecognition;
|
|
||||||
const speechRecognitionList = window.SpeechGrammarList || window.webkitSpeechGrammarList;
|
|
||||||
|
|
||||||
if (!speechRecognition) {
|
|
||||||
console.warn(DEBUG_PREFIX+'Speech recognition is not supported in this browser.');
|
|
||||||
$("#microphone_button").hide();
|
|
||||||
toastr.error("Speech recognition is not supported in this browser, use another browser or another provider of SillyTavern-extras Speech recognition extension.", "Speech recognition activation Failed (Browser)", { timeOut: 10000, extendedTimeOut: 20000, preventDuplicates: true });
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const recognition = new speechRecognition();
|
|
||||||
|
|
||||||
if (speechRecognitionSettings.grammar && speechRecognitionList) {
|
|
||||||
speechRecognitionList.addFromString(speechRecognitionSettings.grammar, 1);
|
|
||||||
recognition.grammars = speechRecognitionList;
|
|
||||||
}
|
|
||||||
|
|
||||||
recognition.continuous = true;
|
|
||||||
recognition.interimResults = true;
|
|
||||||
recognition.lang = this.settings.language;
|
|
||||||
|
|
||||||
const textarea = $('#send_textarea');
|
|
||||||
const button = $('#microphone_button');
|
|
||||||
|
|
||||||
let listening = false;
|
|
||||||
button.off('click').on("click", function () {
|
|
||||||
if (listening) {
|
|
||||||
recognition.stop();
|
|
||||||
} else {
|
|
||||||
recognition.start();
|
|
||||||
}
|
|
||||||
listening = !listening;
|
|
||||||
});
|
|
||||||
|
|
||||||
let initialText = '';
|
|
||||||
|
|
||||||
recognition.onresult = function (speechEvent) {
|
|
||||||
let finalTranscript = '';
|
|
||||||
let interimTranscript = ''
|
|
||||||
|
|
||||||
for (let i = speechEvent.resultIndex; i < speechEvent.results.length; ++i) {
|
|
||||||
const transcript = speechEvent.results[i][0].transcript;
|
|
||||||
|
|
||||||
if (speechEvent.results[i].isFinal) {
|
|
||||||
let interim = BrowserSttProvider.capitalizeInterim(transcript);
|
|
||||||
if (interim != '') {
|
|
||||||
let final = finalTranscript;
|
|
||||||
final = BrowserSttProvider.composeValues(final, interim);
|
|
||||||
if (final.slice(-1) != '.' & final.slice(-1) != '?') final += '.';
|
|
||||||
finalTranscript = final;
|
|
||||||
recognition.abort();
|
|
||||||
listening = false;
|
|
||||||
}
|
|
||||||
interimTranscript = ' ';
|
|
||||||
} else {
|
|
||||||
interimTranscript += transcript;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
interimTranscript = BrowserSttProvider.capitalizeInterim(interimTranscript);
|
|
||||||
|
|
||||||
textarea.val(initialText + finalTranscript + interimTranscript);
|
|
||||||
};
|
|
||||||
|
|
||||||
recognition.onerror = function (event) {
|
|
||||||
console.error('Error occurred in recognition:', event.error);
|
|
||||||
//if ($('#speech_recognition_debug').is(':checked'))
|
|
||||||
// toastr.error('Error occurred in recognition:'+ event.error, 'STT Generation error (Browser)', { timeOut: 10000, extendedTimeOut: 20000, preventDuplicates: true });
|
|
||||||
};
|
|
||||||
|
|
||||||
recognition.onend = function () {
|
|
||||||
listening = false;
|
|
||||||
button.toggleClass('fa-microphone fa-microphone-slash');
|
|
||||||
const newText = textarea.val().substring(initialText.length);
|
|
||||||
textarea.val(textarea.val().substring(0,initialText.length));
|
|
||||||
processTranscript(newText);
|
|
||||||
|
|
||||||
};
|
|
||||||
|
|
||||||
recognition.onstart = function () {
|
|
||||||
initialText = textarea.val();
|
|
||||||
button.toggleClass('fa-microphone fa-microphone-slash');
|
|
||||||
|
|
||||||
if ($("#speech_recognition_message_mode").val() == "replace") {
|
|
||||||
textarea.val("");
|
|
||||||
initialText = ""
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
$("#microphone_button").show();
|
|
||||||
|
|
||||||
console.debug(DEBUG_PREFIX+"Browser STT settings loaded")
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
}
|
|
@@ -1,452 +0,0 @@
|
|||||||
/*
|
|
||||||
TODO:
|
|
||||||
- try pseudo streaming audio by just sending chunk every X seconds and asking VOSK if it is full text.
|
|
||||||
*/
|
|
||||||
|
|
||||||
import { saveSettingsDebounced } from "../../../script.js";
|
|
||||||
import { getContext, extension_settings, ModuleWorkerWrapper } from "../../extensions.js";
|
|
||||||
import { VoskSttProvider } from './vosk.js'
|
|
||||||
import { WhisperSttProvider } from './whisper.js'
|
|
||||||
import { BrowserSttProvider } from './browser.js'
|
|
||||||
import { StreamingSttProvider } from './streaming.js'
|
|
||||||
import { getMessageTimeStamp } from "../../RossAscends-mods.js";
|
|
||||||
export { MODULE_NAME };
|
|
||||||
|
|
||||||
const MODULE_NAME = 'Speech Recognition';
|
|
||||||
const DEBUG_PREFIX = "<Speech Recognition module> "
|
|
||||||
const UPDATE_INTERVAL = 100;
|
|
||||||
|
|
||||||
let inApiCall = false;
|
|
||||||
|
|
||||||
let sttProviders = {
|
|
||||||
None: null,
|
|
||||||
Browser: BrowserSttProvider,
|
|
||||||
Whisper: WhisperSttProvider,
|
|
||||||
Vosk: VoskSttProvider,
|
|
||||||
Streaming: StreamingSttProvider,
|
|
||||||
}
|
|
||||||
|
|
||||||
let sttProvider = null
|
|
||||||
let sttProviderName = "None"
|
|
||||||
|
|
||||||
let audioRecording = false
|
|
||||||
const constraints = { audio: { sampleSize: 16, channelCount: 1, sampleRate: 16000 } };
|
|
||||||
let audioChunks = [];
|
|
||||||
|
|
||||||
async function moduleWorker() {
|
|
||||||
if (sttProviderName != "Streaming") {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// API is busy
|
|
||||||
if (inApiCall) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
inApiCall = true;
|
|
||||||
const userMessageOriginal = await sttProvider.getUserMessage();
|
|
||||||
let userMessageFormatted = userMessageOriginal.trim();
|
|
||||||
|
|
||||||
if (userMessageFormatted.length > 0)
|
|
||||||
{
|
|
||||||
console.debug(DEBUG_PREFIX+"recorded transcript: \""+userMessageFormatted+"\"");
|
|
||||||
|
|
||||||
let userMessageLower = userMessageFormatted.toLowerCase();
|
|
||||||
// remove punctuation
|
|
||||||
let userMessageRaw = userMessageLower.replace(/[^\w\s\']|_/g, "").replace(/\s+/g, " ");
|
|
||||||
|
|
||||||
console.debug(DEBUG_PREFIX+"raw transcript:",userMessageRaw);
|
|
||||||
|
|
||||||
// Detect trigger words
|
|
||||||
let messageStart = -1;
|
|
||||||
|
|
||||||
if (extension_settings.speech_recognition.Streaming.triggerWordsEnabled) {
|
|
||||||
|
|
||||||
for (const triggerWord of extension_settings.speech_recognition.Streaming.triggerWords) {
|
|
||||||
const triggerPos = userMessageRaw.indexOf(triggerWord.toLowerCase());
|
|
||||||
|
|
||||||
// Trigger word not found or not starting message and just a substring
|
|
||||||
if (triggerPos == -1){ // | (triggerPos > 0 & userMessageFormatted[triggerPos-1] != " ")) {
|
|
||||||
console.debug(DEBUG_PREFIX+"trigger word not found: ", triggerWord);
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
console.debug(DEBUG_PREFIX+"Found trigger word: ", triggerWord, " at index ", triggerPos);
|
|
||||||
if (triggerPos < messageStart || messageStart == -1) { // & (triggerPos + triggerWord.length) < userMessageFormatted.length)) {
|
|
||||||
messageStart = triggerPos; // + triggerWord.length + 1;
|
|
||||||
|
|
||||||
if (!extension_settings.speech_recognition.Streaming.triggerWordsIncluded)
|
|
||||||
messageStart = triggerPos + triggerWord.length + 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
messageStart = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (messageStart == -1) {
|
|
||||||
console.debug(DEBUG_PREFIX+"message ignored, no trigger word preceding a message. Voice transcript: \""+ userMessageOriginal +"\"");
|
|
||||||
if (extension_settings.speech_recognition.Streaming.debug) {
|
|
||||||
toastr.info(
|
|
||||||
"No trigger word preceding a message. Voice transcript: \""+ userMessageOriginal +"\"",
|
|
||||||
DEBUG_PREFIX+"message ignored.",
|
|
||||||
{ timeOut: 10000, extendedTimeOut: 20000, preventDuplicates: true },
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else{
|
|
||||||
userMessageFormatted = userMessageFormatted.substring(messageStart);
|
|
||||||
// Trim non alphanumeric character from the start
|
|
||||||
messageStart = 0;
|
|
||||||
for(const i of userMessageFormatted) {
|
|
||||||
if(/^[a-z]$/i.test(i)) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
messageStart += 1;
|
|
||||||
}
|
|
||||||
userMessageFormatted = userMessageFormatted.substring(messageStart);
|
|
||||||
userMessageFormatted = userMessageFormatted.charAt(0).toUpperCase() + userMessageFormatted.substring(1);
|
|
||||||
processTranscript(userMessageFormatted);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
console.debug(DEBUG_PREFIX+"Received empty transcript, ignored");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
catch (error) {
|
|
||||||
console.debug(error);
|
|
||||||
}
|
|
||||||
finally {
|
|
||||||
inApiCall = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async function processTranscript(transcript) {
|
|
||||||
try {
|
|
||||||
const transcriptOriginal = transcript;
|
|
||||||
let transcriptFormatted = transcriptOriginal.trim();
|
|
||||||
|
|
||||||
if (transcriptFormatted.length > 0)
|
|
||||||
{
|
|
||||||
console.debug(DEBUG_PREFIX+"recorded transcript: \""+transcriptFormatted+"\"");
|
|
||||||
const messageMode = extension_settings.speech_recognition.messageMode;
|
|
||||||
console.debug(DEBUG_PREFIX+"mode: "+messageMode);
|
|
||||||
|
|
||||||
let transcriptLower = transcriptFormatted.toLowerCase()
|
|
||||||
// remove punctuation
|
|
||||||
let transcriptRaw = transcriptLower.replace(/[^\w\s\']|_/g, "").replace(/\s+/g, " ");
|
|
||||||
|
|
||||||
// Check message mapping
|
|
||||||
if (extension_settings.speech_recognition.messageMappingEnabled) {
|
|
||||||
console.debug(DEBUG_PREFIX+"Start searching message mapping into:",transcriptRaw)
|
|
||||||
for (const key in extension_settings.speech_recognition.messageMapping) {
|
|
||||||
console.debug(DEBUG_PREFIX+"message mapping searching: ", key,"=>",extension_settings.speech_recognition.messageMapping[key]);
|
|
||||||
if (transcriptRaw.includes(key)) {
|
|
||||||
var message = extension_settings.speech_recognition.messageMapping[key];
|
|
||||||
console.debug(DEBUG_PREFIX+"message mapping found: ", key,"=>",extension_settings.speech_recognition.messageMapping[key]);
|
|
||||||
$("#send_textarea").val(message);
|
|
||||||
|
|
||||||
if (messageMode == "auto_send") await getContext().generate();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
console.debug(DEBUG_PREFIX+"no message mapping found, processing transcript as normal message");
|
|
||||||
|
|
||||||
switch (messageMode) {
|
|
||||||
case "auto_send":
|
|
||||||
$('#send_textarea').val("") // clear message area to avoid double message
|
|
||||||
|
|
||||||
console.debug(DEBUG_PREFIX+"Sending message")
|
|
||||||
const context = getContext();
|
|
||||||
const messageText = transcriptFormatted;
|
|
||||||
const message = {
|
|
||||||
name: context.name1,
|
|
||||||
is_user: true,
|
|
||||||
send_date: getMessageTimeStamp(),
|
|
||||||
mes: messageText,
|
|
||||||
};
|
|
||||||
context.chat.push(message);
|
|
||||||
context.addOneMessage(message);
|
|
||||||
|
|
||||||
await context.generate();
|
|
||||||
|
|
||||||
$('#debug_output').text("<SST-module DEBUG>: message sent: \""+ transcriptFormatted +"\"");
|
|
||||||
break;
|
|
||||||
|
|
||||||
case "replace":
|
|
||||||
console.debug(DEBUG_PREFIX+"Replacing message")
|
|
||||||
$('#send_textarea').val(transcriptFormatted);
|
|
||||||
break;
|
|
||||||
|
|
||||||
case "append":
|
|
||||||
console.debug(DEBUG_PREFIX+"Appending message")
|
|
||||||
$('#send_textarea').val($('#send_textarea').val()+" "+transcriptFormatted);
|
|
||||||
break;
|
|
||||||
|
|
||||||
default:
|
|
||||||
console.debug(DEBUG_PREFIX+"Not supported stt message mode: "+messageMode)
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
console.debug(DEBUG_PREFIX+"Empty transcript, do nothing");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
catch (error) {
|
|
||||||
console.debug(error);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function loadNavigatorAudioRecording() {
|
|
||||||
if (navigator.mediaDevices.getUserMedia) {
|
|
||||||
console.debug(DEBUG_PREFIX+' getUserMedia supported by browser.');
|
|
||||||
|
|
||||||
let onSuccess = function(stream) {
|
|
||||||
const mediaRecorder = new MediaRecorder(stream);
|
|
||||||
|
|
||||||
$("#microphone_button").off('click').on("click", function() {
|
|
||||||
if (!audioRecording) {
|
|
||||||
mediaRecorder.start();
|
|
||||||
console.debug(mediaRecorder.state);
|
|
||||||
console.debug("recorder started");
|
|
||||||
audioRecording = true;
|
|
||||||
$("#microphone_button").toggleClass('fa-microphone fa-microphone-slash');
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
mediaRecorder.stop();
|
|
||||||
console.debug(mediaRecorder.state);
|
|
||||||
console.debug("recorder stopped");
|
|
||||||
audioRecording = false;
|
|
||||||
$("#microphone_button").toggleClass('fa-microphone fa-microphone-slash');
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
mediaRecorder.onstop = async function() {
|
|
||||||
console.debug(DEBUG_PREFIX+"data available after MediaRecorder.stop() called: ", audioChunks.length, " chunks");
|
|
||||||
const audioBlob = new Blob(audioChunks, { type: "audio/wav; codecs=0" });
|
|
||||||
audioChunks = [];
|
|
||||||
|
|
||||||
const transcript = await sttProvider.processAudio(audioBlob);
|
|
||||||
|
|
||||||
// TODO: lock and release recording while processing?
|
|
||||||
console.debug(DEBUG_PREFIX+"received transcript:", transcript);
|
|
||||||
processTranscript(transcript);
|
|
||||||
}
|
|
||||||
|
|
||||||
mediaRecorder.ondataavailable = function(e) {
|
|
||||||
audioChunks.push(e.data);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let onError = function(err) {
|
|
||||||
console.debug(DEBUG_PREFIX+"The following error occured: " + err);
|
|
||||||
}
|
|
||||||
|
|
||||||
navigator.mediaDevices.getUserMedia(constraints).then(onSuccess, onError);
|
|
||||||
|
|
||||||
} else {
|
|
||||||
console.debug(DEBUG_PREFIX+"getUserMedia not supported on your browser!");
|
|
||||||
toastr.error("getUserMedia not supported", DEBUG_PREFIX+"not supported for your browser.", { timeOut: 10000, extendedTimeOut: 20000, preventDuplicates: true });
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
//##############//
|
|
||||||
// STT Provider //
|
|
||||||
//##############//
|
|
||||||
|
|
||||||
function loadSttProvider(provider) {
|
|
||||||
//Clear the current config and add new config
|
|
||||||
$("#speech_recognition_provider_settings").html("");
|
|
||||||
|
|
||||||
// Init provider references
|
|
||||||
extension_settings.speech_recognition.currentProvider = provider;
|
|
||||||
sttProviderName = provider;
|
|
||||||
|
|
||||||
if (!(sttProviderName in extension_settings.speech_recognition)) {
|
|
||||||
console.warn(`Provider ${sttProviderName} not in Extension Settings, initiatilizing provider in settings`);
|
|
||||||
extension_settings.speech_recognition[sttProviderName] = {};
|
|
||||||
}
|
|
||||||
|
|
||||||
$('#speech_recognition_provider').val(sttProviderName);
|
|
||||||
|
|
||||||
if (sttProviderName == "None") {
|
|
||||||
$("#microphone_button").hide();
|
|
||||||
$("#speech_recognition_message_mode_div").hide();
|
|
||||||
$("#speech_recognition_message_mapping_div").hide();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
$("#speech_recognition_message_mode_div").show();
|
|
||||||
$("#speech_recognition_message_mapping_div").show();
|
|
||||||
|
|
||||||
sttProvider = new sttProviders[sttProviderName]
|
|
||||||
|
|
||||||
// Init provider settings
|
|
||||||
$('#speech_recognition_provider_settings').append(sttProvider.settingsHtml);
|
|
||||||
|
|
||||||
// Use microphone button as push to talk
|
|
||||||
if (sttProviderName == "Browser") {
|
|
||||||
sttProvider.processTranscriptFunction = processTranscript;
|
|
||||||
sttProvider.loadSettings(extension_settings.speech_recognition[sttProviderName]);
|
|
||||||
$("#microphone_button").show();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (sttProviderName == "Vosk" | sttProviderName == "Whisper") {
|
|
||||||
sttProvider.loadSettings(extension_settings.speech_recognition[sttProviderName]);
|
|
||||||
loadNavigatorAudioRecording();
|
|
||||||
$("#microphone_button").show();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (sttProviderName == "Streaming") {
|
|
||||||
sttProvider.loadSettings(extension_settings.speech_recognition[sttProviderName]);
|
|
||||||
$("#microphone_button").off('click');
|
|
||||||
$("#microphone_button").hide();
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
function onSttProviderChange() {
|
|
||||||
const sttProviderSelection = $('#speech_recognition_provider').val();
|
|
||||||
loadSttProvider(sttProviderSelection);
|
|
||||||
saveSettingsDebounced();
|
|
||||||
}
|
|
||||||
|
|
||||||
function onSttProviderSettingsInput() {
|
|
||||||
sttProvider.onSettingsChange();
|
|
||||||
|
|
||||||
// Persist changes to SillyTavern stt extension settings
|
|
||||||
extension_settings.speech_recognition[sttProviderName] = sttProvider.settings;
|
|
||||||
saveSettingsDebounced();
|
|
||||||
console.info(`Saved settings ${sttProviderName} ${JSON.stringify(sttProvider.settings)}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
//#############################//
|
|
||||||
// Extension UI and Settings //
|
|
||||||
//#############################//
|
|
||||||
|
|
||||||
const defaultSettings = {
|
|
||||||
currentProvider: "None",
|
|
||||||
messageMode: "append",
|
|
||||||
messageMappingText: "",
|
|
||||||
messageMapping: [],
|
|
||||||
messageMappingEnabled: false,
|
|
||||||
}
|
|
||||||
|
|
||||||
function loadSettings() {
|
|
||||||
if (Object.keys(extension_settings.speech_recognition).length === 0) {
|
|
||||||
Object.assign(extension_settings.speech_recognition, defaultSettings)
|
|
||||||
}
|
|
||||||
$('#speech_recognition_enabled').prop('checked',extension_settings.speech_recognition.enabled);
|
|
||||||
$('#speech_recognition_message_mode').val(extension_settings.speech_recognition.messageMode);
|
|
||||||
|
|
||||||
if (extension_settings.speech_recognition.messageMappingText.length > 0) {
|
|
||||||
$('#speech_recognition_message_mapping').val(extension_settings.speech_recognition.messageMappingText);
|
|
||||||
}
|
|
||||||
|
|
||||||
$('#speech_recognition_message_mapping_enabled').prop('checked',extension_settings.speech_recognition.messageMappingEnabled);
|
|
||||||
}
|
|
||||||
|
|
||||||
async function onMessageModeChange() {
|
|
||||||
extension_settings.speech_recognition.messageMode = $('#speech_recognition_message_mode').val();
|
|
||||||
|
|
||||||
if(sttProviderName != "Browser" & extension_settings.speech_recognition.messageMode == "auto_send") {
|
|
||||||
$("#speech_recognition_wait_response_div").show()
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
$("#speech_recognition_wait_response_div").hide()
|
|
||||||
}
|
|
||||||
|
|
||||||
saveSettingsDebounced();
|
|
||||||
}
|
|
||||||
|
|
||||||
async function onMessageMappingChange() {
|
|
||||||
let array = $('#speech_recognition_message_mapping').val().split(",");
|
|
||||||
array = array.map(element => {return element.trim();});
|
|
||||||
array = array.filter((str) => str !== '');
|
|
||||||
extension_settings.speech_recognition.messageMapping = {};
|
|
||||||
for (const text of array) {
|
|
||||||
if (text.includes("=")) {
|
|
||||||
const pair = text.toLowerCase().split("=")
|
|
||||||
extension_settings.speech_recognition.messageMapping[pair[0].trim()] = pair[1].trim()
|
|
||||||
console.debug(DEBUG_PREFIX+"Added mapping", pair[0],"=>", extension_settings.speech_recognition.messageMapping[pair[0]]);
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
console.debug(DEBUG_PREFIX+"Wrong syntax for message mapping, no '=' found in:", text);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
$("#speech_recognition_message_mapping_status").text("Message mapping updated to: "+JSON.stringify(extension_settings.speech_recognition.messageMapping))
|
|
||||||
console.debug(DEBUG_PREFIX+"Updated message mapping", extension_settings.speech_recognition.messageMapping);
|
|
||||||
extension_settings.speech_recognition.messageMappingText = $('#speech_recognition_message_mapping').val()
|
|
||||||
saveSettingsDebounced();
|
|
||||||
}
|
|
||||||
|
|
||||||
async function onMessageMappingEnabledClick() {
|
|
||||||
extension_settings.speech_recognition.messageMappingEnabled = $('#speech_recognition_message_mapping_enabled').is(':checked');
|
|
||||||
saveSettingsDebounced()
|
|
||||||
}
|
|
||||||
|
|
||||||
$(document).ready(function () {
|
|
||||||
function addExtensionControls() {
|
|
||||||
const settingsHtml = `
|
|
||||||
<div id="speech_recognition_settings">
|
|
||||||
<div class="inline-drawer">
|
|
||||||
<div class="inline-drawer-toggle inline-drawer-header">
|
|
||||||
<b>Speech Recognition</b>
|
|
||||||
<div class="inline-drawer-icon fa-solid fa-circle-chevron-down down"></div>
|
|
||||||
</div>
|
|
||||||
<div class="inline-drawer-content">
|
|
||||||
<div>
|
|
||||||
<span>Select Speech-to-text Provider</span> </br>
|
|
||||||
<select id="speech_recognition_provider">
|
|
||||||
</select>
|
|
||||||
</div>
|
|
||||||
<div id="speech_recognition_message_mode_div">
|
|
||||||
<span>Message Mode</span> </br>
|
|
||||||
<select id="speech_recognition_message_mode">
|
|
||||||
<option value="append">Append</option>
|
|
||||||
<option value="replace">Replace</option>
|
|
||||||
<option value="auto_send">Auto send</option>
|
|
||||||
</select>
|
|
||||||
</div>
|
|
||||||
<div id="speech_recognition_message_mapping_div">
|
|
||||||
<span>Message Mapping</span>
|
|
||||||
<textarea id="speech_recognition_message_mapping" class="text_pole textarea_compact" type="text" rows="4" placeholder="Enter comma separated phrases mapping, example:\ncommand delete = /del 2,\nslash delete = /del 2,\nsystem roll = /roll 2d6,\nhey continue = /continue"></textarea>
|
|
||||||
<span id="speech_recognition_message_mapping_status"></span>
|
|
||||||
<label class="checkbox_label" for="speech_recognition_message_mapping_enabled">
|
|
||||||
<input type="checkbox" id="speech_recognition_message_mapping_enabled" name="speech_recognition_message_mapping_enabled">
|
|
||||||
<small>Enable messages mapping</small>
|
|
||||||
</label>
|
|
||||||
</div>
|
|
||||||
<form id="speech_recognition_provider_settings" class="inline-drawer-content">
|
|
||||||
</form>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
`;
|
|
||||||
$('#extensions_settings').append(settingsHtml);
|
|
||||||
$('#speech_recognition_provider_settings').on('input', onSttProviderSettingsInput);
|
|
||||||
for (const provider in sttProviders) {
|
|
||||||
$('#speech_recognition_provider').append($("<option />").val(provider).text(provider));
|
|
||||||
console.debug(DEBUG_PREFIX+"added option "+provider);
|
|
||||||
}
|
|
||||||
$('#speech_recognition_provider').on('change', onSttProviderChange);
|
|
||||||
$('#speech_recognition_message_mode').on('change', onMessageModeChange);
|
|
||||||
$('#speech_recognition_message_mapping').on('change', onMessageMappingChange);
|
|
||||||
$('#speech_recognition_message_mapping_enabled').on('click', onMessageMappingEnabledClick);
|
|
||||||
|
|
||||||
const $button = $('<div id="microphone_button" class="fa-solid fa-microphone speech-toggle" title="Click to speak"></div>');
|
|
||||||
$('#send_but_sheld').prepend($button);
|
|
||||||
|
|
||||||
}
|
|
||||||
addExtensionControls(); // No init dependencies
|
|
||||||
loadSettings(); // Depends on Extension Controls and loadTtsProvider
|
|
||||||
loadSttProvider(extension_settings.speech_recognition.currentProvider); // No dependencies
|
|
||||||
const wrapper = new ModuleWorkerWrapper(moduleWorker);
|
|
||||||
setInterval(wrapper.update.bind(wrapper), UPDATE_INTERVAL); // Init depends on all the things
|
|
||||||
moduleWorker();
|
|
||||||
})
|
|
@@ -1,14 +0,0 @@
|
|||||||
{
|
|
||||||
"display_name": "Speech Recognition",
|
|
||||||
"loading_order": 13,
|
|
||||||
"requires": [],
|
|
||||||
"optional": [
|
|
||||||
"vosk-speech-recognition",
|
|
||||||
"whisper-speech-recognition"
|
|
||||||
],
|
|
||||||
"js": "index.js",
|
|
||||||
"css": "style.css",
|
|
||||||
"author": "Cohee#1207 and Keij#6799",
|
|
||||||
"version": "1.1.0",
|
|
||||||
"homePage": "https://github.com/SillyTavern/SillyTavern"
|
|
||||||
}
|
|
@@ -1,109 +0,0 @@
|
|||||||
import { getApiUrl, doExtrasFetch, modules } from "../../extensions.js";
|
|
||||||
export { StreamingSttProvider }
|
|
||||||
|
|
||||||
const DEBUG_PREFIX = "<Speech Recognition module (streaming)> "
|
|
||||||
|
|
||||||
class StreamingSttProvider {
|
|
||||||
//########//
|
|
||||||
// Config //
|
|
||||||
//########//
|
|
||||||
|
|
||||||
settings
|
|
||||||
|
|
||||||
defaultSettings = {
|
|
||||||
triggerWordsText: "",
|
|
||||||
triggerWords : [],
|
|
||||||
triggerWordsEnabled : false,
|
|
||||||
triggerWordsIncluded: false,
|
|
||||||
debug : false,
|
|
||||||
}
|
|
||||||
|
|
||||||
get settingsHtml() {
|
|
||||||
let html = '\
|
|
||||||
<div id="speech_recognition_streaming_trigger_words_div">\
|
|
||||||
<span>Trigger words</span>\
|
|
||||||
<textarea id="speech_recognition_streaming_trigger_words" class="text_pole textarea_compact" type="text" rows="4" placeholder="Enter comma separated words that triggers new message, example:\nhey, hey aqua, record, listen"></textarea>\
|
|
||||||
<label class="checkbox_label" for="speech_recognition_streaming_trigger_words_enabled">\
|
|
||||||
<input type="checkbox" id="speech_recognition_streaming_trigger_words_enabled" name="speech_recognition_trigger_words_enabled">\
|
|
||||||
<small>Enable trigger words</small>\
|
|
||||||
</label>\
|
|
||||||
<label class="checkbox_label" for="speech_recognition_trigger_words_included">\
|
|
||||||
<input type="checkbox" id="speech_recognition_trigger_words_included" name="speech_recognition_trigger_words_included">\
|
|
||||||
<small>Include trigger words in message</small>\
|
|
||||||
</label>\
|
|
||||||
<label class="checkbox_label" for="speech_recognition_streaming_debug">\
|
|
||||||
<input type="checkbox" id="speech_recognition_streaming_debug" name="speech_recognition_streaming_debug">\
|
|
||||||
<small>Enable debug pop ups</small>\
|
|
||||||
</label>\
|
|
||||||
</div>\
|
|
||||||
'
|
|
||||||
return html
|
|
||||||
}
|
|
||||||
|
|
||||||
onSettingsChange() {
|
|
||||||
this.settings.triggerWordsText = $('#speech_recognition_streaming_trigger_words').val();
|
|
||||||
let array = $('#speech_recognition_streaming_trigger_words').val().split(",");
|
|
||||||
array = array.map(element => {return element.trim().toLowerCase();});
|
|
||||||
array = array.filter((str) => str !== '');
|
|
||||||
this.settings.triggerWords = array;
|
|
||||||
this.settings.triggerWordsEnabled = $("#speech_recognition_streaming_trigger_words_enabled").is(':checked');
|
|
||||||
this.settings.triggerWordsIncluded = $("#speech_recognition_trigger_words_included").is(':checked');
|
|
||||||
this.settings.debug = $("#speech_recognition_streaming_debug").is(':checked');
|
|
||||||
console.debug(DEBUG_PREFIX+" Updated settings: ", this.settings);
|
|
||||||
this.loadSettings(this.settings);
|
|
||||||
}
|
|
||||||
|
|
||||||
loadSettings(settings) {
|
|
||||||
// Populate Provider UI given input settings
|
|
||||||
if (Object.keys(settings).length == 0) {
|
|
||||||
console.debug(DEBUG_PREFIX+"Using default Whisper STT extension settings")
|
|
||||||
}
|
|
||||||
|
|
||||||
// Only accept keys defined in defaultSettings
|
|
||||||
this.settings = this.defaultSettings
|
|
||||||
|
|
||||||
for (const key in settings){
|
|
||||||
if (key in this.settings){
|
|
||||||
this.settings[key] = settings[key]
|
|
||||||
} else {
|
|
||||||
throw `Invalid setting passed to STT extension: ${key}`
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
$("#speech_recognition_streaming_trigger_words").val(this.settings.triggerWordsText);
|
|
||||||
$("#speech_recognition_streaming_trigger_words_enabled").prop('checked',this.settings.triggerWordsEnabled);
|
|
||||||
$("#speech_recognition_trigger_words_included").prop('checked',this.settings.triggerWordsIncluded);
|
|
||||||
$("#speech_recognition_streaming_debug").prop('checked',this.settings.debug);
|
|
||||||
|
|
||||||
console.debug(DEBUG_PREFIX+"streaming STT settings loaded")
|
|
||||||
}
|
|
||||||
|
|
||||||
async getUserMessage() {
|
|
||||||
// Return if module is not loaded
|
|
||||||
if (!modules.includes('streaming-stt')) {
|
|
||||||
console.debug(DEBUG_PREFIX+"Module streaming-stt must be activated in Sillytavern Extras for streaming user voice.")
|
|
||||||
return "";
|
|
||||||
}
|
|
||||||
|
|
||||||
const url = new URL(getApiUrl());
|
|
||||||
url.pathname = '/api/speech-recognition/streaming/record-and-transcript';
|
|
||||||
|
|
||||||
const apiResult = await doExtrasFetch(url, {
|
|
||||||
method: 'POST',
|
|
||||||
headers: {
|
|
||||||
'Content-Type': 'application/json',
|
|
||||||
'Bypass-Tunnel-Reminder': 'bypass',
|
|
||||||
},
|
|
||||||
body: JSON.stringify({ text: "" }),
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!apiResult.ok) {
|
|
||||||
toastr.error(apiResult.statusText, DEBUG_PREFIX+'STT Generation Failed (streaming)', { timeOut: 10000, extendedTimeOut: 20000, preventDuplicates: true });
|
|
||||||
throw new Error(`HTTP ${apiResult.status}: ${await apiResult.text()}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
const data = await apiResult.json();
|
|
||||||
return data.transcript;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@@ -1,3 +0,0 @@
|
|||||||
.speech-toggle {
|
|
||||||
display: flex;
|
|
||||||
}
|
|
@@ -1,65 +0,0 @@
|
|||||||
import { getApiUrl, doExtrasFetch } from "../../extensions.js";
|
|
||||||
export { VoskSttProvider }
|
|
||||||
|
|
||||||
const DEBUG_PREFIX = "<Speech Recognition module (Vosk)> "
|
|
||||||
|
|
||||||
class VoskSttProvider {
|
|
||||||
//########//
|
|
||||||
// Config //
|
|
||||||
//########//
|
|
||||||
|
|
||||||
settings
|
|
||||||
|
|
||||||
defaultSettings = {
|
|
||||||
}
|
|
||||||
|
|
||||||
get settingsHtml() {
|
|
||||||
let html = ""
|
|
||||||
return html
|
|
||||||
}
|
|
||||||
|
|
||||||
onSettingsChange() {
|
|
||||||
// Used when provider settings are updated from UI
|
|
||||||
}
|
|
||||||
|
|
||||||
loadSettings(settings) {
|
|
||||||
// Populate Provider UI given input settings
|
|
||||||
if (Object.keys(settings).length == 0) {
|
|
||||||
console.debug(DEBUG_PREFIX+"Using default vosk STT extension settings")
|
|
||||||
}
|
|
||||||
|
|
||||||
// Only accept keys defined in defaultSettings
|
|
||||||
this.settings = this.defaultSettings
|
|
||||||
|
|
||||||
for (const key in settings){
|
|
||||||
if (key in this.settings){
|
|
||||||
this.settings[key] = settings[key]
|
|
||||||
} else {
|
|
||||||
throw `Invalid setting passed to STT extension: ${key}`
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
console.debug(DEBUG_PREFIX+"Vosk STT settings loaded")
|
|
||||||
}
|
|
||||||
|
|
||||||
async processAudio(audioblob) {
|
|
||||||
var requestData = new FormData();
|
|
||||||
requestData.append('AudioFile', audioblob, 'record.wav');
|
|
||||||
|
|
||||||
const url = new URL(getApiUrl());
|
|
||||||
url.pathname = '/api/speech-recognition/vosk/process-audio';
|
|
||||||
|
|
||||||
const apiResult = await doExtrasFetch(url, {
|
|
||||||
method: 'POST',
|
|
||||||
body: requestData,
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!apiResult.ok) {
|
|
||||||
toastr.error(apiResult.statusText, 'STT Generation Failed (Vosk)', { timeOut: 10000, extendedTimeOut: 20000, preventDuplicates: true });
|
|
||||||
throw new Error(`HTTP ${apiResult.status}: ${await apiResult.text()}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
const result = await apiResult.json();
|
|
||||||
return result.transcript;
|
|
||||||
}
|
|
||||||
}
|
|
@@ -1,67 +0,0 @@
|
|||||||
import { getApiUrl, doExtrasFetch } from "../../extensions.js";
|
|
||||||
export { WhisperSttProvider }
|
|
||||||
|
|
||||||
const DEBUG_PREFIX = "<Speech Recognition module (Vosk)> "
|
|
||||||
|
|
||||||
class WhisperSttProvider {
|
|
||||||
//########//
|
|
||||||
// Config //
|
|
||||||
//########//
|
|
||||||
|
|
||||||
settings
|
|
||||||
|
|
||||||
defaultSettings = {
|
|
||||||
//model_path: "",
|
|
||||||
}
|
|
||||||
|
|
||||||
get settingsHtml() {
|
|
||||||
let html = ""
|
|
||||||
return html
|
|
||||||
}
|
|
||||||
|
|
||||||
onSettingsChange() {
|
|
||||||
// Used when provider settings are updated from UI
|
|
||||||
}
|
|
||||||
|
|
||||||
loadSettings(settings) {
|
|
||||||
// Populate Provider UI given input settings
|
|
||||||
if (Object.keys(settings).length == 0) {
|
|
||||||
console.debug(DEBUG_PREFIX+"Using default Whisper STT extension settings")
|
|
||||||
}
|
|
||||||
|
|
||||||
// Only accept keys defined in defaultSettings
|
|
||||||
this.settings = this.defaultSettings
|
|
||||||
|
|
||||||
for (const key in settings){
|
|
||||||
if (key in this.settings){
|
|
||||||
this.settings[key] = settings[key]
|
|
||||||
} else {
|
|
||||||
throw `Invalid setting passed to STT extension: ${key}`
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
console.debug(DEBUG_PREFIX+"Whisper STT settings loaded")
|
|
||||||
}
|
|
||||||
|
|
||||||
async processAudio(audioblob) {
|
|
||||||
var requestData = new FormData();
|
|
||||||
requestData.append('AudioFile', audioblob, 'record.wav');
|
|
||||||
|
|
||||||
const url = new URL(getApiUrl());
|
|
||||||
url.pathname = '/api/speech-recognition/whisper/process-audio';
|
|
||||||
|
|
||||||
const apiResult = await doExtrasFetch(url, {
|
|
||||||
method: 'POST',
|
|
||||||
body: requestData,
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!apiResult.ok) {
|
|
||||||
toastr.error(apiResult.statusText, 'STT Generation Failed (Whisper)', { timeOut: 10000, extendedTimeOut: 20000, preventDuplicates: true });
|
|
||||||
throw new Error(`HTTP ${apiResult.status}: ${await apiResult.text()}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
const result = await apiResult.json();
|
|
||||||
return result.transcript;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@@ -1555,11 +1555,14 @@ function doCurMemberListPopout() {
|
|||||||
<div id="groupMemberListPopoutClose" class="fa-solid fa-circle-xmark hoverglow"></div>
|
<div id="groupMemberListPopoutClose" class="fa-solid fa-circle-xmark hoverglow"></div>
|
||||||
</div>`
|
</div>`
|
||||||
const newElement = $(template);
|
const newElement = $(template);
|
||||||
newElement.attr('id', 'groupMemberListPopout');
|
|
||||||
newElement.removeClass('zoomed_avatar')
|
|
||||||
newElement.empty()
|
|
||||||
|
|
||||||
newElement.append(controlBarHtml).append(memberListClone)
|
newElement.attr('id', 'groupMemberListPopout')
|
||||||
|
.removeClass('zoomed_avatar')
|
||||||
|
.addClass('draggable')
|
||||||
|
.empty()
|
||||||
|
.append(controlBarHtml)
|
||||||
|
.append(memberListClone)
|
||||||
|
|
||||||
$('body').append(newElement);
|
$('body').append(newElement);
|
||||||
loadMovingUIState();
|
loadMovingUIState();
|
||||||
$("#groupMemberListPopout").fadeIn(250)
|
$("#groupMemberListPopout").fadeIn(250)
|
||||||
|
@@ -1413,12 +1413,14 @@ async function resetMovablePanels(type) {
|
|||||||
'floatingPrompt',
|
'floatingPrompt',
|
||||||
'expression-holder',
|
'expression-holder',
|
||||||
'groupMemberListPopout',
|
'groupMemberListPopout',
|
||||||
'summaryExtensionPopout'
|
'summaryExtensionPopout',
|
||||||
|
'gallery'
|
||||||
];
|
];
|
||||||
|
|
||||||
const panelStyles = ['top', 'left', 'right', 'bottom', 'height', 'width', 'margin',];
|
const panelStyles = ['top', 'left', 'right', 'bottom', 'height', 'width', 'margin',];
|
||||||
|
|
||||||
panelIds.forEach((id) => {
|
panelIds.forEach((id) => {
|
||||||
|
console.log(id)
|
||||||
const panel = document.getElementById(id);
|
const panel = document.getElementById(id);
|
||||||
|
|
||||||
if (panel) {
|
if (panel) {
|
||||||
|
@@ -1,6 +1,6 @@
|
|||||||
import { saveSettings, callPopup, substituteParams, getRequestHeaders, chat_metadata, this_chid, characters, saveCharacterDebounced, menu_type, eventSource, event_types } from "../script.js";
|
import { saveSettings, callPopup, substituteParams, getRequestHeaders, chat_metadata, this_chid, characters, saveCharacterDebounced, menu_type, eventSource, event_types, getExtensionPrompt, MAX_INJECTION_DEPTH, extension_prompt_types, getExtensionPromptByName } from "../script.js";
|
||||||
import { download, debounce, initScrollHeight, resetScrollHeight, parseJsonFile, extractDataFromPng, getFileBuffer, getCharaFilename, getSortableDelay, escapeRegex, PAGINATION_TEMPLATE, navigation_option, waitUntilCondition } from "./utils.js";
|
import { download, debounce, initScrollHeight, resetScrollHeight, parseJsonFile, extractDataFromPng, getFileBuffer, getCharaFilename, getSortableDelay, escapeRegex, PAGINATION_TEMPLATE, navigation_option, waitUntilCondition } from "./utils.js";
|
||||||
import { getContext } from "./extensions.js";
|
import { extension_settings, getContext } from "./extensions.js";
|
||||||
import { NOTE_MODULE_NAME, metadata_keys, shouldWIAddPrompt } from "./authors-note.js";
|
import { NOTE_MODULE_NAME, metadata_keys, shouldWIAddPrompt } from "./authors-note.js";
|
||||||
import { registerSlashCommand } from "./slash-commands.js";
|
import { registerSlashCommand } from "./slash-commands.js";
|
||||||
import { getDeviceInfo } from "./RossAscends-mods.js";
|
import { getDeviceInfo } from "./RossAscends-mods.js";
|
||||||
@@ -581,8 +581,8 @@ function getWorldEntry(name, data, entry) {
|
|||||||
data.entries[uid].selectiveLogic = !isNaN(value) ? value : 0;
|
data.entries[uid].selectiveLogic = !isNaN(value) ? value : 0;
|
||||||
setOriginalDataValue(data, uid, "selectiveLogic", data.entries[uid].selectiveLogic);
|
setOriginalDataValue(data, uid, "selectiveLogic", data.entries[uid].selectiveLogic);
|
||||||
saveWorldInfo(name, data);
|
saveWorldInfo(name, data);
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
template
|
template
|
||||||
.find(`select[name="entryLogicType"] option[value=${entry.selectiveLogic}]`)
|
.find(`select[name="entryLogicType"] option[value=${entry.selectiveLogic}]`)
|
||||||
.prop("selected", true)
|
.prop("selected", true)
|
||||||
@@ -1341,7 +1341,27 @@ async function getSortedEntries() {
|
|||||||
async function checkWorldInfo(chat, maxContext) {
|
async function checkWorldInfo(chat, maxContext) {
|
||||||
const context = getContext();
|
const context = getContext();
|
||||||
const messagesToLookBack = world_info_depth * 2 || 1;
|
const messagesToLookBack = world_info_depth * 2 || 1;
|
||||||
let textToScan = transformString(chat.slice(0, messagesToLookBack).join(""));
|
|
||||||
|
// Combine the chat
|
||||||
|
let textToScan = chat.slice(0, messagesToLookBack).join("");
|
||||||
|
|
||||||
|
// Add the depth or AN if enabled
|
||||||
|
// Put this code here since otherwise, the chat reference is modified
|
||||||
|
if (extension_settings.note.allowWIScan) {
|
||||||
|
let depthPrompt = getExtensionPromptByName("DEPTH_PROMPT")
|
||||||
|
if (depthPrompt) {
|
||||||
|
textToScan = `${depthPrompt}\n${textToScan}`
|
||||||
|
}
|
||||||
|
|
||||||
|
let anPrompt = getExtensionPromptByName(NOTE_MODULE_NAME);
|
||||||
|
if (anPrompt) {
|
||||||
|
textToScan = `${anPrompt}\n${textToScan}`
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Transform the resulting string
|
||||||
|
textToScan = transformString(textToScan);
|
||||||
|
|
||||||
let needsToScan = true;
|
let needsToScan = true;
|
||||||
let count = 0;
|
let count = 0;
|
||||||
let allActivatedEntries = new Set();
|
let allActivatedEntries = new Set();
|
||||||
|
103
public/style.css
103
public/style.css
@@ -346,11 +346,12 @@ code {
|
|||||||
|
|
||||||
|
|
||||||
hr {
|
hr {
|
||||||
background-image: linear-gradient(90deg, var(--transparent), var(--SmartThemeBorderColor), var(--transparent));
|
background-image: linear-gradient(90deg, var(--transparent), var(--SmartThemeBodyColor), var(--transparent));
|
||||||
margin: 5px 0;
|
margin: 5px 0;
|
||||||
height: 1px;
|
height: 1px;
|
||||||
min-height: 1px;
|
min-height: 1px;
|
||||||
border: 0;
|
border: 0;
|
||||||
|
opacity: 0.2;
|
||||||
}
|
}
|
||||||
|
|
||||||
#bg1,
|
#bg1,
|
||||||
@@ -389,7 +390,7 @@ hr {
|
|||||||
display: inline-block;
|
display: inline-block;
|
||||||
height: var(--bottomFormBlockSize);
|
height: var(--bottomFormBlockSize);
|
||||||
position: absolute;
|
position: absolute;
|
||||||
border-bottom: 1px solid var(--SmartThemeBorderColor);
|
/* border-bottom: 1px solid var(--SmartThemeBorderColor); */
|
||||||
box-shadow: 0 2px 20px 0 var(--black70a);
|
box-shadow: 0 2px 20px 0 var(--black70a);
|
||||||
backdrop-filter: blur(var(--SmartThemeBlurStrength));
|
backdrop-filter: blur(var(--SmartThemeBlurStrength));
|
||||||
background-color: var(--SmartThemeBlurTintColor);
|
background-color: var(--SmartThemeBlurTintColor);
|
||||||
@@ -421,23 +422,25 @@ hr {
|
|||||||
|
|
||||||
.drag-grabber {
|
.drag-grabber {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
width: 20px !important;
|
/* width: 20px !important;
|
||||||
height: 20px !important;
|
height: 20px !important; */
|
||||||
|
margin: 0px 5px;
|
||||||
color: var(--SmartThemeBodyColor);
|
color: var(--SmartThemeBodyColor);
|
||||||
z-index: 2000;
|
z-index: 2000;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
border-radius: 5px;
|
/* border-radius: 5px; */
|
||||||
vertical-align: middle;
|
vertical-align: middle;
|
||||||
right: 0px;
|
right: 0px;
|
||||||
top: 0px;
|
top: 0px;
|
||||||
opacity: 0.5;
|
opacity: 0.5;
|
||||||
cursor: grab;
|
cursor: grab;
|
||||||
border: 1px solid var(--SmartThemeBorderColor);
|
/* border: 1px solid var(--SmartThemeBorderColor); */
|
||||||
cursor: -moz-grab;
|
cursor: -moz-grab;
|
||||||
cursor: -webkit-grab;
|
cursor: -webkit-grab;
|
||||||
display: none;
|
display: none;
|
||||||
filter: drop-shadow(0px 0px 0px black);
|
filter: drop-shadow(0px 0px 0px black);
|
||||||
transition: all 250ms;
|
transition: all 250ms;
|
||||||
|
font-size: calc(var(--mainFontSize)*1.3);
|
||||||
}
|
}
|
||||||
|
|
||||||
.drag-grabber:hover {
|
.drag-grabber:hover {
|
||||||
@@ -480,9 +483,9 @@ hr {
|
|||||||
overflow-y: scroll;
|
overflow-y: scroll;
|
||||||
display: flex;
|
display: flex;
|
||||||
bottom: 10px;
|
bottom: 10px;
|
||||||
border-bottom: 1px solid var(--SmartThemeBorderColor);
|
/* border-bottom: 1px solid var(--SmartThemeBorderColor);
|
||||||
border-left: 1px solid var(--SmartThemeBorderColor);
|
border-left: 1px solid var(--SmartThemeBorderColor);
|
||||||
border-right: 1px solid var(--SmartThemeBorderColor);
|
border-right: 1px solid var(--SmartThemeBorderColor); */
|
||||||
backdrop-filter: blur(var(--SmartThemeBlurStrength));
|
backdrop-filter: blur(var(--SmartThemeBlurStrength));
|
||||||
background-color: var(--SmartThemeChatTintColor);
|
background-color: var(--SmartThemeChatTintColor);
|
||||||
-webkit-backdrop-filter: blur(var(--SmartThemeBlurStrength));
|
-webkit-backdrop-filter: blur(var(--SmartThemeBlurStrength));
|
||||||
@@ -959,10 +962,10 @@ select {
|
|||||||
#character_cross,
|
#character_cross,
|
||||||
#select_chat_cross {
|
#select_chat_cross {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
right: 15px;
|
right: 5px;
|
||||||
top: 15px;
|
top: 5px;
|
||||||
width: 20px;
|
/* width: 20px;
|
||||||
height: 20px;
|
height: 20px; */
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
opacity: 0.6;
|
opacity: 0.6;
|
||||||
font-size: 24px;
|
font-size: 24px;
|
||||||
@@ -1251,9 +1254,11 @@ input[type="file"] {
|
|||||||
.dragClose {
|
.dragClose {
|
||||||
height: 15px;
|
height: 15px;
|
||||||
aspect-ratio: 1 / 1;
|
aspect-ratio: 1 / 1;
|
||||||
font-size: 20px;
|
font-size: calc(var(--mainFontSize)*1.3);
|
||||||
opacity: 0.5;
|
opacity: 0.5;
|
||||||
transition: all 250ms;
|
transition: all 250ms;
|
||||||
|
filter: drop-shadow(0px 0px 2px black);
|
||||||
|
text-shadow: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
.dragClose:hover {
|
.dragClose:hover {
|
||||||
@@ -1604,11 +1609,6 @@ input[type=search]:focus::-webkit-search-cancel-button {
|
|||||||
align-items: center;
|
align-items: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
#user-settings-block h4,
|
|
||||||
#user-settings-block h3 {
|
|
||||||
margin: 5px 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
#avatar-and-name-block {
|
#avatar-and-name-block {
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
display: flex;
|
display: flex;
|
||||||
@@ -3409,51 +3409,38 @@ a {
|
|||||||
color: var(--SmartThemeBodyColor);
|
color: var(--SmartThemeBodyColor);
|
||||||
}
|
}
|
||||||
|
|
||||||
.zoomed_avatar,
|
|
||||||
#groupMemberListPopout,
|
|
||||||
#summaryExtensionPopout {
|
|
||||||
min-width: 100px;
|
|
||||||
min-height: 100px;
|
|
||||||
max-height: 90vh;
|
|
||||||
max-width: 90vh;
|
|
||||||
width: clamp(100px, 400px, calc((100vw - var(--sheldWidth)) /2));
|
|
||||||
/* width: calc((100vw - var(--sheldWidth)) /2); */
|
|
||||||
position: absolute;
|
|
||||||
padding: 0;
|
|
||||||
/* filter: drop-shadow(2px 2px 2px var(--grey7070a)); */
|
|
||||||
box-shadow: 1px 2px 2px var(--black30a);
|
|
||||||
z-index: 29;
|
|
||||||
overflow: hidden;
|
|
||||||
display: none;
|
|
||||||
bottom: 0;
|
|
||||||
border-radius: 10px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.zoomed_avatar {
|
|
||||||
aspect-ratio: 2 / 3;
|
|
||||||
}
|
|
||||||
|
|
||||||
#groupMemberListPopout,
|
|
||||||
#summaryExtensionPopout {
|
|
||||||
border: 1px solid var(--SmartThemeBorderColor);
|
|
||||||
padding: 5px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.draggable {
|
.draggable {
|
||||||
min-width: 100px;
|
min-width: 100px;
|
||||||
min-height: 100px;
|
min-height: 100px;
|
||||||
max-height: 90vh;
|
max-height: 90vh;
|
||||||
max-width: 90vh;
|
max-width: 90vw;
|
||||||
width: clamp(100px, 400px, calc((100vw - var(--sheldWidth)) /2));
|
width: calc((100vw - var(--sheldWidth)) /2);
|
||||||
position: absolute;
|
position: absolute;
|
||||||
padding: 0;
|
padding: 0;
|
||||||
filter: drop-shadow(2px 2px 2px var(--grey7070a));
|
filter: drop-shadow(1px 1px 2px var(--black50a));
|
||||||
z-index: 29;
|
z-index: 29;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
display: none;
|
display: none;
|
||||||
bottom: 0;
|
bottom: 0;
|
||||||
aspect-ratio: 2 / 3;
|
|
||||||
border-radius: 10px;
|
border-radius: 10px;
|
||||||
|
border: 1px solid var(--SmartThemeBorderColor);
|
||||||
|
aspect-ratio: unset;
|
||||||
|
backdrop-filter: blur(var(--SmartThemeBlurStrength));
|
||||||
|
background-color: var(--SmartThemeBlurTintColor);
|
||||||
|
padding: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.zoomed_avatar {
|
||||||
|
aspect-ratio: 2 / 3;
|
||||||
|
padding: 0;
|
||||||
|
border: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.zoomed_avatar img {
|
||||||
|
height: 100%;
|
||||||
|
width: 100%;
|
||||||
|
vertical-align: bottom;
|
||||||
|
object-fit: cover;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Hide scrollbar for Chrome, Safari and Opera */
|
/* Hide scrollbar for Chrome, Safari and Opera */
|
||||||
@@ -3477,13 +3464,13 @@ a {
|
|||||||
transition: all 250ms;
|
transition: all 250ms;
|
||||||
}
|
}
|
||||||
|
|
||||||
#groupMemberListPopout,
|
/* #groupMemberListPopout,
|
||||||
#summaryExtensionPopout {
|
#summaryExtensionPopout {
|
||||||
aspect-ratio: unset;
|
aspect-ratio: unset;
|
||||||
backdrop-filter: blur(var(--SmartThemeBlurStrength));
|
backdrop-filter: blur(var(--SmartThemeBlurStrength));
|
||||||
background-color: var(--SmartThemeBlurTintColor);
|
background-color: var(--SmartThemeBlurTintColor);
|
||||||
overflow: auto;
|
overflow: auto;
|
||||||
}
|
} */
|
||||||
|
|
||||||
#groupMemberListPopout {
|
#groupMemberListPopout {
|
||||||
padding: 0;
|
padding: 0;
|
||||||
@@ -3497,13 +3484,7 @@ a {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.zoomed_avatar img {
|
|
||||||
border-radius: 20px;
|
|
||||||
height: 100%;
|
|
||||||
width: 100%;
|
|
||||||
vertical-align: bottom;
|
|
||||||
object-fit: cover;
|
|
||||||
}
|
|
||||||
|
|
||||||
.timestamp {
|
.timestamp {
|
||||||
font-size: calc(var(--mainFontSize) * 0.7);
|
font-size: calc(var(--mainFontSize) * 0.7);
|
||||||
|
@@ -627,6 +627,9 @@ app.post("/generate_textgenerationwebui", jsonParser, async function (request, r
|
|||||||
yield message.text;
|
yield message.text;
|
||||||
break;
|
break;
|
||||||
case 'stream_end':
|
case 'stream_end':
|
||||||
|
if (message.error) {
|
||||||
|
yield `\n[API Error] ${message.error}\n`
|
||||||
|
}
|
||||||
websocket.close();
|
websocket.close();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
Reference in New Issue
Block a user