From 95452921fef4c7000d0c0a1a131f6a383344f1e9 Mon Sep 17 00:00:00 2001 From: kwaroran Date: Sat, 29 Jun 2024 05:19:39 +0900 Subject: [PATCH] feat: add lua triggers --- pnpm-lock.yaml | 14 +- src/lang/en.ts | 4 +- src/lib/SideBars/CharConfig.svelte | 47 +++-- src/ts/process/lua.ts | 15 ++ src/ts/process/triggers.ts | 307 ++++++++++++++++++++++++++++- 5 files changed, 364 insertions(+), 23 deletions(-) create mode 100644 src/ts/process/lua.ts diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index e36f08a1..0cdf5c07 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -184,7 +184,7 @@ importers: specifier: ^5.6.0 version: 5.6.0 '@sveltejs/vite-plugin-svelte': - specifier: ^3.0.1 + specifier: 3.0.1 version: 3.0.1(svelte@4.2.8)(vite@5.2.12(@types/node@18.19.7)) '@swc/core': specifier: 1.5.7 @@ -250,13 +250,13 @@ importers: specifier: ^8.4.33 version: 8.4.33 svelte: - specifier: ^4.2.8 + specifier: 4.2.8 version: 4.2.8 svelte-check: - specifier: ^3.6.3 + specifier: 3.6.3 version: 3.6.3(postcss-load-config@4.0.2(postcss@8.4.33)(ts-node@10.9.2(@swc/core@1.5.7)(@types/node@18.19.7)(typescript@5.3.3)))(postcss@8.4.33)(svelte@4.2.8) svelte-preprocess: - specifier: ^5.1.3 + specifier: 5.1.3 version: 5.1.3(postcss-load-config@4.0.2(postcss@8.4.33)(ts-node@10.9.2(@swc/core@1.5.7)(@types/node@18.19.7)(typescript@5.3.3)))(postcss@8.4.33)(svelte@4.2.8)(typescript@5.3.3) tailwindcss: specifier: ^3.4.1 @@ -268,13 +268,13 @@ importers: specifier: ^5.3.3 version: 5.3.3 vite: - specifier: ^5.2.12 + specifier: 5.2.12 version: 5.2.12(@types/node@18.19.7) vite-plugin-top-level-await: - specifier: ^1.4.1 + specifier: 1.4.1 version: 1.4.1(rollup@3.29.4)(vite@5.2.12(@types/node@18.19.7)) vite-plugin-wasm: - specifier: ^3.3.0 + specifier: 3.3.0 version: 3.3.0(vite@5.2.12(@types/node@18.19.7)) packages: diff --git a/src/lang/en.ts b/src/lang/en.ts index 529c31a2..757874d6 100644 --- a/src/lang/en.ts +++ b/src/lang/en.ts @@ -664,6 +664,6 @@ export const languageEnglish = { includePersonaName: "Include Persona Name", hidePersonaName: "Hide Persona Name", triggerSwitchWarn: "If you change the trigger type, current triggers will be lost. do you want to continue?", - codeMode: "Code Mode", - blockMode: "Block Mode", + codeMode: "Code", + blockMode: "Block", } \ No newline at end of file diff --git a/src/lib/SideBars/CharConfig.svelte b/src/lib/SideBars/CharConfig.svelte index 5c6703ba..1fa391d2 100644 --- a/src/lib/SideBars/CharConfig.svelte +++ b/src/lib/SideBars/CharConfig.svelte @@ -581,19 +581,25 @@ {language.triggerScript}
- - +
- {#if currentChar.data?.triggerscript?.[0]?.effect?.[0]?.type === 'triggercode'} + {#if currentChar.data?.triggerscript?.[0]?.effect?.[0]?.type === 'triggercode' || currentChar.data?.triggerscript?.[0]?.effect?.[0]?.type === 'triggerlua'} {:else} diff --git a/src/ts/process/lua.ts b/src/ts/process/lua.ts new file mode 100644 index 00000000..dbbb4674 --- /dev/null +++ b/src/ts/process/lua.ts @@ -0,0 +1,15 @@ +import type { LuaEngine } from "wasmoon"; +import type { character, groupChat } from "../storage/database"; +import { risuChatParser } from "../parser"; + +export let LuaSafeIds = new Set() + +export function registerLuaProcess(engine:LuaEngine, char:character|groupChat) { + engine.global.set('cbs', (code:string) => { + const parsed = risuChatParser(code, { + chara: char, + }) + return parsed + }) +} + diff --git a/src/ts/process/triggers.ts b/src/ts/process/triggers.ts index 5e9ccca1..6e044f4c 100644 --- a/src/ts/process/triggers.ts +++ b/src/ts/process/triggers.ts @@ -12,6 +12,14 @@ import { HypaProcesser } from "./memory/hypamemory"; import { requestChatData } from "./request"; import { generateAIImage } from "./stableDiff"; import { writeInlayImage } from "./files/image"; +import { LuaEngine, LuaFactory } from "wasmoon"; +import { v4 } from "uuid"; + +let luaFactory:LuaFactory +let luaEngine:LuaEngine +let lastCode = '' +let LuaSafeIds = new Set() +let LuaLowLevelIds = new Set() export interface triggerscript{ comment: string; @@ -33,7 +41,7 @@ export type triggerConditionsVar = { } export type triggerCode = { - type: 'triggercode', + type: 'triggercode'|'triggerlua', code: string } @@ -202,12 +210,15 @@ export async function runTrigger(char:character,mode:triggerMode, arg:{ for(const trigger of triggers){ - if(arg.manualName){ + if(trigger.effect[0]?.type === 'triggercode' || trigger.effect[0]?.type === 'triggerlua'){ + // + } + else if(arg.manualName){ if(trigger.comment !== arg.manualName){ continue } } - else if(mode !== trigger.type && trigger.effect[0]?.type !== 'triggercode'){ + else if(mode !== trigger.type){ continue } @@ -535,7 +546,297 @@ export async function runTrigger(char:character,mode:triggerMode, arg:{ stopSending = true } break + } + case 'triggerlua':{ + if(!luaEngine || lastCode !== effect.code){ + if(luaEngine){ + luaEngine.global.close() + } + luaFactory = new LuaFactory() + luaEngine = await luaFactory.createEngine() + luaEngine.global.set('cbs', (code:string) => { + const parsed = risuChatParser(code, { + chara: char, + }) + return parsed + }) + luaEngine.global.set('setChatVar', (id:string,key:string, value:string) => { + if(!LuaSafeIds.has(id)){ + return + } + setVar(key, value) + }) + luaEngine.global.set('getChatVar', (id:string,key:string) => { + if(!LuaSafeIds.has(id)){ + return + } + return getVar(key) + }) + luaEngine.global.set('stopChat', (id:string) => { + if(!LuaSafeIds.has(id)){ + return + } + stopSending = true + }) + luaEngine.global.set('alertError', (id:string, value:string) => { + if(!LuaSafeIds.has(id)){ + return + } + alertError(value) + }) + luaEngine.global.set('alertNormal', (id:string, value:string) => { + if(!LuaSafeIds.has(id)){ + return + } + alertNormal(value) + }) + luaEngine.global.set('alertInput', (id:string, value:string) => { + if(!LuaSafeIds.has(id)){ + return + } + return alertInput(value) + }) + luaEngine.global.set('setChat', (id:string, index:number, value:string) => { + if(!LuaSafeIds.has(id)){ + return + } + const message = chat.message?.at(index) + if(message){ + message.data = value + } + CurrentChat.set(chat) + }) + luaEngine.global.set('setChatRole', (id:string, index:number, value:string) => { + if(!LuaSafeIds.has(id)){ + return + } + const message = chat.message?.at(index) + if(message){ + message.role = value === 'user' ? 'user' : 'char' + } + CurrentChat.set(chat) + }) + luaEngine.global.set('cutChat', (id:string, start:number, end:number) => { + if(!LuaSafeIds.has(id)){ + return + } + chat.message = chat.message.slice(start,end) + CurrentChat.set(chat) + }) + luaEngine.global.set('addChat', (id:string, role:string, value:string) => { + if(!LuaSafeIds.has(id)){ + return + } + let roleData:'user'|'char' = role === 'user' ? 'user' : 'char' + chat.message.push({role: roleData, data: value}) + CurrentChat.set(chat) + }) + luaEngine.global.set('insertChat', (id:string, index:number, role:string, value:string) => { + if(!LuaSafeIds.has(id)){ + return + } + let roleData:'user'|'char' = role === 'user' ? 'user' : 'char' + chat.message.splice(index, 0, {role: roleData, data: value}) + CurrentChat.set(chat) + }) + luaEngine.global.set('removeChat', (id:string, index:number) => { + if(!LuaSafeIds.has(id)){ + return + } + chat.message.splice(index, 1) + CurrentChat.set(chat) + }) + luaEngine.global.set('getChatLength', (id:string) => { + if(!LuaSafeIds.has(id)){ + return + } + return chat.message.length + }) + luaEngine.global.set('getFullChat', (id:string) => { + if(!LuaSafeIds.has(id)){ + return + } + return chat.message.map((v) => { + return { + role: v.role, + data: v.data + } + }) + }) + luaEngine.global.set('setFullChat', (id:string, value:any[]) => { + if(!LuaSafeIds.has(id)){ + return + } + chat.message = value.map((v) => { + return { + role: v.role, + data: v.data + } + }) + CurrentChat.set(chat) + }) + + //Low Level Access + luaEngine.global.set('similarity', (id:string, source:string, value:string[]) => { + if(!LuaLowLevelIds.has(id)){ + return + } + const processer = new HypaProcesser('MiniLM') + processer.addText(value) + return processer.similaritySearch(source) + }) + + luaEngine.global.set('generateImage', async (id:string, value:string, negValue:string = '') => { + if(!LuaLowLevelIds.has(id)){ + return + } + const gen = await generateAIImage(value, char, negValue, 'inlay') + if(!gen){ + return 'Error: Image generation failed' + } + const imgHTML = new Image() + imgHTML.src = gen + const inlay = await writeInlayImage(imgHTML) + return `{{inlay::${inlay}}}` + }) + + luaEngine.global.set('LLM', async (id:string, prompt:{ + role: string, + content: string + }[]) => { + if(!LuaLowLevelIds.has(id)){ + return + } + let promptbody:OpenAIChat[] = prompt.map((dict) => { + 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 { + success: false, + result: 'Error: ' + result.result + } + } + + if(result.type === 'streaming' || result.type === 'multiline'){ + return { + success: false, + result: result.result + } + } + + return { + success: true, + result: result.result + } + }) + + luaEngine.global.set('simpleLLM', async (id:string, prompt:string) => { + const result = await requestChatData({ + formated: [{ + role: 'user', + content: prompt + }], + bias: {}, + useStreaming: false, + noMultiGen: true, + }, 'model') + + if(result.type === 'fail'){ + return { + success: false, + result: 'Error: ' + result.result + } + } + + if(result.type === 'streaming' || result.type === 'multiline'){ + return { + success: false, + result: result.result + } + } + + return { + success: true, + result: result.result + } + }) + + await luaEngine.doString(effect.code) + lastCode = effect.code + } + let accessKey = v4() + LuaSafeIds.add(accessKey) + if(trigger.lowLevelAccess){ + LuaLowLevelIds.add(v4()) + } + try { + let res:any + switch(mode){ + case 'input':{ + const func = luaEngine.global.get('onInput') + if(func){ + res = await func(accessKey) + } + } + case 'output':{ + const func = luaEngine.global.get('onOutput') + if(func){ + res = await func(accessKey) + } + } + case 'start':{ + const func = luaEngine.global.get('onStart') + if(func){ + res = await func(accessKey) + } + } + case 'manual':{ + const func = luaEngine.global.get(arg.manualName) + if(func){ + res = await func(accessKey) + } + } + } + if(res === false){ + stopSending = true + } + } catch (error) { + + alertError('Lua Error: ' + error) + console.error(error) + } + + LuaSafeIds.delete(accessKey) + LuaLowLevelIds.delete(accessKey) + break } } }