-
-
-
+
diff --git a/app/api/ai/stream/route.ts b/app/api/ai/stream/route.ts
new file mode 100644
index 0000000..577e608
--- /dev/null
+++ b/app/api/ai/stream/route.ts
@@ -0,0 +1,18 @@
+import { NextRequest } from "next/server";
+import { aiUseChatAdapter } from "@upstash/rag-chat/nextjs";
+import { ragChat } from "@/lib/rag";
+
+export const POST = async (req: NextRequest) => {
+ const { messages } = await req.json();
+
+ const lastMsg = messages[messages.length - 1].content;
+ const res = await ragChat.chat(lastMsg, {
+ namespace: "acter-db",
+ streaming: true,
+ historyLength: 100,
+ historyTTL: 604_800,
+ similarityThreshold: 0.7,
+ });
+
+ return aiUseChatAdapter(res);
+};
\ No newline at end of file
diff --git a/app/api/vectors/route.ts b/app/api/vectors/route.ts
new file mode 100644
index 0000000..9768394
--- /dev/null
+++ b/app/api/vectors/route.ts
@@ -0,0 +1,20 @@
+import { ragChat } from '@/lib/rag';
+import { NextRequest, NextResponse } from 'next/server';
+
+export async function POST(req: NextRequest) {
+ try {
+ const { source, namespace, metadata } = await req.json();
+
+ await ragChat.context.add({
+ type: "html",
+ source: source,
+ config: { chunkOverlap: 50, chunkSize: 200 },
+ options: { namespace: namespace, metadata: { title: metadata.title, description: metadata.description } },
+ });
+
+ return NextResponse.json({ message: "Content added successfully to ragChat context!" });
+ } catch (error) {
+ console.error("Error adding content to ragChat context:", error);
+ return NextResponse.json({ error: "Internal Server Error" }, { status: 500 });
+ }
+}
diff --git a/bun.lockb b/bun.lockb
index fc58ebf..7b3beba 100644
Binary files a/bun.lockb and b/bun.lockb differ
diff --git a/components/custom/chat/comp.tsx b/components/custom/chat/comp.tsx
new file mode 100644
index 0000000..eb59a0f
--- /dev/null
+++ b/components/custom/chat/comp.tsx
@@ -0,0 +1,83 @@
+"use client";
+import { Message, useChat } from 'ai/react';
+import { useEffect, useState } from 'react'
+import { CustomTextArea } from "@/components/custom/text.area";
+import { PaperclipIcon, ArrowUpIcon } from "lucide-react";
+import { Button } from '@/components/ui/button';
+import { LibSelector } from '../lib.selector';
+
+export const ChatComp = ({
+ chatId,
+ history,
+ onHasMessagesChange,
+ msgs,
+ }: {
+ chatId: number | string;
+ history?: Message[];
+ onHasMessagesChange?: (hasMessages: boolean) => void;
+ msgs?: (Messages: Message[]) => void;
+ }) => {
+ const [space, setSpace] = useState
(null);
+
+ useEffect(() => {
+ const handleKeyDown = (event: KeyboardEvent) => {
+ if (event.key === "Enter" && !event.shiftKey) {
+ event.preventDefault();
+ const form = document.querySelector("form");
+ if (form) {
+ form.requestSubmit();
+ }
+ }
+ };
+
+ document.addEventListener("keydown", handleKeyDown);
+
+ return () => {
+ document.removeEventListener("keydown", handleKeyDown);
+ };
+ }, []);
+
+ const { messages, handleInputChange, handleSubmit, input, isLoading } =
+ useChat({
+ api: "/api/ai/stream",
+ body: { chatId, namespace: space },
+ initialMessages: history,
+ });
+
+ useEffect(() => {
+ if (messages.length > 0) {
+ if (onHasMessagesChange) {
+ onHasMessagesChange(true);
+ }
+ if (msgs) {
+ msgs(messages);
+ }
+ }
+ }, [ chatId, onHasMessagesChange, messages, msgs]);
+
+ return (
+
+ )
+ }
diff --git a/components/custom/lib.selector.tsx b/components/custom/lib.selector.tsx
new file mode 100644
index 0000000..90569f5
--- /dev/null
+++ b/components/custom/lib.selector.tsx
@@ -0,0 +1,96 @@
+"use client"
+
+import * as React from "react"
+import { Check, ChevronsUpDown } from "lucide-react"
+
+import { cn } from "@/lib/utils"
+import { Button } from "@/components/ui/button"
+import {
+ Command,
+ CommandEmpty,
+ CommandGroup,
+ CommandInput,
+ CommandItem,
+ CommandList,
+} from "@/components/ui/command"
+import {
+ Popover,
+ PopoverContent,
+ PopoverTrigger,
+} from "@/components/ui/popover"
+
+const frameworks = [
+ {
+ value: "acterniy-ui",
+ label: "Acternity UI",
+ },
+ {
+ value: "magic-ui",
+ label: "Magic UI",
+ },
+ {
+ value: "default-ui",
+ label: "Default UI",
+ }
+]
+
+interface SpaceCompProps {
+ onSpaceSelect?: (selectedSpaceId: string | null) => void;
+ }
+
+export function LibSelector({ onSpaceSelect }: SpaceCompProps) {
+ const [open, setOpen] = React.useState(false)
+ const [value, setValue] = React.useState("default-ui")
+
+ React.useEffect(() => {
+ if (onSpaceSelect) {
+ onSpaceSelect(value);
+ }
+ }, [value, onSpaceSelect]);
+
+ return (
+
+
+
+ {value
+ ? frameworks.find((framework) => framework.value === value)?.label
+ : "Select library..."}
+
+
+
+
+
+
+
+ No library found.
+
+ {frameworks.map((framework) => (
+ {
+ setValue(currentValue === value ? "" : currentValue)
+ setOpen(false)
+ }}
+ >
+
+ {framework.label}
+
+ ))}
+
+
+
+
+
+ )
+}
diff --git a/components/custom/msgs/msgs.tsx b/components/custom/msgs/msgs.tsx
new file mode 100644
index 0000000..156e65b
--- /dev/null
+++ b/components/custom/msgs/msgs.tsx
@@ -0,0 +1,67 @@
+import { cn } from "@/lib/utils";
+import { Bot, CheckCheck } from "lucide-react";
+import React from "react";
+import { Badge } from "@/components/ui/badge";
+
+interface MsgProps {
+ content: string;
+ isUser: boolean;
+}
+
+export default function Message({ content, isUser }: MsgProps) {
+ return (
+
+
+
+ {!isUser && (
+
+
+
+ )}
+
+
+
+
+ {!isUser && "x0-GPT"}
+
+ {!isUser && (
+
+
+ Bot
+
+ )}
+
+
+
+ {content}
+
+
+
+
+
+ );
+}
\ No newline at end of file
diff --git a/components/custom/msgs/msgs.wrapper.tsx b/components/custom/msgs/msgs.wrapper.tsx
new file mode 100644
index 0000000..4c0b4fa
--- /dev/null
+++ b/components/custom/msgs/msgs.wrapper.tsx
@@ -0,0 +1,34 @@
+"use client";
+
+import React, { useEffect, useRef } from "react";
+import { type Message as TMsg } from "ai/react";
+import Message from "./msgs";
+
+interface MsgsProps {
+ msgs: TMsg[];
+}
+
+export default function MsgsWrapper({ msgs }: MsgsProps) {
+ const containerRef = useRef(null);
+
+ useEffect(() => {
+ if (containerRef.current) {
+ containerRef.current.scrollTop = containerRef.current.scrollHeight;
+ }
+ }, [msgs]);
+
+ return (
+
+ {msgs.map((message, i) => (
+
+ ))}
+
+ );
+}
\ No newline at end of file
diff --git a/components/custom/sidebar/sidebar.tsx b/components/custom/sidebar/sidebar.tsx
new file mode 100644
index 0000000..e2bc2ac
--- /dev/null
+++ b/components/custom/sidebar/sidebar.tsx
@@ -0,0 +1,90 @@
+import { Button } from "@/components/ui/button";
+import { menuItems } from "@/db/defaults";
+import { cn } from "@/lib/utils";
+import {
+ PanelLeftClose,
+ PanelLeftOpen,
+} from "lucide-react";
+import Image from "next/image";
+import Link from "next/link";
+
+interface SidebarProps {
+ isSidebarExpanded: boolean | string;
+ setIsSidebarExpanded: (expanded: boolean | string) => void;
+}
+
+export const SideBar = ({
+ isSidebarExpanded,
+ setIsSidebarExpanded,
+}: SidebarProps) => {
+ return (
+
+ {/* Header Section */}
+
+ {/* Logo */}
+
+ {/* Toggle Button */}
+ {isSidebarExpanded &&
setIsSidebarExpanded(!isSidebarExpanded)}
+ className="ml-auto"
+ >
+
+ }
+
+
+ {/* Navigation Menu */}
+
+
+ {/* Menu Items */}
+
+ {menuItems.map((item, index) => (
+
+ item.disabled && e.preventDefault()}
+ >
+
+ {isSidebarExpanded && (
+ {item.label}
+ )}
+
+
+ ))}
+
+
+
+
+ {/* Footer Section */}
+
+ {!isSidebarExpanded && (
+
setIsSidebarExpanded(!isSidebarExpanded)}
+ >
+
+
+ )}
+
+
+ );
+};
diff --git a/components/custom/text.area.tsx b/components/custom/text.area.tsx
index 8a7ffd0..d601e2b 100644
--- a/components/custom/text.area.tsx
+++ b/components/custom/text.area.tsx
@@ -17,7 +17,7 @@ const CustomTextArea = React.forwardRef(
const minHeight = 20; // Default height
// Reset the height to minHeight if there's no content
- if (textarea.value.trim() === "" || null || undefined) {
+ if (textarea.value.trim() === "") {
textarea.style.height = `${minHeight}px`;
textarea.style.overflowY = "hidden";
} else {
diff --git a/components/ui/command.tsx b/components/ui/command.tsx
new file mode 100644
index 0000000..ade5441
--- /dev/null
+++ b/components/ui/command.tsx
@@ -0,0 +1,152 @@
+"use client"
+
+import * as React from "react"
+import { type DialogProps } from "@radix-ui/react-dialog"
+import { Command as CommandPrimitive } from "cmdk"
+import { cn } from "@/lib/utils"
+import { Dialog, DialogContent } from "@/components/ui/dialog"
+import { MagnifyingGlassIcon } from "@radix-ui/react-icons"
+
+const Command = React.forwardRef<
+ React.ElementRef,
+ React.ComponentPropsWithoutRef
+>(({ className, ...props }, ref) => (
+
+))
+Command.displayName = CommandPrimitive.displayName
+
+const CommandDialog = ({ children, ...props }: DialogProps) => {
+ return (
+
+
+
+ {children}
+
+
+
+ )
+}
+
+const CommandInput = React.forwardRef<
+ React.ElementRef,
+ React.ComponentPropsWithoutRef
+>(({ className, ...props }, ref) => (
+
+
+
+
+))
+
+CommandInput.displayName = CommandPrimitive.Input.displayName
+
+const CommandList = React.forwardRef<
+ React.ElementRef,
+ React.ComponentPropsWithoutRef
+>(({ className, ...props }, ref) => (
+
+))
+
+CommandList.displayName = CommandPrimitive.List.displayName
+
+const CommandEmpty = React.forwardRef<
+ React.ElementRef,
+ React.ComponentPropsWithoutRef
+>((props, ref) => (
+
+))
+
+CommandEmpty.displayName = CommandPrimitive.Empty.displayName
+
+const CommandGroup = React.forwardRef<
+ React.ElementRef,
+ React.ComponentPropsWithoutRef
+>(({ className, ...props }, ref) => (
+
+))
+
+CommandGroup.displayName = CommandPrimitive.Group.displayName
+
+const CommandSeparator = React.forwardRef<
+ React.ElementRef,
+ React.ComponentPropsWithoutRef
+>(({ className, ...props }, ref) => (
+
+))
+CommandSeparator.displayName = CommandPrimitive.Separator.displayName
+
+const CommandItem = React.forwardRef<
+ React.ElementRef,
+ React.ComponentPropsWithoutRef
+>(({ className, ...props }, ref) => (
+
+))
+
+CommandItem.displayName = CommandPrimitive.Item.displayName
+
+const CommandShortcut = ({
+ className,
+ ...props
+}: React.HTMLAttributes) => {
+ return (
+
+ )
+}
+CommandShortcut.displayName = "CommandShortcut"
+
+export {
+ Command,
+ CommandDialog,
+ CommandInput,
+ CommandList,
+ CommandEmpty,
+ CommandGroup,
+ CommandItem,
+ CommandShortcut,
+ CommandSeparator,
+}
diff --git a/components/ui/popover.tsx b/components/ui/popover.tsx
new file mode 100644
index 0000000..29c7bd2
--- /dev/null
+++ b/components/ui/popover.tsx
@@ -0,0 +1,33 @@
+"use client"
+
+import * as React from "react"
+import * as PopoverPrimitive from "@radix-ui/react-popover"
+
+import { cn } from "@/lib/utils"
+
+const Popover = PopoverPrimitive.Root
+
+const PopoverTrigger = PopoverPrimitive.Trigger
+
+const PopoverAnchor = PopoverPrimitive.Anchor
+
+const PopoverContent = React.forwardRef<
+ React.ElementRef,
+ React.ComponentPropsWithoutRef
+>(({ className, align = "center", sideOffset = 4, ...props }, ref) => (
+
+
+
+))
+PopoverContent.displayName = PopoverPrimitive.Content.displayName
+
+export { Popover, PopoverTrigger, PopoverContent, PopoverAnchor }
diff --git a/components/ui/tooltip.tsx b/components/ui/tooltip.tsx
new file mode 100644
index 0000000..9e74821
--- /dev/null
+++ b/components/ui/tooltip.tsx
@@ -0,0 +1,30 @@
+"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/db/defaults.ts b/db/defaults.ts
index dbef003..1742ecb 100644
--- a/db/defaults.ts
+++ b/db/defaults.ts
@@ -1,3 +1,14 @@
+import {
+ PlusIcon,
+ BookOpenIcon,
+ LayoutDashboardIcon,
+} from "lucide-react";
+
+export const ragConfig = {
+ baseUrl: "https://api.deepseek.com",
+ model: "deepseek-ai/deepseek-llm-67b-chat",
+}
+
export const OfcLinks = {
github: "https://l.devwtf.in/discord",
twitter: "https://x.com/SaidevDhal",
@@ -7,6 +18,12 @@ export const OfcLinks = {
discord: "https://l.devwtf.in/discord"
};
+export const menuItems = [
+ { label: "New Chat", icon: PlusIcon, href: "/chat", disabled: false },
+ { label: "Docs", icon: BookOpenIcon, href: "/docs", disabled: true },
+ { label: "Dashboard", icon: LayoutDashboardIcon, href: "/dashboard", disabled: true },
+];
+
export const DATA = {
name: "Acter",
url: "https://acter.devwtf.in",
diff --git a/lib/rag.ts b/lib/rag.ts
new file mode 100644
index 0000000..0b13788
--- /dev/null
+++ b/lib/rag.ts
@@ -0,0 +1,45 @@
+import { ragConfig } from "@/db/defaults";
+import { RAGChat, togetherai } from "@upstash/rag-chat";
+
+export const ragChat = new RAGChat({
+ model: togetherai(ragConfig.model, { apiKey: process.env.RAG_API_KEY }),
+ promptFn: ({ context, question, chatHistory }) =>
+ `You are Acter, an AI assistant created by Saidev Dhal.
+ Your task is to generate complete, high-quality code solutions based on the user's question or prompt.
+ Use the provided context and chat history to craft the code.
+ Ensure that the generated code incorporates and appropriately uses the components and styles mentioned in the context and explain all the details related to that component.
+ Only create new code without using any component from the context if it is absolutely necessary, and be sure to explain why.
+ If you lack sufficient information in the context or chat history, politely suggest the user provide additional details.
+ ------
+ Chat history:
+ ${chatHistory}
+ ------
+ Context:
+ ${context}
+ ------
+ Question:
+ ${question}
+ ------
+ Full Code:`,
+});
+
+async function AddTXTContext(data: string) {
+ await ragChat.context.add({
+ type: "text",
+ data: data,
+ options: { namespace: "acter-db" },
+ });
+ return true;
+}
+
+async function AddWEBContext(src: string) {
+ await ragChat.context.add({
+ type: "html",
+ source: src,
+ config: { chunkOverlap: 50, chunkSize: 200 },
+ options: { namespace: "acter-db" },
+ });
+ return true;
+}
+
+export { AddTXTContext, AddWEBContext }
\ No newline at end of file
diff --git a/lib/use.local.ts b/lib/use.local.ts
new file mode 100644
index 0000000..058fb7f
--- /dev/null
+++ b/lib/use.local.ts
@@ -0,0 +1,53 @@
+import { useEffect, useState } from "react";
+
+const useLocalStorage = (key: string, initialValue: string | boolean) => {
+ const [value, setValue] = useState(() => {
+ if (typeof window !== 'undefined') {
+ const storedValue = localStorage.getItem(key);
+ // Return the stored value if it exists, otherwise return the initial value
+ return storedValue !== null
+ ? typeof initialValue === "boolean"
+ ? storedValue === "true"
+ : storedValue
+ : initialValue;
+ }
+ return initialValue;
+ });
+
+ const setLocalStorageValue = (newValue: string | boolean) => {
+ setValue(newValue);
+ if (typeof window !== 'undefined') {
+ localStorage.setItem(key, newValue.toString());
+ }
+ };
+
+ useEffect(() => {
+ const handleStorageChange = () => {
+ if (typeof window !== 'undefined') {
+ const newValue = localStorage.getItem(key);
+ // Update state based on the new value from localStorage
+ setValue(
+ newValue !== null
+ ? typeof initialValue === "boolean"
+ ? newValue === "true"
+ : newValue
+ : initialValue,
+ );
+ }
+ };
+
+ if (typeof window !== 'undefined') {
+ window.addEventListener("storage", handleStorageChange);
+ }
+
+ return () => {
+ if (typeof window !== 'undefined') {
+ window.removeEventListener("storage", handleStorageChange);
+ }
+ };
+ }, [key, initialValue]);
+
+ return [value, setLocalStorageValue] as const;
+};
+
+export default useLocalStorage;
\ No newline at end of file
diff --git a/package.json b/package.json
index 638804f..7cd131d 100644
--- a/package.json
+++ b/package.json
@@ -18,15 +18,22 @@
"@nextui-org/tabs": "^2.0.37",
"@prisma/client": "^5.20.0",
"@radix-ui/react-avatar": "^1.1.1",
- "@radix-ui/react-dialog": "^1.1.2",
+ "@radix-ui/react-dialog": "^1.1.4",
"@radix-ui/react-icons": "^1.3.0",
+ "@radix-ui/react-popover": "^1.1.4",
"@radix-ui/react-scroll-area": "^1.2.0",
"@radix-ui/react-slot": "^1.1.0",
+ "@radix-ui/react-tooltip": "^1.1.3",
"@tabler/icons-react": "^3.19.0",
- "@upstash/rag-chat": "^1.6.4",
+ "@upstash/rag-chat": "^2.0.0",
+ "@upstash/redis": "^1.34.2",
+ "@upstash/vector": "^1.1.7",
+ "ai": "^3.4.9",
"class-variance-authority": "^0.7.0",
"clsx": "^2.1.1",
+ "cmdk": "1.0.0",
"framer-motion": "^11.11.1",
+ "fs": "^0.0.1-security",
"lucide-react": "^0.446.0",
"next": "14.2.13",
"next-auth": "^5.0.0-beta.22",
diff --git a/tsconfig.json b/tsconfig.json
index e7ff90f..590f435 100644
--- a/tsconfig.json
+++ b/tsconfig.json
@@ -1,6 +1,10 @@
{
"compilerOptions": {
- "lib": ["dom", "dom.iterable", "esnext"],
+ "lib": [
+ "dom",
+ "dom.iterable",
+ "esnext"
+ ],
"allowJs": true,
"skipLibCheck": true,
"strict": true,
@@ -18,9 +22,18 @@
}
],
"paths": {
- "@/*": ["./*"]
+ "@/*": [
+ "./*"
+ ]
}
},
- "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"],
- "exclude": ["node_modules"]
+ "include": [
+ "next-env.d.ts",
+ "**/*.ts",
+ "**/*.tsx",
+ ".next/types/**/*.ts"
+ ],
+ "exclude": [
+ "node_modules"
+ ]
}