Compare commits
3 Commits
b8f7cbcf30
...
c1e6ab9360
Author | SHA1 | Date | |
---|---|---|---|
c1e6ab9360 | |||
0fba24b173 | |||
8ddc13d1eb |
@ -12,7 +12,7 @@ import { StatusBox, StatusIcon } from './ui/StatusIcon';
|
||||
import { CopyBubble } from './CopyBubble';
|
||||
import { useAppState } from 'hooks/GlobalContext';
|
||||
import { StreamingOptions } from 'services/api-client';
|
||||
import { Navigate, useNavigate } from 'react-router-dom';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
|
||||
interface ResumeGeneratorProps {
|
||||
job: Job;
|
||||
|
@ -39,7 +39,7 @@ const BackstoryPageContainer = (props: BackstoryPageContainerProps): JSX.Element
|
||||
flexGrow: 1,
|
||||
p: '0 !important',
|
||||
m: '0 auto !important',
|
||||
minWidth: variant === 'normal' ? '1024px' : '100%',
|
||||
// minWidth: variant === 'normal' ? '1024px' : '100%',
|
||||
maxWidth: variant === 'normal' ? '1024px' : '100%',
|
||||
height: '100%',
|
||||
minHeight: 0,
|
||||
|
@ -1,19 +1,16 @@
|
||||
// components/layout/Header.tsx
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import React, { useState } from 'react';
|
||||
import { NavigateFunction, useLocation } from 'react-router-dom';
|
||||
import {
|
||||
AppBar,
|
||||
Toolbar,
|
||||
Tooltip,
|
||||
Typography,
|
||||
Button,
|
||||
IconButton,
|
||||
Box,
|
||||
Drawer,
|
||||
Divider,
|
||||
Avatar,
|
||||
Tabs,
|
||||
Tab,
|
||||
Container,
|
||||
Fade,
|
||||
Popover,
|
||||
@ -26,25 +23,18 @@ import {
|
||||
List,
|
||||
ListItem,
|
||||
ListItemButton,
|
||||
SxProps,
|
||||
} from '@mui/material';
|
||||
import { styled, useTheme } from '@mui/material/styles';
|
||||
import {
|
||||
Menu as MenuIcon,
|
||||
Dashboard,
|
||||
Person,
|
||||
Logout,
|
||||
Settings,
|
||||
ExpandMore,
|
||||
ExpandLess,
|
||||
KeyboardArrowDown,
|
||||
} from '@mui/icons-material';
|
||||
import FaceRetouchingNaturalIcon from '@mui/icons-material/FaceRetouchingNatural';
|
||||
import { getUserMenuItemsByGroup } from 'config/navigationConfig';
|
||||
import { NavigationItem } from 'types/navigation';
|
||||
import { Beta } from 'components/ui/Beta';
|
||||
import { Candidate, Employer } from 'types/types';
|
||||
import { SetSnackType } from 'components/Snack';
|
||||
import { CopyBubble } from 'components/CopyBubble';
|
||||
|
||||
import 'components/layout/Header.css';
|
||||
@ -181,7 +171,9 @@ const Header: React.FC<HeaderProps> = (props: HeaderProps) => {
|
||||
id: 'divider',
|
||||
label: '',
|
||||
icon: null,
|
||||
action: () => {},
|
||||
action: () => {
|
||||
console.log('Divider clicked');
|
||||
},
|
||||
group: 'divider',
|
||||
});
|
||||
}
|
||||
@ -193,7 +185,9 @@ const Header: React.FC<HeaderProps> = (props: HeaderProps) => {
|
||||
id: item.id,
|
||||
label: item.label as string,
|
||||
icon: item.icon || null,
|
||||
action: () => item.path && navigate(item.path.replace(/:.*$/, '')),
|
||||
action: () => {
|
||||
item.path && navigate(item.path.replace(/:.*$/, ''));
|
||||
},
|
||||
group: 'account',
|
||||
});
|
||||
}
|
||||
@ -205,7 +199,9 @@ const Header: React.FC<HeaderProps> = (props: HeaderProps) => {
|
||||
id: 'divider',
|
||||
label: '',
|
||||
icon: null,
|
||||
action: () => {},
|
||||
action: () => {
|
||||
console.log('Divider clicked');
|
||||
},
|
||||
group: 'divider',
|
||||
});
|
||||
}
|
||||
@ -217,7 +213,9 @@ const Header: React.FC<HeaderProps> = (props: HeaderProps) => {
|
||||
id: item.id,
|
||||
label: item.label as string,
|
||||
icon: item.icon || null,
|
||||
action: () => item.path && navigate(item.path.replace(/:.*$/, '')),
|
||||
action: () => {
|
||||
item.path && navigate(item.path.replace(/:.*$/, ''));
|
||||
},
|
||||
group: 'admin',
|
||||
});
|
||||
}
|
||||
@ -229,7 +227,9 @@ const Header: React.FC<HeaderProps> = (props: HeaderProps) => {
|
||||
id: 'divider',
|
||||
label: '',
|
||||
icon: null,
|
||||
action: () => {},
|
||||
action: () => {
|
||||
console.log('Divider clicked');
|
||||
},
|
||||
group: 'divider',
|
||||
});
|
||||
}
|
||||
@ -252,7 +252,9 @@ const Header: React.FC<HeaderProps> = (props: HeaderProps) => {
|
||||
id: item.id,
|
||||
label: item.label as string,
|
||||
icon: item.icon || null,
|
||||
action: () => item.path && navigate(item.path.replace(/:.*$/, '')),
|
||||
action: () => {
|
||||
item.path && navigate(item.path.replace(/:.*$/, ''));
|
||||
},
|
||||
group: 'system',
|
||||
});
|
||||
}
|
||||
|
@ -1,6 +1,6 @@
|
||||
import React, { useState, useRef, useEffect } from 'react';
|
||||
import { Box, Link, Typography, Avatar, Grid, SxProps, Tooltip, IconButton } from '@mui/material';
|
||||
import { Card, CardContent, Divider, useTheme } from '@mui/material';
|
||||
import { Box, Link, Typography, Avatar, SxProps, Tooltip, IconButton } from '@mui/material';
|
||||
import { Divider, useTheme } from '@mui/material';
|
||||
import DeleteIcon from '@mui/icons-material/Delete';
|
||||
import { useMediaQuery } from '@mui/material';
|
||||
import { Candidate, CandidateAI } from 'types/types';
|
||||
@ -8,7 +8,6 @@ import { CopyBubble } from 'components/CopyBubble';
|
||||
import { rest } from 'lodash';
|
||||
import { AIBanner } from 'components/ui/AIBanner';
|
||||
import { useAuth } from 'hooks/AuthContext';
|
||||
import { DeleteConfirmation } from '../DeleteConfirmation';
|
||||
|
||||
interface CandidateInfoProps {
|
||||
candidate: Candidate;
|
||||
@ -21,7 +20,7 @@ interface CandidateInfoProps {
|
||||
const CandidateInfo: React.FC<CandidateInfoProps> = (props: CandidateInfoProps) => {
|
||||
const { candidate } = props;
|
||||
const { user, apiClient } = useAuth();
|
||||
const { sx, action = '', elevation = 1, variant = 'normal' } = props;
|
||||
const { sx, action = '', variant = 'normal' } = props;
|
||||
const theme = useTheme();
|
||||
const isMobile = useMediaQuery(theme.breakpoints.down('md')) || variant === 'minimal';
|
||||
const ai: CandidateAI | null = 'isAI' in candidate ? (candidate as CandidateAI) : null;
|
||||
|
@ -1,11 +1,9 @@
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
import Button from '@mui/material/Button';
|
||||
import Box from '@mui/material/Box';
|
||||
|
||||
import { BackstoryElementProps } from 'components/BackstoryTab';
|
||||
import { CandidateInfo } from 'components/ui/CandidateInfo';
|
||||
import { Candidate, CandidateAI } from 'types/types';
|
||||
import { Candidate } from 'types/types';
|
||||
import { useAuth } from 'hooks/AuthContext';
|
||||
import { useAppState, useSelectedCandidate } from 'hooks/GlobalContext';
|
||||
import { Paper } from '@mui/material';
|
||||
@ -16,9 +14,8 @@ interface CandidatePickerProps extends BackstoryElementProps {
|
||||
|
||||
const CandidatePicker = (props: CandidatePickerProps) => {
|
||||
const { onSelect, sx } = props;
|
||||
const { apiClient, user } = useAuth();
|
||||
const { apiClient } = useAuth();
|
||||
const { selectedCandidate, setSelectedCandidate } = useSelectedCandidate();
|
||||
const navigate = useNavigate();
|
||||
const { setSnack } = useAppState();
|
||||
const [candidates, setCandidates] = useState<Candidate[] | null>(null);
|
||||
|
||||
@ -64,7 +61,7 @@ const CandidatePicker = (props: CandidatePickerProps) => {
|
||||
justifyContent: 'center',
|
||||
}}
|
||||
>
|
||||
{candidates?.map((u, i) => (
|
||||
{candidates?.map(u => (
|
||||
<Paper
|
||||
key={`${u.username}`}
|
||||
onClick={() => {
|
||||
|
@ -1,7 +1,5 @@
|
||||
import React, { useEffect, useRef, useState } from 'react';
|
||||
import React from 'react';
|
||||
import Box from '@mui/material/Box';
|
||||
import useMediaQuery from '@mui/material/useMediaQuery';
|
||||
import { SxProps, useTheme } from '@mui/material/styles';
|
||||
|
||||
import './ComingSoon.css';
|
||||
|
||||
@ -11,7 +9,6 @@ type ComingSoonProps = {
|
||||
|
||||
const ComingSoon: React.FC<ComingSoonProps> = (props: ComingSoonProps) => {
|
||||
const { children } = props;
|
||||
const theme = useTheme();
|
||||
return (
|
||||
<Box className="ComingSoon">
|
||||
<Box className="ComingSoon-label">Coming Soon</Box>
|
||||
|
@ -1,17 +1,12 @@
|
||||
import React, { JSX, useActionState, useEffect, useRef, useState } from 'react';
|
||||
import React, { JSX, useEffect, useRef, useState } from 'react';
|
||||
import {
|
||||
Box,
|
||||
Link,
|
||||
Typography,
|
||||
Avatar,
|
||||
Grid,
|
||||
SxProps,
|
||||
CardActions,
|
||||
Chip,
|
||||
Stack,
|
||||
CardHeader,
|
||||
Button,
|
||||
styled,
|
||||
LinearProgress,
|
||||
IconButton,
|
||||
Tooltip,
|
||||
@ -20,11 +15,8 @@ import { Card, CardContent, Divider, useTheme } from '@mui/material';
|
||||
import DeleteIcon from '@mui/icons-material/Delete';
|
||||
import { useMediaQuery } from '@mui/material';
|
||||
import { Job } from 'types/types';
|
||||
import { CopyBubble } from 'components/CopyBubble';
|
||||
import { rest } from 'lodash';
|
||||
import { AIBanner } from 'components/ui/AIBanner';
|
||||
import { useAuth } from 'hooks/AuthContext';
|
||||
import { DeleteConfirmation } from '../DeleteConfirmation';
|
||||
import { Build, CheckCircle, Description, Psychology, Star, Work } from '@mui/icons-material';
|
||||
import ModelTrainingIcon from '@mui/icons-material/ModelTraining';
|
||||
import { StatusIcon, StatusBox } from 'components/ui/StatusIcon';
|
||||
@ -45,7 +37,7 @@ interface JobInfoProps {
|
||||
const JobInfo: React.FC<JobInfoProps> = (props: JobInfoProps) => {
|
||||
const { setSnack } = useAppState();
|
||||
const { user, apiClient } = useAuth();
|
||||
const { sx, action = '', elevation = 1, variant = 'normal', job } = props;
|
||||
const { sx, variant = 'normal', job } = props;
|
||||
const theme = useTheme();
|
||||
const isMobile = useMediaQuery(theme.breakpoints.down('md')) || variant === 'minimal';
|
||||
const isAdmin = user?.isAdmin;
|
||||
|
@ -1,6 +1,4 @@
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
import Button from '@mui/material/Button';
|
||||
import Box from '@mui/material/Box';
|
||||
|
||||
import { BackstoryElementProps } from 'components/BackstoryTab';
|
||||
@ -17,7 +15,7 @@ interface JobPickerProps extends BackstoryElementProps {
|
||||
const JobPicker = (props: JobPickerProps) => {
|
||||
const { onSelect } = props;
|
||||
const { apiClient } = useAuth();
|
||||
const { selectedJob, setSelectedJob } = useSelectedJob();
|
||||
const { selectedJob } = useSelectedJob();
|
||||
const { setSnack } = useAppState();
|
||||
const [jobs, setJobs] = useState<Job[] | null>(null);
|
||||
|
||||
@ -55,7 +53,7 @@ const JobPicker = (props: JobPickerProps) => {
|
||||
justifyContent: 'center',
|
||||
}}
|
||||
>
|
||||
{jobs?.map((j, i) => (
|
||||
{jobs?.map(j => (
|
||||
<Paper
|
||||
key={`${j.id}`}
|
||||
onClick={() => {
|
||||
|
@ -28,7 +28,6 @@ import {
|
||||
Business as BusinessIcon,
|
||||
Work as WorkIcon,
|
||||
Schedule as ScheduleIcon,
|
||||
Close as CloseIcon,
|
||||
ArrowBack as ArrowBackIcon,
|
||||
} from '@mui/icons-material';
|
||||
import { TransitionProps } from '@mui/material/transitions';
|
||||
@ -36,7 +35,7 @@ import { JobInfo } from 'components/ui/JobInfo';
|
||||
import { Job } from 'types/types';
|
||||
import { useAuth } from 'hooks/AuthContext';
|
||||
import { useAppState, useSelectedJob } from 'hooks/GlobalContext';
|
||||
import { Navigate, useNavigate, useParams } from 'react-router-dom';
|
||||
import { useNavigate, useParams } from 'react-router-dom';
|
||||
|
||||
type SortField = 'updatedAt' | 'createdAt' | 'company' | 'title';
|
||||
type SortOrder = 'asc' | 'desc';
|
||||
@ -71,8 +70,8 @@ const JobViewer: React.FC<JobViewerProps> = ({ onSelect }) => {
|
||||
const { jobId } = useParams<{ jobId?: string }>();
|
||||
|
||||
useEffect(() => {
|
||||
if (loading) return; // Prevent multiple calls
|
||||
const getJobs = async () => {
|
||||
setLoading(true);
|
||||
try {
|
||||
const results = await apiClient.getJobs();
|
||||
const jobsData: Job[] = results.data || [];
|
||||
@ -101,8 +100,9 @@ const JobViewer: React.FC<JobViewerProps> = ({ onSelect }) => {
|
||||
}
|
||||
};
|
||||
|
||||
setLoading(true);
|
||||
getJobs();
|
||||
}, [apiClient, setSnack]);
|
||||
}, [apiClient, setSnack, loading]);
|
||||
|
||||
const sortJobs = (jobsList: Job[], field: SortField, order: SortOrder): Job[] => {
|
||||
return [...jobsList].sort((a, b) => {
|
||||
|
@ -59,7 +59,7 @@ const JobsTable: React.FC<JobsTableProps> = ({
|
||||
|
||||
// Fetch jobs from API
|
||||
const fetchJobs = React.useCallback(
|
||||
async (pageNum: number = 0, searchTerm: string = '') => {
|
||||
async (pageNum = 0, searchTerm = '') => {
|
||||
try {
|
||||
setLoading(true);
|
||||
setError(null);
|
||||
@ -71,7 +71,6 @@ const JobsTable: React.FC<JobsTableProps> = ({
|
||||
};
|
||||
|
||||
let paginationResponse: Types.PaginatedResponse;
|
||||
let url = `/api/1.0/jobs`;
|
||||
if (searchTerm.trim()) {
|
||||
paginationResponse = await apiClient.searchJobs(searchTerm);
|
||||
} else {
|
||||
@ -152,17 +151,12 @@ const JobsTable: React.FC<JobsTableProps> = ({
|
||||
onJobSelect?.(selectedJobsList);
|
||||
};
|
||||
|
||||
// Utility functions
|
||||
const formatDate = (dateString: string) => {
|
||||
return new Date(dateString).toLocaleDateString();
|
||||
};
|
||||
|
||||
const getOwnerName = (owner?: Types.Job['owner']) => {
|
||||
if (!owner) return 'Unknown';
|
||||
return `${owner.firstName || ''} ${owner.lastName || ''}`.trim() || owner.email || 'Unknown';
|
||||
};
|
||||
|
||||
const truncateDescription = (description: string, maxLength: number = 100) => {
|
||||
const truncateDescription = (description: string, maxLength = 100) => {
|
||||
if (description.length <= maxLength) return description;
|
||||
return description.substring(0, maxLength) + '...';
|
||||
};
|
||||
|
@ -1,3 +1,4 @@
|
||||
import React from 'react';
|
||||
import Box from '@mui/material/Box';
|
||||
import './LoginRestricted.css';
|
||||
|
||||
|
@ -1,13 +1,9 @@
|
||||
import React, { useEffect, useRef, useState } from 'react';
|
||||
import {
|
||||
Box,
|
||||
Link,
|
||||
Typography,
|
||||
Avatar,
|
||||
Grid,
|
||||
SxProps,
|
||||
CardActions,
|
||||
Chip,
|
||||
Stack,
|
||||
CardHeader,
|
||||
Button,
|
||||
@ -19,7 +15,6 @@ import {
|
||||
Divider,
|
||||
useTheme,
|
||||
useMediaQuery,
|
||||
TextField,
|
||||
Dialog,
|
||||
DialogTitle,
|
||||
DialogContent,
|
||||
@ -38,8 +33,6 @@ import {
|
||||
Work as WorkIcon,
|
||||
Person as PersonIcon,
|
||||
Schedule as ScheduleIcon,
|
||||
Visibility as VisibilityIcon,
|
||||
VisibilityOff as VisibilityOffIcon,
|
||||
ModelTraining,
|
||||
} from '@mui/icons-material';
|
||||
import InputIcon from '@mui/icons-material/Input';
|
||||
@ -73,14 +66,13 @@ const ResumeInfo: React.FC<ResumeInfoProps> = (props: ResumeInfoProps) => {
|
||||
const { setSnack } = useAppState();
|
||||
const { resume } = props;
|
||||
const { user, apiClient } = useAuth();
|
||||
const { sx, action = '', elevation = 1, variant = 'normal' } = props;
|
||||
const { sx, variant = 'normal' } = props;
|
||||
const theme = useTheme();
|
||||
const isMobile = useMediaQuery(theme.breakpoints.down('md')) || variant === 'minimal';
|
||||
const isAdmin = user?.isAdmin;
|
||||
const [activeResume, setActiveResume] = useState<Resume>({ ...resume });
|
||||
const [deleted, setDeleted] = useState<boolean>(false);
|
||||
const [editDialogOpen, setEditDialogOpen] = useState<boolean>(false);
|
||||
const [printDialogOpen, setPrintDialogOpen] = useState<boolean>(false);
|
||||
const [editContent, setEditContent] = useState<string>('');
|
||||
const [editSystemPrompt, setEditSystemPrompt] = useState<string>('');
|
||||
const [editPrompt, setEditPrompt] = useState<string>('');
|
||||
@ -437,32 +429,12 @@ const ResumeInfo: React.FC<ResumeInfoProps> = (props: ResumeInfoProps) => {
|
||||
</Box>
|
||||
)}
|
||||
|
||||
{/* Print Dialog */}
|
||||
<Dialog
|
||||
open={printDialogOpen}
|
||||
onClose={() => {}} //setPrintDialogOpen(false)}
|
||||
maxWidth="lg"
|
||||
fullWidth
|
||||
fullScreen={true}
|
||||
>
|
||||
<StyledMarkdown
|
||||
content={activeResume.resume}
|
||||
sx={{
|
||||
position: 'relative',
|
||||
maxHeight: '100%',
|
||||
width: '100%',
|
||||
display: 'flex',
|
||||
flexGrow: 1,
|
||||
flex: 1 /* Take remaining space in some-container */,
|
||||
overflowY: 'auto' /* Scroll if content overflows */,
|
||||
}}
|
||||
/>
|
||||
</Dialog>
|
||||
|
||||
{/* Edit Dialog */}
|
||||
<Dialog
|
||||
open={editDialogOpen}
|
||||
onClose={() => setEditDialogOpen(false)}
|
||||
onClose={() => {
|
||||
setEditDialogOpen(false);
|
||||
}}
|
||||
maxWidth="lg"
|
||||
fullWidth
|
||||
disableEscapeKeyDown={true}
|
||||
|
@ -13,7 +13,6 @@ import {
|
||||
Select,
|
||||
MenuItem,
|
||||
InputLabel,
|
||||
Chip,
|
||||
IconButton,
|
||||
Dialog,
|
||||
AppBar,
|
||||
@ -27,11 +26,9 @@ import {
|
||||
import {
|
||||
KeyboardArrowUp as ArrowUpIcon,
|
||||
KeyboardArrowDown as ArrowDownIcon,
|
||||
Description as DescriptionIcon,
|
||||
Work as WorkIcon,
|
||||
Person as PersonIcon,
|
||||
Schedule as ScheduleIcon,
|
||||
Close as CloseIcon,
|
||||
ArrowBack as ArrowBackIcon,
|
||||
Search as SearchIcon,
|
||||
Clear as ClearIcon,
|
||||
@ -40,7 +37,7 @@ import { TransitionProps } from '@mui/material/transitions';
|
||||
import { ResumeInfo } from 'components/ui/ResumeInfo';
|
||||
import { useAuth } from 'hooks/AuthContext';
|
||||
import { useAppState, useSelectedResume } from 'hooks/GlobalContext'; // Assuming similar context exists
|
||||
import { Navigate, useNavigate, useParams } from 'react-router-dom';
|
||||
import { useNavigate, useParams } from 'react-router-dom';
|
||||
import { Resume } from 'types/types';
|
||||
|
||||
type SortField = 'updatedAt' | 'createdAt' | 'candidateId' | 'jobId';
|
||||
@ -71,7 +68,6 @@ const ResumeViewer: React.FC<ResumeViewerProps> = ({ onSelect, candidateId, jobI
|
||||
const { selectedResume, setSelectedResume } = useSelectedResume(); // Assuming similar context
|
||||
const { setSnack } = useAppState();
|
||||
const [resumes, setResumes] = useState<Resume[]>([]);
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [sortField, setSortField] = useState<SortField>('updatedAt');
|
||||
const [sortOrder, setSortOrder] = useState<SortOrder>('desc');
|
||||
const [mobileDialogOpen, setMobileDialogOpen] = useState(false);
|
||||
@ -81,7 +77,6 @@ const ResumeViewer: React.FC<ResumeViewerProps> = ({ onSelect, candidateId, jobI
|
||||
|
||||
useEffect(() => {
|
||||
const getResumes = async () => {
|
||||
setLoading(true);
|
||||
try {
|
||||
let results;
|
||||
|
||||
@ -115,8 +110,6 @@ const ResumeViewer: React.FC<ResumeViewerProps> = ({ onSelect, candidateId, jobI
|
||||
} catch (err) {
|
||||
console.error('Failed to load resumes:', err);
|
||||
setSnack('Failed to load resumes: ' + err, 'error');
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -2,33 +2,17 @@ import React from 'react';
|
||||
import {
|
||||
Chat as ChatIcon,
|
||||
Dashboard as DashboardIcon,
|
||||
Description as DescriptionIcon,
|
||||
BarChart as BarChartIcon,
|
||||
Settings as SettingsIcon,
|
||||
Work as WorkIcon,
|
||||
Info as InfoIcon,
|
||||
Person as PersonIcon,
|
||||
Business as BusinessIcon,
|
||||
Search as SearchIcon,
|
||||
Bookmark as BookmarkIcon,
|
||||
History as HistoryIcon,
|
||||
QuestionAnswer as QuestionAnswerIcon,
|
||||
AttachMoney as AttachMoneyIcon,
|
||||
Quiz as QuizIcon,
|
||||
Analytics as AnalyticsIcon,
|
||||
BubbleChart,
|
||||
AutoFixHigh,
|
||||
} from '@mui/icons-material';
|
||||
import EditDocumentIcon from '@mui/icons-material/EditDocument';
|
||||
|
||||
import { BackstoryLogo } from 'components/ui/BackstoryLogo';
|
||||
import { HomePage } from 'pages/HomePage';
|
||||
import { CandidateChatPage } from 'pages/CandidateChatPage';
|
||||
import { DocsPage } from 'pages/DocsPage';
|
||||
import { CreateProfilePage } from 'pages/candidate/ProfileWizard';
|
||||
import { VectorVisualizerPage } from 'pages/VectorVisualizerPage';
|
||||
import { BetaPage } from 'pages/BetaPage';
|
||||
import { CandidateListingPage } from 'pages/FindCandidatePage';
|
||||
import { JobAnalysisPage } from 'pages/JobAnalysisPage';
|
||||
import { GenerateCandidate } from 'pages/GenerateCandidate';
|
||||
import { LoginPage } from 'pages/LoginPage';
|
||||
@ -37,7 +21,6 @@ import { Box, Typography } from '@mui/material';
|
||||
import { CandidateDashboard } from 'pages/candidate/Dashboard';
|
||||
import { NavigationConfig, NavigationItem } from 'types/navigation';
|
||||
import { HowItWorks } from 'pages/HowItWorks';
|
||||
import SchoolIcon from '@mui/icons-material/School';
|
||||
import { CandidateProfile } from 'pages/candidate/Profile';
|
||||
import { Settings } from 'pages/candidate/Settings';
|
||||
import { VectorVisualizer } from 'components/VectorVisualizer';
|
||||
@ -45,49 +28,11 @@ import { DocumentManager } from 'components/DocumentManager';
|
||||
import { useAuth } from 'hooks/AuthContext';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
import { JobViewer } from 'components/ui/JobViewer';
|
||||
import { CandidatePicker } from 'components/ui/CandidatePicker';
|
||||
import { ResumeViewer } from 'components/ui/ResumeViewer';
|
||||
|
||||
import { JobsTable } from 'components/ui/JobsTable';
|
||||
import * as Types from 'types/types';
|
||||
|
||||
// Beta page components for placeholder routes
|
||||
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 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 = () => {
|
||||
const { logout } = useAuth();
|
||||
const navigate = useNavigate();
|
||||
@ -96,16 +41,6 @@ const LogoutPage = () => {
|
||||
});
|
||||
return <Typography variant="h4">Logging out...</Typography>;
|
||||
};
|
||||
const AnalyticsPage = () => (
|
||||
<BetaPage>
|
||||
<Typography variant="h4">Analytics</Typography>
|
||||
</BetaPage>
|
||||
);
|
||||
const SettingsPage = () => (
|
||||
<BetaPage>
|
||||
<Typography variant="h4">Settings</Typography>
|
||||
</BetaPage>
|
||||
);
|
||||
|
||||
export const navigationConfig: NavigationConfig = {
|
||||
items: [
|
||||
|
@ -2,12 +2,7 @@
|
||||
|
||||
import React, { createContext, useContext, useState, useCallback, useEffect, useRef } from 'react';
|
||||
import * as Types from '../types/types';
|
||||
import {
|
||||
ApiClient,
|
||||
CreateCandidateRequest,
|
||||
CreateEmployerRequest,
|
||||
GuestConversionRequest,
|
||||
} from 'services/api-client';
|
||||
import { ApiClient, CreateEmployerRequest, GuestConversionRequest } from 'services/api-client';
|
||||
import { formatApiRequest, toCamelCase } from 'types/conversion';
|
||||
|
||||
// ============================
|
||||
|
@ -365,7 +365,7 @@ export function useAppStateLogic(): AppStateContextType {
|
||||
console.log('Cleared all route state');
|
||||
}, []);
|
||||
|
||||
const emptySetSnack: SetSnackType = (message: string, severity?: SeverityType) => {
|
||||
const emptySetSnack: SetSnackType = (_message: string, _severity?: SeverityType) => {
|
||||
return;
|
||||
};
|
||||
|
||||
|
@ -68,10 +68,10 @@ const useResizeObserverAndMutationObserver = (
|
||||
requestAnimationFrame(() => callbackRef.current());
|
||||
}, 500);
|
||||
|
||||
const resizeObserver = new ResizeObserver((e: any) => {
|
||||
const resizeObserver = new ResizeObserver((_e: any) => {
|
||||
debouncedCallback('resize');
|
||||
});
|
||||
const mutationObserver = new MutationObserver((e: any) => {
|
||||
const mutationObserver = new MutationObserver((_e: any) => {
|
||||
debouncedCallback('mutation');
|
||||
});
|
||||
|
||||
|
@ -202,7 +202,7 @@ const BetaPage: React.FC<BetaPageProps> = ({
|
||||
}}
|
||||
/>
|
||||
<Typography>
|
||||
We're working hard to bring you this exciting new feature!
|
||||
We're working hard to bring you this exciting new feature!
|
||||
</Typography>
|
||||
<Typography color="textSecondary" sx={{ mt: 1 }}>
|
||||
Check back soon for updates.
|
||||
|
@ -1,5 +1,5 @@
|
||||
import React, { forwardRef, useState, useEffect, useRef } from 'react';
|
||||
import { Box, Paper, Button, Divider, useTheme, useMediaQuery, Tooltip } from '@mui/material';
|
||||
import { Box, Paper, Button, Tooltip } from '@mui/material';
|
||||
import { Send as SendIcon } from '@mui/icons-material';
|
||||
import { useAuth } from 'hooks/AuthContext';
|
||||
import {
|
||||
@ -15,7 +15,6 @@ import { BackstoryPageProps } from 'components/BackstoryTab';
|
||||
import { Message } from 'components/Message';
|
||||
import { DeleteConfirmation } from 'components/DeleteConfirmation';
|
||||
import { CandidateInfo } from 'components/ui/CandidateInfo';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
import { useAppState, useSelectedCandidate } from 'hooks/GlobalContext';
|
||||
import PropagateLoader from 'react-spinners/PropagateLoader';
|
||||
import { BackstoryTextField, BackstoryTextFieldRef } from 'components/BackstoryTextField';
|
||||
@ -34,11 +33,9 @@ const defaultMessage: ChatMessage = {
|
||||
};
|
||||
|
||||
const CandidateChatPage = forwardRef<ConversationHandle, BackstoryPageProps>(
|
||||
(props: BackstoryPageProps, ref) => {
|
||||
(_props: BackstoryPageProps, ref) => {
|
||||
const { apiClient } = useAuth();
|
||||
const navigate = useNavigate();
|
||||
const { selectedCandidate, setSelectedCandidate } = useSelectedCandidate();
|
||||
const theme = useTheme();
|
||||
const [processingMessage, setProcessingMessage] = useState<
|
||||
ChatMessageStatus | ChatMessageError | null
|
||||
>(null);
|
||||
@ -124,11 +121,9 @@ const CandidateChatPage = forwardRef<ConversationHandle, BackstoryPageProps>(
|
||||
},
|
||||
onError: (error: string | ChatMessageError) => {
|
||||
console.log('onError:', error);
|
||||
let message: string;
|
||||
// Type-guard to determine if this is a ChatMessageBase or a string
|
||||
if (typeof error === 'object' && error !== null && 'content' in error) {
|
||||
setProcessingMessage(error);
|
||||
message = error.content as string;
|
||||
} else {
|
||||
setProcessingMessage({
|
||||
...defaultMessage,
|
||||
@ -300,7 +295,7 @@ const CandidateChatPage = forwardRef<ConversationHandle, BackstoryPageProps>(
|
||||
</Scrollable>
|
||||
)}
|
||||
{selectedCandidate.questions?.length !== 0 &&
|
||||
selectedCandidate.questions?.map(q => <BackstoryQuery question={q} />)}
|
||||
selectedCandidate.questions?.map((q, i) => <BackstoryQuery key={i} question={q} />)}
|
||||
{/* Fixed Message Input */}
|
||||
<Box sx={{ display: 'flex', flexShrink: 1, gap: 1 }}>
|
||||
<DeleteConfirmation
|
||||
@ -346,5 +341,5 @@ const CandidateChatPage = forwardRef<ConversationHandle, BackstoryPageProps>(
|
||||
);
|
||||
}
|
||||
);
|
||||
|
||||
CandidateChatPage.displayName = 'CandidateChatPage';
|
||||
export { CandidateChatPage };
|
||||
|
@ -39,7 +39,7 @@ const defaultMessage: ChatMessage = {
|
||||
metadata: null as any,
|
||||
};
|
||||
|
||||
const GenerateCandidate = (props: BackstoryElementProps) => {
|
||||
const GenerateCandidate = (_props: BackstoryElementProps) => {
|
||||
const { apiClient, user } = useAuth();
|
||||
const { setSnack } = useAppState();
|
||||
const [processingMessage, setProcessingMessage] = useState<ChatMessage | null>(null);
|
||||
|
@ -7,15 +7,11 @@ import {
|
||||
Paper,
|
||||
Typography,
|
||||
Grid,
|
||||
Card,
|
||||
CardContent,
|
||||
Chip,
|
||||
Step,
|
||||
StepLabel,
|
||||
Stepper,
|
||||
Stack,
|
||||
ButtonProps,
|
||||
useMediaQuery,
|
||||
useTheme,
|
||||
} from '@mui/material';
|
||||
import { styled } from '@mui/material/styles';
|
||||
@ -80,16 +76,6 @@ const ImageContainer = styled(Box)(({ theme }) => ({
|
||||
},
|
||||
}));
|
||||
|
||||
const StepCard = styled(Card)(({ theme }) => ({
|
||||
height: '100%',
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
border: `1px solid ${theme.palette.action.active}`,
|
||||
'&:hover': {
|
||||
boxShadow: theme.shadows[4],
|
||||
},
|
||||
}));
|
||||
|
||||
const steps = [
|
||||
'Select Job Analysis',
|
||||
'Choose a Job',
|
||||
@ -241,8 +227,6 @@ const HeroButton = (props: HeroButtonProps) => {
|
||||
|
||||
const HowItWorks: React.FC = () => {
|
||||
const navigate = useNavigate();
|
||||
const theme = useTheme();
|
||||
const isMobile = useMediaQuery(theme.breakpoints.down('md'));
|
||||
|
||||
const handleGetStarted = () => {
|
||||
navigate('/job-analysis');
|
||||
@ -270,7 +254,7 @@ const HowItWorks: React.FC = () => {
|
||||
component="h1"
|
||||
sx={{
|
||||
fontWeight: 700,
|
||||
fontSize: { xs: '2rem', md: '3rem' },
|
||||
fontSize: { xs: '1.5rem', md: '2rem' },
|
||||
mb: 2,
|
||||
color: 'white',
|
||||
}}
|
||||
|
@ -5,7 +5,6 @@ import {
|
||||
Step,
|
||||
StepLabel,
|
||||
Button,
|
||||
Typography,
|
||||
Paper,
|
||||
useTheme,
|
||||
Snackbar,
|
||||
@ -14,7 +13,6 @@ import {
|
||||
Tab,
|
||||
Avatar,
|
||||
useMediaQuery,
|
||||
Divider,
|
||||
} from '@mui/material';
|
||||
import { Add, WorkOutline } from '@mui/icons-material';
|
||||
import PersonIcon from '@mui/icons-material/Person';
|
||||
@ -22,19 +20,15 @@ import WorkIcon from '@mui/icons-material/Work';
|
||||
import AssessmentIcon from '@mui/icons-material/Assessment';
|
||||
import { JobMatchAnalysis } from 'components/JobMatchAnalysis';
|
||||
import { Candidate, Job, SkillAssessment } from 'types/types';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
import { BackstoryPageProps } from 'components/BackstoryTab';
|
||||
import { useAuth } from 'hooks/AuthContext';
|
||||
import { useAppState, useSelectedCandidate, useSelectedJob } from 'hooks/GlobalContext';
|
||||
import { useSelectedCandidate, useSelectedJob } from 'hooks/GlobalContext';
|
||||
import { CandidateInfo } from 'components/ui/CandidateInfo';
|
||||
import { ComingSoon } from 'components/ui/ComingSoon';
|
||||
import { LoginRequired } from 'components/ui/LoginRequired';
|
||||
import { Scrollable } from 'components/Scrollable';
|
||||
import { CandidatePicker } from 'components/ui/CandidatePicker';
|
||||
import { JobPicker } from 'components/ui/JobPicker';
|
||||
import { JobCreator } from 'components/JobCreator';
|
||||
import { LoginRestricted } from 'components/ui/LoginRestricted';
|
||||
import JsonView from '@uiw/react-json-view';
|
||||
import { ResumeGenerator } from 'components/ResumeGenerator';
|
||||
import { JobInfo } from 'components/ui/JobInfo';
|
||||
|
||||
@ -110,7 +104,7 @@ const capitalize = (str: string) => {
|
||||
};
|
||||
|
||||
// Main component
|
||||
const JobAnalysisPage: React.FC<BackstoryPageProps> = (props: BackstoryPageProps) => {
|
||||
const JobAnalysisPage: React.FC<BackstoryPageProps> = (_props: BackstoryPageProps) => {
|
||||
const theme = useTheme();
|
||||
const { user, guest } = useAuth();
|
||||
const { selectedCandidate, setSelectedCandidate } = useSelectedCandidate();
|
||||
@ -329,7 +323,7 @@ const JobAnalysisPage: React.FC<BackstoryPageProps> = (props: BackstoryPageProps
|
||||
<Paper elevation={4} sx={{ m: 0, borderRadius: 0, mb: 1, p: 0, gap: 1 }}>
|
||||
<Stepper activeStep={activeStep.index} alternativeLabel sx={{ mt: 2, mb: 2 }}>
|
||||
{steps.map((step, index) => (
|
||||
<Step>
|
||||
<Step key={step.index}>
|
||||
<StepLabel
|
||||
sx={{ cursor: 'pointer' }}
|
||||
onClick={() => {
|
||||
|
@ -1,7 +1,6 @@
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import {
|
||||
Box,
|
||||
Container,
|
||||
Paper,
|
||||
Typography,
|
||||
Alert,
|
||||
@ -25,17 +24,14 @@ import { LoginForm } from 'components/EmailVerificationComponents';
|
||||
import { CandidateRegistrationForm } from 'pages/candidate/RegistrationForms';
|
||||
import { useNavigate, useParams } from 'react-router-dom';
|
||||
import { useAppState } from 'hooks/GlobalContext';
|
||||
import * as Types from 'types/types';
|
||||
|
||||
const LoginPage: React.FC<BackstoryPageProps> = (props: BackstoryPageProps) => {
|
||||
const LoginPage: React.FC<BackstoryPageProps> = (_props: BackstoryPageProps) => {
|
||||
const navigate = useNavigate();
|
||||
const { setSnack } = useAppState();
|
||||
const [tabValue, setTabValue] = useState<string>('login');
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [success, setSuccess] = useState<string | null>(null);
|
||||
const { guest, user, login, isLoading, error } = useAuth();
|
||||
const name =
|
||||
user?.userType === 'candidate' ? (user as Types.Candidate).username : user?.email || '';
|
||||
const { guest, user, error } = useAuth();
|
||||
const [errorMessage, setErrorMessage] = useState<string | null>(null);
|
||||
const theme = useTheme();
|
||||
const isMobile = useMediaQuery(theme.breakpoints.down('sm'));
|
||||
|
@ -17,7 +17,7 @@ import { useAppState } from 'hooks/GlobalContext';
|
||||
|
||||
type CandidateDashboardProps = BackstoryElementProps;
|
||||
|
||||
const CandidateDashboard = (props: CandidateDashboardProps) => {
|
||||
const CandidateDashboard = (_props: CandidateDashboardProps) => {
|
||||
const { setSnack } = useAppState();
|
||||
const navigate = useNavigate();
|
||||
const { user } = useAuth();
|
||||
|
@ -1,5 +1,4 @@
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import CheckIcon from '@mui/icons-material/Check';
|
||||
import {
|
||||
Box,
|
||||
Button,
|
||||
@ -29,8 +28,6 @@ import {
|
||||
InputLabel,
|
||||
Switch,
|
||||
FormControlLabel,
|
||||
ToggleButton,
|
||||
Checkbox,
|
||||
} from '@mui/material';
|
||||
import { styled } from '@mui/material/styles';
|
||||
import {
|
||||
@ -100,12 +97,11 @@ function TabPanel(props: TabPanelProps) {
|
||||
);
|
||||
}
|
||||
|
||||
const CandidateProfile: React.FC<BackstoryPageProps> = (props: BackstoryPageProps) => {
|
||||
const CandidateProfile: React.FC<BackstoryPageProps> = (_props: BackstoryPageProps) => {
|
||||
const { setSnack } = useAppState();
|
||||
const theme = useTheme();
|
||||
const isMobile = useMediaQuery(theme.breakpoints.down('sm'));
|
||||
const { user, updateUserData, apiClient } = useAuth();
|
||||
const [isPublic, setIsPublic] = React.useState(true);
|
||||
|
||||
// Check if user is a candidate
|
||||
const candidate = user?.userType === 'candidate' ? (user as Types.Candidate) : null;
|
||||
@ -427,24 +423,11 @@ const CandidateProfile: React.FC<BackstoryPageProps> = (props: BackstoryPageProp
|
||||
</Box>
|
||||
|
||||
<Box className="entry">
|
||||
{false && editMode.basic ? (
|
||||
<TextField
|
||||
fullWidth
|
||||
label="Email"
|
||||
type="email"
|
||||
value={formData.email || ''}
|
||||
onChange={e => handleInputChange('email', e.target.value)}
|
||||
variant="outlined"
|
||||
/>
|
||||
) : (
|
||||
<>
|
||||
<Box className="title">
|
||||
<Email sx={{ mr: 1, verticalAlign: 'middle' }} />
|
||||
Email
|
||||
</Box>
|
||||
<Box className="value">{candidate.email}</Box>
|
||||
</>
|
||||
)}
|
||||
<Box className="title">
|
||||
<Email sx={{ mr: 1, verticalAlign: 'middle' }} />
|
||||
Email
|
||||
</Box>
|
||||
<Box className="value">{candidate.email}</Box>
|
||||
</Box>
|
||||
|
||||
<Box className="entry">
|
||||
@ -487,31 +470,13 @@ const CandidateProfile: React.FC<BackstoryPageProps> = (props: BackstoryPageProp
|
||||
</Box>
|
||||
|
||||
<Box className="entry">
|
||||
{false && editMode.basic ? (
|
||||
<TextField
|
||||
fullWidth
|
||||
label="Location"
|
||||
value={formData.location?.city || ''}
|
||||
onChange={e =>
|
||||
handleInputChange('location', {
|
||||
...formData.location,
|
||||
city: e.target.value,
|
||||
})
|
||||
}
|
||||
variant="outlined"
|
||||
placeholder="City, State, Country"
|
||||
/>
|
||||
) : (
|
||||
<>
|
||||
<Box className="title">
|
||||
<LocationOn sx={{ mr: 1, verticalAlign: 'middle' }} />
|
||||
Location
|
||||
</Box>
|
||||
<Box className="value">
|
||||
{candidate.location?.city || 'Not specified'} {candidate.location?.country || ''}
|
||||
</Box>
|
||||
</>
|
||||
)}
|
||||
<Box className="title">
|
||||
<LocationOn sx={{ mr: 1, verticalAlign: 'middle' }} />
|
||||
Location
|
||||
</Box>
|
||||
<Box className="value">
|
||||
{candidate.location?.city || 'Not specified'} {candidate.location?.country || ''}
|
||||
</Box>
|
||||
</Box>
|
||||
|
||||
<Box className="entry">
|
||||
@ -655,7 +620,7 @@ const CandidateProfile: React.FC<BackstoryPageProps> = (props: BackstoryPageProp
|
||||
|
||||
{(!formData.skills || formData.skills.length === 0) && (
|
||||
<Typography variant="body2" color="text.secondary" sx={{ textAlign: 'center', py: 4 }}>
|
||||
No skills added yet. Click "Add Skill" to get started.
|
||||
No skills added yet. Click "Add Skill" to get started.
|
||||
</Typography>
|
||||
)}
|
||||
</Box>
|
||||
@ -781,7 +746,7 @@ const CandidateProfile: React.FC<BackstoryPageProps> = (props: BackstoryPageProp
|
||||
fontSize: { xs: '0.8rem', sm: '0.875rem' },
|
||||
}}
|
||||
>
|
||||
No work experience added yet. Click "Add Experience" to get started.
|
||||
No work experience added yet. Click "Add Experience" to get started.
|
||||
</Typography>
|
||||
)}
|
||||
</Box>
|
||||
@ -820,7 +785,7 @@ const CandidateProfile: React.FC<BackstoryPageProps> = (props: BackstoryPageProp
|
||||
fontSize: { xs: '0.8rem', sm: '0.875rem' },
|
||||
}}
|
||||
>
|
||||
No work experience added yet. Click "Add Experience" to get started.
|
||||
No work experience added yet. Click "Add Experience" to get started.
|
||||
</Typography>
|
||||
)}
|
||||
</Box>
|
||||
|
@ -811,7 +811,7 @@ export function RegistrationTypeSelector() {
|
||||
Join Backstory
|
||||
</Typography>
|
||||
<Typography variant="h6" color="text.secondary">
|
||||
Choose how you'd like to get started
|
||||
Choose how you'd like to get started
|
||||
</Typography>
|
||||
</Box>
|
||||
|
||||
@ -836,7 +836,7 @@ export function RegistrationTypeSelector() {
|
||||
👤
|
||||
</Typography>
|
||||
<Typography variant="h5" component="h3" sx={{ mb: 1.5 }}>
|
||||
I'm looking for work
|
||||
I'm looking for work
|
||||
</Typography>
|
||||
<Typography variant="body2" color="text.secondary" sx={{ mb: 3 }}>
|
||||
Create a candidate profile to find your next opportunity
|
||||
@ -869,7 +869,7 @@ export function RegistrationTypeSelector() {
|
||||
🏢
|
||||
</Typography>
|
||||
<Typography variant="h5" component="h3" sx={{ mb: 1.5 }}>
|
||||
I'm hiring
|
||||
I'm hiring
|
||||
</Typography>
|
||||
<Typography variant="body2" color="text.secondary" sx={{ mb: 3 }}>
|
||||
Create a company account to find and hire talent
|
||||
|
@ -83,7 +83,7 @@ const SystemInfoComponent: React.FC<{
|
||||
return <div className="SystemInfo">{systemElements}</div>;
|
||||
};
|
||||
|
||||
const Settings = (props: BackstoryPageProps) => {
|
||||
const Settings = (_props: BackstoryPageProps) => {
|
||||
const { apiClient } = useAuth();
|
||||
const { setSnack } = useAppState();
|
||||
// const [editSystemPrompt, setEditSystemPrompt] = useState<string>("");
|
||||
|
@ -2,7 +2,6 @@ import React, { useEffect } from 'react';
|
||||
import { useParams, useNavigate } from 'react-router-dom';
|
||||
import { Box } from '@mui/material';
|
||||
|
||||
import { SetSnackType } from '../components/Snack';
|
||||
import { LoadingComponent } from '../components/LoadingComponent';
|
||||
import { User, Guest, Candidate } from 'types/types';
|
||||
import { useAuth } from 'hooks/AuthContext';
|
||||
@ -13,7 +12,7 @@ interface CandidateRouteProps {
|
||||
user?: User | null;
|
||||
}
|
||||
|
||||
const CandidateRoute: React.FC<CandidateRouteProps> = (props: CandidateRouteProps) => {
|
||||
const CandidateRoute: React.FC<CandidateRouteProps> = (_props: CandidateRouteProps) => {
|
||||
const { apiClient } = useAuth();
|
||||
const { selectedCandidate, setSelectedCandidate } = useSelectedCandidate();
|
||||
const { setSnack } = useAppState();
|
||||
|
@ -1442,7 +1442,8 @@ class ApiClient {
|
||||
|
||||
const messageId = '';
|
||||
let finalMessage: T | null = null;
|
||||
const promise = new Promise<T>(async (resolve, reject) => {
|
||||
|
||||
const processStream = async (): Promise<T> => {
|
||||
try {
|
||||
const response = await fetch(`${this.baseUrl}${api}`, {
|
||||
method,
|
||||
@ -1467,12 +1468,15 @@ class ApiClient {
|
||||
const decoder = new TextDecoder();
|
||||
let buffer = '';
|
||||
let streamingMessage: Types.ChatMessageStreaming | null = null;
|
||||
|
||||
try {
|
||||
while (true) {
|
||||
let exit = false;
|
||||
while (!exit) {
|
||||
const { done, value } = await reader.read();
|
||||
|
||||
if (done) {
|
||||
// Stream ended naturally
|
||||
exit = true;
|
||||
break;
|
||||
}
|
||||
|
||||
@ -1493,40 +1497,48 @@ class ApiClient {
|
||||
// Handle different status types
|
||||
switch (incoming.status) {
|
||||
case 'streaming':
|
||||
const streaming = Types.convertChatMessageStreamingFromApi(incoming);
|
||||
if (streamingMessage === null) {
|
||||
streamingMessage = { ...streaming };
|
||||
} else {
|
||||
// Can't do a simple += as typescript thinks .content might not be there
|
||||
streamingMessage.content =
|
||||
(streamingMessage?.content || '') + streaming.content;
|
||||
// Update timestamp to latest
|
||||
streamingMessage.timestamp = streaming.timestamp;
|
||||
{
|
||||
const streaming = Types.convertChatMessageStreamingFromApi(incoming);
|
||||
if (streamingMessage === null) {
|
||||
streamingMessage = { ...streaming };
|
||||
} else {
|
||||
// Can't do a simple += as typescript thinks .content might not be there
|
||||
streamingMessage.content =
|
||||
(streamingMessage?.content || '') + streaming.content;
|
||||
// Update timestamp to latest
|
||||
streamingMessage.timestamp = streaming.timestamp;
|
||||
}
|
||||
options.onStreaming?.(streamingMessage);
|
||||
}
|
||||
options.onStreaming?.(streamingMessage);
|
||||
break;
|
||||
|
||||
case 'status':
|
||||
const status = Types.convertChatMessageStatusFromApi(incoming);
|
||||
options.onStatus?.(status);
|
||||
{
|
||||
const status = Types.convertChatMessageStatusFromApi(incoming);
|
||||
options.onStatus?.(status);
|
||||
}
|
||||
break;
|
||||
|
||||
case 'error':
|
||||
const error = Types.convertChatMessageErrorFromApi(incoming);
|
||||
options.onError?.(error);
|
||||
{
|
||||
const error = Types.convertChatMessageErrorFromApi(incoming);
|
||||
options.onError?.(error);
|
||||
}
|
||||
break;
|
||||
|
||||
case 'done':
|
||||
const message = (
|
||||
modelType
|
||||
? convertFromApi<T>(parseApiResponse<T>(incoming), modelType)
|
||||
: incoming
|
||||
) as T;
|
||||
finalMessage = message;
|
||||
try {
|
||||
options.onMessage?.(message);
|
||||
} catch (error) {
|
||||
console.error('onMessage handler failed: ', error);
|
||||
{
|
||||
const message = (
|
||||
modelType
|
||||
? convertFromApi<T>(parseApiResponse<T>(incoming), modelType)
|
||||
: incoming
|
||||
) as T;
|
||||
finalMessage = message;
|
||||
try {
|
||||
options.onMessage?.(message);
|
||||
} catch (error) {
|
||||
console.error('onMessage handler failed: ', error);
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
@ -1536,7 +1548,6 @@ class ApiClient {
|
||||
if (error instanceof Error) {
|
||||
options.onWarn?.(error.message);
|
||||
}
|
||||
// Continue processing other lines
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1545,11 +1556,11 @@ class ApiClient {
|
||||
}
|
||||
|
||||
options.onComplete?.();
|
||||
resolve(finalMessage as T);
|
||||
return finalMessage as T;
|
||||
} catch (error) {
|
||||
if (signal.aborted) {
|
||||
options.onComplete?.();
|
||||
reject(new Error('Request was aborted'));
|
||||
throw new Error('Request was aborted');
|
||||
} else {
|
||||
console.error(error);
|
||||
options.onError?.({
|
||||
@ -1559,15 +1570,15 @@ class ApiClient {
|
||||
content: (error as Error).message,
|
||||
});
|
||||
options.onComplete?.();
|
||||
reject(error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
return {
|
||||
messageId,
|
||||
cancel: () => abortController.abort(),
|
||||
promise,
|
||||
promise: processStream(),
|
||||
};
|
||||
}
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user