diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md new file mode 100644 index 000000000..e2a9c48c0 --- /dev/null +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -0,0 +1,16 @@ +# Description + +Please include a summary of the changes and the related issue. Please also include relevant motivation and context. + +## Checklist before requesting a review + +Please delete options that are not relevant. + +- [ ] My code follows the style guidelines of this project +- [ ] I have performed a self-review of my code +- [ ] I have commented hard-to-understand areas +- [ ] I have ideally added tests that prove my fix is effective or that my feature works +- [ ] New and existing unit tests pass locally with my changes +- [ ] Any dependent changes have been merged + +## Screenshots (if appropriate): diff --git a/.vscode/settings.json b/.vscode/settings.json index 3ecb8fcd5..1bb4e1d7d 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,6 +1,9 @@ { - "[python]": { - "editor.defaultFormatter": "ms-python.autopep8" - }, - "python.formatting.provider": "none" + "[python]": { + "editor.defaultFormatter": "ms-python.autopep8" + }, + "python.formatting.provider": "none", + "editor.codeActionsOnSave": { + "source.organizeImports": true + } } diff --git a/frontend/.eslintrc.json b/frontend/.eslintrc.json index bffb357a7..52dd97ae3 100644 --- a/frontend/.eslintrc.json +++ b/frontend/.eslintrc.json @@ -1,3 +1,14 @@ { - "extends": "next/core-web-vitals" + "extends": [ + "next/core-web-vitals", + "eslint:recommended", + "plugin:@typescript-eslint/recommended" + ], + "plugins": ["@typescript-eslint"], + "parser": "@typescript-eslint/parser", + "rules": { + "no-unused-vars": "off", + "@typescript-eslint/no-unused-vars": "error", + "@typescript-eslint/no-explicit-any": "error" + } } diff --git a/frontend/app/(auth)/logout/page.tsx b/frontend/app/(auth)/logout/page.tsx index ee38c463f..2160b3fd5 100644 --- a/frontend/app/(auth)/logout/page.tsx +++ b/frontend/app/(auth)/logout/page.tsx @@ -1,12 +1,12 @@ "use client"; -import { useEffect, useRef, useState } from "react"; -import { useRouter } from "next/navigation"; -import { useSupabase } from "@/app/supabase-provider"; -import Toast, { ToastRef } from "@/app/components/ui/Toast"; -import PageHeading from "@/app/components/ui/PageHeading"; import Button from "@/app/components/ui/Button"; import Card from "@/app/components/ui/Card"; +import PageHeading from "@/app/components/ui/PageHeading"; +import Toast, { ToastRef } from "@/app/components/ui/Toast"; +import { useSupabase } from "@/app/supabase-provider"; import Link from "next/link"; +import { useRouter } from "next/navigation"; +import { useRef, useState } from "react"; export default function Logout() { const { supabase } = useSupabase(); diff --git a/frontend/app/(home)/Features.tsx b/frontend/app/(home)/Features.tsx index 40136413b..979456c0b 100644 --- a/frontend/app/(home)/Features.tsx +++ b/frontend/app/(home)/Features.tsx @@ -1,6 +1,4 @@ import { FC, ReactNode } from "react"; -import PageHeading from "../components/ui/PageHeading"; -import Card from "../components/ui/Card"; import { GiArtificialIntelligence, GiBrain, @@ -9,10 +7,9 @@ import { GiLockedDoor, GiOpenBook, } from "react-icons/gi"; +import Card from "../components/ui/Card"; -interface FeaturesProps {} - -const Features: FC = ({}) => { +const Features: FC = () => { return (
diff --git a/frontend/app/(home)/Hero.tsx b/frontend/app/(home)/Hero.tsx index 29fb642d1..dedc4bda6 100644 --- a/frontend/app/(home)/Hero.tsx +++ b/frontend/app/(home)/Hero.tsx @@ -1,13 +1,11 @@ "use client"; +import { motion, useScroll, useSpring, useTransform } from "framer-motion"; import Link from "next/link"; import { FC, useRef } from "react"; -import Button from "../components/ui/Button"; import { MdNorthEast } from "react-icons/md"; -import { motion, useScroll, useSpring, useTransform } from "framer-motion"; +import Button from "../components/ui/Button"; -interface HeroProps {} - -const Hero: FC = ({}) => { +const Hero: FC = () => { const targetRef = useRef(null); const { scrollYProgress } = useScroll({ target: targetRef, diff --git a/frontend/app/chat/page.tsx b/frontend/app/chat/page.tsx index 9ea2d71fc..c406cf70a 100644 --- a/frontend/app/chat/page.tsx +++ b/frontend/app/chat/page.tsx @@ -1,14 +1,14 @@ "use client"; -import { useState } from "react"; import axios from "axios"; -import Card from "../components/ui/Card"; -import Button from "../components/ui/Button"; -import Modal from "../components/ui/Modal"; +import { redirect } from "next/navigation"; +import { useState } from "react"; import { MdSettings } from "react-icons/md"; -import ChatMessages from "./ChatMessages"; +import Button from "../components/ui/Button"; +import Card from "../components/ui/Card"; +import Modal from "../components/ui/Modal"; import PageHeading from "../components/ui/PageHeading"; import { useSupabase } from "../supabase-provider"; -import { redirect } from "next/navigation"; +import ChatMessages from "./ChatMessages"; export default function ChatPage() { const [question, setQuestion] = useState(""); @@ -17,7 +17,7 @@ export default function ChatPage() { const [temperature, setTemperature] = useState(0); const [maxTokens, setMaxTokens] = useState(500); const [isPending, setIsPending] = useState(false); - const { supabase, session } = useSupabase(); + const { session } = useSupabase(); if (session === null) { redirect("/login"); } diff --git a/frontend/app/components/NavBar/DarkModeToggle.tsx b/frontend/app/components/NavBar/DarkModeToggle.tsx index d81d796e8..0af98b139 100644 --- a/frontend/app/components/NavBar/DarkModeToggle.tsx +++ b/frontend/app/components/NavBar/DarkModeToggle.tsx @@ -1,11 +1,9 @@ "use client"; import { FC, useEffect, useLayoutEffect, useState } from "react"; -import Button from "../ui/Button"; import { MdDarkMode, MdLightMode } from "react-icons/md"; +import Button from "../ui/Button"; -interface DarkModeToggleProps {} - -const DarkModeToggle: FC = ({}) => { +const DarkModeToggle: FC = () => { const [dark, setDark] = useState(false); useLayoutEffect(() => { diff --git a/frontend/app/components/NavBar/MobileMenu.tsx b/frontend/app/components/NavBar/MobileMenu.tsx index 285fe073e..8134c371b 100644 --- a/frontend/app/components/NavBar/MobileMenu.tsx +++ b/frontend/app/components/NavBar/MobileMenu.tsx @@ -1,13 +1,10 @@ -import { FC, ReactNode, useState } from "react"; import * as Dialog from "@radix-ui/react-dialog"; -import { MdClose, MdMenu } from "react-icons/md"; import { AnimatePresence, motion } from "framer-motion"; -import Button from "../ui/Button"; +import { FC, useState } from "react"; +import { MdClose, MdMenu } from "react-icons/md"; import NavItems from "./NavItems"; -interface MobileMenuProps {} - -const MobileMenu: FC = ({}) => { +const MobileMenu: FC = () => { const [open, setOpen] = useState(false); return ( diff --git a/frontend/app/components/NavBar/index.tsx b/frontend/app/components/NavBar/index.tsx index 628c1db45..1e1b6a3b7 100644 --- a/frontend/app/components/NavBar/index.tsx +++ b/frontend/app/components/NavBar/index.tsx @@ -1,17 +1,13 @@ "use client"; import Image from "next/image"; import { FC, useEffect, useRef, useState } from "react"; -import logo from "../../logo.png"; -import Link from "next/link"; -import Button from "../ui/Button"; -import DarkModeToggle from "./DarkModeToggle"; + import { motion } from "framer-motion"; +import Link from "next/link"; import MobileMenu from "./MobileMenu"; import NavItems from "./NavItems"; -interface NavBarProps {} - -const NavBar: FC = ({}) => { +const NavBar: FC = () => { const scrollPos = useRef(0); const [hidden, setHidden] = useState(false); @@ -42,7 +38,7 @@ const NavBar: FC = ({}) => { Quivr Logo {} +type CardProps = HTMLAttributes; const Card: FC = forwardRef( ({ children, className, ...props }, ref) => { diff --git a/frontend/app/components/ui/Field.tsx b/frontend/app/components/ui/Field.tsx index 3ed4d79c6..418c96a46 100644 --- a/frontend/app/components/ui/Field.tsx +++ b/frontend/app/components/ui/Field.tsx @@ -1,7 +1,6 @@ import { cn } from "@/lib/utils"; import { DetailedHTMLProps, - FC, InputHTMLAttributes, RefObject, forwardRef, @@ -17,7 +16,7 @@ interface FieldProps } const Field = forwardRef( - ({ label, className, name, id, ...props }: FieldProps, forwardedRef) => { + ({ label, className, name, ...props }: FieldProps, forwardedRef) => { return (
{label && ( diff --git a/frontend/app/components/ui/Spinner.tsx b/frontend/app/components/ui/Spinner.tsx index 722b54160..951f20db8 100644 --- a/frontend/app/components/ui/Spinner.tsx +++ b/frontend/app/components/ui/Spinner.tsx @@ -1,9 +1,7 @@ import { FC } from "react"; import { FaSpinner } from "react-icons/fa"; -interface SpinnerProps {} - -const Spinner: FC = ({}) => { +const Spinner: FC = () => { return ; }; diff --git a/frontend/app/components/ui/Toast.tsx b/frontend/app/components/ui/Toast.tsx index e8b3e3f9c..f35c53643 100644 --- a/frontend/app/components/ui/Toast.tsx +++ b/frontend/app/components/ui/Toast.tsx @@ -1,10 +1,10 @@ "use client"; -import { ReactNode, forwardRef, useImperativeHandle, useState } from "react"; -import * as ToastPrimitive from "@radix-ui/react-toast"; -import Button from "./Button"; -import { VariantProps, cva } from "class-variance-authority"; import { cn, generateUniqueId } from "@/lib/utils"; +import * as ToastPrimitive from "@radix-ui/react-toast"; +import { VariantProps, cva } from "class-variance-authority"; import { AnimatePresence, motion } from "framer-motion"; +import { ReactNode, forwardRef, useImperativeHandle, useState } from "react"; +import Button from "./Button"; export interface ToastRef { publish: (toast: ToastContent) => void; @@ -38,76 +38,74 @@ interface ToastProps children?: ReactNode; } -export const Toast = forwardRef( - ({ children, variant, ...props }: ToastProps, forwardedRef) => { - const [toasts, setToasts] = useState([]); +export const Toast = forwardRef(({ ...props }: ToastProps, forwardedRef) => { + const [toasts, setToasts] = useState([]); - const toggleToast = (value: boolean, index: number) => { - setToasts((toasts) => - toasts.map((toast, i) => { - if (i === index) { - toast.open = value; - } - return toast; - }) - ); - }; - - useImperativeHandle( - forwardedRef, - (): ToastRef => ({ - publish: (toast: ToastContent) => { - setToasts((toasts) => { - const newToasts = [...toasts]; - toast.open = true; - toast.id = generateUniqueId(); - newToasts.push(toast); - return newToasts; - }); - }, + const toggleToast = (value: boolean, index: number) => { + setToasts((toasts) => + toasts.map((toast, i) => { + if (i === index) { + toast.open = value; + } + return toast; }) ); + }; - return ( - <> - - {toasts.map((toast, index) => { - if (!toast.open) return; - return ( - toggleToast(value, index)} - asChild - forceMount - key={toast.id} - {...props} + useImperativeHandle( + forwardedRef, + (): ToastRef => ({ + publish: (toast: ToastContent) => { + setToasts((toasts) => { + const newToasts = [...toasts]; + toast.open = true; + toast.id = generateUniqueId(); + newToasts.push(toast); + return newToasts; + }); + }, + }) + ); + + return ( + <> + + {toasts.map((toast, index) => { + if (!toast.open) return; + return ( + toggleToast(value, index)} + asChild + forceMount + key={toast.id} + {...props} + > + - - - {toast.text} - - - - - - - ); - })} - - - - ); - } -); + + {toast.text} + + + + + + + ); + })} + + + + ); +}); export const ToastProvider = ({ children }: { children?: ReactNode }) => { return {children}; diff --git a/frontend/app/explore/DocumentItem/DocumentData.tsx b/frontend/app/explore/DocumentItem/DocumentData.tsx index b1d861a0c..065f79253 100644 --- a/frontend/app/explore/DocumentItem/DocumentData.tsx +++ b/frontend/app/explore/DocumentItem/DocumentData.tsx @@ -1,5 +1,4 @@ import axios from "axios"; -import { Document } from "../types"; import { useSupabase } from "../../supabase-provider"; interface DocumentDataProps { @@ -7,21 +6,22 @@ interface DocumentDataProps { } const DocumentData = async ({ documentName }: DocumentDataProps) => { - const { supabase, session } = useSupabase(); + const { session } = useSupabase(); if (!session) { - throw new Error('User session not found'); + throw new Error("User session not found"); } const res = await axios.get( `${process.env.NEXT_PUBLIC_BACKEND_URL}/explore/${documentName}`, { headers: { - 'Authorization': `Bearer ${session.access_token}`, + Authorization: `Bearer ${session.access_token}`, }, } ); + // TODO: review the logic of this part and try to use unknown instead of any + // eslint-disable-next-line @typescript-eslint/no-explicit-any const documents = res.data.documents as any[]; - const doc = documents[0]; return (

No. of documents: {documents.length}

diff --git a/frontend/app/explore/DocumentItem/index.tsx b/frontend/app/explore/DocumentItem/index.tsx index 5b22ad918..45c79e1e1 100644 --- a/frontend/app/explore/DocumentItem/index.tsx +++ b/frontend/app/explore/DocumentItem/index.tsx @@ -1,13 +1,13 @@ "use client"; -import { Document } from "../types"; -import Button from "../../components/ui/Button"; -import Modal from "../../components/ui/Modal"; -import { AnimatedCard } from "../../components/ui/Card"; -import { Dispatch, SetStateAction, Suspense, useState } from "react"; -import axios from "axios"; -import DocumentData from "./DocumentData"; import Spinner from "@/app/components/ui/Spinner"; import { useSupabase } from "@/app/supabase-provider"; +import axios from "axios"; +import { Dispatch, SetStateAction, Suspense, useState } from "react"; +import Button from "../../components/ui/Button"; +import { AnimatedCard } from "../../components/ui/Card"; +import Modal from "../../components/ui/Modal"; +import { Document } from "../types"; +import DocumentData from "./DocumentData"; interface DocumentProps { document: Document; @@ -16,17 +16,16 @@ interface DocumentProps { const DocumentItem = ({ document, setDocuments }: DocumentProps) => { const [isDeleting, setIsDeleting] = useState(false); - const { supabase, session } = useSupabase() + const { session } = useSupabase(); if (!session) { - throw new Error('User session not found'); + throw new Error("User session not found"); } - const deleteDocument = async (name: string) => { setIsDeleting(true); try { console.log(`Deleting Document ${name}`); - const response = await axios.delete( + await axios.delete( `${process.env.NEXT_PUBLIC_BACKEND_URL}/explore/${name}`, { headers: { @@ -57,7 +56,7 @@ const DocumentItem = ({ document, setDocuments }: DocumentProps) => { Trigger={} > }> - {/* @ts-expect-error */} + {/* @ts-expect-error TODO: check if DocumentData component can be sync */} diff --git a/frontend/app/explore/page.tsx b/frontend/app/explore/page.tsx index e98c821c9..66cfeb0a5 100644 --- a/frontend/app/explore/page.tsx +++ b/frontend/app/explore/page.tsx @@ -1,19 +1,19 @@ "use client"; -import { useState, useEffect } from "react"; import axios from "axios"; +import { AnimatePresence } from "framer-motion"; +import Link from "next/link"; +import { redirect } from "next/navigation"; +import { useEffect, useState } from "react"; +import Button from "../components/ui/Button"; +import Spinner from "../components/ui/Spinner"; +import { useSupabase } from "../supabase-provider"; import DocumentItem from "./DocumentItem"; import { Document } from "./types"; -import Button from "../components/ui/Button"; -import Link from "next/link"; -import Spinner from "../components/ui/Spinner"; -import { AnimatePresence } from "framer-motion"; -import { useSupabase } from "../supabase-provider"; -import { redirect } from "next/navigation"; export default function ExplorePage() { const [documents, setDocuments] = useState([]); const [isPending, setIsPending] = useState(true); - const { supabase, session } = useSupabase(); + const { session } = useSupabase(); if (session === null) { redirect("/login"); } diff --git a/frontend/app/upload/page.tsx b/frontend/app/upload/page.tsx index aff864538..6fe1be077 100644 --- a/frontend/app/upload/page.tsx +++ b/frontend/app/upload/page.tsx @@ -1,25 +1,25 @@ "use client"; +import { Message } from "@/lib/types"; +import axios from "axios"; +import { AnimatePresence, motion } from "framer-motion"; +import Link from "next/link"; +import { redirect } from "next/navigation"; import { Dispatch, SetStateAction, useCallback, - useState, - useRef, useEffect, + useRef, + useState, } from "react"; import { FileRejection, useDropzone } from "react-dropzone"; -import axios from "axios"; -import { Message } from "@/lib/types"; -import Button from "../components/ui/Button"; import { MdClose } from "react-icons/md"; -import { AnimatePresence, motion } from "framer-motion"; -import Link from "next/link"; +import Button from "../components/ui/Button"; import Card from "../components/ui/Card"; -import PageHeading from "../components/ui/PageHeading"; -import { useSupabase } from "../supabase-provider"; -import { redirect } from "next/navigation"; import Field from "../components/ui/Field"; +import PageHeading from "../components/ui/PageHeading"; import Toast, { ToastRef } from "../components/ui/Toast"; +import { useSupabase } from "../supabase-provider"; export default function UploadPage() { const [message, setMessage] = useState(null); @@ -27,7 +27,7 @@ export default function UploadPage() { const [files, setFiles] = useState([]); const [pendingFileIndex, setPendingFileIndex] = useState(0); const urlInputRef = useRef(null); - const { supabase, session } = useSupabase(); + const { session } = useSupabase(); if (session === null) { redirect("/login"); } @@ -83,10 +83,10 @@ export default function UploadPage() { type: response.data.type, text: response.data.message, }); - } catch (error: any) { + } catch (error: unknown) { setMessage({ type: "error", - text: "Failed to crawl website: " + error.toString(), + text: "Failed to crawl website: " + JSON.stringify(error), }); } }, [session.access_token]); @@ -113,10 +113,10 @@ export default function UploadPage() { ? "File uploaded successfully: " : "") + JSON.stringify(response.data.message), }); - } catch (error: any) { + } catch (error: unknown) { setMessage({ type: "error", - text: "Failed to upload file: " + error.toString(), + text: "Failed to upload file: " + JSON.stringify(error), }); } }, @@ -179,7 +179,7 @@ export default function UploadPage() {
{files.length > 0 ? ( - {files.map((file, i) => ( + {files.map((file) => (