This commit is contained in:
DESKTOP-RTLN3BA\$punk 2024-08-12 00:32:42 -07:00
parent 24f641347d
commit 63146aa9b7
86 changed files with 18766 additions and 0 deletions

View file

@ -0,0 +1,8 @@
import React from 'react';
import MarkdownPreview from '@uiw/react-markdown-preview';
export default function MarkDownTest({source} : {source: string}) {
return (
<MarkdownPreview source={source} style={{ padding: 16 }} />
)
}

344
web/app/chat/page.tsx Normal file
View file

@ -0,0 +1,344 @@
"use client";
import React, { useEffect, useState } from "react";
import Image from "next/image";
import logo from "@/public/SurfSense.png";
import { FileCheck } from "lucide-react";
import {
Collapsible,
CollapsibleContent,
CollapsibleTrigger,
} from "@/components/ui/collapsible";
import {
Table,
TableBody,
TableCell,
TableRow,
} from "@/components/ui/table";
import MarkDownTest from "./markdown";
import { useRouter } from "next/navigation";
type Document = {
BrowsingSessionId: string;
VisitedWebPageURL: string;
VisitedWebPageTitle: string;
VisitedWebPageDateWithTimeInISOString: string;
VisitedWebPageReffererURL: string;
VisitedWebPageVisitDurationInMilliseconds: number;
VisitedWebPageContent: string;
};
// type Description = {
// response: string;
// };
// type NormalResponse = {
// response: string;
// relateddocs: Document[];
// };
// type ChatMessage = {
// type: string;
// userquery: string;
// message: NormalResponse | string;
// };
function ProtectedPage() {
// const navigate = useNavigate();
const router = useRouter();
const [loading, setLoading] = useState<boolean>(false);
const [currentChat, setCurrentChat] = useState<any[]>([]);
useEffect(() => {
const verifyToken = async () => {
const token = window.localStorage.getItem('token');
// console.log(token)
try {
const response = await fetch(`${process.env.NEXT_PUBLIC_BACKEND_URL!}/verify-token/${token}`);
if (!response.ok) {
throw new Error('Token verification failed');
}else{
const NEO4JURL = localStorage.getItem('neourl');
const NEO4JUSERNAME = localStorage.getItem('neouser');
const NEO4JPASSWORD = localStorage.getItem('neopass');
const OPENAIKEY = localStorage.getItem('openaikey');
const check = (NEO4JURL && NEO4JUSERNAME && NEO4JPASSWORD && OPENAIKEY)
if(!check){
router.push('/settings');
}
}
} catch (error) {
window.localStorage.removeItem('token');
router.push('/login');
}
};
verifyToken();
}, [router]);
const handleSubmit = async (formData: any) => {
setLoading(true);
const query = formData.get("query");
if (!query) {
console.log("Query cant be empty!!");
return;
}
const requestOptions = {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
query: query,
neourl: localStorage.getItem('neourl'),
neouser: localStorage.getItem('neouser'),
neopass: localStorage.getItem('neopass'),
openaikey: localStorage.getItem('openaikey'),
apisecretkey: process.env.NEXT_PUBLIC_API_SECRET_KEY
}),
};
fetch(`${process.env.NEXT_PUBLIC_BACKEND_URL!}`, requestOptions)
.then(res=>res.json())
.then(data=> {
let cur = currentChat;
cur.push({
type: "normal",
userquery: query,
message: data,
});
setCurrentChat([...cur]);
setLoading(false);
});
};
const getDocDescription = async (document: Document) => {
setLoading(true);
const requestOptions = {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
query: JSON.stringify(document),
neourl: localStorage.getItem('neourl'),
neouser: localStorage.getItem('neouser'),
neopass: localStorage.getItem('neopass'),
openaikey: localStorage.getItem('openaikey'),
apisecretkey: process.env.NEXT_PUBLIC_API_SECRET_KEY
}),
};
const response = await fetch(
`${process.env.NEXT_PUBLIC_BACKEND_URL!}/kb/doc`,
requestOptions
);
const res = await response.json();
let cur = currentChat;
cur.push({
type: "description",
doctitle: document.VisitedWebPageTitle,
message: res.response,
});
setLoading(false);
setCurrentChat([...cur]);
// console.log(document);
};
if (currentChat) {
return (
<>
<div className="relative flex h-[calc(100vh_-_theme(spacing.16))] overflow-hidden mt-16">
<div className="group w-full overflow-auto pl-0 peer-[[data-state=open]]:lg:pl-[250px] peer-[[data-state=open]]:xl:pl-[300px]">
<div className="pb-[200px] pt-4 md:pt-10">
<div className="mx-auto max-w-4xl px-4 flex flex-col gap-3">
<div className="bg-background flex flex-col gap-2 rounded-lg border p-8">
<h1 className="text-sm font-semibold">
Welcome to SurfSense
</h1>
<p className="text-muted-foreground leading-normal">
🧠 Ask Your Knowledge Graph Brain 🧠
</p>
</div>
{currentChat.map((chat, index) => {
// console.log("chat", chat);
if (chat.type === "normal") {
return (
<div
className="bg-background flex flex-col gap-2 rounded-lg border p-8"
key={index}
>
<p className="text-3xl font-semibold">
{chat.userquery}
</p>
<p className="font-sm font-semibold">
SurfSense Response:
</p>
<MarkDownTest source={chat.message.response} />
<p className="font-sm font-semibold">
Related Browsing Sessions
</p>
{
//@ts-ignore
chat.message.relateddocs.map((doc) => {
return (
<Collapsible className="border rounded-lg p-3">
<CollapsibleTrigger className="flex justify-between gap-2 mb-2">
<FileCheck />
{doc.VisitedWebPageTitle}
</CollapsibleTrigger>
<CollapsibleContent className="flex flex-col gap-4">
<Table>
<TableBody>
<TableRow>
<TableCell className="font-medium">
Browsing Session Id
</TableCell>
<TableCell>
{doc.BrowsingSessionId}
</TableCell>
</TableRow>
<TableRow>
<TableCell className="font-medium">
URL
</TableCell>
<TableCell>
{doc.VisitedWebPageURL}
</TableCell>
</TableRow>
<TableRow>
<TableCell className="font-medium">
Reffering URL
</TableCell>
<TableCell>
{doc.VisitedWebPageReffererURL}
</TableCell>
</TableRow>
<TableRow>
<TableCell className="font-medium">
Date & Time Visited
</TableCell>
<TableCell>
{
doc.VisitedWebPageDateWithTimeInISOString
}
</TableCell>
</TableRow>
<TableRow>
<TableCell className="font-medium">
Visit Duration (In Milliseconds)
</TableCell>
<TableCell>
{
doc.VisitedWebPageVisitDurationInMilliseconds
}
</TableCell>
</TableRow>
</TableBody>
</Table>
<button
type="button"
onClick={() => getDocDescription(doc)}
className="text-gray-900 w-full hover:text-white border border-gray-800 hover:bg-gray-900 focus:ring-4 focus:outline-none focus:ring-gray-300 font-medium rounded-lg text-sm px-5 py-2.5 text-center me-2 mb-2 dark:border-gray-600 dark:text-gray-400 dark:hover:text-white dark:hover:bg-gray-600 dark:focus:ring-gray-800"
>
Get More Information
</button>
</CollapsibleContent>
</Collapsible>
);
})
}
</div>
);
}
if (chat.type === "description") {
return (
<div
className="bg-background flex flex-col gap-2 rounded-lg border p-8"
key={index}
>
<p className="text-3xl font-semibold">
{chat.doctitle}
</p>
<MarkDownTest source={chat.message} />
</div>
);
}
})}
</div>
<div className="h-px w-full"></div>
</div>
<div className="from-muted/30 to-muted/30 animate-in dark:from-background/10 dark:to-background/80 inset-x-0 bottom-0 w-full duration-300 ease-in-out peer-[[data-state=open]]:group-[]:lg:pl-[250px] peer-[[data-state=open]]:group-[]:xl:pl-[300px] dark:from-10%">
<div className="mx-auto sm:max-w-4xl sm:px-4">
<div className={loading ? "rounded-md p-4 w-full my-4" : "hidden"}>
<div className="animate-pulse flex space-x-4">
<div className="rounded-full bg-slate-700 h-10 w-10">
</div>
<div className="flex-1 space-y-6 py-1">
<div className="h-2 bg-slate-700 rounded"></div>
<div className="space-y-3">
<div className="grid grid-cols-3 gap-4">
<div className="h-2 bg-slate-700 rounded col-span-2"></div>
<div className="h-2 bg-slate-700 rounded col-span-1"></div>
</div>
<div className="h-2 bg-slate-700 rounded"></div>
</div>
</div>
</div>
</div>
<div className="bg-background space-y-4 border-t px-4 py-2 shadow-lg sm:rounded-t-xl sm:border md:py-4">
<form action={handleSubmit}>
<div className="bg-background relative flex max-h-60 w-full grow flex-col overflow-hidden px-8 sm:rounded-md sm:border sm:px-12">
<Image
className="inline-flex items-center justify-center whitespace-nowrap 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 border border-input shadow-sm hover:bg-accent hover:text-accent-foreground h-9 w-9 bg-background absolute left-0 top-[13px] size-8 rounded-full p-0 sm:left-4"
src={logo}
alt="aiicon"
/>
<span className="sr-only">New Chat</span>
<textarea
placeholder="Send a message."
className="min-h-[60px] w-full resize-none bg-transparent px-4 py-[1.3rem] focus-within:outline-none sm:text-sm"
name="query"
></textarea>
<div className="absolute right-0 top-[13px] sm:right-4">
<button
className="inline-flex items-center justify-center 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 bg-primary text-primary-foreground shadow hover:bg-primary/90 h-9 w-9"
type="submit"
data-state="closed"
>
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 256 256"
fill="currentColor"
className="size-4"
>
<path d="M200 32v144a8 8 0 0 1-8 8H67.31l34.35 34.34a8 8 0 0 1-11.32 11.32l-48-48a8 8 0 0 1 0-11.32l48-48a8 8 0 0 1 11.32 11.32L67.31 168H184V32a8 8 0 0 1 16 0Z"></path>
</svg>
<span className="sr-only">Send message</span>
</button>
</div>
</div>
</form>
</div>
</div>
</div>
</div>
</div>
</>
);
}
}
export default ProtectedPage;

BIN
web/app/favicon.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

69
web/app/globals.css Normal file
View file

@ -0,0 +1,69 @@
@tailwind base;
@tailwind components;
@tailwind utilities;
@layer base {
:root {
--background: 0 0% 100%;
--foreground: 0 0% 3.9%;
--card: 0 0% 100%;
--card-foreground: 0 0% 3.9%;
--popover: 0 0% 100%;
--popover-foreground: 0 0% 3.9%;
--primary: 0 0% 9%;
--primary-foreground: 0 0% 98%;
--secondary: 0 0% 96.1%;
--secondary-foreground: 0 0% 9%;
--muted: 0 0% 96.1%;
--muted-foreground: 0 0% 45.1%;
--accent: 0 0% 96.1%;
--accent-foreground: 0 0% 9%;
--destructive: 0 84.2% 60.2%;
--destructive-foreground: 0 0% 98%;
--border: 0 0% 89.8%;
--input: 0 0% 89.8%;
--ring: 0 0% 3.9%;
--radius: 0.5rem;
--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%;
}
.dark {
--background: 0 0% 3.9%;
--foreground: 0 0% 98%;
--card: 0 0% 3.9%;
--card-foreground: 0 0% 98%;
--popover: 0 0% 3.9%;
--popover-foreground: 0 0% 98%;
--primary: 0 0% 98%;
--primary-foreground: 0 0% 9%;
--secondary: 0 0% 14.9%;
--secondary-foreground: 0 0% 98%;
--muted: 0 0% 14.9%;
--muted-foreground: 0 0% 63.9%;
--accent: 0 0% 14.9%;
--accent-foreground: 0 0% 98%;
--destructive: 0 62.8% 30.6%;
--destructive-foreground: 0 0% 98%;
--border: 0 0% 14.9%;
--input: 0 0% 14.9%;
--ring: 0 0% 83.1%;
--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%;
}
}
@layer base {
* {
@apply border-border;
}
body {
@apply bg-background text-foreground;
}
}

73
web/app/layout.tsx Normal file
View file

@ -0,0 +1,73 @@
import type { Metadata } from "next";
import { Inter } from "next/font/google";
import "./globals.css";
import { ThemeProvider } from "@/components/theme-provider";
import { MainNavbar } from "@/components/homepage/NavBar";
import { Toaster } from "@/components/ui/toaster"
import { Footer } from "@/components/homepage/Footer";
const inter = Inter({ subsets: ["latin"] });
export const metadata: Metadata = {
title: "SurfSense - A Knowledge Graph Brain for World Wide Web Surfers.",
description:
"Save anything you see or browse on the Internet and save it to ask AI about it.",
openGraph: {
images: [
{
url: "https://surfsense.net/og-image.png",
width: 1200,
height: 627,
alt: "SurfSense - A Knowledge Graph Brain for World Wide Web Surfers.",
},
],
},
twitter: {
card: "summary_large_image",
site: "https://surfsense.net",
creator: "https://surfsense.net",
title: "SurfSense - A Knowledge Graph Brain for World Wide Web Surfers.",
description:
"Save anything you see or browse on the Internet and save it to ask AI about it.",
images: [
{
url: "https://surfsense.net/og-image.png",
width: 1200,
height: 627,
alt: "SurfSense - A Knowledge Graph Brain for World Wide Web Surfers.",
},
],
},
};
export default function RootLayout({
children,
}: Readonly<{
children: React.ReactNode;
}>) {
return (
<html lang="en">
<body className={inter.className}>
<ThemeProvider
attribute="class"
defaultTheme="system"
enableSystem
disableTransitionOnChange
>
<div className="flex flex-col">
<MainNavbar />
<div className="grow">
{children}
</div>
<Footer />
</div>
<Toaster />
</ThemeProvider>
</body>
</html>
);
}

9
web/app/login/page.tsx Normal file
View file

@ -0,0 +1,9 @@
import { LoginForm } from "@/components/logins/LoginForm"
const page = () => {
return (
<><LoginForm /></>
)
}
export default page

10
web/app/page.tsx Normal file
View file

@ -0,0 +1,10 @@
import { HomePage } from "@/components/homepage/HomePage";
export default function Home() {
return (
<>
<HomePage />
</>
);
}

80
web/app/settings/page.tsx Normal file
View file

@ -0,0 +1,80 @@
"use client"
import React, { useState } from "react";
import { useRouter } from "next/navigation";
const FillEnvVariables = () => {
const [neourl, setNeourl] = useState('');
const [neouser, setNeouser] = useState('');
const [neopass, setNeopass] = useState('');
const [openaikey, setOpenaiKey] = useState('');
const [error, setError] = useState('');
const [loading, setLoading] = useState(false);
const router = useRouter();
const validateForm = () => {
if (!neourl || !neouser || !neopass || !openaikey) {
setError('All values are required');
return false;
}
setError('');
return true;
};
const handleSubmit = async (event: { preventDefault: () => void; }) => {
event.preventDefault();
if (!validateForm()) return;
setLoading(true);
localStorage.setItem('neourl', neourl);
localStorage.setItem('neouser', neouser);
localStorage.setItem('neopass', neopass);
localStorage.setItem('openaikey', openaikey);
setLoading(false);
router.push('/chat')
};
return (
<section className="bg-gray-50 dark:bg-gray-900">
<div className="flex flex-col items-center justify-center px-6 py-8 mx-auto md:h-screen lg:py-0">
<a href="#" className="flex items-center mb-6 text-2xl font-semibold text-gray-900 dark:text-white">
<img className="w-8 h-8 mr-2" src="./icon-128.png" alt="logo" />
SurfSense
</a>
<div className="w-full bg-white rounded-lg shadow dark:border md:mt-0 sm:max-w-md xl:p-0 dark:bg-gray-800 dark:border-gray-700">
<div className="p-6 space-y-4 md:space-y-6 sm:p-8">
<h1 className="text-xl font-bold leading-tight tracking-tight text-gray-900 md:text-2xl dark:text-white">
Required Values
</h1>
<form className="space-y-4 md:space-y-6" onSubmit={handleSubmit}>
<div>
<label className="block mb-2 text-sm font-medium text-gray-900 dark:text-white">Neo4J URL</label>
<input type="text" value={neourl} onChange={(e) => setNeourl(e.target.value)} name="neourl" id="neourl" className="bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-primary-600 focus:border-primary-600 block w-full p-2.5 dark:bg-gray-700 dark:border-gray-600 dark:placeholder-gray-400 dark:text-white dark:focus:ring-blue-500 dark:focus:border-blue-500" placeholder="name@company.com" />
</div>
<div>
<label className="block mb-2 text-sm font-medium text-gray-900 dark:text-white">Neo4J Username</label>
<input type="text" value={neouser} onChange={(e) => setNeouser(e.target.value)} name="neouser" id="neouser" className="bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-primary-600 focus:border-primary-600 block w-full p-2.5 dark:bg-gray-700 dark:border-gray-600 dark:placeholder-gray-400 dark:text-white dark:focus:ring-blue-500 dark:focus:border-blue-500" placeholder="name@company.com" />
</div>
<div>
<label className="block mb-2 text-sm font-medium text-gray-900 dark:text-white">Neo4J Password</label>
<input type="text" value={neopass} onChange={(e) => setNeopass(e.target.value)} name="neopass" id="neopass" placeholder="••••••••" className="bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-primary-600 focus:border-primary-600 block w-full p-2.5 dark:bg-gray-700 dark:border-gray-600 dark:placeholder-gray-400 dark:text-white dark:focus:ring-blue-500 dark:focus:border-blue-500" />
</div>
<div>
<label className="block mb-2 text-sm font-medium text-gray-900 dark:text-white">OpenAI API Key</label>
<input type="text" value={openaikey} onChange={(e) => setOpenaiKey(e.target.value)} name="openaikey" id="openaikey" className="bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-primary-600 focus:border-primary-600 block w-full p-2.5 dark:bg-gray-700 dark:border-gray-600 dark:placeholder-gray-400 dark:text-white dark:focus:ring-blue-500 dark:focus:border-blue-500" placeholder="name@company.com" />
</div>
<button type="submit" className="mt-4 w-full text-white bg-primary-600 hover:bg-primary-700 focus:ring-4 focus:outline-none focus:ring-primary-300 font-medium rounded-lg text-sm px-5 py-2.5 text-center dark:bg-primary-600 dark:hover:bg-primary-700 dark:focus:ring-primary-800">{loading ? 'Saving....' : 'Save & Proceed'}</button>
{error && <p style={{ color: 'red' }}>{error}</p>}
</form>
</div>
</div>
</div>
</section>
)
}
export default FillEnvVariables

9
web/app/signup/page.tsx Normal file
View file

@ -0,0 +1,9 @@
import { RegisterForm } from "@/components/logins/RegisterForm"
const page = () => {
return (
<RegisterForm />
)
}
export default page