feat: add dual-action translation (cached/regenerate) in HypaV3 modal

Implements shift+click (desktop) and double tap (mobile) for regenerating translations while maintaining regular click/tap for cached translations.
This commit is contained in:
Bo26fhmC5M
2025-01-15 13:45:34 +09:00
parent f397b6ef1a
commit 3e234dcb15
2 changed files with 96 additions and 14 deletions

View File

@@ -61,7 +61,10 @@
expandedMessage: null, expandedMessage: null,
}); });
async function toggleTranslate(summary: ExtendedSummary): Promise<void> { async function toggleTranslate(
summary: ExtendedSummary,
regenerate?: boolean
): Promise<void> {
if (summary.state.isTranslating) return; if (summary.state.isTranslating) return;
if (summary.state.translation) { if (summary.state.translation) {
@@ -72,7 +75,7 @@
summary.state.isTranslating = true; summary.state.isTranslating = true;
summary.state.translation = "Loading..."; summary.state.translation = "Loading...";
const result = await translate(summary.text); const result = await translate(summary.text, regenerate);
summary.state.translation = result; summary.state.translation = result;
summary.state.isTranslating = false; summary.state.isTranslating = false;
@@ -144,7 +147,8 @@
} }
async function toggleTranslateRerolled( async function toggleTranslateRerolled(
summary: ExtendedSummary summary: ExtendedSummary,
regenerate?: boolean
): Promise<void> { ): Promise<void> {
if (summary.state.isRerolledTranslating) return; if (summary.state.isRerolledTranslating) return;
@@ -158,13 +162,15 @@
summary.state.isRerolledTranslating = true; summary.state.isRerolledTranslating = true;
summary.state.rerolledTranslation = "Loading..."; summary.state.rerolledTranslation = "Loading...";
const result = await translate(summary.state.rerolledText); const result = await translate(summary.state.rerolledText, regenerate);
summary.state.rerolledTranslation = result; summary.state.rerolledTranslation = result;
summary.state.isRerolledTranslating = false; summary.state.isRerolledTranslating = false;
} }
async function toggleTranslateExpandedMessage(): Promise<void> { async function toggleTranslateExpandedMessage(
regenerate?: boolean
): Promise<void> {
if (!modalState.expandedMessage || modalState.expandedMessage.isTranslating) if (!modalState.expandedMessage || modalState.expandedMessage.isTranslating)
return; return;
@@ -180,7 +186,7 @@
modalState.expandedMessage.isTranslating = true; modalState.expandedMessage.isTranslating = true;
modalState.expandedMessage.translation = "Loading..."; modalState.expandedMessage.translation = "Loading...";
const result = await translate(messageData.data); const result = await translate(messageData.data, regenerate);
modalState.expandedMessage.translation = result; modalState.expandedMessage.translation = result;
modalState.expandedMessage.isTranslating = false; modalState.expandedMessage.isTranslating = false;
@@ -235,13 +241,78 @@
}; };
} }
async function translate(text) { async function translate(
text: string,
regenerate?: boolean
): Promise<string> {
try { try {
return await translateHTML(text, false, "", -1); return await translateHTML(text, false, "", -1, regenerate);
} catch (error) { } catch (error) {
return `Translation failed: ${error}`; return `Translation failed: ${error}`;
} }
} }
type DualActionParams = {
onMainAction?: () => void;
onAlternativeAction?: () => void;
};
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;
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;
// Delayed single tap execution
state.tapTimeout = setTimeout(() => {
if (state.lastTap === currentTime) {
// If no double tap occurred
params.onMainAction?.();
}
}, DOUBLE_TAP_DELAY);
}
} else {
// Desktop environment
if ((event as MouseEvent).shiftKey) {
params.onAlternativeAction?.();
} else {
params.onMainAction?.();
}
}
}
node.addEventListener("click", handleInteraction);
node.addEventListener("touchend", handleInteraction);
return {
destroy() {
node.removeEventListener("click", handleInteraction);
node.removeEventListener("touchend", handleInteraction);
clearTimeout(state.tapTimeout); // Cleanup timeout
},
update(newParams: DualActionParams) {
params = newParams;
},
};
}
</script> </script>
<div class="fixed inset-0 z-50 bg-black/50 p-4"> <div class="fixed inset-0 z-50 bg-black/50 p-4">
@@ -310,7 +381,10 @@
<!-- Translate Button --> <!-- Translate Button -->
<button <button
class="p-2 text-zinc-400 hover:text-zinc-200 transition-colors" class="p-2 text-zinc-400 hover:text-zinc-200 transition-colors"
onclick={async () => await toggleTranslate(summary)} use:handleDualAction={{
onMainAction: () => toggleTranslate(summary, false),
onAlternativeAction: () => toggleTranslate(summary, true),
}}
> >
<LanguagesIcon size={16} /> <LanguagesIcon size={16} />
</button> </button>
@@ -367,8 +441,12 @@
<!-- Translate Rerolled Button --> <!-- Translate Rerolled Button -->
<button <button
class="p-2 text-zinc-400 hover:text-zinc-200 transition-colors" class="p-2 text-zinc-400 hover:text-zinc-200 transition-colors"
onclick={async () => use:handleDualAction={{
await toggleTranslateRerolled(summary)} onMainAction: () =>
toggleTranslateRerolled(summary, false),
onAlternativeAction: () =>
toggleTranslateRerolled(summary, true),
}}
> >
<LanguagesIcon size={16} /> <LanguagesIcon size={16} />
</button> </button>
@@ -427,7 +505,11 @@
<!-- Translate Message Button --> <!-- Translate Message Button -->
<button <button
class="p-2 text-zinc-400 hover:text-zinc-200 transition-colors" class="p-2 text-zinc-400 hover:text-zinc-200 transition-colors"
onclick={async () => await toggleTranslateExpandedMessage()} use:handleDualAction={{
onMainAction: () => toggleTranslateExpandedMessage(false),
onAlternativeAction: () =>
toggleTranslateExpandedMessage(true),
}}
> >
<LanguagesIcon size={16} /> <LanguagesIcon size={16} />
</button> </button>

View File

@@ -854,7 +854,7 @@ class HypaProcesserEx extends HypaProcesser {
summaryChunkVectors: SummaryChunkVector[] = []; summaryChunkVectors: SummaryChunkVector[] = [];
// Calculate dot product similarity between two vectors // Calculate dot product similarity between two vectors
similarity(a: VectorArray, b: VectorArray) { similarity(a: VectorArray, b: VectorArray): number {
let dot = 0; let dot = 0;
for (let i = 0; i < a.length; i++) { for (let i = 0; i < a.length; i++) {
@@ -864,7 +864,7 @@ class HypaProcesserEx extends HypaProcesser {
return dot; return dot;
} }
async addSummaryChunks(chunks: SummaryChunk[]) { async addSummaryChunks(chunks: SummaryChunk[]): Promise<void> {
// Maintain the superclass's caching structure by adding texts // Maintain the superclass's caching structure by adding texts
const texts = chunks.map((chunk) => chunk.text); const texts = chunks.map((chunk) => chunk.text);