From d3f889a42dc3f0bb1710dfa4ea0b9e3932c44f31 Mon Sep 17 00:00:00 2001 From: Stephen Zhou <38493346+hyoban@users.noreply.github.com> Date: Wed, 11 Jun 2025 12:36:26 +0800 Subject: [PATCH] refactor(desktop): use translation and readability from shared store (#3902) --- .../layer/renderer/src/atoms/readability.ts | 35 -------- .../src/hooks/biz/useEntryActions.tsx | 44 +++------- .../layer/renderer/src/lib/translate.ts | 80 ------------------- .../modules/command/commands/integration.tsx | 5 +- .../entry-column/Items/picture-masonry.tsx | 10 +-- .../modules/entry-column/Items/video-item.tsx | 21 +++-- .../src/modules/entry-column/item.tsx | 16 +++- .../src/modules/entry-column/translation.tsx | 2 +- .../src/modules/entry-column/types.ts | 7 +- .../entry-content/components/EntryTitle.tsx | 9 ++- .../src/modules/entry-content/hooks.tsx | 42 +++++----- .../modules/entry-content/index.shared.tsx | 3 +- apps/desktop/layer/renderer/src/queries/ai.ts | 24 +----- .../layer/renderer/src/store/ai/hook.ts | 53 ------------ packages/internal/store/src/entry/store.ts | 30 +++++-- 15 files changed, 104 insertions(+), 277 deletions(-) delete mode 100644 apps/desktop/layer/renderer/src/store/ai/hook.ts diff --git a/apps/desktop/layer/renderer/src/atoms/readability.ts b/apps/desktop/layer/renderer/src/atoms/readability.ts index 5ebba8eb5..a6fe369d9 100644 --- a/apps/desktop/layer/renderer/src/atoms/readability.ts +++ b/apps/desktop/layer/renderer/src/atoms/readability.ts @@ -1,45 +1,12 @@ -import { getStorageNS } from "@follow/utils/ns" import { atom } from "jotai" -import { atomWithStorage } from "jotai/utils" import { createAtomHooks } from "~/lib/jotai" -type Readability = { - title?: string | null - content?: string | null - textContent?: string | null - length?: number | null - excerpt?: string | null - byline?: string | null - dir?: string | null - siteName?: string | null - lang?: string | null - publishedTime?: string | null -} - const mergeObjectSetter = (setter: (prev: T) => void, getter: () => T) => (value: Partial) => setter({ ...getter(), ...value }) -export const [ - , - , - useReadabilityContent, - , - getReadabilityContent, - __setReadabilityContent, - useReadabilityContentSelector, -] = createAtomHooks( - atomWithStorage>(getStorageNS("readability-content"), {}, undefined, { - getOnInit: true, - }), -) -export const setReadabilityContent = mergeObjectSetter( - __setReadabilityContent, - getReadabilityContent, -) - export enum ReadabilityStatus { INITIAL = 1, WAITING = 2, @@ -77,5 +44,3 @@ export const useEntryInReadabilityStatus = (entryId?: string) => export const isInReadability = (status: ReadabilityStatus) => status !== ReadabilityStatus.INITIAL && !!status -export const useEntryReadabilityContent = (entryId: string) => - useReadabilityContentSelector((map) => map[entryId], [entryId]) diff --git a/apps/desktop/layer/renderer/src/hooks/biz/useEntryActions.tsx b/apps/desktop/layer/renderer/src/hooks/biz/useEntryActions.tsx index ff0008289..14f0936ea 100644 --- a/apps/desktop/layer/renderer/src/hooks/biz/useEntryActions.tsx +++ b/apps/desktop/layer/renderer/src/hooks/biz/useEntryActions.tsx @@ -2,7 +2,9 @@ import { isMobile } from "@follow/components/hooks/useMobile.js" import { FeedViewType, UserRole, views } from "@follow/constants" import { IN_ELECTRON } from "@follow/shared/constants" import { useIsEntryStarred } from "@follow/store/collection/hooks" +import { getEntry } from "@follow/store/entry/getter" import { useEntry } from "@follow/store/entry/hooks" +import { entrySyncServices } from "@follow/store/entry/store" import type { EntryModel } from "@follow/store/entry/types" import { useFeedById } from "@follow/store/feed/hooks" import { useIsInbox } from "@follow/store/inbox/hooks" @@ -13,16 +15,13 @@ import { useShowAISummaryAuto, useShowAISummaryOnce } from "~/atoms/ai-summary" import { useShowAITranslationAuto, useShowAITranslationOnce } from "~/atoms/ai-translation" import { MENU_ITEM_SEPARATOR, MenuItemSeparator, MenuItemText } from "~/atoms/context-menu" import { - getReadabilityContent, getReadabilityStatus, ReadabilityStatus, - setReadabilityContent, setReadabilityStatus, useEntryIsInReadability, } from "~/atoms/readability" import { useShowSourceContent } from "~/atoms/source-content" import { useUserRole, whoami } from "~/atoms/user" -import { apiClient } from "~/lib/api-fetch" import { ipcServices } from "~/lib/client" import { COMMAND_ID } from "~/modules/command/commands/id" import { getCommand, useRunCommandFn } from "~/modules/command/hooks/use-command" @@ -46,39 +45,22 @@ export const toggleEntryReadability = async ({ id, url }: { id: string; url: str [id]: ReadabilityStatus.WAITING, }) try { - let data = getReadabilityContent()[id] + const data = getEntry(id)?.readabilityContent if (!data) { - const result = await apiClient.entries.readability.$get({ query: { id } }) - if (result.data) { - data = result.data - } + await entrySyncServices.fetchEntryReadabilityContent(id, async () => { + const res = await ipcServices?.reader.readability({ url }) + return res?.content + }) } - if (data) { - const status = getReadabilityStatus()[id] - if (status !== ReadabilityStatus.WAITING) return - setReadabilityStatus({ - [id]: ReadabilityStatus.SUCCESS, - }) - setReadabilityContent({ - [id]: data, - }) - } + setReadabilityStatus({ + [id]: ReadabilityStatus.SUCCESS, + }) } catch { - const result = await ipcServices?.reader.readability({ url }) - if (result) { - setReadabilityContent({ - [id]: result, - }) - setReadabilityStatus({ - [id]: ReadabilityStatus.SUCCESS, - }) - } else { - setReadabilityStatus({ - [id]: ReadabilityStatus.FAILURE, - }) - } + setReadabilityStatus({ + [id]: ReadabilityStatus.FAILURE, + }) } } else { setReadabilityStatus({ diff --git a/apps/desktop/layer/renderer/src/lib/translate.ts b/apps/desktop/layer/renderer/src/lib/translate.ts index 51c7a163a..a3ea5d2e6 100644 --- a/apps/desktop/layer/renderer/src/lib/translate.ts +++ b/apps/desktop/layer/renderer/src/lib/translate.ts @@ -1,15 +1,9 @@ import { parseHtml } from "@follow/components/ui/markdown/parse-html.js" -import { views } from "@follow/constants" import type { SupportedActionLanguage } from "@follow/shared" import { ACTION_LANGUAGE_MAP } from "@follow/shared" -import { getEntry } from "@follow/store/entry/getter" import { duplicateIfLengthLessThan } from "@follow/utils/utils" import { franc } from "franc-min" -import { getReadabilityContent } from "~/atoms/readability" - -import { apiClient } from "./api-fetch" - export const checkLanguage = ({ content, language, @@ -32,77 +26,3 @@ export const checkLanguage = ({ return sourceLanguage === code } - -export async function translate({ - entryId, - view, - language, - extraFields, - part, -}: { - entryId?: string | null - view?: number | null - language?: SupportedActionLanguage - extraFields?: string[] - part?: string -}) { - if (!language || !entryId) { - return null - } - let fields = language && typeof view === "number" ? views[view!]!.translation.split(",") : [] - if (extraFields) { - fields = [...fields, ...extraFields] - } - - const readabilityContent = getReadabilityContent()[entryId]?.content - const entries = getEntry(entryId) - fields = fields.filter((field) => { - if (language && field === "readabilityContent") { - if (!readabilityContent) return false - const isLanguageMatch = checkLanguage({ - content: readabilityContent, - language, - }) - return !isLanguageMatch - } - - if (language && entries?.[field]) { - const isLanguageMatch = checkLanguage({ - content: entries[field], - language, - }) - return !isLanguageMatch - } else { - return false - } - }) - - if (fields.length === 0) { - return null - } - - const res = await apiClient.ai.translation.$get({ - query: { - id: entryId, - language, - fields: fields?.join(",") || "title", - part, - }, - }) - - const data: { - description?: string - title?: string - content?: string - readabilityContent?: string - } = {} - - fields.forEach((field) => { - const content = field === "readabilityContent" ? readabilityContent : entries?.[field] - if (content !== res.data?.[field]) { - data[field] = res.data?.[field] - } - }) - - return data -} diff --git a/apps/desktop/layer/renderer/src/modules/command/commands/integration.tsx b/apps/desktop/layer/renderer/src/modules/command/commands/integration.tsx index 44ef5877e..b3ef8a938 100644 --- a/apps/desktop/layer/renderer/src/modules/command/commands/integration.tsx +++ b/apps/desktop/layer/renderer/src/modules/command/commands/integration.tsx @@ -18,7 +18,7 @@ import { ofetch } from "ofetch" import { useTranslation } from "react-i18next" import { toast } from "sonner" -import { getReadabilityContent, getReadabilityStatus, ReadabilityStatus } from "~/atoms/readability" +import { getReadabilityStatus, ReadabilityStatus } from "~/atoms/readability" import { getActionLanguage } from "~/atoms/settings/general" import { getIntegrationSettings, useIntegrationSettingKey } from "~/atoms/settings/integration" import { useRouteParams } from "~/hooks/biz/useRouteParams" @@ -252,8 +252,7 @@ const useRegisterInstapaperCommands = () => { const getEntryContentAsMarkdown = async (entry: EntryModel) => { const isReadabilityReady = getReadabilityStatus()[entry.id] === ReadabilityStatus.SUCCESS - const content = - (isReadabilityReady ? getReadabilityContent()[entry.id]!.content : entry.content) || "" + const content = (isReadabilityReady ? entry.readabilityContent || "" : entry.content) || "" const [toMarkdown, toMdast, gfmTableToMarkdown] = await Promise.all([ import("mdast-util-to-markdown").then((m) => m.toMarkdown), import("hast-util-to-mdast").then((m) => m.toMdast), diff --git a/apps/desktop/layer/renderer/src/modules/entry-column/Items/picture-masonry.tsx b/apps/desktop/layer/renderer/src/modules/entry-column/Items/picture-masonry.tsx index 7b0872c79..425f584c7 100644 --- a/apps/desktop/layer/renderer/src/modules/entry-column/Items/picture-masonry.tsx +++ b/apps/desktop/layer/renderer/src/modules/entry-column/Items/picture-masonry.tsx @@ -9,9 +9,9 @@ import { useMasonryColumn } from "@follow/components/ui/masonry/hooks.js" import { Masonry } from "@follow/components/ui/masonry/index.js" import { useScrollViewElement } from "@follow/components/ui/scroll-area/hooks.js" import { Skeleton } from "@follow/components/ui/skeleton/index.jsx" -import { FeedViewType } from "@follow/constants" import { useRefValue } from "@follow/hooks" import { getEntry } from "@follow/store/entry/getter" +import { useEntryTranslation } from "@follow/store/translation/hooks" import { clsx } from "@follow/utils/utils" import type { RenderComponentProps } from "masonic" import { useInfiniteLoader } from "masonic" @@ -30,9 +30,8 @@ import { } from "react" import { useEventCallback } from "usehooks-ts" -import { useGeneralSettingKey } from "~/atoms/settings/general" +import { useActionLanguage, useGeneralSettingKey } from "~/atoms/settings/general" import { MediaContainerWidthProvider } from "~/components/ui/media" -import { useEntryTranslation } from "~/store/ai/hook" import { imageActions } from "~/store/image" import { getMasonryColumnValue, setMasonryColumnValue, useMasonryColumnValue } from "../atoms" @@ -259,7 +258,8 @@ const MasonryRender: React.ComponentType< }> > = ({ data, index }) => { const firstScreenReady = use(FirstScreenReadyContext) - const translation = useEntryTranslation({ entryId: data.entryId, view: FeedViewType.Pictures }) + const actionLanguage = useActionLanguage() + const translation = useEntryTranslation(data.entryId, actionLanguage) if (data.entryId.startsWith("placeholder")) { return @@ -273,7 +273,7 @@ const MasonryRender: React.ComponentType< )} entryId={data.entryId} index={index} - translation={translation.data} + translation={translation} /> ) } diff --git a/apps/desktop/layer/renderer/src/modules/entry-column/Items/video-item.tsx b/apps/desktop/layer/renderer/src/modules/entry-column/Items/video-item.tsx index cc045b521..1f6a5bd5f 100644 --- a/apps/desktop/layer/renderer/src/modules/entry-column/Items/video-item.tsx +++ b/apps/desktop/layer/renderer/src/modules/entry-column/Items/video-item.tsx @@ -1,8 +1,8 @@ import { isMobile } from "@follow/components/hooks/useMobile.js" import { Skeleton } from "@follow/components/ui/skeleton/index.jsx" -import { FeedViewType } from "@follow/constants" import { IN_ELECTRON } from "@follow/shared/constants" import { useEntry } from "@follow/store/entry/hooks" +import { useEntryTranslation, usePrefetchEntryTranslation } from "@follow/store/translation/hooks" import { stopPropagation } from "@follow/utils/dom" import { formatDuration } from "@follow/utils/duration" import { transformVideoUrl } from "@follow/utils/url-for-video" @@ -11,6 +11,7 @@ import { useHover } from "@use-gesture/react" import { useEffect, useMemo, useRef, useState } from "react" import { AudioPlayer } from "~/atoms/player" +import { useActionLanguage, useGeneralSettingKey } from "~/atoms/settings/general" import { m } from "~/components/common/Motion" import { RelativeTime } from "~/components/ui/datetime" import { HTML } from "~/components/ui/markdown/HTML" @@ -22,9 +23,9 @@ import { PlainModal } from "~/components/ui/modal/stacked/custom-modal" import { useModalStack } from "~/components/ui/modal/stacked/hooks" import { useRenderStyle } from "~/hooks/biz/useRenderStyle" import { useRouteParamsSelector } from "~/hooks/biz/useRouteParams" +import { checkLanguage } from "~/lib/translate" import { FeedIcon } from "~/modules/feed/feed-icon" import { FeedTitle } from "~/modules/feed/feed-title" -import { useEntryTranslation } from "~/store/ai/hook" import { GridItem } from "../templates/grid-item-template" import type { EntryItemStatelessProps, UniversalItemProps } from "../types" @@ -169,12 +170,18 @@ const PreviewVideoModalContent: ModalContentComponent<{ entryId: string }> = ({ dismiss, src, entryId }) => { const entry = useEntry(entryId, (state) => ({ content: state.content })) - const translation = useEntryTranslation({ - entryId, - extraFields: ["content"], - view: FeedViewType.Videos, + + const actionLanguage = useActionLanguage() + const enableTranslation = useGeneralSettingKey("translation") + const translation = useEntryTranslation(entryId, actionLanguage) + usePrefetchEntryTranslation({ + entryIds: [entryId], + checkLanguage, + translation: enableTranslation, + language: actionLanguage, + withContent: true, }) - const content = translation.data?.content || entry?.content + const content = translation?.content || entry?.content const currentAudioPlayerIsPlay = useRef(AudioPlayer.get().status === "playing") const renderStyle = useRenderStyle() diff --git a/apps/desktop/layer/renderer/src/modules/entry-column/item.tsx b/apps/desktop/layer/renderer/src/modules/entry-column/item.tsx index ef27e452b..1f912f943 100644 --- a/apps/desktop/layer/renderer/src/modules/entry-column/item.tsx +++ b/apps/desktop/layer/renderer/src/modules/entry-column/item.tsx @@ -1,9 +1,11 @@ import type { FeedViewType } from "@follow/constants" import { useEntry } from "@follow/store/entry/hooks" +import { useEntryTranslation, usePrefetchEntryTranslation } from "@follow/store/translation/hooks" import type { FC } from "react" import { memo } from "react" -import { useEntryTranslation } from "~/store/ai/hook" +import { useActionLanguage, useGeneralSettingKey } from "~/atoms/settings/general" +import { checkLanguage } from "~/lib/translate" import { getItemComponentByView } from "./Items/getItemComponentByView" import { EntryItemWrapper } from "./layouts/EntryItemWrapper" @@ -20,13 +22,21 @@ const EntryItemImpl = memo(function EntryItemImpl({ entryId: string view: FeedViewType }) { - const translation = useEntryTranslation({ entryId, view }) + const actionLanguage = useActionLanguage() + const enableTranslation = useGeneralSettingKey("translation") + const translation = useEntryTranslation(entryId, actionLanguage) + usePrefetchEntryTranslation({ + entryIds: [entryId], + checkLanguage, + translation: enableTranslation, + language: actionLanguage, + }) const Item: EntryListItemFC = getItemComponentByView(view) return ( - + ) }) diff --git a/apps/desktop/layer/renderer/src/modules/entry-column/translation.tsx b/apps/desktop/layer/renderer/src/modules/entry-column/translation.tsx index 8567326f3..7c09fbc76 100644 --- a/apps/desktop/layer/renderer/src/modules/entry-column/translation.tsx +++ b/apps/desktop/layer/renderer/src/modules/entry-column/translation.tsx @@ -6,7 +6,7 @@ import { HTML } from "~/components/ui/markdown/HTML" export const EntryTranslation: Component<{ source?: string | null - target?: string + target?: string | null isHTML?: boolean inline?: boolean bilingual?: boolean diff --git a/apps/desktop/layer/renderer/src/modules/entry-column/types.ts b/apps/desktop/layer/renderer/src/modules/entry-column/types.ts index a30d4bb7e..340328989 100644 --- a/apps/desktop/layer/renderer/src/modules/entry-column/types.ts +++ b/apps/desktop/layer/renderer/src/modules/entry-column/types.ts @@ -4,6 +4,7 @@ import type { FeedModel, FeedOrListRespModel, } from "@follow/models/types" +import type { EntryTranslation } from "@follow/store/translation/types" import type { FC } from "react" export type UniversalItemProps = { @@ -13,11 +14,7 @@ export type UniversalItemProps = { feedId: string inboxId: string } - translation?: { - title?: string - description?: string - content?: string - } | null + translation?: EntryTranslation } export type EntryListItemFC

= FC

& { diff --git a/apps/desktop/layer/renderer/src/modules/entry-content/components/EntryTitle.tsx b/apps/desktop/layer/renderer/src/modules/entry-content/components/EntryTitle.tsx index 2370ecffb..43b1ef9be 100644 --- a/apps/desktop/layer/renderer/src/modules/entry-content/components/EntryTitle.tsx +++ b/apps/desktop/layer/renderer/src/modules/entry-content/components/EntryTitle.tsx @@ -1,9 +1,11 @@ import { useEntry, useEntryReadHistory } from "@follow/store/entry/hooks" import { useFeedById } from "@follow/store/feed/hooks" import { useInboxById } from "@follow/store/inbox/hooks" +import { useEntryTranslation } from "@follow/store/translation/hooks" import { formatEstimatedMins, formatTimeToSeconds } from "@follow/utils" import { titleCase } from "title-case" +import { useActionLanguage } from "~/atoms/settings/general" import { useUISettingKey } from "~/atoms/settings/ui" import { useWhoami } from "~/atoms/user" import { RelativeTime } from "~/components/ui/datetime" @@ -11,7 +13,6 @@ import { useNavigateEntry } from "~/hooks/biz/useNavigateEntry" import { useFeedSafeUrl } from "~/hooks/common/useFeedSafeUrl" import type { FeedIconEntry } from "~/modules/feed/feed-icon" import { FeedIcon } from "~/modules/feed/feed-icon" -import { useEntryTranslation } from "~/store/ai/hook" import { getPreferredTitle } from "~/store/feed/hooks" import { EntryTranslation } from "../../entry-column/translation" @@ -56,7 +57,9 @@ export const EntryTitle = ({ entryId, compact }: EntryLinkProps) => { const data = useEntryReadHistory(entryId) const entryHistory = data?.entryReadHistories const populatedFullHref = useFeedSafeUrl(entryId) - const translation = useEntryTranslation({ entryId, extraFields: ["title"], view: undefined }) + const actionLanguage = useActionLanguage() + + const translation = useEntryTranslation(entryId, actionLanguage) const dateFormat = useUISettingKey("dateFormat") @@ -88,7 +91,7 @@ export const EntryTitle = ({ entryId, compact }: EntryLinkProps) => { > { export const useEntryContent = (entryId: string) => { const entry = useEntry(entryId, (state) => { - const { inboxHandle, content } = state - return { inboxId: inboxHandle, content } + const { inboxHandle, content, readabilityContent } = state + return { inboxId: inboxHandle, content, readabilityContent } }) const { error, data, isPending } = usePrefetchEntryDetail(entryId) const isInReadabilityMode = useEntryIsInReadability(entryId) const isReadabilitySuccess = useEntryIsInReadabilitySuccess(entryId) - const readabilityContent = useEntryReadabilityContent(entryId) - const contentTranslated = useEntryTranslation({ - entryId, - extraFields: isReadabilitySuccess ? ["readabilityContent"] : ["content"], - view: undefined, + + const actionLanguage = useActionLanguage() + const enableTranslation = useGeneralSettingKey("translation") + const contentTranslated = useEntryTranslation(entryId, actionLanguage) + usePrefetchEntryTranslation({ + entryIds: [entryId], + checkLanguage, + translation: enableTranslation, + language: actionLanguage, + withContent: true, + target: isReadabilitySuccess ? "readabilityContent" : "content", }) return useMemo(() => { const entryContent = isInReadabilityMode - ? readabilityContent?.content + ? entry?.readabilityContent : (entry?.content ?? data?.content) const translatedContent = isInReadabilityMode - ? contentTranslated.data?.readabilityContent - : contentTranslated.data?.content + ? contentTranslated?.readabilityContent + : contentTranslated?.content const content = translatedContent || entryContent return { content, @@ -68,14 +72,14 @@ export const useEntryContent = (entryId: string) => { isPending, } }, [ - contentTranslated.data?.content, - contentTranslated.data?.readabilityContent, + contentTranslated?.content, + contentTranslated?.readabilityContent, data?.content, entry?.content, error, isInReadabilityMode, isPending, - readabilityContent?.content, + entry?.readabilityContent, ]) } diff --git a/apps/desktop/layer/renderer/src/modules/entry-content/index.shared.tsx b/apps/desktop/layer/renderer/src/modules/entry-content/index.shared.tsx index d3848f58b..199ac69c6 100644 --- a/apps/desktop/layer/renderer/src/modules/entry-content/index.shared.tsx +++ b/apps/desktop/layer/renderer/src/modules/entry-content/index.shared.tsx @@ -21,7 +21,6 @@ import { setReadabilityStatus, useEntryInReadabilityStatus, useEntryIsInReadability, - useEntryReadabilityContent, } from "~/atoms/readability" import { enableShowSourceContent } from "~/atoms/source-content" import type { TocRef } from "~/components/ui/markdown/components/Toc" @@ -81,7 +80,7 @@ export const TitleMetaHandler: Component<{ export const ReadabilityNotice = ({ entryId }: { entryId: string }) => { const { t } = useTranslation() const { t: T } = useTranslation("common") - const result = useEntryReadabilityContent(entryId) + const result = useEntry(entryId, (state) => state.readabilityContent) const isInReadability = useEntryIsInReadability(entryId) const status = useEntryInReadabilityStatus(entryId) diff --git a/apps/desktop/layer/renderer/src/queries/ai.ts b/apps/desktop/layer/renderer/src/queries/ai.ts index 8ed0fde5e..bf79827f6 100644 --- a/apps/desktop/layer/renderer/src/queries/ai.ts +++ b/apps/desktop/layer/renderer/src/queries/ai.ts @@ -1,29 +1,11 @@ import type { SupportedLanguages } from "@follow/models/types" import { getEntry } from "@follow/store/entry/getter" -import { getReadabilityContent } from "~/atoms/readability" import { apiClient } from "~/lib/api-fetch" import { defineQuery } from "~/lib/defineQuery" import { parseHtml } from "~/lib/parse-html" -import { translate } from "~/lib/translate" export const ai = { - translation: ({ - entryId, - view, - language, - extraFields, - part, - }: { - entryId?: string | null - view?: number | null - language?: SupportedLanguages - extraFields?: string[] - part?: string - }) => - defineQuery(["translation", entryId, view, language, extraFields, part], () => - translate({ entryId, view, language, extraFields, part }), - ), summary: ({ entryId, language, @@ -34,10 +16,8 @@ export const ai = { target?: "content" | "readabilityContent" }) => defineQuery(["summary", entryId, language, target], async () => { - const content = - target === "readabilityContent" - ? getReadabilityContent()[entryId]?.content - : getEntry(entryId)?.content + const entry = getEntry(entryId) + const content = target === "readabilityContent" ? entry?.readabilityContent : entry?.content if (!content) { return null } diff --git a/apps/desktop/layer/renderer/src/store/ai/hook.ts b/apps/desktop/layer/renderer/src/store/ai/hook.ts deleted file mode 100644 index 9a7600a98..000000000 --- a/apps/desktop/layer/renderer/src/store/ai/hook.ts +++ /dev/null @@ -1,53 +0,0 @@ -import type { FeedViewType } from "@follow/constants" -import { useEntry } from "@follow/store/entry/hooks" -import { useMemo } from "react" - -import { useShowAITranslation, useShowAITranslationAuto } from "~/atoms/ai-translation" -import { useActionLanguage } from "~/atoms/settings/general" -import { useAuthQuery } from "~/hooks/common/useBizQuery" -import { Queries } from "~/queries" - -export function useEntryTranslation({ - entryId, - extraFields, - view, -}: { - entryId?: string - extraFields?: string[] - view: FeedViewType | undefined -}) { - const entry = useEntry(entryId, (state) => ({ - translation: state.settings?.translation, - })) - const actionLanguage = useActionLanguage() - const showAITranslationFinal = useShowAITranslation(!!entry?.translation) - const showAITranslationAuto = useShowAITranslationAuto(!!entry?.translation) - const showAITranslation = - !extraFields || extraFields.length === 0 ? showAITranslationAuto : showAITranslationFinal - - const res = useAuthQuery( - Queries.ai.translation({ - entryId, - view, - language: actionLanguage, - extraFields, - }), - { - enabled: showAITranslation, - refetchOnMount: false, - refetchOnWindowFocus: false, - meta: { - persist: true, - }, - }, - ) - - return useMemo( - () => ({ - ...res, - // with persist option enabled, we need to explicitly set data to null when showAITranslation is false - data: showAITranslation ? res.data : null, - }), - [res, showAITranslation], - ) -} diff --git a/packages/internal/store/src/entry/store.ts b/packages/internal/store/src/entry/store.ts index b769d3220..d10ae6b72 100644 --- a/packages/internal/store/src/entry/store.ts +++ b/packages/internal/store/src/entry/store.ts @@ -536,19 +536,33 @@ class EntrySyncServices { return entry } - async fetchEntryReadabilityContent(entryId: EntryId) { + async fetchEntryReadabilityContent( + entryId: EntryId, + fallBack?: () => Promise, + ) { const entry = getEntry(entryId) if (entry?.url && entry?.readabilityContent === null) { - const { data: contentByFetch } = await apiClient().entries.readability.$get({ - query: { - id: entryId, - }, - }) - if (contentByFetch?.content && entry?.readabilityContent !== contentByFetch.content) { + let readabilityContent: string | null | undefined + + try { + const { data: contentByFetch } = await apiClient().entries.readability.$get({ + query: { + id: entryId, + }, + }) + readabilityContent = contentByFetch?.content || null + } catch (error) { + if (fallBack) { + readabilityContent = await fallBack() + } else { + throw error + } + } + if (readabilityContent && entry?.readabilityContent !== readabilityContent) { await entryActions.updateEntryContent({ entryId, - readabilityContent: contentByFetch.content, + readabilityContent, }) } }