diff --git a/src/lib/SideBars/CharConfig.svelte b/src/lib/SideBars/CharConfig.svelte index 50d7b9ed..03b5e2ca 100644 --- a/src/lib/SideBars/CharConfig.svelte +++ b/src/lib/SideBars/CharConfig.svelte @@ -5,7 +5,7 @@ import { selectedCharID } from "../../ts/stores"; import { PlusIcon, SmileIcon, TrashIcon, UserIcon, ActivityIcon, BookIcon, LoaderIcon, User } from 'lucide-svelte' import Check from "../Others/Check.svelte"; - import { addCharEmotion, addingEmotion, exportChar, getCharImage, rmCharEmotion, selectCharImg, makeGroupImage } from "../../ts/characters"; + import { addCharEmotion, addingEmotion, getCharImage, rmCharEmotion, selectCharImg, makeGroupImage } from "../../ts/characters"; import LoreBook from "./LoreBookSetting.svelte"; import { alertConfirm, alertError, alertSelectChar } from "../../ts/alert"; import BarIcon from "./BarIcon.svelte"; @@ -14,6 +14,7 @@ import {isEqual, cloneDeep} from 'lodash' import Help from "../Others/Help.svelte"; import RegexData from "./RegexData.svelte"; + import { exportChar } from "src/ts/characterCards"; let subMenu = 0 let subberMenu = 0 diff --git a/src/lib/SideBars/Sidebar.svelte b/src/lib/SideBars/Sidebar.svelte index d784e27c..515c3106 100644 --- a/src/lib/SideBars/Sidebar.svelte +++ b/src/lib/SideBars/Sidebar.svelte @@ -3,7 +3,8 @@ import { DataBase } from "../../ts/database"; import BarIcon from "./BarIcon.svelte"; import { Plus, User, X, Settings, Users, Edit3Icon, ArrowUp, ArrowDown, ListIcon, LayoutGridIcon, PlusIcon} from 'lucide-svelte' - import { characterFormatUpdate, createNewCharacter, createNewGroup, getCharImage, importCharacter } from "../../ts/characters"; + import { characterFormatUpdate, createNewCharacter, createNewGroup, getCharImage } from "../../ts/characters"; + import {importCharacter} from 'src/ts/characterCards' import SettingsDom from './Settings.svelte' import CharConfig from "./CharConfig.svelte"; import { language } from "../../lang"; diff --git a/src/ts/characterCards.ts b/src/ts/characterCards.ts new file mode 100644 index 00000000..bc7fdacd --- /dev/null +++ b/src/ts/characterCards.ts @@ -0,0 +1,336 @@ +import { get } from "svelte/store" +import { alertConfirm, alertError, alertNormal, alertStore } from "./alert" +import { DataBase, defaultSdDataFunc, type character, saveImage, setDatabase } from "./database" +import { checkNullish, selectSingleFile, sleep } from "./util" +import { language } from "src/lang" +import { encode as encodeMsgpack, decode as decodeMsgpack } from "@msgpack/msgpack"; +import { v4 as uuidv4 } from 'uuid'; +import exifr from 'exifr' +import { PngMetadata } from "./exif" +import { characterFormatUpdate } from "./characters" +import { downloadFile, readImage } from "./globalApi" + +type OfficialCardSpec = { + spec: 'chara_card_v2' + spec_version: '2.0' // May 8th addition + data: { + name: string + description: string + personality: string + scenario: string + first_mes: string + mes_example: string + creator_notes: string + system_prompt: string + post_history_instructions: string + alternate_greetings: string[] + character_book?: CharacterBook + tags: string[] + creator: string + character_version: number + extensions: Record + } +} + +type CharacterBook = null + +export async function importCharacter() { + try { + const f = await selectSingleFile(['png', 'json']) + if(!f){ + return + } + if(f.name.endsWith('json')){ + const da = JSON.parse(Buffer.from(f.data).toString('utf-8')) + if(await importSpecv2(da)){ + return + } + if((da.char_name || da.name) && (da.char_persona || da.description) && (da.char_greeting || da.first_mes)){ + let db = get(DataBase) + db.characters.push({ + name: da.char_name ?? da.name, + firstMessage: da.char_greeting ?? da.first_mes, + desc: da.char_persona ?? da.description, + notes: '', + chats: [{ + message: [], + note: '', + name: 'Chat 1', + localLore: [] + }], + chatPage: 0, + image: '', + emotionImages: [], + bias: [], + globalLore: [], + viewScreen: 'none', + chaId: uuidv4(), + sdData: defaultSdDataFunc(), + utilityBot: false, + customscript: [], + exampleMessage: '' + }) + DataBase.set(db) + alertNormal(language.importedCharacter) + return + } + else{ + alertError(language.errors.noData) + return + } + } + alertStore.set({ + type: 'wait', + msg: 'Loading... (Reading)' + }) + await sleep(10) + const img = f.data + const readed = (await exifr.parse(img, true)) + + console.log(readed) + if(readed.chara){ + // standard spec v2 imports + const charaData:CharacterCardV2 = JSON.parse(Buffer.from(readed.chara, 'base64').toString('utf-8')) + if(await importSpecv2(charaData)){ + return + } + } + if(readed.risuai){ + // old risu imports + await sleep(10) + const va = decodeMsgpack(Buffer.from(readed.risuai, 'base64')) as any + if(va.type !== 101){ + alertError(language.errors.noData) + return + } + + + let char:character = va.data + let db = get(DataBase) + if(char.emotionImages && char.emotionImages.length > 0){ + for(let i=0;i 0){ + for(let i=0;i", + name: char.name, + personality: "", + scenario: "", + talkativeness: "0.5" + } + + await sleep(10) + img = PngMetadata.write(img, { + 'chara': Buffer.from(JSON.stringify(tavernData)).toString('base64'), + 'risuai': data + }) + + alertStore.set({ + type: 'wait', + msg: 'Loading... (Writing)' + }) + + char.image = '' + await sleep(10) + await downloadFile(`${char.name.replace(/[<>:"/\\|?*\.\,]/g, "")}_export.png`, img) + + alertNormal(language.successExport) + + } + catch(e){ + alertError(`${e}`) + } + +} + + +async function importSpecv2(card:CharacterCardV2):Promise{ + if(!card ||card.spec !== 'chara_card_v2'){ + return false + } + let data = card.data + + let db = get(DataBase) + + const risuext = data.extensions.risuai + if(risuext && risuext.emotions){ + for(let i=0;i" + name: string + personality: "" + scenario: "" + talkativeness: "0.5" +} diff --git a/src/ts/characters.ts b/src/ts/characters.ts index 92bc5dcd..705b58e4 100644 --- a/src/ts/characters.ts +++ b/src/ts/characters.ts @@ -42,143 +42,6 @@ export function createNewGroup(){ return db.characters.length - 1 } -export async function importCharacter() { - try { - const f = await selectSingleFile(['png', 'json']) - if(!f){ - return - } - if(f.name.endsWith('json')){ - const da = JSON.parse(Buffer.from(f.data).toString('utf-8')) - if((da.char_name || da.name) && (da.char_persona || da.description) && (da.char_greeting || da.first_mes)){ - let db = get(DataBase) - db.characters.push({ - name: da.char_name ?? da.name, - firstMessage: da.char_greeting ?? da.first_mes, - desc: da.char_persona ?? da.description, - notes: '', - chats: [{ - message: [], - note: '', - name: 'Chat 1', - localLore: [] - }], - chatPage: 0, - image: '', - emotionImages: [], - bias: [], - globalLore: [], - viewScreen: 'none', - chaId: uuidv4(), - sdData: defaultSdDataFunc(), - utilityBot: false, - customscript: [], - exampleMessage: '' - }) - DataBase.set(db) - alertNormal(language.importedCharacter) - return - } - else{ - alertError(language.errors.noData) - return - } - } - alertStore.set({ - type: 'wait', - msg: 'Loading... (Reading)' - }) - await sleep(10) - const img = f.data - const readed = (await exifr.parse(img, true)) - - console.log(readed) - if(readed.risuai){ - await sleep(10) - const va = decodeMsgpack(Buffer.from(readed.risuai, 'base64')) as any - if(va.type !== 101){ - alertError(language.errors.noData) - return - } - - - let char:character = va.data - let db = get(DataBase) - if(char.emotionImages && char.emotionImages.length > 0){ - for(let i=0;i" - name: string - personality: "" - scenario: "" - talkativeness: "0.5" -} - export async function selectCharImg(charId:number) { const selected = await selectSingleFile(['png']) if(!selected){ @@ -270,94 +120,6 @@ export async function rmCharEmotion(charId:number, emotionId:number) { setDatabase(db) } -export async function exportChar(charaID:number) { - const db = get(DataBase) - let char:character = JSON.parse(JSON.stringify(db.characters[charaID])) - - if(!char.image){ - alertError('Image Required') - return - } - const conf = await alertConfirm(language.exportConfirm) - if(!conf){ - return - } - - alertStore.set({ - type: 'wait', - msg: 'Loading...' - }) - - let img = await readImage(char.image) - - try{ - if(char.emotionImages && char.emotionImages.length > 0){ - for(let i=0;i", - name: char.name, - personality: "", - scenario: "", - talkativeness: "0.5" - } - - await sleep(10) - img = PngMetadata.write(img, { - 'chara': Buffer.from(JSON.stringify(tavernData)).toString('base64'), - 'risuai': data - }) - - alertStore.set({ - type: 'wait', - msg: 'Loading... (Writing)' - }) - - char.image = '' - await sleep(10) - await downloadFile(`${char.name.replace(/[<>:"/\\|?*\.\,]/g, "")}_export.png`, img) - - alertNormal(language.successExport) - - } - catch(e){ - alertError(`${e}`) - } - -} - export async function exportChat(page:number){ try {