aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorMohamed Bassem <me@mbassem.com>2025-11-23 11:20:18 +0000
committerGitHub <noreply@github.com>2025-11-23 11:20:18 +0000
commit45081dcb472f6aa197d6ebdc129485f77fe3ee88 (patch)
tree41f0b03b18383cd8526669e98ea15ffa471cb93a
parentad66f78dc9ccd2c6c8f0e67ac8a6c33519db5ce7 (diff)
downloadkarakeep-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.tsx154
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