From f7d7eb3ab91e8a51bd31fd5c87f920a68b58493f Mon Sep 17 00:00:00 2001 From: kwaroran Date: Sat, 13 May 2023 21:24:54 +0900 Subject: [PATCH] [feat] add tts --- src/lang/en.ts | 3 +- src/lib/SideBars/CharConfig.svelte | 49 ++++++++++++++++++- src/lib/SideBars/Settings.svelte | 6 ++- src/ts/database.ts | 6 +++ src/ts/process/index.ts | 9 ++-- src/ts/process/tts.ts | 75 ++++++++++++++++++++++++++++-- 6 files changed, 133 insertions(+), 15 deletions(-) diff --git a/src/lang/en.ts b/src/lang/en.ts index fd3b9f89..1d47e3ad 100644 --- a/src/lang/en.ts +++ b/src/lang/en.ts @@ -241,5 +241,6 @@ export const languageEnglish = { useGlobalSettings: "Use Global Settings", recursiveScanning: "Recursive Scanning", creator: "Creator", - CharVersion: "Character Version" + CharVersion: "Character Version", + Speech: "Speech" } diff --git a/src/lib/SideBars/CharConfig.svelte b/src/lib/SideBars/CharConfig.svelte index 1169bef8..489aaca0 100644 --- a/src/lib/SideBars/CharConfig.svelte +++ b/src/lib/SideBars/CharConfig.svelte @@ -3,7 +3,7 @@ import { tokenize } from "../../ts/tokenizer"; import { DataBase, type Database, type character, type groupChat } from "../../ts/database"; import { selectedCharID } from "../../ts/stores"; - import { PlusIcon, SmileIcon, TrashIcon, UserIcon, ActivityIcon, BookIcon, LoaderIcon, User, DnaIcon, CurlyBracesIcon } from 'lucide-svelte' + import { PlusIcon, SmileIcon, TrashIcon, UserIcon, ActivityIcon, BookIcon, LoaderIcon, User, DnaIcon, CurlyBracesIcon, Volume2Icon } from 'lucide-svelte' import Check from "../Others/Check.svelte"; import { addCharEmotion, addingEmotion, getCharImage, rmCharEmotion, selectCharImg, makeGroupImage } from "../../ts/characters"; import LoreBook from "./LoreBookSetting.svelte"; @@ -15,6 +15,7 @@ import Help from "../Others/Help.svelte"; import RegexData from "./RegexData.svelte"; import { exportChar } from "src/ts/characterCards"; + import { getElevenTTSVoices, getWebSpeechTTSVoices } from "src/ts/process/tts"; let subMenu = 0 let subberMenu = 0 @@ -157,6 +158,9 @@ {#if currentChar.type === 'character'} + @@ -445,6 +449,49 @@ } }}> {/if} +{:else if subMenu === 5} + {#if currentChar.type === 'character'} +

TTS

+ {language.provider} + + + + {#if currentChar.data.ttsMode === 'webspeech'} + {#if !speechSynthesis} + Web Speech isn't supported in your browser or OS + {:else} + {language.Speech} + + {#if currentChar.data.ttsSpeech !== ''} + If you do not set it to Auto, it may not work properly when importing from another OS or browser. + {/if} + {/if} + {:else if currentChar.data.ttsMode === 'elevenlab'} + Please set the ElevenLabs API key in "global Settings → Bot Settings → Others → ElevenLabs API key" + {#await getElevenTTSVoices() then voices} + {language.Speech} + + {/await} + {/if} + {/if} {:else if subMenu === 2}

{language.advancedSettings}

{#if currentChar.type !== 'group'} diff --git a/src/lib/SideBars/Settings.svelte b/src/lib/SideBars/Settings.svelte index 0d08f968..596271f7 100644 --- a/src/lib/SideBars/Settings.svelte +++ b/src/lib/SideBars/Settings.svelte @@ -277,8 +277,10 @@ {/if} - - + TTS + ElevenLabs API key + + {:else if subMenu == 3}

{language.display}

diff --git a/src/ts/database.ts b/src/ts/database.ts index f8fc02b1..cefd711f 100644 --- a/src/ts/database.ts +++ b/src/ts/database.ts @@ -181,6 +181,9 @@ export function setDatabase(data:Database){ if(checkNullish(data.showUnrecommended)){ data.showUnrecommended = false } + if(checkNullish(data.elevenLabKey)){ + data.elevenLabKey = '' + } if(checkNullish(data.sdConfig)){ data.sdConfig = { width:512, @@ -262,6 +265,8 @@ export interface character{ creator?:string character_version?:number } + ttsMode?:string + ttsSpeech?:string } @@ -386,6 +391,7 @@ export interface Database{ requestmet: string requestproxy: string showUnrecommended:boolean + elevenLabKey:string } diff --git a/src/ts/process/index.ts b/src/ts/process/index.ts index 939ad5c0..deb88c18 100644 --- a/src/ts/process/index.ts +++ b/src/ts/process/index.ts @@ -10,6 +10,7 @@ import { requestChatData } from "./request"; import { stableDiff } from "./stableDiff"; import { processScript, processScriptFull } from "./scripts"; import { exampleMessage } from "./exampleMessages"; +import { sayTTS } from "./tts"; export interface OpenAIChat{ role: 'system'|'user'|'assistant' @@ -165,7 +166,7 @@ export async function sendChat(chatProcessIndex = -1):Promise { }).join('\n\n')) + db.maxResponse) + 150 let chats:OpenAIChat[] = exampleMessage(currentChar) - + chats.push({ role: 'system', content: '[Start a new chat]' @@ -214,9 +215,6 @@ export async function sendChat(chatProcessIndex = -1):Promise { currentTokens += (await tokenize(systemMsg) + 1) } - console.log(currentTokens) - console.log(maxContextTokens) - while(currentTokens > maxContextTokens){ if(chats.length <= 1){ alertError(language.errors.toomuchtoken) @@ -228,8 +226,6 @@ export async function sendChat(chatProcessIndex = -1):Promise { chats.splice(0, 1) } - console.log(currentTokens) - let bias:{[key:number]:number} = {} for(let i=0;i { data: result, saying: currentChar.chaId }) + await sayTTS(currentChar, result) setDatabase(db) } diff --git a/src/ts/process/tts.ts b/src/ts/process/tts.ts index 2b4ac802..d761f3ad 100644 --- a/src/ts/process/tts.ts +++ b/src/ts/process/tts.ts @@ -1,6 +1,71 @@ -export async function sayTTS(text:string) { - const utterThis = new SpeechSynthesisUtterance(text); - const voices = speechSynthesis.getVoices(); - utterThis.voice = voices[0] - speechSynthesis.speak(utterThis) +import { get } from "svelte/store"; +import { alertError } from "../alert"; +import { DataBase, type character } from "../database"; + +export async function sayTTS(character:character,text:string) { + + let db = get(DataBase) + + switch(character.ttsMode){ + case "webspeech":{ + if(speechSynthesis && SpeechSynthesisUtterance){ + const utterThis = new SpeechSynthesisUtterance(text); + const voices = speechSynthesis.getVoices(); + let voiceIndex = 0 + for(let i=0;i= 200 && da.status < 300){ + const audioBuffer = await audioContext.decodeAudioData(await da.arrayBuffer()) + const sourceNode = audioContext.createBufferSource(); + sourceNode.buffer = audioBuffer; + sourceNode.connect(audioContext.destination); + sourceNode.start(); + } + else{ + alertError(await da.text()) + } + } + } + +} + + +export function getWebSpeechTTSVoices() { + return speechSynthesis.getVoices().map(v => { + return v.name + }) +} + +export async function getElevenTTSVoices() { + let db = get(DataBase) + + const data = await fetch('https://api.elevenlabs.io/v1/voices', { + headers: { + 'xi-api-key': db.elevenLabKey || undefined + } + }) + const res = await data.json() + + console.log(res) + return res.voices } \ No newline at end of file