Update to 0.9.0 (#50)

This commit is contained in:
kwaroran
2023-05-13 21:31:10 +09:00
committed by GitHub
10 changed files with 367 additions and 214 deletions

View File

@@ -8,7 +8,7 @@
}, },
"package": { "package": {
"productName": "RisuAI", "productName": "RisuAI",
"version": "0.8.2" "version": "0.9.0"
}, },
"tauri": { "tauri": {
"allowlist": { "allowlist": {

View File

@@ -241,5 +241,6 @@ export const languageEnglish = {
useGlobalSettings: "Use Global Settings", useGlobalSettings: "Use Global Settings",
recursiveScanning: "Recursive Scanning", recursiveScanning: "Recursive Scanning",
creator: "Creator", creator: "Creator",
CharVersion: "Character Version" CharVersion: "Character Version",
Speech: "Speech"
} }

View File

@@ -12,6 +12,27 @@
selectedCharID.set(index) selectedCharID.set(index)
endGrid() 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
}
</script> </script>
<div class="h-full w-full flex justify-center"> <div class="h-full w-full flex justify-center">
@@ -20,14 +41,12 @@
<input class="text-neutral-200 mt-2 mb-4 p-2 bg-transparent input-text focus:bg-selected w-4/5 text-xl" placeholder="Search" bind:value={search}> <input class="text-neutral-200 mt-2 mb-4 p-2 bg-transparent input-text focus:bg-selected w-4/5 text-xl" placeholder="Search" bind:value={search}>
<div class="w-full flex justify-center"> <div class="w-full flex justify-center">
<div class="flex flex-wrap gap-2 mx-auto container"> <div class="flex flex-wrap gap-2 mx-auto container">
{#each $DataBase.characters.filter((c) => { {#each formatChars(search) as char}
return c.name.toLocaleLowerCase().includes(search.toLocaleLowerCase())
}) as char, i}
<div class="flex items-center text-neutral-200"> <div class="flex items-center text-neutral-200">
{#if char.image} {#if char.image}
<BarIcon onClick={() => {changeChar(i)}} additionalStyle={getCharImage($DataBase.characters[i].image, 'css')}></BarIcon> <BarIcon onClick={() => {changeChar(char.index)}} additionalStyle={getCharImage(char.image, 'css')}></BarIcon>
{:else} {:else}
<BarIcon onClick={() => {changeChar(i)}} additionalStyle={i === $selectedCharID ? 'background:#44475a' : ''}> <BarIcon onClick={() => {changeChar(char.index)}} additionalStyle={char.index === $selectedCharID ? 'background:#44475a' : ''}>
{#if char.type === 'group'} {#if char.type === 'group'}
<Users /> <Users />
{:else} {:else}

View File

@@ -3,7 +3,7 @@
import { tokenize } from "../../ts/tokenizer"; import { tokenize } from "../../ts/tokenizer";
import { DataBase, type Database, type character, type groupChat } from "../../ts/database"; import { DataBase, type Database, type character, type groupChat } from "../../ts/database";
import { selectedCharID } from "../../ts/stores"; 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 Check from "../Others/Check.svelte";
import { addCharEmotion, addingEmotion, getCharImage, rmCharEmotion, selectCharImg, makeGroupImage } from "../../ts/characters"; import { addCharEmotion, addingEmotion, getCharImage, rmCharEmotion, selectCharImg, makeGroupImage } from "../../ts/characters";
import LoreBook from "./LoreBookSetting.svelte"; import LoreBook from "./LoreBookSetting.svelte";
@@ -15,6 +15,7 @@
import Help from "../Others/Help.svelte"; import Help from "../Others/Help.svelte";
import RegexData from "./RegexData.svelte"; import RegexData from "./RegexData.svelte";
import { exportChar } from "src/ts/characterCards"; import { exportChar } from "src/ts/characterCards";
import { getElevenTTSVoices, getWebSpeechTTSVoices } from "src/ts/process/tts";
let subMenu = 0 let subMenu = 0
let subberMenu = 0 let subberMenu = 0
@@ -157,6 +158,9 @@
<BookIcon /> <BookIcon />
</button> </button>
{#if currentChar.type === 'character'} {#if currentChar.type === 'character'}
<button class={subMenu === 5 ? 'text-gray-200' : 'text-gray-500'} on:click={() => {subMenu = 5}}>
<Volume2Icon />
</button>
<button class={subMenu === 4 ? 'text-gray-200' : 'text-gray-500'} on:click={() => {subMenu = 4}}> <button class={subMenu === 4 ? 'text-gray-200' : 'text-gray-500'} on:click={() => {subMenu = 4}}>
<CurlyBracesIcon /> <CurlyBracesIcon />
</button> </button>
@@ -445,6 +449,49 @@
} }
}}><PlusIcon /></button> }}><PlusIcon /></button>
{/if} {/if}
{:else if subMenu === 5}
{#if currentChar.type === 'character'}
<h2 class="mb-2 text-2xl font-bold mt-2">TTS</h2>
<span class="text-neutral-200">{language.provider}</span>
<select class="bg-transparent input-text mt-2 mb-4 text-gray-200 appearance-none text-sm" bind:value={currentChar.data.ttsMode} on:change={() => {
if(currentChar.type === 'character'){
currentChar.data.ttsSpeech = ''
}
}}>
<option value="" class="bg-darkbg appearance-none">{language.disabled}</option>
<option value="elevenlab" class="bg-darkbg appearance-none">ElevenLabs</option>
<option value="webspeech" class="bg-darkbg appearance-none">Web Speech</option>
</select>
{#if currentChar.data.ttsMode === 'webspeech'}
{#if !speechSynthesis}
<span class="text-neutral-200">Web Speech isn't supported in your browser or OS</span>
{:else}
<span class="text-neutral-200">{language.Speech}</span>
<select class="bg-transparent input-text mt-2 mb-4 text-gray-200 appearance-none text-sm" bind:value={currentChar.data.ttsSpeech}>
<option value="" class="bg-darkbg appearance-none">Auto</option>
{#each getWebSpeechTTSVoices() as voice}
<option value={voice} class="bg-darkbg appearance-none">{voice}</option>
{/each}
</select>
{#if currentChar.data.ttsSpeech !== ''}
<span class="text-red-400 text-sm">If you do not set it to Auto, it may not work properly when importing from another OS or browser.</span>
{/if}
{/if}
{:else if currentChar.data.ttsMode === 'elevenlab'}
<span class="text-sm mb-2 text-gray-400">Please set the ElevenLabs API key in "global Settings → Bot Settings → Others → ElevenLabs API key"</span>
{#await getElevenTTSVoices() then voices}
<span class="text-neutral-200">{language.Speech}</span>
<select class="bg-transparent input-text mt-2 mb-4 text-gray-200 appearance-none text-sm" bind:value={currentChar.data.ttsSpeech}>
<option value="" class="bg-darkbg appearance-none">Unset</option>
{#each voices as voice}
<option value={voice.voice_id} class="bg-darkbg appearance-none">{voice.name}</option>
{/each}
</select>
{/await}
{/if}
{/if}
{:else if subMenu === 2} {:else if subMenu === 2}
<h2 class="mb-2 text-2xl font-bold mt-2">{language.advancedSettings}</h2> <h2 class="mb-2 text-2xl font-bold mt-2">{language.advancedSettings}</h2>
{#if currentChar.type !== 'group'} {#if currentChar.type !== 'group'}

View File

@@ -277,8 +277,10 @@
{/if} {/if}
<span class="text-neutral-200 mt-4 text-lg font-bold">TTS</span>
<span class="text-neutral-200 mt-2">ElevenLabs API key</span>
<input class="text-neutral-200 mb-4 p-2 bg-transparent input-text focus:bg-selected text-sm" bind:value={$DataBase.elevenLabKey}>
{:else if subMenu == 3} {:else if subMenu == 3}
<h2 class="mb-2 text-2xl font-bold mt-2">{language.display}</h2> <h2 class="mb-2 text-2xl font-bold mt-2">{language.display}</h2>

View File

@@ -7,7 +7,7 @@ import { cloneDeep } from 'lodash';
export const DataBase = writable({} as any as Database) export const DataBase = writable({} as any as Database)
export const loadedStore = writable(false) export const loadedStore = writable(false)
export let appVer = '0.8.2' export let appVer = '0.9.0'
export function setDatabase(data:Database){ export function setDatabase(data:Database){
@@ -181,6 +181,9 @@ export function setDatabase(data:Database){
if(checkNullish(data.showUnrecommended)){ if(checkNullish(data.showUnrecommended)){
data.showUnrecommended = false data.showUnrecommended = false
} }
if(checkNullish(data.elevenLabKey)){
data.elevenLabKey = ''
}
if(checkNullish(data.sdConfig)){ if(checkNullish(data.sdConfig)){
data.sdConfig = { data.sdConfig = {
width:512, width:512,
@@ -262,6 +265,8 @@ export interface character{
creator?:string creator?:string
character_version?:number character_version?:number
} }
ttsMode?:string
ttsSpeech?:string
} }
@@ -386,6 +391,7 @@ export interface Database{
requestmet: string requestmet: string
requestproxy: string requestproxy: string
showUnrecommended:boolean showUnrecommended:boolean
elevenLabKey:string
} }

View File

@@ -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"}) { export async function globalFetch(url:string, arg:{body?:any,headers?:{[key:string]:string}, rawResponse?:boolean, method?:"POST"|"GET"}) {
const db = get(DataBase) try {
const method = arg.method ?? "POST" const db = get(DataBase)
const method = arg.method ?? "POST"
function addFetchLog(response:any, success:boolean){
try{ function addFetchLog(response:any, success:boolean){
fetchLog.unshift({ try{
body: JSON.stringify(arg.body, null, 2), fetchLog.unshift({
header: JSON.stringify(arg.headers ?? {}, null, 2), body: JSON.stringify(arg.body, null, 2),
response: JSON.stringify(response, null, 2), header: JSON.stringify(arg.headers ?? {}, null, 2),
success: success, response: JSON.stringify(response, null, 2),
date: (new Date()).toLocaleTimeString(), success: success,
url: url 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`
} }
const furl = new URL(db.requestproxy) catch{
furl.pathname = url fetchLog.unshift({
body: JSON.stringify(arg.body, null, 2),
const da = await fetch(furl, { header: JSON.stringify(arg.headers ?? {}, null, 2),
body: JSON.stringify(arg.body), response: `${response}`,
headers: arg.headers, success: success,
method: method date: (new Date()).toLocaleTimeString(),
}) url: url
})
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(db.requestmet === 'plain'){ const urlHost = (new URL(url)).hostname
try { let forcePlainFetch = knownHostes.includes(urlHost)
let headers = arg.headers ?? {}
if(!headers["Content-Type"]){ if(db.requestmet === 'plain' || forcePlainFetch){
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'){
try { try {
let preHeader = arg.headers ?? {} let headers = arg.headers ?? {}
preHeader["Content-Type"] = `application/json` if(!headers["Content-Type"]){
const body = JSON.stringify(arg.body) headers["Content-Type"] = `application/json`
const header = JSON.stringify(preHeader) }
const res:string = await invoke('native_request', {url:url, body:body, header:header, method: method}) const furl = new URL(url)
const d:{
success: boolean const da = await fetch(furl, {
body:string body: JSON.stringify(arg.body),
} = JSON.parse(res) headers: arg.headers,
method: method
if(!d.success){ })
addFetchLog(Buffer.from(d.body, 'base64').toString('utf-8'), false)
if(arg.rawResponse){
addFetchLog("Uint8Array Response", da.ok)
return { return {
ok:false, ok: da.ok,
data: Buffer.from(d.body, 'base64').toString('utf-8') data: new Uint8Array(await da.arrayBuffer())
} }
} }
else{ else{
if(arg.rawResponse){ const dat = await da.json()
addFetchLog("Uint8Array Response", true) addFetchLog(dat, da.ok)
return { return {
ok:true, ok: da.ok,
data: new Uint8Array(Buffer.from(d.body, 'base64')) 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) { } catch (error) {
return { return {
ok: false, ok: false,
@@ -435,77 +358,164 @@ export async function globalFetch(url:string, arg:{body?:any,headers?:{[key:stri
} }
} }
} }
if(db.requestmet === 'proxy'){
const body = Body.json(arg.body) try {
const headers = arg.headers ?? {} let headers = arg.headers ?? {}
const d = await TauriFetch(url, { if(!headers["Content-Type"]){
body: body, headers["Content-Type"] = `application/json`
method: method, }
headers: headers, const furl = new URL(db.requestproxy)
timeout: { furl.pathname = url
secs: db.timeOut,
nanos: 0 const da = await fetch(furl, {
}, body: JSON.stringify(arg.body),
responseType: arg.rawResponse ? ResponseType.Binary : ResponseType.JSON headers: arg.headers,
}) method: method
if(arg.rawResponse){ })
addFetchLog("Uint8Array Response", d.ok)
return { if(arg.rawResponse){
ok: d.ok, addFetchLog("Uint8Array Response", da.ok)
data: new Uint8Array(d.data as number[]), 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{ else{
addFetchLog(d.data, d.ok) try {
return { let headers = arg.headers ?? {}
ok: d.ok, if(!headers["Content-Type"]){
data: d.data, headers["Content-Type"] = `application/json`
} }
} if(arg.rawResponse){
} const furl = `/proxy?url=${encodeURIComponent(url)}`
else{
try { const da = await fetch(furl, {
let headers = arg.headers ?? {} body: JSON.stringify(arg.body),
if(!headers["Content-Type"]){ headers: arg.headers,
headers["Content-Type"] = `application/json` method: method
} })
if(arg.rawResponse){
const furl = `/proxy?url=${encodeURIComponent(url)}` addFetchLog("Uint8Array Response", da.ok)
return {
const da = await fetch(furl, { ok: da.ok,
body: JSON.stringify(arg.body), data: new Uint8Array(await da.arrayBuffer())
headers: arg.headers, }
method: method }
}) else{
const furl = `/proxy?url=${encodeURIComponent(url)}`
addFetchLog("Uint8Array Response", da.ok)
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 { return {
ok: da.ok, ok:false,
data: new Uint8Array(await da.arrayBuffer()) data: `${error}`
}
}
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) } catch (error) {
return { return {
ok:false, ok:false,
data: `${error}` data: `${error}`
}
} }
} }
} }

View File

@@ -10,6 +10,7 @@ import { requestChatData } from "./request";
import { stableDiff } from "./stableDiff"; import { stableDiff } from "./stableDiff";
import { processScript, processScriptFull } from "./scripts"; import { processScript, processScriptFull } from "./scripts";
import { exampleMessage } from "./exampleMessages"; import { exampleMessage } from "./exampleMessages";
import { sayTTS } from "./tts";
export interface OpenAIChat{ export interface OpenAIChat{
role: 'system'|'user'|'assistant' role: 'system'|'user'|'assistant'
@@ -165,7 +166,7 @@ export async function sendChat(chatProcessIndex = -1):Promise<boolean> {
}).join('\n\n')) + db.maxResponse) + 150 }).join('\n\n')) + db.maxResponse) + 150
let chats:OpenAIChat[] = exampleMessage(currentChar) let chats:OpenAIChat[] = exampleMessage(currentChar)
chats.push({ chats.push({
role: 'system', role: 'system',
content: '[Start a new chat]' content: '[Start a new chat]'
@@ -214,9 +215,6 @@ export async function sendChat(chatProcessIndex = -1):Promise<boolean> {
currentTokens += (await tokenize(systemMsg) + 1) currentTokens += (await tokenize(systemMsg) + 1)
} }
console.log(currentTokens)
console.log(maxContextTokens)
while(currentTokens > maxContextTokens){ while(currentTokens > maxContextTokens){
if(chats.length <= 1){ if(chats.length <= 1){
alertError(language.errors.toomuchtoken) alertError(language.errors.toomuchtoken)
@@ -228,8 +226,6 @@ export async function sendChat(chatProcessIndex = -1):Promise<boolean> {
chats.splice(0, 1) chats.splice(0, 1)
} }
console.log(currentTokens)
let bias:{[key:number]:number} = {} let bias:{[key:number]:number} = {}
for(let i=0;i<currentChar.bias.length;i++){ for(let i=0;i<currentChar.bias.length;i++){
@@ -318,6 +314,7 @@ export async function sendChat(chatProcessIndex = -1):Promise<boolean> {
data: result, data: result,
saying: currentChar.chaId saying: currentChar.chaId
}) })
await sayTTS(currentChar, result)
setDatabase(db) setDatabase(db)
} }

71
src/ts/process/tts.ts Normal file
View File

@@ -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<voices.length;i++){
if(voices[i].name === character.ttsSpeech){
voiceIndex = i
}
}
utterThis.voice = voices[voiceIndex]
speechSynthesis.speak(utterThis)
}
break
}
case "elevenlab": {
const audioContext = new AudioContext();
const da = await fetch(`https://api.elevenlabs.io/v1/text-to-speech/${character.ttsSpeech}`, {
body: JSON.stringify({
text: text
}),
method: "POST",
headers: {
"Content-Type": "application/json",
'xi-api-key': db.elevenLabKey || undefined
}
})
if(da.status >= 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
}

View File

@@ -1 +1 @@
{"version":"0.8.2"} {"version":"0.9.0"}