Implement lazy loading of chats via lazy portal

This commit is contained in:
kwaroran
2025-04-28 15:38:57 +09:00
parent 39048dd142
commit 05c51f333f
3 changed files with 190 additions and 96 deletions

View File

@@ -29,11 +29,11 @@
import PlaygroundMenu from '../Playground/PlaygroundMenu.svelte'; import PlaygroundMenu from '../Playground/PlaygroundMenu.svelte';
import { ConnectionOpenStore } from 'src/ts/sync/multiuser'; import { ConnectionOpenStore } from 'src/ts/sync/multiuser';
import { coldStorageHeader, preLoadChat } from 'src/ts/process/coldstorage.svelte'; import { coldStorageHeader, preLoadChat } from 'src/ts/process/coldstorage.svelte';
import LazyPortal from '../UI/GUI/LazyPortal.svelte';
let messageInput:string = $state('') let messageInput:string = $state('')
let messageInputTranslate:string = $state('') let messageInputTranslate:string = $state('')
let openMenu = $state(false) let openMenu = $state(false)
let loadPages = $state(30)
let autoMode = $state(false) let autoMode = $state(false)
let rerolls:Message[][] = [] let rerolls:Message[][] = []
let rerollid = -1 let rerollid = -1
@@ -42,6 +42,23 @@
let currentCharacter:character|groupChat = $state(DBState.db.characters[$selectedCharID]) let currentCharacter:character|groupChat = $state(DBState.db.characters[$selectedCharID])
let toggleStickers:boolean = $state(false) let toggleStickers:boolean = $state(false)
let fileInput:string[] = $state([]) let fileInput:string[] = $state([])
let blocks = $state(new Uint8Array(500) )//hacky hacky
let blockEle:HTMLElement[] = []
let root: HTMLElement = $state(null)
for(let i=0;i<500;i++){
blockEle.push(null)
}
$effect(() => {
if(blocks.length-10 < DBState.db.characters[$selectedCharID].chats[DBState.db.characters[$selectedCharID].chatPage].message.length){
blocks = new Uint8Array(blocks.length + 500)
for(let i=0;i<500;i++){
blockEle.push(null)
}
}
})
async function send(){ async function send(){
return sendMain(false) return sendMain(false)
@@ -365,7 +382,6 @@
async function screenShot(){ async function screenShot(){
try { try {
loadPages = Infinity
const html2canvas = await import('html-to-image'); const html2canvas = await import('html-to-image');
const chats = document.querySelectorAll('.default-chat-screen .risu-chat') const chats = document.querySelectorAll('.default-chat-screen .risu-chat')
alertWait("Taking screenShot...") alertWait("Taking screenShot...")
@@ -412,7 +428,6 @@
mergedCanvas.remove(); mergedCanvas.remove();
} }
alertNormal(language.screenshotSaved) alertNormal(language.screenshotSaved)
loadPages = 10
} catch (error) { } catch (error) {
console.error(error) console.error(error)
alertError("Error while taking screenshot") alertError("Error while taking screenshot")
@@ -435,13 +450,7 @@
<PlaygroundMenu /> <PlaygroundMenu />
{/if} {/if}
{:else} {:else}
<div class="h-full w-full flex flex-col-reverse overflow-y-auto relative default-chat-screen" onscroll={(e) => { <div class="h-full w-full flex flex-col-reverse overflow-y-auto relative default-chat-screen" bind:this={root}>
//@ts-ignore
const scrolled = (e.target.scrollHeight - e.target.clientHeight + e.target.scrollTop)
if(scrolled < 100 && DBState.db.characters[$selectedCharID].chats[DBState.db.characters[$selectedCharID].chatPage].message.length > loadPages){
loadPages += 15
}
}}>
<div <div
class="{DBState.db.fixedChatTextarea ? 'sticky pt-2 pb-2 right-0 bottom-0 bg-bgcolor' : 'mt-2 mb-2'} flex items-stretch w-full" class="{DBState.db.fixedChatTextarea ? 'sticky pt-2 pb-2 right-0 bottom-0 bg-bgcolor' : 'mt-2 mb-2'} flex items-stretch w-full"
style="{DBState.db.fixedChatTextarea ? 'z-index:29;' : ''}" style="{DBState.db.fixedChatTextarea ? 'z-index:29;' : ''}"
@@ -649,6 +658,15 @@
)} {send}/> )} {send}/>
{/if} {/if}
{#each blocks as block, i}
<div
class="w-full max-w-full" id={'x-chat-' + (blocks.length - i - 1)}
bind:this={blockEle[blocks.length - i - 1]}
>
</div>
{/each}
{#if DBState.db.characters[$selectedCharID].chats[DBState.db.characters[$selectedCharID].chatPage].message?.[0]?.data?.startsWith(coldStorageHeader) } {#if DBState.db.characters[$selectedCharID].chats[DBState.db.characters[$selectedCharID].chatPage].message?.[0]?.data?.startsWith(coldStorageHeader) }
{#await preLoadChat($selectedCharID, DBState.db.characters[$selectedCharID].chatPage)} {#await preLoadChat($selectedCharID, DBState.db.characters[$selectedCharID].chatPage)}
<div class="w-full flex justify-center text-textcolor2 italic mb-12"> <div class="w-full flex justify-center text-textcolor2 italic mb-12">
@@ -658,103 +676,108 @@
<div></div> <div></div>
{/await} {/await}
{:else} {:else}
{#each messageForm(DBState.db.characters[$selectedCharID].chats[DBState.db.characters[$selectedCharID].chatPage].message, loadPages) as chat, i}
{#if chat.role === 'char'} {#each DBState.db.characters[$selectedCharID].chats[DBState.db.characters[$selectedCharID].chatPage].message as chat, i}
{#if DBState.db.characters[$selectedCharID].type !== 'group'} <LazyPortal root={root} idx={i} target={blockEle[i + 1]}>
<Chat {#if chat.role === 'char'}
idx={chat.index} {#if DBState.db.characters[$selectedCharID].type !== 'group'}
name={DBState.db.characters[$selectedCharID].name} <Chat
message={chat.data} idx={i}
img={getCharImage(DBState.db.characters[$selectedCharID].image, 'css')} name={DBState.db.characters[$selectedCharID].name}
rerollIcon={i === 0} message={chat.data}
onReroll={reroll} img={getCharImage(DBState.db.characters[$selectedCharID].image, 'css')}
unReroll={unReroll} rerollIcon={i === 0}
isLastMemory={DBState.db.characters[$selectedCharID].chats[DBState.db.characters[$selectedCharID].chatPage].lastMemory === (chat.chatId ?? 'none') && DBState.db.showMemoryLimit} onReroll={reroll}
character={createSimpleCharacter(DBState.db.characters[$selectedCharID])} unReroll={unReroll}
largePortrait={DBState.db.characters[$selectedCharID].largePortrait} isLastMemory={DBState.db.characters[$selectedCharID].chats[DBState.db.characters[$selectedCharID].chatPage].lastMemory === (chat.chatId ?? 'none') && DBState.db.showMemoryLimit}
messageGenerationInfo={chat.generationInfo} character={createSimpleCharacter(DBState.db.characters[$selectedCharID])}
/> largePortrait={DBState.db.characters[$selectedCharID].largePortrait}
messageGenerationInfo={chat.generationInfo}
/>
{:else}
<Chat
idx={i}
name={findCharacterbyId(chat.saying).name}
rerollIcon={i === 0}
message={chat.data}
onReroll={reroll}
unReroll={unReroll}
img={getCharImage(findCharacterbyId(chat.saying).image, 'css')}
isLastMemory={DBState.db.characters[$selectedCharID].chats[DBState.db.characters[$selectedCharID].chatPage].lastMemory === (chat.chatId ?? 'none') && DBState.db.showMemoryLimit}
character={chat.saying}
largePortrait={findCharacterbyId(chat.saying).largePortrait}
messageGenerationInfo={chat.generationInfo}
/>
{/if}
{:else} {:else}
<Chat <Chat
idx={chat.index} character={createSimpleCharacter(DBState.db.characters[$selectedCharID])}
name={findCharacterbyId(chat.saying).name} idx={i}
rerollIcon={i === 0} name={chat.name ?? currentUsername}
message={chat.data} message={chat.data}
onReroll={reroll} img={$ConnectionOpenStore ? '' : getCharImage(userIcon, 'css')}
unReroll={unReroll}
img={getCharImage(findCharacterbyId(chat.saying).image, 'css')}
isLastMemory={DBState.db.characters[$selectedCharID].chats[DBState.db.characters[$selectedCharID].chatPage].lastMemory === (chat.chatId ?? 'none') && DBState.db.showMemoryLimit} isLastMemory={DBState.db.characters[$selectedCharID].chats[DBState.db.characters[$selectedCharID].chatPage].lastMemory === (chat.chatId ?? 'none') && DBState.db.showMemoryLimit}
character={chat.saying} largePortrait={userIconProtrait}
largePortrait={findCharacterbyId(chat.saying).largePortrait}
messageGenerationInfo={chat.generationInfo} messageGenerationInfo={chat.generationInfo}
/> />
{/if} {/if}
{:else} </LazyPortal>
<Chat
character={createSimpleCharacter(DBState.db.characters[$selectedCharID])}
idx={chat.index}
name={chat.name ?? currentUsername}
message={chat.data}
img={$ConnectionOpenStore ? '' : getCharImage(userIcon, 'css')}
isLastMemory={DBState.db.characters[$selectedCharID].chats[DBState.db.characters[$selectedCharID].chatPage].lastMemory === (chat.chatId ?? 'none') && DBState.db.showMemoryLimit}
largePortrait={userIconProtrait}
messageGenerationInfo={chat.generationInfo}
/>
{/if}
{/each} {/each}
{#if DBState.db.characters[$selectedCharID].chats[DBState.db.characters[$selectedCharID].chatPage].message.length <= loadPages} <LazyPortal root={root} target={blockEle[0]}>
{#if DBState.db.characters[$selectedCharID].type !== 'group' }
<Chat
character={createSimpleCharacter(DBState.db.characters[$selectedCharID])}
name={DBState.db.characters[$selectedCharID].name}
message={DBState.db.characters[$selectedCharID].chats[DBState.db.characters[$selectedCharID].chatPage].fmIndex === -1 ? DBState.db.characters[$selectedCharID].firstMessage :
DBState.db.characters[$selectedCharID].alternateGreetings[DBState.db.characters[$selectedCharID].chats[DBState.db.characters[$selectedCharID].chatPage].fmIndex]}
img={getCharImage(DBState.db.characters[$selectedCharID].image, 'css')}
idx={-1}
altGreeting={DBState.db.characters[$selectedCharID].alternateGreetings.length > 0}
largePortrait={DBState.db.characters[$selectedCharID].largePortrait}
firstMessage={true}
onReroll={() => {
const cha = DBState.db.characters[$selectedCharID]
const chat = DBState.db.characters[$selectedCharID].chats[DBState.db.characters[$selectedCharID].chatPage]
if(cha.type !== 'group'){
if (chat.fmIndex >= (cha.alternateGreetings.length - 1)){
chat.fmIndex = -1
}
else{
chat.fmIndex += 1
}
}
DBState.db.characters[$selectedCharID].chats[DBState.db.characters[$selectedCharID].chatPage] = chat
}}
unReroll={() => {
const cha = DBState.db.characters[$selectedCharID]
const chat = DBState.db.characters[$selectedCharID].chats[DBState.db.characters[$selectedCharID].chatPage]
if(cha.type !== 'group'){
if (chat.fmIndex === -1){
chat.fmIndex = (cha.alternateGreetings.length - 1)
}
else{
chat.fmIndex -= 1
}
}
DBState.db.characters[$selectedCharID].chats[DBState.db.characters[$selectedCharID].chatPage] = chat
}}
isLastMemory={false}
/> {#if DBState.db.characters[$selectedCharID].type !== 'group' }
{#if !DBState.db.characters[$selectedCharID].removedQuotes && DBState.db.characters[$selectedCharID].creatorNotes.length >= 2} <Chat
<CreatorQuote quote={DBState.db.characters[$selectedCharID].creatorNotes} onRemove={() => { character={createSimpleCharacter(DBState.db.characters[$selectedCharID])}
const cha = DBState.db.characters[$selectedCharID] name={DBState.db.characters[$selectedCharID].name}
if(cha.type !== 'group'){ message={DBState.db.characters[$selectedCharID].chats[DBState.db.characters[$selectedCharID].chatPage].fmIndex === -1 ? DBState.db.characters[$selectedCharID].firstMessage :
cha.removedQuotes = true DBState.db.characters[$selectedCharID].alternateGreetings[DBState.db.characters[$selectedCharID].chats[DBState.db.characters[$selectedCharID].chatPage].fmIndex]}
} img={getCharImage(DBState.db.characters[$selectedCharID].image, 'css')}
DBState.db.characters[$selectedCharID] = cha idx={-1}
}} /> altGreeting={DBState.db.characters[$selectedCharID].alternateGreetings.length > 0}
largePortrait={DBState.db.characters[$selectedCharID].largePortrait}
firstMessage={true}
onReroll={() => {
const cha = DBState.db.characters[$selectedCharID]
const chat = DBState.db.characters[$selectedCharID].chats[DBState.db.characters[$selectedCharID].chatPage]
if(cha.type !== 'group'){
if (chat.fmIndex >= (cha.alternateGreetings.length - 1)){
chat.fmIndex = -1
}
else{
chat.fmIndex += 1
}
}
DBState.db.characters[$selectedCharID].chats[DBState.db.characters[$selectedCharID].chatPage] = chat
}}
unReroll={() => {
const cha = DBState.db.characters[$selectedCharID]
const chat = DBState.db.characters[$selectedCharID].chats[DBState.db.characters[$selectedCharID].chatPage]
if(cha.type !== 'group'){
if (chat.fmIndex === -1){
chat.fmIndex = (cha.alternateGreetings.length - 1)
}
else{
chat.fmIndex -= 1
}
}
DBState.db.characters[$selectedCharID].chats[DBState.db.characters[$selectedCharID].chatPage] = chat
}}
isLastMemory={false}
/>
{#if !DBState.db.characters[$selectedCharID].removedQuotes && DBState.db.characters[$selectedCharID].creatorNotes.length >= 2}
<CreatorQuote quote={DBState.db.characters[$selectedCharID].creatorNotes} onRemove={() => {
const cha = DBState.db.characters[$selectedCharID]
if(cha.type !== 'group'){
cha.removedQuotes = true
}
DBState.db.characters[$selectedCharID] = cha
}} />
{/if}
{/if} {/if}
{/if}
{/if} </LazyPortal>
{/if} {/if}

View File

@@ -0,0 +1,65 @@
<script lang="ts">
import { getAllContexts, mount, onDestroy, onMount, unmount } from "svelte";
//@ts-ignore
import PortalConsumer from "./PortalConsumer.svelte";
import { sleep } from "src/ts/util";
interface Props {
target?: HTMLElement;
children: any;
root?: HTMLElement
idx?: number
}
const { target: target = document.body, children, root, idx }:Props = $props();
const paddingEle = document.createElement('div')
const context = getAllContexts();
let instance;
let seen = $state(false)
onMount(() => {
paddingEle.style.height = '48px';
paddingEle.style.width = '100%'
paddingEle.style.backgroundColor = '#ffffff'
paddingEle.style.color = 'black'
target.appendChild(paddingEle)
sleep(100).then(() => {
const observer = new IntersectionObserver((v) => {
if(v[0].intersectionRatio > 0.5){
seen = true
target.removeChild(paddingEle)
observer.disconnect()
}
}, {
threshold: 0.5,
})
observer.observe(target)
return () => {
if(!seen){
observer.disconnect()
}
}
})
})
$effect(() => {
if(seen){
instance = mount(PortalConsumer, { target, props: { children }, context })
}
return () => {
if(instance){
unmount(instance);
try {
target.removeChild(paddingEle)
} catch (error) {}
}
}
});
</script>

View File

@@ -1708,6 +1708,12 @@ function basicMatcher (p1:string,matcherArg:matcherArg,vars:{[key:string]:string
} }
return total.toString() return total.toString()
} }
case 'fromhex':{
return Number.parseInt(arra[1], 16).toString()
}
case 'tohex':{
return Number.parseInt(arra[1]).toString(16)
}
} }
} }
if(p1.startsWith('random')){ if(p1.startsWith('random')){