From 82d5b9fb64e993763369f1264d4ce8b6fd0df7fa Mon Sep 17 00:00:00 2001 From: Martti Malmi Date: Sat, 27 Jan 2024 10:19:08 +0200 Subject: [PATCH] note translation sw & lru cache --- .../app/src/Components/Event/Note/Note.tsx | 19 ++++++++++-- .../Components/Event/Note/NoteContextMenu.tsx | 31 ++++++++++++------- .../src/Components/Event/Note/NoteHeader.tsx | 6 ++-- .../src/Components/Event/Note/NoteText.tsx | 4 +-- .../Components/Event/Note/TranslationInfo.tsx | 2 +- packages/app/src/service-worker.ts | 20 ++++++++++-- 6 files changed, 61 insertions(+), 21 deletions(-) diff --git a/packages/app/src/Components/Event/Note/Note.tsx b/packages/app/src/Components/Event/Note/Note.tsx index f28fa09a..08857282 100644 --- a/packages/app/src/Components/Event/Note/Note.tsx +++ b/packages/app/src/Components/Event/Note/Note.tsx @@ -4,6 +4,7 @@ import React, { useCallback, useState } from "react"; import { useInView } from "react-intersection-observer"; import { FormattedMessage } from "react-intl"; import { useNavigate } from "react-router-dom"; +import { LRUCache } from "typescript-lru-cache"; import NoteHeader from "@/Components/Event/Note/NoteHeader"; import { NoteText } from "@/Components/Event/Note/NoteText"; @@ -30,6 +31,7 @@ const defaultOptions = { }; const canRenderAsTextNote = [EventKind.TextNote, EventKind.Polls]; +const translationCache = new LRUCache({ maxSize: 300 }); export function Note(props: NoteProps) { const { data: ev, highlight, options: opt, ignoreModeration = false, className, waitUntilInView } = props; @@ -37,7 +39,14 @@ export function Note(props: NoteProps) { const { isEventMuted } = useModeration(); const { ref, inView } = useInView({ triggerOnce: true, rootMargin: "2000px" }); const [showTranslation, setShowTranslation] = useState(true); - const [translated, setTranslated] = useState(); + const [translated, setTranslated] = useState(translationCache.get(ev.id)); + const cachedSetTranslated = useCallback( + (translation: NoteTranslation) => { + translationCache.set(ev.id, translation); + setTranslated(translation); + }, + [ev.id], + ); const optionsMerged = { ...defaultOptions, ...opt }; const goToEvent = useGoToEvent(props, optionsMerged); @@ -50,7 +59,13 @@ export function Note(props: NoteProps) { if (waitUntilInView && !inView) return null; return ( <> - {optionsMerged.showHeader && } + {optionsMerged.showHeader && ( + + )}
goToEvent(e, ev)}> {translated && } diff --git a/packages/app/src/Components/Event/Note/NoteContextMenu.tsx b/packages/app/src/Components/Event/Note/NoteContextMenu.tsx index a8262f34..de4c7a76 100644 --- a/packages/app/src/Components/Event/Note/NoteContextMenu.tsx +++ b/packages/app/src/Components/Event/Note/NoteContextMenu.tsx @@ -18,6 +18,7 @@ export interface NoteTranslation { text: string; fromLanguage: string; confidence: number; + skipped?: boolean; } interface NosteContextMenuProps { @@ -60,6 +61,7 @@ export function NoteContextMenu({ ev, ...props }: NosteContextMenuProps) { } async function translate() { + if (!props.onTranslated) return; const api = new SnortApi(); const targetLang = lang.split("-")[0].toUpperCase(); const result = await api.translate({ @@ -67,18 +69,23 @@ export function NoteContextMenu({ ev, ...props }: NosteContextMenuProps) { target_lang: targetLang, }); - if ("translations" in result) { - if ( - typeof props.onTranslated === "function" && - result.translations.length > 0 && - targetLang != result.translations[0].detected_source_language - ) { - props.onTranslated({ - text: result.translations[0].text, - fromLanguage: langNames.of(result.translations[0].detected_source_language), - confidence: 1, - } as NoteTranslation); - } + if ( + "translations" in result && + result.translations.length > 0 && + targetLang != result.translations[0].detected_source_language + ) { + props.onTranslated({ + text: result.translations[0].text, + fromLanguage: langNames.of(result.translations[0].detected_source_language), + confidence: 1, + } as NoteTranslation); + } else { + props.onTranslated({ + text: "", + fromLanguage: "", + confidence: 0, + skipped: true, + }); } } diff --git a/packages/app/src/Components/Event/Note/NoteHeader.tsx b/packages/app/src/Components/Event/Note/NoteHeader.tsx index 0a20c963..789e805c 100644 --- a/packages/app/src/Components/Event/Note/NoteHeader.tsx +++ b/packages/app/src/Components/Event/Note/NoteHeader.tsx @@ -17,7 +17,7 @@ import { setBookmarked, setPinned } from "@/Utils/Login"; export default function NoteHeader(props: { ev: TaggedNostrEvent; options: NotePropsOptions; - setTranslated: (t: NoteTranslation) => void; + setTranslated?: (t: NoteTranslation) => void; context?: React.ReactNode; }) { const [showReactions, setShowReactions] = useState(false); @@ -49,6 +49,8 @@ export default function NoteHeader(props: { } } + const onTranslated = setTranslated ? (t: NoteTranslation) => setTranslated(t) : undefined; + return (
{}} - onTranslated={t => setTranslated(t)} + onTranslated={onTranslated} setShowReactions={setShowReactions} /> )} diff --git a/packages/app/src/Components/Event/Note/NoteText.tsx b/packages/app/src/Components/Event/Note/NoteText.tsx index 7b0c190a..d4652bda 100644 --- a/packages/app/src/Components/Event/Note/NoteText.tsx +++ b/packages/app/src/Components/Event/Note/NoteText.tsx @@ -15,8 +15,8 @@ export const NoteText = function InnerContent( const { data: ev, options, translated, showTranslation } = props; const appData = useLogin(s => s.appData); const [showMore, setShowMore] = useState(false); - const body = translated && showTranslation ? translated.text : ev?.content ?? ""; - const id = translated && showTranslation ? `${ev.id}-translated` : ev.id; + const body = translated && !translated.skipped && showTranslation ? translated.text : ev?.content ?? ""; + const id = translated && !translated.skipped && showTranslation ? `${ev.id}-translated` : ev.id; const shouldTruncate = options?.truncate && body.length > TEXT_TRUNCATE_LENGTH; const ToggleShowMore = () => ( diff --git a/packages/app/src/Components/Event/Note/TranslationInfo.tsx b/packages/app/src/Components/Event/Note/TranslationInfo.tsx index 988efc01..62b034fe 100644 --- a/packages/app/src/Components/Event/Note/TranslationInfo.tsx +++ b/packages/app/src/Components/Event/Note/TranslationInfo.tsx @@ -23,7 +23,7 @@ export function TranslationInfo({ translated, setShowTranslation }: TranslationI ); - } else if (translated) { + } else if (translated && !translated.skipped) { return (

diff --git a/packages/app/src/service-worker.ts b/packages/app/src/service-worker.ts index ca2d1b9b..a877f87e 100644 --- a/packages/app/src/service-worker.ts +++ b/packages/app/src/service-worker.ts @@ -89,9 +89,25 @@ registerRoute( registerRoute( ({ url }) => url.origin === "https://api.snort.social" && url.pathname.startsWith("/api/v1/preview"), - new StaleWhileRevalidate({ + new CacheFirst({ cacheName: "preview-cache", - plugins: [new ExpirationPlugin({ maxAgeSeconds: 24 * 60 * 60 })], + plugins: [ + new ExpirationPlugin({ maxAgeSeconds: 24 * 60 * 60 }), + new CacheableResponsePlugin({ statuses: [0, 200] }), + ], + }), +); + +registerRoute( + ({ url }) => url.origin === "https://api.snort.social" && url.pathname.startsWith("/api/v1/translate"), + new CacheFirst({ + cacheName: "translate-cache", + plugins: [ + new ExpirationPlugin({ maxEntries: 1000 }), + new CacheableResponsePlugin({ + statuses: [0, 200, 204], + }), + ], }), );