add export via htmls
This commit is contained in:
@@ -658,4 +658,9 @@ export const languageEnglish = {
|
|||||||
notCharxWarn: "This character uses multiple assets. it is recommended to export this character as a CharX format for better compatibility.",
|
notCharxWarn: "This character uses multiple assets. it is recommended to export this character as a CharX format for better compatibility.",
|
||||||
noPlugins: "No Plugins Installed",
|
noPlugins: "No Plugins Installed",
|
||||||
legacyTranslation: "Legacy Translation",
|
legacyTranslation: "Legacy Translation",
|
||||||
|
clipboardSuccess: "Copied to Clipboard",
|
||||||
|
translateContent: 'Translate Content',
|
||||||
|
doNotTranslate: "Do Not Translate",
|
||||||
|
includePersonaName: "Include Persona Name",
|
||||||
|
hidePersonaName: "Hide Persona Name",
|
||||||
}
|
}
|
||||||
@@ -95,7 +95,7 @@
|
|||||||
{/if}
|
{/if}
|
||||||
<button class="text-textcolor2 hover:text-green-500" on:click|stopPropagation={async () => {
|
<button class="text-textcolor2 hover:text-green-500" on:click|stopPropagation={async () => {
|
||||||
await navigator.clipboard.writeText(`https://realm.risuai.net/character/${openedData.id}`)
|
await navigator.clipboard.writeText(`https://realm.risuai.net/character/${openedData.id}`)
|
||||||
alertNormal("Copied to clipboard")
|
alertNormal(language.clipboardSuccess)
|
||||||
}}>
|
}}>
|
||||||
<PaperclipIcon />
|
<PaperclipIcon />
|
||||||
</button>
|
</button>
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { get, writable } from "svelte/store";
|
import { get, writable } from "svelte/store";
|
||||||
import { DataBase, saveImage, setDatabase, type character, type Chat, defaultSdDataFunc, type loreBook } from "./storage/database";
|
import { DataBase, saveImage, setDatabase, type character, type Chat, defaultSdDataFunc, type loreBook } from "./storage/database";
|
||||||
import { alertConfirm, alertError, alertNormal, alertSelect, alertStore } from "./alert";
|
import { alertConfirm, alertError, alertNormal, alertSelect, alertStore, alertWait } from "./alert";
|
||||||
import { language } from "../lang";
|
import { language } from "../lang";
|
||||||
import { decode as decodeMsgpack } from "msgpackr";
|
import { decode as decodeMsgpack } from "msgpackr";
|
||||||
import { checkNullish, findCharacterbyId, selectMultipleFile, selectSingleFile, sleep } from "./util";
|
import { checkNullish, findCharacterbyId, selectMultipleFile, selectSingleFile, sleep } from "./util";
|
||||||
@@ -10,6 +10,8 @@ import { checkCharOrder, downloadFile, getFileSrc } from "./storage/globalApi";
|
|||||||
import { reencodeImage } from "./process/files/image";
|
import { reencodeImage } from "./process/files/image";
|
||||||
import { updateInlayScreen } from "./process/inlayScreen";
|
import { updateInlayScreen } from "./process/inlayScreen";
|
||||||
import { PngChunk } from "./pngChunk";
|
import { PngChunk } from "./pngChunk";
|
||||||
|
import { parseMarkdownSafe } from "./parser";
|
||||||
|
import { translateHTML } from "./translator/translator";
|
||||||
|
|
||||||
export function createNewCharacter() {
|
export function createNewCharacter() {
|
||||||
let db = get(DataBase)
|
let db = get(DataBase)
|
||||||
@@ -153,12 +155,31 @@ export async function rmCharEmotion(charId:number, emotionId:number) {
|
|||||||
export async function exportChat(page:number){
|
export async function exportChat(page:number){
|
||||||
try {
|
try {
|
||||||
|
|
||||||
const mode = await alertSelect(['Export as JSON', "Export as TXT"])
|
const mode = await alertSelect(['Export as JSON', "Export as TXT", "Export as HTML File", "Export as HTML Embed"])
|
||||||
|
const doTranslate = (mode === '2' || mode === '3') ? (await alertSelect([language.translateContent, language.doNotTranslate])) === '0' : false
|
||||||
|
const anonymous = (mode === '2' || mode === '3') ? ((await alertSelect([language.includePersonaName, language.hidePersonaName])) === '1') : false
|
||||||
const selectedID = get(selectedCharID)
|
const selectedID = get(selectedCharID)
|
||||||
const db = get(DataBase)
|
const db = get(DataBase)
|
||||||
const chat = db.characters[selectedID].chats[page]
|
const chat = db.characters[selectedID].chats[page]
|
||||||
const char = db.characters[selectedID]
|
const char = db.characters[selectedID]
|
||||||
const date = new Date().toJSON();
|
const date = new Date().toJSON();
|
||||||
|
const htmlChatParse = async (v:string) => {
|
||||||
|
v = parseMarkdownSafe(v)
|
||||||
|
|
||||||
|
if(doTranslate){
|
||||||
|
v = await translateHTML(v, false, '', -1)
|
||||||
|
}
|
||||||
|
|
||||||
|
if(anonymous){
|
||||||
|
//case insensitive match, replace all
|
||||||
|
const excapedName = char.name.replace(/[-\/\\^$*+\?\.()|[\]{}]/g, '\\$&')
|
||||||
|
|
||||||
|
v = v.replace(new RegExp(`${excapedName}`, 'gi'), '×××')
|
||||||
|
}
|
||||||
|
|
||||||
|
return v
|
||||||
|
}
|
||||||
|
|
||||||
if(mode === '0'){
|
if(mode === '0'){
|
||||||
const stringl = Buffer.from(JSON.stringify({
|
const stringl = Buffer.from(JSON.stringify({
|
||||||
type: 'risuChat',
|
type: 'risuChat',
|
||||||
@@ -168,20 +189,132 @@ export async function exportChat(page:number){
|
|||||||
|
|
||||||
await downloadFile(`${char.name}_${date}_chat`.replace(/[<>:"/\\|?*\.\,]/g, "") + '.json', stringl)
|
await downloadFile(`${char.name}_${date}_chat`.replace(/[<>:"/\\|?*\.\,]/g, "") + '.json', stringl)
|
||||||
|
|
||||||
|
}
|
||||||
|
else if(mode === '2'){
|
||||||
|
|
||||||
|
let chatContentHTML = ''
|
||||||
|
|
||||||
|
let i = 0
|
||||||
|
for(const v of chat.message){
|
||||||
|
alertWait(`Translating... ${i++}/${chat.message.length}`)
|
||||||
|
const name = v.saying ? findCharacterbyId(v.saying).name : v.role === 'char' ? char.name : anonymous ? '×××' : db.username
|
||||||
|
chatContentHTML += `<div class="chat">
|
||||||
|
<h2>${name}</h2>
|
||||||
|
<div>${await htmlChatParse(v.data)}</div>
|
||||||
|
</div>`
|
||||||
|
}
|
||||||
|
|
||||||
|
const doc = `
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<title>${char.name} Chat</title>
|
||||||
|
<style>
|
||||||
|
body{
|
||||||
|
font-family: Arial, sans-serif;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
.container{
|
||||||
|
max-width: 800px;
|
||||||
|
padding: 1rem;
|
||||||
|
border-radius: 10px;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 1rem;
|
||||||
|
}
|
||||||
|
.chat{
|
||||||
|
background: #f0f0f0;
|
||||||
|
padding: 1rem;
|
||||||
|
border-radius: 10px;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
.idat{
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
h2{
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
.chat div{
|
||||||
|
margin-top: 0.5rem;
|
||||||
|
break-word: break-all;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div class="container">
|
||||||
|
<div class="chat">
|
||||||
|
<h2>${char.name}</h2>
|
||||||
|
<div>${await htmlChatParse(
|
||||||
|
char.firstMsgIndex === -1 ? char.firstMessage : char.alternateGreetings?.[char.firstMsgIndex ?? 0]
|
||||||
|
)}</div>
|
||||||
|
</div>
|
||||||
|
${chatContentHTML}
|
||||||
|
</div>
|
||||||
|
<div class="idat">${
|
||||||
|
JSON.stringify(chat).replace(/</g, '<').replace(/>/g, '>')
|
||||||
|
}</div>
|
||||||
|
</body>
|
||||||
|
`
|
||||||
|
|
||||||
|
|
||||||
|
await downloadFile(`${char.name}_${date}_chat`.replace(/[<>:"/\\|?*\.\,]/g, "") + '.html', Buffer.from(doc, 'utf-8'))
|
||||||
|
}
|
||||||
|
else if(mode === '3'){
|
||||||
|
//create a html table
|
||||||
|
let chatContentHTML = ''
|
||||||
|
|
||||||
|
let i = 0
|
||||||
|
for(const v of chat.message){
|
||||||
|
alertWait(`Translating... ${i++}/${chat.message.length}`)
|
||||||
|
const name = v.saying ? findCharacterbyId(v.saying).name : v.role === 'char' ? char.name : anonymous ? '×××' : db.username
|
||||||
|
chatContentHTML += `<tr>
|
||||||
|
<td>${name}</td>
|
||||||
|
<td>${await htmlChatParse(v.data)}</td>
|
||||||
|
</tr>`
|
||||||
|
}
|
||||||
|
|
||||||
|
const template = `
|
||||||
|
<table>
|
||||||
|
<tr>
|
||||||
|
<th>Character</th>
|
||||||
|
<th>Message</th>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>${char.name}</td>
|
||||||
|
<td>${await htmlChatParse(char.firstMessage)}</td>
|
||||||
|
</tr>
|
||||||
|
${chatContentHTML}
|
||||||
|
</table>
|
||||||
|
<p>Chat from RisuAI</p>
|
||||||
|
`
|
||||||
|
|
||||||
|
//copy to clipboard
|
||||||
|
|
||||||
|
const item = new ClipboardItem({
|
||||||
|
'text/html': new Blob([template], { type: 'text/html' }),
|
||||||
|
'text/plain': new Blob([template], { type: 'text/plain' })
|
||||||
|
})
|
||||||
|
await navigator.clipboard.write([item])
|
||||||
|
|
||||||
|
alertNormal(language.clipboardSuccess)
|
||||||
|
return
|
||||||
|
|
||||||
}
|
}
|
||||||
else{
|
else{
|
||||||
|
|
||||||
let stringl = chat.message.map((v) => {
|
let stringl = chat.message.map((v) => {
|
||||||
if(v.saying){
|
if(v.saying){
|
||||||
return `${findCharacterbyId(v.saying).name}\n${v.data}`
|
return `--${findCharacterbyId(v.saying).name}\n${v.data}`
|
||||||
}
|
}
|
||||||
else{
|
else{
|
||||||
return `${v.role === 'char' ? char.name : db.username}\n${v.data}`
|
return `--${v.role === 'char' ? char.name : db.username}\n${v.data}`
|
||||||
}
|
}
|
||||||
}).join('\n\n')
|
}).join('\n\n')
|
||||||
|
|
||||||
if(char.type !== 'group'){
|
if(char.type !== 'group'){
|
||||||
stringl = `${char.name}\n${char.firstMessage}\n\n` + stringl
|
stringl = `--${char.name}\n${char.firstMessage}\n\n` + stringl
|
||||||
}
|
}
|
||||||
|
|
||||||
await downloadFile(`${char.name}_${date}_chat`.replace(/[<>:"/\\|?*\.\,]/g, "") + '.txt', Buffer.from(stringl, 'utf-8'))
|
await downloadFile(`${char.name}_${date}_chat`.replace(/[<>:"/\\|?*\.\,]/g, "") + '.txt', Buffer.from(stringl, 'utf-8'))
|
||||||
@@ -194,7 +327,7 @@ export async function exportChat(page:number){
|
|||||||
}
|
}
|
||||||
|
|
||||||
export async function importChat(){
|
export async function importChat(){
|
||||||
const dat =await selectSingleFile(['json','jsonl'])
|
const dat =await selectSingleFile(['json','jsonl','txt','html'])
|
||||||
if(!dat){
|
if(!dat){
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -236,7 +369,7 @@ export async function importChat(){
|
|||||||
setDatabase(db)
|
setDatabase(db)
|
||||||
alertNormal(language.successImport)
|
alertNormal(language.successImport)
|
||||||
}
|
}
|
||||||
else{
|
else if(dat.name.endsWith('json')){
|
||||||
const json = JSON.parse(Buffer.from(dat.data).toString('utf-8'))
|
const json = JSON.parse(Buffer.from(dat.data).toString('utf-8'))
|
||||||
if(json.type === 'risuChat' && json.ver === 1){
|
if(json.type === 'risuChat' && json.ver === 1){
|
||||||
const das:Chat = json.data
|
const das:Chat = json.data
|
||||||
@@ -256,7 +389,19 @@ export async function importChat(){
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
else if(dat.name.endsWith('html')){
|
||||||
|
const doc = new DOMParser().parseFromString(Buffer.from(dat.data).toString('utf-8'), 'text/html')
|
||||||
|
const chat = doc.querySelector('.idat').textContent
|
||||||
|
const json = JSON.parse(chat)
|
||||||
|
if(json.message && json.note && json.name && json.localLore){
|
||||||
|
db.characters[selectedID].chats.unshift(json)
|
||||||
|
setDatabase(db)
|
||||||
|
alertNormal(language.successImport)
|
||||||
|
}
|
||||||
|
else{
|
||||||
|
alertError(language.errors.noData)
|
||||||
|
}
|
||||||
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
alertError(`${error}`)
|
alertError(`${error}`)
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user