{/if}
{#if currentChar.data.ttsMode}
diff --git a/src/ts/process/tts.ts b/src/ts/process/tts.ts
index 18b39caf..9b82082c 100644
--- a/src/ts/process/tts.ts
+++ b/src/ts/process/tts.ts
@@ -311,6 +311,49 @@ export async function sayTTS(character:character,text:string) {
throw new Error(text);
}
}
+ case 'fishspeech':{
+ if (character.fishSpeechConfig.model._id === ''){
+ throw new Error('FishSpeech Model is not selected')
+ }
+ const audioContext = new AudioContext();
+
+ const body = {
+ text: text,
+ reference_id: character.fishSpeechConfig.model._id,
+ chunk_length: character.fishSpeechConfig.chunk_length,
+ normalize: character.fishSpeechConfig.normalize,
+ format: 'mp3',
+ mp3_bitrate: 192,
+ }
+
+
+ console.log(body)
+
+ const response = await globalFetch(`https://api.fish.audio/v1/tts`, {
+ method: 'POST',
+ headers: {
+ 'Content-Type': 'application/json',
+ 'Authorization': `Bearer ${db.fishSpeechKey}`
+ },
+ body: body,
+ rawResponse: true,
+ })
+ console.log(response)
+
+ if (response.ok) {
+ const audioBuffer = response.data.buffer;
+ audioContext.decodeAudioData(audioBuffer, (decodedData) => {
+ const sourceNode = audioContext.createBufferSource();
+ sourceNode.buffer = decodedData;
+ sourceNode.connect(audioContext.destination);
+ sourceNode.start();
+ });
+ } else {
+ const textBuffer: Uint8Array = response.data.buffer
+ const text = Buffer.from(textBuffer).toString('utf-8')
+ throw new Error(text);
+ }
+ }
}
} catch (error) {
alertError(`TTS Error: ${error}`)
diff --git a/src/ts/storage/database.ts b/src/ts/storage/database.ts
index 35bbdd6f..aa7cf35b 100644
--- a/src/ts/storage/database.ts
+++ b/src/ts/storage/database.ts
@@ -351,6 +351,7 @@ export function setDatabase(data:Database){
data.newOAIHandle ??= true
data.gptVisionQuality ??= 'low'
data.huggingfaceKey ??= ''
+ data.fishSpeechKey ??= ''
data.statistics ??= {}
data.reverseProxyOobaArgs ??= {
mode: 'instruct'
@@ -651,6 +652,7 @@ export interface Database{
tpo?:boolean
automark?:boolean
huggingfaceKey:string
+ fishSpeechKey:string
allowAllExtentionFiles?:boolean
translatorPrompt:string
translatorMaxResponse:number
@@ -863,6 +865,16 @@ export interface character{
top_k?:number
text_split_method?: "cut0" | "cut1" | "cut2" | "cut3" | "cut4" | "cut5"
}
+ fishSpeechConfig?:{
+ model?: {
+ _id:string
+ title:string
+ description:string
+ },
+ chunk_length:number,
+ normalize:boolean,
+
+ }
supaMemory?:boolean
additionalAssets?:[string, string, string][]
ttsReadOnlyQuoted?:boolean
diff --git a/src/ts/storage/globalApi.ts b/src/ts/storage/globalApi.ts
index 9af06234..07cf9243 100644
--- a/src/ts/storage/globalApi.ts
+++ b/src/ts/storage/globalApi.ts
@@ -100,6 +100,12 @@ let fileCache:{
let pathCache:{[key:string]:string} = {}
let checkedPaths:string[] = []
+/**
+ * Checks if a file exists in the Capacitor filesystem.
+ *
+ * @param {CapFS.GetUriOptions} getUriOptions - The options for getting the URI of the file.
+ * @returns {Promise} - A promise that resolves to true if the file exists, false otherwise.
+ */
async function checkCapFileExists(getUriOptions: CapFS.GetUriOptions): Promise {
try {
await CapFS.Filesystem.stat(getUriOptions);
@@ -112,6 +118,13 @@ async function checkCapFileExists(getUriOptions: CapFS.GetUriOptions): Promise} - A promise that resolves to the source URL of the file.
+ */
export async function getFileSrc(loc:string) {
if(isTauri){
if(loc.startsWith('assets')){
@@ -213,6 +226,12 @@ export async function getFileSrc(loc:string) {
let appDataDirPath = ''
+/**
+ * Reads an image file and returns its data.
+ *
+ * @param {string} data - The path to the image file.
+ * @returns {Promise} - A promise that resolves to the data of the image file.
+ */
export async function readImage(data:string) {
if(isTauri){
if(data.startsWith('assets')){
@@ -228,6 +247,14 @@ export async function readImage(data:string) {
}
}
+/**
+ * Saves an asset file with the given data, custom ID, and file name.
+ *
+ * @param {Uint8Array} data - The data of the asset file.
+ * @param {string} [customId=''] - The custom ID for the asset file.
+ * @param {string} [fileName=''] - The name of the asset file.
+ * @returns {Promise} - A promise that resolves to the path of the saved asset file.
+ */
export async function saveAsset(data:Uint8Array, customId:string = '', fileName:string = ''){
let id = ''
if(customId !== ''){
@@ -260,6 +287,12 @@ export async function saveAsset(data:Uint8Array, customId:string = '', fileName:
}
}
+/**
+ * Loads an asset file with the given ID.
+ *
+ * @param {string} id - The ID of the asset file to load.
+ * @returns {Promise} - A promise that resolves to the data of the loaded asset file.
+ */
export async function loadAsset(id:string){
if(isTauri){
return await readFile(id,{baseDir: BaseDirectory.AppData})
@@ -271,8 +304,13 @@ export async function loadAsset(id:string){
let lastSave = ''
+/**
+ * Saves the current state of the database.
+ *
+ * @returns {Promise} - A promise that resolves when the database has been saved.
+ */
export async function saveDb(){
- lastSave =JSON.stringify(get(DataBase))
+ lastSave = JSON.stringify(get(DataBase))
let changed = false
syncDrive()
DataBase.subscribe(() => {
@@ -349,7 +387,11 @@ export async function saveDb(){
}
}
-
+/**
+ * Retrieves the database backups.
+ *
+ * @returns {Promise} - A promise that resolves to an array of backup timestamps.
+ */
async function getDbBackups() {
let db = get(DataBase)
if(db?.account?.useSync){
@@ -392,6 +434,11 @@ async function getDbBackups() {
let usingSw = false
+/**
+ * Loads the application data.
+ *
+ * @returns {Promise} - A promise that resolves when the data has been loaded.
+ */
export async function loadData() {
const loaded = get(loadedStore)
if(!loaded){
@@ -556,31 +603,53 @@ export async function loadData() {
}
}
-export async function getFetchData(id:string) {
- for(const log of fetchLog){
- if(log.chatId === id){
- return log
- }
+/**
+ * Retrieves fetch data for a given chat ID.
+ *
+ * @param {string} id - The chat ID to search for in the fetch log.
+ * @returns {fetchLog | null} - The fetch log entry if found, otherwise null.
+ */
+export async function getFetchData(id: string) {
+ for (const log of fetchLog) {
+ if (log.chatId === id) {
+ return log;
+ }
}
- return null
+ return null;
}
-function updateErrorHandling(){
- removeDefaultHandler()
- const errorHandler = (event: ErrorEvent) => {
- console.error(event.error)
- alertError(event.error)
- }
- const rejectHandler = (event: PromiseRejectionEvent) => {
- console.error(event.reason)
- alertError(event.reason)
- }
- window.addEventListener('error', errorHandler)
- window.addEventListener('unhandledrejection', rejectHandler)
+/**
+ * Updates the error handling by removing the default handler and adding custom handlers for errors and unhandled promise rejections.
+ */
+function updateErrorHandling() {
+ removeDefaultHandler();
+ const errorHandler = (event: ErrorEvent) => {
+ console.error(event.error);
+ alertError(event.error);
+ };
+ const rejectHandler = (event: PromiseRejectionEvent) => {
+ console.error(event.reason);
+ alertError(event.reason);
+ };
+ window.addEventListener('error', errorHandler);
+ window.addEventListener('unhandledrejection', rejectHandler);
}
-const knownHostes = ["localhost","127.0.0.1","0.0.0.0"]
+const knownHostes = ["localhost", "127.0.0.1", "0.0.0.0"];
+/**
+ * Interface representing the arguments for the global fetch function.
+ *
+ * @interface GlobalFetchArgs
+ * @property {boolean} [plainFetchForce] - Whether to force plain fetch.
+ * @property {any} [body] - The body of the request.
+ * @property {{ [key: string]: string }} [headers] - The headers of the request.
+ * @property {boolean} [rawResponse] - Whether to return the raw response.
+ * @property {'POST' | 'GET'} [method] - The HTTP method to use.
+ * @property {AbortSignal} [abortSignal] - The abort signal to cancel the request.
+ * @property {boolean} [useRisuToken] - Whether to use the Risu token.
+ * @property {string} [chatId] - The chat ID associated with the request.
+ */
interface GlobalFetchArgs {
plainFetchForce?: boolean;
plainFetchDeforce?: boolean;
@@ -593,70 +662,103 @@ interface GlobalFetchArgs {
chatId?: string;
}
+/**
+ * Interface representing the result of the global fetch function.
+ *
+ * @interface GlobalFetchResult
+ * @property {boolean} ok - Whether the request was successful.
+ * @property {any} data - The data returned from the request.
+ * @property {{ [key: string]: string }} headers - The headers returned from the request.
+ */
interface GlobalFetchResult {
ok: boolean;
data: any;
headers: { [key: string]: string };
}
-export function addFetchLog(arg:{
- body:any,
- headers?:{[key:string]:string},
- response:any,
- success:boolean,
- url:string,
- resType?:string,
- chatId?:string
-}){
+/**
+ * Adds a fetch log entry.
+ *
+ * @param {Object} arg - The arguments for the fetch log entry.
+ * @param {any} arg.body - The body of the request.
+ * @param {{ [key: string]: string }} [arg.headers] - The headers of the request.
+ * @param {any} arg.response - The response from the request.
+ * @param {boolean} arg.success - Whether the request was successful.
+ * @param {string} arg.url - The URL of the request.
+ * @param {string} [arg.resType] - The response type.
+ * @param {string} [arg.chatId] - The chat ID associated with the request.
+ * @returns {number} - The index of the added fetch log entry.
+ */
+export function addFetchLog(arg: {
+ body: any,
+ headers?: { [key: string]: string },
+ response: any,
+ success: boolean,
+ url: string,
+ resType?: string,
+ chatId?: string
+}): number {
fetchLog.unshift({
- body: typeof(arg.body) === 'string' ? arg.body : JSON.stringify(arg.body, null, 2),
- header: JSON.stringify(arg.headers ?? {}, null, 2),
- response: typeof(arg.response) === 'string' ? arg.response : JSON.stringify(arg.response, null, 2),
- responseType: arg.resType ?? 'json',
- success: arg.success,
- date: (new Date()).toLocaleTimeString(),
- url: arg.url,
- chatId: arg.chatId
- })
- return fetchLog.length - 1
+ body: typeof (arg.body) === 'string' ? arg.body : JSON.stringify(arg.body, null, 2),
+ header: JSON.stringify(arg.headers ?? {}, null, 2),
+ response: typeof (arg.response) === 'string' ? arg.response : JSON.stringify(arg.response, null, 2),
+ responseType: arg.resType ?? 'json',
+ success: arg.success,
+ date: (new Date()).toLocaleTimeString(),
+ url: arg.url,
+ chatId: arg.chatId
+ });
+ return fetchLog.length - 1;
}
-
-
+/**
+ * Performs a global fetch request.
+ *
+ * @param {string} url - The URL to fetch.
+ * @param {GlobalFetchArgs} [arg={}] - The arguments for the fetch request.
+ * @returns {Promise} - The result of the fetch request.
+ */
export async function globalFetch(url: string, arg: GlobalFetchArgs = {}): Promise {
try {
- const db = get(DataBase)
- const method = arg.method ?? "POST"
- db.requestmet = "normal"
+ const db = get(DataBase);
+ const method = arg.method ?? "POST";
+ db.requestmet = "normal";
- if (arg.abortSignal?.aborted) { return { ok: false, data: 'aborted', headers: {} }}
+ if (arg.abortSignal?.aborted) { return { ok: false, data: 'aborted', headers: {} }; }
const urlHost = new URL(url).hostname
const forcePlainFetch = ((knownHostes.includes(urlHost) && !isTauri) || db.usePlainFetch || arg.plainFetchForce) && !arg.plainFetchDeforce
- if (knownHostes.includes(urlHost) && !isTauri && !isNodeServer){
- return { ok: false, headers: {}, data: 'You are trying local request on web version. This is not allowed due to browser security policy. Use the desktop version instead, or use a tunneling service like ngrok and set the CORS to allow all.' }
+ if (knownHostes.includes(urlHost) && !isTauri && !isNodeServer) {
+ return { ok: false, headers: {}, data: 'You are trying local request on web version. This is not allowed due to browser security policy. Use the desktop version instead, or use a tunneling service like ngrok and set the CORS to allow all.' };
}
// Simplify the globalFetch function: Detach built-in functions
if (forcePlainFetch) {
- return await fetchWithPlainFetch(url, arg);
+ return await fetchWithPlainFetch(url, arg);
}
if (isTauri) {
- return await fetchWithTauri(url, arg);
+ return await fetchWithTauri(url, arg);
}
if (Capacitor.isNativePlatform()) {
- return await fetchWithCapacitor(url, arg);
+ return await fetchWithCapacitor(url, arg);
}
return await fetchWithProxy(url, arg);
-
+
} catch (error) {
console.error(error);
return { ok: false, data: `${error}`, headers: {} };
}
}
-// Decoupled globalFetch built-in function
+/**
+ * Adds a fetch log entry in the global fetch log.
+ *
+ * @param {any} response - The response data.
+ * @param {boolean} success - Indicates if the fetch was successful.
+ * @param {string} url - The URL of the fetch request.
+ * @param {GlobalFetchArgs} arg - The arguments for the fetch request.
+ */
function addFetchLogInGlobalFetch(response:any, success:boolean, url:string, arg:GlobalFetchArgs){
try{
fetchLog.unshift({
@@ -686,7 +788,13 @@ function addFetchLogInGlobalFetch(response:any, success:boolean, url:string, arg
}
}
-// Decoupled globalFetch built-in function
+/**
+ * Performs a fetch request using plain fetch.
+ *
+ * @param {string} url - The URL to fetch.
+ * @param {GlobalFetchArgs} arg - The arguments for the fetch request.
+ * @returns {Promise} - The result of the fetch request.
+ */
async function fetchWithPlainFetch(url: string, arg: GlobalFetchArgs): Promise {
try {
const headers = { 'Content-Type': 'application/json', ...arg.headers };
@@ -700,7 +808,13 @@ async function fetchWithPlainFetch(url: string, arg: GlobalFetchArgs): Promise} - The result of the fetch request.
+ */
async function fetchWithTauri(url: string, arg: GlobalFetchArgs): Promise {
try {
const headers = { 'Content-Type': 'application/json', ...arg.headers };
@@ -730,7 +844,13 @@ async function fetchWithCapacitor(url: string, arg: GlobalFetchArgs): Promise} - The result of the fetch request.
+ */
async function fetchWithProxy(url: string, arg: GlobalFetchArgs): Promise {
try {
const furl = !isTauri && !isNodeServer ? `${hubURL}/proxy2` : `/proxy2`;
@@ -768,71 +888,97 @@ async function fetchWithProxy(url: string, arg: GlobalFetchArgs): Promise} - A promise that resolves when the service worker is registered and initialized.
+ */
async function registerSw() {
await navigator.serviceWorker.register("/sw.js", {
scope: "/"
});
- await sleep(100)
- const da = await fetch('/sw/init')
- if(!(da.status >= 200 && da.status < 300)){
- location.reload()
- }
- else{
-
+ await sleep(100);
+ const da = await fetch('/sw/init');
+ if (!(da.status >= 200 && da.status < 300)) {
+ location.reload();
}
}
-const re = /\\/g
-function getBasename(data:string){
- const splited = data.replace(re, '/').split('/')
- const lasts = splited[splited.length-1]
- return lasts
+/**
+ * Regular expression to match backslashes.
+ *
+ * @constant {RegExp}
+ */
+const re = /\\/g;
+
+/**
+ * Gets the basename of a given path.
+ *
+ * @param {string} data - The path to get the basename from.
+ * @returns {string} - The basename of the path.
+ */
+function getBasename(data: string) {
+ const splited = data.replace(re, '/').split('/');
+ const lasts = splited[splited.length - 1];
+ return lasts;
}
-export function getUnpargeables(db:Database, uptype:'basename'|'pure' = 'basename') {
- let unpargeable:string[] = []
+/**
+ * Retrieves unpargeable resources from the database.
+ *
+ * @param {Database} db - The database to retrieve unpargeable resources from.
+ * @param {'basename'|'pure'} [uptype='basename'] - The type of unpargeable resources to retrieve.
+ * @returns {string[]} - An array of unpargeable resources.
+ */
+export function getUnpargeables(db: Database, uptype: 'basename' | 'pure' = 'basename') {
+ let unpargeable: string[] = [];
- function addUnparge(data:string){
- if(!data){
- return
+ /**
+ * Adds a resource to the unpargeable list if it is not already included.
+ *
+ * @param {string} data - The resource to add.
+ */
+ function addUnparge(data: string) {
+ if (!data) {
+ return;
}
- if(data === ''){
- return
+ if (data === '') {
+ return;
}
- const bn = uptype === 'basename' ? getBasename(data) : data
- if(!unpargeable.includes(bn)){
- unpargeable.push(bn)
+ const bn = uptype === 'basename' ? getBasename(data) : data;
+ if (!unpargeable.includes(bn)) {
+ unpargeable.push(bn);
}
}
- addUnparge(db.customBackground)
- addUnparge(db.userIcon)
+ addUnparge(db.customBackground);
+ addUnparge(db.userIcon);
- for(const cha of db.characters){
- if(cha.image){
- addUnparge(cha.image)
+ for (const cha of db.characters) {
+ if (cha.image) {
+ addUnparge(cha.image);
}
- if(cha.emotionImages){
- for(const em of cha.emotionImages){
- addUnparge(em[1])
+ if (cha.emotionImages) {
+ for (const em of cha.emotionImages) {
+ addUnparge(em[1]);
}
}
- if(cha.type !== 'group'){
- if(cha.additionalAssets){
- for(const em of cha.additionalAssets){
- addUnparge(em[1])
+ if (cha.type !== 'group') {
+ if (cha.additionalAssets) {
+ for (const em of cha.additionalAssets) {
+ addUnparge(em[1]);
}
}
- if(cha.vits){
- const keys = Object.keys(cha.vits.files)
- for(const key of keys){
- const vit = cha.vits.files[key]
- addUnparge(vit)
+ if (cha.vits) {
+ const keys = Object.keys(cha.vits.files);
+ for (const key of keys) {
+ const vit = cha.vits.files[key];
+ addUnparge(vit);
}
}
- if(cha.ccAssets){
- for(const asset of cha.ccAssets){
- addUnparge(asset.uri)
+ if (cha.ccAssets) {
+ for (const asset of cha.ccAssets) {
+ addUnparge(asset.uri);
}
}
}
@@ -851,83 +997,101 @@ export function getUnpargeables(db:Database, uptype:'basename'|'pure' = 'basenam
if(db.personas){
db.personas.map((v) => {
- addUnparge(v.icon)
- })
+ addUnparge(v.icon);
+ });
}
- return unpargeable
+ return unpargeable;
}
-export function replaceDbResources(db:Database,replacer:{[key:string]:string}) {
- let unpargeable:string[] = []
+/**
+ * Replaces database resources with the provided replacer object.
+ *
+ * @param {Database} db - The database object containing resources to be replaced.
+ * @param {{[key: string]: string}} replacer - An object mapping original resource keys to their replacements.
+ * @returns {Database} - The updated database object with replaced resources.
+ */
+export function replaceDbResources(db: Database, replacer: { [key: string]: string }): Database {
+ let unpargeable: string[] = [];
- function replaceData(data:string){
- if(!data){
- return data
+ /**
+ * Replaces a given data string with its corresponding value from the replacer object.
+ *
+ * @param {string} data - The data string to be replaced.
+ * @returns {string} - The replaced data string or the original data if no replacement is found.
+ */
+ function replaceData(data: string): string {
+ if (!data) {
+ return data;
}
- return replacer[data] ?? data
+ return replacer[data] ?? data;
}
- db.customBackground = replaceData(db.customBackground)
- db.userIcon = replaceData(db.userIcon)
+ db.customBackground = replaceData(db.customBackground);
+ db.userIcon = replaceData(db.userIcon);
- for(const cha of db.characters){
- if(cha.image){
- cha.image = replaceData(cha.image)
+ for (const cha of db.characters) {
+ if (cha.image) {
+ cha.image = replaceData(cha.image);
}
- if(cha.emotionImages){
- for(let i=0;i} - A promise that resolves when the database format check and update is complete.
+ */
+async function checkNewFormat(): Promise {
+ let db = get(DataBase);
- //check data integrity
+ // Check data integrity
db.characters = db.characters.map((v) => {
- if(!v){
- return null
+ if (!v) {
+ return null;
}
- v.chaId ??= uuidv4()
- v.type ??= 'character'
- v.chatPage ??= 0
- v.chats ??= []
- v.customscript ??= []
- v.firstMessage ??= ''
- v.globalLore ??= []
- v.name ??= ''
- v.viewScreen ??= 'none'
- v.emotionImages = v.emotionImages ?? []
+ v.chaId ??= uuidv4();
+ v.type ??= 'character';
+ v.chatPage ??= 0;
+ v.chats ??= [];
+ v.customscript ??= [];
+ v.firstMessage ??= '';
+ v.globalLore ??= [];
+ v.name ??= '';
+ v.viewScreen ??= 'none';
+ v.emotionImages = v.emotionImages ?? [];
- if(v.type === 'character'){
- v.bias ??= []
- v.characterVersion ??= ''
- v.creator ??= ''
- v.desc ??= ''
- v.utilityBot ??= false
- v.tags ??= []
- v.systemPrompt ??= ''
- v.scenario ??= ''
+ if (v.type === 'character') {
+ v.bias ??= [];
+ v.characterVersion ??= '';
+ v.creator ??= '';
+ v.desc ??= '';
+ v.utilityBot ??= false;
+ v.tags ??= [];
+ v.systemPrompt ??= '';
+ v.scenario ??= '';
}
- return v
+ return v;
}).filter((v) => {
- return v !== null
- })
+ return v !== null;
+ });
db.modules = (db.modules ?? []).map((v) => {
- if(v.lorebook){
- v.lorebook = updateLorebooks(v.lorebook)
+ if (v.lorebook) {
+ v.lorebook = updateLorebooks(v.lorebook);
}
return v
})
@@ -948,99 +1112,102 @@ async function checkNewFormat() {
if(!d){
return data
}
- return d
+ return d;
}
}
-
- db.customBackground = checkParge(db.customBackground)
- db.userIcon = checkParge(db.userIcon)
-
- for(let i=0;i= 2){
- db.characters[i].emotionImages[i2][1] = checkParge(db.characters[i].emotionImages[i2][1])
+ if (db.characters[i].emotionImages) {
+ for (let i2 = 0; i2 < db.characters[i].emotionImages.length; i2++) {
+ if (db.characters[i].emotionImages[i2] && db.characters[i].emotionImages[i2].length >= 2) {
+ db.characters[i].emotionImages[i2][1] = checkParge(db.characters[i].emotionImages[i2][1]);
}
}
}
}
-
- db.formatversion = 2
- }
- if(db.formatversion < 3){
- for(let i=0;i 0){
- const id = v4()
- let regexModule:RisuModule = {
+ if (db.formatversion < 4) {
+ db.modules ??= [];
+ db.enabledModules ??= [];
+ // Convert global lore and global regex to modules
+ if (db.globalscript && db.globalscript.length > 0) {
+ const id = v4();
+ let regexModule: RisuModule = {
name: "Global Regex",
description: "Converted from legacy global regex",
id: id,
regex: structuredClone(db.globalscript)
- }
- db.modules.push(regexModule)
- db.enabledModules.push(id)
- db.globalscript = []
+ };
+ db.modules.push(regexModule);
+ db.enabledModules.push(id);
+ db.globalscript = [];
}
- if(db.loreBook && db.loreBook.length > 0){
- const selIndex = db.loreBookPage
- for(let i=0;i 0) {
+ const selIndex = db.loreBookPage;
+ for (let i = 0; i < db.loreBook.length; i++) {
+ const id = v4();
+ let lbModule: RisuModule = {
name: db.loreBook[i].name || "Unnamed Global Lorebook",
description: "Converted from legacy global lorebook",
id: id,
lorebook: structuredClone(db.loreBook[i].data)
+ };
+ db.modules.push(lbModule);
+ if (i === selIndex) {
+ db.enabledModules.push(id);
}
- db.modules.push(lbModule)
- if(i === selIndex){
- db.enabledModules.push(id)
- }
- db.globalscript = []
+ db.globalscript = [];
}
- db.loreBook = []
+ db.loreBook = [];
}
- db.formatversion = 4
+ db.formatversion = 4;
}
- if(!db.characterOrder){
- db.characterOrder = []
+ if (!db.characterOrder) {
+ db.characterOrder = [];
}
- if(db.mainPrompt === oldMainPrompt){
- db.mainPrompt = defaultMainPrompt
+ if (db.mainPrompt === oldMainPrompt) {
+ db.mainPrompt = defaultMainPrompt;
}
- if(db.mainPrompt === oldJailbreak){
- db.mainPrompt = defaultJailbreak
+ if (db.mainPrompt === oldJailbreak) {
+ db.mainPrompt = defaultJailbreak;
}
- for(let i=0;i} - A promise that resolves to a boolean indicating success.
+ */
+ async init(name = 'Binary', ext = ['bin']): Promise {
+ if (isTauri) {
const filePath = await save({
filters: [{
- name: name,
- extensions: ext
+ name: name,
+ extensions: ext
}]
});
- if(!filePath){
+ if (!filePath) {
return false
}
this.writer = new TauriWriter(filePath)
return true
}
- if(Capacitor.isNativePlatform()){
+ if (Capacitor.isNativePlatform()) {
this.writer = new MobileWriter(name + '.' + ext[0])
return true
}
@@ -1269,7 +1507,14 @@ export class LocalWriter{
this.writer = writableStream.getWriter()
return true
}
- async writeBackup(name:string,data: Uint8Array){
+
+ /**
+ * Writes backup data to the file.
+ *
+ * @param {string} name - The name of the backup.
+ * @param {Uint8Array} data - The data to write.
+ */
+ async writeBackup(name: string, data: Uint8Array): Promise {
const encodedName = new TextEncoder().encode(getBasename(name))
const nameLength = new Uint32Array([encodedName.byteLength])
await this.writer.write(new Uint8Array(nameLength.buffer))
@@ -1278,55 +1523,132 @@ export class LocalWriter{
await this.writer.write(new Uint8Array(dataLength.buffer))
await this.writer.write(data)
}
- async write(data:Uint8Array) {
+
+ /**
+ * Writes data to the file.
+ *
+ * @param {Uint8Array} data - The data to write.
+ */
+ async write(data: Uint8Array): Promise {
await this.writer.write(data)
}
- async close(){
+
+ /**
+ * Closes the writer.
+ */
+ async close(): Promise {
await this.writer.close()
}
}
-export class VirtualWriter{
+/**
+ * Class representing a virtual writer.
+ */
+export class VirtualWriter {
buf = new AppendableBuffer()
- async write(data:Uint8Array) {
+
+ /**
+ * Writes data to the buffer.
+ *
+ * @param {Uint8Array} data - The data to write.
+ */
+ async write(data: Uint8Array): Promise {
this.buf.append(data)
}
- async close(){
+
+ /**
+ * Closes the writer. (No operation for VirtualWriter)
+ */
+ async close(): Promise {
// do nothing
}
}
+/**
+ * Index for fetch operations.
+ * @type {number}
+ */
let fetchIndex = 0
-let nativeFetchData:{[key:string]:StreamedFetchChunk[]} = {}
-interface StreamedFetchChunkData{
- type:'chunk',
- body:string,
- id:string
+/**
+ * Stores native fetch data.
+ * @type {{ [key: string]: StreamedFetchChunk[] }}
+ */
+let nativeFetchData: { [key: string]: StreamedFetchChunk[] } = {}
+
+/**
+ * Interface representing a streamed fetch chunk data.
+ * @interface
+ */
+interface StreamedFetchChunkData {
+ type: 'chunk',
+ body: string,
+ id: string
}
-interface StreamedFetchHeaderData{
- type:'headers',
- body:{[key:string]:string},
- id:string,
- status:number
+/**
+ * Interface representing a streamed fetch header data.
+ * @interface
+ */
+interface StreamedFetchHeaderData {
+ type: 'headers',
+ body: { [key: string]: string },
+ id: string,
+ status: number
}
-interface StreamedFetchEndData{
- type:'end',
- id:string
+/**
+ * Interface representing a streamed fetch end data.
+ * @interface
+ */
+interface StreamedFetchEndData {
+ type: 'end',
+ id: string
}
-type StreamedFetchChunk = StreamedFetchChunkData|StreamedFetchHeaderData|StreamedFetchEndData
+/**
+ * Type representing a streamed fetch chunk.
+ * @typedef {StreamedFetchChunkData | StreamedFetchHeaderData | StreamedFetchEndData} StreamedFetchChunk
+ */
+type StreamedFetchChunk = StreamedFetchChunkData | StreamedFetchHeaderData | StreamedFetchEndData
+
+/**
+ * Interface representing a streamed fetch plugin.
+ * @interface
+ */
interface StreamedFetchPlugin {
- streamedFetch(options: { id: string, url:string, body:string, headers:{[key:string]:string} }): Promise<{"error":string,"success":boolean}>;
- addListener(eventName: 'streamed_fetch', listenerFunc: (data:StreamedFetchChunk) => void): void;
+ /**
+ * Performs a streamed fetch operation.
+ * @param {Object} options - The options for the fetch operation.
+ * @param {string} options.id - The ID of the fetch operation.
+ * @param {string} options.url - The URL to fetch.
+ * @param {string} options.body - The body of the fetch request.
+ * @param {{ [key: string]: string }} options.headers - The headers of the fetch request.
+ * @returns {Promise<{ error: string, success: boolean }>} - The result of the fetch operation.
+ */
+ streamedFetch(options: { id: string, url: string, body: string, headers: { [key: string]: string } }): Promise<{ "error": string, "success": boolean }>;
+
+ /**
+ * Adds a listener for the specified event.
+ * @param {string} eventName - The name of the event.
+ * @param {(data: StreamedFetchChunk) => void} listenerFunc - The function to call when the event is triggered.
+ */
+ addListener(eventName: 'streamed_fetch', listenerFunc: (data: StreamedFetchChunk) => void): void;
}
+/**
+ * Indicates whether streamed fetch listening is active.
+ * @type {boolean}
+ */
let streamedFetchListening = false
-let capStreamedFetch:StreamedFetchPlugin|undefined
-if(isTauri){
+/**
+ * The streamed fetch plugin instance.
+ * @type {StreamedFetchPlugin | undefined}
+ */
+let capStreamedFetch: StreamedFetchPlugin | undefined
+
+if (isTauri) {
listen('streamed_fetch', (event) => {
try {
const parsed = JSON.parse(event.payload as string)
@@ -1339,7 +1661,8 @@ if(isTauri){
streamedFetchListening = true
})
}
-if(Capacitor.isNativePlatform()){
+
+if (Capacitor.isNativePlatform()) {
capStreamedFetch = registerPlugin('CapacitorHttp', CapacitorHttp)
capStreamedFetch.addListener('streamed_fetch', (data) => {
@@ -1352,37 +1675,71 @@ if(Capacitor.isNativePlatform()){
streamedFetchListening = true
}
-export class AppendableBuffer{
- buffer:Uint8Array
- deapended:number = 0
- constructor(){
+/**
+ * A class to manage a buffer that can be appended to and deappended from.
+ */
+export class AppendableBuffer {
+ buffer: Uint8Array
+ deapended: number = 0
+
+ /**
+ * Creates an instance of AppendableBuffer.
+ */
+ constructor() {
this.buffer = new Uint8Array(0)
}
- append(data:Uint8Array){
+
+ /**
+ * Appends data to the buffer.
+ * @param {Uint8Array} data - The data to append.
+ */
+ append(data: Uint8Array) {
const newBuffer = new Uint8Array(this.buffer.length + data.length)
newBuffer.set(this.buffer, 0)
newBuffer.set(data, this.buffer.length)
this.buffer = newBuffer
}
- deappend(length:number){
+
+ /**
+ * Deappends a specified length from the buffer.
+ * @param {number} length - The length to deappend.
+ */
+ deappend(length: number) {
this.buffer = this.buffer.slice(length)
this.deapended += length
}
- slice(start:number, end:number){
+
+ /**
+ * Slices the buffer from start to end.
+ * @param {number} start - The start index.
+ * @param {number} end - The end index.
+ * @returns {Uint8Array} - The sliced buffer.
+ */
+ slice(start: number, end: number) {
return this.buffer.slice(start - this.deapended, end - this.deapended)
}
- length(){
+
+ /**
+ * Gets the total length of the buffer including deappended length.
+ * @returns {number} - The total length.
+ */
+ length() {
return this.buffer.length + this.deapended
}
-
}
-const pipeFetchLog = (fetchLogIndex:number, readableStream:ReadableStream) => {
+/**
+ * Pipes the fetch log to a readable stream.
+ * @param {number} fetchLogIndex - The index of the fetch log.
+ * @param {ReadableStream} readableStream - The readable stream to pipe.
+ * @returns {ReadableStream} - The new readable stream.
+ */
+const pipeFetchLog = (fetchLogIndex: number, readableStream: ReadableStream) => {
let textDecoderBuffer = new AppendableBuffer()
let textDecoderPointer = 0
const textDecoder = TextDecoderStream ? (new TextDecoderStream()) : new TransformStream({
transform(chunk, controller) {
- try{
+ try {
textDecoderBuffer.append(chunk)
const decoded = new TextDecoder('utf-8', {
fatal: true
@@ -1390,8 +1747,7 @@ const pipeFetchLog = (fetchLogIndex:number, readableStream:ReadableStream} - A promise that resolves to an object containing the response body, headers, and status.
+ * @returns {ReadableStream} body - The response body as a readable stream.
+ * @returns {Headers} headers - The response headers.
+ * @returns {number} status - The response status code.
+ * @throws {Error} - Throws an error if the request is aborted or if there is an error in the response.
+ */
export async function fetchNative(url:string, arg:{
body:string,
headers?:{[key:string]:string},
@@ -1564,24 +1936,46 @@ export async function fetchNative(url:string, arg:{
}
}
+/**
+ * Converts a ReadableStream of Uint8Array to a text string.
+ *
+ * @param {ReadableStream} stream - The readable stream to convert.
+ * @returns {Promise} A promise that resolves to the text content of the stream.
+ */
export function textifyReadableStream(stream:ReadableStream){
return new Response(stream).text()
}
+/**
+ * Toggles the fullscreen mode of the document.
+ * If the document is currently in fullscreen mode, it exits fullscreen.
+ * If the document is not in fullscreen mode, it requests fullscreen with navigation UI hidden.
+ */
export function toggleFullscreen(){
-
const fullscreenElement = document.fullscreenElement
fullscreenElement ? document.exitFullscreen() : document.documentElement.requestFullscreen({
navigationUI: "hide"
})
}
+/**
+ * Removes non-Latin characters from a string, replaces multiple spaces with a single space, and trims the string.
+ *
+ * @param {string} data - The input string to be processed.
+ * @returns {string} The processed string with non-Latin characters removed, multiple spaces replaced by a single space, and trimmed.
+ */
export function trimNonLatin(data:string){
return data .replace(/[^\x00-\x7F]/g, "")
.replace(/ +/g, ' ')
.trim()
}
+/**
+ * Updates the height mode of the document based on the value stored in the database.
+ *
+ * The height mode can be one of the following values: 'auto', 'vh', 'dvh', 'lvh', 'svh', or 'percent'.
+ * The corresponding CSS variable '--risu-height-size' is set accordingly.
+ */
export function updateHeightMode(){
const db = get(DataBase)
const root = document.querySelector(':root') as HTMLElement;
@@ -1607,16 +2001,41 @@ export function updateHeightMode(){
}
}
+/**
+ * A class that provides a blank writer implementation.
+ *
+ * This class is used to provide a no-op implementation of a writer, making it compatible with other writer interfaces.
+ */
export class BlankWriter{
constructor(){
}
+
+ /**
+ * Initializes the writer.
+ *
+ * This method does nothing and is provided for compatibility with other writer interfaces.
+ */
async init(){
//do nothing, just to make compatible with other writer
-
}
+
+ /**
+ * Writes data to the writer.
+ *
+ * This method does nothing and is provided for compatibility with other writer interfaces.
+ *
+ * @param {string} key - The key associated with the data.
+ * @param {Uint8Array|string} data - The data to be written.
+ */
async write(key:string,data:Uint8Array|string){
//do nothing, just to make compatible with other writer
}
+
+ /**
+ * Ends the writing process.
+ *
+ * This method does nothing and is provided for compatibility with other writer interfaces.
+ */
async end(){
//do nothing, just to make compatible with other writer
}