feat: add auto-scroll to translations in HypaV3 modal
This commit is contained in:
@@ -7,6 +7,7 @@
|
|||||||
RefreshCw,
|
RefreshCw,
|
||||||
CheckIcon,
|
CheckIcon,
|
||||||
} from "lucide-svelte";
|
} from "lucide-svelte";
|
||||||
|
import { tick } from "svelte";
|
||||||
import TextAreaInput from "../UI/GUI/TextAreaInput.svelte";
|
import TextAreaInput from "../UI/GUI/TextAreaInput.svelte";
|
||||||
import { alertConfirm, showHypaV3Alert } from "../../ts/alert";
|
import { alertConfirm, showHypaV3Alert } from "../../ts/alert";
|
||||||
import { DBState, alertStore, selectedCharID } from "src/ts/stores.svelte";
|
import { DBState, alertStore, selectedCharID } from "src/ts/stores.svelte";
|
||||||
@@ -18,17 +19,20 @@
|
|||||||
interface SummaryUI {
|
interface SummaryUI {
|
||||||
isTranslating: boolean;
|
isTranslating: boolean;
|
||||||
translation: string | null;
|
translation: string | null;
|
||||||
|
translationRef: HTMLDivElement;
|
||||||
isRerolling: boolean;
|
isRerolling: boolean;
|
||||||
rerolledText: string | null;
|
rerolledText: string | null;
|
||||||
isRerolledTranslating: boolean;
|
isRerolledTranslating: boolean;
|
||||||
rerolledTranslation: string | null;
|
rerolledTranslation: string | null;
|
||||||
|
rerolledTranslationRef: HTMLDivElement;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface ExpandedMessageUI {
|
interface ExpandedMessageUI {
|
||||||
summaryIndex: number;
|
summaryIndex: number;
|
||||||
selectedChatMemo: string;
|
selectedChatMemo: string;
|
||||||
isTranslating: boolean;
|
isTranslating: boolean;
|
||||||
translation?: string | null;
|
translation: string | null;
|
||||||
|
translationRef: HTMLDivElement;
|
||||||
}
|
}
|
||||||
|
|
||||||
const hypaV3DataState = $derived(
|
const hypaV3DataState = $derived(
|
||||||
@@ -44,10 +48,12 @@
|
|||||||
summaryUIStates = hypaV3DataState.summaries.map(() => ({
|
summaryUIStates = hypaV3DataState.summaries.map(() => ({
|
||||||
isTranslating: false,
|
isTranslating: false,
|
||||||
translation: null,
|
translation: null,
|
||||||
|
translationRef: null,
|
||||||
isRerolling: false,
|
isRerolling: false,
|
||||||
rerolledText: null,
|
rerolledText: null,
|
||||||
isRerolledTranslating: false,
|
isRerolledTranslating: false,
|
||||||
rerolledTranslation: null,
|
rerolledTranslation: null,
|
||||||
|
rerolledTranslationRef: null,
|
||||||
}));
|
}));
|
||||||
|
|
||||||
expandedMessageUIState = null;
|
expandedMessageUIState = null;
|
||||||
@@ -105,6 +111,18 @@
|
|||||||
summaryUIState.isTranslating = true;
|
summaryUIState.isTranslating = true;
|
||||||
summaryUIState.translation = "Loading...";
|
summaryUIState.translation = "Loading...";
|
||||||
|
|
||||||
|
// Focus on translation element after it's rendered
|
||||||
|
await tick();
|
||||||
|
|
||||||
|
if (summaryUIState.translationRef) {
|
||||||
|
summaryUIState.translationRef.focus();
|
||||||
|
summaryUIState.translationRef.scrollIntoView({
|
||||||
|
behavior: "smooth",
|
||||||
|
block: "nearest",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Translate
|
||||||
const result = await translate(
|
const result = await translate(
|
||||||
hypaV3DataState.summaries[summaryIndex].text,
|
hypaV3DataState.summaries[summaryIndex].text,
|
||||||
regenerate
|
regenerate
|
||||||
@@ -178,37 +196,24 @@
|
|||||||
summaryUIState.isRerolledTranslating = true;
|
summaryUIState.isRerolledTranslating = true;
|
||||||
summaryUIState.rerolledTranslation = "Loading...";
|
summaryUIState.rerolledTranslation = "Loading...";
|
||||||
|
|
||||||
|
// Focus on rerolled translation element after it's rendered
|
||||||
|
await tick();
|
||||||
|
|
||||||
|
if (summaryUIState.rerolledTranslationRef) {
|
||||||
|
summaryUIState.rerolledTranslationRef.focus();
|
||||||
|
summaryUIState.rerolledTranslationRef.scrollIntoView({
|
||||||
|
behavior: "smooth",
|
||||||
|
block: "nearest",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Translate
|
||||||
const result = await translate(summaryUIState.rerolledText, regenerate);
|
const result = await translate(summaryUIState.rerolledText, regenerate);
|
||||||
|
|
||||||
summaryUIState.rerolledTranslation = result;
|
summaryUIState.rerolledTranslation = result;
|
||||||
summaryUIState.isRerolledTranslating = false;
|
summaryUIState.isRerolledTranslating = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
async function toggleTranslateExpandedMessage(
|
|
||||||
regenerate?: boolean
|
|
||||||
): Promise<void> {
|
|
||||||
if (!expandedMessageUIState || expandedMessageUIState.isTranslating) return;
|
|
||||||
|
|
||||||
if (expandedMessageUIState.translation) {
|
|
||||||
expandedMessageUIState.translation = null;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const message = getMessageFromChatMemo(
|
|
||||||
expandedMessageUIState.selectedChatMemo
|
|
||||||
);
|
|
||||||
|
|
||||||
if (!message) return;
|
|
||||||
|
|
||||||
expandedMessageUIState.isTranslating = true;
|
|
||||||
expandedMessageUIState.translation = "Loading...";
|
|
||||||
|
|
||||||
const result = await translate(message.data, regenerate);
|
|
||||||
|
|
||||||
expandedMessageUIState.translation = result;
|
|
||||||
expandedMessageUIState.isTranslating = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
function isMessageExpanded(
|
function isMessageExpanded(
|
||||||
summaryIndex: number,
|
summaryIndex: number,
|
||||||
chatMemo: string | null
|
chatMemo: string | null
|
||||||
@@ -234,9 +239,47 @@
|
|||||||
selectedChatMemo: chatMemo,
|
selectedChatMemo: chatMemo,
|
||||||
isTranslating: false,
|
isTranslating: false,
|
||||||
translation: null,
|
translation: null,
|
||||||
|
translationRef: null,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function toggleTranslateExpandedMessage(
|
||||||
|
regenerate?: boolean
|
||||||
|
): Promise<void> {
|
||||||
|
if (!expandedMessageUIState || expandedMessageUIState.isTranslating) return;
|
||||||
|
|
||||||
|
if (expandedMessageUIState.translation) {
|
||||||
|
expandedMessageUIState.translation = null;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const message = getMessageFromChatMemo(
|
||||||
|
expandedMessageUIState.selectedChatMemo
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!message) return;
|
||||||
|
|
||||||
|
expandedMessageUIState.isTranslating = true;
|
||||||
|
expandedMessageUIState.translation = "Loading...";
|
||||||
|
|
||||||
|
// Focus on translation element after it's rendered
|
||||||
|
await tick();
|
||||||
|
|
||||||
|
if (expandedMessageUIState.translationRef) {
|
||||||
|
expandedMessageUIState.translationRef.focus();
|
||||||
|
expandedMessageUIState.translationRef.scrollIntoView({
|
||||||
|
behavior: "smooth",
|
||||||
|
block: "nearest",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Translate
|
||||||
|
const result = await translate(message.data, regenerate);
|
||||||
|
|
||||||
|
expandedMessageUIState.translation = result;
|
||||||
|
expandedMessageUIState.isTranslating = false;
|
||||||
|
}
|
||||||
|
|
||||||
function getNextMessageToSummarize(): Message | null {
|
function getNextMessageToSummarize(): Message | null {
|
||||||
const char = DBState.db.characters[$selectedCharID];
|
const char = DBState.db.characters[$selectedCharID];
|
||||||
const chat = char.chats[DBState.db.characters[$selectedCharID].chatPage];
|
const chat = char.chats[DBState.db.characters[$selectedCharID].chatPage];
|
||||||
@@ -326,7 +369,7 @@
|
|||||||
params.onAlternativeAction?.();
|
params.onAlternativeAction?.();
|
||||||
} else {
|
} else {
|
||||||
params.onMainAction?.();
|
params.onMainAction?.();
|
||||||
`` }
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
if ("ontouchend" in window) {
|
if ("ontouchend" in window) {
|
||||||
@@ -354,17 +397,21 @@
|
|||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
<!-- Modal backdrop -->
|
||||||
<div class="fixed inset-0 z-50 bg-black/50 p-4">
|
<div class="fixed inset-0 z-50 bg-black/50 p-4">
|
||||||
|
<!-- Modal wrapper -->
|
||||||
<div class="h-full w-full flex justify-center">
|
<div class="h-full w-full flex justify-center">
|
||||||
|
<!-- Modal window -->
|
||||||
<div
|
<div
|
||||||
class="bg-zinc-900 p-6 rounded-lg flex flex-col w-full max-w-3xl {hypaV3DataState
|
class="bg-zinc-900 p-6 rounded-lg flex flex-col w-full max-w-3xl {hypaV3DataState
|
||||||
.summaries.length === 0
|
.summaries.length === 0
|
||||||
? 'max-h-[26rem]'
|
? 'h-fit'
|
||||||
: 'max-h-full'}"
|
: 'max-h-full'}"
|
||||||
>
|
>
|
||||||
<!-- Header -->
|
<!-- Header -->
|
||||||
<div class="flex justify-between items-center w-full mb-4">
|
<div class="flex justify-between items-center w-full mb-4">
|
||||||
<h1 class="text-2xl font-semibold text-zinc-100">HypaV3 Data</h1>
|
<h1 class="text-2xl font-semibold text-zinc-100">HypaV3 Data</h1>
|
||||||
|
<!-- Button Container -->
|
||||||
<div class="flex items-center gap-2">
|
<div class="flex items-center gap-2">
|
||||||
<!-- Reset Button -->
|
<!-- Reset Button -->
|
||||||
<button
|
<button
|
||||||
@@ -405,14 +452,16 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Summaries List -->
|
<!-- Scrollable Container -->
|
||||||
<div class="flex flex-col gap-3 w-full overflow-y-auto">
|
<div class="flex flex-col gap-3 w-full overflow-y-auto">
|
||||||
{#if hypaV3DataState.summaries.length === 0}
|
{#if hypaV3DataState.summaries.length === 0}
|
||||||
<span class="text-textcolor2 text-center p-4">No summaries yet</span>
|
<span class="text-textcolor2 text-center p-4">No summaries yet</span>
|
||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
|
<!-- Summaries List -->
|
||||||
{#each hypaV3DataState.summaries as summary, i}
|
{#each hypaV3DataState.summaries as summary, i}
|
||||||
{#if summaryUIStates[i]}
|
{#if summaryUIStates[i]}
|
||||||
|
<!-- Summary Item -->
|
||||||
<div
|
<div
|
||||||
class="flex flex-col p-4 rounded-lg border border-zinc-700 bg-zinc-800/50"
|
class="flex flex-col p-4 rounded-lg border border-zinc-700 bg-zinc-800/50"
|
||||||
>
|
>
|
||||||
@@ -468,6 +517,8 @@
|
|||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
class="p-2 max-h-48 overflow-y-auto bg-zinc-800 rounded-md whitespace-pre-wrap"
|
class="p-2 max-h-48 overflow-y-auto bg-zinc-800 rounded-md whitespace-pre-wrap"
|
||||||
|
bind:this={summaryUIStates[i].translationRef}
|
||||||
|
tabindex="-1"
|
||||||
>
|
>
|
||||||
{summaryUIStates[i].translation}
|
{summaryUIStates[i].translation}
|
||||||
</div>
|
</div>
|
||||||
@@ -531,6 +582,8 @@
|
|||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
class="p-2 max-h-48 overflow-y-auto bg-zinc-800 rounded-md whitespace-pre-wrap"
|
class="p-2 max-h-48 overflow-y-auto bg-zinc-800 rounded-md whitespace-pre-wrap"
|
||||||
|
bind:this={summaryUIStates[i].rerolledTranslationRef}
|
||||||
|
tabindex="-1"
|
||||||
>
|
>
|
||||||
{summaryUIStates[i].rerolledTranslation}
|
{summaryUIStates[i].rerolledTranslation}
|
||||||
</div>
|
</div>
|
||||||
@@ -575,7 +628,7 @@
|
|||||||
{/each}
|
{/each}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Selected Message Content -->
|
<!-- Selected Message Content (if selected) -->
|
||||||
{#if expandedMessageUIState?.summaryIndex === i}
|
{#if expandedMessageUIState?.summaryIndex === i}
|
||||||
{@const message = getMessageFromChatMemo(
|
{@const message = getMessageFromChatMemo(
|
||||||
expandedMessageUIState.selectedChatMemo
|
expandedMessageUIState.selectedChatMemo
|
||||||
@@ -604,6 +657,8 @@
|
|||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
class="p-2 max-h-48 overflow-y-auto bg-zinc-800 rounded-md whitespace-pre-wrap"
|
class="p-2 max-h-48 overflow-y-auto bg-zinc-800 rounded-md whitespace-pre-wrap"
|
||||||
|
bind:this={expandedMessageUIState.translationRef}
|
||||||
|
tabindex="-1"
|
||||||
>
|
>
|
||||||
{expandedMessageUIState.translation}
|
{expandedMessageUIState.translation}
|
||||||
</div>
|
</div>
|
||||||
@@ -616,13 +671,15 @@
|
|||||||
{/if}
|
{/if}
|
||||||
{/each}
|
{/each}
|
||||||
|
|
||||||
|
<!-- Next message to be summarized -->
|
||||||
{#if true}
|
{#if true}
|
||||||
<!-- Next message to summarize -->
|
|
||||||
{@const nextMessage = getNextMessageToSummarize()}
|
{@const nextMessage = getNextMessageToSummarize()}
|
||||||
{#if nextMessage}
|
{#if nextMessage}
|
||||||
<div class="mt-4">
|
<div class="mt-4">
|
||||||
<span class="text-sm text-textcolor2 mb-2 block">
|
<span class="text-sm text-textcolor2 mb-2 block">
|
||||||
HypaV3 will summarize [{nextMessage.chatId}]
|
HypaV3 will summarize [{nextMessage.chatId
|
||||||
|
? nextMessage.chatId
|
||||||
|
: "no message id"}]
|
||||||
</span>
|
</span>
|
||||||
<div
|
<div
|
||||||
class="p-2 max-h-48 overflow-y-auto bg-zinc-800 rounded-md whitespace-pre-wrap"
|
class="p-2 max-h-48 overflow-y-auto bg-zinc-800 rounded-md whitespace-pre-wrap"
|
||||||
|
|||||||
Reference in New Issue
Block a user