Add cold storage functionality and preload chat data
This commit is contained in:
@@ -20,6 +20,7 @@
|
|||||||
import { getModuleToggles } from "src/ts/process/modules";
|
import { getModuleToggles } from "src/ts/process/modules";
|
||||||
import { language } from "src/lang";
|
import { language } from "src/lang";
|
||||||
import Toggles from "./Toggles.svelte";
|
import Toggles from "./Toggles.svelte";
|
||||||
|
import { preLoadChat } from "src/ts/process/coldstorage.svelte";
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
chara: character|groupChat;
|
chara: character|groupChat;
|
||||||
@@ -67,7 +68,6 @@
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
chara.chatPage = newChats.indexOf(chara.chats[currentChatPage])
|
|
||||||
chara.chats = newChats
|
chara.chats = newChats
|
||||||
|
|
||||||
try {
|
try {
|
||||||
@@ -76,6 +76,10 @@
|
|||||||
sorted += 1
|
sorted += 1
|
||||||
await sleep(1)
|
await sleep(1)
|
||||||
createStb()
|
createStb()
|
||||||
|
|
||||||
|
await preLoadChat($selectedCharID, newChats.indexOf(chara.chats[currentChatPage]))
|
||||||
|
chara.chatPage = newChats.indexOf(chara.chats[currentChatPage])
|
||||||
|
|
||||||
},
|
},
|
||||||
...sortableOptions
|
...sortableOptions
|
||||||
}))
|
}))
|
||||||
@@ -107,14 +111,17 @@
|
|||||||
})
|
})
|
||||||
|
|
||||||
chara.chatFolders = newFolders
|
chara.chatFolders = newFolders
|
||||||
chara.chatPage = newChats.indexOf(chara.chats[currentChatPage])
|
|
||||||
chara.chats = newChats
|
chara.chats = newChats
|
||||||
|
|
||||||
try {
|
try {
|
||||||
folderStb.destroy()
|
folderStb.destroy()
|
||||||
} catch (e) {}
|
} catch (e) {}
|
||||||
sorted += 1
|
sorted += 1
|
||||||
await sleep(1)
|
await sleep(1)
|
||||||
createStb()
|
createStb()
|
||||||
|
|
||||||
|
await preLoadChat($selectedCharID, newChats.indexOf(chara.chats[currentChatPage]))
|
||||||
|
chara.chatPage = newChats.indexOf(chara.chats[currentChatPage])
|
||||||
},
|
},
|
||||||
...sortableOptions
|
...sortableOptions
|
||||||
})
|
})
|
||||||
@@ -244,8 +251,9 @@
|
|||||||
<div></div>
|
<div></div>
|
||||||
{:else}
|
{:else}
|
||||||
{#each chara.chats.filter(chat => chat.folderId == chara.chatFolders[i].id) as chat}
|
{#each chara.chats.filter(chat => chat.folderId == chara.chatFolders[i].id) as chat}
|
||||||
<button data-risu-chat-idx={chara.chats.indexOf(chat)} onclick={() => {
|
<button data-risu-chat-idx={chara.chats.indexOf(chat)} onclick={async () => {
|
||||||
if(!editMode){
|
if(!editMode){
|
||||||
|
await preLoadChat($selectedCharID, chara.chats.indexOf(chat))
|
||||||
chara.chatPage = chara.chats.indexOf(chat)
|
chara.chatPage = chara.chats.indexOf(chat)
|
||||||
$ReloadGUIPointer += 1
|
$ReloadGUIPointer += 1
|
||||||
}
|
}
|
||||||
@@ -267,6 +275,7 @@
|
|||||||
const newChat = safeStructuredClone($state.snapshot(chara.chats[chara.chats.indexOf(chat)]))
|
const newChat = safeStructuredClone($state.snapshot(chara.chats[chara.chats.indexOf(chat)]))
|
||||||
newChat.name = `Copy of ${newChat.name}`
|
newChat.name = `Copy of ${newChat.name}`
|
||||||
chara.chats.unshift(newChat)
|
chara.chats.unshift(newChat)
|
||||||
|
await preLoadChat($selectedCharID, 0)
|
||||||
chara.chatPage = 0
|
chara.chatPage = 0
|
||||||
chara.chats = chara.chats
|
chara.chats = chara.chats
|
||||||
break
|
break
|
||||||
@@ -293,6 +302,7 @@
|
|||||||
break
|
break
|
||||||
}
|
}
|
||||||
case 2:{
|
case 2:{
|
||||||
|
await preLoadChat($selectedCharID, chara.chats.indexOf(chat))
|
||||||
chara.chatPage = chara.chats.indexOf(chat)
|
chara.chatPage = chara.chats.indexOf(chat)
|
||||||
createMultiuserRoom()
|
createMultiuserRoom()
|
||||||
}
|
}
|
||||||
@@ -315,6 +325,7 @@
|
|||||||
}
|
}
|
||||||
}} class="text-textcolor2 hover:text-green-500 mr-1 cursor-pointer" onclick={async (e) => {
|
}} class="text-textcolor2 hover:text-green-500 mr-1 cursor-pointer" onclick={async (e) => {
|
||||||
e.stopPropagation()
|
e.stopPropagation()
|
||||||
|
await preLoadChat($selectedCharID, chara.chats.indexOf(chat))
|
||||||
exportChat(chara.chats.indexOf(chat))
|
exportChat(chara.chats.indexOf(chat))
|
||||||
}}>
|
}}>
|
||||||
<DownloadIcon size={18}/>
|
<DownloadIcon size={18}/>
|
||||||
@@ -331,6 +342,7 @@
|
|||||||
}
|
}
|
||||||
const d = await alertConfirm(`${language.removeConfirm}${chat.name}`)
|
const d = await alertConfirm(`${language.removeConfirm}${chat.name}`)
|
||||||
if(d){
|
if(d){
|
||||||
|
await preLoadChat($selectedCharID, 0)
|
||||||
chara.chatPage = 0
|
chara.chatPage = 0
|
||||||
$ReloadGUIPointer += 1
|
$ReloadGUIPointer += 1
|
||||||
let chats = chara.chats
|
let chats = chara.chats
|
||||||
@@ -352,8 +364,9 @@
|
|||||||
<div class="risu-chat flex flex-col">
|
<div class="risu-chat flex flex-col">
|
||||||
{#each chara.chats as chat, i}
|
{#each chara.chats as chat, i}
|
||||||
{#if chat.folderId == null}
|
{#if chat.folderId == null}
|
||||||
<button data-risu-chat-idx={i} onclick={() => {
|
<button data-risu-chat-idx={i} onclick={async () => {
|
||||||
if(!editMode){
|
if(!editMode){
|
||||||
|
await preLoadChat($selectedCharID, i)
|
||||||
chara.chatPage = i
|
chara.chatPage = i
|
||||||
$ReloadGUIPointer += 1
|
$ReloadGUIPointer += 1
|
||||||
}
|
}
|
||||||
@@ -377,6 +390,7 @@
|
|||||||
const newChat = safeStructuredClone($state.snapshot(chara.chats[i]))
|
const newChat = safeStructuredClone($state.snapshot(chara.chats[i]))
|
||||||
newChat.name = `Copy of ${newChat.name}`
|
newChat.name = `Copy of ${newChat.name}`
|
||||||
chara.chats.unshift(newChat)
|
chara.chats.unshift(newChat)
|
||||||
|
await preLoadChat($selectedCharID, 0)
|
||||||
chara.chatPage = 0
|
chara.chatPage = 0
|
||||||
chara.chats = chara.chats
|
chara.chats = chara.chats
|
||||||
break
|
break
|
||||||
@@ -404,6 +418,7 @@
|
|||||||
break
|
break
|
||||||
}
|
}
|
||||||
case 2:{
|
case 2:{
|
||||||
|
await preLoadChat($selectedCharID, i)
|
||||||
chara.chatPage = i
|
chara.chatPage = i
|
||||||
createMultiuserRoom()
|
createMultiuserRoom()
|
||||||
}
|
}
|
||||||
@@ -442,6 +457,7 @@
|
|||||||
}
|
}
|
||||||
const d = await alertConfirm(`${language.removeConfirm}${chat.name}`)
|
const d = await alertConfirm(`${language.removeConfirm}${chat.name}`)
|
||||||
if(d){
|
if(d){
|
||||||
|
await preLoadChat($selectedCharID, 0)
|
||||||
chara.chatPage = 0
|
chara.chatPage = 0
|
||||||
$ReloadGUIPointer += 1
|
$ReloadGUIPointer += 1
|
||||||
let chats = chara.chats
|
let chats = chara.chats
|
||||||
|
|||||||
@@ -43,6 +43,7 @@ import { initMobileGesture } from "./hotkey";
|
|||||||
import { fetch as TauriHTTPFetch } from '@tauri-apps/plugin-http';
|
import { fetch as TauriHTTPFetch } from '@tauri-apps/plugin-http';
|
||||||
import { moduleUpdate } from "./process/modules";
|
import { moduleUpdate } from "./process/modules";
|
||||||
import type { AccountStorage } from "./storage/accountStorage";
|
import type { AccountStorage } from "./storage/accountStorage";
|
||||||
|
import { makeColdData } from "./process/coldstorage.svelte";
|
||||||
|
|
||||||
//@ts-ignore
|
//@ts-ignore
|
||||||
export const isTauri = !!window.__TAURI_INTERNALS__
|
export const isTauri = !!window.__TAURI_INTERNALS__
|
||||||
@@ -675,6 +676,7 @@ export async function loadData() {
|
|||||||
loadedStore.set(true)
|
loadedStore.set(true)
|
||||||
selectedCharID.set(-1)
|
selectedCharID.set(-1)
|
||||||
startObserveDom()
|
startObserveDom()
|
||||||
|
makeColdData()
|
||||||
saveDb()
|
saveDb()
|
||||||
moduleUpdate()
|
moduleUpdate()
|
||||||
if(import.meta.env.VITE_RISU_TOS === 'TRUE'){
|
if(import.meta.env.VITE_RISU_TOS === 'TRUE'){
|
||||||
|
|||||||
159
src/ts/process/coldstorage.svelte.ts
Normal file
159
src/ts/process/coldstorage.svelte.ts
Normal file
@@ -0,0 +1,159 @@
|
|||||||
|
import {
|
||||||
|
writeFile,
|
||||||
|
BaseDirectory,
|
||||||
|
readFile,
|
||||||
|
exists,
|
||||||
|
mkdir,
|
||||||
|
readDir,
|
||||||
|
remove
|
||||||
|
} from "@tauri-apps/plugin-fs"
|
||||||
|
import { isTauri } from "../globalApi.svelte"
|
||||||
|
import { DBState } from "../stores.svelte"
|
||||||
|
|
||||||
|
const coldStorageHeader = '\uEF01COLDSTORAGE\uEF01'
|
||||||
|
|
||||||
|
async function getColdStorageItem(key:string) {
|
||||||
|
|
||||||
|
if(isTauri){
|
||||||
|
try {
|
||||||
|
const f = await readFile('./coldstorage/'+key+'.json')
|
||||||
|
const text = new TextDecoder().decode(f)
|
||||||
|
return JSON.parse(text)
|
||||||
|
} catch (error) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else{
|
||||||
|
//use opfs
|
||||||
|
try {
|
||||||
|
const opfs = await navigator.storage.getDirectory()
|
||||||
|
const file = await opfs.getFileHandle('coldstorage_' + key+'.json')
|
||||||
|
if(!file){
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
const d = await file.getFile()
|
||||||
|
if(!d){
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
const buf = await d.arrayBuffer()
|
||||||
|
const text = new TextDecoder().decode(buf)
|
||||||
|
return JSON.parse(text)
|
||||||
|
} catch (error) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function setColdStorageItem(key:string, value:any) {
|
||||||
|
if(isTauri){
|
||||||
|
try {
|
||||||
|
if(!(await exists('./coldstorage'))){
|
||||||
|
await mkdir('./coldstorage', { recursive: true })
|
||||||
|
}
|
||||||
|
const text = JSON.stringify(value)
|
||||||
|
await writeFile('./coldstorage/'+key+'.json', new TextEncoder().encode(text))
|
||||||
|
} catch (error) {
|
||||||
|
console.error(error)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else{
|
||||||
|
//use opfs
|
||||||
|
try {
|
||||||
|
const opfs = await navigator.storage.getDirectory()
|
||||||
|
const file = await opfs.getFileHandle('coldstorage_' + key+'.json', { create: true })
|
||||||
|
const writable = await file.createWritable()
|
||||||
|
const text = JSON.stringify(value)
|
||||||
|
await writable.write(new TextEncoder().encode(text))
|
||||||
|
await writable.close()
|
||||||
|
} catch (error) {
|
||||||
|
console.error(error)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function removeColdStorageItem(key:string) {
|
||||||
|
if(isTauri){
|
||||||
|
try {
|
||||||
|
await remove('./coldstorage/'+key+'.json')
|
||||||
|
} catch (error) {
|
||||||
|
console.error(error)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else{
|
||||||
|
//use opfs
|
||||||
|
try {
|
||||||
|
const opfs = await navigator.storage.getDirectory()
|
||||||
|
await opfs.removeEntry('coldstorage_' + key+'.json')
|
||||||
|
} catch (error) {
|
||||||
|
console.error(error)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function makeColdData(){
|
||||||
|
|
||||||
|
const currentTime = Date.now()
|
||||||
|
const coldTime = currentTime - 1000 * 60 * 60 * 24 * 30 //30 days before now
|
||||||
|
|
||||||
|
for(let i=0;i<DBState.db.characters.length;i++){
|
||||||
|
for(let j=0;j<DBState.db.characters[i].chats.length;j++){
|
||||||
|
|
||||||
|
const chat = DBState.db.characters[i].chats[j]
|
||||||
|
let greatestTime = 0
|
||||||
|
|
||||||
|
if(chat.message.length < 4){
|
||||||
|
//it is inefficient to store small data
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if(chat.message?.[0]?.data?.startsWith(coldStorageHeader)){
|
||||||
|
//already cold storage
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
for(let k=0;k<chat.message.length;k++){
|
||||||
|
const message = chat.message[k]
|
||||||
|
const time = message.time
|
||||||
|
if(!time){
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if(time > greatestTime){
|
||||||
|
greatestTime = time
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if(greatestTime < coldTime){
|
||||||
|
const id = crypto.randomUUID()
|
||||||
|
await setColdStorageItem(id, chat.message)
|
||||||
|
chat.message = [{
|
||||||
|
time: currentTime,
|
||||||
|
data: coldStorageHeader + id,
|
||||||
|
role: 'char'
|
||||||
|
}]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function preLoadChat(characterIndex:number, chatIndex:number){
|
||||||
|
const chat = DBState.db?.characters?.[characterIndex]?.chats?.[chatIndex]
|
||||||
|
|
||||||
|
if(!chat){
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if(chat.message?.[0]?.data?.startsWith(coldStorageHeader)){
|
||||||
|
//bring back from cold storage
|
||||||
|
const coldDataKey = chat.message[0].data.slice(coldStorageHeader.length)
|
||||||
|
const coldData = await getColdStorageItem(coldDataKey)
|
||||||
|
if(coldData){
|
||||||
|
chat.message = coldData
|
||||||
|
}
|
||||||
|
await setColdStorageItem(coldDataKey + '_accessMeta', {
|
||||||
|
lastAccess: Date.now()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user