mirror of
https://github.com/supermemoryai/supermemory.git
synced 2026-09-08 22:21:07 +00:00
added recommended items
This commit is contained in:
parent
f310781ad7
commit
568531f323
7 changed files with 177 additions and 55 deletions
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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">
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
};
|
||||
}
|
||||
|
|
|
|||
2
apps/web/env.d.ts
vendored
2
apps/web/env.d.ts
vendored
|
|
@ -6,4 +6,6 @@ interface CloudflareEnv {
|
|||
DATABASE: D1Database;
|
||||
DEV_IMAGES: R2Bucket;
|
||||
CANVAS_SNAPS: KVNamespace;
|
||||
AI: Ai;
|
||||
RECOMMENDATIONS: KVNamespace;
|
||||
}
|
||||
|
|
|
|||
23
apps/web/lib/utils.ts
Normal file
23
apps/web/lib/utils.ts
Normal file
|
|
@ -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;
|
||||
}
|
||||
|
|
@ -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"
|
||||
database_id = "fc562605-157a-4f60-b439-2a24ffed5b4c"
|
||||
|
||||
[ai]
|
||||
binding = "AI"
|
||||
Loading…
Add table
Reference in a new issue