mirror of
https://github.com/supermemoryai/supermemory.git
synced 2026-08-28 05:25:33 +00:00
added top bar nav
This commit is contained in:
parent
5fe0caabf8
commit
0a282da5b1
9 changed files with 125 additions and 35 deletions
48
apps/web/app/(dash)/actions.ts
Normal file
48
apps/web/app/(dash)/actions.ts
Normal file
|
|
@ -0,0 +1,48 @@
|
|||
"use server";
|
||||
|
||||
import { cookies, headers } from "next/headers";
|
||||
import { db } from "../helpers/server/db";
|
||||
import { sessions, users, space } from "../helpers/server/db/schema";
|
||||
import { eq } from "drizzle-orm";
|
||||
import { redirect } from "next/navigation";
|
||||
|
||||
export async function ensureAuth() {
|
||||
const token =
|
||||
cookies().get("next-auth.session-token")?.value ??
|
||||
cookies().get("__Secure-authjs.session-token")?.value ??
|
||||
cookies().get("authjs.session-token")?.value ??
|
||||
headers().get("Authorization")?.replace("Bearer ", "");
|
||||
|
||||
if (!token) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const sessionData = await db
|
||||
.select()
|
||||
.from(sessions)
|
||||
.innerJoin(users, eq(users.id, sessions.userId))
|
||||
.where(eq(sessions.sessionToken, token));
|
||||
|
||||
if (!sessionData || sessionData.length < 0) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
return {
|
||||
user: sessionData[0]!.user,
|
||||
session: sessionData[0]!,
|
||||
};
|
||||
}
|
||||
|
||||
export async function getSpaces() {
|
||||
const data = await ensureAuth();
|
||||
if (!data) {
|
||||
redirect("/signin");
|
||||
}
|
||||
|
||||
const sp = await db
|
||||
.select()
|
||||
.from(space)
|
||||
.where(eq(space.user, data.user.email));
|
||||
|
||||
return sp;
|
||||
}
|
||||
|
|
@ -7,7 +7,7 @@ import { cn } from "@repo/ui/lib/utils";
|
|||
import { motion } from "framer-motion";
|
||||
import { useRouter } from "next/navigation";
|
||||
|
||||
function ChatWindow({ q, spaces }: { q: string; spaces: number[] }) {
|
||||
function ChatWindow({ q }: { q: string }) {
|
||||
const [layout, setLayout] = useState<"chat" | "initial">("initial");
|
||||
|
||||
const router = useRouter();
|
||||
|
|
@ -31,11 +31,7 @@ function ChatWindow({ q, spaces }: { q: string; spaces: number[] }) {
|
|||
className="max-w-3xl flex mx-auto w-full flex-col"
|
||||
>
|
||||
<div className="w-full h-96">
|
||||
<QueryInput
|
||||
initialQuery={q}
|
||||
initialSpaces={spaces ?? []}
|
||||
disabled
|
||||
/>
|
||||
<QueryInput initialQuery={q} initialSpaces={[]} disabled />
|
||||
</div>
|
||||
</motion.div>
|
||||
) : (
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import { chatSearchParamsCache } from "../../helpers/lib/searchParams";
|
||||
import ChatWindow from "./chatWindow";
|
||||
import { chatSearchParamsCache } from "../../helpers/lib/searchParams";
|
||||
|
||||
function Page({
|
||||
searchParams,
|
||||
|
|
@ -8,7 +8,9 @@ function Page({
|
|||
}) {
|
||||
const { firstTime, q, spaces } = chatSearchParamsCache.parse(searchParams);
|
||||
|
||||
return <ChatWindow q={q} spaces={spaces ?? []} />;
|
||||
console.log(spaces);
|
||||
|
||||
return <ChatWindow q={q} />;
|
||||
}
|
||||
|
||||
export default Page;
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ import Image from "next/image";
|
|||
import Link from "next/link";
|
||||
import Logo from "../../public/logo.svg";
|
||||
import { AddIcon, ChatIcon } from "@repo/ui/icons";
|
||||
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@repo/ui/shadcn/tabs";
|
||||
|
||||
function Header() {
|
||||
return (
|
||||
|
|
@ -16,11 +17,32 @@ function Header() {
|
|||
/>
|
||||
</Link>
|
||||
|
||||
<div className="absolute flex justify-center w-full -z-10">
|
||||
<button className="bg-secondary all-center h-11 rounded-full p-2 min-w-14">
|
||||
<Image src={AddIcon} alt="Add icon" />
|
||||
</button>
|
||||
</div>
|
||||
<Tabs
|
||||
className="absolute flex flex-col justify-center items-center w-full -z-10 group top-0 transition-transform duration-1000 ease-out"
|
||||
defaultValue="account"
|
||||
>
|
||||
<div className="bg-secondary all-center h-11 rounded-full p-2 min-w-14">
|
||||
<button className="p-2 group-hover:hidden transition duration-500 ease-in-out">
|
||||
<Image src={AddIcon} alt="Add icon" />
|
||||
</button>
|
||||
|
||||
<div className="hidden group-hover:flex inset-0 transition-opacity duration-500 ease-in-out">
|
||||
<TabsList className="p-2">
|
||||
<TabsTrigger value="account">Account</TabsTrigger>
|
||||
<TabsTrigger value="password">Password</TabsTrigger>
|
||||
</TabsList>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="bg-secondary all-center rounded-full p-2 mt-4 min-w-14 hidden group-hover:block">
|
||||
<TabsContent value="account">
|
||||
Make changes to your account here.
|
||||
</TabsContent>
|
||||
<TabsContent value="password">
|
||||
Change your password here.
|
||||
</TabsContent>
|
||||
</div>
|
||||
</Tabs>
|
||||
|
||||
<button className="flex shrink-0 duration-200 items-center gap-2 px-2 py-1.5 rounded-xl hover:bg-secondary">
|
||||
<Image src={ChatIcon} alt="Chat icon" />
|
||||
|
|
|
|||
|
|
@ -3,8 +3,9 @@ import Menu from "../menu";
|
|||
import Header from "../header";
|
||||
import QueryInput from "./queryinput";
|
||||
import { homeSearchParamsCache } from "@/app/helpers/lib/searchParams";
|
||||
import { getSpaces } from "../actions";
|
||||
|
||||
function Page({
|
||||
async function Page({
|
||||
searchParams,
|
||||
}: {
|
||||
searchParams: Record<string, string | string[] | undefined>;
|
||||
|
|
@ -12,13 +13,15 @@ function Page({
|
|||
// TODO: use this to show a welcome page/modal
|
||||
const { firstTime } = homeSearchParamsCache.parse(searchParams);
|
||||
|
||||
const spaces = await getSpaces();
|
||||
|
||||
return (
|
||||
<div className="max-w-3xl flex mx-auto w-full flex-col">
|
||||
{/* all content goes here */}
|
||||
{/* <div className="">hi {firstTime ? 'first time' : ''}</div> */}
|
||||
|
||||
<div className="w-full h-96">
|
||||
<QueryInput />
|
||||
<QueryInput initialSpaces={spaces} />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -7,43 +7,45 @@ import Divider from "@repo/ui/shadcn/divider";
|
|||
import { MultipleSelector, Option } from "@repo/ui/shadcn/combobox";
|
||||
import { useRouter } from "next/navigation";
|
||||
|
||||
const OPTIONS: Option[] = [
|
||||
{ label: "nextjs", value: "0" },
|
||||
{ label: "React", value: "1" },
|
||||
{ label: "Remix", value: "2" },
|
||||
{ label: "Vite", value: "3" },
|
||||
{ label: "Nuxt", value: "4" },
|
||||
{ label: "Vue", value: "5" },
|
||||
{ label: "Svelte", value: "6" },
|
||||
{ label: "Angular", value: "7" },
|
||||
{ label: "Ember", value: "8" },
|
||||
{ label: "Gatsby", value: "9" },
|
||||
];
|
||||
|
||||
function QueryInput({
|
||||
initialQuery = "",
|
||||
initialSpaces = [],
|
||||
disabled = false,
|
||||
}: {
|
||||
initialQuery?: string;
|
||||
initialSpaces?: number[];
|
||||
initialSpaces?: { user: string | null; id: number; name: string }[];
|
||||
disabled?: boolean;
|
||||
}) {
|
||||
const [q, setQ] = useState(initialQuery);
|
||||
|
||||
const [selectedSpaces, setSelectedSpaces] = useState<number[]>(initialSpaces);
|
||||
const [selectedSpaces, setSelectedSpaces] = useState<number[]>([]);
|
||||
|
||||
const { push } = useRouter();
|
||||
|
||||
const parseQ = () => {
|
||||
// preparedSpaces is list of spaces selected by user, with id and name
|
||||
const preparedSpaces = initialSpaces
|
||||
.filter((x) => selectedSpaces.includes(x.id))
|
||||
.map((x) => {
|
||||
return {
|
||||
id: x.id,
|
||||
name: x.name,
|
||||
};
|
||||
});
|
||||
|
||||
const newQ =
|
||||
"/chat?q=" +
|
||||
encodeURI(q) +
|
||||
(selectedSpaces ? "&spaces=" + selectedSpaces.join(",") : "");
|
||||
(selectedSpaces ? "&spaces=" + JSON.stringify(preparedSpaces) : "");
|
||||
|
||||
return newQ;
|
||||
};
|
||||
|
||||
const options = initialSpaces.map((x) => ({
|
||||
label: x.name,
|
||||
value: x.id.toString(),
|
||||
}));
|
||||
|
||||
return (
|
||||
<div>
|
||||
<div className="bg-secondary rounded-t-[24px] w-full mt-40">
|
||||
|
|
@ -81,7 +83,7 @@ function QueryInput({
|
|||
<div className="flex items-center gap-6 p-2 h-auto bg-secondary rounded-b-[24px]">
|
||||
<MultipleSelector
|
||||
disabled={disabled}
|
||||
defaultOptions={OPTIONS}
|
||||
defaultOptions={options}
|
||||
onChange={(e) => setSelectedSpaces(e.map((x) => parseInt(x.value)))}
|
||||
placeholder="Focus on specific spaces..."
|
||||
emptyIndicator={
|
||||
|
|
|
|||
|
|
@ -1,8 +1,15 @@
|
|||
import React from "react";
|
||||
import Header from "./header";
|
||||
import Menu from "./menu";
|
||||
import { ensureAuth } from "./actions";
|
||||
import { redirect } from "next/navigation";
|
||||
|
||||
async function Layout({ children }: { children: React.ReactNode }) {
|
||||
const info = await ensureAuth();
|
||||
|
||||
if (!info) {
|
||||
return redirect("/signin");
|
||||
}
|
||||
|
||||
function Layout({ children }: { children: React.ReactNode }) {
|
||||
return (
|
||||
<main className="h-screen flex flex-col p-4 relative">
|
||||
<Header />
|
||||
|
|
|
|||
|
|
@ -4,7 +4,9 @@ import {
|
|||
parseAsString,
|
||||
parseAsBoolean,
|
||||
parseAsArrayOf,
|
||||
parseAsJson,
|
||||
} from "nuqs/server";
|
||||
import { z } from "zod";
|
||||
|
||||
export const homeSearchParamsCache = createSearchParamsCache({
|
||||
firstTime: parseAsBoolean.withDefault(false),
|
||||
|
|
@ -13,5 +15,12 @@ export const homeSearchParamsCache = createSearchParamsCache({
|
|||
export const chatSearchParamsCache = createSearchParamsCache({
|
||||
firstTime: parseAsBoolean.withDefault(false),
|
||||
q: parseAsString.withDefault(""),
|
||||
spaces: parseAsArrayOf(parseAsInteger, ","),
|
||||
spaces: parseAsArrayOf(
|
||||
parseAsJson(() =>
|
||||
z.object({
|
||||
id: z.string(),
|
||||
name: z.string(),
|
||||
}),
|
||||
),
|
||||
).withDefault([]),
|
||||
});
|
||||
|
|
|
|||
|
|
@ -57,6 +57,7 @@
|
|||
"@radix-ui/react-progress": "^1.0.3",
|
||||
"@radix-ui/react-scroll-area": "^1.0.5",
|
||||
"@radix-ui/react-slot": "^1.0.2",
|
||||
"@radix-ui/react-tabs": "^1.0.4",
|
||||
"@radix-ui/react-toast": "^1.1.5",
|
||||
"@types/readline-sync": "^1.4.8",
|
||||
"ai": "^3.1.14",
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue