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