Merge branch 'main' of https://github.com/hashcoko/RisuAI
This commit is contained in:
@@ -246,7 +246,7 @@ export async function alertCardExport(type:string = ''){
|
||||
|
||||
export async function alertTOS(){
|
||||
|
||||
if(localStorage.getItem('tos') === 'true'){
|
||||
if(localStorage.getItem('tos2') === 'true'){
|
||||
return true
|
||||
}
|
||||
|
||||
@@ -263,7 +263,7 @@ export async function alertTOS(){
|
||||
}
|
||||
|
||||
if(get(alertStore).msg === 'yes'){
|
||||
localStorage.setItem('tos', 'true')
|
||||
localStorage.setItem('tos2', 'true')
|
||||
return true
|
||||
}
|
||||
|
||||
|
||||
@@ -14,6 +14,9 @@ import { PngChunk } from "./pngChunk"
|
||||
import type { OnnxModelFiles } from "./process/transformers"
|
||||
import { CharXReader, CharXWriter } from "./process/processzip"
|
||||
import { Capacitor } from "@capacitor/core"
|
||||
import { exportModule, readModule, type RisuModule } from "./process/modules"
|
||||
import { readFile } from "@tauri-apps/plugin-fs"
|
||||
import { onOpenUrl } from '@tauri-apps/plugin-deep-link';
|
||||
|
||||
export const hubURL = "https://sv.risuai.xyz"
|
||||
|
||||
@@ -65,6 +68,9 @@ async function importCharacterProcess(f:{
|
||||
}
|
||||
}
|
||||
|
||||
let db = get(DataBase)
|
||||
db.statics.imports += 1
|
||||
|
||||
if(f.name.endsWith('charx')){
|
||||
console.log('reading charx')
|
||||
alertStore.set({
|
||||
@@ -80,11 +86,18 @@ async function importCharacterProcess(f:{
|
||||
alertError(language.errors.noData)
|
||||
return
|
||||
}
|
||||
const card = JSON.parse(cardData)
|
||||
const card:CharacterCardV3 = JSON.parse(cardData)
|
||||
if(CCardLib.character.check(card) !== 'v3'){
|
||||
alertError(language.errors.noData)
|
||||
return
|
||||
}
|
||||
if(reader.moduleData){
|
||||
const md = await readModule(Buffer.from(reader.moduleData))
|
||||
card.data.extensions ??= {}
|
||||
card.data.extensions.risuai ??= {}
|
||||
card.data.extensions.risuai.triggerscript = md.trigger ?? []
|
||||
card.data.extensions.risuai.customScripts = md.regex ?? []
|
||||
}
|
||||
await importCharacterCardSpec(card, undefined, 'normal', reader.assets)
|
||||
let db = get(DataBase)
|
||||
return db.characters.length - 1
|
||||
@@ -133,7 +146,7 @@ async function importCharacterProcess(f:{
|
||||
continue
|
||||
}
|
||||
if(chunk.key.startsWith('chara-ext-asset_')){
|
||||
const assetIndex = (chunk.key.replace('chara-ext-asset_', ''))
|
||||
const assetIndex = chunk.key.replace('chara-ext-asset_:', '').replace('chara-ext-asset_', '')
|
||||
alertWait('Loading... (Reading Asset ' + assetIndex + ')' )
|
||||
const assetData = Buffer.from(chunk.value, 'base64')
|
||||
const assetId = await saveAsset(assetData)
|
||||
@@ -145,7 +158,7 @@ async function importCharacterProcess(f:{
|
||||
return
|
||||
}
|
||||
|
||||
if(!readedChara){
|
||||
if(readedCCv3){
|
||||
readedChara = readedCCv3
|
||||
}
|
||||
|
||||
@@ -226,7 +239,6 @@ async function importCharacterProcess(f:{
|
||||
const charaData:OldTavernChar = JSON.parse(Buffer.from(readedChara, 'base64').toString('utf-8'))
|
||||
console.log(charaData)
|
||||
const imgp = await saveAsset(await reencodeImage(img))
|
||||
let db = get(DataBase)
|
||||
db.characters.push(convertOffSpecCards(charaData, imgp))
|
||||
DataBase.set(db)
|
||||
alertNormal(language.importedCharacter)
|
||||
@@ -289,6 +301,14 @@ export async function characterURLImport() {
|
||||
|
||||
|
||||
const hash = location.hash
|
||||
if(hash.startsWith('#import=')){
|
||||
const url = hash.replace('#import=', '')
|
||||
const res = await fetch(url, {
|
||||
method: 'GET',
|
||||
})
|
||||
const data = new Uint8Array(await res.arrayBuffer())
|
||||
importFile(getFileName(res), data)
|
||||
}
|
||||
if(hash.startsWith('#import_module=')){
|
||||
const data = hash.replace('#import_module=', '')
|
||||
const importData = JSON.parse(Buffer.from(decodeURIComponent(data), 'base64').toString('utf-8'))
|
||||
@@ -304,7 +324,7 @@ export async function characterURLImport() {
|
||||
db.modules.push(importData)
|
||||
setDatabase(db)
|
||||
alertNormal(language.successImport)
|
||||
SettingsMenuIndex.set(1)
|
||||
SettingsMenuIndex.set(14)
|
||||
settingsOpen.set(true)
|
||||
return
|
||||
}
|
||||
@@ -315,10 +335,148 @@ export async function characterURLImport() {
|
||||
name: 'imported.risupreset',
|
||||
data: importData
|
||||
})
|
||||
SettingsMenuIndex.set(14)
|
||||
SettingsMenuIndex.set(1)
|
||||
settingsOpen.set(true)
|
||||
return
|
||||
}
|
||||
if(hash.startsWith('#share_character')){
|
||||
const data = await fetch("/sw/share/character")
|
||||
if(data.status !== 200){
|
||||
return
|
||||
}
|
||||
const charx = new Uint8Array(await data.arrayBuffer())
|
||||
await importCharacterProcess({
|
||||
name: 'shared.charx',
|
||||
data: charx
|
||||
})
|
||||
}
|
||||
if(hash.startsWith('#share_module')){
|
||||
const data = await fetch("/sw/share/module")
|
||||
if(data.status !== 200){
|
||||
return
|
||||
}
|
||||
const module = new Uint8Array(await data.arrayBuffer())
|
||||
const md = await readModule(Buffer.from(module))
|
||||
md.id = v4()
|
||||
const db = get(DataBase)
|
||||
db.modules.push(md)
|
||||
setDatabase(db)
|
||||
alertNormal(language.successImport)
|
||||
SettingsMenuIndex.set(14)
|
||||
settingsOpen.set(true)
|
||||
}
|
||||
if(hash.startsWith('#share_preset')){
|
||||
const data = await fetch("/sw/share/preset")
|
||||
if(data.status !== 200){
|
||||
return
|
||||
}
|
||||
const preset = new Uint8Array(await data.arrayBuffer())
|
||||
await importPreset({
|
||||
name: 'shared.risup',
|
||||
data: preset
|
||||
})
|
||||
SettingsMenuIndex.set(1)
|
||||
settingsOpen.set(true)
|
||||
}
|
||||
if ("launchQueue" in window) {
|
||||
const handleFiles = async (files:FileSystemFileHandle[]) => {
|
||||
for(const f of files){
|
||||
const file = await f.getFile()
|
||||
const data = new Uint8Array(await file.arrayBuffer())
|
||||
importFile(f.name, data);
|
||||
}
|
||||
}
|
||||
//@ts-ignore
|
||||
window.launchQueue.setConsumer((launchParams) => {
|
||||
if (launchParams.files && launchParams.files.length) {
|
||||
const files = launchParams.files as FileSystemFileHandle[]
|
||||
handleFiles(files)
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
if("tauriOpenedFiles" in window){
|
||||
//@ts-ignore
|
||||
const files:string[] = window.tauriOpenedFiles
|
||||
if(files){
|
||||
for(const file of files){
|
||||
const data = await readFile(file)
|
||||
importFile(file, data)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if(isTauri){
|
||||
await onOpenUrl((urls) => {
|
||||
for(const url of urls){
|
||||
const splited = url.split('/')
|
||||
const id = splited[splited.length - 1]
|
||||
const type = splited[splited.length - 2]
|
||||
switch(type){
|
||||
case 'realm':{
|
||||
downloadRisuHub(id)
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
async function importFile(name:string, data:Uint8Array) {
|
||||
if(name.endsWith('.charx') || name.endsWith('.png')){
|
||||
await importCharacterProcess({
|
||||
name: name,
|
||||
data: data
|
||||
})
|
||||
return
|
||||
}
|
||||
if(name.endsWith('.risupreset') || name.endsWith('.risup')){
|
||||
await importPreset({
|
||||
name: name,
|
||||
data: data
|
||||
})
|
||||
SettingsMenuIndex.set(1)
|
||||
settingsOpen.set(true)
|
||||
alertNormal(language.successImport)
|
||||
return
|
||||
}
|
||||
if(name.endsWith('risum')){
|
||||
const md = await readModule(Buffer.from(data))
|
||||
md.id = v4()
|
||||
const db = get(DataBase)
|
||||
db.modules.push(md)
|
||||
setDatabase(db)
|
||||
alertNormal(language.successImport)
|
||||
SettingsMenuIndex.set(14)
|
||||
settingsOpen.set(true)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
function getFileName(res : Response) : string {
|
||||
return getFormContent(res.headers.get('content-disposition')) || getFromURL(res.url);
|
||||
|
||||
function getFormContent(contentDisposition : string) {
|
||||
if (!contentDisposition) return null;
|
||||
const pattern = /filename\*=UTF-8''([^"';\n]+)|filename[^;\n=]*=["']?([^"';\n]+)["']?/;
|
||||
const matches = contentDisposition.match(pattern);
|
||||
if (matches) {
|
||||
if (matches[1]) {
|
||||
return decodeURIComponent(matches[1]);
|
||||
} else if (matches[2]) {
|
||||
return matches[2];
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
function getFromURL(url : string) : string {
|
||||
try {
|
||||
const path = new URL(url).pathname;
|
||||
return path.substring(path.lastIndexOf('/') + 1);
|
||||
} catch {
|
||||
return "";
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -326,6 +484,43 @@ export async function characterURLImport() {
|
||||
function convertOffSpecCards(charaData:OldTavernChar|CharacterCardV2Risu, imgp:string|undefined = undefined):character{
|
||||
const data = charaData.spec_version === '2.0' ? charaData.data : charaData
|
||||
console.log("Off spec detected, converting")
|
||||
const charbook = charaData.spec_version === '2.0' ? charaData.data.character_book : null
|
||||
let lorebook:loreBook[] = []
|
||||
let loresettings:undefined|loreSettings = undefined
|
||||
let loreExt:undefined|any = undefined
|
||||
if(charbook){
|
||||
if((!checkNullish(charbook.recursive_scanning)) &&
|
||||
(!checkNullish(charbook.scan_depth)) &&
|
||||
(!checkNullish(charbook.token_budget))){
|
||||
loresettings = {
|
||||
tokenBudget:charbook.token_budget,
|
||||
scanDepth:charbook.scan_depth,
|
||||
recursiveScanning: charbook.recursive_scanning,
|
||||
fullWordMatching: charbook?.extensions?.risu_fullWordMatching ?? false,
|
||||
}
|
||||
}
|
||||
|
||||
loreExt = charbook.extensions
|
||||
|
||||
for(const book of charbook.entries){
|
||||
lorebook.push({
|
||||
key: book.keys.join(', '),
|
||||
secondkey: book.secondary_keys?.join(', ') ?? '',
|
||||
insertorder: book.insertion_order,
|
||||
comment: book.name ?? book.comment ?? "",
|
||||
content: book.content,
|
||||
mode: "normal",
|
||||
alwaysActive: book.constant ?? false,
|
||||
selective: book.selective ?? false,
|
||||
extentions: {...book.extensions, risu_case_sensitive: book.case_sensitive},
|
||||
activationPercent: book.extensions?.risu_activationPercent,
|
||||
loreCache: book.extensions?.risu_loreCache ?? null,
|
||||
//@ts-ignore
|
||||
useRegex: book.use_regex ?? false
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
name: data.name ?? 'unknown name',
|
||||
firstMessage: data.first_mes ?? 'unknown first message',
|
||||
@@ -341,7 +536,7 @@ function convertOffSpecCards(charaData:OldTavernChar|CharacterCardV2Risu, imgp:s
|
||||
image: imgp,
|
||||
emotionImages: [],
|
||||
bias: [],
|
||||
globalLore: [],
|
||||
globalLore: lorebook,
|
||||
viewScreen: 'none',
|
||||
chaId: uuidv4(),
|
||||
sdData: defaultSdDataFunc(),
|
||||
@@ -360,7 +555,10 @@ function convertOffSpecCards(charaData:OldTavernChar|CharacterCardV2Risu, imgp:s
|
||||
firstMsgIndex: -1,
|
||||
replaceGlobalNote: "",
|
||||
triggerscript: [],
|
||||
additionalText: ''
|
||||
additionalText: '',
|
||||
loreExt: loreExt,
|
||||
loreSettings: loresettings,
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -663,7 +861,7 @@ async function importCharacterCardSpec(card:CharacterCardV2Risu|CharacterCardV3,
|
||||
alternateGreetings:data.alternate_greetings ?? [],
|
||||
tags:data.tags ?? [],
|
||||
creator:data.creator ?? '',
|
||||
characterVersion: `${data.character_version}` ?? '',
|
||||
characterVersion: `${data.character_version}` || '',
|
||||
personality:data.personality ?? '',
|
||||
scenario:data.scenario ?? '',
|
||||
firstMsgIndex: -1,
|
||||
@@ -775,7 +973,7 @@ async function createBaseV2(char:character) {
|
||||
},
|
||||
tags: char.tags ?? [],
|
||||
creator: char.additionalData?.creator ?? '',
|
||||
character_version: `${char.additionalData?.character_version}` ?? '',
|
||||
character_version: `${char.additionalData?.character_version}` || '',
|
||||
extensions: {
|
||||
risuai: {
|
||||
// emotions: char.emotionImages,
|
||||
@@ -850,7 +1048,7 @@ export async function exportCharacterCard(char:character, type:'png'|'json'|'cha
|
||||
const b64encoded = Buffer.from(await convertImage(rData)).toString('base64')
|
||||
assetIndex++
|
||||
card.data.extensions.risuai.emotions[i][1] = `__asset:${assetIndex}`
|
||||
await writer.write("chara-ext-asset_" + assetIndex, b64encoded)
|
||||
await writer.write("chara-ext-asset_:" + assetIndex, b64encoded)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -866,7 +1064,7 @@ export async function exportCharacterCard(char:character, type:'png'|'json'|'cha
|
||||
const b64encoded = Buffer.from(await convertImage(rData)).toString('base64')
|
||||
assetIndex++
|
||||
card.data.extensions.risuai.additionalAssets[i][1] = `__asset:${assetIndex}`
|
||||
await writer.write("chara-ext-asset_" + assetIndex, b64encoded)
|
||||
await writer.write("chara-ext-asset_:" + assetIndex, b64encoded)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -882,7 +1080,7 @@ export async function exportCharacterCard(char:character, type:'png'|'json'|'cha
|
||||
const b64encoded = Buffer.from(rData).toString('base64')
|
||||
assetIndex++
|
||||
card.data.extensions.risuai.vits[key] = `__asset:${assetIndex}`
|
||||
await writer.write("chara-ext-asset_" + assetIndex, b64encoded)
|
||||
await writer.write("chara-ext-asset_:" + assetIndex, b64encoded)
|
||||
}
|
||||
}
|
||||
if(type === 'json'){
|
||||
@@ -923,7 +1121,7 @@ export async function exportCharacterCard(char:character, type:'png'|'json'|'cha
|
||||
if(type === 'png'){
|
||||
const b64encoded = Buffer.from(await convertImage(rData)).toString('base64')
|
||||
card.data.assets[i].uri = `__asset:${assetIndex}`
|
||||
await writer.write("chara-ext-asset_" + assetIndex, b64encoded)
|
||||
await writer.write("chara-ext-asset_:" + assetIndex, b64encoded)
|
||||
}
|
||||
else if(type === 'json'){
|
||||
const b64encoded = Buffer.from(await convertImage(rData)).toString('base64')
|
||||
@@ -1015,6 +1213,20 @@ export async function exportCharacterCard(char:character, type:'png'|'json'|'cha
|
||||
})
|
||||
|
||||
if(type === 'charx'){
|
||||
const md:RisuModule = {
|
||||
name: `${char.name} Module`,
|
||||
description: "Module for " + char.name,
|
||||
id: v4(),
|
||||
trigger: card.data.extensions.risuai.triggerscript ?? [],
|
||||
regex: card.data.extensions.risuai.customScripts ?? [],
|
||||
lorebook: char.globalLore ?? [],
|
||||
}
|
||||
delete card.data.extensions.risuai.triggerscript
|
||||
delete card.data.extensions.risuai.customScripts
|
||||
await writer.write("module.risum", await exportModule(md, {
|
||||
alertEnd: false,
|
||||
saveData: false
|
||||
}))
|
||||
await writer.write("card.json", Buffer.from(JSON.stringify(card, null, 4)))
|
||||
}
|
||||
else{
|
||||
@@ -1131,7 +1343,7 @@ export function createBaseV3(char:character){
|
||||
},
|
||||
tags: char.tags ?? [],
|
||||
creator: char.additionalData?.creator ?? '',
|
||||
character_version: `${char.additionalData?.character_version}` ?? '',
|
||||
character_version: `${char.additionalData?.character_version}` || '',
|
||||
extensions: {
|
||||
risuai: {
|
||||
bias: char.bias,
|
||||
|
||||
@@ -1,17 +1,19 @@
|
||||
import { get, writable } from "svelte/store";
|
||||
import { DataBase, saveImage, setDatabase, type character, type Chat, defaultSdDataFunc, type loreBook } from "./storage/database";
|
||||
import { alertConfirm, alertError, alertNormal, alertSelect, alertStore, alertWait } from "./alert";
|
||||
import { alertAddCharacter, alertConfirm, alertError, alertNormal, alertSelect, alertStore, alertWait } from "./alert";
|
||||
import { language } from "../lang";
|
||||
import { decode as decodeMsgpack } from "msgpackr";
|
||||
import { checkNullish, findCharacterbyId, getUserName, selectMultipleFile, selectSingleFile, sleep } from "./util";
|
||||
import { v4 as uuidv4 } from 'uuid';
|
||||
import { selectedCharID } from "./stores";
|
||||
import { MobileGUIStack, OpenRealmStore, selectedCharID } from "./stores";
|
||||
import { checkCharOrder, downloadFile, getFileSrc } from "./storage/globalApi";
|
||||
import { reencodeImage } from "./process/files/image";
|
||||
import { updateInlayScreen } from "./process/inlayScreen";
|
||||
import { PngChunk } from "./pngChunk";
|
||||
import { parseMarkdownSafe } from "./parser";
|
||||
import { translateHTML } from "./translator/translator";
|
||||
import { doingChat } from "./process";
|
||||
import { importCharacter } from "./characterCards";
|
||||
|
||||
export function createNewCharacter() {
|
||||
let db = get(DataBase)
|
||||
@@ -247,7 +249,7 @@ export async function exportChat(page:number){
|
||||
<div class="chat">
|
||||
<h2>${char.name}</h2>
|
||||
<div>${await htmlChatParse(
|
||||
char.firstMsgIndex === -1 ? char.firstMessage : char.alternateGreetings?.[char.firstMsgIndex ?? 0]
|
||||
chat.fmIndex === -1 ? char.firstMessage : char.alternateGreetings?.[chat.fmIndex ?? 0]
|
||||
)}</div>
|
||||
</div>
|
||||
${chatContentHTML}
|
||||
@@ -341,7 +343,8 @@ export async function importChat(){
|
||||
message: [],
|
||||
note: "",
|
||||
name: "Imported Chat",
|
||||
localLore: []
|
||||
localLore: [],
|
||||
fmIndex: -1
|
||||
}
|
||||
|
||||
let isFirst = true
|
||||
@@ -374,6 +377,7 @@ export async function importChat(){
|
||||
if(json.type === 'risuChat' && json.ver === 1){
|
||||
const das:Chat = json.data
|
||||
if(!(checkNullish(das.message) || checkNullish(das.note) || checkNullish(das.name) || checkNullish(das.localLore))){
|
||||
das.fmIndex ??= -1
|
||||
db.characters[selectedID].chats.unshift(das)
|
||||
setDatabase(db)
|
||||
alertNormal(language.successImport)
|
||||
@@ -412,7 +416,9 @@ function formatTavernChat(chat:string, charName:string){
|
||||
return chat.replace(/<([Uu]ser)>|\{\{([Uu]ser)\}\}/g, getUserName()).replace(/((\{\{)|<)([Cc]har)(=.+)?((\}\})|>)/g, charName)
|
||||
}
|
||||
|
||||
export function characterFormatUpdate(index:number|character){
|
||||
export function characterFormatUpdate(index:number|character, arg:{
|
||||
updateInteraction?:boolean,
|
||||
} = {}){
|
||||
let db = get(DataBase)
|
||||
let cha = typeof(index) === 'number' ? db.characters[index] : index
|
||||
if(cha.chats.length === 0){
|
||||
@@ -504,10 +510,15 @@ export function characterFormatUpdate(index:number|character){
|
||||
if(checkNullish(cha.customscript)){
|
||||
cha.customscript = []
|
||||
}
|
||||
cha.lastInteraction = Date.now()
|
||||
if(typeof(index) === 'number'){
|
||||
db.characters[index] = cha
|
||||
setDatabase(db)
|
||||
}
|
||||
cha.chats = cha.chats.map((v) => {
|
||||
v.fmIndex ??= cha.firstMsgIndex ?? -1
|
||||
return v
|
||||
})
|
||||
return cha
|
||||
}
|
||||
|
||||
@@ -731,4 +742,52 @@ export async function removeChar(index:number,name:string, type:'normal'|'perman
|
||||
db.characters = chars
|
||||
setDatabase(db)
|
||||
selectedCharID.set(-1)
|
||||
}
|
||||
|
||||
export async function addCharacter(arg:{
|
||||
reseter?:()=>any,
|
||||
} = {}){
|
||||
MobileGUIStack.set(100)
|
||||
const reseter = arg.reseter ?? (() => {})
|
||||
const r = await alertAddCharacter()
|
||||
if(r === 'importFromRealm'){
|
||||
selectedCharID.set(-1)
|
||||
OpenRealmStore.set(true)
|
||||
MobileGUIStack.set(0)
|
||||
return
|
||||
}
|
||||
reseter();
|
||||
switch(r){
|
||||
case 'createfromScratch':
|
||||
createNewCharacter()
|
||||
break
|
||||
case 'createGroup':
|
||||
createNewGroup()
|
||||
break
|
||||
case 'importCharacter':
|
||||
await importCharacter()
|
||||
break
|
||||
default:
|
||||
MobileGUIStack.set(1)
|
||||
return
|
||||
}
|
||||
let db = get(DataBase)
|
||||
if(db.characters[db.characters.length-1]){
|
||||
changeChar(db.characters.length-1)
|
||||
}
|
||||
MobileGUIStack.set(1)
|
||||
}
|
||||
|
||||
export function changeChar(index: number, arg:{
|
||||
reseter?:()=>any,
|
||||
} = {}) {
|
||||
const reseter = arg.reseter ?? (() => {})
|
||||
if(get(doingChat)){
|
||||
return
|
||||
}
|
||||
reseter();
|
||||
characterFormatUpdate(index, {
|
||||
updateInteraction: true,
|
||||
});
|
||||
selectedCharID.set(index);
|
||||
}
|
||||
@@ -1,11 +1,11 @@
|
||||
import { BaseDirectory, readBinaryFile, readDir, writeBinaryFile } from "@tauri-apps/api/fs";
|
||||
import { BaseDirectory, readFile, readDir, writeFile } from "@tauri-apps/plugin-fs";
|
||||
import { alertError, alertNormal, alertStore, alertWait } from "../alert";
|
||||
import { LocalWriter, forageStorage, isTauri } from "../storage/globalApi";
|
||||
import { decodeRisuSave, encodeRisuSave } from "../storage/risuSave";
|
||||
import { get } from "svelte/store";
|
||||
import { DataBase } from "../storage/database";
|
||||
import { save } from "@tauri-apps/api/dialog";
|
||||
import { relaunch } from "@tauri-apps/api/process";
|
||||
import { save } from "@tauri-apps/plugin-dialog";
|
||||
import { relaunch } from "@tauri-apps/plugin-process";
|
||||
import { sleep } from "../util";
|
||||
import { hubURL } from "../characterCards";
|
||||
|
||||
@@ -41,7 +41,7 @@ export async function SaveLocalBackup(){
|
||||
|
||||
|
||||
if(isTauri){
|
||||
const assets = await readDir('assets', {dir: BaseDirectory.AppData})
|
||||
const assets = await readDir('assets', {baseDir: BaseDirectory.AppData})
|
||||
let i = 0;
|
||||
for(let asset of assets){
|
||||
i += 1;
|
||||
@@ -50,7 +50,7 @@ export async function SaveLocalBackup(){
|
||||
if(!key || !key.endsWith('.png')){
|
||||
continue
|
||||
}
|
||||
await writer.writeBackup(key, await readBinaryFile(asset.path))
|
||||
await writer.writeBackup(key, await readFile(asset.name, {baseDir: BaseDirectory.AppData}))
|
||||
}
|
||||
}
|
||||
else{
|
||||
@@ -63,7 +63,7 @@ export async function SaveLocalBackup(){
|
||||
if(!key || !key.endsWith('.png')){
|
||||
continue
|
||||
}
|
||||
await writer.writeBackup(key, await forageStorage.getItem(key))
|
||||
await writer.writeBackup(key, await forageStorage.getItem(key) as unknown as Uint8Array)
|
||||
if(forageStorage.isAccount){
|
||||
await sleep(1000)
|
||||
}
|
||||
@@ -115,7 +115,7 @@ export async function LoadLocalBackup(){
|
||||
const dbData = await decodeRisuSave(db)
|
||||
DataBase.set(dbData)
|
||||
if(isTauri){
|
||||
await writeBinaryFile('database/database.bin', db, {dir: BaseDirectory.AppData})
|
||||
await writeFile('database/database.bin', db, {baseDir: BaseDirectory.AppData})
|
||||
relaunch()
|
||||
alertStore.set({
|
||||
type: "wait",
|
||||
@@ -133,7 +133,7 @@ export async function LoadLocalBackup(){
|
||||
continue
|
||||
}
|
||||
if(isTauri){
|
||||
await writeBinaryFile(`assets/` + name, data ,{dir: BaseDirectory.AppData})
|
||||
await writeFile(`assets/` + name, data ,{baseDir: BaseDirectory.AppData})
|
||||
}
|
||||
else{
|
||||
await forageStorage.setItem('assets/' + name, data)
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
import { get } from "svelte/store";
|
||||
import { alertError, alertErrorWait, alertInput, alertNormal, alertSelect, alertStore } from "../alert";
|
||||
import { DataBase, setDatabase, type Database } from "../storage/database";
|
||||
import { forageStorage, getUnpargeables, isNodeServer, isTauri, openURL } from "../storage/globalApi";
|
||||
import { BaseDirectory, exists, readBinaryFile, readDir, writeBinaryFile } from "@tauri-apps/api/fs";
|
||||
import { alertError, alertInput, alertNormal, alertSelect, alertStore } from "../alert";
|
||||
import { DataBase, type Database } from "../storage/database";
|
||||
import { forageStorage, getUnpargeables, isTauri, openURL } from "../storage/globalApi";
|
||||
import { BaseDirectory, exists, readFile, readDir, writeFile } from "@tauri-apps/plugin-fs";
|
||||
import { language } from "../../lang";
|
||||
import { relaunch } from '@tauri-apps/api/process';
|
||||
import { relaunch } from '@tauri-apps/plugin-process';
|
||||
import { isEqual } from "lodash";
|
||||
import { sleep } from "../util";
|
||||
import { hubURL } from "../characterCards";
|
||||
@@ -112,89 +112,6 @@ let BackupDb:Database = null
|
||||
export async function syncDrive() {
|
||||
BackupDb = structuredClone(get(DataBase))
|
||||
return
|
||||
while(true){
|
||||
const maindb = get(DataBase)
|
||||
if(maindb?.account?.data?.access_token && maindb?.account?.data?.refresh_token && maindb?.account?.data?.expires_in){
|
||||
if(maindb.account.data.expires_in < Date.now()){
|
||||
if(!maindb.account){
|
||||
alertError("Not logged in error")
|
||||
return
|
||||
}
|
||||
const s = await fetch(hubURL + '/drive/refresh', {
|
||||
method: "POST",
|
||||
body: JSON.stringify({
|
||||
token: maindb.account.token
|
||||
})
|
||||
})
|
||||
if(s.status !== 200){
|
||||
alertError(await s.text())
|
||||
return
|
||||
}
|
||||
maindb.account.data = await s.json()
|
||||
}
|
||||
const ACCESS_TOKEN = maindb.account.data.access_token
|
||||
const d = await loadDrive(ACCESS_TOKEN, 'sync')
|
||||
lastSaved = Math.floor(Date.now() / 1000)
|
||||
localStorage.setItem('risu_lastsaved', `${lastSaved}`)
|
||||
const hadNoSync = d === 'noSync'
|
||||
if((!isEqual(maindb, BackupDb)) || hadNoSync){
|
||||
BackupDb = structuredClone(maindb)
|
||||
const files:DriveFile[] = await getFilesInFolder(ACCESS_TOKEN)
|
||||
const fileNames = files.map((d) => {
|
||||
return d.name
|
||||
})
|
||||
if(isTauri){
|
||||
const assets = await readDir('assets', {dir: BaseDirectory.AppData})
|
||||
let i = 0;
|
||||
for(let asset of assets){
|
||||
i += 1;
|
||||
if(hadNoSync){
|
||||
alertStore.set({
|
||||
type: "wait",
|
||||
msg: `Uploading Sync Files... (${i} / ${assets.length})`
|
||||
})
|
||||
}
|
||||
const key = asset.name
|
||||
if(!key || !key.endsWith('.png')){
|
||||
continue
|
||||
}
|
||||
const formatedKey = formatKeys(key)
|
||||
if(!fileNames.includes(formatedKey)){
|
||||
await createFileInFolder(ACCESS_TOKEN, formatedKey, await readBinaryFile(asset.path))
|
||||
}
|
||||
}
|
||||
}
|
||||
else{
|
||||
const keys = await forageStorage.keys()
|
||||
|
||||
for(let i=0;i<keys.length;i++){
|
||||
if(hadNoSync){
|
||||
alertStore.set({
|
||||
type: "wait",
|
||||
msg: `Uploading Sync Files... (${i} / ${keys.length})`
|
||||
})
|
||||
}
|
||||
const key = keys[i]
|
||||
if(!key.endsWith('.png')){
|
||||
continue
|
||||
}
|
||||
const formatedKey = formatKeys(key)
|
||||
if(!fileNames.includes(formatedKey)){
|
||||
await createFileInFolder(ACCESS_TOKEN, formatedKey, await forageStorage.getItem(key))
|
||||
}
|
||||
}
|
||||
}
|
||||
const dbjson = JSON.stringify(get(DataBase))
|
||||
lastSaved = Math.floor(Date.now() / 1000)
|
||||
localStorage.setItem('risu_lastsaved', `${lastSaved}`)
|
||||
await createFileInFolder(ACCESS_TOKEN, `${lastSaved}-database.risudat2`, Buffer.from(dbjson, 'utf-8'))
|
||||
if(hadNoSync){
|
||||
alertNormal("First Setup Success")
|
||||
}
|
||||
}
|
||||
}
|
||||
await sleep(3000)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -224,7 +141,7 @@ async function backupDrive(ACCESS_TOKEN:string) {
|
||||
})
|
||||
|
||||
if(isTauri){
|
||||
const assets = await readDir('assets', {dir: BaseDirectory.AppData})
|
||||
const assets = await readDir('assets', {baseDir: BaseDirectory.AppData})
|
||||
let i = 0;
|
||||
for(let asset of assets){
|
||||
i += 1;
|
||||
@@ -238,7 +155,7 @@ async function backupDrive(ACCESS_TOKEN:string) {
|
||||
}
|
||||
const formatedKey = newFormatKeys(key)
|
||||
if(!fileNames.includes(formatedKey)){
|
||||
await createFileInFolder(ACCESS_TOKEN, formatedKey, await readBinaryFile(asset.path))
|
||||
await createFileInFolder(ACCESS_TOKEN, formatedKey, await readFile(asset.name, {baseDir: BaseDirectory.AppData}))
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -256,7 +173,7 @@ async function backupDrive(ACCESS_TOKEN:string) {
|
||||
}
|
||||
const formatedKey = newFormatKeys(key)
|
||||
if(!fileNames.includes(formatedKey)){
|
||||
await createFileInFolder(ACCESS_TOKEN, formatedKey, await forageStorage.getItem(key))
|
||||
await createFileInFolder(ACCESS_TOKEN, formatedKey, await forageStorage.getItem(key) as unknown as Uint8Array)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -297,7 +214,7 @@ async function loadDrive(ACCESS_TOKEN:string, mode: 'backup'|'sync'):Promise<voi
|
||||
return false
|
||||
}
|
||||
if(isTauri){
|
||||
return await exists(`assets/` + images, {dir: BaseDirectory.AppData})
|
||||
return await exists(`assets/` + images, {baseDir: BaseDirectory.AppData})
|
||||
}
|
||||
else{
|
||||
if(!loadedForageKeys){
|
||||
@@ -408,7 +325,7 @@ async function loadDrive(ACCESS_TOKEN:string, mode: 'backup'|'sync'):Promise<voi
|
||||
if(file.name === formatedImage){
|
||||
const fData = await getFileData(ACCESS_TOKEN, file.id)
|
||||
if(isTauri){
|
||||
await writeBinaryFile(`assets/` + images, fData ,{dir: BaseDirectory.AppData})
|
||||
await writeFile(`assets/` + images, fData ,{baseDir: BaseDirectory.AppData})
|
||||
|
||||
}
|
||||
else{
|
||||
@@ -433,7 +350,7 @@ async function loadDrive(ACCESS_TOKEN:string, mode: 'backup'|'sync'):Promise<voi
|
||||
const dbData = encodeRisuSave(db, 'compression')
|
||||
|
||||
if(isTauri){
|
||||
await writeBinaryFile('database/database.bin', dbData, {dir: BaseDirectory.AppData})
|
||||
await writeFile('database/database.bin', dbData, {baseDir: BaseDirectory.AppData})
|
||||
relaunch()
|
||||
alertStore.set({
|
||||
type: "wait",
|
||||
|
||||
@@ -4,6 +4,7 @@ import { downloadFile } from "../storage/globalApi";
|
||||
import { BufferToText, selectSingleFile } from "../util";
|
||||
import { alertError } from "../alert";
|
||||
import { isLite } from "../lite";
|
||||
import { CustomCSSStore, SafeModeStore } from "../stores";
|
||||
|
||||
export interface ColorScheme{
|
||||
bgcolor: string;
|
||||
@@ -35,8 +36,8 @@ export const defaultColorScheme: ColorScheme = {
|
||||
const colorShemes = {
|
||||
"default": defaultColorScheme,
|
||||
"light": {
|
||||
bgcolor: "#f0f0f0",
|
||||
darkbg: "#ffffff",
|
||||
bgcolor: "#ffffff",
|
||||
darkbg: "#f0f0f0",
|
||||
borderc: "#0f172a",
|
||||
selected: "#e0e0e0",
|
||||
draculared: "#ff5555",
|
||||
@@ -93,6 +94,18 @@ const colorShemes = {
|
||||
darkBorderc: "#4b5563",
|
||||
darkbutton: "#374151",
|
||||
type:'dark'
|
||||
},
|
||||
"lite": {
|
||||
bgcolor: "#1f2937",
|
||||
darkbg: "#1C2533",
|
||||
borderc: "#475569",
|
||||
selected: "#475569",
|
||||
draculared: "#ff5555",
|
||||
textcolor: "#f8f8f2",
|
||||
textcolor2: "#64748b",
|
||||
darkBorderc: "#030712",
|
||||
darkbutton: "#374151",
|
||||
type:'dark'
|
||||
}
|
||||
|
||||
} as const
|
||||
@@ -121,7 +134,7 @@ export function updateColorScheme(){
|
||||
}
|
||||
|
||||
if(get(isLite)){
|
||||
colorScheme = structuredClone(colorShemes.light)
|
||||
colorScheme = structuredClone(colorShemes.lite)
|
||||
}
|
||||
|
||||
//set css variables
|
||||
@@ -179,14 +192,15 @@ export async function importColorScheme(){
|
||||
|
||||
}
|
||||
}
|
||||
export function updateTextTheme(){
|
||||
|
||||
export function updateTextThemeAndCSS(){
|
||||
let db = get(DataBase)
|
||||
const root = document.querySelector(':root') as HTMLElement;
|
||||
if(!root){
|
||||
return
|
||||
}
|
||||
let textTheme = get(isLite) ? 'standard' : db.textTheme
|
||||
let colorScheme = get(isLite) ? 'light' : db.colorScheme.type
|
||||
let colorScheme = get(isLite) ? 'dark' : db.colorScheme.type
|
||||
switch(textTheme){
|
||||
case "standard":{
|
||||
if(colorScheme === 'dark'){
|
||||
@@ -230,8 +244,8 @@ export function updateTextTheme(){
|
||||
root.style.setProperty('--FontColorItalic', db.customTextTheme.FontColorItalic);
|
||||
root.style.setProperty('--FontColorBold', db.customTextTheme.FontColorBold);
|
||||
root.style.setProperty('--FontColorItalicBold', db.customTextTheme.FontColorItalicBold);
|
||||
root.style.setProperty('--FontColorQuote1', db.customTextTheme.FontColorQuote1);
|
||||
root.style.setProperty('--FontColorQuote2', db.customTextTheme.FontColorQuote2);
|
||||
root.style.setProperty('--FontColorQuote1', db.customTextTheme.FontColorQuote1 ?? '#8BE9FD');
|
||||
root.style.setProperty('--FontColorQuote2', db.customTextTheme.FontColorQuote2 ?? '#FFB86C');
|
||||
break
|
||||
}
|
||||
}
|
||||
@@ -250,4 +264,11 @@ export function updateTextTheme(){
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if(!get(SafeModeStore)){
|
||||
CustomCSSStore.set(db.customCSS ?? '')
|
||||
}
|
||||
else{
|
||||
CustomCSSStore.set('')
|
||||
}
|
||||
}
|
||||
@@ -5,48 +5,53 @@ type HighlightInt = [Range, HighlightType]
|
||||
let highLights = new Map<number, HighlightInt[]>();
|
||||
|
||||
export const highlighter = (highlightDom:HTMLElement, id:number) => {
|
||||
if(highlightDom){
|
||||
if(!CSS.highlights){
|
||||
return
|
||||
}
|
||||
|
||||
const walker = document.createTreeWalker(highlightDom, NodeFilter.SHOW_TEXT)
|
||||
const nodes:Node[] = []
|
||||
let currentNode = walker.nextNode();
|
||||
while (currentNode) {
|
||||
nodes.push(currentNode);
|
||||
currentNode = walker.nextNode();
|
||||
}
|
||||
const str = "{{char}}"
|
||||
if (!str) {
|
||||
return;
|
||||
}
|
||||
|
||||
const ranges:HighlightInt[] = []
|
||||
|
||||
nodes.map((el) => {
|
||||
const text = el.textContent.toLowerCase()
|
||||
|
||||
const cbsParsed = simpleCBSHighlightParser(el,text)
|
||||
ranges.push(...cbsParsed)
|
||||
|
||||
for(const syntax of highlighterSyntax){
|
||||
const regex = syntax.regex
|
||||
let match:RegExpExecArray | null;
|
||||
while ((match = regex.exec(text)) !== null) {
|
||||
const length = match[0].length;
|
||||
const index = match.index;
|
||||
const range = new Range();
|
||||
range.setStart(el, index);
|
||||
range.setEnd(el, index + length);
|
||||
ranges.push([range, syntax.type])
|
||||
}
|
||||
try {
|
||||
|
||||
if(highlightDom){
|
||||
if(!CSS.highlights){
|
||||
return
|
||||
}
|
||||
});
|
||||
|
||||
highLights.set(id, ranges)
|
||||
|
||||
runHighlight()
|
||||
|
||||
const walker = document.createTreeWalker(highlightDom, NodeFilter.SHOW_TEXT)
|
||||
const nodes:Node[] = []
|
||||
let currentNode = walker.nextNode();
|
||||
while (currentNode) {
|
||||
nodes.push(currentNode);
|
||||
currentNode = walker.nextNode();
|
||||
}
|
||||
const str = "{{char}}"
|
||||
if (!str) {
|
||||
return;
|
||||
}
|
||||
|
||||
const ranges:HighlightInt[] = []
|
||||
|
||||
nodes.map((el) => {
|
||||
const text = el.textContent.toLowerCase()
|
||||
|
||||
const cbsParsed = simpleCBSHighlightParser(el,text)
|
||||
ranges.push(...cbsParsed)
|
||||
|
||||
for(const syntax of highlighterSyntax){
|
||||
const regex = syntax.regex
|
||||
let match:RegExpExecArray | null;
|
||||
while ((match = regex.exec(text)) !== null) {
|
||||
const length = match[0].length;
|
||||
const index = match.index;
|
||||
const range = new Range();
|
||||
range.setStart(el, index);
|
||||
range.setEnd(el, index + length);
|
||||
ranges.push([range, syntax.type])
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
highLights.set(id, ranges)
|
||||
|
||||
runHighlight()
|
||||
}
|
||||
} catch (error) {
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -80,13 +85,14 @@ export const removeHighlight = (id:number) => {
|
||||
}
|
||||
|
||||
const normalCBS = [
|
||||
'previous_char_chat', 'lastcharmessage', 'previous_user_chat', 'lastusermessage', 'char', 'bot',
|
||||
'user', 'char_persona', 'description', 'char_desc', 'example_dialogue',
|
||||
'char', 'user', 'char_persona', 'description', 'char_desc', 'example_dialogue', 'previous_char_chat',
|
||||
'lastcharmessage', 'previous_user_chat', 'lastusermessage',
|
||||
'example_message', 'persona', 'user_persona', 'lorebook', 'world_info', 'history', 'messages',
|
||||
'chat_index', 'first_msg_index', 'blank', 'none', 'message_time', 'message_date', 'time',
|
||||
'date', 'isotime', 'isodate', 'message_idle_duration', 'idle_duration', 'br', 'newline',
|
||||
'model', 'axmodel', 'role', 'jbtoggled', 'random', 'maxcontext', 'lastmessage', 'lastmessageid',
|
||||
'lastmessageindex', 'emotionlist', 'assetlist', 'prefill_supported', 'unixtime', '/', '/if', '/each', '/pure', '/if_pure', 'slot', 'module_enabled'
|
||||
'lastmessageindex', 'emotionlist', 'assetlist', 'prefill_supported', 'unixtime', 'slot', 'module_enabled',
|
||||
'is_first_message', '/', '/if', '/each', '/pure', '/if_pure', '/func', '/pure_display'
|
||||
]
|
||||
|
||||
const normalCBSwithParams = [
|
||||
@@ -97,15 +103,19 @@ const normalCBSwithParams = [
|
||||
'arraypop', 'array_pop', 'arraypush', 'array_push', 'arraysplice', 'array_splice',
|
||||
'makearray', 'array', 'a', 'make_array', 'history', 'messages', 'range', 'date', 'time', 'datetimeformat', 'date_time_format',
|
||||
'random', 'pick', 'roll', 'datetimeformat', 'hidden_key', 'reverse', 'getglobalvar', 'position', 'slot', 'rollp',
|
||||
'and', 'or', 'not', 'message_time_array', 'filter', 'greater', 'less', 'greater_equal', 'less_equal'
|
||||
'and', 'or', 'not', 'message_time_array', 'filter', 'greater', 'less', 'greater_equal', 'less_equal', 'arg'
|
||||
]
|
||||
|
||||
const displayRelatedCBS = [
|
||||
'raw', 'img', 'video', 'audio', 'bg', 'emotion', 'asset', 'video-img', 'comment'
|
||||
'raw', 'img', 'video', 'audio', 'bg', 'emotion', 'asset', 'video-img', 'comment', 'image'
|
||||
];
|
||||
|
||||
const nestedCBS = [
|
||||
'#if', '#if_pure ', '#pure ', '#each ', '#func', '#pure_display'
|
||||
]
|
||||
|
||||
const specialCBS = [
|
||||
'#if', '#if_pure ', '#pure ', '#each ', 'random:', 'pick:', 'roll:', 'datetimeformat:', '? ', 'hidden_key: ', 'reverse: ',
|
||||
'random:', 'pick:', 'roll:', 'datetimeformat:', '? ', 'hidden_key: ', 'reverse: ', ...nestedCBS
|
||||
]
|
||||
|
||||
const deprecatedCBS = [
|
||||
@@ -125,6 +135,11 @@ const decorators = [
|
||||
const deprecatedDecorators = [
|
||||
'end', 'assistant', 'user', 'system'
|
||||
]
|
||||
|
||||
export const AllCBS = [...normalCBS, ...(normalCBSwithParams.concat(displayRelatedCBS).map((v) => {
|
||||
return v + ':'
|
||||
})), ...nestedCBS]
|
||||
|
||||
const highlighterSyntax = [
|
||||
{
|
||||
regex: /<(char|user|bot)>/gi,
|
||||
|
||||
112
src/ts/hotkey.ts
112
src/ts/hotkey.ts
@@ -1,7 +1,9 @@
|
||||
import { get } from "svelte/store"
|
||||
import { alertToast, doingAlert } from "./alert"
|
||||
import { alertSelect, alertToast, doingAlert } from "./alert"
|
||||
import { DataBase, changeToPreset as changeToPreset2 } from "./storage/database"
|
||||
import { selectedCharID, settingsOpen } from "./stores"
|
||||
import { MobileGUIStack, MobileSideBar, openPersonaList, openPresetList, SafeModeStore, selectedCharID, settingsOpen } from "./stores"
|
||||
import { language } from "src/lang"
|
||||
import { updateTextThemeAndCSS } from "./gui/colorscheme"
|
||||
|
||||
export function initHotkey(){
|
||||
document.addEventListener('keydown', (ev) => {
|
||||
@@ -73,6 +75,25 @@ export function initHotkey(){
|
||||
ev.stopPropagation()
|
||||
break
|
||||
}
|
||||
case 'p':{
|
||||
openPresetList.set(!get(openPresetList))
|
||||
ev.preventDefault()
|
||||
ev.stopPropagation()
|
||||
break
|
||||
}
|
||||
case 'e':{
|
||||
openPersonaList.set(!get(openPersonaList))
|
||||
ev.preventDefault()
|
||||
ev.stopPropagation()
|
||||
break
|
||||
}
|
||||
case '.':{
|
||||
SafeModeStore.set(!get(SafeModeStore))
|
||||
updateTextThemeAndCSS()
|
||||
ev.preventDefault()
|
||||
ev.stopPropagation()
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
if(ev.key === 'Escape'){
|
||||
@@ -85,6 +106,93 @@ export function initHotkey(){
|
||||
ev.preventDefault()
|
||||
}
|
||||
})
|
||||
|
||||
|
||||
let touchs = 0
|
||||
let touchStartTime = 0
|
||||
//check for triple touch
|
||||
document.addEventListener('touchstart', async (ev) => {
|
||||
touchs++
|
||||
if(touchs > 2){
|
||||
if(Date.now() - touchStartTime > 300){
|
||||
return
|
||||
}
|
||||
touchs = 0
|
||||
if(doingAlert()){
|
||||
return
|
||||
}
|
||||
const selStr = await alertSelect([
|
||||
language.presets,
|
||||
language.persona,
|
||||
language.cancel
|
||||
])
|
||||
const sel = parseInt(selStr)
|
||||
if(sel === 0){
|
||||
openPresetList.set(!get(openPresetList))
|
||||
}
|
||||
if(sel === 1){
|
||||
openPersonaList.set(!get(openPersonaList))
|
||||
}
|
||||
}
|
||||
if(touchs === 1){
|
||||
touchStartTime = Date.now()
|
||||
}
|
||||
})
|
||||
document.addEventListener('touchend', (ev) => {
|
||||
touchs = 0
|
||||
})
|
||||
}
|
||||
|
||||
export function initMobileGesture(){
|
||||
|
||||
let pressingPointers = new Map<number, {x:number, y:number}>()
|
||||
|
||||
document.addEventListener('touchstart', (ev) => {
|
||||
for(const touch of ev.changedTouches){
|
||||
const ele = touch.target as HTMLElement
|
||||
if(ele.tagName === 'BUTTON' || ele.tagName === 'INPUT' || ele.tagName === 'SELECT' || ele.tagName === 'TEXTAREA'){
|
||||
return
|
||||
}
|
||||
pressingPointers.set(touch.identifier, {x: touch.clientX, y: touch.clientY})
|
||||
}
|
||||
}, {
|
||||
passive: true
|
||||
})
|
||||
document.addEventListener('touchend', (ev) => {
|
||||
for(const touch of ev.changedTouches){
|
||||
const d = pressingPointers.get(touch.identifier)
|
||||
const moveX = touch.clientX - d.x
|
||||
const moveY = touch.clientY - d.y
|
||||
pressingPointers.delete(touch.identifier)
|
||||
|
||||
if(moveX > 50 && Math.abs(moveY) < Math.abs(moveX)){
|
||||
if(get(selectedCharID) === -1){
|
||||
if(get(MobileGUIStack) > 0){
|
||||
MobileGUIStack.update(v => v - 1)
|
||||
}
|
||||
}
|
||||
else{
|
||||
if(get(MobileSideBar) > 0){
|
||||
MobileSideBar.update(v => v - 1)
|
||||
}
|
||||
}
|
||||
}
|
||||
else if(moveX < -50 && Math.abs(moveY) < Math.abs(moveX)){
|
||||
if(get(selectedCharID) === -1){
|
||||
if(get(MobileGUIStack) < 2){
|
||||
MobileGUIStack.update(v => v + 1)
|
||||
}
|
||||
}
|
||||
else{
|
||||
if(get(MobileSideBar) < 3){
|
||||
MobileSideBar.update(v => v + 1)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}, {
|
||||
passive: true
|
||||
})
|
||||
}
|
||||
|
||||
function changeToPreset(num:number){
|
||||
|
||||
@@ -105,10 +105,18 @@ export function getModelName(name:string){
|
||||
return 'GPT-4o ChatGPT'
|
||||
case 'gpt4om':
|
||||
return 'GPT-4o Mini'
|
||||
case 'gpt4o1-preview':
|
||||
return 'o1 Preview'
|
||||
case 'gpt4o1-mini':
|
||||
return 'o1 Mini'
|
||||
case 'gpt4om-2024-07-18':
|
||||
return 'GPT-4o Mini (2024-07-18)'
|
||||
case 'gemini-1.5-pro-latest':
|
||||
return 'Gemini 1.5 Pro'
|
||||
case 'gemini-1.5-pro-exp-0801':
|
||||
return 'Gemini 1.5 Pro Exp (0801)'
|
||||
case 'gemini-1.5-pro-exp-0827':
|
||||
return 'Gemini 1.5 Pro Exp (0827)'
|
||||
case 'gemini-1.5-flash':
|
||||
return 'Gemini 1.5 Flash'
|
||||
case 'ollama-hosted':
|
||||
|
||||
671
src/ts/parser.ts
671
src/ts/parser.ts
@@ -4,17 +4,13 @@ import { DataBase, setDatabase, type Database, type Message, type character, typ
|
||||
import { getFileSrc } from './storage/globalApi';
|
||||
import { processScriptFull } from './process/scripts';
|
||||
import { get } from 'svelte/store';
|
||||
import css from '@adobe/css-tools'
|
||||
import { CurrentCharacter, CurrentChat, SizeStore, selectedCharID } from './stores';
|
||||
import css, { type CssAtRuleAST } from '@adobe/css-tools'
|
||||
import { CurrentCharacter, SizeStore, selectedCharID } from './stores';
|
||||
import { calcString } from './process/infunctions';
|
||||
import { findCharacterbyId, getPersonaPrompt, getUserIcon, getUserName, parseKeyValue, sfc32, sleep, uuidtoNumber } from './util';
|
||||
import { getInlayImage, writeInlayImage } from './process/files/image';
|
||||
import { getModuleLorebooks } from './process/modules';
|
||||
import { HypaProcesser } from './process/memory/hypamemory';
|
||||
import { generateAIImage } from './process/stableDiff';
|
||||
import { requestChatData } from './process/request';
|
||||
import { getInlayImage } from './process/files/image';
|
||||
import { getModuleAssets, getModuleLorebooks } from './process/modules';
|
||||
import type { OpenAIChat } from './process';
|
||||
import { alertInput, alertNormal } from './alert';
|
||||
import hljs from 'highlight.js/lib/core'
|
||||
import 'highlight.js/styles/atom-one-dark.min.css'
|
||||
|
||||
@@ -79,11 +75,24 @@ DOMPurify.addHook("uponSanitizeAttribute", (node, data) => {
|
||||
|
||||
|
||||
function renderMarkdown(md:markdownit, data:string){
|
||||
return md.render(data.replace(/“|”/g, '"').replace(/‘|’/g, "'"))
|
||||
.replace(/\uE9b0/gu, '<mark risu-mark="quote2">“')
|
||||
.replace(/\uE9b1/gu, '”</mark>')
|
||||
.replace(/\uE9b2/gu, '<mark risu-mark="quote1">‘')
|
||||
.replace(/\uE9b3/gu, '’</mark>')
|
||||
const db = get(DataBase)
|
||||
let quotes = ['“', '”', '‘', '’']
|
||||
if(db?.customQuotes){
|
||||
quotes = db.customQuotesData ?? quotes
|
||||
}
|
||||
|
||||
let text = md.render(data.replace(/“|”/g, '"').replace(/‘|’/g, "'"))
|
||||
|
||||
if(db?.unformatQuotes){
|
||||
text = text.replace(/\uE9b0/gu, quotes[0]).replace(/\uE9b1/gu, quotes[1])
|
||||
text = text.replace(/\uE9b2/gu, quotes[2]).replace(/\uE9b3/gu, quotes[3])
|
||||
}
|
||||
else{
|
||||
text = text.replace(/\uE9b0/gu, '<mark risu-mark="quote2">' + quotes[0]).replace(/\uE9b1/gu, quotes[1] + '</mark>')
|
||||
text = text.replace(/\uE9b2/gu, '<mark risu-mark="quote1">' + quotes[2]).replace(/\uE9b3/gu, quotes[3] + '</mark>')
|
||||
}
|
||||
|
||||
return text
|
||||
}
|
||||
|
||||
async function renderHighlightableMarkdown(data:string) {
|
||||
@@ -242,7 +251,7 @@ async function renderHighlightableMarkdown(data:string) {
|
||||
}
|
||||
|
||||
|
||||
export const assetRegex = /{{(raw|img|video|audio|bg|emotion|asset|video-img|source)::(.+?)}}/g
|
||||
export const assetRegex = /{{(raw|path|img|image|video|audio|bg|emotion|asset|video-img|source)::(.+?)}}/g
|
||||
|
||||
async function parseAdditionalAssets(data:string, char:simpleCharacterArgument|character, mode:'normal'|'back', mode2:'unset'|'pre'|'post' = 'unset'){
|
||||
const db = get(DataBase)
|
||||
@@ -273,6 +282,16 @@ async function parseAdditionalAssets(data:string, char:simpleCharacterArgument|c
|
||||
}
|
||||
}
|
||||
}
|
||||
const moduleAssets = getModuleAssets()
|
||||
if(moduleAssets.length > 0){
|
||||
for(const asset of moduleAssets){
|
||||
const assetPath = await getFileSrc(asset[1])
|
||||
assetPaths[asset[0].toLocaleLowerCase()] = {
|
||||
path: assetPath,
|
||||
ext: asset[2]
|
||||
}
|
||||
}
|
||||
}
|
||||
const videoExtention = ['mp4', 'webm', 'avi', 'm4p', 'm4v']
|
||||
let needsSourceAccess = false
|
||||
data = data.replaceAll(assetRegex, (full:string, type:string, name:string) => {
|
||||
@@ -301,15 +320,18 @@ async function parseAdditionalAssets(data:string, char:simpleCharacterArgument|c
|
||||
}
|
||||
switch(type){
|
||||
case 'raw':
|
||||
case 'path':
|
||||
return path.path
|
||||
case 'img':
|
||||
return `<img src="${path.path}" alt="${path.path}" style="${assetWidthString} "/>`
|
||||
case 'image':
|
||||
return `<div class="risu-inlay-image"><img src="${path.path}" alt="${path.path}" style="${assetWidthString}"/></div>\n`
|
||||
case 'video':
|
||||
return `<video controls autoplay loop><source src="${path.path}" type="video/mp4"></video>`
|
||||
return `<video controls autoplay loop><source src="${path.path}" type="video/mp4"></video>\n`
|
||||
case 'video-img':
|
||||
return `<video autoplay muted loop><source src="${path.path}" type="video/mp4"></video>`
|
||||
return `<video autoplay muted loop><source src="${path.path}" type="video/mp4"></video>\n`
|
||||
case 'audio':
|
||||
return `<audio controls autoplay loop><source src="${path.path}" type="audio/mpeg"></audio>`
|
||||
return `<audio controls autoplay loop><source src="${path.path}" type="audio/mpeg"></audio>\n`
|
||||
case 'bg':
|
||||
if(mode === 'back'){
|
||||
return `<div style="width:100%;height:100%;background: linear-gradient(rgba(0, 0, 0, 0.8), rgba(0, 0, 0, 0.8)),url(${path.path}); background-size: cover;"></div>`
|
||||
@@ -317,9 +339,9 @@ async function parseAdditionalAssets(data:string, char:simpleCharacterArgument|c
|
||||
break
|
||||
case 'asset':{
|
||||
if(path.ext && videoExtention.includes(path.ext)){
|
||||
return `<video autoplay muted loop><source src="${path.path}" type="video/mp4"></video>`
|
||||
return `<video autoplay muted loop><source src="${path.path}" type="video/mp4"></video>\n`
|
||||
}
|
||||
return `<img src="${path.path}" alt="${path.path}" style="${assetWidthString} "/>`
|
||||
return `<img src="${path.path}" alt="${path.path}" style="${assetWidthString} "/>\n`
|
||||
}
|
||||
}
|
||||
return ''
|
||||
@@ -365,7 +387,13 @@ export interface simpleCharacterArgument{
|
||||
}
|
||||
|
||||
|
||||
export async function ParseMarkdown(data:string, charArg:(character|simpleCharacterArgument | groupChat | string) = null, mode:'normal'|'back'|'pretranslate' = 'normal', chatID=-1) {
|
||||
export async function ParseMarkdown(
|
||||
data:string,
|
||||
charArg:(character|simpleCharacterArgument | groupChat | string) = null,
|
||||
mode:'normal'|'back'|'pretranslate' = 'normal',
|
||||
chatID=-1,
|
||||
cbsConditions:CbsConditions = {}
|
||||
) {
|
||||
let firstParsed = ''
|
||||
const additionalAssetMode = (mode === 'back') ? 'back' : 'normal'
|
||||
let char = (typeof(charArg) === 'string') ? (findCharacterbyId(charArg)) : (charArg)
|
||||
@@ -374,7 +402,7 @@ export async function ParseMarkdown(data:string, charArg:(character|simpleCharac
|
||||
firstParsed = data
|
||||
}
|
||||
if(char){
|
||||
data = (await processScriptFull(char, data, 'editdisplay', chatID)).data
|
||||
data = (await processScriptFull(char, data, 'editdisplay', chatID, cbsConditions)).data
|
||||
}
|
||||
if(firstParsed !== data && char && char.type !== 'group'){
|
||||
data = await parseAdditionalAssets(data, char, additionalAssetMode, 'post')
|
||||
@@ -421,6 +449,32 @@ function encodeStyle(txt:string){
|
||||
}
|
||||
const styleDecodeRegex = /\<risu-style\>(.+?)\<\/risu-style\>/gms
|
||||
|
||||
function decodeStyleRule(rule:CssAtRuleAST){
|
||||
if(rule.type === 'rule'){
|
||||
if(rule.selectors){
|
||||
for(let i=0;i<rule.selectors.length;i++){
|
||||
let slt:string = rule.selectors[i]
|
||||
if(slt){
|
||||
let selectors = (slt.split(' ') ?? []).map((v) => {
|
||||
if(v.startsWith('.')){
|
||||
return ".x-risu-" + v.substring(1)
|
||||
}
|
||||
return v
|
||||
}).join(' ')
|
||||
|
||||
rule.selectors[i] = ".chattext " + selectors
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if(rule.type === 'media' || rule.type === 'supports' || rule.type === 'document' || rule.type === 'host' || rule.type === 'container' ){
|
||||
for(let i=0;i<rule.rules.length;i++){
|
||||
rule.rules[i] = decodeStyleRule(rule.rules[i])
|
||||
}
|
||||
}
|
||||
return rule
|
||||
}
|
||||
|
||||
function decodeStyle(text:string){
|
||||
|
||||
return text.replaceAll(styleDecodeRegex, (full, txt:string) => {
|
||||
@@ -428,26 +482,10 @@ function decodeStyle(text:string){
|
||||
const ast = css.parse(Buffer.from(txt, 'hex').toString('utf-8'))
|
||||
const rules = ast?.stylesheet?.rules
|
||||
if(rules){
|
||||
for(const rule of rules){
|
||||
|
||||
if(rule.type === 'rule'){
|
||||
if(rule.selectors){
|
||||
for(let i=0;i<rule.selectors.length;i++){
|
||||
let slt:string = rule.selectors[i]
|
||||
if(slt){
|
||||
let selectors = (slt.split(' ') ?? []).map((v) => {
|
||||
if(v.startsWith('.')){
|
||||
return ".x-risu-" + v.substring(1)
|
||||
}
|
||||
return v
|
||||
}).join(' ')
|
||||
|
||||
rule.selectors[i] = ".chattext " + selectors
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
for(let i=0;i<rules.length;i++){
|
||||
rules[i] = decodeStyleRule(rules[i])
|
||||
}
|
||||
ast.stylesheet.rules = rules
|
||||
}
|
||||
return `<style>${css.stringify(ast)}</style>`
|
||||
|
||||
@@ -594,6 +632,12 @@ type matcherArg = {
|
||||
text?:string,
|
||||
recursiveCount?:number
|
||||
lowLevelAccess?:boolean
|
||||
cbsConditions:CbsConditions
|
||||
}
|
||||
|
||||
export type CbsConditions = {
|
||||
firstmsg?:boolean
|
||||
chatRole?:string
|
||||
}
|
||||
function basicMatcher (p1:string,matcherArg:matcherArg,vars:{[key:string]:string}|null = null ):{
|
||||
text:string,
|
||||
@@ -619,7 +663,7 @@ function basicMatcher (p1:string,matcherArg:matcherArg,vars:{[key:string]:string
|
||||
}
|
||||
pointer--
|
||||
}
|
||||
return selchar.firstMsgIndex === -1 ? selchar.firstMessage : selchar.alternateGreetings[selchar.firstMsgIndex]
|
||||
return chat.fmIndex === -1 ? selchar.firstMessage : selchar.alternateGreetings[chat.fmIndex]
|
||||
}
|
||||
case 'previous_user_chat':
|
||||
case 'lastusermessage':{
|
||||
@@ -633,7 +677,7 @@ function basicMatcher (p1:string,matcherArg:matcherArg,vars:{[key:string]:string
|
||||
}
|
||||
pointer--
|
||||
}
|
||||
return selchar.firstMsgIndex === -1 ? selchar.firstMessage : selchar.alternateGreetings[selchar.firstMsgIndex]
|
||||
return chat.fmIndex === -1 ? selchar.firstMessage : selchar.alternateGreetings[chat.fmIndex]
|
||||
}
|
||||
return ''
|
||||
}
|
||||
@@ -764,7 +808,8 @@ function basicMatcher (p1:string,matcherArg:matcherArg,vars:{[key:string]:string
|
||||
}
|
||||
case 'first_msg_index':{
|
||||
const selchar = db.characters[get(selectedCharID)]
|
||||
return selchar.firstMsgIndex.toString()
|
||||
const chat = selchar.chats[selchar.chatPage]
|
||||
return chat.fmIndex.toString()
|
||||
}
|
||||
case 'blank':
|
||||
case 'none':{
|
||||
@@ -926,11 +971,26 @@ function basicMatcher (p1:string,matcherArg:matcherArg,vars:{[key:string]:string
|
||||
return db.subModel
|
||||
}
|
||||
case 'role': {
|
||||
if(matcherArg.cbsConditions.chatRole){
|
||||
return matcherArg.cbsConditions.chatRole
|
||||
}
|
||||
if(matcherArg.cbsConditions.firstmsg){
|
||||
return 'char'
|
||||
}
|
||||
if (chatID !== -1) {
|
||||
const selchar = db.characters[get(selectedCharID)]
|
||||
return selchar.chats[selchar.chatPage].message[chatID].role;
|
||||
}
|
||||
return matcherArg.role ?? 'role'
|
||||
return matcherArg.role ?? 'null'
|
||||
}
|
||||
case 'isfirstmsg':
|
||||
case 'is_first_msg':
|
||||
case 'is_first_message':
|
||||
case 'isfirstmessage':{
|
||||
if(matcherArg.cbsConditions.firstmsg){
|
||||
return '1'
|
||||
}
|
||||
return '0'
|
||||
}
|
||||
case 'jbtoggled':{
|
||||
return db.jailbreakToggle ? '1' : '0'
|
||||
@@ -1174,6 +1234,16 @@ function basicMatcher (p1:string,matcherArg:matcherArg,vars:{[key:string]:string
|
||||
case 'object_element':{
|
||||
return parseDict(arra[1])[arra[2]] ?? 'null'
|
||||
}
|
||||
case 'object_assert':
|
||||
case 'dict_assert':
|
||||
case 'dictassert':
|
||||
case 'objectassert':{
|
||||
const dict = parseDict(arra[1])
|
||||
if(!dict[arra[2]]){
|
||||
dict[arra[2]] = arra[3]
|
||||
}
|
||||
return JSON.stringify(dict)
|
||||
}
|
||||
|
||||
case 'element':
|
||||
case 'ele':{
|
||||
@@ -1218,6 +1288,15 @@ function basicMatcher (p1:string,matcherArg:matcherArg,vars:{[key:string]:string
|
||||
arr.splice(Number(arra[2]), Number(arra[3]), arra[4])
|
||||
return makeArray(arr)
|
||||
}
|
||||
case 'arrayassert':
|
||||
case 'array_assert':{
|
||||
const arr = parseArray(arra[1])
|
||||
const index = Number(arra[2])
|
||||
if(index >= arr.length){
|
||||
arr[index] = arra[3]
|
||||
}
|
||||
return makeArray(arr)
|
||||
}
|
||||
case 'makearray':
|
||||
case 'array':
|
||||
case 'a':
|
||||
@@ -1232,11 +1311,6 @@ function basicMatcher (p1:string,matcherArg:matcherArg,vars:{[key:string]:string
|
||||
case 'object':
|
||||
case 'o':
|
||||
case 'make_object':{
|
||||
//ideas:
|
||||
// - {{o::key1:value1::key2:value2}} - its confusing for users
|
||||
// - {{o::key1:value1,key2:value2}} - since comma can break the parser, this is not good
|
||||
// - {{o::key=value::key2=value2}} - this is good enough I think, just need to escape the equal sign
|
||||
|
||||
const sliced = arra.slice(1)
|
||||
let out = {}
|
||||
|
||||
@@ -1600,7 +1674,7 @@ const legacyBlockMatcher = (p1:string,matcherArg:matcherArg) => {
|
||||
return null
|
||||
}
|
||||
|
||||
type blockMatch = 'ignore'|'parse'|'nothing'|'parse-pure'|'pure'|'each'
|
||||
type blockMatch = 'ignore'|'parse'|'nothing'|'parse-pure'|'pure'|'each'|'function'|'pure-display'
|
||||
|
||||
function parseArray(p1:string):string[]{
|
||||
try {
|
||||
@@ -1643,14 +1717,18 @@ function blockStartMatcher(p1:string,matcherArg:matcherArg):{type:blockMatch,typ
|
||||
if(p1 === '#pure'){
|
||||
return {type:'pure'}
|
||||
}
|
||||
if(p1 === '#pure_display' || p1 === '#puredisplay'){
|
||||
return {type:'pure-display'}
|
||||
}
|
||||
if(p1.startsWith('#each')){
|
||||
return {type:'each',type2:p1.substring(5).trim()}
|
||||
}
|
||||
if(p1.startsWith('#func')){
|
||||
const statement = p1.split(' ')
|
||||
if(matcherArg.funcName === statement[1]){
|
||||
return {type:'parse',funcArg:statement.slice(2)}
|
||||
if(statement.length > 1){
|
||||
return {type:'function',funcArg:statement.slice(1)}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
@@ -1666,7 +1744,9 @@ function trimLines(p1:string){
|
||||
function blockEndMatcher(p1:string,type:{type:blockMatch,type2?:string},matcherArg:matcherArg):string{
|
||||
switch(type.type){
|
||||
case 'pure':
|
||||
case 'parse-pure':{
|
||||
case 'parse-pure':
|
||||
case 'pure-display':
|
||||
case 'function':{
|
||||
return p1
|
||||
}
|
||||
case 'parse':
|
||||
@@ -1690,6 +1770,9 @@ export function risuChatParser(da:string, arg:{
|
||||
visualize?:boolean,
|
||||
role?:string
|
||||
runVar?:boolean
|
||||
functions?:Map<string,{data:string,arg:string[]}>
|
||||
callStack?:number
|
||||
cbsConditions?:CbsConditions
|
||||
} = {}):string{
|
||||
const chatID = arg.chatID ?? -1
|
||||
const db = arg.db ?? get(DataBase)
|
||||
@@ -1699,9 +1782,14 @@ export function risuChatParser(da:string, arg:{
|
||||
|
||||
if(aChara){
|
||||
if(typeof(aChara) !== 'string' && aChara.type === 'group'){
|
||||
const gc = findCharacterbyId(aChara.chats[aChara.chatPage].message.at(-1).saying ?? '')
|
||||
if(gc.name !== 'Unknown Character'){
|
||||
chara = gc
|
||||
if(aChara.chats[aChara.chatPage].message.length > 0){
|
||||
const gc = findCharacterbyId(aChara.chats[aChara.chatPage].message.at(-1).saying ?? '')
|
||||
if(gc.name !== 'Unknown Character'){
|
||||
chara = gc
|
||||
}
|
||||
}
|
||||
else{
|
||||
chara = 'bot'
|
||||
}
|
||||
}
|
||||
else{
|
||||
@@ -1722,12 +1810,20 @@ export function risuChatParser(da:string, arg:{
|
||||
let stackType = new Uint8Array(512)
|
||||
let pureModeNest:Map<number,boolean> = new Map()
|
||||
let pureModeNestType:Map<number,string> = new Map()
|
||||
let blockNestType:Map<number,{type:blockMatch,type2?:string}> = new Map()
|
||||
let blockNestType:Map<number,{
|
||||
type:blockMatch,
|
||||
type2?:string
|
||||
funcArg?:string[]
|
||||
}> = new Map()
|
||||
let commentMode = false
|
||||
let commentLatest:string[] = [""]
|
||||
let commentV = new Uint8Array(512)
|
||||
let thinkingMode = false
|
||||
let tempVar:{[key:string]:string} = {}
|
||||
let functions:Map<string,{
|
||||
data:string,
|
||||
arg:string[]
|
||||
}> = arg.functions ?? (new Map())
|
||||
|
||||
const matcherObj = {
|
||||
chatID: chatID,
|
||||
@@ -1740,6 +1836,7 @@ export function risuChatParser(da:string, arg:{
|
||||
role: arg.role,
|
||||
runVar: arg.runVar ?? false,
|
||||
consistantChar: arg.consistantChar ?? false,
|
||||
cbsConditions: arg.cbsConditions ?? {}
|
||||
}
|
||||
|
||||
|
||||
@@ -1808,7 +1905,10 @@ export function risuChatParser(da:string, arg:{
|
||||
nested.unshift('')
|
||||
stackType[nested.length] = 5
|
||||
blockNestType.set(nested.length, matchResult)
|
||||
if(matchResult.type === 'ignore' || matchResult.type === 'pure' || matchResult.type === 'each'){
|
||||
if( matchResult.type === 'ignore' || matchResult.type === 'pure' ||
|
||||
matchResult.type === 'each' || matchResult.type === 'function' ||
|
||||
matchResult.type === 'pure-display'
|
||||
){
|
||||
pureModeNest.set(nested.length, true)
|
||||
pureModeNestType.set(nested.length, "block")
|
||||
}
|
||||
@@ -1818,7 +1918,10 @@ export function risuChatParser(da:string, arg:{
|
||||
if(dat.startsWith('/')){
|
||||
if(stackType[nested.length] === 5){
|
||||
const blockType = blockNestType.get(nested.length)
|
||||
if(blockType.type === 'ignore' || blockType.type === 'pure' || blockType.type === 'each'){
|
||||
if( blockType.type === 'ignore' || blockType.type === 'pure' ||
|
||||
blockType.type === 'each' || blockType.type === 'function' ||
|
||||
blockType.type === 'pure-display'
|
||||
){
|
||||
pureModeNest.delete(nested.length)
|
||||
pureModeNestType.delete(nested.length)
|
||||
}
|
||||
@@ -1837,6 +1940,18 @@ export function risuChatParser(da:string, arg:{
|
||||
da = da.substring(0, pointer + 1) + added.trim() + da.substring(pointer + 1)
|
||||
break
|
||||
}
|
||||
if(blockType.type === 'function'){
|
||||
console.log(matchResult)
|
||||
functions.set(blockType.funcArg[0], {
|
||||
data: matchResult,
|
||||
arg: blockType.funcArg.slice(1)
|
||||
})
|
||||
break
|
||||
}
|
||||
if(blockType.type === 'pure-display'){
|
||||
nested[0] += matchResult.replaceAll('{{', '\\{\\{').replaceAll('}}', '\\}\\}')
|
||||
break
|
||||
}
|
||||
if(matchResult === ''){
|
||||
break
|
||||
}
|
||||
@@ -1849,6 +1964,26 @@ export function risuChatParser(da:string, arg:{
|
||||
break
|
||||
}
|
||||
}
|
||||
if(dat.startsWith('call::')){
|
||||
if(arg.callStack && arg.callStack > 10){
|
||||
nested[0] += `ERROR: Call stack limit reached`
|
||||
break
|
||||
}
|
||||
const argData = dat.split('::').slice(1)
|
||||
const funcName = argData[0]
|
||||
const func = functions.get(funcName)
|
||||
console.log(func)
|
||||
if(func){
|
||||
let data = func.data
|
||||
for(let i = 0;i < argData.length;i++){
|
||||
data = data.replaceAll(`{{arg::${i}}}`, argData[i])
|
||||
}
|
||||
arg.functions = functions
|
||||
arg.callStack = (arg.callStack ?? 0) + 1
|
||||
nested[0] += risuChatParser(data, arg)
|
||||
break
|
||||
}
|
||||
}
|
||||
const mc = isPureMode() ? null :basicMatcher(dat, matcherObj, tempVar)
|
||||
if(!mc && mc !== ''){
|
||||
nested[0] += `{{${dat}}}`
|
||||
@@ -1965,424 +2100,6 @@ export function risuChatParser(da:string, arg:{
|
||||
return nested[0] + result
|
||||
}
|
||||
|
||||
export async function commandMatcher(p1:string,matcherArg:matcherArg,vars:{[key:string]:string}):Promise<{
|
||||
text:string,
|
||||
var:{[key:string]:string}
|
||||
}|void|null> {
|
||||
const arra = p1.split('::')
|
||||
|
||||
switch(arra[0]){
|
||||
case 'pushchat':
|
||||
case 'addchat':
|
||||
case 'add_chat':
|
||||
case 'push_chat':{
|
||||
const chat = get(CurrentChat)
|
||||
chat.message.push({role: arra[1] === 'user' ? 'user' : 'char', data: arra[2] ?? ''})
|
||||
CurrentChat.set(chat)
|
||||
return {
|
||||
text: '',
|
||||
var: vars
|
||||
}
|
||||
}
|
||||
case 'yield':{
|
||||
vars['__return__'] = (vars['__return__'] ?? '') + arra[1]
|
||||
return {
|
||||
text: '',
|
||||
var: vars
|
||||
}
|
||||
}
|
||||
case 'setchat':
|
||||
case 'set_chat':{
|
||||
const chat = get(CurrentChat)
|
||||
const message = chat.message?.at(Number(arra[1]))
|
||||
if(message){
|
||||
message.data = arra[2] ?? ''
|
||||
}
|
||||
CurrentChat.set(chat)
|
||||
}
|
||||
case 'set_chat_role':
|
||||
case 'setchatrole':{
|
||||
const chat = get(CurrentChat)
|
||||
const message = chat.message?.at(Number(arra[1]))
|
||||
if(message){
|
||||
message.role = arra[2] === 'user' ? 'user' : 'char'
|
||||
}
|
||||
CurrentChat.set(chat)
|
||||
}
|
||||
case 'cutchat':
|
||||
case 'cut_chat':{
|
||||
const chat = get(CurrentChat)
|
||||
chat.message = chat.message.slice(Number(arra[1]), Number(arra[2]))
|
||||
CurrentChat.set(chat)
|
||||
return {
|
||||
text: '',
|
||||
var: vars
|
||||
}
|
||||
}
|
||||
case 'insertchat':
|
||||
case 'insert_chat':{
|
||||
const chat = get(CurrentChat)
|
||||
chat.message.splice(Number(arra[1]), 0, {role: arra[2] === 'user' ? 'user' : 'char', data: arra[3] ?? ''})
|
||||
CurrentChat.set(chat)
|
||||
return {
|
||||
text: '',
|
||||
var: vars
|
||||
}
|
||||
}
|
||||
case 'removechat':
|
||||
case 'remove_chat':{
|
||||
const chat = get(CurrentChat)
|
||||
chat.message.splice(Number(arra[1]), 1)
|
||||
CurrentChat.set(chat)
|
||||
return {
|
||||
text: '',
|
||||
var: vars
|
||||
}
|
||||
}
|
||||
case 'regex':{
|
||||
const reg = new RegExp(arra[1], arra[2])
|
||||
const match = reg.exec(arra[3])
|
||||
let res:string[] = []
|
||||
for(let i = 0;i < match.length;i++){
|
||||
res.push(match[i])
|
||||
}
|
||||
return {
|
||||
text: makeArray(res),
|
||||
var: vars
|
||||
}
|
||||
}
|
||||
case 'replace_regex':
|
||||
case 'replaceregex':{
|
||||
const reg = new RegExp(arra[1], arra[2])
|
||||
return {
|
||||
text: arra[3].replace(reg, arra[4]),
|
||||
var: vars
|
||||
}
|
||||
}
|
||||
case 'call':{
|
||||
//WIP
|
||||
const called = await risuCommandParser(arra[1], {
|
||||
db: matcherArg.db,
|
||||
chara: matcherArg.chara,
|
||||
funcName: arra[2],
|
||||
passed: arra.slice(3),
|
||||
recursiveCount: (matcherArg.recursiveCount ?? 0) + 1
|
||||
})
|
||||
|
||||
return {
|
||||
text: called['__return__'] ?? '',
|
||||
var: vars
|
||||
}
|
||||
}
|
||||
case 'stop_chat':{
|
||||
vars['__stop_chat__'] = '1'
|
||||
return {
|
||||
text: '',
|
||||
var: vars
|
||||
}
|
||||
}
|
||||
case 'similaritysearch':
|
||||
case 'similarity_search':
|
||||
case 'similarity':{
|
||||
if(!matcherArg.lowLevelAccess){
|
||||
return {
|
||||
text: '',
|
||||
var: vars
|
||||
}
|
||||
}
|
||||
const processer = new HypaProcesser('MiniLM')
|
||||
const source = arra[1]
|
||||
const target = parseArray(arra[2])
|
||||
await processer.addText(target)
|
||||
const result = await processer.similaritySearch(source)
|
||||
return {
|
||||
text: makeArray(result),
|
||||
var: vars
|
||||
}
|
||||
}
|
||||
case 'image_generation':
|
||||
case 'imagegeneration':
|
||||
case 'imagegen':
|
||||
case 'image_gen':{
|
||||
if(!matcherArg.lowLevelAccess){
|
||||
return {
|
||||
text: '',
|
||||
var: vars
|
||||
}
|
||||
}
|
||||
const prompt = arra[1]
|
||||
const negative = arra[2] || ''
|
||||
const char = matcherArg.chara
|
||||
|
||||
const gen = await generateAIImage(prompt, char as character, negative, 'inlay')
|
||||
if(!gen){
|
||||
return {
|
||||
text: 'ERROR: Image generation failed',
|
||||
var: vars
|
||||
}
|
||||
}
|
||||
const imgHTML = new Image()
|
||||
imgHTML.src = gen
|
||||
const inlay = await writeInlayImage(imgHTML)
|
||||
return {
|
||||
text: inlay,
|
||||
var: vars
|
||||
}
|
||||
}
|
||||
case 'send_llm':
|
||||
case 'llm':{
|
||||
if(!matcherArg.lowLevelAccess){
|
||||
return {
|
||||
text: '',
|
||||
var: vars
|
||||
}
|
||||
}
|
||||
const prompt = parseArray(arra[1])
|
||||
let promptbody:OpenAIChat[] = prompt.map((f) => {
|
||||
const dict = parseDict(f)
|
||||
let role:'system'|'user'|'assistant' = 'assistant'
|
||||
switch(dict['role']){
|
||||
case 'system':
|
||||
case 'sys':
|
||||
role = 'system'
|
||||
break
|
||||
case 'user':
|
||||
role = 'user'
|
||||
break
|
||||
case 'assistant':
|
||||
case 'bot':
|
||||
case 'char':{
|
||||
role = 'assistant'
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
content: dict['content'] ?? '',
|
||||
role: role,
|
||||
}
|
||||
})
|
||||
const result = await requestChatData({
|
||||
formated: promptbody,
|
||||
bias: {},
|
||||
useStreaming: false,
|
||||
noMultiGen: true,
|
||||
}, 'model')
|
||||
|
||||
if(result.type === 'fail'){
|
||||
return {
|
||||
text: 'ERROR: ' + result.result,
|
||||
var: vars
|
||||
}
|
||||
}
|
||||
|
||||
if(result.type === 'streaming' || result.type === 'multiline'){
|
||||
return {
|
||||
text: 'ERROR: Streaming and multiline is not supported in this context',
|
||||
var: vars
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
text: result.result,
|
||||
var: vars
|
||||
}
|
||||
}
|
||||
case 'alert':{
|
||||
alertNormal(arra[1])
|
||||
return {
|
||||
text: '',
|
||||
var: vars
|
||||
}
|
||||
}
|
||||
case 'input':
|
||||
case 'alert_input':
|
||||
case 'alertinput':{
|
||||
const input = await alertInput(arra[1])
|
||||
return {
|
||||
text: input,
|
||||
var: vars
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const matched = basicMatcher(p1, matcherArg)
|
||||
if(typeof(matched) === 'string'){
|
||||
return {
|
||||
text: matched,
|
||||
var: vars
|
||||
}
|
||||
}
|
||||
|
||||
return matched
|
||||
}
|
||||
|
||||
|
||||
export async function risuCommandParser(da:string, arg:{
|
||||
db?:Database
|
||||
chara?:string|character|groupChat
|
||||
funcName?:string
|
||||
passed?:string[],
|
||||
recursiveCount?:number
|
||||
lowLevelAccess?:boolean
|
||||
} = {}):Promise<{[key:string]:string}>{
|
||||
const db = arg.db ?? get(DataBase)
|
||||
const aChara = arg.chara
|
||||
let chara:character|string = null
|
||||
let passed = arg.passed ?? []
|
||||
let recursiveCount = arg.recursiveCount ?? 0
|
||||
|
||||
if(recursiveCount > 30){
|
||||
return {}
|
||||
}
|
||||
|
||||
if(aChara){
|
||||
if(typeof(aChara) !== 'string' && aChara.type === 'group'){
|
||||
const gc = findCharacterbyId(aChara.chats[aChara.chatPage].message.at(-1).saying ?? '')
|
||||
if(gc.name !== 'Unknown Character'){
|
||||
chara = gc
|
||||
}
|
||||
}
|
||||
else{
|
||||
chara = aChara
|
||||
}
|
||||
}
|
||||
|
||||
let pointer = 0;
|
||||
let nested:string[] = [""]
|
||||
let stackType = new Uint8Array(512)
|
||||
let pureModeNest:Map<number,boolean> = new Map()
|
||||
let pureModeNestType:Map<number,string> = new Map()
|
||||
let blockNestType:Map<number,{type:blockMatch,type2?:string}> = new Map()
|
||||
const matcherObj = {
|
||||
chatID: -1,
|
||||
chara: chara,
|
||||
rmVar: false,
|
||||
db: db,
|
||||
var: null,
|
||||
tokenizeAccurate: false,
|
||||
displaying: false,
|
||||
role: null,
|
||||
runVar: false,
|
||||
consistantChar: false,
|
||||
funcName: arg.funcName ?? null,
|
||||
text: da,
|
||||
recursiveCount: recursiveCount,
|
||||
lowLevelAccess: arg.lowLevelAccess ?? false
|
||||
}
|
||||
|
||||
let tempVar:{[key:string]:string} = {}
|
||||
|
||||
const isPureMode = () => {
|
||||
return pureModeNest.size > 0
|
||||
}
|
||||
while(pointer < da.length){
|
||||
switch(da[pointer]){
|
||||
case '{':{
|
||||
if(da[pointer + 1] !== '{' && da[pointer + 1] !== '#'){
|
||||
nested[0] += da[pointer]
|
||||
break
|
||||
}
|
||||
pointer++
|
||||
nested.unshift('')
|
||||
stackType[nested.length] = 1
|
||||
break
|
||||
}
|
||||
case '}':{
|
||||
if(da[pointer + 1] !== '}' || nested.length === 1 || stackType[nested.length] !== 1){
|
||||
nested[0] += da[pointer]
|
||||
break
|
||||
}
|
||||
pointer++
|
||||
const dat = nested.shift()
|
||||
if(dat.startsWith('#')){
|
||||
if(isPureMode()){
|
||||
nested[0] += `{{${dat}}}`
|
||||
nested.unshift('')
|
||||
stackType[nested.length] = 6
|
||||
break
|
||||
}
|
||||
const matchResult = blockStartMatcher(dat, matcherObj)
|
||||
if(matchResult.type === 'nothing'){
|
||||
nested[0] += `{{${dat}}}`
|
||||
break
|
||||
}
|
||||
else{
|
||||
nested.unshift('')
|
||||
stackType[nested.length] = 5
|
||||
blockNestType.set(nested.length, matchResult)
|
||||
if(matchResult.type === 'ignore' || matchResult.type === 'pure' || matchResult.type === 'each'){
|
||||
pureModeNest.set(nested.length, true)
|
||||
pureModeNestType.set(nested.length, "block")
|
||||
}
|
||||
if(matchResult.funcArg){
|
||||
for(let i = 0;i < matchResult.funcArg.length;i++){
|
||||
tempVar[matchResult.funcArg[i]] = passed[i]
|
||||
}
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
if(dat.startsWith('/')){
|
||||
if(stackType[nested.length] === 5){
|
||||
const blockType = blockNestType.get(nested.length)
|
||||
if(blockType.type === 'ignore' || blockType.type === 'pure' || blockType.type === 'each'){
|
||||
pureModeNest.delete(nested.length)
|
||||
pureModeNestType.delete(nested.length)
|
||||
}
|
||||
blockNestType.delete(nested.length)
|
||||
const dat2 = nested.shift()
|
||||
const matchResult = blockEndMatcher(dat2.trim(), blockType, matcherObj)
|
||||
if(blockType.type === 'each'){
|
||||
const subind = blockType.type2.lastIndexOf(' ')
|
||||
const sub = blockType.type2.substring(subind + 1)
|
||||
const array = parseArray(blockType.type2.substring(0, subind))
|
||||
let added = ''
|
||||
for(let i = 0;i < array.length;i++){
|
||||
const res = matchResult.replaceAll(`{{slot::${sub}}}`, array[i])
|
||||
added += res
|
||||
}
|
||||
da = da.substring(0, pointer + 1) + added.trim() + da.substring(pointer + 1)
|
||||
break
|
||||
}
|
||||
if(matchResult === ''){
|
||||
break
|
||||
}
|
||||
nested[0] += matchResult
|
||||
break
|
||||
}
|
||||
if(stackType[nested.length] === 6){
|
||||
const sft = nested.shift()
|
||||
nested[0] += sft + `{{${dat}}}`
|
||||
break
|
||||
}
|
||||
}
|
||||
const mc = isPureMode() ? {
|
||||
text:null,
|
||||
var:tempVar
|
||||
} : (await commandMatcher(dat, matcherObj, tempVar))
|
||||
if(mc && (mc.text || mc.text === '')){
|
||||
tempVar = mc.var
|
||||
if(tempVar['__force_return__']){
|
||||
return tempVar
|
||||
}
|
||||
nested[0] += mc.text ?? `{{${dat}}}`
|
||||
}
|
||||
else{
|
||||
nested[0] += `{{${dat}}}`
|
||||
}
|
||||
break
|
||||
}
|
||||
default:{
|
||||
nested[0] += da[pointer]
|
||||
break
|
||||
}
|
||||
}
|
||||
pointer++
|
||||
}
|
||||
return tempVar
|
||||
|
||||
}
|
||||
|
||||
|
||||
export function getChatVar(key:string){
|
||||
|
||||
@@ -1,91 +0,0 @@
|
||||
import type { OpenAIChat } from ".";
|
||||
|
||||
|
||||
let lastShift = 0
|
||||
|
||||
const cipher:'caesar'|'base64' = 'caesar'
|
||||
|
||||
export function cipherChat(chat: OpenAIChat[]): OpenAIChat[] {
|
||||
|
||||
const shift = Math.floor(Math.random() * 26) + 1
|
||||
lastShift = shift
|
||||
|
||||
for(let i = 0; i < chat.length; i++){
|
||||
chat[i].content = ciphers[cipher].encode(chat[i].content, shift)
|
||||
}
|
||||
|
||||
chat.unshift({
|
||||
content: ciphers[cipher].prompt.replace("${shift}", shift.toString()),
|
||||
role: 'system'
|
||||
})
|
||||
return chat
|
||||
|
||||
}
|
||||
|
||||
|
||||
export function decipherChat(chat: string): string {
|
||||
return ciphers[cipher].decode(chat, lastShift)
|
||||
}
|
||||
|
||||
//Caesar Chiper
|
||||
const caesarCipher = (text: string, shift: number) => {
|
||||
return text
|
||||
.split('')
|
||||
.map(char => {
|
||||
const code = char.charCodeAt(0)
|
||||
if ((code >= 65) && (code <= 90)) {
|
||||
return String.fromCharCode(((code - 65 + shift) % 26) + 65)
|
||||
} else if ((code >= 97) && (code <= 122)) {
|
||||
return String.fromCharCode(((code - 97 + shift) % 26) + 97)
|
||||
} else {
|
||||
return char
|
||||
}
|
||||
})
|
||||
.join('')
|
||||
}
|
||||
|
||||
const caesarDecipher = (text: string, shift: number) => {
|
||||
return text
|
||||
.split('')
|
||||
.map(char => {
|
||||
const code = char.charCodeAt(0)
|
||||
if ((code >= 65) && (code <= 90)) {
|
||||
const shifted = (code - 65 - shift)
|
||||
if(shifted < 0){
|
||||
return String.fromCharCode(((code - 65 - shift + 26) % 26) + 65)
|
||||
}
|
||||
return String.fromCharCode(((code - 65 - shift) % 26) + 65)
|
||||
} else if ((code >= 97) && (code <= 122)) {
|
||||
const shifted = (code - 97 - shift)
|
||||
if(shifted < 0){
|
||||
return String.fromCharCode(((code - 97 - shift + 26) % 26) + 97)
|
||||
}
|
||||
return String.fromCharCode(((code - 97 - shift) % 26) + 97)
|
||||
} else {
|
||||
return char
|
||||
}
|
||||
})
|
||||
.join('')
|
||||
}
|
||||
|
||||
const base64Encode = (text: string) => {
|
||||
return Buffer.from(text).toString('base64')
|
||||
}
|
||||
|
||||
const base64Decode = (text: string) => {
|
||||
return Buffer.from(text, 'base64').toString('ascii')
|
||||
}
|
||||
|
||||
|
||||
const ciphers = {
|
||||
caesar: {
|
||||
encode: caesarCipher,
|
||||
decode: caesarDecipher,
|
||||
prompt: "You are an expert on The Caesar Cipher. We will communicate in Caesar Cipher. Do not be a translator. We are using shift ${shift}"
|
||||
},
|
||||
base64: {
|
||||
encode: base64Encode,
|
||||
decode: base64Decode,
|
||||
prompt: "You are an expert on The Base64 Cipher. We will communicate in Base64. Do not be a translator."
|
||||
}
|
||||
}
|
||||
@@ -17,10 +17,9 @@ import { groupOrder } from "./group";
|
||||
import { runTrigger } from "./triggers";
|
||||
import { HypaProcesser } from "./memory/hypamemory";
|
||||
import { additionalInformations } from "./embedding/addinfo";
|
||||
import { cipherChat, decipherChat } from "./cipherChat";
|
||||
import { getInlayImage, supportsInlayImage } from "./files/image";
|
||||
import { getGenerationModelString } from "./models/modelString";
|
||||
import { sendPeerChar } from "../sync/multiuser";
|
||||
import { connectionOpen, peerRevertChat, peerSafeCheck, peerSync } from "../sync/multiuser";
|
||||
import { runInlayScreen } from "./inlayScreen";
|
||||
import { runCharacterJS } from "../plugins/embedscript";
|
||||
import { addRerolls } from "./prereroll";
|
||||
@@ -56,8 +55,15 @@ export interface OpenAIChatFull extends OpenAIChat{
|
||||
export const doingChat = writable(false)
|
||||
export const chatProcessStage = writable(0)
|
||||
export const abortChat = writable(false)
|
||||
export let previewFormated:OpenAIChat[] = []
|
||||
|
||||
export async function sendChat(chatProcessIndex = -1,arg:{chatAdditonalTokens?:number,signal?:AbortSignal,continue?:boolean,usedContinueTokens?:number} = {}):Promise<boolean> {
|
||||
export async function sendChat(chatProcessIndex = -1,arg:{
|
||||
chatAdditonalTokens?:number,
|
||||
signal?:AbortSignal,
|
||||
continue?:boolean,
|
||||
usedContinueTokens?:number,
|
||||
preview?:boolean
|
||||
} = {}):Promise<boolean> {
|
||||
|
||||
chatProcessStage.set(0)
|
||||
const abortSignal = arg.signal ?? (new AbortController()).signal
|
||||
@@ -101,9 +107,24 @@ export async function sendChat(chatProcessIndex = -1,arg:{chatAdditonalTokens?:n
|
||||
}
|
||||
doingChat.set(true)
|
||||
|
||||
if(connectionOpen){
|
||||
chatProcessStage.set(4)
|
||||
const peerSafe = await peerSafeCheck()
|
||||
if(!peerSafe){
|
||||
peerRevertChat()
|
||||
doingChat.set(false)
|
||||
alertError(language.otherUserRequesting)
|
||||
return false
|
||||
}
|
||||
await peerSync()
|
||||
chatProcessStage.set(0)
|
||||
}
|
||||
|
||||
let db = get(DataBase)
|
||||
db.statics.messages += 1
|
||||
let selectedChar = get(selectedCharID)
|
||||
const nowChatroom = db.characters[selectedChar]
|
||||
nowChatroom.lastInteraction = Date.now()
|
||||
let selectedChat = nowChatroom.chatPage
|
||||
nowChatroom.chats[nowChatroom.chatPage].message = nowChatroom.chats[nowChatroom.chatPage].message.map((v) => {
|
||||
v.chatId = v.chatId ?? v4()
|
||||
@@ -591,7 +612,7 @@ export async function sendChat(chatProcessIndex = -1,arg:{chatAdditonalTokens?:n
|
||||
}
|
||||
|
||||
if(nowChatroom.type !== 'group'){
|
||||
const firstMsg = nowChatroom.firstMsgIndex === -1 ? nowChatroom.firstMessage : nowChatroom.alternateGreetings[nowChatroom.firstMsgIndex]
|
||||
const firstMsg = currentChat.fmIndex === -1 ? nowChatroom.firstMessage : nowChatroom.alternateGreetings[currentChat.fmIndex]
|
||||
|
||||
const chat:OpenAIChat = {
|
||||
role: 'assistant',
|
||||
@@ -623,7 +644,9 @@ export async function sendChat(chatProcessIndex = -1,arg:{chatAdditonalTokens?:n
|
||||
|
||||
let index = 0
|
||||
for(const msg of ms){
|
||||
let formatedChat = await processScript(nowChatroom,risuChatParser(msg.data, {chara: currentChar, role: msg.role}), 'editprocess')
|
||||
let formatedChat = await processScript(nowChatroom,risuChatParser(msg.data, {chara: currentChar, role: msg.role}), 'editprocess', {
|
||||
chatRole: msg.role,
|
||||
})
|
||||
let name = ''
|
||||
if(msg.role === 'char'){
|
||||
if(msg.saying){
|
||||
@@ -676,10 +699,25 @@ export async function sendChat(chatProcessIndex = -1,arg:{chatAdditonalTokens?:n
|
||||
}
|
||||
|
||||
let attr:string[] = []
|
||||
let role:'user'|'assistant'|'system' = msg.role === 'user' ? 'user' : 'assistant'
|
||||
|
||||
if(nowChatroom.type === 'group' || (usingPromptTemplate && db.promptSettings.sendName)){
|
||||
formatedChat = name + ': ' + formatedChat
|
||||
attr.push('nameAdded')
|
||||
if(
|
||||
(nowChatroom.type === 'group' && findCharacterbyIdwithCache(msg.saying).chaId !== currentChar.chaId) ||
|
||||
(nowChatroom.type === 'group' && db.groupOtherBotRole === 'assistant') ||
|
||||
(usingPromptTemplate && db.promptSettings.sendName)
|
||||
){
|
||||
const form = db.groupTemplate || `<{{char}}\'s Message>\n{{slot}}\n</{{char}}\'s Message>`
|
||||
formatedChat = risuChatParser(form, {chara: findCharacterbyIdwithCache(msg.saying).name}).replace('{{slot}}', formatedChat)
|
||||
switch(db.groupOtherBotRole){
|
||||
case 'user':
|
||||
case 'assistant':
|
||||
case 'system':
|
||||
role = db.groupOtherBotRole
|
||||
break
|
||||
default:
|
||||
role = 'assistant'
|
||||
break
|
||||
}
|
||||
}
|
||||
if(usingPromptTemplate && db.promptSettings.maxThoughtTagDepth !== -1){
|
||||
const depth = ms.length - index
|
||||
@@ -689,7 +727,7 @@ export async function sendChat(chatProcessIndex = -1,arg:{chatAdditonalTokens?:n
|
||||
}
|
||||
|
||||
const chat:OpenAIChat = {
|
||||
role: msg.role === 'user' ? 'user' : 'assistant',
|
||||
role: role,
|
||||
content: formatedChat,
|
||||
memo: msg.chatId,
|
||||
attr: attr,
|
||||
@@ -702,6 +740,7 @@ export async function sendChat(chatProcessIndex = -1,arg:{chatAdditonalTokens?:n
|
||||
currentTokens += await tokenizer.tokenizeChat(chat)
|
||||
index++
|
||||
}
|
||||
console.log(JSON.stringify(chats, null, 2))
|
||||
|
||||
const depthPrompts = lorepmt.actives.filter(v => {
|
||||
return (v.pos === 'depth' && v.depth > 0) || v.pos === 'reverse_depth'
|
||||
@@ -779,7 +818,7 @@ export async function sendChat(chatProcessIndex = -1,arg:{chatAdditonalTokens?:n
|
||||
}
|
||||
|
||||
let biases:[string,number][] = db.bias.concat(currentChar.bias).map((v) => {
|
||||
return [risuChatParser(v[0].replaceAll("\\n","\n"), {chara: currentChar}),v[1]]
|
||||
return [risuChatParser(v[0].replaceAll("\\n","\n").replaceAll("\\r","\r").replaceAll("\\\\","\\"), {chara: currentChar}),v[1]]
|
||||
})
|
||||
|
||||
let memories:OpenAIChat[] = []
|
||||
@@ -1027,10 +1066,6 @@ export async function sendChat(chatProcessIndex = -1,arg:{chatAdditonalTokens?:n
|
||||
return v
|
||||
})
|
||||
|
||||
if(db.cipherChat){
|
||||
formated = cipherChat(formated)
|
||||
}
|
||||
|
||||
|
||||
if(currentChar.depth_prompt && currentChar.depth_prompt.prompt && currentChar.depth_prompt.prompt.length > 0){
|
||||
//depth_prompt
|
||||
@@ -1090,6 +1125,11 @@ export async function sendChat(chatProcessIndex = -1,arg:{chatAdditonalTokens?:n
|
||||
maxContext: maxContextTokens,
|
||||
}
|
||||
chatProcessStage.set(3)
|
||||
if(arg.preview){
|
||||
previewFormated = formated
|
||||
return true
|
||||
}
|
||||
|
||||
const req = await requestChatData({
|
||||
formated: formated,
|
||||
biasString: biases,
|
||||
@@ -1140,9 +1180,6 @@ export async function sendChat(chatProcessIndex = -1,arg:{chatAdditonalTokens?:n
|
||||
if(!result){
|
||||
result = ''
|
||||
}
|
||||
if(db.cipherChat){
|
||||
result = decipherChat(result)
|
||||
}
|
||||
if(db.removeIncompleteResponse){
|
||||
result = trimUntilPunctuation(result)
|
||||
}
|
||||
@@ -1181,7 +1218,9 @@ export async function sendChat(chatProcessIndex = -1,arg:{chatAdditonalTokens?:n
|
||||
db.characters[selectedChar].chats[selectedChat] = currentChat
|
||||
setDatabase(db)
|
||||
}
|
||||
await sayTTS(currentChar, result)
|
||||
if(db.ttsAutoSpeech){
|
||||
await sayTTS(currentChar, result)
|
||||
}
|
||||
}
|
||||
else{
|
||||
const msgs = (req.type === 'success') ? [['char',req.result]] as const
|
||||
@@ -1191,9 +1230,6 @@ export async function sendChat(chatProcessIndex = -1,arg:{chatAdditonalTokens?:n
|
||||
for(let i=0;i<msgs.length;i++){
|
||||
let msg = msgs[i]
|
||||
let mess = msg[1]
|
||||
if(db.cipherChat){
|
||||
mess = decipherChat(result)
|
||||
}
|
||||
let msgIndex = db.characters[selectedChar].chats[selectedChat].message.length
|
||||
let result2 = await processScriptFull(nowChatroom, reformatContent(mess), 'editoutput', msgIndex)
|
||||
if(i === 0 && arg.continue){
|
||||
@@ -1240,7 +1276,9 @@ export async function sendChat(chatProcessIndex = -1,arg:{chatAdditonalTokens?:n
|
||||
mrerolls.push(result)
|
||||
}
|
||||
db.characters[selectedChar].reloadKeys += 1
|
||||
await sayTTS(currentChar, result)
|
||||
if(db.ttsAutoSpeech){
|
||||
await sayTTS(currentChar, result)
|
||||
}
|
||||
setDatabase(db)
|
||||
}
|
||||
|
||||
@@ -1291,7 +1329,7 @@ export async function sendChat(chatProcessIndex = -1,arg:{chatAdditonalTokens?:n
|
||||
|
||||
chatProcessStage.set(4)
|
||||
|
||||
sendPeerChar()
|
||||
peerSync()
|
||||
|
||||
if(req.special){
|
||||
if(req.special.emotion){
|
||||
|
||||
@@ -229,12 +229,16 @@ export async function loadLoreBookV3Prompt(){
|
||||
searchDepth:number,
|
||||
regex:boolean
|
||||
fullWordMatching:boolean
|
||||
recursiveAdditionalPrompt:string
|
||||
}) => {
|
||||
const sliced = messages.slice(messages.length - arg.searchDepth,messages.length)
|
||||
arg.keys = arg.keys.map(key => key.trim()).filter(key => key.length > 0)
|
||||
let mText = sliced.map((msg) => {
|
||||
return msg.data
|
||||
}).join('||')
|
||||
if(arg.recursiveAdditionalPrompt){
|
||||
mText += arg.recursiveAdditionalPrompt
|
||||
}
|
||||
if(arg.regex){
|
||||
for(const regexString of arg.keys){
|
||||
if(!regexString.startsWith('/')){
|
||||
@@ -284,11 +288,13 @@ export async function loadLoreBookV3Prompt(){
|
||||
pos:string,
|
||||
prompt:string
|
||||
role:'system'|'user'|'assistant'
|
||||
priority:number
|
||||
order:number
|
||||
tokens:number
|
||||
priority:number
|
||||
}[] = []
|
||||
let activatedIndexes:number[] = []
|
||||
let disabledUIPrompts:string[] = []
|
||||
let matchTimes = 0
|
||||
while(matching){
|
||||
matching = false
|
||||
for(let i=0;i<fullLore.length;i++){
|
||||
@@ -302,6 +308,7 @@ export async function loadLoreBookV3Prompt(){
|
||||
let pos = ''
|
||||
let depth = 0
|
||||
let scanDepth = loreDepth
|
||||
let order = fullLore[i].insertorder
|
||||
let priority = fullLore[i].insertorder
|
||||
let forceState:string = 'none'
|
||||
let role:'system'|'user'|'assistant' = 'system'
|
||||
@@ -376,7 +383,7 @@ export async function loadLoreBookV3Prompt(){
|
||||
if(Number.isNaN(int)){
|
||||
return false
|
||||
}
|
||||
if(((char.firstMsgIndex ?? -1) + 1) !== int){
|
||||
if(((char.chats[page].fmIndex ?? -1) + 1) !== int){
|
||||
activated = false
|
||||
}
|
||||
}
|
||||
@@ -429,6 +436,10 @@ export async function loadLoreBookV3Prompt(){
|
||||
activated = false
|
||||
}
|
||||
}
|
||||
case 'priority':{
|
||||
priority = parseInt(arg[0])
|
||||
return
|
||||
}
|
||||
default:{
|
||||
return false
|
||||
}
|
||||
@@ -450,7 +461,8 @@ export async function loadLoreBookV3Prompt(){
|
||||
keys: query.keys,
|
||||
searchDepth: scanDepth,
|
||||
regex: fullLore[i].useRegex,
|
||||
fullWordMatching: fullWordMatching
|
||||
fullWordMatching: fullWordMatching,
|
||||
recursiveAdditionalPrompt: recursiveAdditionalPrompt
|
||||
})
|
||||
if(query.negative){
|
||||
if(result){
|
||||
@@ -480,8 +492,9 @@ export async function loadLoreBookV3Prompt(){
|
||||
pos: pos,
|
||||
prompt: content,
|
||||
role: role,
|
||||
priority: priority,
|
||||
tokens: await tokenize(content)
|
||||
order: order,
|
||||
tokens: await tokenize(content),
|
||||
priority: priority
|
||||
})
|
||||
activatedIndexes.push(i)
|
||||
if(recursiveScanning){
|
||||
@@ -506,8 +519,12 @@ export async function loadLoreBookV3Prompt(){
|
||||
return false
|
||||
})
|
||||
|
||||
const activesResorted = activesFiltered.sort((a,b) => {
|
||||
return b.order - a.order
|
||||
})
|
||||
|
||||
return {
|
||||
actives: activesFiltered.reverse()
|
||||
actives: activesResorted.reverse()
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import { getChatVar, risuChatParser, setChatVar, type simpleCharacterArgument } from "../parser";
|
||||
import { getChatVar, hasher, risuChatParser, setChatVar, type simpleCharacterArgument } from "../parser";
|
||||
import { LuaEngine, LuaFactory } from "wasmoon";
|
||||
import { DataBase, setDatabase, type Chat, type character, type groupChat } from "../storage/database";
|
||||
import { get } from "svelte/store";
|
||||
import { CurrentCharacter, CurrentChat, CurrentVariablePointer, ReloadGUIPointer, selectedCharID } from "../stores";
|
||||
import { CurrentCharacter, CurrentChat, ReloadGUIPointer, selectedCharID } from "../stores";
|
||||
import { alertError, alertInput, alertNormal } from "../alert";
|
||||
import { HypaProcesser } from "./memory/hypamemory";
|
||||
import { generateAIImage } from "./stableDiff";
|
||||
@@ -104,6 +104,7 @@ export async function runLua(code:string, arg:{
|
||||
if(!LuaSafeIds.has(id)){
|
||||
return
|
||||
}
|
||||
chat = get(CurrentChat)
|
||||
const message = chat.message?.at(index)
|
||||
if(message){
|
||||
message.data = value
|
||||
@@ -114,6 +115,7 @@ export async function runLua(code:string, arg:{
|
||||
if(!LuaSafeIds.has(id)){
|
||||
return
|
||||
}
|
||||
chat = get(CurrentChat)
|
||||
const message = chat.message?.at(index)
|
||||
if(message){
|
||||
message.role = value === 'user' ? 'user' : 'char'
|
||||
@@ -124,6 +126,7 @@ export async function runLua(code:string, arg:{
|
||||
if(!LuaSafeIds.has(id)){
|
||||
return
|
||||
}
|
||||
chat = get(CurrentChat)
|
||||
chat.message = chat.message.slice(start,end)
|
||||
CurrentChat.set(chat)
|
||||
})
|
||||
@@ -131,6 +134,7 @@ export async function runLua(code:string, arg:{
|
||||
if(!LuaSafeIds.has(id)){
|
||||
return
|
||||
}
|
||||
chat = get(CurrentChat)
|
||||
chat.message.splice(index, 1)
|
||||
CurrentChat.set(chat)
|
||||
})
|
||||
@@ -138,6 +142,7 @@ export async function runLua(code:string, arg:{
|
||||
if(!LuaSafeIds.has(id)){
|
||||
return
|
||||
}
|
||||
chat = get(CurrentChat)
|
||||
let roleData:'user'|'char' = role === 'user' ? 'user' : 'char'
|
||||
chat.message.push({role: roleData, data: value})
|
||||
CurrentChat.set(chat)
|
||||
@@ -146,6 +151,7 @@ export async function runLua(code:string, arg:{
|
||||
if(!LuaSafeIds.has(id)){
|
||||
return
|
||||
}
|
||||
chat = get(CurrentChat)
|
||||
let roleData:'user'|'char' = role === 'user' ? 'user' : 'char'
|
||||
chat.message.splice(index, 0, {role: roleData, data: value})
|
||||
CurrentChat.set(chat)
|
||||
@@ -154,6 +160,7 @@ export async function runLua(code:string, arg:{
|
||||
if(!LuaSafeIds.has(id)){
|
||||
return
|
||||
}
|
||||
chat = get(CurrentChat)
|
||||
chat.message.splice(index, 1)
|
||||
CurrentChat.set(chat)
|
||||
})
|
||||
@@ -161,9 +168,11 @@ export async function runLua(code:string, arg:{
|
||||
if(!LuaSafeIds.has(id)){
|
||||
return
|
||||
}
|
||||
chat = get(CurrentChat)
|
||||
return chat.message.length
|
||||
})
|
||||
luaEngine.global.set('getFullChatMain', (id:string) => {
|
||||
chat = get(CurrentChat)
|
||||
const data = JSON.stringify(chat.message.map((v) => {
|
||||
return {
|
||||
role: v.role,
|
||||
@@ -178,6 +187,7 @@ export async function runLua(code:string, arg:{
|
||||
if(!LuaSafeIds.has(id)){
|
||||
return
|
||||
}
|
||||
chat = get(CurrentChat)
|
||||
chat.message = realValue.map((v) => {
|
||||
return {
|
||||
role: v.role,
|
||||
@@ -222,6 +232,10 @@ export async function runLua(code:string, arg:{
|
||||
return `{{inlay::${inlay}}}`
|
||||
})
|
||||
|
||||
luaEngine.global.set('hash', async (id:string, value:string) => {
|
||||
return await hasher(new TextEncoder().encode(value))
|
||||
})
|
||||
|
||||
luaEngine.global.set('LLMMain', async (id:string, promptStr:string) => {
|
||||
let prompt:{
|
||||
role: string,
|
||||
|
||||
@@ -1,12 +1,11 @@
|
||||
import { invoke } from "@tauri-apps/api/tauri";
|
||||
import { invoke } from "@tauri-apps/api/core";
|
||||
import { globalFetch } from "src/ts/storage/globalApi";
|
||||
import { sleep } from "src/ts/util";
|
||||
import * as path from "@tauri-apps/api/path";
|
||||
import { exists, readTextFile } from "@tauri-apps/api/fs";
|
||||
import { exists, readTextFile } from "@tauri-apps/plugin-fs";
|
||||
import { alertClear, alertError, alertMd, alertWait } from "src/ts/alert";
|
||||
import { get } from "svelte/store";
|
||||
import { DataBase } from "src/ts/storage/database";
|
||||
import { resolveResource } from '@tauri-apps/api/path'
|
||||
let serverRunning = false;
|
||||
|
||||
export function checkLocalModel():Promise<string>{
|
||||
|
||||
@@ -1,12 +1,16 @@
|
||||
import { language } from "src/lang"
|
||||
import { alertConfirm, alertError, alertModuleSelect, alertNormal } from "../alert"
|
||||
import { alertConfirm, alertError, alertModuleSelect, alertNormal, alertStore } from "../alert"
|
||||
import { DataBase, setDatabase, type customscript, type loreBook, type triggerscript } from "../storage/database"
|
||||
import { downloadFile } from "../storage/globalApi"
|
||||
import { AppendableBuffer, downloadFile, isNodeServer, isTauri, readImage, saveAsset } from "../storage/globalApi"
|
||||
import { get } from "svelte/store"
|
||||
import { CurrentCharacter, CurrentChat } from "../stores"
|
||||
import { selectSingleFile } from "../util"
|
||||
import { selectSingleFile, sleep } from "../util"
|
||||
import { v4 } from "uuid"
|
||||
import { convertExternalLorebook } from "./lorebook"
|
||||
import { encode } from "msgpackr"
|
||||
import { decodeRPack, encodeRPack } from "../rpack/rpack_bg"
|
||||
import { convertImage } from "../parser"
|
||||
import { Capacitor } from "@capacitor/core"
|
||||
|
||||
export interface RisuModule{
|
||||
name: string
|
||||
@@ -19,25 +23,171 @@ export interface RisuModule{
|
||||
lowLevelAccess?: boolean
|
||||
hideIcon?: boolean
|
||||
backgroundEmbedding?:string
|
||||
assets?:[string,string,string][]
|
||||
namespace?:string
|
||||
}
|
||||
|
||||
export async function exportModule(module:RisuModule){
|
||||
await downloadFile(module.name + '.json', JSON.stringify({
|
||||
...module,
|
||||
export async function exportModule(module:RisuModule, arg:{
|
||||
alertEnd?:boolean
|
||||
saveData?:boolean
|
||||
} = {}){
|
||||
const alertEnd = arg.alertEnd ?? true
|
||||
const saveData = arg.saveData ?? true
|
||||
const apb = new AppendableBuffer()
|
||||
const writeLength = (len:number) => {
|
||||
const lenbuf = Buffer.alloc(4)
|
||||
lenbuf.writeUInt32LE(len, 0)
|
||||
apb.append(lenbuf)
|
||||
}
|
||||
const writeByte = (byte:number) => {
|
||||
//byte is 0-255
|
||||
const buf = Buffer.alloc(1)
|
||||
buf.writeUInt8(byte, 0)
|
||||
apb.append(buf)
|
||||
}
|
||||
|
||||
const assets = module.assets ?? []
|
||||
module = structuredClone(module)
|
||||
module.assets ??= []
|
||||
module.assets = module.assets.map((asset) => {
|
||||
return [asset[0], '', asset[2]] as [string,string,string]
|
||||
})
|
||||
|
||||
const mainbuf = await encodeRPack(Buffer.from(JSON.stringify({
|
||||
module: module,
|
||||
type: 'risuModule'
|
||||
}, null, 2))
|
||||
alertNormal(language.successExport)
|
||||
}, null, 2), 'utf-8'))
|
||||
|
||||
writeByte(111) //magic number
|
||||
writeByte(0) //version
|
||||
writeLength(mainbuf.length)
|
||||
apb.append(mainbuf)
|
||||
|
||||
for(let i=0;i<assets.length;i++){
|
||||
const asset = assets[i]
|
||||
writeByte(1) //mark as asset
|
||||
alertStore.set({
|
||||
type: 'wait',
|
||||
msg: `Loading... (Adding Assets ${i} / ${assets.length})`
|
||||
})
|
||||
let rData = await readImage(asset[1])
|
||||
if(!rData){
|
||||
rData = new Uint8Array(0) //blank buffer
|
||||
}
|
||||
let encoded = await encodeRPack(Buffer.from(await convertImage(rData)))
|
||||
writeLength(encoded.length)
|
||||
apb.append(encoded)
|
||||
}
|
||||
|
||||
writeByte(0) //end of file
|
||||
|
||||
if(saveData){
|
||||
await downloadFile(module.name + '.risum', apb.buffer)
|
||||
}
|
||||
if(alertEnd){
|
||||
alertNormal(language.successExport)
|
||||
}
|
||||
|
||||
return apb.buffer
|
||||
}
|
||||
|
||||
export async function readModule(buf:Buffer):Promise<RisuModule> {
|
||||
let pos = 0
|
||||
|
||||
const readLength = () => {
|
||||
const len = buf.readUInt32LE(pos)
|
||||
pos += 4
|
||||
return len
|
||||
}
|
||||
const readByte = () => {
|
||||
const byte = buf.readUInt8(pos)
|
||||
pos += 1
|
||||
return byte
|
||||
}
|
||||
const readData = (len:number) => {
|
||||
const data = buf.subarray(pos, pos + len)
|
||||
pos += len
|
||||
return data
|
||||
}
|
||||
|
||||
if(readByte() !== 111){
|
||||
console.error("Invalid magic number")
|
||||
alertError(language.errors.noData)
|
||||
return
|
||||
}
|
||||
if(readByte() !== 0){ //Version check
|
||||
console.error("Invalid version")
|
||||
alertError(language.errors.noData)
|
||||
return
|
||||
}
|
||||
|
||||
const mainLen = readLength()
|
||||
const mainData = readData(mainLen)
|
||||
const main:{
|
||||
type:'risuModule'
|
||||
module:RisuModule
|
||||
} = JSON.parse(Buffer.from(await decodeRPack(mainData)).toString())
|
||||
|
||||
if(main.type !== 'risuModule'){
|
||||
console.error("Invalid module type")
|
||||
alertError(language.errors.noData)
|
||||
return
|
||||
}
|
||||
|
||||
let module = main.module
|
||||
|
||||
let i = 0
|
||||
while(true){
|
||||
const mark = readByte()
|
||||
if(mark === 0){
|
||||
break
|
||||
}
|
||||
if(mark !== 1){
|
||||
alertError(language.errors.noData)
|
||||
return
|
||||
}
|
||||
const len = readLength()
|
||||
const data = readData(len)
|
||||
module.assets[i][1] = await saveAsset(Buffer.from(await decodeRPack(data)))
|
||||
alertStore.set({
|
||||
type: 'wait',
|
||||
msg: `Loading... (Adding Assets ${i} / ${module.assets.length})`
|
||||
})
|
||||
if(!isTauri && !Capacitor.isNativePlatform() &&!isNodeServer){
|
||||
await sleep(100)
|
||||
}
|
||||
i++
|
||||
}
|
||||
alertStore.set({
|
||||
type: 'none',
|
||||
msg: ''
|
||||
})
|
||||
|
||||
module.id = v4()
|
||||
return module
|
||||
}
|
||||
|
||||
export async function importModule(){
|
||||
const f = await selectSingleFile(['json', 'lorebook'])
|
||||
const f = await selectSingleFile(['json', 'lorebook', 'risum'])
|
||||
if(!f){
|
||||
return
|
||||
}
|
||||
const file = f.data
|
||||
let fileData = f.data
|
||||
const db = get(DataBase)
|
||||
if(f.name.endsWith('.risum')){
|
||||
try {
|
||||
const buf = Buffer.from(fileData)
|
||||
const module = await readModule(buf)
|
||||
db.modules.push(module)
|
||||
setDatabase(db)
|
||||
return
|
||||
} catch (error) {
|
||||
console.error(error)
|
||||
alertError(language.errors.noData)
|
||||
}
|
||||
}
|
||||
try {
|
||||
const importData = JSON.parse(Buffer.from(file).toString())
|
||||
const importData = JSON.parse(Buffer.from(fileData).toString())
|
||||
if(importData.type === 'risuModule'){
|
||||
if(
|
||||
(!importData.name)
|
||||
@@ -109,19 +259,37 @@ function getModuleById(id:string){
|
||||
return null
|
||||
}
|
||||
|
||||
function getModuleByIds(ids:string[]){
|
||||
let modules:RisuModule[] = []
|
||||
const db = get(DataBase)
|
||||
for(let i=0;i<ids.length;i++){
|
||||
const module = db.modules.find((m) => m.id === ids[i] || (m.namespace === ids[i] && m.namespace))
|
||||
if(module){
|
||||
modules.push(module)
|
||||
}
|
||||
}
|
||||
return modules
|
||||
}
|
||||
|
||||
let lastModules = ''
|
||||
let lastModuleData:RisuModule[] = []
|
||||
export function getModules(ids:string[]){
|
||||
export function getModules(){
|
||||
const currentChat = get(CurrentChat)
|
||||
const db = get(DataBase)
|
||||
let ids = db.enabledModules ?? []
|
||||
if (currentChat){
|
||||
ids = ids.concat(currentChat.modules ?? [])
|
||||
}
|
||||
if(db.moduleIntergration){
|
||||
const intList = db.moduleIntergration.split(',').map((s) => s.trim())
|
||||
ids = ids.concat(intList)
|
||||
}
|
||||
const idsJoined = ids.join('-')
|
||||
if(lastModules === idsJoined){
|
||||
return lastModuleData
|
||||
}
|
||||
|
||||
let modules:RisuModule[] = []
|
||||
for(const id of ids){
|
||||
const module = getModuleById(id)
|
||||
modules.push(module)
|
||||
}
|
||||
let modules:RisuModule[] = getModuleByIds(ids)
|
||||
lastModules = idsJoined
|
||||
lastModuleData = modules
|
||||
return modules
|
||||
@@ -130,12 +298,7 @@ export function getModules(ids:string[]){
|
||||
|
||||
|
||||
export function getModuleLorebooks() {
|
||||
const currentChat = get(CurrentChat)
|
||||
const db = get(DataBase)
|
||||
if (!currentChat) return []
|
||||
let moduleIds = currentChat.modules ?? []
|
||||
moduleIds = moduleIds.concat(db.enabledModules)
|
||||
const modules = getModules(moduleIds)
|
||||
const modules = getModules()
|
||||
let lorebooks: loreBook[] = []
|
||||
for (const module of modules) {
|
||||
if(!module){
|
||||
@@ -148,14 +311,23 @@ export function getModuleLorebooks() {
|
||||
return lorebooks
|
||||
}
|
||||
|
||||
export function getModuleAssets() {
|
||||
const modules = getModules()
|
||||
let assets: [string,string,string][] = []
|
||||
for (const module of modules) {
|
||||
if(!module){
|
||||
continue
|
||||
}
|
||||
if (module.assets) {
|
||||
assets = assets.concat(module.assets)
|
||||
}
|
||||
}
|
||||
return assets
|
||||
}
|
||||
|
||||
|
||||
export function getModuleTriggers() {
|
||||
const currentChat = get(CurrentChat)
|
||||
const db = get(DataBase)
|
||||
if (!currentChat) return []
|
||||
let moduleIds = currentChat.modules ?? []
|
||||
moduleIds = moduleIds.concat(db.enabledModules)
|
||||
const modules = getModules(moduleIds)
|
||||
const modules = getModules()
|
||||
let triggers: triggerscript[] = []
|
||||
for (const module of modules) {
|
||||
if(!module){
|
||||
@@ -172,12 +344,7 @@ export function getModuleTriggers() {
|
||||
}
|
||||
|
||||
export function getModuleRegexScripts() {
|
||||
const currentChat = get(CurrentChat)
|
||||
const db = get(DataBase)
|
||||
if (!currentChat) return []
|
||||
let moduleIds = currentChat.modules ?? []
|
||||
moduleIds = moduleIds.concat(db.enabledModules)
|
||||
const modules = getModules(moduleIds)
|
||||
const modules = getModules()
|
||||
let customscripts: customscript[] = []
|
||||
for (const module of modules) {
|
||||
if(!module){
|
||||
|
||||
@@ -81,6 +81,7 @@ export class CharXReader{
|
||||
assetPromises:Promise<void>[] = []
|
||||
excludedFiles:string[] = []
|
||||
cardData:string|undefined
|
||||
moduleData:Uint8Array|undefined
|
||||
constructor(){
|
||||
this.unzip = new fflate.Unzip()
|
||||
this.unzip.register(fflate.UnzipInflate)
|
||||
@@ -98,6 +99,9 @@ export class CharXReader{
|
||||
else if(file.name === 'card.json'){
|
||||
this.cardData = new TextDecoder().decode(assetData)
|
||||
}
|
||||
else if(file.name === 'module.risum'){
|
||||
this.moduleData = assetData
|
||||
}
|
||||
else{
|
||||
this.assetPromises.push((async () => {
|
||||
const assetId = await saveAsset(assetData)
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
import { get } from "svelte/store";
|
||||
import { tokenizeAccurate } from "../tokenizer";
|
||||
import type { Database } from "../storage/database";
|
||||
import { DataBase, presetTemplate, setDatabase, type Database } from "../storage/database";
|
||||
import { alertError, alertNormal } from "../alert";
|
||||
import type { OobaChatCompletionRequestParams } from "../model/ooba";
|
||||
|
||||
export type PromptItem = PromptItemPlain|PromptItemTyped|PromptItemChat|PromptItemAuthorNote;
|
||||
export type PromptType = PromptItem['type'];
|
||||
@@ -64,3 +66,404 @@ export async function tokenizePreset(prompts:PromptItem[], consti:boolean = fals
|
||||
}
|
||||
return total
|
||||
}
|
||||
|
||||
export function detectPromptJSONType(text:string){
|
||||
|
||||
function notNull<T>(x:T|null):x is T{
|
||||
return x !== null && x !== undefined
|
||||
}
|
||||
|
||||
try {
|
||||
const parsed = JSON.parse(text)
|
||||
if(notNull(parsed.chat_completion_source) && Array.isArray(parsed.prompts)&& Array.isArray(parsed.prompt_order)){
|
||||
return "STCHAT"
|
||||
}
|
||||
else if(notNull(parsed.temp) && notNull(parsed.rep_pen) && notNull(parsed.min_length)){
|
||||
return "PARAMETERS"
|
||||
}
|
||||
else if(notNull(parsed.story_string) && notNull(parsed.chat_start)){
|
||||
return "STCONTEXT"
|
||||
}
|
||||
else if(notNull(parsed.input_sequence) && notNull(parsed.output_sequence)){
|
||||
return "STINST"
|
||||
}
|
||||
} catch (e) {}
|
||||
return 'NOTSUPPORTED'
|
||||
}
|
||||
|
||||
const typePriority = [
|
||||
'STINST',
|
||||
'PARAMETERS',
|
||||
'STCONTEXT',
|
||||
'STCHAT',
|
||||
]
|
||||
|
||||
|
||||
type InstData = {
|
||||
"system_prompt": string,
|
||||
"input_sequence": string,
|
||||
"output_sequence": string,
|
||||
"last_output_sequence": string,
|
||||
"system_sequence": string,
|
||||
"stop_sequence": string,
|
||||
"system_sequence_prefix": string,
|
||||
"system_sequence_suffix": string,
|
||||
"first_output_sequence": string,
|
||||
"output_suffix": string,
|
||||
"input_suffix": string,
|
||||
"system_suffix": string,
|
||||
"user_alignment_message": string,
|
||||
"system_same_as_user": boolean,
|
||||
"last_system_sequence": string,
|
||||
"first_input_sequence": string,
|
||||
"last_input_sequence": string,
|
||||
"name": string
|
||||
}
|
||||
|
||||
export function stChatConvert(pre:any){
|
||||
//ST preset
|
||||
let promptTemplate = []
|
||||
|
||||
function findPrompt(identifier:number){
|
||||
return pre.prompts.find((p:any) => p.identifier === identifier)
|
||||
}
|
||||
|
||||
for(const prompt of pre?.prompt_order?.[0]?.order){
|
||||
if(!prompt?.enabled){
|
||||
continue
|
||||
}
|
||||
const p = findPrompt(prompt?.identifier ?? '')
|
||||
if(p){
|
||||
switch(p.identifier){
|
||||
case 'main':{
|
||||
promptTemplate.push({
|
||||
type: 'plain',
|
||||
type2: 'main',
|
||||
text: p.content ?? "",
|
||||
role: p.role ?? "system"
|
||||
})
|
||||
break
|
||||
}
|
||||
case 'jailbreak':
|
||||
case 'nsfw':{
|
||||
promptTemplate.push({
|
||||
type: 'jailbreak',
|
||||
type2: 'normal',
|
||||
text: p.content ?? "",
|
||||
role: p.role ?? "system"
|
||||
})
|
||||
break
|
||||
}
|
||||
case 'dialogueExamples':
|
||||
case 'charPersonality':
|
||||
case 'scenario':{
|
||||
break //ignore
|
||||
}
|
||||
case 'chatHistory':{
|
||||
promptTemplate.push({
|
||||
type: 'chat',
|
||||
rangeEnd: 'end',
|
||||
rangeStart: 0
|
||||
})
|
||||
break
|
||||
}
|
||||
case 'worldInfoBefore':{
|
||||
promptTemplate.push({
|
||||
type: 'lorebook'
|
||||
})
|
||||
break
|
||||
}
|
||||
case 'worldInfoAfter':{
|
||||
break
|
||||
}
|
||||
case 'charDescription':{
|
||||
promptTemplate.push({
|
||||
type: 'description'
|
||||
})
|
||||
break
|
||||
}
|
||||
case 'personaDescription':{
|
||||
promptTemplate.push({
|
||||
type: 'persona'
|
||||
})
|
||||
break
|
||||
}
|
||||
default:{
|
||||
console.log(p)
|
||||
promptTemplate.push({
|
||||
type: 'plain',
|
||||
type2: 'normal',
|
||||
text: p.content ?? "",
|
||||
role: p.role ?? "system"
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
else{
|
||||
console.log("Prompt not found", prompt)
|
||||
|
||||
}
|
||||
}
|
||||
if(pre?.assistant_prefill){
|
||||
promptTemplate.push({
|
||||
type: 'postEverything'
|
||||
})
|
||||
promptTemplate.push({
|
||||
type: 'plain',
|
||||
type2: 'main',
|
||||
text: `{{#if {{prefill_supported}}}}${pre?.assistant_prefill}{{/if}}`,
|
||||
role: 'bot'
|
||||
})
|
||||
}
|
||||
|
||||
return promptTemplate
|
||||
}
|
||||
|
||||
export const OobaParams = [
|
||||
"tokenizer",
|
||||
"min_p",
|
||||
"top_k",
|
||||
"repetition_penalty",
|
||||
"repetition_penalty_range",
|
||||
"typical_p",
|
||||
"tfs",
|
||||
"top_a",
|
||||
"epsilon_cutoff",
|
||||
"eta_cutoff",
|
||||
"guidance_scale",
|
||||
"negative_prompt",
|
||||
"penalty_alpha",
|
||||
"mirostat_mode",
|
||||
"mirostat_tau",
|
||||
"mirostat_eta",
|
||||
"temperature_last",
|
||||
"do_sample",
|
||||
"seed",
|
||||
"encoder_repetition_penalty",
|
||||
"no_repeat_ngram_size",
|
||||
"min_length",
|
||||
"num_beams",
|
||||
"length_penalty",
|
||||
"early_stopping",
|
||||
"truncation_length",
|
||||
"max_tokens_second",
|
||||
"custom_token_bans",
|
||||
"auto_max_new_tokens",
|
||||
"ban_eos_token",
|
||||
"add_bos_token",
|
||||
"skip_special_tokens",
|
||||
"grammar_string"
|
||||
]
|
||||
|
||||
export function promptConvertion(files:{ name: string, content: string, type:string }[]){
|
||||
let preset = structuredClone(presetTemplate)
|
||||
let instData = {
|
||||
"system_prompt": "",
|
||||
"input_sequence": "",
|
||||
"output_sequence": "",
|
||||
"last_output_sequence": "",
|
||||
"system_sequence": "",
|
||||
"stop_sequence": "",
|
||||
"system_sequence_prefix": "",
|
||||
"system_sequence_suffix": "",
|
||||
"first_output_sequence": "",
|
||||
"output_suffix": "",
|
||||
"input_suffix": "",
|
||||
"system_suffix": "",
|
||||
"user_alignment_message": "",
|
||||
"system_same_as_user": false,
|
||||
"last_system_sequence": "",
|
||||
"first_input_sequence": "",
|
||||
"last_input_sequence": "",
|
||||
"name": ""
|
||||
}
|
||||
let story_string = ''
|
||||
let chat_start = ''
|
||||
preset.name = ''
|
||||
|
||||
let type = ''
|
||||
|
||||
files = files.filter(x=>x.type !== 'NOTSUPPORTED').sort((a,b)=>{
|
||||
return typePriority.indexOf(a.type) - typePriority.indexOf(b.type)
|
||||
})
|
||||
|
||||
|
||||
if(files.findIndex(x=>x.type === 'STINST') !== -1){
|
||||
type = 'STINST'
|
||||
}
|
||||
if(files.findIndex(x=>x.type === 'STCHAT') !== -1){
|
||||
if(type !== ''){
|
||||
alertError(`Both ${type} and STCHAT are not supported together.`)
|
||||
return
|
||||
}
|
||||
type = 'STCHAT'
|
||||
}
|
||||
|
||||
let samplers:string[] = []
|
||||
|
||||
let oobaData:OobaChatCompletionRequestParams = {
|
||||
mode: 'instruct',
|
||||
}
|
||||
|
||||
|
||||
for(let i=0;i<files.length;i++){
|
||||
const file = files[i]
|
||||
const data = JSON.parse(file.content)
|
||||
|
||||
const getParam = (setname:keyof(typeof preset), getname:string = '', arg:{
|
||||
multiplier?: number
|
||||
}={}) => {
|
||||
if(getname === ''){
|
||||
getname = setname
|
||||
}
|
||||
let multiplier = arg.multiplier ?? 1
|
||||
if(samplers.includes(getname)){
|
||||
//@ts-ignore
|
||||
preset[setname] = data[getname] * multiplier
|
||||
}
|
||||
else{
|
||||
// @ts-ignore
|
||||
preset[setname] = -1000
|
||||
}
|
||||
|
||||
if(OobaParams.includes(getname)){
|
||||
oobaData[getname] = data[getname]
|
||||
}
|
||||
}
|
||||
|
||||
preset.name ||= instData.name ?? ''
|
||||
switch(file.type){
|
||||
case 'STINST':{
|
||||
instData = data as InstData
|
||||
if(data.system_same_as_user){
|
||||
instData.system_sequence = ''
|
||||
instData.system_sequence_prefix = instData.input_sequence
|
||||
instData.system_sequence_suffix = instData.output_sequence
|
||||
}
|
||||
break
|
||||
}
|
||||
case 'PARAMETERS':{
|
||||
samplers = data.samplers
|
||||
getParam('temperature', 'temp', {multiplier: 100})
|
||||
getParam('top_p')
|
||||
getParam('top_k')
|
||||
getParam('top_a')
|
||||
getParam('min_p')
|
||||
getParam('repetition_penalty', 'rep_pen')
|
||||
getParam('frequencyPenalty', 'freq_pen', {multiplier: 100})
|
||||
getParam('PresensePenalty', 'presence_penalty', {multiplier: 100})
|
||||
for(const key of OobaParams){
|
||||
if(samplers.includes(key) && (data[key] !== undefined) && (data[key] !== null)){
|
||||
oobaData[key] = data[key]
|
||||
}
|
||||
}
|
||||
break
|
||||
}
|
||||
case 'STCONTEXT':{
|
||||
story_string = data.story_string
|
||||
chat_start = data.chat_start
|
||||
break
|
||||
}
|
||||
case 'STCHAT':{
|
||||
samplers = []
|
||||
getParam('temperature', 'temperature', {multiplier: 100})
|
||||
getParam('top_p')
|
||||
getParam('top_k')
|
||||
getParam('top_a')
|
||||
getParam('min_p')
|
||||
getParam('repetition_penalty', 'repetition_penalty')
|
||||
getParam('frequencyPenalty', 'frequency_penalty', {multiplier: 100})
|
||||
getParam('PresensePenalty', 'presence_penalty', {multiplier: 100})
|
||||
const prompts = stChatConvert(data)
|
||||
preset.promptTemplate = prompts
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if(type === 'STCHAT'){
|
||||
preset.aiModel = 'openrouter'
|
||||
preset.subModel = 'openrouter'
|
||||
const db = get(DataBase)
|
||||
db.botPresets.push(preset)
|
||||
setDatabase(db)
|
||||
|
||||
alertNormal('Preset converted successfully. You can find it in bot setting presets')
|
||||
return
|
||||
}
|
||||
|
||||
preset.reverseProxyOobaArgs = oobaData
|
||||
|
||||
preset.promptTemplate = [{
|
||||
type: 'plain',
|
||||
type2: 'main',
|
||||
text: '',
|
||||
role: 'system'
|
||||
},{
|
||||
type: 'description',
|
||||
},{
|
||||
type: 'persona',
|
||||
},{
|
||||
type: 'lorebook',
|
||||
},{
|
||||
type: 'chat',
|
||||
rangeStart: 0,
|
||||
rangeEnd: 'end',
|
||||
}, {
|
||||
type: 'authornote',
|
||||
}, {
|
||||
type: 'plain',
|
||||
type2: 'globalNote',
|
||||
text: '',
|
||||
role: 'system'
|
||||
}]
|
||||
|
||||
|
||||
|
||||
//build a jinja template from the instData
|
||||
let jinja = ''
|
||||
|
||||
jinja += story_string
|
||||
.replace(/{{user}}/gi, '{{risu_user}}')
|
||||
.replace(/{{user}}/gi, '{{risu_user}}')
|
||||
.replace(/{{system_prompt}}/gi, instData.system_prompt)
|
||||
.replace(/{{system}}/gi, instData.system_prompt)
|
||||
.replace(/{{#if (.+?){{\/if}}/gis, '')
|
||||
.replace(/{{(.+?)}}/gi, '')
|
||||
.replace(/\n\n+/g, '\n\n')
|
||||
jinja += chat_start
|
||||
jinja += `{% for message in messages %}`
|
||||
jinja += `{% if message.role == 'user' %}`
|
||||
jinja += instData.input_sequence
|
||||
jinja += `{{ message.content }}`
|
||||
jinja += instData.input_suffix
|
||||
jinja += `{% endif %}`
|
||||
jinja += `{% if message.role == 'assistant' %}`
|
||||
jinja += instData.output_sequence
|
||||
jinja += `{{ message.content }}`
|
||||
jinja += instData.output_suffix
|
||||
jinja += `{% endif %}`
|
||||
jinja += `{% if message.role == 'system' %}`
|
||||
jinja += instData.system_sequence
|
||||
jinja += instData.system_sequence_prefix
|
||||
jinja += `{{ message.content }}`
|
||||
jinja += instData.system_sequence_suffix
|
||||
jinja += instData.system_suffix
|
||||
jinja += `{% endif %}`
|
||||
jinja += `{% endfor %}`
|
||||
jinja += instData.output_sequence
|
||||
|
||||
preset.instructChatTemplate = "jinja"
|
||||
preset.JinjaTemplate = jinja
|
||||
preset.aiModel = 'openrouter'
|
||||
preset.subModel = 'openrouter'
|
||||
preset.useInstructPrompt = true
|
||||
|
||||
preset.name ||= 'Converted from JSON'
|
||||
|
||||
|
||||
const db = get(DataBase)
|
||||
db.botPresets.push(preset)
|
||||
setDatabase(db)
|
||||
|
||||
alertNormal('Preset converted successfully. You can find it in bot setting presets')
|
||||
}
|
||||
@@ -21,6 +21,8 @@ import { runTransformers } from "./transformers";
|
||||
import {createParser} from 'eventsource-parser'
|
||||
import {Ollama} from 'ollama/dist/browser.mjs'
|
||||
import { applyChatTemplate } from "./templates/chatTemplate";
|
||||
import { OobaParams } from "./prompt";
|
||||
import { extractJSON, getOpenAIJSONSchema } from "./templates/jsonSchema";
|
||||
|
||||
|
||||
|
||||
@@ -79,6 +81,60 @@ interface OaiFunctions {
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
type Parameter = 'temperature'|'top_k'|'repetition_penalty'|'min_p'|'top_a'|'top_p'|'frequency_penalty'|'presence_penalty'
|
||||
type ParameterMap = {
|
||||
[key in Parameter]?: string;
|
||||
};
|
||||
|
||||
function applyParameters(data: { [key: string]: any }, parameters: Parameter[], rename: ParameterMap = {}) {
|
||||
const db = get(DataBase)
|
||||
for(const parameter of parameters){
|
||||
let value = 0
|
||||
switch(parameter){
|
||||
case 'temperature':{
|
||||
value = db.temperature === -1000 ? -1000 : (db.temperature / 100)
|
||||
break
|
||||
}
|
||||
case 'top_k':{
|
||||
value = db.top_k
|
||||
break
|
||||
}
|
||||
case 'repetition_penalty':{
|
||||
value = db.repetition_penalty
|
||||
break
|
||||
}
|
||||
case 'min_p':{
|
||||
value = db.min_p
|
||||
break
|
||||
}
|
||||
case 'top_a':{
|
||||
value = db.top_a
|
||||
break
|
||||
}
|
||||
case 'top_p':{
|
||||
value = db.top_p
|
||||
break
|
||||
}
|
||||
case 'frequency_penalty':{
|
||||
value = db.frequencyPenalty === -1000 ? -1000 : (db.frequencyPenalty / 100)
|
||||
break
|
||||
}
|
||||
case 'presence_penalty':{
|
||||
value = db.PresensePenalty === -1000 ? -1000 : (db.PresensePenalty / 100)
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if(value === -1000){
|
||||
continue
|
||||
}
|
||||
|
||||
data[rename[parameter] ?? parameter] = value
|
||||
}
|
||||
return data
|
||||
}
|
||||
|
||||
export async function requestChatData(arg:requestDataArgument, model:'model'|'submodel', abortSignal:AbortSignal=null):Promise<requestDataResponse> {
|
||||
const db = get(DataBase)
|
||||
let trys = 0
|
||||
@@ -131,7 +187,7 @@ export async function requestChatDataMain(arg:requestDataArgument, model:'model'
|
||||
let useStreaming = db.useStreaming && arg.useStreaming
|
||||
arg.continue = arg.continue ?? false
|
||||
let biasString = arg.biasString ?? []
|
||||
const aiModel = (model === 'model' || (!db.advancedBotSettings)) ? db.aiModel : db.subModel
|
||||
const aiModel = model === 'model' ? db.aiModel : db.subModel
|
||||
const multiGen = (db.genTime > 1 && aiModel.startsWith('gpt') && (!arg.continue)) && (!arg.noMultiGen)
|
||||
|
||||
let raiModel = aiModel
|
||||
@@ -180,6 +236,10 @@ export async function requestChatDataMain(arg:requestDataArgument, model:'model'
|
||||
case 'gpt4om-2024-07-18':
|
||||
case 'gpt4o-2024-08-06':
|
||||
case 'gpt4o-chatgpt':
|
||||
case 'gpt4o1-preview':
|
||||
case 'gpt4o1-mini':
|
||||
case 'jamba-1.5-large':
|
||||
case 'jamba-1.5-medium':
|
||||
case 'reverse_proxy':{
|
||||
let formatedChat:OpenAIChatExtra[] = []
|
||||
for(let i=0;i<formated.length;i++){
|
||||
@@ -245,6 +305,15 @@ export async function requestChatDataMain(arg:requestDataArgument, model:'model'
|
||||
})
|
||||
}
|
||||
|
||||
if(aiModel.startsWith('gpt4o1')){
|
||||
for(let i=0;i<formatedChat.length;i++){
|
||||
if(formatedChat[i].role === 'system'){
|
||||
formatedChat[i].content = `<system>${formatedChat[i].content}</system>`
|
||||
formatedChat[i].role = 'user'
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for(let i=0;i<biasString.length;i++){
|
||||
const bia = biasString[i]
|
||||
if(bia[1] === -101){
|
||||
@@ -399,7 +468,9 @@ export async function requestChatDataMain(arg:requestDataArgument, model:'model'
|
||||
}
|
||||
|
||||
db.cipherChat = false
|
||||
let body = ({
|
||||
let body:{
|
||||
[key:string]:any
|
||||
} = ({
|
||||
model: aiModel === 'openrouter' ? openrouterRequestModel :
|
||||
requestModel === 'gpt35' ? 'gpt-3.5-turbo'
|
||||
: requestModel === 'gpt35_0613' ? 'gpt-3.5-turbo-0613'
|
||||
@@ -425,65 +496,60 @@ export async function requestChatDataMain(arg:requestDataArgument, model:'model'
|
||||
: requestModel === 'gpt4om-2024-07-18' ? 'gpt-4o-mini-2024-07-18'
|
||||
: requestModel === 'gpt4o-2024-08-06' ? 'gpt-4o-2024-08-06'
|
||||
: requestModel === 'gpt4o-chatgpt' ? 'chatgpt-4o-latest'
|
||||
: requestModel === 'gpt4o1-preview' ? 'o1-preview'
|
||||
: requestModel === 'gpt4o1-mini' ? 'o1-mini'
|
||||
: (!requestModel) ? 'gpt-3.5-turbo'
|
||||
: requestModel,
|
||||
messages: formatedChat,
|
||||
temperature: temperature,
|
||||
max_tokens: maxTokens,
|
||||
presence_penalty: arg.PresensePenalty || (db.PresensePenalty / 100),
|
||||
frequency_penalty: arg.frequencyPenalty || (db.frequencyPenalty / 100),
|
||||
logit_bias: bias,
|
||||
stream: false,
|
||||
top_p: db.top_p,
|
||||
|
||||
})
|
||||
|
||||
if(aiModel.startsWith('gpt4o1')){
|
||||
body.max_completion_tokens = body.max_tokens
|
||||
delete body.max_tokens
|
||||
}
|
||||
|
||||
if(db.generationSeed > 0){
|
||||
// @ts-ignore
|
||||
body.seed = db.generationSeed
|
||||
}
|
||||
|
||||
if(db.putUserOpen){
|
||||
// @ts-ignore
|
||||
body.user = getOpenUserString()
|
||||
}
|
||||
|
||||
if(aiModel === 'openrouter'){
|
||||
if(db.top_k !== 0){
|
||||
//@ts-ignore
|
||||
body.top_k = db.top_k
|
||||
if(db.jsonSchemaEnabled){
|
||||
body.response_format = {
|
||||
"type": "json_schema",
|
||||
"json_schema": getOpenAIJSONSchema()
|
||||
}
|
||||
}
|
||||
|
||||
if(aiModel === 'openrouter'){
|
||||
if(db.openrouterFallback){
|
||||
//@ts-ignore
|
||||
body.route = "fallback"
|
||||
}
|
||||
//@ts-ignore
|
||||
body.repetition_penalty = db.repetition_penalty
|
||||
//@ts-ignore
|
||||
body.min_p = db.min_p
|
||||
//@ts-ignore
|
||||
body.top_a = db.top_a
|
||||
//@ts-ignore
|
||||
body.transforms = db.openrouterMiddleOut ? ['middle-out'] : []
|
||||
|
||||
if(db.openrouterProvider){
|
||||
//@ts-ignore
|
||||
body.provider = {
|
||||
order: [db.openrouterProvider]
|
||||
}
|
||||
}
|
||||
|
||||
if(db.useInstructPrompt){
|
||||
//@ts-ignore
|
||||
delete body.messages
|
||||
|
||||
const prompt = applyChatTemplate(formated)
|
||||
|
||||
//@ts-ignore
|
||||
body.prompt = prompt
|
||||
}
|
||||
}
|
||||
|
||||
body = applyParameters(body,
|
||||
aiModel === 'openrouter' ? ['temperature', 'top_p', 'frequency_penalty', 'presence_penalty', 'repetition_penalty', 'min_p', 'top_a', 'top_k'] : ['temperature', 'top_p', 'frequency_penalty', 'presence_penalty']
|
||||
)
|
||||
|
||||
if(aiModel === 'reverse_proxy' && db.reverseProxyOobaMode){
|
||||
const OobaBodyTemplate = db.reverseProxyOobaArgs
|
||||
|
||||
@@ -549,6 +615,10 @@ export async function requestChatDataMain(arg:requestDataArgument, model:'model'
|
||||
if(risuIdentify){
|
||||
headers["X-Proxy-Risu"] = 'RisuAI'
|
||||
}
|
||||
if(aiModel.startsWith('jamba')){
|
||||
headers['Authorization'] = 'Bearer ' + db.ai21Key
|
||||
replacerURL = 'https://api.ai21.com/studio/v1/chat/completions'
|
||||
}
|
||||
if(multiGen){
|
||||
// @ts-ignore
|
||||
body.n = db.genTime
|
||||
@@ -594,11 +664,12 @@ export async function requestChatDataMain(arg:requestDataArgument, model:'model'
|
||||
url: replacerURL,
|
||||
})
|
||||
|
||||
let dataUint = new Uint8Array([])
|
||||
let dataUint:Uint8Array|Buffer = new Uint8Array([])
|
||||
|
||||
const transtream = new TransformStream<Uint8Array, StreamResponseChunk>( {
|
||||
async transform(chunk, control) {
|
||||
dataUint = Buffer.from(new Uint8Array([...dataUint, ...chunk]))
|
||||
let JSONreaded:{[key:string]:string} = {}
|
||||
try {
|
||||
const datas = dataUint.toString().split('\n')
|
||||
let readed:{[key:string]:string} = {}
|
||||
@@ -607,7 +678,17 @@ export async function requestChatDataMain(arg:requestDataArgument, model:'model'
|
||||
try {
|
||||
const rawChunk = data.replace("data: ", "")
|
||||
if(rawChunk === "[DONE]"){
|
||||
control.enqueue(readed)
|
||||
if(db.extractJson && db.jsonSchemaEnabled){
|
||||
for(const key in readed){
|
||||
const extracted = extractJSON(readed[key], db.extractJson)
|
||||
JSONreaded[key] = extracted
|
||||
}
|
||||
console.log(JSONreaded)
|
||||
control.enqueue(JSONreaded)
|
||||
}
|
||||
else{
|
||||
control.enqueue(readed)
|
||||
}
|
||||
return
|
||||
}
|
||||
const choices = JSON.parse(rawChunk).choices
|
||||
@@ -632,7 +713,17 @@ export async function requestChatDataMain(arg:requestDataArgument, model:'model'
|
||||
} catch (error) {}
|
||||
}
|
||||
}
|
||||
control.enqueue(readed)
|
||||
if(db.extractJson && db.jsonSchemaEnabled){
|
||||
for(const key in readed){
|
||||
const extracted = extractJSON(readed[key], db.extractJson)
|
||||
JSONreaded[key] = extracted
|
||||
}
|
||||
console.log(JSONreaded)
|
||||
control.enqueue(JSONreaded)
|
||||
}
|
||||
else{
|
||||
control.enqueue(readed)
|
||||
}
|
||||
} catch (error) {
|
||||
|
||||
}
|
||||
@@ -699,6 +790,21 @@ export async function requestChatDataMain(arg:requestDataArgument, model:'model'
|
||||
if(res.ok){
|
||||
try {
|
||||
if(multiGen && dat.choices){
|
||||
if(db.extractJson && db.jsonSchemaEnabled){
|
||||
|
||||
const c = dat.choices.map((v:{
|
||||
message:{content:string}
|
||||
}) => {
|
||||
const extracted = extractJSON(v.message.content, db.extractJson)
|
||||
return ["char",extracted]
|
||||
})
|
||||
|
||||
return {
|
||||
type: 'multiline',
|
||||
result: c
|
||||
}
|
||||
|
||||
}
|
||||
return {
|
||||
type: 'multiline',
|
||||
result: dat.choices.map((v) => {
|
||||
@@ -709,11 +815,33 @@ export async function requestChatDataMain(arg:requestDataArgument, model:'model'
|
||||
}
|
||||
|
||||
if(dat?.choices[0]?.text){
|
||||
if(db.extractJson && db.jsonSchemaEnabled){
|
||||
try {
|
||||
const parsed = JSON.parse(dat.choices[0].text)
|
||||
const extracted = extractJSON(parsed, db.extractJson)
|
||||
return {
|
||||
type: 'success',
|
||||
result: extracted
|
||||
}
|
||||
} catch (error) {
|
||||
console.log(error)
|
||||
return {
|
||||
type: 'success',
|
||||
result: dat.choices[0].text
|
||||
}
|
||||
}
|
||||
}
|
||||
return {
|
||||
type: 'success',
|
||||
result: dat.choices[0].text
|
||||
}
|
||||
}
|
||||
if(db.extractJson && db.jsonSchemaEnabled){
|
||||
return {
|
||||
type: 'success',
|
||||
result: extractJSON(dat.choices[0].message.content, db.extractJson)
|
||||
}
|
||||
}
|
||||
const msg:OpenAIChatFull = (dat.choices[0].message)
|
||||
return {
|
||||
type: 'success',
|
||||
@@ -814,7 +942,7 @@ export async function requestChatDataMain(arg:requestDataArgument, model:'model'
|
||||
"parameters":payload
|
||||
}
|
||||
|
||||
const da = await globalFetch("https://api.novelai.net/ai/generate", {
|
||||
const da = await globalFetch(aiModel === 'novelai_kayra' ? "https://text.novelai.net/ai/generate" : "https://api.novelai.net/ai/generate", {
|
||||
body: body,
|
||||
headers: {
|
||||
"Authorization": "Bearer " + db.novelai.token
|
||||
@@ -889,7 +1017,7 @@ export async function requestChatDataMain(arg:requestDataArgument, model:'model'
|
||||
case 'mancer':{
|
||||
let streamUrl = db.textgenWebUIStreamURL.replace(/\/api.*/, "/api/v1/stream")
|
||||
let blockingUrl = db.textgenWebUIBlockingURL.replace(/\/api.*/, "/api/v1/generate")
|
||||
let bodyTemplate:any
|
||||
let bodyTemplate:{[key:string]:any} = {}
|
||||
const suggesting = model === "submodel"
|
||||
const prompt = applyChatTemplate(formated)
|
||||
let stopStrings = getStopStrings(suggesting)
|
||||
@@ -1035,9 +1163,12 @@ export async function requestChatDataMain(arg:requestDataArgument, model:'model'
|
||||
const OobaBodyTemplate = db.reverseProxyOobaArgs
|
||||
const keys = Object.keys(OobaBodyTemplate)
|
||||
for(const key of keys){
|
||||
if(OobaBodyTemplate[key] !== undefined && OobaBodyTemplate[key] !== null){
|
||||
if(OobaBodyTemplate[key] !== undefined && OobaBodyTemplate[key] !== null && OobaParams.includes(key)){
|
||||
bodyTemplate[key] = OobaBodyTemplate[key]
|
||||
}
|
||||
else if(bodyTemplate[key]){
|
||||
delete bodyTemplate[key]
|
||||
}
|
||||
}
|
||||
|
||||
const response = await globalFetch(urlStr, {
|
||||
@@ -1109,7 +1240,7 @@ export async function requestChatDataMain(arg:requestDataArgument, model:'model'
|
||||
const API_ENDPOINT="us-central1-aiplatform.googleapis.com"
|
||||
const PROJECT_ID=db.google.projectId
|
||||
const MODEL_ID= aiModel === 'palm2' ? 'text-bison' :
|
||||
'palm2_unicorn' ? 'text-unicorn' :
|
||||
aiModel ==='palm2_unicorn' ? 'text-unicorn' :
|
||||
''
|
||||
const LOCATION_ID="us-central1"
|
||||
|
||||
@@ -1153,7 +1284,10 @@ export async function requestChatDataMain(arg:requestDataArgument, model:'model'
|
||||
case 'gemini-pro':
|
||||
case 'gemini-pro-vision':
|
||||
case 'gemini-1.5-pro-latest':
|
||||
case 'gemini-1.5-pro-exp-0801':
|
||||
case 'gemini-1.5-pro-exp-0827':
|
||||
case 'gemini-1.5-flash':
|
||||
case 'gemini-1.5-pro-002':
|
||||
case 'gemini-ultra':
|
||||
case 'gemini-ultra-vision':{
|
||||
interface GeminiPart{
|
||||
@@ -1292,11 +1426,11 @@ export async function requestChatDataMain(arg:requestDataArgument, model:'model'
|
||||
|
||||
const body = {
|
||||
contents: reformatedChat,
|
||||
generation_config: {
|
||||
generation_config: applyParameters({
|
||||
"maxOutputTokens": maxTokens,
|
||||
"temperature": temperature,
|
||||
"topP": db.top_p,
|
||||
},
|
||||
}, ['temperature', 'top_p'], {
|
||||
'top_p': "topP"
|
||||
}),
|
||||
safetySettings: uncensoredCatagory
|
||||
}
|
||||
|
||||
@@ -1366,14 +1500,20 @@ export async function requestChatDataMain(arg:requestDataArgument, model:'model'
|
||||
url.pathname = 'api/v1/generate'
|
||||
}
|
||||
|
||||
const body:KoboldGenerationInputSchema = {
|
||||
const body = applyParameters({
|
||||
"prompt": prompt,
|
||||
"temperature": (db.temperature / 100),
|
||||
"top_p": db.top_p,
|
||||
max_length: maxTokens,
|
||||
max_context_length: db.maxContext,
|
||||
n: 1
|
||||
}
|
||||
}, [
|
||||
'temperature',
|
||||
'top_p',
|
||||
'repetition_penalty',
|
||||
'top_k',
|
||||
'top_a'
|
||||
], {
|
||||
'repetition_penalty': 'rep_pen'
|
||||
}) as KoboldGenerationInputSchema
|
||||
|
||||
const da = await globalFetch(url.toString(), {
|
||||
method: "POST",
|
||||
@@ -1535,7 +1675,11 @@ export async function requestChatDataMain(arg:requestDataArgument, model:'model'
|
||||
}
|
||||
}
|
||||
case 'cohere-command-r':
|
||||
case 'cohere-command-r-plus':{
|
||||
case 'cohere-command-r-plus':
|
||||
case 'cohere-command-r-08-2024':
|
||||
case 'cohere-command-r-03-2024':
|
||||
case 'cohere-command-r-plus-04-2024':
|
||||
case 'cohere-command-r-plus-08-2024':{
|
||||
const modelName = aiModel.replace('cohere-', '')
|
||||
let lastChatPrompt = ''
|
||||
let preamble = ''
|
||||
@@ -1566,10 +1710,7 @@ export async function requestChatDataMain(arg:requestDataArgument, model:'model'
|
||||
|
||||
//reformat chat
|
||||
|
||||
|
||||
|
||||
|
||||
let body = {
|
||||
let body = applyParameters({
|
||||
message: lastChatPrompt,
|
||||
chat_history: formated.map((v) => {
|
||||
if(v.role === 'assistant'){
|
||||
@@ -1594,13 +1735,17 @@ export async function requestChatDataMain(arg:requestDataArgument, model:'model'
|
||||
}).filter((v) => v !== null).filter((v) => {
|
||||
return v.message
|
||||
}),
|
||||
temperature: temperature,
|
||||
k: db.top_k,
|
||||
p: (db.top_p > 0.99) ? 0.99 : (db.top_p < 0.01) ? 0.01 : db.top_p,
|
||||
presence_penalty: arg.PresensePenalty || (db.PresensePenalty / 100),
|
||||
frequency_penalty: arg.frequencyPenalty || (db.frequencyPenalty / 100),
|
||||
}
|
||||
}, [
|
||||
'temperature', 'top_k', 'top_p', 'presence_penalty', 'frequency_penalty'
|
||||
], {
|
||||
'top_k': 'k',
|
||||
'top_p': 'p',
|
||||
})
|
||||
|
||||
if(aiModel !== 'cohere-command-r-03-2024' && aiModel !== 'cohere-command-r-plus-04-2024'){
|
||||
body.safety_mode = "NONE"
|
||||
}
|
||||
|
||||
if(preamble){
|
||||
if(body.chat_history.length > 0){
|
||||
// @ts-ignore
|
||||
@@ -1873,16 +2018,13 @@ export async function requestChatDataMain(arg:requestDataArgument, model:'model'
|
||||
}
|
||||
|
||||
|
||||
let body = {
|
||||
let body = applyParameters({
|
||||
model: raiModel,
|
||||
messages: finalChat,
|
||||
system: systemPrompt.trim(),
|
||||
max_tokens: maxTokens,
|
||||
temperature: temperature,
|
||||
top_p: db.top_p,
|
||||
top_k: db.top_k,
|
||||
stream: useStreaming ?? false
|
||||
}
|
||||
}, ['temperature', 'top_k', 'top_p'])
|
||||
|
||||
if(systemPrompt === ''){
|
||||
delete body.system
|
||||
@@ -1997,6 +2139,10 @@ export async function requestChatDataMain(arg:requestDataArgument, model:'model'
|
||||
headers['anthropic-beta'] = 'prompt-caching-2024-07-31'
|
||||
}
|
||||
|
||||
if(db.usePlainFetch){
|
||||
headers['anthropic-dangerous-direct-browser-access'] = 'true'
|
||||
}
|
||||
|
||||
if(useStreaming){
|
||||
|
||||
const res = await fetchNative(replacerURL, {
|
||||
|
||||
@@ -5,9 +5,9 @@ import { downloadFile } from "../storage/globalApi";
|
||||
import { alertError, alertNormal } from "../alert";
|
||||
import { language } from "src/lang";
|
||||
import { selectSingleFile } from "../util";
|
||||
import { assetRegex, risuChatParser as risuChatParserOrg, type simpleCharacterArgument } from "../parser";
|
||||
import { assetRegex, type CbsConditions, risuChatParser as risuChatParserOrg, type simpleCharacterArgument } from "../parser";
|
||||
import { runCharacterJS } from "../plugins/embedscript";
|
||||
import { getModuleRegexScripts } from "./modules";
|
||||
import { getModuleAssets, getModuleRegexScripts } from "./modules";
|
||||
import { HypaProcesser } from "./memory/hypamemory";
|
||||
import { runLuaEditTrigger } from "./lua";
|
||||
|
||||
@@ -16,13 +16,19 @@ const randomness = /\|\|\|/g
|
||||
|
||||
export type ScriptMode = 'editinput'|'editoutput'|'editprocess'|'editdisplay'
|
||||
|
||||
export async function processScript(char:character|groupChat, data:string, mode:ScriptMode){
|
||||
return (await processScriptFull(char, data, mode)).data
|
||||
type pScript = {
|
||||
script: customscript,
|
||||
order: number
|
||||
actions: string[]
|
||||
}
|
||||
|
||||
export function exportRegex(){
|
||||
export async function processScript(char:character|groupChat, data:string, mode:ScriptMode, cbsConditions:CbsConditions = {}){
|
||||
return (await processScriptFull(char, data, mode, -1, cbsConditions)).data
|
||||
}
|
||||
|
||||
export function exportRegex(s?:customscript[]){
|
||||
let db = get(DataBase)
|
||||
const script = db.globalscript
|
||||
const script = s ?? db.globalscript
|
||||
const data = Buffer.from(JSON.stringify({
|
||||
type: 'regex',
|
||||
data: script
|
||||
@@ -31,22 +37,22 @@ export function exportRegex(){
|
||||
alertNormal(language.successExport)
|
||||
}
|
||||
|
||||
export async function importRegex(){
|
||||
export async function importRegex(o?:customscript[]):Promise<customscript[]>{
|
||||
o = o ?? []
|
||||
const filedata = (await selectSingleFile(['json'])).data
|
||||
if(!filedata){
|
||||
return
|
||||
return o
|
||||
}
|
||||
let db = get(DataBase)
|
||||
try {
|
||||
const imported= JSON.parse(Buffer.from(filedata).toString('utf-8'))
|
||||
if(imported.type === 'regex' && imported.data){
|
||||
const datas:customscript[] = imported.data
|
||||
const script = db.globalscript
|
||||
const script = o
|
||||
for(const data of datas){
|
||||
script.push(data)
|
||||
}
|
||||
db.globalscript = script
|
||||
setDatabase(db)
|
||||
return o
|
||||
}
|
||||
else{
|
||||
alertError("File invaid or corrupted")
|
||||
@@ -55,11 +61,12 @@ export async function importRegex(){
|
||||
} catch (error) {
|
||||
alertError(`${error}`)
|
||||
}
|
||||
return o
|
||||
}
|
||||
|
||||
let bestMatchCache = new Map<string, string>()
|
||||
|
||||
export async function processScriptFull(char:character|groupChat|simpleCharacterArgument, data:string, mode:ScriptMode, chatID = -1){
|
||||
export async function processScriptFull(char:character|groupChat|simpleCharacterArgument, data:string, mode:ScriptMode, chatID = -1, cbsConditions:CbsConditions = {}){
|
||||
let db = get(DataBase)
|
||||
let emoChanged = false
|
||||
const scripts = (db.globalscript ?? []).concat(char.customscript).concat(getModuleRegexScripts())
|
||||
@@ -72,7 +79,9 @@ export async function processScriptFull(char:character|groupChat|simpleCharacter
|
||||
if(scripts.length === 0){
|
||||
return {data, emoChanged}
|
||||
}
|
||||
for (const script of scripts){
|
||||
function executeScript(pscript:pScript){
|
||||
const script = pscript.script
|
||||
|
||||
if(script.type === mode){
|
||||
|
||||
let outScript2 = script.out.replaceAll("$n", "\n")
|
||||
@@ -81,18 +90,29 @@ export async function processScriptFull(char:character|groupChat|simpleCharacter
|
||||
if(script.ableFlag){
|
||||
flag = script.flag || 'g'
|
||||
}
|
||||
if(outScript.startsWith('@@move_top') || outScript.startsWith('@@move_bottom')){
|
||||
if(outScript.startsWith('@@move_top') || outScript.startsWith('@@move_bottom') || pscript.actions.includes('move_top') || pscript.actions.includes('move_bottom')){
|
||||
flag = flag.replace('g', '') //temperary fix
|
||||
}
|
||||
if(outScript.endsWith('>') && !pscript.actions.includes('no_end_nl')){
|
||||
outScript += '\n'
|
||||
}
|
||||
//remove unsupported flag
|
||||
flag = flag.replace(/[^gimuy]/g, '')
|
||||
flag = flag.trim().replace(/[^dgimsuvy]/g, '')
|
||||
|
||||
//remove repeated flags
|
||||
flag = flag.split('').filter((v, i, a) => a.indexOf(v) === i).join('')
|
||||
|
||||
if(flag.length === 0){
|
||||
flag = 'u'
|
||||
}
|
||||
|
||||
const reg = new RegExp(script.in, flag)
|
||||
if(outScript.startsWith('@@')){
|
||||
let input = script.in
|
||||
if(pscript.actions.includes('cbs')){
|
||||
input = risuChatParser(input, { chatID: chatID, cbsConditions })
|
||||
}
|
||||
|
||||
const reg = new RegExp(input, flag)
|
||||
if(outScript.startsWith('@@') || pscript.actions.length > 0){
|
||||
if(reg.test(data)){
|
||||
if(outScript.startsWith('@@emo ')){
|
||||
const emoName = script.out.substring(6).trim()
|
||||
@@ -117,17 +137,19 @@ export async function processScriptFull(char:character|groupChat|simpleCharacter
|
||||
}
|
||||
}
|
||||
}
|
||||
if(outScript.startsWith('@@inject') && chatID !== -1){
|
||||
else if((outScript.startsWith('@@inject') || pscript.actions.includes('inject')) && chatID !== -1){
|
||||
const selchar = db.characters[get(selectedCharID)]
|
||||
selchar.chats[selchar.chatPage].message[chatID].data = data
|
||||
data = data.replace(reg, "")
|
||||
}
|
||||
if(outScript.startsWith('@@move_top') || outScript.startsWith('@@move_bottom')){
|
||||
else if(
|
||||
outScript.startsWith('@@move_top') || outScript.startsWith('@@move_bottom') ||
|
||||
pscript.actions.includes('move_top') || pscript.actions.includes('move_bottom')
|
||||
){
|
||||
const isGlobal = flag.includes('g')
|
||||
const matchAll = isGlobal ? data.matchAll(reg) : [data.match(reg)]
|
||||
data = data.replace(reg, "")
|
||||
for(const matched of matchAll){
|
||||
console.log(matched)
|
||||
if(matched){
|
||||
const inData = matched[0]
|
||||
let out = outScript.replace('@@move_top ', '').replace('@@move_bottom ', '')
|
||||
@@ -146,8 +168,7 @@ export async function processScriptFull(char:character|groupChat|simpleCharacter
|
||||
}
|
||||
return v
|
||||
})
|
||||
console.log(out)
|
||||
if(outScript.startsWith('@@move_top')){
|
||||
if(outScript.startsWith('@@move_top') || pscript.actions.includes('move_top')){
|
||||
data = out + '\n' +data
|
||||
}
|
||||
else{
|
||||
@@ -156,13 +177,16 @@ export async function processScriptFull(char:character|groupChat|simpleCharacter
|
||||
}
|
||||
}
|
||||
}
|
||||
else{
|
||||
data = risuChatParser(data.replace(reg, outScript), { chatID: chatID, cbsConditions })
|
||||
}
|
||||
}
|
||||
else{
|
||||
if(outScript.startsWith('@@repeat_back') && chatID !== -1){
|
||||
if((outScript.startsWith('@@repeat_back') || pscript.actions.includes('repeat_back')) && chatID !== -1){
|
||||
const v = outScript.split(' ', 2)[1]
|
||||
const selchar = db.characters[get(selectedCharID)]
|
||||
const chat = selchar.chats[selchar.chatPage]
|
||||
let lastChat = selchar.firstMsgIndex === -1 ? selchar.firstMessage : selchar.alternateGreetings[selchar.firstMsgIndex]
|
||||
let lastChat = chat.fmIndex === -1 ? selchar.firstMessage : selchar.alternateGreetings[chat.fmIndex]
|
||||
let pointer = chatID - 1
|
||||
while(pointer >= 0){
|
||||
if(chat.message[pointer].role === chat.message[chatID].role){
|
||||
@@ -197,16 +221,72 @@ export async function processScriptFull(char:character|groupChat|simpleCharacter
|
||||
}
|
||||
}
|
||||
else{
|
||||
data = risuChatParser(data.replace(reg, outScript), { chatID: chatID })
|
||||
data = risuChatParser(data.replace(reg, outScript), { chatID: chatID, cbsConditions })
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let parsedScripts:pScript[] = []
|
||||
let orderChanged = false
|
||||
for (const script of scripts){
|
||||
if(script.ableFlag && script.flag?.includes('<')){
|
||||
const rregex = /<(.+?)>/g
|
||||
const scriptData = structuredClone(script)
|
||||
let order = 0
|
||||
const actions:string[] = []
|
||||
scriptData.flag = scriptData.flag?.replace(rregex, (v:string, p1:string) => {
|
||||
const meta = p1.split(',').map((v) => v.trim())
|
||||
for(const m of meta){
|
||||
if(m.startsWith('order ')){
|
||||
order = parseInt(m.substring(6))
|
||||
}
|
||||
else{
|
||||
actions.push(m)
|
||||
}
|
||||
}
|
||||
|
||||
return ''
|
||||
})
|
||||
parsedScripts.push({
|
||||
script: scriptData,
|
||||
order,
|
||||
actions
|
||||
})
|
||||
continue
|
||||
}
|
||||
parsedScripts.push({
|
||||
script,
|
||||
order: 0,
|
||||
actions: []
|
||||
})
|
||||
}
|
||||
|
||||
if(orderChanged){
|
||||
parsedScripts.sort((a, b) => b.order - a.order) //sort by order
|
||||
}
|
||||
for (const script of parsedScripts){
|
||||
try {
|
||||
executeScript(script)
|
||||
} catch (error) {
|
||||
console.error(error)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
if(db.dynamicAssets && (char.type === 'simple' || char.type === 'character') && char.additionalAssets && char.additionalAssets.length > 0){
|
||||
if(!db.dynamicAssetsEditDisplay && mode === 'editdisplay'){
|
||||
return {data, emoChanged}
|
||||
}
|
||||
const assetNames = char.additionalAssets.map((v) => v[0])
|
||||
|
||||
const moduleAssets = getModuleAssets()
|
||||
if(moduleAssets.length > 0){
|
||||
for(const asset of moduleAssets){
|
||||
assetNames.push(asset[0])
|
||||
}
|
||||
}
|
||||
|
||||
const processer = new HypaProcesser('MiniLM')
|
||||
await processer.addText(assetNames)
|
||||
const matches = data.matchAll(assetRegex)
|
||||
|
||||
@@ -6,6 +6,7 @@ import { globalFetch, readImage } from "../storage/globalApi"
|
||||
import { CharEmotion } from "../stores"
|
||||
import type { OpenAIChat } from "."
|
||||
import { processZip } from "./processzip"
|
||||
import { keiServerURL } from "../kei/kei"
|
||||
export async function stableDiff(currentChar:character,prompt:string){
|
||||
let db = get(DataBase)
|
||||
|
||||
@@ -488,5 +489,102 @@ export async function generateAIImage(genPrompt:string, currentChar:character, n
|
||||
return false
|
||||
}
|
||||
}
|
||||
if(db.sdProvider === 'kei'){
|
||||
const db = get(DataBase)
|
||||
let auth = db?.account?.token
|
||||
if(!auth){
|
||||
db.account = JSON.parse(localStorage.getItem("fallbackRisuToken"))
|
||||
auth = db?.account?.token
|
||||
db.account.useSync = true
|
||||
}
|
||||
const da = await globalFetch(keiServerURL() + '/imaggen', {
|
||||
body: {
|
||||
"prompt": genPrompt,
|
||||
},
|
||||
headers: {
|
||||
"x-api-key": auth
|
||||
}
|
||||
})
|
||||
|
||||
if(!da.ok || !da.data.success){
|
||||
alertError(Buffer.from(da.data.message || da.data).toString())
|
||||
return false
|
||||
}
|
||||
if(returnSdData === 'inlay'){
|
||||
return da.data.data
|
||||
}
|
||||
else{
|
||||
let charemotions = get(CharEmotion)
|
||||
const img = da.data.data
|
||||
const emos:[string, string,number][] = [[img, img, Date.now()]]
|
||||
charemotions[currentChar.chaId] = emos
|
||||
CharEmotion.set(charemotions)
|
||||
}
|
||||
return returnSdData
|
||||
|
||||
}
|
||||
if(db.sdProvider === 'fal'){
|
||||
const model = db.falModel
|
||||
const token = db.falToken
|
||||
|
||||
let body:{[key:string]:any} = {
|
||||
prompt: genPrompt,
|
||||
enable_safety_checker: false,
|
||||
sync_mode: true,
|
||||
image_size: {
|
||||
"width": db.sdConfig.width,
|
||||
"height": db.sdConfig.height,
|
||||
}
|
||||
}
|
||||
|
||||
if(db.falModel === 'fal-ai/flux-lora'){
|
||||
let loraPath = db.falLora
|
||||
if(loraPath.startsWith('urn:') || loraPath.startsWith('civitai:')){
|
||||
const id = loraPath.split('@').pop()
|
||||
loraPath = `https://civitai.com/api/download/models/${id}?type=Model&format=SafeTensor`
|
||||
}
|
||||
body.loras = [{
|
||||
"path": loraPath,
|
||||
"scale": db.falLoraScale
|
||||
}]
|
||||
}
|
||||
|
||||
if(db.falModel === 'fal-ai/flux-pro'){
|
||||
delete body.enable_safety_checker
|
||||
}
|
||||
console.log(body)
|
||||
|
||||
const res = await globalFetch('https://fal.run/' + model, {
|
||||
headers: {
|
||||
"Authorization": "Key " + token,
|
||||
"Content-Type": "application/json"
|
||||
},
|
||||
method: 'POST',
|
||||
body: body
|
||||
})
|
||||
|
||||
console.log(res)
|
||||
|
||||
if(!res.ok){
|
||||
alertError(JSON.stringify(res.data))
|
||||
return false
|
||||
}
|
||||
|
||||
let image = res.data?.images?.[0]?.url
|
||||
if(!image){
|
||||
alertError(JSON.stringify(res.data))
|
||||
return false
|
||||
}
|
||||
|
||||
if(returnSdData === 'inlay'){
|
||||
return image
|
||||
}
|
||||
else{
|
||||
let charemotions = get(CharEmotion)
|
||||
const emos:[string, string,number][] = [[image, image, Date.now()]]
|
||||
charemotions[currentChar.chaId] = emos
|
||||
CharEmotion.set(charemotions)
|
||||
}
|
||||
}
|
||||
return ''
|
||||
}
|
||||
|
||||
@@ -11,7 +11,7 @@ export const chatTemplates = {
|
||||
'chatml': `{% for message in messages %}{{'<|im_start|>' + message['role'] + '\n' + message['content'] + '<|im_end|>' + '\n'}}{% endfor %}{% if add_generation_prompt %}{{ '<|im_start|>assistant\n' }}{% endif %}`,
|
||||
'gpt2': `{% for message in messages %}{{'<|im_start|>' + message['role'] + '\n' + message['content'] + '<|im_end|>' + '\n'}}{% endfor %}{% if add_generation_prompt %}{{ '<|im_start|>assistant\n' }}{% endif %}`,
|
||||
'gemma': "{% if messages[0]['role'] == 'system' %}{{ raise_exception('System role not supported') }}{% endif %}{% for message in messages %}{% if (message['role'] == 'user') != (loop.index0 % 2 == 0) %}{{ raise_exception('Conversation roles must alternate user/assistant/user/assistant/...') }}{% endif %}{% if (message['role'] == 'assistant') %}{% set role = 'model' %}{% else %}{% set role = message['role'] %}{% endif %}{{ '<start_of_turn>' + role + '\n' + message['content'] | trim + '<end_of_turn>\n' }}{% endfor %}{% if add_generation_prompt %}{{'<start_of_turn>model\n'}}{% endif %}",
|
||||
'mistral': "{% for message in messages %}{% if (message['role'] == 'user') != (loop.index0 % 2 == 0) %}{{ raise_exception('Conversation roles must alternate user/assistant/user/assistant/...') }}{% endif %}{% if message['role'] == 'user' %}{{ ' [INST] ' + message['content'] + ' [/INST]' }}{% elif message['role'] == 'assistant' %}{{ ' ' + message['content'] + ' ' + eos_token}}{% else %}{{ raise_exception('Only user and assistant roles are supported!') }}{% endif %}{% endfor %}",
|
||||
'mistral': "{% for message in messages %}{% if message['role'] == 'user' %}{{ ' [INST] ' + message['content'] + ' [/INST]' }}{% elif message['role'] == 'assistant' %}{{ ' ' + message['content'] + ' ' + eos_token}}{% else %}{{ raise_exception('Only user and assistant roles are supported!') }}{% endif %}{% endfor %}",
|
||||
'vicuna': "{%- set ns = namespace(found=false) -%}\n{%- for message in messages -%}\n {%- if message['role'] == 'system' -%}\n {%- set ns.found = true -%}\n {%- endif -%}\n{%- endfor -%}\n{%- if not ns.found -%}\n {{- '' + 'A chat between a curious user and an artificial intelligence assistant. The assistant gives helpful, detailed, and polite answers to the user\\'s questions.' + '\\n\\n' -}}\n{%- endif %}\n{%- for message in messages %}\n {%- if message['role'] == 'system' -%}\n {{- '' + message['content'] + '\\n\\n' -}}\n {%- else -%}\n {%- if message['role'] == 'user' -%}\n {{-'USER: ' + message['content'] + '\\n'-}}\n {%- else -%}\n {{-'ASSISTANT: ' + message['content'] + '</s>\\n' -}}\n {%- endif -%}\n {%- endif -%}\n{%- endfor -%}\n{%- if add_generation_prompt -%}\n {{-'ASSISTANT:'-}}\n{%- endif -%}",
|
||||
"alpaca": "{%- set ns = namespace(found=false) -%}\n{%- for message in messages -%}\n {%- if message['role'] == 'system' -%}\n {%- set ns.found = true -%}\n {%- endif -%}\n{%- endfor -%}\n{%- if not ns.found -%}\n {{- '' + 'Below is an instruction that describes a task. Write a response that appropriately completes the request.' + '\\n\\n' -}}\n{%- endif %}\n{%- for message in messages %}\n {%- if message['role'] == 'system' -%}\n {{- '' + message['content'] + '\\n\\n' -}}\n {%- else -%}\n {%- if message['role'] == 'user' -%}\n {{-'### Instruction:\\n' + message['content'] + '\\n\\n'-}}\n {%- else -%}\n {{-'### Response:\\n' + message['content'] + '\\n\\n' -}}\n {%- endif -%}\n {%- endif -%}\n{%- endfor -%}\n{%- if add_generation_prompt -%}\n {{-'### Response:\\n'-}}\n{%- endif -%}"
|
||||
}
|
||||
@@ -22,19 +22,22 @@ export const templateEffect = {
|
||||
],
|
||||
'mistral': [
|
||||
'no_system_messages',
|
||||
'alter_user_assistant_roles'
|
||||
'alter_user_assistant_roles',
|
||||
],
|
||||
} as {[key:string]:TemplateEffect[]}
|
||||
|
||||
export const applyChatTemplate = (messages:OpenAIChat[]) => {
|
||||
export const applyChatTemplate = (messages:OpenAIChat[], arg:{
|
||||
type?: string
|
||||
custom?: string
|
||||
} = {}) => {
|
||||
const db = get(DataBase)
|
||||
const currentChar = get(CurrentCharacter)
|
||||
const type = db.instructChatTemplate
|
||||
const type = arg.type ?? db.instructChatTemplate
|
||||
if(!type){
|
||||
throw new Error('Template type is not set')
|
||||
}
|
||||
let clonedMessages = structuredClone(messages)
|
||||
const template = type === 'jinja' ? (new Template(db.JinjaTemplate)) :(new Template(chatTemplates[type]))
|
||||
const template = type === 'jinja' ? (new Template(arg.custom ?? db.JinjaTemplate)) :(new Template(chatTemplates[type]))
|
||||
let formatedMessages:{
|
||||
"role": 'user'|'assistant'|'system',
|
||||
"content": string
|
||||
@@ -94,6 +97,8 @@ export const applyChatTemplate = (messages:OpenAIChat[]) => {
|
||||
"messages": formatedMessages,
|
||||
"add_generation_prompt": true,
|
||||
"risu_char": currentChar.name,
|
||||
"risu_user": getUserName()
|
||||
"risu_user": getUserName(),
|
||||
"eos_token": "",
|
||||
"bos_token": "",
|
||||
})
|
||||
}
|
||||
172
src/ts/process/templates/jsonSchema.ts
Normal file
172
src/ts/process/templates/jsonSchema.ts
Normal file
@@ -0,0 +1,172 @@
|
||||
import { risuChatParser } from "src/ts/parser"
|
||||
import { DataBase } from "src/ts/storage/database"
|
||||
import { get } from "svelte/store"
|
||||
|
||||
export function convertInterfaceToSchema(int:string){
|
||||
if(!int.startsWith('interface ') && !int.startsWith('export interface ')){
|
||||
return JSON.parse(int)
|
||||
}
|
||||
|
||||
int = risuChatParser(int)
|
||||
|
||||
type SchemaProp = {
|
||||
"type": "array"|"string"|"number"|"boolean",
|
||||
"items"?:SchemaProp
|
||||
"enum"?:string[]
|
||||
"const"?:string
|
||||
}
|
||||
|
||||
const lines = int.split('\n')
|
||||
let schema = {
|
||||
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
||||
"type": "object",
|
||||
"additionalProperties": false,
|
||||
"properties": {} as {[key:string]:SchemaProp},
|
||||
"required": [] as string[],
|
||||
}
|
||||
for(let i = 1; i < lines.length; i++){
|
||||
let content = lines[i].trim()
|
||||
if(content === '{'){
|
||||
continue
|
||||
}
|
||||
if(content === '}'){
|
||||
continue
|
||||
}
|
||||
if(content === ''){
|
||||
continue
|
||||
}
|
||||
|
||||
let placeHolders:string[] = []
|
||||
|
||||
content = content
|
||||
.replace(/\\"/gu, '\uE9b4a')
|
||||
.replace(/\\'/gu, '\uE9b4b')
|
||||
.replace(/"(.+?)"/gu, function(match, p1){
|
||||
placeHolders.push(match)
|
||||
return `\uE9b4d${placeHolders.length - 1}`
|
||||
})
|
||||
.replace(/'(.+?)'/gu, function(match, p1){
|
||||
placeHolders.push(`"${p1}"`)
|
||||
return `\uE9b4d${placeHolders.length - 1}`
|
||||
})
|
||||
|
||||
.split('//')[0].trim() //remove comments
|
||||
|
||||
.replace(/((number)|(string)|(boolean))\[\]/gu, 'Array<$1>')
|
||||
|
||||
if(content.endsWith(',') || content.endsWith(';')){
|
||||
content = content.slice(0, -1)
|
||||
}
|
||||
|
||||
let spData = content.replace(/ /g, '').split(':')
|
||||
|
||||
if(spData.length !== 2){
|
||||
throw "SyntaxError Found"
|
||||
}
|
||||
|
||||
let [property,typeData] = spData
|
||||
|
||||
switch(typeData){
|
||||
case 'string':
|
||||
case 'number':
|
||||
case 'boolean':{
|
||||
schema.properties[property] = {
|
||||
type: typeData
|
||||
}
|
||||
break
|
||||
}
|
||||
case 'Array<string>':
|
||||
case 'Array<number>':
|
||||
case 'Array<boolean>':{
|
||||
const ogType = typeData.slice(6,-1)
|
||||
|
||||
schema.properties[property] = {
|
||||
type: 'array',
|
||||
items: {
|
||||
type: ogType as 'string'|'number'|'boolean'
|
||||
}
|
||||
}
|
||||
break
|
||||
}
|
||||
default:{
|
||||
const types = typeData.split("|")
|
||||
const strings:string[] = []
|
||||
for(const t of types){
|
||||
if(!t.startsWith('\uE9b4d')){
|
||||
throw "Unsupported Type Detected"
|
||||
}
|
||||
const textIndex = t.replace('\uE9b4d','')
|
||||
const text = placeHolders[parseInt(textIndex)]
|
||||
const textParsed = JSON.parse(text.replace(/\uE9b4a/gu, '\\"').replace(/\uE9b4b/gu, "\\'"))
|
||||
strings.push(textParsed)
|
||||
}
|
||||
if(strings.length === 1){
|
||||
schema.properties[property] = {
|
||||
type: 'string',
|
||||
const: strings[0]
|
||||
}
|
||||
}
|
||||
else{
|
||||
schema.properties[property] = {
|
||||
type: 'string',
|
||||
enum: strings
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
schema.required.push(property)
|
||||
|
||||
}
|
||||
return schema
|
||||
}
|
||||
|
||||
export function getOpenAIJSONSchema(){
|
||||
const db = get(DataBase)
|
||||
const schema = {
|
||||
"name": "format",
|
||||
"strict": db.strictJsonSchema,
|
||||
"schema": convertInterfaceToSchema(db.jsonSchema)
|
||||
}
|
||||
return schema
|
||||
}
|
||||
|
||||
export function extractJSON(data:string, format:string){
|
||||
const extract = (data:any, format:string) => {
|
||||
try {
|
||||
if(data === undefined || data === null){
|
||||
return ''
|
||||
}
|
||||
|
||||
const fp = format.split('.')
|
||||
const current = data[fp[0]]
|
||||
|
||||
if(current === undefined){
|
||||
return ''
|
||||
}
|
||||
else if(fp.length === 1){
|
||||
return `${current ?? ''}`
|
||||
}
|
||||
else if(typeof current === 'object'){
|
||||
return extractJSON(current, fp.slice(1).join('.'))
|
||||
}
|
||||
else if(Array.isArray(current)){
|
||||
const index = parseInt(fp[1])
|
||||
return extractJSON(current[index], fp.slice(1).join('.'))
|
||||
}
|
||||
else{
|
||||
return `${current ?? ''}`
|
||||
}
|
||||
} catch (error) {
|
||||
return ''
|
||||
}
|
||||
}
|
||||
try {
|
||||
format = risuChatParser(format)
|
||||
data = data.trim()
|
||||
if(data.startsWith('{')){
|
||||
return extract(JSON.parse(data), format)
|
||||
}
|
||||
} catch (error) {}
|
||||
return data
|
||||
}
|
||||
@@ -1,9 +1,9 @@
|
||||
import { parseChatML, risuChatParser, risuCommandParser } from "../parser";
|
||||
import { parseChatML, risuChatParser } from "../parser";
|
||||
import { DataBase, type Chat, type character } from "../storage/database";
|
||||
import { tokenize } from "../tokenizer";
|
||||
import { getModuleTriggers } from "./modules";
|
||||
import { get } from "svelte/store";
|
||||
import { CurrentCharacter, CurrentChat, selectedCharID } from "../stores";
|
||||
import { CurrentCharacter, CurrentChat, ReloadGUIPointer, selectedCharID } from "../stores";
|
||||
import { processMultiCommand } from "./command";
|
||||
import { parseKeyValue } from "../util";
|
||||
import { alertError, alertInput, alertNormal, alertSelect } from "../alert";
|
||||
@@ -301,28 +301,34 @@ export async function runTrigger(char:character,mode:triggerMode, arg:{
|
||||
case'setvar': {
|
||||
const effectValue = risuChatParser(effect.value,{chara:char})
|
||||
const varKey = risuChatParser(effect.var,{chara:char})
|
||||
let originalVar = Number(getVar(varKey))
|
||||
if(Number.isNaN(originalVar)){
|
||||
originalVar = 0
|
||||
}
|
||||
let resultValue = ''
|
||||
switch(effect.operator){
|
||||
case '=':{
|
||||
setVar(varKey, effectValue)
|
||||
resultValue = effectValue
|
||||
break
|
||||
}
|
||||
case '+=':{
|
||||
setVar(varKey, (Number(getVar(varKey)) + Number(effectValue)).toString())
|
||||
resultValue = (originalVar + Number(effectValue)).toString()
|
||||
break
|
||||
}
|
||||
case '-=':{
|
||||
setVar(varKey, (Number(getVar(varKey)) - Number(effectValue)).toString())
|
||||
resultValue = (originalVar - Number(effectValue)).toString()
|
||||
break
|
||||
}
|
||||
case '*=':{
|
||||
setVar(varKey, (Number(getVar(varKey)) * Number(effectValue)).toString())
|
||||
resultValue = (originalVar * Number(effectValue)).toString()
|
||||
break
|
||||
}
|
||||
case '/=':{
|
||||
setVar(varKey, (Number(getVar(varKey)) / Number(effectValue)).toString())
|
||||
resultValue = (originalVar / Number(effectValue)).toString()
|
||||
break
|
||||
}
|
||||
}
|
||||
setVar(varKey, resultValue)
|
||||
break
|
||||
}
|
||||
case 'systemprompt':{
|
||||
@@ -498,19 +504,6 @@ export async function runTrigger(char:character,mode:triggerMode, arg:{
|
||||
setVar(effect.inputVar, res)
|
||||
break
|
||||
}
|
||||
|
||||
case 'triggercode':{
|
||||
const triggerCodeResult = await risuCommandParser(effect.code,{
|
||||
chara:char,
|
||||
lowLevelAccess: trigger.lowLevelAccess,
|
||||
funcName: mode
|
||||
})
|
||||
|
||||
if(triggerCodeResult['__stop_chat__'] === '1'){
|
||||
stopSending = true
|
||||
}
|
||||
break
|
||||
}
|
||||
case 'triggerlua':{
|
||||
const triggerCodeResult = await runLua(effect.code,{
|
||||
lowLevelAccess: trigger.lowLevelAccess,
|
||||
@@ -524,7 +517,7 @@ export async function runTrigger(char:character,mode:triggerMode, arg:{
|
||||
if(triggerCodeResult.stopSending){
|
||||
stopSending = true
|
||||
}
|
||||
chat = triggerCodeResult.chat
|
||||
chat = get(CurrentChat)
|
||||
break
|
||||
}
|
||||
}
|
||||
@@ -544,6 +537,7 @@ export async function runTrigger(char:character,mode:triggerMode, arg:{
|
||||
if(varChanged){
|
||||
const currentChat = get(CurrentChat)
|
||||
currentChat.scriptstate = chat.scriptstate
|
||||
ReloadGUIPointer.set(get(ReloadGUIPointer) + 1)
|
||||
}
|
||||
|
||||
return {additonalSysPrompt, chat, tokens:caculatedTokens, stopSending, sendAIprompt}
|
||||
|
||||
@@ -2,7 +2,7 @@ import { get } from "svelte/store";
|
||||
import { alertError } from "../alert";
|
||||
import { DataBase, type character } from "../storage/database";
|
||||
import { runTranslator, translateVox } from "../translator/translator";
|
||||
import { globalFetch } from "../storage/globalApi";
|
||||
import { globalFetch, loadAsset } from "../storage/globalApi";
|
||||
import { language } from "src/lang";
|
||||
import { getCurrentCharacter, sleep } from "../util";
|
||||
import { registerOnnxModel, runVITS } from "./transformers";
|
||||
@@ -27,7 +27,7 @@ export async function sayTTS(character:character,text:string) {
|
||||
text = text.replace(/\*/g,'')
|
||||
|
||||
if(character.ttsReadOnlyQuoted){
|
||||
const matches = text.match(/"(.*?)"/g)
|
||||
const matches = text.match(/["「](.*?)["」]/g)
|
||||
if(matches && matches.length > 0){
|
||||
text = matches.map(match => match.slice(1, -1)).join("");
|
||||
}
|
||||
@@ -231,12 +231,94 @@ export async function sayTTS(character:character,text:string) {
|
||||
case 'vits':{
|
||||
await runVITS(text, character.vits)
|
||||
}
|
||||
case 'gptsovits':{
|
||||
const audioContext = new AudioContext();
|
||||
|
||||
const audio: Uint8Array = await loadAsset(character.gptSoVitsConfig.ref_audio_data.assetId);
|
||||
const base64Audio = btoa(new Uint8Array(audio).reduce((data, byte) => data + String.fromCharCode(byte), ''));
|
||||
|
||||
const body = {
|
||||
text: text,
|
||||
text_lang: character.gptSoVitsConfig.text_lang,
|
||||
ref_audio_path: undefined,
|
||||
ref_audio_name: character.gptSoVitsConfig.ref_audio_data.fileName,
|
||||
ref_audio_data: base64Audio,
|
||||
prompt_text: undefined,
|
||||
prompt_lang: character.gptSoVitsConfig.prompt_lang,
|
||||
top_p: character.gptSoVitsConfig.top_p,
|
||||
temperature: character.gptSoVitsConfig.temperature,
|
||||
speed_factor: character.gptSoVitsConfig.speed,
|
||||
top_k: character.gptSoVitsConfig.top_k,
|
||||
text_split_method: character.gptSoVitsConfig.text_split_method,
|
||||
parallel_infer: true,
|
||||
// media_type: character.gptSoVitsConfig.ref_audio_data.fileName.split('.')[1],
|
||||
ref_free: character.gptSoVitsConfig.use_long_audio || !character.gptSoVitsConfig.use_prompt,
|
||||
}
|
||||
|
||||
if (character.gptSoVitsConfig.use_prompt){
|
||||
body.prompt_text = character.gptSoVitsConfig.prompt
|
||||
}
|
||||
|
||||
if (character.gptSoVitsConfig.use_auto_path){
|
||||
console.log('auto')
|
||||
const path = await globalFetch(`${character.gptSoVitsConfig.url}/get_path`, {
|
||||
method: 'GET',
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
rawResponse: false,
|
||||
plainFetchDeforce: true,
|
||||
})
|
||||
console.log(path)
|
||||
if(path.ok){
|
||||
body.ref_audio_path = path.data.message + '/public/audio/' + character.gptSoVitsConfig.ref_audio_data.fileName
|
||||
}
|
||||
else{
|
||||
throw new Error('Failed to Auto get path')
|
||||
}
|
||||
} else {
|
||||
body.ref_audio_path = character.gptSoVitsConfig.ref_audio_path + '/public/audio/' + character.gptSoVitsConfig.ref_audio_data.fileName
|
||||
}
|
||||
console.log(body)
|
||||
|
||||
const response = await globalFetch(`${character.gptSoVitsConfig.url}/tts`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
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;
|
||||
|
||||
const gainNode = audioContext.createGain();
|
||||
gainNode.gain.value = character.gptSoVitsConfig.volume || 1.0;
|
||||
|
||||
sourceNode.connect(gainNode);
|
||||
gainNode.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}`)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
export const oaiVoices = [
|
||||
'alloy', 'echo', 'fable', 'onyx', 'nova', 'shimmer'
|
||||
]
|
||||
|
||||
674
src/ts/rpack/LICENSE
Normal file
674
src/ts/rpack/LICENSE
Normal file
@@ -0,0 +1,674 @@
|
||||
GNU GENERAL PUBLIC LICENSE
|
||||
Version 3, 29 June 2007
|
||||
|
||||
Copyright (C) 2007 Free Software Foundation, Inc. <https://fsf.org/>
|
||||
Everyone is permitted to copy and distribute verbatim copies
|
||||
of this license document, but changing it is not allowed.
|
||||
|
||||
Preamble
|
||||
|
||||
The GNU General Public License is a free, copyleft license for
|
||||
software and other kinds of works.
|
||||
|
||||
The licenses for most software and other practical works are designed
|
||||
to take away your freedom to share and change the works. By contrast,
|
||||
the GNU General Public License is intended to guarantee your freedom to
|
||||
share and change all versions of a program--to make sure it remains free
|
||||
software for all its users. We, the Free Software Foundation, use the
|
||||
GNU General Public License for most of our software; it applies also to
|
||||
any other work released this way by its authors. You can apply it to
|
||||
your programs, too.
|
||||
|
||||
When we speak of free software, we are referring to freedom, not
|
||||
price. Our General Public Licenses are designed to make sure that you
|
||||
have the freedom to distribute copies of free software (and charge for
|
||||
them if you wish), that you receive source code or can get it if you
|
||||
want it, that you can change the software or use pieces of it in new
|
||||
free programs, and that you know you can do these things.
|
||||
|
||||
To protect your rights, we need to prevent others from denying you
|
||||
these rights or asking you to surrender the rights. Therefore, you have
|
||||
certain responsibilities if you distribute copies of the software, or if
|
||||
you modify it: responsibilities to respect the freedom of others.
|
||||
|
||||
For example, if you distribute copies of such a program, whether
|
||||
gratis or for a fee, you must pass on to the recipients the same
|
||||
freedoms that you received. You must make sure that they, too, receive
|
||||
or can get the source code. And you must show them these terms so they
|
||||
know their rights.
|
||||
|
||||
Developers that use the GNU GPL protect your rights with two steps:
|
||||
(1) assert copyright on the software, and (2) offer you this License
|
||||
giving you legal permission to copy, distribute and/or modify it.
|
||||
|
||||
For the developers' and authors' protection, the GPL clearly explains
|
||||
that there is no warranty for this free software. For both users' and
|
||||
authors' sake, the GPL requires that modified versions be marked as
|
||||
changed, so that their problems will not be attributed erroneously to
|
||||
authors of previous versions.
|
||||
|
||||
Some devices are designed to deny users access to install or run
|
||||
modified versions of the software inside them, although the manufacturer
|
||||
can do so. This is fundamentally incompatible with the aim of
|
||||
protecting users' freedom to change the software. The systematic
|
||||
pattern of such abuse occurs in the area of products for individuals to
|
||||
use, which is precisely where it is most unacceptable. Therefore, we
|
||||
have designed this version of the GPL to prohibit the practice for those
|
||||
products. If such problems arise substantially in other domains, we
|
||||
stand ready to extend this provision to those domains in future versions
|
||||
of the GPL, as needed to protect the freedom of users.
|
||||
|
||||
Finally, every program is threatened constantly by software patents.
|
||||
States should not allow patents to restrict development and use of
|
||||
software on general-purpose computers, but in those that do, we wish to
|
||||
avoid the special danger that patents applied to a free program could
|
||||
make it effectively proprietary. To prevent this, the GPL assures that
|
||||
patents cannot be used to render the program non-free.
|
||||
|
||||
The precise terms and conditions for copying, distribution and
|
||||
modification follow.
|
||||
|
||||
TERMS AND CONDITIONS
|
||||
|
||||
0. Definitions.
|
||||
|
||||
"This License" refers to version 3 of the GNU General Public License.
|
||||
|
||||
"Copyright" also means copyright-like laws that apply to other kinds of
|
||||
works, such as semiconductor masks.
|
||||
|
||||
"The Program" refers to any copyrightable work licensed under this
|
||||
License. Each licensee is addressed as "you". "Licensees" and
|
||||
"recipients" may be individuals or organizations.
|
||||
|
||||
To "modify" a work means to copy from or adapt all or part of the work
|
||||
in a fashion requiring copyright permission, other than the making of an
|
||||
exact copy. The resulting work is called a "modified version" of the
|
||||
earlier work or a work "based on" the earlier work.
|
||||
|
||||
A "covered work" means either the unmodified Program or a work based
|
||||
on the Program.
|
||||
|
||||
To "propagate" a work means to do anything with it that, without
|
||||
permission, would make you directly or secondarily liable for
|
||||
infringement under applicable copyright law, except executing it on a
|
||||
computer or modifying a private copy. Propagation includes copying,
|
||||
distribution (with or without modification), making available to the
|
||||
public, and in some countries other activities as well.
|
||||
|
||||
To "convey" a work means any kind of propagation that enables other
|
||||
parties to make or receive copies. Mere interaction with a user through
|
||||
a computer network, with no transfer of a copy, is not conveying.
|
||||
|
||||
An interactive user interface displays "Appropriate Legal Notices"
|
||||
to the extent that it includes a convenient and prominently visible
|
||||
feature that (1) displays an appropriate copyright notice, and (2)
|
||||
tells the user that there is no warranty for the work (except to the
|
||||
extent that warranties are provided), that licensees may convey the
|
||||
work under this License, and how to view a copy of this License. If
|
||||
the interface presents a list of user commands or options, such as a
|
||||
menu, a prominent item in the list meets this criterion.
|
||||
|
||||
1. Source Code.
|
||||
|
||||
The "source code" for a work means the preferred form of the work
|
||||
for making modifications to it. "Object code" means any non-source
|
||||
form of a work.
|
||||
|
||||
A "Standard Interface" means an interface that either is an official
|
||||
standard defined by a recognized standards body, or, in the case of
|
||||
interfaces specified for a particular programming language, one that
|
||||
is widely used among developers working in that language.
|
||||
|
||||
The "System Libraries" of an executable work include anything, other
|
||||
than the work as a whole, that (a) is included in the normal form of
|
||||
packaging a Major Component, but which is not part of that Major
|
||||
Component, and (b) serves only to enable use of the work with that
|
||||
Major Component, or to implement a Standard Interface for which an
|
||||
implementation is available to the public in source code form. A
|
||||
"Major Component", in this context, means a major essential component
|
||||
(kernel, window system, and so on) of the specific operating system
|
||||
(if any) on which the executable work runs, or a compiler used to
|
||||
produce the work, or an object code interpreter used to run it.
|
||||
|
||||
The "Corresponding Source" for a work in object code form means all
|
||||
the source code needed to generate, install, and (for an executable
|
||||
work) run the object code and to modify the work, including scripts to
|
||||
control those activities. However, it does not include the work's
|
||||
System Libraries, or general-purpose tools or generally available free
|
||||
programs which are used unmodified in performing those activities but
|
||||
which are not part of the work. For example, Corresponding Source
|
||||
includes interface definition files associated with source files for
|
||||
the work, and the source code for shared libraries and dynamically
|
||||
linked subprograms that the work is specifically designed to require,
|
||||
such as by intimate data communication or control flow between those
|
||||
subprograms and other parts of the work.
|
||||
|
||||
The Corresponding Source need not include anything that users
|
||||
can regenerate automatically from other parts of the Corresponding
|
||||
Source.
|
||||
|
||||
The Corresponding Source for a work in source code form is that
|
||||
same work.
|
||||
|
||||
2. Basic Permissions.
|
||||
|
||||
All rights granted under this License are granted for the term of
|
||||
copyright on the Program, and are irrevocable provided the stated
|
||||
conditions are met. This License explicitly affirms your unlimited
|
||||
permission to run the unmodified Program. The output from running a
|
||||
covered work is covered by this License only if the output, given its
|
||||
content, constitutes a covered work. This License acknowledges your
|
||||
rights of fair use or other equivalent, as provided by copyright law.
|
||||
|
||||
You may make, run and propagate covered works that you do not
|
||||
convey, without conditions so long as your license otherwise remains
|
||||
in force. You may convey covered works to others for the sole purpose
|
||||
of having them make modifications exclusively for you, or provide you
|
||||
with facilities for running those works, provided that you comply with
|
||||
the terms of this License in conveying all material for which you do
|
||||
not control copyright. Those thus making or running the covered works
|
||||
for you must do so exclusively on your behalf, under your direction
|
||||
and control, on terms that prohibit them from making any copies of
|
||||
your copyrighted material outside their relationship with you.
|
||||
|
||||
Conveying under any other circumstances is permitted solely under
|
||||
the conditions stated below. Sublicensing is not allowed; section 10
|
||||
makes it unnecessary.
|
||||
|
||||
3. Protecting Users' Legal Rights From Anti-Circumvention Law.
|
||||
|
||||
No covered work shall be deemed part of an effective technological
|
||||
measure under any applicable law fulfilling obligations under article
|
||||
11 of the WIPO copyright treaty adopted on 20 December 1996, or
|
||||
similar laws prohibiting or restricting circumvention of such
|
||||
measures.
|
||||
|
||||
When you convey a covered work, you waive any legal power to forbid
|
||||
circumvention of technological measures to the extent such circumvention
|
||||
is effected by exercising rights under this License with respect to
|
||||
the covered work, and you disclaim any intention to limit operation or
|
||||
modification of the work as a means of enforcing, against the work's
|
||||
users, your or third parties' legal rights to forbid circumvention of
|
||||
technological measures.
|
||||
|
||||
4. Conveying Verbatim Copies.
|
||||
|
||||
You may convey verbatim copies of the Program's source code as you
|
||||
receive it, in any medium, provided that you conspicuously and
|
||||
appropriately publish on each copy an appropriate copyright notice;
|
||||
keep intact all notices stating that this License and any
|
||||
non-permissive terms added in accord with section 7 apply to the code;
|
||||
keep intact all notices of the absence of any warranty; and give all
|
||||
recipients a copy of this License along with the Program.
|
||||
|
||||
You may charge any price or no price for each copy that you convey,
|
||||
and you may offer support or warranty protection for a fee.
|
||||
|
||||
5. Conveying Modified Source Versions.
|
||||
|
||||
You may convey a work based on the Program, or the modifications to
|
||||
produce it from the Program, in the form of source code under the
|
||||
terms of section 4, provided that you also meet all of these conditions:
|
||||
|
||||
a) The work must carry prominent notices stating that you modified
|
||||
it, and giving a relevant date.
|
||||
|
||||
b) The work must carry prominent notices stating that it is
|
||||
released under this License and any conditions added under section
|
||||
7. This requirement modifies the requirement in section 4 to
|
||||
"keep intact all notices".
|
||||
|
||||
c) You must license the entire work, as a whole, under this
|
||||
License to anyone who comes into possession of a copy. This
|
||||
License will therefore apply, along with any applicable section 7
|
||||
additional terms, to the whole of the work, and all its parts,
|
||||
regardless of how they are packaged. This License gives no
|
||||
permission to license the work in any other way, but it does not
|
||||
invalidate such permission if you have separately received it.
|
||||
|
||||
d) If the work has interactive user interfaces, each must display
|
||||
Appropriate Legal Notices; however, if the Program has interactive
|
||||
interfaces that do not display Appropriate Legal Notices, your
|
||||
work need not make them do so.
|
||||
|
||||
A compilation of a covered work with other separate and independent
|
||||
works, which are not by their nature extensions of the covered work,
|
||||
and which are not combined with it such as to form a larger program,
|
||||
in or on a volume of a storage or distribution medium, is called an
|
||||
"aggregate" if the compilation and its resulting copyright are not
|
||||
used to limit the access or legal rights of the compilation's users
|
||||
beyond what the individual works permit. Inclusion of a covered work
|
||||
in an aggregate does not cause this License to apply to the other
|
||||
parts of the aggregate.
|
||||
|
||||
6. Conveying Non-Source Forms.
|
||||
|
||||
You may convey a covered work in object code form under the terms
|
||||
of sections 4 and 5, provided that you also convey the
|
||||
machine-readable Corresponding Source under the terms of this License,
|
||||
in one of these ways:
|
||||
|
||||
a) Convey the object code in, or embodied in, a physical product
|
||||
(including a physical distribution medium), accompanied by the
|
||||
Corresponding Source fixed on a durable physical medium
|
||||
customarily used for software interchange.
|
||||
|
||||
b) Convey the object code in, or embodied in, a physical product
|
||||
(including a physical distribution medium), accompanied by a
|
||||
written offer, valid for at least three years and valid for as
|
||||
long as you offer spare parts or customer support for that product
|
||||
model, to give anyone who possesses the object code either (1) a
|
||||
copy of the Corresponding Source for all the software in the
|
||||
product that is covered by this License, on a durable physical
|
||||
medium customarily used for software interchange, for a price no
|
||||
more than your reasonable cost of physically performing this
|
||||
conveying of source, or (2) access to copy the
|
||||
Corresponding Source from a network server at no charge.
|
||||
|
||||
c) Convey individual copies of the object code with a copy of the
|
||||
written offer to provide the Corresponding Source. This
|
||||
alternative is allowed only occasionally and noncommercially, and
|
||||
only if you received the object code with such an offer, in accord
|
||||
with subsection 6b.
|
||||
|
||||
d) Convey the object code by offering access from a designated
|
||||
place (gratis or for a charge), and offer equivalent access to the
|
||||
Corresponding Source in the same way through the same place at no
|
||||
further charge. You need not require recipients to copy the
|
||||
Corresponding Source along with the object code. If the place to
|
||||
copy the object code is a network server, the Corresponding Source
|
||||
may be on a different server (operated by you or a third party)
|
||||
that supports equivalent copying facilities, provided you maintain
|
||||
clear directions next to the object code saying where to find the
|
||||
Corresponding Source. Regardless of what server hosts the
|
||||
Corresponding Source, you remain obligated to ensure that it is
|
||||
available for as long as needed to satisfy these requirements.
|
||||
|
||||
e) Convey the object code using peer-to-peer transmission, provided
|
||||
you inform other peers where the object code and Corresponding
|
||||
Source of the work are being offered to the general public at no
|
||||
charge under subsection 6d.
|
||||
|
||||
A separable portion of the object code, whose source code is excluded
|
||||
from the Corresponding Source as a System Library, need not be
|
||||
included in conveying the object code work.
|
||||
|
||||
A "User Product" is either (1) a "consumer product", which means any
|
||||
tangible personal property which is normally used for personal, family,
|
||||
or household purposes, or (2) anything designed or sold for incorporation
|
||||
into a dwelling. In determining whether a product is a consumer product,
|
||||
doubtful cases shall be resolved in favor of coverage. For a particular
|
||||
product received by a particular user, "normally used" refers to a
|
||||
typical or common use of that class of product, regardless of the status
|
||||
of the particular user or of the way in which the particular user
|
||||
actually uses, or expects or is expected to use, the product. A product
|
||||
is a consumer product regardless of whether the product has substantial
|
||||
commercial, industrial or non-consumer uses, unless such uses represent
|
||||
the only significant mode of use of the product.
|
||||
|
||||
"Installation Information" for a User Product means any methods,
|
||||
procedures, authorization keys, or other information required to install
|
||||
and execute modified versions of a covered work in that User Product from
|
||||
a modified version of its Corresponding Source. The information must
|
||||
suffice to ensure that the continued functioning of the modified object
|
||||
code is in no case prevented or interfered with solely because
|
||||
modification has been made.
|
||||
|
||||
If you convey an object code work under this section in, or with, or
|
||||
specifically for use in, a User Product, and the conveying occurs as
|
||||
part of a transaction in which the right of possession and use of the
|
||||
User Product is transferred to the recipient in perpetuity or for a
|
||||
fixed term (regardless of how the transaction is characterized), the
|
||||
Corresponding Source conveyed under this section must be accompanied
|
||||
by the Installation Information. But this requirement does not apply
|
||||
if neither you nor any third party retains the ability to install
|
||||
modified object code on the User Product (for example, the work has
|
||||
been installed in ROM).
|
||||
|
||||
The requirement to provide Installation Information does not include a
|
||||
requirement to continue to provide support service, warranty, or updates
|
||||
for a work that has been modified or installed by the recipient, or for
|
||||
the User Product in which it has been modified or installed. Access to a
|
||||
network may be denied when the modification itself materially and
|
||||
adversely affects the operation of the network or violates the rules and
|
||||
protocols for communication across the network.
|
||||
|
||||
Corresponding Source conveyed, and Installation Information provided,
|
||||
in accord with this section must be in a format that is publicly
|
||||
documented (and with an implementation available to the public in
|
||||
source code form), and must require no special password or key for
|
||||
unpacking, reading or copying.
|
||||
|
||||
7. Additional Terms.
|
||||
|
||||
"Additional permissions" are terms that supplement the terms of this
|
||||
License by making exceptions from one or more of its conditions.
|
||||
Additional permissions that are applicable to the entire Program shall
|
||||
be treated as though they were included in this License, to the extent
|
||||
that they are valid under applicable law. If additional permissions
|
||||
apply only to part of the Program, that part may be used separately
|
||||
under those permissions, but the entire Program remains governed by
|
||||
this License without regard to the additional permissions.
|
||||
|
||||
When you convey a copy of a covered work, you may at your option
|
||||
remove any additional permissions from that copy, or from any part of
|
||||
it. (Additional permissions may be written to require their own
|
||||
removal in certain cases when you modify the work.) You may place
|
||||
additional permissions on material, added by you to a covered work,
|
||||
for which you have or can give appropriate copyright permission.
|
||||
|
||||
Notwithstanding any other provision of this License, for material you
|
||||
add to a covered work, you may (if authorized by the copyright holders of
|
||||
that material) supplement the terms of this License with terms:
|
||||
|
||||
a) Disclaiming warranty or limiting liability differently from the
|
||||
terms of sections 15 and 16 of this License; or
|
||||
|
||||
b) Requiring preservation of specified reasonable legal notices or
|
||||
author attributions in that material or in the Appropriate Legal
|
||||
Notices displayed by works containing it; or
|
||||
|
||||
c) Prohibiting misrepresentation of the origin of that material, or
|
||||
requiring that modified versions of such material be marked in
|
||||
reasonable ways as different from the original version; or
|
||||
|
||||
d) Limiting the use for publicity purposes of names of licensors or
|
||||
authors of the material; or
|
||||
|
||||
e) Declining to grant rights under trademark law for use of some
|
||||
trade names, trademarks, or service marks; or
|
||||
|
||||
f) Requiring indemnification of licensors and authors of that
|
||||
material by anyone who conveys the material (or modified versions of
|
||||
it) with contractual assumptions of liability to the recipient, for
|
||||
any liability that these contractual assumptions directly impose on
|
||||
those licensors and authors.
|
||||
|
||||
All other non-permissive additional terms are considered "further
|
||||
restrictions" within the meaning of section 10. If the Program as you
|
||||
received it, or any part of it, contains a notice stating that it is
|
||||
governed by this License along with a term that is a further
|
||||
restriction, you may remove that term. If a license document contains
|
||||
a further restriction but permits relicensing or conveying under this
|
||||
License, you may add to a covered work material governed by the terms
|
||||
of that license document, provided that the further restriction does
|
||||
not survive such relicensing or conveying.
|
||||
|
||||
If you add terms to a covered work in accord with this section, you
|
||||
must place, in the relevant source files, a statement of the
|
||||
additional terms that apply to those files, or a notice indicating
|
||||
where to find the applicable terms.
|
||||
|
||||
Additional terms, permissive or non-permissive, may be stated in the
|
||||
form of a separately written license, or stated as exceptions;
|
||||
the above requirements apply either way.
|
||||
|
||||
8. Termination.
|
||||
|
||||
You may not propagate or modify a covered work except as expressly
|
||||
provided under this License. Any attempt otherwise to propagate or
|
||||
modify it is void, and will automatically terminate your rights under
|
||||
this License (including any patent licenses granted under the third
|
||||
paragraph of section 11).
|
||||
|
||||
However, if you cease all violation of this License, then your
|
||||
license from a particular copyright holder is reinstated (a)
|
||||
provisionally, unless and until the copyright holder explicitly and
|
||||
finally terminates your license, and (b) permanently, if the copyright
|
||||
holder fails to notify you of the violation by some reasonable means
|
||||
prior to 60 days after the cessation.
|
||||
|
||||
Moreover, your license from a particular copyright holder is
|
||||
reinstated permanently if the copyright holder notifies you of the
|
||||
violation by some reasonable means, this is the first time you have
|
||||
received notice of violation of this License (for any work) from that
|
||||
copyright holder, and you cure the violation prior to 30 days after
|
||||
your receipt of the notice.
|
||||
|
||||
Termination of your rights under this section does not terminate the
|
||||
licenses of parties who have received copies or rights from you under
|
||||
this License. If your rights have been terminated and not permanently
|
||||
reinstated, you do not qualify to receive new licenses for the same
|
||||
material under section 10.
|
||||
|
||||
9. Acceptance Not Required for Having Copies.
|
||||
|
||||
You are not required to accept this License in order to receive or
|
||||
run a copy of the Program. Ancillary propagation of a covered work
|
||||
occurring solely as a consequence of using peer-to-peer transmission
|
||||
to receive a copy likewise does not require acceptance. However,
|
||||
nothing other than this License grants you permission to propagate or
|
||||
modify any covered work. These actions infringe copyright if you do
|
||||
not accept this License. Therefore, by modifying or propagating a
|
||||
covered work, you indicate your acceptance of this License to do so.
|
||||
|
||||
10. Automatic Licensing of Downstream Recipients.
|
||||
|
||||
Each time you convey a covered work, the recipient automatically
|
||||
receives a license from the original licensors, to run, modify and
|
||||
propagate that work, subject to this License. You are not responsible
|
||||
for enforcing compliance by third parties with this License.
|
||||
|
||||
An "entity transaction" is a transaction transferring control of an
|
||||
organization, or substantially all assets of one, or subdividing an
|
||||
organization, or merging organizations. If propagation of a covered
|
||||
work results from an entity transaction, each party to that
|
||||
transaction who receives a copy of the work also receives whatever
|
||||
licenses to the work the party's predecessor in interest had or could
|
||||
give under the previous paragraph, plus a right to possession of the
|
||||
Corresponding Source of the work from the predecessor in interest, if
|
||||
the predecessor has it or can get it with reasonable efforts.
|
||||
|
||||
You may not impose any further restrictions on the exercise of the
|
||||
rights granted or affirmed under this License. For example, you may
|
||||
not impose a license fee, royalty, or other charge for exercise of
|
||||
rights granted under this License, and you may not initiate litigation
|
||||
(including a cross-claim or counterclaim in a lawsuit) alleging that
|
||||
any patent claim is infringed by making, using, selling, offering for
|
||||
sale, or importing the Program or any portion of it.
|
||||
|
||||
11. Patents.
|
||||
|
||||
A "contributor" is a copyright holder who authorizes use under this
|
||||
License of the Program or a work on which the Program is based. The
|
||||
work thus licensed is called the contributor's "contributor version".
|
||||
|
||||
A contributor's "essential patent claims" are all patent claims
|
||||
owned or controlled by the contributor, whether already acquired or
|
||||
hereafter acquired, that would be infringed by some manner, permitted
|
||||
by this License, of making, using, or selling its contributor version,
|
||||
but do not include claims that would be infringed only as a
|
||||
consequence of further modification of the contributor version. For
|
||||
purposes of this definition, "control" includes the right to grant
|
||||
patent sublicenses in a manner consistent with the requirements of
|
||||
this License.
|
||||
|
||||
Each contributor grants you a non-exclusive, worldwide, royalty-free
|
||||
patent license under the contributor's essential patent claims, to
|
||||
make, use, sell, offer for sale, import and otherwise run, modify and
|
||||
propagate the contents of its contributor version.
|
||||
|
||||
In the following three paragraphs, a "patent license" is any express
|
||||
agreement or commitment, however denominated, not to enforce a patent
|
||||
(such as an express permission to practice a patent or covenant not to
|
||||
sue for patent infringement). To "grant" such a patent license to a
|
||||
party means to make such an agreement or commitment not to enforce a
|
||||
patent against the party.
|
||||
|
||||
If you convey a covered work, knowingly relying on a patent license,
|
||||
and the Corresponding Source of the work is not available for anyone
|
||||
to copy, free of charge and under the terms of this License, through a
|
||||
publicly available network server or other readily accessible means,
|
||||
then you must either (1) cause the Corresponding Source to be so
|
||||
available, or (2) arrange to deprive yourself of the benefit of the
|
||||
patent license for this particular work, or (3) arrange, in a manner
|
||||
consistent with the requirements of this License, to extend the patent
|
||||
license to downstream recipients. "Knowingly relying" means you have
|
||||
actual knowledge that, but for the patent license, your conveying the
|
||||
covered work in a country, or your recipient's use of the covered work
|
||||
in a country, would infringe one or more identifiable patents in that
|
||||
country that you have reason to believe are valid.
|
||||
|
||||
If, pursuant to or in connection with a single transaction or
|
||||
arrangement, you convey, or propagate by procuring conveyance of, a
|
||||
covered work, and grant a patent license to some of the parties
|
||||
receiving the covered work authorizing them to use, propagate, modify
|
||||
or convey a specific copy of the covered work, then the patent license
|
||||
you grant is automatically extended to all recipients of the covered
|
||||
work and works based on it.
|
||||
|
||||
A patent license is "discriminatory" if it does not include within
|
||||
the scope of its coverage, prohibits the exercise of, or is
|
||||
conditioned on the non-exercise of one or more of the rights that are
|
||||
specifically granted under this License. You may not convey a covered
|
||||
work if you are a party to an arrangement with a third party that is
|
||||
in the business of distributing software, under which you make payment
|
||||
to the third party based on the extent of your activity of conveying
|
||||
the work, and under which the third party grants, to any of the
|
||||
parties who would receive the covered work from you, a discriminatory
|
||||
patent license (a) in connection with copies of the covered work
|
||||
conveyed by you (or copies made from those copies), or (b) primarily
|
||||
for and in connection with specific products or compilations that
|
||||
contain the covered work, unless you entered into that arrangement,
|
||||
or that patent license was granted, prior to 28 March 2007.
|
||||
|
||||
Nothing in this License shall be construed as excluding or limiting
|
||||
any implied license or other defenses to infringement that may
|
||||
otherwise be available to you under applicable patent law.
|
||||
|
||||
12. No Surrender of Others' Freedom.
|
||||
|
||||
If conditions are imposed on you (whether by court order, agreement or
|
||||
otherwise) that contradict the conditions of this License, they do not
|
||||
excuse you from the conditions of this License. If you cannot convey a
|
||||
covered work so as to satisfy simultaneously your obligations under this
|
||||
License and any other pertinent obligations, then as a consequence you may
|
||||
not convey it at all. For example, if you agree to terms that obligate you
|
||||
to collect a royalty for further conveying from those to whom you convey
|
||||
the Program, the only way you could satisfy both those terms and this
|
||||
License would be to refrain entirely from conveying the Program.
|
||||
|
||||
13. Use with the GNU Affero General Public License.
|
||||
|
||||
Notwithstanding any other provision of this License, you have
|
||||
permission to link or combine any covered work with a work licensed
|
||||
under version 3 of the GNU Affero General Public License into a single
|
||||
combined work, and to convey the resulting work. The terms of this
|
||||
License will continue to apply to the part which is the covered work,
|
||||
but the special requirements of the GNU Affero General Public License,
|
||||
section 13, concerning interaction through a network will apply to the
|
||||
combination as such.
|
||||
|
||||
14. Revised Versions of this License.
|
||||
|
||||
The Free Software Foundation may publish revised and/or new versions of
|
||||
the GNU General Public License from time to time. Such new versions will
|
||||
be similar in spirit to the present version, but may differ in detail to
|
||||
address new problems or concerns.
|
||||
|
||||
Each version is given a distinguishing version number. If the
|
||||
Program specifies that a certain numbered version of the GNU General
|
||||
Public License "or any later version" applies to it, you have the
|
||||
option of following the terms and conditions either of that numbered
|
||||
version or of any later version published by the Free Software
|
||||
Foundation. If the Program does not specify a version number of the
|
||||
GNU General Public License, you may choose any version ever published
|
||||
by the Free Software Foundation.
|
||||
|
||||
If the Program specifies that a proxy can decide which future
|
||||
versions of the GNU General Public License can be used, that proxy's
|
||||
public statement of acceptance of a version permanently authorizes you
|
||||
to choose that version for the Program.
|
||||
|
||||
Later license versions may give you additional or different
|
||||
permissions. However, no additional obligations are imposed on any
|
||||
author or copyright holder as a result of your choosing to follow a
|
||||
later version.
|
||||
|
||||
15. Disclaimer of Warranty.
|
||||
|
||||
THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
|
||||
APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
|
||||
HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
|
||||
OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
|
||||
THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
|
||||
PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
|
||||
IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
|
||||
ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
|
||||
|
||||
16. Limitation of Liability.
|
||||
|
||||
IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
|
||||
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
|
||||
THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
|
||||
GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
|
||||
USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
|
||||
DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
|
||||
PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
|
||||
EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
|
||||
SUCH DAMAGES.
|
||||
|
||||
17. Interpretation of Sections 15 and 16.
|
||||
|
||||
If the disclaimer of warranty and limitation of liability provided
|
||||
above cannot be given local legal effect according to their terms,
|
||||
reviewing courts shall apply local law that most closely approximates
|
||||
an absolute waiver of all civil liability in connection with the
|
||||
Program, unless a warranty or assumption of liability accompanies a
|
||||
copy of the Program in return for a fee.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
How to Apply These Terms to Your New Programs
|
||||
|
||||
If you develop a new program, and you want it to be of the greatest
|
||||
possible use to the public, the best way to achieve this is to make it
|
||||
free software which everyone can redistribute and change under these terms.
|
||||
|
||||
To do so, attach the following notices to the program. It is safest
|
||||
to attach them to the start of each source file to most effectively
|
||||
state the exclusion of warranty; and each file should have at least
|
||||
the "copyright" line and a pointer to where the full notice is found.
|
||||
|
||||
<one line to give the program's name and a brief idea of what it does.>
|
||||
Copyright (C) 2024 Kwaroran
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
Also add information on how to contact you by electronic and paper mail.
|
||||
|
||||
If the program does terminal interaction, make it output a short
|
||||
notice like this when it starts in an interactive mode:
|
||||
|
||||
<program> Copyright (C) 2024 Kwaroran
|
||||
This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
|
||||
This is free software, and you are welcome to redistribute it
|
||||
under certain conditions; type `show c' for details.
|
||||
|
||||
The hypothetical commands `show w' and `show c' should show the appropriate
|
||||
parts of the General Public License. Of course, your program's commands
|
||||
might be different; for a GUI interface, you would use an "about box".
|
||||
|
||||
You should also get your employer (if you work as a programmer) or school,
|
||||
if any, to sign a "copyright disclaimer" for the program, if necessary.
|
||||
For more information on this, and how to apply and follow the GNU GPL, see
|
||||
<https://www.gnu.org/licenses/>.
|
||||
|
||||
The GNU General Public License does not permit incorporating your program
|
||||
into proprietary programs. If your program is a subroutine library, you
|
||||
may consider it more useful to permit linking proprietary applications with
|
||||
the library. If this is what you want to do, use the GNU Lesser General
|
||||
Public License instead of this License. But first, please read
|
||||
<https://www.gnu.org/licenses/why-not-lgpl.html>.
|
||||
85
src/ts/rpack/rpack_bg.js
Normal file
85
src/ts/rpack/rpack_bg.js
Normal file
@@ -0,0 +1,85 @@
|
||||
import init from './rpack_bg.wasm?init';
|
||||
let wasm
|
||||
|
||||
let cachedUint8ArrayMemory0 = null;
|
||||
|
||||
function getUint8ArrayMemory0() {
|
||||
if (cachedUint8ArrayMemory0 === null || cachedUint8ArrayMemory0.byteLength === 0) {
|
||||
cachedUint8ArrayMemory0 = new Uint8Array(wasm.memory.buffer);
|
||||
}
|
||||
return cachedUint8ArrayMemory0;
|
||||
}
|
||||
|
||||
async function initWasm() {
|
||||
if(wasm) {
|
||||
return null
|
||||
}
|
||||
const instance = await init()
|
||||
wasm = instance.exports;
|
||||
return;
|
||||
}
|
||||
|
||||
let WASM_VECTOR_LEN = 0;
|
||||
|
||||
function passArray8ToWasm0(arg, malloc) {
|
||||
const ptr = malloc(arg.length * 1, 1) >>> 0;
|
||||
getUint8ArrayMemory0().set(arg, ptr / 1);
|
||||
WASM_VECTOR_LEN = arg.length;
|
||||
return ptr;
|
||||
}
|
||||
|
||||
let cachedDataViewMemory0 = null;
|
||||
|
||||
function getDataViewMemory0() {
|
||||
if (cachedDataViewMemory0 === null || cachedDataViewMemory0.buffer.detached === true || (cachedDataViewMemory0.buffer.detached === undefined && cachedDataViewMemory0.buffer !== wasm.memory.buffer)) {
|
||||
cachedDataViewMemory0 = new DataView(wasm.memory.buffer);
|
||||
}
|
||||
return cachedDataViewMemory0;
|
||||
}
|
||||
|
||||
function getArrayU8FromWasm0(ptr, len) {
|
||||
ptr = ptr >>> 0;
|
||||
return getUint8ArrayMemory0().subarray(ptr / 1, ptr / 1 + len);
|
||||
}
|
||||
/**
|
||||
* @param {Uint8Array} datas
|
||||
* @returns {Promise<Uint8Array>}
|
||||
*/
|
||||
export async function encodeRPack(datas) {
|
||||
await initWasm();
|
||||
try {
|
||||
const retptr = wasm.__wbindgen_add_to_stack_pointer(-16);
|
||||
const ptr0 = passArray8ToWasm0(datas, wasm.__wbindgen_malloc);
|
||||
const len0 = WASM_VECTOR_LEN;
|
||||
wasm.encode(retptr, ptr0, len0);
|
||||
var r0 = getDataViewMemory0().getInt32(retptr + 4 * 0, true);
|
||||
var r1 = getDataViewMemory0().getInt32(retptr + 4 * 1, true);
|
||||
var v2 = getArrayU8FromWasm0(r0, r1).slice();
|
||||
wasm.__wbindgen_free(r0, r1 * 1, 1);
|
||||
return v2;
|
||||
} finally {
|
||||
wasm.__wbindgen_add_to_stack_pointer(16);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {Uint8Array} datas
|
||||
* @returns {Promise<Uint8Array>}
|
||||
*/
|
||||
export async function decodeRPack(datas) {
|
||||
await initWasm();
|
||||
try {
|
||||
const retptr = wasm.__wbindgen_add_to_stack_pointer(-16);
|
||||
const ptr0 = passArray8ToWasm0(datas, wasm.__wbindgen_malloc);
|
||||
const len0 = WASM_VECTOR_LEN;
|
||||
wasm.decode(retptr, ptr0, len0);
|
||||
var r0 = getDataViewMemory0().getInt32(retptr + 4 * 0, true);
|
||||
var r1 = getDataViewMemory0().getInt32(retptr + 4 * 1, true);
|
||||
var v2 = getArrayU8FromWasm0(r0, r1).slice();
|
||||
wasm.__wbindgen_free(r0, r1 * 1, 1);
|
||||
return v2;
|
||||
} finally {
|
||||
wasm.__wbindgen_add_to_stack_pointer(16);
|
||||
}
|
||||
}
|
||||
|
||||
BIN
src/ts/rpack/rpack_bg.wasm
Normal file
BIN
src/ts/rpack/rpack_bg.wasm
Normal file
Binary file not shown.
@@ -2,12 +2,14 @@ import { get, writable } from "svelte/store"
|
||||
import { DataBase } from "./database"
|
||||
import { hubURL } from "../characterCards"
|
||||
import localforage from "localforage"
|
||||
import { alertLogin, alertStore } from "../alert"
|
||||
import { alertError, alertLogin, alertStore, alertWait } from "../alert"
|
||||
import { forageStorage, getUnpargeables, replaceDbResources } from "./globalApi"
|
||||
import { encodeRisuSave } from "./risuSave"
|
||||
import { v4 } from "uuid"
|
||||
import { language } from "src/lang"
|
||||
|
||||
export const AccountWarning = writable('')
|
||||
const risuSession = Date.now().toFixed(0)
|
||||
|
||||
let seenWarnings:string[] = []
|
||||
|
||||
@@ -26,7 +28,8 @@ export class AccountStorage{
|
||||
'content-type': 'application/json',
|
||||
'x-risu-key': key,
|
||||
'x-risu-auth': this.auth,
|
||||
'X-Format': 'nocheck'
|
||||
'X-Format': 'nocheck',
|
||||
'x-risu-session': risuSession
|
||||
}
|
||||
})
|
||||
if(da.headers.get('Content-Type') === 'application/json'){
|
||||
@@ -37,6 +40,11 @@ export class AccountStorage{
|
||||
AccountWarning.set(json.warning)
|
||||
}
|
||||
}
|
||||
if(json?.reloadSession){
|
||||
alertWait(language.reloadSession)
|
||||
location.reload()
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
if(da.status === 304){
|
||||
|
||||
@@ -2,7 +2,7 @@ import localforage from "localforage"
|
||||
import { isNodeServer, replaceDbResources } from "./globalApi"
|
||||
import { NodeStorage } from "./nodeStorage"
|
||||
import { OpfsStorage } from "./opfsStorage"
|
||||
import { alertSelect, alertStore } from "../alert"
|
||||
import { alertInput, alertSelect, alertStore } from "../alert"
|
||||
import { get } from "svelte/store"
|
||||
import { DataBase, type Database } from "./database"
|
||||
import { AccountStorage } from "./accountStorage"
|
||||
@@ -67,6 +67,13 @@ export class AutoStorage{
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
const confirm = await alertInput(`to overwrite your data, type "RISUAI"`)
|
||||
if(confirm !== "RISUAI"){
|
||||
localStorage.setItem('dosync', 'avoid')
|
||||
return false
|
||||
}
|
||||
|
||||
let replaced:{[key:string]:string} = {}
|
||||
|
||||
for(const key of keys){
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
export const DataBase = writable({} as any as Database)
|
||||
export const loadedStore = writable(false)
|
||||
export let appVer = "124.2.2"
|
||||
export let appVer = "136.0.1"
|
||||
export let webAppSubVer = ''
|
||||
|
||||
import { get, writable } from 'svelte/store';
|
||||
@@ -428,7 +428,21 @@ export function setDatabase(data:Database){
|
||||
negInputName: 'text',
|
||||
timeout: 30
|
||||
}
|
||||
|
||||
data.hideApiKey ??= true
|
||||
data.unformatQuotes ??= false
|
||||
data.ttsAutoSpeech ??= false
|
||||
data.translatorInputLanguage ??= 'auto'
|
||||
data.falModel ??= 'fal-ai/flux/dev'
|
||||
data.falLoraScale ??= 1
|
||||
data.customCSS ??= ''
|
||||
data.strictJsonSchema ??= true
|
||||
data.statics ??= {
|
||||
messages: 0,
|
||||
imports: 0
|
||||
}
|
||||
data.customQuotes ??= false
|
||||
data.customQuotesData ??= ['“','”','‘','’']
|
||||
data.groupOtherBotRole ??= 'user'
|
||||
changeLanguage(data.language)
|
||||
DataBase.set(data)
|
||||
}
|
||||
@@ -508,6 +522,7 @@ export interface Database{
|
||||
NAII2I:boolean
|
||||
NAIREF:boolean
|
||||
NAIImgConfig:NAIImgConfig
|
||||
ttsAutoSpeech?:boolean
|
||||
runpodKey:string
|
||||
promptPreprocess:boolean
|
||||
bias: [string, number][]
|
||||
@@ -612,6 +627,7 @@ export interface Database{
|
||||
emotionProcesser:'submodel'|'embedding',
|
||||
showMenuChatList?:boolean,
|
||||
translatorType:'google'|'deepl'|'none'|'llm'|'deeplX',
|
||||
translatorInputLanguage?:string
|
||||
NAIadventure?:boolean,
|
||||
NAIappendName?:boolean,
|
||||
deeplOptions:{
|
||||
@@ -710,6 +726,30 @@ export interface Database{
|
||||
comfyUiUrl: string
|
||||
useLegacyGUI: boolean
|
||||
claudeCachingExperimental: boolean
|
||||
hideApiKey: boolean
|
||||
unformatQuotes: boolean
|
||||
enableDevTools: boolean
|
||||
falToken: string
|
||||
falModel: string
|
||||
falLora: string
|
||||
falLoraName: string
|
||||
falLoraScale: number
|
||||
moduleIntergration: string
|
||||
customCSS: string
|
||||
betaMobileGUI:boolean
|
||||
jsonSchemaEnabled:boolean
|
||||
jsonSchema:string
|
||||
strictJsonSchema:boolean
|
||||
extractJson:string
|
||||
ai21Key:string
|
||||
statics: {
|
||||
messages: number
|
||||
imports: number
|
||||
}
|
||||
customQuotes:boolean
|
||||
customQuotesData?:[string, string, string, string]
|
||||
groupTemplate?:string
|
||||
groupOtherBotRole?:string
|
||||
}
|
||||
|
||||
export interface customscript{
|
||||
@@ -802,6 +842,27 @@ export interface character{
|
||||
voice?: string
|
||||
version?: string
|
||||
}
|
||||
gptSoVitsConfig?:{
|
||||
url?:string
|
||||
use_auto_path?:boolean
|
||||
ref_audio_path?:string
|
||||
use_long_audio?:boolean
|
||||
ref_audio_data?: {
|
||||
fileName:string
|
||||
assetId:string
|
||||
}
|
||||
volume?:number
|
||||
text_lang?: "auto" | "auto_yue" | "en" | "zh" | "ja" | "yue" | "ko" | "all_zh" | "all_ja" | "all_yue" | "all_ko"
|
||||
text?:string
|
||||
use_prompt?:boolean
|
||||
prompt?:string | null
|
||||
prompt_lang?: "auto" | "auto_yue" | "en" | "zh" | "ja" | "yue" | "ko" | "all_zh" | "all_ja" | "all_yue" | "all_ko"
|
||||
top_p?:number
|
||||
temperature?:number
|
||||
speed?:number
|
||||
top_k?:number
|
||||
text_split_method?: "cut0" | "cut1" | "cut2" | "cut3" | "cut4" | "cut5"
|
||||
}
|
||||
supaMemory?:boolean
|
||||
additionalAssets?:[string, string, string][]
|
||||
ttsReadOnlyQuoted?:boolean
|
||||
@@ -842,6 +903,8 @@ export interface character{
|
||||
defaultVariables?:string
|
||||
lowLevelAccess?:boolean
|
||||
hideChatIcon?:boolean
|
||||
lastInteraction?:number
|
||||
translatorNote?:string
|
||||
}
|
||||
|
||||
|
||||
@@ -890,6 +953,7 @@ export interface groupChat{
|
||||
defaultVariables?:string
|
||||
lowLevelAccess?:boolean
|
||||
hideChatIcon?:boolean
|
||||
lastInteraction?:number
|
||||
}
|
||||
|
||||
export interface botPreset{
|
||||
@@ -939,6 +1003,16 @@ export interface botPreset{
|
||||
useInstructPrompt?:boolean
|
||||
customPromptTemplateToggle?:string
|
||||
templateDefaultVariables?:string
|
||||
moduleIntergration?:string
|
||||
top_k?:number
|
||||
instructChatTemplate?:string
|
||||
JinjaTemplate?:string
|
||||
jsonSchemaEnabled?:boolean
|
||||
jsonSchema?:string
|
||||
strictJsonSchema?:boolean
|
||||
extractJson?:string
|
||||
groupTemplate?:string
|
||||
groupOtherBotRole?:string
|
||||
}
|
||||
|
||||
|
||||
@@ -1009,6 +1083,7 @@ export interface Chat{
|
||||
modules?:string[]
|
||||
id?:string
|
||||
bindedPersona?:string
|
||||
fmIndex?:number
|
||||
}
|
||||
|
||||
export interface Message{
|
||||
@@ -1018,6 +1093,8 @@ export interface Message{
|
||||
chatId?:string
|
||||
time?: number
|
||||
generationInfo?: MessageGenerationInfo
|
||||
name?:string
|
||||
otherUser?:boolean
|
||||
}
|
||||
|
||||
export interface MessageGenerationInfo{
|
||||
@@ -1040,7 +1117,7 @@ interface AINsettings{
|
||||
top_k:number
|
||||
}
|
||||
|
||||
interface OobaSettings{
|
||||
export interface OobaSettings{
|
||||
max_new_tokens: number,
|
||||
do_sample: boolean,
|
||||
temperature: number,
|
||||
@@ -1218,6 +1295,16 @@ export function saveCurrentPreset(){
|
||||
useInstructPrompt: db.useInstructPrompt,
|
||||
customPromptTemplateToggle: db.customPromptTemplateToggle ?? "",
|
||||
templateDefaultVariables: db.templateDefaultVariables ?? "",
|
||||
moduleIntergration: db.moduleIntergration ?? "",
|
||||
top_k: db.top_k,
|
||||
instructChatTemplate: db.instructChatTemplate,
|
||||
JinjaTemplate: db.JinjaTemplate ?? '',
|
||||
jsonSchemaEnabled:db.jsonSchemaEnabled??false,
|
||||
jsonSchema:db.jsonSchema ?? '',
|
||||
strictJsonSchema:db.strictJsonSchema ?? true,
|
||||
extractJson:db.extractJson ?? '',
|
||||
groupOtherBotRole: db.groupOtherBotRole ?? 'user',
|
||||
groupTemplate: db.groupTemplate ?? '',
|
||||
}
|
||||
db.botPresets = pres
|
||||
setDatabase(db)
|
||||
@@ -1302,6 +1389,16 @@ export function setPreset(db:Database, newPres: botPreset){
|
||||
db.useInstructPrompt = newPres.useInstructPrompt ?? false
|
||||
db.customPromptTemplateToggle = newPres.customPromptTemplateToggle ?? ''
|
||||
db.templateDefaultVariables = newPres.templateDefaultVariables ?? ''
|
||||
db.moduleIntergration = newPres.moduleIntergration ?? ''
|
||||
db.top_k = newPres.top_k ?? db.top_k
|
||||
db.instructChatTemplate = newPres.instructChatTemplate ?? db.instructChatTemplate
|
||||
db.JinjaTemplate = newPres.JinjaTemplate ?? db.JinjaTemplate
|
||||
db.jsonSchemaEnabled = newPres.jsonSchemaEnabled ?? false
|
||||
db.jsonSchema = newPres.jsonSchema ?? ''
|
||||
db.strictJsonSchema = newPres.strictJsonSchema ?? true
|
||||
db.extractJson = newPres.extractJson ?? ''
|
||||
db.groupOtherBotRole = newPres.groupOtherBotRole ?? 'user'
|
||||
db.groupTemplate = newPres.groupTemplate ?? ''
|
||||
return db
|
||||
}
|
||||
|
||||
@@ -1310,6 +1407,7 @@ import * as fflate from "fflate";
|
||||
import type { OnnxModelFiles } from '../process/transformers';
|
||||
import type { RisuModule } from '../process/modules';
|
||||
import type { HypaV2Data } from '../process/memory/hypav2';
|
||||
import { decodeRPack, encodeRPack } from '../rpack/rpack_bg';
|
||||
|
||||
export async function downloadPreset(id:number, type:'json'|'risupreset'|'return' = 'json'){
|
||||
saveCurrentPreset()
|
||||
@@ -1334,8 +1432,10 @@ export async function downloadPreset(id:number, type:'json'|'risupreset'|'return
|
||||
'risupreset'
|
||||
)
|
||||
}))
|
||||
|
||||
if(type === 'risupreset'){
|
||||
downloadFile(pres.name + "_preset.risupreset", buf)
|
||||
const buf2 = await encodeRPack(buf)
|
||||
downloadFile(pres.name + "_preset.risup", buf2)
|
||||
}
|
||||
else{
|
||||
return {
|
||||
@@ -1361,14 +1461,18 @@ export async function importPreset(f:{
|
||||
data:Uint8Array
|
||||
}|null = null){
|
||||
if(!f){
|
||||
f = await selectSingleFile(["json", "preset", "risupreset"])
|
||||
f = await selectSingleFile(["json", "preset", "risupreset", "risup"])
|
||||
}
|
||||
if(!f){
|
||||
return
|
||||
}
|
||||
let pre:any
|
||||
if(f.name.endsWith('.risupreset')){
|
||||
const decoded = await decodeMsgpack(fflate.decompressSync(f.data))
|
||||
if(f.name.endsWith('.risupreset') || f.name.endsWith('.risup')){
|
||||
let data = f.data
|
||||
if(f.name.endsWith('.risup')){
|
||||
data = await decodeRPack(data)
|
||||
}
|
||||
const decoded = await decodeMsgpack(fflate.decompressSync(data))
|
||||
console.log(decoded)
|
||||
if((decoded.presetVersion === 0 || decoded.presetVersion === 2) && decoded.type === 'preset'){
|
||||
pre = {...presetTemplate,...decodeMsgpack(Buffer.from(await decryptBuffer(decoded.preset ?? decoded.pres, 'risupreset')))}
|
||||
|
||||
@@ -1,18 +1,24 @@
|
||||
import { writeBinaryFile,BaseDirectory, readBinaryFile, exists, createDir, readDir, removeFile } from "@tauri-apps/api/fs"
|
||||
|
||||
import {
|
||||
writeFile,
|
||||
BaseDirectory,
|
||||
readFile,
|
||||
exists,
|
||||
mkdir,
|
||||
readDir,
|
||||
remove
|
||||
} from "@tauri-apps/plugin-fs"
|
||||
import { changeFullscreen, checkNullish, findCharacterbyId, sleep } from "../util"
|
||||
import { convertFileSrc, invoke } from "@tauri-apps/api/tauri"
|
||||
import { convertFileSrc, invoke } from "@tauri-apps/api/core"
|
||||
import { v4 as uuidv4, v4 } from 'uuid';
|
||||
import { appDataDir, join } from "@tauri-apps/api/path";
|
||||
import { get } from "svelte/store";
|
||||
import {open} from '@tauri-apps/api/shell'
|
||||
import {open} from '@tauri-apps/plugin-shell'
|
||||
import { DataBase, loadedStore, setDatabase, type Database, defaultSdDataFunc } from "./database";
|
||||
import { appWindow } from "@tauri-apps/api/window";
|
||||
import { getCurrentWebviewWindow } from "@tauri-apps/api/webviewWindow";
|
||||
import { checkRisuUpdate } from "../update";
|
||||
import { botMakerMode, selectedCharID } from "../stores";
|
||||
import { Body, ResponseType, fetch as TauriFetch } from "@tauri-apps/api/http";
|
||||
import { MobileGUI, botMakerMode, selectedCharID } from "../stores";
|
||||
import { loadPlugins } from "../plugins/plugins";
|
||||
import { alertConfirm, alertError, alertNormal, alertNormalWait, alertSelect } from "../alert";
|
||||
import { alertConfirm, alertError, alertNormal, alertNormalWait, alertSelect, alertTOS, alertWait } from "../alert";
|
||||
import { checkDriverInit, syncDrive } from "../drive/drive";
|
||||
import { hasher } from "../parser";
|
||||
import { characterURLImport, hubURL } from "../characterCards";
|
||||
@@ -21,11 +27,11 @@ import { loadRisuAccountData } from "../drive/accounter";
|
||||
import { decodeRisuSave, encodeRisuSave } from "./risuSave";
|
||||
import { AutoStorage } from "./autoStorage";
|
||||
import { updateAnimationSpeed } from "../gui/animation";
|
||||
import { updateColorScheme, updateTextTheme } from "../gui/colorscheme";
|
||||
import { updateColorScheme, updateTextThemeAndCSS } from "../gui/colorscheme";
|
||||
import { saveDbKei } from "../kei/backup";
|
||||
import { Capacitor, CapacitorHttp } from '@capacitor/core';
|
||||
import * as CapFS from '@capacitor/filesystem'
|
||||
import { save } from "@tauri-apps/api/dialog";
|
||||
import { save } from "@tauri-apps/plugin-dialog";
|
||||
import type { RisuModule } from "../process/modules";
|
||||
import { listen } from '@tauri-apps/api/event'
|
||||
import { registerPlugin } from '@capacitor/core';
|
||||
@@ -35,13 +41,18 @@ import { removeDefaultHandler } from "src/main";
|
||||
import { updateGuisize } from "../gui/guisize";
|
||||
import { encodeCapKeySafe } from "./mobileStorage";
|
||||
import { updateLorebooks } from "../characters";
|
||||
import { initMobileGesture } from "../hotkey";
|
||||
import { fetch as TauriHTTPFetch } from '@tauri-apps/plugin-http';
|
||||
|
||||
//@ts-ignore
|
||||
export const isTauri = !!window.__TAURI__
|
||||
export const isTauri = !!window.__TAURI_INTERNALS__
|
||||
//@ts-ignore
|
||||
export const isNodeServer = !!globalThis.__NODE__
|
||||
export const forageStorage = new AutoStorage()
|
||||
export const googleBuild = false
|
||||
export const isMobile = navigator.userAgent.match(/(iPad)|(iPhone)|(iPod)|(android)|(webOS)/i)
|
||||
|
||||
const appWindow = isTauri ? getCurrentWebviewWindow() : null
|
||||
|
||||
interface fetchLog{
|
||||
body:string
|
||||
@@ -56,26 +67,6 @@ interface fetchLog{
|
||||
|
||||
let fetchLog:fetchLog[] = []
|
||||
|
||||
async function writeBinaryFileFast(appPath: string, data: Uint8Array) {
|
||||
const secret = await invoke('get_http_secret') as string;
|
||||
const port = await invoke('get_http_port') as number;
|
||||
|
||||
const apiUrl = `http://127.0.0.1:${port}/?path=${encodeURIComponent(appPath)}`;
|
||||
|
||||
const response = await fetch(apiUrl, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/octet-stream',
|
||||
'x-tauri-secret': secret
|
||||
},
|
||||
body: new Blob([data])
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(`HTTP error! status: ${response.status}`);
|
||||
}
|
||||
}
|
||||
|
||||
export async function downloadFile(name:string, dat:Uint8Array|ArrayBuffer|string) {
|
||||
if(typeof(dat) === 'string'){
|
||||
dat = Buffer.from(dat, 'utf-8')
|
||||
@@ -92,7 +83,7 @@ export async function downloadFile(name:string, dat:Uint8Array|ArrayBuffer|strin
|
||||
}
|
||||
|
||||
if(isTauri){
|
||||
await writeBinaryFile(name, data, {dir: BaseDirectory.Download})
|
||||
await writeFile(name, data, {baseDir: BaseDirectory.Download})
|
||||
}
|
||||
else{
|
||||
downloadURL(`data:png/image;base64,${Buffer.from(data).toString('base64')}`, name)
|
||||
@@ -183,7 +174,7 @@ export async function getFileSrc(loc:string) {
|
||||
return "/sw/img/" + encoded
|
||||
}
|
||||
else{
|
||||
const f:Uint8Array = await forageStorage.getItem(loc)
|
||||
const f:Uint8Array = await forageStorage.getItem(loc) as unknown as Uint8Array
|
||||
await fetch("/sw/register/" + encoded, {
|
||||
method: "POST",
|
||||
body: f
|
||||
@@ -212,7 +203,7 @@ export async function getFileSrc(loc:string) {
|
||||
ind = fileCache.origin.length
|
||||
fileCache.origin.push(loc)
|
||||
fileCache.res.push('loading')
|
||||
const f:Uint8Array = await forageStorage.getItem(loc)
|
||||
const f:Uint8Array = await forageStorage.getItem(loc) as unknown as Uint8Array
|
||||
fileCache.res[ind] = f
|
||||
return `data:image/png;base64,${Buffer.from(f).toString('base64')}`
|
||||
}
|
||||
@@ -247,12 +238,12 @@ export async function readImage(data:string) {
|
||||
if(appDataDirPath === ''){
|
||||
appDataDirPath = await appDataDir();
|
||||
}
|
||||
return await readBinaryFile(await join(appDataDirPath,data))
|
||||
return await readFile(await join(appDataDirPath,data))
|
||||
}
|
||||
return await readBinaryFile(data)
|
||||
return await readFile(data)
|
||||
}
|
||||
else{
|
||||
return (await forageStorage.getItem(data) as Uint8Array)
|
||||
return (await forageStorage.getItem(data) as unknown as Uint8Array)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -281,7 +272,9 @@ export async function saveAsset(data:Uint8Array, customId:string = '', fileName:
|
||||
fileExtension = fileName.split('.').pop()
|
||||
}
|
||||
if(isTauri){
|
||||
await writeBinaryFileFast(`assets/${id}.${fileExtension}`, data);
|
||||
await writeFile(`assets/${id}.${fileExtension}`, data, {
|
||||
baseDir: BaseDirectory.AppData
|
||||
});
|
||||
return `assets/${id}.${fileExtension}`
|
||||
}
|
||||
else{
|
||||
@@ -302,10 +295,10 @@ export async function saveAsset(data:Uint8Array, customId:string = '', fileName:
|
||||
*/
|
||||
export async function loadAsset(id:string){
|
||||
if(isTauri){
|
||||
return await readBinaryFile(id,{dir: BaseDirectory.AppData})
|
||||
return await readFile(id,{baseDir: BaseDirectory.AppData})
|
||||
}
|
||||
else{
|
||||
return await forageStorage.getItem(id) as Uint8Array
|
||||
return await forageStorage.getItem(id) as unknown as Uint8Array
|
||||
}
|
||||
}
|
||||
|
||||
@@ -336,8 +329,8 @@ export async function saveDb(){
|
||||
}
|
||||
if(!gotChannel){
|
||||
gotChannel = true
|
||||
await alertNormalWait(language.activeTabChange)
|
||||
gotChannel = false
|
||||
alertWait(language.activeTabChange)
|
||||
location.reload()
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -358,8 +351,8 @@ export async function saveDb(){
|
||||
db.saveTime = Math.floor(Date.now() / 1000)
|
||||
if(isTauri){
|
||||
const dbData = encodeRisuSave(db)
|
||||
await writeBinaryFileFast('database/database.bin', dbData);
|
||||
await writeBinaryFileFast(`database/dbbackup-${(Date.now()/100).toFixed()}.bin`, dbData);
|
||||
await writeFile('database/database.bin', dbData, {baseDir: BaseDirectory.AppData});
|
||||
await writeFile(`database/dbbackup-${(Date.now()/100).toFixed()}.bin`, dbData, {baseDir: BaseDirectory.AppData});
|
||||
}
|
||||
else{
|
||||
if(!forageStorage.isAccount){
|
||||
@@ -405,7 +398,7 @@ async function getDbBackups() {
|
||||
return []
|
||||
}
|
||||
if(isTauri){
|
||||
const keys = await readDir('database', {dir: BaseDirectory.AppData})
|
||||
const keys = await readDir('database', {baseDir: BaseDirectory.AppData})
|
||||
let backups:number[] = []
|
||||
for(const key of keys){
|
||||
if(key.name.startsWith("dbbackup-")){
|
||||
@@ -417,7 +410,7 @@ async function getDbBackups() {
|
||||
backups.sort((a, b) => b - a)
|
||||
while(backups.length > 20){
|
||||
const last = backups.pop()
|
||||
await removeFile(`database/dbbackup-${last}.bin`,{dir: BaseDirectory.AppData})
|
||||
await remove(`database/dbbackup-${last}.bin`,{baseDir: BaseDirectory.AppData})
|
||||
}
|
||||
return backups
|
||||
}
|
||||
@@ -452,27 +445,27 @@ export async function loadData() {
|
||||
try {
|
||||
if(isTauri){
|
||||
appWindow.maximize()
|
||||
if(!await exists('', {dir: BaseDirectory.AppData})){
|
||||
await createDir('', {dir: BaseDirectory.AppData})
|
||||
if(!await exists('', {baseDir: BaseDirectory.AppData})){
|
||||
await mkdir('', {baseDir: BaseDirectory.AppData})
|
||||
}
|
||||
if(!await exists('database', {dir: BaseDirectory.AppData})){
|
||||
await createDir('database', {dir: BaseDirectory.AppData})
|
||||
if(!await exists('database', {baseDir: BaseDirectory.AppData})){
|
||||
await mkdir('database', {baseDir: BaseDirectory.AppData})
|
||||
}
|
||||
if(!await exists('assets', {dir: BaseDirectory.AppData})){
|
||||
await createDir('assets', {dir: BaseDirectory.AppData})
|
||||
if(!await exists('assets', {baseDir: BaseDirectory.AppData})){
|
||||
await mkdir('assets', {baseDir: BaseDirectory.AppData})
|
||||
}
|
||||
if(!await exists('database/database.bin', {dir: BaseDirectory.AppData})){
|
||||
await writeBinaryFileFast('database/database.bin', encodeRisuSave({}));
|
||||
if(!await exists('database/database.bin', {baseDir: BaseDirectory.AppData})){
|
||||
await writeFile('database/database.bin', encodeRisuSave({}), {baseDir: BaseDirectory.AppData});
|
||||
}
|
||||
try {
|
||||
const decoded = decodeRisuSave(await readBinaryFile('database/database.bin',{dir: BaseDirectory.AppData}))
|
||||
const decoded = decodeRisuSave(await readFile('database/database.bin',{baseDir: BaseDirectory.AppData}))
|
||||
setDatabase(decoded)
|
||||
} 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})
|
||||
const backupData = await readFile(`database/dbbackup-${backup}.bin`,{baseDir: BaseDirectory.AppData})
|
||||
setDatabase(
|
||||
decodeRisuSave(backupData)
|
||||
)
|
||||
@@ -490,7 +483,7 @@ export async function loadData() {
|
||||
|
||||
}
|
||||
else{
|
||||
let gotStorage:Uint8Array = await forageStorage.getItem('database/database.bin')
|
||||
let gotStorage:Uint8Array = await forageStorage.getItem('database/database.bin') as unknown as Uint8Array
|
||||
if(checkNullish(gotStorage)){
|
||||
gotStorage = encodeRisuSave({})
|
||||
await forageStorage.setItem('database/database.bin', gotStorage)
|
||||
@@ -505,7 +498,7 @@ export async function loadData() {
|
||||
let backupLoaded = false
|
||||
for(const backup of backups){
|
||||
try {
|
||||
const backupData:Uint8Array = await forageStorage.getItem(`database/dbbackup-${backup}.bin`)
|
||||
const backupData:Uint8Array = await forageStorage.getItem(`database/dbbackup-${backup}.bin`) as unknown as Uint8Array
|
||||
setDatabase(
|
||||
decodeRisuSave(backupData)
|
||||
)
|
||||
@@ -517,7 +510,7 @@ export async function loadData() {
|
||||
}
|
||||
}
|
||||
if(await forageStorage.checkAccountSync()){
|
||||
let gotStorage:Uint8Array = await forageStorage.getItem('database/database.bin')
|
||||
let gotStorage:Uint8Array = await forageStorage.getItem('database/database.bin') as unknown as Uint8Array
|
||||
if(checkNullish(gotStorage)){
|
||||
gotStorage = encodeRisuSave({})
|
||||
await forageStorage.setItem('database/database.bin', gotStorage)
|
||||
@@ -531,7 +524,7 @@ export async function loadData() {
|
||||
let backupLoaded = false
|
||||
for(const backup of backups){
|
||||
try {
|
||||
const backupData:Uint8Array = await forageStorage.getItem(`database/dbbackup-${backup}.bin`)
|
||||
const backupData:Uint8Array = await forageStorage.getItem(`database/dbbackup-${backup}.bin`) as unknown as Uint8Array
|
||||
setDatabase(
|
||||
decodeRisuSave(backupData)
|
||||
)
|
||||
@@ -568,11 +561,20 @@ export async function loadData() {
|
||||
try {
|
||||
await loadRisuAccountData()
|
||||
} catch (error) {}
|
||||
}
|
||||
try {
|
||||
//@ts-ignore
|
||||
const isInStandaloneMode = (window.matchMedia('(display-mode: standalone)').matches) || (window.navigator.standalone) || document.referrer.includes('android-app://');
|
||||
if(isInStandaloneMode){
|
||||
await navigator.storage.persist()
|
||||
}
|
||||
} catch (error) {
|
||||
|
||||
}
|
||||
await checkNewFormat()
|
||||
const db = get(DataBase);
|
||||
updateColorScheme()
|
||||
updateTextTheme()
|
||||
updateTextThemeAndCSS()
|
||||
updateAnimationSpeed()
|
||||
updateHeightMode()
|
||||
updateErrorHandling()
|
||||
@@ -580,10 +582,21 @@ export async function loadData() {
|
||||
if(db.botSettingAtStart){
|
||||
botMakerMode.set(true)
|
||||
}
|
||||
if((db.betaMobileGUI && window.innerWidth <= 800) || import.meta.env.VITE_RISU_LITE === 'TRUE'){
|
||||
initMobileGesture()
|
||||
MobileGUI.set(true)
|
||||
}
|
||||
loadedStore.set(true)
|
||||
selectedCharID.set(-1)
|
||||
startObserveDom()
|
||||
saveDb()
|
||||
saveDb()
|
||||
if(import.meta.env.VITE_RISU_TOS === 'TRUE'){
|
||||
alertTOS().then((a) => {
|
||||
if(a === false){
|
||||
location.reload()
|
||||
}
|
||||
})
|
||||
}
|
||||
} catch (error) {
|
||||
alertError(`${error}`)
|
||||
}
|
||||
@@ -639,6 +652,7 @@ const knownHostes = ["localhost", "127.0.0.1", "0.0.0.0"];
|
||||
*/
|
||||
interface GlobalFetchArgs {
|
||||
plainFetchForce?: boolean;
|
||||
plainFetchDeforce?: boolean;
|
||||
body?: any;
|
||||
headers?: { [key: string]: string };
|
||||
rawResponse?: boolean;
|
||||
@@ -712,8 +726,8 @@ export async function globalFetch(url: string, arg: GlobalFetchArgs = {}): Promi
|
||||
|
||||
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;
|
||||
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.' };
|
||||
@@ -802,41 +816,19 @@ async function fetchWithPlainFetch(url: string, arg: GlobalFetchArgs): Promise<G
|
||||
* @returns {Promise<GlobalFetchResult>} - The result of the fetch request.
|
||||
*/
|
||||
async function fetchWithTauri(url: string, arg: GlobalFetchArgs): Promise<GlobalFetchResult> {
|
||||
const body = !arg.body ? null : arg.body instanceof URLSearchParams ? Body.text(arg.body.toString()) : Body.json(arg.body);
|
||||
const headers = arg.headers ?? {};
|
||||
const fetchPromise = TauriFetch(url, {
|
||||
body,
|
||||
method: arg.method ?? 'POST',
|
||||
headers,
|
||||
timeout: { secs: get(DataBase).timeOut, nanos: 0 },
|
||||
responseType: arg.rawResponse ? ResponseType.Binary : ResponseType.JSON,
|
||||
});
|
||||
|
||||
let abortFn = () => {};
|
||||
const abortPromise = new Promise<"aborted">((res, rej) => {
|
||||
abortFn = () => res("aborted");
|
||||
arg.abortSignal?.addEventListener('abort', abortFn);
|
||||
});
|
||||
|
||||
const result = await Promise.any([fetchPromise, abortPromise]);
|
||||
arg.abortSignal?.removeEventListener('abort', abortFn);
|
||||
|
||||
if (result === 'aborted') {
|
||||
return { ok: false, data: 'aborted', headers: {} };
|
||||
}
|
||||
|
||||
const data = arg.rawResponse ? new Uint8Array(result.data as number[]) : result.data;
|
||||
addFetchLogInGlobalFetch(data, result.ok, url, arg);
|
||||
return { ok: result.ok, data, headers: result.headers };
|
||||
try {
|
||||
const headers = { 'Content-Type': 'application/json', ...arg.headers };
|
||||
const response = await TauriHTTPFetch(new URL(url), { body: JSON.stringify(arg.body), headers, method: arg.method ?? "POST", signal: arg.abortSignal });
|
||||
const data = arg.rawResponse ? new Uint8Array(await response.arrayBuffer()) : await response.json();
|
||||
const ok = response.status >= 200 && response.status < 300;
|
||||
addFetchLogInGlobalFetch(data, ok, url, arg);
|
||||
return { ok, data, headers: Object.fromEntries(response.headers) };
|
||||
} catch (error) {
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Performs a fetch request using Capacitor.
|
||||
*
|
||||
* @param {string} url - The URL to fetch.
|
||||
* @param {GlobalFetchArgs} arg - The arguments for the fetch request.
|
||||
* @returns {Promise<GlobalFetchResult>} - The result of the fetch request.
|
||||
*/
|
||||
// Decoupled globalFetch built-in function
|
||||
async function fetchWithCapacitor(url: string, arg: GlobalFetchArgs): Promise<GlobalFetchResult> {
|
||||
const { body, headers = {}, rawResponse } = arg;
|
||||
headers["Content-Type"] = body instanceof URLSearchParams ? "application/x-www-form-urlencoded" : "application/json";
|
||||
@@ -992,7 +984,18 @@ export function getUnpargeables(db: Database, uptype: 'basename' | 'pure' = 'bas
|
||||
}
|
||||
}
|
||||
|
||||
if (db.personas) {
|
||||
if(db.modules){
|
||||
for(const module of db.modules){
|
||||
const assets = module.assets
|
||||
if(assets){
|
||||
for(const asset of assets){
|
||||
addUnparge(asset[1])
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if(db.personas){
|
||||
db.personas.map((v) => {
|
||||
addUnparge(v.icon);
|
||||
});
|
||||
@@ -1280,13 +1283,13 @@ async function pargeChunks(){
|
||||
|
||||
const unpargeable = getUnpargeables(db)
|
||||
if(isTauri){
|
||||
const assets = await readDir('assets', {dir: BaseDirectory.AppData})
|
||||
const assets = await readDir('assets', {baseDir: BaseDirectory.AppData})
|
||||
for(const asset of assets){
|
||||
const n = getBasename(asset.name)
|
||||
if(unpargeable.includes(n)){
|
||||
}
|
||||
else{
|
||||
await removeFile(asset.path)
|
||||
await remove(asset.name, {baseDir: BaseDirectory.AppData})
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1401,7 +1404,7 @@ export class TauriWriter{
|
||||
* @param {Uint8Array} data - The data to write.
|
||||
*/
|
||||
async write(data:Uint8Array) {
|
||||
await writeBinaryFile(this.path, data, {
|
||||
await writeFile(this.path, data, {
|
||||
append: !this.firstWrite
|
||||
})
|
||||
this.firstWrite = false
|
||||
@@ -1864,7 +1867,7 @@ export async function fetchNative(url:string, arg:{
|
||||
const data = nativeFetchData[fetchId].shift()
|
||||
if(data.type === 'chunk'){
|
||||
const chunk = Buffer.from(data.body, 'base64')
|
||||
controller.enqueue(chunk)
|
||||
controller.enqueue(chunk as unknown as Uint8Array)
|
||||
}
|
||||
if(data.type === 'headers'){
|
||||
resHeaders = data.body
|
||||
@@ -2040,7 +2043,7 @@ export class BlankWriter{
|
||||
|
||||
export async function loadInternalBackup(){
|
||||
|
||||
const keys = isTauri ? (await readDir('database', {dir: BaseDirectory.AppData})).map((v) => {
|
||||
const keys = isTauri ? (await readDir('database', {baseDir: BaseDirectory.AppData})).map((v) => {
|
||||
return v.name
|
||||
}) : (await forageStorage.keys())
|
||||
let internalBackups:string[] = []
|
||||
@@ -2068,11 +2071,11 @@ export async function loadInternalBackup(){
|
||||
const selectedBackup = internalBackups[alertResult]
|
||||
|
||||
const data = isTauri ? (
|
||||
await readBinaryFile('database/' + selectedBackup, {dir: BaseDirectory.AppData})
|
||||
await readFile('database/' + selectedBackup, {baseDir: BaseDirectory.AppData})
|
||||
) : (await forageStorage.getItem(selectedBackup))
|
||||
|
||||
setDatabase(
|
||||
decodeRisuSave(data)
|
||||
decodeRisuSave(Buffer.from(data) as unknown as Uint8Array)
|
||||
)
|
||||
|
||||
await alertNormal('Loaded backup')
|
||||
|
||||
@@ -26,9 +26,12 @@ export const ViewBoxsize = writable({ width: 12 * 16, height: 12 * 16 }); // Def
|
||||
export const settingsOpen = writable(false)
|
||||
export const botMakerMode = writable(false)
|
||||
export const moduleBackgroundEmbedding = writable('')
|
||||
|
||||
export const openPresetList = writable(false)
|
||||
export const openPersonaList = writable(false)
|
||||
export const MobileGUI = writable(false)
|
||||
export const MobileGUIStack = writable(0)
|
||||
export const MobileSideBar = writable(0)
|
||||
//optimization
|
||||
|
||||
export const CurrentCharacter = writable(null) as Writable<character | groupChat>
|
||||
export const CurrentSimpleCharacter = writable(null) as Writable<simpleCharacterArgument>
|
||||
export const CurrentChat = writable(null) as Writable<Chat>
|
||||
@@ -37,18 +40,37 @@ export const CurrentUserIcon = writable('') as Writable<string>
|
||||
export const CurrentShowMemoryLimit = writable(false) as Writable<boolean>
|
||||
export const ShowVN = writable(false)
|
||||
export const SettingsMenuIndex = writable(-1)
|
||||
export const CurrentVariablePointer = writable({} as {[key:string]: string|number|boolean})
|
||||
export const ReloadGUIPointer = writable(0)
|
||||
export const OpenRealmStore = writable(false)
|
||||
export const ShowRealmFrameStore = writable('')
|
||||
export const PlaygroundStore = writable(0)
|
||||
export const HideIconStore = writable(false)
|
||||
export const UserIconProtrait = writable(false)
|
||||
export const CustomCSSStore = writable('')
|
||||
export const SafeModeStore = writable(false)
|
||||
export const MobileSearch = writable('')
|
||||
export const CharConfigSubMenu = writable(0)
|
||||
|
||||
let lastGlobalEnabledModules: string[] = []
|
||||
let lastChatEnabledModules: string[] = []
|
||||
let moduleHideIcon = false
|
||||
let characterHideIcon = false
|
||||
|
||||
|
||||
CustomCSSStore.subscribe((css) => {
|
||||
console.log(css)
|
||||
const q = document.querySelector('#customcss')
|
||||
if(q){
|
||||
q.innerHTML = css
|
||||
}
|
||||
else{
|
||||
const s = document.createElement('style')
|
||||
s.id = 'customcss'
|
||||
s.innerHTML = css
|
||||
document.body.appendChild(s)
|
||||
}
|
||||
})
|
||||
|
||||
function createSimpleCharacter(char:character|groupChat){
|
||||
if((!char) || char.type === 'group'){
|
||||
return null
|
||||
@@ -208,13 +230,6 @@ async function preInit(){
|
||||
if(getUserIcon() !== get(CurrentUserIcon)){
|
||||
CurrentUserIcon.set(getUserIcon())
|
||||
}
|
||||
|
||||
const variablePointer = get(CurrentVariablePointer)
|
||||
const currentState = structuredClone(chat?.scriptstate)
|
||||
|
||||
if(!isEqual(variablePointer, currentState)){
|
||||
CurrentVariablePointer.set(currentState)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
@@ -226,9 +241,7 @@ function onModuleUpdate(){
|
||||
lastChatEnabledModules = []
|
||||
}
|
||||
|
||||
const m = getModules([
|
||||
...lastGlobalEnabledModules, ...lastChatEnabledModules
|
||||
])
|
||||
const m = getModules()
|
||||
|
||||
let moduleHideIcon = false
|
||||
let backgroundEmbedding = ''
|
||||
|
||||
@@ -1,23 +1,65 @@
|
||||
import { v4 } from 'uuid';
|
||||
import { alertError, alertInput, alertNormal, alertWait } from '../alert';
|
||||
import { get } from 'svelte/store';
|
||||
import { DataBase, setDatabase, type character, saveImage } from '../storage/database';
|
||||
import { selectedCharID } from '../stores';
|
||||
import { alertError, alertInput, alertNormal, alertStore, alertWait } from '../alert';
|
||||
import { get, writable } from 'svelte/store';
|
||||
import { DataBase, setDatabase, type character, saveImage, type Chat } from '../storage/database';
|
||||
import { CurrentChat, selectedCharID } from '../stores';
|
||||
import { findCharacterIndexbyId, sleep } from '../util';
|
||||
import type { DataConnection, Peer } from 'peerjs';
|
||||
import { readImage } from '../storage/globalApi';
|
||||
import { doingChat } from '../process';
|
||||
|
||||
async function importPeerJS(){
|
||||
return await import('peerjs');
|
||||
}
|
||||
|
||||
interface ReciveFirst{
|
||||
type: 'receive-char',
|
||||
data: character
|
||||
}
|
||||
interface RequestFirst{
|
||||
type: 'request-char'
|
||||
}
|
||||
interface ReciveAsset{
|
||||
type: 'receive-asset',
|
||||
id: string,
|
||||
data: Uint8Array
|
||||
}
|
||||
interface RequestSync{
|
||||
type: 'request-chat-sync',
|
||||
id: string,
|
||||
data: Chat
|
||||
}
|
||||
interface ReciveSync{
|
||||
type: 'receive-chat',
|
||||
data: Chat
|
||||
}
|
||||
interface RequestChatSafe{
|
||||
type: 'request-chat-safe',
|
||||
id: string
|
||||
}
|
||||
interface ResponseChatSafe{
|
||||
type: 'response-chat-safe'
|
||||
data: boolean,
|
||||
id: string
|
||||
}
|
||||
interface RequestChat{
|
||||
type: 'request-chat'
|
||||
}
|
||||
|
||||
type ReciveData = ReciveFirst|RequestFirst|ReciveAsset|RequestSync|ReciveSync|RequestChatSafe|ResponseChatSafe|RequestChat
|
||||
|
||||
let conn:DataConnection
|
||||
let peer:Peer
|
||||
let connections:DataConnection[] = []
|
||||
let connectionOpen = false
|
||||
export let connectionOpen = false
|
||||
let requestChatSafeQueue = new Map<string, {remaining:number,safe:boolean,conn?:DataConnection}>()
|
||||
export let ConnectionOpenStore = writable(false)
|
||||
export let ConnectionIsHost = writable(false)
|
||||
export let RoomIdStore = writable('')
|
||||
|
||||
export async function createMultiuserRoom(){
|
||||
//create a room with webrtc
|
||||
ConnectionIsHost.set(true)
|
||||
alertWait("Loading...")
|
||||
|
||||
const peerJS = await importPeerJS();
|
||||
@@ -93,7 +135,96 @@ export async function createMultiuserRoom(){
|
||||
return
|
||||
}
|
||||
char.chats[char.chatPage] = recivedChar.chats[0]
|
||||
sendPeerChar()
|
||||
}
|
||||
if(data.type === 'request-chat-sync'){
|
||||
const db = get(DataBase)
|
||||
const selectedCharId = get(selectedCharID)
|
||||
const char = db.characters[selectedCharId]
|
||||
char.chats[char.chatPage] = data.data
|
||||
db.characters[selectedCharId] = char
|
||||
latestSyncChat = data.data
|
||||
setDatabase(db)
|
||||
|
||||
for(const connection of connections){
|
||||
if(connection.connectionId === conn.connectionId){
|
||||
continue
|
||||
}
|
||||
const rs:ReciveSync = {
|
||||
type: 'receive-chat',
|
||||
data: data.data
|
||||
}
|
||||
connection.send(rs)
|
||||
}
|
||||
}
|
||||
if(data.type === 'request-chat'){
|
||||
const db = get(DataBase)
|
||||
const selectedCharId = get(selectedCharID)
|
||||
const char = db.characters[selectedCharId]
|
||||
const chat = char.chats[char.chatPage]
|
||||
const rs:ReciveSync = {
|
||||
type: 'receive-chat',
|
||||
data: chat
|
||||
}
|
||||
conn.send(rs)
|
||||
}
|
||||
if(data.type === 'request-chat-safe'){
|
||||
const queue = {
|
||||
remaining: connections.length,
|
||||
safe: true,
|
||||
conn: conn
|
||||
}
|
||||
requestChatSafeQueue.set(data.id, queue)
|
||||
for(const connection of connections){
|
||||
if(connection.connectionId === conn.connectionId){
|
||||
queue.remaining--
|
||||
requestChatSafeQueue.set(data.id, queue)
|
||||
continue
|
||||
}
|
||||
const rs:RequestChatSafe = {
|
||||
type: 'request-chat-safe',
|
||||
id: data.id
|
||||
}
|
||||
connection.send(rs)
|
||||
}
|
||||
if(queue.remaining === 0){
|
||||
if(waitingMultiuserId === data.id){
|
||||
waitingMultiuserId = ''
|
||||
waitingMultiuserSafe = queue.safe
|
||||
}
|
||||
else if(queue.conn){
|
||||
const rs:ResponseChatSafe = {
|
||||
type: 'response-chat-safe',
|
||||
data: queue.safe,
|
||||
id: data.id
|
||||
}
|
||||
queue.conn.send(rs)
|
||||
requestChatSafeQueue.delete(data.id)
|
||||
}
|
||||
}
|
||||
}
|
||||
if(data.type === 'response-chat-safe'){
|
||||
const queue = requestChatSafeQueue.get(data.id)
|
||||
if(queue){
|
||||
queue.remaining--
|
||||
if(!data.data){
|
||||
queue.safe = false
|
||||
}
|
||||
if(queue.remaining === 0){
|
||||
if(waitingMultiuserId === data.id){
|
||||
waitingMultiuserId = ''
|
||||
waitingMultiuserSafe = queue.safe
|
||||
}
|
||||
else if(queue.conn){
|
||||
const rs:ResponseChatSafe = {
|
||||
type: 'response-chat-safe',
|
||||
data: queue.safe,
|
||||
id: data.id
|
||||
}
|
||||
queue.conn.send(rs)
|
||||
requestChatSafeQueue.delete(data.id)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
@@ -111,28 +242,23 @@ export async function createMultiuserRoom(){
|
||||
}
|
||||
|
||||
connectionOpen = true
|
||||
alertNormal("Room ID: " + roomId)
|
||||
ConnectionOpenStore.set(true)
|
||||
RoomIdStore.set(roomId)
|
||||
alertStore.set({
|
||||
type: 'none',
|
||||
msg: ''
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
interface ReciveFirst{
|
||||
type: 'receive-char',
|
||||
data: character
|
||||
}
|
||||
interface RequestFirst{
|
||||
type: 'request-char'
|
||||
}
|
||||
interface ReciveAsset{
|
||||
type: 'receive-asset',
|
||||
id: string,
|
||||
data: Uint8Array
|
||||
}
|
||||
|
||||
type ReciveData = ReciveFirst|RequestFirst|ReciveAsset
|
||||
let waitingMultiuserId = ''
|
||||
let waitingMultiuserSafe = false
|
||||
let latestSyncChat:Chat|null = null
|
||||
|
||||
export async function joinMultiuserRoom(){
|
||||
|
||||
//join a room with webrtc
|
||||
ConnectionIsHost.set(false)
|
||||
alertWait("Loading...")
|
||||
const peerJS = await importPeerJS();
|
||||
peer = new peerJS.Peer(
|
||||
@@ -145,6 +271,7 @@ export async function joinMultiuserRoom(){
|
||||
|
||||
let open = false
|
||||
conn = peer.connect(roomId);
|
||||
RoomIdStore.set(roomId)
|
||||
|
||||
conn.on('open', function() {
|
||||
alertWait("Waiting for host to accept connection")
|
||||
@@ -179,6 +306,32 @@ export async function joinMultiuserRoom(){
|
||||
}
|
||||
case 'receive-asset':{
|
||||
saveImage(data.data, data.id)
|
||||
break
|
||||
}
|
||||
case 'receive-chat':{
|
||||
const db = get(DataBase)
|
||||
const selectedCharId = get(selectedCharID)
|
||||
const char = structuredClone(db.characters[selectedCharId])
|
||||
char.chats[char.chatPage] = data.data
|
||||
db.characters[selectedCharId] = char
|
||||
latestSyncChat = data.data
|
||||
setDatabase(db)
|
||||
break
|
||||
}
|
||||
case 'request-chat-safe':{
|
||||
const rs:ResponseChatSafe = {
|
||||
type: 'response-chat-safe',
|
||||
data: !get(doingChat) || data.id === waitingMultiuserId,
|
||||
id: data.id
|
||||
}
|
||||
conn.send(rs)
|
||||
break
|
||||
}
|
||||
case 'response-chat-safe':{
|
||||
if(data.id === waitingMultiuserId){
|
||||
waitingMultiuserId = ''
|
||||
waitingMultiuserSafe = data.data
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
@@ -186,6 +339,7 @@ export async function joinMultiuserRoom(){
|
||||
conn.on('close', function() {
|
||||
alertError("Connection closed")
|
||||
connectionOpen = false
|
||||
ConnectionOpenStore.set(false)
|
||||
selectedCharID.set(-1)
|
||||
})
|
||||
|
||||
@@ -199,29 +353,78 @@ export async function joinMultiuserRoom(){
|
||||
}
|
||||
}
|
||||
connectionOpen = true
|
||||
ConnectionOpenStore.set(true)
|
||||
alertNormal("Connected")
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
|
||||
export function sendPeerChar(){
|
||||
export async function peerSync(){
|
||||
if(!connectionOpen){
|
||||
return
|
||||
}
|
||||
await sleep(1)
|
||||
const chat = get(CurrentChat)
|
||||
latestSyncChat = chat
|
||||
if(!conn){
|
||||
// host user
|
||||
for(const connection of connections){
|
||||
connection.send({
|
||||
type: 'receive-char',
|
||||
data: get(DataBase).characters[get(selectedCharID)]
|
||||
type: 'receive-chat',
|
||||
data: chat
|
||||
});
|
||||
}
|
||||
}
|
||||
else{
|
||||
conn.send({
|
||||
type: 'receive-char',
|
||||
data: get(DataBase).characters[get(selectedCharID)]
|
||||
});
|
||||
type: 'request-chat-sync',
|
||||
data: chat
|
||||
} as RequestSync)
|
||||
}
|
||||
}
|
||||
|
||||
export async function peerSafeCheck() {
|
||||
if(!connectionOpen){
|
||||
return true
|
||||
}
|
||||
await sleep(500)
|
||||
if(!conn){
|
||||
waitingMultiuserId = v4()
|
||||
requestChatSafeQueue.set(waitingMultiuserId, {
|
||||
remaining: connections.length,
|
||||
safe: true,
|
||||
})
|
||||
for(const connection of connections){
|
||||
const rs:RequestChatSafe = {
|
||||
type: 'request-chat-safe',
|
||||
id: waitingMultiuserId
|
||||
}
|
||||
connection.send(rs)
|
||||
}
|
||||
while(waitingMultiuserId !== ''){
|
||||
await sleep(100)
|
||||
}
|
||||
return waitingMultiuserSafe
|
||||
}
|
||||
else{
|
||||
waitingMultiuserId = v4()
|
||||
const rs:RequestChatSafe = {
|
||||
type: 'request-chat-safe',
|
||||
id: waitingMultiuserId
|
||||
}
|
||||
conn.send(rs)
|
||||
while(waitingMultiuserId !== ''){
|
||||
await sleep(100)
|
||||
}
|
||||
return waitingMultiuserSafe
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
export function peerRevertChat() {
|
||||
if(!connectionOpen || !latestSyncChat){
|
||||
return
|
||||
}
|
||||
CurrentChat.set(latestSyncChat)
|
||||
}
|
||||
@@ -1,12 +1,13 @@
|
||||
import type { Tiktoken } from "@dqbd/tiktoken";
|
||||
import type { Tokenizer } from "@mlc-ai/web-tokenizers";
|
||||
import { DataBase, type character } from "./storage/database";
|
||||
import { DataBase, type groupChat, type character, type Chat } from "./storage/database";
|
||||
import { get } from "svelte/store";
|
||||
import type { MultiModal, OpenAIChat } from "./process";
|
||||
import { supportsInlayImage } from "./process/files/image";
|
||||
import { risuChatParser } from "./parser";
|
||||
import { tokenizeGGUFModel } from "./process/models/local";
|
||||
import { globalFetch } from "./storage/globalApi";
|
||||
import { CurrentCharacter } from "./stores";
|
||||
|
||||
|
||||
export const tokenizerList = [
|
||||
@@ -18,6 +19,7 @@ export const tokenizerList = [
|
||||
['llama3', 'Llama3'],
|
||||
['novellist', 'Novellist'],
|
||||
['gemma', 'Gemma'],
|
||||
['cohere', 'Cohere'],
|
||||
] as const
|
||||
|
||||
export async function encode(data:string):Promise<(number[]|Uint32Array|Int32Array)>{
|
||||
@@ -38,6 +40,8 @@ export async function encode(data:string):Promise<(number[]|Uint32Array|Int32Arr
|
||||
return await tokenizeWebTokenizers(data, 'llama')
|
||||
case 'gemma':
|
||||
return await tokenizeWebTokenizers(data, 'gemma')
|
||||
case 'cohere':
|
||||
return await tokenizeWebTokenizers(data, 'cohere')
|
||||
default:
|
||||
// Add exception for gpt-4o tokenizers on reverse_proxy
|
||||
if(db.proxyRequestModel?.startsWith('gpt4o') ||
|
||||
@@ -342,4 +346,57 @@ export async function strongBan(data:string, bias:{[key:number]:number}) {
|
||||
}
|
||||
localStorage.setItem('strongBan_' + data, JSON.stringify(bias))
|
||||
return bias
|
||||
}
|
||||
|
||||
export async function getCharToken(char?:character|groupChat|null){
|
||||
let persistant = 0
|
||||
let dynamic = 0
|
||||
|
||||
if(!char){
|
||||
const c = get(CurrentCharacter)
|
||||
char = c
|
||||
}
|
||||
if(char.type === 'group'){
|
||||
return {persistant:0, dynamic:0}
|
||||
}
|
||||
|
||||
const basicTokenize = async (data:string) => {
|
||||
data = data.replace(/{{char}}/g, char.name).replace(/<char>/g, char.name)
|
||||
return await tokenize(data)
|
||||
}
|
||||
|
||||
persistant += await basicTokenize(char.desc)
|
||||
persistant += await basicTokenize(char.personality ?? '')
|
||||
persistant += await basicTokenize(char.scenario ?? '')
|
||||
for(const lore of char.globalLore){
|
||||
let cont = lore.content.split('\n').filter((line) => {
|
||||
if(line.startsWith('@@')){
|
||||
return false
|
||||
}
|
||||
if(line === ''){
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}).join('\n')
|
||||
dynamic += await basicTokenize(cont)
|
||||
}
|
||||
|
||||
return {persistant, dynamic}
|
||||
}
|
||||
|
||||
export async function getChatToken(chat:Chat) {
|
||||
let persistant = 0
|
||||
|
||||
const chatTokenizer = new ChatTokenizer(0, 'name')
|
||||
const chatf = chat.message.map((d) => {
|
||||
return {
|
||||
role: d.role === 'user' ? 'user' : 'assistant',
|
||||
content: d.data,
|
||||
} as OpenAIChat
|
||||
})
|
||||
for(const chat of chatf){
|
||||
persistant += await chatTokenizer.tokenizeChat(chat)
|
||||
}
|
||||
|
||||
return persistant
|
||||
}
|
||||
@@ -165,7 +165,7 @@ async function translateMain(text:string, arg:{from:string, to:string, host:stri
|
||||
}
|
||||
|
||||
|
||||
const url = `https://${arg.host}/translate_a/single?client=gtx&dt=t&sl=${arg.from}&tl=${arg.to}&q=` + encodeURIComponent(text)
|
||||
const url = `https://${arg.host}/translate_a/single?client=gtx&dt=t&sl=${db.translatorInputLanguage}&tl=${arg.to}&q=` + encodeURIComponent(text)
|
||||
|
||||
|
||||
|
||||
@@ -239,7 +239,7 @@ export async function translateHTML(html: string, reverse:boolean, charArg:simpl
|
||||
return html
|
||||
}
|
||||
}
|
||||
if(db.translatorType === 'llm' && (!(isTauri || Capacitor.isNativePlatform()))){
|
||||
if(db.translatorType === 'llm'){
|
||||
const tr = db.translator || 'en'
|
||||
return translateLLM(html, {to: tr})
|
||||
}
|
||||
@@ -456,14 +456,23 @@ async function translateLLM(text:string, arg:{to:string}){
|
||||
})
|
||||
|
||||
const db = get(DataBase)
|
||||
const charIndex = get(selectedCharID)
|
||||
const currentChar = db.characters[charIndex]
|
||||
let translatorNote
|
||||
if (currentChar.type === "character") {
|
||||
translatorNote = currentChar.translatorNote ?? ""
|
||||
} else {
|
||||
translatorNote = ""
|
||||
}
|
||||
|
||||
let formated:OpenAIChat[] = []
|
||||
let prompt = db.translatorPrompt || `You are a translator. translate the following html or text into {{slot}}. do not output anything other than the translation.`
|
||||
let parsedPrompt = parseChatML(prompt.replaceAll('{{slot}}', arg.to).replaceAll('{{solt::content}}', text))
|
||||
let parsedPrompt = parseChatML(prompt.replaceAll('{{slot}}', arg.to).replaceAll('{{solt::content}}', text).replaceAll('{{slot::tnote}}', translatorNote))
|
||||
if(parsedPrompt){
|
||||
formated = parsedPrompt
|
||||
}
|
||||
else{
|
||||
prompt = prompt.replaceAll('{{slot}}', arg.to)
|
||||
prompt = prompt.replaceAll('{{slot}}', arg.to).replaceAll('{{slot::tnote}}', translatorNote)
|
||||
formated = [
|
||||
{
|
||||
'role': 'system',
|
||||
|
||||
@@ -2,10 +2,9 @@ import { alertConfirm, alertWait } from "./alert";
|
||||
import { language } from "../lang";
|
||||
import { Capacitor } from "@capacitor/core";
|
||||
import {
|
||||
checkUpdate,
|
||||
installUpdate,
|
||||
} from '@tauri-apps/api/updater'
|
||||
import { relaunch } from '@tauri-apps/api/process'
|
||||
check,
|
||||
} from '@tauri-apps/plugin-updater'
|
||||
import { relaunch } from '@tauri-apps/plugin-process'
|
||||
|
||||
export async function checkRisuUpdate(){
|
||||
|
||||
@@ -14,12 +13,12 @@ export async function checkRisuUpdate(){
|
||||
}
|
||||
|
||||
try {
|
||||
const checked = await checkUpdate()
|
||||
if(checked.shouldUpdate){
|
||||
const checked = await check()
|
||||
if(checked){
|
||||
const conf = await alertConfirm(language.newVersion)
|
||||
if(conf){
|
||||
alertWait(`Updating to ${checked.manifest.version}...`)
|
||||
await installUpdate()
|
||||
alertWait(`Updating to ${checked.version}...`)
|
||||
await checked.downloadAndInstall()
|
||||
await relaunch()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,12 +2,13 @@ import { get, writable, type Writable } from "svelte/store"
|
||||
import type { Database, Message } from "./storage/database"
|
||||
import { DataBase } from "./storage/database"
|
||||
import { selectedCharID } from "./stores"
|
||||
import {open} from '@tauri-apps/api/dialog'
|
||||
import { readBinaryFile } from "@tauri-apps/api/fs"
|
||||
import {open} from '@tauri-apps/plugin-dialog'
|
||||
import { readFile } from "@tauri-apps/plugin-fs"
|
||||
import { basename } from "@tauri-apps/api/path"
|
||||
import { createBlankChar, getCharImage } from "./characters"
|
||||
import { appWindow } from '@tauri-apps/api/window';
|
||||
import { getCurrentWebviewWindow } from '@tauri-apps/api/webviewWindow';
|
||||
import { isTauri } from "./storage/globalApi"
|
||||
const appWindow = isTauri ? getCurrentWebviewWindow() : null
|
||||
|
||||
export const isFirefox = navigator.userAgent.toLowerCase().indexOf('firefox') > -1
|
||||
|
||||
@@ -64,7 +65,7 @@ export async function selectSingleFile(ext:string[]){
|
||||
} else if (selected === null) {
|
||||
return null
|
||||
} else {
|
||||
return {name: await basename(selected),data:await readBinaryFile(selected)}
|
||||
return {name: await basename(selected),data:await readFile(selected)}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -88,13 +89,13 @@ export async function selectMultipleFile(ext:string[]){
|
||||
if (Array.isArray(selected)) {
|
||||
let arr:{name:string, data:Uint8Array}[] = []
|
||||
for(const file of selected){
|
||||
arr.push({name: await basename(file),data:await readBinaryFile(file)})
|
||||
arr.push({name: await basename(file),data:await readFile(file)})
|
||||
}
|
||||
return arr
|
||||
} else if (selected === null) {
|
||||
return null
|
||||
} else {
|
||||
return [{name: await basename(selected),data:await readBinaryFile(selected)}]
|
||||
return [{name: await basename(selected),data:await readFile(selected)}]
|
||||
}
|
||||
}
|
||||
|
||||
@@ -471,13 +472,17 @@ export function parseMultilangString(data:string){
|
||||
}
|
||||
|
||||
export const toLangName = (code:string) => {
|
||||
switch(code){
|
||||
case 'xx':{ //Special case for unknown language
|
||||
return 'Unknown Language'
|
||||
}
|
||||
default:{
|
||||
return new Intl.DisplayNames([code, 'en'], {type: 'language'}).of(code)
|
||||
}
|
||||
try {
|
||||
switch(code){
|
||||
case 'xx':{ //Special case for unknown language
|
||||
return 'Unknown Language'
|
||||
}
|
||||
default:{
|
||||
return new Intl.DisplayNames([code, 'en'], {type: 'language'}).of(code)
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
return code
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user