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.type === 'character'){
+ currentChar.data.ttsSpeech = ''
+ }
+ }}>
+ {language.disabled}
+ ElevenLabs
+ Web Speech
+
+
+
+ {#if currentChar.data.ttsMode === 'webspeech'}
+ {#if !speechSynthesis}
+ Web Speech isn't supported in your browser or OS
+ {:else}
+ {language.Speech}
+
+ Auto
+ {#each getWebSpeechTTSVoices() as voice}
+ {voice}
+ {/each}
+
+ {#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}
+
+ Unset
+ {#each voices as voice}
+ {voice.name}
+ {/each}
+
+ {/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