diff options
| author | Mohamed Bassem <me@mbassem.com> | 2025-11-23 11:20:18 +0000 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2025-11-23 11:20:18 +0000 |
| commit | 45081dcb472f6aa197d6ebdc129485f77fe3ee88 (patch) | |
| tree | 41f0b03b18383cd8526669e98ea15ffa471cb93a | |
| parent | ad66f78dc9ccd2c6c8f0e67ac8a6c33519db5ce7 (diff) | |
| download | karakeep-45081dcb472f6aa197d6ebdc129485f77fe3ee88.tar.zst | |
feat(mobile): Add AI summary field to mobile bookmark info (#2157)
* feat: Add AI summary field to mobile bookmark info page
Add a new AI summary section to the mobile bookmark info page that allows users to:
- Generate AI summaries for link bookmarks
- View existing summaries with expand/collapse functionality
- Regenerate summaries with the refresh button
- Delete summaries
The implementation is inspired by the web app's AI summary feature and includes:
- Purple-themed styling to match the AI branding
- Loading states for all actions
- Toast notifications for success/error feedback
- Support for dark mode
- Only displays for LINK type bookmarks
The summary is rendered using markdown and appears prominently in the bookmark info page,
positioned between the title editor and tags section.
* refactor: Simplify AI summary styling to match normal fields
Remove purple border and background from the AI summary display to make it
look more like a standard field. The summary now uses the default card
background from InfoSection, making it visually consistent with other
fields on the page.
The purple "Summarize with AI" button is retained for the generate action.
* make the expand button more clear
---------
Co-authored-by: Claude <noreply@anthropic.com>
| -rw-r--r-- | apps/mobile/app/dashboard/bookmarks/[slug]/info.tsx | 154 |
1 files changed, 153 insertions, 1 deletions
diff --git a/apps/mobile/app/dashboard/bookmarks/[slug]/info.tsx b/apps/mobile/app/dashboard/bookmarks/[slug]/info.tsx index 1781ec74..15e2a082 100644 --- a/apps/mobile/app/dashboard/bookmarks/[slug]/info.tsx +++ b/apps/mobile/app/dashboard/bookmarks/[slug]/info.tsx @@ -1,11 +1,12 @@ import React from "react"; -import { Alert, Pressable, View } from "react-native"; +import { ActivityIndicator, Alert, Pressable, View } from "react-native"; import { KeyboardAwareScrollView, KeyboardGestureArea, } from "react-native-keyboard-controller"; import { useSafeAreaInsets } from "react-native-safe-area-context"; import { router, Stack, useLocalSearchParams } from "expo-router"; +import BookmarkTextMarkdown from "@/components/bookmarks/BookmarkTextMarkdown"; import TagPill from "@/components/bookmarks/TagPill"; import FullPageError from "@/components/FullPageError"; import { Button } from "@/components/ui/Button"; @@ -17,10 +18,12 @@ import { Skeleton } from "@/components/ui/Skeleton"; import { Text } from "@/components/ui/Text"; import { useToast } from "@/components/ui/Toast"; import { cn } from "@/lib/utils"; +import { ChevronUp, RefreshCw, Sparkles, Trash2 } from "lucide-react-native"; import { useAutoRefreshingBookmarkQuery, useDeleteBookmark, + useSummarizeBookmark, useUpdateBookmark, } from "@karakeep/shared-react/hooks/bookmarks"; import { BookmarkTypes, ZBookmark } from "@karakeep/shared/types/bookmarks"; @@ -138,6 +141,154 @@ function NotesEditor({ ); } +function AISummarySection({ bookmark }: { bookmark: ZBookmark }) { + const { toast } = useToast(); + const [isExpanded, setIsExpanded] = React.useState(false); + + const { mutate: summarize, isPending: isSummarizing } = useSummarizeBookmark({ + onSuccess: () => { + toast({ + message: "Summary generated successfully!", + showProgress: false, + }); + }, + onError: () => { + toast({ + message: "Failed to generate summary", + showProgress: false, + }); + }, + }); + + const { mutate: resummarize, isPending: isResummarizing } = + useSummarizeBookmark({ + onSuccess: () => { + toast({ + message: "Summary regenerated successfully!", + showProgress: false, + }); + }, + onError: () => { + toast({ + message: "Failed to regenerate summary", + showProgress: false, + }); + }, + }); + + const { mutate: updateBookmark, isPending: isDeletingSummary } = + useUpdateBookmark({ + onSuccess: () => { + toast({ + message: "Summary deleted!", + showProgress: false, + }); + }, + onError: () => { + toast({ + message: "Failed to delete summary", + showProgress: false, + }); + }, + }); + + // Only show for LINK bookmarks + if (bookmark.content.type !== BookmarkTypes.LINK) { + return null; + } + + // If there's a summary, show it + if (bookmark.summary) { + return ( + <InfoSection> + <View className={isExpanded ? "" : "max-h-20 overflow-hidden"}> + <BookmarkTextMarkdown text={bookmark.summary} /> + </View> + {!isExpanded && ( + <Pressable + onPress={() => setIsExpanded(true)} + className="rounded-md bg-gray-100 py-2 dark:bg-gray-800" + > + <Text className="text-center text-sm font-medium text-gray-600 dark:text-gray-400"> + Show more + </Text> + </Pressable> + )} + {isExpanded && ( + <View className="mt-2 flex flex-row justify-end gap-2"> + <Pressable + onPress={() => resummarize({ bookmarkId: bookmark.id })} + disabled={isResummarizing} + className="rounded-full bg-gray-200 p-2 dark:bg-gray-700" + > + {isResummarizing ? ( + <ActivityIndicator size="small" /> + ) : ( + <RefreshCw + size={16} + className="text-gray-600 dark:text-gray-400" + /> + )} + </Pressable> + <Pressable + onPress={() => + updateBookmark({ bookmarkId: bookmark.id, summary: null }) + } + disabled={isDeletingSummary} + className="rounded-full bg-gray-200 p-2 dark:bg-gray-700" + > + {isDeletingSummary ? ( + <ActivityIndicator size="small" /> + ) : ( + <Trash2 + size={16} + className="text-gray-600 dark:text-gray-400" + /> + )} + </Pressable> + <Pressable + onPress={() => setIsExpanded(false)} + className="rounded-full bg-gray-200 p-2 dark:bg-gray-700" + > + <ChevronUp + size={16} + className="text-gray-600 dark:text-gray-400" + /> + </Pressable> + </View> + )} + </InfoSection> + ); + } + + // If no summary, show button to generate one + return ( + <InfoSection> + <Pressable + onPress={() => summarize({ bookmarkId: bookmark.id })} + disabled={isSummarizing} + className="rounded-lg bg-purple-500 p-3 dark:bg-purple-600" + > + <View className="flex flex-row items-center justify-center gap-2"> + {isSummarizing ? ( + <> + <ActivityIndicator size="small" color="#fff" /> + <Text className="font-medium text-white"> + Generating summary... + </Text> + </> + ) : ( + <> + <Text className="font-medium text-white">Summarize with AI</Text> + <Sparkles size={16} color="#fff" /> + </> + )} + </View> + </Pressable> + </InfoSection> + ); +} + const ViewBookmarkPage = () => { const insets = useSafeAreaInsets(); const { slug } = useLocalSearchParams(); @@ -266,6 +417,7 @@ const ViewBookmarkPage = () => { } isPending={isEditPending} /> + <AISummarySection bookmark={bookmark} /> <TagList bookmark={bookmark} /> <ManageLists bookmark={bookmark} /> <NotesEditor |
