diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 00000000..cc672fed --- /dev/null +++ b/.gitattributes @@ -0,0 +1 @@ +*.lockb binary diff=lockb \ No newline at end of file diff --git a/.vscode/settings.json b/.vscode/settings.json index 4bc75484..15394cb2 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -2,5 +2,6 @@ "css.validate": false, "editor.quickSuggestions": { "strings": true - } + }, + "typescript.tsdk": "node_modules/typescript/lib" } diff --git a/SETUP-GUIDE.md b/SETUP-GUIDE.md index 1ebff77e..72052931 100644 --- a/SETUP-GUIDE.md +++ b/SETUP-GUIDE.md @@ -1,81 +1,134 @@ -# Setup guide +# Self Hosting Guide + +This guide will help you set up your own instance of Supermemory. This is neccessary if you want to contribute to the project or if you want to self host the project. You can read more about the stack [here](https://github.com/supermemoryai/supermemory/?tab=readme-ov-file#-the-stack). ## Prerequisites - [bun](https://bun.sh/) - [turbo](https://turbo.build/repo/docs/installing) - [wrangler](https://developers.cloudflare.com/workers/cli-wrangler/install-update) +- [yarn](https://yarnpkg.com/getting-started/install): yarn is required to run scripts using turborepo. bun is not supported by turborepo yet vercel/turbo#4762 +- [Cloudflare Workers](https://developers.cloudflare.com/workers/platform/pricing/): You also need to have a paid Workers plan to use the vectorize feature which is needed run the AI backend. It is currently $5/mo + usage costs. +- [Cloudflare R2](https://developers.cloudflare.com/r2/): You need to enable R2 in the Cloudflare Dashboard for use in the web app. ## Steps 1. Clone the repo 2. Run `bun install` in the root directory -3. Create a `.dev.vars` file in `apps/web` with the following content: + +### web + +1. You need to create OAuth credentials for Google which is need for auth.js (nextauth). Visit https://developers.google.com/identity/protocols/oauth2 to learn more and https://console.cloud.google.com/apis/dashboard to create a new project and OAuth credentials. You need to set the redirect URL to `http://localhost:3000/api/auth/callback/google` for development. You can also set the redirect URL to your own domain if you are deploying the app. +2. Create a `.dev.vars` file in `apps/web` with the following content: ```bash -GOOGLE_CLIENT_ID="-" // required, visit https://developers.google.com/identity/protocols/oauth2 -GOOGLE_CLIENT_SECRET="-" // required -NEXTAUTH_SECRET='nextauthsecret' +GOOGLE_CLIENT_ID="" // required +GOOGLE_CLIENT_SECRET="" // required +NEXTAUTH_SECRET="" // generate by running `openssl rand -base64 32` DATABASE_URL='database.sqlite' NEXTAUTH_URL='http://localhost:3000' -BACKEND_SECURITY_KEY='veryrandomsecuritykey' -BACKEND_BASE_URL="where your backend is hosted" +BACKEND_SECURITY_KEY="" // used to authenticate with the backend. generate a random string using `openssl rand -base64 32` +BACKEND_BASE_URL="http://localhost:8686" ``` -4. Setup the database: +> [!NOTE] +> The `BACKEND_SECURITY_KEY` should be the same as the `SECURITY_KEY` in the `.dev.vars` file in `apps/cf-ai-backend`. -First, edit the `wrangler.toml` file in `apps/web` to point the d1 database to your account. +3. KV Namespaces -You can create a d1 database by running this command - -``` -bunx wrangler d1 create +```bash +bunx wrangler kv namespace create canvas-snaps ``` -And then replace database_name and database_id with the values +```bash +bunx wrangler kv namespace create recommendations +``` + +Do not change the binding value in the `wrangler.toml` but update the id for the namespaces with the values you get from the above commands. + +4. R2 Storage + +```bash +bunx wrangler r2 bucket create supermemory-r2 +``` + +Update bucket_name in the `wrangler.toml` file in `apps/web` to `supermemory-r2` + +5. D1 Database + +```bash +bunx wrangler d1 create supermemory-db-prod +``` + +Update the database_name and database_id in `[[env.production.d1_databases]]` with the values you get from the above command. + +```bash +bunx wrangler d1 create supermemory-db-preview +``` + +Update the database_name and database_id in `[[d1_databases]]` and `[[env.preview.d1_databases]]` with the values you get from the above command. > [!NOTE] > please don't change the binding value even if wrangler cli suggests you to do so. -``` +```bash [[d1_databases]] binding = "DATABASE" -database_name = "YOUR_DATABASE_NAME" +database_name = "supermemory-db-preview" database_id = "YOUR_DB_ID" ``` Simply run this command in `apps/web` -``` -bunx wrangler d1 migrations apply +```bash +bunx wrangler d1 migrations apply supermemory-db-preview ``` -If it runs, you can set up the cloud database as well by removing the `--local` flag, +If it runs, you can set up the cloud database as well by add the `--remote` flag, if you just want to contribute to frontend then just run `bun run dev` in the root of the project and done! (you won't be able to try ai stuff), otherwise continue... -5. You need to host your own worker for the `apps/cf-ai-backend` module. +### cf-ai-backend + +1. You need to host your own worker for the `apps/cf-ai-backend` module. To do this, first edit the `.dev.vars` file in `apps/cf-ai-backend` with the following content: ```bash -SECURITY_KEY="veryrandomsecuritykey" +SECURITY_KEY="veryrandomsecuritykey" // same as BACKEND_SECURITY_KEY in web // Why? to generate embeddings with 4000+ tokens OPENAI_API_KEY="sk-" ``` -6. Run this command to initialise vector database +2. Run this command to initialise vector database > Note: You need to use the workers paid plan to use vectorize for now. -``` -wrangler vectorize create --dimensions=1536 supermem-vector-1 --metric=cosine +```bash +bunx wrangler vectorize create --dimensions=1536 supermemory --metric=cosine ``` -7. Change the `wrangler.toml` file in `apps/cf-ai-backend` to point to your KV namespace +Update the index_name for `[[vectorize]]` in `wrangler.toml` file in `apps/cf-ai-backend` with the `supermemory` or the name you used in the above command. -8. Run `bun dev` in the root directory and Voila! You have your own supermemory instance running! +3. Create KV namespaces for the `cf-ai-backend` module -> Note: You need to replace the url `https://cf-ai-backend.dhr.wtf` everywhere with your own url for the cf-ai-backend module. +```bash +bunx wrangler kv namespace create prod +``` + +Update the id in `[[kv_namespaces]]` in the `wrangler.toml` file in `apps/cf-ai-backend` with the value you get from the above command. + +```bash +bunx wrangler kv namespace create preview +``` + +Update the preview_id in `[[kv_namespaces]]` in the `wrangler.toml` file in `apps/cf-ai-backend` with the value you get from the above command. + +## Local Development + +- Run `bun dev` in the root directory and Voila! You have your own supermemory instance running! + +> [!NOTE] +> It sometimes takes multiple tries to successfully run the `bun dev` command. If you encounter any issues, try running the command again. ## Deploying diff --git a/apps/cf-ai-backend/src/index.ts b/apps/cf-ai-backend/src/index.ts index cf6507c2..a3ac1380 100644 --- a/apps/cf-ai-backend/src/index.ts +++ b/apps/cf-ai-backend/src/index.ts @@ -69,43 +69,42 @@ app.get("/api/health", (c) => { }); app.post("/api/add", zValidator("json", vectorObj), async (c) => { - try{ - const body = c.req.valid("json"); + try { + const body = c.req.valid("json"); - const { store } = await initQuery(c); + const { store } = await initQuery(c); - console.log(body.spaces); - let chunks: TweetChunks | PageOrNoteChunks; - // remove everything in tags - const newPageContent = body.pageContent?.replace(/.*?<\/raw>/g, ""); + console.log(body.spaces); + let chunks: TweetChunks | PageOrNoteChunks; + // remove everything in tags + const newPageContent = body.pageContent?.replace(/.*?<\/raw>/g, ""); - switch (body.type) { - case "tweet": - chunks = chunkThread(newPageContent); - break; + switch (body.type) { + case "tweet": + chunks = chunkThread(newPageContent); + break; - case "page": - chunks = chunkPage(newPageContent); - break; + case "page": + chunks = chunkPage(newPageContent); + break; - case "note": - chunks = chunkNote(newPageContent); - break; + case "note": + chunks = chunkNote(newPageContent); + break; + } + + await batchCreateChunksAndEmbeddings({ + store, + body, + chunks: chunks, + context: c, + }); + + return c.json({ status: "ok" }); + } catch (error) { + console.error("Error processing request:", error); + return c.json({ status: "error", message: error.message }, 500); } - - - await batchCreateChunksAndEmbeddings({ - store, - body, - chunks: chunks, - context: c, - }); - - return c.json({ status: "ok" }); -}catch(error){ - console.error("Error processing request:", error); - return c.json({ status: "error", message: error.message }, 500); -} }); app.post( @@ -425,6 +424,7 @@ app.post( spaces: z.string().optional(), sourcesOnly: z.string().optional().default("false"), model: z.string().optional().default("gpt-4o"), + proMode: z.string().optional().default("false"), }), ), zValidator("json", chatObj), @@ -433,6 +433,7 @@ app.post( const body = c.req.valid("json"); const sourcesOnly = query.sourcesOnly === "true"; + const proMode = query.proMode === "true"; // Return early for dumb requests if (sourcesOnly && body.sources) { @@ -440,7 +441,6 @@ app.post( } const spaces = query.spaces?.split(",") ?? [undefined]; - console.log(spaces); // Get the AI model maker and vector store const { model, store } = await initQuery(c, query.model); @@ -449,14 +449,67 @@ app.post( const filter: VectorizeVectorMetadataFilter = { [`user-${query.user}`]: 1, }; - console.log("Spaces", spaces); + + let proModeListedQueries: string[] = []; + + if (proMode) { + const addedToQuery = (await c.env.AI.run( + // @ts-ignore + "@hf/nousresearch/hermes-2-pro-mistral-7b", + { + messages: [ + { + role: "system", + content: + "You are a query enhancer. You must enhance a user's query to make it more relevant to what the user might be looking for. If there's any mention of dates like 'last summer' or 'this year', you should return 'DAY: X, MONTH: Y, YEAR: Z'. If there's any mention of locations, add that to the query too. Try to keep your responses as short as possible. Add to the user's query, don't replace it. Make sure to keep your answers short.", + }, + { role: "user", content: query.query }, + ], + tools: [ + { + type: "function", + function: { + name: "Enhance query get list", + description: + "Enhance the user's query to make it more relevant", + parameters: { + type: "object", + properties: { + listedQueries: { + type: "array", + description: "List of queries that the user has asked", + items: { + type: "string", + }, + }, + }, + required: ["Enhance query get list"], + }, + }, + }, + ], + max_tokens: 200, + }, + )) as { + response?: string; + tool_calls?: { + name: string; + arguments: { + listedQueries: string[]; + }; + }[]; + }; + + proModeListedQueries = + addedToQuery.tool_calls?.[0]?.arguments?.listedQueries ?? []; + } // Converting the query to a vector so that we can search for similar vectors - const queryAsVector = await store.embeddings.embedQuery(query.query); + const queryAsVector = await store.embeddings.embedQuery( + query.query + " " + proModeListedQueries.join(" "), + ); const responses: VectorizeMatches = { matches: [], count: 0 }; - console.log("hello world", spaces); - // SLICED to 5 to avoid too many queries for (const space of spaces.slice(0, 5)) { if (space && space.length >= 1) { @@ -523,7 +576,13 @@ app.post( ); const metadata = normalizedData.map((datapoint) => datapoint.metadata); - return c.json({ ids: storedContent, metadata, normalizedData }); + + return c.json({ + ids: storedContent, + metadata, + normalizedData, + proModeListedQueries, + }); } } diff --git a/apps/cf-ai-backend/src/utils/OpenAIEmbedder.ts b/apps/cf-ai-backend/src/utils/OpenAIEmbedder.ts index 4a6ff593..a151afc0 100644 --- a/apps/cf-ai-backend/src/utils/OpenAIEmbedder.ts +++ b/apps/cf-ai-backend/src/utils/OpenAIEmbedder.ts @@ -50,6 +50,7 @@ export class OpenAIEmbeddings { const json = zodTypeExpected.safeParse(data); if (!json.success) { + console.log(JSON.stringify(data)); throw new Error("Invalid response from OpenAI: " + json.error.message); } diff --git a/apps/extension/manifest.json b/apps/extension/manifest.json index 0edcd320..7f998412 100644 --- a/apps/extension/manifest.json +++ b/apps/extension/manifest.json @@ -1,7 +1,7 @@ { "manifest_version": 3, "$schema": "https://json.schemastore.org/chrome-manifest", - "version": "2.63", + "version": "2.63.1", "name": "supermemory", "description": "An extension for https://supermemory.ai - an AI hub for all your bookmarks.", "background": { diff --git a/apps/web/app/(auth)/onboarding/page.tsx b/apps/web/app/(auth)/onboarding/page.tsx index 1912a8e8..10dd2325 100644 --- a/apps/web/app/(auth)/onboarding/page.tsx +++ b/apps/web/app/(auth)/onboarding/page.tsx @@ -1,17 +1,16 @@ "use client"; -import Link from "next/link"; import { + ArrowUturnDownIcon, ChevronLeftIcon, ChevronRightIcon, QuestionMarkCircleIcon, - ArrowTurnDownLeftIcon, } from "@heroicons/react/24/solid"; import { CheckIcon, PlusCircleIcon } from "@heroicons/react/24/outline"; import { motion } from "framer-motion"; -import { useState } from "react"; +import { useEffect, useState } from "react"; import { toast } from "sonner"; -import { createMemory } from "@repo/web/app/actions/doers"; +import { completeOnboarding, createMemory } from "@repo/web/app/actions/doers"; import { useRouter } from "next/navigation"; import Logo from "../../../public/logo.svg"; import Image from "next/image"; @@ -20,6 +19,18 @@ import gradientStyle from "../signin/_components/TextGradient/gradient.module.cs export default function Home() { const [currStep, setCurrStep] = useState(0); + const { push } = useRouter(); + + useEffect(() => { + const updateDb = async () => { + await completeOnboarding(); + }; + if (currStep > 3) { + updateDb().then(() => { + push("/home?q=what%20is%20supermemory"); + }); + } + }, [currStep]); return (
@@ -175,7 +186,7 @@ function StepIndicator({ />

Step: {currStep}/3

currStep <= 3 && setCurrStep(currStep + 1)} /> @@ -300,7 +311,7 @@ function StepThree({ currStep }: { currStep: number }) { type="submit" className="rounded-lg bg-[#369DFD1A] p-3 absolute bottom-4 right-2" > - + @@ -374,6 +385,12 @@ function StepTwo({ currStep }: { currStep: number }) { } function Navbar() { + const router = useRouter(); + const handleSkip = async () => { + await completeOnboarding(); + router.push("/home?q=what%20is%20supermemory"); + }; + return (
- - - +
); } diff --git a/apps/web/app/(auth)/signin/page.tsx b/apps/web/app/(auth)/signin/page.tsx index a31343cd..3b563b90 100644 --- a/apps/web/app/(auth)/signin/page.tsx +++ b/apps/web/app/(auth)/signin/page.tsx @@ -18,7 +18,7 @@ async function Signin({ const user = await auth(); if (user) { - await redirect("/home"); + redirect("/home"); } return ( @@ -64,7 +64,7 @@ async function Signin({ action={async () => { "use server"; await signIn("google", { - redirectTo: "/home?firstTime=true", + redirectTo: "/home", }); }} > diff --git a/apps/web/app/(dash)/chat/[chatid]/loading.tsx b/apps/web/app/(dash)/chat/[chatid]/loading.tsx index d28961a6..422adb8e 100644 --- a/apps/web/app/(dash)/chat/[chatid]/loading.tsx +++ b/apps/web/app/(dash)/chat/[chatid]/loading.tsx @@ -7,9 +7,15 @@ async function Page({ }: { searchParams: Record; }) { - const q = (searchParams?.q as string) ?? "from_loading"; + const q = (searchParams?.q as string) ?? ""; return ( - + ); } diff --git a/apps/web/app/(dash)/chat/[chatid]/page.tsx b/apps/web/app/(dash)/chat/[chatid]/page.tsx index 87fd0b19..29ffb3d8 100644 --- a/apps/web/app/(dash)/chat/[chatid]/page.tsx +++ b/apps/web/app/(dash)/chat/[chatid]/page.tsx @@ -9,7 +9,8 @@ async function Page({ params: { chatid: string }; searchParams: Record; }) { - const { firstTime, q, spaces } = chatSearchParamsCache.parse(searchParams); + const { firstTime, q, spaces, proMode } = + chatSearchParamsCache.parse(searchParams); let chat: Awaited>; @@ -31,6 +32,7 @@ async function Page({ spaces={spaces ?? []} initialChat={chat.data.length > 0 ? chat.data : undefined} threadId={params.chatid} + proMode={proMode} /> ); } diff --git a/apps/web/app/(dash)/chat/chatWindow.tsx b/apps/web/app/(dash)/chat/chatWindow.tsx index 28b99c9d..ed65bf7a 100644 --- a/apps/web/app/(dash)/chat/chatWindow.tsx +++ b/apps/web/app/(dash)/chat/chatWindow.tsx @@ -35,14 +35,19 @@ function ChatWindow({ parts: [], sources: [], }, + proModeProcessing: { + queries: [], + }, }, ], threadId, + proMode, }: { q: string; spaces: { id: number; name: string }[]; initialChat?: ChatHistory[]; threadId: string; + proMode: boolean; }) { const [layout, setLayout] = useState<"chat" | "initial">("chat"); const [chatHistory, setChatHistory] = useState(initialChat); @@ -63,13 +68,17 @@ function ChatWindow({ const router = useRouter(); - const getAnswer = async (query: string, spaces: string[]) => { + const getAnswer = async ( + query: string, + spaces: string[], + proMode: boolean = false, + ) => { if (query.trim() === "from_loading" || query.trim().length === 0) { return; } const sourcesFetch = await fetch( - `/api/chat?q=${query}&spaces=${spaces}&sourcesOnly=true&threadId=${threadId}`, + `/api/chat?q=${query}&spaces=${spaces}&sourcesOnly=true&threadId=${threadId}&proMode=${proMode}`, { method: "POST", body: JSON.stringify({ chatHistory }), @@ -91,6 +100,8 @@ function ChatWindow({ behavior: "smooth", }); + let proModeListedQueries: string[] = []; + const updateChatHistoryAndFetch = async () => { // Step 1: Update chat history with the assistant's response await new Promise((resolve) => { @@ -123,6 +134,11 @@ function ChatWindow({ ).length, })); + lastAnswer.proModeProcessing.queries = + sourcesParsed.data.proModeListedQueries ?? []; + + proModeListedQueries = lastAnswer.proModeProcessing.queries; + resolve(newChatHistory); return newChatHistory; }); @@ -130,7 +146,7 @@ function ChatWindow({ // Step 2: Fetch data from the API const resp = await fetch( - `/api/chat?q=${query}&spaces=${spaces}&threadId=${threadId}`, + `/api/chat?q=${(query += proModeListedQueries.join(" "))}&spaces=${spaces}&threadId=${threadId}`, { method: "POST", body: JSON.stringify({ chatHistory, sources: sourcesParsed.data }), @@ -181,6 +197,7 @@ function ChatWindow({ getAnswer( q, spaces.map((s) => `${s.id}`), + proMode, ); } } else { @@ -224,6 +241,28 @@ function ChatWindow({ {chat.question} + {chat.proModeProcessing?.queries?.length > 0 && ( +
+
+ Pro Mode +
+
+
+ {chat.proModeProcessing.queries.map( + (query, idx) => ( +
+ {query} +
+ ), + )} +
+
+
+ )} +
Answer
@@ -407,6 +446,9 @@ function ChatWindow({ parts: [], sources: [], }, + proModeProcessing: { + queries: [], + }, }, ]; }); diff --git a/apps/web/app/(dash)/chat/route.ts b/apps/web/app/(dash)/chat/route.ts new file mode 100644 index 00000000..94f250ff --- /dev/null +++ b/apps/web/app/(dash)/chat/route.ts @@ -0,0 +1,5 @@ +import { redirect } from "next/navigation"; + +export async function GET() { + return redirect("/home"); +} diff --git a/apps/web/app/(dash)/dialogContentContainer.tsx b/apps/web/app/(dash)/dialogContentContainer.tsx index aae71237..4e8d81ef 100644 --- a/apps/web/app/(dash)/dialogContentContainer.tsx +++ b/apps/web/app/(dash)/dialogContentContainer.tsx @@ -100,7 +100,7 @@ export function DialogContentContainer({ }, []); return ( - +
{ const content = e.get("content")?.toString(); diff --git a/apps/web/app/(dash)/header/autoBreadCrumbs.tsx b/apps/web/app/(dash)/header/autoBreadCrumbs.tsx index a823671c..671464ff 100644 --- a/apps/web/app/(dash)/header/autoBreadCrumbs.tsx +++ b/apps/web/app/(dash)/header/autoBreadCrumbs.tsx @@ -13,8 +13,6 @@ import React from "react"; function AutoBreadCrumbs() { const pathname = usePathname(); - console.log(pathname.split("/").filter(Boolean)); - return ( @@ -31,7 +29,7 @@ function AutoBreadCrumbs() { .filter(Boolean) .map((path, idx, paths) => ( <> - + {path.charAt(0).toUpperCase() + path.slice(1)} diff --git a/apps/web/app/(dash)/header/header.tsx b/apps/web/app/(dash)/header/header.tsx index b9d400c9..eaade258 100644 --- a/apps/web/app/(dash)/header/header.tsx +++ b/apps/web/app/(dash)/header/header.tsx @@ -6,6 +6,14 @@ import Logo from "../../../public/logo.svg"; import { getChatHistory } from "../../actions/fetchers"; import NewChatButton from "./newChatButton"; import AutoBreadCrumbs from "./autoBreadCrumbs"; +import SignOutButton from "./signOutButton"; +import { + DropdownMenu, + DropdownMenuContent, + DropdownMenuItem, + DropdownMenuTrigger, +} from "@repo/ui/shadcn/dropdown-menu"; +import { CaretDownIcon } from "@radix-ui/react-icons"; async function Header() { const chatThreads = await getChatHistory(); @@ -32,26 +40,28 @@ async function Header() {
-
- - -
-
- {chatThreads.data.map((thread) => ( + + + + {chatThreads.data.map((thread) => ( + {thread.firstMessage} - ))} -
-
-
+ + ))} + + + +
diff --git a/apps/web/app/(dash)/header/signOutButton.tsx b/apps/web/app/(dash)/header/signOutButton.tsx new file mode 100644 index 00000000..4c61c74d --- /dev/null +++ b/apps/web/app/(dash)/header/signOutButton.tsx @@ -0,0 +1,22 @@ +import { signOut } from "@/server/auth"; +import { Button } from "@repo/ui/shadcn/button"; + +export default function SignOutButton() { + return ( + { + "use server"; + await signOut(); + }} + > + + + ); +} diff --git a/apps/web/app/(dash)/home/history.tsx b/apps/web/app/(dash)/home/history.tsx index 3d8d5a28..a4cd11d0 100644 --- a/apps/web/app/(dash)/home/history.tsx +++ b/apps/web/app/(dash)/home/history.tsx @@ -1,53 +1,65 @@ -import { getChatHistory } from '@repo/web/app/actions/fetchers'; -import { ArrowLongRightIcon } from '@heroicons/react/24/outline'; -import { Skeleton } from '@repo/ui/shadcn/skeleton'; -import Link from 'next/link'; -import { memo, useEffect, useState } from 'react'; -import { motion } from 'framer-motion'; -import { chatThreads } from '@/server/db/schema'; +import { ArrowLongRightIcon } from "@heroicons/react/24/outline"; +import { Skeleton } from "@repo/ui/shadcn/skeleton"; +import { memo, useEffect, useState } from "react"; +import { motion } from "framer-motion"; +import { getQuerySuggestions } from "@/app/actions/doers"; -const History = memo(() => { - const [chatThreads_, setChatThreads] = useState< - (typeof chatThreads.$inferSelect)[] | null - >(null); +const History = memo(({ setQuery }: { setQuery: (q: string) => void }) => { + const [suggestions, setSuggestions] = useState(null); - useEffect(() => { - (async () => { - const chatThreads = await getChatHistory(); - if (!chatThreads.success || !chatThreads.data) { - console.error(chatThreads.error); - return; - } - setChatThreads(chatThreads.data.reverse().slice(0, 3)); - })(); - }, []); + useEffect(() => { + (async () => { + const suggestions = await getQuerySuggestions(); + if (!suggestions.success || !suggestions.data) { + console.error(suggestions.error); + setSuggestions([]); + return; + } + console.log(suggestions); + if (typeof suggestions.data === "string") { + const queries = suggestions.data.slice(1, -1).split(", "); + const parsedQueries = queries.map((query) => + query.replace(/^'|'$/g, ""), + ); + console.log(parsedQueries); + setSuggestions(parsedQueries); + return; + } + setSuggestions(suggestions.data.reverse().slice(0, 3)); + })(); + }, []); - if (!chatThreads) { - return ( - <> - - - - - ); - } - - return ( -
    - {chatThreads_?.map((thread) => ( - - {' '} - - {thread.firstMessage} - - - ))} -
- ); + return ( +
    + {!suggestions && ( + <> + + + + + )} + {suggestions?.map((suggestion) => ( + setQuery(suggestion)} + > + {suggestion} + + ))} +
+ ); }); export default History; diff --git a/apps/web/app/(dash)/home/page.tsx b/apps/web/app/(dash)/home/page.tsx index cc1856b4..d192d07d 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 { ChromeIcon, GithubIcon, MailIcon, TwitterIcon } from "lucide-react"; import Link from "next/link"; -import { homeSearchParamsCache } from "@/lib/searchParams"; import History from "./history"; const slap = { @@ -26,28 +29,14 @@ const slap = { }; function Page({ searchParams }: { searchParams: Record }) { - // TODO: use this to show a welcome page/modal - const firstTime = searchParams.firstTime === "true"; - - const query = searchParams.q || ""; - - if (firstTime) { - redirect("/onboarding"); - } - - const [queryPresent, setQueryPresent] = useState(false); - - const [telegramUser, setTelegramUser] = useState( - searchParams.telegramUser as string, - ); - const [extensionInstalled, setExtensionInstalled] = useState< - string | undefined - >(searchParams.extension as string); - - const { push } = useRouter(); + const telegramUser = searchParams.telegramUser; + const extensionInstalled = searchParams.extension; + const [query, setQuery] = useState(searchParams.q || ""); const [spaces, setSpaces] = useState<{ id: number; name: string }[]>([]); + const { push } = useRouter(); + useEffect(() => { if (telegramUser) { const linkTelegram = async () => { @@ -63,10 +52,6 @@ function Page({ searchParams }: { searchParams: Record }) { linkTelegram(); } - if (extensionInstalled) { - toast.success("Extension installed successfully"); - } - getSpaces().then((res) => { if (res.success && res.data) { setSpaces(res.data); @@ -77,21 +62,21 @@ function Page({ searchParams }: { searchParams: Record }) { getSessionAuthToken().then((token) => { if (typeof window === "undefined") return; + if (extensionInstalled) { + toast.success("Extension installed successfully"); + } window.postMessage({ token: token.data }, "*"); }); }, [telegramUser]); return (
- {/* all content goes here */} - {/*
hi {firstTime ? 'first time' : ''}
*/} - Ask your{" "} @@ -99,17 +84,16 @@ function Page({ searchParams }: { searchParams: Record }) { -
+
{ + query={query} + setQuery={setQuery} + handleSubmit={async (q, spaces, proMode) => { if (q.length === 0) { toast.error("Query is required"); return; } - console.log("creating thread"); const threadid = await createChatThread(q); if (!threadid.success || !threadid.data) { @@ -117,15 +101,14 @@ function Page({ searchParams }: { searchParams: Record }) { return; } - console.log("pushing to chat"); push( - `/chat/${threadid.data}?spaces=${JSON.stringify(spaces)}&q=${q}`, + `/chat/${threadid.data}?spaces=${JSON.stringify(spaces)}&q=${q}&proMode=${proMode}`, ); }} initialSpaces={spaces} /> - +
@@ -140,12 +123,12 @@ function Page({ searchParams }: { searchParams: Record }) { Install extension - + Bug report void; initialSpaces?: { id: number; name: string; }[]; - initialQuery?: string; mini?: boolean; - handleSubmit: (q: string, spaces: { id: number; name: string }[]) => void; + 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< { id: number; name: string }[] @@ -34,11 +40,11 @@ function QueryInput({ {/* input and action button */}
{ - if (q.trim().length === 0) { + if (query.trim().length === 0) { return; } - handleSubmit(q, selectedSpaces); - setQ(""); + handleSubmit(query, selectedSpaces, proMode); + setQuery(""); }} >