Scaffolding of Natural Language Search

This commit is contained in:
2024-07-30 13:59:40 +08:00
parent b9e0abe7d0
commit 4627da2efb
11 changed files with 3030 additions and 3217 deletions

View File

@@ -14,6 +14,7 @@
"axios": "^1.7.2",
"formik": "^2.4.6",
"framer-motion": "^11.2.10",
"openai": "^4.53.2",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"react-hot-toast": "^2.4.1",

5810
client/pnpm-lock.yaml generated

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,146 @@
import {
Button,
Input,
Kbd,
Modal,
ModalBody,
ModalContent,
ModalFooter,
useDisclosure,
} from "@nextui-org/react";
import { useEffect, useState } from "react";
import { MagnifyingGlassIcon } from "../icons";
import config from "../config";
import instance from "../security/http";
import EcoconnectFullLogo from "./EcoconnectFullLogo";
export default function EcoconnectSearch() {
const [searchQuery, setSearchQuery] = useState("");
const [aiResponse, setAiResponse] = useState("");
const [isQueryLoading, setIsQueryLoading] = useState(false);
const {
isOpen: isAiDialogOpen,
onOpen: onAiDialogOpen,
onOpenChange: onAiDialogOpenChange,
} = useDisclosure();
const dialogOpenChange = () => {
onAiDialogOpenChange();
setSearchQuery("");
setAiResponse("");
};
const handleKeyPress = (event: KeyboardEvent) => {
if (event.ctrlKey && event.key === "s") {
event.preventDefault();
onAiDialogOpen();
}
};
const executeSearch = async () => {
if (searchQuery.length <= 0) return;
setIsQueryLoading(true);
instance
.get(
`${config.serverAddress}/connections/openai-chat-completion/${searchQuery}`
)
.then((response) => {
console.log(response.data.response);
setAiResponse(response.data.response);
})
.finally(() => {
setIsQueryLoading(false);
});
};
useEffect(() => {
window.addEventListener("keydown", handleKeyPress);
return () => {
window.removeEventListener("keydown", handleKeyPress);
};
}, []);
return (
<div className="-mb-1">
<Button
size="sm"
className="p-0"
variant="light"
isDisabled={isAiDialogOpen}
onPress={() => {
onAiDialogOpen();
}}
>
<Input
size="sm"
disabled
className="w-44 h-min"
startContent={<MagnifyingGlassIcon />}
endContent={
<div className="-mr-1">
<Kbd keys={["ctrl"]}>S</Kbd>
</div>
}
placeholder="Search..."
/>
</Button>
<Modal
isOpen={isAiDialogOpen}
onOpenChange={dialogOpenChange}
closeButton={<></>}
placement="top"
>
<ModalContent>
{() => {
return (
<>
<ModalBody>
<div className="py-4 flex flex-col gap-4">
<Input
placeholder="Search..."
size="lg"
value={searchQuery}
onValueChange={setSearchQuery}
isDisabled={isQueryLoading}
onKeyDown={(keyEvent) => {
if (keyEvent.key == "Enter") {
executeSearch();
}
}}
endContent={
<Button
isIconOnly
variant="flat"
onPress={executeSearch}
isLoading={isQueryLoading}
className="-mr-2"
>
<MagnifyingGlassIcon />
</Button>
}
/>
{aiResponse.length > 0 && (
<p className="bg-neutral-50 p-4 border-2 rounded-xl">
{aiResponse}
</p>
)}
</div>
</ModalBody>
<ModalFooter className="bg-red-50 dark:bg-red-950 w-full h-full">
<div className="w-full h-full flex flex-row justify-between *:my-auto">
<EcoconnectFullLogo />
<p className="text-lg text-red-900 dark:text-red-100">
Natural Language Search
</p>
</div>
</ModalFooter>
</>
);
}}
</ModalContent>
</Modal>
</div>
);
}

View File

@@ -19,6 +19,7 @@ import { retrieveUserInformation } from "../security/users";
import { useEffect, useState } from "react";
import { useNavigate } from "react-router-dom";
import EcoconnectFullLogo from "./EcoconnectFullLogo";
import EcoconnectSearch from "./EcoconnectSearch";
export default function NavigationBar() {
const [userProfileImageURL, setUserProfileImageURL] = useState("");
@@ -58,7 +59,7 @@ export default function NavigationBar() {
}
>
<div className="relative bg-primary-50 dark:bg-primary-800 border-2 border-primary-100 dark:border-primary-950 shadow-lg w-full h-full rounded-xl flex flex-col justify-center p-1">
<div className=" w-full flex flex-row justify-between gap-4">
<div className="w-full flex flex-row justify-between gap-4">
<div className="flex flex-row gap-0 my-auto *:my-auto">
<Button
variant="light"
@@ -109,60 +110,65 @@ export default function NavigationBar() {
</div>
</div>
{userInformation && (
<div className="my-auto pr-1">
<Dropdown placement="bottom" backdrop="blur">
<DropdownTrigger>
<Avatar src={userProfileImageURL} as="button" size="sm" />
</DropdownTrigger>
<DropdownMenu aria-label="Profile Actions">
<DropdownSection showDivider>
<DropdownItem key="account-overview" isReadOnly>
<div className="flex flex-col gap-2 text-center *:mx-auto p-2">
<Avatar
src={userProfileImageURL}
as="button"
size="lg"
isBordered
/>
<div className="flex flex-col">
<p>Signed in as</p>
<p className="text-lg font-bold">
<span>{userInformation.firstName}</span>{" "}
<span>{userInformation.lastName}</span>
</p>
<p className="opacity-50">{userInformation.email}</p>
<div className="my-auto pr-1 flex flex-row justify-end">
<div className="flex flex-row gap-2 w-min">
<EcoconnectSearch />
<Dropdown placement="bottom" backdrop="blur">
<DropdownTrigger>
<Avatar src={userProfileImageURL} as="button" size="sm" />
</DropdownTrigger>
<DropdownMenu aria-label="Profile Actions">
<DropdownSection showDivider>
<DropdownItem key="account-overview" isReadOnly>
<div className="flex flex-col gap-2 text-center *:mx-auto p-2 w-full">
<Avatar
src={userProfileImageURL}
as="button"
size="lg"
isBordered
/>
<div className="flex flex-col">
<p>Signed in as</p>
<p className="text-lg font-bold">
<span>{userInformation.firstName}</span>{" "}
<span>{userInformation.lastName}</span>
</p>
<p className="opacity-50">
{userInformation.email}
</p>
</div>
</div>
</div>
</DropdownItem>
</DropdownItem>
<DropdownItem
key="dashboard"
title="Dashboard"
startContent={<RocketLaunchIcon />}
onPress={() => {
navigate("/springboard");
}}
/>
<DropdownItem
key="manage-account"
title="Manage your account"
startContent={<PencilSquareIcon />}
onPress={() => {
navigate("/manage-account");
}}
/>
</DropdownSection>
<DropdownItem
key="dashboard"
title="Dashboard"
startContent={<RocketLaunchIcon />}
key="signout"
startContent={<ArrowRightStartOnRectangleIcon />}
color="danger"
title="Sign out"
onPress={() => {
navigate("/springboard");
localStorage.clear();
window.location.reload();
}}
/>
<DropdownItem
key="manage-account"
title="Manage your account"
startContent={<PencilSquareIcon />}
onPress={() => {
navigate("/manage-account");
}}
/>
</DropdownSection>
<DropdownItem
key="signout"
startContent={<ArrowRightStartOnRectangleIcon />}
color="danger"
title="Sign out"
onPress={() => {
localStorage.clear();
window.location.reload();
}}
/>
</DropdownMenu>
</Dropdown>
</DropdownMenu>
</Dropdown>
</div>
</div>
)}
{!userInformation && doneLoading && (