feat: persona binding

This commit is contained in:
kwaroran
2024-07-30 00:40:09 +09:00
parent 634056b0cb
commit b9b7201889
11 changed files with 187 additions and 31 deletions

View File

@@ -670,4 +670,11 @@ export const languageEnglish = {
helpBlock: "Help", helpBlock: "Help",
hideChatIcon: "Hide Icon UI", hideChatIcon: "Hide Icon UI",
loadInternalBackup: "Load Internal Backup", loadInternalBackup: "Load Internal Backup",
createCopy: "Create a Copy",
bindPersona: "Bind Persona",
chatOptions: "Chat Options",
doYouWantToBindCurrentPersona: "Do you want to bind the current persona to this chat?",
doYouWantToUnbindCurrentPersona: "Do you want to unbind the persona from this chat?",
personaBindedSuccess: "Persona is successfully binded",
personaUnbindedSuccess: "Persona is successfully unbinded",
} }

View File

@@ -2,7 +2,7 @@
import Suggestion from './Suggestion.svelte'; import Suggestion from './Suggestion.svelte';
import AdvancedChatEditor from './AdvancedChatEditor.svelte'; import AdvancedChatEditor from './AdvancedChatEditor.svelte';
import { CameraIcon, DatabaseIcon, DicesIcon, GlobeIcon, ImagePlusIcon, LanguagesIcon, Laugh, MenuIcon, MicOffIcon, PackageIcon, Plus, RefreshCcwIcon, ReplyIcon, Send, StepForwardIcon } from "lucide-svelte"; import { CameraIcon, DatabaseIcon, DicesIcon, GlobeIcon, ImagePlusIcon, LanguagesIcon, Laugh, MenuIcon, MicOffIcon, PackageIcon, Plus, RefreshCcwIcon, ReplyIcon, Send, StepForwardIcon } from "lucide-svelte";
import { CurrentCharacter, CurrentChat, CurrentUsername, selectedCharID, CurrentUserIcon, CurrentShowMemoryLimit,CurrentSimpleCharacter, PlaygroundStore } from "../../ts/stores"; import { CurrentCharacter, CurrentChat, CurrentUsername, selectedCharID, CurrentUserIcon, CurrentShowMemoryLimit,CurrentSimpleCharacter, PlaygroundStore, UserIconProtrait } from "../../ts/stores";
import Chat from "./Chat.svelte"; import Chat from "./Chat.svelte";
import { DataBase, type Message, type character, type groupChat } from "../../ts/storage/database"; import { DataBase, type Message, type character, type groupChat } from "../../ts/storage/database";
import { getCharImage } from "../../ts/characters"; import { getCharImage } from "../../ts/characters";
@@ -587,7 +587,7 @@
message={chat.data} message={chat.data}
img={getCharImage($CurrentUserIcon, 'css')} img={getCharImage($CurrentUserIcon, 'css')}
isLastMemory={$CurrentChat.lastMemory === (chat.chatId ?? 'none') && $CurrentShowMemoryLimit} isLastMemory={$CurrentChat.lastMemory === (chat.chatId ?? 'none') && $CurrentShowMemoryLimit}
largePortrait={$DataBase.personas[$DataBase.selectedPersona].largePortrait} largePortrait={$UserIconProtrait}
MessageGenerationInfo={chat.generationInfo} MessageGenerationInfo={chat.generationInfo}
/> />
{/if} {/if}

View File

@@ -83,7 +83,7 @@
<div class="text-textcolor">You should accept RisuRealm's <a class="text-green-600 hover:text-green-500 transition-colors duration-200 cursor-pointer" on:click={() => { <div class="text-textcolor">You should accept RisuRealm's <a class="text-green-600 hover:text-green-500 transition-colors duration-200 cursor-pointer" on:click={() => {
openURL('https://sv.risuai.xyz/hub/tos') openURL('https://sv.risuai.xyz/hub/tos')
}}>Terms of Service</a> to continue</div> }}>Terms of Service</a> to continue</div>
{:else if $alertStore.type !== 'select' && $alertStore.type !== 'requestdata' && $alertStore.type !== 'addchar' && $alertStore.type !== 'hypaV2'} {:else if $alertStore.type !== 'select' && $alertStore.type !== 'requestdata' && $alertStore.type !== 'addchar' && $alertStore.type !== 'hypaV2' && $alertStore.type !== 'chatOptions'}
<span class="text-gray-300">{$alertStore.msg}</span> <span class="text-gray-300">{$alertStore.msg}</span>
{#if $alertStore.submsg} {#if $alertStore.submsg}
<span class="text-gray-500 text-sm">{$alertStore.submsg}</span> <span class="text-gray-500 text-sm">{$alertStore.submsg}</span>
@@ -368,6 +368,48 @@
</div> </div>
</button> </button>
</div> </div>
{:else if $alertStore.type === 'chatOptions'}
<div class="w-2xl flex flex-col max-w-full">
<h1 class="text-xl mb-4 font-bold">
{language.chatOptions}
</h1>
<button class="border-darkborderc border py-2 px-8 flex rounded-md hover:ring-2 items-center mt-2" on:click={() => {
alertStore.set({
type: 'none',
msg: '0'
})
}}>
<div class="flex flex-col justify-start items-start">
<span>{language.createCopy}</span>
</div>
<div class="ml-9 float-right flex-1 flex justify-end">
<ChevronRightIcon />
</div>
</button>
<button class="border-darkborderc border py-2 px-8 flex rounded-md hover:ring-2 items-center mt-2" on:click={() => {
alertStore.set({
type: 'none',
msg: '1'
})
}}>
<div class="flex flex-col justify-start items-start">
<span>{language.bindPersona}</span>
</div>
<div class="ml-9 float-right flex-1 flex justify-end">
<ChevronRightIcon />
</div>
</button>
<button class="border-darkborderc border py-2 px-8 flex rounded-md hover:ring-2 items-center mt-2" on:click={() => {
alertStore.set({
type: 'none',
msg: 'cancel'
})
}}>
<div class="flex flex-col justify-start items-start">
<span>{language.cancel}</span>
</div>
</button>
</div>
{/if} {/if}
</div> </div>
</div> </div>

View File

@@ -9,7 +9,6 @@
import { getCharImage } from "src/ts/characters"; import { getCharImage } from "src/ts/characters";
import { changeUserPersona, exportUserPersona, importUserPersona, saveUserPersona, selectUserImg } from "src/ts/persona"; import { changeUserPersona, exportUserPersona, importUserPersona, saveUserPersona, selectUserImg } from "src/ts/persona";
import { DataBase, setDatabase } from "src/ts/storage/database"; import { DataBase, setDatabase } from "src/ts/storage/database";
import { CurrentUserIcon } from "src/ts/stores";
import { get } from "svelte/store"; import { get } from "svelte/store";
</script> </script>
@@ -68,10 +67,10 @@
<div class="flex w-full items-starts rounded-md bg-darkbg p-4 max-w-full flex-wrap"> <div class="flex w-full items-starts rounded-md bg-darkbg p-4 max-w-full flex-wrap">
<div class="flex flex-col mt-4 mr-4"> <div class="flex flex-col mt-4 mr-4">
<button on:click={() => {selectUserImg()}}> <button on:click={() => {selectUserImg()}}>
{#if $CurrentUserIcon === ''} {#if $DataBase.userIcon === ''}
<div class="rounded-md h-28 w-28 shadow-lg bg-textcolor2 cursor-pointer hover:text-green-500" /> <div class="rounded-md h-28 w-28 shadow-lg bg-textcolor2 cursor-pointer hover:text-green-500" />
{:else} {:else}
{#await getCharImage($CurrentUserIcon, $DataBase.personas[$DataBase.selectedPersona].largePortrait ? 'lgcss' : 'css')} {#await getCharImage($DataBase.userIcon, $DataBase.personas[$DataBase.selectedPersona].largePortrait ? 'lgcss' : 'css')}
<div class="rounded-md h-28 w-28 shadow-lg bg-textcolor2 cursor-pointer hover:text-green-500" /> <div class="rounded-md h-28 w-28 shadow-lg bg-textcolor2 cursor-pointer hover:text-green-500" />
{:then im} {:then im}
<div class="rounded-md h-28 w-28 shadow-lg bg-textcolor2 cursor-pointer hover:text-green-500" style={im} /> <div class="rounded-md h-28 w-28 shadow-lg bg-textcolor2 cursor-pointer hover:text-green-500" style={im} />
@@ -81,7 +80,7 @@
</div> </div>
<div class="flex flex-grow flex-col p-2 max-w-full"> <div class="flex flex-grow flex-col p-2 max-w-full">
<span class="text-sm text-textcolor2">{language.name}</span> <span class="text-sm text-textcolor2">{language.name}</span>
<TextInput marginBottom size="lg" placeholder="User" bind:value={$DataBase.username} /> <TextInput marginBottom size="lg" placeholder="User" bind:value={$DataBase.username}/>
<span class="text-sm text-textcolor2">{language.description}</span> <span class="text-sm text-textcolor2">{language.description}</span>
<TextAreaInput autocomplete="off" bind:value={$DataBase.personaPrompt} placeholder={`Put the description of this persona here.\nExample: [<user> is a 20 year old girl.]`} /> <TextAreaInput autocomplete="off" bind:value={$DataBase.personaPrompt} placeholder={`Put the description of this persona here.\nExample: [<user> is a 20 year old girl.]`} />
<div class="flex gap-2 mt-4 max-w-full flex-wrap"> <div class="flex gap-2 mt-4 max-w-full flex-wrap">

View File

@@ -1,10 +1,10 @@
<script lang="ts"> <script lang="ts">
import type { Chat, character, groupChat } from "src/ts/storage/database"; import type { Chat, character, groupChat } from "src/ts/storage/database";
import { DataBase } from "src/ts/storage/database"; import { DataBase, setDatabase } from "src/ts/storage/database";
import TextInput from "../UI/GUI/TextInput.svelte"; import TextInput from "../UI/GUI/TextInput.svelte";
import { DownloadIcon, PencilIcon, FolderUpIcon, MenuIcon, TrashIcon } from "lucide-svelte"; import { DownloadIcon, PencilIcon, FolderUpIcon, MenuIcon, TrashIcon } from "lucide-svelte";
import { exportChat, importChat } from "src/ts/characters"; import { exportChat, importChat } from "src/ts/characters";
import { alertConfirm, alertError, alertSelect } from "src/ts/alert"; import { alertChatOptions, alertConfirm, alertError, alertNormal, alertSelect } from "src/ts/alert";
import { language } from "src/lang"; import { language } from "src/lang";
import Button from "../UI/GUI/Button.svelte"; import Button from "../UI/GUI/Button.svelte";
import { findCharacterbyId, parseKeyValue, sleep, sortableOptions } from "src/ts/util"; import { findCharacterbyId, parseKeyValue, sleep, sortableOptions } from "src/ts/util";
@@ -13,6 +13,7 @@
import { CurrentCharacter } from "src/ts/stores"; import { CurrentCharacter } from "src/ts/stores";
import Sortable from 'sortablejs/modular/sortable.core.esm.js'; import Sortable from 'sortablejs/modular/sortable.core.esm.js';
import { onDestroy, onMount } from "svelte"; import { onDestroy, onMount } from "svelte";
import { v4 } from "uuid";
export let chara:character|groupChat export let chara:character|groupChat
let editMode = false let editMode = false
@@ -95,16 +96,41 @@
<span>{chat.name}</span> <span>{chat.name}</span>
{/if} {/if}
<div class="flex-grow flex justify-end"> <div class="flex-grow flex justify-end">
{#if $DataBase.tpo} <button class="text-textcolor2 hover:text-green-500 mr-1 cursor-pointer" on:click={async () => {
<button class="text-textcolor2 hover:text-green-500 mr-1 cursor-pointer" on:click={async () => { const option = await alertChatOptions()
const multiuser = parseInt(await alertSelect(["Open Multiuser Room"])) switch(option){
if(multiuser === 0){ case 0:{
createMultiuserRoom() const newChat = structuredClone(chara.chats[i])
newChat.name = `Copy of ${newChat.name}`
chara.chats.unshift(newChat)
chara.chatPage = 0
chara.chats = chara.chats
} }
}}> case 1:{
<MenuIcon size={18}/> const chat = chara.chats[i]
</button> if(chat.bindedPersona){
{/if} const confirm = await alertConfirm(language.doYouWantToUnbindCurrentPersona)
if(confirm){
chat.bindedPersona = ''
alertNormal(language.personaUnbindedSuccess)
}
}
else{
const confirm = await alertConfirm(language.doYouWantToBindCurrentPersona)
if(confirm){
if(!$DataBase.personas[$DataBase.selectedPersona].id){
$DataBase.personas[$DataBase.selectedPersona].id = v4()
}
chat.bindedPersona = $DataBase.personas[$DataBase.selectedPersona].id
console.log($DataBase.personas[$DataBase.selectedPersona])
alertNormal(language.personaBindedSuccess)
}
}
}
}
}}>
<MenuIcon size={18}/>
</button>
<button class="text-textcolor2 hover:text-green-500 mr-1 cursor-pointer" on:click={() => { <button class="text-textcolor2 hover:text-green-500 mr-1 cursor-pointer" on:click={() => {
editMode = !editMode editMode = !editMode
}}> }}>

View File

@@ -6,7 +6,10 @@ import { Capacitor } from "@capacitor/core"
import { DataBase, type MessageGenerationInfo } from "./storage/database" import { DataBase, type MessageGenerationInfo } from "./storage/database"
interface alertData{ interface alertData{
type: 'error'| 'normal'|'none'|'ask'|'wait'|'selectChar'|'input'|'toast'|'wait2'|'markdown'|'select'|'login'|'tos'|'cardexport'|'requestdata'|'addchar'|'hypaV2'|'selectModule', type: 'error'|'normal'|'none'|'ask'|'wait'|'selectChar'
|'input'|'toast'|'wait2'|'markdown'|'select'|'login'
|'tos'|'cardexport'|'requestdata'|'addchar'|'hypaV2'|'selectModule'
|'chatOptions',
msg: string, msg: string,
submsg?: string submsg?: string
} }
@@ -94,6 +97,21 @@ export async function alertAddCharacter() {
return get(alertStore).msg return get(alertStore).msg
} }
export async function alertChatOptions() {
alertStore.set({
'type': 'chatOptions',
'msg': language.chatOptions
})
while(true){
if (get(alertStore).type === 'none'){
break
}
await sleep(10)
}
return parseInt(get(alertStore).msg)
}
export async function alertLogin(){ export async function alertLogin(){
alertStore.set({ alertStore.set({
'type': 'login', 'type': 'login',

View File

@@ -6,6 +6,7 @@ import { downloadFile, readImage } from "./storage/globalApi"
import { language } from "src/lang" import { language } from "src/lang"
import { reencodeImage } from "./process/files/image" import { reencodeImage } from "./process/files/image"
import { PngChunk } from "./pngChunk" import { PngChunk } from "./pngChunk"
import { v4 } from "uuid"
export async function selectUserImg() { export async function selectUserImg() {
const selected = await selectSingleFile(['png']) const selected = await selectSingleFile(['png'])
@@ -17,21 +18,20 @@ export async function selectUserImg() {
const imgp = await saveImage(img) const imgp = await saveImage(img)
db.userIcon = imgp db.userIcon = imgp
db.personas[db.selectedPersona] = { db.personas[db.selectedPersona] = {
name: getUserName(), name: db.username,
icon: db.userIcon, icon: db.userIcon,
personaPrompt: db.personaPrompt personaPrompt: db.personaPrompt,
id: v4()
} }
setDatabase(db) setDatabase(db)
} }
export function saveUserPersona() { export function saveUserPersona() {
let db = get(DataBase) let db = get(DataBase)
db.personas[db.selectedPersona] = { db.personas[db.selectedPersona].name=db.username
name: getUserName(), db.personas[db.selectedPersona].icon=db.userIcon,
icon: db.userIcon, db.personas[db.selectedPersona].personaPrompt=db.personaPrompt,
personaPrompt: db.personaPrompt, db.personas[db.selectedPersona].largePortrait=db.personas[db.selectedPersona]?.largePortrait,
largePortrait: db.personas[db.selectedPersona]?.largePortrait,
}
setDatabase(db) setDatabase(db)
} }
@@ -111,7 +111,8 @@ export async function importUserPersona(){
db.personas.push({ db.personas.push({
name: data.name, name: data.name,
icon: await saveImage(await reencodeImage(v.data)), icon: await saveImage(await reencodeImage(v.data)),
personaPrompt: data.personaPrompt personaPrompt: data.personaPrompt,
id: v4()
}) })
setDatabase(db) setDatabase(db)
alertNormal(language.successImport) alertNormal(language.successImport)

View File

@@ -595,6 +595,7 @@ export interface Database{
name:string name:string
icon:string icon:string
largePortrait?:boolean largePortrait?:boolean
id?:string
}[] }[]
assetWidth:number assetWidth:number
animationSpeed:number animationSpeed:number
@@ -1005,6 +1006,7 @@ export interface Chat{
scriptstate?:{[key:string]:string|number|boolean} scriptstate?:{[key:string]:string|number|boolean}
modules?:string[] modules?:string[]
id?:string id?:string
bindedPersona?:string
} }
export interface Message{ export interface Message{

View File

@@ -923,6 +923,11 @@ async function checkNewFormat() {
return v return v
}) })
db.personas = (db.personas ?? []).map((v) => {
v.id ??= uuidv4()
return v
})
if(!db.formatversion){ if(!db.formatversion){
function checkParge(data:string){ function checkParge(data:string){

View File

@@ -2,7 +2,7 @@ import { get, writable, type Writable } from "svelte/store";
import { DataBase, type Chat, type character, type groupChat } from "./storage/database"; import { DataBase, type Chat, type character, type groupChat } from "./storage/database";
import { isEqual } from "lodash"; import { isEqual } from "lodash";
import type { simpleCharacterArgument } from "./parser"; import type { simpleCharacterArgument } from "./parser";
import { getUserIcon, getUserName, sleep } from "./util"; import { getUserIcon, getUserIconProtrait, getUserName, sleep } from "./util";
import { getModules } from "./process/modules"; import { getModules } from "./process/modules";
function updateSize(){ function updateSize(){
@@ -43,6 +43,7 @@ export const OpenRealmStore = writable(false)
export const ShowRealmFrameStore = writable('') export const ShowRealmFrameStore = writable('')
export const PlaygroundStore = writable(0) export const PlaygroundStore = writable(0)
export const HideIconStore = writable(false) export const HideIconStore = writable(false)
export const UserIconProtrait = writable(false)
let lastGlobalEnabledModules: string[] = [] let lastGlobalEnabledModules: string[] = []
let lastChatEnabledModules: string[] = [] let lastChatEnabledModules: string[] = []
let moduleHideIcon = false let moduleHideIcon = false
@@ -136,7 +137,10 @@ async function preInit(){
CurrentUsername.set(getUserName()) CurrentUsername.set(getUserName())
} }
if(getUserIcon() !== get(CurrentUserIcon)){ if(getUserIcon() !== get(CurrentUserIcon)){
CurrentUserIcon.set(data.userIcon) CurrentUserIcon.set(getUserIcon())
}
if(getUserIconProtrait() !== get(UserIconProtrait)){
UserIconProtrait.set(getUserIconProtrait())
} }
if(data.showMemoryLimit !== get(CurrentShowMemoryLimit)){ if(data.showMemoryLimit !== get(CurrentShowMemoryLimit)){
CurrentShowMemoryLimit.set(data.showMemoryLimit) CurrentShowMemoryLimit.set(data.showMemoryLimit)
@@ -161,7 +165,15 @@ async function preInit(){
characterHideIcon = char?.hideChatIcon characterHideIcon = char?.hideChatIcon
HideIconStore.set(characterHideIcon || moduleHideIcon) HideIconStore.set(characterHideIcon || moduleHideIcon)
} }
if(getUserName() !== get(CurrentUsername)){
CurrentUsername.set(getUserName())
}
if(getUserIcon() !== get(CurrentUserIcon)){
CurrentUserIcon.set(getUserIcon())
}
if(getUserIconProtrait() !== get(UserIconProtrait)){
UserIconProtrait.set(getUserIconProtrait())
}
if(charId === -1 || charId > db.characters.length){ if(charId === -1 || charId > db.characters.length){
return return
} }

View File

@@ -107,21 +107,65 @@ export const replacePlaceholders = (msg:string, name:string) => {
.replace(/(\{\{((set)|(get))var::.+?\}\})/gu,'') .replace(/(\{\{((set)|(get))var::.+?\}\})/gu,'')
} }
function checkPersonaBinded(){
try {
let db = get(DataBase)
const selectedChar = get(selectedCharID)
const character = db.characters[selectedChar]
const chat = character.chats[character.chatPage]
console.log(chat.bindedPersona)
if(!chat.bindedPersona){
return null
}
const persona = db.personas.find(v => v.id === chat.bindedPersona)
console.log(db.personas, persona)
return persona
} catch (error) {
return null
}
}
export function getUserName(){ export function getUserName(){
const bindedPersona = checkPersonaBinded()
if(bindedPersona){
return bindedPersona.name
}
const db = get(DataBase) const db = get(DataBase)
return db.username ?? 'User' return db.username ?? 'User'
} }
export function getUserIcon(){ export function getUserIcon(){
const bindedPersona = checkPersonaBinded()
console.log(`Icon: ${bindedPersona?.icon}`)
if(bindedPersona){
return bindedPersona.icon
}
const db = get(DataBase) const db = get(DataBase)
return db.userIcon ?? '' return db.userIcon ?? ''
} }
export function getPersonaPrompt(){ export function getPersonaPrompt(){
const bindedPersona = checkPersonaBinded()
if(bindedPersona){
return bindedPersona.personaPrompt
}
const db = get(DataBase) const db = get(DataBase)
return db.personaPrompt ?? '' return db.personaPrompt ?? ''
} }
export function getUserIconProtrait(){
try {
const bindedPersona = checkPersonaBinded()
if(bindedPersona){
return bindedPersona.largePortrait
}
const db = get(DataBase)
return db.personas[db.selectedPersona].largePortrait
} catch (error) {
return false
}
}
export function checkIsIos(){ export function checkIsIos(){
return /(iPad|iPhone|iPod)/g.test(navigator.userAgent) return /(iPad|iPhone|iPod)/g.test(navigator.userAgent)
} }