Add character card v3 import/export
This commit is contained in:
@@ -24,7 +24,7 @@
|
|||||||
"@dqbd/tiktoken": "^1.0.7",
|
"@dqbd/tiktoken": "^1.0.7",
|
||||||
"@huggingface/jinja": "^0.2.2",
|
"@huggingface/jinja": "^0.2.2",
|
||||||
"@mlc-ai/web-tokenizers": "^0.1.2",
|
"@mlc-ai/web-tokenizers": "^0.1.2",
|
||||||
"@risuai/ccardlib": "^0.3.0",
|
"@risuai/ccardlib": "^0.4.1",
|
||||||
"@smithy/protocol-http": "^3.0.12",
|
"@smithy/protocol-http": "^3.0.12",
|
||||||
"@smithy/signature-v4": "^2.0.19",
|
"@smithy/signature-v4": "^2.0.19",
|
||||||
"@tauri-apps/api": "1.5.3",
|
"@tauri-apps/api": "1.5.3",
|
||||||
|
|||||||
10
pnpm-lock.yaml
generated
10
pnpm-lock.yaml
generated
@@ -33,8 +33,8 @@ importers:
|
|||||||
specifier: ^0.1.2
|
specifier: ^0.1.2
|
||||||
version: 0.1.2
|
version: 0.1.2
|
||||||
'@risuai/ccardlib':
|
'@risuai/ccardlib':
|
||||||
specifier: ^0.3.0
|
specifier: ^0.4.1
|
||||||
version: 0.3.0
|
version: 0.4.1
|
||||||
'@smithy/protocol-http':
|
'@smithy/protocol-http':
|
||||||
specifier: ^3.0.12
|
specifier: ^3.0.12
|
||||||
version: 3.0.12
|
version: 3.0.12
|
||||||
@@ -676,8 +676,8 @@ packages:
|
|||||||
'@protobufjs/utf8@1.1.0':
|
'@protobufjs/utf8@1.1.0':
|
||||||
resolution: {integrity: sha512-Vvn3zZrhQZkkBE8LSuW3em98c0FwgO4nxzv6OdSxPKJIEKY2bGbHn+mhGIPerzI4twdxaP8/0+06HBpwf345Lw==}
|
resolution: {integrity: sha512-Vvn3zZrhQZkkBE8LSuW3em98c0FwgO4nxzv6OdSxPKJIEKY2bGbHn+mhGIPerzI4twdxaP8/0+06HBpwf345Lw==}
|
||||||
|
|
||||||
'@risuai/ccardlib@0.3.0':
|
'@risuai/ccardlib@0.4.1':
|
||||||
resolution: {integrity: sha512-NQG+xtsDwROFaqi2eFia5P4kJe60+4HgbRN3jjj0xfkTJDiBoxjQjqYWLeHX52aM1+OAyRbL5Bq7ZQ58XJidNw==}
|
resolution: {integrity: sha512-b9xL0umf772icKnKfTIyP/hU1Skfikd5/MbmUFI9PXpbpMxbGQMw0NqpTFHda2iFu6Qxhpigr8bheAE8zxoYnw==}
|
||||||
|
|
||||||
'@rollup/plugin-virtual@3.0.2':
|
'@rollup/plugin-virtual@3.0.2':
|
||||||
resolution: {integrity: sha512-10monEYsBp3scM4/ND4LNH5Rxvh3e/cVeL3jWTgZ2SrQ+BmUoQcopVQvnaMcOnykb1VkxUFuDAN+0FnpTFRy2A==}
|
resolution: {integrity: sha512-10monEYsBp3scM4/ND4LNH5Rxvh3e/cVeL3jWTgZ2SrQ+BmUoQcopVQvnaMcOnykb1VkxUFuDAN+0FnpTFRy2A==}
|
||||||
@@ -4110,7 +4110,7 @@ snapshots:
|
|||||||
|
|
||||||
'@protobufjs/utf8@1.1.0': {}
|
'@protobufjs/utf8@1.1.0': {}
|
||||||
|
|
||||||
'@risuai/ccardlib@0.3.0': {}
|
'@risuai/ccardlib@0.4.1': {}
|
||||||
|
|
||||||
'@rollup/plugin-virtual@3.0.2(rollup@3.29.4)':
|
'@rollup/plugin-virtual@3.0.2(rollup@3.29.4)':
|
||||||
optionalDependencies:
|
optionalDependencies:
|
||||||
|
|||||||
@@ -528,7 +528,8 @@ export const languageEnglish = {
|
|||||||
removePunctuationHypa: "Memory Punctuation Removal",
|
removePunctuationHypa: "Memory Punctuation Removal",
|
||||||
memoryLimitThickness: "Memory Limit Thickness",
|
memoryLimitThickness: "Memory Limit Thickness",
|
||||||
inputCardPassword: "Input Card Password",
|
inputCardPassword: "Input Card Password",
|
||||||
ccv2Desc: 'Character Card V2 is is a format widely used in chatbot programs.',
|
ccv2Desc: 'Character Card V2 is a format widely used in chatbot programs.',
|
||||||
|
ccv3Desc: 'Character Card V3 is a next generation format that is used in chatbot programs.',
|
||||||
realmDesc: 'RisuRealm is a content sharing platform for RisuAI. you can share your character to other users.',
|
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.',
|
rccDesc: 'Risu Refined Character Card is a format with additional features like password, integrity check and etc.',
|
||||||
password: "Password",
|
password: "Password",
|
||||||
|
|||||||
@@ -396,10 +396,12 @@
|
|||||||
{#if $alertStore.submsg !== 'preset'}
|
{#if $alertStore.submsg !== 'preset'}
|
||||||
<span class="text-textcolor2 text-sm">{language.risupresetDesc}</span>
|
<span class="text-textcolor2 text-sm">{language.risupresetDesc}</span>
|
||||||
{:else}
|
{:else}
|
||||||
<span class="text-textcolor2 text-sm">{language.ccv2Desc}</span>
|
<span class="text-textcolor2 text-sm">{language.ccv3Desc}</span>
|
||||||
{/if}
|
{/if}
|
||||||
{:else if cardExportType === 'json'}
|
{:else if cardExportType === 'json'}
|
||||||
<span class="text-textcolor2 text-sm">{language.jsonDesc}</span>
|
<span class="text-textcolor2 text-sm">{language.jsonDesc}</span>
|
||||||
|
{:else if cardExportType === 'ccv2'}
|
||||||
|
<span class="text-textcolor2 text-sm">{language.ccv2Desc}</span>
|
||||||
{:else}
|
{:else}
|
||||||
<span class="text-textcolor2 text-sm">{language.realmDesc}</span>
|
<span class="text-textcolor2 text-sm">{language.realmDesc}</span>
|
||||||
{/if}
|
{/if}
|
||||||
@@ -409,7 +411,8 @@
|
|||||||
<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 === '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>
|
<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}
|
{:else}
|
||||||
<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 flex-1" class:ring-1={cardExportType === ''} on:click={() => {cardExportType = ''}}>Character Card V3</button>
|
||||||
|
<button class="bg-bgcolor px-2 py-4 rounded-lg flex-1 ml-2" class:ring-1={cardExportType === 'ccv2'} on:click={() => {cardExportType = 'ccv2'}}>Character Card V2</button>
|
||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
<Button className="mt-4" on:click={() => {
|
<Button className="mt-4" on:click={() => {
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ import { characterFormatUpdate } from "./characters"
|
|||||||
import { AppendableBuffer, checkCharOrder, downloadFile, loadAsset, LocalWriter, openURL, readImage, saveAsset, VirtualWriter } from "./storage/globalApi"
|
import { AppendableBuffer, checkCharOrder, downloadFile, loadAsset, LocalWriter, openURL, readImage, saveAsset, VirtualWriter } from "./storage/globalApi"
|
||||||
import { CurrentCharacter, selectedCharID } from "./stores"
|
import { CurrentCharacter, selectedCharID } from "./stores"
|
||||||
import { convertImage, hasher } from "./parser"
|
import { convertImage, hasher } from "./parser"
|
||||||
import { CCardLib } from '@risuai/ccardlib'
|
import { CCardLib, type CharacterCardV3, type LorebookEntry } from '@risuai/ccardlib'
|
||||||
import { reencodeImage } from "./process/files/image"
|
import { reencodeImage } from "./process/files/image"
|
||||||
import { PngChunk } from "./pngChunk"
|
import { PngChunk } from "./pngChunk"
|
||||||
import type { OnnxModelFiles } from "./process/transformers"
|
import type { OnnxModelFiles } from "./process/transformers"
|
||||||
@@ -46,7 +46,7 @@ async function importCharacterProcess(f:{
|
|||||||
}
|
}
|
||||||
const data = f.data instanceof Uint8Array ? f.data : new Uint8Array(await f.data.arrayBuffer())
|
const data = f.data instanceof Uint8Array ? f.data : new Uint8Array(await f.data.arrayBuffer())
|
||||||
const da = JSON.parse(Buffer.from(data).toString('utf-8'))
|
const da = JSON.parse(Buffer.from(data).toString('utf-8'))
|
||||||
if(await importSpecv2(da)){
|
if(await importCharacterCardSpec(da)){
|
||||||
let db = get(DataBase)
|
let db = get(DataBase)
|
||||||
return db.characters.length - 1
|
return db.characters.length - 1
|
||||||
}
|
}
|
||||||
@@ -144,8 +144,8 @@ async function importCharacterProcess(f:{
|
|||||||
else{
|
else{
|
||||||
try {
|
try {
|
||||||
const decrypted = await decryptBuffer(encrypted, password)
|
const decrypted = await decryptBuffer(encrypted, password)
|
||||||
const charaData:CharacterCardV2 = JSON.parse(Buffer.from(decrypted).toString('utf-8'))
|
const charaData:CharacterCardV2Risu = JSON.parse(Buffer.from(decrypted).toString('utf-8'))
|
||||||
if(await importSpecv2(charaData, img, "normal", assets)){
|
if(await importCharacterCardSpec(charaData, img, "normal", assets)){
|
||||||
let db = get(DataBase)
|
let db = get(DataBase)
|
||||||
return db.characters.length - 1
|
return db.characters.length - 1
|
||||||
}
|
}
|
||||||
@@ -161,8 +161,8 @@ async function importCharacterProcess(f:{
|
|||||||
else{
|
else{
|
||||||
const decrypted = await decryptBuffer(encrypted, 'RISU_NONE')
|
const decrypted = await decryptBuffer(encrypted, 'RISU_NONE')
|
||||||
try {
|
try {
|
||||||
const charaData:CharacterCardV2 = JSON.parse(Buffer.from(decrypted).toString('utf-8'))
|
const charaData:CharacterCardV2Risu = JSON.parse(Buffer.from(decrypted).toString('utf-8'))
|
||||||
if(await importSpecv2(charaData, img, "normal", assets)){
|
if(await importCharacterCardSpec(charaData, img, "normal", assets)){
|
||||||
let db = get(DataBase)
|
let db = get(DataBase)
|
||||||
return db.characters.length - 1
|
return db.characters.length - 1
|
||||||
}
|
}
|
||||||
@@ -178,14 +178,7 @@ async function importCharacterProcess(f:{
|
|||||||
const parsed = JSON.parse(Buffer.from(readedChara, 'base64').toString('utf-8'))
|
const parsed = JSON.parse(Buffer.from(readedChara, 'base64').toString('utf-8'))
|
||||||
const checkedVersion = CCardLib.character.check(parsed)
|
const checkedVersion = CCardLib.character.check(parsed)
|
||||||
if(checkedVersion === 'v2' || checkedVersion === 'v3'){
|
if(checkedVersion === 'v2' || checkedVersion === 'v3'){
|
||||||
const charaData:CharacterCardV2 = CCardLib.character.convert(parsed, {
|
if(await importCharacterCardSpec(parsed, img, "normal", assets)){
|
||||||
from: checkedVersion,
|
|
||||||
to: 'v2',
|
|
||||||
options: {
|
|
||||||
convertRisuFields: true
|
|
||||||
}
|
|
||||||
})
|
|
||||||
if(await importSpecv2(charaData, img, "normal", assets)){
|
|
||||||
let db = get(DataBase)
|
let db = get(DataBase)
|
||||||
return db.characters.length - 1
|
return db.characters.length - 1
|
||||||
}
|
}
|
||||||
@@ -308,7 +301,10 @@ export async function exportChar(charaID:number):Promise<string> {
|
|||||||
|
|
||||||
const option = await alertCardExport()
|
const option = await alertCardExport()
|
||||||
if(option.type === ''){
|
if(option.type === ''){
|
||||||
exportSpecV2(char,'png')
|
exportCharacterCard(char,'png', {spec: 'v3'})
|
||||||
|
}
|
||||||
|
else if(option.type === 'ccv2'){
|
||||||
|
exportCharacterCard(char,'png', {spec: 'v2'})
|
||||||
}
|
}
|
||||||
else{
|
else{
|
||||||
return option.type
|
return option.type
|
||||||
@@ -317,12 +313,13 @@ export async function exportChar(charaID:number):Promise<string> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
async function importSpecv2(card:CharacterCardV2, img?:Uint8Array, mode:'hub'|'normal' = 'normal', assetDict:{[key:string]:string} = {}):Promise<boolean>{
|
async function importCharacterCardSpec(card:CharacterCardV2Risu|CharacterCardV3, img?:Uint8Array, mode:'hub'|'normal' = 'normal', assetDict:{[key:string]:string} = {}):Promise<boolean>{
|
||||||
if(!card ||card.spec !== 'chara_card_v2'){
|
if(!card ||(card.spec !== 'chara_card_v2' && card.spec !== 'chara_card_v3' )){
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
const data = card.data
|
const data = card.data
|
||||||
|
console.log(card)
|
||||||
const im = img ? await saveAsset(await reencodeImage(img)) : undefined
|
const im = img ? await saveAsset(await reencodeImage(img)) : undefined
|
||||||
let db = get(DataBase)
|
let db = get(DataBase)
|
||||||
|
|
||||||
@@ -334,8 +331,15 @@ async function importSpecv2(card:CharacterCardV2, img?:Uint8Array, mode:'hub'|'n
|
|||||||
let utilityBot = false
|
let utilityBot = false
|
||||||
let sdData = defaultSdDataFunc()
|
let sdData = defaultSdDataFunc()
|
||||||
let extAssets:[string,string,string][] = []
|
let extAssets:[string,string,string][] = []
|
||||||
|
let ccAssets:{
|
||||||
|
type: string
|
||||||
|
uri: string
|
||||||
|
name: string
|
||||||
|
ext: string
|
||||||
|
}[] = []
|
||||||
|
|
||||||
let vits:null|OnnxModelFiles = null
|
let vits:null|OnnxModelFiles = null
|
||||||
if(risuext){
|
if(risuext && card.spec === 'chara_card_v2'){
|
||||||
if(risuext.emotions){
|
if(risuext.emotions){
|
||||||
for(let i=0;i<risuext.emotions.length;i++){
|
for(let i=0;i<risuext.emotions.length;i++){
|
||||||
alertStore.set({
|
alertStore.set({
|
||||||
@@ -417,6 +421,46 @@ async function importSpecv2(card:CharacterCardV2, img?:Uint8Array, mode:'hub'|'n
|
|||||||
utilityBot = risuext.utilityBot ?? utilityBot
|
utilityBot = risuext.utilityBot ?? utilityBot
|
||||||
sdData = risuext.sdData ?? sdData
|
sdData = risuext.sdData ?? sdData
|
||||||
}
|
}
|
||||||
|
if(card.spec === 'chara_card_v3'){
|
||||||
|
const data = card.data //required for type checking
|
||||||
|
if(data.assets){
|
||||||
|
for(let i=0;i<data.assets.length;i++){
|
||||||
|
alertStore.set({
|
||||||
|
type: 'wait',
|
||||||
|
msg: `Loading... (Getting Assets ${i} / ${data.assets.length})`
|
||||||
|
})
|
||||||
|
await sleep(10)
|
||||||
|
let fileName = ''
|
||||||
|
if(data.assets[i].name){
|
||||||
|
fileName = data.assets[i].name
|
||||||
|
}
|
||||||
|
if(data.assets[i].uri.startsWith('__asset:')){
|
||||||
|
const key = data.assets[i].uri.replace('__asset:', '')
|
||||||
|
const imgp = assetDict[key]
|
||||||
|
if(!imgp){
|
||||||
|
throw new Error('Error while importing, asset ' + key + ' not found')
|
||||||
|
}
|
||||||
|
extAssets.push([fileName,imgp,data.assets[i].ext])
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
const imgp = await saveAsset(mode === 'hub' ? (await getHubResources(data.assets[i].uri)) :Buffer.from(data.assets[i].uri, 'base64'), '', fileName)
|
||||||
|
if(data.assets[i].type === 'emotion'){
|
||||||
|
emotions.push([fileName,imgp])
|
||||||
|
}
|
||||||
|
else if(data.assets[i].type === 'x-risu-asset'){
|
||||||
|
extAssets.push([fileName,imgp, data.assets[i].ext ?? 'unknown'])
|
||||||
|
}
|
||||||
|
else{
|
||||||
|
ccAssets.push({
|
||||||
|
type: data.assets[i].type ?? 'asset',
|
||||||
|
uri: imgp,
|
||||||
|
name: fileName,
|
||||||
|
ext: data.assets[i].ext ?? 'unknown'
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const charbook = data.character_book
|
const charbook = data.character_book
|
||||||
let lorebook:loreBook[] = []
|
let lorebook:loreBook[] = []
|
||||||
@@ -449,6 +493,8 @@ async function importSpecv2(card:CharacterCardV2, img?:Uint8Array, mode:'hub'|'n
|
|||||||
extentions: {...book.extensions, risu_case_sensitive: book.case_sensitive},
|
extentions: {...book.extensions, risu_case_sensitive: book.case_sensitive},
|
||||||
activationPercent: book.extensions?.risu_activationPercent,
|
activationPercent: book.extensions?.risu_activationPercent,
|
||||||
loreCache: book.extensions?.risu_loreCache ?? null,
|
loreCache: book.extensions?.risu_loreCache ?? null,
|
||||||
|
//@ts-ignore
|
||||||
|
useRegex: book.use_regex ?? false
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -521,6 +567,15 @@ async function importSpecv2(card:CharacterCardV2, img?:Uint8Array, mode:'hub'|'n
|
|||||||
vits: vits,
|
vits: vits,
|
||||||
ttsMode: vits ? 'vits' : 'normal',
|
ttsMode: vits ? 'vits' : 'normal',
|
||||||
imported: true,
|
imported: true,
|
||||||
|
source: card?.data?.extensions?.risuai?.source ?? [],
|
||||||
|
}
|
||||||
|
|
||||||
|
if(card.spec === 'chara_card_v3'){
|
||||||
|
char.group_only_greetings = card.data.group_only_greetings ?? []
|
||||||
|
char.nickname = card.data.nickname ?? ''
|
||||||
|
char.source = card.data.source ?? card.data?.extensions?.risuai?.source ?? []
|
||||||
|
char.creation_date = card.data.creation_date ?? 0
|
||||||
|
char.modification_date = card.data.modification_date ?? 0
|
||||||
}
|
}
|
||||||
|
|
||||||
db.characters.push(char)
|
db.characters.push(char)
|
||||||
@@ -534,6 +589,7 @@ async function importSpecv2(card:CharacterCardV2, img?:Uint8Array, mode:'hub'|'n
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
async function createBaseV2(char:character) {
|
async function createBaseV2(char:character) {
|
||||||
|
|
||||||
let charBook:charBookEntry[] = []
|
let charBook:charBookEntry[] = []
|
||||||
@@ -569,7 +625,7 @@ async function createBaseV2(char:character) {
|
|||||||
|
|
||||||
char.loreExt.risu_fullWordMatching = char.loreSettings?.fullWordMatching ?? false
|
char.loreExt.risu_fullWordMatching = char.loreSettings?.fullWordMatching ?? false
|
||||||
|
|
||||||
const card:CharacterCardV2 = {
|
const card:CharacterCardV2Risu = {
|
||||||
spec: "chara_card_v2",
|
spec: "chara_card_v2",
|
||||||
spec_version: "2.0",
|
spec_version: "2.0",
|
||||||
data: {
|
data: {
|
||||||
@@ -630,15 +686,15 @@ async function createBaseV2(char:character) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
export async function exportSpecV2(char:character, type:'png'|'json'|'rcc' = 'png', arg:{
|
export async function exportCharacterCard(char:character, type:'png'|'json' = 'png', arg:{
|
||||||
password?:string
|
password?:string
|
||||||
writer?:LocalWriter|VirtualWriter
|
writer?:LocalWriter|VirtualWriter,
|
||||||
|
spec?:'v2'|'v3'
|
||||||
} = {}) {
|
} = {}) {
|
||||||
let img = await readImage(char.image)
|
let img = await readImage(char.image)
|
||||||
|
const spec:'v2'|'v3' = arg.spec ?? 'v2' //backward compatibility
|
||||||
try{
|
try{
|
||||||
char.image = ''
|
char.image = ''
|
||||||
const card = await createBaseV2(char)
|
|
||||||
img = await reencodeImage(img)
|
img = await reencodeImage(img)
|
||||||
const localWriter = arg.writer ?? (new LocalWriter())
|
const localWriter = arg.writer ?? (new LocalWriter())
|
||||||
if(!arg.writer){
|
if(!arg.writer){
|
||||||
@@ -647,81 +703,98 @@ export async function exportSpecV2(char:character, type:'png'|'json'|'rcc' = 'pn
|
|||||||
const writer = new PngChunk.streamWriter(img, localWriter)
|
const writer = new PngChunk.streamWriter(img, localWriter)
|
||||||
await writer.init()
|
await writer.init()
|
||||||
let assetIndex = 0
|
let assetIndex = 0
|
||||||
if(card.data.extensions.risuai.emotions && card.data.extensions.risuai.emotions.length > 0){
|
if(spec === 'v2'){
|
||||||
for(let i=0;i<card.data.extensions.risuai.emotions.length;i++){
|
const card = await createBaseV2(char)
|
||||||
alertStore.set({
|
if(card.data.extensions.risuai.emotions && card.data.extensions.risuai.emotions.length > 0){
|
||||||
type: 'wait',
|
for(let i=0;i<card.data.extensions.risuai.emotions.length;i++){
|
||||||
msg: `Loading... (Adding Emotions ${i} / ${card.data.extensions.risuai.emotions.length})`
|
alertStore.set({
|
||||||
})
|
type: 'wait',
|
||||||
const key = card.data.extensions.risuai.emotions[i][1]
|
msg: `Loading... (Adding Emotions ${i} / ${card.data.extensions.risuai.emotions.length})`
|
||||||
const rData = await readImage(key)
|
})
|
||||||
const b64encoded = Buffer.from(await convertImage(rData)).toString('base64')
|
const key = card.data.extensions.risuai.emotions[i][1]
|
||||||
assetIndex++
|
const rData = await readImage(key)
|
||||||
card.data.extensions.risuai.emotions[i][1] = `__asset:${assetIndex}`
|
const b64encoded = Buffer.from(await convertImage(rData)).toString('base64')
|
||||||
await writer.write("chara-ext-asset_" + assetIndex, b64encoded)
|
assetIndex++
|
||||||
|
card.data.extensions.risuai.emotions[i][1] = `__asset:${assetIndex}`
|
||||||
|
await writer.write("chara-ext-asset_" + assetIndex, b64encoded)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
if(card.data.extensions.risuai.additionalAssets && card.data.extensions.risuai.additionalAssets.length > 0){
|
if(card.data.extensions.risuai.additionalAssets && card.data.extensions.risuai.additionalAssets.length > 0){
|
||||||
for(let i=0;i<card.data.extensions.risuai.additionalAssets.length;i++){
|
for(let i=0;i<card.data.extensions.risuai.additionalAssets.length;i++){
|
||||||
alertStore.set({
|
alertStore.set({
|
||||||
type: 'wait',
|
type: 'wait',
|
||||||
msg: `Loading... (Adding Additional Assets ${i} / ${card.data.extensions.risuai.additionalAssets.length})`
|
msg: `Loading... (Adding Additional Assets ${i} / ${card.data.extensions.risuai.additionalAssets.length})`
|
||||||
})
|
})
|
||||||
const key = card.data.extensions.risuai.additionalAssets[i][1]
|
const key = card.data.extensions.risuai.additionalAssets[i][1]
|
||||||
const rData = await readImage(key)
|
const rData = await readImage(key)
|
||||||
const b64encoded = Buffer.from(await convertImage(rData)).toString('base64')
|
const b64encoded = Buffer.from(await convertImage(rData)).toString('base64')
|
||||||
assetIndex++
|
assetIndex++
|
||||||
card.data.extensions.risuai.additionalAssets[i][1] = `__asset:${assetIndex}`
|
card.data.extensions.risuai.additionalAssets[i][1] = `__asset:${assetIndex}`
|
||||||
await writer.write("chara-ext-asset_" + assetIndex, b64encoded)
|
await writer.write("chara-ext-asset_" + assetIndex, b64encoded)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
if(char.vits && char.ttsMode === 'vits'){
|
if(char.vits && char.ttsMode === 'vits'){
|
||||||
const keys = Object.keys(char.vits.files)
|
const keys = Object.keys(char.vits.files)
|
||||||
for(let i=0;i<keys.length;i++){
|
for(let i=0;i<keys.length;i++){
|
||||||
alertStore.set({
|
alertStore.set({
|
||||||
type: 'wait',
|
type: 'wait',
|
||||||
msg: `Loading... (Adding VITS ${i} / ${keys.length})`
|
msg: `Loading... (Adding VITS ${i} / ${keys.length})`
|
||||||
})
|
})
|
||||||
const key = keys[i]
|
const key = keys[i]
|
||||||
const rData = await loadAsset(char.vits.files[key])
|
const rData = await loadAsset(char.vits.files[key])
|
||||||
const b64encoded = Buffer.from(rData).toString('base64')
|
const b64encoded = Buffer.from(rData).toString('base64')
|
||||||
assetIndex++
|
assetIndex++
|
||||||
card.data.extensions.risuai.vits[key] = `__asset:${assetIndex}`
|
card.data.extensions.risuai.vits[key] = `__asset:${assetIndex}`
|
||||||
await writer.write("chara-ext-asset_" + assetIndex, b64encoded)
|
await writer.write("chara-ext-asset_" + assetIndex, b64encoded)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
if(type === 'json'){
|
||||||
|
await downloadFile(`${char.name.replace(/[<>:"/\\|?*\.\,]/g, "")}_export.json`, Buffer.from(JSON.stringify(card, null, 4), 'utf-8'))
|
||||||
if(type === 'json'){
|
alertNormal(language.successExport)
|
||||||
await downloadFile(`${char.name.replace(/[<>:"/\\|?*\.\,]/g, "")}_export.json`, Buffer.from(JSON.stringify(card, null, 4), 'utf-8'))
|
return
|
||||||
alertNormal(language.successExport)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
await sleep(10)
|
|
||||||
alertStore.set({
|
|
||||||
type: 'wait',
|
|
||||||
msg: 'Loading... (Writing)'
|
|
||||||
})
|
|
||||||
|
|
||||||
if(type === 'rcc'){
|
|
||||||
const password = arg.password || 'RISU_NONE'
|
|
||||||
const json = JSON.stringify(card)
|
|
||||||
const encrypted = Buffer.from(await encryptBuffer(Buffer.from(json, 'utf-8'), password))
|
|
||||||
const hashed = await hasher(encrypted)
|
|
||||||
const metaData:RccCardMetaData = {}
|
|
||||||
if(password !== 'RISU_NONE'){
|
|
||||||
metaData.usePassword = true
|
|
||||||
}
|
}
|
||||||
const rccString = 'rcc||rccv1||' + encrypted.toString('base64') + '||' + hashed + '||' + Buffer.from(JSON.stringify(metaData)).toString('base64')
|
|
||||||
await writer.write("chara", rccString)
|
await sleep(10)
|
||||||
}
|
alertStore.set({
|
||||||
else{
|
type: 'wait',
|
||||||
|
msg: 'Loading... (Writing)'
|
||||||
|
})
|
||||||
|
|
||||||
await writer.write("chara", Buffer.from(JSON.stringify(card)).toString('base64'))
|
await writer.write("chara", Buffer.from(JSON.stringify(card)).toString('base64'))
|
||||||
}
|
}
|
||||||
|
else if(spec === 'v3'){
|
||||||
|
const card = createBaseV3(char)
|
||||||
|
if(card.data.assets && card.data.assets.length > 0){
|
||||||
|
for(let i=0;i<card.data.assets.length;i++){
|
||||||
|
alertStore.set({
|
||||||
|
type: 'wait',
|
||||||
|
msg: `Loading... (Adding Assets ${i} / ${card.data.assets.length})`
|
||||||
|
})
|
||||||
|
const key = card.data.assets[i].uri
|
||||||
|
const rData = await readImage(key)
|
||||||
|
const b64encoded = Buffer.from(await convertImage(rData)).toString('base64')
|
||||||
|
assetIndex++
|
||||||
|
card.data.assets[i].uri = `__asset:${assetIndex}`
|
||||||
|
await writer.write("chara-ext-asset_" + assetIndex, b64encoded)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if(type === 'json'){
|
||||||
|
await downloadFile(`${char.name.replace(/[<>:"/\\|?*\.\,]/g, "")}_export.json`, Buffer.from(JSON.stringify(card, null, 4), 'utf-8'))
|
||||||
|
alertNormal(language.successExport)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
await sleep(10)
|
||||||
|
alertStore.set({
|
||||||
|
type: 'wait',
|
||||||
|
msg: 'Loading... (Writing)'
|
||||||
|
})
|
||||||
|
|
||||||
|
await writer.write("ccv3", Buffer.from(JSON.stringify(card)).toString('base64'))
|
||||||
|
}
|
||||||
await writer.end()
|
await writer.end()
|
||||||
|
|
||||||
await sleep(10)
|
await sleep(10)
|
||||||
@@ -737,9 +810,130 @@ export async function exportSpecV2(char:character, type:'png'|'json'|'rcc' = 'pn
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function shareRisuHub3() {
|
export function createBaseV3(char:character){
|
||||||
|
|
||||||
|
let charBook:LorebookEntry[] = []
|
||||||
|
let assets:Array<{
|
||||||
|
type: string
|
||||||
|
uri: string
|
||||||
|
name: string
|
||||||
|
ext: string
|
||||||
|
}> = structuredClone(char.ccAssets ?? [])
|
||||||
|
|
||||||
|
for(const asset of char.additionalAssets){
|
||||||
|
assets.push({
|
||||||
|
type: 'x-risu-asset',
|
||||||
|
uri: asset[1],
|
||||||
|
name: asset[0],
|
||||||
|
ext: asset[2] || 'unknown'
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
for(const asset of char.emotionImages){
|
||||||
|
assets.push({
|
||||||
|
type: 'emotion',
|
||||||
|
uri: asset[1],
|
||||||
|
name: asset[0],
|
||||||
|
ext: 'unknown'
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
for(const lore of char.globalLore){
|
||||||
|
let ext:{
|
||||||
|
risu_case_sensitive?: boolean;
|
||||||
|
risu_activationPercent?: number
|
||||||
|
risu_loreCache?: {
|
||||||
|
key:string
|
||||||
|
data:string[]
|
||||||
|
}
|
||||||
|
} = structuredClone(lore.extentions ?? {})
|
||||||
|
|
||||||
|
let caseSensitive = ext.risu_case_sensitive ?? false
|
||||||
|
ext.risu_activationPercent = lore.activationPercent
|
||||||
|
ext.risu_loreCache = lore.loreCache
|
||||||
|
|
||||||
|
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: ext,
|
||||||
|
enabled: true,
|
||||||
|
insertion_order: lore.insertorder,
|
||||||
|
constant: lore.alwaysActive,
|
||||||
|
selective:lore.selective,
|
||||||
|
name: lore.comment,
|
||||||
|
comment: lore.comment,
|
||||||
|
case_sensitive: caseSensitive,
|
||||||
|
use_regex: lore.useRegex ?? false,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
char.loreExt ??= {}
|
||||||
|
|
||||||
|
char.loreExt.risu_fullWordMatching = char.loreSettings?.fullWordMatching ?? false
|
||||||
|
|
||||||
|
const card:CharacterCardV3 = {
|
||||||
|
spec: "chara_card_v3",
|
||||||
|
spec_version: "3.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.replaceGlobalNote ?? '',
|
||||||
|
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: charBook
|
||||||
|
},
|
||||||
|
tags: char.tags ?? [],
|
||||||
|
creator: char.additionalData?.creator ?? '',
|
||||||
|
character_version: `${char.additionalData?.character_version}` ?? '',
|
||||||
|
extensions: {
|
||||||
|
risuai: {
|
||||||
|
bias: char.bias,
|
||||||
|
viewScreen: char.viewScreen,
|
||||||
|
customScripts: char.customscript,
|
||||||
|
utilityBot: char.utilityBot,
|
||||||
|
sdData: char.sdData,
|
||||||
|
backgroundHTML: char.backgroundHTML,
|
||||||
|
license: char.license,
|
||||||
|
triggerscript: char.triggerscript,
|
||||||
|
additionalText: char.additionalText,
|
||||||
|
virtualscript: '', //removed dude to security issue
|
||||||
|
largePortrait: char.largePortrait,
|
||||||
|
lorePlus: char.lorePlus,
|
||||||
|
inlayViewScreen: char.inlayViewScreen,
|
||||||
|
newGenData: char.newGenData,
|
||||||
|
vits: {}
|
||||||
|
},
|
||||||
|
depth_prompt: char.depth_prompt
|
||||||
|
},
|
||||||
|
group_only_greetings: char.group_only_greetings ?? [],
|
||||||
|
nickname: char.nickname ?? '',
|
||||||
|
source: char.source ?? [],
|
||||||
|
creation_date: char.creation_date ?? 0,
|
||||||
|
modification_date: Math.floor(Date.now() / 1000),
|
||||||
|
assets: assets
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if(char.extentions){
|
||||||
|
for(const key in char.extentions){
|
||||||
|
if(key === 'risuai' || key === 'depth_prompt'){
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
card.data.extensions[key] = char.extentions[key]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return card
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -769,7 +963,7 @@ export async function shareRisuHub2(char:character, arg:{
|
|||||||
|
|
||||||
|
|
||||||
const writer = new VirtualWriter()
|
const writer = new VirtualWriter()
|
||||||
await exportSpecV2(char, 'png', {writer: writer})
|
await exportCharacterCard(char, 'png', {writer: writer})
|
||||||
const dat = Buffer.from(writer.buf.buffer).toString('base64') + '&' + 'rt.png'
|
const dat = Buffer.from(writer.buf.buffer).toString('base64') + '&' + 'rt.png'
|
||||||
|
|
||||||
openURL(`https://realm.risuai.net/hub/realm/upload#filedata=${encodeURIComponent(dat)}`)
|
openURL(`https://realm.risuai.net/hub/realm/upload#filedata=${encodeURIComponent(dat)}`)
|
||||||
@@ -881,10 +1075,10 @@ export async function downloadRisuHub(id:string) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const result = await res.json()
|
const result = await res.json()
|
||||||
const data:CharacterCardV2 = result.card
|
const data:CharacterCardV2Risu = result.card
|
||||||
const img:string = result.img
|
const img:string = result.img
|
||||||
|
|
||||||
await importSpecv2(data, await getHubResources(img), 'hub')
|
await importCharacterCardSpec(data, await getHubResources(img), 'hub')
|
||||||
checkCharOrder()
|
checkCharOrder()
|
||||||
let db = get(DataBase)
|
let db = get(DataBase)
|
||||||
if(db.characters[db.characters.length-1]){
|
if(db.characters[db.characters.length-1]){
|
||||||
@@ -908,7 +1102,7 @@ export async function getHubResources(id:string) {
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
type CharacterCardV2 = {
|
type CharacterCardV2Risu = {
|
||||||
spec: 'chara_card_v2'
|
spec: 'chara_card_v2'
|
||||||
spec_version: '2.0' // May 8th addition
|
spec_version: '2.0' // May 8th addition
|
||||||
data: {
|
data: {
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { get } from "svelte/store";
|
import { get } from "svelte/store";
|
||||||
import { exportSpecV2 } from "./characterCards";
|
import { exportCharacterCard } from "./characterCards";
|
||||||
import { VirtualWriter, isTauri, openURL } from "./storage/globalApi";
|
import { VirtualWriter, isTauri, openURL } from "./storage/globalApi";
|
||||||
import { sleep } from "./util";
|
import { sleep } from "./util";
|
||||||
import { CurrentCharacter } from "./stores";
|
import { CurrentCharacter } from "./stores";
|
||||||
@@ -21,7 +21,7 @@ export async function shareRealmCardData():Promise<{ name: ArrayBuffer; data: Ar
|
|||||||
const trimedName = char.name.replace(/[^a-zA-Z0-9]/g, '') || 'character';
|
const trimedName = char.name.replace(/[^a-zA-Z0-9]/g, '') || 'character';
|
||||||
const writer = new VirtualWriter()
|
const writer = new VirtualWriter()
|
||||||
const namebuf = new TextEncoder().encode(trimedName + '.png')
|
const namebuf = new TextEncoder().encode(trimedName + '.png')
|
||||||
await exportSpecV2(char, 'png', {writer: writer})
|
await exportCharacterCard(char, 'png', {writer: writer})
|
||||||
alertStore.set({
|
alertStore.set({
|
||||||
type: 'none',
|
type: 'none',
|
||||||
msg: ''
|
msg: ''
|
||||||
|
|||||||
@@ -687,7 +687,8 @@ export interface loreBook{
|
|||||||
loreCache?:{
|
loreCache?:{
|
||||||
key:string
|
key:string
|
||||||
data:string[]
|
data:string[]
|
||||||
}
|
},
|
||||||
|
useRegex?:boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface character{
|
export interface character{
|
||||||
@@ -773,6 +774,17 @@ export interface character{
|
|||||||
realmId?:string
|
realmId?:string
|
||||||
imported?:boolean
|
imported?:boolean
|
||||||
trashTime?:number
|
trashTime?:number
|
||||||
|
nickname?:string
|
||||||
|
source?:string[]
|
||||||
|
group_only_greetings?:string[]
|
||||||
|
creation_date?:number
|
||||||
|
modification_date?:number
|
||||||
|
ccAssets?: Array<{
|
||||||
|
type: string
|
||||||
|
uri: string
|
||||||
|
name: string
|
||||||
|
ext: string
|
||||||
|
}>
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user