[feat] lorebook plus
This commit is contained in:
@@ -100,6 +100,7 @@ export const languageEnglish = {
|
|||||||
oaiRandomUser: "If enabled, random uuid would be put on user parameter on request, and would be changed on refresh. this can be used to prevent AI from identifying user.",
|
oaiRandomUser: "If enabled, random uuid would be put on user parameter on request, and would be changed on refresh. this can be used to prevent AI from identifying user.",
|
||||||
inlayImages: "If enabled, images could be inlayed to the chat and AIs can see it if they support it.",
|
inlayImages: "If enabled, images could be inlayed to the chat and AIs can see it if they support it.",
|
||||||
metrica: 'Metric Systemizer is a plugin that converts metrics to imperial units when request, and vice versa on output to show user metric system while using imperial for performace. it is not recommended to use this plugin when using imperial units on chat.',
|
metrica: 'Metric Systemizer is a plugin that converts metrics to imperial units when request, and vice versa on output to show user metric system while using imperial for performace. it is not recommended to use this plugin when using imperial units on chat.',
|
||||||
|
lorePlus: "LoreBook+ is a experimental feature that uses vectordb instead of just string matching for better bot making experience and better matching performace."
|
||||||
},
|
},
|
||||||
setup: {
|
setup: {
|
||||||
chooseProvider: "Choose AI Provider",
|
chooseProvider: "Choose AI Provider",
|
||||||
@@ -447,4 +448,5 @@ export const languageEnglish = {
|
|||||||
depthPrompt: "Depth Prompt",
|
depthPrompt: "Depth Prompt",
|
||||||
largePortrait: "Portrait",
|
largePortrait: "Portrait",
|
||||||
postImage: "Post Image",
|
postImage: "Post Image",
|
||||||
|
lorePlus: "LoreBook+",
|
||||||
}
|
}
|
||||||
@@ -12,6 +12,7 @@
|
|||||||
export let onRemove: () => void = () => {}
|
export let onRemove: () => void = () => {}
|
||||||
export let onClose: () => void = () => {}
|
export let onClose: () => void = () => {}
|
||||||
export let onOpen: () => void = () => {}
|
export let onOpen: () => void = () => {}
|
||||||
|
export let lorePlus = false
|
||||||
|
|
||||||
export let idx:number
|
export let idx:number
|
||||||
let open = false
|
let open = false
|
||||||
@@ -47,47 +48,57 @@
|
|||||||
<div class="border-0 outline-none w-full mt-2 flex flex-col mb-2">
|
<div class="border-0 outline-none w-full mt-2 flex flex-col mb-2">
|
||||||
<span class="text-textcolor mt-6">{language.name} <Help key="loreName"/></span>
|
<span class="text-textcolor mt-6">{language.name} <Help key="loreName"/></span>
|
||||||
<TextInput size="sm" bind:value={value.comment}/>
|
<TextInput size="sm" bind:value={value.comment}/>
|
||||||
{#if !value.alwaysActive}
|
{#if !lorePlus}
|
||||||
<span class="text-textcolor mt-6">{language.activationKeys} <Help key="loreActivationKey"/></span>
|
{#if !value.alwaysActive}
|
||||||
<span class="text-xs text-textcolor2">{language.activationKeysInfo}</span>
|
<span class="text-textcolor mt-6">{language.activationKeys} <Help key="loreActivationKey"/></span>
|
||||||
<TextInput size="sm" bind:value={value.key}/>
|
|
||||||
|
|
||||||
{#if value.selective}
|
|
||||||
<span class="text-textcolor mt-6">{language.SecondaryKeys}</span>
|
|
||||||
<span class="text-xs text-textcolor2">{language.activationKeysInfo}</span>
|
<span class="text-xs text-textcolor2">{language.activationKeysInfo}</span>
|
||||||
<TextInput size="sm" bind:value={value.secondkey}/>
|
<TextInput size="sm" bind:value={value.key}/>
|
||||||
|
|
||||||
|
{#if value.selective}
|
||||||
|
<span class="text-textcolor mt-6">{language.SecondaryKeys}</span>
|
||||||
|
<span class="text-xs text-textcolor2">{language.activationKeysInfo}</span>
|
||||||
|
<TextInput size="sm" bind:value={value.secondkey}/>
|
||||||
|
{/if}
|
||||||
{/if}
|
{/if}
|
||||||
{/if}
|
{/if}
|
||||||
{#if !(value.activationPercent === undefined || value.activationPercent === null)}
|
{#if !lorePlus}
|
||||||
<span class="text-textcolor mt-6">{language.activationProbability}</span>
|
{#if !(value.activationPercent === undefined || value.activationPercent === null)}
|
||||||
<NumberInput size="sm" bind:value={value.activationPercent} onChange={() => {
|
<span class="text-textcolor mt-6">{language.activationProbability}</span>
|
||||||
if(isNaN(value.activationPercent) || !value.activationPercent || value.activationPercent < 0){
|
<NumberInput size="sm" bind:value={value.activationPercent} onChange={() => {
|
||||||
value.activationPercent = 0
|
if(isNaN(value.activationPercent) || !value.activationPercent || value.activationPercent < 0){
|
||||||
}
|
value.activationPercent = 0
|
||||||
if(value.activationPercent > 100){
|
}
|
||||||
value.activationPercent = 100
|
if(value.activationPercent > 100){
|
||||||
}
|
value.activationPercent = 100
|
||||||
}} />
|
}
|
||||||
|
}} />
|
||||||
|
{/if}
|
||||||
|
{/if}
|
||||||
|
{#if !lorePlus}
|
||||||
|
<span class="text-textcolor mt-4">{language.insertOrder} <Help key="loreorder"/></span>
|
||||||
|
<NumberInput size="sm" bind:value={value.insertorder} min={0} max={1000}/>
|
||||||
{/if}
|
{/if}
|
||||||
<span class="text-textcolor mt-4">{language.insertOrder} <Help key="loreorder"/></span>
|
|
||||||
<NumberInput size="sm" bind:value={value.insertorder} min={0} max={1000}/>
|
|
||||||
<span class="text-textcolor mt-4 mb-2">{language.prompt}</span>
|
<span class="text-textcolor mt-4 mb-2">{language.prompt}</span>
|
||||||
<TextAreaInput autocomplete="off" bind:value={value.content} />
|
<TextAreaInput autocomplete="off" bind:value={value.content} />
|
||||||
<div class="flex items-center mt-4">
|
<div class="flex items-center mt-4">
|
||||||
<Check bind:check={value.alwaysActive} name={language.alwaysActive}/>
|
<Check bind:check={value.alwaysActive} name={language.alwaysActive}/>
|
||||||
</div>
|
</div>
|
||||||
<div class="flex items-center mt-2">
|
{#if !lorePlus}
|
||||||
<Check bind:check={value.selective} name={language.selective}/>
|
<div class="flex items-center mt-2">
|
||||||
<Help key="loreSelective" name={language.selective}/>
|
<Check bind:check={value.selective} name={language.selective}/>
|
||||||
</div>
|
<Help key="loreSelective" name={language.selective}/>
|
||||||
<div class="flex items-center mt-2 mb-6">
|
</div>
|
||||||
{#if value.activationPercent === undefined || value.activationPercent === null}
|
{/if}
|
||||||
<Check name={language.loreRandomActivation} check={false} onChange={() => {value.activationPercent = 50}}/>
|
{#if !lorePlus}
|
||||||
{:else}
|
<div class="flex items-center mt-2 mb-6">
|
||||||
<Check name={language.loreRandomActivation} check={true} onChange={() => {value.activationPercent = null}}/>
|
{#if value.activationPercent === undefined || value.activationPercent === null}
|
||||||
{/if}
|
<Check name={language.loreRandomActivation} check={false} onChange={() => {value.activationPercent = 50}}/>
|
||||||
<span><Help name={language.loreRandomActivation} key="loreRandomActivation"/></span>
|
{:else}
|
||||||
</div>
|
<Check name={language.loreRandomActivation} check={true} onChange={() => {value.activationPercent = null}}/>
|
||||||
|
{/if}
|
||||||
|
<span><Help name={language.loreRandomActivation} key="loreRandomActivation"/></span>
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -8,6 +8,7 @@
|
|||||||
|
|
||||||
export let globalMode = false
|
export let globalMode = false
|
||||||
export let submenu = 0
|
export let submenu = 0
|
||||||
|
export let lorePlus = false
|
||||||
let stb: Sortable = null
|
let stb: Sortable = null
|
||||||
let ele: HTMLDivElement
|
let ele: HTMLDivElement
|
||||||
let sorted = 0
|
let sorted = 0
|
||||||
@@ -94,7 +95,7 @@
|
|||||||
let lore = $CurrentCharacter.globalLore
|
let lore = $CurrentCharacter.globalLore
|
||||||
lore.splice(i, 1)
|
lore.splice(i, 1)
|
||||||
$CurrentCharacter.globalLore = lore
|
$CurrentCharacter.globalLore = lore
|
||||||
}} onOpen={onOpen} onClose={onClose}/>
|
}} onOpen={onOpen} onClose={onClose} lorePlus={lorePlus}/>
|
||||||
{/each}
|
{/each}
|
||||||
{/if}
|
{/if}
|
||||||
{:else if submenu === 1}
|
{:else if submenu === 1}
|
||||||
@@ -106,7 +107,7 @@
|
|||||||
let lore = $CurrentChat.localLore
|
let lore = $CurrentChat.localLore
|
||||||
lore.splice(i, 1)
|
lore.splice(i, 1)
|
||||||
$CurrentChat.localLore = lore
|
$CurrentChat.localLore = lore
|
||||||
}} onOpen={onOpen} onClose={onClose}/>
|
}} onOpen={onOpen} onClose={onClose} lorePlus={lorePlus}/>
|
||||||
{/each}
|
{/each}
|
||||||
{/if}
|
{/if}
|
||||||
{/if}
|
{/if}
|
||||||
|
|||||||
@@ -7,6 +7,7 @@
|
|||||||
import Check from "../../UI/GUI/CheckInput.svelte";
|
import Check from "../../UI/GUI/CheckInput.svelte";
|
||||||
import NumberInput from "../../UI/GUI/NumberInput.svelte";
|
import NumberInput from "../../UI/GUI/NumberInput.svelte";
|
||||||
import LoreBookList from "./LoreBookList.svelte";
|
import LoreBookList from "./LoreBookList.svelte";
|
||||||
|
import Help from "src/lib/Others/Help.svelte";
|
||||||
|
|
||||||
let submenu = 0
|
let submenu = 0
|
||||||
export let globalMode = false
|
export let globalMode = false
|
||||||
@@ -35,7 +36,7 @@
|
|||||||
{#if !globalMode}
|
{#if !globalMode}
|
||||||
<span class="text-textcolor2 mt-2 mb-6 text-sm">{submenu === 0 ? $CurrentCharacter.type === 'group' ? language.groupLoreInfo : language.globalLoreInfo : language.localLoreInfo}</span>
|
<span class="text-textcolor2 mt-2 mb-6 text-sm">{submenu === 0 ? $CurrentCharacter.type === 'group' ? language.groupLoreInfo : language.globalLoreInfo : language.localLoreInfo}</span>
|
||||||
{/if}
|
{/if}
|
||||||
<LoreBookList bind:globalMode bind:submenu />
|
<LoreBookList bind:globalMode bind:submenu lorePlus={$CurrentCharacter.lorePlus} />
|
||||||
{:else}
|
{:else}
|
||||||
{#if $CurrentCharacter.loreSettings}
|
{#if $CurrentCharacter.loreSettings}
|
||||||
<div class="flex items-center mt-4">
|
<div class="flex items-center mt-4">
|
||||||
@@ -68,6 +69,11 @@
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
|
<div class="flex items-center mt-4">
|
||||||
|
<Check bind:check={$CurrentCharacter.lorePlus}
|
||||||
|
name={language.lorePlus}
|
||||||
|
><Help key="lorePlus"></Help><Help key="experimental"></Help></Check>
|
||||||
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
{#if submenu !== 2}
|
{#if submenu !== 2}
|
||||||
|
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import { checkNullish, selectSingleFile } from "../util";
|
|||||||
import { alertError, alertNormal } from "../alert";
|
import { alertError, alertNormal } from "../alert";
|
||||||
import { language } from "../../lang";
|
import { language } from "../../lang";
|
||||||
import { downloadFile } from "../storage/globalApi";
|
import { downloadFile } from "../storage/globalApi";
|
||||||
|
import { HypaProcesser } from "./memory/hypamemory";
|
||||||
|
|
||||||
export function addLorebook(type:number) {
|
export function addLorebook(type:number) {
|
||||||
let selectedID = get(selectedCharID)
|
let selectedID = get(selectedCharID)
|
||||||
@@ -61,6 +62,8 @@ interface formatedLore{
|
|||||||
const rmRegex = / |\n/g
|
const rmRegex = / |\n/g
|
||||||
|
|
||||||
export async function loadLoreBookPrompt(){
|
export async function loadLoreBookPrompt(){
|
||||||
|
|
||||||
|
|
||||||
const selectedID = get(selectedCharID)
|
const selectedID = get(selectedCharID)
|
||||||
const db = get(DataBase)
|
const db = get(DataBase)
|
||||||
const char = db.characters[selectedID]
|
const char = db.characters[selectedID]
|
||||||
@@ -74,6 +77,10 @@ export async function loadLoreBookPrompt(){
|
|||||||
const loreToken = char.loreSettings?.tokenBudget ?? db.loreBookToken
|
const loreToken = char.loreSettings?.tokenBudget ?? db.loreBookToken
|
||||||
const fullWordMatching = char.loreSettings?.fullWordMatching ?? false
|
const fullWordMatching = char.loreSettings?.fullWordMatching ?? false
|
||||||
|
|
||||||
|
if(char.lorePlus){
|
||||||
|
return await loadLoreBookPlusPrompt()
|
||||||
|
}
|
||||||
|
|
||||||
let activatiedPrompt: string[] = []
|
let activatiedPrompt: string[] = []
|
||||||
|
|
||||||
let formatedLore:formatedLore[] = []
|
let formatedLore:formatedLore[] = []
|
||||||
@@ -201,6 +208,94 @@ export async function loadLoreBookPrompt(){
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export async function loadLoreBookPlusPrompt(){
|
||||||
|
const selectedID = get(selectedCharID)
|
||||||
|
const db = get(DataBase)
|
||||||
|
const char = db.characters[selectedID]
|
||||||
|
const page = char.chatPage
|
||||||
|
const characterLore = char.globalLore ?? []
|
||||||
|
const chatLore = char.chats[page].localLore ?? []
|
||||||
|
const globalLore = db.loreBook[db.loreBookPage]?.data ?? []
|
||||||
|
const fullLore = characterLore.concat(chatLore.concat(globalLore)).filter((v) => { return v.content })
|
||||||
|
const currentChat = char.chats[page].message
|
||||||
|
const loreDepth = char.loreSettings?.scanDepth ?? db.loreBookDepth
|
||||||
|
const loreToken = char.loreSettings?.tokenBudget ?? db.loreBookToken
|
||||||
|
|
||||||
|
interface formatedLorePlus{
|
||||||
|
content: string
|
||||||
|
simularity:number
|
||||||
|
}
|
||||||
|
|
||||||
|
let formatedLores:formatedLorePlus[] = []
|
||||||
|
let activatiedPrompt: string[] = []
|
||||||
|
const hypaProcesser = new HypaProcesser('MiniLM')
|
||||||
|
|
||||||
|
|
||||||
|
const formatedChatMain = currentChat.slice(currentChat.length - loreDepth,currentChat.length).map((msg) => {
|
||||||
|
return msg.data
|
||||||
|
}).join('||').replace(rmRegex,'').toLocaleLowerCase()
|
||||||
|
const chatVec = await hypaProcesser.testText(formatedChatMain)
|
||||||
|
|
||||||
|
|
||||||
|
for(const lore of fullLore){
|
||||||
|
let key = (lore.key ?? '').replace(rmRegex, '').toLocaleLowerCase().split(',')
|
||||||
|
key.push(lore.comment)
|
||||||
|
|
||||||
|
let vec:number[]
|
||||||
|
|
||||||
|
if(lore.loreCache && lore.loreCache.key === lore.content){
|
||||||
|
const vect = lore.loreCache.data[0]
|
||||||
|
const v = Buffer.from(vect, 'base64')
|
||||||
|
const f = new Float32Array(v.buffer)
|
||||||
|
vec = Array.from(f)
|
||||||
|
}
|
||||||
|
else{
|
||||||
|
vec = await hypaProcesser.testText(lore.content)
|
||||||
|
lore.loreCache = {
|
||||||
|
key: lore.content,
|
||||||
|
data: [Buffer.from(new Float32Array(vec).buffer).toString('base64')]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
formatedLores.push({
|
||||||
|
content: lore.content,
|
||||||
|
simularity: hypaProcesser.similarityCheck(chatVec, vec)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
formatedLores.sort((a, b) => {
|
||||||
|
return b.simularity - a.simularity
|
||||||
|
})
|
||||||
|
|
||||||
|
let i=0;
|
||||||
|
while(i < formatedLores.length){
|
||||||
|
const lore = formatedLores[i]
|
||||||
|
const totalTokens = await tokenize(activatiedPrompt.concat([lore.content]).join('\n\n'))
|
||||||
|
if(totalTokens > loreToken){
|
||||||
|
break
|
||||||
|
}
|
||||||
|
activatiedPrompt.push(lore.content)
|
||||||
|
i++
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
let sactivated:string[] = []
|
||||||
|
activatiedPrompt = activatiedPrompt.filter((v) => {
|
||||||
|
if(v.startsWith("@@@end")){
|
||||||
|
sactivated.push(v.replace('@@@end','').trim())
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
})
|
||||||
|
|
||||||
|
return {
|
||||||
|
act: activatiedPrompt.reverse().join('\n\n'),
|
||||||
|
special_act: sactivated.reverse().join('\n\n')
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
export async function importLoreBook(mode:'global'|'local'|'sglobal'){
|
export async function importLoreBook(mode:'global'|'local'|'sglobal'){
|
||||||
const selectedID = get(selectedCharID)
|
const selectedID = get(selectedCharID)
|
||||||
|
|||||||
@@ -79,7 +79,15 @@ export class HypaProcesser{
|
|||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async testText(text:string){
|
||||||
|
const forageResult:number[] = await this.forage.getItem(text)
|
||||||
|
if(forageResult){
|
||||||
|
return forageResult
|
||||||
|
}
|
||||||
|
const vec = (await this.embedDocuments([text]))[0]
|
||||||
|
await this.forage.setItem(text, vec)
|
||||||
|
return vec
|
||||||
|
}
|
||||||
|
|
||||||
async addText(texts:string[]) {
|
async addText(texts:string[]) {
|
||||||
|
|
||||||
@@ -147,7 +155,11 @@ export class HypaProcesser{
|
|||||||
]);
|
]);
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
similarityCheck(query1:number[],query2: number[]) {
|
||||||
|
return similarity.cosine(query1, query2)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -526,6 +526,10 @@ export interface loreBook{
|
|||||||
risu_case_sensitive:boolean
|
risu_case_sensitive:boolean
|
||||||
}
|
}
|
||||||
activationPercent?:number
|
activationPercent?:number
|
||||||
|
loreCache?:{
|
||||||
|
key:string
|
||||||
|
data:string[]
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface character{
|
export interface character{
|
||||||
@@ -590,6 +594,7 @@ export interface character{
|
|||||||
depth_prompt?: { depth: number, prompt: string }
|
depth_prompt?: { depth: number, prompt: string }
|
||||||
extentions?:{[key:string]:any}
|
extentions?:{[key:string]:any}
|
||||||
largePortrait?:boolean
|
largePortrait?:boolean
|
||||||
|
lorePlus?:boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -632,6 +637,7 @@ export interface groupChat{
|
|||||||
backgroundCSS?:string
|
backgroundCSS?:string
|
||||||
oneAtTime?:boolean
|
oneAtTime?:boolean
|
||||||
virtualscript?:string
|
virtualscript?:string
|
||||||
|
lorePlus?:boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface botPreset{
|
export interface botPreset{
|
||||||
|
|||||||
Reference in New Issue
Block a user