diff --git a/pkg_client/package.json b/pkg_client/package.json index bd4b27d..c08d18d 100644 --- a/pkg_client/package.json +++ b/pkg_client/package.json @@ -15,6 +15,8 @@ "react": "^18.2.0", "react-bootstrap": "^2.9.1", "react-dom": "^18.2.0", + "react-icons": "^4.12.0", + "react-router-dom": "^6.21.0", "react-scripts": "5.0.1", "typescript": "^4.9.5", "web-vitals": "^2.1.4" diff --git a/pkg_client/src/App.css b/pkg_client/src/App.css index 74b5e05..2d26ac5 100644 --- a/pkg_client/src/App.css +++ b/pkg_client/src/App.css @@ -1,7 +1,3 @@ -.App { - text-align: center; -} - .App-logo { height: 40vmin; pointer-events: none; diff --git a/pkg_client/src/App.tsx b/pkg_client/src/App.tsx index baae969..405122f 100644 --- a/pkg_client/src/App.tsx +++ b/pkg_client/src/App.tsx @@ -2,7 +2,7 @@ import React, { useContext } from "react"; import "./App.css"; import APIHandler from "./components/APIHandler"; import LoginForm from "./components/LoginForm"; -import Container from 'react-bootstrap/Container' +import Container from "react-bootstrap/Container"; import { UserContext } from "./contexts/UserContext"; function App() { @@ -13,9 +13,7 @@ function App() { return ( - - {content} - + {content} ); diff --git a/pkg_client/src/components/APIHandler.tsx b/pkg_client/src/components/APIHandler.tsx index 4f2f070..6d46369 100644 --- a/pkg_client/src/components/APIHandler.tsx +++ b/pkg_client/src/components/APIHandler.tsx @@ -1,50 +1,39 @@ -import React, { useContext, useEffect, useState } from "react"; -import axios from "axios"; +import React, { useContext } from "react"; import { UserContext } from "../contexts/UserContext"; import Container from "react-bootstrap/Container"; +import { BrowserRouter, Routes, Route } from "react-router-dom"; +import Layout from "./Layout"; +import NLtoPKG from "./NLtoPKG"; const APIHandler: React.FC = () => { const { user } = useContext(UserContext); - // State tracker for service management data. - const [serviceData, setServiceData] = useState(null); - // State tracker for personal facts data. - const [factsData, setFactsData] = useState(null); - // State tracker for PKG exploration data. Data presentation, graphs, etc. - const [exploreData, setExploreData] = useState(null); - - useEffect(() => { - const baseURL = - (window as any)["PKG_API_BASE_URL"] || "http://localhost:5000"; - - axios - .get(`${baseURL}/service`) - .then((response) => setServiceData(response.data)); - axios - .get(`${baseURL}/facts`) - .then((response) => setFactsData(response.data)); - axios - .get(`${baseURL}/explore`) - .then((response) => setExploreData(response.data)); - }, []); return ( - Personal Knowledge Graph API - - Welcome {JSON.stringify(user, null, 2)} - - - Service Management Data - {JSON.stringify(serviceData, null, 2)} - - - Personal Facts Data - {JSON.stringify(factsData, null, 2)} - - - PKG Exploration Data - {JSON.stringify(exploreData, null, 2)} - + Personal Knowledge Graph + + + + }> + + Welcome {user?.username}. + + + } + /> + Service Management} /> + Population form} /> + Personal Preferences} + /> + PKG Exploration} /> + + + ); }; diff --git a/pkg_client/src/components/Layout.tsx b/pkg_client/src/components/Layout.tsx new file mode 100644 index 0000000..1f0ccb5 --- /dev/null +++ b/pkg_client/src/components/Layout.tsx @@ -0,0 +1,47 @@ +// Layout component for the application includes a navigation bar and content +// of the current tab. +import { Outlet, Link } from "react-router-dom"; +import Nav from "react-bootstrap/Nav"; +import { useState } from "react"; + +const Layout = () => { + const [activeKey, setActiveKey] = useState("/"); + + const handleSelect = (eventKey: string | null) => { + if (eventKey !== null) { + setActiveKey(eventKey); + } + }; + + return ( + <> + + + + Home + + + + + Service Management + + + + + Populate PKG + + + + + Explore PKG + + + + + + + > + ); +}; + +export default Layout; diff --git a/pkg_client/src/components/NLtoPKG.tsx b/pkg_client/src/components/NLtoPKG.tsx new file mode 100644 index 0000000..b6db7b6 --- /dev/null +++ b/pkg_client/src/components/NLtoPKG.tsx @@ -0,0 +1,44 @@ +// Natural language to PKG component + +import { Container } from "react-bootstrap"; +import { UserContext } from "../contexts/UserContext"; +import { useContext, useState } from "react"; +import axios from "axios"; +import QueryForm from "./QueryForm"; + +const NLtoPKG = () => { + const { user } = useContext(UserContext); + const [error, setError] = useState(""); + const baseURL = + (window as any)["PKG_API_BASE_URL"] || "http://127.0.0.1:5000"; + + const handleSubmit = (query: string) => { + return axios + .post(`${baseURL}/nl`, { + query: query, + username: user?.username, + user_uri: user?.uri, + }) + .then((response) => { + // TODO: Handle response. + // Output can be displayed in information-container. + setError(""); + }) + .catch((error) => { + setError(error.message); + throw error; + }); + }; + + return ( + + + Manage your PKG with natural language queries. + + + + + ); +}; + +export default NLtoPKG; diff --git a/pkg_client/src/components/QueryForm.tsx b/pkg_client/src/components/QueryForm.tsx new file mode 100644 index 0000000..ba4cb7a --- /dev/null +++ b/pkg_client/src/components/QueryForm.tsx @@ -0,0 +1,66 @@ +// Form comprising a single text input and a submit button. + +import { useState } from "react"; +import { Alert, Button, Spinner } from "react-bootstrap"; +import Form from "react-bootstrap/Form"; + +interface QueryFormProps { + handleSubmit: (query: string) => Promise; + error: string; +} + +const QueryForm: React.FC = ({ handleSubmit, error = "" }) => { + const [query, setQuery] = useState(""); + const [isSubmitting, setIsSubmitting] = useState(false); + + const handleClick = async (query: string) => { + try { + setIsSubmitting(true); + await handleSubmit(query); + setQuery(""); + } catch (error) { + } finally { + setIsSubmitting(false); + } + }; + + return ( + + {error && {error}} + + + setQuery(e.target.value)} + /> + handleClick(query)} + style={{ marginTop: "10px" }} + disabled={query === ""} + > + {isSubmitting ? ( + + ) : null} + {isSubmitting ? "Submitting..." : "Submit"} + + + + + ); +}; + +export default QueryForm; diff --git a/pkg_client/src/contexts/UserContext.tsx b/pkg_client/src/contexts/UserContext.tsx index 0e00073..5684d10 100644 --- a/pkg_client/src/contexts/UserContext.tsx +++ b/pkg_client/src/contexts/UserContext.tsx @@ -1,22 +1,29 @@ import React, { useState } from "react"; export type User = { - username: string; - uri: string; -} + username: string; + uri: string; +}; type UserProviderProps = { - children: React.ReactNode; + children: React.ReactNode; }; -export const UserContext = React.createContext<{ user: User | null; setUser: React.Dispatch>; }>({ user: null, setUser: () => { } }); +export const UserContext = React.createContext<{ + user: User | null; + setUser: React.Dispatch>; +}>({ user: null, setUser: () => {} }); -export const UserProvider: React.FC = ({ children, }: { children: React.ReactNode }) => { - const [user, setUser] = useState(null); +export const UserProvider: React.FC = ({ + children, +}: { + children: React.ReactNode; +}) => { + const [user, setUser] = useState(null); - return ( - - {children} - - ); -} \ No newline at end of file + return ( + + {children} + + ); +};
Welcome {JSON.stringify(user, null, 2)}
{JSON.stringify(serviceData, null, 2)}
{JSON.stringify(factsData, null, 2)}
{JSON.stringify(exploreData, null, 2)}