[feat] persona import/export

This commit is contained in:
kwaroran
2023-08-17 18:45:34 +09:00
parent 5f7a5386d4
commit 5c83634d25
7 changed files with 226 additions and 62 deletions

View File

@@ -19,7 +19,8 @@ export const languageEnglish = {
httpError: 'Error: error in request:',
noData: 'There is no data in file, or the file is corrupted',
onlyOneChat: 'There must be least one chat',
alreadyCharInGroup: "There is already a character with the same name in the group."
alreadyCharInGroup: "There is already a character with the same name in the group.",
noUserIcon: "You must set your icon first.",
},
showHelp: "Show Help",
help:{
@@ -421,4 +422,8 @@ export const languageEnglish = {
translatorType: "Translator Type",
deeplKey: "deepL API Key",
deeplFreeKey: "deepL Free API Key",
exportPersona: "Export Persona",
importPersona: "Import Persona",
export: "Export",
import: "Import",
}

View File

@@ -1,12 +1,14 @@
<script lang="ts">
import { language } from "src/lang";
import BaseRoundedButton from "src/lib/UI/BaseRoundedButton.svelte";
import TextAreaInput from "src/lib/UI/GUI/TextAreaInput.svelte";
import TextInput from "src/lib/UI/GUI/TextInput.svelte";
import { alertConfirm, alertError } from "src/ts/alert";
import { changeUserPersona, getCharImage, saveUserPersona, selectUserImg } from "src/ts/characters";
import BaseRoundedButton from "src/lib/UI/BaseRoundedButton.svelte";
import Button from "src/lib/UI/GUI/Button.svelte";
import TextAreaInput from "src/lib/UI/GUI/TextAreaInput.svelte";
import TextInput from "src/lib/UI/GUI/TextInput.svelte";
import { alertConfirm, alertError, alertSelect } from "src/ts/alert";
import { getCharImage } from "src/ts/characters";
import { changeUserPersona, exportUserPersona, importUserPersona, saveUserPersona, selectUserImg } from "src/ts/persona";
import { DataBase, setDatabase } from "src/ts/storage/database";
import { get } from "svelte/store";
import { get } from "svelte/store";
</script>
<h2 class="mb-2 text-2xl font-bold mt-2">{language.persona}</h2>
@@ -29,7 +31,11 @@
{/each}
<div class="flex justify-center items-center ml-2 mr-2">
<BaseRoundedButton
onClick={() => {
onClick={async () => {
const sel = parseInt(await alertSelect([language.createfromScratch, language.importCharacter]))
if(sel === 1){
return
}
let db = get(DataBase)
db.personas.push({
name: 'New Persona',
@@ -57,8 +63,8 @@
<div class="mt-4 mb-4 border-y-1 border-y-selected">
</div>
<div class="flex w-full items-starts rounded-md bg-darkbg p-4">
<div class="flex flex-col mt-4">
<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">
<button on:click={() => {selectUserImg()}}>
{#if $DataBase.userIcon === ''}
<div class="rounded-md h-28 w-28 shadow-lg bg-textcolor2 cursor-pointer hover:text-green-500" />
@@ -71,13 +77,16 @@
{/if}
</button>
</div>
<div class="flex flex-grow flex-col p-2 ml-4">
<div class="flex flex-grow flex-col p-2 max-w-full">
<span class="text-sm text-textcolor2">{language.name}</span>
<TextInput marginBottom size="lg" placeholder="User" bind:value={$DataBase.username} />
<span class="text-sm text-textcolor2">{language.description}</span>
<TextAreaInput height="32" autocomplete="off" bind:value={$DataBase.personaPrompt} placeholder={`Put the description of this persona here.\nExample: [<user> is a 20 year old girl.]`} />
<div>
<button class="float-right rounded-md border border-red-700 p-2 hover:bg-red-700 transition-colors mt-4 text-sm" on:click={async () => {
<div class="flex gap-2 mt-4 max-w-full flex-wrap">
<Button on:click={exportUserPersona}>{language.export}</Button>
<Button on:click={importUserPersona}>{language.import}</Button>
<Button styled="danger" on:click={async () => {
if($DataBase.personas.length === 1){
return
}
@@ -89,9 +98,7 @@
$DataBase.personas = personas
changeUserPersona(0, 'noSave')
}
}}>
{language.remove}
</button>
}}>{language.remove}</Button>
</div>
</div>
</div>

View File

@@ -1,14 +1,13 @@
<script lang="ts">
import { language } from "src/lang";
import { hubURL } from "src/ts/characterCards";
import { getCharImage, selectUserImg } from "src/ts/characters";
import { loadRisuAccountData, saveRisuAccountData } from "src/ts/drive/accounter";
import { DataBase } from "src/ts/storage/database";
import Check from "src/lib/UI/GUI/CheckInput.svelte";
import { alertConfirm, alertSelect } from "src/ts/alert";
import { forageStorage, isNodeServer, isTauri } from "src/ts/storage/globalApi";
import { unMigrationAccount } from "src/ts/storage/accountStorage";
import { checkDriver } from "src/ts/drive/drive";
import { alertConfirm } from "src/ts/alert";
import { forageStorage, isNodeServer, isTauri } from "src/ts/storage/globalApi";
import { unMigrationAccount } from "src/ts/storage/accountStorage";
import { checkDriver } from "src/ts/drive/drive";
let openIframe = false
let openIframeURL = ''
let popup:Window = null

View File

@@ -1,6 +1,10 @@
<button
on:click
class="{selected ? 'bg-borderc' : 'bg-darkbutton'} border border-darkborderc text-textcolor rounded-md shadow-sm hover:bg-borderc focus:outline-none focus:ring-2 focus:ring-borderc transition-colors duration-200{className ? (" " + className) : ""}"
class="{
styled === 'primary' ?
((selected ? 'bg-borderc' : 'bg-darkbutton') + " hover:bg-borderc focus:ring-borderc border-darkborderc")
: ((selected ? 'bg-red-800' : 'bg-red-700') + ' hover:bg-red-500 focus:ring-red-600 border-red-600')
} border text-textcolor rounded-md shadow-sm focus:outline-none focus:ring-2 transition-colors duration-200{className ? (" " + className) : ""}"
class:px-4 = {size == "md"}
class:px-2 = {size == "sm"}
class:px-6 = {size == "lg"}
@@ -14,6 +18,7 @@
</button>
<script lang="ts">
export let selected = false
export let styled:'primary'|'danger' = 'primary'
export let className = ""
export let size: "sm" | "md" | "lg" = "md"
</script>

View File

@@ -77,46 +77,6 @@ export async function selectCharImg(charId:number) {
setDatabase(db)
}
export async function selectUserImg() {
const selected = await selectSingleFile(['png'])
if(!selected){
return
}
const img = selected.data
let db = get(DataBase)
const imgp = await saveImage(img)
db.userIcon = imgp
db.personas[db.selectedPersona] = {
name: db.username,
icon: db.userIcon,
personaPrompt: db.personaPrompt
}
setDatabase(db)
}
export function saveUserPersona() {
let db = get(DataBase)
db.personas[db.selectedPersona] = {
name: db.username,
icon: db.userIcon,
personaPrompt: db.personaPrompt
}
setDatabase(db)
}
export function changeUserPersona(id:number, save:'save'|'noSave' = 'save') {
if(save === 'save'){
saveUserPersona()
}
let db = get(DataBase)
const pr = db.personas[id]
db.personaPrompt = pr.personaPrompt
db.username = pr.name,
db.userIcon = pr.icon
db.selectedPersona = id
setDatabase(db)
}
export const addingEmotion = writable(false)

118
src/ts/persona.ts Normal file
View File

@@ -0,0 +1,118 @@
import { get } from "svelte/store"
import { DataBase, saveImage, setDatabase } from "./storage/database"
import { selectSingleFile, sleep } from "./util"
import { alertError, alertNormal, alertStore } from "./alert"
import * as yuso from 'yuso'
import { downloadFile, readImage } from "./storage/globalApi"
import { language } from "src/lang"
import { cloneDeep } from "lodash"
export async function selectUserImg() {
const selected = await selectSingleFile(['png'])
if(!selected){
return
}
const img = selected.data
let db = get(DataBase)
const imgp = await saveImage(img)
db.userIcon = imgp
db.personas[db.selectedPersona] = {
name: db.username,
icon: db.userIcon,
personaPrompt: db.personaPrompt
}
setDatabase(db)
}
export function saveUserPersona() {
let db = get(DataBase)
db.personas[db.selectedPersona] = {
name: db.username,
icon: db.userIcon,
personaPrompt: db.personaPrompt
}
setDatabase(db)
}
export function changeUserPersona(id:number, save:'save'|'noSave' = 'save') {
if(save === 'save'){
saveUserPersona()
}
let db = get(DataBase)
const pr = db.personas[id]
db.personaPrompt = pr.personaPrompt
db.username = pr.name,
db.userIcon = pr.icon
db.selectedPersona = id
setDatabase(db)
}
interface PersonaCard {
name: string
personaPrompt: string
}
export async function exportUserPersona(){
let db = get(DataBase)
if(!db.userIcon){
alertError(language.errors.noUserIcon)
return
}
if((!db.username) || (!db.personaPrompt)){
alertError("username or persona prompt is empty")
return
}
let img = await readImage(db.userIcon)
let card:PersonaCard = cloneDeep({
name: db.username,
personaPrompt: db.personaPrompt,
})
alertStore.set({
type: 'wait',
msg: 'Loading... (Writing Exif)'
})
await sleep(10)
img = yuso.encode(yuso.trim(img), "persona",Buffer.from(JSON.stringify(card)).toString('base64'))
alertStore.set({
type: 'wait',
msg: 'Loading... (Writing)'
})
await sleep(10)
await downloadFile(`${db.username.replace(/[<>:"/\\|?*\.\,]/g, "")}_export.png`, img)
alertNormal(language.successExport)
}
export async function importUserPersona(){
try {
const v = await selectSingleFile(['png'])
const data:PersonaCard = JSON.parse(Buffer.from(yuso.decode(v.data, "persona"), 'base64').toString('utf-8'))
if(data.name && data.personaPrompt){
let db = get(DataBase)
db.personas.push({
name: data.name,
icon: await saveImage(yuso.trim(v.data)),
personaPrompt: data.personaPrompt
})
setDatabase(db)
alertNormal(language.successImport)
}else{
alertError(language.errors.noData)
}
} catch (error) {
alertError(`${error}`)
return
}
}

View File

@@ -0,0 +1,70 @@
// vite.config.ts
import { defineConfig } from "file:///C:/Users/blueb/OneDrive/Documents/VSC/_Risu/kakuna/RisuAI/node_modules/.pnpm/vite@4.4.5_@types+node@18.16.19/node_modules/vite/dist/node/index.js";
import { svelte } from "file:///C:/Users/blueb/OneDrive/Documents/VSC/_Risu/kakuna/RisuAI/node_modules/.pnpm/@sveltejs+vite-plugin-svelte@2.4.2_svelte@4.1.0_vite@4.4.5/node_modules/@sveltejs/vite-plugin-svelte/src/index.js";
import sveltePreprocess from "file:///C:/Users/blueb/OneDrive/Documents/VSC/_Risu/kakuna/RisuAI/node_modules/.pnpm/svelte-preprocess@5.0.4_postcss@8.4.26_svelte@4.1.0_typescript@5.1.6/node_modules/svelte-preprocess/dist/index.js";
import wasm from "file:///C:/Users/blueb/OneDrive/Documents/VSC/_Risu/kakuna/RisuAI/node_modules/.pnpm/vite-plugin-wasm@3.2.2_vite@4.4.5/node_modules/vite-plugin-wasm/exports/import.mjs";
import { internalIpV4 } from "file:///C:/Users/blueb/OneDrive/Documents/VSC/_Risu/kakuna/RisuAI/node_modules/.pnpm/internal-ip@7.0.0/node_modules/internal-ip/index.js";
import topLevelAwait from "file:///C:/Users/blueb/OneDrive/Documents/VSC/_Risu/kakuna/RisuAI/node_modules/.pnpm/vite-plugin-top-level-await@1.3.1_rollup@3.26.3_vite@4.4.5/node_modules/vite-plugin-top-level-await/exports/import.mjs";
var vite_config_default = defineConfig(async () => {
const host = await internalIpV4();
return {
plugins: [
svelte({
preprocess: [
sveltePreprocess({
typescript: true
})
],
onwarn: (warning, handler) => {
if (warning.code.startsWith("a11y-"))
return;
handler(warning);
}
}),
wasm(),
topLevelAwait()
],
// Vite options tailored for Tauri development and only applied in `tauri dev` or `tauri build`
// prevent vite from obscuring rust errors
clearScreen: false,
// tauri expects a fixed port, fail if that port is not available
server: {
host: "0.0.0.0",
// listen on all addresses
port: 5174,
strictPort: true,
hmr: {
protocol: "ws",
host,
port: 5184
}
},
// to make use of `TAURI_DEBUG` and other env variables
// https://tauri.studio/v1/api/config#buildconfig.beforedevcommand
envPrefix: ["VITE_", "TAURI_"],
build: {
// Tauri supports es2021
target: "es2021",
// don't minify for debug builds
minify: process.env.TAURI_DEBUG ? false : "esbuild",
// produce sourcemaps for debug builds
sourcemap: !!process.env.TAURI_DEBUG,
chunkSizeWarningLimit: 2e3
},
optimizeDeps: {
needsInterop: [
"@mlc-ai/web-tokenizers"
]
},
resolve: {
alias: {
"src": "/src",
"modules": "/modules"
}
}
};
});
export {
vite_config_default as default
};
//# sourceMappingURL=data:application/json;base64,ewogICJ2ZXJzaW9uIjogMywKICAic291cmNlcyI6IFsidml0ZS5jb25maWcudHMiXSwKICAic291cmNlc0NvbnRlbnQiOiBbImNvbnN0IF9fdml0ZV9pbmplY3RlZF9vcmlnaW5hbF9kaXJuYW1lID0gXCJDOlxcXFxVc2Vyc1xcXFxibHVlYlxcXFxPbmVEcml2ZVxcXFxEb2N1bWVudHNcXFxcVlNDXFxcXF9SaXN1XFxcXGtha3VuYVxcXFxSaXN1QUlcIjtjb25zdCBfX3ZpdGVfaW5qZWN0ZWRfb3JpZ2luYWxfZmlsZW5hbWUgPSBcIkM6XFxcXFVzZXJzXFxcXGJsdWViXFxcXE9uZURyaXZlXFxcXERvY3VtZW50c1xcXFxWU0NcXFxcX1Jpc3VcXFxca2FrdW5hXFxcXFJpc3VBSVxcXFx2aXRlLmNvbmZpZy50c1wiO2NvbnN0IF9fdml0ZV9pbmplY3RlZF9vcmlnaW5hbF9pbXBvcnRfbWV0YV91cmwgPSBcImZpbGU6Ly8vQzovVXNlcnMvYmx1ZWIvT25lRHJpdmUvRG9jdW1lbnRzL1ZTQy9fUmlzdS9rYWt1bmEvUmlzdUFJL3ZpdGUuY29uZmlnLnRzXCI7aW1wb3J0IHsgZGVmaW5lQ29uZmlnIH0gZnJvbSBcInZpdGVcIjtcclxuaW1wb3J0IHsgc3ZlbHRlIH0gZnJvbSBcIkBzdmVsdGVqcy92aXRlLXBsdWdpbi1zdmVsdGVcIjtcclxuaW1wb3J0IHN2ZWx0ZVByZXByb2Nlc3MgZnJvbSBcInN2ZWx0ZS1wcmVwcm9jZXNzXCI7XHJcbmltcG9ydCB3YXNtIGZyb20gXCJ2aXRlLXBsdWdpbi13YXNtXCI7XHJcbmltcG9ydCB7IGludGVybmFsSXBWNCB9IGZyb20gJ2ludGVybmFsLWlwJ1xyXG5pbXBvcnQgdG9wTGV2ZWxBd2FpdCBmcm9tIFwidml0ZS1wbHVnaW4tdG9wLWxldmVsLWF3YWl0XCI7XHJcblxyXG4vLyBodHRwczovL3ZpdGVqcy5kZXYvY29uZmlnL1xyXG5leHBvcnQgZGVmYXVsdCBkZWZpbmVDb25maWcoYXN5bmMgKCkgPT4ge1xyXG4gIFxyXG4gIGNvbnN0IGhvc3QgPSBhd2FpdCBpbnRlcm5hbElwVjQoKVxyXG5cclxuICByZXR1cm4ge1xyXG4gICAgcGx1Z2luczogW1xyXG4gICAgICBcclxuICAgICAgc3ZlbHRlKHtcclxuICAgICAgICBwcmVwcm9jZXNzOiBbXHJcbiAgICAgICAgICBzdmVsdGVQcmVwcm9jZXNzKHtcclxuICAgICAgICAgICAgdHlwZXNjcmlwdDogdHJ1ZSxcclxuICAgICAgICAgIH0pLFxyXG4gICAgICAgIF0sXHJcbiAgICAgICAgb253YXJuOiAod2FybmluZywgaGFuZGxlcikgPT4ge1xyXG4gICAgICAgICAgLy8gZGlzYWJsZSBhMTF5IHdhcm5pbmdzXHJcbiAgICAgICAgICBpZiAod2FybmluZy5jb2RlLnN0YXJ0c1dpdGgoXCJhMTF5LVwiKSkgcmV0dXJuO1xyXG4gICAgICAgICAgaGFuZGxlcih3YXJuaW5nKTtcclxuICAgICAgICB9LFxyXG4gICAgICB9KSxcclxuICAgICAgd2FzbSgpLFxyXG4gICAgICB0b3BMZXZlbEF3YWl0KCksXHJcbiAgICBdLFxyXG5cclxuICAgIC8vIFZpdGUgb3B0aW9ucyB0YWlsb3JlZCBmb3IgVGF1cmkgZGV2ZWxvcG1lbnQgYW5kIG9ubHkgYXBwbGllZCBpbiBgdGF1cmkgZGV2YCBvciBgdGF1cmkgYnVpbGRgXHJcbiAgICAvLyBwcmV2ZW50IHZpdGUgZnJvbSBvYnNjdXJpbmcgcnVzdCBlcnJvcnNcclxuICAgIGNsZWFyU2NyZWVuOiBmYWxzZSxcclxuICAgIC8vIHRhdXJpIGV4cGVjdHMgYSBmaXhlZCBwb3J0LCBmYWlsIGlmIHRoYXQgcG9ydCBpcyBub3QgYXZhaWxhYmxlXHJcbiAgICBzZXJ2ZXI6IHtcclxuICAgICAgaG9zdDogJzAuMC4wLjAnLCAvLyBsaXN0ZW4gb24gYWxsIGFkZHJlc3Nlc1xyXG4gICAgICBwb3J0OiA1MTc0LFxyXG4gICAgICBzdHJpY3RQb3J0OiB0cnVlLFxyXG4gICAgICBobXI6IHtcclxuICAgICAgICBwcm90b2NvbDogJ3dzJyxcclxuICAgICAgICBob3N0LFxyXG4gICAgICAgIHBvcnQ6IDUxODQsXHJcbiAgICAgIH0sXHJcbiAgICB9LFxyXG4gICAgLy8gdG8gbWFrZSB1c2Ugb2YgYFRBVVJJX0RFQlVHYCBhbmQgb3RoZXIgZW52IHZhcmlhYmxlc1xyXG4gICAgLy8gaHR0cHM6Ly90YXVyaS5zdHVkaW8vdjEvYXBpL2NvbmZpZyNidWlsZGNvbmZpZy5iZWZvcmVkZXZjb21tYW5kXHJcbiAgICBlbnZQcmVmaXg6IFtcIlZJVEVfXCIsIFwiVEFVUklfXCJdLFxyXG4gICAgYnVpbGQ6IHtcclxuICAgICAgLy8gVGF1cmkgc3VwcG9ydHMgZXMyMDIxXHJcbiAgICAgIHRhcmdldDpcImVzMjAyMVwiLFxyXG4gICAgICAvLyBkb24ndCBtaW5pZnkgZm9yIGRlYnVnIGJ1aWxkc1xyXG4gICAgICBtaW5pZnk6IHByb2Nlc3MuZW52LlRBVVJJX0RFQlVHID8gZmFsc2UgOiAnZXNidWlsZCcsXHJcbiAgICAgIC8vIHByb2R1Y2Ugc291cmNlbWFwcyBmb3IgZGVidWcgYnVpbGRzXHJcbiAgICAgIHNvdXJjZW1hcDogISFwcm9jZXNzLmVudi5UQVVSSV9ERUJVRyxcclxuICAgICAgY2h1bmtTaXplV2FybmluZ0xpbWl0OiAyMDAwXHJcbiAgICB9LFxyXG4gICAgXHJcbiAgICBvcHRpbWl6ZURlcHM6e1xyXG4gICAgICBuZWVkc0ludGVyb3A6W1xyXG4gICAgICAgIFwiQG1sYy1haS93ZWItdG9rZW5pemVyc1wiXHJcbiAgICAgIF1cclxuICAgIH0sXHJcblxyXG4gICAgcmVzb2x2ZTp7XHJcbiAgICAgIGFsaWFzOntcclxuICAgICAgICAnc3JjJzonL3NyYycsXHJcbiAgICAgICAgJ21vZHVsZXMnOiAnL21vZHVsZXMnXHJcbiAgICAgIH1cclxuICAgIH1cclxufX0pO1xyXG4iXSwKICAibWFwcGluZ3MiOiAiO0FBQStXLFNBQVMsb0JBQW9CO0FBQzVZLFNBQVMsY0FBYztBQUN2QixPQUFPLHNCQUFzQjtBQUM3QixPQUFPLFVBQVU7QUFDakIsU0FBUyxvQkFBb0I7QUFDN0IsT0FBTyxtQkFBbUI7QUFHMUIsSUFBTyxzQkFBUSxhQUFhLFlBQVk7QUFFdEMsUUFBTSxPQUFPLE1BQU0sYUFBYTtBQUVoQyxTQUFPO0FBQUEsSUFDTCxTQUFTO0FBQUEsTUFFUCxPQUFPO0FBQUEsUUFDTCxZQUFZO0FBQUEsVUFDVixpQkFBaUI7QUFBQSxZQUNmLFlBQVk7QUFBQSxVQUNkLENBQUM7QUFBQSxRQUNIO0FBQUEsUUFDQSxRQUFRLENBQUMsU0FBUyxZQUFZO0FBRTVCLGNBQUksUUFBUSxLQUFLLFdBQVcsT0FBTztBQUFHO0FBQ3RDLGtCQUFRLE9BQU87QUFBQSxRQUNqQjtBQUFBLE1BQ0YsQ0FBQztBQUFBLE1BQ0QsS0FBSztBQUFBLE1BQ0wsY0FBYztBQUFBLElBQ2hCO0FBQUE7QUFBQTtBQUFBLElBSUEsYUFBYTtBQUFBO0FBQUEsSUFFYixRQUFRO0FBQUEsTUFDTixNQUFNO0FBQUE7QUFBQSxNQUNOLE1BQU07QUFBQSxNQUNOLFlBQVk7QUFBQSxNQUNaLEtBQUs7QUFBQSxRQUNILFVBQVU7QUFBQSxRQUNWO0FBQUEsUUFDQSxNQUFNO0FBQUEsTUFDUjtBQUFBLElBQ0Y7QUFBQTtBQUFBO0FBQUEsSUFHQSxXQUFXLENBQUMsU0FBUyxRQUFRO0FBQUEsSUFDN0IsT0FBTztBQUFBO0FBQUEsTUFFTCxRQUFPO0FBQUE7QUFBQSxNQUVQLFFBQVEsUUFBUSxJQUFJLGNBQWMsUUFBUTtBQUFBO0FBQUEsTUFFMUMsV0FBVyxDQUFDLENBQUMsUUFBUSxJQUFJO0FBQUEsTUFDekIsdUJBQXVCO0FBQUEsSUFDekI7QUFBQSxJQUVBLGNBQWE7QUFBQSxNQUNYLGNBQWE7QUFBQSxRQUNYO0FBQUEsTUFDRjtBQUFBLElBQ0Y7QUFBQSxJQUVBLFNBQVE7QUFBQSxNQUNOLE9BQU07QUFBQSxRQUNKLE9BQU07QUFBQSxRQUNOLFdBQVc7QUFBQSxNQUNiO0FBQUEsSUFDRjtBQUFBLEVBQ0o7QUFBQyxDQUFDOyIsCiAgIm5hbWVzIjogW10KfQo=