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": { "package": {
"productName": "RisuAI", "productName": "RisuAI",
"version": "0.7.9" "version": "0.8.0"
}, },
"tauri": { "tauri": {
"allowlist": { "allowlist": {

View File

@@ -35,7 +35,7 @@ export const languageEnglish = {
msgSound: "Plays *ding* sound when character responses", msgSound: "Plays *ding* sound when character responses",
charDesc: "Brief description of the character. this effects characters response.", charDesc: "Brief description of the character. this effects characters response.",
charFirstMessage: "First message of the character. this highly 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.", 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.", 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.", 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." 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 Input** modifys user's input"
+ "- **Modify Output** modifys character's output" + "- **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 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 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.", + "\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.", 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" 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: { setup: {
chooseProvider: "Choose AI Provider", chooseProvider: "Choose AI Provider",
@@ -211,6 +223,22 @@ export const languageEnglish = {
editOutput: "Modfiy Output", editOutput: "Modfiy Output",
editProcess: "Modfiy Request Data", editProcess: "Modfiy Request Data",
loadLatest: "Load Latest Backup", 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타입은 세가지가 있습니다." regexScript: "정규식 스크립트는 캐릭터에 종속된 커스텀 스크립트입니다. IN의 조건에 맞는 문자열을 OUT으로 변경합니다.\n\n타입은 세가지가 있습니다."
+ "- **입력문 수정** 유저의 입력문을 수정합니다" + "- **입력문 수정** 유저의 입력문을 수정합니다"
+ "- **출력문 수정** 캐릭터의 출력문을 수정합니다" + "- **출력문 수정** 캐릭터의 출력문을 수정합니다"
+ "- **리퀘스트 데이터 수정** 리퀘스트를 보낼 때 채팅 데이터를 수정합니다.\n\nIN은 flag와 *\\* 가 없는 Regex여야 합니다.\n\nOUT은 일반 문자열입니다." + "- **리퀘스트 데이터 수정** 리퀘스트를 보낼 때 채팅 데이터를 수정합니다.\n\nIN은 flag가 없고, 양끝에 슬레시가 없는 Regex여야 합니다.\n\nOUT은 일반 문자열입니다."
+ "\n\n 만약 OUT 문자열에 {{data}}가 있으면, 매칭된 문자열로 바뀝니다." + "\n\n 만약 OUT 문자열에 {{data}}가 있으면, 매칭된 문자열로 바뀝니다."
+ "\n\n 만약 OUT이 @@로 시작된다면, 특수한 효과를 냅니다" + "\n\n 만약 OUT이 @@로 시작된다면, 특수한 효과를 냅니다"
+ "\n\n- @@emo (emotion name)\n\n 감정 이미지 모드일 시 (emotion name)을 감정으로 정하고 감정 처리를 하지 않습니다.", + "\n\n- @@emo (emotion name)\n\n 감정 이미지 모드일 시 (emotion name)을 감정으로 정하고 감정 처리를 하지 않습니다.",
@@ -208,5 +208,22 @@ export const languageKorean = {
editOutput: "출력문 수정", editOutput: "출력문 수정",
editProcess: "리퀘스트 데이터 수정", editProcess: "리퀘스트 데이터 수정",
loadLatest: "가장 최근 백업 불러오기", 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"; import { replacePlaceholders } from "../../ts/util";
export let message = '' export let message = ''
export let name = '' export let name = ''
export let img = '' export let img:string|Promise<string> = ''
export let idx = -1 export let idx = -1
export let rerollIcon = false export let rerollIcon = false
export let onReroll = () => {} export let onReroll = () => {}
export let unReroll = () => {} export let unReroll = () => {}
let translating = false let translating = false
let editMode = false let editMode = false
export let altGreeting = false
let msgDisplay = '' let msgDisplay = ''
@@ -60,15 +61,13 @@
$: displaya(message) $: displaya(message)
</script> </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"> <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 === ''} {#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`}> <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> <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`} />
{:else} {/await}
<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}
<span class="flex flex-col ml-4 w-full"> <span class="flex flex-col ml-4 w-full">
<div class="flexium items-center chat"> <div class="flexium items-center chat">
<span class="chat text-xl unmargin">{name}</span> <span class="chat text-xl unmargin">{name}</span>
@@ -106,8 +105,8 @@
<LanguagesIcon /> <LanguagesIcon />
</button> </button>
{/if} {/if}
{#if rerollIcon} {#if rerollIcon || altGreeting}
{#if $DataBase.swipe} {#if $DataBase.swipe || altGreeting}
<button class="ml-2 hover:text-green-500 transition-colors" on:click={unReroll}> <button class="ml-2 hover:text-green-500 transition-colors" on:click={unReroll}>
<ArrowLeft size={22}/> <ArrowLeft size={22}/>
</button> </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 {cloneDeep} from 'lodash'
import { processScript } from "src/ts/process/scripts"; import { processScript } from "src/ts/process/scripts";
import GithubStars from "../Others/GithubStars.svelte"; import GithubStars from "../Others/GithubStars.svelte";
import CreatorQuote from "./CreatorQuote.svelte";
let messageInput = '' let messageInput = ''
let openMenu = false let openMenu = false
@@ -237,88 +238,81 @@
{#each messageForm($DataBase.characters[$selectedCharID].chats[$DataBase.characters[$selectedCharID].chatPage].message, loadPages) as chat, i} {#each messageForm($DataBase.characters[$selectedCharID].chats[$DataBase.characters[$selectedCharID].chatPage].message, loadPages) as chat, i}
{#if chat.role === 'char'} {#if chat.role === 'char'}
{#if $DataBase.characters[$selectedCharID].type !== 'group'} {#if $DataBase.characters[$selectedCharID].type !== 'group'}
{#await getCharImage($DataBase.characters[$selectedCharID].image, 'css')} <Chat
<Chat idx={chat.index}
idx={chat.index} name={$DataBase.characters[$selectedCharID].name}
name={$DataBase.characters[$selectedCharID].name} message={chat.data}
message={chat.data} img={getCharImage($DataBase.characters[$selectedCharID].image, 'css')}
img={''} rerollIcon={i === 0}
rerollIcon={i === 0} onReroll={reroll}
onReroll={reroll} unReroll={unReroll}
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}
{:else} {:else}
{#await getCharImage(findCharacterbyId(chat.saying).image, 'css')} <Chat
<Chat idx={chat.index}
idx={chat.index} name={findCharacterbyId(chat.saying).name}
name={findCharacterbyId(chat.saying).name} rerollIcon={i === 0}
message={chat.data} message={chat.data}
rerollIcon={i === 0} onReroll={reroll}
onReroll={reroll} unReroll={unReroll}
unReroll={unReroll} img={getCharImage(findCharacterbyId(chat.saying).image, 'css')}
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}
{/if} {/if}
{:else} {:else}
{#await getCharImage($DataBase.userIcon, 'css')} <Chat
<Chat idx={chat.index}
idx={chat.index} name={$DataBase.username}
name={$DataBase.username} message={chat.data}
message={chat.data} img={getCharImage($DataBase.userIcon, 'css')}
img={''} />
/>
{:then im}
<Chat
idx={chat.index}
name={$DataBase.username}
message={chat.data}
img={im}
/>
{/await}
{/if} {/if}
{/each} {/each}
{#if $DataBase.characters[$selectedCharID].chats[$DataBase.characters[$selectedCharID].chatPage].message.length <= loadPages} {#if $DataBase.characters[$selectedCharID].chats[$DataBase.characters[$selectedCharID].chatPage].message.length <= loadPages}
{#if $DataBase.characters[$selectedCharID].type !== 'group'} {#if $DataBase.characters[$selectedCharID].type !== 'group'}
{#await getCharImage($DataBase.characters[$selectedCharID].image, 'css')} <Chat
<Chat name={$DataBase.characters[$selectedCharID].name}
name={$DataBase.characters[$selectedCharID].name} message={$DataBase.characters[$selectedCharID].firstMsgIndex === -1 ? $DataBase.characters[$selectedCharID].firstMessage :
message={ $DataBase.characters[$selectedCharID].firstMessage} $DataBase.characters[$selectedCharID].alternateGreetings[$DataBase.characters[$selectedCharID].firstMsgIndex]}
img={''} img={getCharImage($DataBase.characters[$selectedCharID].image, 'css')}
idx={-1} idx={-1}
/> altGreeting={$DataBase.characters[$selectedCharID].alternateGreetings.length > 0}
{:then im} onReroll={() => {
<Chat const cha = $DataBase.characters[$selectedCharID]
name={$DataBase.characters[$selectedCharID].name} if(cha.type !== 'group'){
message={ $DataBase.characters[$selectedCharID].firstMessage} if (cha.firstMsgIndex >= (cha.alternateGreetings.length - 1)){
img={im} cha.firstMsgIndex = -1
idx={-1} }
/> else{
{/await} 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} {/if}
{#if openMenu} {#if openMenu}
<div class="absolute right-2 bottom-16 p-5 bg-darkbg flex flex-col gap-3 text-gray-200" on:click={(e) => { <div class="absolute right-2 bottom-16 p-5 bg-darkbg flex flex-col gap-3 text-gray-200" on:click={(e) => {
e.stopPropagation() e.stopPropagation()

View File

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

View File

@@ -3,7 +3,7 @@
import { tokenize } from "../../ts/tokenizer"; import { tokenize } from "../../ts/tokenizer";
import { DataBase, type Database, type character, type groupChat } from "../../ts/database"; import { DataBase, type Database, type character, type groupChat } from "../../ts/database";
import { selectedCharID } from "../../ts/stores"; 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 Check from "../Others/Check.svelte";
import { addCharEmotion, addingEmotion, getCharImage, rmCharEmotion, selectCharImg, makeGroupImage } from "../../ts/characters"; import { addCharEmotion, addingEmotion, getCharImage, rmCharEmotion, selectCharImg, makeGroupImage } from "../../ts/characters";
import LoreBook from "./LoreBookSetting.svelte"; import LoreBook from "./LoreBookSetting.svelte";
@@ -22,13 +22,15 @@
let tokens = { let tokens = {
desc: 0, desc: 0,
firstMsg: 0, firstMsg: 0,
localNote: 0 localNote: 0,
charaNote: 0
} }
let lasttokens = { let lasttokens = {
desc: '', desc: '',
firstMsg: '', firstMsg: '',
localNote: '' localNote: '',
charaNote: ''
} }
async function loadTokenize(chara){ async function loadTokenize(chara){
@@ -45,12 +47,18 @@
lasttokens.firstMsg = chara.firstMessage lasttokens.firstMsg = chara.firstMessage
tokens.firstMsg = await tokenize(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){ if(lasttokens.localNote !== currentChar.data.chats[currentChar.data.chatPage].note){
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) 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}}> <button class={subMenu === 3 ? 'text-gray-200' : 'text-gray-500'} on:click={() => {subMenu = 3;subberMenu = 0}}>
<BookIcon /> <BookIcon />
</button> </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}}> <button class={subMenu === 2 ? 'text-gray-200' : 'text-gray-500'} on:click={() => {subMenu = 2}}>
<ActivityIcon /> <ActivityIcon />
</button> </button>
@@ -163,6 +176,10 @@
<span class="text-neutral-200">{language.firstMessage} <Help key="charFirstMessage"/></span> <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> <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-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} {: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}> <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> <span class="text-neutral-200">{language.character}</span>
@@ -191,11 +208,13 @@
</button> </button>
</div> </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} {/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"> <div class="flex mt-6 items-center">
<Check bind:check={$DataBase.jailbreakToggle}/> <Check bind:check={$DataBase.jailbreakToggle}/>
<span class="text-neutral-200 ml-2">{language.jailbreakToggle}</span> <span class="text-neutral-200 ml-2">{language.jailbreakToggle}</span>
@@ -356,9 +375,10 @@
{:else if subMenu === 3} {:else if subMenu === 3}
<h2 class="mb-2 text-2xl font-bold mt-2">{language.loreBook} <Help key="lorebook"/></h2> <h2 class="mb-2 text-2xl font-bold mt-2">{language.loreBook} <Help key="lorebook"/></h2>
<LoreBook /> <LoreBook />
{:else if subMenu === 2} {:else if subMenu === 4}
<h2 class="mb-2 text-2xl font-bold mt-2">{language.advancedSettings}</h2> {#if currentChar.type === 'character'}
{#if currentChar.type !== 'group'} <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> <span class="text-neutral-200 mt-2">Bias <Help key="bias"/></span>
<table class="contain w-full max-w-full tabler mt-2"> <table class="contain w-full max-w-full tabler mt-2">
<tr> <tr>
@@ -374,7 +394,7 @@
</tr> </tr>
{#if currentChar.data.bias.length === 0} {#if currentChar.data.bias.length === 0}
<tr> <tr>
<div class="text-gray-500">{language.noBias}</div> <div class="text-gray-500"> {language.noBias}</div>
</tr> </tr>
{/if} {/if}
{#each currentChar.data.bias as bias, i} {#each currentChar.data.bias as bias, i}
@@ -394,7 +414,9 @@
}}><TrashIcon /></button> }}><TrashIcon /></button>
</tr> </tr>
{/each} {/each}
</table> </table>
<span class="text-neutral-200 mt-4">{language.regexScript} <Help key="regexScript"/></span> <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"> <table class="contain w-full max-w-full tabler mt-2 flex flex-col p-2 gap-2">
{#if currentChar.data.customscript.length === 0} {#if currentChar.data.customscript.length === 0}
@@ -410,7 +432,7 @@
}}/> }}/>
{/each} {/each}
</table> </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'){ if(currentChar.type === 'character'){
let script = currentChar.data.customscript let script = currentChar.data.customscript
script.push({ script.push({
@@ -421,21 +443,95 @@
}) })
currentChar.data.customscript = script currentChar.data.customscript = script
} }
}}><PlusIcon /></th> }}><PlusIcon /></button>
<div class="flex items-center mt-4"> {/if}
<Check bind:check={currentChar.data.utilityBot}/> {:else if subMenu === 2}
<span>{language.utilityBot}</span> <h2 class="mb-2 text-2xl font-bold mt-2">{language.advancedSettings}</h2>
</div> {#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 () => { <button on:click={async () => {
exportChar($selectedCharID) 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> }} 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} {:else}
<div class="flex mb-2 items-center"> <div class="flex mb-2 items-center">
<Check bind:check={currentChar.data.useCharacterLore}/> <Check bind:check={currentChar.data.useCharacterLore}/>
<span class="text-neutral-200 ml-2">{language.useCharLorebook} <Help key="experimental"/></span> <span class="text-neutral-200 ml-2">{language.useCharLorebook} <Help key="experimental"/></span>
</div> </div>
{/if} {/if}
<button on:click={async () => { <button on:click={async () => {

View File

@@ -13,9 +13,10 @@
<div class="w-full flex flex-col"> <div class="w-full flex flex-col">
<div class="flex items-center transition-colors w-full "> <div class="flex items-center transition-colors w-full ">
<button class="endflex valuer border-borderc" on:click={() => { <button class="endflex valuer border-borderc" on:click={() => {
value.secondkey = value.secondkey ?? ''
open = !open 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>
<button class="valuer" on:click={async () => { <button class="valuer" on:click={async () => {
const d = await alertConfirm(language.removeConfirm + value.comment) 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-neutral-200 mt-6">{language.activationKeys} <Help key="loreActivationKey"/></span>
<span class="text-xs text-gray-500">{language.activationKeysInfo}</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}> <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} {/if}
<span class="text-neutral-200 mt-4">{language.insertOrder} <Help key="loreorder"/></span> <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}> <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> <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> <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}/> <Check bind:check={value.alwaysActive}/>
<span>{language.alwaysActive}</span> <span>{language.alwaysActive}</span>
</div> </div>
<div class="flex items-center mt-2 mb-6">
<Check bind:check={value.selective}/>
<span>{language.selective} <Help key="loreSelective"/></span>
</div>
</div> </div>
{/if} {/if}
</div> </div>

View File

@@ -3,8 +3,9 @@
import { language } from "../../lang"; import { language } from "../../lang";
import {selectedCharID} from '../../ts/stores' import {selectedCharID} from '../../ts/stores'
import { DownloadIcon, FolderUpIcon, ImportIcon, PlusIcon } from "lucide-svelte"; 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 LoreBookData from "./LoreBookData.svelte";
import Check from "../Others/Check.svelte";
let submenu = 0 let submenu = 0
</script> </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}> }} 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> <span>{language.Chat}</span>
</button> </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> </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> {#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"> <div class="border-solid border-borderc p-2 flex flex-col border-1">
{#if submenu === 0} {#if submenu === 0}
{#if $DataBase.characters[$selectedCharID].globalLore.length === 0} {#if $DataBase.characters[$selectedCharID].globalLore.length === 0}
<span class="text-gray-500">No Lorebook</span> <span class="text-gray-500">No Lorebook</span>
{:else} {:else}
{#each $DataBase.characters[$selectedCharID].globalLore as book, i} {#each $DataBase.characters[$selectedCharID].globalLore as book, i}
{#if i !== 0} {#if i !== 0}
<div class="border-borderc mt-2 mb-2 w-full border-solid border-b-1 seperator"></div> <div class="border-borderc mt-2 mb-2 w-full border-solid border-b-1 seperator"></div>
{/if} {/if}
<LoreBookData bind:value={$DataBase.characters[$selectedCharID].globalLore[i]} onRemove={() => { <LoreBookData bind:value={$DataBase.characters[$selectedCharID].globalLore[i]} onRemove={() => {
let lore = $DataBase.characters[$selectedCharID].globalLore let lore = $DataBase.characters[$selectedCharID].globalLore
lore.splice(i, 1) lore.splice(i, 1)
$DataBase.characters[$selectedCharID].globalLore = lore $DataBase.characters[$selectedCharID].globalLore = lore
}}/> }}/>
{/each} {/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} {/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} {:else}
{#if $DataBase.characters[$selectedCharID].chats[$DataBase.characters[$selectedCharID].chatPage].localLore.length === 0} <div class="flex items-center mt-4">
<span class="text-gray-500">No Lorebook</span> <Check check={true} onChange={() => {
{:else} $DataBase.characters[$selectedCharID].loreSettings = {
{#each $DataBase.characters[$selectedCharID].chats[$DataBase.characters[$selectedCharID].chatPage].localLore as book, i} tokenBudget: $DataBase.loreBookToken,
{#if i !== 0} scanDepth:$DataBase.loreBookDepth,
<div class="border-borderc mt-2 mb-2 w-full border-solid border-b-1 seperator"></div> recursiveScanning: false
{/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 <span>{language.useGlobalSettings}</span>
lore.splice(i, 1) </div>
$DataBase.characters[$selectedCharID].chats[$DataBase.characters[$selectedCharID].chatPage].localLore = lore
}}/>
{/each}
{/if}
{/if} {/if}
{/if}
</div> {#if submenu !== 2}
<div class="text-gray-500 mt-2 flex"> <div class="text-gray-500 mt-2 flex">
<button on:click={() => {addLorebook(submenu)}} class="hover:text-neutral-200 cursor-pointer"> <button on:click={() => {addLorebook(submenu)}} class="hover:text-neutral-200 cursor-pointer">
@@ -72,7 +107,7 @@
<FolderUpIcon /> <FolderUpIcon />
</button> </button>
</div> </div>
{/if}
<style> <style>
.seperator{ .seperator{
border-top: 0px; border-top: 0px;

View File

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

View File

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

View File

@@ -1,6 +1,6 @@
import { get } from "svelte/store" import { get } from "svelte/store"
import { alertConfirm, alertError, alertNormal, alertStore } from "./alert" import { alertConfirm, alertError, alertNormal, alertSelect, alertStore } from "./alert"
import { DataBase, defaultSdDataFunc, type character, saveImage, setDatabase, type customscript } from "./database" import { DataBase, defaultSdDataFunc, type character, saveImage, setDatabase, type customscript, type loreSettings, type loreBook } from "./database"
import { checkNullish, selectSingleFile, sleep } from "./util" import { checkNullish, selectSingleFile, sleep } from "./util"
import { language } from "src/lang" import { language } from "src/lang"
import { encode as encodeMsgpack, decode as decodeMsgpack } from "@msgpack/msgpack"; import { encode as encodeMsgpack, decode as decodeMsgpack } from "@msgpack/msgpack";
@@ -11,7 +11,6 @@ import { characterFormatUpdate } from "./characters"
import { downloadFile, readImage } from "./globalApi" import { downloadFile, readImage } from "./globalApi"
import { cloneDeep } from "lodash" import { cloneDeep } from "lodash"
type CharacterBook = null
export async function importCharacter() { export async function importCharacter() {
try { try {
@@ -160,20 +159,11 @@ export async function characterHubImport() {
function convertOldTavernAndJSON(charaData:OldTavernChar, imgp:string|undefined = undefined):character{ 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 { return {
name: charaData.name ?? 'unknown name', name: charaData.name ?? 'unknown name',
firstMessage: charaData.first_mes ?? 'unknown first message', firstMessage: charaData.first_mes ?? 'unknown first message',
desc: desc, desc: charaData.description ?? '',
notes: '', notes: '',
chats: [{ chats: [{
message: [], message: [],
@@ -191,13 +181,27 @@ function convertOldTavernAndJSON(charaData:OldTavernChar, imgp:string|undefined
sdData: defaultSdDataFunc(), sdData: defaultSdDataFunc(),
utilityBot: false, utilityBot: false,
customscript: [], 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) { export async function exportChar(charaID:number) {
const db = get(DataBase) 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){ if(!char.image){
alertError('Image Required') alertError('Image Required')
@@ -208,6 +212,12 @@ export async function exportChar(charaID:number) {
return return
} }
const sel = await alertSelect(['Export as Spec V2','Export as Old RisuCard'])
if(sel === '0'){
exportSpecV2(char)
return
}
alertStore.set({ alertStore.set({
type: 'wait', type: 'wait',
msg: 'Loading...' msg: 'Loading...'
@@ -288,6 +298,7 @@ async function importSpecv2(card:CharacterCardV2, img?:Uint8Array):Promise<boole
if(!card ||card.spec !== 'chara_card_v2'){ if(!card ||card.spec !== 'chara_card_v2'){
return false return false
} }
const data = card.data const data = card.data
const im = img ? await saveImage(PngMetadata.filter(img)) : undefined const im = img ? await saveImage(PngMetadata.filter(img)) : undefined
let db = get(DataBase) let db = get(DataBase)
@@ -319,11 +330,45 @@ async function importSpecv2(card:CharacterCardV2, img?:Uint8Array):Promise<boole
sdData = risuext.sdData ?? sdData 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 = { let char:character = {
name: data.name, name: data.name ?? '',
firstMessage: data.first_mes, firstMessage: data.first_mes ?? '',
desc: data.description, desc: data.description ?? '',
notes: data.post_history_instructions, notes: '',
chats: [{ chats: [{
message: [], message: [],
note: '', note: '',
@@ -334,15 +379,34 @@ async function importSpecv2(card:CharacterCardV2, img?:Uint8Array):Promise<boole
image: im, image: im,
emotionImages: emotions, emotionImages: emotions,
bias: bias, bias: bias,
globalLore: [], //lorebook globalLore: lorebook, //lorebook
viewScreen: viewScreen, viewScreen: viewScreen,
chaId: uuidv4(), chaId: uuidv4(),
sdData: sdData, sdData: sdData,
utilityBot: utilityBot, utilityBot: utilityBot,
customscript: customScripts, 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) 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 = { type CharacterCardV2 = {
spec: 'chara_card_v2' spec: 'chara_card_v2'
@@ -396,3 +557,33 @@ interface OldTavernChar{
scenario: "" scenario: ""
talkativeness: "0.5" 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: [], emotionImages: [],
customscript: [], customscript: [],
chaId: uuidv4(), chaId: uuidv4(),
firstMsgIndex: -1
}) })
setDatabase(db) setDatabase(db)
return db.characters.length - 1 return db.characters.length - 1
@@ -269,6 +270,18 @@ export function characterFormatUpdate(index:number|character){
if(checkNullish(cha.utilityBot)){ if(checkNullish(cha.utilityBot)){
cha.utilityBot = false 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)){ if(checkNullish(cha.customscript)){
cha.customscript = [] cha.customscript = []
@@ -303,7 +316,17 @@ export function createBlankChar():character{
sdData: defaultSdDataFunc(), sdData: defaultSdDataFunc(),
utilityBot: false, utilityBot: false,
customscript: [], 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 DataBase = writable({} as any as Database)
export const loadedStore = writable(false) export const loadedStore = writable(false)
export let appVer = '0.7.9' export let appVer = '0.8.0'
export function setDatabase(data:Database){ export function setDatabase(data:Database){
@@ -178,6 +178,9 @@ export function setDatabase(data:Database){
if(checkNullish(data.requestproxy)){ if(checkNullish(data.requestproxy)){
data.requestproxy = '' data.requestproxy = ''
} }
if(checkNullish(data.showUnrecommended)){
data.showUnrecommended = false
}
if(checkNullish(data.sdConfig)){ if(checkNullish(data.sdConfig)){
data.sdConfig = { data.sdConfig = {
width:512, width:512,
@@ -213,11 +216,14 @@ export interface customscript{
export interface loreBook{ export interface loreBook{
key:string key:string
secondkey:string
insertorder: number insertorder: number
comment: string comment: string
content: string content: string
mode: 'multiple'|'constant'|'normal', mode: 'multiple'|'constant'|'normal',
alwaysActive: boolean alwaysActive: boolean
selective:boolean
extentions?:{}
} }
export interface character{ export interface character{
@@ -238,8 +244,34 @@ export interface character{
customscript: customscript[] customscript: customscript[]
utilityBot: boolean utilityBot: boolean
exampleMessage:string 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{ export interface groupChat{
type: 'group' type: 'group'
image?:string image?:string
@@ -255,6 +287,11 @@ export interface groupChat{
emotionImages: [string, string][] emotionImages: [string, string][]
customscript: customscript[], customscript: customscript[],
chaId: string chaId: string
alternateGreetings?: string[]
creatorNotes?:string,
removedQuotes?:boolean
firstMsgIndex?:number,
loreSettings?:loreSettings
} }
export interface botPreset{ export interface botPreset{
@@ -348,6 +385,7 @@ export interface Database{
didFirstSetup: boolean didFirstSetup: boolean
requestmet: string requestmet: string
requestproxy: 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 { tokenize, tokenizeNum } from "../tokenizer";
import { language } from "../../lang"; import { language } from "../../lang";
import { alertError } from "../alert"; import { alertError } from "../alert";
import { loadLoreBookPrompt } from "../lorebook"; import { loadLoreBookPrompt } from "./lorebook";
import { findCharacterbyId, replacePlaceholders } from "../util"; import { findCharacterbyId, replacePlaceholders } from "../util";
import { requestChatData } from "./request"; import { requestChatData } from "./request";
import { stableDiff } from "./stableDiff"; import { stableDiff } from "./stableDiff";
import { processScript, processScriptFull } from "./scripts"; import { processScript, processScriptFull } from "./scripts";
import { exampleMessage } from "./exampleMessages";
export interface OpenAIChat{ export interface OpenAIChat{
role: 'system'|'user'|'assistant' role: 'system'|'user'|'assistant'
@@ -99,9 +100,11 @@ export async function sendChat(chatProcessIndex = -1):Promise<boolean> {
} }
if(!currentChar.utilityBot){ if(!currentChar.utilityBot){
const mainp = currentChar.systemPrompt.length > 3 ? currentChar.systemPrompt : db.mainPrompt
unformated.main.push({ unformated.main.push({
role: 'system', 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){ if(db.jailbreakToggle){
@@ -122,10 +125,30 @@ export async function sendChat(chatProcessIndex = -1):Promise<boolean> {
content: replacePlaceholders(currentChat.note, currentChar.name) content: replacePlaceholders(currentChat.note, currentChar.name)
}) })
unformated.description.push({ if(currentChar.postHistoryInstructions !== ''){
role: 'system', unformated.authorNote.push({
content: replacePlaceholders((db.promptPreprocess ? db.descriptionPrefix: '') + currentChar.desc, currentChar.name) 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({ unformated.lorebook.push({
role: 'system', role: 'system',
@@ -139,20 +162,12 @@ export async function sendChat(chatProcessIndex = -1):Promise<boolean> {
}).join('\n\n') }).join('\n\n')
}).join('\n\n')) + db.maxResponse) + 150 }).join('\n\n')) + db.maxResponse) + 150
let chats:OpenAIChat[] = [] let chats:OpenAIChat[] = exampleMessage(currentChar)
if(nowChatroom.type === 'group'){ chats.push({
chats.push({ role: 'system',
role: 'system', content: '[Start a new chat]'
content: '[Start a new group chat]' })
})
}
else{
chats.push({
role: 'system',
content: '[Start a new chat]'
})
}
chats.push({ chats.push({
role: 'assistant', 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[] = [ const promptbody:OpenAIChat[] = [
{ {

View File

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

View File

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

View File

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