aboutsummaryrefslogtreecommitdiff
path: root/apps/web
diff options
context:
space:
mode:
authorDhravya Shah <[email protected]>2024-08-06 11:05:02 -0700
committerDhravya Shah <[email protected]>2024-08-06 11:05:02 -0700
commitbe3e13b4bfb847af410f2159be462cc912141fc3 (patch)
treea3783a7e7798de60f599ea0f0a5db24c5c30395f /apps/web
parentchanges for prod (diff)
parentMerge pull request #219 from Deepakchowdavarapu/readme-issue (diff)
downloadsupermemory-be3e13b4bfb847af410f2159be462cc912141fc3.tar.xz
supermemory-be3e13b4bfb847af410f2159be462cc912141fc3.zip
merged latest changes with queue branch and ready for prod
Diffstat (limited to 'apps/web')
-rw-r--r--apps/web/app/(auth)/onboarding/page.tsx18
-rw-r--r--apps/web/app/(auth)/signin/page.tsx3
-rw-r--r--apps/web/app/(dash)/(memories)/content.tsx114
-rw-r--r--apps/web/app/(dash)/chat/chatWindow.tsx138
-rw-r--r--apps/web/app/(dash)/home/page.tsx2
-rw-r--r--apps/web/app/(dash)/home/queryinput.tsx4
-rw-r--r--apps/web/app/(dash)/menu.tsx53
-rw-r--r--apps/web/app/(thinkpad)/thinkpad/image.tsx2
-rw-r--r--apps/web/app/(thinkpad)/thinkpad/page.tsx4
-rw-r--r--apps/web/app/actions/doers.ts11
-rw-r--r--apps/web/app/api/store/helper.ts4
-rw-r--r--apps/web/app/api/store/route.ts39
-rw-r--r--apps/web/app/api/telegram/route.ts4
-rw-r--r--apps/web/app/ref/page.tsx118
-rw-r--r--apps/web/lib/useKeyPress.ts15
-rw-r--r--apps/web/package.json1
-rw-r--r--apps/web/server/auth.ts8
-rw-r--r--apps/web/tailwind.config.ts21
18 files changed, 319 insertions, 240 deletions
diff --git a/apps/web/app/(auth)/onboarding/page.tsx b/apps/web/app/(auth)/onboarding/page.tsx
index c311ea13..9a6ac481 100644
--- a/apps/web/app/(auth)/onboarding/page.tsx
+++ b/apps/web/app/(auth)/onboarding/page.tsx
@@ -392,6 +392,24 @@ function Navbar() {
const router = useRouter();
const handleSkip = async () => {
await completeOnboarding();
+ toast.info("Creating memory...", {
+ icon: <PlusCircleIcon className="w-4 h-4 text-white animate-spin" />,
+ duration: 7500,
+ });
+
+ const cont = await createMemory({
+ content: "https://supermemory.ai",
+ spaces: undefined,
+ });
+
+ if (cont.success) {
+ toast.success("Memory created", {
+ richColors: true,
+ });
+ } else {
+ toast.error(`Memory creation failed: ${cont.error}`);
+ }
+
router.push("/home?q=what%20is%20supermemory");
};
diff --git a/apps/web/app/(auth)/signin/page.tsx b/apps/web/app/(auth)/signin/page.tsx
index 3b563b90..35e6bab6 100644
--- a/apps/web/app/(auth)/signin/page.tsx
+++ b/apps/web/app/(auth)/signin/page.tsx
@@ -15,10 +15,11 @@ async function Signin({
}: {
searchParams: Record<string, string>;
}) {
+ const telegramUser = searchParams.telegramUser;
const user = await auth();
if (user) {
- redirect("/home");
+ redirect(`/home` + (telegramUser ? `?telegramUser=${telegramUser}` : ""));
}
return (
diff --git a/apps/web/app/(dash)/(memories)/content.tsx b/apps/web/app/(dash)/(memories)/content.tsx
index 431109be..6e2659cb 100644
--- a/apps/web/app/(dash)/(memories)/content.tsx
+++ b/apps/web/app/(dash)/(memories)/content.tsx
@@ -12,7 +12,7 @@ import {
} from "lucide-react";
import Image from "next/image";
import Link from "next/link";
-import React, { useEffect, useMemo, useState } from "react";
+import React, { useMemo, useState } from "react";
import Masonry from "react-layout-masonry";
import { getRawTweet } from "@repo/shared-types/utils";
import { MyTweet } from "../../../components/twitter/render-tweet";
@@ -20,16 +20,19 @@ import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuItem,
- DropdownMenuLabel,
DropdownMenuPortal,
- DropdownMenuSeparator,
DropdownMenuSub,
DropdownMenuSubContent,
DropdownMenuSubTrigger,
DropdownMenuTrigger,
} from "@repo/ui/shadcn/dropdown-menu";
import { Button } from "@repo/ui/shadcn/button";
-import { addUserToSpace, deleteItem, moveItem } from "@/app/actions/doers";
+import {
+ addUserToSpace,
+ deleteItem,
+ deleteSpace,
+ moveItem,
+} from "@/app/actions/doers";
import { toast } from "sonner";
import { Input } from "@repo/ui/shadcn/input";
import { motion } from "framer-motion";
@@ -59,6 +62,39 @@ export function MemoriesPage({
}, [tab]);
const [filter, setFilter] = useState(initialFilter);
+ const [spaces, setSpaces] = useState<StoredSpace[]>(memoriesAndSpaces.spaces);
+
+ // to delete a space
+ const handleDeleteSpace = async (id: number) => {
+ const response = await deleteSpace(id);
+
+ if (response?.success) {
+ setSpaces(spaces.filter((space) => space.id !== id));
+ toast.success("Space deleted");
+ } else {
+ toast.error("Failed to delete space");
+ }
+ };
+
+ const handleExport = () => {
+ const dataToExport = sortedItems.map((item) => ({
+ type: item.item,
+ date: new Date(item.date).toISOString(),
+ data: item.data,
+ }));
+
+ const json = JSON.stringify(dataToExport, null, 2);
+ const blob = new Blob([json], { type: "application/json" });
+ const url = URL.createObjectURL(blob);
+
+ const a = document.createElement("a");
+ a.href = url;
+ a.download = "memories_and_spaces.json";
+ document.body.appendChild(a);
+ a.click();
+ document.body.removeChild(a);
+ URL.revokeObjectURL(url);
+ };
// Sort Both memories and spaces by their savedAt and createdAt dates respectfully.
// The output should be just one single list of items
@@ -71,7 +107,7 @@ export function MemoriesPage({
date: new Date(memory.savedAt), // Assuming savedAt is a string date
data: memory,
})),
- ...memoriesAndSpaces.spaces.map((space) => ({
+ ...spaces.map((space) => ({
item: "space",
date: new Date(space.createdAt), // Assuming createdAt is a string date
data: space,
@@ -103,7 +139,7 @@ export function MemoriesPage({
return false;
})
.sort((a, b) => b.date - a.date);
- }, [memoriesAndSpaces.memories, memoriesAndSpaces.spaces, filter]);
+ }, [memoriesAndSpaces.memories, spaces, filter]);
return (
<div
@@ -172,13 +208,21 @@ export function MemoriesPage({
</div>
)}
- <Filters
- setFilter={setFilter}
- filter={filter}
- filterMethods={
- currentSpace ? SpaceFilterMethods : MemoriesFilterMethods
- }
- />
+ <div className="flex justify-between w-full">
+ <Filters
+ setFilter={setFilter}
+ filter={filter}
+ filterMethods={
+ currentSpace ? SpaceFilterMethods : MemoriesFilterMethods
+ }
+ />
+ <button
+ onClick={handleExport}
+ className={`transition px-6 py-2 rounded-xl hover:text-[#369DFD]" text-[#B3BCC5] bg-secondary hover:bg-secondary hover:text-[#76a3cc]`}
+ >
+ JSON Export
+ </button>
+ </div>
<Masonry
className="mt-6 relative"
@@ -216,6 +260,7 @@ export function MemoriesPage({
title={(item.data as StoredSpace).name}
description={`${(item.data as StoredSpace).numItems} memories`}
id={(item.data as StoredSpace).id}
+ handleDeleteSpace={handleDeleteSpace}
/>
);
}
@@ -231,34 +276,45 @@ function TabComponent({
title,
description,
id,
+ handleDeleteSpace,
}: {
title: string;
description: string;
id: number;
+ handleDeleteSpace: (id: number) => void;
}) {
return (
- <Link
- href={`/space/${id}`}
- className="flex flex-col gap-4 bg-[#161f2a]/30 backdrop-blur-md border-2 border-border w-full rounded-xl p-4"
- >
+ <div className="flex group flex-col gap-4 bg-[#161f2a]/30 backdrop-blur-md border-2 border-border w-full rounded-xl p-4">
<div className="flex items-center gap-2 text-xs">
<Image alt="Spaces icon" src={MemoriesIcon} className="size-3" /> Space
</div>
- <div className="flex items-center">
- <div>
- <div className="h-12 w-12 flex justify-center items-center rounded-md">
- {title.slice(0, 2).toUpperCase()} {id}
+
+ <div>
+ <Link
+ href={`/space/${id}`}
+ className="flex items-center justify-between w-full"
+ >
+ <div>
+ <div className="h-12 w-12 flex justify-center items-center rounded-md">
+ {title.slice(0, 2).toUpperCase()} {id}
+ </div>
</div>
- </div>
- <div className="grow px-4">
- <div className="text-lg text-[#fff] line-clamp-2">{title}</div>
- <div>{description}</div>
- </div>
- <div>
- <Image src={NextIcon} alt="Search icon" />
+ <div className="grow px-2">
+ <div className="text-lg text-[#fff] line-clamp-2">{title}</div>
+ <div>{description}</div>
+ </div>
+ <div>
+ <Image src={NextIcon} alt="Search icon" />
+ </div>
+ </Link>
+ <div className="absolute z-40 right-3 top-3 opacity-0 group-hover:opacity-100 hover:text-red-600">
+ <TrashIcon
+ onClick={() => handleDeleteSpace(id)}
+ className="w-4 cursor-pointer"
+ />
</div>
</div>
- </Link>
+ </div>
);
}
diff --git a/apps/web/app/(dash)/chat/chatWindow.tsx b/apps/web/app/(dash)/chat/chatWindow.tsx
index 3d7ca295..60acd16d 100644
--- a/apps/web/app/(dash)/chat/chatWindow.tsx
+++ b/apps/web/app/(dash)/chat/chatWindow.tsx
@@ -6,7 +6,7 @@ import QueryInput from "./chatQueryInput";
import { cn } from "@repo/ui/lib/utils";
import { motion } from "framer-motion";
import { useRouter } from "next/navigation";
-import { ChatHistory, sourcesZod } from "@repo/shared-types";
+import { type ChatHistory, sourcesZod } from "@repo/shared-types";
import {
Accordion,
AccordionContent,
@@ -23,7 +23,11 @@ import { codeLanguageSubset } from "@/lib/constants";
import { toast } from "sonner";
import Link from "next/link";
import { createChatObject } from "@/app/actions/doers";
-import { ClipboardIcon } from "@heroicons/react/24/outline";
+import {
+ ClipboardIcon,
+ SpeakerWaveIcon,
+ SpeakerXMarkIcon,
+} from "@heroicons/react/24/outline";
function ChatWindow({
q,
@@ -51,6 +55,8 @@ function ChatWindow({
}) {
const [layout, setLayout] = useState<"chat" | "initial">("chat");
const [chatHistory, setChatHistory] = useState<ChatHistory[]>(initialChat);
+ const [speakingIdx, setSpeakingIdx] = useState<number | null>(null);
+ const speechSynth: SpeechSynthesis = window.speechSynthesis;
const removeJustificationFromText = (text: string) => {
// remove everything after the first "<justification>" word
@@ -66,12 +72,31 @@ function ChatWindow({
return text;
};
+ const handleTTS = (text: string, idx: number) => {
+ if (speakingIdx != null) return stopTTS();
+ if (!text) return;
+ const utterThis: SpeechSynthesisUtterance = new SpeechSynthesisUtterance(
+ text,
+ );
+ utterThis.lang = "en-US";
+ speechSynth.speak(utterThis);
+ setSpeakingIdx(idx);
+ utterThis.onend = () => {
+ setSpeakingIdx(null);
+ };
+ };
+
+ const stopTTS = () => {
+ speechSynth.cancel();
+ setSpeakingIdx(null);
+ };
+
const router = useRouter();
const getAnswer = async (
query: string,
spaces: string[],
- proMode: boolean = false,
+ proMode = false,
) => {
if (query.trim() === "from_loading" || query.trim().length === 0) {
return;
@@ -263,10 +288,10 @@ function ChatWindow({
</div>
)}
- <div className="flex flex-col mt-2">
- <div>
+ <div className="flex flex-col mt-2 w-full">
+ <div className="w-full">
<div className="text-foreground-menu py-2">Answer</div>
- <div className="text-base">
+ <div className="text-base prose prose-invert prose-headings:py-0 prose-h1:py-4 prose-h2:py-4 prose-headings:-my-2 prose-h1:text-2xl prose-h2:text-xl prose:min-w-full min-w-full">
{/* Loading state */}
{(chat.answer.parts.length === 0 ||
chat.answer.parts.join("").length === 0) && (
@@ -291,11 +316,7 @@ function ChatWindow({
},
],
]}
- components={{
- code: code as any,
- p: p as any,
- }}
- className="flex flex-col gap-2 text-base"
+ className="flex flex-col gap-2 w-full"
>
{removeJustificationFromText(
chat.answer.parts
@@ -305,10 +326,6 @@ function ChatWindow({
</Markdown>
<div className="mt-3 relative -left-2 flex items-center gap-1">
- {/* TODO: speak response */}
- {/* <button className="group h-8 w-8 flex justify-center items-center active:scale-75 duration-200">
- <SpeakerWaveIcon className="size-[18px] group-hover:text-primary" />
- </button> */}
{/* copy response */}
<button
onClick={() =>
@@ -322,6 +339,27 @@ function ChatWindow({
>
<ClipboardIcon className="size-[18px] group-hover:text-primary" />
</button>
+ {/* speak response */}
+ <button
+ disabled={
+ speakingIdx !== null && speakingIdx !== idx
+ }
+ onClick={() => {
+ handleTTS(
+ chat.answer.parts
+ .map((part) => part.text)
+ .join(""),
+ idx,
+ );
+ }}
+ className="group h-8 w-8 flex justify-center items-center active:scale-75 duration-200"
+ >
+ {speakingIdx === idx ? (
+ <SpeakerXMarkIcon className="size-[18px] group-hover:text-primary" />
+ ) : (
+ <SpeakerWaveIcon className="size-[18px] group-hover:text-primary group-disabled:text-gray-600" />
+ )}
+ </button>
</div>
</div>
</div>
@@ -342,10 +380,10 @@ function ChatWindow({
</AccordionTrigger>
{/* TODO: fade out content on the right side, the fade goes away when the user scrolls */}
<AccordionContent
- className="flex flex-col no-scrollbar overflow-auto gap-4 relative max-w-3xl no-scrollbar"
+ className="flex flex-col gap-4 relative max-w-3xl overflow-x-auto scrollbar-thin scrollbar-thumb-scrollbar-thumb scrollbar-track-scrollbar-track scrollbar-thumb-rounded"
defaultChecked
>
- <div className="w-full no-scrollbar flex gap-4">
+ <div className="w-full flex gap-3">
{/* Loading state */}
{chat.answer.sources.length > 0 ||
(chat.answer.parts.length === 0 && (
@@ -373,7 +411,9 @@ function ChatWindow({
<span>{source.type}</span>
{source.numChunks > 1 && (
- <span>{source.numChunks} chunks</span>
+ <span className="font-bold">
+ {source.numChunks} chunks
+ </span>
)}
</div>
<div className="text-base">
@@ -388,40 +428,36 @@ function ChatWindow({
))}
</div>
- {chat.answer.justification &&
- chat.answer.justification.length && (
- <div
- className={`${chat.answer.justification && chat.answer.justification.length > 0 ? "flex" : "hidden"}`}
+ {chat.answer.justification?.length && (
+ <div
+ className={`${chat.answer.justification && chat.answer.justification.length > 0 ? "flex" : "hidden"}`}
+ >
+ <Accordion
+ defaultValue={""}
+ type="single"
+ collapsible
>
- <Accordion
- defaultValue={""}
- type="single"
- collapsible
- >
- <AccordionItem value="justification">
- <AccordionTrigger className="text-foreground-menu">
- Justification
- </AccordionTrigger>
- <AccordionContent
- className="relative flex gap-2 max-w-3xl overflow-auto no-scrollbar"
- defaultChecked
- >
- {chat.answer.justification.length > 0
- ? chat.answer.justification
- .replaceAll(
- "<justification>",
- "",
- )
- .replaceAll(
- "</justification>",
- "",
- )
- : "No justification provided."}
- </AccordionContent>
- </AccordionItem>
- </Accordion>
- </div>
- )}
+ <AccordionItem value="justification">
+ <AccordionTrigger className="text-foreground-menu">
+ Justification
+ </AccordionTrigger>
+ <AccordionContent
+ className="relative flex gap-2 max-w-3xl overflow-auto no-scrollbar"
+ defaultChecked
+ >
+ {chat.answer.justification.length > 0
+ ? chat.answer.justification
+ .replaceAll("<justification>", "")
+ .replaceAll(
+ "</justification>",
+ "",
+ )
+ : "No justification provided."}
+ </AccordionContent>
+ </AccordionItem>
+ </Accordion>
+ </div>
+ )}
</AccordionContent>
</AccordionItem>
</Accordion>
diff --git a/apps/web/app/(dash)/home/page.tsx b/apps/web/app/(dash)/home/page.tsx
index 0545d1ff..18bd934d 100644
--- a/apps/web/app/(dash)/home/page.tsx
+++ b/apps/web/app/(dash)/home/page.tsx
@@ -78,7 +78,7 @@ function Page({ searchParams }: { searchParams: Record<string, string> }) {
}}
>
<motion.h1
- className="text-center mx-auto bg-[linear-gradient(180deg,_#FFF_0%,_rgba(255,_255,_255,_0.00)_202.08%)] bg-clip-text text-4xl tracking-tighter text-transparent md:text-5xl"
+ className="text-center mx-auto bg-[linear-gradient(180deg,_#FFF_0%,_rgba(255,_255,_255,_0.00)_202.08%)] bg-clip-text text-4xl tracking-tighter text-transparent md:text-5xl pb-1"
animate={{
opacity: query.length ? 0 : 1,
translateY: query.length ? "10px" : "0px",
diff --git a/apps/web/app/(dash)/home/queryinput.tsx b/apps/web/app/(dash)/home/queryinput.tsx
index 995fea53..e7f400da 100644
--- a/apps/web/app/(dash)/home/queryinput.tsx
+++ b/apps/web/app/(dash)/home/queryinput.tsx
@@ -74,7 +74,7 @@ function QueryInput({
initialSpaces={initialSpaces || []}
/>
<div className="flex items-center gap-4">
- <div className="flex items-center gap-2 p-2 rounded-lg bg-[#369DFD1A]">
+ {/* <div className="flex items-center gap-2 p-2 rounded-lg bg-[#369DFD1A]">
<Label htmlFor="pro-mode" className="text-sm">
Pro mode
</Label>
@@ -84,7 +84,7 @@ function QueryInput({
id="pro-mode"
about="Pro mode"
/>
- </div>
+ </div> */}
<button type="submit" className="rounded-lg bg-[#369DFD1A] p-3">
<Image src={ArrowRightIcon} alt="Enter" />
</button>
diff --git a/apps/web/app/(dash)/menu.tsx b/apps/web/app/(dash)/menu.tsx
index 783a4780..1c0ce1ee 100644
--- a/apps/web/app/(dash)/menu.tsx
+++ b/apps/web/app/(dash)/menu.tsx
@@ -30,6 +30,7 @@ import { createMemory, createSpace } from "../actions/doers";
import ComboboxWithCreate from "@repo/ui/shadcn/combobox";
import { StoredSpace } from "@repo/db/schema";
import useMeasure from "react-use-measure";
+import { useKeyPress } from "@/lib/useKeyPress";
function Menu() {
const [spaces, setSpaces] = useState<StoredSpace[]>([]);
@@ -48,7 +49,11 @@ function Menu() {
setSpaces(spaces.data);
})();
}, []);
-
+ useKeyPress("a", () => {
+ if (!dialogOpen) {
+ setDialogOpen(true);
+ }
+ });
const menuItems = [
{
icon: HomeIconWeb,
@@ -106,33 +111,23 @@ function Menu() {
const handleSubmit = async (content?: string, spaces?: number[]) => {
setDialogOpen(false);
-
- toast.info("Creating memory...", {
- icon: <PlusCircleIcon className="w-4 h-4 text-white animate-spin" />,
- duration: 7500,
- });
-
if (!content || content.length === 0) {
- toast.error("Content is required");
- return;
+ throw new Error("Content is required");
}
-
- console.log(spaces);
-
const cont = await createMemory({
content: content,
spaces: spaces ?? undefined,
});
-
setContent("");
setSelectedSpaces([]);
-
if (cont.success) {
toast.success("Memory queued", {
richColors: true,
});
} else {
toast.error(`Memory creation failed: ${cont.error}`);
+ throw new Error(`Memory creation failed: ${cont.error}`);
+ return cont;
}
};
@@ -188,8 +183,17 @@ function Menu() {
<form
action={async (e: FormData) => {
const content = e.get("content")?.toString();
-
- await handleSubmit(content, selectedSpaces);
+ toast.promise(handleSubmit(content, selectedSpaces), {
+ loading: (
+ <span>
+ <PlusCircleIcon className="w-4 h-4 inline mr-2 text-white animate-spin" />{" "}
+ Creating memory...
+ </span>
+ ),
+ success: (data) => "Memory created",
+ error: (error) => error.message,
+ richColors: true,
+ });
}}
className="flex flex-col gap-4 "
>
@@ -213,7 +217,17 @@ function Menu() {
onKeyDown={(e) => {
if (e.key === "Enter" && !e.shiftKey) {
e.preventDefault();
- handleSubmit(content, selectedSpaces);
+ toast.promise(handleSubmit(content, selectedSpaces), {
+ loading: (
+ <span>
+ <PlusCircleIcon className="w-4 h-4 inline mr-2 text-white animate-spin" />{" "}
+ Creating memory...
+ </span>
+ ),
+ success: (data) => "Memory created",
+ error: (error) => error.message,
+ richColors: true,
+ });
}
}}
/>
@@ -266,10 +280,7 @@ function Menu() {
]);
setSelectedSpaces((prev) => [...prev, creationTask.data!]);
} else {
- toast.error(
- "Space creation failed: " + creationTask.error ??
- "Unknown error",
- );
+ toast.error("Space creation failed: " + creationTask.error);
}
}}
placeholder="Select or create a new space."
diff --git a/apps/web/app/(thinkpad)/thinkpad/image.tsx b/apps/web/app/(thinkpad)/thinkpad/image.tsx
index b55b9a81..7f3de2ff 100644
--- a/apps/web/app/(thinkpad)/thinkpad/image.tsx
+++ b/apps/web/app/(thinkpad)/thinkpad/image.tsx
@@ -39,7 +39,7 @@ const ImageComponent = memo(({ id }: { id: string }) => {
return (
<div className="w-full aspect-video bg-[#2C3439] flex justify-center items-center">
- Drew things to seee here
+ Draw things. They will appear here.
</div>
);
});
diff --git a/apps/web/app/(thinkpad)/thinkpad/page.tsx b/apps/web/app/(thinkpad)/thinkpad/page.tsx
index defa8e43..ab2bf88e 100644
--- a/apps/web/app/(thinkpad)/thinkpad/page.tsx
+++ b/apps/web/app/(thinkpad)/thinkpad/page.tsx
@@ -21,7 +21,7 @@ async function page() {
<BlurHeaderMenu />
- <div className="w-full flex py-20">
+ <div className="w-full flex py-20 justify-center">
{!canvas.success || canvas.error ? (
<div>Hmmm... Something went wrong. :/</div>
) : (
@@ -37,7 +37,7 @@ async function page() {
</div>
<h3 className="fixed left-1/2 -translate-x-1/2 bottom-4 text-gray-400 pt-20 text-center">
- *this is under beta and only one canvas is allowed per user
+ Thinkpads is under beta and only one thinkpad is allowed per user.
</h3>
</div>
);
diff --git a/apps/web/app/actions/doers.ts b/apps/web/app/actions/doers.ts
index 500a8608..c11d5f0a 100644
--- a/apps/web/app/actions/doers.ts
+++ b/apps/web/app/actions/doers.ts
@@ -167,6 +167,17 @@ const getTweetData = async (tweetID: string) => {
return data;
};
+export const deleteSpace = async (id: number) => {
+ try {
+ await db.delete(space).where(eq(space.id, id));
+ return {
+ success: true,
+ };
+ } catch (e) {
+ console.log(e);
+ }
+};
+
export const createMemory = async (input: {
content: string;
spaces?: number[];
diff --git a/apps/web/app/api/store/helper.ts b/apps/web/app/api/store/helper.ts
index 6ab7fb23..db13ca91 100644
--- a/apps/web/app/api/store/helper.ts
+++ b/apps/web/app/api/store/helper.ts
@@ -24,8 +24,8 @@ export const createMemoryFromAPI = async (input: {
method: "POST",
body: JSON.stringify({
pageContent: input.data.pageContent,
- title: input.data.title,
- description: input.data.description,
+ title: input.data.title.slice(0, 500),
+ description: input.data.description.slice(0, 500),
url: input.data.url,
spaces: input.data.spaces,
user: input.userId,
diff --git a/apps/web/app/api/store/route.ts b/apps/web/app/api/store/route.ts
index 12af7894..ad81c7c4 100644
--- a/apps/web/app/api/store/route.ts
+++ b/apps/web/app/api/store/route.ts
@@ -2,6 +2,7 @@ import { type NextRequest } from "next/server";
import { addFromAPIType } from "@repo/shared-types";
import { ensureAuth } from "../ensureAuth";
import { createMemoryFromAPI } from "./helper";
+import { getRawTweet } from "@repo/shared-types/utils";
export const runtime = "edge";
@@ -16,7 +17,43 @@ export async function POST(req: NextRequest) {
return new Response("Missing BACKEND_SECURITY_KEY", { status: 500 });
}
- const body = await req.json();
+ let body;
+
+ try {
+ body = await req.json();
+ } catch (e) {
+ const error = (e as Error).message;
+
+ console.log(error);
+
+ const tryJson = getRawTweet(await req.text());
+ console.log(tryJson);
+
+ if (tryJson) {
+ try {
+ body = JSON.parse(tryJson);
+ } catch (e) {
+ console.log(e);
+ return new Response(
+ JSON.stringify({
+ message: "Raw found but not json?" + error,
+ }),
+ {
+ status: 400,
+ },
+ );
+ }
+ } else {
+ return new Response(
+ JSON.stringify({
+ message: "Raw not found & not json." + error,
+ }),
+ {
+ status: 400,
+ },
+ );
+ }
+ }
const validated = addFromAPIType.safeParse(body);
diff --git a/apps/web/app/api/telegram/route.ts b/apps/web/app/api/telegram/route.ts
index dddfa2f4..c629e409 100644
--- a/apps/web/app/api/telegram/route.ts
+++ b/apps/web/app/api/telegram/route.ts
@@ -68,9 +68,9 @@ bot.on("message", async (ctx) => {
if (response.status !== 200) {
console.log("Failed to get response from backend");
console.log(response.status);
- console.log(await response.text());
await ctx.reply(
- "Sorry, I am not able to process your request at the moment.",
+ "Sorry, I am not able to process your request at the moment." +
+ (await response.text()),
);
return;
}
diff --git a/apps/web/app/ref/page.tsx b/apps/web/app/ref/page.tsx
deleted file mode 100644
index c582fe5c..00000000
--- a/apps/web/app/ref/page.tsx
+++ /dev/null
@@ -1,118 +0,0 @@
-import { Button } from "@repo/ui/shadcn/button";
-import { auth, signIn, signOut } from "../../server/auth";
-import { db } from "../../server/db";
-import { sql } from "drizzle-orm";
-import { users } from "@repo/db/schema";
-import { getThemeToggler } from "../../lib/get-theme-button";
-
-export const runtime = "edge";
-
-export default async function Page() {
- const usr = await auth();
-
- const userCount = await db
- .select({
- count: sql<number>`count(*)`.mapWith(Number),
- })
- .from(users);
-
- const SetThemeButton = getThemeToggler();
-
- return (
- <main className="flex flex-col items-center justify-center min-h-screen">
- <div className="flex max-w-2xl justify-between w-full">
- <SetThemeButton />
-
- <div className="flex gap-2 items-center justify-center">
- {" "}
- <svg
- viewBox="0 0 256 116"
- xmlns="http://www.w3.org/2000/svg"
- width="45px"
- height="45px"
- preserveAspectRatio="xMidYMid"
- >
- <path
- fill="#FFF"
- d="m202.357 49.394-5.311-2.124C172.085 103.434 72.786 69.289 66.81 85.997c-.996 11.286 54.227 2.146 93.706 4.059 12.039.583 18.076 9.671 12.964 24.484l10.069.031c11.615-36.209 48.683-17.73 50.232-29.68-2.545-7.857-42.601 0-31.425-35.497Z"
- />
- <path
- fill="#F4811F"
- d="M176.332 108.348c1.593-5.31 1.062-10.622-1.593-13.809-2.656-3.187-6.374-5.31-11.154-5.842L71.17 87.634c-.531 0-1.062-.53-1.593-.53-.531-.532-.531-1.063 0-1.594.531-1.062 1.062-1.594 2.124-1.594l92.946-1.062c11.154-.53 22.839-9.56 27.087-20.182l5.312-13.809c0-.532.531-1.063 0-1.594C191.203 20.182 166.772 0 138.091 0 111.535 0 88.697 16.995 80.73 40.896c-5.311-3.718-11.684-5.843-19.12-5.31-12.747 1.061-22.838 11.683-24.432 24.43-.531 3.187 0 6.374.532 9.56C16.996 70.107 0 87.103 0 108.348c0 2.124 0 3.718.531 5.842 0 1.063 1.062 1.594 1.594 1.594h170.489c1.062 0 2.125-.53 2.125-1.594l1.593-5.842Z"
- />
- <path
- fill="#FAAD3F"
- d="M205.544 48.863h-2.656c-.531 0-1.062.53-1.593 1.062l-3.718 12.747c-1.593 5.31-1.062 10.623 1.594 13.809 2.655 3.187 6.373 5.31 11.153 5.843l19.652 1.062c.53 0 1.062.53 1.593.53.53.532.53 1.063 0 1.594-.531 1.063-1.062 1.594-2.125 1.594l-20.182 1.062c-11.154.53-22.838 9.56-27.087 20.182l-1.063 4.78c-.531.532 0 1.594 1.063 1.594h70.108c1.062 0 1.593-.531 1.593-1.593 1.062-4.25 2.124-9.03 2.124-13.81 0-27.618-22.838-50.456-50.456-50.456"
- />
- </svg>
- <span className="italic">Cloudflare Next Saas Starter</span>
- </div>
-
- <div className="border border-black dark:border-white rounded-2xl p-2 flex items-center">
- Start by editing apps/web/page.tsx
- </div>
- </div>
-
- <div className="max-w-2xl text-start w-full mt-16">
- Welcome to Cloudflare Next Saas Starter. <br /> Built a full stack app
- using production-ready tools and frameworks, host on Cloudflare
- instantly.
- <br />
- An opinionated, batteries-included framework with{" "}
- <a
- className="text-transparent bg-clip-text bg-gradient-to-r from-[#a93d64] to-[#275ba9]"
- href="https://turbo.build"
- >
- Turborepo
- </a>{" "}
- and Nextjs. Fully Typesafe. Best practices followed by default.
- <br /> <br />
- Here's what the stack includes:
- <ul className="list-disc mt-4 prose dark:prose-invert">
- <li>
- Authentication with <code>next-auth</code>
- </li>
- <li>Database using Cloudflare's D1 serverless databases</li>
- <li>Drizzle ORM, already connected to your database and auth ⚡</li>
- <li>Light/darkmode theming that works with server components (!)</li>
- <li>Styling using TailwindCSS and ShadcnUI</li>
- <li>Turborepo with a landing page and shared components</li>
- <li>Cloudflare wrangler for quick functions on the edge</li>
- <li>
- ... best part: everything's already set up for you. Just code!
- </li>
- </ul>
- <div className="mt-4 flex flex-col gap-2">
- <span>Number of users in database: {userCount[0]!.count}</span>
- </div>
- {usr?.user?.email ? (
- <>
- <div className="mt-4 flex flex-col gap-2">
- <span>Hello {usr.user.name} 👋</span>
- <span>{usr.user.email}</span>
- </div>
- <form
- action={async () => {
- "use server";
- await signOut();
- }}
- >
- <Button variant={"destructive"} className="mt-4">
- Sign out
- </Button>
- </form>
- </>
- ) : (
- <form
- action={async () => {
- "use server";
- await signIn("google");
- }}
- >
- <Button className="mt-4">Login with Google</Button>
- </form>
- )}
- </div>
- </main>
- );
-}
diff --git a/apps/web/lib/useKeyPress.ts b/apps/web/lib/useKeyPress.ts
new file mode 100644
index 00000000..eee23acb
--- /dev/null
+++ b/apps/web/lib/useKeyPress.ts
@@ -0,0 +1,15 @@
+import { useEffect } from "react";
+
+export const useKeyPress = (key: string, callback: () => void) => {
+ useEffect(() => {
+ const handler = (e: KeyboardEvent) => {
+ if (e.key === key && e.altKey) {
+ callback();
+ }
+ };
+ window.addEventListener("keydown", handler);
+ return () => {
+ window.removeEventListener("keydown", handler);
+ };
+ }, [key, callback]);
+};
diff --git a/apps/web/package.json b/apps/web/package.json
index a5332157..d3bf1f48 100644
--- a/apps/web/package.json
+++ b/apps/web/package.json
@@ -21,6 +21,7 @@
"@radix-ui/react-popover": "^1.0.7",
"@radix-ui/react-slot": "^1.1.0",
"@sentry/nextjs": "^8",
+ "ai": "^3.3.0",
"clsx": "^2.1.1",
"cmdk": "^1.0.0",
"drizzle-orm": "0.30.0",
diff --git a/apps/web/server/auth.ts b/apps/web/server/auth.ts
index 5d8b15d1..645989fa 100644
--- a/apps/web/server/auth.ts
+++ b/apps/web/server/auth.ts
@@ -12,14 +12,6 @@ export const {
} = NextAuth({
secret: process.env.BACKEND_SECURITY_KEY,
trustHost: true,
- // callbacks: {
- // session: ({ session, token, user }) => ({
- // ...session,
- // user: {
- // ...session.user,
- // },
- // }),
- // },
adapter: DrizzleAdapter(db, {
usersTable: users,
accountsTable: accounts,
diff --git a/apps/web/tailwind.config.ts b/apps/web/tailwind.config.ts
index cf1434cf..2a05cd74 100644
--- a/apps/web/tailwind.config.ts
+++ b/apps/web/tailwind.config.ts
@@ -1 +1,20 @@
-module.exports = require("@repo/tailwind-config/tailwind.config");
+// Import the existing Tailwind config from your shared repository
+const sharedConfig = require("@repo/tailwind-config/tailwind.config");
+
+module.exports = {
+ presets: [sharedConfig],
+ theme: {
+ extend: {
+ colors: {
+ scrollbar: {
+ // thumb: "#d1d5db",
+ // thumbHover: "#1D4ED8",
+ thumb: "#303c4c",
+ thumbHover: "#2E3A48",
+ track: "#1F2937",
+ },
+ },
+ },
+ },
+ plugins: [require("tailwind-scrollbar")({ nocompatible: true })],
+};