diff --git a/.cspell.json b/.cspell.json
index 8aecb8a..64183de 100644
--- a/.cspell.json
+++ b/.cspell.json
@@ -7,7 +7,8 @@
"rebackk",
"chatbots",
"repobeats",
- "octocat"
+ "octocat",
+ "zustand"
],
"flagWords": [],
"ignorePaths": [
diff --git a/.github/workflows/prs.yaml b/.github/workflows/prs.yaml
deleted file mode 100644
index e69de29..0000000
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index 3974428..de4c4f6 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -16,18 +16,16 @@ Please review our [Code of Conduct](./CODE_OF_CONDUCT.md). By participating, you
## :bulb: Asking Questions
-If you have any question that does not relate to a bug or a feature request, please use [GitHub Discussions](https://github.com/ghUserName/packageName/discussions) instead of GitHub issues.
+If you have any question that does not relate to a bug or a feature request, please use [GitHub Discussions](https://github.com/RebackkHQ/NextJSRAG/discussions) instead of GitHub issues.
## :inbox_tray: How can I Contribute?
**GitHub issues**
-If you encounter a problem with this library or if you have a new feature you'd like to see in this project, please create [a new issue](https://github.com/Rebackk-Team/pocketbase-adapter/issues/new/choose).
+If you encounter a problem with this library or if you have a new feature you'd like to see in this project, please create [a new issue](https://github.com/RebackkHQ/NextJSRAG/issues/new/choose).
**GitHub Pull requests**
Please leverage the repository's own tools to make sure the code is aligned with our standards:
-1. Run all check commands before submitting the PR (`type:check`, `lint:check`, `test:coverage` and `spell:check`)
-2. Please commit your changes and run a `setup` command so you can actually check how would the template look like once cleaned up
-3. Always leverage the `cz` command to create a commit. We heavily rely on this for automatic releases.
\ No newline at end of file
+1. Run all check commands before submitting the PR (`type:check`, `lint:check`, `test:coverage` and `spell:check`)
\ No newline at end of file
diff --git a/biome.json b/biome.json
index 48364f8..d2848d4 100644
--- a/biome.json
+++ b/biome.json
@@ -7,7 +7,10 @@
},
"files": {
"ignoreUnknown": false,
- "ignore": []
+ "ignore": [
+ "node_modules",
+ ".next"
+ ]
},
"formatter": {
"enabled": true,
@@ -19,7 +22,7 @@
"linter": {
"enabled": true,
"rules": {
- "recommended": true
+ "recommended": false
}
},
"javascript": {
diff --git a/components.json b/components.json
new file mode 100644
index 0000000..7a63543
--- /dev/null
+++ b/components.json
@@ -0,0 +1,20 @@
+{
+ "$schema": "https://ui.shadcn.com/schema.json",
+ "style": "new-york",
+ "rsc": true,
+ "tsx": true,
+ "tailwind": {
+ "config": "tailwind.config.ts",
+ "css": "src/app/globals.css",
+ "baseColor": "zinc",
+ "cssVariables": true,
+ "prefix": ""
+ },
+ "aliases": {
+ "components": "@/components",
+ "utils": "@/lib/utils",
+ "ui": "@/components/ui",
+ "lib": "@/lib",
+ "hooks": "@/hooks"
+ }
+}
\ No newline at end of file
diff --git a/docker-compose.yaml b/docker-compose.yaml
new file mode 100644
index 0000000..337c6bb
--- /dev/null
+++ b/docker-compose.yaml
@@ -0,0 +1,20 @@
+version: '3.8'
+
+services:
+ ollama:
+ build:
+ context: .
+ dockerfile: ./docker/Dockerfile.ollama
+ ports: [ "11434:11434" ]
+ volumes:
+ - ollama_data:/root/.ollama
+ networks:
+ - net
+
+volumes:
+ ollama_data:
+ driver: local
+
+networks:
+ net:
+ driver: bridge
diff --git a/docker/Dockerfile.ollama b/docker/Dockerfile.ollama
new file mode 100644
index 0000000..7a4b629
--- /dev/null
+++ b/docker/Dockerfile.ollama
@@ -0,0 +1,17 @@
+# Use the Ollama base image as the builder stage
+FROM ollama/ollama as builder
+
+# Copy the setup script from the local 'scripts' directory to the root of the container
+COPY ./scripts/setup-ollama.sh ./setup-ollama.sh
+
+# Execute the setup script to configure the Ollama environment
+RUN bash ./setup-ollama.sh
+
+# Start a new stage from the Ollama base image to create the final image
+FROM ollama/ollama
+
+# Copy the configured Ollama setup from the builder stage to the final image
+COPY --from=builder /root/.ollama /root/.ollama
+
+# Expose port 11434 for external access to the application
+EXPOSE 11434
\ No newline at end of file
diff --git a/next.config.mjs b/next.config.mjs
new file mode 100644
index 0000000..64fc567
--- /dev/null
+++ b/next.config.mjs
@@ -0,0 +1,20 @@
+import createMDX from "@next/mdx";
+
+/**
+ * @type {import('next').NextConfig}
+ */
+const nextConfig = {
+ pageExtensions: ["mdx", "ts", "tsx"],
+ eslint: {
+ ignoreDuringBuilds: true,
+ },
+ typescript: {
+ ignoreBuildErrors: true,
+ },
+};
+
+const withMDX = createMDX({
+ extension: /\.mdx?$/,
+});
+
+export default withMDX(nextConfig);
diff --git a/next.config.ts b/next.config.ts
deleted file mode 100644
index f40a6f9..0000000
--- a/next.config.ts
+++ /dev/null
@@ -1,12 +0,0 @@
-import type { NextConfig } from "next";
-
-const nextConfig: NextConfig = {
- eslint: {
- ignoreDuringBuilds: true,
- },
- typescript: {
- ignoreBuildErrors: true,
- },
-};
-
-export default nextConfig;
diff --git a/package.json b/package.json
index 0d92858..5904c8b 100644
--- a/package.json
+++ b/package.json
@@ -1,9 +1,9 @@
{
- "name": "test",
+ "name": "@rebackk/nextjs-rag",
"version": "0.1.0",
"private": true,
"scripts": {
- "dev": "next dev --turbopack",
+ "dev": "next dev",
"build": "next build",
"start": "next start",
"lint": "biome lint",
@@ -13,12 +13,66 @@
"spell:check": "cspell \"{README.md,CODE_OF_CONDUCT.md,CONTRIBUTING.md,.github/*.md,src/**/*.ts}\""
},
"dependencies": {
- "next": "15.0.1",
- "react": "19.0.0-rc-69d4b800-20241021",
- "react-dom": "19.0.0-rc-69d4b800-20241021"
+ "@hookform/resolvers": "^3.9.0",
+ "@mdx-js/loader": "^3.1.0",
+ "@mdx-js/react": "^3.1.0",
+ "@next/mdx": "^15.0.1",
+ "@radix-ui/react-accordion": "^1.2.1",
+ "@radix-ui/react-alert-dialog": "^1.1.2",
+ "@radix-ui/react-aspect-ratio": "^1.1.0",
+ "@radix-ui/react-avatar": "^1.1.1",
+ "@radix-ui/react-checkbox": "^1.1.2",
+ "@radix-ui/react-collapsible": "^1.1.1",
+ "@radix-ui/react-context-menu": "^2.2.2",
+ "@radix-ui/react-dialog": "^1.1.2",
+ "@radix-ui/react-dropdown-menu": "^2.1.2",
+ "@radix-ui/react-hover-card": "^1.1.2",
+ "@radix-ui/react-icons": "^1.3.0",
+ "@radix-ui/react-label": "^2.1.0",
+ "@radix-ui/react-menubar": "^1.1.2",
+ "@radix-ui/react-navigation-menu": "^1.2.1",
+ "@radix-ui/react-popover": "^1.1.2",
+ "@radix-ui/react-progress": "^1.1.0",
+ "@radix-ui/react-radio-group": "^1.2.1",
+ "@radix-ui/react-scroll-area": "^1.2.0",
+ "@radix-ui/react-select": "^2.1.2",
+ "@radix-ui/react-separator": "^1.1.0",
+ "@radix-ui/react-slider": "^1.2.1",
+ "@radix-ui/react-slot": "^1.1.0",
+ "@radix-ui/react-switch": "^1.1.1",
+ "@radix-ui/react-tabs": "^1.1.1",
+ "@radix-ui/react-toast": "^1.2.2",
+ "@radix-ui/react-toggle": "^1.1.0",
+ "@radix-ui/react-toggle-group": "^1.1.0",
+ "@radix-ui/react-tooltip": "^1.1.3",
+ "@types/mdx": "^2.0.13",
+ "class-variance-authority": "^0.7.0",
+ "clsx": "^2.1.1",
+ "cmdk": "1.0.0",
+ "date-fns": "^4.1.0",
+ "embla-carousel-react": "^8.3.0",
+ "immer": "^10.1.1",
+ "input-otp": "^1.2.4",
+ "lucide-react": "^0.453.0",
+ "next": "14.2.14",
+ "next-themes": "^0.3.0",
+ "react": "18.3.1",
+ "react-day-picker": "8.10.1",
+ "react-dom": "18.3.1",
+ "react-hook-form": "^7.53.1",
+ "react-resizable-panels": "^2.1.4",
+ "recharts": "^2.13.0",
+ "shadcn-ui": "^0.9.2",
+ "sonner": "^1.5.0",
+ "tailwind-merge": "^2.5.4",
+ "tailwindcss-animate": "^1.0.7",
+ "vaul": "^1.1.0",
+ "zod": "^3.23.8",
+ "zustand": "^5.0.0"
},
"devDependencies": {
"@biomejs/biome": "1.9.4",
+ "@tailwindcss/typography": "^0.5.15",
"@types/node": "^20",
"@types/react": "^18",
"@types/react-dom": "^18",
diff --git a/src/app/favicon.ico b/public/favicon.ico
similarity index 100%
rename from src/app/favicon.ico
rename to public/favicon.ico
diff --git a/public/file.svg b/public/file.svg
deleted file mode 100644
index 004145c..0000000
--- a/public/file.svg
+++ /dev/null
@@ -1 +0,0 @@
-
\ No newline at end of file
diff --git a/public/globe.svg b/public/globe.svg
deleted file mode 100644
index 567f17b..0000000
--- a/public/globe.svg
+++ /dev/null
@@ -1 +0,0 @@
-
\ No newline at end of file
diff --git a/public/logo.svg b/public/logo.svg
new file mode 100644
index 0000000..3e9098b
--- /dev/null
+++ b/public/logo.svg
@@ -0,0 +1,32 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/public/next.svg b/public/next.svg
deleted file mode 100644
index 5174b28..0000000
--- a/public/next.svg
+++ /dev/null
@@ -1 +0,0 @@
-
\ No newline at end of file
diff --git a/public/vercel.svg b/public/vercel.svg
deleted file mode 100644
index 7705396..0000000
--- a/public/vercel.svg
+++ /dev/null
@@ -1 +0,0 @@
-
\ No newline at end of file
diff --git a/public/window.svg b/public/window.svg
deleted file mode 100644
index b2b2a44..0000000
--- a/public/window.svg
+++ /dev/null
@@ -1 +0,0 @@
-
\ No newline at end of file
diff --git a/scripts/setup-ollama.sh b/scripts/setup-ollama.sh
new file mode 100644
index 0000000..a4948e2
--- /dev/null
+++ b/scripts/setup-ollama.sh
@@ -0,0 +1,12 @@
+#!/usr/bin/env bash
+# This line indicates that the script should be run using the Bash shell.
+# It uses /usr/bin/env to locate Bash in the user's environment.
+
+# Start the Ollama server in the background
+ollama serve &
+
+# Pause the script for 10 seconds to allow the server to initialize
+sleep 10
+
+# Pull the 'orca-mini' model from the Ollama repository
+ollama pull qwen2.5:1.5b
\ No newline at end of file
diff --git a/src/ mdx-components.tsx b/src/ mdx-components.tsx
new file mode 100644
index 0000000..e253f9b
--- /dev/null
+++ b/src/ mdx-components.tsx
@@ -0,0 +1,14 @@
+import type { MDXComponents } from "mdx/types";
+
+export function useMDXComponents(components: MDXComponents): MDXComponents {
+ console.log("components", components);
+ return {
+ ...components,
+ h1: (props) =>
,
+ h2: (props) => ,
+ h3: (props) => ,
+ h4: (props) => ,
+ h5: (props) => ,
+ h6: (props) => ,
+ };
+}
diff --git a/src/app/(chat)/knowledge-base/add/page.tsx b/src/app/(chat)/knowledge-base/add/page.tsx
new file mode 100644
index 0000000..0e5cf5f
--- /dev/null
+++ b/src/app/(chat)/knowledge-base/add/page.tsx
@@ -0,0 +1,20 @@
+import { ContentLayout } from "@/components/admin-panel/content-layout";
+import Image from "next/image";
+
+export default function AddToKnowledgeBase() {
+ return (
+
+
+
+
+ Rebackk RAG Demo | Add To Knowledge Base
+
+
+ Add a new knowledge base entry to the Rebackk RAG Demo. The entry will
+ be used to train the model and improve the generation of responses
+ tailored to the user's input.
+
+
+
+ );
+}
\ No newline at end of file
diff --git a/src/app/(chat)/knowledge-base/list/page.tsx b/src/app/(chat)/knowledge-base/list/page.tsx
new file mode 100644
index 0000000..881704f
--- /dev/null
+++ b/src/app/(chat)/knowledge-base/list/page.tsx
@@ -0,0 +1,25 @@
+import { ContentLayout } from "@/components/admin-panel/content-layout";
+import Image from "next/image";
+
+export default function ListKnowledgeBase() {
+ return (
+
+
+
+
+ Rebackk RAG Demo | Knowledge Base List
+
+
+ View the knowledge base entries in the Rebackk RAG Demo. The entries
+ are used to train the model and improve the generation of responses
+ tailored to the user's input.
+
+
+
+ );
+}
diff --git a/src/app/(chat)/knowledge-base/remove/page.tsx b/src/app/(chat)/knowledge-base/remove/page.tsx
new file mode 100644
index 0000000..c7bfbfa
--- /dev/null
+++ b/src/app/(chat)/knowledge-base/remove/page.tsx
@@ -0,0 +1,25 @@
+import { ContentLayout } from "@/components/admin-panel/content-layout";
+import Image from "next/image";
+
+export default function RemoveFromKnowledgeBase() {
+ return (
+
+
+
+
+ Rebackk RAG Demo | Remove From Knowledge Base
+
+
+ Remove a knowledge base entry from the Rebackk RAG Demo. The entry will
+ be removed from the training data and will no longer be used to generate
+ responses tailored to the user's input.
+
+
+
+ );
+}
diff --git a/src/app/(chat)/layout.tsx b/src/app/(chat)/layout.tsx
new file mode 100644
index 0000000..c4b9f0d
--- /dev/null
+++ b/src/app/(chat)/layout.tsx
@@ -0,0 +1,37 @@
+import { AdminPanelLayout } from "@/components/admin-panel/admin-panel-layout"; // Import the AdminPanelLayout component for structuring the admin panel layout.
+import "@/styles/globals.css"; // Import global CSS styles for the application.
+import type { Metadata } from "next"; // Import the Metadata type from Next.js for defining page metadata.
+
+export const metadata: Metadata = {
+ title: "RAG Demo | Rebackk", // Title of the page, displayed in the browser tab.
+ description:
+ "Retrieval Augmented Generation Demo using Next.js, OLLAMA and VercelAI. For The Community, By Rebackk.", // Brief description of the page's content for SEO and sharing purposes.
+};
+
+/**
+ * RootLayout component serves as the main layout for the application.
+ * It wraps all pages and components with a consistent structure and styling.
+ *
+ * @param {Readonly<{ children: React.ReactNode }>} props - The component props containing children elements.
+ * @returns {JSX.Element} The rendered layout containing the admin panel structure and the main content.
+ */
+export default function RootLayout({
+ children,
+}: Readonly<{
+ children: React.ReactNode; // The content to be rendered within the layout, passed as children props.
+}>) {
+ return (
+
+ {/* Sets the language of the document to English for accessibility and SEO. */}
+
+
+ {/* Wraps the main content with the AdminPanelLayout for consistent styling and structure. */}
+
+ {/* Main content area with horizontal padding. */}
+ {children}
+
+
+
+
+ );
+}
diff --git a/src/app/(chat)/page.tsx b/src/app/(chat)/page.tsx
new file mode 100644
index 0000000..3fbef84
--- /dev/null
+++ b/src/app/(chat)/page.tsx
@@ -0,0 +1,22 @@
+import { ContentLayout } from "@/components/admin-panel/content-layout";
+import Image from "next/image";
+
+export default function Home() {
+ return (
+
+
+
+
Welcome to Rebackk
+
+ Retrieval Augmented Generation Demo using Next.js, OLLAMA and
+ VercelAI. For The Community, By Rebackk.
+
+
+
+ );
+}
diff --git a/src/app/(content)/about/page.tsx b/src/app/(content)/about/page.tsx
new file mode 100644
index 0000000..0d784d4
--- /dev/null
+++ b/src/app/(content)/about/page.tsx
@@ -0,0 +1,11 @@
+"use client";
+
+import AboutContent from "@/content/about.mdx";
+
+export default function About() {
+ return (
+
+
+
+ );
+}
diff --git a/src/app/(content)/contributing/page.tsx b/src/app/(content)/contributing/page.tsx
new file mode 100644
index 0000000..e0f25a5
--- /dev/null
+++ b/src/app/(content)/contributing/page.tsx
@@ -0,0 +1,11 @@
+"use client";
+
+import AboutContent from "@/content/contributing.mdx";
+
+export default function ContributingGuide() {
+ return (
+
+
+
+ );
+}
diff --git a/src/app/(content)/layout.tsx b/src/app/(content)/layout.tsx
new file mode 100644
index 0000000..2c2b93f
--- /dev/null
+++ b/src/app/(content)/layout.tsx
@@ -0,0 +1,23 @@
+import React from "react";
+import "@/styles/globals.css";
+import { ContentNavbar } from "@/components/content/navbar";
+import { Footer } from "@/components/admin-panel/footer";
+
+export default function MarketingLayout({
+ children,
+}: Readonly<{
+ children: React.ReactNode;
+}>) {
+ return (
+
+
+
+
+
+ {children}
+
+
+
+
+ );
+}
diff --git a/src/app/fonts/GeistMonoVF.woff b/src/app/fonts/GeistMonoVF.woff
deleted file mode 100644
index f2ae185..0000000
Binary files a/src/app/fonts/GeistMonoVF.woff and /dev/null differ
diff --git a/src/app/fonts/GeistVF.woff b/src/app/fonts/GeistVF.woff
deleted file mode 100644
index 1b62daa..0000000
Binary files a/src/app/fonts/GeistVF.woff and /dev/null differ
diff --git a/src/app/globals.css b/src/app/globals.css
deleted file mode 100644
index 6b717ad..0000000
--- a/src/app/globals.css
+++ /dev/null
@@ -1,21 +0,0 @@
-@tailwind base;
-@tailwind components;
-@tailwind utilities;
-
-:root {
- --background: #ffffff;
- --foreground: #171717;
-}
-
-@media (prefers-color-scheme: dark) {
- :root {
- --background: #0a0a0a;
- --foreground: #ededed;
- }
-}
-
-body {
- color: var(--foreground);
- background: var(--background);
- font-family: Arial, Helvetica, sans-serif;
-}
diff --git a/src/app/layout.tsx b/src/app/layout.tsx
index a36cde0..a50e97f 100644
--- a/src/app/layout.tsx
+++ b/src/app/layout.tsx
@@ -1,22 +1,37 @@
-import type { Metadata } from "next";
-import localFont from "next/font/local";
-import "./globals.css";
+import "@/styles/globals.css";
+import { Poppins, Roboto, Fira_Code, Lato } from "next/font/google";
+import { ToastProvider } from "@/components/ui/toast";
+import { Metadata } from "next";
-const geistSans = localFont({
- src: "./fonts/GeistVF.woff",
- variable: "--font-geist-sans",
- weight: "100 900",
+export const metadata: Metadata = {
+ title: "RAG Demo | Rebackk", // Title of the page, displayed in the browser tab.
+ description:
+ "Retrieval Augmented Generation Demo using Next.js, OLLAMA and VercelAI. For The Community, By Rebackk.", // Brief description of the page's content for SEO and sharing purposes.
+};
+
+const poppins = Poppins({
+ subsets: ["latin"],
+ weight: ["400", "700"],
+ variable: "--font-poppins",
});
-const geistMono = localFont({
- src: "./fonts/GeistMonoVF.woff",
- variable: "--font-geist-mono",
- weight: "100 900",
+
+const roboto = Roboto({
+ subsets: ["latin"],
+ weight: ["400", "500", "700"],
+ variable: "--font-roboto",
});
-export const metadata: Metadata = {
- title: "Create Next App",
- description: "Generated by create next app",
-};
+const firaCode = Fira_Code({
+ subsets: ["latin"],
+ weight: ["400", "500", "700"],
+ variable: "--font-fira-code",
+});
+
+const lato = Lato({
+ subsets: ["latin"],
+ weight: ["400", "700"],
+ variable: "--font-lato",
+});
export default function RootLayout({
children,
@@ -25,10 +40,14 @@ export default function RootLayout({
}>) {
return (
-
- {children}
+
+
+
+ {children}
+
+
);
diff --git a/src/app/page.tsx b/src/app/page.tsx
deleted file mode 100644
index 3eee014..0000000
--- a/src/app/page.tsx
+++ /dev/null
@@ -1,101 +0,0 @@
-import Image from "next/image";
-
-export default function Home() {
- return (
-
-
-
-
-
- Get started by editing{" "}
-
- src/app/page.tsx
-
- .
-
- Save and see your changes instantly.
-
-
-
-
-
-
- );
-}
diff --git a/src/components/admin-panel/admin-panel-layout.tsx b/src/components/admin-panel/admin-panel-layout.tsx
new file mode 100644
index 0000000..55fcf61
--- /dev/null
+++ b/src/components/admin-panel/admin-panel-layout.tsx
@@ -0,0 +1,38 @@
+"use client";
+
+import { Footer } from "@/components/admin-panel/footer";
+import { Sidebar } from "@/components/admin-panel/sidebar";
+import { useSidebar } from "@/hooks/use-sidebar";
+import { useStore } from "@/hooks/use-store";
+import { cn } from "@/lib/utils";
+
+export function AdminPanelLayout({
+ children
+}: {
+ children: React.ReactNode;
+}) {
+ const sidebar = useStore(useSidebar, (x) => x);
+ if (!sidebar) return null;
+ const { getOpenState, settings } = sidebar;
+ return (
+ <>
+
+
+ {children}
+
+
+ >
+ );
+}
diff --git a/src/components/admin-panel/collapse-menu-button.tsx b/src/components/admin-panel/collapse-menu-button.tsx
new file mode 100644
index 0000000..dd99548
--- /dev/null
+++ b/src/components/admin-panel/collapse-menu-button.tsx
@@ -0,0 +1,190 @@
+"use client";
+
+import Link from "next/link";
+import { useState } from "react";
+import { ChevronDown, Dot, LucideIcon } from "lucide-react";
+
+import { cn } from "@/lib/utils";
+import { Button } from "@/components/ui/button";
+import { DropdownMenuArrow } from "@radix-ui/react-dropdown-menu";
+import {
+ Collapsible,
+ CollapsibleContent,
+ CollapsibleTrigger
+} from "@/components/ui/collapsible";
+import {
+ Tooltip,
+ TooltipTrigger,
+ TooltipContent,
+ TooltipProvider
+} from "@/components/ui/tooltip";
+import {
+ DropdownMenu,
+ DropdownMenuItem,
+ DropdownMenuLabel,
+ DropdownMenuTrigger,
+ DropdownMenuContent,
+ DropdownMenuSeparator
+} from "@/components/ui/dropdown-menu";
+import { usePathname } from "next/navigation";
+
+type Submenu = {
+ href: string;
+ label: string;
+ active?: boolean;
+};
+
+interface CollapseMenuButtonProps {
+ icon: LucideIcon;
+ label: string;
+ active: boolean;
+ submenus: Submenu[];
+ isOpen: boolean | undefined;
+}
+
+export function CollapseMenuButton({
+ icon: Icon,
+ label,
+ active,
+ submenus,
+ isOpen
+}: CollapseMenuButtonProps) {
+ const pathname = usePathname();
+ const isSubmenuActive = submenus.some((submenu) =>
+ submenu.active === undefined ? submenu.href === pathname : submenu.active
+ );
+ const [isCollapsed, setIsCollapsed] = useState(isSubmenuActive);
+
+ return isOpen ? (
+
+
+
+
+
+
+
+ {submenus.map(({ href, label, active }, index) => (
+
+
+
+
+
+
+ {label}
+
+
+
+ ))}
+
+
+ ) : (
+
+
+
+
+
+
+
+
+
+
+
+ {label}
+
+
+
+
+
+ {label}
+
+
+ {submenus.map(({ href, label, active }, index) => (
+
+
+ {label}
+
+
+ ))}
+
+
+
+ );
+}
diff --git a/src/components/admin-panel/content-layout.tsx b/src/components/admin-panel/content-layout.tsx
new file mode 100644
index 0000000..cdc2fda
--- /dev/null
+++ b/src/components/admin-panel/content-layout.tsx
@@ -0,0 +1,20 @@
+import { Navbar } from "@/components/admin-panel/navbar";
+
+interface ContentLayoutProps {
+ title: string;
+ children: React.ReactNode;
+}
+
+export function ContentLayout({ title, children }: ContentLayoutProps) {
+ return (
+
+
+
+
{title}
+
+ {children}
+
+
+
+ );
+}
diff --git a/src/components/admin-panel/footer.tsx b/src/components/admin-panel/footer.tsx
new file mode 100644
index 0000000..fbce5eb
--- /dev/null
+++ b/src/components/admin-panel/footer.tsx
@@ -0,0 +1,31 @@
+import Link from "next/link";
+
+export function Footer() {
+ return (
+
+
+
+ Built with ❤️ by{" "}
+
+ Rebackk
+
+ . The source code is available on{" "}
+
+ GitHub
+
+ .
+
+
+
+ );
+}
diff --git a/src/components/admin-panel/menu.tsx b/src/components/admin-panel/menu.tsx
new file mode 100644
index 0000000..da1f3f7
--- /dev/null
+++ b/src/components/admin-panel/menu.tsx
@@ -0,0 +1,120 @@
+"use client";
+
+import Link from "next/link";
+import { Ellipsis, LogOut } from "lucide-react";
+import { usePathname } from "next/navigation";
+
+import { cn } from "@/lib/utils";
+import { getMenuList } from "@/lib/menu-list";
+import { Button } from "@/components/ui/button";
+import { ScrollArea } from "@/components/ui/scroll-area";
+import { CollapseMenuButton } from "@/components/admin-panel/collapse-menu-button";
+import {
+ Tooltip,
+ TooltipTrigger,
+ TooltipContent,
+ TooltipProvider,
+} from "@/components/ui/tooltip";
+
+interface MenuProps {
+ isOpen: boolean | undefined;
+}
+
+export function Menu({ isOpen }: MenuProps) {
+ const pathname = usePathname();
+ const menuList = getMenuList(pathname);
+
+ return (
+
+
+
+ {menuList.map(({ groupLabel, menus }, index) => (
+
+ {(isOpen && groupLabel) || isOpen === undefined ? (
+
+ {groupLabel}
+
+ ) : !isOpen && isOpen !== undefined && groupLabel ? (
+
+
+
+
+
+
+
+
+ {groupLabel}
+
+
+
+ ) : (
+
+ )}
+ {menus.map(
+ ({ href, label, icon: Icon, active, submenus }, index) =>
+ !submenus || submenus.length === 0 ? (
+
+
+
+
+
+
+
+
+
+
+ {label}
+
+
+
+
+ {isOpen === false && (
+
+ {label}
+
+ )}
+
+
+
+ ) : (
+
+
+
+ )
+ )}
+
+ ))}
+
+
+
+ );
+}
diff --git a/src/components/admin-panel/navbar.tsx b/src/components/admin-panel/navbar.tsx
new file mode 100644
index 0000000..518959f
--- /dev/null
+++ b/src/components/admin-panel/navbar.tsx
@@ -0,0 +1,18 @@
+import { SheetMenu } from "@/components/admin-panel/sheet-menu";
+
+interface NavbarProps {
+ title: string;
+}
+
+export function Navbar({ title }: NavbarProps) {
+ return (
+
+ );
+}
diff --git a/src/components/admin-panel/sheet-menu.tsx b/src/components/admin-panel/sheet-menu.tsx
new file mode 100644
index 0000000..f1eee0f
--- /dev/null
+++ b/src/components/admin-panel/sheet-menu.tsx
@@ -0,0 +1,43 @@
+import Link from "next/link";
+import { MenuIcon, PanelsTopLeft } from "lucide-react";
+
+import { Button } from "@/components/ui/button";
+import { Menu } from "@/components/admin-panel/menu";
+import {
+ Sheet,
+ SheetHeader,
+ SheetContent,
+ SheetTrigger,
+ SheetTitle,
+} from "@/components/ui/sheet";
+import Image from "next/image";
+
+export function SheetMenu() {
+ return (
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Rebackk | RAG Demo
+
+
+
+
+
+
+
+ );
+}
diff --git a/src/components/admin-panel/sidebar-toggle.tsx b/src/components/admin-panel/sidebar-toggle.tsx
new file mode 100644
index 0000000..c95ea09
--- /dev/null
+++ b/src/components/admin-panel/sidebar-toggle.tsx
@@ -0,0 +1,29 @@
+import { ChevronLeft } from "lucide-react";
+
+import { cn } from "@/lib/utils";
+import { Button } from "@/components/ui/button";
+
+interface SidebarToggleProps {
+ isOpen: boolean | undefined;
+ setIsOpen?: () => void;
+}
+
+export function SidebarToggle({ isOpen, setIsOpen }: SidebarToggleProps) {
+ return (
+
+ setIsOpen?.()}
+ className="rounded-md w-8 h-8"
+ variant="outline"
+ size="icon"
+ >
+
+
+
+ );
+}
diff --git a/src/components/admin-panel/sidebar.tsx b/src/components/admin-panel/sidebar.tsx
new file mode 100644
index 0000000..0a5e47f
--- /dev/null
+++ b/src/components/admin-panel/sidebar.tsx
@@ -0,0 +1,56 @@
+"use client";
+import { Menu } from "@/components/admin-panel/menu";
+import { SidebarToggle } from "@/components/admin-panel/sidebar-toggle";
+import { Button } from "@/components/ui/button";
+import { useSidebar } from "@/hooks/use-sidebar";
+import { useStore } from "@/hooks/use-store";
+import { cn } from "@/lib/utils";
+import { PanelsTopLeft } from "lucide-react";
+import Image from "next/image";
+import Link from "next/link";
+
+export function Sidebar() {
+ const sidebar = useStore(useSidebar, (x) => x);
+ if (!sidebar) return null;
+ const { isOpen, toggleOpen, getOpenState, setIsHover, settings } = sidebar;
+ return (
+
+
+ setIsHover(true)}
+ onMouseLeave={() => setIsHover(false)}
+ className="relative h-full flex flex-col px-3 py-4 overflow-y-auto shadow-md dark:shadow-zinc-800"
+ >
+
+
+
+
+ Rebackk | RAG Demo
+
+
+
+
+
+
+ );
+}
diff --git a/src/components/content/navbar.tsx b/src/components/content/navbar.tsx
new file mode 100644
index 0000000..d1bbffd
--- /dev/null
+++ b/src/components/content/navbar.tsx
@@ -0,0 +1,83 @@
+import { Card } from "@/components/ui/card";
+import {
+ NavigationMenu,
+ NavigationMenuItem,
+ NavigationMenuList,
+} from "@/components/ui/navigation-menu";
+import Image from "next/image";
+import Link from "next/link";
+import { Button } from "../ui/button";
+import { GitHubLogoIcon } from "@radix-ui/react-icons";
+
+const navItems = [
+ {
+ title: "Chat",
+ href: "/",
+ isExternal: false,
+ },
+ {
+ title: "Contribution Guide",
+ href: "/contributing",
+ isExternal: false,
+ },
+];
+
+export const ContentNavbar = () => {
+ return (
+
+
+
+
+
+ RAG Demo
+
+
+
+
+
+ {navItems.map((item, i) => (
+
+
+ {item.title}
+ {item.isExternal && ↗ }
+
+
+ ))}
+
+
+
+
+
+
+
+
+ View on GitHub
+
+
+
+
+
+
+ );
+};
diff --git a/src/components/providers/theme-provider.tsx b/src/components/providers/theme-provider.tsx
new file mode 100644
index 0000000..b0ff266
--- /dev/null
+++ b/src/components/providers/theme-provider.tsx
@@ -0,0 +1,9 @@
+"use client";
+
+import * as React from "react";
+import { ThemeProvider as NextThemesProvider } from "next-themes";
+import { type ThemeProviderProps } from "next-themes/dist/types";
+
+export function ThemeProvider({ children, ...props }: ThemeProviderProps) {
+ return {children} ;
+}
diff --git a/src/components/ui/avatar.tsx b/src/components/ui/avatar.tsx
new file mode 100644
index 0000000..51e507b
--- /dev/null
+++ b/src/components/ui/avatar.tsx
@@ -0,0 +1,50 @@
+"use client"
+
+import * as React from "react"
+import * as AvatarPrimitive from "@radix-ui/react-avatar"
+
+import { cn } from "@/lib/utils"
+
+const Avatar = React.forwardRef<
+ React.ElementRef,
+ React.ComponentPropsWithoutRef
+>(({ className, ...props }, ref) => (
+
+))
+Avatar.displayName = AvatarPrimitive.Root.displayName
+
+const AvatarImage = React.forwardRef<
+ React.ElementRef,
+ React.ComponentPropsWithoutRef
+>(({ className, ...props }, ref) => (
+
+))
+AvatarImage.displayName = AvatarPrimitive.Image.displayName
+
+const AvatarFallback = React.forwardRef<
+ React.ElementRef,
+ React.ComponentPropsWithoutRef
+>(({ className, ...props }, ref) => (
+
+))
+AvatarFallback.displayName = AvatarPrimitive.Fallback.displayName
+
+export { Avatar, AvatarImage, AvatarFallback }
diff --git a/src/components/ui/button.tsx b/src/components/ui/button.tsx
new file mode 100644
index 0000000..65d4fcd
--- /dev/null
+++ b/src/components/ui/button.tsx
@@ -0,0 +1,57 @@
+import * as React from "react"
+import { Slot } from "@radix-ui/react-slot"
+import { cva, type VariantProps } from "class-variance-authority"
+
+import { cn } from "@/lib/utils"
+
+const buttonVariants = cva(
+ "inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium transition-colors focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0",
+ {
+ variants: {
+ variant: {
+ default:
+ "bg-primary text-primary-foreground shadow hover:bg-primary/90",
+ destructive:
+ "bg-destructive text-destructive-foreground shadow-sm hover:bg-destructive/90",
+ outline:
+ "border border-input bg-background shadow-sm hover:bg-accent hover:text-accent-foreground",
+ secondary:
+ "bg-secondary text-secondary-foreground shadow-sm hover:bg-secondary/80",
+ ghost: "hover:bg-accent hover:text-accent-foreground",
+ link: "text-primary underline-offset-4 hover:underline",
+ },
+ size: {
+ default: "h-9 px-4 py-2",
+ sm: "h-8 rounded-md px-3 text-xs",
+ lg: "h-10 rounded-md px-8",
+ icon: "h-9 w-9",
+ },
+ },
+ defaultVariants: {
+ variant: "default",
+ size: "default",
+ },
+ }
+)
+
+export interface ButtonProps
+ extends React.ButtonHTMLAttributes,
+ VariantProps {
+ asChild?: boolean
+}
+
+const Button = React.forwardRef(
+ ({ className, variant, size, asChild = false, ...props }, ref) => {
+ const Comp = asChild ? Slot : "button"
+ return (
+
+ )
+ }
+)
+Button.displayName = "Button"
+
+export { Button, buttonVariants }
diff --git a/src/components/ui/card.tsx b/src/components/ui/card.tsx
new file mode 100644
index 0000000..77e9fb7
--- /dev/null
+++ b/src/components/ui/card.tsx
@@ -0,0 +1,76 @@
+import * as React from "react"
+
+import { cn } from "@/lib/utils"
+
+const Card = React.forwardRef<
+ HTMLDivElement,
+ React.HTMLAttributes
+>(({ className, ...props }, ref) => (
+
+))
+Card.displayName = "Card"
+
+const CardHeader = React.forwardRef<
+ HTMLDivElement,
+ React.HTMLAttributes
+>(({ className, ...props }, ref) => (
+
+))
+CardHeader.displayName = "CardHeader"
+
+const CardTitle = React.forwardRef<
+ HTMLParagraphElement,
+ React.HTMLAttributes
+>(({ className, ...props }, ref) => (
+
+))
+CardTitle.displayName = "CardTitle"
+
+const CardDescription = React.forwardRef<
+ HTMLParagraphElement,
+ React.HTMLAttributes
+>(({ className, ...props }, ref) => (
+
+))
+CardDescription.displayName = "CardDescription"
+
+const CardContent = React.forwardRef<
+ HTMLDivElement,
+ React.HTMLAttributes
+>(({ className, ...props }, ref) => (
+
+))
+CardContent.displayName = "CardContent"
+
+const CardFooter = React.forwardRef<
+ HTMLDivElement,
+ React.HTMLAttributes
+>(({ className, ...props }, ref) => (
+
+))
+CardFooter.displayName = "CardFooter"
+
+export { Card, CardHeader, CardFooter, CardTitle, CardDescription, CardContent }
diff --git a/src/components/ui/collapsible.tsx b/src/components/ui/collapsible.tsx
new file mode 100644
index 0000000..9fa4894
--- /dev/null
+++ b/src/components/ui/collapsible.tsx
@@ -0,0 +1,11 @@
+"use client"
+
+import * as CollapsiblePrimitive from "@radix-ui/react-collapsible"
+
+const Collapsible = CollapsiblePrimitive.Root
+
+const CollapsibleTrigger = CollapsiblePrimitive.CollapsibleTrigger
+
+const CollapsibleContent = CollapsiblePrimitive.CollapsibleContent
+
+export { Collapsible, CollapsibleTrigger, CollapsibleContent }
diff --git a/src/components/ui/dropdown-menu.tsx b/src/components/ui/dropdown-menu.tsx
new file mode 100644
index 0000000..2049afa
--- /dev/null
+++ b/src/components/ui/dropdown-menu.tsx
@@ -0,0 +1,205 @@
+"use client"
+
+import * as React from "react"
+import * as DropdownMenuPrimitive from "@radix-ui/react-dropdown-menu"
+import {
+ CheckIcon,
+ ChevronRightIcon,
+ DotFilledIcon,
+} from "@radix-ui/react-icons"
+
+import { cn } from "@/lib/utils"
+
+const DropdownMenu = DropdownMenuPrimitive.Root
+
+const DropdownMenuTrigger = DropdownMenuPrimitive.Trigger
+
+const DropdownMenuGroup = DropdownMenuPrimitive.Group
+
+const DropdownMenuPortal = DropdownMenuPrimitive.Portal
+
+const DropdownMenuSub = DropdownMenuPrimitive.Sub
+
+const DropdownMenuRadioGroup = DropdownMenuPrimitive.RadioGroup
+
+const DropdownMenuSubTrigger = React.forwardRef<
+ React.ElementRef,
+ React.ComponentPropsWithoutRef & {
+ inset?: boolean
+ }
+>(({ className, inset, children, ...props }, ref) => (
+
+ {children}
+
+
+))
+DropdownMenuSubTrigger.displayName =
+ DropdownMenuPrimitive.SubTrigger.displayName
+
+const DropdownMenuSubContent = React.forwardRef<
+ React.ElementRef,
+ React.ComponentPropsWithoutRef
+>(({ className, ...props }, ref) => (
+
+))
+DropdownMenuSubContent.displayName =
+ DropdownMenuPrimitive.SubContent.displayName
+
+const DropdownMenuContent = React.forwardRef<
+ React.ElementRef,
+ React.ComponentPropsWithoutRef
+>(({ className, sideOffset = 4, ...props }, ref) => (
+
+
+
+))
+DropdownMenuContent.displayName = DropdownMenuPrimitive.Content.displayName
+
+const DropdownMenuItem = React.forwardRef<
+ React.ElementRef,
+ React.ComponentPropsWithoutRef & {
+ inset?: boolean
+ }
+>(({ className, inset, ...props }, ref) => (
+ svg]:size-4 [&>svg]:shrink-0",
+ inset && "pl-8",
+ className
+ )}
+ {...props}
+ />
+))
+DropdownMenuItem.displayName = DropdownMenuPrimitive.Item.displayName
+
+const DropdownMenuCheckboxItem = React.forwardRef<
+ React.ElementRef,
+ React.ComponentPropsWithoutRef
+>(({ className, children, checked, ...props }, ref) => (
+
+
+
+
+
+
+ {children}
+
+))
+DropdownMenuCheckboxItem.displayName =
+ DropdownMenuPrimitive.CheckboxItem.displayName
+
+const DropdownMenuRadioItem = React.forwardRef<
+ React.ElementRef,
+ React.ComponentPropsWithoutRef
+>(({ className, children, ...props }, ref) => (
+
+
+
+
+
+
+ {children}
+
+))
+DropdownMenuRadioItem.displayName = DropdownMenuPrimitive.RadioItem.displayName
+
+const DropdownMenuLabel = React.forwardRef<
+ React.ElementRef,
+ React.ComponentPropsWithoutRef & {
+ inset?: boolean
+ }
+>(({ className, inset, ...props }, ref) => (
+
+))
+DropdownMenuLabel.displayName = DropdownMenuPrimitive.Label.displayName
+
+const DropdownMenuSeparator = React.forwardRef<
+ React.ElementRef,
+ React.ComponentPropsWithoutRef
+>(({ className, ...props }, ref) => (
+
+))
+DropdownMenuSeparator.displayName = DropdownMenuPrimitive.Separator.displayName
+
+const DropdownMenuShortcut = ({
+ className,
+ ...props
+}: React.HTMLAttributes) => {
+ return (
+
+ )
+}
+DropdownMenuShortcut.displayName = "DropdownMenuShortcut"
+
+export {
+ DropdownMenu,
+ DropdownMenuTrigger,
+ DropdownMenuContent,
+ DropdownMenuItem,
+ DropdownMenuCheckboxItem,
+ DropdownMenuRadioItem,
+ DropdownMenuLabel,
+ DropdownMenuSeparator,
+ DropdownMenuShortcut,
+ DropdownMenuGroup,
+ DropdownMenuPortal,
+ DropdownMenuSub,
+ DropdownMenuSubContent,
+ DropdownMenuSubTrigger,
+ DropdownMenuRadioGroup,
+}
diff --git a/src/components/ui/navigation-menu.tsx b/src/components/ui/navigation-menu.tsx
new file mode 100644
index 0000000..5841fb3
--- /dev/null
+++ b/src/components/ui/navigation-menu.tsx
@@ -0,0 +1,128 @@
+import * as React from "react"
+import { ChevronDownIcon } from "@radix-ui/react-icons"
+import * as NavigationMenuPrimitive from "@radix-ui/react-navigation-menu"
+import { cva } from "class-variance-authority"
+
+import { cn } from "@/lib/utils"
+
+const NavigationMenu = React.forwardRef<
+ React.ElementRef,
+ React.ComponentPropsWithoutRef
+>(({ className, children, ...props }, ref) => (
+
+ {children}
+
+
+))
+NavigationMenu.displayName = NavigationMenuPrimitive.Root.displayName
+
+const NavigationMenuList = React.forwardRef<
+ React.ElementRef,
+ React.ComponentPropsWithoutRef
+>(({ className, ...props }, ref) => (
+
+))
+NavigationMenuList.displayName = NavigationMenuPrimitive.List.displayName
+
+const NavigationMenuItem = NavigationMenuPrimitive.Item
+
+const navigationMenuTriggerStyle = cva(
+ "group inline-flex h-9 w-max items-center justify-center rounded-md bg-background px-4 py-2 text-sm font-medium transition-colors hover:bg-accent hover:text-accent-foreground focus:bg-accent focus:text-accent-foreground focus:outline-none disabled:pointer-events-none disabled:opacity-50 data-[active]:bg-accent/50 data-[state=open]:bg-accent/50"
+)
+
+const NavigationMenuTrigger = React.forwardRef<
+ React.ElementRef,
+ React.ComponentPropsWithoutRef
+>(({ className, children, ...props }, ref) => (
+
+ {children}{" "}
+
+
+))
+NavigationMenuTrigger.displayName = NavigationMenuPrimitive.Trigger.displayName
+
+const NavigationMenuContent = React.forwardRef<
+ React.ElementRef,
+ React.ComponentPropsWithoutRef
+>(({ className, ...props }, ref) => (
+
+))
+NavigationMenuContent.displayName = NavigationMenuPrimitive.Content.displayName
+
+const NavigationMenuLink = NavigationMenuPrimitive.Link
+
+const NavigationMenuViewport = React.forwardRef<
+ React.ElementRef,
+ React.ComponentPropsWithoutRef
+>(({ className, ...props }, ref) => (
+
+
+
+))
+NavigationMenuViewport.displayName =
+ NavigationMenuPrimitive.Viewport.displayName
+
+const NavigationMenuIndicator = React.forwardRef<
+ React.ElementRef,
+ React.ComponentPropsWithoutRef
+>(({ className, ...props }, ref) => (
+
+
+
+))
+NavigationMenuIndicator.displayName =
+ NavigationMenuPrimitive.Indicator.displayName
+
+export {
+ navigationMenuTriggerStyle,
+ NavigationMenu,
+ NavigationMenuList,
+ NavigationMenuItem,
+ NavigationMenuContent,
+ NavigationMenuTrigger,
+ NavigationMenuLink,
+ NavigationMenuIndicator,
+ NavigationMenuViewport,
+}
diff --git a/src/components/ui/scroll-area.tsx b/src/components/ui/scroll-area.tsx
new file mode 100644
index 0000000..0b4a48d
--- /dev/null
+++ b/src/components/ui/scroll-area.tsx
@@ -0,0 +1,48 @@
+"use client"
+
+import * as React from "react"
+import * as ScrollAreaPrimitive from "@radix-ui/react-scroll-area"
+
+import { cn } from "@/lib/utils"
+
+const ScrollArea = React.forwardRef<
+ React.ElementRef,
+ React.ComponentPropsWithoutRef
+>(({ className, children, ...props }, ref) => (
+
+
+ {children}
+
+
+
+
+))
+ScrollArea.displayName = ScrollAreaPrimitive.Root.displayName
+
+const ScrollBar = React.forwardRef<
+ React.ElementRef,
+ React.ComponentPropsWithoutRef
+>(({ className, orientation = "vertical", ...props }, ref) => (
+
+
+
+))
+ScrollBar.displayName = ScrollAreaPrimitive.ScrollAreaScrollbar.displayName
+
+export { ScrollArea, ScrollBar }
diff --git a/src/components/ui/sheet.tsx b/src/components/ui/sheet.tsx
new file mode 100644
index 0000000..417e7e1
--- /dev/null
+++ b/src/components/ui/sheet.tsx
@@ -0,0 +1,140 @@
+"use client"
+
+import * as React from "react"
+import * as SheetPrimitive from "@radix-ui/react-dialog"
+import { Cross2Icon } from "@radix-ui/react-icons"
+import { cva, type VariantProps } from "class-variance-authority"
+
+import { cn } from "@/lib/utils"
+
+const Sheet = SheetPrimitive.Root
+
+const SheetTrigger = SheetPrimitive.Trigger
+
+const SheetClose = SheetPrimitive.Close
+
+const SheetPortal = SheetPrimitive.Portal
+
+const SheetOverlay = React.forwardRef<
+ React.ElementRef,
+ React.ComponentPropsWithoutRef
+>(({ className, ...props }, ref) => (
+
+))
+SheetOverlay.displayName = SheetPrimitive.Overlay.displayName
+
+const sheetVariants = cva(
+ "fixed z-50 gap-4 bg-background p-6 shadow-lg transition ease-in-out data-[state=closed]:duration-300 data-[state=open]:duration-500 data-[state=open]:animate-in data-[state=closed]:animate-out",
+ {
+ variants: {
+ side: {
+ top: "inset-x-0 top-0 border-b data-[state=closed]:slide-out-to-top data-[state=open]:slide-in-from-top",
+ bottom:
+ "inset-x-0 bottom-0 border-t data-[state=closed]:slide-out-to-bottom data-[state=open]:slide-in-from-bottom",
+ left: "inset-y-0 left-0 h-full w-3/4 border-r data-[state=closed]:slide-out-to-left data-[state=open]:slide-in-from-left sm:max-w-sm",
+ right:
+ "inset-y-0 right-0 h-full w-3/4 border-l data-[state=closed]:slide-out-to-right data-[state=open]:slide-in-from-right sm:max-w-sm",
+ },
+ },
+ defaultVariants: {
+ side: "right",
+ },
+ }
+)
+
+interface SheetContentProps
+ extends React.ComponentPropsWithoutRef,
+ VariantProps {}
+
+const SheetContent = React.forwardRef<
+ React.ElementRef,
+ SheetContentProps
+>(({ side = "right", className, children, ...props }, ref) => (
+
+
+
+
+
+ Close
+
+ {children}
+
+
+))
+SheetContent.displayName = SheetPrimitive.Content.displayName
+
+const SheetHeader = ({
+ className,
+ ...props
+}: React.HTMLAttributes) => (
+
+)
+SheetHeader.displayName = "SheetHeader"
+
+const SheetFooter = ({
+ className,
+ ...props
+}: React.HTMLAttributes) => (
+
+)
+SheetFooter.displayName = "SheetFooter"
+
+const SheetTitle = React.forwardRef<
+ React.ElementRef,
+ React.ComponentPropsWithoutRef
+>(({ className, ...props }, ref) => (
+
+))
+SheetTitle.displayName = SheetPrimitive.Title.displayName
+
+const SheetDescription = React.forwardRef<
+ React.ElementRef,
+ React.ComponentPropsWithoutRef
+>(({ className, ...props }, ref) => (
+
+))
+SheetDescription.displayName = SheetPrimitive.Description.displayName
+
+export {
+ Sheet,
+ SheetPortal,
+ SheetOverlay,
+ SheetTrigger,
+ SheetClose,
+ SheetContent,
+ SheetHeader,
+ SheetFooter,
+ SheetTitle,
+ SheetDescription,
+}
diff --git a/src/components/ui/toast.tsx b/src/components/ui/toast.tsx
new file mode 100644
index 0000000..cc4e0ab
--- /dev/null
+++ b/src/components/ui/toast.tsx
@@ -0,0 +1,129 @@
+"use client"
+
+import * as React from "react"
+import { Cross2Icon } from "@radix-ui/react-icons"
+import * as ToastPrimitives from "@radix-ui/react-toast"
+import { cva, type VariantProps } from "class-variance-authority"
+
+import { cn } from "@/lib/utils"
+
+const ToastProvider = ToastPrimitives.Provider
+
+const ToastViewport = React.forwardRef<
+ React.ElementRef,
+ React.ComponentPropsWithoutRef
+>(({ className, ...props }, ref) => (
+
+))
+ToastViewport.displayName = ToastPrimitives.Viewport.displayName
+
+const toastVariants = cva(
+ "group pointer-events-auto relative flex w-full items-center justify-between space-x-2 overflow-hidden rounded-md border p-4 pr-6 shadow-lg transition-all data-[swipe=cancel]:translate-x-0 data-[swipe=end]:translate-x-[var(--radix-toast-swipe-end-x)] data-[swipe=move]:translate-x-[var(--radix-toast-swipe-move-x)] data-[swipe=move]:transition-none data-[state=open]:animate-in data-[state=closed]:animate-out data-[swipe=end]:animate-out data-[state=closed]:fade-out-80 data-[state=closed]:slide-out-to-right-full data-[state=open]:slide-in-from-top-full data-[state=open]:sm:slide-in-from-bottom-full",
+ {
+ variants: {
+ variant: {
+ default: "border bg-background text-foreground",
+ destructive:
+ "destructive group border-destructive bg-destructive text-destructive-foreground",
+ },
+ },
+ defaultVariants: {
+ variant: "default",
+ },
+ }
+)
+
+const Toast = React.forwardRef<
+ React.ElementRef,
+ React.ComponentPropsWithoutRef &
+ VariantProps
+>(({ className, variant, ...props }, ref) => {
+ return (
+
+ )
+})
+Toast.displayName = ToastPrimitives.Root.displayName
+
+const ToastAction = React.forwardRef<
+ React.ElementRef,
+ React.ComponentPropsWithoutRef
+>(({ className, ...props }, ref) => (
+
+))
+ToastAction.displayName = ToastPrimitives.Action.displayName
+
+const ToastClose = React.forwardRef<
+ React.ElementRef,
+ React.ComponentPropsWithoutRef
+>(({ className, ...props }, ref) => (
+
+
+
+))
+ToastClose.displayName = ToastPrimitives.Close.displayName
+
+const ToastTitle = React.forwardRef<
+ React.ElementRef,
+ React.ComponentPropsWithoutRef
+>(({ className, ...props }, ref) => (
+
+))
+ToastTitle.displayName = ToastPrimitives.Title.displayName
+
+const ToastDescription = React.forwardRef<
+ React.ElementRef,
+ React.ComponentPropsWithoutRef
+>(({ className, ...props }, ref) => (
+
+))
+ToastDescription.displayName = ToastPrimitives.Description.displayName
+
+type ToastProps = React.ComponentPropsWithoutRef
+
+type ToastActionElement = React.ReactElement
+
+export {
+ type ToastProps,
+ type ToastActionElement,
+ ToastProvider,
+ ToastViewport,
+ Toast,
+ ToastTitle,
+ ToastDescription,
+ ToastClose,
+ ToastAction,
+}
diff --git a/src/components/ui/toaster.tsx b/src/components/ui/toaster.tsx
new file mode 100644
index 0000000..171beb4
--- /dev/null
+++ b/src/components/ui/toaster.tsx
@@ -0,0 +1,35 @@
+"use client"
+
+import { useToast } from "@/hooks/use-toast"
+import {
+ Toast,
+ ToastClose,
+ ToastDescription,
+ ToastProvider,
+ ToastTitle,
+ ToastViewport,
+} from "@/components/ui/toast"
+
+export function Toaster() {
+ const { toasts } = useToast()
+
+ return (
+
+ {toasts.map(function ({ id, title, description, action, ...props }) {
+ return (
+
+
+ {title && {title} }
+ {description && (
+ {description}
+ )}
+
+ {action}
+
+
+ )
+ })}
+
+
+ )
+}
diff --git a/src/components/ui/tooltip.tsx b/src/components/ui/tooltip.tsx
new file mode 100644
index 0000000..a66b3f2
--- /dev/null
+++ b/src/components/ui/tooltip.tsx
@@ -0,0 +1,32 @@
+"use client"
+
+import * as React from "react"
+import * as TooltipPrimitive from "@radix-ui/react-tooltip"
+
+import { cn } from "@/lib/utils"
+
+const TooltipProvider = TooltipPrimitive.Provider
+
+const Tooltip = TooltipPrimitive.Root
+
+const TooltipTrigger = TooltipPrimitive.Trigger
+
+const TooltipContent = React.forwardRef<
+ React.ElementRef,
+ React.ComponentPropsWithoutRef
+>(({ className, sideOffset = 4, ...props }, ref) => (
+
+
+
+))
+TooltipContent.displayName = TooltipPrimitive.Content.displayName
+
+export { Tooltip, TooltipTrigger, TooltipContent, TooltipProvider }
diff --git a/src/content/about.mdx b/src/content/about.mdx
new file mode 100644
index 0000000..a2a4a8f
--- /dev/null
+++ b/src/content/about.mdx
@@ -0,0 +1,26 @@
+## Why We Created the Next.js RAG Demo
+
+At [Rebackk](https://rebackk.xyz), our focus is on delivering robust cybersecurity products tailored to the needs of startups. We’re always exploring cutting-edge technologies to enhance our offerings, but our core mission remains centered around building practical, effective security tools. In developing our product, Sentinel (Coming Soon), we looked for ways to make vulnerability detection and response faster and more efficient. While we are primarily a security SaaS provider, we occasionally use technologies like AI and Zero-Knowledge Proofs (ZKProofs) to enhance our products. That’s where the concept of Retrieval-Augmented Generation (RAG) came into play for us.
+
+### What is RAG?
+
+Retrieval-Augmented Generation (RAG) is an approach that boosts AI’s capabilities by allowing it to access real-time data. Instead of generating responses solely from pre-trained models, RAG pulls in current, relevant information. For Sentinel, this means more accurate vulnerability reports and quicker response times, giving our users real-time insights they can act on immediately.
+
+### Why We Built the Demo
+
+We built an RAG system for Sentinel to help developers with fixing vulnerabilities that we found on their webapps and APIs. We Thought it would be a good idea to show the power of RAG to the world by creating a demo that can be used by anyone to generate responses to questions. We hope that this demo will help developers understand the potential of RAG and inspire them to use it in their projects.
+
+### For the Community, By Rebackk
+
+At Rebackk, we believe in sharing what we learn. Our products are built to empower startups with affordable, high-quality cybersecurity solutions, and we want to extend that ethos to the broader developer community. By open-sourcing this demo, we aim to provide a tool that others can use, adapt, and extend in ways that suit their needs, whether it’s for enhancing security tools, improving user interactions, or automating workflows.
+
+
+We’re releasing this to foster collaboration. Whether you’re a startup or an individual developer, we want to provide a tool that helps you enhance your projects with the latest tech.
+
+### Why Open Source?
+
+We open-sourced this project because we believe that sharing knowledge helps everyone. Even though this demo supports Sentinel’s capabilities, its potential goes beyond our immediate use. We hope that other developers can use this foundation to build their own solutions and improve security, automation, or customer-facing tools.
+
+### What’s Next?
+
+We’re excited to continue improving our products and sharing new developments with the community. While our core focus will always be on delivering reliable cybersecurity products, we’ll keep exploring ways that other deep tech can enhance our offerings. We hope that this demo will inspire others to experiment with RAG and other cutting-edge technologies, and we look forward to seeing what the community creates with it.
diff --git a/src/content/contributing.mdx b/src/content/contributing.mdx
new file mode 100644
index 0000000..5fedc5b
--- /dev/null
+++ b/src/content/contributing.mdx
@@ -0,0 +1,25 @@
+# Contributing Guidelines
+
+_Pull requests, bug reports, and all other forms of contribution are welcomed and highly encouraged!_
+
+> **This guide serves to set clear expectations for everyone involved with the project so that we can improve it together while also creating a welcoming space for everyone to participate. Following these guidelines will help ensure a positive experience for contributors and maintainers.**
+
+## Code of Conduct
+
+Please review our [Code of Conduct](https://github.com/RebackkHQ/NextJSRAG/blob/main/CODE_OF_CONDUCT.md). By participating, you are expected to uphold this code.
+
+## Asking Questions
+
+If you have any question that does not relate to a bug or a feature request, please use [GitHub Discussions](https://github.com/RebackkHQ/NextJSRAG/discussions) instead of GitHub issues.
+
+## How can I Contribute?
+
+**GitHub issues**
+
+If you encounter a problem with this library or if you have a new feature you'd like to see in this project, please create [a new issue](https://github.com/RebackkHQ/NextJSRAG/issues/new/choose).
+
+**GitHub Pull requests**
+
+Please leverage the repository's own tools to make sure the code is aligned with our standards:
+
+Run all check commands before submitting the PR (`type:check`, `lint:check`, `test:coverage` and `spell:check`)
\ No newline at end of file
diff --git a/src/hooks/use-mobile.tsx b/src/hooks/use-mobile.tsx
new file mode 100644
index 0000000..2b0fe1d
--- /dev/null
+++ b/src/hooks/use-mobile.tsx
@@ -0,0 +1,19 @@
+import * as React from "react"
+
+const MOBILE_BREAKPOINT = 768
+
+export function useIsMobile() {
+ const [isMobile, setIsMobile] = React.useState(undefined)
+
+ React.useEffect(() => {
+ const mql = window.matchMedia(`(max-width: ${MOBILE_BREAKPOINT - 1}px)`)
+ const onChange = () => {
+ setIsMobile(window.innerWidth < MOBILE_BREAKPOINT)
+ }
+ mql.addEventListener("change", onChange)
+ setIsMobile(window.innerWidth < MOBILE_BREAKPOINT)
+ return () => mql.removeEventListener("change", onChange)
+ }, [])
+
+ return !!isMobile
+}
diff --git a/src/hooks/use-sidebar.ts b/src/hooks/use-sidebar.ts
new file mode 100644
index 0000000..e37b86f
--- /dev/null
+++ b/src/hooks/use-sidebar.ts
@@ -0,0 +1,49 @@
+import { create } from "zustand";
+import { persist, createJSONStorage } from "zustand/middleware";
+import { produce } from "immer";
+
+type SidebarSettings = { disabled: boolean; isHoverOpen: boolean };
+type SidebarStore = {
+ isOpen: boolean;
+ isHover: boolean;
+ settings: SidebarSettings;
+ toggleOpen: () => void;
+ setIsOpen: (isOpen: boolean) => void;
+ setIsHover: (isHover: boolean) => void;
+ getOpenState: () => boolean;
+ setSettings: (settings: Partial) => void;
+};
+
+export const useSidebar = create(
+ persist(
+ (set, get) => ({
+ isOpen: true,
+ isHover: false,
+ settings: { disabled: false, isHoverOpen: false },
+ toggleOpen: () => {
+ set({ isOpen: !get().isOpen });
+ },
+ setIsOpen: (isOpen: boolean) => {
+ set({ isOpen });
+ },
+ setIsHover: (isHover: boolean) => {
+ set({ isHover });
+ },
+ getOpenState: () => {
+ const state = get();
+ return state.isOpen || (state.settings.isHoverOpen && state.isHover);
+ },
+ setSettings: (settings: Partial) => {
+ set(
+ produce((state: SidebarStore) => {
+ state.settings = { ...state.settings, ...settings };
+ })
+ );
+ }
+ }),
+ {
+ name: "sidebar",
+ storage: createJSONStorage(() => localStorage)
+ }
+ )
+);
diff --git a/src/hooks/use-store.ts b/src/hooks/use-store.ts
new file mode 100644
index 0000000..6941296
--- /dev/null
+++ b/src/hooks/use-store.ts
@@ -0,0 +1,17 @@
+import { useState, useEffect } from "react";
+/**
+ * This hook fix hydration when use persist to save hook data to localStorage
+ */
+export const useStore = (
+ store: (callback: (state: T) => unknown) => unknown,
+ callback: (state: T) => F
+) => {
+ const result = store(callback) as F;
+ const [data, setData] = useState();
+
+ useEffect(() => {
+ setData(result);
+ }, [result]);
+
+ return data;
+};
diff --git a/src/hooks/use-toast.ts b/src/hooks/use-toast.ts
new file mode 100644
index 0000000..02e111d
--- /dev/null
+++ b/src/hooks/use-toast.ts
@@ -0,0 +1,194 @@
+"use client"
+
+// Inspired by react-hot-toast library
+import * as React from "react"
+
+import type {
+ ToastActionElement,
+ ToastProps,
+} from "@/components/ui/toast"
+
+const TOAST_LIMIT = 1
+const TOAST_REMOVE_DELAY = 1000000
+
+type ToasterToast = ToastProps & {
+ id: string
+ title?: React.ReactNode
+ description?: React.ReactNode
+ action?: ToastActionElement
+}
+
+const actionTypes = {
+ ADD_TOAST: "ADD_TOAST",
+ UPDATE_TOAST: "UPDATE_TOAST",
+ DISMISS_TOAST: "DISMISS_TOAST",
+ REMOVE_TOAST: "REMOVE_TOAST",
+} as const
+
+let count = 0
+
+function genId() {
+ count = (count + 1) % Number.MAX_SAFE_INTEGER
+ return count.toString()
+}
+
+type ActionType = typeof actionTypes
+
+type Action =
+ | {
+ type: ActionType["ADD_TOAST"]
+ toast: ToasterToast
+ }
+ | {
+ type: ActionType["UPDATE_TOAST"]
+ toast: Partial
+ }
+ | {
+ type: ActionType["DISMISS_TOAST"]
+ toastId?: ToasterToast["id"]
+ }
+ | {
+ type: ActionType["REMOVE_TOAST"]
+ toastId?: ToasterToast["id"]
+ }
+
+interface State {
+ toasts: ToasterToast[]
+}
+
+const toastTimeouts = new Map>()
+
+const addToRemoveQueue = (toastId: string) => {
+ if (toastTimeouts.has(toastId)) {
+ return
+ }
+
+ const timeout = setTimeout(() => {
+ toastTimeouts.delete(toastId)
+ dispatch({
+ type: "REMOVE_TOAST",
+ toastId: toastId,
+ })
+ }, TOAST_REMOVE_DELAY)
+
+ toastTimeouts.set(toastId, timeout)
+}
+
+export const reducer = (state: State, action: Action): State => {
+ switch (action.type) {
+ case "ADD_TOAST":
+ return {
+ ...state,
+ toasts: [action.toast, ...state.toasts].slice(0, TOAST_LIMIT),
+ }
+
+ case "UPDATE_TOAST":
+ return {
+ ...state,
+ toasts: state.toasts.map((t) =>
+ t.id === action.toast.id ? { ...t, ...action.toast } : t
+ ),
+ }
+
+ case "DISMISS_TOAST": {
+ const { toastId } = action
+
+ // ! Side effects ! - This could be extracted into a dismissToast() action,
+ // but I'll keep it here for simplicity
+ if (toastId) {
+ addToRemoveQueue(toastId)
+ } else {
+ state.toasts.forEach((toast) => {
+ addToRemoveQueue(toast.id)
+ })
+ }
+
+ return {
+ ...state,
+ toasts: state.toasts.map((t) =>
+ t.id === toastId || toastId === undefined
+ ? {
+ ...t,
+ open: false,
+ }
+ : t
+ ),
+ }
+ }
+ case "REMOVE_TOAST":
+ if (action.toastId === undefined) {
+ return {
+ ...state,
+ toasts: [],
+ }
+ }
+ return {
+ ...state,
+ toasts: state.toasts.filter((t) => t.id !== action.toastId),
+ }
+ }
+}
+
+const listeners: Array<(state: State) => void> = []
+
+let memoryState: State = { toasts: [] }
+
+function dispatch(action: Action) {
+ memoryState = reducer(memoryState, action)
+ listeners.forEach((listener) => {
+ listener(memoryState)
+ })
+}
+
+type Toast = Omit
+
+function toast({ ...props }: Toast) {
+ const id = genId()
+
+ const update = (props: ToasterToast) =>
+ dispatch({
+ type: "UPDATE_TOAST",
+ toast: { ...props, id },
+ })
+ const dismiss = () => dispatch({ type: "DISMISS_TOAST", toastId: id })
+
+ dispatch({
+ type: "ADD_TOAST",
+ toast: {
+ ...props,
+ id,
+ open: true,
+ onOpenChange: (open) => {
+ if (!open) dismiss()
+ },
+ },
+ })
+
+ return {
+ id: id,
+ dismiss,
+ update,
+ }
+}
+
+function useToast() {
+ const [state, setState] = React.useState(memoryState)
+
+ React.useEffect(() => {
+ listeners.push(setState)
+ return () => {
+ const index = listeners.indexOf(setState)
+ if (index > -1) {
+ listeners.splice(index, 1)
+ }
+ }
+ }, [state])
+
+ return {
+ ...state,
+ toast,
+ dismiss: (toastId?: string) => dispatch({ type: "DISMISS_TOAST", toastId }),
+ }
+}
+
+export { useToast, toast }
diff --git a/src/lib/menu-list.ts b/src/lib/menu-list.ts
new file mode 100644
index 0000000..9090796
--- /dev/null
+++ b/src/lib/menu-list.ts
@@ -0,0 +1,79 @@
+import {
+ Bookmark,
+ LucideIcon,
+ Bot,
+ List,
+ Trash,
+ BookA,
+ GitPullRequest,
+} from "lucide-react";
+
+type Submenu = {
+ href: string;
+ label: string;
+ active?: boolean;
+};
+
+type Menu = {
+ href: string;
+ label: string;
+ active?: boolean;
+ icon: LucideIcon;
+ submenus?: Submenu[];
+};
+
+type Group = {
+ groupLabel: string;
+ menus: Menu[];
+};
+
+export function getMenuList(pathname: string): Group[] {
+ return [
+ {
+ groupLabel: "",
+ menus: [
+ {
+ href: "/",
+ label: "Chat",
+ icon: Bot,
+ submenus: [],
+ },
+ ],
+ },
+ {
+ groupLabel: "Knowledge Base",
+ menus: [
+ {
+ href: "/knowledge-base/list",
+ label: "Details",
+ icon: Bookmark,
+ },
+ {
+ href: "/knowledge-base/add",
+ label: "Add",
+ icon: List,
+ },
+ {
+ href: "/knowledge-base/remove",
+ label: "Remove",
+ icon: Trash,
+ },
+ ],
+ },
+ {
+ groupLabel: "Content",
+ menus: [
+ {
+ href: "/about",
+ label: "About",
+ icon: BookA,
+ },
+ {
+ href: "/contributing",
+ label: "Contribution Guide",
+ icon: GitPullRequest,
+ },
+ ],
+ },
+ ];
+}
diff --git a/src/lib/utils.ts b/src/lib/utils.ts
new file mode 100644
index 0000000..bd0c391
--- /dev/null
+++ b/src/lib/utils.ts
@@ -0,0 +1,6 @@
+import { clsx, type ClassValue } from "clsx"
+import { twMerge } from "tailwind-merge"
+
+export function cn(...inputs: ClassValue[]) {
+ return twMerge(clsx(inputs))
+}
diff --git a/src/styles/globals.css b/src/styles/globals.css
new file mode 100644
index 0000000..d4528b5
--- /dev/null
+++ b/src/styles/globals.css
@@ -0,0 +1,88 @@
+@tailwind base;
+@tailwind components;
+@tailwind utilities;
+
+body {
+ font-family: Arial, Helvetica, sans-serif;
+}
+
+@layer base {
+ :root {
+ --background: 0 0% 100%;
+ --foreground: 240 10% 3.9%;
+ --card: 0 0% 100%;
+ --card-foreground: 240 10% 3.9%;
+ --popover: 0 0% 100%;
+ --popover-foreground: 240 10% 3.9%;
+ --primary: 240 5.9% 10%;
+ --primary-foreground: 0 0% 98%;
+ --secondary: 240 4.8% 95.9%;
+ --secondary-foreground: 240 5.9% 10%;
+ --muted: 240 4.8% 95.9%;
+ --muted-foreground: 240 3.8% 46.1%;
+ --accent: 240 4.8% 95.9%;
+ --accent-foreground: 240 5.9% 10%;
+ --destructive: 0 84.2% 60.2%;
+ --destructive-foreground: 0 0% 98%;
+ --border: 240 5.9% 90%;
+ --input: 240 5.9% 90%;
+ --ring: 240 10% 3.9%;
+ --chart-1: 12 76% 61%;
+ --chart-2: 173 58% 39%;
+ --chart-3: 197 37% 24%;
+ --chart-4: 43 74% 66%;
+ --chart-5: 27 87% 67%;
+ --radius: 0.5rem;
+ --sidebar-background: 0 0% 98%;
+ --sidebar-foreground: 240 5.3% 26.1%;
+ --sidebar-primary: 240 5.9% 10%;
+ --sidebar-primary-foreground: 0 0% 98%;
+ --sidebar-accent: 240 4.8% 95.9%;
+ --sidebar-accent-foreground: 240 5.9% 10%;
+ --sidebar-border: 220 13% 91%;
+ --sidebar-ring: 217.2 91.2% 59.8%;
+ }
+ .dark {
+ --background: 240 10% 3.9%;
+ --foreground: 0 0% 98%;
+ --card: 240 10% 3.9%;
+ --card-foreground: 0 0% 98%;
+ --popover: 240 10% 3.9%;
+ --popover-foreground: 0 0% 98%;
+ --primary: 0 0% 98%;
+ --primary-foreground: 240 5.9% 10%;
+ --secondary: 240 3.7% 15.9%;
+ --secondary-foreground: 0 0% 98%;
+ --muted: 240 3.7% 15.9%;
+ --muted-foreground: 240 5% 64.9%;
+ --accent: 240 3.7% 15.9%;
+ --accent-foreground: 0 0% 98%;
+ --destructive: 0 62.8% 30.6%;
+ --destructive-foreground: 0 0% 98%;
+ --border: 240 3.7% 15.9%;
+ --input: 240 3.7% 15.9%;
+ --ring: 240 4.9% 83.9%;
+ --chart-1: 220 70% 50%;
+ --chart-2: 160 60% 45%;
+ --chart-3: 30 80% 55%;
+ --chart-4: 280 65% 60%;
+ --chart-5: 340 75% 55%;
+ --sidebar-background: 240 5.9% 10%;
+ --sidebar-foreground: 240 4.8% 95.9%;
+ --sidebar-primary: 224.3 76.3% 48%;
+ --sidebar-primary-foreground: 0 0% 100%;
+ --sidebar-accent: 240 3.7% 15.9%;
+ --sidebar-accent-foreground: 240 4.8% 95.9%;
+ --sidebar-border: 240 3.7% 15.9%;
+ --sidebar-ring: 217.2 91.2% 59.8%;
+ }
+}
+
+@layer base {
+ * {
+ @apply border-border;
+ }
+ body {
+ @apply bg-background text-foreground;
+ }
+}
diff --git a/tailwind.config.ts b/tailwind.config.ts
index 021c393..23cc30d 100644
--- a/tailwind.config.ts
+++ b/tailwind.config.ts
@@ -1,6 +1,7 @@
import type { Config } from "tailwindcss";
const config: Config = {
+ darkMode: ["class"],
content: [
"./src/pages/**/*.{js,ts,jsx,tsx,mdx}",
"./src/components/**/*.{js,ts,jsx,tsx,mdx}",
@@ -9,11 +10,104 @@ const config: Config = {
theme: {
extend: {
colors: {
- background: "var(--background)",
- foreground: "var(--foreground)",
+ background: "hsl(var(--background))",
+ foreground: "hsl(var(--foreground))",
+ card: {
+ DEFAULT: "hsl(var(--card))",
+ foreground: "hsl(var(--card-foreground))",
+ },
+ popover: {
+ DEFAULT: "hsl(var(--popover))",
+ foreground: "hsl(var(--popover-foreground))",
+ },
+ primary: {
+ DEFAULT: "hsl(var(--primary))",
+ foreground: "hsl(var(--primary-foreground))",
+ },
+ secondary: {
+ DEFAULT: "hsl(var(--secondary))",
+ foreground: "hsl(var(--secondary-foreground))",
+ },
+ muted: {
+ DEFAULT: "hsl(var(--muted))",
+ foreground: "hsl(var(--muted-foreground))",
+ },
+ accent: {
+ DEFAULT: "hsl(var(--accent))",
+ foreground: "hsl(var(--accent-foreground))",
+ },
+ destructive: {
+ DEFAULT: "hsl(var(--destructive))",
+ foreground: "hsl(var(--destructive-foreground))",
+ },
+ border: "hsl(var(--border))",
+ input: "hsl(var(--input))",
+ ring: "hsl(var(--ring))",
+ chart: {
+ "1": "hsl(var(--chart-1))",
+ "2": "hsl(var(--chart-2))",
+ "3": "hsl(var(--chart-3))",
+ "4": "hsl(var(--chart-4))",
+ "5": "hsl(var(--chart-5))",
+ },
+ sidebar: {
+ DEFAULT: "hsl(var(--sidebar-background))",
+ foreground: "hsl(var(--sidebar-foreground))",
+ primary: "hsl(var(--sidebar-primary))",
+ "primary-foreground": "hsl(var(--sidebar-primary-foreground))",
+ accent: "hsl(var(--sidebar-accent))",
+ "accent-foreground": "hsl(var(--sidebar-accent-foreground))",
+ border: "hsl(var(--sidebar-border))",
+ ring: "hsl(var(--sidebar-ring))",
+ },
+ },
+ borderRadius: {
+ lg: "var(--radius)",
+ md: "calc(var(--radius) - 2px)",
+ sm: "calc(var(--radius) - 4px)",
+ },
+ keyframes: {
+ "accordion-down": {
+ from: {
+ height: "0",
+ },
+ to: {
+ height: "var(--radix-accordion-content-height)",
+ },
+ },
+ "accordion-up": {
+ from: {
+ height: "var(--radix-accordion-content-height)",
+ },
+ to: {
+ height: "0",
+ },
+ },
+ "collapsible-down": {
+ from: {
+ height: "0",
+ },
+ to: {
+ height: "var(--radix-collapsible-content-height)",
+ },
+ },
+ "collapsible-up": {
+ from: {
+ height: "var(--radix-collapsible-content-height)",
+ },
+ to: {
+ height: "0",
+ },
+ },
+ },
+ animation: {
+ "accordion-down": "accordion-down 0.2s ease-out",
+ "accordion-up": "accordion-up 0.2s ease-out",
+ "collapsible-down": "collapsible-down 0.2s ease-out",
+ "collapsible-up": "collapsible-up 0.2s ease-out",
},
},
},
- plugins: [],
+ plugins: [require("tailwindcss-animate"), require("@tailwindcss/typography")],
};
export default config;
diff --git a/tsconfig.json b/tsconfig.json
index c133409..7a51449 100644
--- a/tsconfig.json
+++ b/tsconfig.json
@@ -22,6 +22,6 @@
"@/*": ["./src/*"]
}
},
- "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"],
+ "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts", "next.config.mjs"],
"exclude": ["node_modules"]
}