[feat] basic js run in character

This commit is contained in:
kwaroran
2023-11-08 14:46:15 +09:00
parent 23e0d5f6bc
commit e7ca31ff39
9 changed files with 272 additions and 16 deletions

View File

@@ -77,7 +77,7 @@
cha.push({
role: 'user',
data: processScript(char,messageInput,'editinput'),
data: await processScript(char,messageInput,'editinput'),
time: Date.now()
})
}

View File

@@ -41,7 +41,7 @@
}
const unsub = doingChat.subscribe((v) => {
const unsub = doingChat.subscribe(async (v) => {
if(v) {
progress=false
abortController?.abort()
@@ -55,7 +55,7 @@
const firstMsg:string = currentChar.firstMsgIndex === -1 ? currentChar.firstMessage : currentChar.alternateGreetings[currentChar.firstMsgIndex]
messages.push({
role: 'char',
data: processScript(currentChar,
data: await processScript(currentChar,
replacePlaceholders(firstMsg, currentChar.name),
'editprocess')
})

View File

@@ -4,7 +4,7 @@ import { Marked } from 'marked';
import { DataBase, type Database, type Message, type character, type customscript, type groupChat } from './storage/database';
import { getFileSrc } from './storage/globalApi';
import { processScript, processScriptFull } from './process/scripts';
import { processScriptFull } from './process/scripts';
import { get } from 'svelte/store';
import css from '@adobe/css-tools'
import { selectedCharID } from './stores';
@@ -102,6 +102,7 @@ export interface simpleCharacterArgument{
additionalAssets?: [string, string, string][]
customscript: customscript[]
chaId: string,
virtualscript?: string
}
@@ -115,7 +116,7 @@ export async function ParseMarkdown(data:string, charArg:(simpleCharacterArgumen
firstParsed = data
}
if(char){
data = processScriptFull(char, data, 'editdisplay', chatID).data
data = (await processScriptFull(char, data, 'editdisplay', chatID)).data
}
if(firstParsed !== data && char && char.type !== 'group'){
data = await parseAdditionalAssets(data, char, mode)

View File

@@ -0,0 +1,102 @@
import { get } from 'svelte/store'
import type { ScriptMode } from '../process/scripts'
import myWorkerUrl from './embedworker?worker&url'
import { DataBase } from '../storage/database'
import { selectedCharID } from '../stores'
import { cloneDeep } from 'lodash'
let worker = new Worker(new URL(myWorkerUrl), {type: 'module'})
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)
console.log(performance.now() - startTime )
if(result){
clearInterval(interval)
resolve(result.result)
}
else if(performance.now() - startTime > 400){
clearInterval(interval)
//restart worker
worker.terminate()
worker = new Worker(new URL('./worker.ts', import.meta.url), {type: 'module'})
reject('timeout')
}
},100)
})
}
addWorkerFunction('getCharacter', async () => {
const db = get(DataBase)
const selectedChar = get(selectedCharID)
return cloneDeep(db.characters[selectedChar])
})
export async function runCharacterJS(arg:{
code: string,
mode: ScriptMode
data: string
}):Promise<string>{
try {
const codes = {
"editinput": 'editInput',
"editoutput": 'editOutput',
"editprocess": 'editProcess',
"editdisplay": 'editDisplay',
} as const
const runCode = codes[arg.mode]
const result = await runVirtualJS(`${arg.code}\n${runCode}(${JSON.stringify(arg.data)})`)
if(!result){
return arg.data
}
return result.toString()
} catch (error) {
if(arg.mode !== 'editprocess'){
return `Error: ${error}`
}
return arg.data
}
}

View File

@@ -0,0 +1,142 @@
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",
"WebAssembly",
"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"
]
const evaluation = global.eval
Object.getOwnPropertyNames( global ).forEach( function( prop ) {
if( !whitelist.includes(prop) ) {
Object.defineProperty( global, prop, {
get : function() {
throw "Security Exception: cannot access "+prop;
return 1;
},
configurable : false
});
}
});
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( global, 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)
}
},100)
})
}
}
});
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

@@ -430,9 +430,9 @@ export async function sendChat(chatProcessIndex = -1,arg:{chatAdditonalTokens?:n
const chat:OpenAIChat = {
role: 'assistant',
content: processScript(nowChatroom,
content: await (processScript(nowChatroom,
risuChatParser(firstMsg, {chara: currentChar, rmVar: true}),
'editprocess')
'editprocess'))
}
chats.push(chat)
currentTokens += await tokenizer.tokenizeChat(chat)
@@ -448,7 +448,7 @@ export async function sendChat(chatProcessIndex = -1,arg:{chatAdditonalTokens?:n
}
for(const msg of ms){
let formedChat = processScript(nowChatroom,risuChatParser(msg.data, {chara: currentChar, rmVar: true}), 'editprocess')
let formedChat = await processScript(nowChatroom,risuChatParser(msg.data, {chara: currentChar, rmVar: true}), 'editprocess')
let name = ''
if(msg.role === 'char'){
if(msg.saying){
@@ -798,7 +798,7 @@ export async function sendChat(chatProcessIndex = -1,arg:{chatAdditonalTokens?:n
if(db.cipherChat){
result = decipherChat(result)
}
const result2 = processScriptFull(nowChatroom, reformatContent(prefix + result), 'editoutput', msgIndex)
const result2 = await processScriptFull(nowChatroom, reformatContent(prefix + result), 'editoutput', msgIndex)
db.characters[selectedChar].chats[selectedChat].message[msgIndex].data = result2.data
emoChanged = result2.emoChanged
db.characters[selectedChar].reloadKeys += 1
@@ -832,11 +832,11 @@ export async function sendChat(chatProcessIndex = -1,arg:{chatAdditonalTokens?:n
mess = decipherChat(result)
}
let msgIndex = db.characters[selectedChar].chats[selectedChat].message.length
let result2 = processScriptFull(nowChatroom, reformatContent(mess), 'editoutput', msgIndex)
let result2 = await processScriptFull(nowChatroom, reformatContent(mess), 'editoutput', msgIndex)
if(i === 0 && arg.continue){
msgIndex -= 1
let beforeChat = db.characters[selectedChar].chats[selectedChat].message[msgIndex]
result2 = processScriptFull(nowChatroom, reformatContent(beforeChat.data + mess), 'editoutput', msgIndex)
result2 = await processScriptFull(nowChatroom, reformatContent(beforeChat.data + mess), 'editoutput', msgIndex)
}
result = result2.data
emoChanged = result2.emoChanged

View File

@@ -7,14 +7,15 @@ import { language } from "src/lang";
import { selectSingleFile } from "../util";
import { risuChatParser as risuChatParserOrg, type simpleCharacterArgument } from "../parser";
import { autoMarkPlugin } from "../plugins/automark";
import { runCharacterJS } from "../plugins/embedscript";
const dreg = /{{data}}/g
const randomness = /\|\|\|/g
type ScriptMode = 'editinput'|'editoutput'|'editprocess'|'editdisplay'
export type ScriptMode = 'editinput'|'editoutput'|'editprocess'|'editdisplay'
export function processScript(char:character|groupChat, data:string, mode:ScriptMode){
return processScriptFull(char, data, mode).data
export async function processScript(char:character|groupChat, data:string, mode:ScriptMode){
return (await processScriptFull(char, data, mode)).data
}
export function exportRegex(){
@@ -54,13 +55,20 @@ export async function importRegex(){
}
}
export 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){
let db = get(DataBase)
let emoChanged = false
const scripts = (db.globalscript ?? []).concat(char.customscript)
if(db.officialplugins.automark && mode === 'editdisplay'){
data = autoMarkPlugin(data)
}
if(char.virtualscript){
data = await runCharacterJS({
code: char.virtualscript,
mode,
data,
})
}
if(scripts.length === 0){
return {data, emoChanged}
}

View File

@@ -577,6 +577,7 @@ export interface character{
private?:boolean
additionalText:string
oaiVoice?:string
virtualscript?:string
}
@@ -618,6 +619,7 @@ export interface groupChat{
reloadKeys?:number
backgroundCSS?:string
oneAtTime?:boolean
virtualscript?:string
}
export interface botPreset{

View File

@@ -46,7 +46,8 @@ function createSimpleCharacter(char:character|groupChat){
type: "simple",
customscript: cloneDeep(char.customscript),
chaId: char.chaId,
additionalAssets: char.additionalAssets
additionalAssets: char.additionalAssets,
virtualscript: char.virtualscript,
}
return simpleChar