diff --git a/package.json b/package.json
index fe36bd3d..4585bd18 100644
--- a/package.json
+++ b/package.json
@@ -32,6 +32,7 @@
"gpt3-tokenizer": "^1.1.5",
"html-to-image": "^1.11.11",
"isomorphic-dompurify": "^1.8.0",
+ "libsodium-wrappers-sumo": "^0.7.11",
"localforage": "^1.10.0",
"lodash": "^4.17.21",
"lucide-svelte": "^0.260.0",
@@ -61,6 +62,7 @@
"@tsconfig/svelte": "^3.0.0",
"@types/blueimp-md5": "^2.18.0",
"@types/dompurify": "^3.0.2",
+ "@types/libsodium-wrappers-sumo": "^0.7.5",
"@types/lodash": "^4.14.195",
"@types/lodash.clonedeep": "^4.5.7",
"@types/lodash.isequal": "^4.5.6",
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index 8952fe2e..fddd26dc 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -59,6 +59,9 @@ dependencies:
isomorphic-dompurify:
specifier: ^1.8.0
version: 1.8.0
+ libsodium-wrappers-sumo:
+ specifier: ^0.7.11
+ version: 0.7.11
localforage:
specifier: ^1.10.0
version: 1.10.0
@@ -142,6 +145,9 @@ devDependencies:
'@types/dompurify':
specifier: ^3.0.2
version: 3.0.2
+ '@types/libsodium-wrappers-sumo':
+ specifier: ^0.7.5
+ version: 0.7.5
'@types/lodash':
specifier: ^4.14.195
version: 4.14.195
@@ -847,6 +853,16 @@ packages:
/@types/estree@1.0.1:
resolution: {integrity: sha512-LG4opVs2ANWZ1TJoKc937iMmNstM/d0ae1vNbnBvBhqCSezgVUOzcLCqbI5elV8Vy6WKwKjaqR+zO9VKirBBCA==}
+ /@types/libsodium-wrappers-sumo@0.7.5:
+ resolution: {integrity: sha512-CL7rmLxw28H/FpFUnMu5BzzRsE+ICxHBpRoaY8ks+3HMsCJdA/Vp809sj+qNhw64Ht0OEnfoN3BC1sHwagoVaw==}
+ dependencies:
+ '@types/libsodium-wrappers': 0.7.10
+ dev: true
+
+ /@types/libsodium-wrappers@0.7.10:
+ resolution: {integrity: sha512-BqI9B92u+cM3ccp8mpHf+HzJ8fBlRwdmyd6+fz3p99m3V6ifT5O3zmOMi612PGkpeFeG/G6loxUnzlDNhfjPSA==}
+ dev: true
+
/@types/lodash.clonedeep@4.5.7:
resolution: {integrity: sha512-ccNqkPptFIXrpVqUECi60/DFxjNKsfoQxSQsgcBJCX/fuX1wgyQieojkcWH/KpE3xzLoWN/2k+ZeGqIN3paSvw==}
dependencies:
@@ -1903,6 +1919,16 @@ packages:
engines: {node: '>=6'}
dev: true
+ /libsodium-sumo@0.7.11:
+ resolution: {integrity: sha512-bY+7ph7xpk51Ez2GbE10lXAQ5sJma6NghcIDaSPbM/G9elfrjLa0COHl/7P6Wb/JizQzl5UQontOOP1z0VwbLA==}
+ dev: false
+
+ /libsodium-wrappers-sumo@0.7.11:
+ resolution: {integrity: sha512-DGypHOmJbB1nZn89KIfGOAkDgfv5N6SBGC3Qvmy/On0P0WD1JQvNRS/e3UL3aFF+xC0m+MYz5M+MnRnK2HMrKQ==}
+ dependencies:
+ libsodium-sumo: 0.7.11
+ dev: false
+
/lie@3.1.1:
resolution: {integrity: sha512-RiNhHysUjhrDQntfYSfY4MU24coXXdEOgw9WGcKHNeEwffDYbF//u87M1EWaMGzuFoSbqW0C9C6lEEhDOAswfw==}
dependencies:
diff --git a/src/lib/Setting/Pages/BotSettings.svelte b/src/lib/Setting/Pages/BotSettings.svelte
index 1adb3629..6fd1a737 100644
--- a/src/lib/Setting/Pages/BotSettings.svelte
+++ b/src/lib/Setting/Pages/BotSettings.svelte
@@ -19,6 +19,7 @@
import SelectInput from "src/lib/UI/GUI/SelectInput.svelte";
import OptionInput from "src/lib/UI/GUI/OptionInput.svelte";
import { openRouterModels } from "src/ts/model/openrouter";
+ import { novelLogin } from "src/ts/process/models/nai";
let tokens = {
mainPrompt: 0,
@@ -192,9 +193,15 @@
{/if}
{#if $DataBase.aiModel === "novelai" || $DataBase.subModel === "novelai" || $DataBase.aiModel === 'novelai_kayra' || $DataBase.subModel === 'novelai_kayra'}
+
NovelAI Bearer Token
+ {#if !($DataBase.novelai.token)}
+
+
+
+ {/if}
{/if}
{#if $DataBase.aiModel === "kobold" || $DataBase.subModel === "kobold"}
diff --git a/src/ts/process/models/nai.ts b/src/ts/process/models/nai.ts
index 28779b8e..b0d0f35c 100644
--- a/src/ts/process/models/nai.ts
+++ b/src/ts/process/models/nai.ts
@@ -1,6 +1,9 @@
-import { DataBase } from "src/ts/storage/database"
+import { DataBase, setDatabase } from "src/ts/storage/database"
import type { OpenAIChat } from ".."
import { get } from "svelte/store"
+import { globalFetch } from "src/ts/storage/globalApi"
+import { alertError, alertInput, alertNormal, alertWait } from "src/ts/alert"
+import { sleep } from "src/ts/util"
export function stringlizeNAIChat(formated:OpenAIChat[], char:string = ''){
const db = get(DataBase)
@@ -28,6 +31,68 @@ export function stringlizeNAIChat(formated:OpenAIChat[], char:string = ''){
return resultString.join('\n\n') + `\n\n${char}:`
}
+export const novelLogin = async () => {
+ try {
+ const username = await alertInput("NovelAI ID")
+
+ const password = await alertInput("NovelAI Password")
+
+
+ alertWait('Logging in to NovelAI')
+ const _sodium = await import('libsodium-wrappers-sumo')
+ await sleep(1000)
+ await _sodium.ready
+ const sodium = _sodium;
+
+ // I don't know why, but this is needed to make it work
+ console.log(sodium)
+ await sleep(1000)
+
+ const key = sodium
+ .crypto_pwhash(
+ 64,
+ new Uint8Array(Buffer.from(password)),
+ sodium.crypto_generichash(
+ sodium.crypto_pwhash_SALTBYTES,
+ password.slice(0, 6) + username + 'novelai_data_access_key'
+ ),
+ 2,
+ 2e6,
+ sodium.crypto_pwhash_ALG_ARGON2ID13,
+ 'base64'
+ )
+ .slice(0, 64)
+
+ const r = await globalFetch('https://api.novelai.net/user/login', {
+ method: 'POST',
+ headers: {
+ Accept: 'application/json',
+ 'Content-Type': 'application/json',
+ },
+ body:{
+ key: key
+ }
+ })
+
+ if ((!r.ok) || (!r.data?.accessToken)) {
+ alertError(`Failed to authenticate with NovelAI: ${r.data?.message ?? r.data}`)
+ return
+ }
+
+ const data = r.data?.accessToken
+
+ const db = get(DataBase)
+ db.novelai.token = data
+
+ alertNormal('Logged in to NovelAI')
+ setDatabase(db)
+
+
+ } catch (error) {
+ alertError(`Failed to authenticate with NovelAI: ${error}`)
+ }
+}
+
export interface NAISettings{
topK: number
topP: number