aboutsummaryrefslogtreecommitdiffstats
path: root/apps/mobile/app
diff options
context:
space:
mode:
Diffstat (limited to 'apps/mobile/app')
-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