From 731d2dfbea39aa140ccb6d2d2cabd49186320299 Mon Sep 17 00:00:00 2001 From: Mohamed Bassem Date: Sun, 27 Oct 2024 00:12:11 +0000 Subject: feature: Add a summarize with AI button for links --- .../dashboard/bookmarks/SummarizeBookmarkArea.tsx | 151 +++++++++++++++++++++ .../dashboard/preview/BookmarkPreview.tsx | 2 + apps/workers/openaiWorker.ts | 9 +- 3 files changed, 159 insertions(+), 3 deletions(-) create mode 100644 apps/web/components/dashboard/bookmarks/SummarizeBookmarkArea.tsx (limited to 'apps') diff --git a/apps/web/components/dashboard/bookmarks/SummarizeBookmarkArea.tsx b/apps/web/components/dashboard/bookmarks/SummarizeBookmarkArea.tsx new file mode 100644 index 00000000..c1f3a18a --- /dev/null +++ b/apps/web/components/dashboard/bookmarks/SummarizeBookmarkArea.tsx @@ -0,0 +1,151 @@ +import React from "react"; +import { ActionButton } from "@/components/ui/action-button"; +import { Button } from "@/components/ui/button"; +import LoadingSpinner from "@/components/ui/spinner"; +import { toast } from "@/components/ui/use-toast"; +import { cn } from "@/lib/utils"; +import { + ChevronDown, + ChevronUp, + Loader2, + RefreshCw, + Trash2, +} from "lucide-react"; + +import { + useSummarizeBookmark, + useUpdateBookmark, +} from "@hoarder/shared-react/hooks/bookmarks"; +import { BookmarkTypes, ZBookmark } from "@hoarder/shared/types/bookmarks"; + +function AISummary({ + bookmarkId, + summary, +}: { + bookmarkId: string; + summary: string; +}) { + const [isExpanded, setIsExpanded] = React.useState(false); + const { mutate: resummarize, isPending: isResummarizing } = + useSummarizeBookmark({ + onError: () => { + toast({ + description: "Something went wrong", + variant: "destructive", + }); + }, + }); + const { mutate: updateBookmark, isPending: isUpdatingBookmark } = + useUpdateBookmark({ + onError: () => { + toast({ + description: "Something went wrong", + variant: "destructive", + }); + }, + }); + return ( +
+ {/* eslint-disable-next-line jsx-a11y/click-events-have-key-events, jsx-a11y/no-static-element-interactions */} +
!isExpanded && setIsExpanded(true)} + > +
+

+ {summary} +

+ {!isExpanded && ( +
+ )} + + {isExpanded && ( + <> + } + className="rounded-full bg-gray-200 p-1 text-gray-600 dark:bg-gray-700 dark:text-gray-400" + aria-label={isExpanded ? "Collapse" : "Expand"} + loading={isResummarizing} + onClick={() => resummarize({ bookmarkId })} + > + + + } + className="rounded-full bg-gray-200 p-1 text-gray-600 dark:bg-gray-700 dark:text-gray-400" + aria-label={isExpanded ? "Collapse" : "Expand"} + loading={isUpdatingBookmark} + onClick={() => updateBookmark({ bookmarkId, summary: null })} + > + + + + )} + + +
+
+
+ ); +} + +export default function SummarizeBookmarkArea({ + bookmark, +}: { + bookmark: ZBookmark; +}) { + const { mutate, isPending } = useSummarizeBookmark({ + onError: () => { + toast({ + description: "Something went wrong", + variant: "destructive", + }); + }, + }); + + if (bookmark.content.type !== BookmarkTypes.LINK) { + return null; + } + + if (bookmark.summary) { + return ; + } else { + return ( +
+ +
+ ); + } +} diff --git a/apps/web/components/dashboard/preview/BookmarkPreview.tsx b/apps/web/components/dashboard/preview/BookmarkPreview.tsx index 5e2764f4..e37c4b86 100644 --- a/apps/web/components/dashboard/preview/BookmarkPreview.tsx +++ b/apps/web/components/dashboard/preview/BookmarkPreview.tsx @@ -24,6 +24,7 @@ import { } from "@hoarder/shared-react/utils/bookmarkUtils"; import { BookmarkTypes, ZBookmark } from "@hoarder/shared/types/bookmarks"; +import SummarizeBookmarkArea from "../bookmarks/SummarizeBookmarkArea"; import ActionBar from "./ActionBar"; import { AssetContentSection } from "./AssetContentSection"; import AttachmentBox from "./AttachmentBox"; @@ -137,6 +138,7 @@ export default function BookmarkPreview({
+

Tags

diff --git a/apps/workers/openaiWorker.ts b/apps/workers/openaiWorker.ts index b1394f73..4fe74f44 100644 --- a/apps/workers/openaiWorker.ts +++ b/apps/workers/openaiWorker.ts @@ -180,6 +180,7 @@ async function inferTagsFromImage( ), metadata.contentType, base64, + { json: true }, ); } @@ -235,14 +236,16 @@ async function inferTagsFromPDF( `Content: ${pdfParse.text}`, serverConfig.inference.contextLength, ); - return inferenceClient.inferFromText(prompt); + return inferenceClient.inferFromText(prompt, { json: true }); } async function inferTagsFromText( bookmark: NonNullable>>, inferenceClient: InferenceClient, ) { - return await inferenceClient.inferFromText(await buildPrompt(bookmark)); + return await inferenceClient.inferFromText(await buildPrompt(bookmark), { + json: true, + }); } async function inferTags( @@ -290,7 +293,7 @@ async function inferTags( return tags; } catch (e) { - const responseSneak = response.response.substr(0, 20); + const responseSneak = response.response.substring(0, 20); throw new Error( `[inference][${jobId}] The model ignored our prompt and didn't respond with the expected JSON: ${JSON.stringify(e)}. Here's a sneak peak from the response: ${responseSneak}`, ); -- cgit v1.2.3-70-g09d2