Add cold storage functionality and preload chat data

This commit is contained in:
kwaroran
2025-03-10 16:13:40 +09:00
parent 5c2d821445
commit 488ca25d0f
3 changed files with 181 additions and 4 deletions

View File

@@ -20,6 +20,7 @@
import { getModuleToggles } from "src/ts/process/modules";
import { language } from "src/lang";
import Toggles from "./Toggles.svelte";
import { preLoadChat } from "src/ts/process/coldstorage.svelte";
interface Props {
chara: character|groupChat;
@@ -67,7 +68,6 @@
}
})
chara.chatPage = newChats.indexOf(chara.chats[currentChatPage])
chara.chats = newChats
try {
@@ -76,6 +76,10 @@
sorted += 1
await sleep(1)
createStb()
await preLoadChat($selectedCharID, newChats.indexOf(chara.chats[currentChatPage]))
chara.chatPage = newChats.indexOf(chara.chats[currentChatPage])
},
...sortableOptions
}))
@@ -107,14 +111,17 @@
})
chara.chatFolders = newFolders
chara.chatPage = newChats.indexOf(chara.chats[currentChatPage])
chara.chats = newChats
try {
folderStb.destroy()
} catch (e) {}
sorted += 1
await sleep(1)
createStb()
await preLoadChat($selectedCharID, newChats.indexOf(chara.chats[currentChatPage]))
chara.chatPage = newChats.indexOf(chara.chats[currentChatPage])
},
...sortableOptions
})
@@ -244,8 +251,9 @@
<div></div>
{:else}
{#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){
await preLoadChat($selectedCharID, chara.chats.indexOf(chat))
chara.chatPage = chara.chats.indexOf(chat)
$ReloadGUIPointer += 1
}
@@ -267,6 +275,7 @@
const newChat = safeStructuredClone($state.snapshot(chara.chats[chara.chats.indexOf(chat)]))
newChat.name = `Copy of ${newChat.name}`
chara.chats.unshift(newChat)
await preLoadChat($selectedCharID, 0)
chara.chatPage = 0
chara.chats = chara.chats
break
@@ -293,6 +302,7 @@
break
}
case 2:{
await preLoadChat($selectedCharID, chara.chats.indexOf(chat))
chara.chatPage = chara.chats.indexOf(chat)
createMultiuserRoom()
}
@@ -315,6 +325,7 @@
}
}} class="text-textcolor2 hover:text-green-500 mr-1 cursor-pointer" onclick={async (e) => {
e.stopPropagation()
await preLoadChat($selectedCharID, chara.chats.indexOf(chat))
exportChat(chara.chats.indexOf(chat))
}}>
<DownloadIcon size={18}/>
@@ -331,6 +342,7 @@
}
const d = await alertConfirm(`${language.removeConfirm}${chat.name}`)
if(d){
await preLoadChat($selectedCharID, 0)
chara.chatPage = 0
$ReloadGUIPointer += 1
let chats = chara.chats
@@ -352,8 +364,9 @@
<div class="risu-chat flex flex-col">
{#each chara.chats as chat, i}
{#if chat.folderId == null}
<button data-risu-chat-idx={i} onclick={() => {
<button data-risu-chat-idx={i} onclick={async () => {
if(!editMode){
await preLoadChat($selectedCharID, i)
chara.chatPage = i
$ReloadGUIPointer += 1
}
@@ -377,6 +390,7 @@
const newChat = safeStructuredClone($state.snapshot(chara.chats[i]))
newChat.name = `Copy of ${newChat.name}`
chara.chats.unshift(newChat)
await preLoadChat($selectedCharID, 0)
chara.chatPage = 0
chara.chats = chara.chats
break
@@ -404,6 +418,7 @@
break
}
case 2:{
await preLoadChat($selectedCharID, i)
chara.chatPage = i
createMultiuserRoom()
}
@@ -442,6 +457,7 @@
}
const d = await alertConfirm(`${language.removeConfirm}${chat.name}`)
if(d){
await preLoadChat($selectedCharID, 0)
chara.chatPage = 0
$ReloadGUIPointer += 1
let chats = chara.chats

View File

@@ -43,6 +43,7 @@ import { initMobileGesture } from "./hotkey";
import { fetch as TauriHTTPFetch } from '@tauri-apps/plugin-http';
import { moduleUpdate } from "./process/modules";
import type { AccountStorage } from "./storage/accountStorage";
import { makeColdData } from "./process/coldstorage.svelte";
//@ts-ignore
export const isTauri = !!window.__TAURI_INTERNALS__
@@ -675,6 +676,7 @@ export async function loadData() {
loadedStore.set(true)
selectedCharID.set(-1)
startObserveDom()
makeColdData()
saveDb()
moduleUpdate()
if(import.meta.env.VITE_RISU_TOS === 'TRUE'){

View 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()
})
}
}