Add character card v2 v3 import/export

This commit is contained in:
kwaroran
2024-05-25 09:23:59 +09:00
parent e5e7533e1a
commit 76a7bfd0f0
7 changed files with 319 additions and 109 deletions

View File

@@ -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
View File

@@ -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:

View File

@@ -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",

View File

@@ -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={() => {

View File

@@ -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: {

View File

@@ -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: ''

View File

@@ -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
}>
} }