From 1b2ea2b15b6532ceaff8c31838c7b9e2b2fe7e4d Mon Sep 17 00:00:00 2001 From: Bo26fhmC5M <88071760+Bo26fhmC5M@users.noreply.github.com> Date: Sat, 18 Jan 2025 14:16:50 +0900 Subject: [PATCH 01/15] fix: handle null case for firstMessage in HypaV3 modal --- src/lib/Others/HypaV3Modal.svelte | 53 ++++++++++++++++++------------- 1 file changed, 31 insertions(+), 22 deletions(-) diff --git a/src/lib/Others/HypaV3Modal.svelte b/src/lib/Others/HypaV3Modal.svelte index 3ac09afd..8cf7156c 100644 --- a/src/lib/Others/HypaV3Modal.svelte +++ b/src/lib/Others/HypaV3Modal.svelte @@ -62,21 +62,31 @@ ); } + function getFirstMessage(): string | null { + const char = DBState.db.characters[$selectedCharID]; + const chat = char.chats[DBState.db.characters[$selectedCharID].chatPage]; + + return chat.fmIndex === -1 + ? char.firstMessage + : char.alternateGreetings?.[chat.fmIndex] + ? char.alternateGreetings[chat.fmIndex] + : null; + } + function getMessageFromChatMemo(chatMemo: string | null): Message | null { const char = DBState.db.characters[$selectedCharID]; const chat = char.chats[DBState.db.characters[$selectedCharID].chatPage]; - const firstMessage = - chat.fmIndex === -1 - ? char.firstMessage - : char.alternateGreetings?.[chat.fmIndex ?? 0]; + const firstMessage = getFirstMessage(); - const targetMessage = ( - chatMemo == null - ? { role: "char", data: firstMessage } - : chat.message.find((m) => m.chatId === chatMemo) - ) as Message; + if (!firstMessage) { + return null; + } - return targetMessage; + if (chatMemo == null) { + return { role: "char", data: firstMessage }; + } + + return chat.message.find((m) => m.chatId === chatMemo) || null; } async function toggleTranslate( @@ -108,16 +118,11 @@ const summary = hypaV3DataState.summaries[summaryIndex]; for (const chatMemo of summary.chatMemos) { - if (typeof chatMemo === "string") { - const char = DBState.db.characters[$selectedCharID]; - const chat = - char.chats[DBState.db.characters[$selectedCharID].chatPage]; - - if (!chat.message.find((m) => m.chatId === chatMemo)) { - return false; - } + if (!getMessageFromChatMemo(chatMemo)) { + return false; } } + return true; } @@ -235,11 +240,13 @@ function getNextMessageToSummarize(): Message | null { const char = DBState.db.characters[$selectedCharID]; const chat = char.chats[DBState.db.characters[$selectedCharID].chatPage]; - const firstMessage = - chat.fmIndex === -1 - ? char.firstMessage - : char.alternateGreetings?.[chat.fmIndex ?? 0]; + const firstMessage = getFirstMessage(); + if (!firstMessage) { + return null; + } + + // Summaries exist if (hypaV3DataState.summaries.length > 0) { const lastSummary = hypaV3DataState.summaries.at(-1); const lastMessageIndex = chat.message.findIndex( @@ -255,6 +262,7 @@ } } + // No summaries if (firstMessage?.trim() === "") { if (chat.message.length > 0) { return chat.message[0]; @@ -263,6 +271,7 @@ return null; } + // will summarize first message return { role: "char", chatId: "first message", data: firstMessage }; } From 0e86d15c6258e589a82d4173f858c3166c4b1036 Mon Sep 17 00:00:00 2001 From: Bo26fhmC5M <88071760+Bo26fhmC5M@users.noreply.github.com> Date: Sun, 19 Jan 2025 09:30:54 +0900 Subject: [PATCH 02/15] fix: resolve mobile dual-action translation in HypaV3 modal --- src/lib/Others/HypaV3Modal.svelte | 81 ++++++++++++++++--------------- 1 file changed, 43 insertions(+), 38 deletions(-) diff --git a/src/lib/Others/HypaV3Modal.svelte b/src/lib/Others/HypaV3Modal.svelte index 8cf7156c..a1e1b6f8 100644 --- a/src/lib/Others/HypaV3Modal.svelte +++ b/src/lib/Others/HypaV3Modal.svelte @@ -292,54 +292,59 @@ }; function handleDualAction(node: HTMLElement, params: DualActionParams = {}) { - const state = { - lastTap: 0, - tapTimeout: null as any, - }; - const DOUBLE_TAP_DELAY = 300; - function handleInteraction(event: Event) { - if ("ontouchend" in window) { - // Mobile environment - const currentTime = new Date().getTime(); - const tapLength = currentTime - state.lastTap; + const state = { + lastTap: 0, + tapTimeout: null, + }; - if (tapLength < DOUBLE_TAP_DELAY && tapLength > 0) { - // Double tap detected - event.preventDefault(); - clearTimeout(state.tapTimeout); // Cancel the first tap timeout - params.onAlternativeAction?.(); - state.lastTap = 0; // Reset state - } else { - // First tap - state.lastTap = currentTime; + const handleTouch = (event: TouchEvent) => { + const currentTime = new Date().getTime(); + const tapLength = currentTime - state.lastTap; - // Delayed single tap execution - state.tapTimeout = setTimeout(() => { - if (state.lastTap === currentTime) { - // If no double tap occurred - params.onMainAction?.(); - } - }, DOUBLE_TAP_DELAY); - } + if (tapLength < DOUBLE_TAP_DELAY && tapLength > 0) { + // Double tap detected + event.preventDefault(); + clearTimeout(state.tapTimeout); // Cancel the first tap timeout + params.onAlternativeAction?.(); + state.lastTap = 0; // Reset state } else { - // Desktop environment - if ((event as MouseEvent).shiftKey) { - params.onAlternativeAction?.(); - } else { - params.onMainAction?.(); - } + state.lastTap = currentTime; // First tap + // Delayed single tap execution + state.tapTimeout = setTimeout(() => { + if (state.lastTap === currentTime) { + // If no double tap occurred + params.onMainAction?.(); + } + }, DOUBLE_TAP_DELAY); } - } + }; - node.addEventListener("click", handleInteraction); - node.addEventListener("touchend", handleInteraction); + const handleClick = (event: MouseEvent) => { + if (event.shiftKey) { + params.onAlternativeAction?.(); + } else { + params.onMainAction?.(); +`` } + }; + + if ("ontouchend" in window) { + // Mobile environment + node.addEventListener("touchend", handleTouch); + } else { + // Desktop environment + node.addEventListener("click", handleClick); + } return { destroy() { - node.removeEventListener("click", handleInteraction); - node.removeEventListener("touchend", handleInteraction); + if ("ontouchend" in window) { + node.removeEventListener("touchend", handleTouch); + } else { + node.removeEventListener("click", handleClick); + } + clearTimeout(state.tapTimeout); // Cleanup timeout }, update(newParams: DualActionParams) { From ec2dd091aaa83700baa3a08cbbf9e2e7c86de5aa Mon Sep 17 00:00:00 2001 From: Bo26fhmC5M <88071760+Bo26fhmC5M@users.noreply.github.com> Date: Sun, 19 Jan 2025 11:30:50 +0900 Subject: [PATCH 03/15] feat: add auto-scroll to translations in HypaV3 modal --- src/lib/Others/HypaV3Modal.svelte | 121 ++++++++++++++++++++++-------- 1 file changed, 89 insertions(+), 32 deletions(-) diff --git a/src/lib/Others/HypaV3Modal.svelte b/src/lib/Others/HypaV3Modal.svelte index a1e1b6f8..83d39cba 100644 --- a/src/lib/Others/HypaV3Modal.svelte +++ b/src/lib/Others/HypaV3Modal.svelte @@ -7,6 +7,7 @@ RefreshCw, CheckIcon, } from "lucide-svelte"; + import { tick } from "svelte"; import TextAreaInput from "../UI/GUI/TextAreaInput.svelte"; import { alertConfirm, showHypaV3Alert } from "../../ts/alert"; import { DBState, alertStore, selectedCharID } from "src/ts/stores.svelte"; @@ -18,17 +19,20 @@ interface SummaryUI { isTranslating: boolean; translation: string | null; + translationRef: HTMLDivElement; isRerolling: boolean; rerolledText: string | null; isRerolledTranslating: boolean; rerolledTranslation: string | null; + rerolledTranslationRef: HTMLDivElement; } interface ExpandedMessageUI { summaryIndex: number; selectedChatMemo: string; isTranslating: boolean; - translation?: string | null; + translation: string | null; + translationRef: HTMLDivElement; } const hypaV3DataState = $derived( @@ -44,10 +48,12 @@ summaryUIStates = hypaV3DataState.summaries.map(() => ({ isTranslating: false, translation: null, + translationRef: null, isRerolling: false, rerolledText: null, isRerolledTranslating: false, rerolledTranslation: null, + rerolledTranslationRef: null, })); expandedMessageUIState = null; @@ -105,6 +111,18 @@ summaryUIState.isTranslating = true; 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( hypaV3DataState.summaries[summaryIndex].text, regenerate @@ -178,37 +196,24 @@ summaryUIState.isRerolledTranslating = true; 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); summaryUIState.rerolledTranslation = result; summaryUIState.isRerolledTranslating = false; } - async function toggleTranslateExpandedMessage( - regenerate?: boolean - ): Promise { - 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( summaryIndex: number, chatMemo: string | null @@ -234,9 +239,47 @@ selectedChatMemo: chatMemo, isTranslating: false, translation: null, + translationRef: null, }; } + async function toggleTranslateExpandedMessage( + regenerate?: boolean + ): Promise { + 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 { const char = DBState.db.characters[$selectedCharID]; const chat = char.chats[DBState.db.characters[$selectedCharID].chatPage]; @@ -326,7 +369,7 @@ params.onAlternativeAction?.(); } else { params.onMainAction?.(); -`` } + } }; if ("ontouchend" in window) { @@ -354,17 +397,21 @@ } +
+
+

HypaV3 Data

+
- +
{#if hypaV3DataState.summaries.length === 0} No summaries yet {/if} + {#each hypaV3DataState.summaries as summary, i} {#if summaryUIStates[i]} +
@@ -468,6 +517,8 @@ >
{summaryUIStates[i].translation}
@@ -531,6 +582,8 @@ >
{summaryUIStates[i].rerolledTranslation}
@@ -575,7 +628,7 @@ {/each}
- + {#if expandedMessageUIState?.summaryIndex === i} {@const message = getMessageFromChatMemo( expandedMessageUIState.selectedChatMemo @@ -604,6 +657,8 @@ >
{expandedMessageUIState.translation}
@@ -616,13 +671,15 @@ {/if} {/each} + {#if true} - {@const nextMessage = getNextMessageToSummarize()} {#if nextMessage}
- HypaV3 will summarize [{nextMessage.chatId}] + HypaV3 will summarize [{nextMessage.chatId + ? nextMessage.chatId + : "no message id"}]
Date: Sun, 19 Jan 2025 11:55:09 +0900 Subject: [PATCH 04/15] feat: add settings button to HypaV3 modal header --- src/lib/Others/HypaV3Modal.svelte | 35 +++++++++++++++++++++++++------ 1 file changed, 29 insertions(+), 6 deletions(-) diff --git a/src/lib/Others/HypaV3Modal.svelte b/src/lib/Others/HypaV3Modal.svelte index 83d39cba..eb7ee052 100644 --- a/src/lib/Others/HypaV3Modal.svelte +++ b/src/lib/Others/HypaV3Modal.svelte @@ -1,5 +1,6 @@ -
+
-
+
-
-

HypaV3 Data

- +
+ +

+ HypaV3 Data +

+
-
+
{#if hypaV3DataState.summaries.length === 0} {#if isHypaV2ConversionPossible()}
-
- No summaries yet, but you may convert HypaV2 data to V3. +
+
+ No summaries yet, but you may convert HypaV2 data to V3. +
{:else} - No summaries yet +
+ No summaries yet +
{/if} {/if} @@ -647,12 +650,13 @@ {#if summaryUIStates[i]}
- -
- Summary #{i + 1} -
+ +
+ Summary #{i + 1} + +
@@ -682,12 +686,12 @@ onclick={async () => await toggleReroll(i)} disabled={!isRerollable(i)} > - +
- +
+ +
- + {#if summaryUIStates[i].translation} -
- Translation -
+
+ Translation +
+ +
{/if} - {#if summaryUIStates[i].rerolledText} -
-
- Rerolled Summary + +
+
+ Rerolled Summary
- + + +
+
- -
- {#each summary.chatMemos as chatMemo} - - {/each} -
+ + {#if summaryUIStates[i].rerolledTranslation} +
+
+ Rerolled Translation +
- - {#if expandedMessageUIState?.summaryIndex === i} -
- - {#await getProcessedMessageFromChatMemo(expandedMessageUIState.selectedChatMemo) then expandedMessage} - {#if expandedMessage} - -
- {expandedMessage.role}'s Message -
- -
- {expandedMessage.data} -
- {:else} -
- Message not found -
- {/if} - {:catch error} -
- Error loading expanded message: {error.message} -
- {/await} - - - {#if expandedMessageUIState.translation} -
- Translation -
- {expandedMessageUIState.translation} -
-
- {/if} +
{/if} + {/if} + + +
+
+ Connected Messages ({summary.chatMemos.length}) + +
+ + +
+
+ + +
+ {#each summary.chatMemos as chatMemo} + + {/each} +
+ + {#if expandedMessageUIState?.summaryIndex === i} + +
+ + {#await getProcessedMessageFromChatMemo(expandedMessageUIState.selectedChatMemo) then expandedMessage} + {#if expandedMessage} + +
+ {expandedMessage.role}'s Message +
+ + + + {:else} + Message not found + {/if} + {:catch error} + Error loading expanded message: {error.message} + {/await} +
+ + + {#if expandedMessageUIState.translation} +
+
+ Translation +
+ + +
+ {/if} + {/if}
{/if} {/each} - {#await getProcessedNextSummarizationTarget() then nextMessage} - {#if nextMessage} - {@const chatId = - nextMessage.chatId === "first" - ? "First Message" - : nextMessage.chatId == null - ? "No Message ID" - : nextMessage.chatId} -
- +
+ {#await getProcessedNextSummarizationTarget() then nextMessage} + {#if nextMessage} + {@const chatId = + nextMessage.chatId === "first" + ? "First Message" + : nextMessage.chatId == null + ? "No Message ID" + : nextMessage.chatId} +
HypaV3 will summarize [{chatId}] - -
- {nextMessage.data}
-
- {/if} - {:catch error} -
- Error loading next message: {error.message} -
- {/await} + + + {:else} + WARN: No messages found + {/if} + {:catch error} + Error loading next message: {error.message} + {/await} +
{#if !getFirstMessage()} -
-
- WARN: Selected first message is empty -
+
+ WARN: Selected first message is empty
{/if}
From 9877931d1eb52a47a5e6027d86a4f8f3098a398e Mon Sep 17 00:00:00 2001 From: Bo26fhmC5M <88071760+Bo26fhmC5M@users.noreply.github.com> Date: Tue, 21 Jan 2025 09:17:03 +0900 Subject: [PATCH 12/15] fix: improve contrast in HypaV3 modal --- src/lib/Others/HypaV3Modal.svelte | 21 ++++++++++----------- 1 file changed, 10 insertions(+), 11 deletions(-) diff --git a/src/lib/Others/HypaV3Modal.svelte b/src/lib/Others/HypaV3Modal.svelte index 389a1180..20daf0e5 100644 --- a/src/lib/Others/HypaV3Modal.svelte +++ b/src/lib/Others/HypaV3Modal.svelte @@ -10,7 +10,6 @@ ScissorsLineDashed, CheckIcon, } from "lucide-svelte"; - import TextAreaInput from "../../lib/UI/GUI/TextAreaInput.svelte"; import { alertConfirm, alertNormalWait, @@ -617,7 +616,7 @@ No summaries yet, but you may convert HypaV2 data to V3.