feat: Open read-only access to lore books from Lua (#846)
# PR Checklist - [ ] Have you checked if it works normally in all models? *Ignore this if it doesn't use models.* - [x] Have you checked if it works normally in all web, local, and node hosted versions? If it doesn't, have you blocked it in those versions? - [x] Have you added type definitions? # Description This PR adds read-only lore books access from Lua. - `getLoreBooks(triggerId, search)`: Gets all lore books of the name (comment). No additional sorting is done - API user will need to sort themselves. All lores are parsed before returning. - `loadLoreBooks(triggerId, reserve)`: Retrieves all active lore books in current context. This function takes account of max context length and cut low priority lores, similar to a user submitting their message. All lores are parsed before returning. - Specifying `reserve` higher than `0` would reserve that much tokens for other prompts. With `loadLoreBooks()`, character and module creators would be able to separate token- and context-heavy data generations into Lua and separate LLM workflow for improved accuracy.
This commit is contained in:
@@ -1,6 +1,6 @@
|
|||||||
import { getChatVar, hasher, setChatVar, getGlobalChatVar, type simpleCharacterArgument, risuChatParser } from "../parser.svelte";
|
import { getChatVar, hasher, setChatVar, getGlobalChatVar, type simpleCharacterArgument, risuChatParser } from "../parser.svelte";
|
||||||
import { LuaEngine, LuaFactory } from "wasmoon";
|
import { LuaEngine, LuaFactory } from "wasmoon";
|
||||||
import { getCurrentCharacter, getCurrentChat, getDatabase, setDatabase, type Chat, type character, type groupChat } from "../storage/database.svelte";
|
import { getCurrentCharacter, getCurrentChat, getDatabase, setDatabase, type Chat, type character, type groupChat, type loreBook } from "../storage/database.svelte";
|
||||||
import { get } from "svelte/store";
|
import { get } from "svelte/store";
|
||||||
import { ReloadGUIPointer, selectedCharID } from "../stores.svelte";
|
import { ReloadGUIPointer, selectedCharID } from "../stores.svelte";
|
||||||
import { alertSelect, alertError, alertInput, alertNormal } from "../alert";
|
import { alertSelect, alertError, alertInput, alertNormal } from "../alert";
|
||||||
@@ -10,10 +10,11 @@ import { writeInlayImage } from "./files/inlays";
|
|||||||
import type { OpenAIChat } from "./index.svelte";
|
import type { OpenAIChat } from "./index.svelte";
|
||||||
import { requestChatData } from "./request";
|
import { requestChatData } from "./request";
|
||||||
import { v4 } from "uuid";
|
import { v4 } from "uuid";
|
||||||
import { getModuleTriggers } from "./modules";
|
import { getModuleLorebooks, getModuleTriggers } from "./modules";
|
||||||
import { Mutex } from "../mutex";
|
import { Mutex } from "../mutex";
|
||||||
import { tokenize } from "../tokenizer";
|
import { tokenize } from "../tokenizer";
|
||||||
import { fetchNative } from "../globalApi.svelte";
|
import { fetchNative } from "../globalApi.svelte";
|
||||||
|
import { loadLoreBookV3Prompt } from './lorebook.svelte';
|
||||||
import { getPersonaPrompt, getUserName } from '../util';
|
import { getPersonaPrompt, getUserName } from '../util';
|
||||||
|
|
||||||
let luaFactory:LuaFactory
|
let luaFactory:LuaFactory
|
||||||
@@ -505,6 +506,67 @@ export async function runLua(code:string, arg:{
|
|||||||
return true
|
return true
|
||||||
})
|
})
|
||||||
|
|
||||||
|
// Lore books
|
||||||
|
luaEngine.global.set('getLoreBooksMain', (id:string, search: string) => {
|
||||||
|
if(!LuaSafeIds.has(id)){
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const db = getDatabase()
|
||||||
|
const selectedChar = db.characters[get(selectedCharID)]
|
||||||
|
if (selectedChar.type !== 'character') {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const loreBooks = [...selectedChar.chats[selectedChar.chatPage]?.localLore ?? [], ...selectedChar.globalLore, ...getModuleLorebooks()]
|
||||||
|
const found = loreBooks.filter((b) => b.comment === search)
|
||||||
|
|
||||||
|
return JSON.stringify(found.map((b) => ({ ...b, content: risuChatParser(b.content, { chara: selectedChar }) })))
|
||||||
|
})
|
||||||
|
|
||||||
|
luaEngine.global.set('loadLoreBooksMain', async (id:string, usedContext:number) => {
|
||||||
|
if(!LuaLowLevelIds.has(id)){
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const db = getDatabase()
|
||||||
|
|
||||||
|
const selectedChar = db.characters[get(selectedCharID)]
|
||||||
|
|
||||||
|
if (selectedChar.type !== 'character') {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const fullLoreBooks = (await loadLoreBookV3Prompt()).actives
|
||||||
|
const maxContext = db.maxContext - usedContext
|
||||||
|
if (maxContext < 0) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
let totalTokens = 0
|
||||||
|
const loreBooks = []
|
||||||
|
|
||||||
|
for (const book of fullLoreBooks) {
|
||||||
|
const parsed = risuChatParser(book.prompt, { chara: selectedChar }).trim()
|
||||||
|
if (parsed.length === 0) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
const tokens = await tokenize(parsed)
|
||||||
|
|
||||||
|
if (totalTokens + tokens > maxContext) {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
totalTokens += tokens
|
||||||
|
loreBooks.push({
|
||||||
|
data: parsed,
|
||||||
|
role: book.role === 'assistant' ? 'char' : book.role,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
return JSON.stringify(loreBooks)
|
||||||
|
})
|
||||||
|
|
||||||
luaEngine.global.set('axLLMMain', async (id:string, promptStr:string) => {
|
luaEngine.global.set('axLLMMain', async (id:string, promptStr:string) => {
|
||||||
let prompt:{
|
let prompt:{
|
||||||
role: string,
|
role: string,
|
||||||
@@ -729,6 +791,15 @@ function log(value)
|
|||||||
logMain(json.encode(value))
|
logMain(json.encode(value))
|
||||||
end
|
end
|
||||||
|
|
||||||
|
function getLoreBooks(id, search)
|
||||||
|
return json.decode(getLoreBooksMain(id, search))
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
function loadLoreBooks(id)
|
||||||
|
return json.decode(loadLoreBooksMain(id):await())
|
||||||
|
end
|
||||||
|
|
||||||
function LLM(id, prompt)
|
function LLM(id, prompt)
|
||||||
return json.decode(LLMMain(id, json.encode(prompt)):await())
|
return json.decode(LLMMain(id, json.encode(prompt)):await())
|
||||||
end
|
end
|
||||||
|
|||||||
Reference in New Issue
Block a user