diff --git a/src-tauri/tauri.conf.json b/src-tauri/tauri.conf.json index 7c162c75..afe9181c 100644 --- a/src-tauri/tauri.conf.json +++ b/src-tauri/tauri.conf.json @@ -8,7 +8,7 @@ }, "package": { "productName": "RisuAI", - "version": "0.8.2" + "version": "0.9.0" }, "tauri": { "allowlist": { 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/Others/GridCatalog.svelte b/src/lib/Others/GridCatalog.svelte index 42a723fc..3f5c226c 100644 --- a/src/lib/Others/GridCatalog.svelte +++ b/src/lib/Others/GridCatalog.svelte @@ -12,6 +12,27 @@ selectedCharID.set(index) endGrid() } + + function formatChars(search:string){ + let charas:{ + image:string + index:number + type:string + }[] = [] + + for(let i=0;i<$DataBase.characters.length;i++){ + const c = $DataBase.characters[i] + if(c.name.replace(/ /g,"").toLocaleLowerCase().includes(search.toLocaleLowerCase().replace(/ /g,""))){ + charas.push({ + image: c.image, + index: i, + type: c.type + }) + } + } + return charas + + }
@@ -20,14 +41,12 @@
- {#each $DataBase.characters.filter((c) => { - return c.name.toLocaleLowerCase().includes(search.toLocaleLowerCase()) - }) as char, i} + {#each formatChars(search) as char}
{#if char.image} - {changeChar(i)}} additionalStyle={getCharImage($DataBase.characters[i].image, 'css')}> + {changeChar(char.index)}} additionalStyle={getCharImage(char.image, 'css')}> {:else} - {changeChar(i)}} additionalStyle={i === $selectedCharID ? 'background:#44475a' : ''}> + {changeChar(char.index)}} additionalStyle={char.index === $selectedCharID ? 'background:#44475a' : ''}> {#if char.type === 'group'} {:else} 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..a02c69a0 100644 --- a/src/ts/database.ts +++ b/src/ts/database.ts @@ -7,7 +7,7 @@ import { cloneDeep } from 'lodash'; export const DataBase = writable({} as any as Database) export const loadedStore = writable(false) -export let appVer = '0.8.2' +export let appVer = '0.9.0' export function setDatabase(data:Database){ @@ -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/globalApi.ts b/src/ts/globalApi.ts index f38f82ec..e2fa009f 100644 --- a/src/ts/globalApi.ts +++ b/src/ts/globalApi.ts @@ -288,146 +288,69 @@ export async function loadData() { } } +const knownHostes = ["localhost","172.0.0.1"] + export async function globalFetch(url:string, arg:{body?:any,headers?:{[key:string]:string}, rawResponse?:boolean, method?:"POST"|"GET"}) { - const db = get(DataBase) - const method = arg.method ?? "POST" - - function addFetchLog(response:any, success:boolean){ - try{ - fetchLog.unshift({ - body: JSON.stringify(arg.body, null, 2), - header: JSON.stringify(arg.headers ?? {}, null, 2), - response: JSON.stringify(response, null, 2), - success: success, - date: (new Date()).toLocaleTimeString(), - url: url - }) - } - catch{ - fetchLog.unshift({ - body: JSON.stringify(arg.body, null, 2), - header: JSON.stringify(arg.headers ?? {}, null, 2), - response: `${response}`, - success: success, - date: (new Date()).toLocaleTimeString(), - url: url - }) - } - } - - if(db.requestmet === 'proxy'){ - try { - let headers = arg.headers ?? {} - if(!headers["Content-Type"]){ - headers["Content-Type"] = `application/json` + try { + const db = get(DataBase) + const method = arg.method ?? "POST" + + function addFetchLog(response:any, success:boolean){ + try{ + fetchLog.unshift({ + body: JSON.stringify(arg.body, null, 2), + header: JSON.stringify(arg.headers ?? {}, null, 2), + response: JSON.stringify(response, null, 2), + success: success, + date: (new Date()).toLocaleTimeString(), + url: url + }) } - const furl = new URL(db.requestproxy) - furl.pathname = url - - const da = await fetch(furl, { - body: JSON.stringify(arg.body), - headers: arg.headers, - method: method - }) - - if(arg.rawResponse){ - addFetchLog("Uint8Array Response", da.ok) - return { - ok: da.ok, - data: new Uint8Array(await da.arrayBuffer()) - } - } - else{ - const dat = await da.json() - addFetchLog(dat, da.ok) - return { - ok: da.ok, - data: dat - } - } - - } catch (error) { - return { - ok: false, - data: `${error}`, + catch{ + fetchLog.unshift({ + body: JSON.stringify(arg.body, null, 2), + header: JSON.stringify(arg.headers ?? {}, null, 2), + response: `${response}`, + success: success, + date: (new Date()).toLocaleTimeString(), + url: url + }) } } - } - if(db.requestmet === 'plain'){ - try { - let headers = arg.headers ?? {} - if(!headers["Content-Type"]){ - headers["Content-Type"] = `application/json` - } - const furl = new URL(url) - - const da = await fetch(furl, { - body: JSON.stringify(arg.body), - headers: arg.headers, - method: method - }) - - if(arg.rawResponse){ - addFetchLog("Uint8Array Response", da.ok) - return { - ok: da.ok, - data: new Uint8Array(await da.arrayBuffer()) - } - } - else{ - const dat = await da.json() - addFetchLog(dat, da.ok) - return { - ok: da.ok, - data: dat - } - } - - } catch (error) { - return { - ok: false, - data: `${error}`, - } - } - } - - - if(isTauri){ - if(db.requester === 'new'){ + + const urlHost = (new URL(url)).hostname + let forcePlainFetch = knownHostes.includes(urlHost) + + if(db.requestmet === 'plain' || forcePlainFetch){ try { - let preHeader = arg.headers ?? {} - preHeader["Content-Type"] = `application/json` - const body = JSON.stringify(arg.body) - const header = JSON.stringify(preHeader) - const res:string = await invoke('native_request', {url:url, body:body, header:header, method: method}) - const d:{ - success: boolean - body:string - } = JSON.parse(res) - - if(!d.success){ - addFetchLog(Buffer.from(d.body, 'base64').toString('utf-8'), false) + let headers = arg.headers ?? {} + if(!headers["Content-Type"]){ + headers["Content-Type"] = `application/json` + } + const furl = new URL(url) + + const da = await fetch(furl, { + body: JSON.stringify(arg.body), + headers: arg.headers, + method: method + }) + + if(arg.rawResponse){ + addFetchLog("Uint8Array Response", da.ok) return { - ok:false, - data: Buffer.from(d.body, 'base64').toString('utf-8') - } + ok: da.ok, + data: new Uint8Array(await da.arrayBuffer()) + } } else{ - if(arg.rawResponse){ - addFetchLog("Uint8Array Response", true) - return { - ok:true, - data: new Uint8Array(Buffer.from(d.body, 'base64')) - } + const dat = await da.json() + addFetchLog(dat, da.ok) + return { + ok: da.ok, + data: dat } - else{ - addFetchLog(JSON.parse(Buffer.from(d.body, 'base64').toString('utf-8')), true) - return { - ok:true, - data: JSON.parse(Buffer.from(d.body, 'base64').toString('utf-8')) - } - } - } + } + } catch (error) { return { ok: false, @@ -435,77 +358,164 @@ export async function globalFetch(url:string, arg:{body?:any,headers?:{[key:stri } } } - - const body = Body.json(arg.body) - const headers = arg.headers ?? {} - const d = await TauriFetch(url, { - body: body, - method: method, - headers: headers, - timeout: { - secs: db.timeOut, - nanos: 0 - }, - responseType: arg.rawResponse ? ResponseType.Binary : ResponseType.JSON - }) - if(arg.rawResponse){ - addFetchLog("Uint8Array Response", d.ok) - return { - ok: d.ok, - data: new Uint8Array(d.data as number[]), + if(db.requestmet === 'proxy'){ + try { + let headers = arg.headers ?? {} + if(!headers["Content-Type"]){ + headers["Content-Type"] = `application/json` + } + const furl = new URL(db.requestproxy) + furl.pathname = url + + const da = await fetch(furl, { + body: JSON.stringify(arg.body), + headers: arg.headers, + method: method + }) + + if(arg.rawResponse){ + addFetchLog("Uint8Array Response", da.ok) + return { + ok: da.ok, + data: new Uint8Array(await da.arrayBuffer()) + } + } + else{ + const dat = await da.json() + addFetchLog(dat, da.ok) + return { + ok: da.ok, + data: dat + } + } + + } catch (error) { + return { + ok: false, + data: `${error}`, + } + } + } + if(isTauri){ + if(db.requester === 'new'){ + try { + let preHeader = arg.headers ?? {} + preHeader["Content-Type"] = `application/json` + const body = JSON.stringify(arg.body) + const header = JSON.stringify(preHeader) + const res:string = await invoke('native_request', {url:url, body:body, header:header, method: method}) + const d:{ + success: boolean + body:string + } = JSON.parse(res) + + if(!d.success){ + addFetchLog(Buffer.from(d.body, 'base64').toString('utf-8'), false) + return { + ok:false, + data: Buffer.from(d.body, 'base64').toString('utf-8') + } + } + else{ + if(arg.rawResponse){ + addFetchLog("Uint8Array Response", true) + return { + ok:true, + data: new Uint8Array(Buffer.from(d.body, 'base64')) + } + } + else{ + addFetchLog(JSON.parse(Buffer.from(d.body, 'base64').toString('utf-8')), true) + return { + ok:true, + data: JSON.parse(Buffer.from(d.body, 'base64').toString('utf-8')) + } + } + } + } catch (error) { + return { + ok: false, + data: `${error}`, + } + } + } + + const body = Body.json(arg.body) + const headers = arg.headers ?? {} + const d = await TauriFetch(url, { + body: body, + method: method, + headers: headers, + timeout: { + secs: db.timeOut, + nanos: 0 + }, + responseType: arg.rawResponse ? ResponseType.Binary : ResponseType.JSON + }) + if(arg.rawResponse){ + addFetchLog("Uint8Array Response", d.ok) + return { + ok: d.ok, + data: new Uint8Array(d.data as number[]), + } + } + else{ + addFetchLog(d.data, d.ok) + return { + ok: d.ok, + data: d.data, + } } } else{ - addFetchLog(d.data, d.ok) - return { - ok: d.ok, - data: d.data, - } - } - } - else{ - try { - let headers = arg.headers ?? {} - if(!headers["Content-Type"]){ - headers["Content-Type"] = `application/json` - } - if(arg.rawResponse){ - const furl = `/proxy?url=${encodeURIComponent(url)}` - - const da = await fetch(furl, { - body: JSON.stringify(arg.body), - headers: arg.headers, - method: method - }) - - addFetchLog("Uint8Array Response", da.ok) + try { + let headers = arg.headers ?? {} + if(!headers["Content-Type"]){ + headers["Content-Type"] = `application/json` + } + if(arg.rawResponse){ + const furl = `/proxy?url=${encodeURIComponent(url)}` + + const da = await fetch(furl, { + body: JSON.stringify(arg.body), + headers: arg.headers, + method: method + }) + + addFetchLog("Uint8Array Response", da.ok) + return { + ok: da.ok, + data: new Uint8Array(await da.arrayBuffer()) + } + } + else{ + const furl = `/proxy?url=${encodeURIComponent(url)}` + + const da = await fetch(furl, { + body: JSON.stringify(arg.body), + headers: arg.headers, + method: method + }) + + const dat = await da.json() + addFetchLog(dat, da.ok) + return { + ok: da.ok, + data: dat + } + } + } catch (error) { + console.log(error) return { - ok: da.ok, - data: new Uint8Array(await da.arrayBuffer()) - } - } - else{ - const furl = `/proxy?url=${encodeURIComponent(url)}` - - const da = await fetch(furl, { - body: JSON.stringify(arg.body), - headers: arg.headers, - method: method - }) - - const dat = await da.json() - addFetchLog(dat, da.ok) - return { - ok: da.ok, - data: dat + ok:false, + data: `${error}` } } - } catch (error) { - console.log(error) - return { - ok:false, - data: `${error}` - } + } + } catch (error) { + return { + ok:false, + data: `${error}` } } } 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 new file mode 100644 index 00000000..d761f3ad --- /dev/null +++ b/src/ts/process/tts.ts @@ -0,0 +1,71 @@ +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 diff --git a/version.json b/version.json index 43f7d154..8ef02cbc 100644 --- a/version.json +++ b/version.json @@ -1 +1 @@ -{"version":"0.8.2"} \ No newline at end of file +{"version":"0.9.0"} \ No newline at end of file