feat: apply title and description ai translation

This commit is contained in:
DIYgod 2024-06-13 02:29:49 +08:00
parent 78c030613f
commit 810111291c
No known key found for this signature in database
13 changed files with 127 additions and 62 deletions

View File

@ -8,7 +8,7 @@ import { useEntry } from "@renderer/store/entry"
import { ReactVirtuosoItemPlaceholder } from "../ui/placeholder"
import type { UniversalItemProps } from "./types"
export function ArticleItem({ entryId, entryPreview }: UniversalItemProps) {
export function ArticleItem({ entryId, entryPreview, translation }: UniversalItemProps) {
const entry = useEntry(entryId) || entryPreview
const asRead = useAsRead(entry)
@ -33,13 +33,13 @@ export function ArticleItem({ entryId, entryPreview }: UniversalItemProps) {
</span>
</div>
<div className={cn("relative my-0.5 break-words", !!entry.collections && "pr-4", entry.entries.title ? "font-medium" : "text-[13px]")}>
{entry.entries.title || entry.entries.description}
{translation?.title || translation?.description || entry.entries.title || entry.entries.description}
{!!entry.collections && (
<i className="i-mingcute-star-fill absolute right-0 top-0.5 text-orange-400" />
)}
</div>
<div className={cn("text-[13px]", asRead ? "text-zinc-400" : "text-zinc-500")}>
{entry.entries.description}
{translation?.description || entry.entries.description}
</div>
</div>
{entry.entries.images?.[0] && (

View File

@ -6,7 +6,7 @@ import { useEntry } from "@renderer/store/entry"
import { ReactVirtuosoItemPlaceholder } from "../ui/placeholder"
import type { UniversalItemProps } from "./types"
export function AudioItem({ entryId, entryPreview }: UniversalItemProps) {
export function AudioItem({ entryId, entryPreview, translation }: UniversalItemProps) {
const entry = useEntry(entryId) || entryPreview
if (!entry) return <ReactVirtuosoItemPlaceholder />
return (
@ -26,7 +26,7 @@ export function AudioItem({ entryId, entryPreview }: UniversalItemProps) {
</span>
</div>
<div className={cn("relative my-0.5", !!entry.collections && "pr-4")}>
{entry.entries.title}
{translation?.title || entry.entries.title}
{!!entry.collections && (
<i className="i-mingcute-star-fill absolute right-0 top-0.5 text-orange-400" />
)}

View File

@ -7,7 +7,6 @@ import {
} from "@renderer/components/ui/popover"
import { apiClient } from "@renderer/lib/api-fetch"
import { views } from "@renderer/lib/constants"
import { FeedViewType } from "@renderer/lib/enum"
import { buildStorageNS } from "@renderer/lib/ns"
import { getEntriesParams } from "@renderer/lib/utils"
import { useEntries } from "@renderer/queries/entries"
@ -29,14 +28,7 @@ import { useEventCallback } from "usehooks-ts"
import { useShallow } from "zustand/react/shallow"
import { EmptyIcon } from "../icons/empty"
import { ArticleItem } from "./article-item"
import { AudioItem } from "./audio-item"
import { EntryItemWrapper } from "./item-wrapper"
import { NotificationItem } from "./notification-item"
import { PictureItem } from "./picture-item"
import { SocialMediaItem } from "./social-media-item"
import type { UniversalItemProps } from "./types"
import { VideoItem } from "./video-item"
const unreadOnlyAtom = atomWithStorage<boolean>(
buildStorageNS("entry-unreadonly"),
@ -50,38 +42,6 @@ export function EntryColumn() {
const entries = useEntriesByView()
const { entriesIds } = entries
let Item: FC<UniversalItemProps>
switch (activeList?.view) {
case FeedViewType.Articles: {
Item = ArticleItem
break
}
case FeedViewType.SocialMedia: {
Item = SocialMediaItem
break
}
case FeedViewType.Pictures: {
Item = PictureItem
break
}
case FeedViewType.Videos: {
Item = VideoItem
break
}
case FeedViewType.Audios: {
Item = AudioItem
break
}
case FeedViewType.Notifications: {
Item = NotificationItem
break
}
default: {
Item = ArticleItem
}
}
const handleRangeChange = useEventCallback(
debounce(
async ({ startIndex }: ListRange) => {
@ -134,12 +94,10 @@ export function EntryColumn() {
key={entryId}
entryId={entryId}
view={activeList?.view}
>
<Item entryId={entryId} />
</EntryItemWrapper>
/>
)
},
[Item, activeList?.view],
[activeList?.view],
),
}

View File

@ -1,20 +1,29 @@
import { useAsRead } from "@renderer/hooks/useAsRead"
import { useBizQuery } from "@renderer/hooks/useBizQuery"
import { useEntryActions, useRead } from "@renderer/hooks/useEntryActions"
import { views } from "@renderer/lib/constants"
import { FeedViewType } from "@renderer/lib/enum"
import { showNativeMenu } from "@renderer/lib/native-menu"
import { cn } from "@renderer/lib/utils"
import { Queries } from "@renderer/queries"
import { feedActions, useFeedStore } from "@renderer/store"
import { useEntry } from "@renderer/store/entry"
import type { FC } from "react"
import { ReactVirtuosoItemPlaceholder } from "../ui/placeholder"
import { ArticleItem } from "./article-item"
import { AudioItem } from "./audio-item"
import { NotificationItem } from "./notification-item"
import { PictureItem } from "./picture-item"
import { SocialMediaItem } from "./social-media-item"
import type { UniversalItemProps } from "./types"
import { VideoItem } from "./video-item"
export function EntryItemWrapper({
children,
entryId,
view,
}: {
entryId: string
children: React.ReactNode
view?: number
}) {
const entry = useEntry(entryId)
@ -23,6 +32,14 @@ export function EntryItemWrapper({
entry,
})
const translation = useBizQuery(Queries.ai.translation({
id: entry.entries.id,
language: entry.settings?.translation,
fields: view ? views[view].translation : "",
}), {
enabled: !!entry.settings?.translation,
})
const activeEntry = useFeedStore((state) => state.activeEntry)
const asRead = useAsRead(entry)
@ -32,6 +49,38 @@ export function EntryItemWrapper({
// NOTE: prevent 0 height element, react virtuoso will not stop render any more
if (!entry) return <ReactVirtuosoItemPlaceholder />
let Item: FC<UniversalItemProps>
switch (view) {
case FeedViewType.Articles: {
Item = ArticleItem
break
}
case FeedViewType.SocialMedia: {
Item = SocialMediaItem
break
}
case FeedViewType.Pictures: {
Item = PictureItem
break
}
case FeedViewType.Videos: {
Item = VideoItem
break
}
case FeedViewType.Audios: {
Item = AudioItem
break
}
case FeedViewType.Notifications: {
Item = NotificationItem
break
}
default: {
Item = ArticleItem
}
}
return (
<div className={cn(!views[view || 0].wideMode && "pb-3")}>
<div
@ -66,7 +115,7 @@ export function EntryItemWrapper({
)
}}
>
{children}
<Item entryId={entryId} translation={translation.data} />
</div>
</div>
)

View File

@ -6,7 +6,7 @@ import { useEntry } from "@renderer/store/entry"
import { ReactVirtuosoItemPlaceholder } from "../ui/placeholder"
import type { UniversalItemProps } from "./types"
export function NotificationItem({ entryId, entryPreview }: UniversalItemProps) {
export function NotificationItem({ entryId, entryPreview, translation }: UniversalItemProps) {
const entry = useEntry(entryId) || entryPreview
if (!entry) return <ReactVirtuosoItemPlaceholder />
return (
@ -26,7 +26,7 @@ export function NotificationItem({ entryId, entryPreview }: UniversalItemProps)
</span>
</div>
<div className={cn("relative my-0.5", !!entry.collections && "pr-4")}>
{entry.entries.title}
{translation?.title || entry.entries.title}
{!!entry.collections && (
<i className="i-mingcute-star-fill absolute right-0 top-0.5 text-orange-400" />
)}

View File

@ -8,7 +8,7 @@ import { useEntry } from "@renderer/store/entry"
import { usePreviewImages } from "../ui/image/hooks"
import type { UniversalItemProps } from "./types"
export function PictureItem({ entryId, entryPreview }: UniversalItemProps) {
export function PictureItem({ entryId, entryPreview, translation }: UniversalItemProps) {
const entry = useEntry(entryId) || entryPreview
const previewImage = usePreviewImages()
if (!entry) return <ReactVirtuosoItemPlaceholder />
@ -37,7 +37,7 @@ export function PictureItem({ entryId, entryPreview }: UniversalItemProps) {
!!entry.collections && "pr-4",
)}
>
{entry.entries.title}
{translation?.title || entry.entries.title}
{!!entry.collections && (
<i className="i-mingcute-star-fill absolute right-0 top-0.5 text-orange-400" />
)}

View File

@ -7,7 +7,7 @@ import { useEntry } from "@renderer/store/entry"
import { ReactVirtuosoItemPlaceholder } from "../ui/placeholder"
import type { UniversalItemProps } from "./types"
export function SocialMediaItem({ entryId, entryPreview }: UniversalItemProps) {
export function SocialMediaItem({ entryId, entryPreview, translation }: UniversalItemProps) {
const entry = useEntry(entryId) || entryPreview
// NOTE: prevent 0 height element, react virtuoso will not stop render any more
@ -30,7 +30,7 @@ export function SocialMediaItem({ entryId, entryPreview }: UniversalItemProps) {
</span>
</div>
<div className={cn("relative mt-0.5", !!entry.collections && "pr-4")}>
{entry.entries.description}
{translation?.description || entry.entries.description}
{!!entry.collections && (
<i className="i-mingcute-star-fill absolute right-0 top-0.5 text-orange-400" />
)}

View File

@ -5,4 +5,8 @@ export type UniversalItemProps = {
entryPreview?: EntryModel | {
feeds: FeedModel
}
translation?: {
title?: string
description?: string
} | null
}

View File

@ -10,7 +10,7 @@ import { useMemo, useRef, useState } from "react"
import { ReactVirtuosoItemPlaceholder } from "../ui/placeholder"
import type { UniversalItemProps } from "./types"
export function VideoItem({ entryId, entryPreview }: UniversalItemProps) {
export function VideoItem({ entryId, entryPreview, translation }: UniversalItemProps) {
const entry = useEntry(entryId) || entryPreview
const iframeSrc = useMemo(() => urlToIframe(entry.entries.url), [entry.entries.url])
@ -52,7 +52,7 @@ export function VideoItem({ entryId, entryPreview }: UniversalItemProps) {
</div>
<div className="flex-1 px-2 pb-3 pt-1 text-sm">
<div className={cn("relative mb-0.5 mt-1 truncate font-medium", !!entry.collections && "pr-4")}>
{entry.entries.title}
{translation?.title || entry.entries.title}
{!!entry.collections && (
<i className="i-mingcute-star-fill absolute right-0 top-0.5 text-orange-400" />
)}

View File

@ -3,6 +3,27 @@ import * as hono_utils_http_status from 'hono/utils/http-status';
import * as hono from 'hono';
declare const routes: hono_hono_base.HonoBase<hono.Env, {
"/ai/translation": {
$get: {
input: {
query: {
id: string;
language: "en" | "ja" | "zh-CN" | "zh-TW";
fields: string;
};
};
output: {
code: 0;
data?: {
description?: string | undefined;
title?: string | undefined;
} | undefined;
};
outputFormat: "json";
status: 200;
};
};
} & {
"/actions": {
$get: {
input: {};
@ -373,7 +394,6 @@ declare const routes: hono_hono_base.HonoBase<hono.Env, {
feedIdList?: string[] | undefined;
publishedAfter?: string | undefined;
collected?: boolean | undefined;
withContent?: boolean | undefined;
};
};
output: {
@ -393,7 +413,6 @@ declare const routes: hono_hono_base.HonoBase<hono.Env, {
publishedAt: string;
images: string[] | null;
categories: string[] | null;
content?: string | null | undefined;
enclosures?: {
url: string;
length?: number | undefined;

View File

@ -12,12 +12,14 @@ export const views = [
name: "Articles",
icon: <i className="i-mingcute-news-fill" />,
className: "text-amber-700",
translation: "title,description",
},
{
name: "Social Media",
icon: <i className="i-mingcute-twitter-fill" />,
className: "text-sky-600",
wideMode: true,
translation: "description",
},
{
name: "Pictures",
@ -25,6 +27,7 @@ export const views = [
className: "text-green-600",
gridMode: true,
wideMode: true,
translation: "title",
},
{
name: "Videos",
@ -32,16 +35,19 @@ export const views = [
className: "text-red-600",
gridMode: true,
wideMode: true,
translation: "title",
},
{
name: "Audios",
icon: <i className="i-mingcute-headphone-fill" />,
className: "text-purple-600",
translation: "title",
},
{
name: "Notifications",
icon: <i className="i-mingcute-notification-fill" />,
className: "text-yellow-600",
translation: "title",
},
]

View File

@ -0,0 +1,27 @@
import { apiClient } from "@renderer/lib/api-fetch"
import { defineQuery } from "@renderer/lib/defineQuery"
export const ai = {
translation: ({
id,
language,
fields,
}: {
id: string
language?: string
fields: string
}) =>
defineQuery(["translation", id, language], async () => {
if (!language) {
return null
}
const res = await apiClient.ai.translation.$get({
query: {
id,
language: language as "en" | "ja" | "zh-CN" | "zh-TW",
fields,
},
})
return res.data
}),
}

View File

@ -1,4 +1,5 @@
import { action } from "./actions"
import { ai } from "./ai"
import { auth } from "./auth"
import { entries } from "./entries"
import { feed } from "./feed"
@ -10,4 +11,5 @@ export const Queries = {
feed,
action,
auth,
ai,
}