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