diff --git a/package.json b/package.json
index c55db78c..3d00d790 100644
--- a/package.json
+++ b/package.json
@@ -36,6 +36,7 @@
"gpt3-tokenizer": "^1.1.5",
"html-to-image": "^1.11.11",
"isomorphic-dompurify": "^1.8.0",
+ "jszip": "^3.10.1",
"libsodium-wrappers-sumo": "^0.7.11",
"localforage": "^1.10.0",
"lodash": "^4.17.21",
@@ -91,4 +92,4 @@
"vite-plugin-top-level-await": "^1.3.1",
"vite-plugin-wasm": "^3.2.2"
}
-}
\ No newline at end of file
+}
diff --git a/server.sh b/server.sh
old mode 100755
new mode 100644
diff --git a/server/node/server.cjs b/server/node/server.cjs
index faf1aa48..46d41d10 100644
--- a/server/node/server.cjs
+++ b/server/node/server.cjs
@@ -61,7 +61,6 @@ const reverseProxyFunc = async (req, res, next) => {
headers: header,
body: JSON.stringify(req.body)
});
-
// get response body as stream
const originalBody = originalResponse.body;
// get response headers
diff --git a/src/lib/Setting/Pages/OtherBotSettings.svelte b/src/lib/Setting/Pages/OtherBotSettings.svelte
index 7e1e2e89..d098445a 100644
--- a/src/lib/Setting/Pages/OtherBotSettings.svelte
+++ b/src/lib/Setting/Pages/OtherBotSettings.svelte
@@ -2,12 +2,16 @@
import Check from "src/lib/UI/GUI/CheckInput.svelte";
import { language } from "src/lang";
import Help from "src/lib/Others/Help.svelte";
+ import { selectSingleFile } from "src/ts/util";
import { DataBase } from "src/ts/storage/database";
import { isTauri } from "src/ts/storage/globalApi";
import NumberInput from "src/lib/UI/GUI/NumberInput.svelte";
import TextInput from "src/lib/UI/GUI/TextInput.svelte";
import SelectInput from "src/lib/UI/GUI/SelectInput.svelte";
import OptionInput from "src/lib/UI/GUI/OptionInput.svelte";
+ import SliderInput from "src/lib/UI/GUI/SliderInput.svelte";
+ import Button from "src/lib/UI/GUI/Button.svelte";
+ import { convertToBase64 } from "src/ts/process/uinttobase64";
{language.otherBots}
@@ -18,6 +22,7 @@
None
Stable Diffusion WebUI
+ Novel AI
@@ -56,6 +61,65 @@
{/if}
{/if}
+{#if $DataBase.sdProvider === 'novelai'}
+ Novel AI {language.providerURL}
+
+ API Key
+
+
+ Model
+
+
+ Enable I2I
+
+
+
+ {#if $DataBase.NAII2I}
+
+ strength
+
+ {$DataBase.NAIImgConfig.strength}
+ noise
+
+ {$DataBase.NAIImgConfig.noise}
+
+ base image
+
+ If empty, a profile picture is sent.
+
+
+ {/if}
+
+ Width
+
+ Height
+
+ Sampler
+
+ steps
+
+ CFG scale
+
+
+ {#if !$DataBase.NAII2I}
+ Use SMEA
+
+ Use DYN
+
+ {/if}
+{/if}
TTS
ElevenLabs API key
@@ -64,6 +128,9 @@
VOICEVOX URL
+NovelAI API key
+
+
{language.emotionImage}
{language.emotionMethod}
diff --git a/src/lib/SideBars/CharConfig.svelte b/src/lib/SideBars/CharConfig.svelte
index 51d05dfe..5092502d 100644
--- a/src/lib/SideBars/CharConfig.svelte
+++ b/src/lib/SideBars/CharConfig.svelte
@@ -15,7 +15,7 @@
import Help from "../Others/Help.svelte";
import RegexData from "./Scripts/RegexData.svelte";
import { exportChar, shareRisuHub } from "src/ts/characterCards";
- import { getElevenTTSVoices, getWebSpeechTTSVoices, getVOICEVOXVoices, oaiVoices } from "src/ts/process/tts";
+ import { getElevenTTSVoices, getWebSpeechTTSVoices, getVOICEVOXVoices, oaiVoices, getNovelAIVoices, FixNAITTS } from "src/ts/process/tts";
import { checkCharOrder, getFileSrc } from "src/ts/storage/globalApi";
import { addGroupChar, rmCharFromGroup } from "src/ts/process/group";
import RealmUpload from "../UI/Realm/RealmUpload.svelte";
@@ -27,6 +27,7 @@
import OptionInput from "../UI/GUI/OptionInput.svelte";
import RegexList from "./Scripts/RegexList.svelte";
import TriggerList from "./Scripts/TriggerList.svelte";
+
let subMenu = 0
let openHubUpload = false
@@ -130,12 +131,20 @@
}
}
}
+
}
onDestroy(unsub);
$:licensed = (currentChar.type === 'character') ? currentChar.data.license : ''
+ $: if (currentChar.data.ttsMode === 'novelai' && (currentChar.data as character).naittsConfig === undefined) {
+ (currentChar.data as character).naittsConfig = {
+ customvoice: false,
+ voice: 'Aini',
+ version: 'v2'
+ };
+ }
{#if licensed !== 'private'}
@@ -542,6 +551,7 @@
Web Speech
VOICEVOX
OpenAI
+ NovelAI
@@ -600,6 +610,31 @@
Intonation scale
To use VOICEVOX, you need to run a colab and put the localtunnel URL in "Settings → Other Bots". https://colab.research.google.com/drive/1tyeXJSklNfjW-aZJAib1JfgOMFarAwze
+ {:else if currentChar.data.ttsMode === 'novelai'}
+ Custom Voice Seed
+
+ {#if !currentChar.data.naittsConfig.customvoice}
+ Voice
+
+ {#await getNovelAIVoices() then voices}
+ {#each voices as voiceGroup}
+
+ {/each}
+ {/await}
+
+ {:else}
+ Voice
+
+ {/if}
+ Version
+
+ v1
+ v2
+
{/if}
{#if currentChar.data.ttsMode === 'openai'}
OpenAI TTS uses your OpenAI key on the chat model section
@@ -610,7 +645,7 @@
{/each}
{/if}
- {#if currentChar.data.ttsMode === 'webspeech' || currentChar.data.ttsMode === 'elevenlab' || currentChar.data.ttsMode === 'VOICEVOX'}
+ {#if currentChar.data.ttsMode === 'webspeech' || currentChar.data.ttsMode === 'elevenlab' || currentChar.data.ttsMode === 'VOICEVOX' || currentChar.data.ttsMode === 'novelai'}
diff --git a/src/ts/process/generateSeed.ts b/src/ts/process/generateSeed.ts
new file mode 100644
index 00000000..670cbc98
--- /dev/null
+++ b/src/ts/process/generateSeed.ts
@@ -0,0 +1,9 @@
+export function generateRandomSeed(length) {
+ let result = '';
+ const characters = '0123456789';
+ const charactersLength = characters.length;
+ for (let i = 0; i < length; i++) {
+ result += characters.charAt(Math.floor(Math.random() * charactersLength));
+ }
+ return result;
+}
\ No newline at end of file
diff --git a/src/ts/process/processzip.ts b/src/ts/process/processzip.ts
new file mode 100644
index 00000000..6ef98fb9
--- /dev/null
+++ b/src/ts/process/processzip.ts
@@ -0,0 +1,15 @@
+import JSZip from "jszip";
+
+export async function processZip(dataArray: Uint8Array): Promise {
+ const blob = new Blob([dataArray], { type: "application/zip" });
+ const zip = new JSZip();
+ const zipData = await zip.loadAsync(blob);
+
+ const imageFile = Object.keys(zipData.files).find(fileName => /\.(jpg|jpeg|png)$/.test(fileName));
+ if (imageFile) {
+ const imageData = await zipData.files[imageFile].async("base64");
+ return `data:image/png;base64,${imageData}`;
+ } else {
+ throw new Error("No image found in ZIP file");
+ }
+}
\ No newline at end of file
diff --git a/src/ts/process/stableDiff.ts b/src/ts/process/stableDiff.ts
index bcf11ffb..262fbb14 100644
--- a/src/ts/process/stableDiff.ts
+++ b/src/ts/process/stableDiff.ts
@@ -2,11 +2,13 @@ import { get } from "svelte/store"
import { DataBase, type character } from "../storage/database"
import { requestChatData } from "./request"
import { alertError } from "../alert"
-import { globalFetch } from "../storage/globalApi"
+import { globalFetch, readImage } from "../storage/globalApi"
import { CharEmotion } from "../stores"
import type { OpenAIChat } from "."
-
-
+import { processZip } from "./processzip"
+import { convertToBase64 } from "./uinttobase64"
+import type { List } from "lodash"
+import { generateRandomSeed } from "./generateSeed"
export async function stableDiff(currentChar:character,prompt:string){
const mainPrompt = "assistant is a chat analyzer.\nuser will input a data of situation with key and values before chat, and a chat of a user and character.\nView the status of the chat and change the data.\nif data's key starts with $, it must change it every time.\nif data value is none, it must change it."
let db = get(DataBase)
@@ -129,7 +131,7 @@ export async function stableDiff(currentChar:character,prompt:string){
"cfg_scale": db.sdCFG,
"prompt": prompts.join(','),
"negative_prompt": neg,
- 'sampler_name': db.sdConfig.sampler_name,
+ "sampler_name": db.sdConfig.sampler_name,
"enable_hr": db.sdConfig.enable_hr,
"denoising_strength": db.sdConfig.denoising_strength,
"hr_scale": db.sdConfig.hr_scale,
@@ -161,6 +163,113 @@ export async function stableDiff(currentChar:character,prompt:string){
return false
}
}
+ if(db.sdProvider === 'novelai'){
+ let prompts:string[] = []
+ let neg = ''
+ for(let i=0;i {
+ const sourceNode = audioContext.createBufferSource();
+ sourceNode.buffer = decodedData;
+ sourceNode.connect(audioContext.destination);
+ sourceNode.start();
+ });
+ } else {
+ alertError("Error fetching or decoding audio data");
+ }
+ break;
+ }
}
-
}
export const oaiVoices = [
@@ -189,4 +210,29 @@ export async function getVOICEVOXVoices() {
})
speakersInfo.unshift({ name: "None", list: null})
return speakersInfo;
+}
+
+export async function getNovelAIVoices(){
+ return [
+ {
+ gender: "UNISEX",
+ voices: ['Anananan']
+ },
+ {
+ gender: "FEMALE",
+ voices: ['Aini', 'Orea', 'Claea', 'Lim', 'Aurae', 'Naia']
+ },
+ {
+ gender: "MALE",
+ voices: ['Aulon', 'Elei', 'Ogma', 'Raid', 'Pega', 'Lam']
+ }
+ ];
+}
+
+export async function FixNAITTS(data:character){
+ if (data.naittsConfig === undefined){
+ data.naittsConfig.voice = 'Anananan'
+ }
+
+ return data
}
\ No newline at end of file
diff --git a/src/ts/process/uinttobase64.ts b/src/ts/process/uinttobase64.ts
new file mode 100644
index 00000000..230d911a
--- /dev/null
+++ b/src/ts/process/uinttobase64.ts
@@ -0,0 +1,17 @@
+export async function convertToBase64(data: Uint8Array): Promise {
+ return new Promise((resolve, reject) => {
+ const blob = new Blob([data]);
+ const reader = new FileReader();
+
+ reader.onloadend = function() {
+ const base64String = reader.result as string;
+ resolve(base64String);
+ };
+
+ reader.onerror = function(error) {
+ reject(error);
+ };
+
+ reader.readAsDataURL(blob);
+ });
+}
\ No newline at end of file
diff --git a/src/ts/storage/database.ts b/src/ts/storage/database.ts
index 2da37b67..d3fe223f 100644
--- a/src/ts/storage/database.ts
+++ b/src/ts/storage/database.ts
@@ -174,6 +174,18 @@ export function setDatabase(data:Database){
if(checkNullish(data.sdCFG)){
data.sdCFG = 7
}
+ if(checkNullish(data.NAIImgUrl)){
+ data.NAIImgUrl = 'https://api.novelai.net/ai/generate-image'
+ }
+ if(checkNullish(data.NAIApiKey)){
+ data.NAIApiKey = ''
+ }
+ if(checkNullish(data.NAIImgModel)){
+ data.NAIImgModel = 'nai-diffusion-3'
+ }
+ if(checkNullish(data.NAII2I)){
+ data.NAII2I = true
+ }
if(checkNullish(data.textTheme)){
data.textTheme = "standard"
}
@@ -231,6 +243,20 @@ export function setDatabase(data:Database){
hr_upscaler:"Latent"
}
}
+ if(checkNullish(data.NAIImgConfig)){
+ data.NAIImgConfig = {
+ width:512,
+ height:768,
+ sampler:"k_dpmpp_sde",
+ steps:28,
+ scale:5,
+ sm:true,
+ sm_dyn:true,
+ noise:0.0,
+ strength:0.3,
+ image:""
+ }
+ }
if(checkNullish(data.customTextTheme)){
data.customTextTheme = {
FontColorStandard: "#f8f8f2",
@@ -394,6 +420,11 @@ export interface Database{
sdSteps:number
sdCFG:number
sdConfig:sdConfig
+ NAIImgUrl:string
+ NAIApiKey:string
+ NAIImgModel:string
+ NAII2I:boolean
+ NAIImgConfig:NAIImgConfig
runpodKey:string
promptPreprocess:boolean
bias: [string, number][]
@@ -588,6 +619,11 @@ export interface character{
INTONATION_SCALE?: number
VOLUME_SCALE?: number
}
+ naittsConfig?:{
+ customvoice?: boolean
+ voice?: string
+ version?: string
+ }
supaMemory?:boolean
additionalAssets?:[string, string, string][]
ttsReadOnlyQuoted?:boolean
@@ -717,6 +753,18 @@ interface sdConfig{
hr_upscaler:string
}
+interface NAIImgConfig{
+ width:number,
+ height:number,
+ sampler:string,
+ steps:number,
+ scale:number,
+ sm:boolean,
+ sm_dyn:boolean,
+ noise:number,
+ strength:number,
+ image:string
+}
export type FormatingOrderItem = 'main'|'jailbreak'|'chats'|'lorebook'|'globalNote'|'authorNote'|'lastChat'|'description'|'postEverything'|'personaPrompt'
export interface Chat{
diff --git a/src/ts/storage/globalApi.ts b/src/ts/storage/globalApi.ts
index 5ddae585..e7db8f9b 100644
--- a/src/ts/storage/globalApi.ts
+++ b/src/ts/storage/globalApi.ts
@@ -661,7 +661,7 @@ export async function globalFetch(url:string, arg:{
method: method,
signal: arg.abortSignal
})
-
+
addFetchLog("Uint8Array Response", da.ok && da.status >= 200 && da.status < 300)
return {
ok: da.ok && da.status >= 200 && da.status < 300,
@@ -685,6 +685,16 @@ export async function globalFetch(url:string, arg:{
headers: headers,
method: method
})
+ if(da.headers.get('content-type')?.includes('application/x-zip-compressed')){
+ const daText = await da.blob()
+
+ addFetchLog(daText, da.ok && da.status >= 200 && da.status < 300)
+ return {
+ ok: da.ok && da.status >= 200 && da.status < 300,
+ data: daText,
+ headers: Object.fromEntries(da.headers)
+ }
+ }
const daText = await da.text()
try {
const dat = JSON.parse(daText)