This commit is contained in:
RossAscends
2023-06-06 01:59:41 +09:00
14 changed files with 464 additions and 60 deletions

View File

@@ -1382,7 +1382,7 @@
</h4> </h4>
<small class="horde_multiple_hint">You can select multiple models.<br>Avoid sending <small class="horde_multiple_hint">You can select multiple models.<br>Avoid sending
sensitive information to the Horde. <a id="horde_privacy_disclaimer" target="_blank" sensitive information to the Horde. <a id="horde_privacy_disclaimer" target="_blank"
href="/notes#horde">Learn more</a></small> href="https://docs.sillytavern.app/usage/guidebook/#horde">Learn more</a></small>
<select id="horde_model" multiple> <select id="horde_model" multiple>
<option>-- Horde models not loaded --</option> <option>-- Horde models not loaded --</option>
</select> </select>
@@ -1416,7 +1416,7 @@
<span> <span>
<ol> <ol>
<li> <li>
<span data-i18n="Follow">Follow</span> <a href="/notes#apikey" <span data-i18n="Follow">Follow</span> <a href="https://docs.sillytavern.app/usage/guidebook/#api-key"
class="notes-link" target="_blank"> <span data-i18n="these directions">these class="notes-link" target="_blank"> <span data-i18n="these directions">these
directions</span> </a> <span data-i18n="to get your NovelAI API key.">to directions</span> </a> <span data-i18n="to get your NovelAI API key.">to
get your NovelAI API key.</span> get your NovelAI API key.</span>
@@ -1437,7 +1437,7 @@
<input id="api_button_novel" class="menu_button" type="submit" value="Connect"> <input id="api_button_novel" class="menu_button" type="submit" value="Connect">
<div id="api_loading_novel" class="api-load-icon fa-solid fa-hourglass fa-spin"></div> <div id="api_loading_novel" class="api-load-icon fa-solid fa-hourglass fa-spin"></div>
<h4><span data-i18n="Novel AI Model">Novel AI Model</span> <h4><span data-i18n="Novel AI Model">Novel AI Model</span>
<a href="/notes#models" class="notes-link" target="_blank"> <a href="https://docs.sillytavern.app/usage/guidebook/#models" class="notes-link" target="_blank">
<span class="note-link-span">?</span> <span class="note-link-span">?</span>
</a> </a>
</h4> </h4>
@@ -1490,7 +1490,7 @@
<label for="use_window_ai" class="checkbox_label"> <label for="use_window_ai" class="checkbox_label">
<input id="use_window_ai" type="checkbox" /> <input id="use_window_ai" type="checkbox" />
Use Window.ai Use Window.ai
<a href="/notes#windowai" class="notes-link" target="_blank"> <a href="https://docs.sillytavern.app/usage/guidebook/#windowai" class="notes-link" target="_blank">
<span class="note-link-span">?</span> <span class="note-link-span">?</span>
</a> </a>
</label> </label>
@@ -1545,8 +1545,7 @@
<span> <span>
<ol> <ol>
<li> <li>
Follow<a href="/notes#apikey-2" class="notes-link" target="_blank"> these directions Follow <a href="https://docs.sillytavern.app/usage/guidebook/#api-key-2" class="notes-link" target="_blank">these directions</a> to get your 'p-b cookie'
</a> to get your 'p-b cookie'
</li> </li>
<li>Enter it in the box below:</li> <li>Enter it in the box below:</li>
</ol> </ol>
@@ -1736,6 +1735,8 @@
<option value="1">GPT-3 (OpenAI)</option> <option value="1">GPT-3 (OpenAI)</option>
<option value="2">GPT-3 (Alternative / Classic)</option> <option value="2">GPT-3 (Alternative / Classic)</option>
<option value="3">Sentencepiece (LLaMA)</option> <option value="3">Sentencepiece (LLaMA)</option>
<option value="4">NerdStash (NovelAI Krake)</option>
<option value="5">NerdStash v2 (NovelAI Clio)</option>
</select> </select>
</div> </div>
<div class="range-block"> <div class="range-block">

View File

@@ -119,7 +119,6 @@ import {
end_trim_to_sentence, end_trim_to_sentence,
countOccurrences, countOccurrences,
isOdd, isOdd,
isElementInViewport,
sortMoments, sortMoments,
timestampToMoment, timestampToMoment,
download, download,
@@ -481,11 +480,23 @@ function getTokenCount(str, padding = undefined) {
case tokenizers.CLASSIC: case tokenizers.CLASSIC:
return encode(str).length + padding; return encode(str).length + padding;
case tokenizers.LLAMA: case tokenizers.LLAMA:
return countTokensRemote('/tokenize_llama', str, padding);
case tokenizers.NERD:
return countTokensRemote('/tokenize_nerdstash', str, padding);
case tokenizers.NERD2:
return countTokensRemote('/tokenize_nerdstash_v2', str, padding);
default:
console.warn("Unknown tokenizer type", tokenizerType);
return Math.ceil(str.length / CHARACTERS_PER_TOKEN_RATIO) + padding;
}
}
function countTokensRemote(endpoint, str, padding) {
let tokenCount = 0; let tokenCount = 0;
jQuery.ajax({ jQuery.ajax({
async: false, async: false,
type: 'POST', // type: 'POST',
url: `/tokenize_llama`, url: endpoint,
data: JSON.stringify({ text: str }), data: JSON.stringify({ text: str }),
dataType: "json", dataType: "json",
contentType: "application/json", contentType: "application/json",
@@ -495,7 +506,6 @@ function getTokenCount(str, padding = undefined) {
}); });
return tokenCount + padding; return tokenCount + padding;
} }
}
function reloadMarkdownProcessor(render_formulas = false) { function reloadMarkdownProcessor(render_formulas = false) {
if (render_formulas) { if (render_formulas) {
@@ -2589,12 +2599,14 @@ function getMaxContextSize() {
} else { } else {
this_max_context = Number(max_context); this_max_context = Number(max_context);
if (nai_settings.model_novel == 'krake-v2') { if (nai_settings.model_novel == 'krake-v2') {
this_max_context -= 160; // Krake has a max context of 2048
// Should be used with nerdstash tokenizer for best results
this_max_context = Math.min(max_context, 2048);
} }
if (nai_settings.model_novel == 'clio-v1') { if (nai_settings.model_novel == 'clio-v1') {
// Clio has a max context of 8192 // Clio has a max context of 8192
// TODO: Evaluate the relevance of nerdstash-v1 tokenizer, changes quite a bit. // Should be used with nerdstash_v2 tokenizer for best results
this_max_context = 8192 - 60 - 160; this_max_context = Math.min(max_context, 8192);
} }
} }
} }
@@ -3397,7 +3409,15 @@ async function renameCharacter() {
// Also rename as a group member // Also rename as a group member
await renameGroupMember(oldAvatar, newAvatar, newValue); await renameGroupMember(oldAvatar, newAvatar, newValue);
callPopup('<h3>Character renamed!</h3>Sprites folder (if any) should be renamed manually.', 'text'); const renamePastChatsConfirm = await callPopup(`<h3>Character renamed!</h3>
<p>Past chats will still contain the old character name. Would you like to update the character name in previous chats as well?</p>
<i><b>Sprites folder (if any) should be renamed manually.</b></i>`, 'confirm');
if (renamePastChatsConfirm) {
await renamePastChats(newAvatar, newValue);
await reloadCurrentChat();
toastr.success('Character renamed and past chats updated!');
}
} }
else { else {
throw new Error('Newly renamed character was lost?'); throw new Error('Newly renamed character was lost?');
@@ -3415,6 +3435,59 @@ async function renameCharacter() {
} }
} }
async function renamePastChats(newAvatar, newValue) {
const pastChats = await getPastCharacterChats();
for (const { file_name } of pastChats) {
try {
const fileNameWithoutExtension = file_name.replace('.jsonl', '');
const getChatResponse = await fetch('/getchat', {
method: 'POST',
headers: getRequestHeaders(),
body: JSON.stringify({
ch_name: newValue,
file_name: fileNameWithoutExtension,
avatar_url: newAvatar,
}),
cache: 'no-cache',
});
if (getChatResponse.ok) {
const currentChat = await getChatResponse.json();
for (const message of currentChat) {
if (message.is_user || message.is_system || message.extra?.type == system_message_types.NARRATOR) {
continue;
}
if (message.name !== undefined) {
message.name = newValue;
}
}
const saveChatResponse = await fetch('/savechat', {
method: "POST",
headers: getRequestHeaders(),
body: JSON.stringify({
ch_name: newValue,
file_name: fileNameWithoutExtension,
chat: currentChat,
avatar_url: newAvatar,
}),
cache: 'no-cache',
});
if (!saveChatResponse.ok) {
throw new Error('Could not save chat');
}
}
} catch (error) {
toastr.error(`Past chat could not be updated: ${file_name}`);
console.error(error);
}
}
}
async function saveChat(chat_name, withMetadata) { async function saveChat(chat_name, withMetadata) {
const metadata = { ...chat_metadata, ...(withMetadata || {}) }; const metadata = { ...chat_metadata, ...(withMetadata || {}) };
let file_name = chat_name ?? characters[this_chid].chat; let file_name = chat_name ?? characters[this_chid].chat;

View File

@@ -0,0 +1,110 @@
// Borrowed from Agnai (AGPLv3)
// https://github.com/agnaistic/agnai/blob/dev/web/pages/Chat/components/SpeechRecognitionRecorder.tsx
function 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;
}
function composeValues(previous, interim) {
let spacing = '';
if (previous.endsWith('.')) spacing = ' ';
return previous + spacing + interim;
}
(function ($) {
$.fn.speechRecognitionPlugin = function (options) {
const settings = $.extend({
grammar: '' // Custom grammar
}, options);
const speechRecognition = window.SpeechRecognition || webkitSpeechRecognition;
const speechRecognitionList = window.SpeechGrammarList || webkitSpeechGrammarList;
if (!speechRecognition) {
console.warn('Speech recognition is not supported in this browser.');
return;
}
const recognition = new speechRecognition();
if (settings.grammar) {
speechRecognitionList.addFromString(settings.grammar, 1);
recognition.grammars = speechRecognitionList;
}
recognition.continuous = true;
recognition.interimResults = true;
// TODO: This should be configurable.
recognition.lang = 'en-US'; // Set the language to English (US).
const $textarea = this;
const $button = $('<div class="fa-solid fa-microphone speech-toggle" title="Click to speak"></div>');
$('#send_but_sheld').prepend($button);
let listening = false;
$button.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 = capitalizeInterim(transcript);
if (interim != '') {
let final = finalTranscript;
final = composeValues(final, interim) + '.';
finalTranscript = final;
recognition.abort();
listening = false;
}
interimTranscript = ' ';
} else {
interimTranscript += transcript;
}
}
interimTranscript = capitalizeInterim(interimTranscript);
$textarea.val(initialText + finalTranscript + interimTranscript);
};
recognition.onerror = function (event) {
console.error('Error occurred in recognition:', event.error);
};
recognition.onend = function () {
listening = false;
$button.toggleClass('fa-microphone fa-microphone-slash');
};
recognition.onstart = function () {
initialText = $textarea.val();
$button.toggleClass('fa-microphone fa-microphone-slash');
};
};
}(jQuery));
jQuery(() => {
const $textarea = $('#send_textarea');
$textarea.speechRecognitionPlugin();
});

View File

@@ -0,0 +1,11 @@
{
"display_name": "Speech Recognition",
"loading_order": 13,
"requires": [],
"optional": [],
"js": "index.js",
"css": "style.css",
"author": "Cohee#1207",
"version": "1.0.0",
"homePage": "https://github.com/SillyTavern/SillyTavern"
}

View File

@@ -0,0 +1,3 @@
.speech-toggle {
display: flex;
}

View File

@@ -84,7 +84,7 @@ class EdgeTtsProvider {
url.pathname = `/api/edge-tts/list` url.pathname = `/api/edge-tts/list`
const response = await doExtrasFetch(url) const response = await doExtrasFetch(url)
if (!response.ok) { if (!response.ok) {
throw new Error(`HTTP ${response.status}: ${await response.json()}`) throw new Error(`HTTP ${response.status}: ${await response.text()}`)
} }
let responseJson = await response.json() let responseJson = await response.json()
responseJson = responseJson responseJson = responseJson
@@ -101,7 +101,7 @@ class EdgeTtsProvider {
const text = getPreviewString(voice.lang); const text = getPreviewString(voice.lang);
const response = await this.fetchTtsGeneration(text, id) const response = await this.fetchTtsGeneration(text, id)
if (!response.ok) { if (!response.ok) {
throw new Error(`HTTP ${response.status}: ${await response.json()}`) throw new Error(`HTTP ${response.status}: ${await response.text()}`)
} }
const audio = await response.blob(); const audio = await response.blob();
@@ -127,14 +127,14 @@ class EdgeTtsProvider {
} }
) )
if (!response.ok) { if (!response.ok) {
throw new Error(`HTTP ${response.status}: ${await response.json()}`) toastr.error(response.statusText, 'TTS Generation Failed');
throw new Error(`HTTP ${response.status}: ${await response.text()}`);
} }
return response return response
} }
} }
function throwIfModuleMissing() { function throwIfModuleMissing() {
if (!modules.includes('edge-tts')) { if (!modules.includes('edge-tts')) {
toastr.error(`Edge TTS module not loaded. Add edge-tts to enable-modules and restart the Extras API.`) toastr.error(`Edge TTS module not loaded. Add edge-tts to enable-modules and restart the Extras API.`)
throw new Error(`Edge TTS module not loaded.`) throw new Error(`Edge TTS module not loaded.`)
} }

View File

@@ -149,7 +149,7 @@ class ElevenLabsTtsProvider {
headers: headers headers: headers
}) })
if (!response.ok) { if (!response.ok) {
throw new Error(`HTTP ${response.status}: ${await response.json()}`) throw new Error(`HTTP ${response.status}: ${await response.text()}`)
} }
const responseJson = await response.json() const responseJson = await response.json()
return responseJson.voices return responseJson.voices
@@ -166,7 +166,7 @@ class ElevenLabsTtsProvider {
} }
) )
if (!response.ok) { if (!response.ok) {
throw new Error(`HTTP ${response.status}: ${await response.json()}`) throw new Error(`HTTP ${response.status}: ${await response.text()}`)
} }
return response.json() return response.json()
} }
@@ -193,7 +193,8 @@ class ElevenLabsTtsProvider {
} }
) )
if (!response.ok) { if (!response.ok) {
throw new Error(`HTTP ${response.status}: ${await response.json()}`) toastr.error(response.statusText, 'TTS Generation Failed');
throw new Error(`HTTP ${response.status}: ${await response.text()}`);
} }
return response return response
} }
@@ -209,7 +210,7 @@ class ElevenLabsTtsProvider {
} }
) )
if (!response.ok) { if (!response.ok) {
throw new Error(`HTTP ${response.status}: ${await response.json()}`) throw new Error(`HTTP ${response.status}: ${await response.text()}`)
} }
return response return response
} }
@@ -222,7 +223,7 @@ class ElevenLabsTtsProvider {
headers: headers headers: headers
}) })
if (!response.ok) { if (!response.ok) {
throw new Error(`HTTP ${response.status}: ${await response.json()}`) throw new Error(`HTTP ${response.status}: ${await response.text()}`)
} }
const responseJson = await response.json() const responseJson = await response.json()
return responseJson.history return responseJson.history

View File

@@ -5,6 +5,7 @@ import { EdgeTtsProvider } from './edge.js'
import { ElevenLabsTtsProvider } from './elevenlabs.js' import { ElevenLabsTtsProvider } from './elevenlabs.js'
import { SileroTtsProvider } from './silerotts.js' import { SileroTtsProvider } from './silerotts.js'
import { SystemTtsProvider } from './system.js' import { SystemTtsProvider } from './system.js'
import { NovelTtsProvider } from './novel.js'
const UPDATE_INTERVAL = 1000 const UPDATE_INTERVAL = 1000
@@ -62,6 +63,7 @@ let ttsProviders = {
Silero: SileroTtsProvider, Silero: SileroTtsProvider,
System: SystemTtsProvider, System: SystemTtsProvider,
Edge: EdgeTtsProvider, Edge: EdgeTtsProvider,
Novel: NovelTtsProvider,
} }
let ttsProvider let ttsProvider
let ttsProviderName let ttsProviderName
@@ -244,7 +246,7 @@ async function playAudioData(audioBlob) {
window['tts_preview'] = function (id) { window['tts_preview'] = function (id) {
const audio = document.getElementById(id) const audio = document.getElementById(id)
if (!$(audio).data('disabled')) { if (audio && !$(audio).data('disabled')) {
audio.play() audio.play()
} }
else { else {
@@ -265,8 +267,10 @@ async function onTtsVoicesClick() {
<b class="voice_name">${voice.name}</b> <b class="voice_name">${voice.name}</b>
<i onclick="tts_preview('${voice.voice_id}')" class="fa-solid fa-play"></i> <i onclick="tts_preview('${voice.voice_id}')" class="fa-solid fa-play"></i>
</div>` </div>`
if (voice.preview_url) {
popupText += `<audio id="${voice.voice_id}" src="${voice.preview_url}" data-disabled="${voice.preview_url == false}"></audio>` popupText += `<audio id="${voice.voice_id}" src="${voice.preview_url}" data-disabled="${voice.preview_url == false}"></audio>`
} }
}
} catch { } catch {
popupText = 'Could not load voices list. Check your API key.' popupText = 'Could not load voices list. Check your API key.'
} }
@@ -327,7 +331,7 @@ function completeCurrentAudioJob() {
*/ */
async function addAudioJob(response) { async function addAudioJob(response) {
const audioData = await response.blob() const audioData = await response.blob()
if (!audioData.type in ['audio/mpeg', 'audio/wav', 'audio/x-wav', 'audio/wave']) { if (!audioData.type in ['audio/mpeg', 'audio/wav', 'audio/x-wav', 'audio/wave', 'audio/webm']) {
throw `TTS received HTTP response with invalid data format. Expecting audio/mpeg, got ${audioData.type}` throw `TTS received HTTP response with invalid data format. Expecting audio/mpeg, got ${audioData.type}`
} }
audioJobQueue.push(audioData) audioJobQueue.push(audioData)
@@ -414,6 +418,7 @@ async function processTtsQueue() {
const voice = await ttsProvider.getVoice((voiceMap[char])) const voice = await ttsProvider.getVoice((voiceMap[char]))
const voiceId = voice.voice_id const voiceId = voice.voice_id
if (voiceId == null) { if (voiceId == null) {
toastr.error(`Specified voice for ${char} was not found. Check the TTS extension settings.`)
throw `Unable to attain voiceId for ${char}` throw `Unable to attain voiceId for ${char}`
} }
tts(text, voiceId) tts(text, voiceId)
@@ -494,7 +499,6 @@ async function voicemapIsValid(parsedVoiceMap) {
async function updateVoiceMap() { async function updateVoiceMap() {
let isValidResult = false let isValidResult = false
const context = getContext()
const value = $('#tts_voice_map').val() const value = $('#tts_voice_map').val()
const parsedVoiceMap = parseVoiceMap(value) const parsedVoiceMap = parseVoiceMap(value)

View File

@@ -0,0 +1,130 @@
import { getRequestHeaders } from "../../../script.js"
import { getPreviewString } from "./index.js"
export { NovelTtsProvider }
class NovelTtsProvider {
//########//
// Config //
//########//
settings
voices = []
separator = ' . '
audioElement = document.createElement('audio')
defaultSettings = {
voiceMap: {}
}
get settingsHtml() {
let html = `Use NovelAI's TTS engine.<br>
The Voice IDs in the preview list are only examples, as it can be any string of text. Feel free to try different options!<br>
<small><i>Hint: Save an API key in the NovelAI API settings to use it here.</i></small>`;
return html;
}
onSettingsChange() {
}
loadSettings(settings) {
// Populate Provider UI given input settings
if (Object.keys(settings).length == 0) {
console.info("Using default TTS Provider 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 TTS Provider: ${key}`
}
}
console.info("Settings loaded")
}
async onApplyClick() {
return
}
//#################//
// TTS Interfaces //
//#################//
async getVoice(voiceName) {
if (!voiceName) {
throw `TTS Voice name not provided`
}
return { name: voiceName, voice_id: voiceName, lang: 'en-US', preview_url: false}
}
async generateTts(text, voiceId) {
const response = await this.fetchTtsGeneration(text, voiceId)
return response
}
//###########//
// API CALLS //
//###########//
async fetchTtsVoiceIds() {
const voices = [
{ name: 'Ligeia', voice_id: 'Ligeia', lang: 'en-US', preview_url: false },
{ name: 'Aini', voice_id: 'Aini', lang: 'en-US', preview_url: false },
{ name: 'Orea', voice_id: 'Orea', lang: 'en-US', preview_url: false },
{ name: 'Claea', voice_id: 'Claea', lang: 'en-US', preview_url: false },
{ name: 'Lim', voice_id: 'Lim', lang: 'en-US', preview_url: false },
{ name: 'Aurae', voice_id: 'Aurae', lang: 'en-US', preview_url: false },
{ name: 'Naia', voice_id: 'Naia', lang: 'en-US', preview_url: false },
{ name: 'Aulon', voice_id: 'Aulon', lang: 'en-US', preview_url: false },
{ name: 'Elei', voice_id: 'Elei', lang: 'en-US', preview_url: false },
{ name: 'Ogma', voice_id: 'Ogma', lang: 'en-US', preview_url: false },
{ name: 'Raid', voice_id: 'Raid', lang: 'en-US', preview_url: false },
{ name: 'Pega', voice_id: 'Pega', lang: 'en-US', preview_url: false },
{ name: 'Lam', voice_id: 'Lam', lang: 'en-US', preview_url: false },
];
return voices;
}
async previewTtsVoice(id) {
this.audioElement.pause();
this.audioElement.currentTime = 0;
const text = getPreviewString('en-US')
const response = await this.fetchTtsGeneration(text, id)
if (!response.ok) {
throw new Error(`HTTP ${response.status}`)
}
const audio = await response.blob();
const url = URL.createObjectURL(audio);
this.audioElement.src = url;
this.audioElement.play();
}
async fetchTtsGeneration(inputText, voiceId) {
console.info(`Generating new TTS for voice_id ${voiceId}`)
const response = await fetch(`/novel_tts`,
{
method: 'POST',
headers: getRequestHeaders(),
body: JSON.stringify({
"text": inputText,
"voice": voiceId,
})
}
)
if (!response.ok) {
toastr.error(response.statusText, 'TTS Generation Failed');
throw new Error(`HTTP ${response.status}: ${await response.text()}`);
}
return response
}
}

View File

@@ -118,7 +118,8 @@ class SileroTtsProvider {
} }
) )
if (!response.ok) { if (!response.ok) {
throw new Error(`HTTP ${response.status}: ${await response.json()}`) toastr.error(response.statusText, 'TTS Generation Failed');
throw new Error(`HTTP ${response.status}: ${await response.text()}`);
} }
return response return response
} }

View File

@@ -60,6 +60,8 @@ const tokenizers = {
GPT3: 1, GPT3: 1,
CLASSIC: 2, CLASSIC: 2,
LLAMA: 3, LLAMA: 3,
NERD: 4,
NERD2: 5,
} }
const send_on_enter_options = { const send_on_enter_options = {

View File

@@ -128,23 +128,25 @@ const delay = ms => new Promise(resolve => setTimeout(resolve, ms))
const { SentencePieceProcessor, cleanText } = require("sentencepiece-js"); const { SentencePieceProcessor, cleanText } = require("sentencepiece-js");
let spp; let spp_llama;
let spp_nerd;
let spp_nerd_v2;
async function loadSentencepieceTokenizer() { async function loadSentencepieceTokenizer(modelPath) {
try { try {
const spp = new SentencePieceProcessor(); const spp = new SentencePieceProcessor();
await spp.load("src/sentencepiece/tokenizer.model"); await spp.load(modelPath);
return spp; return spp;
} catch (error) { } catch (error) {
console.error("Sentencepiece tokenizer failed to load."); console.error("Sentencepiece tokenizer failed to load: " + modelPath, error);
return null; return null;
} }
}; };
async function countTokensLlama(text) { async function countSentencepieceTokens(spp, text) {
// Fallback to strlen estimation // Fallback to strlen estimation
if (!spp) { if (!spp) {
return Math.ceil(v.length / 3.35); return Math.ceil(text.length / 3.35);
} }
let cleaned = cleanText(text); let cleaned = cleanText(text);
@@ -2795,14 +2797,22 @@ app.post("/savepreset_openai", jsonParser, function (request, response) {
return response.send({ name }); return response.send({ name });
}); });
app.post("/tokenize_llama", jsonParser, async function (request, response) { function createTokenizationHandler(getTokenizerFn) {
return async function (request, response) {
if (!request.body) { if (!request.body) {
return response.sendStatus(400); return response.sendStatus(400);
} }
const count = await countTokensLlama(request.body.text); const text = request.body.text || '';
const tokenizer = getTokenizerFn();
const count = await countSentencepieceTokens(tokenizer, text);
return response.send({ count }); return response.send({ count });
}); };
}
app.post("/tokenize_llama", jsonParser, createTokenizationHandler(() => spp_llama));
app.post("/tokenize_nerdstash", jsonParser, createTokenizationHandler(() => spp_nerd));
app.post("/tokenize_nerdstash_v2", jsonParser, createTokenizationHandler(() => spp_nerd_v2));
// ** REST CLIENT ASYNC WRAPPERS ** // ** REST CLIENT ASYNC WRAPPERS **
@@ -2861,7 +2871,11 @@ const setupTasks = async function () {
// Colab users could run the embedded tool // Colab users could run the embedded tool
if (!is_colab) await convertWebp(); if (!is_colab) await convertWebp();
spp = await loadSentencepieceTokenizer(); [spp_llama, spp_nerd, spp_nerd_v2] = await Promise.all([
loadSentencepieceTokenizer('src/sentencepiece/tokenizer.model'),
loadSentencepieceTokenizer('src/sentencepiece/nerdstash.model'),
loadSentencepieceTokenizer('src/sentencepiece/nerdstash_v2.model'),
]);
console.log('Launching...'); console.log('Launching...');
@@ -3197,6 +3211,40 @@ app.post('/google_translate', jsonParser, async (request, response) => {
}); });
}); });
app.post('/novel_tts', jsonParser, async (request, response) => {
const token = readSecret(SECRET_KEYS.NOVEL);
if (!token) {
return response.sendStatus(401);
}
const text = request.body.text;
const voice = request.body.voice;
if (!text || !voice) {
return response.sendStatus(400);
}
try {
const fetch = require('node-fetch').default;
const url = `${api_novelai}/ai/generate-voice?text=${encodeURIComponent(text)}&voice=-1&seed=${encodeURIComponent(voice)}&opus=false&version=v2`;
const result = await fetch(url, { method: 'GET', headers: { 'Authorization': `Bearer ${token}`, 'Accept': 'audio/webm' } });
if (!result.ok) {
return response.sendStatus(result.status);
}
const chunks = await readAllChunks(result.body);
const buffer = Buffer.concat(chunks);
response.setHeader('Content-Type', 'audio/webm');
return response.send(buffer);
}
catch (error) {
console.error(error);
return response.sendStatus(500);
}
});
app.post('/delete_sprite', jsonParser, async (request, response) => { app.post('/delete_sprite', jsonParser, async (request, response) => {
const label = request.body.label; const label = request.body.label;
const name = request.body.name; const name = request.body.name;
@@ -3343,6 +3391,26 @@ function readSecret(key) {
return secrets[key]; return secrets[key];
} }
async function readAllChunks(readableStream) {
return new Promise((resolve, reject) => {
// Consume the readable stream
const chunks = [];
readableStream.on('data', (chunk) => {
chunks.push(chunk);
});
readableStream.on('end', () => {
console.log('Finished reading the stream.');
resolve(chunks);
});
readableStream.on('error', (error) => {
console.error('Error while reading the stream:', error);
reject();
});
});
}
async function getImageBuffers(zipFilePath) { async function getImageBuffers(zipFilePath) {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
// Check if the zip file exists // Check if the zip file exists

Binary file not shown.

Binary file not shown.