added recommended items

This commit is contained in:
Dhravya Shah 2024-07-23 23:33:11 -05:00
parent f310781ad7
commit 568531f323
7 changed files with 177 additions and 55 deletions

View file

@ -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>

View file

@ -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">

View file

@ -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

View file

@ -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
View file

@ -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
View 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;
}

View file

@ -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"