From 97967a552637f0d2fb372c707118138dcfc24ad1 Mon Sep 17 00:00:00 2001 From: Stephen Zhou <38493346+hyoban@users.noreply.github.com> Date: Tue, 26 Nov 2024 23:06:55 +0800 Subject: [PATCH 01/14] feat: manual action for ai summary --- apps/renderer/src/atoms/ai-summary.ts | 11 +++++++++++ apps/renderer/src/hooks/biz/useEntryActions.tsx | 4 ++++ apps/renderer/src/hooks/biz/useNavigateEntry.ts | 2 ++ .../src/modules/command/commands/entry.tsx | 16 ++++++++++++++++ apps/renderer/src/modules/command/commands/id.ts | 1 + .../src/modules/command/commands/types.ts | 6 ++++++ .../src/modules/entry-content/index.desktop.tsx | 7 +++++-- icons/mgc/magic_2_cute_fi.svg | 1 + 8 files changed, 46 insertions(+), 2 deletions(-) create mode 100644 apps/renderer/src/atoms/ai-summary.ts create mode 100644 icons/mgc/magic_2_cute_fi.svg diff --git a/apps/renderer/src/atoms/ai-summary.ts b/apps/renderer/src/atoms/ai-summary.ts new file mode 100644 index 0000000000..a1a6f485ae --- /dev/null +++ b/apps/renderer/src/atoms/ai-summary.ts @@ -0,0 +1,11 @@ +import { atom } from "jotai" + +import { createAtomHooks } from "~/lib/jotai" + +export const [, , useShowAISummary, , getShowAISummary, setShowAISummary] = createAtomHooks( + atom(false), +) + +export const toggleShowAISummary = () => setShowAISummary(!getShowAISummary()) +export const enableShowAISummary = () => setShowAISummary(true) +export const disableShowAISummary = () => setShowAISummary(false) diff --git a/apps/renderer/src/hooks/biz/useEntryActions.tsx b/apps/renderer/src/hooks/biz/useEntryActions.tsx index af2f6ff8b8..4ee1b21ee5 100644 --- a/apps/renderer/src/hooks/biz/useEntryActions.tsx +++ b/apps/renderer/src/hooks/biz/useEntryActions.tsx @@ -150,6 +150,10 @@ export const useEntryActions = ({ entryId, view }: { entryId: string; view?: Fee onClick: runCmdFn(COMMAND_ID.entry.viewSourceContent, [{ entryId }]), hide: isMobile() || isShowSourceContent || !entry?.entries.url, }, + { + id: COMMAND_ID.entry.showAISummary, + onClick: runCmdFn(COMMAND_ID.entry.showAISummary, []), + }, { id: COMMAND_ID.entry.viewEntryContent, onClick: runCmdFn(COMMAND_ID.entry.viewEntryContent, []), diff --git a/apps/renderer/src/hooks/biz/useNavigateEntry.ts b/apps/renderer/src/hooks/biz/useNavigateEntry.ts index c8f5092211..05ca3603c3 100644 --- a/apps/renderer/src/hooks/biz/useNavigateEntry.ts +++ b/apps/renderer/src/hooks/biz/useNavigateEntry.ts @@ -5,6 +5,7 @@ import { FeedViewType } from "@follow/constants" import { isUndefined } from "es-toolkit/compat" import { useCallback } from "react" +import { disableShowAISummary } from "~/atoms/ai-summary" import { setSidebarActiveView } from "~/atoms/sidebar" import { resetShowSourceContent } from "~/atoms/source-content" import { @@ -69,6 +70,7 @@ export const navigateEntry = (options: NavigateEntryOptions) => { setSidebarActiveView(view) } resetShowSourceContent() + disableShowAISummary() const finalView = nextSearchParams.get("view") diff --git a/apps/renderer/src/modules/command/commands/entry.tsx b/apps/renderer/src/modules/command/commands/entry.tsx index a7d96ef624..4ea7783c3b 100644 --- a/apps/renderer/src/modules/command/commands/entry.tsx +++ b/apps/renderer/src/modules/command/commands/entry.tsx @@ -5,6 +5,7 @@ import { useMutation } from "@tanstack/react-query" import { useTranslation } from "react-i18next" import { toast } from "sonner" +import { toggleShowAISummary, useShowAISummary } from "~/atoms/ai-summary" import { setShowSourceContent, useSourceContentModal } from "~/atoms/source-content" import { navigateEntry } from "~/hooks/biz/useNavigateEntry" import { getRouteParams } from "~/hooks/biz/useRouteParams" @@ -86,6 +87,7 @@ export const useRegisterEntryCommands = () => { const openTipModal = useTipModal() const read = useRead() const unread = useUnread() + const showAISummary = useShowAISummary() useRegisterFollowCommand([ { @@ -291,4 +293,18 @@ export const useRegisterEntryCommands = () => { }, }, ]) + + useRegisterFollowCommand( + [ + { + id: COMMAND_ID.entry.showAISummary, + label: "Show AI Summary", + icon: , + run: () => { + toggleShowAISummary() + }, + }, + ], + { deps: [showAISummary] }, + ) } diff --git a/apps/renderer/src/modules/command/commands/id.ts b/apps/renderer/src/modules/command/commands/id.ts index 2af9f98e2e..91d304f928 100644 --- a/apps/renderer/src/modules/command/commands/id.ts +++ b/apps/renderer/src/modules/command/commands/id.ts @@ -12,6 +12,7 @@ export const COMMAND_ID = { share: "entry:share", read: "entry:read", unread: "entry:unread", + showAISummary: "entry:show-ai-summary", }, integration: { saveToEagle: "integration:save-to-eagle", diff --git a/apps/renderer/src/modules/command/commands/types.ts b/apps/renderer/src/modules/command/commands/types.ts index 244be39bcf..786c4a6064 100644 --- a/apps/renderer/src/modules/command/commands/types.ts +++ b/apps/renderer/src/modules/command/commands/types.ts @@ -63,6 +63,11 @@ export type UnReadCommand = Command<{ fn: ({ entryId }) => void }> +export type ShowAISummaryCommand = Command<{ + id: typeof COMMAND_ID.entry.showAISummary + fn: () => void +}> + export type EntryCommand = | TipCommand | StarCommand @@ -76,6 +81,7 @@ export type EntryCommand = | ShareCommand | ReadCommand | UnReadCommand + | ShowAISummaryCommand // Integration commands diff --git a/apps/renderer/src/modules/entry-content/index.desktop.tsx b/apps/renderer/src/modules/entry-content/index.desktop.tsx index 549202d945..ca42a2788b 100644 --- a/apps/renderer/src/modules/entry-content/index.desktop.tsx +++ b/apps/renderer/src/modules/entry-content/index.desktop.tsx @@ -10,6 +10,7 @@ import { ErrorBoundary } from "@sentry/react" import { useEffect, useMemo, useRef } from "react" import { useTranslation } from "react-i18next" +import { useShowAISummary } from "~/atoms/ai-summary" import { useEntryIsInReadability } from "~/atoms/readability" import { useUISettingKey } from "~/atoms/settings/ui" import { ShadowDOM } from "~/components/common/ShadowDOM" @@ -67,13 +68,15 @@ export const EntryContent: Component = ({ }, ) + const showAISummary = useShowAISummary() || !!entry?.settings?.summary + const summary = useAuthQuery( Queries.ai.summary({ entryId, language: entry?.settings?.translation, }), { - enabled: !!entry?.settings?.summary, + enabled: showAISummary, refetchOnMount: false, refetchOnWindowFocus: false, meta: { @@ -192,7 +195,7 @@ export const EntryContent: Component = ({
- {(summary.isLoading || summary.data) && ( + {(summary.isLoading || summary.data) && showAISummary && (
diff --git a/icons/mgc/magic_2_cute_fi.svg b/icons/mgc/magic_2_cute_fi.svg new file mode 100644 index 0000000000..d49113081f --- /dev/null +++ b/icons/mgc/magic_2_cute_fi.svg @@ -0,0 +1 @@ + \ No newline at end of file From cc05cb8e731d9902221d4233ee357467a261b8fe Mon Sep 17 00:00:00 2001 From: Stephen Zhou <38493346+hyoban@users.noreply.github.com> Date: Tue, 26 Nov 2024 23:26:39 +0800 Subject: [PATCH 02/14] hide when enable from action --- apps/renderer/src/hooks/biz/useEntryActions.tsx | 1 + 1 file changed, 1 insertion(+) diff --git a/apps/renderer/src/hooks/biz/useEntryActions.tsx b/apps/renderer/src/hooks/biz/useEntryActions.tsx index 4ee1b21ee5..14b0f4b868 100644 --- a/apps/renderer/src/hooks/biz/useEntryActions.tsx +++ b/apps/renderer/src/hooks/biz/useEntryActions.tsx @@ -153,6 +153,7 @@ export const useEntryActions = ({ entryId, view }: { entryId: string; view?: Fee { id: COMMAND_ID.entry.showAISummary, onClick: runCmdFn(COMMAND_ID.entry.showAISummary, []), + hide: !!entry?.settings?.summary, }, { id: COMMAND_ID.entry.viewEntryContent, From faf76c68e0268c2b5c9cbb64af1a73d330e42c66 Mon Sep 17 00:00:00 2001 From: Stephen Zhou <38493346+hyoban@users.noreply.github.com> Date: Tue, 26 Nov 2024 23:35:05 +0800 Subject: [PATCH 03/14] spilt ai summary component --- .../modules/entry-content/index.desktop.tsx | 36 +----------------- .../modules/entry-content/index.mobile.tsx | 33 +--------------- .../modules/entry-content/index.shared.tsx | 38 +++++++++++++++++++ 3 files changed, 42 insertions(+), 65 deletions(-) diff --git a/apps/renderer/src/modules/entry-content/index.desktop.tsx b/apps/renderer/src/modules/entry-content/index.desktop.tsx index ca42a2788b..39c3e85bf6 100644 --- a/apps/renderer/src/modules/entry-content/index.desktop.tsx +++ b/apps/renderer/src/modules/entry-content/index.desktop.tsx @@ -1,5 +1,4 @@ import { MemoedDangerousHTMLStyle } from "@follow/components/common/MemoedDangerousHTMLStyle.js" -import { AutoResizeHeight } from "@follow/components/ui/auto-resize-height/index.jsx" import { ScrollArea } from "@follow/components/ui/scroll-area/index.js" import { useTitle } from "@follow/hooks" import type { FeedModel, InboxModel } from "@follow/models/types" @@ -8,9 +7,7 @@ import { clearSelection, stopPropagation } from "@follow/utils/dom" import { cn } from "@follow/utils/utils" import { ErrorBoundary } from "@sentry/react" import { useEffect, useMemo, useRef } from "react" -import { useTranslation } from "react-i18next" -import { useShowAISummary } from "~/atoms/ai-summary" import { useEntryIsInReadability } from "~/atoms/readability" import { useUISettingKey } from "~/atoms/settings/ui" import { ShadowDOM } from "~/components/common/ShadowDOM" @@ -34,12 +31,12 @@ import { EntryHeader } from "./header" import { useFocusEntryContainerSubscriptions } from "./hooks" import type { EntryContentProps } from "./index.shared" import { + AISummary, ContainerToc, NoContent, ReadabilityAutoToggleEffect, ReadabilityContent, RenderError, - SummaryLoadingSkeleton, TitleMetaHandler, ViewSourceContentAutoToggleEffect, } from "./index.shared" @@ -52,8 +49,6 @@ export const EntryContent: Component = ({ compact, classNames, }) => { - const { t } = useTranslation() - const entry = useEntry(entryId) useTitle(entry?.entries.title) @@ -68,23 +63,6 @@ export const EntryContent: Component = ({ }, ) - const showAISummary = useShowAISummary() || !!entry?.settings?.summary - - const summary = useAuthQuery( - Queries.ai.summary({ - entryId, - language: entry?.settings?.translation, - }), - { - enabled: showAISummary, - refetchOnMount: false, - refetchOnWindowFocus: false, - meta: { - persist: true, - }, - }, - ) - const readerFontFamily = useUISettingKey("readerFontFamily") const view = useRouteParamsSelector((route) => route.view) @@ -195,17 +173,7 @@ export const EntryContent: Component = ({
- {(summary.isLoading || summary.data) && showAISummary && ( -
-
- - {t("entry_content.ai_summary")} -
- - {summary.isLoading ? SummaryLoadingSkeleton : summary.data} - -
- )} + {!isInReadabilityMode ? ( diff --git a/apps/renderer/src/modules/entry-content/index.mobile.tsx b/apps/renderer/src/modules/entry-content/index.mobile.tsx index 9a61542f71..b05bbbaebd 100644 --- a/apps/renderer/src/modules/entry-content/index.mobile.tsx +++ b/apps/renderer/src/modules/entry-content/index.mobile.tsx @@ -1,4 +1,3 @@ -import { AutoResizeHeight } from "@follow/components/ui/auto-resize-height/index.jsx" import { ScrollElementContext } from "@follow/components/ui/scroll-area/ctx.js" import { useTitle } from "@follow/hooks" import type { FeedModel, InboxModel } from "@follow/models/types" @@ -6,7 +5,6 @@ import { stopPropagation } from "@follow/utils/dom" import { cn } from "@follow/utils/utils" import { ErrorBoundary } from "@sentry/react" import { useMemo, useState } from "react" -import { useTranslation } from "react-i18next" import { useAudioPlayerAtomSelector } from "~/atoms/player" import { useUISettingKey } from "~/atoms/settings/ui" @@ -27,7 +25,7 @@ import { EntryReadHistory } from "./components/EntryReadHistory" import { EntryTitle } from "./components/EntryTitle" import { SupportCreator } from "./components/SupportCreator" import { EntryHeader } from "./header" -import { NoContent, RenderError, SummaryLoadingSkeleton, TitleMetaHandler } from "./index.shared" +import { AISummary, NoContent, RenderError, TitleMetaHandler } from "./index.shared" import { EntryContentLoading } from "./loading" export interface EntryContentClassNames { @@ -40,8 +38,6 @@ export const EntryContent: Component<{ compact?: boolean classNames?: EntryContentClassNames }> = ({ entryId, noMedia, compact, classNames }) => { - const { t } = useTranslation() - const entry = useEntry(entryId) useTitle(entry?.entries.title) @@ -57,21 +53,6 @@ export const EntryContent: Component<{ }, ) - const summary = useAuthQuery( - Queries.ai.summary({ - entryId, - language: entry?.settings?.translation, - }), - { - enabled: !!entry?.settings?.summary, - refetchOnMount: false, - refetchOnWindowFocus: false, - meta: { - persist: true, - }, - }, - ) - const view = useRouteParamsSelector((route) => route.view) const mediaInfo = useMemo( @@ -170,17 +151,7 @@ export const EntryContent: Component<{
- {(summary.isLoading || summary.data) && ( -
-
- - {t("entry_content.ai_summary")} -
- - {summary.isLoading ? SummaryLoadingSkeleton : summary.data} - -
- )} + { ) }) + +export function AISummary({ entryId }: { entryId: string }) { + const { t } = useTranslation() + const entry = useEntry(entryId) + const showAISummary = useShowAISummary() || !!entry?.settings?.summary + const summary = useAuthQuery( + Queries.ai.summary({ + entryId, + language: entry?.settings?.translation, + }), + { + enabled: showAISummary, + refetchOnMount: false, + refetchOnWindowFocus: false, + meta: { + persist: true, + }, + }, + ) + return ( + (summary.isLoading || summary.data) && + showAISummary && ( +
+
+ + {t("entry_content.ai_summary")} +
+ + {summary.isLoading ? SummaryLoadingSkeleton : summary.data} + +
+ ) + ) +} From 0b6b0bac1d47a3b5d780ad2ee29339bc847006f6 Mon Sep 17 00:00:00 2001 From: Stephen Zhou <38493346+hyoban@users.noreply.github.com> Date: Tue, 26 Nov 2024 23:39:56 +0800 Subject: [PATCH 04/14] update --- .../modules/entry-content/index.shared.tsx | 24 ++++++++++--------- 1 file changed, 13 insertions(+), 11 deletions(-) diff --git a/apps/renderer/src/modules/entry-content/index.shared.tsx b/apps/renderer/src/modules/entry-content/index.shared.tsx index 843d6e30a7..4bdc50f29e 100644 --- a/apps/renderer/src/modules/entry-content/index.shared.tsx +++ b/apps/renderer/src/modules/entry-content/index.shared.tsx @@ -250,18 +250,20 @@ export function AISummary({ entryId }: { entryId: string }) { }, }, ) + + if (!showAISummary || (!summary.isLoading && !summary.data)) { + return null + } + return ( - (summary.isLoading || summary.data) && - showAISummary && ( -
-
- - {t("entry_content.ai_summary")} -
- - {summary.isLoading ? SummaryLoadingSkeleton : summary.data} - +
+
+ + {t("entry_content.ai_summary")}
- ) + + {summary.isLoading ? SummaryLoadingSkeleton : summary.data} + +
) } From b966751a0bdd4a910d11ccfc6ecbdc0c36ef14fd Mon Sep 17 00:00:00 2001 From: Stephen Zhou <38493346+hyoban@users.noreply.github.com> Date: Wed, 27 Nov 2024 11:33:29 +0800 Subject: [PATCH 05/14] manual translation --- apps/renderer/src/atoms/ai-translation.ts | 10 ++ apps/renderer/src/atoms/settings/general.ts | 2 + .../src/components/ui/markdown/HTML.tsx | 11 +- .../src/hooks/biz/useEntryActions.tsx | 11 +- apps/renderer/src/lib/immersive-translate.ts | 28 ++++- apps/renderer/src/lib/translate.ts | 17 ++- .../src/modules/command/commands/entry.tsx | 18 +++ .../src/modules/command/commands/id.ts | 1 + .../src/modules/command/commands/types.ts | 6 + .../src/modules/entry-column/translation.tsx | 119 +++++++++--------- .../entry-content/components/EntryTitle.tsx | 11 +- .../modules/entry-content/index.desktop.tsx | 15 ++- .../modules/entry-content/index.mobile.tsx | 3 + .../src/modules/settings/tabs/general.tsx | 31 +++++ locales/settings/en.json | 1 + packages/shared/src/interface/settings.ts | 1 + 16 files changed, 207 insertions(+), 78 deletions(-) create mode 100644 apps/renderer/src/atoms/ai-translation.ts diff --git a/apps/renderer/src/atoms/ai-translation.ts b/apps/renderer/src/atoms/ai-translation.ts new file mode 100644 index 0000000000..c08e5a8502 --- /dev/null +++ b/apps/renderer/src/atoms/ai-translation.ts @@ -0,0 +1,10 @@ +import { atom } from "jotai" + +import { createAtomHooks } from "~/lib/jotai" + +export const [, , useShowAITranslation, , getShowAITranslation, setShowAITranslation] = + createAtomHooks(atom(false)) + +export const toggleShowAITranslation = () => setShowAITranslation(!getShowAITranslation()) +export const enableShowAITranslation = () => setShowAITranslation(true) +export const disableShowAITranslation = () => setShowAITranslation(false) diff --git a/apps/renderer/src/atoms/settings/general.ts b/apps/renderer/src/atoms/settings/general.ts index c6021f948e..66323ee14f 100644 --- a/apps/renderer/src/atoms/settings/general.ts +++ b/apps/renderer/src/atoms/settings/general.ts @@ -7,6 +7,8 @@ const createDefaultSettings = (): GeneralSettings => ({ // App appLaunchOnStartup: false, language: "en", + translationLanguage: "zh-CN", + // mobile app startupScreen: "timeline", // Data control diff --git a/apps/renderer/src/components/ui/markdown/HTML.tsx b/apps/renderer/src/components/ui/markdown/HTML.tsx index c6f45f6da9..d189fa3fdc 100644 --- a/apps/renderer/src/components/ui/markdown/HTML.tsx +++ b/apps/renderer/src/components/ui/markdown/HTML.tsx @@ -1,7 +1,8 @@ import { MemoedDangerousHTMLStyle } from "@follow/components/common/MemoedDangerousHTMLStyle.js" import katexStyle from "katex/dist/katex.min.css?raw" -import { createElement, Fragment, memo, useEffect, useMemo, useRef, useState } from "react" +import { createElement, Fragment, memo, useEffect, useMemo, useState } from "react" +import { useShowAITranslation } from "~/atoms/ai-translation" import { ENTRY_CONTENT_RENDER_CONTAINER_ID } from "~/constants/dom" import { parseHtml } from "~/lib/parse-html" import { useWrappedElementSize } from "~/providers/wrapped-element-provider" @@ -53,15 +54,15 @@ const HTMLImpl = (props: HTMLProp const [refElement, setRefElement] = useState(null) - const onceRef = useRef(false) + const showAITranslation = useShowAITranslation() + useEffect(() => { - if (onceRef.current || !refElement) { + if (!refElement) { return } translate?.(refElement) - onceRef.current = true - }, [translate, refElement]) + }, [translate, refElement, showAITranslation]) const markdownElement = useMemo( () => diff --git a/apps/renderer/src/hooks/biz/useEntryActions.tsx b/apps/renderer/src/hooks/biz/useEntryActions.tsx index 14b0f4b868..42d675bff6 100644 --- a/apps/renderer/src/hooks/biz/useEntryActions.tsx +++ b/apps/renderer/src/hooks/biz/useEntryActions.tsx @@ -1,5 +1,5 @@ import { isMobile } from "@follow/components/hooks/useMobile.js" -import type { FeedViewType } from "@follow/constants" +import { FeedViewType } from "@follow/constants" import type { ReactNode } from "react" import { useCallback, useMemo } from "react" @@ -155,6 +155,15 @@ export const useEntryActions = ({ entryId, view }: { entryId: string; view?: Fee onClick: runCmdFn(COMMAND_ID.entry.showAISummary, []), hide: !!entry?.settings?.summary, }, + { + id: COMMAND_ID.entry.showAITranslation, + onClick: runCmdFn(COMMAND_ID.entry.showAITranslation, []), + hide: + !!entry?.settings?.translation || + ([FeedViewType.SocialMedia, FeedViewType.Videos] as (number | undefined)[]).includes( + entry?.view, + ), + }, { id: COMMAND_ID.entry.viewEntryContent, onClick: runCmdFn(COMMAND_ID.entry.viewEntryContent, []), diff --git a/apps/renderer/src/lib/immersive-translate.ts b/apps/renderer/src/lib/immersive-translate.ts index 29c53da8d6..b212a6ae49 100644 --- a/apps/renderer/src/lib/immersive-translate.ts +++ b/apps/renderer/src/lib/immersive-translate.ts @@ -1,3 +1,4 @@ +import type { SupportedLanguages } from "@follow/models/types" import { franc } from "franc-min" import type { FlatEntryModel } from "~/store/entry" @@ -22,15 +23,32 @@ export function immersiveTranslate({ html, entry, cache, + targetLanguage, }: { html?: HTMLElement entry: FlatEntryModel cache?: { get: (key: string) => string | undefined set: (key: string, value: string) => void + clear: () => void } + targetLanguage?: SupportedLanguages }) { - if (!html || !entry.settings?.translation) { + const translation = entry.settings?.translation ?? targetLanguage + + if (!html) { + return + } + + const immersiveTranslateMark = html.querySelectorAll("[data-immersive-translate-mark=true]") + if (immersiveTranslateMark) { + for (const mark of immersiveTranslateMark) { + mark.remove() + } + cache?.clear() + } + + if (!translation) { return } @@ -42,7 +60,7 @@ export function immersiveTranslate({ translate({ entry, - language: entry.settings?.translation, + language: translation, part: textNode.textContent, extraFields: ["content"], }).then((transformed) => { @@ -70,7 +88,7 @@ export function immersiveTranslate({ for (const tag of tags) { const sourceLanguage = franc(tag.textContent ?? "") - if (sourceLanguage === LanguageMap[entry.settings?.translation].code) { + if (sourceLanguage === LanguageMap[translation].code) { return } @@ -78,6 +96,7 @@ export function immersiveTranslate({ tag.dataset.childCount = children.filter((child) => child.textContent).length.toString() const fontTag = document.createElement("font") + fontTag.dataset["immersiveTranslateMark"] = "true" if (children.length > 0) { fontTag.style.display = "none" @@ -121,7 +140,7 @@ export function immersiveTranslate({ translate({ entry, - language: entry.settings?.translation, + language: translation, part: textContent, extraFields: ["content"], }).then((transformed) => { @@ -140,6 +159,7 @@ export function immersiveTranslate({ } const parentFontTag = document.createElement("font") + parentFontTag.dataset["immersiveTranslateMark"] = "true" parentFontTag.append(document.createElement("br")) parentFontTag.append(fontTag) diff --git a/apps/renderer/src/lib/translate.ts b/apps/renderer/src/lib/translate.ts index 23306dd776..2736c925ac 100644 --- a/apps/renderer/src/lib/translate.ts +++ b/apps/renderer/src/lib/translate.ts @@ -8,19 +8,29 @@ import { apiClient } from "./api-fetch" export const LanguageMap: Record< SupportedLanguages, { + label: string + value: string code: string } > = { en: { + value: "en", + label: "English", code: "eng", }, ja: { + value: "ja", + label: "Japanese", code: "jpn", }, "zh-CN": { + value: "zh-CN", + label: "Simplified Chinese", code: "cmn", }, "zh-TW": { + value: "zh-TW", + label: "Traditional Chinese(Taiwan)", code: "cmn", }, } @@ -40,18 +50,17 @@ export async function translate({ if (!language) { return null } - let fields = - entry.settings?.translation && view !== undefined ? views[view!].translation.split(",") : [] + let fields = language && view !== undefined ? views[view!].translation.split(",") : [] if (extraFields) { fields = [...fields, ...extraFields] } const { franc } = await import("franc-min") fields = fields.filter((field) => { - if (entry.settings?.translation && entry.entries[field]) { + if (language && entry.entries[field]) { const sourceLanguage = franc(entry.entries[field]) - if (sourceLanguage === LanguageMap[entry.settings?.translation].code) { + if (sourceLanguage === LanguageMap[language].code) { return false } else { return true diff --git a/apps/renderer/src/modules/command/commands/entry.tsx b/apps/renderer/src/modules/command/commands/entry.tsx index 4ea7783c3b..ec0424b39e 100644 --- a/apps/renderer/src/modules/command/commands/entry.tsx +++ b/apps/renderer/src/modules/command/commands/entry.tsx @@ -6,6 +6,7 @@ import { useTranslation } from "react-i18next" import { toast } from "sonner" import { toggleShowAISummary, useShowAISummary } from "~/atoms/ai-summary" +import { toggleShowAITranslation, useShowAITranslation } from "~/atoms/ai-translation" import { setShowSourceContent, useSourceContentModal } from "~/atoms/source-content" import { navigateEntry } from "~/hooks/biz/useNavigateEntry" import { getRouteParams } from "~/hooks/biz/useRouteParams" @@ -88,6 +89,7 @@ export const useRegisterEntryCommands = () => { const read = useRead() const unread = useUnread() const showAISummary = useShowAISummary() + const showAITranslation = useShowAITranslation() useRegisterFollowCommand([ { @@ -307,4 +309,20 @@ export const useRegisterEntryCommands = () => { ], { deps: [showAISummary] }, ) + + useRegisterFollowCommand( + [ + { + id: COMMAND_ID.entry.showAITranslation, + label: "Show AI Translation", + icon: ( + + ), + run: () => { + toggleShowAITranslation() + }, + }, + ], + { deps: [showAITranslation] }, + ) } diff --git a/apps/renderer/src/modules/command/commands/id.ts b/apps/renderer/src/modules/command/commands/id.ts index 91d304f928..ae2bf975c2 100644 --- a/apps/renderer/src/modules/command/commands/id.ts +++ b/apps/renderer/src/modules/command/commands/id.ts @@ -13,6 +13,7 @@ export const COMMAND_ID = { read: "entry:read", unread: "entry:unread", showAISummary: "entry:show-ai-summary", + showAITranslation: "entry:show-ai-translation", }, integration: { saveToEagle: "integration:save-to-eagle", diff --git a/apps/renderer/src/modules/command/commands/types.ts b/apps/renderer/src/modules/command/commands/types.ts index 786c4a6064..9f629dc0f4 100644 --- a/apps/renderer/src/modules/command/commands/types.ts +++ b/apps/renderer/src/modules/command/commands/types.ts @@ -68,6 +68,11 @@ export type ShowAISummaryCommand = Command<{ fn: () => void }> +export type ShowAITranslationCommand = Command<{ + id: typeof COMMAND_ID.entry.showAITranslation + fn: () => void +}> + export type EntryCommand = | TipCommand | StarCommand @@ -82,6 +87,7 @@ export type EntryCommand = | ReadCommand | UnReadCommand | ShowAISummaryCommand + | ShowAITranslationCommand // Integration commands diff --git a/apps/renderer/src/modules/entry-column/translation.tsx b/apps/renderer/src/modules/entry-column/translation.tsx index fd0aa42534..a6ea34ca1e 100644 --- a/apps/renderer/src/modules/entry-column/translation.tsx +++ b/apps/renderer/src/modules/entry-column/translation.tsx @@ -9,12 +9,13 @@ import { HTML } from "~/components/ui/markdown/HTML" export const EntryTranslation: Component<{ source?: string | null target?: string + showTranslation?: boolean side?: "top" | "bottom" useOverlay?: boolean isHTML?: boolean -}> = ({ source, target, className, side, useOverlay, isHTML }) => { +}> = ({ source, target, showTranslation, className, side, useOverlay, isHTML }) => { let nextTarget = target if (source === target) { nextTarget = undefined @@ -26,66 +27,66 @@ export const EntryTranslation: Component<{ return null } - if (!nextTarget && source) { - return isHTML ? ( - - {source} - - ) : ( -
{source}
- ) - } - return ( - - - - {isHTML ? ( - - {nextTarget} - - ) : ( - {nextTarget} - )} - - - - {useOverlay ? ( -
- - {isHTML ? ( - - {source} - - ) : ( - {source} - )} - -
- ) : isHTML ? ( - - {source} + if (nextTarget && showTranslation) { + return ( + + + + {isHTML ? ( + + {nextTarget} ) : ( - {source} + {nextTarget} )} -
-
-
+ + + + {useOverlay ? ( +
+ + {isHTML ? ( + + {source} + + ) : ( + {source} + )} + +
+ ) : isHTML ? ( + + {source} + + ) : ( + {source} + )} +
+
+ + ) + } + return isHTML ? ( + + {source} + + ) : ( +
{source}
) } diff --git a/apps/renderer/src/modules/entry-content/components/EntryTitle.tsx b/apps/renderer/src/modules/entry-content/components/EntryTitle.tsx index 30a311e1b8..2e790614cd 100644 --- a/apps/renderer/src/modules/entry-content/components/EntryTitle.tsx +++ b/apps/renderer/src/modules/entry-content/components/EntryTitle.tsx @@ -1,6 +1,9 @@ +import type { SupportedLanguages } from "@follow/models/types" import { cn } from "@follow/utils/utils" import { useMemo } from "react" +import { useShowAITranslation } from "~/atoms/ai-translation" +import { useGeneralSettingSelector } from "~/atoms/settings/general" import { useWhoami } from "~/atoms/user" import { RelativeTime } from "~/components/ui/datetime" import { useAuthQuery } from "~/hooks/common" @@ -43,14 +46,17 @@ export const EntryTitle = ({ entryId, compact }: EntryLinkProps) => { return href }, [entry?.entries.authorUrl, entry?.entries.url, feed?.siteUrl, feed?.type, inbox]) + const showAITranslation = useShowAITranslation() || !!entry?.settings?.translation + const translationLanguage = useGeneralSettingSelector((s) => s.translationLanguage) + const translation = useAuthQuery( Queries.ai.translation({ entry: entry!, - language: entry?.settings?.translation, + language: entry?.settings?.translation || (translationLanguage as SupportedLanguages), extraFields: ["title"], }), { - enabled: !!entry?.settings?.translation, + enabled: showAITranslation, refetchOnMount: false, refetchOnWindowFocus: false, meta: { @@ -88,6 +94,7 @@ export const EntryTitle = ({ entryId, compact }: EntryLinkProps) => { >
= ({ [entry?.entries.media, data?.entries.media], ) const customCSS = useUISettingKey("customCSS") + const showAITranslation = useShowAITranslation() + const translationLanguage = useGeneralSettingSelector((s) => s.translationLanguage) if (!entry) return null const content = entry?.entries.content ?? data?.entries.content const translate = async (html: HTMLElement | null) => { - if (!html || !entry || !entry.settings?.translation) return + if (!html || !entry) return const fullText = html.textContent ?? "" if (!fullText) return const { franc } = await import("franc-min") + const translation = + entry.settings?.translation ?? (showAITranslation ? translationLanguage : undefined) + const sourceLanguage = franc(fullText) - if (sourceLanguage === LanguageMap[entry.settings?.translation].code) { + if (translation && sourceLanguage === LanguageMap[translation].code) { return } @@ -126,10 +133,12 @@ export const EntryContent: Component = ({ immersiveTranslate({ html, entry, + targetLanguage: translation as SupportedLanguages, cache: { get: (key: string) => getTranslationCache()[key], set: (key: string, value: string) => setTranslationCache({ ...getTranslationCache(), [key]: value }), + clear: () => setTranslationCache({}), }, }) } diff --git a/apps/renderer/src/modules/entry-content/index.mobile.tsx b/apps/renderer/src/modules/entry-content/index.mobile.tsx index b05bbbaebd..578364e506 100644 --- a/apps/renderer/src/modules/entry-content/index.mobile.tsx +++ b/apps/renderer/src/modules/entry-content/index.mobile.tsx @@ -100,6 +100,9 @@ export const EntryContent: Component<{ get: (key: string) => getTranslationCache()[key], set: (key: string, value: string) => setTranslationCache({ ...getTranslationCache(), [key]: value }), + clear() { + setTranslationCache({}) + }, }, }) } diff --git a/apps/renderer/src/modules/settings/tabs/general.tsx b/apps/renderer/src/modules/settings/tabs/general.tsx index 2853f5d77b..000dc5929a 100644 --- a/apps/renderer/src/modules/settings/tabs/general.tsx +++ b/apps/renderer/src/modules/settings/tabs/general.tsx @@ -29,6 +29,7 @@ import { useProxyValue, useSetProxy } from "~/hooks/biz/useProxySetting" import { useMinimizeToTrayValue, useSetMinimizeToTray } from "~/hooks/biz/useTraySetting" import { fallbackLanguage } from "~/i18n" import { tipcClient } from "~/lib/client" +import { LanguageMap } from "~/lib/translate" import { SettingDescription, SettingInput, SettingSwitch } from "../control" import { createSetting } from "../helper/builder" @@ -73,6 +74,7 @@ export const SettingGeneral = () => { IN_ELECTRON && MinimizeToTraySetting, isMobile && StartupScreenSelector, LanguageSelector, + TranslateLanguageSelector, { type: "title", @@ -244,6 +246,35 @@ export const LanguageSelector = ({ ) } +const TranslateLanguageSelector = () => { + const { t } = useTranslation("settings") + const translationLanguage = useGeneralSettingKey("translationLanguage") + + return ( +
+ {t("general.translation_language")} + +
+ ) +} + const NettingSetting = () => { const { t } = useTranslation("settings") const proxyConfig = useProxyValue() diff --git a/locales/settings/en.json b/locales/settings/en.json index 7fc7cac9da..1095bf1c2b 100644 --- a/locales/settings/en.json +++ b/locales/settings/en.json @@ -153,6 +153,7 @@ "general.startup_screen.timeline": "Timeline", "general.startup_screen.title": "Startup Screen", "general.timeline": "Timeline", + "general.translation_language": "Translation Language", "general.unread": "Unread", "general.voices": "Voices", "integration.eagle.enable.description": "Display 'Save media to Eagle' button when available.", diff --git a/packages/shared/src/interface/settings.ts b/packages/shared/src/interface/settings.ts index 20d84d841a..c3e77422be 100644 --- a/packages/shared/src/interface/settings.ts +++ b/packages/shared/src/interface/settings.ts @@ -1,6 +1,7 @@ export interface GeneralSettings { appLaunchOnStartup: boolean language: string + translationLanguage: string startupScreen: "subscription" | "timeline" dataPersist: boolean sendAnonymousData: boolean From c6945723b836d88986840837e3b9d49533610f6a Mon Sep 17 00:00:00 2001 From: Stephen Zhou <38493346+hyoban@users.noreply.github.com> Date: Wed, 27 Nov 2024 16:38:51 +0800 Subject: [PATCH 06/14] use active --- .../src/hooks/biz/useEntryActions.tsx | 21 +++++--- .../src/hooks/biz/useNavigateEntry.ts | 2 + .../src/modules/command/commands/entry.tsx | 50 +++++++------------ 3 files changed, 34 insertions(+), 39 deletions(-) diff --git a/apps/renderer/src/hooks/biz/useEntryActions.tsx b/apps/renderer/src/hooks/biz/useEntryActions.tsx index 42d675bff6..51c1a4dfd1 100644 --- a/apps/renderer/src/hooks/biz/useEntryActions.tsx +++ b/apps/renderer/src/hooks/biz/useEntryActions.tsx @@ -3,6 +3,8 @@ import { FeedViewType } from "@follow/constants" import type { ReactNode } from "react" import { useCallback, useMemo } from "react" +import { useShowAISummary } from "~/atoms/ai-summary" +import { useShowAITranslation } from "~/atoms/ai-translation" import { getReadabilityStatus, ReadabilityStatus, @@ -82,6 +84,9 @@ export const useEntryActions = ({ entryId, view }: { entryId: string; view?: Fee const isInbox = !!inbox const isShowSourceContent = useShowSourceContent() + const isShowAISummary = useShowAISummary() + const isShowAITranslation = useShowAITranslation() + const getCmd = useGetCommand() const runCmdFn = useRunCommandFn() const actionConfigs = useMemo(() => { @@ -150,10 +155,17 @@ export const useEntryActions = ({ entryId, view }: { entryId: string; view?: Fee onClick: runCmdFn(COMMAND_ID.entry.viewSourceContent, [{ entryId }]), hide: isMobile() || isShowSourceContent || !entry?.entries.url, }, + { + id: COMMAND_ID.entry.viewEntryContent, + onClick: runCmdFn(COMMAND_ID.entry.viewEntryContent, []), + hide: !isShowSourceContent, + active: true, + }, { id: COMMAND_ID.entry.showAISummary, onClick: runCmdFn(COMMAND_ID.entry.showAISummary, []), hide: !!entry?.settings?.summary, + active: isShowAISummary, }, { id: COMMAND_ID.entry.showAITranslation, @@ -163,12 +175,7 @@ export const useEntryActions = ({ entryId, view }: { entryId: string; view?: Fee ([FeedViewType.SocialMedia, FeedViewType.Videos] as (number | undefined)[]).includes( entry?.view, ), - }, - { - id: COMMAND_ID.entry.viewEntryContent, - onClick: runCmdFn(COMMAND_ID.entry.viewEntryContent, []), - hide: !isShowSourceContent, - active: true, + active: isShowAITranslation, }, { id: COMMAND_ID.entry.share, @@ -208,6 +215,8 @@ export const useEntryActions = ({ entryId, view }: { entryId: string; view?: Fee getCmd, inList, isInbox, + isShowAISummary, + isShowAITranslation, isShowSourceContent, runCmdFn, view, diff --git a/apps/renderer/src/hooks/biz/useNavigateEntry.ts b/apps/renderer/src/hooks/biz/useNavigateEntry.ts index 05ca3603c3..584aada0a0 100644 --- a/apps/renderer/src/hooks/biz/useNavigateEntry.ts +++ b/apps/renderer/src/hooks/biz/useNavigateEntry.ts @@ -6,6 +6,7 @@ import { isUndefined } from "es-toolkit/compat" import { useCallback } from "react" import { disableShowAISummary } from "~/atoms/ai-summary" +import { disableShowAITranslation } from "~/atoms/ai-translation" import { setSidebarActiveView } from "~/atoms/sidebar" import { resetShowSourceContent } from "~/atoms/source-content" import { @@ -71,6 +72,7 @@ export const navigateEntry = (options: NavigateEntryOptions) => { } resetShowSourceContent() disableShowAISummary() + disableShowAITranslation() const finalView = nextSearchParams.get("view") diff --git a/apps/renderer/src/modules/command/commands/entry.tsx b/apps/renderer/src/modules/command/commands/entry.tsx index ec0424b39e..719908adc3 100644 --- a/apps/renderer/src/modules/command/commands/entry.tsx +++ b/apps/renderer/src/modules/command/commands/entry.tsx @@ -5,8 +5,8 @@ import { useMutation } from "@tanstack/react-query" import { useTranslation } from "react-i18next" import { toast } from "sonner" -import { toggleShowAISummary, useShowAISummary } from "~/atoms/ai-summary" -import { toggleShowAITranslation, useShowAITranslation } from "~/atoms/ai-translation" +import { toggleShowAISummary } from "~/atoms/ai-summary" +import { toggleShowAITranslation } from "~/atoms/ai-translation" import { setShowSourceContent, useSourceContentModal } from "~/atoms/source-content" import { navigateEntry } from "~/hooks/biz/useNavigateEntry" import { getRouteParams } from "~/hooks/biz/useRouteParams" @@ -88,8 +88,6 @@ export const useRegisterEntryCommands = () => { const openTipModal = useTipModal() const read = useRead() const unread = useUnread() - const showAISummary = useShowAISummary() - const showAITranslation = useShowAITranslation() useRegisterFollowCommand([ { @@ -294,35 +292,21 @@ export const useRegisterEntryCommands = () => { unread.mutate({ entryId, feedId: entry.feedId }) }, }, - ]) - - useRegisterFollowCommand( - [ - { - id: COMMAND_ID.entry.showAISummary, - label: "Show AI Summary", - icon: , - run: () => { - toggleShowAISummary() - }, + { + id: COMMAND_ID.entry.showAISummary, + label: "Show AI Summary", + icon: , + run: () => { + toggleShowAISummary() }, - ], - { deps: [showAISummary] }, - ) - - useRegisterFollowCommand( - [ - { - id: COMMAND_ID.entry.showAITranslation, - label: "Show AI Translation", - icon: ( - - ), - run: () => { - toggleShowAITranslation() - }, + }, + { + id: COMMAND_ID.entry.showAITranslation, + label: "Show AI Translation", + icon: , + run: () => { + toggleShowAITranslation() }, - ], - { deps: [showAITranslation] }, - ) + }, + ]) } From 34bedafc3a95aaa637bc26367b9dea1ae79ec085 Mon Sep 17 00:00:00 2001 From: Stephen Zhou <38493346+hyoban@users.noreply.github.com> Date: Wed, 27 Nov 2024 16:44:06 +0800 Subject: [PATCH 07/14] update --- apps/renderer/src/hooks/biz/useEntryActions.tsx | 10 ++++++---- apps/renderer/src/modules/command/commands/entry.tsx | 2 +- icons/mgc/magic_2_cute_fi.svg | 1 - 3 files changed, 7 insertions(+), 6 deletions(-) delete mode 100644 icons/mgc/magic_2_cute_fi.svg diff --git a/apps/renderer/src/hooks/biz/useEntryActions.tsx b/apps/renderer/src/hooks/biz/useEntryActions.tsx index 51c1a4dfd1..5c34a6d1bf 100644 --- a/apps/renderer/src/hooks/biz/useEntryActions.tsx +++ b/apps/renderer/src/hooks/biz/useEntryActions.tsx @@ -164,7 +164,11 @@ export const useEntryActions = ({ entryId, view }: { entryId: string; view?: Fee { id: COMMAND_ID.entry.showAISummary, onClick: runCmdFn(COMMAND_ID.entry.showAISummary, []), - hide: !!entry?.settings?.summary, + hide: + !!entry?.settings?.summary || + ([FeedViewType.SocialMedia, FeedViewType.Videos] as (number | undefined)[]).includes( + entry?.view, + ), active: isShowAISummary, }, { @@ -172,9 +176,7 @@ export const useEntryActions = ({ entryId, view }: { entryId: string; view?: Fee onClick: runCmdFn(COMMAND_ID.entry.showAITranslation, []), hide: !!entry?.settings?.translation || - ([FeedViewType.SocialMedia, FeedViewType.Videos] as (number | undefined)[]).includes( - entry?.view, - ), + ([FeedViewType.Videos] as (number | undefined)[]).includes(entry?.view), active: isShowAITranslation, }, { diff --git a/apps/renderer/src/modules/command/commands/entry.tsx b/apps/renderer/src/modules/command/commands/entry.tsx index 719908adc3..0ad412fea2 100644 --- a/apps/renderer/src/modules/command/commands/entry.tsx +++ b/apps/renderer/src/modules/command/commands/entry.tsx @@ -303,7 +303,7 @@ export const useRegisterEntryCommands = () => { { id: COMMAND_ID.entry.showAITranslation, label: "Show AI Translation", - icon: , + icon: , run: () => { toggleShowAITranslation() }, diff --git a/icons/mgc/magic_2_cute_fi.svg b/icons/mgc/magic_2_cute_fi.svg deleted file mode 100644 index d49113081f..0000000000 --- a/icons/mgc/magic_2_cute_fi.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file From eeb49f9f186d3aca2020fd9dec46d22e116ecd63 Mon Sep 17 00:00:00 2001 From: Stephen Zhou <38493346+hyoban@users.noreply.github.com> Date: Wed, 27 Nov 2024 20:00:22 +0800 Subject: [PATCH 08/14] update --- apps/renderer/src/hooks/biz/useEntryActions.tsx | 4 +++- apps/renderer/src/lib/immersive-translate.ts | 14 +++++++++----- 2 files changed, 12 insertions(+), 6 deletions(-) diff --git a/apps/renderer/src/hooks/biz/useEntryActions.tsx b/apps/renderer/src/hooks/biz/useEntryActions.tsx index 5c34a6d1bf..9f5c519af0 100644 --- a/apps/renderer/src/hooks/biz/useEntryActions.tsx +++ b/apps/renderer/src/hooks/biz/useEntryActions.tsx @@ -176,7 +176,9 @@ export const useEntryActions = ({ entryId, view }: { entryId: string; view?: Fee onClick: runCmdFn(COMMAND_ID.entry.showAITranslation, []), hide: !!entry?.settings?.translation || - ([FeedViewType.Videos] as (number | undefined)[]).includes(entry?.view), + ([FeedViewType.SocialMedia, FeedViewType.Videos] as (number | undefined)[]).includes( + entry?.view, + ), active: isShowAITranslation, }, { diff --git a/apps/renderer/src/lib/immersive-translate.ts b/apps/renderer/src/lib/immersive-translate.ts index b212a6ae49..cfce8687f7 100644 --- a/apps/renderer/src/lib/immersive-translate.ts +++ b/apps/renderer/src/lib/immersive-translate.ts @@ -34,20 +34,19 @@ export function immersiveTranslate({ } targetLanguage?: SupportedLanguages }) { - const translation = entry.settings?.translation ?? targetLanguage - if (!html) { return } const immersiveTranslateMark = html.querySelectorAll("[data-immersive-translate-mark=true]") - if (immersiveTranslateMark) { + if (immersiveTranslateMark.length > 0) { for (const mark of immersiveTranslateMark) { mark.remove() } cache?.clear() } + const translation = entry.settings?.translation ?? targetLanguage if (!translation) { return } @@ -70,8 +69,13 @@ export function immersiveTranslate({ const p = document.createElement("p") p.append(document.createTextNode(textNode.textContent!)) - p.append(document.createElement("br")) - p.append(document.createTextNode(transformed.content)) + + const fontTag = document.createElement("font") + fontTag.dataset["immersiveTranslateMark"] = "true" + fontTag.append(document.createElement("br")) + fontTag.append(document.createTextNode(transformed.content)) + + p.append(fontTag) textNode.replaceWith(p) }) From 96c309e94be6b8ed6eddbf726a941675c3da1e8c Mon Sep 17 00:00:00 2001 From: Stephen Zhou <38493346+hyoban@users.noreply.github.com> Date: Wed, 27 Nov 2024 20:05:05 +0800 Subject: [PATCH 09/14] update --- apps/renderer/src/components/ui/markdown/HTML.tsx | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/apps/renderer/src/components/ui/markdown/HTML.tsx b/apps/renderer/src/components/ui/markdown/HTML.tsx index d189fa3fdc..e4f601ee1d 100644 --- a/apps/renderer/src/components/ui/markdown/HTML.tsx +++ b/apps/renderer/src/components/ui/markdown/HTML.tsx @@ -57,12 +57,8 @@ const HTMLImpl =
(props: HTMLProp const showAITranslation = useShowAITranslation() useEffect(() => { - if (!refElement) { - return - } - translate?.(refElement) - }, [translate, refElement, showAITranslation]) + }, [refElement, showAITranslation, translate]) const markdownElement = useMemo( () => From 94d889bfa6a3114506261fdaae4ee68b08c7e6ae Mon Sep 17 00:00:00 2001 From: Stephen Zhou <38493346+hyoban@users.noreply.github.com> Date: Wed, 27 Nov 2024 20:08:29 +0800 Subject: [PATCH 10/14] update --- apps/renderer/src/lib/immersive-translate.ts | 2 -- apps/renderer/src/modules/entry-content/index.desktop.tsx | 1 - apps/renderer/src/modules/entry-content/index.mobile.tsx | 3 --- apps/renderer/src/modules/settings/tabs/general.tsx | 2 ++ 4 files changed, 2 insertions(+), 6 deletions(-) diff --git a/apps/renderer/src/lib/immersive-translate.ts b/apps/renderer/src/lib/immersive-translate.ts index cfce8687f7..1ad81d8f80 100644 --- a/apps/renderer/src/lib/immersive-translate.ts +++ b/apps/renderer/src/lib/immersive-translate.ts @@ -30,7 +30,6 @@ export function immersiveTranslate({ cache?: { get: (key: string) => string | undefined set: (key: string, value: string) => void - clear: () => void } targetLanguage?: SupportedLanguages }) { @@ -43,7 +42,6 @@ export function immersiveTranslate({ for (const mark of immersiveTranslateMark) { mark.remove() } - cache?.clear() } const translation = entry.settings?.translation ?? targetLanguage diff --git a/apps/renderer/src/modules/entry-content/index.desktop.tsx b/apps/renderer/src/modules/entry-content/index.desktop.tsx index 2c2f4abada..1afd91a63c 100644 --- a/apps/renderer/src/modules/entry-content/index.desktop.tsx +++ b/apps/renderer/src/modules/entry-content/index.desktop.tsx @@ -138,7 +138,6 @@ export const EntryContent: Component = ({ get: (key: string) => getTranslationCache()[key], set: (key: string, value: string) => setTranslationCache({ ...getTranslationCache(), [key]: value }), - clear: () => setTranslationCache({}), }, }) } diff --git a/apps/renderer/src/modules/entry-content/index.mobile.tsx b/apps/renderer/src/modules/entry-content/index.mobile.tsx index 578364e506..b05bbbaebd 100644 --- a/apps/renderer/src/modules/entry-content/index.mobile.tsx +++ b/apps/renderer/src/modules/entry-content/index.mobile.tsx @@ -100,9 +100,6 @@ export const EntryContent: Component<{ get: (key: string) => getTranslationCache()[key], set: (key: string, value: string) => setTranslationCache({ ...getTranslationCache(), [key]: value }), - clear() { - setTranslationCache({}) - }, }, }) } diff --git a/apps/renderer/src/modules/settings/tabs/general.tsx b/apps/renderer/src/modules/settings/tabs/general.tsx index 000dc5929a..2d83fbd77b 100644 --- a/apps/renderer/src/modules/settings/tabs/general.tsx +++ b/apps/renderer/src/modules/settings/tabs/general.tsx @@ -30,6 +30,7 @@ import { useMinimizeToTrayValue, useSetMinimizeToTray } from "~/hooks/biz/useTra import { fallbackLanguage } from "~/i18n" import { tipcClient } from "~/lib/client" import { LanguageMap } from "~/lib/translate" +import { setTranslationCache } from "~/modules/entry-content/atoms" import { SettingDescription, SettingInput, SettingSwitch } from "../control" import { createSetting } from "../helper/builder" @@ -258,6 +259,7 @@ const TranslateLanguageSelector = () => { value={translationLanguage} onValueChange={(value) => { setGeneralSetting("translationLanguage", value) + setTranslationCache({}) }} > From 01cd4b288c1c02d7e86dd5112074660dc10acd91 Mon Sep 17 00:00:00 2001 From: Stephen Zhou <38493346+hyoban@users.noreply.github.com> Date: Wed, 27 Nov 2024 20:10:04 +0800 Subject: [PATCH 11/14] update --- .../src/modules/entry-content/index.mobile.tsx | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/apps/renderer/src/modules/entry-content/index.mobile.tsx b/apps/renderer/src/modules/entry-content/index.mobile.tsx index b05bbbaebd..247d222db6 100644 --- a/apps/renderer/src/modules/entry-content/index.mobile.tsx +++ b/apps/renderer/src/modules/entry-content/index.mobile.tsx @@ -1,12 +1,14 @@ import { ScrollElementContext } from "@follow/components/ui/scroll-area/ctx.js" import { useTitle } from "@follow/hooks" -import type { FeedModel, InboxModel } from "@follow/models/types" +import type { FeedModel, InboxModel, SupportedLanguages } from "@follow/models/types" import { stopPropagation } from "@follow/utils/dom" import { cn } from "@follow/utils/utils" import { ErrorBoundary } from "@sentry/react" import { useMemo, useState } from "react" +import { useShowAITranslation } from "~/atoms/ai-translation" import { useAudioPlayerAtomSelector } from "~/atoms/player" +import { useGeneralSettingSelector } from "~/atoms/settings/general" import { useUISettingKey } from "~/atoms/settings/ui" import { ShadowDOM } from "~/components/common/ShadowDOM" import { useRouteParamsSelector } from "~/hooks/biz/useRouteParams" @@ -76,19 +78,26 @@ export const EntryContent: Component<{ usePreventOverscrollBounce() const [scrollElement, setScrollElement] = useState(null) + + const showAITranslation = useShowAITranslation() + const translationLanguage = useGeneralSettingSelector((s) => s.translationLanguage) + if (!entry) return null const content = entry?.entries.content ?? data?.entries.content const translate = async (html: HTMLElement | null) => { - if (!html || !entry || !entry.settings?.translation) return + if (!html || !entry) return const fullText = html.textContent ?? "" if (!fullText) return const { franc } = await import("franc-min") + const translation = + entry.settings?.translation ?? (showAITranslation ? translationLanguage : undefined) + const sourceLanguage = franc(fullText) - if (sourceLanguage === LanguageMap[entry.settings?.translation].code) { + if (translation && sourceLanguage === LanguageMap[translation].code) { return } @@ -96,6 +105,7 @@ export const EntryContent: Component<{ immersiveTranslate({ html, entry, + targetLanguage: translation as SupportedLanguages, cache: { get: (key: string) => getTranslationCache()[key], set: (key: string, value: string) => From 238fcf5b959b9b899718081a59e29d7e01b94a6f Mon Sep 17 00:00:00 2001 From: Stephen Zhou <38493346+hyoban@users.noreply.github.com> Date: Wed, 27 Nov 2024 20:12:05 +0800 Subject: [PATCH 12/14] update --- apps/renderer/src/lib/immersive-translate.ts | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/apps/renderer/src/lib/immersive-translate.ts b/apps/renderer/src/lib/immersive-translate.ts index 1ad81d8f80..f1be28d339 100644 --- a/apps/renderer/src/lib/immersive-translate.ts +++ b/apps/renderer/src/lib/immersive-translate.ts @@ -37,14 +37,19 @@ export function immersiveTranslate({ return } + const translation = entry.settings?.translation ?? targetLanguage + const immersiveTranslateMark = html.querySelectorAll("[data-immersive-translate-mark=true]") if (immersiveTranslateMark.length > 0) { + if (translation) { + return + } + for (const mark of immersiveTranslateMark) { mark.remove() } } - const translation = entry.settings?.translation ?? targetLanguage if (!translation) { return } From 238a47bb45cb1115301c1444fdd596af0bdf9be6 Mon Sep 17 00:00:00 2001 From: Stephen Zhou <38493346+hyoban@users.noreply.github.com> Date: Wed, 27 Nov 2024 20:46:25 +0800 Subject: [PATCH 13/14] update --- apps/renderer/src/hooks/biz/useEntryActions.tsx | 8 ++++---- apps/renderer/src/modules/command/commands/entry.tsx | 8 ++++---- apps/renderer/src/modules/command/commands/id.ts | 4 ++-- apps/renderer/src/modules/command/commands/types.ts | 12 ++++++------ changelog/next.md | 1 + locales/app/en.json | 2 ++ 6 files changed, 19 insertions(+), 16 deletions(-) diff --git a/apps/renderer/src/hooks/biz/useEntryActions.tsx b/apps/renderer/src/hooks/biz/useEntryActions.tsx index 9f5c519af0..b6435b86c1 100644 --- a/apps/renderer/src/hooks/biz/useEntryActions.tsx +++ b/apps/renderer/src/hooks/biz/useEntryActions.tsx @@ -162,8 +162,8 @@ export const useEntryActions = ({ entryId, view }: { entryId: string; view?: Fee active: true, }, { - id: COMMAND_ID.entry.showAISummary, - onClick: runCmdFn(COMMAND_ID.entry.showAISummary, []), + id: COMMAND_ID.entry.toggleAISummary, + onClick: runCmdFn(COMMAND_ID.entry.toggleAISummary, []), hide: !!entry?.settings?.summary || ([FeedViewType.SocialMedia, FeedViewType.Videos] as (number | undefined)[]).includes( @@ -172,8 +172,8 @@ export const useEntryActions = ({ entryId, view }: { entryId: string; view?: Fee active: isShowAISummary, }, { - id: COMMAND_ID.entry.showAITranslation, - onClick: runCmdFn(COMMAND_ID.entry.showAITranslation, []), + id: COMMAND_ID.entry.toggleAITranslation, + onClick: runCmdFn(COMMAND_ID.entry.toggleAITranslation, []), hide: !!entry?.settings?.translation || ([FeedViewType.SocialMedia, FeedViewType.Videos] as (number | undefined)[]).includes( diff --git a/apps/renderer/src/modules/command/commands/entry.tsx b/apps/renderer/src/modules/command/commands/entry.tsx index 0ad412fea2..3f74f8819c 100644 --- a/apps/renderer/src/modules/command/commands/entry.tsx +++ b/apps/renderer/src/modules/command/commands/entry.tsx @@ -293,16 +293,16 @@ export const useRegisterEntryCommands = () => { }, }, { - id: COMMAND_ID.entry.showAISummary, - label: "Show AI Summary", + id: COMMAND_ID.entry.toggleAISummary, + label: t("entry_actions.toggle_ai_summary"), icon: , run: () => { toggleShowAISummary() }, }, { - id: COMMAND_ID.entry.showAITranslation, - label: "Show AI Translation", + id: COMMAND_ID.entry.toggleAITranslation, + label: t("entry_actions.toggle_ai_translation"), icon: , run: () => { toggleShowAITranslation() diff --git a/apps/renderer/src/modules/command/commands/id.ts b/apps/renderer/src/modules/command/commands/id.ts index ae2bf975c2..92304bd913 100644 --- a/apps/renderer/src/modules/command/commands/id.ts +++ b/apps/renderer/src/modules/command/commands/id.ts @@ -12,8 +12,8 @@ export const COMMAND_ID = { share: "entry:share", read: "entry:read", unread: "entry:unread", - showAISummary: "entry:show-ai-summary", - showAITranslation: "entry:show-ai-translation", + toggleAISummary: "entry:toggle-ai-summary", + toggleAITranslation: "entry:toggle-ai-translation", }, integration: { saveToEagle: "integration:save-to-eagle", diff --git a/apps/renderer/src/modules/command/commands/types.ts b/apps/renderer/src/modules/command/commands/types.ts index 9f629dc0f4..fcb69338c0 100644 --- a/apps/renderer/src/modules/command/commands/types.ts +++ b/apps/renderer/src/modules/command/commands/types.ts @@ -63,13 +63,13 @@ export type UnReadCommand = Command<{ fn: ({ entryId }) => void }> -export type ShowAISummaryCommand = Command<{ - id: typeof COMMAND_ID.entry.showAISummary +export type ToggleAISummaryCommand = Command<{ + id: typeof COMMAND_ID.entry.toggleAISummary fn: () => void }> -export type ShowAITranslationCommand = Command<{ - id: typeof COMMAND_ID.entry.showAITranslation +export type ToggleAITranslationCommand = Command<{ + id: typeof COMMAND_ID.entry.toggleAITranslation fn: () => void }> @@ -86,8 +86,8 @@ export type EntryCommand = | ShareCommand | ReadCommand | UnReadCommand - | ShowAISummaryCommand - | ShowAITranslationCommand + | ToggleAISummaryCommand + | ToggleAITranslationCommand // Integration commands diff --git a/changelog/next.md b/changelog/next.md index f62ec5968d..bc6438699d 100644 --- a/changelog/next.md +++ b/changelog/next.md @@ -3,6 +3,7 @@ ## New Features - Customizable columns for masonry view +- Manually trigger AI summary or translation ![](https://fastly.jsdelivr.net/gh/RSSNext/assets@main/masonry.mp4) diff --git a/locales/app/en.json b/locales/app/en.json index b26430bde8..38a10a80be 100644 --- a/locales/app/en.json +++ b/locales/app/en.json @@ -134,6 +134,8 @@ "entry_actions.star": "Star", "entry_actions.starred": "Starred.", "entry_actions.tip": "Tip", + "entry_actions.toggle_ai_summary": "Toggle AI Summary", + "entry_actions.toggle_ai_translation": "Toggle AI Translation", "entry_actions.unstar": "Unstar", "entry_actions.unstarred": "Unstarred.", "entry_actions.view_source_content": "View Source Content", From c1f5538c7fe8ca09769c3d277f916d238ffe448b Mon Sep 17 00:00:00 2001 From: Stephen Zhou <38493346+hyoban@users.noreply.github.com> Date: Thu, 28 Nov 2024 15:35:33 +0800 Subject: [PATCH 14/14] hide some action --- .../entry-column/layouts/EntryItemWrapper.tsx | 24 ++++++++--- .../modules/entry-content/header.desktop.tsx | 12 +++++- .../modules/entry-content/header.mobile.tsx | 40 ++++++++++++------- 3 files changed, 55 insertions(+), 21 deletions(-) diff --git a/apps/renderer/src/modules/entry-column/layouts/EntryItemWrapper.tsx b/apps/renderer/src/modules/entry-column/layouts/EntryItemWrapper.tsx index 466838d025..687efe9a17 100644 --- a/apps/renderer/src/modules/entry-column/layouts/EntryItemWrapper.tsx +++ b/apps/renderer/src/modules/entry-column/layouts/EntryItemWrapper.tsx @@ -14,6 +14,7 @@ import { useFeedActions } from "~/hooks/biz/useFeedActions" import { useNavigateEntry } from "~/hooks/biz/useNavigateEntry" import { useRouteParamsSelector } from "~/hooks/biz/useRouteParams" import { useContextMenu } from "~/hooks/common/useContextMenu" +import { COMMAND_ID } from "~/modules/command/commands/id" import type { FlatEntryModel } from "~/store/entry" import { entryActions } from "~/store/entry" @@ -77,12 +78,23 @@ export const EntryItemWrapper: FC< setIsContextMenuOpen(true) await showContextMenu( [ - ...actionConfigs.map((item) => ({ - type: "text" as const, - label: item.name, - click: () => item.onClick(), - shortcut: item.shortcut, - })), + ...actionConfigs + .filter( + (item) => + !( + [ + COMMAND_ID.entry.viewSourceContent, + COMMAND_ID.entry.toggleAISummary, + COMMAND_ID.entry.toggleAITranslation, + ] as string[] + ).includes(item.id), + ) + .map((item) => ({ + type: "text" as const, + label: item.name, + click: () => item.onClick(), + shortcut: item.shortcut, + })), { type: "separator" as const, }, diff --git a/apps/renderer/src/modules/entry-content/header.desktop.tsx b/apps/renderer/src/modules/entry-content/header.desktop.tsx index 749384be9a..06b1ecfc77 100644 --- a/apps/renderer/src/modules/entry-content/header.desktop.tsx +++ b/apps/renderer/src/modules/entry-content/header.desktop.tsx @@ -46,7 +46,17 @@ const EntryHeaderActions = ({ entryId, view }: { entryId: string; view?: FeedVie }) return actionConfigs - .filter((config) => config.id !== COMMAND_ID.entry.openInBrowser) + .filter( + (item) => + !( + [ + COMMAND_ID.entry.read, + COMMAND_ID.entry.unread, + COMMAND_ID.entry.copyLink, + COMMAND_ID.entry.openInBrowser, + ] as string[] + ).includes(item.id), + ) .map((config) => { return (
- {actions.map((item) => ( - { - setCtxOpen(false) - item.onClick?.() - }} - key={item.name} - layout={false} - className="flex w-full items-center gap-2 px-4 py-2" - > - {item.icon} - {item.name} - - ))} + {actions + .filter( + (item) => + !( + [ + COMMAND_ID.entry.read, + COMMAND_ID.entry.unread, + COMMAND_ID.entry.copyLink, + ] as string[] + ).includes(item.id), + ) + .map((item) => ( + { + setCtxOpen(false) + item.onClick?.() + }} + key={item.name} + layout={false} + className="flex w-full items-center gap-2 px-4 py-2" + > + {item.icon} + {item.name} + + ))}