Merge branch 'kwaroran:main' into main

This commit is contained in:
HyperBlaze
2024-12-26 23:53:59 -08:00
committed by GitHub
36 changed files with 837252 additions and 1545 deletions

View File

@@ -16,7 +16,7 @@ import {open} from '@tauri-apps/plugin-shell'
import { setDatabase, type Database, defaultSdDataFunc, getDatabase, type character } from "./storage/database.svelte";
import { getCurrentWebviewWindow } from "@tauri-apps/api/webviewWindow";
import { checkRisuUpdate } from "./update";
import { MobileGUI, botMakerMode, selectedCharID, loadedStore, DBState } from "./stores.svelte";
import { MobileGUI, botMakerMode, selectedCharID, loadedStore, DBState, LoadingStatusState } from "./stores.svelte";
import { loadPlugins } from "./plugins/plugins";
import { alertConfirm, alertError, alertNormal, alertNormalWait, alertSelect, alertTOS, alertWait } from "./alert";
import { checkDriverInit, syncDrive } from "./drive/drive";
@@ -42,6 +42,7 @@ import { updateLorebooks } from "./characters";
import { initMobileGesture } from "./hotkey";
import { fetch as TauriHTTPFetch } from '@tauri-apps/plugin-http';
import { moduleUpdate } from "./process/modules";
import type { AccountStorage } from "./storage/accountStorage";
//@ts-ignore
export const isTauri = !!window.__TAURI_INTERNALS__
@@ -302,6 +303,9 @@ export async function loadAsset(id:string){
}
let lastSave = ''
export let saving = $state({
state: false
})
/**
* Saves the current state of the database.
@@ -335,6 +339,7 @@ export async function saveDb(){
$effect.root(() => {
let selIdState = $state(0)
let oldSaveHash = ''
selectedCharID.subscribe((v) => {
selIdState = v
@@ -347,6 +352,7 @@ export async function saveDb(){
$state.snapshot(DBState.db[key])
}
}
changed = true
})
})
@@ -356,10 +362,11 @@ export async function saveDb(){
await sleep(1000)
while(true){
if(!changed){
await sleep(1000)
await sleep(500)
continue
}
saving.state = true
changed = false
try {
@@ -430,6 +437,8 @@ export async function saveDb(){
console.error(error)
}
}
saving.state = false
}
}
@@ -490,6 +499,7 @@ export async function loadData() {
if(!loaded){
try {
if(isTauri){
LoadingStatusState.text = "Checking Files..."
appWindow.maximize()
if(!await exists('', {baseDir: BaseDirectory.AppData})){
await mkdir('', {baseDir: BaseDirectory.AppData})
@@ -504,13 +514,18 @@ export async function loadData() {
await writeFile('database/database.bin', encodeRisuSaveLegacy({}), {baseDir: BaseDirectory.AppData});
}
try {
const decoded = await decodeRisuSave(await readFile('database/database.bin',{baseDir: BaseDirectory.AppData}))
LoadingStatusState.text = "Reading Save File..."
const readed = await readFile('database/database.bin',{baseDir: BaseDirectory.AppData})
LoadingStatusState.text = "Decoding Save File..."
const decoded = await decodeRisuSave(readed)
setDatabase(decoded)
} catch (error) {
LoadingStatusState.text = "Reading Backup Files..."
const backups = await getDbBackups()
let backupLoaded = false
for(const backup of backups){
try {
LoadingStatusState.text = `Reading Backup File ${backup}...`
const backupData = await readFile(`database/dbbackup-${backup}.bin`,{baseDir: BaseDirectory.AppData})
setDatabase(
await decodeRisuSave(backupData)
@@ -524,39 +539,19 @@ export async function loadData() {
throw "Your save file is corrupted"
}
}
LoadingStatusState.text = "Checking Update..."
await checkRisuUpdate()
await changeFullscreen()
}
else{
let gotStorage:Uint8Array = await forageStorage.getItem('database/database.bin') as unknown as Uint8Array
if(checkNullish(gotStorage)){
gotStorage = encodeRisuSaveLegacy({})
await forageStorage.setItem('database/database.bin', gotStorage)
}
try {
const decoded = await decodeRisuSave(gotStorage)
console.log(decoded)
setDatabase(decoded)
} catch (error) {
console.error(error)
const backups = await getDbBackups()
let backupLoaded = false
for(const backup of backups){
try {
const backupData:Uint8Array = await forageStorage.getItem(`database/dbbackup-${backup}.bin`) as unknown as Uint8Array
setDatabase(
await decodeRisuSave(backupData)
)
backupLoaded = true
} catch (error) {}
}
if(!backupLoaded){
throw "Your save file is corrupted"
}
}
await forageStorage.Init()
if(await forageStorage.checkAccountSync()){
let gotStorage:Uint8Array = await forageStorage.getItem('database/database.bin') as unknown as Uint8Array
LoadingStatusState.text = "Checking Account Sync..."
let gotStorage:Uint8Array = await (forageStorage.realStorage as AccountStorage).getItem('database/database.bin', (v) => {
LoadingStatusState.text = `Loading Save File ${(v*100).toFixed(2)}%`
})
if(checkNullish(gotStorage)){
gotStorage = encodeRisuSaveLegacy({})
await forageStorage.setItem('database/database.bin', gotStorage)
@@ -570,6 +565,7 @@ export async function loadData() {
let backupLoaded = false
for(const backup of backups){
try {
LoadingStatusState.text = `Reading Backup File ${backup}...`
const backupData:Uint8Array = await forageStorage.getItem(`database/dbbackup-${backup}.bin`) as unknown as Uint8Array
setDatabase(
await decodeRisuSave(backupData)
@@ -582,10 +578,43 @@ export async function loadData() {
}
}
}
else{
LoadingStatusState.text = "Loading Save File..."
let gotStorage:Uint8Array = await forageStorage.getItem('database/database.bin') as unknown as Uint8Array
LoadingStatusState.text = "Decoding Save File..."
if(checkNullish(gotStorage)){
gotStorage = encodeRisuSaveLegacy({})
await forageStorage.setItem('database/database.bin', gotStorage)
}
try {
const decoded = await decodeRisuSave(gotStorage)
console.log(decoded)
setDatabase(decoded)
} catch (error) {
console.error(error)
const backups = await getDbBackups()
let backupLoaded = false
for(const backup of backups){
try {
LoadingStatusState.text = `Reading Backup File ${backup}...`
const backupData:Uint8Array = await forageStorage.getItem(`database/dbbackup-${backup}.bin`) as unknown as Uint8Array
setDatabase(
await decodeRisuSave(backupData)
)
backupLoaded = true
} catch (error) {}
}
if(!backupLoaded){
throw "Your save file is corrupted"
}
}
}
LoadingStatusState.text = "Checking Drive Sync..."
const isDriverMode = await checkDriverInit()
if(isDriverMode){
return
}
LoadingStatusState.text = "Checking Service Worker..."
if(navigator.serviceWorker && (!Capacitor.isNativePlatform())){
usingSw = true
await registerSw()
@@ -597,13 +626,16 @@ export async function loadData() {
characterURLImport()
}
}
LoadingStatusState.text = "Checking Unnecessary Files..."
try {
await pargeChunks()
} catch (error) {}
LoadingStatusState.text = "Loading Plugins..."
try {
await loadPlugins()
} catch (error) {}
if(getDatabase().account){
LoadingStatusState.text = "Checking Account Data..."
try {
await loadRisuAccountData()
} catch (error) {}
@@ -617,8 +649,11 @@ export async function loadData() {
} catch (error) {
}
LoadingStatusState.text = "Checking For Format Update..."
await checkNewFormat()
const db = getDatabase();
LoadingStatusState.text = "Updating States..."
updateColorScheme()
updateTextThemeAndCSS()
updateAnimationSpeed()
@@ -1809,20 +1844,18 @@ const pipeFetchLog = (fetchLogIndex: number, readableStream: ReadableStream<Uint
* @throws {Error} - Throws an error if the request is aborted or if there is an error in the response.
*/
export async function fetchNative(url:string, arg:{
body:string|Uint8Array|ArrayBuffer,
body?:string|Uint8Array|ArrayBuffer,
headers?:{[key:string]:string},
method?:"POST"|"GET"|"PUT"|"DELETE",
signal?:AbortSignal,
useRisuTk?:boolean,
chatId?:string
}):Promise<{
body: ReadableStream<Uint8Array>;
headers: Headers;
status: number;
json: () => Promise<any>;
text: () => Promise<string>;
arrayBuffer: () => Promise<ArrayBuffer>;
}> {
}):Promise<Response> {
console.log(arg.body,'body')
if(arg.body === undefined && (arg.method === 'POST' || arg.method === 'PUT') ){
throw new Error('Body is required for POST and PUT requests')
}
const jsonizer = (body:ReadableStream<Uint8Array>) => {
return async () => {
@@ -1864,9 +1897,9 @@ export async function fetchNative(url:string, arg:{
let realBody:Uint8Array
if(arg.method === 'GET' || arg.method === 'DELETE'){
realBody = new Uint8Array(0)
realBody = undefined
}
if(typeof arg.body === 'string'){
else if(typeof arg.body === 'string'){
realBody = new TextEncoder().encode(arg.body)
}
else if(arg.body instanceof Uint8Array){
@@ -1978,18 +2011,15 @@ export async function fetchNative(url:string, arg:{
throw new Error(error)
}
return {
body: readableStream,
return new Response(readableStream, {
headers: new Headers(resHeaders),
status: status,
json: jsonizer(readableStream),
text: textizer(readableStream),
arrayBuffer: arrayBufferizer(readableStream)
}
status: status
})
}
else if(throughProxy){
const r = await fetch(hubURL + `/proxy2`, {
body: realBody,
headers: arg.useRisuTk ? {
@@ -2006,14 +2036,10 @@ export async function fetchNative(url:string, arg:{
signal: arg.signal
})
return {
body: pipeFetchLog(fetchLogIndex, r.body),
return new Response(r.body, {
headers: r.headers,
status: r.status,
json: jsonizer(r.body),
text: textizer(r.body),
arrayBuffer: arrayBufferizer(r.body)
}
status: r.status
})
}
else{
return await fetch(url, {

View File

@@ -1,5 +1,4 @@
import { runTrigger } from "./process/triggers";
import { runCharacterJS } from "./plugins/embedscript";
import { sleep } from "./util";
import { getCurrentCharacter, getCurrentChat, setCurrentChat } from "./storage/database.svelte";
@@ -36,13 +35,6 @@ function nodeObserve(node:HTMLElement){
}
if(btnEvent){
node.addEventListener('click',async ()=>{
await runCharacterJS({
code: null,
mode: 'onButtonClick',
data: btnEvent
})
}, {passive: true})
node.setAttribute('risu-observer', 'true');
return
}

View File

@@ -2335,9 +2335,17 @@ export function parseChatML(data:string):OpenAIChat[]|null{
v = v.substring(0, v.length - ender.length)
}
let thoughts:string[] = []
v = v.replace(/<Thoughts>(.+)<\/Thoughts>/gms, (match, p1) => {
thoughts.push(p1)
return ''
})
return {
role: role,
content: risuChatParser(v)
content: risuChatParser(v),
thoughts: thoughts
}
})
}

View File

@@ -1,324 +0,0 @@
import { get } from 'svelte/store'
import type { ScriptMode } from '../process/scripts'
//@ts-ignore
import WorkerUrl from './embedworker?worker&url'
import { getDatabase, type Message } from '../storage/database.svelte'
import { selectedCharID } from '../stores.svelte'
import { setDatabase } from '../storage/database.svelte'
let worker = new Worker(WorkerUrl, {type: 'module'})
let additionalCharaJS:string[] = []
export function addAdditionalCharaJS(code:string, position:'front'|'back' = 'back'){
if(position === 'front'){
additionalCharaJS.unshift(code)
}
else{
additionalCharaJS.push(code)
}
}
let results:{
id: string,
result: any
}[] = []
let workerFunctions: {
[key:string]: (...args:any[])=> Promise<any>
} = {}
worker.onmessage = ({data}) => {
if(data.type === 'api'){
workerFunctions[data.name](...data.args).then((result)=>{
worker.postMessage({
type: 'result',
id: data.id,
result
})
})
}
else{
results.push(data)
}
}
function addWorkerFunction(name:string, func: (...args:any[])=> Promise<any>){
workerFunctions[name] = func
worker.postMessage({
type: 'api',
name
})
}
function runVirtualJS(code:string){
const id = `id${Math.random()}`.replace('.','')
worker.postMessage({
id,code
})
let startTime = performance.now()
return new Promise((resolve,reject)=>{
const interval = setInterval(()=>{
const result = results.find(r=>r.id === id)
if(result){
clearInterval(interval)
resolve(result.result)
}
else if(performance.now() - startTime > 800){
clearInterval(interval)
//restart worker
worker.terminate()
worker = new Worker(WorkerUrl, {type: 'module'})
reject('timeout')
}
},10)
})
}
addWorkerFunction('getChat', async () => {
const db = getDatabase({
snapshot: true
})
const selectedChar = get(selectedCharID)
const char = db.characters[selectedChar]
return safeStructuredClone(char.chats[char.chatPage].message)
})
addWorkerFunction('setChat', async (data:Message[]) => {
const db = getDatabase()
const selectedChar = get(selectedCharID)
let newChat:Message[] = []
for(const dat of data){
if(dat.role !== 'char' && dat.role !== 'user'){
return false
}
if(typeof dat.data !== 'string'){
return false
}
if(typeof dat.saying !== 'string' && dat.saying !== null && dat.saying !== undefined){
return false
}
if(typeof dat.time !== 'number' && dat.time !== null && dat.time !== undefined){
return false
}
if(typeof dat.chatId !== 'string' && dat.chatId !== null && dat.chatId !== undefined){
return false
}
newChat.push({
role: dat.role,
data: dat.data,
saying: dat.saying,
time: dat.time,
chatId: dat.chatId
})
}
db.characters[selectedChar].chats[db.characters[selectedChar].chatPage].message = newChat
setDatabase(db)
return true
})
addWorkerFunction('getName', async () => {
const db = getDatabase()
const selectedChar = get(selectedCharID)
const char = db.characters[selectedChar]
return char.name
})
addWorkerFunction('setName', async (data:string) => {
const db = getDatabase()
const selectedChar = get(selectedCharID)
if(typeof data !== 'string'){
return false
}
db.characters[selectedChar].name = data
setDatabase(db)
return true
})
addWorkerFunction('getDescription', async () => {
const db = getDatabase()
const selectedChar = get(selectedCharID)
const char = db.characters[selectedChar]
if(char.type === 'group'){
return ''
}
return char.desc
})
addWorkerFunction('setDescription', async (data:string) => {
const db = getDatabase()
const selectedChar = get(selectedCharID)
const char =db.characters[selectedChar]
if(typeof data !== 'string'){
return false
}
if(char.type === 'group'){
return false
}
char.desc = data
db.characters[selectedChar] = char
setDatabase(db)
return true
})
addWorkerFunction('getCharacterFirstMessage', async () => {
const db = getDatabase()
const selectedChar = get(selectedCharID)
const char = db.characters[selectedChar]
return char.firstMessage
})
addWorkerFunction('setCharacterFirstMessage', async (data:string) => {
const db = getDatabase()
const selectedChar = get(selectedCharID)
const char = db.characters[selectedChar]
if(typeof data !== 'string'){
return false
}
char.firstMessage = data
db.characters[selectedChar] = char
setDatabase(db)
return true
})
addWorkerFunction('getBackgroundEmbedding', async () => {
const db = getDatabase()
const selectedChar = get(selectedCharID)
const char = db.characters[selectedChar]
return char.backgroundHTML
})
addWorkerFunction('setBackgroundEmbedding', async (data:string) => {
const db = getDatabase()
const selectedChar = get(selectedCharID)
if(typeof data !== 'string'){
return false
}
db.characters[selectedChar].backgroundHTML = data
setDatabase(db)
return true
})
addWorkerFunction('getState', async (statename) => {
const db = getDatabase()
const selectedChar = get(selectedCharID)
const char = db.characters[selectedChar]
const chat = char.chats[char.chatPage]
return (chat.scriptstate ?? {})[statename]
})
addWorkerFunction('setState', async (statename, data) => {
const db = getDatabase()
const selectedChar = get(selectedCharID)
const char = db.characters[selectedChar]
const chat = char.chats[char.chatPage]
if(typeof statename !== 'string'){
return false
}
if(typeof data !== 'string' && typeof data !== 'number' && typeof data !== 'boolean'){
return false
}
if(typeof data === 'string' && data.length > 100000){
return false
}
if(!chat.scriptstate){
chat.scriptstate = {}
}
if(Object.keys(chat.scriptstate).length > 50){
return false
}
chat.scriptstate[statename] = data
char.chats[char.chatPage] = chat
db.characters[selectedChar] = char
setDatabase(db)
return true
})
let compCode:{[key:string]:string[]} = {}
export async function runCharacterJS(arg:{
code: string|null,
mode: ScriptMode|'onButtonClick'|'modifyRequestChat'
data: any
}):Promise<any>{
const perf = performance.now()
try {
arg.code = arg.code ?? ''
const codes = {
"editinput": 'editInput',
"editoutput": 'editOutput',
"editprocess": 'editProcess',
"editdisplay": 'editDisplay',
'onButtonClick': "onButtonClick",
'modifyRequestChat': 'modifyRequestChat'
} as const
let runCodes = [...additionalCharaJS, arg.code]
let r = arg.data
for(const code of runCodes){
if(!code){
continue
}
if(!compCode[code]){
let modeList:string[] = []
const codesplit = code.split('\n')
for(let i = 0; i < codesplit.length; i++){
const line = codesplit[i].trim()
if(line.startsWith('//@use')){
modeList.push(line.replace('//@use','').trim())
}
}
compCode[code] = modeList
// compcode length max 100
if(Object.keys(compCode).length > 100){
delete compCode[Object.keys(compCode)[50]]
}
}
const runCode = codes[arg.mode]
if(!compCode[code].includes(runCode)){
continue
}
const result = await runVirtualJS(`${code}\n${runCode}(${JSON.stringify(r)})`)
if(!result){
continue
}
if(arg.mode !== 'modifyRequestChat'){
if(typeof result !== 'string'){
continue
}
}
else{
if(!Array.isArray(result)){
continue
}
}
r = result
if(runCode === 'onButtonClick'){
return r
}
}
return r
} catch (error) {
if(arg.mode !== 'editprocess'){
return `Error: ${error}`
}
return arg.data
}
finally{
console.log('runCharacterJS',performance.now() - perf)
}
}

View File

@@ -1,202 +0,0 @@
let globaly = globalThis
const whitelist = [
"Array",
"ArrayBuffer",
"BigInt",
"BigInt64Array",
"BigUint64Array",
"Boolean",
"DataView",
"Date",
"Error",
"EvalError",
"Float32Array",
"Float64Array",
"Function",
"Infinity",
"Int16Array",
"Int32Array",
"Int8Array",
"JSON",
"Map",
"Math",
"NaN",
"Number",
"Object",
"Promise",
"Proxy",
"RangeError",
"ReferenceError",
"Reflect",
"RegExp",
"Set",
"SharedArrayBuffer",
"String",
"Symbol",
"SyntaxError",
"TypeError",
"URIError",
"Uint16Array",
"Uint32Array",
"Uint8Array",
"Uint8ClampedArray",
"WeakMap",
"WeakSet",
"console",
"decodeURI",
"decodeURIComponent",
"encodeURI",
"encodeURIComponent",
"escape",
"globalThis",
"isFinite",
"isNaN",
"null",
"parseFloat",
"parseInt",
"undefined",
"unescape",
"queueMicrotask",
"setTimeout",
"clearTimeout",
"setInterval",
"clearInterval",
"setImmediate",
"clearImmediate",
"atob",
"btoa",
"Headers",
"Request",
"Response",
"Blob",
"postMessage",
"Node",
"Element",
"Text",
"Comment",
'onmessage',
"DOMParser",
"XMLSerializer",
"TextEncoder",
"TextDecoder",
"AbortController",
"AbortSignal",
"Event",
"CustomEvent",
"EventTarget",
"OffscreenCanvas",
"ImageBitmap",
"ImageBitmapRenderingContext",
"createImageBitmap",
"OffScreenCanvasRenderingContext2D",
"WebGL2RenderingContext",
"WebGLRenderingContext",
]
const evaluation = globaly.eval
const propa = Object.getOwnPropertyNames( this ?? {} )
const prop = Object.getOwnPropertyNames( globaly ).concat( propa)
prop.push(
//unsafe apis
'open',
'close',
'alert',
'confirm',
'prompt',
'print',
'fetch',
'navigator',
'Worker',
'WebSocket',
'XMLHttpRequest',
'localStorage',
'sessionStorage',
'importScripts',
'indexedDB',
'crypto',
'WebAssembly',
'WebSqlDatabase',
)
prop.forEach( function( prop ) {
if( (!whitelist.includes(prop)) && (!prop.startsWith('HTML')) ) {
try {
Object.defineProperty( globaly, prop, {
get : function() {
throw "Security Exception: cannot access "+prop;
return 1;
},
configurable : false
});
} catch (error) {
}
try {
Object.defineProperty( this, prop, {
get : function() {
throw "Security Exception: cannot access "+prop;
return 1;
},
configurable : false
});
} catch (error) {
}
}
else{
}
});
let workerResults:{
id: string,
result: any
}[] = []
self.onmessage = async (event) => {
const da = event.data
if(da.type === 'result'){
workerResults.push(da)
return
}
if(da.type === 'api'){
//add api
Object.defineProperty( globaly, da.name, {
get : function() {
return function (...args:any[]) {
return new Promise((resolve)=>{
const functionCallID = `id${Math.random()}`.replace('.','')
self.postMessage({
type: 'api',
name: da.name,
id: functionCallID,
args
})
const interval = setInterval(()=>{
const result = workerResults.find(r=>r.id === functionCallID)
if(result){
clearInterval(interval)
resolve(result.result)
}
},10)
})
}
}
});
return
}
try{
const d = await evaluation(da.code)
self.postMessage({
id: da.id,
result: d
})
}
catch(e){
console.error(e)
self.postMessage({
id: da.id,
result: e
})
}
}

View File

@@ -1,12 +1,11 @@
import { get, writable } from "svelte/store";
import { language } from "../../lang";
import { alertError } from "../alert";
import { alertError, alertMd } from "../alert";
import { getCurrentCharacter, getDatabase, setDatabaseLite } from "../storage/database.svelte";
import { checkNullish, selectSingleFile, sleep } from "../util";
import type { OpenAIChat } from "../process/index.svelte";
import { fetchNative, globalFetch } from "../globalApi.svelte";
import { selectedCharID } from "../stores.svelte";
import { addAdditionalCharaJS } from "./embedscript";
import type { ScriptMode } from "../process/scripts";
export const customProviderStore = writable([] as string[])
@@ -33,40 +32,30 @@ export async function importPlugin(){
const jsFile = Buffer.from(f.data).toString('utf-8').replace(/^\uFEFF/gm, "");
const splitedJs = jsFile.split('\n')
let name = ''
let version:1|2 = 1
let displayName:string = undefined
let arg:{[key:string]:'int'|'string'|string[]} = {}
let realArg:{[key:string]:number|string} = {}
for(const line of splitedJs){
if(line.startsWith('//@risu-name')){
const provied = line.slice(13)
if(provied === ''){
alertError('plugin name must be longer than "", did you put it correctly?')
return
}
name = provied.trim()
alertMd('V1 plugin is not supported anymore, please use V2 plugin instead. for more information, please check the documentation. `https://github.com/kwaroran/RisuAI/blob/main/plugins.md`')
return
}
if(line.startsWith('//@risu-display-name')){
alertMd('V1 plugin is not supported anymore, please use V2 plugin instead. for more information, please check the documentation. `https://github.com/kwaroran/RisuAI/blob/main/plugins.md`')
return
}
if(line.startsWith('//@name')){
const provied = line.slice(7)
if(provied === ''){
alertError('plugin name must be longer than "", did you put it correctly?')
alertError('plugin name must be longer than 0, did you put it correctly?')
return
}
version = 2
name = provied.trim()
}
if(line.startsWith('//@risu-display-name')){
const provied = line.slice('//@risu-display-name'.length + 1)
if(provied === ''){
alertError('plugin display name must be longer than "", did you put it correctly?')
return
}
displayName = provied.trim()
}
if(line.startsWith('//@display-name')){
const provied = line.slice('//@display-name'.length + 1)
if(provied === ''){
alertError('plugin display name must be longer than "", did you put it correctly?')
alertError('plugin display name must be longer than 0, did you put it correctly?')
return
}
displayName = provied.trim()
@@ -105,7 +94,7 @@ export async function importPlugin(){
realArg: realArg,
arguments: arg,
displayName: displayName,
version: version
version: 2
}
db.plugins ??= []
@@ -119,174 +108,15 @@ export async function importPlugin(){
}
}
export function getCurrentPluginMax(prov:string){
return 12000
}
let pluginWorker:Worker = null
let providerRes:{success:boolean, content:string} = null
let translatorRes:{success:boolean, content:string} = null
function postMsgPluginWorker(type:string, body:any){
const bod = {
type: type,
body: body
}
pluginWorker.postMessage(bod)
}
let pluginTranslator = false
export async function loadPlugins() {
let db = getDatabase()
if(pluginWorker){
pluginWorker.terminate()
pluginWorker = null
}
const plugins = safeStructuredClone(db.plugins).filter((a:RisuPlugin) => a.version === 1)
const pluginV2 = safeStructuredClone(db.plugins).filter((a:RisuPlugin) => a.version === 2)
await loadV2Plugin(pluginV2)
if(plugins.length > 0){
const da = await fetch("/pluginApi.js")
const pluginApiString = await da.text()
let pluginjs = `${pluginApiString}\n`
let pluginLoadedJs = ''
for(const plug of db.plugins){
pluginLoadedJs += `\n(() => {${plug.script}})();\n`
}
pluginjs = pluginjs.replace('//{{placeholder}}',pluginLoadedJs)
const blob = new Blob([pluginjs], {type: 'application/javascript'});
pluginWorker = new Worker(URL.createObjectURL(blob));
pluginWorker.addEventListener('message', async (msg) => {
const data:{type:string,body:any} = msg.data
switch(data.type){
case "addProvider":{
let provs = get(customProviderStore)
provs.push(data.body)
customProviderStore.set(provs)
console.log(provs)
break
}
case "resProvider":{
const provres:{success:boolean, content:string} = data.body
if(checkNullish(provres.success) || checkNullish(provres.content)){
providerRes = {
success: false,
content :"provider didn't respond 'success' or 'content' in response object"
}
}
else if(typeof(provres.content) !== 'string'){
providerRes = {
success: false,
content :"provider didn't respond 'content' in response object in string"
}
}
else{
providerRes = {
success: !!provres.success,
content: provres.content
}
}
break
}
case "resTrans":{
const provres:{success:boolean, content:string} = data.body
if(checkNullish(provres.success) || checkNullish(provres.content)){
translatorRes = {
success: false,
content :"plugin didn't respond 'success' or 'content' in response object"
}
}
else if(typeof(provres.content) !== 'string'){
translatorRes = {
success: false,
content :"plugin didn't respond 'content' in response object in string"
}
}
else{
translatorRes = {
success: !!provres.success,
content: provres.content
}
}
break
}
case "useTranslator": {
pluginTranslator = true
break
}
case "fetch": {
postMsgPluginWorker('fetchData',{
id: data.body.id,
data: await globalFetch(data.body.url, data.body.arg)
})
break
}
case 'addCharaJs': {
let c:string = data.body.code
c.trim()
if(c.startsWith('{') && c.endsWith('}')){
c = c.slice(1, -1)
}
addAdditionalCharaJS(c, data.body.position)
break
}
case "getArg":{
try {
const db = getDatabase()
const arg:string[] = data.body.arg.split('::')
for(const plug of db.plugins){
if(arg[0] === plug.name){
postMsgPluginWorker('fetchData',{
id: data.body.id,
data: plug.realArg[arg[1]]
})
return
}
}
postMsgPluginWorker('fetchData',{
id: data.body.id,
data: null
})
} catch (error) {
postMsgPluginWorker('fetchData',{
id: data.body.id,
data: null
})
}
break
}
case "getChar":{
const db = getDatabase()
const charid = get(selectedCharID)
const char = db.characters[charid]
postMsgPluginWorker('fetchData',{
id: data.body.id,
data: char
})
break
}
case "setChar":{
const db = getDatabase()
const charid = get(selectedCharID)
db.characters[charid] = data.body
break
}
case "log":{
console.log(data.body)
break
}
}
})
}
}
type PluginV2ProviderArgument = {
@@ -301,11 +131,17 @@ type PluginV2ProviderArgument = {
mode: string
}
type PluginV2ProviderOptions = {
tokenizer?: string
tokenizerFunc?: (content:string) => number[]|Promise<number[]>
}
type EditFunction = (content:string) => string|null|undefined|Promise<string|null|undefined>
type ReplacerFunction = (content:OpenAIChat[], type:string) => OpenAIChat[]|Promise<OpenAIChat[]>
export const pluginV2 = {
providers: new Map<string, (arg:PluginV2ProviderArgument) => Promise<{success:boolean,content:string}> >(),
providers: new Map<string, (arg:PluginV2ProviderArgument) => Promise<{success:boolean,content:string|ReadableStream<string>}> >(),
providerOptions: new Map<string, PluginV2ProviderOptions>(),
editdisplay: new Set<EditFunction>(),
editoutput: new Set<EditFunction>(),
editprocess: new Set<EditFunction>(),
@@ -336,8 +172,9 @@ export async function loadV2Plugin(plugins:RisuPlugin[]){
risuFetch: globalFetch,
nativeFetch: fetchNative,
getArg: (arg:string) => {
const db = getDatabase()
const [name, realArg] = arg.split('::')
for(const plug of plugins){
for(const plug of db.plugins){
if(plug.name === name){
return plug.realArg[realArg]
}
@@ -352,10 +189,11 @@ export async function loadV2Plugin(plugins:RisuPlugin[]){
db.characters[charid] = char
setDatabaseLite(db)
},
addProvider: (name:string, func:(arg:PluginV2ProviderArgument) => Promise<{success:boolean,content:string}>) => {
addProvider: (name:string, func:(arg:PluginV2ProviderArgument) => Promise<{success:boolean,content:string}>, options?:PluginV2ProviderOptions) => {
let provs = get(customProviderStore)
provs.push(name)
pluginV2.providers.set(name, func)
pluginV2.providerOptions.set(name, options ?? {})
customProviderStore.set(provs)
},
addRisuScriptHandler: (name:ScriptMode, func:EditFunction) => {
@@ -407,6 +245,8 @@ export async function loadV2Plugin(plugins:RisuPlugin[]){
const setChar = globalThis.__pluginApis__.setChar
const addProvider = globalThis.__pluginApis__.addProvider
const addRisuEventHandler = globalThis.__pluginApis__.addRisuEventHandler
const addRisuReplacer = globalThis.__pluginApis__.addRisuReplacer
const removeRisuReplacer = globalThis.__pluginApis__.removeRisuReplacer
const onUnload = globalThis.__pluginApis__.onUnload
${data}
@@ -424,30 +264,7 @@ export async function loadV2Plugin(plugins:RisuPlugin[]){
}
export async function translatorPlugin(text:string, from:string, to:string) {
if(!pluginTranslator){
return false
}
else{
try {
translatorRes = null
postMsgPluginWorker("requestTrans", {text, from, to})
while(true){
await sleep(50)
if(providerRes){
break
}
}
return {
success: translatorRes.success,
content: translatorRes.content
}
} catch (error) {
return {
success: false,
content: "unknownError"
}
}
}
return false
}
export async function pluginProcess(arg:{
@@ -458,35 +275,10 @@ export async function pluginProcess(arg:{
frequency_penalty: number
bias: {[key:string]:string}
}|{}){
try {
let db = getDatabase()
if(!pluginWorker){
return {
success: false,
content: "plugin worker not found error"
}
}
postMsgPluginWorker("requestProvider", {
key: db.currentPluginProvider,
arg: arg
})
providerRes = null
while(true){
await sleep(50)
if(providerRes){
break
}
}
return {
success: providerRes.success,
content: providerRes.content
}
} catch (error) {
return {
success: false,
content: "unknownError"
}
}
return {
success: false,
content: "Plugin V1 is not supported anymore, please use V2 plugin instead."
}
}

View File

@@ -22,7 +22,6 @@ import { getInlayAsset, supportsInlayImage } from "./files/inlays";
import { getGenerationModelString } from "./models/modelString";
import { connectionOpen, peerRevertChat, peerSafeCheck, peerSync } from "../sync/multiuser";
import { runInlayScreen } from "./inlayScreen";
import { runCharacterJS } from "../plugins/embedscript";
import { addRerolls } from "./prereroll";
import { runImageEmbedding } from "./transformers";
import { hanuraiMemory } from "./memory/hanuraiMemory";
@@ -30,7 +29,6 @@ import { hypaMemoryV2 } from "./memory/hypav2";
import { runLuaEditTrigger } from "./lua";
import { parseChatML } from "../parser.svelte";
import { getModelInfo, LLMFlags } from "../model/modellist";
import { pluginV2 } from "../plugins/plugins";
export interface OpenAIChat{
role: 'system'|'user'|'assistant'|'function'
@@ -755,7 +753,7 @@ export async function sendChat(chatProcessIndex = -1,arg:{
}
}
let thoughts:string[] = []
formatedChat = formatedChat.replace(/<Thoughts>(.+?)<\/Thoughts>/gm, (match, p1) => {
formatedChat = formatedChat.replace(/<Thoughts>(.+)<\/Thoughts>/gms, (match, p1) => {
thoughts.push(p1)
return ''
})
@@ -1116,12 +1114,6 @@ export async function sendChat(chatProcessIndex = -1,arg:{
})
}
formated = await runCharacterJS({
code: null,
mode: 'modifyRequestChat',
data: formated
})
formated = await runLuaEditTrigger(currentChar, 'editRequest', formated)
//token rechecking

View File

@@ -279,10 +279,6 @@ export function getModules(){
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

View File

@@ -259,6 +259,7 @@ export interface OpenAIChatExtra {
removable?:boolean
attr?:string[]
multimodals?:MultiModal[]
thoughts?:string[]
}
function reformater(formated:OpenAIChat[],modelInfo:LLMModel){
@@ -451,6 +452,7 @@ async function requestOpenAI(arg:RequestDataArgumentExtended):Promise<requestDat
delete formatedChat[i].removable
delete formatedChat[i].attr
delete formatedChat[i].multimodals
delete formatedChat[i].thoughts
}
if(aiModel === 'reverse_proxy' && db.reverseProxyOobaMode && formatedChat[i].role === 'system'){
const cont = formatedChat[i].content
@@ -1390,43 +1392,69 @@ async function requestOoba(arg:RequestDataArgumentExtended):Promise<requestDataR
}
async function requestPlugin(arg:RequestDataArgumentExtended):Promise<requestDataResponse> {
const formated = arg.formated
const db = getDatabase()
const maxTokens = arg.maxTokens
const bias = arg.biasString
const v2Function = pluginV2.providers.get(db.currentPluginProvider)
const d = v2Function ? (await v2Function(applyParameters({
prompt_chat: formated,
mode: arg.mode,
bias: []
}, [
'frequency_penalty','min_p','presence_penalty','repetition_penalty','top_k','top_p','temperature'
], {}, arg.mode) as any)) : await pluginProcess({
bias: bias,
prompt_chat: formated,
temperature: (db.temperature / 100),
max_tokens: maxTokens,
presence_penalty: (db.PresensePenalty / 100),
frequency_penalty: (db.frequencyPenalty / 100)
})
if(!d){
try {
const formated = arg.formated
const maxTokens = arg.maxTokens
const bias = arg.biasString
const v2Function = pluginV2.providers.get(db.currentPluginProvider)
const d = v2Function ? (await v2Function(applyParameters({
prompt_chat: formated,
mode: arg.mode,
bias: [],
max_tokens: maxTokens,
}, [
'frequency_penalty','min_p','presence_penalty','repetition_penalty','top_k','top_p','temperature'
], {}, arg.mode) as any)) : await pluginProcess({
bias: bias,
prompt_chat: formated,
temperature: (db.temperature / 100),
max_tokens: maxTokens,
presence_penalty: (db.PresensePenalty / 100),
frequency_penalty: (db.frequencyPenalty / 100)
})
if(!d){
return {
type: 'fail',
result: (language.errors.unknownModel)
}
}
else if(!d.success){
return {
type: 'fail',
result: d.content instanceof ReadableStream ? await (new Response(d.content)).text() : d.content
}
}
else if(d.content instanceof ReadableStream){
let fullText = ''
const piper = new TransformStream<string, StreamResponseChunk>( {
transform(chunk, control) {
fullText += chunk
control.enqueue({
"0": fullText
})
}
})
return {
type: 'streaming',
result: d.content.pipeThrough(piper)
}
}
else{
return {
type: 'success',
result: d.content
}
}
} catch (error) {
console.error(error)
return {
type: 'fail',
result: (language.errors.unknownModel)
}
}
else if(!d.success){
return {
type: 'fail',
result: d.content
}
}
else{
return {
type: 'success',
result: d.content
result: `Plugin Error from ${db.currentPluginProvider}: ` + JSON.stringify(error)
}
}
}

View File

@@ -6,7 +6,6 @@ import { alertError, alertNormal } from "../alert";
import { language } from "src/lang";
import { selectSingleFile } from "../util";
import { assetRegex, type CbsConditions, risuChatParser as risuChatParserOrg, type simpleCharacterArgument } from "../parser.svelte";
import { runCharacterJS } from "../plugins/embedscript";
import { getModuleAssets, getModuleRegexScripts } from "./modules";
import { HypaProcesser } from "./memory/hypamemory";
import { runLuaEditTrigger } from "./lua";
@@ -98,17 +97,12 @@ export function resetScriptCache(){
export async function processScriptFull(char:character|groupChat|simpleCharacterArgument, data:string, mode:ScriptMode, chatID = -1, cbsConditions:CbsConditions = {}){
let db = getDatabase()
const originalData = data
const cached = getScriptCache((db.globalscript ?? []).concat(char.customscript), originalData, mode)
const cached = getScriptCache((db.presetRegex ?? []).concat(char.customscript), originalData, mode)
if(cached){
return {data: cached, emoChanged: false}
}
let emoChanged = false
const scripts = (db.globalscript ?? []).concat(char.customscript).concat(getModuleRegexScripts())
data = await runCharacterJS({
code: char.virtualscript ?? null,
mode,
data,
})
const scripts = (db.presetRegex ?? []).concat(char.customscript).concat(getModuleRegexScripts())
data = await runLuaEditTrigger(char, mode, data)
if(pluginV2[mode].size > 0){
for(const plugin of pluginV2[mode]){

View File

@@ -2,7 +2,7 @@ import { get } from "svelte/store"
import { getDatabase, type character } from "../storage/database.svelte"
import { requestChatData } from "./request"
import { alertError } from "../alert"
import { globalFetch, readImage } from "../globalApi.svelte"
import { fetchNative, globalFetch, readImage } from "../globalApi.svelte"
import { CharEmotion } from "../stores.svelte"
import type { OpenAIChat } from "./index.svelte"
import { processZip } from "./processzip"
@@ -415,12 +415,14 @@ export async function generateAIImage(genPrompt:string, currentChar:character, n
}
if(db.sdProvider === 'comfy'){
if(db.sdProvider === 'comfy' || db.sdProvider === 'comfyui'){
const legacy = db.sdProvider === 'comfy' // Legacy Comfy mode
const {workflow, posNodeID, posInputName, negNodeID, negInputName} = db.comfyConfig
const baseUrl = new URL(db.comfyUiUrl)
const createUrl = (pathname: string, params: Record<string, string> = {}) => {
const url = new URL(pathname, baseUrl)
const url = db.comfyUiUrl.endsWith('/api') ? new URL(`${db.comfyUiUrl}${pathname}`) : new URL(pathname, baseUrl)
url.search = new URLSearchParams(params).toString()
return url.toString()
}
@@ -437,8 +439,31 @@ export async function generateAIImage(genPrompt:string, currentChar:character, n
try {
const prompt = JSON.parse(workflow)
prompt[posNodeID].inputs[posInputName] = genPrompt
prompt[negNodeID].inputs[negInputName] = neg
if(legacy){
prompt[posNodeID].inputs[posInputName] = genPrompt
prompt[negNodeID].inputs[negInputName] = neg
}
else{
//search all nodes for the prompt and negative prompt
const keys = Object.keys(prompt)
for(let i = 0; i < keys.length; i++){
const node = prompt[keys[i]]
const inputKeys = Object.keys(node.inputs)
for(let j = 0; j < inputKeys.length; j++){
let input = node.inputs[inputKeys[j]]
if(typeof input === 'string'){
input = input.replaceAll('{{risu_prompt}}', genPrompt)
input = input.replaceAll('{{risu_neg}}', neg)
}
if(inputKeys[j] === 'seed' && typeof input === 'number'){
input = Math.floor(Math.random() * 1000000000)
}
node.inputs[inputKeys[j]] = input
}
}
}
const { prompt_id: id } = await fetchWrapper(createUrl('/prompt'), {
method: 'POST',
@@ -451,9 +476,10 @@ export async function generateAIImage(genPrompt:string, currentChar:character, n
const startTime = Date.now()
const timeout = db.comfyConfig.timeout * 1000
while (!(item = (await (await fetch(createUrl('/history'), {
while (!(item = (await (await fetchNative(createUrl('/history'), {
headers: { 'Content-Type': 'application/json' },
method: 'GET'})).json())[id])) {
method: 'GET'
})).json())[id])) {
console.log("Checking /history...")
if (Date.now() - startTime >= timeout) {
alertError("Error: Image generation took longer than expected.");
@@ -463,13 +489,14 @@ export async function generateAIImage(genPrompt:string, currentChar:character, n
} // Check history until the generation is complete.
const genImgInfo = Object.values(item.outputs).flatMap((output: any) => output.images)[0];
const imgResponse = await fetch(createUrl('/view', {
const imgResponse = await fetchNative(createUrl('/view', {
filename: genImgInfo.filename,
subfolder: genImgInfo.subfolder,
type: genImgInfo.type
}), {
headers: { 'Content-Type': 'application/json' },
method: 'GET'})
method: 'GET'
})
const img64 = Buffer.from(await imgResponse.arrayBuffer()).toString('base64')
if(returnSdData === 'inlay'){
@@ -552,7 +579,6 @@ export async function generateAIImage(genPrompt:string, currentChar:character, n
if(db.falModel === 'fal-ai/flux-pro'){
delete body.enable_safety_checker
}
console.log(body)
const res = await globalFetch('https://fal.run/' + model, {
headers: {
@@ -563,8 +589,6 @@ export async function generateAIImage(genPrompt:string, currentChar:character, n
body: body
})
console.log(res)
if(!res.ok){
alertError(JSON.stringify(res.data))
return false

View File

@@ -3,7 +3,7 @@ import { getDatabase } from "./database.svelte"
import { hubURL } from "../characterCards"
import localforage from "localforage"
import { alertLogin, alertNormalWait, alertStore, alertWait } from "../alert"
import { forageStorage, getUnpargeables } from "../globalApi.svelte"
import { AppendableBuffer, forageStorage, getUnpargeables } from "../globalApi.svelte"
import { encodeRisuSaveLegacy } from "./risuSave"
import { v4 } from "uuid"
import { language } from "src/lang"
@@ -87,7 +87,7 @@ export class AccountStorage{
}
return await getDaText()
}
async getItem(key:string):Promise<Buffer> {
async getItem(key:string, callback?:(status:number) => void):Promise<Buffer> {
this.checkAuth()
if(key.startsWith('assets/')){
const k:ArrayBuffer = await localforage.getItem(key)
@@ -131,11 +131,38 @@ export class AccountStorage{
if(da.status === 204){
return null
}
const ab = await da.arrayBuffer()
if(key.startsWith('assets/')){
const ab = await da.arrayBuffer()
await localforage.setItem(key, ab)
return Buffer.from(ab)
}
return Buffer.from(ab)
if(!callback){
const ab = await da.arrayBuffer()
return Buffer.from(ab)
}
const size = parseInt(da.headers.get('x-body-size'))
const appendable = new Uint8Array(size)
const reader = da.body.getReader()
//log all headers
console.log('logging headers')
for(const [key, value] of da.headers.entries()){
console.log(key, value)
}
let i = 0
while(true){
const {done, value} = await reader.read()
if(done){
break
}
console.log(value, size)
appendable.set(value, i)
i += value.length
callback(i/size)
}
return Buffer.from(appendable)
}
async keys():Promise<string[]>{
let db = getDatabase()

View File

@@ -117,7 +117,7 @@ export class AutoStorage{
return false
}
private async Init(){
async Init(){
if(!this.realStorage){
if(localStorage.getItem('accountst') === 'able'){
this.realStorage = new AccountStorage()

View File

@@ -5,14 +5,14 @@ import type { RisuPlugin } from '../plugins/plugins';
import type {triggerscript as triggerscriptMain} from '../process/triggers';
import { downloadFile, saveAsset as saveImageGlobal } from '../globalApi.svelte';
import { defaultAutoSuggestPrompt, defaultJailbreak, defaultMainPrompt } from './defaultPrompts';
import { alertNormal, alertSelect } from '../alert';
import { alertError, alertNormal, alertSelect } from '../alert';
import type { NAISettings } from '../process/models/nai';
import { prebuiltNAIpresets, prebuiltPresets } from '../process/templates/templates';
import { defaultColorScheme, type ColorScheme } from '../gui/colorscheme';
import type { PromptItem, PromptSettings } from '../process/prompt';
import type { OobaChatCompletionRequestParams } from '../model/ooba';
export let appVer = "144.1.0"
export let appVer = "145.0.1"
export let webAppSubVer = ''
@@ -356,6 +356,7 @@ export function setDatabase(data:Database){
data.huggingfaceKey ??= ''
data.fishSpeechKey ??= ''
data.statistics ??= {}
data.presetRegex ??= []
data.reverseProxyOobaArgs ??= {
mode: 'instruct'
}
@@ -465,6 +466,7 @@ export function setDatabase(data:Database){
data.customFlags ??= []
data.enableCustomFlags ??= false
data.assetMaxDifference ??= 4
data.showSavingIcon ??= false
changeLanguage(data.language)
setDatabaseLite(data)
}
@@ -862,6 +864,8 @@ export interface Database{
assetMaxDifference:number
menuSideBar:boolean
pluginV2: RisuPlugin[]
showSavingIcon:boolean
presetRegex: customscript[]
}
interface SeparateParameters{
@@ -1183,6 +1187,8 @@ export interface botPreset{
openAIPrediction?: string
enableCustomFlags?: boolean
customFlags?: LLMFlags[]
image?:string
regex?:customscript[]
}
@@ -1483,6 +1489,8 @@ export function saveCurrentPreset(){
systemRoleReplacement: db.systemRoleReplacement,
customFlags: safeStructuredClone(db.customFlags),
enableCustomFlags: db.enableCustomFlags,
regex: db.presetRegex,
image: pres?.[db.botPresetsId]?.image ?? '',
}
db.botPresets = pres
setDatabase(db)
@@ -1590,6 +1598,7 @@ export function setPreset(db:Database, newPres: botPreset){
db.systemRoleReplacement = newPres.systemRoleReplacement ?? 'user'
db.customFlags = safeStructuredClone(newPres.customFlags) ?? []
db.enableCustomFlags = newPres.enableCustomFlags ?? false
db.presetRegex = newPres.regex ?? []
return db
}
@@ -1615,6 +1624,12 @@ export async function downloadPreset(id:number, type:'json'|'risupreset'|'return
pres.proxyKey = ''
pres.textgenWebUIStreamURL= ''
pres.textgenWebUIBlockingURL= ''
if((pres.image || pres.regex?.length > 0) && type !== 'return'){
alertError("Preset with image or regexes cannot be exported for now. use RisuRealm to share the preset.")
return
}
if(type === 'json'){
downloadFile(pres.name + "_preset.json", Buffer.from(JSON.stringify(pres, null, 2)))
}
@@ -1627,15 +1642,16 @@ export async function downloadPreset(id:number, type:'json'|'risupreset'|'return
'risupreset'
)
}))
const buf2 = await encodeRPack(buf)
if(type === 'risupreset'){
const buf2 = await encodeRPack(buf)
downloadFile(pres.name + "_preset.risup", buf2)
}
else{
return {
data: pres,
buf
buf: buf2
}
}

View File

@@ -94,6 +94,10 @@ export const DBState = $state({
db: {} as any as Database
});
export const LoadingStatusState = $state({
text: '',
})
export const disableHighlight = writable(true)
ReloadGUIPointer.subscribe(() => {
@@ -111,7 +115,6 @@ $effect.root(() => {
DBState?.db?.characters?.[selIdState.selId]?.chats?.[DBState?.db?.characters?.[selIdState.selId]?.chatPage]?.modules?.length
DBState?.db?.characters?.[selIdState.selId]?.hideChatIcon
DBState?.db?.characters?.[selIdState.selId]?.backgroundHTML
DBState?.db?.moduleIntergration
moduleUpdate()
})
})

View File

@@ -7,6 +7,8 @@ import { risuChatParser } from "./parser.svelte";
import { tokenizeGGUFModel } from "./process/models/local";
import { globalFetch } from "./globalApi.svelte";
import { getModelInfo, LLMTokenizer } from "./model/modellist";
import { pluginV2 } from "./plugins/plugins";
import type { GemmaTokenizer } from "@huggingface/transformers";
export const tokenizerList = [
@@ -38,15 +40,46 @@ export async function encode(data:string):Promise<(number[]|Uint32Array|Int32Arr
case 'llama3':
return await tokenizeWebTokenizers(data, 'llama')
case 'gemma':
return await tokenizeWebTokenizers(data, 'gemma')
return await gemmaTokenize(data)
case 'cohere':
return await tokenizeWebTokenizers(data, 'cohere')
default:
return await tikJS(data, 'o200k_base')
}
}
const modelInfo = getModelInfo(db.aiModel)
if(db.aiModel === 'custom' && pluginV2.providerOptions.get(db.currentPluginProvider)?.tokenizer){
const tokenizer = pluginV2.providerOptions.get(db.currentPluginProvider)?.tokenizer
switch(tokenizer){
case 'mistral':
return await tokenizeWebTokenizers(data, 'mistral')
case 'llama':
return await tokenizeWebTokenizers(data, 'llama')
case 'novelai':
return await tokenizeWebTokenizers(data, 'novelai')
case 'claude':
return await tokenizeWebTokenizers(data, 'claude')
case 'novellist':
return await tokenizeWebTokenizers(data, 'novellist')
case 'llama3':
return await tokenizeWebTokenizers(data, 'llama')
case 'gemma':
return await gemmaTokenize(data)
case 'cohere':
return await tokenizeWebTokenizers(data, 'cohere')
case 'o200k_base':
return await tikJS(data, 'o200k_base')
case 'cl100k_base':
return await tikJS(data, 'cl100k_base')
case 'custom':
return await pluginV2.providerOptions.get(db.currentPluginProvider)?.tokenizerFunc?.(data) ?? [0]
default:
return await tikJS(data, 'o200k_base')
}
}
if(modelInfo.tokenizer === LLMTokenizer.NovelList){
const nv= await tokenizeWebTokenizers(data, 'novellist')
return nv
@@ -73,7 +106,7 @@ export async function encode(data:string):Promise<(number[]|Uint32Array|Int32Arr
return await tokenizeGoogleCloud(data)
}
if(modelInfo.tokenizer === LLMTokenizer.Gemma || modelInfo.tokenizer === LLMTokenizer.GoogleCloud){
return await tokenizeWebTokenizers(data, 'gemma')
return await gemmaTokenize(data)
}
if(modelInfo.tokenizer === LLMTokenizer.Cohere){
return await tokenizeWebTokenizers(data, 'cohere')
@@ -125,6 +158,17 @@ async function tokenizeGoogleCloud(text:string) {
return new Uint32Array(count)
}
let gemmaTokenizer:GemmaTokenizer = null
async function gemmaTokenize(text:string) {
if(!gemmaTokenizer){
const {GemmaTokenizer} = await import('@huggingface/transformers')
gemmaTokenizer = new GemmaTokenizer(
await (await fetch("/token/llama/llama3.json")
).json(), {})
}
return gemmaTokenizer.encode(text)
}
async function tikJS(text:string, model='cl100k_base') {
if(!tikParser || lastTikModel !== model){
if(model === 'cl100k_base'){