Update to 1.17.0 (#105)

This commit is contained in:
kwaroran
2023-05-25 21:55:20 +09:00
committed by GitHub
20 changed files with 3886 additions and 32 deletions

3600
package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

View File

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

View File

@@ -21,7 +21,7 @@
</script>
<main class="flex bg-bg w-full h-full">
<main class="flex bg-bg w-full h-full max-w-100vw">
{#if !$loadedStore}
<div class="w-full h-full flex justify-center items-center text-gray-200 text-xl">
<span>Loading...</span>

View File

@@ -42,7 +42,7 @@ export const languageEnglish = {
loreActivationKey: "If one of the activation key exists in context, the lore will be activated and prompt will go in. seperated by commas.",
loreorder: "If insert Order is higher, it will effect the model more, and it will more lessly cuted when activated lore are many.",
bias:"bias is a key-value data which modifies the likelihood of string appearing.\nit can be -100 to 100, higher values will be more likely to appear, and lower values will be more unlikely to appear \nWarning: if the tokenizer is wrong, it not work properly.",
emotion: "Emotion Images option shows image depending at character's emotion which is analized by character's response. you must input emotion name as words *(like joy, happy, fear and etc.)* .emotion named **netural** will be default emotion if it exists. must be more then 3 images to work properly.",
emotion: "Emotion Images option shows image depending at character's emotion which is analized by character's response. you must input emotion name as words *(like joy, happy, fear and etc.)* .emotion named **neutral** will be default emotion if it exists. must be more then 3 images to work properly.",
imggen: "Image Generation option generates and shows image from external program. the image is generated by image prompt, which is made by analizing current chat. \n\n image generation is analized based on key-value arguments, which are configarable in below."
+ "\n\Zn**'always'** key applys always, and dosen't changes. **'negative'** key applys always in negative value for image generation."
+ "\n\nobjects with other key's value will change according to the key's name as the chat progresses."
@@ -51,7 +51,7 @@ export const languageEnglish = {
+ "\n- if the key starts with **$**, the key's value will more likely to change."
+ "\n\nwhen the image is first generated, you can only change it by modifying 'Current Image Generation Data' in below.",
regexScript: "Regex Script is a custom script that is embbedded to the character. it replaces string that matches IN to OUT.\n\nThere are three type options."
regexScript: "Regex Script is a custom script that replaces string that matches IN to OUT.\n\nThere are three type options."
+ "- **Modify Input** modifys user's input"
+ "- **Modify Output** modifys character's output"
+ "- **Modify Request Data** modifys current chat data when sent.\n\nIN must be a regex without flags and without slashes in start and end.\n\nOUT is a normal string."
@@ -271,4 +271,6 @@ export const languageEnglish = {
replaceGlobalNote: "Global Note Replacement",
charLoreBook: 'Character Lorebook',
globalLoreBook: 'Global Lorebook',
globalRegexScript: "Regex Script",
}

View File

@@ -171,7 +171,7 @@ export const languageKorean = {
loreActivationKey: "활성화 키 중 하나가 컨텍스트에 존재하면 해당 로어가 활성화됩니다. 쉼표로 구분된 활성화를 구분하세요.",
loreorder: "순서가 높을수록 모델에 더 많은 영향을 미치며, 활성화된 로어가 많을 때 잘리지 않습니다.",
bias:"바이어스는 문자열이 나타날 가능성을 수정하는 키-값 데이터로, -100에서 100까지 가능하며 값이 클수록 나타날 가능성이 높고, 값이 작을수록 나타날 가능성이 낮습니다 \n경고: 토큰라이저가 잘못되면 제대로 작동하지 않습니다.",
emotion: "감정 이미지 옵션은 캐릭터의 반응으로 분석된 캐릭터의 감정에 따라 이미지를 표시합니다. 감정 이름은 단어 *(예시: joy, happy, fear 등)* 로 입력해야 하며, **netural** 이라는 이름의 감정이 존재하면 기본 감정이 됩니다. 제대로 작동하려면 이미지가 3개 이상이어야 합니다.",
emotion: "감정 이미지 옵션은 캐릭터의 반응으로 분석된 캐릭터의 감정에 따라 이미지를 표시합니다. 감정 이름은 단어 *(예시: joy, happy, fear 등)* 로 입력해야 하며, **neutral** 이라는 이름의 감정이 존재하면 기본 감정이 됩니다. 제대로 작동하려면 이미지가 3개 이상이어야 합니다.",
imggen: "이미지 생성 옵션은 외부 프로그램에서 이미지를 생성하고 생성한 이미지를 표시합니다. 이미지는 현재 채팅을 분석하여 만든 이미지 프롬프트에 의해 생성됩니다. \n이미지 생성은 아래에서 구성할 수 있는 키-값 인수를 기반으로 분석됩니다."
+ "\n\n**'always'** 키는 언제나 들어가며, 바뀌지 않습니다. **'negative'** 키는 항상 이미지 생성의 네거티브 값으로 들어갑니다."
+ "\n\n채팅이 진행됨에 따라 키의 이름에 따라 값이 변경됩니다."
@@ -180,7 +180,7 @@ export const languageKorean = {
+ "\n- 키의 이름이 **$** 로 시작할 시, 값은 더 자주 변합니다."
+ "\n\n이미지가 처음 생성된 이후부터는 '현재 이미지 생성 데이터'를 수정하여 변경할 수 있습니다.",
experimental: "실험적 기능입니다. 불안정할 수 있습니다.",
regexScript: "정규식 스크립트는 캐릭터에 종속된 커스텀 스크립트입니다. IN의 조건에 맞는 문자열을 OUT으로 변경합니다.\n\n타입은 세가지가 있습니다."
regexScript: "정규식 스크립트는 IN의 조건에 맞는 문자열을 OUT으로 변경합니다.\n\n타입은 세가지가 있습니다."
+ "- **입력문 수정** 유저의 입력문을 수정합니다"
+ "- **출력문 수정** 캐릭터의 출력문을 수정합니다"
+ "- **리퀘스트 데이터 수정** 리퀘스트를 보낼 때 채팅 데이터를 수정합니다.\n\nIN은 flag가 없고, 양끝에 슬레시가 없는 Regex여야 합니다.\n\nOUT은 일반 문자열입니다."
@@ -250,5 +250,5 @@ export const languageKorean = {
replaceGlobalNote: "글로벌 노트 덮어쓰기",
charLoreBook: '캐릭터 로어북',
globalLoreBook: '글로벌 로어북',
globalRegexScript: "글로별 정규식 스크립트",
}

View File

@@ -64,15 +64,15 @@
$: displaya(message)
</script>
<div class="flex max-w-full justify-center" class:bgc={isLastMemory}>
<div class="text-neutral-200 mt-1 mb-1 p-2 bg-transparent flex-grow ml-4 mr-4 border-t-gray-900 border-opacity-30 border-transparent flexium items-start">
<div class="text-neutral-200 mt-1 ml-4 mr-4 mb-1 p-2 bg-transparent flex-grow border-t-gray-900 border-opacity-30 border-transparent flexium items-start max-w-full" >
{#await img}
<div class="shadow-lg bg-gray-500 mt-2" style={`height:${$DataBase.iconsize * 3.5 / 100}rem;width:${$DataBase.iconsize * 3.5 / 100}rem`}
<div class="shadow-lg bg-gray-500 mt-2" style={`height:${$DataBase.iconsize * 3.5 / 100}rem;width:${$DataBase.iconsize * 3.5 / 100}rem;min-width:${$DataBase.iconsize * 3.5 / 100}rem`}
class:rounded-md={!$DataBase.roundIcons} class:rounded-full={$DataBase.roundIcons} />
{:then m}
<div class="shadow-lg bg-gray-500 mt-2" style={m + `height:${$DataBase.iconsize * 3.5 / 100}rem;width:${$DataBase.iconsize * 3.5 / 100}rem`}
<div class="shadow-lg bg-gray-500 mt-2" style={m + `height:${$DataBase.iconsize * 3.5 / 100}rem;width:${$DataBase.iconsize * 3.5 / 100}rem;min-width:${$DataBase.iconsize * 3.5 / 100}rem`}
class:rounded-md={!$DataBase.roundIcons} class:rounded-full={$DataBase.roundIcons} />
{/await}
<span class="flex flex-col ml-4 w-full">
<span class="flex flex-col ml-4 w-full max-w-full min-w-0">
<div class="flexium items-center chat">
<span class="chat text-xl unmargin">{name}</span>
<div class="flex-grow flex items-center justify-end text-gray-500">
@@ -129,7 +129,7 @@
<AutoresizeArea bind:value={message} />
{:else}
{#await ParseMarkdown(msgDisplay, character) then md}
<span class="text chat chattext prose prose-invert"
<span class="text chat chattext prose prose-invert minw-0"
style:font-size="{0.875 * ($DataBase.zoomsize / 100)}rem"
style:line-height="{1.25 * ($DataBase.zoomsize / 100)}rem"
>{@html md}</span>

View File

@@ -26,7 +26,7 @@
})()
</script>
{#if $DataBase.theme === ''}
<div class="flex-grow h-full" style={bgImg}>
<div class="flex-grow h-full min-w-0" style={bgImg}>
{#if $selectedCharID >= 0}
{#if $DataBase.characters[$selectedCharID].viewScreen !== 'none'}
<ResizeBox />

View File

@@ -14,7 +14,7 @@
import { processScript } from "src/ts/process/scripts";
import GithubStars from "../Others/GithubStars.svelte";
import CreatorQuote from "./CreatorQuote.svelte";
import { stopTTS } from "src/ts/process/tts";
import { stopTTS } from "src/ts/process/tts";
let messageInput = ''
let openMenu = false
@@ -200,7 +200,7 @@
}
}}>
<div class="flex items-end mt-2 mb-2">
<textarea class="text-neutral-200 p-2 bg-transparent input-text text-xl flex-grow ml-4 mr-2 border-gray-700 resize-none focus:bg-selected maxw overflow-y-hidden overflow-x-hidden"
<textarea class="text-neutral-200 p-2 bg-transparent input-text text-xl flex-grow ml-4 mr-2 border-gray-700 resize-none focus:bg-selected maxw overflow-y-hidden overflow-x-hidden max-w-full"
bind:value={messageInput}
bind:this={inputEle}
on:keydown={(e) => {

View File

@@ -0,0 +1,30 @@
<script lang="ts">
import { PlusIcon } from "lucide-svelte";
import { language } from "src/lang";
import Help from "src/lib/Others/Help.svelte";
import RegexData from "src/lib/SideBars/RegexData.svelte";
import { DataBase } from "src/ts/database";
</script>
<h2 class="mb-2 text-2xl font-bold mt-2">{language.globalRegexScript} <Help key="regexScript" /></h2>
<table class="contain w-full max-w-full tabler mt-4 flex flex-col p-2 gap-2 border-selected border-1">
{#if $DataBase.globalscript.length === 0}
<div class="text-gray-500">No Scripts</div>
{/if}
{#each $DataBase.globalscript as customscript, i}
<RegexData bind:value={$DataBase.globalscript[i]} onRemove={() => {
let customscript = $DataBase.globalscript
customscript.splice(i, 1)
$DataBase.globalscript = customscript
}}/>
{/each}
</table>
<button class="font-medium cursor-pointer hover:text-green-500 mb-2" on:click={() => {
let script = $DataBase.globalscript
script.push({
comment: "",
in: "",
out: "",
type: "editinput"
})
$DataBase.globalscript = script
}}><PlusIcon /></button>

View File

@@ -58,6 +58,8 @@
<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}>
<span class="text-neutral-200 mt-2">VOICEVOX URL</span>
<input class="text-neutral-200 mb-4 p-2 bg-transparent input-text focus:bg-selected text-sm" bind:value={$DataBase.voicevoxUrl}>
<span class="text-neutral-200 mt-4 text-lg font-bold">{language.SuperMemory} <Help key="superMemory" /></span>
<span class="text-neutral-200 mt-4">{language.SuperMemory} {language.model}</span>

View File

@@ -1,5 +1,5 @@
<script lang="ts">
import { ActivityIcon, BookIcon, BotIcon, BoxIcon, CodeIcon, DiamondIcon, FolderIcon, MonitorIcon, Sailboat, UserIcon, XCircleIcon } from "lucide-svelte";
import { ActivityIcon, AlignLeft, BookIcon, BotIcon, BoxIcon, CodeIcon, DiamondIcon, FolderIcon, MonitorIcon, Sailboat, UserIcon, XCircleIcon } from "lucide-svelte";
import { language } from "src/lang";
import DisplaySettings from "./Pages/DisplaySettings.svelte";
import UserSettings from "./Pages/UserSettings.svelte";
@@ -13,6 +13,7 @@
import Communities from "./Pages/Communities.svelte";
import GlobalLoreBookSettings from "./Pages/GlobalLoreBookSettings.svelte";
import Lorepreset from "./lorepreset.svelte";
import GlobalRegex from "./Pages/GlobalRegex.svelte";
let selected = -1
let openPresetList = false
let openLoreList = false
@@ -56,6 +57,12 @@
<BookIcon />
<span>{language.globalLoreBook}</span>
</button>
<button class="text-gray-400 flex gap-2 items-center hover:text-gray-200" class:text-white={selected === 9} on:click={() => {
selected = 9
}}>
<AlignLeft />
<span>{language.globalRegexScript}</span>
</button>
<button class="text-gray-400 flex gap-2 items-center hover:text-gray-200" class:text-white={selected === 4} on:click={() => {
selected = 4
}}>
@@ -107,6 +114,8 @@
<Communities />
{:else if selected === 8}
<GlobalLoreBookSettings bind:openLoreList />
{:else if selected === 9}
<GlobalRegex/>
{/if}
<button class="absolute top-2 right-2 hover:text-green-500" on:click={() => {
if(window.innerWidth >= 700){

View File

@@ -15,7 +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";
import { getElevenTTSVoices, getWebSpeechTTSVoices, getVOICEVOXVoices } from "src/ts/process/tts";
import { checkCharOrder } from "src/ts/globalApi";
let subMenu = 0
@@ -460,6 +460,7 @@
<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>
<option value="VOICEVOX" class="bg-darkbg appearance-none">VOICEVOX</option>
</select>
@@ -489,8 +490,28 @@
{/each}
</select>
{/await}
{:else if currentChar.data.ttsMode === 'VOICEVOX'}
<span class="text-neutral-200">Voice</span>
<select class="bg-transparent input-text mt-2 mb-4 text-gray-200 appearance-none text-sm" bind:value={currentChar.data.ttsSpeech}>
{#await getVOICEVOXVoices() then voices}
{#each voices as voice}
<option value={voice.id} class="bg-darkbg appearance-none">{voice.name}</option>
{/each}
{/await}
</select>
<span class="text-neutral-200">Speed scale</span>
<input class="bg-transparent input-text mt-2 mb-2 text-gray-200 text-xs resize-none h-5 focus:bg-selected" autocomplete="off" bind:value={currentChar.data.voicevoxConfig.SPEED_SCALE}/>
<span class="text-neutral-200">Pitch scale</span>
<input class="bg-transparent input-text mt-2 mb-2 text-gray-200 text-xs resize-none h-5 focus:bg-selected" autocomplete="off" bind:value={currentChar.data.voicevoxConfig.PITCH_SCALE}/>
<span class="text-neutral-200">Volume scale</span>
<input class="bg-transparent input-text mt-2 mb-2 text-gray-200 text-xs resize-none h-5 focus:bg-selected" autocomplete="off" bind:value={currentChar.data.voicevoxConfig.VOLUME_SCALE}/>
<span class="text-neutral-200">Intonation scale</span>
<input class="bg-transparent input-text mt-2 mb-2 text-gray-200 text-xs resize-none h-5 focus:bg-selected" autocomplete="off" bind:value={currentChar.data.voicevoxConfig.INTONATION_SCALE}/>
{/if}
{#if currentChar.data.ttsMode === 'webspeech' || currentChar.data.ttsMode === 'elevenlab'}
{#if currentChar.data.ttsMode === 'webspeech' || currentChar.data.ttsMode === 'elevenlab' || currentChar.data.ttsMode === 'VOICEVOX'}
<div class="flex items-center mt-2">
<Check bind:check={currentChar.data.ttsReadOnlyQuoted}/>
<span>{language.ttsReadOnlyQuoted}</span>

View File

@@ -286,7 +286,12 @@ export function characterFormatUpdate(index:number|character){
creator: '',
character_version: 0
}
cha.voicevoxConfig = cha.voicevoxConfig ?? {
SPEED_SCALE: 1,
PITCH_SCALE: 0,
INTONATION_SCALE: 1,
VOLUME_SCALE: 1
}
if(cha.postHistoryInstructions){
cha.chats[cha.chatPage].note += "\n" + cha.postHistoryInstructions
cha.chats[cha.chatPage].note = cha.chats[cha.chatPage].note.trim()

View File

@@ -7,7 +7,7 @@ import { cloneDeep } from 'lodash';
export const DataBase = writable({} as any as Database)
export const loadedStore = writable(false)
export let appVer = '1.16.4'
export let appVer = '1.17.0'
export function setDatabase(data:Database){
@@ -187,6 +187,9 @@ export function setDatabase(data:Database){
if(checkNullish(data.elevenLabKey)){
data.elevenLabKey = ''
}
if(checkNullish(data.voicevoxUrl)){
data.voicevoxUrl = ''
}
if(checkNullish(data.supaMemoryPrompt)){
data.supaMemoryPrompt = ''
}
@@ -242,6 +245,12 @@ export function setDatabase(data:Database){
data: []
}]
}
if(checkNullish(data.loreBookPage) || data.loreBook.length < data.loreBookPage){
data.loreBookPage = 0
}
if(checkNullish(data.globalscript)){
data.globalscript = []
}
changeLanguage(data.language)
@@ -309,6 +318,12 @@ export interface character{
}
ttsMode?:string
ttsSpeech?:string
voicevoxConfig?:{
SPEED_SCALE?: number
PITCH_SCALE?: number
INTONATION_SCALE?: number
VOLUME_SCALE?: number
}
supaMemory?:boolean
additionalAssets?:[string, string][]
ttsReadOnlyQuoted?:boolean
@@ -447,6 +462,7 @@ export interface Database{
requestproxy: string
showUnrecommended:boolean
elevenLabKey:string
voicevoxUrl:string
useExperimental:boolean
showMemoryLimit:boolean
roundIcons:boolean
@@ -464,6 +480,8 @@ export interface Database{
token:string,
model:string
}
globalscript: customscript[]
}
interface hordeConfig{

View File

@@ -200,17 +200,56 @@ export async function saveDb(){
)
if(isTauri){
await writeBinaryFile('database/database.bin', dbData, {dir: BaseDirectory.AppData})
await writeBinaryFile(`database/dbbackup-${(Date.now()/100).toFixed()}.bin`, dbData, {dir: BaseDirectory.AppData})
}
else{
await forageStorage.setItem('database/database.bin', dbData)
await forageStorage.setItem(`database/dbbackup-${(Date.now()/100).toFixed()}.bin`, dbData)
}
console.log('saved')
await getDbBackups()
}
await sleep(500)
}
}
async function getDbBackups() {
if(isTauri){
const keys = await readDir('database', {dir: BaseDirectory.AppData})
let backups:number[] = []
for(const key of keys){
if(key.name.startsWith("dbbackup-")){
let da = key.name.substring(9)
da = da.substring(0,da.length-4)
backups.push(parseInt(da))
}
}
backups.sort((a, b) => b - a)
while(backups.length > 20){
const last = backups.pop()
await removeFile(`database/dbbackup-${last}.bin`,{dir: BaseDirectory.AppData})
}
return backups
}
else{
const keys = await forageStorage.keys()
let backups:number[] = []
for(const key of keys){
if(key.startsWith("database/dbbackup-")){
let da = key.substring(18)
da = da.substring(0,da.length-4)
backups.push(parseInt(da))
}
}
console.log(backups)
while(backups.length > 20){
const last = backups.pop()
await forageStorage.removeItem(`database/dbbackup-${last}.bin`)
}
return backups
}
}
let usingSw = false
export async function loadData() {
@@ -233,9 +272,26 @@ export async function loadData() {
pako.deflate(Buffer.from(JSON.stringify({}), 'utf-8'))
,{dir: BaseDirectory.AppData})
}
setDatabase(
JSON.parse(Buffer.from(pako.inflate(Buffer.from(await readBinaryFile('database/database.bin',{dir: BaseDirectory.AppData})))).toString('utf-8'))
)
try {
setDatabase(
JSON.parse(Buffer.from(pako.inflate(Buffer.from(await readBinaryFile('database/database.bin',{dir: BaseDirectory.AppData})))).toString('utf-8'))
)
} catch (error) {
const backups = await getDbBackups()
let backupLoaded = false
for(const backup of backups){
try {
const backupData = await readBinaryFile(`database/dbbackup-${backup}.bin`,{dir: BaseDirectory.AppData})
setDatabase(
JSON.parse(Buffer.from(pako.inflate(Buffer.from(backupData))).toString('utf-8'))
)
backupLoaded = true
} catch (error) {}
}
if(!backupLoaded){
throw "Your save file is corrupted"
}
}
await checkUpdate()
await changeFullscreen()
@@ -246,9 +302,26 @@ export async function loadData() {
gotStorage = pako.deflate(Buffer.from(JSON.stringify({}), 'utf-8'))
await forageStorage.setItem('database/database.bin', gotStorage)
}
setDatabase(
JSON.parse(Buffer.from(pako.inflate(Buffer.from(gotStorage))).toString('utf-8'))
)
try {
setDatabase(
JSON.parse(Buffer.from(pako.inflate(Buffer.from(gotStorage))).toString('utf-8'))
)
} catch (error) {
const backups = await getDbBackups()
let backupLoaded = false
for(const backup of backups){
try {
const backupData:Uint8Array = await forageStorage.getItem(`database/dbbackup-${backup}.bin`)
setDatabase(
JSON.parse(Buffer.from(pako.inflate(Buffer.from(backupData))).toString('utf-8'))
)
backupLoaded = true
} catch (error) {}
}
if(!backupLoaded){
throw "Your save file is corrupted"
}
}
const isDriverMode = await checkDriverInit()
if(isDriverMode){
return

View File

@@ -164,7 +164,7 @@ export async function loadLoreBookPrompt(){
export async function importLoreBook(mode:'global'|'local'|'sglobal'){
const selectedID = get(selectedCharID)
let db = get(DataBase)
const page = db.characters[selectedID].chatPage
const page = mode === 'sglobal' ? -1 : db.characters[selectedID].chatPage
let lore =
mode === 'global' ? db.characters[selectedID].globalLore :
mode === 'sglobal' ? db.loreBook[db.loreBookPage].data :
@@ -225,7 +225,7 @@ export async function exportLoreBook(mode:'global'|'local'|'sglobal'){
try {
const selectedID = get(selectedCharID)
const db = get(DataBase)
const page = db.characters[selectedID].chatPage
const page = mode === 'sglobal' ? -1 : db.characters[selectedID].chatPage
const lore =
mode === 'global' ? db.characters[selectedID].globalLore :
mode === 'sglobal' ? db.loreBook[db.loreBookPage].data :

View File

@@ -1,6 +1,6 @@
import { get } from "svelte/store";
import { CharEmotion, selectedCharID } from "../stores";
import type { character } from "../database";
import { DataBase, type character } from "../database";
const dreg = /{{data}}/g
@@ -11,7 +11,9 @@ export function processScript(char:character, data:string, mode:ScriptMode){
}
export function processScriptFull(char:character, data:string, mode:ScriptMode){
let db = get(DataBase)
let emoChanged = false
const scripts = char.customscript.concat(db.globalscript ?? [])
for (const script of char.customscript){
if(script.type === mode){
const reg = new RegExp(script.in,'g')

View File

@@ -1,6 +1,7 @@
import { get } from "svelte/store";
import { alertError } from "../alert";
import { DataBase, type character } from "../database";
import { translateVox } from "../translator/translator";
let sourceNode:AudioBufferSourceNode = null
@@ -58,6 +59,44 @@ export async function sayTTS(character:character,text:string) {
alertError(await da.text())
}
}
case "VOICEVOX": {
const jpText = await translateVox(text)
console.log(jpText);
const audioContext = new AudioContext();
const query = await fetch(`${db.voicevoxUrl}/audio_query?text=${jpText}&speaker=${character.ttsSpeech}`, {
method: 'POST',
headers: { "Content-Type": "application/json"},
})
if (query.status == 200){
const queryJson = await query.json();
const bodyData = {
accent_phrases: queryJson.accent_phrases,
speedScale: character.voicevoxConfig.SPEED_SCALE,
pitchScale: character.voicevoxConfig.PITCH_SCALE,
volumeScale: character.voicevoxConfig.VOLUME_SCALE,
intonationScale: character.voicevoxConfig.INTONATION_SCALE,
prePhonemeLength: queryJson.prePhonemeLength,
postPhonemeLength: queryJson.postPhonemeLength,
outputSamplingRate: queryJson.outputSamplingRate,
outputStereo: queryJson.outputStereo,
kana: queryJson.kana,
}
console.log(JSON.stringify(bodyData))
console.log (bodyData)
const getVoice = await fetch(`${db.voicevoxUrl}/synthesis?speaker=${character.ttsSpeech}`, {
method: 'POST',
headers: { "Content-Type": "application/json"},
body: JSON.stringify(bodyData),
})
if (getVoice.status == 200 && getVoice.headers.get('content-type') === 'audio/wav'){
const audioBuffer = await audioContext.decodeAudioData(await getVoice.arrayBuffer())
sourceNode = audioContext.createBufferSource();
sourceNode.buffer = audioBuffer;
sourceNode.connect(audioContext.destination);
sourceNode.start();
}
}
}
}
}
@@ -90,4 +129,16 @@ export async function getElevenTTSVoices() {
console.log(res)
return res.voices
}
export async function getVOICEVOXVoices() {
const db = get(DataBase);
const speakerData = await fetch(`${db.voicevoxUrl}/speakers`)
const speakerList = await speakerData.json()
const speakersInfo = speakerList.map((speaker) => {
const normalStyle = speaker.styles.find((style) => style.name === 'ノーマル' || 'ふつう' || '人間ver.')
return {'name': speaker.name, 'id': normalStyle.id}
})
return speakersInfo;
}

View File

@@ -70,4 +70,45 @@ async function googleTrans(text:string, reverse:boolean) {
return result
}
export async function translateVox(text:string) {
const plug = await translatorPlugin(text, 'en', 'jp')
if(plug){
return plug.content
}
return jpTrans(text)
}
async function jpTrans(text:string) {
const host = 'translate.googleapis.com'
const url = `https://${host}/translate_a/single?client=gtx&sl=auto&tl=ja&dt=t&q=` + encodeURIComponent(text)
const f = await fetch(url, {
method: "GET",
})
const res = await f.json()
if(typeof(res) === 'string'){
return res as unknown as string
}
const result = res[0].map((s) => s[0]).filter(Boolean).join('');
return result
}

View File

@@ -1 +1 @@
{"version":"1.16.4"}
{"version":"1.17.0"}