Update to 0.8.0 (#42)

This commit is contained in:
kwaroran
2023-05-13 00:26:26 +09:00
committed by GitHub
20 changed files with 818 additions and 246 deletions

View File

@@ -8,7 +8,7 @@
},
"package": {
"productName": "RisuAI",
"version": "0.7.9"
"version": "0.8.0"
},
"tauri": {
"allowlist": {

View File

@@ -35,7 +35,7 @@ export const languageEnglish = {
msgSound: "Plays *ding* sound when character responses",
charDesc: "Brief description of the character. this effects characters response.",
charFirstMessage: "First message of the character. this highly effects characters response.",
charNote: "a note that strongly effects model behavior. works in current chat. also known as memory.",
charNote: "a note that strongly effects model behavior. embbedded to current character. also known as UJB.",
toggleNsfw: "toggles NSFW/jailbreak prompt on and off.",
lorebook: "Lorebook is a user-made dictionary for AI. AI only sees it when where is an activation keys in the context.",
loreName: "name of the lore. it dosen't effects the Ai.",
@@ -54,13 +54,25 @@ export const languageEnglish = {
regexScript: "Regex Script is a custom script that is embbedded to the character. it replaces string that matches IN to OUT.\n\nThere are three type options."
+ "- **Modify Input** modifys user's input"
+ "- **Modify Output** modifys character's output"
+ "- **Modify Request Data** modifys current chat data when sent.\n\nIN must be a regex without flags and *\\*.\n\nOUT is a normal string."
+ "- **Modify Request Data** modifys current chat data when sent.\n\nIN must be a regex without flags and without slashes in start and end.\n\nOUT is a normal string."
+ "\n\n If OUT has {{data}} in string, it replaces to matched string."
+ "\n\n If OUT starts with **@@**, it doesn't replaces the string, but instead does a special effect if matching string founds."
+ "\n\n- @@emo (emotion name)\n\n if character is Emotion Images mode, sets (emotion name) as emotion and prevents default.",
experimental: "This is a experimental setting. it might be unstable.",
oogaboogaURL: "If your WebUI supports older version of api, your url should look *like https:.../run/textgen*\n\n"
+ "If your WebUI supports newVersion of api, your url should look like *https://.../api/v1/generate* and use the api server as host, and add --api to arguments."
+ "If your WebUI supports newVersion of api, your url should look like *https://.../api/v1/generate* and use the api server as host, and add --api to arguments.",
exampleMessage: "Example conversations that effects output of the character. it dosen't uses tokens permanently."
+ "\n\nExample format of conversations:"
+ "\n\n```\n<START>\n{{user}}: hi\n{{char}}: hello\n<START>\n{{user}}: hi\nHaruhi: hello\n```"
+ "\n\n```<START>``` Marks the beginning of a new conversation.",
creatorQuotes: "Note that appearances on top of first message. Used to inform users about this character. It doesn't go into prompt.",
systemPrompt: "A prompt that replaces main prompt in settings if its not blank.",
chatNote: "a note that strongly effects model behavior. embbedded to current chat. also known as memory.",
personality: "A brief description about character's personality. \n\n**It is not recommended to use this option. Describe it in character description instead.**",
scenario: "A brief description about character's scenario. \n\n**It is not recommended to use this option. Describe it in character description instead.**",
utilityBot: "When activated, it ignores main prompt. \n\n**It is not recommended to use this option. Modifiy system prompt instead.**",
loreSelective: "If Selective mode is toggled, both Activation Key and Secondary key should have a match to activate the lore."
},
setup: {
chooseProvider: "Choose AI Provider",
@@ -211,6 +223,22 @@ export const languageEnglish = {
editOutput: "Modfiy Output",
editProcess: "Modfiy Request Data",
loadLatest: "Load Latest Backup",
loadOthers: "Load Other Backups"
loadOthers: "Load Other Backups",
exampleMessage: "Example Message",
creatorNotes: "Creator's Comment",
systemPrompt: "System Prompt",
characterNotes: "Character Notes",
personality: "Personality",
scenario: "Scenario",
alternateGreetings: "Alternate Greetings",
unrecommended: "Not Recommended",
chatNotes: "Chat Notes",
showUnrecommended: "Show Unrecommended Settings",
altGreet: "Alternative First Messages",
scripts: "Scripts",
settings: "Settings",
selective: "Selective",
SecondaryKeys: 'Secondary keys',
useGlobalSettings: "Use Global Settings",
recursiveScanning: "Recursive Scanning"
}

View File

@@ -183,7 +183,7 @@ export const languageKorean = {
regexScript: "정규식 스크립트는 캐릭터에 종속된 커스텀 스크립트입니다. IN의 조건에 맞는 문자열을 OUT으로 변경합니다.\n\n타입은 세가지가 있습니다."
+ "- **입력문 수정** 유저의 입력문을 수정합니다"
+ "- **출력문 수정** 캐릭터의 출력문을 수정합니다"
+ "- **리퀘스트 데이터 수정** 리퀘스트를 보낼 때 채팅 데이터를 수정합니다.\n\nIN은 flag와 *\\* 가 없는 Regex여야 합니다.\n\nOUT은 일반 문자열입니다."
+ "- **리퀘스트 데이터 수정** 리퀘스트를 보낼 때 채팅 데이터를 수정합니다.\n\nIN은 flag가 없고, 양끝에 슬레시가 없는 Regex여야 합니다.\n\nOUT은 일반 문자열입니다."
+ "\n\n 만약 OUT 문자열에 {{data}}가 있으면, 매칭된 문자열로 바뀝니다."
+ "\n\n 만약 OUT이 @@로 시작된다면, 특수한 효과를 냅니다"
+ "\n\n- @@emo (emotion name)\n\n 감정 이미지 모드일 시 (emotion name)을 감정으로 정하고 감정 처리를 하지 않습니다.",
@@ -208,5 +208,22 @@ export const languageKorean = {
editOutput: "출력문 수정",
editProcess: "리퀘스트 데이터 수정",
loadLatest: "가장 최근 백업 불러오기",
loadOthers: "다른 백업 불러오기"
loadOthers: "다른 백업 불러오기",
exampleMessage: "예시 대화",
creatorNotes: "제작자 코멘트",
systemPrompt: "시스템 프롬프트",
characterNotes: "캐릭터 노트",
personality: "성격",
scenario: "시나리오",
alternateGreetings: "추가 첫 메시지",
unrecommended: "비권장",
chatNotes: "채팅 노트",
showUnrecommended: "비권장 설정 보이기",
altGreet: "추가 첫 메시지",
scripts: "스크립트",
settings: "설정",
selective: "멀티플 키",
SecondaryKeys: '두번째 키',
useGlobalSettings: "글로벌 설정 사용",
recursiveScanning: "재귀 검색"
}

View File

@@ -10,13 +10,14 @@
import { replacePlaceholders } from "../../ts/util";
export let message = ''
export let name = ''
export let img = ''
export let img:string|Promise<string> = ''
export let idx = -1
export let rerollIcon = false
export let onReroll = () => {}
export let unReroll = () => {}
let translating = false
let editMode = false
export let altGreeting = false
let msgDisplay = ''
@@ -60,15 +61,13 @@
$: displaya(message)
</script>
<div class="flex">
<div class="flex max-w-full">
<div class="text-neutral-200 mt-2 p-2 bg-transparent flex-grow ml-4 mr-4 border-t-gray-900 border-opacity-30 border-transparent flexium items-start">
{#if img === ''}
<div class="rounded-md shadow-lg bg-gray-500 mt-2" style={`height:${$DataBase.iconsize * 3.5 / 100}rem;width:${$DataBase.iconsize * 3.5 / 100}rem`}>
</div>
{:else}
<div class="rounded-md shadow-lg bg-gray-500 mt-2" style={img + `height:${$DataBase.iconsize * 3.5 / 100}rem;width:${$DataBase.iconsize * 3.5 / 100}rem`} />
{/if}
{#await img}
<div class="rounded-md shadow-lg bg-gray-500 mt-2" style={`height:${$DataBase.iconsize * 3.5 / 100}rem;width:${$DataBase.iconsize * 3.5 / 100}rem`} />
{:then m}
<div class="rounded-md shadow-lg bg-gray-500 mt-2" style={m + `height:${$DataBase.iconsize * 3.5 / 100}rem;width:${$DataBase.iconsize * 3.5 / 100}rem`} />
{/await}
<span class="flex flex-col ml-4 w-full">
<div class="flexium items-center chat">
<span class="chat text-xl unmargin">{name}</span>
@@ -106,8 +105,8 @@
<LanguagesIcon />
</button>
{/if}
{#if rerollIcon}
{#if $DataBase.swipe}
{#if rerollIcon || altGreeting}
{#if $DataBase.swipe || altGreeting}
<button class="ml-2 hover:text-green-500 transition-colors" on:click={unReroll}>
<ArrowLeft size={22}/>
</button>

View File

@@ -0,0 +1,20 @@
<div class="flex w-full justify-center mt-4 max-w-100vw">
<div class="w-5/6 max-w-80vw bg-darkbg rounded-md p-3 text-white text-sm">
<h1 class="font-bold mb-2">{language.creatorNotes}
<button class="float-right" on:click={onRemove}>
<XIcon />
</button>
</h1>
<div class="ml-2 max-w-full break-words text chat chattext prose prose-invert">
{@html ParseMarkdown(quote)}
</div>
</div>
</div>
<script lang="ts">
import { XIcon } from "lucide-svelte";
import { language } from "src/lang";
import { ParseMarkdown } from "src/ts/parser";
export let onRemove: () => void
export let quote:string
</script>

View File

@@ -13,6 +13,7 @@
import {cloneDeep} from 'lodash'
import { processScript } from "src/ts/process/scripts";
import GithubStars from "../Others/GithubStars.svelte";
import CreatorQuote from "./CreatorQuote.svelte";
let messageInput = ''
let openMenu = false
@@ -237,88 +238,81 @@
{#each messageForm($DataBase.characters[$selectedCharID].chats[$DataBase.characters[$selectedCharID].chatPage].message, loadPages) as chat, i}
{#if chat.role === 'char'}
{#if $DataBase.characters[$selectedCharID].type !== 'group'}
{#await getCharImage($DataBase.characters[$selectedCharID].image, 'css')}
<Chat
idx={chat.index}
name={$DataBase.characters[$selectedCharID].name}
message={chat.data}
img={''}
rerollIcon={i === 0}
onReroll={reroll}
unReroll={unReroll}
/>
{:then im}
<Chat
idx={chat.index}
name={$DataBase.characters[$selectedCharID].name}
message={chat.data}
img={im}
rerollIcon={i === 0}
onReroll={reroll}
unReroll={unReroll}
/>
{/await}
<Chat
idx={chat.index}
name={$DataBase.characters[$selectedCharID].name}
message={chat.data}
img={getCharImage($DataBase.characters[$selectedCharID].image, 'css')}
rerollIcon={i === 0}
onReroll={reroll}
unReroll={unReroll}
/>
{:else}
{#await getCharImage(findCharacterbyId(chat.saying).image, 'css')}
<Chat
idx={chat.index}
name={findCharacterbyId(chat.saying).name}
message={chat.data}
rerollIcon={i === 0}
onReroll={reroll}
unReroll={unReroll}
img={''}
/>
{:then im}
<Chat
idx={chat.index}
name={findCharacterbyId(chat.saying).name}
rerollIcon={i === 0}
message={chat.data}
onReroll={reroll}
unReroll={unReroll}
img={im}
/>
{/await}
<Chat
idx={chat.index}
name={findCharacterbyId(chat.saying).name}
rerollIcon={i === 0}
message={chat.data}
onReroll={reroll}
unReroll={unReroll}
img={getCharImage(findCharacterbyId(chat.saying).image, 'css')}
/>
{/if}
{:else}
{#await getCharImage($DataBase.userIcon, 'css')}
<Chat
idx={chat.index}
name={$DataBase.username}
message={chat.data}
img={''}
/>
{:then im}
<Chat
idx={chat.index}
name={$DataBase.username}
message={chat.data}
img={im}
/>
{/await}
<Chat
idx={chat.index}
name={$DataBase.username}
message={chat.data}
img={getCharImage($DataBase.userIcon, 'css')}
/>
{/if}
{/each}
{#if $DataBase.characters[$selectedCharID].chats[$DataBase.characters[$selectedCharID].chatPage].message.length <= loadPages}
{#if $DataBase.characters[$selectedCharID].type !== 'group'}
{#await getCharImage($DataBase.characters[$selectedCharID].image, 'css')}
<Chat
name={$DataBase.characters[$selectedCharID].name}
message={ $DataBase.characters[$selectedCharID].firstMessage}
img={''}
idx={-1}
/>
{:then im}
<Chat
name={$DataBase.characters[$selectedCharID].name}
message={ $DataBase.characters[$selectedCharID].firstMessage}
img={im}
idx={-1}
/>
{/await}
<Chat
name={$DataBase.characters[$selectedCharID].name}
message={$DataBase.characters[$selectedCharID].firstMsgIndex === -1 ? $DataBase.characters[$selectedCharID].firstMessage :
$DataBase.characters[$selectedCharID].alternateGreetings[$DataBase.characters[$selectedCharID].firstMsgIndex]}
img={getCharImage($DataBase.characters[$selectedCharID].image, 'css')}
idx={-1}
altGreeting={$DataBase.characters[$selectedCharID].alternateGreetings.length > 0}
onReroll={() => {
const cha = $DataBase.characters[$selectedCharID]
if(cha.type !== 'group'){
if (cha.firstMsgIndex >= (cha.alternateGreetings.length - 1)){
cha.firstMsgIndex = -1
}
else{
cha.firstMsgIndex += 1
}
}
$DataBase.characters[$selectedCharID] = cha
}}
unReroll={() => {
const cha = $DataBase.characters[$selectedCharID]
if(cha.type !== 'group'){
if (cha.firstMsgIndex === -1){
cha.firstMsgIndex = (cha.alternateGreetings.length - 1)
}
else{
cha.firstMsgIndex -= 1
}
}
$DataBase.characters[$selectedCharID] = cha
}}
/>
{#if !$DataBase.characters[$selectedCharID].removedQuotes && $DataBase.characters[$selectedCharID].creatorNotes.length >= 2}
<CreatorQuote quote={$DataBase.characters[$selectedCharID].creatorNotes} onRemove={() => {
const cha = $DataBase.characters[$selectedCharID]
if(cha.type !== 'group'){
cha.removedQuotes = true
}
$DataBase.characters[$selectedCharID] = cha
}} />
{/if}
{/if}
{/if}
{#if openMenu}
<div class="absolute right-2 bottom-16 p-5 bg-darkbg flex flex-col gap-3 text-gray-200" on:click={(e) => {
e.stopPropagation()

View File

@@ -5,15 +5,20 @@
}}>
{#if key === "experimental"}
<FlaskConicalIcon size={14} />
{:else if unrecommended}
<div class="text-red-500 hover:text-green-500">
<AlertTriangle size={14} />
</div>
{:else}
<HelpCircleIcon size={14} />
{/if}
</button>
<script lang="ts">
import { FlaskConicalIcon, HelpCircleIcon } from "lucide-svelte";
import { AlertTriangle, FlaskConicalIcon, HelpCircleIcon } from "lucide-svelte";
import { language } from "src/lang";
import { alertMd } from "src/ts/alert";
export let unrecommended = false
export let key: (keyof (typeof language.help))
</script>

View File

@@ -3,7 +3,7 @@
import { tokenize } from "../../ts/tokenizer";
import { DataBase, type Database, type character, type groupChat } from "../../ts/database";
import { selectedCharID } from "../../ts/stores";
import { PlusIcon, SmileIcon, TrashIcon, UserIcon, ActivityIcon, BookIcon, LoaderIcon, User } from 'lucide-svelte'
import { PlusIcon, SmileIcon, TrashIcon, UserIcon, ActivityIcon, BookIcon, LoaderIcon, User, DnaIcon, CurlyBracesIcon } from 'lucide-svelte'
import Check from "../Others/Check.svelte";
import { addCharEmotion, addingEmotion, getCharImage, rmCharEmotion, selectCharImg, makeGroupImage } from "../../ts/characters";
import LoreBook from "./LoreBookSetting.svelte";
@@ -22,13 +22,15 @@
let tokens = {
desc: 0,
firstMsg: 0,
localNote: 0
localNote: 0,
charaNote: 0
}
let lasttokens = {
desc: '',
firstMsg: '',
localNote: ''
localNote: '',
charaNote: ''
}
async function loadTokenize(chara){
@@ -45,12 +47,18 @@
lasttokens.firstMsg = chara.firstMessage
tokens.firstMsg = await tokenize(chara.firstMessage)
}
if(lasttokens.charaNote !== chara.postHistoryInstructions){
lasttokens.charaNote = chara.postHistoryInstructions
tokens.charaNote = await tokenize(chara.postHistoryInstructions)
}
}
if(lasttokens.localNote !== currentChar.data.chats[currentChar.data.chatPage].note){
lasttokens.localNote = currentChar.data.chats[currentChar.data.chatPage].note
tokens.localNote = await tokenize(currentChar.data.chats[currentChar.data.chatPage].note)
}
}
@@ -148,6 +156,11 @@
<button class={subMenu === 3 ? 'text-gray-200' : 'text-gray-500'} on:click={() => {subMenu = 3;subberMenu = 0}}>
<BookIcon />
</button>
{#if currentChar.type === 'character'}
<button class={subMenu === 4 ? 'text-gray-200' : 'text-gray-500'} on:click={() => {subMenu = 4}}>
<CurlyBracesIcon />
</button>
{/if}
<button class={subMenu === 2 ? 'text-gray-200' : 'text-gray-500'} on:click={() => {subMenu = 2}}>
<ActivityIcon />
</button>
@@ -163,6 +176,10 @@
<span class="text-neutral-200">{language.firstMessage} <Help key="charFirstMessage"/></span>
<textarea class="bg-transparent input-text mt-2 mb-2 text-gray-200 text-xs resize-none h-20 focus:bg-selected" autocomplete="off" bind:value={currentChar.data.firstMessage}></textarea>
<span class="text-gray-400 mb-6 text-sm">{tokens.firstMsg} {language.tokens}</span>
<span class="text-neutral-200">{language.authorNote} <Help key="charNote"/></span>
<textarea class="bg-transparent input-text mt-2 mb-2 text-gray-200 resize-none h-20 focus:bg-selected text-xs" autocomplete="off" bind:value={currentChar.data.postHistoryInstructions}></textarea>
<span class="text-gray-400 mb-6 text-sm">{tokens.charaNote} {language.tokens}</span>
{:else}
<input class="text-neutral-200 mt-2 mb-4 p-2 bg-transparent input-text text-xl focus:bg-selected" placeholder="Group Name" bind:value={currentChar.data.name}>
<span class="text-neutral-200">{language.character}</span>
@@ -191,11 +208,13 @@
</button>
</div>
<span class="text-neutral-200">{language.chatNotes} <Help key="charNote"/></span>
<textarea class="bg-transparent input-text mt-2 mb-2 text-gray-200 resize-none h-20 focus:bg-selected text-xs" autocomplete="off" bind:value={currentChar.data.chats[currentChar.data.chatPage].note}></textarea>
<span class="text-gray-400 mb-6 text-sm">{tokens.localNote} {language.tokens}</span>
{/if}
<span class="text-neutral-200">{language.authorNote} <Help key="charNote"/></span>
<textarea class="bg-transparent input-text mt-2 mb-2 text-gray-200 resize-none h-20 focus:bg-selected text-xs" autocomplete="off" bind:value={currentChar.data.chats[currentChar.data.chatPage].note}></textarea>
<span class="text-gray-400 mb-6 text-sm">{tokens.localNote} {language.tokens}</span>
<div class="flex mt-6 items-center">
<Check bind:check={$DataBase.jailbreakToggle}/>
<span class="text-neutral-200 ml-2">{language.jailbreakToggle}</span>
@@ -356,9 +375,10 @@
{:else if subMenu === 3}
<h2 class="mb-2 text-2xl font-bold mt-2">{language.loreBook} <Help key="lorebook"/></h2>
<LoreBook />
{:else if subMenu === 2}
<h2 class="mb-2 text-2xl font-bold mt-2">{language.advancedSettings}</h2>
{#if currentChar.type !== 'group'}
{:else if subMenu === 4}
{#if currentChar.type === 'character'}
<h2 class="mb-2 text-2xl font-bold mt-2">{language.scripts}</h2>
<span class="text-neutral-200 mt-2">Bias <Help key="bias"/></span>
<table class="contain w-full max-w-full tabler mt-2">
<tr>
@@ -374,7 +394,7 @@
</tr>
{#if currentChar.data.bias.length === 0}
<tr>
<div class="text-gray-500">{language.noBias}</div>
<div class="text-gray-500"> {language.noBias}</div>
</tr>
{/if}
{#each currentChar.data.bias as bias, i}
@@ -394,7 +414,9 @@
}}><TrashIcon /></button>
</tr>
{/each}
</table>
<span class="text-neutral-200 mt-4">{language.regexScript} <Help key="regexScript"/></span>
<table class="contain w-full max-w-full tabler mt-2 flex flex-col p-2 gap-2">
{#if currentChar.data.customscript.length === 0}
@@ -410,7 +432,7 @@
}}/>
{/each}
</table>
<th class="font-medium cursor-pointer hover:text-green-500" on:click={() => {
<button class="font-medium cursor-pointer hover:text-green-500 mb-2" on:click={() => {
if(currentChar.type === 'character'){
let script = currentChar.data.customscript
script.push({
@@ -421,21 +443,95 @@
})
currentChar.data.customscript = script
}
}}><PlusIcon /></th>
<div class="flex items-center mt-4">
<Check bind:check={currentChar.data.utilityBot}/>
<span>{language.utilityBot}</span>
</div>
}}><PlusIcon /></button>
{/if}
{:else if subMenu === 2}
<h2 class="mb-2 text-2xl font-bold mt-2">{language.advancedSettings}</h2>
{#if currentChar.type !== 'group'}
<span class="text-neutral-200">{language.exampleMessage} <Help key="exampleMessage"/></span>
<textarea class="bg-transparent input-text mt-2 mb-2 text-gray-200 text-xs resize-none h-20 focus:bg-selected" autocomplete="off" bind:value={currentChar.data.exampleMessage}></textarea>
<span class="text-neutral-200">{language.creatorNotes} <Help key="creatorQuotes"/></span>
<textarea class="bg-transparent input-text mt-2 mb-2 text-gray-200 text-xs resize-none h-20 focus:bg-selected" autocomplete="off" bind:value={currentChar.data.creatorNotes} on:input={() => {
currentChar.data.removedQuotes = false
}}></textarea>
<span class="text-neutral-200">{language.systemPrompt} <Help key="systemPrompt"/></span>
<textarea class="bg-transparent input-text mt-2 mb-2 text-gray-200 text-xs resize-none h-20 focus:bg-selected" autocomplete="off" bind:value={currentChar.data.systemPrompt}></textarea>
<span class="text-neutral-200">{language.chatNotes} <Help key="chatNote"/></span>
<textarea class="bg-transparent input-text mt-2 mb-2 text-gray-200 resize-none h-20 focus:bg-selected text-xs" autocomplete="off" bind:value={currentChar.data.chats[currentChar.data.chatPage].note}></textarea>
<span class="text-gray-400 mb-6 text-sm">{tokens.localNote} {language.tokens}</span>
{#if $DataBase.showUnrecommended || currentChar.data.personality.length > 3}
<span class="text-neutral-200">{language.personality} <Help key="personality" unrecommended/></span>
<textarea class="bg-transparent input-text mt-2 mb-2 text-gray-200 text-xs resize-none h-20 focus:bg-selected" autocomplete="off" bind:value={currentChar.data.personality}></textarea>
{/if}
{#if $DataBase.showUnrecommended || currentChar.data.scenario.length > 3}
<span class="text-neutral-200">{language.scenario} <Help key="scenario" unrecommended/></span>
<textarea class="bg-transparent input-text mt-2 mb-2 text-gray-200 text-xs resize-none h-20 focus:bg-selected" autocomplete="off" bind:value={currentChar.data.scenario}></textarea>
{/if}
<span class="text-neutral-200 mt-2">{language.altGreet}</span>
<table class="contain w-full max-w-full tabler mt-2">
<tr>
<th class="font-medium">{language.value}</th>
<th class="font-medium cursor-pointer w-10">
<button class="hover:text-green-500" on:click={() => {
if(currentChar.type === 'character'){
let alternateGreetings = currentChar.data.alternateGreetings
alternateGreetings.push('')
currentChar.data.alternateGreetings = alternateGreetings
}
}}>
<PlusIcon />
</button>
</th>
</tr>
{#if currentChar.data.alternateGreetings.length === 0}
<tr>
<div class="text-gray-500"> No Messages</div>
</tr>
{/if}
{#each currentChar.data.alternateGreetings as bias, i}
<tr>
<td class="font-medium truncate">
<textarea class="text-neutral-200 mt-2 mb-4 p-2 bg-transparent input-text focus:bg-selected w-full resize-none" bind:value={currentChar.data.alternateGreetings[i]} placeholder="..." />
</td>
<th class="font-medium cursor-pointer w-10">
<button class="hover:text-green-500" on:click={() => {
if(currentChar.type === 'character'){
currentChar.data.firstMsgIndex = -1
let alternateGreetings = currentChar.data.alternateGreetings
alternateGreetings.splice(i, 1)
currentChar.data.alternateGreetings = alternateGreetings
}
}}>
<TrashIcon />
</button>
</th>
</tr>
{/each}
</table>
{#if $DataBase.showUnrecommended || currentChar.data.utilityBot}
<div class="flex items-center mt-4">
<Check bind:check={currentChar.data.utilityBot}/>
<span>{language.utilityBot} <Help key="utilityBot" unrecommended/></span>
</div>
{/if}
<button on:click={async () => {
exportChar($selectedCharID)
}} class="text-neutral-200 mt-6 text-lg bg-transparent border-solid border-1 border-borderc p-4 hover:bg-green-500 transition-colors cursor-pointer">{language.exportCharacter}</button>
{:else}
<div class="flex mb-2 items-center">
<Check bind:check={currentChar.data.useCharacterLore}/>
<span class="text-neutral-200 ml-2">{language.useCharLorebook} <Help key="experimental"/></span>
</div>
<div class="flex mb-2 items-center">
<Check bind:check={currentChar.data.useCharacterLore}/>
<span class="text-neutral-200 ml-2">{language.useCharLorebook} <Help key="experimental"/></span>
</div>
{/if}
<button on:click={async () => {

View File

@@ -13,9 +13,10 @@
<div class="w-full flex flex-col">
<div class="flex items-center transition-colors w-full ">
<button class="endflex valuer border-borderc" on:click={() => {
value.secondkey = value.secondkey ?? ''
open = !open
}}>
<span>{value.comment.length === 0 ? 'Unnamed Lore' : value.comment}</span>
<span>{value.comment.length === 0 ? value.key.length === 0 ? "Unnamed Lore" : value.key : value.comment}</span>
</button>
<button class="valuer" on:click={async () => {
const d = await alertConfirm(language.removeConfirm + value.comment)
@@ -34,15 +35,25 @@
<span class="text-neutral-200 mt-6">{language.activationKeys} <Help key="loreActivationKey"/></span>
<span class="text-xs text-gray-500">{language.activationKeysInfo}</span>
<input class="text-neutral-200 p-2 bg-transparent input-text focus:bg-selected text-sm" bind:value={value.key}>
{#if value.selective}
<span class="text-neutral-200 mt-6">{language.SecondaryKeys}</span>
<span class="text-xs text-gray-500">{language.activationKeysInfo}</span>
<input class="text-neutral-200 p-2 bg-transparent input-text focus:bg-selected text-sm" bind:value={value.secondkey}>
{/if}
{/if}
<span class="text-neutral-200 mt-4">{language.insertOrder} <Help key="loreorder"/></span>
<input class="text-neutral-200 p-2 bg-transparent input-text focus:bg-selected text-sm" bind:value={value.insertorder} type="number" min={0} max={1000}>
<span class="text-neutral-200 mt-4">{language.prompt}</span>
<textarea class="bg-transparent input-text mt-2 text-gray-200 resize-none h-20 focus:bg-selected text-xs" autocomplete="off" bind:value={value.content}></textarea>
<div class="flex items-center mt-4 mb-6">
<div class="flex items-center mt-4">
<Check bind:check={value.alwaysActive}/>
<span>{language.alwaysActive}</span>
</div>
<div class="flex items-center mt-2 mb-6">
<Check bind:check={value.selective}/>
<span>{language.selective} <Help key="loreSelective"/></span>
</div>
</div>
{/if}
</div>

View File

@@ -3,8 +3,9 @@
import { language } from "../../lang";
import {selectedCharID} from '../../ts/stores'
import { DownloadIcon, FolderUpIcon, ImportIcon, PlusIcon } from "lucide-svelte";
import { addLorebook, exportLoreBook, importLoreBook } from "../../ts/lorebook";
import { addLorebook, exportLoreBook, importLoreBook } from "../../ts/process/lorebook";
import LoreBookData from "./LoreBookData.svelte";
import Check from "../Others/Check.svelte";
let submenu = 0
</script>
@@ -19,43 +20,77 @@
}} class="flex-1 border-solid border-borderc border-1 border-l-transparent p-2 flex justify-center cursor-pointer" class:bg-selected={submenu === 1}>
<span>{language.Chat}</span>
</button>
<button on:click={() => {
submenu = 2
}} class="flex-1 border-solid border-borderc border-1 border-l-transparent p-2 flex justify-center cursor-pointer" class:bg-selected={submenu === 2}>
<span>{language.settings}</span>
</button>
</div>
<span class="text-gray-500 mt-2 mb-6 text-sm">{submenu === 0 ? $DataBase.characters[$selectedCharID].type === 'group' ? language.groupLoreInfo : language.globalLoreInfo : language.localLoreInfo}</span>
<div class="border-solid border-borderc p-2 flex flex-col border-1">
{#if submenu === 0}
{#if $DataBase.characters[$selectedCharID].globalLore.length === 0}
<span class="text-gray-500">No Lorebook</span>
{:else}
{#each $DataBase.characters[$selectedCharID].globalLore as book, i}
{#if i !== 0}
<div class="border-borderc mt-2 mb-2 w-full border-solid border-b-1 seperator"></div>
{/if}
<LoreBookData bind:value={$DataBase.characters[$selectedCharID].globalLore[i]} onRemove={() => {
let lore = $DataBase.characters[$selectedCharID].globalLore
lore.splice(i, 1)
$DataBase.characters[$selectedCharID].globalLore = lore
}}/>
{/each}
{#if submenu !== 2}
<span class="text-gray-500 mt-2 mb-6 text-sm">{submenu === 0 ? $DataBase.characters[$selectedCharID].type === 'group' ? language.groupLoreInfo : language.globalLoreInfo : language.localLoreInfo}</span>
<div class="border-solid border-borderc p-2 flex flex-col border-1">
{#if submenu === 0}
{#if $DataBase.characters[$selectedCharID].globalLore.length === 0}
<span class="text-gray-500">No Lorebook</span>
{:else}
{#each $DataBase.characters[$selectedCharID].globalLore as book, i}
{#if i !== 0}
<div class="border-borderc mt-2 mb-2 w-full border-solid border-b-1 seperator"></div>
{/if}
<LoreBookData bind:value={$DataBase.characters[$selectedCharID].globalLore[i]} onRemove={() => {
let lore = $DataBase.characters[$selectedCharID].globalLore
lore.splice(i, 1)
$DataBase.characters[$selectedCharID].globalLore = lore
}}/>
{/each}
{/if}
{:else if submenu === 1}
{#if $DataBase.characters[$selectedCharID].chats[$DataBase.characters[$selectedCharID].chatPage].localLore.length === 0}
<span class="text-gray-500">No Lorebook</span>
{:else}
{#each $DataBase.characters[$selectedCharID].chats[$DataBase.characters[$selectedCharID].chatPage].localLore as book, i}
{#if i !== 0}
<div class="border-borderc mt-2 mb-2 w-full border-solid border-b-1 seperator"></div>
{/if}
<LoreBookData bind:value={$DataBase.characters[$selectedCharID].chats[$DataBase.characters[$selectedCharID].chatPage].localLore[i]} onRemove={() => {
let lore = $DataBase.characters[$selectedCharID].chats[$DataBase.characters[$selectedCharID].chatPage].localLore
lore.splice(i, 1)
$DataBase.characters[$selectedCharID].chats[$DataBase.characters[$selectedCharID].chatPage].localLore = lore
}}/>
{/each}
{/if}
{/if}
</div>
{:else}
{#if $DataBase.characters[$selectedCharID].loreSettings}
<div class="flex items-center mt-4">
<Check check={false} onChange={() => {
$DataBase.characters[$selectedCharID].loreSettings = undefined
}}/>
<span>{language.useGlobalSettings}</span>
</div>
<div class="flex items-center mt-4">
<Check bind:check={$DataBase.characters[$selectedCharID].loreSettings.recursiveScanning}/>
<span>{language.recursiveScanning}</span>
</div>
<span class="text-neutral-200 mt-4 mb-2">{language.loreBookDepth}</span>
<input class="text-neutral-200 mb-4 p-2 bg-transparent input-text focus:bg-selected text-sm" type="number" min={0} max="20" bind:value={$DataBase.characters[$selectedCharID].loreSettings.scanDepth}>
<span class="text-neutral-200">{language.loreBookToken}</span>
<input class="text-neutral-200 mb-4 p-2 bg-transparent input-text focus:bg-selected text-sm" type="number" min={0} max="4096" bind:value={$DataBase.characters[$selectedCharID].loreSettings.tokenBudget}>
{:else}
{#if $DataBase.characters[$selectedCharID].chats[$DataBase.characters[$selectedCharID].chatPage].localLore.length === 0}
<span class="text-gray-500">No Lorebook</span>
{:else}
{#each $DataBase.characters[$selectedCharID].chats[$DataBase.characters[$selectedCharID].chatPage].localLore as book, i}
{#if i !== 0}
<div class="border-borderc mt-2 mb-2 w-full border-solid border-b-1 seperator"></div>
{/if}
<LoreBookData bind:value={$DataBase.characters[$selectedCharID].chats[$DataBase.characters[$selectedCharID].chatPage].localLore[i]} onRemove={() => {
let lore = $DataBase.characters[$selectedCharID].chats[$DataBase.characters[$selectedCharID].chatPage].localLore
lore.splice(i, 1)
$DataBase.characters[$selectedCharID].chats[$DataBase.characters[$selectedCharID].chatPage].localLore = lore
}}/>
{/each}
{/if}
<div class="flex items-center mt-4">
<Check check={true} onChange={() => {
$DataBase.characters[$selectedCharID].loreSettings = {
tokenBudget: $DataBase.loreBookToken,
scanDepth:$DataBase.loreBookDepth,
recursiveScanning: false
}
}}/>
<span>{language.useGlobalSettings}</span>
</div>
{/if}
</div>
{/if}
{#if submenu !== 2}
<div class="text-gray-500 mt-2 flex">
<button on:click={() => {addLorebook(submenu)}} class="hover:text-neutral-200 cursor-pointer">
@@ -72,7 +107,7 @@
<FolderUpIcon />
</button>
</div>
{/if}
<style>
.seperator{
border-top: 0px;

View File

@@ -499,7 +499,10 @@
<Check bind:check={$DataBase.useSayNothing}/>
<span>{language.sayNothing}</span>
</div>
<div class="flex items-center mt-4">
<Check bind:check={$DataBase.showUnrecommended}/>
<span>{language.showUnrecommended}</span>
</div>
<button
on:click={async () => {
alertMd(getRequestLog())

View File

@@ -47,6 +47,7 @@ html, body{
::-webkit-scrollbar {
width: 5px;
height: 5px;
}
/* Track */

View File

@@ -1,6 +1,6 @@
import { get } from "svelte/store"
import { alertConfirm, alertError, alertNormal, alertStore } from "./alert"
import { DataBase, defaultSdDataFunc, type character, saveImage, setDatabase, type customscript } from "./database"
import { alertConfirm, alertError, alertNormal, alertSelect, alertStore } from "./alert"
import { DataBase, defaultSdDataFunc, type character, saveImage, setDatabase, type customscript, type loreSettings, type loreBook } from "./database"
import { checkNullish, selectSingleFile, sleep } from "./util"
import { language } from "src/lang"
import { encode as encodeMsgpack, decode as decodeMsgpack } from "@msgpack/msgpack";
@@ -11,7 +11,6 @@ import { characterFormatUpdate } from "./characters"
import { downloadFile, readImage } from "./globalApi"
import { cloneDeep } from "lodash"
type CharacterBook = null
export async function importCharacter() {
try {
@@ -160,20 +159,11 @@ export async function characterHubImport() {
function convertOldTavernAndJSON(charaData:OldTavernChar, imgp:string|undefined = undefined):character{
let desc = charaData.description ?? ''
if(charaData.personality){
desc += '\n\n' + charaData.personality
}
if(charaData.scenario){
desc += '\n\n' + charaData.scenario
}
return {
name: charaData.name ?? 'unknown name',
firstMessage: charaData.first_mes ?? 'unknown first message',
desc: desc,
desc: charaData.description ?? '',
notes: '',
chats: [{
message: [],
@@ -191,13 +181,27 @@ function convertOldTavernAndJSON(charaData:OldTavernChar, imgp:string|undefined
sdData: defaultSdDataFunc(),
utilityBot: false,
customscript: [],
exampleMessage: charaData.mes_example
exampleMessage: charaData.mes_example,
creatorNotes:'',
systemPrompt:'',
postHistoryInstructions:'',
alternateGreetings:[],
tags:[],
creator:"",
characterVersion: 0,
personality: charaData.personality ?? '',
scenario:charaData.scenario ?? '',
firstMsgIndex: -1
}
}
export async function exportChar(charaID:number) {
const db = get(DataBase)
let char:character = JSON.parse(JSON.stringify(db.characters[charaID]))
let char = cloneDeep(db.characters[charaID])
if(char.type === 'group'){
return
}
if(!char.image){
alertError('Image Required')
@@ -208,6 +212,12 @@ export async function exportChar(charaID:number) {
return
}
const sel = await alertSelect(['Export as Spec V2','Export as Old RisuCard'])
if(sel === '0'){
exportSpecV2(char)
return
}
alertStore.set({
type: 'wait',
msg: 'Loading...'
@@ -288,6 +298,7 @@ async function importSpecv2(card:CharacterCardV2, img?:Uint8Array):Promise<boole
if(!card ||card.spec !== 'chara_card_v2'){
return false
}
const data = card.data
const im = img ? await saveImage(PngMetadata.filter(img)) : undefined
let db = get(DataBase)
@@ -319,11 +330,45 @@ async function importSpecv2(card:CharacterCardV2, img?:Uint8Array):Promise<boole
sdData = risuext.sdData ?? sdData
}
const charbook = data.character_book
let lorebook:loreBook[] = []
let loresettings:undefined|loreSettings = undefined
let loreExt:undefined|any = undefined
if(charbook){
if((!checkNullish(charbook.recursive_scanning)) &&
(!checkNullish(charbook.scan_depth)) &&
(!checkNullish(charbook.token_budget))){
loresettings = {
tokenBudget:charbook.token_budget,
scanDepth:charbook.scan_depth,
recursiveScanning: charbook.recursive_scanning
}
}
loreExt = charbook.extensions
for(const book of charbook.entries){
lorebook.push({
key: book.keys.join(', '),
secondkey: book.secondary_keys?.join(', ') ?? '',
insertorder: book.insertion_order,
comment: book.name ?? book.comment ?? "",
content: book.content,
mode: "normal",
alwaysActive: book.constant ?? false,
selective: book.selective ?? false,
extentions: book.extensions
})
}
}
let char:character = {
name: data.name,
firstMessage: data.first_mes,
desc: data.description,
notes: data.post_history_instructions,
name: data.name ?? '',
firstMessage: data.first_mes ?? '',
desc: data.description ?? '',
notes: '',
chats: [{
message: [],
note: '',
@@ -334,15 +379,34 @@ async function importSpecv2(card:CharacterCardV2, img?:Uint8Array):Promise<boole
image: im,
emotionImages: emotions,
bias: bias,
globalLore: [], //lorebook
globalLore: lorebook, //lorebook
viewScreen: viewScreen,
chaId: uuidv4(),
sdData: sdData,
utilityBot: utilityBot,
customscript: customScripts,
exampleMessage: data.mes_example
exampleMessage: data.mes_example ?? '',
creatorNotes:data.creator_notes ?? '',
systemPrompt:data.system_prompt ?? '',
postHistoryInstructions:data.post_history_instructions ?? '',
alternateGreetings:data.alternate_greetings ?? [],
tags:data.tags ?? [],
creator:data.creator ?? '',
characterVersion: data.character_version ?? 0,
personality:data.personality ?? '',
scenario:data.scenario ?? '',
firstMsgIndex: -1,
removedQuotes: false,
loreSettings: loresettings,
loreExt: loreExt,
additionalData: {
tag: data.tags,
creator: data.creator,
character_version: data.character_version
}
}
db.characters.push(char)
setDatabase(db)
@@ -351,6 +415,103 @@ async function importSpecv2(card:CharacterCardV2, img?:Uint8Array):Promise<boole
}
export async function exportSpecV2(char:character) {
let img = await readImage(char.image)
try{
let charBook:charBookEntry[] = []
for(const lore of char.globalLore){
charBook.push({
keys: lore.key.split(',').map(r => r.trim()),
secondary_keys: lore.selective ? lore.secondkey.split(',').map(r => r.trim()) : undefined,
content: lore.content,
extensions: lore.extentions ?? {},
enabled: true,
insertion_order: lore.insertorder,
constant: lore.alwaysActive,
selective:lore.selective,
name: lore.comment,
comment: lore.comment
})
}
const card:CharacterCardV2 = {
spec: "chara_card_v2",
spec_version: "2.0",
data: {
name: char.name,
description: char.desc,
personality: char.personality,
scenario: char.scenario,
first_mes: char.firstMessage,
mes_example: char.exampleMessage,
creator_notes: char.creatorNotes,
system_prompt: char.systemPrompt,
post_history_instructions: char.postHistoryInstructions,
alternate_greetings: char.alternateGreetings,
character_book: {
scan_depth: char.loreSettings?.scanDepth,
token_budget: char.loreSettings?.tokenBudget,
recursive_scanning: char.loreSettings?.recursiveScanning,
extensions: char.loreExt ?? {},
entries: []
},
tags: char.additionalData?.tag ?? [],
creator: char.additionalData?.creator ?? '',
character_version: char.additionalData?.character_version ?? 0,
extensions: {
risuai: {
emotions: char.emotionImages,
bias: char.bias,
viewScreen: char.viewScreen,
customScripts: char.customscript,
utilityBot: char.utilityBot,
sdData: char.sdData
}
}
}
}
if(card.data.extensions.risuai.emotions && card.data.extensions.risuai.emotions.length > 0){
for(let i=0;i<card.data.extensions.risuai.emotions.length;i++){
alertStore.set({
type: 'wait',
msg: `Loading... (Getting Emotions ${i} / ${card.data.extensions.risuai.emotions.length})`
})
const rData = await readImage(card.data.extensions.risuai.emotions[i][1])
char.emotionImages[i][1] = Buffer.from(rData).toString('base64')
}
}
alertStore.set({
type: 'wait',
msg: 'Loading... (Writing Exif)'
})
await sleep(10)
img = PngMetadata.write(img, {
'chara': Buffer.from(JSON.stringify(card)).toString('base64'),
})
alertStore.set({
type: 'wait',
msg: 'Loading... (Writing)'
})
char.image = ''
await sleep(10)
await downloadFile(`${char.name.replace(/[<>:"/\\|?*\.\,]/g, "")}_export.png`, img)
alertNormal(language.successExport)
}
catch(e){
alertError(`${e}`)
}
}
type CharacterCardV2 = {
spec: 'chara_card_v2'
@@ -396,3 +557,33 @@ interface OldTavernChar{
scenario: ""
talkativeness: "0.5"
}
type CharacterBook = {
name?: string
description?: string
scan_depth?: number // agnai: "Memory: Chat History Depth"
token_budget?: number // agnai: "Memory: Context Limit"
recursive_scanning?: boolean // no agnai equivalent. whether entry content can trigger other entries
extensions: Record<string, any>
entries: Array<charBookEntry>
}
interface charBookEntry{
keys: Array<string>
content: string
extensions: Record<string, any>
enabled: boolean
insertion_order: number // if two entries inserted, lower "insertion order" = inserted higher
// FIELDS WITH NO CURRENT EQUIVALENT IN SILLY
name?: string // not used in prompt engineering
priority?: number // if token budget reached, lower priority value = discarded first
// FIELDS WITH NO CURRENT EQUIVALENT IN AGNAI
id?: number // not used in prompt engineering
comment?: string // not used in prompt engineering
selective?: boolean // if `true`, require a key from both `keys` and `secondary_keys` to trigger the entry
secondary_keys?: Array<string> // see field `selective`. ignored if selective == false
constant?: boolean // if true, always inserted in the prompt (within budget limit)
position?: 'before_char' | 'after_char' // whether the entry is placed before or after the character defs
}

View File

@@ -37,6 +37,7 @@ export function createNewGroup(){
emotionImages: [],
customscript: [],
chaId: uuidv4(),
firstMsgIndex: -1
})
setDatabase(db)
return db.characters.length - 1
@@ -269,6 +270,18 @@ export function characterFormatUpdate(index:number|character){
if(checkNullish(cha.utilityBot)){
cha.utilityBot = false
}
cha.alternateGreetings = cha.alternateGreetings ?? []
cha.exampleMessage = cha.exampleMessage ?? ''
cha.creatorNotes = cha.creatorNotes ?? ''
cha.systemPrompt = cha.systemPrompt ?? ''
cha.postHistoryInstructions = cha.postHistoryInstructions ?? ''
cha.tags = cha.tags ?? []
cha.creator = cha.creator ?? ''
cha.characterVersion = cha.characterVersion ?? 0
cha.personality = cha.personality ?? ''
cha.scenario = cha.scenario ?? ''
cha.firstMsgIndex = cha.firstMsgIndex ?? -1
}
if(checkNullish(cha.customscript)){
cha.customscript = []
@@ -303,7 +316,17 @@ export function createBlankChar():character{
sdData: defaultSdDataFunc(),
utilityBot: false,
customscript: [],
exampleMessage: ''
exampleMessage: '',
creatorNotes:'',
systemPrompt:'',
postHistoryInstructions:'',
alternateGreetings:[],
tags:[],
creator:"",
characterVersion: 0,
personality:"",
scenario:"",
firstMsgIndex: -1
}
}

View File

@@ -7,7 +7,7 @@ import { cloneDeep } from 'lodash';
export const DataBase = writable({} as any as Database)
export const loadedStore = writable(false)
export let appVer = '0.7.9'
export let appVer = '0.8.0'
export function setDatabase(data:Database){
@@ -178,6 +178,9 @@ export function setDatabase(data:Database){
if(checkNullish(data.requestproxy)){
data.requestproxy = ''
}
if(checkNullish(data.showUnrecommended)){
data.showUnrecommended = false
}
if(checkNullish(data.sdConfig)){
data.sdConfig = {
width:512,
@@ -213,11 +216,14 @@ export interface customscript{
export interface loreBook{
key:string
secondkey:string
insertorder: number
comment: string
content: string
mode: 'multiple'|'constant'|'normal',
alwaysActive: boolean
selective:boolean
extentions?:{}
}
export interface character{
@@ -238,8 +244,34 @@ export interface character{
customscript: customscript[]
utilityBot: boolean
exampleMessage:string
removedQuotes?:boolean
creatorNotes:string
systemPrompt:string
postHistoryInstructions:string
alternateGreetings:string[]
tags:string[]
creator:string
characterVersion: number
personality:string
scenario:string
firstMsgIndex:number
loreSettings?:loreSettings
loreExt?:any
additionalData?: {
tag?:string[]
creator?:string
character_version?:number
}
}
export interface loreSettings{
tokenBudget: number
scanDepth:number
recursiveScanning: boolean
}
export interface groupChat{
type: 'group'
image?:string
@@ -255,6 +287,11 @@ export interface groupChat{
emotionImages: [string, string][]
customscript: customscript[],
chaId: string
alternateGreetings?: string[]
creatorNotes?:string,
removedQuotes?:boolean
firstMsgIndex?:number,
loreSettings?:loreSettings
}
export interface botPreset{
@@ -348,6 +385,7 @@ export interface Database{
didFirstSetup: boolean
requestmet: string
requestproxy: string
showUnrecommended:boolean
}

View File

@@ -0,0 +1,62 @@
import type { OpenAIChat } from ".";
import type { character } from "../database";
import { replacePlaceholders } from "../util";
export function exampleMessage(char:character):OpenAIChat[]{
if(char.exampleMessage === ''){
return []
}
const messages = char.exampleMessage.split('\n')
let result:OpenAIChat[] = []
let currentMessage:OpenAIChat
function add(){
if(currentMessage){
result.push(currentMessage)
}
}
for(const mes of messages){
const trimed = mes.trim()
const lowered = trimed.toLocaleLowerCase()
if(lowered === '<start>'){
add()
result.push({
role: "system",
content: '[Start a new chat]'
})
}
else if(lowered.startsWith('{{char}}:') || lowered.startsWith('<bot>:') || lowered.startsWith(`${char.name}:`)){
add()
currentMessage = {
role: "assistant",
content: trimed.split(':', 2)[1]
}
}
else if(lowered.startsWith('{{user}}:') || lowered.startsWith('<user>:')){
add()
currentMessage = {
role: "user",
content: trimed.split(':', 2)[1]
}
}
else{
if(currentMessage){
currentMessage.content += "\n" + trimed
}
}
}
add()
result = result.map((r) => {
return {
role: r.role,
content: replacePlaceholders(r.content, char.name)
}
})
return result
}

View File

@@ -4,11 +4,12 @@ import { CharEmotion, selectedCharID } from "../stores";
import { tokenize, tokenizeNum } from "../tokenizer";
import { language } from "../../lang";
import { alertError } from "../alert";
import { loadLoreBookPrompt } from "../lorebook";
import { loadLoreBookPrompt } from "./lorebook";
import { findCharacterbyId, replacePlaceholders } from "../util";
import { requestChatData } from "./request";
import { stableDiff } from "./stableDiff";
import { processScript, processScriptFull } from "./scripts";
import { exampleMessage } from "./exampleMessages";
export interface OpenAIChat{
role: 'system'|'user'|'assistant'
@@ -99,9 +100,11 @@ export async function sendChat(chatProcessIndex = -1):Promise<boolean> {
}
if(!currentChar.utilityBot){
const mainp = currentChar.systemPrompt.length > 3 ? currentChar.systemPrompt : db.mainPrompt
unformated.main.push({
role: 'system',
content: replacePlaceholders(db.mainPrompt + ((db.additionalPrompt === '' || (!db.promptPreprocess)) ? '' : `\n${db.additionalPrompt}`), currentChar.name)
content: replacePlaceholders(mainp + ((db.additionalPrompt === '' || (!db.promptPreprocess)) ? '' : `\n${db.additionalPrompt}`), currentChar.name)
})
if(db.jailbreakToggle){
@@ -122,10 +125,30 @@ export async function sendChat(chatProcessIndex = -1):Promise<boolean> {
content: replacePlaceholders(currentChat.note, currentChar.name)
})
unformated.description.push({
role: 'system',
content: replacePlaceholders((db.promptPreprocess ? db.descriptionPrefix: '') + currentChar.desc, currentChar.name)
})
if(currentChar.postHistoryInstructions !== ''){
unformated.authorNote.push({
role: 'system',
content: replacePlaceholders(currentChar.postHistoryInstructions, currentChar.name)
})
}
{
let description = replacePlaceholders((db.promptPreprocess ? db.descriptionPrefix: '') + currentChar.desc, currentChar.name)
if(currentChar.personality){
description += replacePlaceholders("\n\nDescription of {{char}}: " + currentChar.personality,currentChar.name)
}
if(currentChar.scenario){
description += replacePlaceholders("\n\nCircumstances and context of the dialogue: " + currentChar.scenario,currentChar.name)
}
unformated.description.push({
role: 'system',
content: description
})
}
unformated.lorebook.push({
role: 'system',
@@ -139,20 +162,12 @@ export async function sendChat(chatProcessIndex = -1):Promise<boolean> {
}).join('\n\n')
}).join('\n\n')) + db.maxResponse) + 150
let chats:OpenAIChat[] = []
let chats:OpenAIChat[] = exampleMessage(currentChar)
if(nowChatroom.type === 'group'){
chats.push({
role: 'system',
content: '[Start a new group chat]'
})
}
else{
chats.push({
role: 'system',
content: '[Start a new chat]'
})
}
chats.push({
role: 'system',
content: '[Start a new chat]'
})
chats.push({
role: 'assistant',
@@ -349,25 +364,6 @@ export async function sendChat(chatProcessIndex = -1):Promise<boolean> {
}
}
}
// const promptbody:OpenAIChat[] = [
// {
// role:'system',
// content: `assistant is a emotion extractor. user will input a prompt of a character, and assistant must output the emotion of a character.\n\n must chosen from this list: ${shuffleArray(emotionList).join(', ')} \noutput only one word.`
// },
// {
// role: 'user',
// content: `"Good morning, Master! Is there anything I can do for you today?"`
// },
// {
// role: 'assistant',
// content: 'happy'
// },
// {
// role: 'user',
// content: result
// },
// ]
const promptbody:OpenAIChat[] = [
{

View File

@@ -1,11 +1,11 @@
import { get } from "svelte/store";
import {selectedCharID} from './stores'
import { DataBase, setDatabase, type loreBook } from "./database";
import { tokenize } from "./tokenizer";
import { selectSingleFile } from "./util";
import { alertError, alertNormal } from "./alert";
import { language } from "../lang";
import { downloadFile } from "./globalApi";
import {selectedCharID} from '../stores'
import { DataBase, setDatabase, type loreBook } from "../database";
import { tokenize } from "../tokenizer";
import { selectSingleFile } from "../util";
import { alertError, alertNormal } from "../alert";
import { language } from "../../lang";
import { downloadFile } from "../globalApi";
export function addLorebook(type:number) {
let selectedID = get(selectedCharID)
@@ -17,7 +17,9 @@ export function addLorebook(type:number) {
content: '',
mode: 'normal',
insertorder: 100,
alwaysActive: false
alwaysActive: false,
secondkey: "",
selective: false
})
}
else{
@@ -28,16 +30,20 @@ export function addLorebook(type:number) {
content: '',
mode: 'normal',
insertorder: 100,
alwaysActive: false
alwaysActive: false,
secondkey: "",
selective: false
})
}
setDatabase(db)
}
interface formatedLore{
keys:string[]|'always'
keys:string[]|'always',
secondKey:string[]
content: string
order: number
activatied: boolean
}
const rmRegex = / |\n/g
@@ -45,11 +51,14 @@ const rmRegex = / |\n/g
export async function loadLoreBookPrompt(){
const selectedID = get(selectedCharID)
const db = get(DataBase)
const page = db.characters[selectedID].chatPage
const globalLore = db.characters[selectedID].globalLore
const charLore = db.characters[selectedID].chats[page].localLore
const char = db.characters[selectedID]
const page = char.chatPage
const globalLore = char.globalLore
const charLore = char.chats[page].localLore
const fullLore = globalLore.concat(charLore)
const currentChat = db.characters[selectedID].chats[page].message
const currentChat = char.chats[page].message
const loreDepth = char.loreSettings?.scanDepth ?? db.loreBookDepth
const loreToken = char.loreSettings?.tokenBudget ?? db.loreBookToken
let activatiedPrompt: string[] = []
@@ -61,8 +70,12 @@ export async function loadLoreBookPrompt(){
keys: lore.alwaysActive ? 'always' : lore.key.replace(rmRegex, '').toLocaleLowerCase().split(',').filter((a) => {
return a.length > 1
}),
secondKey: lore.selective ? lore.secondkey.replace(rmRegex, '').toLocaleLowerCase().split(',').filter((a) => {
return a.length > 1
}) : [],
content: lore.content,
order: lore.insertorder
order: lore.insertorder,
activatied: false
})
}
}
@@ -71,26 +84,58 @@ export async function loadLoreBookPrompt(){
return b.order - a.order
})
const formatedChat = currentChat.slice(currentChat.length - db.loreBookDepth,currentChat.length).map((msg) => {
const formatedChat = currentChat.slice(currentChat.length - loreDepth,currentChat.length).map((msg) => {
return msg.data
}).join('||').replace(rmRegex,'').toLocaleLowerCase()
for(const lore of formatedLore){
const totalTokens = await tokenize(activatiedPrompt.concat([lore.content]).join('\n\n'))
if(totalTokens > db.loreBookToken){
break
}
if(lore.keys === 'always'){
activatiedPrompt.push(lore.content)
continue
}
for(const key of lore.keys){
if(formatedChat.includes(key)){
activatiedPrompt.push(lore.content)
let loreListUpdated = true
while(loreListUpdated){
loreListUpdated = false
for(let i=0;i<formatedLore.length;i++){
const lore = formatedLore[i]
if(lore.activatied){
continue
}
const totalTokens = await tokenize(activatiedPrompt.concat([lore.content]).join('\n\n'))
if(totalTokens > loreToken){
break
}
if(lore.keys === 'always'){
activatiedPrompt.push(lore.content)
lore.activatied = true
loreListUpdated = true
continue
}
let firstKeyActivation = false
for(const key of lore.keys){
if(formatedChat.includes(key)){
firstKeyActivation = true
break
}
}
if(firstKeyActivation){
if(lore.secondKey.length === 0){
activatiedPrompt.push(lore.content)
lore.activatied = true
loreListUpdated = true
continue
}
for(const key of lore.secondKey){
if(formatedChat.includes(key)){
activatiedPrompt.push(lore.content)
lore.activatied = true
loreListUpdated = true
break
}
}
}
}
if(!(char.loreSettings?.recursiveScanning)){
break
}
}
@@ -107,6 +152,8 @@ export async function importLoreBook(mode:'global'|'local'){
if(!lorebook){
return
}
try {
const importedlore = JSON.parse(Buffer.from(lorebook).toString('utf-8'))
@@ -129,10 +176,12 @@ export async function importLoreBook(mode:'global'|'local'){
lore.push({
key: currentLore.key.join(', '),
insertorder: currentLore.order,
comment: currentLore.comment.length < 1 ? 'Unnamed Imported Lore': currentLore.comment,
comment: currentLore.comment.length < 1 ? 'Unnamed Imported Lore' : currentLore.comment,
content: currentLore.content,
mode: "normal",
alwaysActive: currentLore.constant
alwaysActive: currentLore.constant,
secondkey: "",
selective: false
})
}
}

View File

@@ -20,13 +20,17 @@ export default {
},
maxWidth:{
'half': '50%',
'80p': '80%',
'80vw': '80vw',
'14': '3.5rem',
'100vw': '100vw'
},
borderWidth: {
'1': '1px',
},
width: {
'2xl': '48rem',
'3xl': '72rem',
},
minHeight:{
'8': '2rem',

View File

@@ -1 +1 @@
{"version":"0.7.9"}
{"version":"0.8.0"}