Lots of changes
This commit is contained in:
parent
2e6a8d1366
commit
d1e178aa61
BIN
frontend/public/eliza.png
Executable file
BIN
frontend/public/eliza.png
Executable file
Binary file not shown.
After Width: | Height: | Size: 1.5 MiB |
@ -9,7 +9,6 @@ interface CopyBubbleProps extends IconButtonProps {
|
||||
content: string | undefined,
|
||||
sx?: SxProps<Theme>;
|
||||
tooltip?: string;
|
||||
onClick?: () => void;
|
||||
}
|
||||
|
||||
const CopyBubble = ({
|
||||
@ -21,7 +20,7 @@ const CopyBubble = ({
|
||||
} : CopyBubbleProps) => {
|
||||
const [copied, setCopied] = useState(false);
|
||||
|
||||
const handleCopy = () => {
|
||||
const handleCopy = (e: any) => {
|
||||
if (content === undefined) {
|
||||
return;
|
||||
}
|
||||
@ -32,14 +31,14 @@ const CopyBubble = ({
|
||||
});
|
||||
|
||||
if (onClick) {
|
||||
onClick();
|
||||
onClick(e);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<Tooltip title={tooltip} placement="top" arrow>
|
||||
<IconButton
|
||||
onClick={handleCopy}
|
||||
onClick={(e) => { handleCopy(e) }}
|
||||
sx={{
|
||||
width: 24,
|
||||
height: 24,
|
||||
|
@ -104,6 +104,8 @@ const BackstoryApp = () => {
|
||||
newSessionId = (await response.json()).id;
|
||||
}
|
||||
setSessionId(newSessionId);
|
||||
setSnack(`${action} session ${newSessionId}`);
|
||||
|
||||
// Store in cookie if user opts in
|
||||
if (storeInCookie) {
|
||||
setCookie('session_id', newSessionId);
|
||||
@ -116,7 +118,6 @@ const BackstoryApp = () => {
|
||||
// Clear all query parameters, preserve the current path
|
||||
navigate(location.pathname, { replace: true });
|
||||
}
|
||||
setSnack(`${action} session ${newSessionId}`);
|
||||
} catch (err) {
|
||||
setSnack("" + err);
|
||||
}
|
||||
@ -132,9 +133,9 @@ const BackstoryApp = () => {
|
||||
// Render appropriate routes based on user type
|
||||
return (
|
||||
<ThemeProvider theme={backstoryTheme}>
|
||||
<UserProvider>
|
||||
<UserProvider sessionId={sessionId} setSnack={setSnack}>
|
||||
<Routes>
|
||||
<Route path="/u/:user" element={<UserRoute />} />
|
||||
<Route path="/u/:username" element={<UserRoute sessionId={sessionId} setSnack={setSnack} />} />
|
||||
{/* Static/shared routes */}
|
||||
<Route
|
||||
path="/*"
|
||||
@ -149,7 +150,6 @@ const BackstoryApp = () => {
|
||||
/>
|
||||
}
|
||||
/>
|
||||
<Route path="*" element={<BetaPage />} />
|
||||
</Routes>
|
||||
</UserProvider>
|
||||
|
||||
|
@ -1,71 +0,0 @@
|
||||
import React, { Ref, Fragment, ReactNode } from "react";
|
||||
import { Route } from "react-router-dom";
|
||||
import { useUser } from "./UserContext";
|
||||
import { Box, Typography, Container, Paper } from '@mui/material';
|
||||
|
||||
import { BackstoryPageProps } from '../../Components/BackstoryTab';
|
||||
import { ConversationHandle } from '../Components/Conversation';
|
||||
import { UserType } from '../Components/UserContext';
|
||||
|
||||
import { ChatPage } from '../Pages/ChatPage';
|
||||
import { ResumeBuilderPage } from '../../Pages/ResumeBuilderPage';
|
||||
import { DocsPage } from '../Pages/DocsPage';
|
||||
import { CreateProfilePage } from '../Pages/CreateProfilePage';
|
||||
import { VectorVisualizerPage } from 'Pages/VectorVisualizerPage';
|
||||
import { HomePage } from '../Pages/HomePage';
|
||||
import { BetaPage } from '../Pages/BetaPage'
|
||||
|
||||
const DashboardPage = () => <BetaPage><Typography variant="h4">Dashboard</Typography></BetaPage>;
|
||||
const ProfilePage = () => <BetaPage><Typography variant="h4">Profile</Typography></BetaPage>;
|
||||
const BackstoryPage = () => <BetaPage><Typography variant="h4">Backstory</Typography></BetaPage>;
|
||||
const ResumesPage = () => <BetaPage><Typography variant="h4">Resumes</Typography></BetaPage>;
|
||||
const QASetupPage = () => <BetaPage><Typography variant="h4">Q&A Setup</Typography></BetaPage>;
|
||||
const AnalyticsPage = () => <BetaPage><Typography variant="h4">Analytics</Typography></BetaPage>;
|
||||
const SettingsPage = () => <BetaPage><Typography variant="h4">Settings</Typography></BetaPage>;
|
||||
const SearchPage = () => <BetaPage><Typography variant="h4">Search</Typography></BetaPage>;
|
||||
const SavedPage = () => <BetaPage><Typography variant="h4">Saved</Typography></BetaPage>;
|
||||
const JobsPage = () => <BetaPage><Typography variant="h4">Jobs</Typography></BetaPage>;
|
||||
const CompanyPage = () => <BetaPage><Typography variant="h4">Company</Typography></BetaPage>;
|
||||
|
||||
interface BackstoryDynamicRoutesProps extends BackstoryPageProps {
|
||||
chatRef: Ref<ConversationHandle>
|
||||
}
|
||||
const getBackstoryDynamicRoutes = (props : BackstoryDynamicRoutesProps, user?: UserType | null) : ReactNode => {
|
||||
const { sessionId, setSnack, submitQuery, chatRef } = props;
|
||||
|
||||
const routes = [
|
||||
<Route key="backstory" path="/" element={<HomePage/>} />,
|
||||
<Route key="chat" path="/chat" element={<ChatPage ref={chatRef} setSnack={setSnack} sessionId={sessionId} submitQuery={submitQuery} />} />,
|
||||
<Route key="docs" path="/docs" element={<DocsPage setSnack={setSnack} sessionId={sessionId} submitQuery={submitQuery} />} />,
|
||||
<Route key="docs-sub" path="/docs/:subPage" element={<DocsPage setSnack={setSnack} sessionId={sessionId} submitQuery={submitQuery} />} />,
|
||||
<Route key="resume-builder" path="/resume-builder" element={<ResumeBuilderPage setSnack={setSnack} sessionId={sessionId} submitQuery={submitQuery} />} />,
|
||||
<Route key="vector" path="/knowledge-explorer" element={<VectorVisualizerPage setSnack={setSnack} sessionId={sessionId} submitQuery={submitQuery} />} />,
|
||||
<Route key="create-profile" path="/create-your-profile" element={<CreateProfilePage />} />,
|
||||
];
|
||||
|
||||
if (user === undefined || user === null) {
|
||||
return routes;
|
||||
}
|
||||
|
||||
if (user.type === "candidate") {
|
||||
routes.splice(-1, 0, ...[
|
||||
<Route path="/profile" element={<ProfilePage />} />,
|
||||
<Route path="/backstory" element={<BackstoryPage />} />,
|
||||
<Route path="/resumes" element={<ResumesPage />} />,
|
||||
<Route path="/qa-setup" element={<QASetupPage />} />,
|
||||
]);
|
||||
}
|
||||
|
||||
if (user.type === "employer") {
|
||||
routes.splice(-1, 0, ...[
|
||||
<Route path="/search" element={<SearchPage />} />,
|
||||
<Route path="/saved" element={<SavedPage />} />,
|
||||
<Route path="/jobs" element={<JobsPage />} />,
|
||||
<Route path="/company" element={<CompanyPage />} />,
|
||||
]);
|
||||
}
|
||||
|
||||
return routes;
|
||||
};
|
||||
|
||||
export { getBackstoryDynamicRoutes };
|
@ -21,9 +21,10 @@ import { SxProps, Theme } from '@mui/material';
|
||||
import {Header} from './Header';
|
||||
import { Scrollable } from '../../Components/Scrollable';
|
||||
import { Footer } from './Footer';
|
||||
import { Snack } from '../../Components/Snack';
|
||||
import { UserProvider, useUser, UserType } from './UserContext';
|
||||
import { getBackstoryDynamicRoutes } from './BackstoryDynamicRoutes';
|
||||
import { Snack, SetSnackType } from '../../Components/Snack';
|
||||
import { UserProvider, useUser, UserInfo } from './UserContext';
|
||||
import { getBackstoryDynamicRoutes } from './BackstoryRoutes';
|
||||
import { LoadingComponent } from "../Components/LoadingComponent";
|
||||
|
||||
type NavigationLinkType = {
|
||||
name: string;
|
||||
@ -32,10 +33,8 @@ type NavigationLinkType = {
|
||||
label?: ReactElement<any>;
|
||||
};
|
||||
|
||||
const DefaultNavItems : NavigationLinkType[] = [
|
||||
{ name: 'Chat', path: '/chat', icon: <ChatIcon /> },
|
||||
{ name: 'Resume Builder', path: '/resume-builder', icon: <WorkIcon /> },
|
||||
{ name: 'Knowledge Explorer', path: '/knowledge-explorer', icon: <WorkIcon /> },
|
||||
const DefaultNavItems: NavigationLinkType[] = [
|
||||
{ name: 'Find a Candidate', path: '/find-a-candidate', icon: <InfoIcon /> },
|
||||
{ name: 'Docs', path: '/docs', icon: <InfoIcon /> },
|
||||
// { name: 'How It Works', path: '/how-it-works', icon: <InfoIcon/> },
|
||||
// { name: 'For Candidates', path: '/for-candidates', icon: <PersonIcon/> },
|
||||
@ -44,37 +43,45 @@ const DefaultNavItems : NavigationLinkType[] = [
|
||||
];
|
||||
|
||||
const CandidateNavItems : NavigationLinkType[]= [
|
||||
{ name: 'Dashboard', icon: <DashboardIcon />, path: '/dashboard' },
|
||||
{ name: 'Profile', icon: <PersonIcon />, path: '/profile' },
|
||||
{ name: 'Backstory', icon: <HistoryIcon />, path: '/backstory' },
|
||||
{ name: 'Chat', path: '/chat', icon: <ChatIcon /> },
|
||||
{ name: 'Resume Builder', path: '/resume-builder', icon: <WorkIcon /> },
|
||||
{ name: 'Knowledge Explorer', path: '/knowledge-explorer', icon: <WorkIcon /> },
|
||||
{ name: 'Find a Candidate', path: '/find-a-candidate', icon: <InfoIcon /> },
|
||||
// { name: 'Dashboard', icon: <DashboardIcon />, path: '/dashboard' },
|
||||
// { name: 'Profile', icon: <PersonIcon />, path: '/profile' },
|
||||
// { name: 'Backstory', icon: <HistoryIcon />, path: '/backstory' },
|
||||
{ name: 'Resumes', icon: <DescriptionIcon />, path: '/resumes' },
|
||||
{ name: 'Q&A Setup', icon: <QuestionAnswerIcon />, path: '/qa-setup' },
|
||||
// { name: 'Q&A Setup', icon: <QuestionAnswerIcon />, path: '/qa-setup' },
|
||||
{ name: 'Analytics', icon: <BarChartIcon />, path: '/analytics' },
|
||||
{ name: 'Settings', icon: <SettingsIcon />, path: '/settings' },
|
||||
];
|
||||
|
||||
const EmployerNavItems: NavigationLinkType[] = [
|
||||
{ name: 'Dashboard', icon: <DashboardIcon />, path: '/dashboard' },
|
||||
{ name: 'Search', icon: <SearchIcon />, path: '/search' },
|
||||
{ name: 'Saved', icon: <BookmarkIcon />, path: '/saved' },
|
||||
{ name: 'Jobs', icon: <WorkIcon />, path: '/jobs' },
|
||||
{ name: 'Company', icon: <BusinessIcon />, path: '/company' },
|
||||
{ name: 'Analytics', icon: <BarChartIcon />, path: '/analytics' },
|
||||
{ name: 'Settings', icon: <SettingsIcon />, path: '/settings' },
|
||||
{ name: 'Chat', path: '/chat', icon: <ChatIcon /> },
|
||||
{ name: 'Resume Builder', path: '/resume-builder', icon: <WorkIcon /> },
|
||||
{ name: 'Knowledge Explorer', path: '/knowledge-explorer', icon: <WorkIcon /> },
|
||||
{ name: 'Find a Candidate', path: '/find-a-candidate', icon: <InfoIcon /> },
|
||||
// { name: 'Dashboard', icon: <DashboardIcon />, path: '/dashboard' },
|
||||
// { name: 'Search', icon: <SearchIcon />, path: '/search' },
|
||||
// { name: 'Saved', icon: <BookmarkIcon />, path: '/saved' },
|
||||
// { name: 'Jobs', icon: <WorkIcon />, path: '/jobs' },
|
||||
// { name: 'Company', icon: <BusinessIcon />, path: '/company' },
|
||||
// { name: 'Analytics', icon: <BarChartIcon />, path: '/analytics' },
|
||||
// { name: 'Settings', icon: <SettingsIcon />, path: '/settings' },
|
||||
];
|
||||
|
||||
// Navigation links based on user type
|
||||
const getNavigationLinks = (user: UserType | null) : NavigationLinkType[] => {
|
||||
if (!user) {
|
||||
return DefaultNavItems;
|
||||
}
|
||||
const getNavigationLinks = (user: UserInfo | null): NavigationLinkType[] => {
|
||||
if (!user) {
|
||||
return DefaultNavItems;
|
||||
}
|
||||
|
||||
if (user.type === 'candidate') {
|
||||
return CandidateNavItems;
|
||||
}
|
||||
if (user.type === 'candidate' && user.isAuthenticated) {
|
||||
return CandidateNavItems;
|
||||
}
|
||||
|
||||
// Employer navigation
|
||||
return EmployerNavItems;
|
||||
// Employer navigation
|
||||
return EmployerNavItems;
|
||||
};
|
||||
|
||||
interface BackstoryPageContainerProps {
|
||||
@ -101,8 +108,8 @@ const BackstoryPageContainer = (props : BackstoryPageContainerProps) => {
|
||||
}
|
||||
|
||||
const BackstoryLayout: React.FC<{
|
||||
sessionId?: string;
|
||||
setSnack: (msg: string) => void;
|
||||
sessionId: string | undefined;
|
||||
setSnack: SetSnackType;
|
||||
page: string;
|
||||
chatRef: React.Ref<any>;
|
||||
snackRef: React.Ref<any>;
|
||||
@ -129,7 +136,7 @@ const BackstoryLayout: React.FC<{
|
||||
|
||||
return (
|
||||
<>
|
||||
<Header setSnack={setSnack} sessionId={sessionId} user={user} currentPath={page} navigate={navigate} navigationLinks={navigationLinks} showLogin={false} />
|
||||
<Header {...{ setSnack, sessionId, user, currentPath: page, navigate, navigationLinks }} />
|
||||
<Box sx={{ display: "flex", minHeight: "72px", height: "72px" }} />
|
||||
<Scrollable
|
||||
sx={{
|
||||
@ -141,8 +148,20 @@ const BackstoryLayout: React.FC<{
|
||||
}}
|
||||
>
|
||||
<BackstoryPageContainer>
|
||||
<Outlet />
|
||||
{dynamicRoutes !== undefined && <Routes>{dynamicRoutes}</Routes>}
|
||||
{!sessionId &&
|
||||
<Box>
|
||||
<LoadingComponent
|
||||
loadingText="Creating session..."
|
||||
loaderType="linear"
|
||||
withFade={true}
|
||||
fadeDuration={1200} />
|
||||
</Box>
|
||||
}
|
||||
{sessionId && <>
|
||||
<Outlet />
|
||||
{dynamicRoutes !== undefined && <Routes>{dynamicRoutes}</Routes>}
|
||||
</>
|
||||
}
|
||||
{location.pathname === "/" && <Footer />}
|
||||
</BackstoryPageContainer>
|
||||
</Scrollable>
|
||||
|
86
frontend/src/NewApp/Components/BackstoryRoutes.tsx
Normal file
86
frontend/src/NewApp/Components/BackstoryRoutes.tsx
Normal file
@ -0,0 +1,86 @@
|
||||
import React, { Ref, Fragment, ReactNode } from "react";
|
||||
import { Route } from "react-router-dom";
|
||||
import { useUser } from "./UserContext";
|
||||
import { Box, Typography, Container, Paper } from '@mui/material';
|
||||
|
||||
import { BackstoryPageProps } from '../../Components/BackstoryTab';
|
||||
import { ConversationHandle } from './Conversation';
|
||||
import { UserInfo } from './UserContext';
|
||||
|
||||
import { ChatPage } from '../Pages/ChatPage';
|
||||
import { ResumeBuilderPage } from '../../Pages/ResumeBuilderPage';
|
||||
import { DocsPage } from '../Pages/DocsPage';
|
||||
import { CreateProfilePage } from '../Pages/CreateProfilePage';
|
||||
import { VectorVisualizerPage } from 'Pages/VectorVisualizerPage';
|
||||
import { HomePage } from '../Pages/HomePage';
|
||||
import { BetaPage } from '../Pages/BetaPage';
|
||||
import { CandidateListingPage } from '../Pages/CandidateListingPage';
|
||||
|
||||
const DashboardPage = () => (<BetaPage><Typography variant="h4">Dashboard</Typography></BetaPage>);
|
||||
const ProfilePage = () => (<BetaPage><Typography variant="h4">Profile</Typography></BetaPage>);
|
||||
const BackstoryPage = () => (<BetaPage><Typography variant="h4">Backstory</Typography></BetaPage>);
|
||||
const ResumesPage = () => (<BetaPage><Typography variant="h4">Resumes</Typography></BetaPage>);
|
||||
const QASetupPage = () => (<BetaPage><Typography variant="h4">Q&A Setup</Typography></BetaPage>);
|
||||
const AnalyticsPage = () => (<BetaPage><Typography variant="h4">Analytics</Typography></BetaPage>);
|
||||
const SettingsPage = () => (<BetaPage><Typography variant="h4">Settings</Typography></BetaPage>);
|
||||
const SearchPage = () => (<BetaPage><Typography variant="h4">Search</Typography></BetaPage>);
|
||||
const SavedPage = () => (<BetaPage><Typography variant="h4">Saved</Typography></BetaPage>);
|
||||
const JobsPage = () => (<BetaPage><Typography variant="h4">Jobs</Typography></BetaPage>);
|
||||
const CompanyPage = () => (<BetaPage><Typography variant="h4">Company</Typography></BetaPage>);
|
||||
const LogoutPage = () => (<BetaPage><Typography variant="h4">Logout page...</Typography></BetaPage>);
|
||||
const LoginPage = () => (<BetaPage><Typography variant="h4">Login page...</Typography></BetaPage>);
|
||||
|
||||
interface BackstoryDynamicRoutesProps extends BackstoryPageProps {
|
||||
chatRef: Ref<ConversationHandle>
|
||||
}
|
||||
const getBackstoryDynamicRoutes = (props : BackstoryDynamicRoutesProps, user?: UserInfo | null) : ReactNode => {
|
||||
const { sessionId, setSnack, submitQuery, chatRef } = props;
|
||||
let index=0
|
||||
const routes = [
|
||||
<Route key={`${index++}`} path="/" element={<HomePage/>} />,
|
||||
<Route key={`${index++}`} path="/chat" element={<ChatPage ref={chatRef} setSnack={setSnack} sessionId={sessionId} submitQuery={submitQuery} />} />,
|
||||
<Route key={`${index++}`} path="/docs" element={<DocsPage setSnack={setSnack} sessionId={sessionId} submitQuery={submitQuery} />} />,
|
||||
<Route key={`${index++}`} path="/docs/:subPage" element={<DocsPage setSnack={setSnack} sessionId={sessionId} submitQuery={submitQuery} />} />,
|
||||
<Route key={`${index++}`} path="/resume-builder" element={<ResumeBuilderPage setSnack={setSnack} sessionId={sessionId} submitQuery={submitQuery} />} />,
|
||||
<Route key={`${index++}`} path="/knowledge-explorer" element={<VectorVisualizerPage setSnack={setSnack} sessionId={sessionId} submitQuery={submitQuery} />} />,
|
||||
<Route key={`${index++}`} path="/find-a-candidate" element={<CandidateListingPage {...{sessionId, setSnack, submitQuery}} />} />,
|
||||
];
|
||||
|
||||
if (user === undefined || user === null) {
|
||||
routes.push(<Route key={`${index++}`} path="/register" element={(<BetaPage><CreateProfilePage /></BetaPage>)} />);
|
||||
routes.push(<Route key={`${index++}`} path="/login" element={<LoginPage />} />);
|
||||
routes.push(<Route key={`${index++}`} path="*" element={<BetaPage />} />);
|
||||
} else {
|
||||
|
||||
if (!user.isAuthenticated) {
|
||||
routes.push(<Route key={`${index++}`} path="/register" element={(<BetaPage><CreateProfilePage /></BetaPage>)} />);
|
||||
routes.push(<Route key={`${index++}`} path="/login" element={<LoginPage />} />);
|
||||
} else {
|
||||
routes.push(<Route key={`${index++}`} path="/logout" element={<LogoutPage />} />);
|
||||
}
|
||||
|
||||
if (user.type === "candidate" && user.isAuthenticated) {
|
||||
routes.splice(-1, 0, ...[
|
||||
<Route key={`${index++}`} path="/profile" element={<ProfilePage />} />,
|
||||
<Route key={`${index++}`} path="/backstory" element={<BackstoryPage />} />,
|
||||
<Route key={`${index++}`} path="/resumes" element={<ResumesPage />} />,
|
||||
<Route key={`${index++}`} path="/qa-setup" element={<QASetupPage />} />,
|
||||
]);
|
||||
}
|
||||
|
||||
if (user.type === "employer") {
|
||||
routes.splice(-1, 0, ...[
|
||||
<Route key={`${index++}`} path="/search" element={<SearchPage />} />,
|
||||
<Route key={`${index++}`} path="/saved" element={<SavedPage />} />,
|
||||
<Route key={`${index++}`} path="/jobs" element={<JobsPage />} />,
|
||||
<Route key={`${index++}`} path="/company" element={<CompanyPage />} />,
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
routes.push(<Route key={`${index++}`} path="*" element={<BetaPage />} />);
|
||||
|
||||
return routes;
|
||||
};
|
||||
|
||||
export { getBackstoryDynamicRoutes };
|
@ -1,30 +1,10 @@
|
||||
import React from 'react';
|
||||
import { Box, Typography, Avatar, Paper, Grid, Chip } from '@mui/material';
|
||||
import { Box, Link, Typography, Avatar, Paper, Grid, Chip, SxProps } from '@mui/material';
|
||||
import { styled } from '@mui/material/styles';
|
||||
import { Tunables } from '../../Components/ChatQuery';
|
||||
import { Route, Routes, useLocation, useNavigate } from 'react-router-dom';
|
||||
|
||||
// Define the UserInfo interface for type safety
|
||||
interface UserInfo {
|
||||
profile_url: string;
|
||||
description: string;
|
||||
rag_content_size: number;
|
||||
user_name: string;
|
||||
first_name: string;
|
||||
last_name: string;
|
||||
full_name: string;
|
||||
contact_info: Record<string, string>;
|
||||
questions: [{
|
||||
question: string;
|
||||
tunables?: Tunables
|
||||
}]
|
||||
};
|
||||
|
||||
// Define props interface for the component
|
||||
interface CandidateInfoProps {
|
||||
userInfo: UserInfo;
|
||||
}
|
||||
|
||||
import { UserInfo, useUser } from "./UserContext";
|
||||
import LinkIcon from '@mui/icons-material/Link';
|
||||
import { CopyBubble } from "../../Components/CopyBubble";
|
||||
// Styled components
|
||||
const StyledPaper = styled(Paper)(({ theme }) => ({
|
||||
padding: theme.spacing(2),
|
||||
@ -33,7 +13,19 @@ const StyledPaper = styled(Paper)(({ theme }) => ({
|
||||
boxShadow: theme.shadows[2],
|
||||
}));
|
||||
|
||||
const CandidateInfo: React.FC<CandidateInfoProps> = ({ userInfo }) => {
|
||||
interface CandidateInfoProps {
|
||||
user?: UserInfo;
|
||||
sx?: SxProps;
|
||||
action?: string;
|
||||
};
|
||||
|
||||
const CandidateInfo: React.FC<CandidateInfoProps> = (props: CandidateInfoProps) => {
|
||||
const { user } = useUser();
|
||||
const {
|
||||
sx,
|
||||
action = ''
|
||||
} = props;
|
||||
const location = useLocation();
|
||||
const navigate = useNavigate();
|
||||
// Format RAG content size (e.g., if it's in bytes, convert to KB/MB)
|
||||
const formatRagSize = (size: number): string => {
|
||||
@ -41,14 +33,19 @@ const CandidateInfo: React.FC<CandidateInfoProps> = ({ userInfo }) => {
|
||||
if (size < 1000000) return `${(size / 1000).toFixed(1)}K RAG elements`;
|
||||
return `${(size / 1000000).toFixed(1)}M RAG elements`;
|
||||
};
|
||||
const view = props.user || user;
|
||||
|
||||
if (!view) {
|
||||
return <Box>No user loaded.</Box>;
|
||||
}
|
||||
|
||||
return (
|
||||
<StyledPaper>
|
||||
<StyledPaper sx={sx}>
|
||||
<Grid container spacing={2}>
|
||||
<Grid size={{ xs: 12, sm: 2 }} sx={{ display: 'flex', justifyContent: 'center', alignItems: 'center' }}>
|
||||
<Avatar
|
||||
src={userInfo.profile_url}
|
||||
alt={`${userInfo.full_name}'s profile`}
|
||||
src={view.profile_url}
|
||||
alt={`${view.full_name}'s profile`}
|
||||
sx={{
|
||||
width: 80,
|
||||
height: 80,
|
||||
@ -59,20 +56,32 @@ const CandidateInfo: React.FC<CandidateInfoProps> = ({ userInfo }) => {
|
||||
|
||||
<Grid size={{ xs: 12, sm: 10 }}>
|
||||
<Box sx={{ display: 'flex', justifyContent: 'space-between', alignItems: 'flex-start', mb: 1 }}>
|
||||
<Typography variant="h5" component="h1" sx={{ fontWeight: 'bold' }}>
|
||||
{userInfo.full_name}
|
||||
</Typography>
|
||||
<Chip
|
||||
onClick={() => navigate('/knowledge-explorer')}
|
||||
label={formatRagSize(userInfo.rag_content_size)}
|
||||
<Box>
|
||||
<Box sx={{ display: "flex", flexDirection: "row", alignItems: "center", gap: 1, "& > .MuiTypography-root": { m: 0 } }}>
|
||||
{action !== '' && <Typography variant="body1">{action}</Typography>}
|
||||
<Typography variant="h5" component="h1" sx={{ fontWeight: 'bold' }}>
|
||||
{view.full_name}
|
||||
</Typography>
|
||||
</Box>
|
||||
<Box sx={{ fontSize: "0.75rem", alignItems: "center" }} >
|
||||
<Link href={`/u/${view.username}`}>/u/{view.username}</Link>
|
||||
<CopyBubble
|
||||
onClick={(event: any) => { event.stopPropagation() }}
|
||||
tooltip="Copy link" content={`${window.location.origin}/u/{view.username}`} />
|
||||
</Box>
|
||||
</Box>
|
||||
|
||||
{view.rag_content_size !== undefined && view.rag_content_size > 0 && <Chip
|
||||
onClick={(event: React.MouseEvent<HTMLDivElement>) => { navigate('/knowledge-explorer'); event.stopPropagation() }}
|
||||
label={formatRagSize(view.rag_content_size)}
|
||||
color="primary"
|
||||
size="small"
|
||||
sx={{ ml: 2 }}
|
||||
/>
|
||||
/>}
|
||||
</Box>
|
||||
|
||||
<Typography variant="body1" color="text.secondary">
|
||||
{userInfo.description}
|
||||
{view.description}
|
||||
</Typography>
|
||||
</Grid>
|
||||
</Grid>
|
||||
@ -80,8 +89,4 @@ const CandidateInfo: React.FC<CandidateInfoProps> = ({ userInfo }) => {
|
||||
);
|
||||
};
|
||||
|
||||
export type {
|
||||
UserInfo
|
||||
};
|
||||
|
||||
export { CandidateInfo };
|
@ -15,6 +15,7 @@ import { Query } from '../../Components/ChatQuery';
|
||||
import { BackstoryTextField, BackstoryTextFieldRef } from '../../Components/BackstoryTextField';
|
||||
import { BackstoryElementProps } from '../../Components/BackstoryTab';
|
||||
import { connectionBase } from '../../Global';
|
||||
import { useUser } from "../Components/UserContext";
|
||||
|
||||
import './Conversation.css';
|
||||
|
||||
@ -66,6 +67,7 @@ const Conversation = forwardRef<ConversationHandle, ConversationProps>((props: C
|
||||
sx,
|
||||
type,
|
||||
} = props;
|
||||
const { user } = useUser()
|
||||
const [contextUsedPercentage, setContextUsedPercentage] = useState<number>(0);
|
||||
const [processing, setProcessing] = useState<boolean>(false);
|
||||
const [countdown, setCountdown] = useState<number>(0);
|
||||
@ -184,8 +186,15 @@ const Conversation = forwardRef<ConversationHandle, ConversationProps>((props: C
|
||||
return;
|
||||
}
|
||||
|
||||
fetchHistory();
|
||||
}, [fetchHistory, sessionId, setProcessing]);
|
||||
setProcessingMessage(undefined);
|
||||
setStreamingMessage(undefined);
|
||||
setConversation([]);
|
||||
setNoInteractions(true);
|
||||
|
||||
if (user) {
|
||||
fetchHistory();
|
||||
}
|
||||
}, [fetchHistory, sessionId, setProcessing, user]);
|
||||
|
||||
const startCountdown = (seconds: number) => {
|
||||
if (timerRef.current) clearInterval(timerRef.current);
|
||||
@ -549,7 +558,7 @@ const Conversation = forwardRef<ConversationHandle, ConversationProps>((props: C
|
||||
</Tooltip>
|
||||
</Box>
|
||||
</Box>
|
||||
{(noInteractions || !hideDefaultPrompts) && defaultPrompts !== undefined && defaultPrompts.length &&
|
||||
{(noInteractions || !hideDefaultPrompts) && defaultPrompts !== undefined && defaultPrompts.length !== 0 &&
|
||||
<Box sx={{ display: "flex", flexDirection: "column" }}>
|
||||
{
|
||||
defaultPrompts.map((element, index) => {
|
||||
|
@ -34,7 +34,7 @@ import ContentCopyIcon from '@mui/icons-material/ContentCopy';
|
||||
import { NavigationLinkType } from './BackstoryLayout';
|
||||
import { Beta } from './Beta';
|
||||
import './Header.css';
|
||||
import { UserType } from './UserContext';
|
||||
import { useUser, UserInfo } from './UserContext';
|
||||
import { SetSnackType } from '../../Components/Snack';
|
||||
import { CopyBubble } from '../../Components/CopyBubble';
|
||||
|
||||
@ -83,7 +83,6 @@ const MobileDrawer = styled(Drawer)(({ theme }) => ({
|
||||
}));
|
||||
|
||||
interface HeaderProps {
|
||||
user?: UserType | null;
|
||||
transparent?: boolean;
|
||||
onLogout?: () => void;
|
||||
className?: string;
|
||||
@ -96,8 +95,9 @@ interface HeaderProps {
|
||||
}
|
||||
|
||||
const Header: React.FC<HeaderProps> = (props: HeaderProps) => {
|
||||
const { user } = useUser();
|
||||
|
||||
const {
|
||||
user,
|
||||
transparent = false,
|
||||
className,
|
||||
navigate,
|
||||
@ -236,7 +236,7 @@ const Header: React.FC<HeaderProps> = (props: HeaderProps) => {
|
||||
variant="contained"
|
||||
color="secondary"
|
||||
fullWidth
|
||||
onClick={() => navigate("/login") }
|
||||
onClick={() => { navigate("/login"); }}
|
||||
>
|
||||
Login
|
||||
</Button>
|
||||
@ -244,7 +244,7 @@ const Header: React.FC<HeaderProps> = (props: HeaderProps) => {
|
||||
variant="outlined"
|
||||
color="secondary"
|
||||
fullWidth
|
||||
onClick={() => navigate("/register") }
|
||||
onClick={() => { navigate("/register"); }}
|
||||
>
|
||||
Register
|
||||
</Button>
|
||||
@ -277,7 +277,7 @@ const Header: React.FC<HeaderProps> = (props: HeaderProps) => {
|
||||
<Button
|
||||
color="secondary"
|
||||
variant="contained"
|
||||
onClick={() => navigate("/navigate") }
|
||||
onClick={() => { navigate("/register"); }}
|
||||
sx={{ display: { xs: 'none', sm: 'block' } }}
|
||||
>
|
||||
Register
|
||||
@ -299,10 +299,10 @@ const Header: React.FC<HeaderProps> = (props: HeaderProps) => {
|
||||
height: 32,
|
||||
bgcolor: theme.palette.secondary.main,
|
||||
}}>
|
||||
{user?.name.charAt(0).toUpperCase()}
|
||||
{user?.full_name.charAt(0).toUpperCase()}
|
||||
</Avatar>
|
||||
<Box sx={{ display: { xs: 'none', sm: 'block' } }}>
|
||||
{user?.name}
|
||||
{user?.full_name}
|
||||
</Box>
|
||||
<ExpandMore fontSize="small" />
|
||||
</UserButton>
|
||||
@ -418,7 +418,7 @@ const Header: React.FC<HeaderProps> = (props: HeaderProps) => {
|
||||
'&:hover': { bgcolor: 'action.hover', opacity: 1 },
|
||||
}}
|
||||
content={`${window.location.origin}${window.location.pathname}?id=${sessionId}`}
|
||||
onClick={() => setSnack("Link copied!")}
|
||||
onClick={() => { navigate(`${window.location.pathname}?id=${sessionId}`); setSnack("Link copied!") }}
|
||||
size="large"
|
||||
/>}
|
||||
</UserActionsContainer>
|
||||
@ -437,7 +437,7 @@ const Header: React.FC<HeaderProps> = (props: HeaderProps) => {
|
||||
</MobileDrawer>
|
||||
</Toolbar>
|
||||
</Container>
|
||||
<Beta sx={{ left: "-72px", "& .mobile": { right: "-72px" } }} onClick={() => { navigate('/docs/beta'); }} />
|
||||
<Beta sx={{ left: "-90px", "& .mobile": { right: "-72px" } }} onClick={() => { navigate('/docs/beta'); }} />
|
||||
</StyledAppBar>
|
||||
</Box>
|
||||
);
|
||||
|
@ -1,15 +1,29 @@
|
||||
import React, { createContext, useContext, useState } from "react";
|
||||
import React, { createContext, useContext, useEffect, useState } from "react";
|
||||
import { Tunables } from '../../Components/ChatQuery';
|
||||
import { SetSnackType } from '../../Components/Snack';
|
||||
import { connectionBase } from '../../Global';
|
||||
|
||||
type UserType = {
|
||||
type: 'candidate' | 'employer' | 'guest';
|
||||
name: string;
|
||||
avatar?: any;
|
||||
isAuthenticated: boolean;
|
||||
logout: () => void;
|
||||
// Define the UserInfo interface for type safety
|
||||
interface UserInfo {
|
||||
type: 'candidate' | 'employer' | 'guest';
|
||||
profile_url: string;
|
||||
description: string;
|
||||
rag_content_size: number;
|
||||
username: string;
|
||||
first_name: string;
|
||||
last_name: string;
|
||||
full_name: string;
|
||||
contact_info: Record<string, string>;
|
||||
questions: [{
|
||||
question: string;
|
||||
tunables?: Tunables
|
||||
}],
|
||||
isAuthenticated: boolean
|
||||
};
|
||||
|
||||
type UserContextType = {
|
||||
user: UserType | null;
|
||||
setUser: (user: UserType) => void;
|
||||
user: UserInfo | null;
|
||||
setUser: (user: UserInfo | null) => void;
|
||||
};
|
||||
|
||||
const UserContext = createContext<UserContextType | undefined>(undefined);
|
||||
@ -20,8 +34,51 @@ const useUser = () => {
|
||||
return ctx;
|
||||
};
|
||||
|
||||
const UserProvider: React.FC<{ children: React.ReactNode }> = ({ children }) => {
|
||||
const [user, setUser] = useState<UserType | null>(null);
|
||||
interface UserProviderProps {
|
||||
children: React.ReactNode;
|
||||
sessionId: string | undefined;
|
||||
setSnack: SetSnackType;
|
||||
};
|
||||
const UserProvider: React.FC<UserProviderProps> = (props: UserProviderProps) => {
|
||||
const { sessionId, children, setSnack } = props;
|
||||
const [user, setUser] = useState<UserInfo | null>(null);
|
||||
|
||||
useEffect(() => {
|
||||
if (!sessionId || user) {
|
||||
return;
|
||||
}
|
||||
|
||||
const fetchUserFromSession = async (): Promise<UserInfo | null> => {
|
||||
try {
|
||||
let response;
|
||||
response = await fetch(`${connectionBase}/api/user/${sessionId}`, {
|
||||
method: 'GET',
|
||||
credentials: 'include',
|
||||
});
|
||||
if (!response.ok) {
|
||||
throw new Error('Session not found');
|
||||
}
|
||||
const user: UserInfo = {
|
||||
...(await response.json()),
|
||||
type: "guest",
|
||||
isAuthenticated: false,
|
||||
logout: () => { },
|
||||
}
|
||||
console.log("Loaded user:", user);
|
||||
setUser(user);
|
||||
} catch (err) {
|
||||
setSnack("" + err);
|
||||
setUser(null);
|
||||
}
|
||||
return null;
|
||||
};
|
||||
|
||||
fetchUserFromSession();
|
||||
}, [sessionId, user, setUser]);
|
||||
|
||||
if (sessionId === undefined) {
|
||||
return <></>;
|
||||
}
|
||||
return (
|
||||
<UserContext.Provider value={{ user, setUser }}>
|
||||
{children}
|
||||
@ -30,7 +87,7 @@ const UserProvider: React.FC<{ children: React.ReactNode }> = ({ children }) =>
|
||||
};
|
||||
|
||||
export type {
|
||||
UserType
|
||||
UserInfo
|
||||
};
|
||||
|
||||
export {
|
||||
|
@ -25,7 +25,7 @@ interface BetaPageProps {
|
||||
onReturn?: () => void;
|
||||
}
|
||||
|
||||
export const BetaPage: React.FC<BetaPageProps> = ({
|
||||
const BetaPage: React.FC<BetaPageProps> = ({
|
||||
children,
|
||||
title = "Coming Soon",
|
||||
subtitle = "This page is currently in development",
|
||||
@ -38,6 +38,8 @@ export const BetaPage: React.FC<BetaPageProps> = ({
|
||||
const [showSparkle, setShowSparkle] = useState<boolean>(false);
|
||||
const navigate = useNavigate();
|
||||
|
||||
console.log("BetaPage", children);
|
||||
|
||||
// Enhanced sparkle effect for background elements
|
||||
const [sparkles, setSparkles] = useState<Array<{
|
||||
id: number;
|
||||
@ -187,9 +189,9 @@ export const BetaPage: React.FC<BetaPageProps> = ({
|
||||
<Typography color="textSecondary" sx={{ mt: 1 }}>
|
||||
Check back soon for updates.
|
||||
</Typography>
|
||||
<Beta adaptive={false} sx={{ left: "-72px", "& > div": { paddingRight: "30px", background: "gold", color: "#808080" } }} onClick={() => { navigate('/docs/beta'); }} />
|
||||
</Box>
|
||||
)}
|
||||
<Beta adaptive={false} sx={{ opacity: 0.5, left: "-72px", "& > div": { paddingRight: "30px", background: "gold", color: "#808080" } }} onClick={() => { navigate('/docs/beta'); }} />
|
||||
</Box>
|
||||
|
||||
{/* Return button */}
|
||||
@ -264,4 +266,8 @@ export const BetaPage: React.FC<BetaPageProps> = ({
|
||||
/>
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
};
|
||||
|
||||
export {
|
||||
BetaPage
|
||||
}
|
82
frontend/src/NewApp/Pages/CandidateListingPage.tsx
Normal file
82
frontend/src/NewApp/Pages/CandidateListingPage.tsx
Normal file
@ -0,0 +1,82 @@
|
||||
import React, { forwardRef, useEffect, useState, MouseEventHandler } from 'react';
|
||||
import { useNavigate } from "react-router-dom";
|
||||
import useMediaQuery from '@mui/material/useMediaQuery';
|
||||
import Box from '@mui/material/Box';
|
||||
import { useTheme } from '@mui/material/styles';
|
||||
import MuiMarkdown from 'mui-markdown';
|
||||
|
||||
import { BackstoryPageProps } from '../../Components/BackstoryTab';
|
||||
import { Conversation, ConversationHandle } from '../Components/Conversation';
|
||||
import { ChatQuery, Tunables } from '../../Components/ChatQuery';
|
||||
import { MessageList } from '../../Components/Message';
|
||||
import { CandidateInfo } from 'NewApp/Components/CandidateInfo';
|
||||
import { connectionBase } from '../../Global';
|
||||
import { LoadingComponent } from 'NewApp/Components/LoadingComponent';
|
||||
import { useUser, UserInfo } from "../Components/UserContext";
|
||||
import { Navigate } from 'react-router-dom';
|
||||
|
||||
const CandidateListingPage = (props: BackstoryPageProps) => {
|
||||
const navigate = useNavigate();
|
||||
const { sessionId, setSnack, submitQuery } = props;
|
||||
const theme = useTheme();
|
||||
const isMobile = useMediaQuery(theme.breakpoints.down('md'));
|
||||
const { user } = useUser();
|
||||
const [users, setUsers] = useState<UserInfo[] | undefined>(undefined);
|
||||
|
||||
useEffect(() => {
|
||||
if (users !== undefined) {
|
||||
return;
|
||||
}
|
||||
const fetchUsers = async () => {
|
||||
try {
|
||||
let response;
|
||||
response = await fetch(`${connectionBase}/api/u/${sessionId}`, {
|
||||
credentials: 'include',
|
||||
});
|
||||
if (!response.ok) {
|
||||
throw new Error('Session not found');
|
||||
}
|
||||
const users: UserInfo[] = await response.json();
|
||||
users.forEach(u => {
|
||||
u.type = 'guest';
|
||||
u.isAuthenticated = false;
|
||||
});
|
||||
users.sort((a, b) => {
|
||||
let result = a.last_name.localeCompare(b.last_name);
|
||||
if (result === 0) {
|
||||
result = a.first_name.localeCompare(b.first_name);
|
||||
}
|
||||
if (result === 0) {
|
||||
result = a.username.localeCompare(b.username);
|
||||
}
|
||||
return result;
|
||||
});
|
||||
console.log(users);
|
||||
setUsers(users);
|
||||
} catch (err) {
|
||||
setSnack("" + err);
|
||||
}
|
||||
};
|
||||
|
||||
fetchUsers();
|
||||
}, [users]);
|
||||
|
||||
return (
|
||||
<Box>
|
||||
{users?.map((u, i) =>
|
||||
<Box key={`${u.username}`}
|
||||
onClick={(event: React.MouseEvent<HTMLDivElement>) : void => {
|
||||
navigate(`/u/${u.username}`)
|
||||
}}
|
||||
sx={{ cursor: "pointer" }}
|
||||
>
|
||||
<CandidateInfo sx={{ "cursor": "pointer", "&:hover": { border: "2px solid orange" }, border: "2px solid transparent"}} user={u}/>
|
||||
</Box>
|
||||
)}
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
||||
export {
|
||||
CandidateListingPage
|
||||
};
|
@ -1,27 +1,30 @@
|
||||
import React, { forwardRef, useEffect, useState } from 'react';
|
||||
import { useNavigate } from "react-router-dom";
|
||||
import useMediaQuery from '@mui/material/useMediaQuery';
|
||||
import Box from '@mui/material/Box';
|
||||
import { useTheme } from '@mui/material/styles';
|
||||
import MuiMarkdown from 'mui-markdown';
|
||||
|
||||
import { BackstoryPageProps } from '../../Components//BackstoryTab';
|
||||
import { BackstoryPageProps } from '../../Components/BackstoryTab';
|
||||
import { Conversation, ConversationHandle } from '../Components/Conversation';
|
||||
import { ChatQuery, Tunables } from '../../Components/ChatQuery';
|
||||
import { MessageList } from '../../Components/Message';
|
||||
import { CandidateInfo, UserInfo } from 'NewApp/Components/CandidateInfo';
|
||||
import { CandidateInfo } from 'NewApp/Components/CandidateInfo';
|
||||
import { connectionBase } from '../../Global';
|
||||
import { LoadingComponent } from 'NewApp/Components/LoadingComponent';
|
||||
import { Typography } from '@mui/material';
|
||||
import { useUser } from "../Components/UserContext";
|
||||
import { Navigate } from 'react-router-dom';
|
||||
|
||||
const ChatPage = forwardRef<ConversationHandle, BackstoryPageProps>((props: BackstoryPageProps, ref) => {
|
||||
const { sessionId, setSnack, submitQuery } = props;
|
||||
const theme = useTheme();
|
||||
const navigate = useNavigate();
|
||||
const { sessionId, setSnack, submitQuery } = props;
|
||||
const theme = useTheme();
|
||||
const isMobile = useMediaQuery(theme.breakpoints.down('md'));
|
||||
const [questions, setQuestions] = useState<React.ReactElement[]>([]);
|
||||
const [user, setUser] = useState<UserInfo | undefined>(undefined)
|
||||
const [questions, setQuestions] = useState<React.ReactElement[]>([]);
|
||||
const { user } = useUser();
|
||||
|
||||
useEffect(() => {
|
||||
if (user === undefined) {
|
||||
if (!user) {
|
||||
return;
|
||||
}
|
||||
|
||||
@ -38,50 +41,18 @@ const ChatPage = forwardRef<ConversationHandle, BackstoryPageProps>((props: Back
|
||||
</Box>]);
|
||||
}, [user, isMobile, submitQuery]);
|
||||
|
||||
useEffect(() => {
|
||||
const fetchUserInfo = async () => {
|
||||
try {
|
||||
const response = await fetch(connectionBase + `/api/user/${sessionId}`, {
|
||||
method: 'GET',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(`Server responded with ${response.status}: ${response.statusText}`);
|
||||
}
|
||||
|
||||
const data = await response.json();
|
||||
setUser(data);
|
||||
}
|
||||
catch (error) {
|
||||
console.error('Error getting user info:', error);
|
||||
setSnack("Unable to obtain user information.", "error");
|
||||
}
|
||||
};
|
||||
fetchUserInfo();
|
||||
}, [setSnack, sessionId]);
|
||||
|
||||
if (sessionId === undefined || user === undefined) {
|
||||
return (<Box>
|
||||
<LoadingComponent
|
||||
loadingText="Fetching user information..."
|
||||
loaderType="linear"
|
||||
withFade={true}
|
||||
fadeDuration={1200} />
|
||||
</Box>);
|
||||
if (!user) {
|
||||
return (<></>);
|
||||
}
|
||||
|
||||
return (
|
||||
<Box>
|
||||
<CandidateInfo userInfo={user} />
|
||||
<CandidateInfo action="Chat with AI about " />
|
||||
<Conversation
|
||||
ref={ref}
|
||||
{...{
|
||||
multiline: true,
|
||||
type: "chat",
|
||||
placeholder: `What would you like to know about ${user.first_name}?`,
|
||||
placeholder: `What would you like to know about ${user?.first_name}?`,
|
||||
resetLabel: "chat",
|
||||
sessionId,
|
||||
setSnack,
|
||||
|
@ -1,4 +1,5 @@
|
||||
import React, { useState } from 'react';
|
||||
import { Route, Routes, useLocation, useNavigate } from 'react-router-dom';
|
||||
import {
|
||||
Box,
|
||||
Button,
|
||||
@ -20,6 +21,7 @@ import {
|
||||
import { styled } from '@mui/material/styles';
|
||||
import { CloudUpload, PhotoCamera } from '@mui/icons-material';
|
||||
import { useTheme } from '@mui/material/styles';
|
||||
import { Beta } from '../Components/Beta';
|
||||
|
||||
// Interfaces
|
||||
interface ProfileFormData {
|
||||
@ -47,6 +49,7 @@ const VisuallyHiddenInput = styled('input')({
|
||||
|
||||
const CreateProfilePage: React.FC = () => {
|
||||
const theme = useTheme();
|
||||
const navigate = useNavigate();
|
||||
const isMobile = useMediaQuery(theme.breakpoints.down('sm'));
|
||||
|
||||
// State management
|
||||
@ -280,7 +283,7 @@ const CreateProfilePage: React.FC = () => {
|
||||
<Grid size={{xs: 12}}>
|
||||
<Typography variant="body1" component="p">
|
||||
Upload your resume to complete your profile. We'll analyze it to better understand your skills and experience.
|
||||
(Supported formats: PDF, DOCX)
|
||||
(Supported formats: .pdf, .docx, .md, and .txt)
|
||||
</Typography>
|
||||
<Box sx={{ textAlign: 'center', mt: 2 }}>
|
||||
<Button
|
||||
@ -292,7 +295,7 @@ const CreateProfilePage: React.FC = () => {
|
||||
Upload Resume
|
||||
<VisuallyHiddenInput
|
||||
type="file"
|
||||
accept=".pdf,.docx"
|
||||
accept=".pdf,.docx,.txt,.md"
|
||||
onChange={handleResumeUpload}
|
||||
/>
|
||||
</Button>
|
||||
@ -318,7 +321,7 @@ const CreateProfilePage: React.FC = () => {
|
||||
sx={{
|
||||
p: { xs: 2, sm: 4 },
|
||||
mt: { xs: 2, sm: 4 },
|
||||
mb: { xs: 2, sm: 4 }
|
||||
mb: { xs: 2, sm: 4 },
|
||||
}}
|
||||
>
|
||||
<Typography component="h1" variant="h4" align="center" gutterBottom>
|
||||
|
@ -1,38 +1,77 @@
|
||||
import React, { useEffect } from "react";
|
||||
import { useParams } from "react-router-dom";
|
||||
import { useUser, UserType } from "../Components/UserContext";
|
||||
import { Typography, Box } from "@mui/material";
|
||||
import { Navigate, useParams, useNavigate, useLocation } from "react-router-dom";
|
||||
import { useUser, UserInfo } from "../Components/UserContext";
|
||||
import { Box } from "@mui/material";
|
||||
import { connectionBase } from "../../Global";
|
||||
import { SetSnackType } from '../../Components/Snack';
|
||||
import { LoadingComponent } from "../Components/LoadingComponent";
|
||||
|
||||
const mockFetchUser = async (username: string) => {
|
||||
return new Promise<UserType>((resolve) => {
|
||||
const user : UserType = {
|
||||
type: "candidate",
|
||||
name: username,
|
||||
isAuthenticated: true,
|
||||
logout: () => {},
|
||||
};
|
||||
setTimeout(() => resolve(user), 500);
|
||||
});
|
||||
interface UserRouteProps {
|
||||
sessionId?: string | null;
|
||||
setSnack: SetSnackType,
|
||||
};
|
||||
|
||||
const UserRoute: React.FC = () => {
|
||||
const UserRoute: React.FC<UserRouteProps> = (props: UserRouteProps) => {
|
||||
const { sessionId, setSnack } = props;
|
||||
const { username } = useParams<{ username: string }>();
|
||||
const { user, setUser } = useUser();
|
||||
const navigate = useNavigate();
|
||||
const location = useLocation();
|
||||
|
||||
useEffect(() => {
|
||||
if (username) {
|
||||
mockFetchUser(username).then(setUser);
|
||||
if (!sessionId) {
|
||||
return;
|
||||
}
|
||||
}, [username, setUser]);
|
||||
|
||||
return (
|
||||
<Box m={2}>
|
||||
<Typography variant="h5">User Page</Typography>
|
||||
<Typography variant="body1">
|
||||
{user ? `Hello, ${user.name}` : "Loading..."}
|
||||
</Typography>
|
||||
</Box>
|
||||
);
|
||||
const fetchUser = async (username: string): Promise<UserInfo | null> => {
|
||||
try {
|
||||
let response;
|
||||
response = await fetch(`${connectionBase}/api/u/${username}/${sessionId}`, {
|
||||
method: 'POST',
|
||||
credentials: 'include',
|
||||
});
|
||||
if (!response.ok) {
|
||||
throw new Error('Session not found');
|
||||
}
|
||||
const user: UserInfo = {
|
||||
...(await response.json()),
|
||||
type: "guest",
|
||||
isAuthenticated: false,
|
||||
logout: () => { },
|
||||
}
|
||||
console.log("Loaded user:", user);
|
||||
setUser(user);
|
||||
navigate('/chat');
|
||||
} catch (err) {
|
||||
setSnack("" + err);
|
||||
setUser(null);
|
||||
navigate('/');
|
||||
}
|
||||
return null;
|
||||
};
|
||||
|
||||
if (user?.username !== username && username) {
|
||||
fetchUser(username);
|
||||
} else {
|
||||
if (user?.username) {
|
||||
navigate('/chat');
|
||||
} else {
|
||||
navigate('/');
|
||||
}
|
||||
}
|
||||
}, [user, username, setUser, sessionId, setSnack, navigate]);
|
||||
|
||||
if (sessionId === undefined || !user) {
|
||||
return (<Box>
|
||||
<LoadingComponent
|
||||
loadingText="Fetching user information..."
|
||||
loaderType="linear"
|
||||
withFade={true}
|
||||
fadeDuration={1200} />
|
||||
</Box>);
|
||||
} else {
|
||||
return (<></>);
|
||||
}
|
||||
};
|
||||
|
||||
export { UserRoute };
|
||||
|
@ -752,6 +752,69 @@ class WebServer:
|
||||
return JSONResponse({"error": f"{context_id} does not exist."}, 404)
|
||||
return JSONResponse({"id": context.id})
|
||||
|
||||
@self.app.get("/api/u/{context_id}")
|
||||
async def get_users(context_id: str, request: Request):
|
||||
logger.info(f"{request.method} {request.url.path}")
|
||||
try:
|
||||
context = self.load_context(context_id)
|
||||
if not context:
|
||||
return JSONResponse({"error": f"Context {context_id} not found."}, status_code=404)
|
||||
|
||||
users = [User.sanitize(u) for u in User.get_users()]
|
||||
|
||||
return JSONResponse(users)
|
||||
except Exception as e:
|
||||
logger.error(traceback.format_exc())
|
||||
logger.error(f"get_users error: {str(e)}")
|
||||
return JSONResponse({ "error": "Unable to parse users"}, 500)
|
||||
|
||||
@self.app.post("/api/u/{username}/{context_id}")
|
||||
async def post_user(username: str, context_id: str, request: Request):
|
||||
logger.info(f"{request.method} {request.url.path}")
|
||||
try:
|
||||
if not User.exists(username):
|
||||
return JSONResponse({"error": f"User {username} not found."}, status_code=404)
|
||||
context = self.load_context(context_id)
|
||||
if not context:
|
||||
return JSONResponse({"error": f"Context {context_id} not found."}, status_code=404)
|
||||
matching_user = next((user for user in self.users if user.username == username), None)
|
||||
if matching_user:
|
||||
user = matching_user
|
||||
else:
|
||||
user = User(username=username, llm=self.llm)
|
||||
user.initialize(prometheus_collector=self.prometheus_collector)
|
||||
self.users.append(user)
|
||||
reset_map = (
|
||||
"chat",
|
||||
"job_description",
|
||||
"resume",
|
||||
"fact_check",
|
||||
)
|
||||
for mode in reset_map:
|
||||
tmp = context.get_agent(mode)
|
||||
if not tmp:
|
||||
continue
|
||||
logger.info(f"User change: Resetting history for {mode}")
|
||||
if mode != "chat":
|
||||
context.remove_agent(tmp)
|
||||
tmp.conversation.reset()
|
||||
context.user = user
|
||||
user_data = {
|
||||
"username": user.username,
|
||||
"first_name": user.first_name,
|
||||
"last_name": user.last_name,
|
||||
"full_name": user.full_name,
|
||||
"description": user.description,
|
||||
"contact_info": user.contact_info,
|
||||
"rag_content_size": user.rag_content_size,
|
||||
"profile_url": user.profile_url,
|
||||
"questions": [ q.model_dump(mode='json') for q in user.user_questions],
|
||||
}
|
||||
self.save_context(context_id)
|
||||
return JSONResponse(user_data)
|
||||
except Exception as e:
|
||||
return JSONResponse({ "error": "Unable to load user {username}"}, 500)
|
||||
|
||||
@self.app.post("/api/context/u/{username}")
|
||||
async def create_user_context(username: str, request: Request):
|
||||
logger.info(f"{request.method} {request.url.path}")
|
||||
|
@ -70,12 +70,14 @@ class Context(BaseModel):
|
||||
raise ValueError("Attempt to dereference default_factory constructed User")
|
||||
return self.Context__user
|
||||
|
||||
# Only allow setting of 'user' once
|
||||
@user.setter
|
||||
def user(self, new_user: User) -> User:
|
||||
if self.Context__user.username != "__invalid__":
|
||||
raise ValueError("user can only be set once")
|
||||
logger.info(f"Binding context {self.id} to user {new_user.username}")
|
||||
if self.Context__user.username != "__invalid__" and new_user.username != self.Context__user.username:
|
||||
logger.info(f"Resetting context changing from {self.Context__user.username} to user {new_user.username}")
|
||||
self.agents = [a for a in self.agents if a.agent_type == "chat"]
|
||||
for a in self.agents:
|
||||
a.conversation.reset()
|
||||
self.username = new_user.username
|
||||
self.Context__user = new_user
|
||||
return new_user
|
||||
|
@ -171,6 +171,73 @@ class User(BaseModel):
|
||||
yield message
|
||||
return
|
||||
|
||||
@classmethod
|
||||
def sanitize(cls, user: Dict[str, Any]):
|
||||
sanitized : Dict[str, Any] = {}
|
||||
sanitized["username"] = user.get("username")
|
||||
sanitized["first_name"] = user.get("first_name", sanitized["username"])
|
||||
sanitized["last_name"] = user.get("last_name", "")
|
||||
sanitized["full_name"] = user.get("full_name", f"{sanitized["first_name"]} {sanitized["last_name"]}")
|
||||
sanitized["description"] = user.get("description", "")
|
||||
sanitized["profile_url"] = user.get("profile_url", "")
|
||||
contact_info = user.get("contact_info", {})
|
||||
sanitized["contact_info"] = {}
|
||||
for key in contact_info:
|
||||
if not isinstance(contact_info[key], (str, int, float, complex)):
|
||||
continue
|
||||
sanitized["contact_info"][key] = contact_info[key]
|
||||
questions = user.get("questions", [ f"Tell me about {sanitized['first_name']}.", f"What are {sanitized['first_name']}'s professional strengths?"])
|
||||
sanitized["user_questions"] = []
|
||||
for question in questions:
|
||||
if type(question) == str:
|
||||
sanitized["user_questions"].append({"question": question})
|
||||
else:
|
||||
try:
|
||||
tmp = Question.model_validate(question)
|
||||
sanitized["user_questions"].append({"question": tmp.question})
|
||||
except Exception as e:
|
||||
continue
|
||||
return sanitized
|
||||
|
||||
@classmethod
|
||||
def get_users(cls):
|
||||
# Initialize an empty list to store parsed JSON data
|
||||
user_data = []
|
||||
|
||||
# Define the users directory path
|
||||
users_dir = os.path.join(defines.user_dir)
|
||||
|
||||
# Check if the users directory exists
|
||||
if not os.path.exists(users_dir):
|
||||
return user_data
|
||||
|
||||
# Iterate through all items in the users directory
|
||||
for item in os.listdir(users_dir):
|
||||
# Construct the full path to the item
|
||||
item_path = os.path.join(users_dir, item)
|
||||
|
||||
# Check if the item is a directory
|
||||
if os.path.isdir(item_path):
|
||||
# Construct the path to info.json
|
||||
info_path = os.path.join(item_path, "info.json")
|
||||
|
||||
# Check if info.json exists
|
||||
if os.path.exists(info_path):
|
||||
try:
|
||||
# Read and parse the JSON file
|
||||
with open(info_path, 'r') as file:
|
||||
data = json.load(file)
|
||||
data["username"] = item
|
||||
user_data.append(data)
|
||||
except json.JSONDecodeError:
|
||||
# Skip files that aren't valid JSON
|
||||
continue
|
||||
except Exception as e:
|
||||
# Skip files that can't be read
|
||||
continue
|
||||
|
||||
return user_data
|
||||
|
||||
def initialize(self, prometheus_collector):
|
||||
if self.User__initialized:
|
||||
# Initialization can only be attempted once; if there are multiple attempts, it means
|
||||
@ -200,8 +267,8 @@ class User(BaseModel):
|
||||
self.first_name = info.get("first_name", self.username)
|
||||
self.last_name = info.get("last_name", "")
|
||||
self.full_name = info.get("full_name", f"{self.first_name} {self.last_name}")
|
||||
self.description = info.get("description", self.description)
|
||||
self.profile_url = info.get("profile_url", self.description)
|
||||
self.description = info.get("description", "")
|
||||
self.profile_url = info.get("profile_url", "")
|
||||
self.contact_info = info.get("contact_info", {})
|
||||
questions = info.get("questions", [ f"Tell me about {self.first_name}.", f"What are {self.first_name}'s professional strengths?"])
|
||||
self.user_questions = []
|
||||
|
@ -1,6 +1,8 @@
|
||||
{
|
||||
"first_name": "Eliza",
|
||||
"last_name": "Morgan",
|
||||
"description": "Eliza Morgan is an AI generated persona. In addition, she is a conservation botanist with over a decade of experience in leading ecological restoration projects, managing native plant programs, and advancing rare plant propagation methods across the Pacific Northwest. Her proven record of scientific innovation, effective stakeholder engagement, and successful grant writing are key to her professional strengths.",
|
||||
"profile_url": "https://backstory.ketrenos.com/eliza.png",
|
||||
"questions": [
|
||||
"Is Eliza real?",
|
||||
"What are Eliza's skills?"
|
||||
|
Loading…
x
Reference in New Issue
Block a user