diff options
| author | Dhravya Shah <[email protected]> | 2024-07-23 23:33:11 -0500 |
|---|---|---|
| committer | Dhravya Shah <[email protected]> | 2024-07-23 23:33:11 -0500 |
| commit | 568531f3236801f192549e44283a4f1a19ff2f9e (patch) | |
| tree | 35720f9e0a8b886beb938f7dc6b7a17da3f871a4 | |
| parent | Merge pull request #137 from supermemoryai/pro-mode (diff) | |
| download | supermemory-568531f3236801f192549e44283a4f1a19ff2f9e.tar.xz supermemory-568531f3236801f192549e44283a4f1a19ff2f9e.zip | |
added recommended items
| -rw-r--r-- | apps/web/app/(dash)/home/history.tsx | 31 | ||||
| -rw-r--r-- | apps/web/app/(dash)/home/page.tsx | 34 | ||||
| -rw-r--r-- | apps/web/app/(dash)/home/queryinput.tsx | 31 | ||||
| -rw-r--r-- | apps/web/app/actions/doers.ts | 102 | ||||
| -rw-r--r-- | apps/web/env.d.ts | 2 | ||||
| -rw-r--r-- | apps/web/lib/utils.ts | 23 | ||||
| -rw-r--r-- | apps/web/wrangler.toml | 9 |
7 files changed, 177 insertions, 55 deletions
diff --git a/apps/web/app/(dash)/home/history.tsx b/apps/web/app/(dash)/home/history.tsx index 922734df..3e6825eb 100644 --- a/apps/web/app/(dash)/home/history.tsx +++ b/apps/web/app/(dash)/home/history.tsx @@ -5,26 +5,27 @@ import Link from "next/link"; import { memo, useEffect, useState } from "react"; import { motion } from "framer-motion"; import { chatThreads } from "@/server/db/schema"; +import { getQuerySuggestions } from "@/app/actions/doers"; +import { Button } from "@repo/ui/shadcn/button"; -const History = memo(() => { - const [chatThreads_, setChatThreads] = useState< - (typeof chatThreads.$inferSelect)[] | null - >(null); +const History = memo(({ setQuery }: { setQuery: (q: string) => void }) => { + const [suggestions, setSuggestions] = useState<string[] | null>(null); useEffect(() => { (async () => { - const chatThreads = await getChatHistory(); - if (!chatThreads.success || !chatThreads.data) { - console.error(chatThreads.error); + const suggestions = await getQuerySuggestions(); + if (!suggestions.success || !suggestions.data) { + console.error(suggestions.error); return; } - setChatThreads(chatThreads.data.reverse().slice(0, 3)); + console.log(suggestions); + setSuggestions(suggestions.data.reverse().slice(0, 3)); })(); }, []); return ( <ul className="text-base list-none space-y-3 text-[#b9b9b9] mt-8"> - {!chatThreads_ && ( + {!suggestions && ( <> <Skeleton key="loader-1" @@ -40,17 +41,15 @@ const History = memo(() => { ></Skeleton> </> )} - {chatThreads_?.map((thread) => ( + {suggestions?.map((suggestion) => ( <motion.li initial={{ opacity: 0, filter: "blur(1px)" }} animate={{ opacity: 1, filter: "blur(0px)" }} - className="flex items-center gap-2 truncate" - key={thread.id} + className="flex items-center gap-2 truncate cursor-pointer" + key={suggestion} + onClick={() => setQuery(suggestion)} > - <ArrowLongRightIcon className="h-5" />{" "} - <Link prefetch={false} href={`/chat/${thread.id}`}> - {thread.firstMessage} - </Link> + <ArrowLongRightIcon className="h-5" /> {suggestion} </motion.li> ))} </ul> diff --git a/apps/web/app/(dash)/home/page.tsx b/apps/web/app/(dash)/home/page.tsx index ebd4d84b..f75a5ec7 100644 --- a/apps/web/app/(dash)/home/page.tsx +++ b/apps/web/app/(dash)/home/page.tsx @@ -4,12 +4,15 @@ import React, { useEffect, useState } from "react"; import QueryInput from "./queryinput"; import { getSessionAuthToken, getSpaces } from "@/app/actions/fetchers"; import { redirect, useRouter } from "next/navigation"; -import { createChatThread, linkTelegramToUser } from "@/app/actions/doers"; +import { + createChatThread, + getQuerySuggestions, + linkTelegramToUser, +} from "@/app/actions/doers"; import { toast } from "sonner"; import { motion } from "framer-motion"; import { ChromeIcon, GithubIcon, TwitterIcon } from "lucide-react"; import Link from "next/link"; -import { homeSearchParamsCache } from "@/lib/searchParams"; import History from "./history"; const slap = { @@ -28,26 +31,19 @@ const slap = { function Page({ searchParams }: { searchParams: Record<string, string> }) { // TODO: use this to show a welcome page/modal const firstTime = searchParams.firstTime === "true"; + const telegramUser = searchParams.telegramUser; + const extensionInstalled = searchParams.extension; + const [query, setQuery] = useState(searchParams.q || ""); - const query = searchParams.q || ""; + const [querySuggestions, setQuerySuggestions] = useState<string[]>([]); + const [spaces, setSpaces] = useState<{ id: number; name: string }[]>([]); + + const { push } = useRouter(); if (firstTime) { redirect("/onboarding"); } - const [queryPresent, setQueryPresent] = useState<boolean>(false); - - const [telegramUser, setTelegramUser] = useState<string | undefined>( - searchParams.telegramUser as string, - ); - const [extensionInstalled, setExtensionInstalled] = useState< - string | undefined - >(searchParams.extension as string); - - const { push } = useRouter(); - - const [spaces, setSpaces] = useState<{ id: number; name: string }[]>([]); - useEffect(() => { if (telegramUser) { const linkTelegram = async () => { @@ -101,8 +97,8 @@ function Page({ searchParams }: { searchParams: Record<string, string> }) { <div className="w-full pb-20 mt-10"> <QueryInput - initialQuery={query} - setQueryPresent={setQueryPresent} + query={query} + setQuery={setQuery} handleSubmit={async (q, spaces, proMode) => { if (q.length === 0) { toast.error("Query is required"); @@ -123,7 +119,7 @@ function Page({ searchParams }: { searchParams: Record<string, string> }) { initialSpaces={spaces} /> - <History /> + <History setQuery={setQuery} /> </div> <div className="w-full fixed bottom-0 left-0 p-4"> diff --git a/apps/web/app/(dash)/home/queryinput.tsx b/apps/web/app/(dash)/home/queryinput.tsx index 9f1e7292..82561438 100644 --- a/apps/web/app/(dash)/home/queryinput.tsx +++ b/apps/web/app/(dash)/home/queryinput.tsx @@ -8,26 +8,24 @@ import { Switch } from "@repo/ui/shadcn/switch"; import { Label } from "@repo/ui/shadcn/label"; function QueryInput({ - setQueryPresent, - initialQuery, initialSpaces, handleSubmit, + query, + setQuery, }: { - setQueryPresent: (t: boolean) => void; initialSpaces?: { id: number; name: string; }[]; - initialQuery?: string; mini?: boolean; handleSubmit: ( q: string, spaces: { id: number; name: string }[], proMode: boolean, ) => void; + query: string; + setQuery: (q: string) => void; }) { - const [q, setQ] = useState(initialQuery || ""); - const [proMode, setProMode] = useState(false); const [selectedSpaces, setSelectedSpaces] = useState< @@ -42,11 +40,11 @@ function QueryInput({ {/* input and action button */} <form action={async () => { - if (q.trim().length === 0) { + if (query.trim().length === 0) { return; } - handleSubmit(q, selectedSpaces, proMode); - setQ(""); + handleSubmit(query, selectedSpaces, proMode); + setQuery(""); }} > <textarea @@ -59,20 +57,15 @@ function QueryInput({ onKeyDown={(e) => { if (e.key === "Enter" && !e.shiftKey) { e.preventDefault(); - if (q.trim().length === 0) { + if (query.trim().length === 0) { return; } - handleSubmit(q, selectedSpaces, proMode); - setQ(""); + handleSubmit(query, selectedSpaces, proMode); + setQuery(""); } }} - onChange={(e) => - setQ((prev) => { - setQueryPresent(!!e.target.value.length); - return e.target.value; - }) - } - value={q} + onChange={(e) => setQuery(e.target.value)} + value={query} /> <div className="flex p-2 px-3 w-full items-center justify-between rounded-xl overflow-hidden"> <FilterSpaces diff --git a/apps/web/app/actions/doers.ts b/apps/web/app/actions/doers.ts index a2cdb4f5..0c1aa8d9 100644 --- a/apps/web/app/actions/doers.ts +++ b/apps/web/app/actions/doers.ts @@ -22,6 +22,8 @@ import { ChatHistory } from "@repo/shared-types"; import { decipher } from "@/server/encrypt"; import { redirect } from "next/navigation"; import { tweetToMd } from "@repo/shared-types/utils"; +import { ensureAuth } from "../api/ensureAuth"; +import { getRandomSentences } from "@/lib/utils"; export const createSpace = async ( input: string | FormData, @@ -708,3 +710,103 @@ export async function AddCanvasInfo({ }; } } + +export async function getQuerySuggestions() { + const data = await auth(); + + if (!data || !data.user || !data.user.id) { + redirect("/signin"); + return { error: "Not authenticated", success: false }; + } + + const recommendations = await process.env.RECOMMENDATIONS.get(data.user.id); + + if (recommendations) { + return { + success: true, + data: JSON.parse(recommendations), + }; + } + + // Randomly choose some storedContent of the user. + const content = await db + .select() + .from(storedContent) + .where(eq(storedContent.userId, data.user.id)) + .orderBy(sql`random()`) + .limit(3) + .all(); + + const fullQuery = content.map((c) => `${c.title} \n\n${c.content}`).join(" "); + + const sentences = getRandomSentences(fullQuery); + + const suggestionsCall = (await process.env.AI.run( + // @ts-ignore + "@cf/meta/llama-3.1-8b-instruct", + { + messages: [ + { + role: "system", + content: `You are a model that suggests questions based on the user's content.`, + }, + { + role: "user", + content: `Run the function based on this input: ${sentences}`, + }, + ], + tools: [ + { + type: "function", + function: { + name: "querySuggestions", + description: + "Take the user's content to suggest some good questions that they could ask.", + parameters: { + type: "object", + properties: { + querySuggestions: { + type: "array", + description: + "Short questions that the user can ask. Give atleast 3 suggestions. No more than 5.", + items: { + type: "string", + }, + }, + }, + required: ["querySuggestions"], + }, + }, + }, + ], + }, + )) as { + response: string; + tool_calls: { name: string; arguments: { querySuggestions: string[] } }[]; + }; + + const suggestions = + suggestionsCall.tool_calls?.[0]?.arguments?.querySuggestions; + + console.log(suggestions); + + if (!suggestions || suggestions.length === 0) { + return { + success: false, + error: "Failed to get query suggestions", + }; + } + + await process.env.RECOMMENDATIONS.put( + data.user.id, + JSON.stringify(suggestions), + { + expirationTtl: 60 * 5, + }, + ); + + return { + success: true, + data: suggestions, + }; +} diff --git a/apps/web/env.d.ts b/apps/web/env.d.ts index b6a410f9..c80ac0a4 100644 --- a/apps/web/env.d.ts +++ b/apps/web/env.d.ts @@ -6,4 +6,6 @@ interface CloudflareEnv { DATABASE: D1Database; DEV_IMAGES: R2Bucket; CANVAS_SNAPS: KVNamespace; + AI: Ai; + RECOMMENDATIONS: KVNamespace; } diff --git a/apps/web/lib/utils.ts b/apps/web/lib/utils.ts new file mode 100644 index 00000000..98ec6e98 --- /dev/null +++ b/apps/web/lib/utils.ts @@ -0,0 +1,23 @@ +export function getRandomSentences(fullQuery: string): string { + // Split the fullQuery into sentences + const sentences = fullQuery.match(/[^.!?]+[.!?]+/g) || []; + + // Function to get a random integer between min and max + function getRandomInt(min: number, max: number): number { + return Math.floor(Math.random() * (max - min)) + min; + } + + let selectedSentences = ""; + let totalCharacters = 0; + + // Select random sentences until totalCharacters is at least 1000 + while (totalCharacters < 1000 && sentences.length > 0) { + const randomIndex = getRandomInt(0, sentences.length); + const sentence = sentences[randomIndex]; + selectedSentences += sentence; + totalCharacters += sentence?.length || 0; + sentences.splice(randomIndex, 1); // Remove the selected sentence from the array + } + + return selectedSentences; +} diff --git a/apps/web/wrangler.toml b/apps/web/wrangler.toml index ce38285b..8f4c75db 100644 --- a/apps/web/wrangler.toml +++ b/apps/web/wrangler.toml @@ -19,6 +19,10 @@ database_id = "fc562605-157a-4f60-b439-2a24ffed5b4c" binding = "CANVAS_SNAPS" id = "c6446f7190dd4afebe1c318df3400518" +[[kv_namespaces]] +binding = "RECOMMENDATIONS" +id = "83bc7055226c4657948141c2ff9a5425" + [[env.production.d1_databases]] binding = "DATABASE" database_name = "prod-d1-supermemory" @@ -27,4 +31,7 @@ database_id = "f527a727-c472-41d4-8eaf-3d7ba0f2f395" [[env.preview.d1_databases]] binding = "DATABASE" database_name = "dev-d1-anycontext" -database_id = "fc562605-157a-4f60-b439-2a24ffed5b4c"
\ No newline at end of file +database_id = "fc562605-157a-4f60-b439-2a24ffed5b4c" + +[ai] +binding = "AI"
\ No newline at end of file |