[feat] lorebook plus

This commit is contained in:
kwaroran
2023-11-13 13:04:14 +09:00
parent a0923f0ae5
commit c3f422104a
7 changed files with 170 additions and 37 deletions

View File

@@ -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+",
} }

View File

@@ -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>

View File

@@ -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}

View File

@@ -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}

View File

@@ -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)

View File

@@ -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)
}
} }

View File

@@ -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{