feat: add preset sharing

This commit is contained in:
kwaroran
2024-05-24 11:28:32 +09:00
parent 0ce01d8ca1
commit 96ccc1cdd8
10 changed files with 124 additions and 74 deletions

View File

@@ -1,7 +1,6 @@
<script lang="ts">
import Sidebar from './lib/SideBars/Sidebar.svelte';
import {ArrowRight} from 'lucide-svelte'
import { DynamicGUI, settingsOpen, sideBarStore } from './ts/stores';
import { DynamicGUI, settingsOpen, sideBarStore, ShowRealmFrameStore } from './ts/stores';
import { DataBase, loadedStore } from './ts/storage/database';
import ChatScreen from './lib/ChatScreens/ChatScreen.svelte';
import AlertComp from './lib/Others/AlertComp.svelte';
@@ -11,6 +10,7 @@
import WelcomeRisu from './lib/Others/WelcomeRisu.svelte';
import Settings from './lib/Setting/Settings.svelte';
import { showRealmInfoStore } from './ts/characterCards';
import RealmFrame from './lib/UI/Realm/RealmFrame.svelte';
let didFirstSetup: boolean = false
let gridOpen = false
@@ -60,4 +60,7 @@
{#if $showRealmInfoStore}
<RealmPopUp bind:openedData={$showRealmInfoStore} />
{/if}
{#if $ShowRealmFrameStore}
<RealmFrame />
{/if}
</main>

View File

@@ -529,6 +529,7 @@ export const languageEnglish = {
memoryLimitThickness: "Memory Limit Thickness",
inputCardPassword: "Input Card Password",
ccv2Desc: 'Character Card V2 is is a format widely used in chatbot programs.',
realmDesc: 'RisuRealm is a content sharing platform for RisuAI. you can share your character to other users.',
rccDesc: 'Risu Refined Character Card is a format with additional features like password, integrity check and etc.',
password: "Password",
license: "License",
@@ -599,4 +600,7 @@ export const languageEnglish = {
list: "List",
trash: "Trash",
trashDesc: "Deleted characters are moved to trash. you can restore or delete them permanently. deleted characters are automatically purged after 3 days.",
shareExport: "Share/Export",
risupresetDesc: "Risupreset format is a format specifically designed for RisuAI presets.",
jsonDesc: "JSON format is a format that is easy to read and write for both humans and machines.",
}

View File

@@ -24,15 +24,19 @@
let cardExportPassword = ''
let cardLicense = ''
let generationInfoMenuIndex = 0
$: (() => {
$: {
if(btn){
btn.focus()
}
if($alertStore.type !== 'input'){
input = ''
}
})()
if($alertStore.type !== 'cardexport'){
cardExportType = ''
cardExportPassword = ''
cardLicense = ''
}
}
const beautifyJSON = (data:string) =>{
try {
@@ -372,7 +376,7 @@
<div class="bg-darkbg rounded-md p-4 max-w-full flex flex-col w-2xl" on:click|stopPropagation>
<h1 class="font-bold text-2xl w-full">
<span>
Export Character
{language.shareExport}
</span>
<button class="float-right text-textcolor2 hover:text-green-500" on:click={() => {
alertStore.set({
@@ -387,29 +391,27 @@
<XIcon />
</button>
</h1>
<span class="text-textcolor mt-4">Type</span>
<span class="text-textcolor mt-4">{language.type}</span>
{#if cardExportType === ''}
<span class="text-textcolor2 text-sm">{language.ccv2Desc}</span>
{#if $alertStore.submsg !== 'preset'}
<span class="text-textcolor2 text-sm">{language.risupresetDesc}</span>
{:else}
<span class="text-textcolor2 text-sm">{language.ccv2Desc}</span>
{/if}
{:else if cardExportType === 'json'}
<span class="text-textcolor2 text-sm">{language.jsonDesc}</span>
{:else}
<span class="text-textcolor2 text-sm">{language.rccDesc}</span>
<span class="text-textcolor2 text-sm">{language.realmDesc}</span>
{/if}
<div class="flex items-center flex-wrap mt-2">
<button class="bg-bgcolor px-2 py-4 rounded-lg flex-1" class:ring-1={cardExportType === ''} on:click={() => {cardExportType = ''}}>Character Card V2</button>
<button class="bg-bgcolor px-2 py-4 rounded-lg ml-2 flex-1" class:ring-1={cardExportType === 'rcc'} on:click={() => {cardExportType = 'rcc'}}>Risu RCC</button>
{#if $alertStore.submsg === 'preset'}
<button class="bg-bgcolor px-2 py-4 rounded-lg flex-1" class:ring-1={cardExportType === ''} on:click={() => {cardExportType = ''}}>Risupreset</button>
<button class="bg-bgcolor px-2 py-4 rounded-lg ml-2 flex-1" class:ring-1={cardExportType === 'json'} on:click={() => {cardExportType = 'json'}}>JSON</button>
<button class="bg-bgcolor px-2 py-4 rounded-lg ml-2 flex-1" class:ring-1={cardExportType === 'realm'} on:click={() => {cardExportType = 'realm'}}>RisuRealm</button>
{:else}
<button class="bg-bgcolor px-2 py-4 rounded-lg flex-1" class:ring-1={cardExportType === ''} on:click={() => {cardExportType = ''}}>Character Card V2</button>
{/if}
</div>
{#if cardExportType === 'rcc'}
<span class="text-textcolor mt-4">{language.password}</span>
<span class="text-textcolor2 text-sm">{language.passwordDesc}</span>
<TextInput placeholder="" bind:value={cardExportPassword} />
<span class="text-textcolor mt-4">{language.license}</span>
<span class="text-textcolor2 text-sm">{language.licenseDesc}</span>
<SelectInput bind:value={cardLicense}>
<OptionInput value="">None</OptionInput>
{#each Object.keys(CCLicenseData) as ccl}
<OptionInput value={ccl}>{CCLicenseData[ccl][2]} ({CCLicenseData[ccl][1]})</OptionInput>
{/each}
</SelectInput>
{/if}
<Button className="mt-4" on:click={() => {
alertStore.set({
type: 'none',
@@ -419,7 +421,7 @@
license: cardLicense
})
})
}}>{language.export}</Button>
}}>{cardExportType === 'realm' ? language.shareCloud : language.export}</Button>
</div>
</div>

View File

@@ -1,10 +1,11 @@
<script>
import { alertConfirm, alertError } from "../../ts/alert";
<script lang="ts">
import { alertCardExport, alertConfirm, alertError } from "../../ts/alert";
import { language } from "../../lang";
import { DataBase, changeToPreset, copyPreset, downloadPreset, importPreset, presetTemplate } from "../../ts/storage/database";
import { CopyIcon, DownloadIcon, EditIcon, FolderUpIcon, PlusIcon, TrashIcon, XIcon } from "lucide-svelte";
import { DataBase, changeToPreset, copyPreset, downloadPreset, importPreset } from "../../ts/storage/database";
import { CopyIcon, Share2Icon, PencilIcon, FolderUpIcon, PlusIcon, TrashIcon, XIcon } from "lucide-svelte";
import TextInput from "../UI/GUI/TextInput.svelte";
import { prebuiltPresets } from "src/ts/process/templates/templates";
import { ShowRealmFrameStore } from "src/ts/stores";
let editMode = false
export let close = () => {}
@@ -43,12 +44,26 @@
}}>
<CopyIcon size={18}/>
</button>
<button class="text-textcolor2 hover:text-green-500 cursor-pointer mr-2" on:click={(e) => {
<button class="text-textcolor2 hover:text-green-500 cursor-pointer mr-2" on:click={async (e) => {
e.stopPropagation()
downloadPreset(i)
const data = await alertCardExport('preset')
console.log(data.type)
if(data.type === ''){
downloadPreset(i, 'risupreset')
}
if(data.type === 'json'){
downloadPreset(i, 'json')
}
if(data.type === 'realm'){
if(!$DataBase.account){
alertError(language.notLoggedIn)
return
}
$ShowRealmFrameStore = `preset:${i}`
}
}}>
<DownloadIcon size={18} />
<Share2Icon size={18} />
</button>
<button class="text-textcolor2 hover:text-green-500 cursor-pointer" on:click={async (e) => {
e.stopPropagation()
@@ -89,7 +104,7 @@
<button class="text-textcolor2 hover:text-green-500 cursor-pointer" on:click={() => {
editMode = !editMode
}}>
<EditIcon size={18}/>
<PencilIcon size={18}/>
</button>
</div>
<span class="text-textcolor2 text-sm">{language.quickPreset}</span>

View File

@@ -2,7 +2,7 @@
import { language } from "../../lang";
import { tokenizeAccurate } from "../../ts/tokenizer";
import { DataBase, saveImage as saveAsset, type Database, type character, type groupChat } from "../../ts/storage/database";
import { selectedCharID } from "../../ts/stores";
import { ShowRealmFrameStore, selectedCharID } from "../../ts/stores";
import { PlusIcon, SmileIcon, TrashIcon, UserIcon, ActivityIcon, BookIcon, User, CurlyBraces, Volume2Icon } from 'lucide-svelte'
import Check from "../UI/GUI/CheckInput.svelte";
import { addCharEmotion, addingEmotion, getCharImage, rmCharEmotion, selectCharImg, makeGroupImage, removeChar } from "../../ts/characters";
@@ -17,7 +17,6 @@
import { getElevenTTSVoices, getWebSpeechTTSVoices, getVOICEVOXVoices, oaiVoices, getNovelAIVoices, FixNAITTS } from "src/ts/process/tts";
import { checkCharOrder, getFileSrc } from "src/ts/storage/globalApi";
import { addGroupChar, rmCharFromGroup } from "src/ts/process/group";
import RealmUpload from "../UI/Realm/RealmUpload.svelte";
import TextInput from "../UI/GUI/TextInput.svelte";
import NumberInput from "../UI/GUI/NumberInput.svelte";
import TextAreaInput from "../UI/GUI/TextAreaInput.svelte";
@@ -30,11 +29,9 @@
import { updateInlayScreen } from "src/ts/process/inlayScreen";
import { registerOnnxModel } from "src/ts/process/transformers";
import MultiLangInput from "../UI/GUI/MultiLangInput.svelte";
import RealmFrame from "../UI/Realm/RealmFrame.svelte";
let subMenu = 0
let openHubUpload = false
let emos:[string, string][] = []
let tokens = {
desc: 0,
@@ -833,7 +830,7 @@
|| $DataBase.tpo
}
<Button size="lg" on:click={async () => {
exportChar($selectedCharID)
const res = await exportChar($selectedCharID)
}} className="mt-2">{language.exportCharacter}</Button>
{/if}
@@ -846,7 +843,7 @@
return
}
if(await alertTOS()){
openHubUpload = true
$ShowRealmFrameStore = 'character'
}
}} className="mt-2">
{#if currentChar.data.realmId}
@@ -856,11 +853,6 @@
{/if}
</Button>
{/if}
{#if openHubUpload}
<!-- <RealmUpload bind:char={currentChar.data} close={() => {openHubUpload=false}}/> -->
<RealmFrame close={() => {openHubUpload=false}}/>
{/if}
{:else}
{#if currentChar.data.chats[currentChar.data.chatPage].supaMemoryData && currentChar.data.chats[currentChar.data.chatPage].supaMemoryData.length > 4 || currentChar.data.supaMemory}
<span class="text-textcolor">{language.SuperMemory}</span>

View File

@@ -1,12 +1,14 @@
<script lang="ts">
import { alertMd } from "src/ts/alert";
import { shareRealmCardData } from "src/ts/realm";
import { DataBase } from "src/ts/storage/database";
import { CurrentCharacter } from "src/ts/stores";
import { DataBase, downloadPreset } from "src/ts/storage/database";
import { CurrentCharacter, ShowRealmFrameStore } from "src/ts/stores";
import { sleep } from "src/ts/util";
import { onDestroy, onMount } from "svelte";
export let close: () => void
const close = () => {
$ShowRealmFrameStore = ''
}
let iframe: HTMLIFrameElement = null
const tk = $DataBase?.account?.token;
const id = $DataBase?.account?.id
@@ -25,7 +27,10 @@
}
if(e.data.type === 'success'){
alertMd(`## Upload Success\n\nYour character has been uploaded to Realm successfully.\n\n${"```\nhttps://realm.risuai.net/character/" + e.data.id + "\n```"}`)
if($CurrentCharacter.type === 'character'){
if($ShowRealmFrameStore.startsWith('preset')){
//TODO, add preset edit
}
else if($CurrentCharacter.type === 'character'){
loadingStage = 0
$CurrentCharacter.realmId = e.data.id
}
@@ -47,7 +52,23 @@
onMount(async () => {
window.addEventListener('message', pmfunc)
const data = await shareRealmCardData()
let data:{
data: ArrayBuffer,
name: ArrayBuffer
}
if($ShowRealmFrameStore.startsWith('preset')){
const predata = await downloadPreset(Number($ShowRealmFrameStore.split(':')[1]), 'return')
const encodedPredata = predata.buf
const encodedPredataName = new TextEncoder().encode(predata.data.name + '.risupreset')
data = {
data: encodedPredata.buffer,
name: encodedPredataName.buffer
}
}
else{
data = await shareRealmCardData()
}
if(iframe){
await waitPing()
@@ -61,7 +82,10 @@
const getUrl = () => {
let url = `https://realm.risuai.net/upload?token=${tk}&token_id=${id}`
if($CurrentCharacter.type === 'character' && $CurrentCharacter.realmId){
if($ShowRealmFrameStore.startsWith('preset')){
//TODO, add preset edit
}
else if($CurrentCharacter.type === 'character' && $CurrentCharacter.realmId){
url += `&edit=${$CurrentCharacter.realmId}&edit-type=normal`
}
url += '#noLayout'

View File

@@ -205,11 +205,12 @@ export async function alertConfirm(msg:string){
return get(alertStore).msg === 'yes'
}
export async function alertCardExport(){
export async function alertCardExport(type:string = ''){
alertStore.set({
'type': 'cardexport',
'msg': ''
'msg': '',
'submsg': type
})
while(true){

View File

@@ -293,35 +293,27 @@ function convertOldTavernAndJSON(charaData:OldTavernChar, imgp:string|undefined
}
}
export async function exportChar(charaID:number) {
export async function exportChar(charaID:number):Promise<string> {
const db = get(DataBase)
let char = structuredClone(db.characters[charaID])
if(char.type === 'group'){
return
return ''
}
if(!char.image){
alertError('Image Required')
return
}
const conf = await alertConfirm(language.exportConfirm)
if(!conf){
return
return ''
}
const option = await alertCardExport()
if(option.type === 'cancel'){
return
}
else if(option.type === 'rcc'){
char.license = option.license
exportSpecV2(char, 'rcc', {password:option.password})
}
else{
if(option.type === ''){
exportSpecV2(char,'png')
}
return
else{
return option.type
}
return ''
}

View File

@@ -1222,7 +1222,7 @@ import type { OnnxModelFiles } from '../process/transformers';
import type { RisuModule } from '../process/modules';
import type { HypaV2Data } from '../process/memory/hypav2';
export async function downloadPreset(id:number){
export async function downloadPreset(id:number, type:'json'|'risupreset'|'return' = 'json'){
saveCurrentPreset()
let db = get(DataBase)
let pres = structuredClone(db.botPresets[id])
@@ -1233,21 +1233,37 @@ export async function downloadPreset(id:number){
pres.proxyKey = ''
pres.textgenWebUIStreamURL= ''
pres.textgenWebUIBlockingURL= ''
const sel = parseInt(await alertSelect(['RISUPRESET (recommended)','JSON']))
if(sel === 1){
if(type === 'json'){
downloadFile(pres.name + "_preset.json", Buffer.from(JSON.stringify(pres, null, 2)))
}
else{
downloadFile(pres.name + "_preset.risupreset", fflate.compressSync(encodeMsgpack({
else if(type === 'risupreset' || type === 'return'){
const buf = fflate.compressSync(encodeMsgpack({
presetVersion: 0,
type: 'preset',
pres: await encryptBuffer(
encodeMsgpack(pres),
'risupreset'
)
})))
}))
if(type === 'risupreset'){
downloadFile(pres.name + "_preset.risupreset", buf)
}
else{
return {
data: pres,
buf
}
}
}
alertNormal(language.successExport)
return {
data: pres,
buf: null
}
}

View File

@@ -40,6 +40,7 @@ export const ShowVN = writable(false)
export const SettingsMenuIndex = writable(-1)
export const CurrentVariablePointer = writable({} as {[key:string]: string|number|boolean})
export const OpenRealmStore = writable(false)
export const ShowRealmFrameStore = writable('')
export const PlaygroundStore = writable(0)
function createSimpleCharacter(char:character|groupChat){