Fixing eslint issues
This commit is contained in:
parent
66b68270cd
commit
17381dded1
@ -28,8 +28,8 @@
|
|||||||
"react/prop-types": "off",
|
"react/prop-types": "off",
|
||||||
"@typescript-eslint/explicit-function-return-type": "warn",
|
"@typescript-eslint/explicit-function-return-type": "warn",
|
||||||
"no-unused-vars": "off",
|
"no-unused-vars": "off",
|
||||||
"@typescript-eslint/no-unused-vars": ["error", { "varsIgnorePattern": "^_", "argsIgnorePattern": "^_" }],
|
"prettier/prettier": "error",
|
||||||
"prettier/prettier": "error"
|
"@typescript-eslint/no-unused-vars": ["error", { "varsIgnorePattern": "^_", "argsIgnorePattern": "^_" }]
|
||||||
},
|
},
|
||||||
"settings": {
|
"settings": {
|
||||||
"react": {
|
"react": {
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
import React, { JSX } from 'react';
|
||||||
import Box from '@mui/material/Box';
|
import Box from '@mui/material/Box';
|
||||||
import Button from '@mui/material/Button';
|
import Button from '@mui/material/Button';
|
||||||
|
|
||||||
@ -10,7 +11,7 @@ interface BackstoryQueryInterface {
|
|||||||
submitQuery?: ChatSubmitQueryInterface;
|
submitQuery?: ChatSubmitQueryInterface;
|
||||||
}
|
}
|
||||||
|
|
||||||
const BackstoryQuery = (props: BackstoryQueryInterface) => {
|
const BackstoryQuery = (props: BackstoryQueryInterface): JSX.Element => {
|
||||||
const { question, submitQuery } = props;
|
const { question, submitQuery } = props;
|
||||||
|
|
||||||
if (submitQuery === undefined) {
|
if (submitQuery === undefined) {
|
||||||
@ -25,7 +26,7 @@ const BackstoryQuery = (props: BackstoryQueryInterface) => {
|
|||||||
m: 1,
|
m: 1,
|
||||||
}}
|
}}
|
||||||
size="small"
|
size="small"
|
||||||
onClick={(e: any) => {
|
onClick={(): void => {
|
||||||
submitQuery(question);
|
submitQuery(question);
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
|
@ -1,8 +1,6 @@
|
|||||||
import React, { ReactElement, JSXElementConstructor } from 'react';
|
import React, { ReactElement, JSX } from 'react';
|
||||||
import Box from '@mui/material/Box';
|
import Box from '@mui/material/Box';
|
||||||
import { SxProps, Theme } from '@mui/material';
|
import { SxProps, Theme } from '@mui/material';
|
||||||
import { ChatSubmitQueryInterface } from './BackstoryQuery';
|
|
||||||
import { SetSnackType } from './Snack';
|
|
||||||
|
|
||||||
interface BackstoryElementProps {
|
interface BackstoryElementProps {
|
||||||
// setSnack: SetSnackType,
|
// setSnack: SetSnackType,
|
||||||
@ -24,12 +22,12 @@ interface BackstoryTabProps {
|
|||||||
tabProps?: {
|
tabProps?: {
|
||||||
label?: string;
|
label?: string;
|
||||||
sx?: SxProps;
|
sx?: SxProps;
|
||||||
icon?: string | ReactElement<unknown, string | JSXElementConstructor<any>> | undefined;
|
icon?: string | ReactElement<unknown, string> | undefined;
|
||||||
iconPosition?: 'bottom' | 'top' | 'start' | 'end' | undefined;
|
iconPosition?: 'bottom' | 'top' | 'start' | 'end' | undefined;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
function BackstoryPage(props: BackstoryTabProps) {
|
function BackstoryPage(props: BackstoryTabProps): JSX.Element {
|
||||||
const { className, active, children } = props;
|
const { className, active, children } = props;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
@ -72,21 +72,21 @@ const BackstoryTextField = React.forwardRef<BackstoryTextFieldRef, BackstoryText
|
|||||||
});
|
});
|
||||||
|
|
||||||
// Cleanup RAF to prevent memory leaks
|
// Cleanup RAF to prevent memory leaks
|
||||||
return () => cancelAnimationFrame(raf);
|
return (): void => cancelAnimationFrame(raf);
|
||||||
}, [editValue, placeholder]);
|
}, [editValue, placeholder]);
|
||||||
|
|
||||||
// Expose getValue method via ref
|
// Expose getValue method via ref
|
||||||
useImperativeHandle(ref, () => ({
|
useImperativeHandle(ref, () => ({
|
||||||
getValue: () => editValue,
|
getValue: (): string => editValue,
|
||||||
setValue: (value: string) => setEditValue(value),
|
setValue: (value: string): void => setEditValue(value),
|
||||||
getAndResetValue: () => {
|
getAndResetValue: (): string => {
|
||||||
const _ev = editValue;
|
const _ev = editValue;
|
||||||
setEditValue('');
|
setEditValue('');
|
||||||
return _ev;
|
return _ev;
|
||||||
},
|
},
|
||||||
}));
|
}));
|
||||||
|
|
||||||
const handleKeyDown = (event: KeyboardEvent<HTMLTextAreaElement>) => {
|
const handleKeyDown = (event: KeyboardEvent<HTMLTextAreaElement>): void => {
|
||||||
if (!onEnter) {
|
if (!onEnter) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -122,7 +122,7 @@ const BackstoryTextField = React.forwardRef<BackstoryTextFieldRef, BackstoryText
|
|||||||
value={editValue}
|
value={editValue}
|
||||||
disabled={disabled}
|
disabled={disabled}
|
||||||
placeholder={placeholder}
|
placeholder={placeholder}
|
||||||
onChange={e => {
|
onChange={(e): void => {
|
||||||
setEditValue(e.target.value);
|
setEditValue(e.target.value);
|
||||||
onChange && onChange(e.target.value);
|
onChange && onChange(e.target.value);
|
||||||
}}
|
}}
|
||||||
@ -152,7 +152,7 @@ const BackstoryTextField = React.forwardRef<BackstoryTextFieldRef, BackstoryText
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
BackstoryTextField.displayName = 'BackstoryTextField';
|
||||||
export type { BackstoryTextFieldRef };
|
export type { BackstoryTextFieldRef };
|
||||||
|
|
||||||
export { BackstoryTextField };
|
export { BackstoryTextField };
|
||||||
|
@ -30,6 +30,7 @@ import {
|
|||||||
ChatMessageError,
|
ChatMessageError,
|
||||||
ChatMessageStreaming,
|
ChatMessageStreaming,
|
||||||
ChatMessageStatus,
|
ChatMessageStatus,
|
||||||
|
ChatMessageMetaData,
|
||||||
} from 'types/types';
|
} from 'types/types';
|
||||||
import { PaginatedResponse } from 'types/conversion';
|
import { PaginatedResponse } from 'types/conversion';
|
||||||
|
|
||||||
@ -43,7 +44,7 @@ const defaultMessage: ChatMessage = {
|
|||||||
timestamp: new Date(),
|
timestamp: new Date(),
|
||||||
content: '',
|
content: '',
|
||||||
role: 'assistant',
|
role: 'assistant',
|
||||||
metadata: null as any,
|
metadata: null as unknown as ChatMessageMetaData,
|
||||||
};
|
};
|
||||||
|
|
||||||
const loadingMessage: ChatMessage = {
|
const loadingMessage: ChatMessage = {
|
||||||
@ -88,14 +89,11 @@ const Conversation = forwardRef<ConversationHandle, ConversationProps>(
|
|||||||
onResponse,
|
onResponse,
|
||||||
placeholder,
|
placeholder,
|
||||||
preamble,
|
preamble,
|
||||||
resetAction,
|
|
||||||
resetLabel,
|
resetLabel,
|
||||||
sx,
|
sx,
|
||||||
type,
|
|
||||||
} = props;
|
} = props;
|
||||||
const { apiClient } = useAuth();
|
const { apiClient } = useAuth();
|
||||||
const [processing, setProcessing] = useState<boolean>(false);
|
const [processing, setProcessing] = useState<boolean>(false);
|
||||||
const [countdown, setCountdown] = useState<number>(0);
|
|
||||||
const [conversation, setConversation] = useState<ChatMessage[]>([]);
|
const [conversation, setConversation] = useState<ChatMessage[]>([]);
|
||||||
const conversationRef = useRef<ChatMessage[]>([]);
|
const conversationRef = useRef<ChatMessage[]>([]);
|
||||||
const [filteredConversation, setFilteredConversation] = useState<ChatMessage[]>([]);
|
const [filteredConversation, setFilteredConversation] = useState<ChatMessage[]>([]);
|
||||||
@ -144,7 +142,7 @@ const Conversation = forwardRef<ConversationHandle, ConversationProps>(
|
|||||||
if (chatSession) {
|
if (chatSession) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const createChatSession = async () => {
|
const createChatSession = async (): Promise<void> => {
|
||||||
try {
|
try {
|
||||||
const chatContext: ChatContext = { type: 'general' };
|
const chatContext: ChatContext = { type: 'general' };
|
||||||
const response: ChatSession = await apiClient.createChatSession(chatContext);
|
const response: ChatSession = await apiClient.createChatSession(chatContext);
|
||||||
@ -156,7 +154,7 @@ const Conversation = forwardRef<ConversationHandle, ConversationProps>(
|
|||||||
};
|
};
|
||||||
|
|
||||||
createChatSession();
|
createChatSession();
|
||||||
}, [chatSession, setChatSession]);
|
}, [chatSession, setChatSession, apiClient, setSnack]);
|
||||||
|
|
||||||
const getChatMessages = useCallback(async () => {
|
const getChatMessages = useCallback(async () => {
|
||||||
if (!chatSession || !chatSession.id) {
|
if (!chatSession || !chatSession.id) {
|
||||||
@ -193,7 +191,7 @@ const Conversation = forwardRef<ConversationHandle, ConversationProps>(
|
|||||||
}, 3000);
|
}, 3000);
|
||||||
setSnack('Unable to obtain chat history.', 'error');
|
setSnack('Unable to obtain chat history.', 'error');
|
||||||
}
|
}
|
||||||
}, [chatSession]);
|
}, [chatSession, apiClient, setSnack]);
|
||||||
|
|
||||||
// Set the initial chat history to "loading" or the welcome message if loaded.
|
// Set the initial chat history to "loading" or the welcome message if loaded.
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@ -208,9 +206,9 @@ const Conversation = forwardRef<ConversationHandle, ConversationProps>(
|
|||||||
setNoInteractions(true);
|
setNoInteractions(true);
|
||||||
|
|
||||||
getChatMessages();
|
getChatMessages();
|
||||||
}, [chatSession]);
|
}, [chatSession, getChatMessages]);
|
||||||
|
|
||||||
const handleEnter = (value: string) => {
|
const handleEnter = (value: string): void => {
|
||||||
const query: ChatQuery = {
|
const query: ChatQuery = {
|
||||||
prompt: value,
|
prompt: value,
|
||||||
};
|
};
|
||||||
@ -218,10 +216,10 @@ const Conversation = forwardRef<ConversationHandle, ConversationProps>(
|
|||||||
};
|
};
|
||||||
|
|
||||||
useImperativeHandle(ref, () => ({
|
useImperativeHandle(ref, () => ({
|
||||||
submitQuery: (query: ChatQuery) => {
|
submitQuery: (query: ChatQuery): void => {
|
||||||
processQuery(query);
|
processQuery(query);
|
||||||
},
|
},
|
||||||
fetchHistory: () => {
|
fetchHistory: (): void => {
|
||||||
getChatMessages();
|
getChatMessages();
|
||||||
},
|
},
|
||||||
}));
|
}));
|
||||||
@ -256,7 +254,7 @@ const Conversation = forwardRef<ConversationHandle, ConversationProps>(
|
|||||||
// }
|
// }
|
||||||
// };
|
// };
|
||||||
|
|
||||||
const cancelQuery = () => {
|
const cancelQuery = (): void => {
|
||||||
console.log('Stop query');
|
console.log('Stop query');
|
||||||
if (controllerRef.current) {
|
if (controllerRef.current) {
|
||||||
controllerRef.current.cancel();
|
controllerRef.current.cancel();
|
||||||
@ -264,12 +262,10 @@ const Conversation = forwardRef<ConversationHandle, ConversationProps>(
|
|||||||
controllerRef.current = null;
|
controllerRef.current = null;
|
||||||
};
|
};
|
||||||
|
|
||||||
const processQuery = (query: ChatQuery) => {
|
const processQuery = (query: ChatQuery): void => {
|
||||||
if (controllerRef.current || !chatSession || !chatSession.id) {
|
if (controllerRef.current || !chatSession || !chatSession.id) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const sessionId: string = chatSession.id;
|
|
||||||
|
|
||||||
setNoInteractions(false);
|
setNoInteractions(false);
|
||||||
setConversation([
|
setConversation([
|
||||||
...conversationRef.current,
|
...conversationRef.current,
|
||||||
@ -396,17 +392,6 @@ const Conversation = forwardRef<ConversationHandle, ConversationProps>(
|
|||||||
aria-label="Loading Spinner"
|
aria-label="Loading Spinner"
|
||||||
data-testid="loader"
|
data-testid="loader"
|
||||||
/>
|
/>
|
||||||
{processing === true && countdown > 0 && (
|
|
||||||
<Box
|
|
||||||
sx={{
|
|
||||||
pt: 1,
|
|
||||||
fontSize: '0.7rem',
|
|
||||||
color: 'darkgrey',
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
Response will be stopped in: {countdown}s
|
|
||||||
</Box>
|
|
||||||
)}
|
|
||||||
</Box>
|
</Box>
|
||||||
<Box
|
<Box
|
||||||
className="Query"
|
className="Query"
|
||||||
@ -443,7 +428,7 @@ const Conversation = forwardRef<ConversationHandle, ConversationProps>(
|
|||||||
<DeleteConfirmation
|
<DeleteConfirmation
|
||||||
label={resetLabel || 'all data'}
|
label={resetLabel || 'all data'}
|
||||||
disabled={!chatSession || processingMessage !== undefined || noInteractions}
|
disabled={!chatSession || processingMessage !== undefined || noInteractions}
|
||||||
onDelete={() => {
|
onDelete={(): void => {
|
||||||
/*reset(); resetAction && resetAction(); */
|
/*reset(); resetAction && resetAction(); */
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
@ -453,7 +438,7 @@ const Conversation = forwardRef<ConversationHandle, ConversationProps>(
|
|||||||
sx={{ m: 1, gap: 1, flexGrow: 1 }}
|
sx={{ m: 1, gap: 1, flexGrow: 1 }}
|
||||||
variant="contained"
|
variant="contained"
|
||||||
disabled={!chatSession || processingMessage !== undefined}
|
disabled={!chatSession || processingMessage !== undefined}
|
||||||
onClick={() => {
|
onClick={(): void => {
|
||||||
processQuery({
|
processQuery({
|
||||||
prompt:
|
prompt:
|
||||||
(backstoryTextRef.current &&
|
(backstoryTextRef.current &&
|
||||||
@ -473,7 +458,7 @@ const Conversation = forwardRef<ConversationHandle, ConversationProps>(
|
|||||||
{/* This span is used to wrap the IconButton to ensure Tooltip works even when disabled */}
|
{/* This span is used to wrap the IconButton to ensure Tooltip works even when disabled */}
|
||||||
<IconButton
|
<IconButton
|
||||||
aria-label="cancel"
|
aria-label="cancel"
|
||||||
onClick={() => {
|
onClick={(): void => {
|
||||||
cancelQuery();
|
cancelQuery();
|
||||||
}}
|
}}
|
||||||
sx={{ display: 'flex', margin: 'auto 0px' }}
|
sx={{ display: 'flex', margin: 'auto 0px' }}
|
||||||
@ -502,7 +487,7 @@ const Conversation = forwardRef<ConversationHandle, ConversationProps>(
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
Conversation.displayName = 'Conversation';
|
||||||
export type { ConversationProps, ConversationHandle };
|
export type { ConversationProps, ConversationHandle };
|
||||||
|
|
||||||
export { Conversation };
|
export { Conversation };
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
import React, { JSX } from 'react';
|
||||||
import { useState } from 'react';
|
import { useState } from 'react';
|
||||||
import ContentCopyIcon from '@mui/icons-material/ContentCopy';
|
import ContentCopyIcon from '@mui/icons-material/ContentCopy';
|
||||||
import CheckIcon from '@mui/icons-material/Check';
|
import CheckIcon from '@mui/icons-material/Check';
|
||||||
@ -17,10 +18,10 @@ const CopyBubble = ({
|
|||||||
tooltip = 'Copy to clipboard',
|
tooltip = 'Copy to clipboard',
|
||||||
onClick,
|
onClick,
|
||||||
...rest
|
...rest
|
||||||
}: CopyBubbleProps) => {
|
}: CopyBubbleProps): JSX.Element => {
|
||||||
const [copied, setCopied] = useState(false);
|
const [copied, setCopied] = useState(false);
|
||||||
|
|
||||||
const handleCopy = (e: any) => {
|
const handleCopy = (e: React.MouseEvent<HTMLButtonElement, MouseEvent>): void => {
|
||||||
if (content === undefined) {
|
if (content === undefined) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -38,7 +39,7 @@ const CopyBubble = ({
|
|||||||
return (
|
return (
|
||||||
<Tooltip title={tooltip} placement="top" arrow>
|
<Tooltip title={tooltip} placement="top" arrow>
|
||||||
<IconButton
|
<IconButton
|
||||||
onClick={e => {
|
onClick={(e): void => {
|
||||||
handleCopy(e);
|
handleCopy(e);
|
||||||
}}
|
}}
|
||||||
sx={{
|
sx={{
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import React, { useEffect, useState } from 'react';
|
import React, { JSX, useState } from 'react';
|
||||||
import {
|
import {
|
||||||
IconButton,
|
IconButton,
|
||||||
Dialog,
|
Dialog,
|
||||||
@ -45,11 +45,11 @@ interface DeleteConfirmationProps {
|
|||||||
cancelButtonText?: string;
|
cancelButtonText?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
function capitalizeFirstLetter(str: string) {
|
function capitalizeFirstLetter(str: string): string {
|
||||||
return str.charAt(0).toUpperCase() + str.slice(1);
|
return str.charAt(0).toUpperCase() + str.slice(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
const DeleteConfirmation = (props: DeleteConfirmationProps) => {
|
const DeleteConfirmation = (props: DeleteConfirmationProps): JSX.Element => {
|
||||||
const {
|
const {
|
||||||
// Legacy props
|
// Legacy props
|
||||||
onDelete,
|
onDelete,
|
||||||
@ -79,13 +79,13 @@ const DeleteConfirmation = (props: DeleteConfirmationProps) => {
|
|||||||
const isControlled = controlledOpen !== undefined;
|
const isControlled = controlledOpen !== undefined;
|
||||||
const isOpen = isControlled ? controlledOpen : internalOpen;
|
const isOpen = isControlled ? controlledOpen : internalOpen;
|
||||||
|
|
||||||
const handleClickOpen = () => {
|
const handleClickOpen = (): void => {
|
||||||
if (!isControlled) {
|
if (!isControlled) {
|
||||||
setInternalOpen(true);
|
setInternalOpen(true);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleClose = () => {
|
const handleClose = (): void => {
|
||||||
if (isControlled) {
|
if (isControlled) {
|
||||||
controlledOnClose?.();
|
controlledOnClose?.();
|
||||||
} else {
|
} else {
|
||||||
@ -93,7 +93,7 @@ const DeleteConfirmation = (props: DeleteConfirmationProps) => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleConfirm = () => {
|
const handleConfirm = (): void => {
|
||||||
if (isControlled) {
|
if (isControlled) {
|
||||||
onConfirm?.();
|
onConfirm?.();
|
||||||
} else {
|
} else {
|
||||||
@ -122,7 +122,7 @@ const DeleteConfirmation = (props: DeleteConfirmationProps) => {
|
|||||||
{/* This span is used to wrap the IconButton to ensure Tooltip works even when disabled */}
|
{/* This span is used to wrap the IconButton to ensure Tooltip works even when disabled */}
|
||||||
<IconButton
|
<IconButton
|
||||||
aria-label={action}
|
aria-label={action}
|
||||||
onClick={e => {
|
onClick={(e): void => {
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
handleClickOpen();
|
handleClickOpen();
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import React, { useState, useEffect } from 'react';
|
import React, { useState, useEffect, JSX } from 'react';
|
||||||
import { BackstoryElementProps } from './BackstoryTab';
|
import { BackstoryElementProps } from './BackstoryTab';
|
||||||
import { StyledMarkdown } from './StyledMarkdown';
|
import { StyledMarkdown } from './StyledMarkdown';
|
||||||
|
|
||||||
@ -6,7 +6,7 @@ interface DocumentProps extends BackstoryElementProps {
|
|||||||
filepath?: string;
|
filepath?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
const Document = (props: DocumentProps) => {
|
const Document = (props: DocumentProps): JSX.Element => {
|
||||||
const { filepath } = props;
|
const { filepath } = props;
|
||||||
|
|
||||||
const [document, setDocument] = useState<string>('');
|
const [document, setDocument] = useState<string>('');
|
||||||
@ -16,7 +16,7 @@ const Document = (props: DocumentProps) => {
|
|||||||
if (!filepath) {
|
if (!filepath) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const fetchDocument = async () => {
|
const fetchDocument = async (): Promise<void> => {
|
||||||
try {
|
try {
|
||||||
const response = await fetch(filepath, {
|
const response = await fetch(filepath, {
|
||||||
method: 'GET',
|
method: 'GET',
|
||||||
@ -29,7 +29,7 @@ const Document = (props: DocumentProps) => {
|
|||||||
}
|
}
|
||||||
const data = await response.text();
|
const data = await response.text();
|
||||||
setDocument(data);
|
setDocument(data);
|
||||||
} catch (error: any) {
|
} catch (error) {
|
||||||
console.error('Error obtaining Docs content information:', error);
|
console.error('Error obtaining Docs content information:', error);
|
||||||
setDocument(`${filepath} not found.`);
|
setDocument(`${filepath} not found.`);
|
||||||
}
|
}
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import React, { useState, useEffect } from 'react';
|
import React, { useState, useEffect, JSX } from 'react';
|
||||||
import {
|
import {
|
||||||
Box,
|
Box,
|
||||||
Button,
|
Button,
|
||||||
@ -44,7 +44,7 @@ const VisuallyHiddenInput = styled('input')({
|
|||||||
width: 1,
|
width: 1,
|
||||||
});
|
});
|
||||||
|
|
||||||
const DocumentManager = (props: BackstoryElementProps) => {
|
const DocumentManager = (_props: BackstoryElementProps): JSX.Element => {
|
||||||
const theme = useTheme();
|
const theme = useTheme();
|
||||||
const { setSnack } = useAppState();
|
const { setSnack } = useAppState();
|
||||||
const isMobile = useMediaQuery(theme.breakpoints.down('sm'));
|
const isMobile = useMediaQuery(theme.breakpoints.down('sm'));
|
||||||
@ -63,12 +63,7 @@ const DocumentManager = (props: BackstoryElementProps) => {
|
|||||||
|
|
||||||
// Load documents on component mount
|
// Load documents on component mount
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (candidate) {
|
const loadDocuments = async (): Promise<void> => {
|
||||||
loadDocuments();
|
|
||||||
}
|
|
||||||
}, [candidate]);
|
|
||||||
|
|
||||||
const loadDocuments = async () => {
|
|
||||||
try {
|
try {
|
||||||
const results = await apiClient.getCandidateDocuments();
|
const results = await apiClient.getCandidateDocuments();
|
||||||
setDocuments(results.documents);
|
setDocuments(results.documents);
|
||||||
@ -78,8 +73,13 @@ const DocumentManager = (props: BackstoryElementProps) => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
if (candidate) {
|
||||||
|
loadDocuments();
|
||||||
|
}
|
||||||
|
}, [candidate, apiClient, setSnack]);
|
||||||
|
|
||||||
// Handle document upload
|
// Handle document upload
|
||||||
const handleDocumentUpload = async (e: React.ChangeEvent<HTMLInputElement>) => {
|
const handleDocumentUpload = async (e: React.ChangeEvent<HTMLInputElement>): Promise<void> => {
|
||||||
if (e.target.files && e.target.files[0]) {
|
if (e.target.files && e.target.files[0]) {
|
||||||
const file = e.target.files[0];
|
const file = e.target.files[0];
|
||||||
const fileExtension = '.' + file.name.split('.').pop()?.toLowerCase();
|
const fileExtension = '.' + file.name.split('.').pop()?.toLowerCase();
|
||||||
@ -132,7 +132,7 @@ const DocumentManager = (props: BackstoryElementProps) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
// Handle document deletion
|
// Handle document deletion
|
||||||
const handleDeleteDocument = async (document: Types.Document) => {
|
const handleDeleteDocument = async (document: Types.Document): Promise<void> => {
|
||||||
try {
|
try {
|
||||||
// Call API to delete document
|
// Call API to delete document
|
||||||
await apiClient.deleteCandidateDocument(document);
|
await apiClient.deleteCandidateDocument(document);
|
||||||
@ -152,7 +152,10 @@ const DocumentManager = (props: BackstoryElementProps) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
// Handle RAG flag toggle
|
// Handle RAG flag toggle
|
||||||
const handleRAGToggle = async (document: Types.Document, includeInRag: boolean) => {
|
const handleRAGToggle = async (
|
||||||
|
document: Types.Document,
|
||||||
|
includeInRag: boolean
|
||||||
|
): Promise<void> => {
|
||||||
try {
|
try {
|
||||||
document.options = { includeInRag };
|
document.options = { includeInRag };
|
||||||
// Call API to update RAG flag
|
// Call API to update RAG flag
|
||||||
@ -168,7 +171,7 @@ const DocumentManager = (props: BackstoryElementProps) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
// Handle document rename
|
// Handle document rename
|
||||||
const handleRenameDocument = async (document: Types.Document, newName: string) => {
|
const handleRenameDocument = async (document: Types.Document, newName: string): Promise<void> => {
|
||||||
if (!newName.trim()) {
|
if (!newName.trim()) {
|
||||||
setSnack('Document name cannot be empty', 'error');
|
setSnack('Document name cannot be empty', 'error');
|
||||||
return;
|
return;
|
||||||
@ -192,7 +195,7 @@ const DocumentManager = (props: BackstoryElementProps) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
// Handle document content viewing
|
// Handle document content viewing
|
||||||
const handleViewDocument = async (document: Types.Document) => {
|
const handleViewDocument = async (document: Types.Document): Promise<void> => {
|
||||||
try {
|
try {
|
||||||
setSelectedDocument(document);
|
setSelectedDocument(document);
|
||||||
setIsViewingContent(true);
|
setIsViewingContent(true);
|
||||||
@ -207,7 +210,7 @@ const DocumentManager = (props: BackstoryElementProps) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
// Start rename process
|
// Start rename process
|
||||||
const startRename = (document: Types.Document, currentName: string) => {
|
const startRename = (document: Types.Document, currentName: string): void => {
|
||||||
setEditingDocument(document);
|
setEditingDocument(document);
|
||||||
setEditingName(currentName);
|
setEditingName(currentName);
|
||||||
setIsRenameDialogOpen(true);
|
setIsRenameDialogOpen(true);
|
||||||
@ -331,7 +334,9 @@ const DocumentManager = (props: BackstoryElementProps) => {
|
|||||||
control={
|
control={
|
||||||
<Switch
|
<Switch
|
||||||
checked={doc.options?.includeInRag}
|
checked={doc.options?.includeInRag}
|
||||||
onChange={e => handleRAGToggle(doc, e.target.checked)}
|
onChange={(e): void => {
|
||||||
|
handleRAGToggle(doc, e.target.checked);
|
||||||
|
}}
|
||||||
size="small"
|
size="small"
|
||||||
/>
|
/>
|
||||||
}
|
}
|
||||||
@ -346,7 +351,9 @@ const DocumentManager = (props: BackstoryElementProps) => {
|
|||||||
<IconButton
|
<IconButton
|
||||||
edge="end"
|
edge="end"
|
||||||
size="small"
|
size="small"
|
||||||
onClick={() => handleViewDocument(doc)}
|
onClick={(): void => {
|
||||||
|
handleViewDocument(doc);
|
||||||
|
}}
|
||||||
title="View content"
|
title="View content"
|
||||||
>
|
>
|
||||||
<Visibility />
|
<Visibility />
|
||||||
@ -354,7 +361,9 @@ const DocumentManager = (props: BackstoryElementProps) => {
|
|||||||
<IconButton
|
<IconButton
|
||||||
edge="end"
|
edge="end"
|
||||||
size="small"
|
size="small"
|
||||||
onClick={() => startRename(doc, doc.filename)}
|
onClick={(): void => {
|
||||||
|
startRename(doc, doc.filename);
|
||||||
|
}}
|
||||||
title="Rename"
|
title="Rename"
|
||||||
>
|
>
|
||||||
<Edit />
|
<Edit />
|
||||||
@ -362,7 +371,9 @@ const DocumentManager = (props: BackstoryElementProps) => {
|
|||||||
<IconButton
|
<IconButton
|
||||||
edge="end"
|
edge="end"
|
||||||
size="small"
|
size="small"
|
||||||
onClick={() => handleDeleteDocument(doc)}
|
onClick={(): void => {
|
||||||
|
handleDeleteDocument(doc);
|
||||||
|
}}
|
||||||
title="Delete"
|
title="Delete"
|
||||||
color="error"
|
color="error"
|
||||||
>
|
>
|
||||||
@ -395,7 +406,7 @@ const DocumentManager = (props: BackstoryElementProps) => {
|
|||||||
<Typography variant={isMobile ? 'subtitle2' : 'h6'}>Document Content</Typography>
|
<Typography variant={isMobile ? 'subtitle2' : 'h6'}>Document Content</Typography>
|
||||||
<IconButton
|
<IconButton
|
||||||
size="small"
|
size="small"
|
||||||
onClick={() => {
|
onClick={(): void => {
|
||||||
setIsViewingContent(false);
|
setIsViewingContent(false);
|
||||||
setSelectedDocument(null);
|
setSelectedDocument(null);
|
||||||
setDocumentContent('');
|
setDocumentContent('');
|
||||||
@ -433,7 +444,9 @@ const DocumentManager = (props: BackstoryElementProps) => {
|
|||||||
{/* Rename Dialog */}
|
{/* Rename Dialog */}
|
||||||
<Dialog
|
<Dialog
|
||||||
open={isRenameDialogOpen}
|
open={isRenameDialogOpen}
|
||||||
onClose={() => setIsRenameDialogOpen(false)}
|
onClose={(): void => {
|
||||||
|
setIsRenameDialogOpen(false);
|
||||||
|
}}
|
||||||
maxWidth="sm"
|
maxWidth="sm"
|
||||||
fullWidth
|
fullWidth
|
||||||
>
|
>
|
||||||
@ -446,8 +459,10 @@ const DocumentManager = (props: BackstoryElementProps) => {
|
|||||||
fullWidth
|
fullWidth
|
||||||
variant="outlined"
|
variant="outlined"
|
||||||
value={editingName}
|
value={editingName}
|
||||||
onChange={e => setEditingName(e.target.value)}
|
onChange={(e): void => {
|
||||||
onKeyPress={e => {
|
setEditingName(e.target.value);
|
||||||
|
}}
|
||||||
|
onKeyUp={(e): void => {
|
||||||
if (e.key === 'Enter' && editingDocument) {
|
if (e.key === 'Enter' && editingDocument) {
|
||||||
handleRenameDocument(editingDocument, editingName);
|
handleRenameDocument(editingDocument, editingName);
|
||||||
}
|
}
|
||||||
@ -455,9 +470,17 @@ const DocumentManager = (props: BackstoryElementProps) => {
|
|||||||
/>
|
/>
|
||||||
</DialogContent>
|
</DialogContent>
|
||||||
<DialogActions>
|
<DialogActions>
|
||||||
<Button onClick={() => setIsRenameDialogOpen(false)}>Cancel</Button>
|
|
||||||
<Button
|
<Button
|
||||||
onClick={() => editingDocument && handleRenameDocument(editingDocument, editingName)}
|
onClick={(): void => {
|
||||||
|
setIsRenameDialogOpen(false);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Cancel
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
onClick={(): void => {
|
||||||
|
editingDocument && handleRenameDocument(editingDocument, editingName);
|
||||||
|
}}
|
||||||
variant="contained"
|
variant="contained"
|
||||||
disabled={!editingName.trim()}
|
disabled={!editingName.trim()}
|
||||||
>
|
>
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import React, { useState, useEffect } from 'react';
|
import React, { useState, useEffect, JSX } from 'react';
|
||||||
import {
|
import {
|
||||||
Box,
|
Box,
|
||||||
Card,
|
Card,
|
||||||
@ -8,8 +8,6 @@ import {
|
|||||||
Button,
|
Button,
|
||||||
Alert,
|
Alert,
|
||||||
CircularProgress,
|
CircularProgress,
|
||||||
Link,
|
|
||||||
Divider,
|
|
||||||
InputAdornment,
|
InputAdornment,
|
||||||
Dialog,
|
Dialog,
|
||||||
DialogTitle,
|
DialogTitle,
|
||||||
@ -32,14 +30,14 @@ import {
|
|||||||
} from '@mui/icons-material';
|
} from '@mui/icons-material';
|
||||||
import { useAuth } from 'hooks/AuthContext';
|
import { useAuth } from 'hooks/AuthContext';
|
||||||
import { BackstoryPageProps } from './BackstoryTab';
|
import { BackstoryPageProps } from './BackstoryTab';
|
||||||
import { Navigate, useNavigate } from 'react-router-dom';
|
import { useNavigate } from 'react-router-dom';
|
||||||
|
import { MFAData } from 'types/types';
|
||||||
|
|
||||||
// Email Verification Component
|
// Email Verification Component
|
||||||
const EmailVerificationPage = (props: BackstoryPageProps) => {
|
const EmailVerificationPage = (_props: BackstoryPageProps): JSX.Element => {
|
||||||
const { verifyEmail, resendEmailVerification, getPendingVerificationEmail, isLoading, error } =
|
const { verifyEmail, resendEmailVerification, getPendingVerificationEmail, isLoading, error } =
|
||||||
useAuth();
|
useAuth();
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
const [verificationToken, setVerificationToken] = useState('');
|
|
||||||
const [status, setStatus] = useState<'pending' | 'success' | 'error'>('pending');
|
const [status, setStatus] = useState<'pending' | 'success' | 'error'>('pending');
|
||||||
const [message, setMessage] = useState('');
|
const [message, setMessage] = useState('');
|
||||||
const [userType, setUserType] = useState<string>('');
|
const [userType, setUserType] = useState<string>('');
|
||||||
@ -49,13 +47,7 @@ const EmailVerificationPage = (props: BackstoryPageProps) => {
|
|||||||
const urlParams = new URLSearchParams(window.location.search);
|
const urlParams = new URLSearchParams(window.location.search);
|
||||||
const token = urlParams.get('token');
|
const token = urlParams.get('token');
|
||||||
|
|
||||||
if (token) {
|
const handleVerifyEmail = async (token: string): Promise<void> => {
|
||||||
setVerificationToken(token);
|
|
||||||
handleVerifyEmail(token);
|
|
||||||
}
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
const handleVerifyEmail = async (token: string) => {
|
|
||||||
if (!token) {
|
if (!token) {
|
||||||
setStatus('error');
|
setStatus('error');
|
||||||
setMessage('Invalid verification link');
|
setMessage('Invalid verification link');
|
||||||
@ -84,7 +76,12 @@ const EmailVerificationPage = (props: BackstoryPageProps) => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleResendVerification = async () => {
|
if (token) {
|
||||||
|
handleVerifyEmail(token);
|
||||||
|
}
|
||||||
|
}, [navigate, verifyEmail]);
|
||||||
|
|
||||||
|
const handleResendVerification = async (): Promise<void> => {
|
||||||
const email = getPendingVerificationEmail();
|
const email = getPendingVerificationEmail();
|
||||||
if (!email) {
|
if (!email) {
|
||||||
setMessage('No pending verification email found.');
|
setMessage('No pending verification email found.');
|
||||||
@ -146,7 +143,7 @@ const EmailVerificationPage = (props: BackstoryPageProps) => {
|
|||||||
Verification Failed
|
Verification Failed
|
||||||
</Typography>
|
</Typography>
|
||||||
<Typography color="text.secondary">
|
<Typography color="text.secondary">
|
||||||
We couldn't verify your email address.
|
We couldn't verify your email address.
|
||||||
</Typography>
|
</Typography>
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
@ -172,7 +169,13 @@ const EmailVerificationPage = (props: BackstoryPageProps) => {
|
|||||||
<Typography variant="body2" color="text.secondary" mb={2}>
|
<Typography variant="body2" color="text.secondary" mb={2}>
|
||||||
You will be redirected to the login page in a few seconds...
|
You will be redirected to the login page in a few seconds...
|
||||||
</Typography>
|
</Typography>
|
||||||
<Button variant="contained" onClick={() => navigate('/login')} fullWidth>
|
<Button
|
||||||
|
variant="contained"
|
||||||
|
onClick={(): void => {
|
||||||
|
navigate('/login');
|
||||||
|
}}
|
||||||
|
fullWidth
|
||||||
|
>
|
||||||
Go to Login
|
Go to Login
|
||||||
</Button>
|
</Button>
|
||||||
</Box>
|
</Box>
|
||||||
@ -190,7 +193,13 @@ const EmailVerificationPage = (props: BackstoryPageProps) => {
|
|||||||
>
|
>
|
||||||
Resend Verification Email
|
Resend Verification Email
|
||||||
</Button>
|
</Button>
|
||||||
<Button variant="contained" onClick={() => navigate('/login')} fullWidth>
|
<Button
|
||||||
|
variant="contained"
|
||||||
|
onClick={(): void => {
|
||||||
|
navigate('/login');
|
||||||
|
}}
|
||||||
|
fullWidth
|
||||||
|
>
|
||||||
Back to Login
|
Back to Login
|
||||||
</Button>
|
</Button>
|
||||||
</Box>
|
</Box>
|
||||||
@ -205,9 +214,9 @@ const EmailVerificationPage = (props: BackstoryPageProps) => {
|
|||||||
interface MFAVerificationDialogProps {
|
interface MFAVerificationDialogProps {
|
||||||
open: boolean;
|
open: boolean;
|
||||||
onClose: () => void;
|
onClose: () => void;
|
||||||
onVerificationSuccess: (authData: any) => void;
|
onVerificationSuccess: () => void;
|
||||||
}
|
}
|
||||||
const MFAVerificationDialog = (props: MFAVerificationDialogProps) => {
|
const MFAVerificationDialog = (props: MFAVerificationDialogProps): JSX.Element => {
|
||||||
const { open, onClose, onVerificationSuccess } = props;
|
const { open, onClose, onVerificationSuccess } = props;
|
||||||
const { verifyMFA, resendMFACode, clearMFA, mfaResponse, isLoading, error } = useAuth();
|
const { verifyMFA, resendMFACode, clearMFA, mfaResponse, isLoading, error } = useAuth();
|
||||||
const [code, setCode] = useState('');
|
const [code, setCode] = useState('');
|
||||||
@ -243,13 +252,13 @@ const MFAVerificationDialog = (props: MFAVerificationDialogProps) => {
|
|||||||
return () => clearInterval(timer);
|
return () => clearInterval(timer);
|
||||||
}, [open]);
|
}, [open]);
|
||||||
|
|
||||||
const formatTime = (seconds: number) => {
|
const formatTime = (seconds: number): string => {
|
||||||
const mins = Math.floor(seconds / 60);
|
const mins = Math.floor(seconds / 60);
|
||||||
const secs = seconds % 60;
|
const secs = seconds % 60;
|
||||||
return `${mins}:${secs.toString().padStart(2, '0')}`;
|
return `${mins}:${secs.toString().padStart(2, '0')}`;
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleVerifyMFA = async () => {
|
const handleVerifyMFA = async (): Promise<void> => {
|
||||||
if (!code || code.length !== 6) {
|
if (!code || code.length !== 6) {
|
||||||
setLocalError('Please enter a valid 6-digit code');
|
setLocalError('Please enter a valid 6-digit code');
|
||||||
return;
|
return;
|
||||||
@ -271,7 +280,7 @@ const MFAVerificationDialog = (props: MFAVerificationDialogProps) => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
if (success) {
|
if (success) {
|
||||||
onVerificationSuccess({ success: true });
|
onVerificationSuccess();
|
||||||
onClose();
|
onClose();
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
@ -279,7 +288,7 @@ const MFAVerificationDialog = (props: MFAVerificationDialogProps) => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleResendCode = async () => {
|
const handleResendCode = async (): Promise<void> => {
|
||||||
if (!mfaResponse || !mfaResponse.mfaData) {
|
if (!mfaResponse || !mfaResponse.mfaData) {
|
||||||
setLocalError('MFA data not available');
|
setLocalError('MFA data not available');
|
||||||
return;
|
return;
|
||||||
@ -301,12 +310,12 @@ const MFAVerificationDialog = (props: MFAVerificationDialogProps) => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleClose = () => {
|
const handleClose = (): void => {
|
||||||
clearMFA();
|
clearMFA();
|
||||||
onClose();
|
onClose();
|
||||||
};
|
};
|
||||||
|
|
||||||
if (!mfaResponse || !mfaResponse.mfaData) return null;
|
if (!mfaResponse || !mfaResponse.mfaData) return <></>;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Dialog open={open} onClose={handleClose} maxWidth="sm" fullWidth>
|
<Dialog open={open} onClose={handleClose} maxWidth="sm" fullWidth>
|
||||||
@ -319,12 +328,12 @@ const MFAVerificationDialog = (props: MFAVerificationDialogProps) => {
|
|||||||
|
|
||||||
<DialogContent>
|
<DialogContent>
|
||||||
<Alert severity="info" sx={{ mb: 3 }}>
|
<Alert severity="info" sx={{ mb: 3 }}>
|
||||||
We've detected a login from a new device:{' '}
|
We've detected a login from a new device:{' '}
|
||||||
<strong>{mfaResponse.mfaData.deviceName}</strong>
|
<strong>{mfaResponse.mfaData.deviceName}</strong>
|
||||||
</Alert>
|
</Alert>
|
||||||
|
|
||||||
<Typography variant="body1" gutterBottom>
|
<Typography variant="body1" gutterBottom>
|
||||||
We've sent a 6-digit verification code to:
|
We've sent a 6-digit verification code to:
|
||||||
</Typography>
|
</Typography>
|
||||||
<Typography variant="h6" color="primary" gutterBottom>
|
<Typography variant="h6" color="primary" gutterBottom>
|
||||||
{mfaResponse.mfaData.email}
|
{mfaResponse.mfaData.email}
|
||||||
@ -334,7 +343,7 @@ const MFAVerificationDialog = (props: MFAVerificationDialogProps) => {
|
|||||||
fullWidth
|
fullWidth
|
||||||
label="Enter 6-digit code"
|
label="Enter 6-digit code"
|
||||||
value={code}
|
value={code}
|
||||||
onChange={e => {
|
onChange={(e): void => {
|
||||||
const value = e.target.value.replace(/\D/g, '').slice(0, 6);
|
const value = e.target.value.replace(/\D/g, '').slice(0, 6);
|
||||||
setCode(value);
|
setCode(value);
|
||||||
setLocalError('');
|
setLocalError('');
|
||||||
@ -370,7 +379,9 @@ const MFAVerificationDialog = (props: MFAVerificationDialogProps) => {
|
|||||||
control={
|
control={
|
||||||
<Checkbox
|
<Checkbox
|
||||||
checked={rememberDevice}
|
checked={rememberDevice}
|
||||||
onChange={e => setRememberDevice(e.target.checked)}
|
onChange={(e): void => {
|
||||||
|
setRememberDevice(e.target.checked);
|
||||||
|
}}
|
||||||
/>
|
/>
|
||||||
}
|
}
|
||||||
label="Remember this device for 90 days"
|
label="Remember this device for 90 days"
|
||||||
@ -378,7 +389,7 @@ const MFAVerificationDialog = (props: MFAVerificationDialogProps) => {
|
|||||||
|
|
||||||
<Alert severity="warning" sx={{ mt: 2 }}>
|
<Alert severity="warning" sx={{ mt: 2 }}>
|
||||||
<Typography variant="body2">
|
<Typography variant="body2">
|
||||||
If you didn't attempt to log in, please change your password immediately.
|
If you didn't attempt to log in, please change your password immediately.
|
||||||
</Typography>
|
</Typography>
|
||||||
</Alert>
|
</Alert>
|
||||||
</DialogContent>
|
</DialogContent>
|
||||||
@ -410,18 +421,19 @@ const RegistrationSuccessDialog = ({
|
|||||||
onClose: () => void;
|
onClose: () => void;
|
||||||
email: string;
|
email: string;
|
||||||
userType: string;
|
userType: string;
|
||||||
}) => {
|
}): JSX.Element => {
|
||||||
const { resendEmailVerification, isLoading } = useAuth();
|
const { resendEmailVerification, isLoading } = useAuth();
|
||||||
const [resendMessage, setResendMessage] = useState('');
|
const [resendMessage, setResendMessage] = useState('');
|
||||||
|
|
||||||
const handleResendVerification = async () => {
|
const handleResendVerification = async (): Promise<void> => {
|
||||||
try {
|
try {
|
||||||
const success = await resendEmailVerification(email);
|
const success = await resendEmailVerification(email);
|
||||||
if (success) {
|
if (success) {
|
||||||
setResendMessage('Verification email sent!');
|
setResendMessage('Verification email sent!');
|
||||||
}
|
}
|
||||||
} catch (error: any) {
|
} catch (error: unknown) {
|
||||||
setResendMessage(error?.message || 'Network error. Please try again.');
|
const tmp = error as { message?: string };
|
||||||
|
setResendMessage(tmp?.message || 'Network error. Please try again.');
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -435,7 +447,7 @@ const RegistrationSuccessDialog = ({
|
|||||||
</Typography>
|
</Typography>
|
||||||
|
|
||||||
<Typography variant="body1" color="text.secondary" paragraph>
|
<Typography variant="body1" color="text.secondary" paragraph>
|
||||||
We've sent a verification link to:
|
We'e sent a verification link to:
|
||||||
</Typography>
|
</Typography>
|
||||||
|
|
||||||
<Typography variant="h6" color="primary" gutterBottom>
|
<Typography variant="h6" color="primary" gutterBottom>
|
||||||
@ -478,7 +490,7 @@ const RegistrationSuccessDialog = ({
|
|||||||
};
|
};
|
||||||
|
|
||||||
// Enhanced Login Component with MFA Support
|
// Enhanced Login Component with MFA Support
|
||||||
const LoginForm = () => {
|
const LoginForm = (): JSX.Element => {
|
||||||
const { login, mfaResponse, isLoading, error, user } = useAuth();
|
const { login, mfaResponse, isLoading, error, user } = useAuth();
|
||||||
const [email, setEmail] = useState('');
|
const [email, setEmail] = useState('');
|
||||||
const [password, setPassword] = useState('');
|
const [password, setPassword] = useState('');
|
||||||
@ -496,7 +508,7 @@ const LoginForm = () => {
|
|||||||
setErrorMessage(data.error.message);
|
setErrorMessage(data.error.message);
|
||||||
}, [error]);
|
}, [error]);
|
||||||
|
|
||||||
const handleLogin = async (e: React.FormEvent) => {
|
const handleLogin = async (e: React.FormEvent): Promise<void> => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
|
|
||||||
const success = await login({
|
const success = await login({
|
||||||
@ -512,11 +524,11 @@ const LoginForm = () => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleMFASuccess = (authData: any) => {
|
const handleMFASuccess = (): void => {
|
||||||
handleLoginSuccess();
|
handleLoginSuccess();
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleLoginSuccess = () => {
|
const handleLoginSuccess = (): void => {
|
||||||
if (!user) {
|
if (!user) {
|
||||||
navigate('/');
|
navigate('/');
|
||||||
} else {
|
} else {
|
||||||
@ -533,7 +545,9 @@ const LoginForm = () => {
|
|||||||
fullWidth
|
fullWidth
|
||||||
label="Email or Username"
|
label="Email or Username"
|
||||||
value={email}
|
value={email}
|
||||||
onChange={e => setEmail(e.target.value)}
|
onChange={(e): void => {
|
||||||
|
setEmail(e.target.value);
|
||||||
|
}}
|
||||||
autoComplete="email"
|
autoComplete="email"
|
||||||
autoFocus
|
autoFocus
|
||||||
/>
|
/>
|
||||||
@ -542,7 +556,9 @@ const LoginForm = () => {
|
|||||||
label="Password"
|
label="Password"
|
||||||
type={showPassword ? 'text' : 'password'}
|
type={showPassword ? 'text' : 'password'}
|
||||||
value={password}
|
value={password}
|
||||||
onChange={e => setPassword(e.target.value)}
|
onChange={(e): void => {
|
||||||
|
setPassword(e.target.value);
|
||||||
|
}}
|
||||||
autoComplete="current-password"
|
autoComplete="current-password"
|
||||||
placeholder="Create a strong password"
|
placeholder="Create a strong password"
|
||||||
required
|
required
|
||||||
@ -551,8 +567,12 @@ const LoginForm = () => {
|
|||||||
<InputAdornment position="end">
|
<InputAdornment position="end">
|
||||||
<IconButton
|
<IconButton
|
||||||
aria-label="toggle password visibility"
|
aria-label="toggle password visibility"
|
||||||
onClick={() => setShowPassword(!showPassword)}
|
onClick={(): void => {
|
||||||
onMouseDown={e => e.preventDefault()}
|
setShowPassword(!showPassword);
|
||||||
|
}}
|
||||||
|
onMouseDown={(e): void => {
|
||||||
|
e.preventDefault();
|
||||||
|
}}
|
||||||
edge="end"
|
edge="end"
|
||||||
>
|
>
|
||||||
{showPassword ? <VisibilityOff /> : <Visibility />}
|
{showPassword ? <VisibilityOff /> : <Visibility />}
|
||||||
@ -581,7 +601,9 @@ const LoginForm = () => {
|
|||||||
{/* MFA Dialog */}
|
{/* MFA Dialog */}
|
||||||
<MFAVerificationDialog
|
<MFAVerificationDialog
|
||||||
open={mfaResponse?.mfaRequired || false}
|
open={mfaResponse?.mfaRequired || false}
|
||||||
onClose={() => {}} // This will be handled by clearMFA in the dialog
|
onClose={(): void => {
|
||||||
|
console.log();
|
||||||
|
}} // This will be handled by clearMFA in the dialog
|
||||||
onVerificationSuccess={handleMFASuccess}
|
onVerificationSuccess={handleMFASuccess}
|
||||||
/>
|
/>
|
||||||
</Box>
|
</Box>
|
||||||
@ -589,9 +611,9 @@ const LoginForm = () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
// Device Management Component
|
// Device Management Component
|
||||||
const TrustedDevicesManager = () => {
|
const TrustedDevicesManager = (): JSX.Element => {
|
||||||
const [devices, setDevices] = useState<any[]>([]);
|
const [devices, _setDevices] = useState<MFAData[]>([]);
|
||||||
const [loading, setLoading] = useState(true);
|
const [_loading, setLoading] = useState(true);
|
||||||
|
|
||||||
// This would need API endpoints to manage trusted devices
|
// This would need API endpoints to manage trusted devices
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@ -608,8 +630,8 @@ const TrustedDevicesManager = () => {
|
|||||||
</Typography>
|
</Typography>
|
||||||
|
|
||||||
<Typography variant="body2" color="text.secondary" paragraph>
|
<Typography variant="body2" color="text.secondary" paragraph>
|
||||||
Manage devices that you've marked as trusted. You won't need to verify your identity when
|
Manage devices that you've marked as trusted. You won't need to verify your
|
||||||
signing in from these devices.
|
identity when signing in from these devices.
|
||||||
</Typography>
|
</Typography>
|
||||||
|
|
||||||
{devices.length === 0 ? (
|
{devices.length === 0 ? (
|
||||||
@ -624,18 +646,18 @@ const TrustedDevicesManager = () => {
|
|||||||
<Card variant="outlined">
|
<Card variant="outlined">
|
||||||
<CardContent>
|
<CardContent>
|
||||||
<Typography variant="subtitle1">{device.deviceName}</Typography>
|
<Typography variant="subtitle1">{device.deviceName}</Typography>
|
||||||
<Typography variant="body2" color="text.secondary">
|
{/* <Typography variant="body2" color="text.secondary">
|
||||||
Added: {new Date(device.addedAt).toLocaleDateString()}
|
Added: {new Date(device.addedAt).toLocaleDateString()}
|
||||||
</Typography>
|
</Typography> */}
|
||||||
<Typography variant="body2" color="text.secondary">
|
{/* <Typography variant="body2" color="text.secondary">
|
||||||
Last used: {new Date(device.lastUsed).toLocaleDateString()}
|
Last used: {new Date(device.lastUsed).toLocaleDateString()}
|
||||||
</Typography>
|
</Typography> */}
|
||||||
<Button
|
<Button
|
||||||
size="small"
|
size="small"
|
||||||
color="error"
|
color="error"
|
||||||
sx={{ mt: 1 }}
|
sx={{ mt: 1 }}
|
||||||
onClick={() => {
|
onClick={(): void => {
|
||||||
// Remove device
|
console.log('Remove device');
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
Remove
|
Remove
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
import React, { JSX } from 'react';
|
||||||
import { styled } from '@mui/material/styles';
|
import { styled } from '@mui/material/styles';
|
||||||
import IconButton, { IconButtonProps } from '@mui/material/IconButton';
|
import IconButton, { IconButtonProps } from '@mui/material/IconButton';
|
||||||
|
|
||||||
@ -5,7 +6,8 @@ interface ExpandMoreProps extends IconButtonProps {
|
|||||||
expand: boolean;
|
expand: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
const ExpandMore = styled((props: ExpandMoreProps) => {
|
const ExpandMore = styled((props: ExpandMoreProps): JSX.Element => {
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||||
const { expand, ...other } = props;
|
const { expand, ...other } = props;
|
||||||
return <IconButton {...other} />;
|
return <IconButton {...other} />;
|
||||||
})(({ theme }) => ({
|
})(({ theme }) => ({
|
||||||
@ -15,13 +17,13 @@ const ExpandMore = styled((props: ExpandMoreProps) => {
|
|||||||
}),
|
}),
|
||||||
variants: [
|
variants: [
|
||||||
{
|
{
|
||||||
props: ({ expand }) => !expand,
|
props: ({ expand }): boolean => !expand,
|
||||||
style: {
|
style: {
|
||||||
transform: 'rotate(0deg)',
|
transform: 'rotate(0deg)',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
props: ({ expand }) => !!expand,
|
props: ({ expand }): boolean => !!expand,
|
||||||
style: {
|
style: {
|
||||||
transform: 'rotate(180deg)',
|
transform: 'rotate(180deg)',
|
||||||
},
|
},
|
||||||
|
@ -1,9 +1,9 @@
|
|||||||
import React, { useEffect, useState, useRef } from 'react';
|
import React, { useEffect, useState, useRef, JSX } from 'react';
|
||||||
import Box from '@mui/material/Box';
|
import Box from '@mui/material/Box';
|
||||||
import PropagateLoader from 'react-spinners/PropagateLoader';
|
import PropagateLoader from 'react-spinners/PropagateLoader';
|
||||||
import { Quote } from 'components/Quote';
|
import { Quote } from 'components/Quote';
|
||||||
import { BackstoryElementProps } from 'components/BackstoryTab';
|
import { BackstoryElementProps } from 'components/BackstoryTab';
|
||||||
import { Candidate, ChatSession } from 'types/types';
|
import { ChatSession } from 'types/types';
|
||||||
import { useAuth } from 'hooks/AuthContext';
|
import { useAuth } from 'hooks/AuthContext';
|
||||||
import { useAppState } from 'hooks/GlobalContext';
|
import { useAppState } from 'hooks/GlobalContext';
|
||||||
|
|
||||||
@ -12,16 +12,13 @@ interface GenerateImageProps extends BackstoryElementProps {
|
|||||||
chatSession: ChatSession;
|
chatSession: ChatSession;
|
||||||
}
|
}
|
||||||
|
|
||||||
const GenerateImage = (props: GenerateImageProps) => {
|
const GenerateImage = (props: GenerateImageProps): JSX.Element => {
|
||||||
const { user } = useAuth();
|
const { user } = useAuth();
|
||||||
const { chatSession, prompt } = props;
|
const { chatSession, prompt } = props;
|
||||||
const { setSnack } = useAppState();
|
const { setSnack } = useAppState();
|
||||||
const [processing, setProcessing] = useState<boolean>(false);
|
const [processing, setProcessing] = useState<boolean>(false);
|
||||||
const [status, setStatus] = useState<string>('');
|
const [status, setStatus] = useState<string>('');
|
||||||
const [image, setImage] = useState<string>('');
|
const [image, _setImage] = useState<string>('');
|
||||||
|
|
||||||
const name = (user?.userType === 'candidate' ? (user as Candidate).username : user?.email) || '';
|
|
||||||
// Only keep refs that are truly necessary
|
|
||||||
const controllerRef = useRef<string>(null);
|
const controllerRef = useRef<string>(null);
|
||||||
|
|
||||||
// Effect to trigger profile generation when user data is ready
|
// Effect to trigger profile generation when user data is ready
|
||||||
@ -35,8 +32,8 @@ const GenerateImage = (props: GenerateImageProps) => {
|
|||||||
}
|
}
|
||||||
setStatus('Starting image generation...');
|
setStatus('Starting image generation...');
|
||||||
setProcessing(true);
|
setProcessing(true);
|
||||||
const start = Date.now();
|
|
||||||
|
|
||||||
|
// const start = Date.now();
|
||||||
// controllerRef.current = streamQueryResponse({
|
// controllerRef.current = streamQueryResponse({
|
||||||
// query: {
|
// query: {
|
||||||
// prompt: prompt,
|
// prompt: prompt,
|
||||||
|
@ -13,16 +13,9 @@ import {
|
|||||||
CardHeader,
|
CardHeader,
|
||||||
LinearProgress,
|
LinearProgress,
|
||||||
Stack,
|
Stack,
|
||||||
Paper,
|
|
||||||
} from '@mui/material';
|
} from '@mui/material';
|
||||||
import {
|
import {
|
||||||
SyncAlt,
|
|
||||||
Favorite,
|
|
||||||
Settings,
|
|
||||||
Info,
|
|
||||||
Search,
|
|
||||||
AutoFixHigh,
|
AutoFixHigh,
|
||||||
Image,
|
|
||||||
Psychology,
|
Psychology,
|
||||||
Build,
|
Build,
|
||||||
CloudUpload,
|
CloudUpload,
|
||||||
@ -36,9 +29,8 @@ import { styled } from '@mui/material/styles';
|
|||||||
import FileUploadIcon from '@mui/icons-material/FileUpload';
|
import FileUploadIcon from '@mui/icons-material/FileUpload';
|
||||||
|
|
||||||
import { useAuth } from 'hooks/AuthContext';
|
import { useAuth } from 'hooks/AuthContext';
|
||||||
import { useAppState, useSelectedJob } from 'hooks/GlobalContext';
|
import { useAppState } from 'hooks/GlobalContext';
|
||||||
import { BackstoryElementProps } from './BackstoryTab';
|
import { BackstoryElementProps } from './BackstoryTab';
|
||||||
import { LoginRequired } from 'components/ui/LoginRequired';
|
|
||||||
|
|
||||||
import * as Types from 'types/types';
|
import * as Types from 'types/types';
|
||||||
import { StyledMarkdown } from './StyledMarkdown';
|
import { StyledMarkdown } from './StyledMarkdown';
|
||||||
@ -60,7 +52,10 @@ const VisuallyHiddenInput = styled('input')({
|
|||||||
|
|
||||||
const UploadBox = styled(Box)(({ theme }) => ({
|
const UploadBox = styled(Box)(({ theme }) => ({
|
||||||
border: `2px dashed ${theme.palette.primary.main}`,
|
border: `2px dashed ${theme.palette.primary.main}`,
|
||||||
borderRadius: theme.shape.borderRadius * 2,
|
borderRadius:
|
||||||
|
(typeof theme.shape.borderRadius === 'string'
|
||||||
|
? parseInt(theme.shape.borderRadius)
|
||||||
|
: theme.shape.borderRadius) * 2,
|
||||||
padding: theme.spacing(4),
|
padding: theme.spacing(4),
|
||||||
textAlign: 'center',
|
textAlign: 'center',
|
||||||
backgroundColor: theme.palette.action.hover,
|
backgroundColor: theme.palette.action.hover,
|
||||||
@ -75,10 +70,9 @@ const UploadBox = styled(Box)(({ theme }) => ({
|
|||||||
interface JobCreatorProps extends BackstoryElementProps {
|
interface JobCreatorProps extends BackstoryElementProps {
|
||||||
onSave?: (job: Types.Job) => void;
|
onSave?: (job: Types.Job) => void;
|
||||||
}
|
}
|
||||||
const JobCreator = (props: JobCreatorProps) => {
|
const JobCreator = (props: JobCreatorProps): JSX.Element => {
|
||||||
const { user, apiClient } = useAuth();
|
const { user, apiClient } = useAuth();
|
||||||
const { onSave } = props;
|
const { onSave } = props;
|
||||||
const { selectedJob, setSelectedJob } = useSelectedJob();
|
|
||||||
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'));
|
||||||
@ -96,12 +90,12 @@ const JobCreator = (props: JobCreatorProps) => {
|
|||||||
const fileInputRef = useRef<HTMLInputElement>(null);
|
const fileInputRef = useRef<HTMLInputElement>(null);
|
||||||
|
|
||||||
const jobStatusHandlers = {
|
const jobStatusHandlers = {
|
||||||
onStatus: (status: Types.ChatMessageStatus) => {
|
onStatus: (status: Types.ChatMessageStatus): void => {
|
||||||
console.log('status:', status.content);
|
console.log('status:', status.content);
|
||||||
setJobStatusType(status.activity);
|
setJobStatusType(status.activity);
|
||||||
setJobStatus(status.content);
|
setJobStatus(status.content);
|
||||||
},
|
},
|
||||||
onMessage: (jobMessage: Types.JobRequirementsMessage) => {
|
onMessage: (jobMessage: Types.JobRequirementsMessage): void => {
|
||||||
const job: Types.Job = jobMessage.job;
|
const job: Types.Job = jobMessage.job;
|
||||||
console.log('onMessage - job', job);
|
console.log('onMessage - job', job);
|
||||||
setJob(job);
|
setJob(job);
|
||||||
@ -113,19 +107,19 @@ const JobCreator = (props: JobCreatorProps) => {
|
|||||||
setJobStatusType(null);
|
setJobStatusType(null);
|
||||||
setJobStatus('');
|
setJobStatus('');
|
||||||
},
|
},
|
||||||
onError: (error: Types.ChatMessageError) => {
|
onError: (error: Types.ChatMessageError): void => {
|
||||||
console.log('onError', error);
|
console.log('onError', error);
|
||||||
setSnack(error.content, 'error');
|
setSnack(error.content, 'error');
|
||||||
setIsProcessing(false);
|
setIsProcessing(false);
|
||||||
},
|
},
|
||||||
onComplete: () => {
|
onComplete: (): void => {
|
||||||
setJobStatusType(null);
|
setJobStatusType(null);
|
||||||
setJobStatus('');
|
setJobStatus('');
|
||||||
setIsProcessing(false);
|
setIsProcessing(false);
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleJobUpload = async (e: React.ChangeEvent<HTMLInputElement>) => {
|
const handleJobUpload = async (e: React.ChangeEvent<HTMLInputElement>): Promise<void> => {
|
||||||
if (e.target.files && e.target.files[0]) {
|
if (e.target.files && e.target.files[0]) {
|
||||||
const file = e.target.files[0];
|
const file = e.target.files[0];
|
||||||
const fileExtension = '.' + file.name.split('.').pop()?.toLowerCase();
|
const fileExtension = '.' + file.name.split('.').pop()?.toLowerCase();
|
||||||
@ -171,7 +165,7 @@ const JobCreator = (props: JobCreatorProps) => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleUploadClick = () => {
|
const handleUploadClick = (): void => {
|
||||||
fileInputRef.current?.click();
|
fileInputRef.current?.click();
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -180,8 +174,8 @@ const JobCreator = (props: JobCreatorProps) => {
|
|||||||
items: string[] | undefined,
|
items: string[] | undefined,
|
||||||
icon: JSX.Element,
|
icon: JSX.Element,
|
||||||
required = false
|
required = false
|
||||||
) => {
|
): JSX.Element => {
|
||||||
if (!items || items.length === 0) return null;
|
if (!items || items.length === 0) return <></>;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box sx={{ mb: 3 }}>
|
<Box sx={{ mb: 3 }}>
|
||||||
@ -201,8 +195,8 @@ const JobCreator = (props: JobCreatorProps) => {
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
const renderJobRequirements = () => {
|
const renderJobRequirements = (): JSX.Element => {
|
||||||
if (!jobRequirements) return null;
|
if (!jobRequirements) return <></>;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Card elevation={2} sx={{ mt: 3 }}>
|
<Card elevation={2} sx={{ mt: 3 }}>
|
||||||
@ -264,7 +258,7 @@ const JobCreator = (props: JobCreatorProps) => {
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleSave = async () => {
|
const handleSave = async (): Promise<void> => {
|
||||||
const newJob: Types.Job = {
|
const newJob: Types.Job = {
|
||||||
ownerId: user?.id || '',
|
ownerId: user?.id || '',
|
||||||
ownerType: 'candidate',
|
ownerType: 'candidate',
|
||||||
@ -286,7 +280,7 @@ const JobCreator = (props: JobCreatorProps) => {
|
|||||||
onSave && onSave(job);
|
onSave && onSave(job);
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleExtractRequirements = async () => {
|
const handleExtractRequirements = async (): Promise<void> => {
|
||||||
try {
|
try {
|
||||||
setIsProcessing(true);
|
setIsProcessing(true);
|
||||||
const controller = apiClient.createJobFromDescription(jobDescription, jobStatusHandlers);
|
const controller = apiClient.createJobFromDescription(jobDescription, jobStatusHandlers);
|
||||||
@ -304,7 +298,7 @@ const JobCreator = (props: JobCreatorProps) => {
|
|||||||
setIsProcessing(false);
|
setIsProcessing(false);
|
||||||
};
|
};
|
||||||
|
|
||||||
const renderJobCreation = () => {
|
const renderJobCreation = (): JSX.Element => {
|
||||||
return (
|
return (
|
||||||
<Box
|
<Box
|
||||||
sx={{
|
sx={{
|
||||||
@ -371,7 +365,9 @@ const JobCreator = (props: JobCreatorProps) => {
|
|||||||
placeholder="Paste or type the job description here..."
|
placeholder="Paste or type the job description here..."
|
||||||
variant="outlined"
|
variant="outlined"
|
||||||
value={jobDescription}
|
value={jobDescription}
|
||||||
onChange={e => setJobDescription(e.target.value)}
|
onChange={(e): void => {
|
||||||
|
setJobDescription(e.target.value);
|
||||||
|
}}
|
||||||
disabled={isProcessing}
|
disabled={isProcessing}
|
||||||
sx={{ mb: 2 }}
|
sx={{ mb: 2 }}
|
||||||
/>
|
/>
|
||||||
@ -418,7 +414,9 @@ const JobCreator = (props: JobCreatorProps) => {
|
|||||||
label="Job Title"
|
label="Job Title"
|
||||||
variant="outlined"
|
variant="outlined"
|
||||||
value={jobTitle}
|
value={jobTitle}
|
||||||
onChange={e => setJobTitle(e.target.value)}
|
onChange={(e): void => {
|
||||||
|
setJobTitle(e.target.value);
|
||||||
|
}}
|
||||||
required
|
required
|
||||||
disabled={isProcessing}
|
disabled={isProcessing}
|
||||||
InputProps={{
|
InputProps={{
|
||||||
@ -433,7 +431,9 @@ const JobCreator = (props: JobCreatorProps) => {
|
|||||||
label="Company"
|
label="Company"
|
||||||
variant="outlined"
|
variant="outlined"
|
||||||
value={company}
|
value={company}
|
||||||
onChange={e => setCompany(e.target.value)}
|
onChange={(e): void => {
|
||||||
|
setCompany(e.target.value);
|
||||||
|
}}
|
||||||
required
|
required
|
||||||
disabled={isProcessing}
|
disabled={isProcessing}
|
||||||
InputProps={{
|
InputProps={{
|
||||||
|
@ -1,15 +1,12 @@
|
|||||||
import React, { useState, useEffect } from 'react';
|
import React, { useState, useEffect, useCallback, JSX, useMemo } from 'react';
|
||||||
import {
|
import {
|
||||||
Box,
|
Box,
|
||||||
Typography,
|
Typography,
|
||||||
Paper,
|
|
||||||
Accordion,
|
Accordion,
|
||||||
AccordionSummary,
|
AccordionSummary,
|
||||||
AccordionDetails,
|
AccordionDetails,
|
||||||
CircularProgress,
|
CircularProgress,
|
||||||
Grid,
|
|
||||||
Chip,
|
Chip,
|
||||||
Divider,
|
|
||||||
Card,
|
Card,
|
||||||
CardContent,
|
CardContent,
|
||||||
useTheme,
|
useTheme,
|
||||||
@ -22,28 +19,11 @@ import CheckCircleIcon from '@mui/icons-material/CheckCircle';
|
|||||||
import ErrorIcon from '@mui/icons-material/Error';
|
import ErrorIcon from '@mui/icons-material/Error';
|
||||||
import PendingIcon from '@mui/icons-material/Pending';
|
import PendingIcon from '@mui/icons-material/Pending';
|
||||||
import WarningIcon from '@mui/icons-material/Warning';
|
import WarningIcon from '@mui/icons-material/Warning';
|
||||||
import {
|
import { Candidate, ChatMessage, SkillAssessment, SkillStatus } from 'types/types';
|
||||||
Candidate,
|
|
||||||
ChatMessage,
|
|
||||||
ChatMessageError,
|
|
||||||
ChatMessageStatus,
|
|
||||||
ChatMessageStreaming,
|
|
||||||
ChatMessageUser,
|
|
||||||
ChatSession,
|
|
||||||
EvidenceDetail,
|
|
||||||
JobRequirements,
|
|
||||||
SkillAssessment,
|
|
||||||
SkillStatus,
|
|
||||||
} from 'types/types';
|
|
||||||
import { useAuth } from 'hooks/AuthContext';
|
import { useAuth } from 'hooks/AuthContext';
|
||||||
import { BackstoryPageProps } from './BackstoryTab';
|
import { BackstoryPageProps } from './BackstoryTab';
|
||||||
import { Job } from 'types/types';
|
import { Job } from 'types/types';
|
||||||
import { StyledMarkdown } from './StyledMarkdown';
|
|
||||||
import { Scrollable } from './Scrollable';
|
|
||||||
import { useAppState } from 'hooks/GlobalContext';
|
|
||||||
import * as Types from 'types/types';
|
import * as Types from 'types/types';
|
||||||
import JsonView from '@uiw/react-json-view';
|
|
||||||
import { VectorVisualizer } from './VectorVisualizer';
|
|
||||||
import { JobInfo } from './ui/JobInfo';
|
import { JobInfo } from './ui/JobInfo';
|
||||||
|
|
||||||
interface JobAnalysisProps extends BackstoryPageProps {
|
interface JobAnalysisProps extends BackstoryPageProps {
|
||||||
@ -53,16 +33,6 @@ interface JobAnalysisProps extends BackstoryPageProps {
|
|||||||
onAnalysisComplete: (skills: SkillAssessment[]) => void;
|
onAnalysisComplete: (skills: SkillAssessment[]) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
const defaultMessage: ChatMessage = {
|
|
||||||
status: 'done',
|
|
||||||
type: 'text',
|
|
||||||
sessionId: '',
|
|
||||||
timestamp: new Date(),
|
|
||||||
content: '',
|
|
||||||
role: 'assistant',
|
|
||||||
metadata: null as any,
|
|
||||||
};
|
|
||||||
|
|
||||||
interface SkillMatch extends SkillAssessment {
|
interface SkillMatch extends SkillAssessment {
|
||||||
domain: string;
|
domain: string;
|
||||||
status: SkillStatus;
|
status: SkillStatus;
|
||||||
@ -72,20 +42,17 @@ interface SkillMatch extends SkillAssessment {
|
|||||||
const JobMatchAnalysis: React.FC<JobAnalysisProps> = (props: JobAnalysisProps) => {
|
const JobMatchAnalysis: React.FC<JobAnalysisProps> = (props: JobAnalysisProps) => {
|
||||||
const { job, candidate, onAnalysisComplete, variant = 'normal' } = props;
|
const { job, candidate, onAnalysisComplete, variant = 'normal' } = props;
|
||||||
const { apiClient } = useAuth();
|
const { apiClient } = useAuth();
|
||||||
const { setSnack } = useAppState();
|
|
||||||
const theme = useTheme();
|
const theme = useTheme();
|
||||||
const [requirements, setRequirements] = useState<{ requirement: string; domain: string }[]>([]);
|
const [requirements, setRequirements] = useState<{ requirement: string; domain: string }[]>([]);
|
||||||
const [skillMatches, setSkillMatches] = useState<SkillMatch[]>([]);
|
const [skillMatches, setSkillMatches] = useState<SkillMatch[]>([]);
|
||||||
const [creatingSession, setCreatingSession] = useState<boolean>(false);
|
|
||||||
const [loadingRequirements, setLoadingRequirements] = useState<boolean>(false);
|
const [loadingRequirements, setLoadingRequirements] = useState<boolean>(false);
|
||||||
const [expanded, setExpanded] = useState<string | false>(false);
|
const [expanded, setExpanded] = useState<string | false>(false);
|
||||||
const [overallScore, setOverallScore] = useState<number>(0);
|
const [overallScore, setOverallScore] = useState<number>(0);
|
||||||
const [requirementsSession, setRequirementsSession] = useState<ChatSession | null>(null);
|
const [_statusMessage, setStatusMessage] = useState<ChatMessage | null>(null);
|
||||||
const [statusMessage, setStatusMessage] = useState<ChatMessage | null>(null);
|
|
||||||
const [startAnalysis, setStartAnalysis] = useState<boolean>(false);
|
const [startAnalysis, setStartAnalysis] = useState<boolean>(false);
|
||||||
const [analyzing, setAnalyzing] = useState<boolean>(false);
|
const [analyzing, setAnalyzing] = useState<boolean>(false);
|
||||||
const [matchStatus, setMatchStatus] = useState<string>('');
|
const [matchStatus, setMatchStatus] = useState<string>('');
|
||||||
const [matchStatusType, setMatchStatusType] = useState<Types.ApiActivityType | null>(null);
|
const [_matchStatusType, setMatchStatusType] = useState<Types.ApiActivityType | null>(null);
|
||||||
|
|
||||||
const isMobile = useMediaQuery(theme.breakpoints.down('sm'));
|
const isMobile = useMediaQuery(theme.breakpoints.down('sm'));
|
||||||
|
|
||||||
@ -95,7 +62,8 @@ const JobMatchAnalysis: React.FC<JobAnalysisProps> = (props: JobAnalysisProps) =
|
|||||||
setExpanded(isExpanded ? panel : false);
|
setExpanded(isExpanded ? panel : false);
|
||||||
};
|
};
|
||||||
|
|
||||||
const initializeRequirements = (job: Job) => {
|
const initializeRequirements = useCallback(
|
||||||
|
(job: Job): void => {
|
||||||
if (!job || !job.requirements) {
|
if (!job || !job.requirements) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -170,18 +138,22 @@ const JobMatchAnalysis: React.FC<JobAnalysisProps> = (props: JobAnalysisProps) =
|
|||||||
setStatusMessage(null);
|
setStatusMessage(null);
|
||||||
setLoadingRequirements(false);
|
setLoadingRequirements(false);
|
||||||
setOverallScore(0);
|
setOverallScore(0);
|
||||||
};
|
},
|
||||||
|
[candidate.id]
|
||||||
|
);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
initializeRequirements(job);
|
initializeRequirements(job);
|
||||||
}, [job]);
|
}, [job, initializeRequirements]);
|
||||||
|
|
||||||
const skillMatchHandlers = {
|
const skillMatchHandlers = useMemo(() => {
|
||||||
onStatus: (status: Types.ChatMessageStatus) => {
|
return {
|
||||||
|
onStatus: (status: Types.ChatMessageStatus): void => {
|
||||||
setMatchStatusType(status.activity);
|
setMatchStatusType(status.activity);
|
||||||
setMatchStatus(status.content.toLowerCase());
|
setMatchStatus(status.content.toLowerCase());
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
}, [setMatchStatus, setMatchStatusType]);
|
||||||
|
|
||||||
// Fetch match data for each requirement
|
// Fetch match data for each requirement
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@ -189,7 +161,7 @@ const JobMatchAnalysis: React.FC<JobAnalysisProps> = (props: JobAnalysisProps) =
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const fetchMatchData = async (skills: SkillAssessment[]) => {
|
const fetchMatchData = async (skills: SkillAssessment[]): Promise<void> => {
|
||||||
if (requirements.length === 0) return;
|
if (requirements.length === 0) return;
|
||||||
|
|
||||||
// Process requirements one by one
|
// Process requirements one by one
|
||||||
@ -201,7 +173,7 @@ const JobMatchAnalysis: React.FC<JobAnalysisProps> = (props: JobAnalysisProps) =
|
|||||||
return updated;
|
return updated;
|
||||||
});
|
});
|
||||||
|
|
||||||
const request: any = await apiClient.candidateMatchForRequirement(
|
const request = await apiClient.candidateMatchForRequirement(
|
||||||
candidate.id || '',
|
candidate.id || '',
|
||||||
requirements[i].requirement,
|
requirements[i].requirement,
|
||||||
skillMatchHandlers
|
skillMatchHandlers
|
||||||
@ -226,11 +198,11 @@ const JobMatchAnalysis: React.FC<JobAnalysisProps> = (props: JobAnalysisProps) =
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
if (
|
if (
|
||||||
skillMatch.evidenceStrength == 'NONE' &&
|
skillMatch.evidenceStrength == 'none' &&
|
||||||
skillMatch.citations &&
|
skillMatch.evidenceDetails &&
|
||||||
skillMatch.citations.length > 3
|
skillMatch.evidenceDetails.length > 3
|
||||||
) {
|
) {
|
||||||
matchScore = Math.min(skillMatch.citations.length * 8, 40);
|
matchScore = Math.min(skillMatch.evidenceDetails.length * 8, 40);
|
||||||
}
|
}
|
||||||
const match: SkillMatch = {
|
const match: SkillMatch = {
|
||||||
...skillMatch,
|
...skillMatch,
|
||||||
@ -277,7 +249,17 @@ const JobMatchAnalysis: React.FC<JobAnalysisProps> = (props: JobAnalysisProps) =
|
|||||||
setStartAnalysis(false);
|
setStartAnalysis(false);
|
||||||
onAnalysisComplete && onAnalysisComplete(skills);
|
onAnalysisComplete && onAnalysisComplete(skills);
|
||||||
});
|
});
|
||||||
}, [job, onAnalysisComplete, startAnalysis, analyzing, requirements, loadingRequirements]);
|
}, [
|
||||||
|
job,
|
||||||
|
onAnalysisComplete,
|
||||||
|
startAnalysis,
|
||||||
|
analyzing,
|
||||||
|
requirements,
|
||||||
|
loadingRequirements,
|
||||||
|
apiClient,
|
||||||
|
candidate.id,
|
||||||
|
skillMatchHandlers,
|
||||||
|
]);
|
||||||
|
|
||||||
// Get color based on match score
|
// Get color based on match score
|
||||||
const getMatchColor = (score: number): string => {
|
const getMatchColor = (score: number): string => {
|
||||||
@ -288,7 +270,7 @@ const JobMatchAnalysis: React.FC<JobAnalysisProps> = (props: JobAnalysisProps) =
|
|||||||
};
|
};
|
||||||
|
|
||||||
// Get icon based on status
|
// Get icon based on status
|
||||||
const getStatusIcon = (status: string, score: number) => {
|
const getStatusIcon = (status: string, score: number): JSX.Element => {
|
||||||
if (status === 'pending' || status === 'waiting') return <PendingIcon />;
|
if (status === 'pending' || status === 'waiting') return <PendingIcon />;
|
||||||
if (status === 'error') return <ErrorIcon color="error" />;
|
if (status === 'error') return <ErrorIcon color="error" />;
|
||||||
if (score >= 70) return <CheckCircleIcon color="success" />;
|
if (score >= 70) return <CheckCircleIcon color="success" />;
|
||||||
@ -296,7 +278,7 @@ const JobMatchAnalysis: React.FC<JobAnalysisProps> = (props: JobAnalysisProps) =
|
|||||||
return <ErrorIcon color="error" />;
|
return <ErrorIcon color="error" />;
|
||||||
};
|
};
|
||||||
|
|
||||||
const beginAnalysis = () => {
|
const beginAnalysis = (): void => {
|
||||||
initializeRequirements(job);
|
initializeRequirements(job);
|
||||||
setStartAnalysis(true);
|
setStartAnalysis(true);
|
||||||
};
|
};
|
||||||
@ -512,7 +494,7 @@ const JobMatchAnalysis: React.FC<JobAnalysisProps> = (props: JobAnalysisProps) =
|
|||||||
<Box sx={{ width: '100%', p: 2 }}>
|
<Box sx={{ width: '100%', p: 2 }}>
|
||||||
<LinearProgress />
|
<LinearProgress />
|
||||||
<Typography sx={{ mt: 2 }}>
|
<Typography sx={{ mt: 2 }}>
|
||||||
Analyzing candidate's match for this requirement... {matchStatus}
|
Analyzing candidate's match for this requirement... {matchStatus}
|
||||||
</Typography>
|
</Typography>
|
||||||
</Box>
|
</Box>
|
||||||
) : match.status === 'error' ? (
|
) : match.status === 'error' ? (
|
||||||
@ -549,7 +531,7 @@ const JobMatchAnalysis: React.FC<JobAnalysisProps> = (props: JobAnalysisProps) =
|
|||||||
component="div"
|
component="div"
|
||||||
sx={{ mb: 1, fontStyle: 'italic' }}
|
sx={{ mb: 1, fontStyle: 'italic' }}
|
||||||
>
|
>
|
||||||
"{evidence.quote}"
|
"{evidence.quote}"
|
||||||
</Typography>
|
</Typography>
|
||||||
<Box
|
<Box
|
||||||
sx={{
|
sx={{
|
||||||
@ -578,7 +560,7 @@ const JobMatchAnalysis: React.FC<JobAnalysisProps> = (props: JobAnalysisProps) =
|
|||||||
))
|
))
|
||||||
) : (
|
) : (
|
||||||
<Typography color="text.secondary">
|
<Typography color="text.secondary">
|
||||||
No specific evidence found in candidate's profile.
|
No specific evidence found in candidate's profile.
|
||||||
</Typography>
|
</Typography>
|
||||||
)}
|
)}
|
||||||
<Typography variant="h6" gutterBottom>
|
<Typography variant="h6" gutterBottom>
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import React, { useState, useEffect } from 'react';
|
import React, { useState, useEffect, useMemo } from 'react';
|
||||||
import {
|
import {
|
||||||
Box,
|
Box,
|
||||||
TextField,
|
TextField,
|
||||||
@ -45,13 +45,16 @@ const LocationInput: React.FC<LocationInputProps> = ({
|
|||||||
const [isRemote, setIsRemote] = useState<boolean>(value.remote || false);
|
const [isRemote, setIsRemote] = useState<boolean>(value.remote || false);
|
||||||
|
|
||||||
// Get states for selected country
|
// Get states for selected country
|
||||||
const availableStates = selectedCountry ? State.getStatesOfCountry(selectedCountry.isoCode) : [];
|
const availableStates = useMemo(() => {
|
||||||
|
return selectedCountry ? State.getStatesOfCountry(selectedCountry.isoCode) : [];
|
||||||
|
}, [selectedCountry]);
|
||||||
|
|
||||||
// Get cities for selected state
|
// Get cities for selected state
|
||||||
const availableCities =
|
const availableCities = useMemo(() => {
|
||||||
selectedCountry && selectedState
|
return selectedCountry && selectedState
|
||||||
? City.getCitiesOfState(selectedCountry.isoCode, selectedState.isoCode)
|
? City.getCitiesOfState(selectedCountry.isoCode, selectedState.isoCode)
|
||||||
: [];
|
: [];
|
||||||
|
}, [selectedCountry, selectedState]);
|
||||||
|
|
||||||
// Initialize state and city from value prop
|
// Initialize state and city from value prop
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@ -104,24 +107,24 @@ const LocationInput: React.FC<LocationInputProps> = ({
|
|||||||
showCity,
|
showCity,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
const handleCountryChange = (event: any, newValue: ICountry | null) => {
|
const handleCountryChange = (_event: React.SyntheticEvent, newValue: ICountry | null): void => {
|
||||||
setSelectedCountry(newValue);
|
setSelectedCountry(newValue);
|
||||||
// Clear state and city when country changes
|
// Clear state and city when country changes
|
||||||
setSelectedState(null);
|
setSelectedState(null);
|
||||||
setSelectedCity(null);
|
setSelectedCity(null);
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleStateChange = (event: any, newValue: IState | null) => {
|
const handleStateChange = (_event: React.SyntheticEvent, newValue: IState | null): void => {
|
||||||
setSelectedState(newValue);
|
setSelectedState(newValue);
|
||||||
// Clear city when state changes
|
// Clear city when state changes
|
||||||
setSelectedCity(null);
|
setSelectedCity(null);
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleCityChange = (event: any, newValue: ICity | null) => {
|
const handleCityChange = (_event: React.SyntheticEvent, newValue: ICity | null): void => {
|
||||||
setSelectedCity(newValue);
|
setSelectedCity(newValue);
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleRemoteToggle = (event: React.ChangeEvent<HTMLInputElement>) => {
|
const handleRemoteToggle = (event: React.ChangeEvent<HTMLInputElement>): void => {
|
||||||
setIsRemote(event.target.checked);
|
setIsRemote(event.target.checked);
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -139,9 +142,9 @@ const LocationInput: React.FC<LocationInputProps> = ({
|
|||||||
value={selectedCountry}
|
value={selectedCountry}
|
||||||
onChange={handleCountryChange}
|
onChange={handleCountryChange}
|
||||||
options={allCountries}
|
options={allCountries}
|
||||||
getOptionLabel={option => option.name}
|
getOptionLabel={(option): string => option.name}
|
||||||
disabled={disabled}
|
disabled={disabled}
|
||||||
renderInput={params => (
|
renderInput={(params): React.ReactNode => (
|
||||||
<TextField
|
<TextField
|
||||||
{...params}
|
{...params}
|
||||||
label="Country"
|
label="Country"
|
||||||
@ -157,7 +160,7 @@ const LocationInput: React.FC<LocationInputProps> = ({
|
|||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
renderOption={(props, option) => (
|
renderOption={(props, option): React.ReactNode => (
|
||||||
<Box component="li" {...props} key={option.isoCode}>
|
<Box component="li" {...props} key={option.isoCode}>
|
||||||
<img
|
<img
|
||||||
loading="lazy"
|
loading="lazy"
|
||||||
@ -180,9 +183,9 @@ const LocationInput: React.FC<LocationInputProps> = ({
|
|||||||
value={selectedState}
|
value={selectedState}
|
||||||
onChange={handleStateChange}
|
onChange={handleStateChange}
|
||||||
options={availableStates}
|
options={availableStates}
|
||||||
getOptionLabel={option => option.name}
|
getOptionLabel={(option): string => option.name}
|
||||||
disabled={disabled || availableStates.length === 0}
|
disabled={disabled || availableStates.length === 0}
|
||||||
renderInput={params => (
|
renderInput={(params): React.ReactNode => (
|
||||||
<TextField
|
<TextField
|
||||||
{...params}
|
{...params}
|
||||||
label="State/Region"
|
label="State/Region"
|
||||||
@ -203,9 +206,9 @@ const LocationInput: React.FC<LocationInputProps> = ({
|
|||||||
value={selectedCity}
|
value={selectedCity}
|
||||||
onChange={handleCityChange}
|
onChange={handleCityChange}
|
||||||
options={availableCities}
|
options={availableCities}
|
||||||
getOptionLabel={option => option.name}
|
getOptionLabel={(option): string => option.name}
|
||||||
disabled={disabled || availableCities.length === 0}
|
disabled={disabled || availableCities.length === 0}
|
||||||
renderInput={params => (
|
renderInput={(params): React.ReactNode => (
|
||||||
<TextField
|
<TextField
|
||||||
{...params}
|
{...params}
|
||||||
label="City"
|
label="City"
|
||||||
@ -275,94 +278,4 @@ const LocationInput: React.FC<LocationInputProps> = ({
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
// Demo component to show usage with real data
|
|
||||||
const LocationInputDemo: React.FC = () => {
|
|
||||||
const [location, setLocation] = useState<Partial<Location>>({});
|
|
||||||
const [showAdvanced, setShowAdvanced] = useState(false);
|
|
||||||
|
|
||||||
const handleLocationChange = (newLocation: Partial<Location>) => {
|
|
||||||
setLocation(newLocation);
|
|
||||||
console.log('Location updated:', newLocation);
|
|
||||||
};
|
|
||||||
|
|
||||||
// Show some stats about the data
|
|
||||||
const totalCountries = Country.getAllCountries().length;
|
|
||||||
const usStates = State.getStatesOfCountry('US').length;
|
|
||||||
const canadaProvinces = State.getStatesOfCountry('CA').length;
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Box sx={{ p: 3, maxWidth: 800, mx: 'auto' }}>
|
|
||||||
<Typography variant="h4" gutterBottom align="center" color="primary">
|
|
||||||
Location Input with Real Data
|
|
||||||
</Typography>
|
|
||||||
|
|
||||||
<Typography variant="body2" color="text.secondary" align="center" sx={{ mb: 3 }}>
|
|
||||||
Using country-state-city library with {totalCountries} countries,
|
|
||||||
{usStates} US states, {canadaProvinces} Canadian provinces, and thousands of cities
|
|
||||||
</Typography>
|
|
||||||
|
|
||||||
<Grid container spacing={4}>
|
|
||||||
<Grid size={{ xs: 12 }}>
|
|
||||||
<Typography variant="h5" gutterBottom>
|
|
||||||
Basic Location Input
|
|
||||||
</Typography>
|
|
||||||
<LocationInput value={location} onChange={handleLocationChange} required />
|
|
||||||
</Grid>
|
|
||||||
|
|
||||||
<Grid size={{ xs: 12 }}>
|
|
||||||
<FormControlLabel
|
|
||||||
control={
|
|
||||||
<Checkbox
|
|
||||||
checked={showAdvanced}
|
|
||||||
onChange={e => setShowAdvanced(e.target.checked)}
|
|
||||||
color="primary"
|
|
||||||
/>
|
|
||||||
}
|
|
||||||
label="Show city field"
|
|
||||||
/>
|
|
||||||
</Grid>
|
|
||||||
|
|
||||||
{showAdvanced && (
|
|
||||||
<Grid size={{ xs: 12 }}>
|
|
||||||
<Typography variant="h5" gutterBottom>
|
|
||||||
Advanced Location Input (with City)
|
|
||||||
</Typography>
|
|
||||||
<LocationInput
|
|
||||||
value={location}
|
|
||||||
onChange={handleLocationChange}
|
|
||||||
showCity
|
|
||||||
helperText="Include your city for more specific job matches"
|
|
||||||
/>
|
|
||||||
</Grid>
|
|
||||||
)}
|
|
||||||
|
|
||||||
<Grid size={{ xs: 12 }}>
|
|
||||||
<Typography variant="h6" gutterBottom>
|
|
||||||
Current Location Data:
|
|
||||||
</Typography>
|
|
||||||
<Box
|
|
||||||
component="pre"
|
|
||||||
sx={{
|
|
||||||
bgcolor: 'grey.100',
|
|
||||||
p: 2,
|
|
||||||
borderRadius: 1,
|
|
||||||
overflow: 'auto',
|
|
||||||
fontSize: '0.875rem',
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{JSON.stringify(location, null, 2)}
|
|
||||||
</Box>
|
|
||||||
</Grid>
|
|
||||||
|
|
||||||
<Grid size={{ xs: 12 }}>
|
|
||||||
<Typography variant="body2" color="text.secondary">
|
|
||||||
💡 This component uses the country-state-city library which is regularly updated and
|
|
||||||
includes ISO codes, flags, and comprehensive location data.
|
|
||||||
</Typography>
|
|
||||||
</Grid>
|
|
||||||
</Grid>
|
|
||||||
</Box>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export { LocationInput };
|
export { LocationInput };
|
||||||
|
@ -32,7 +32,7 @@ const Mermaid: React.FC<MermaidProps> = (props: MermaidProps) => {
|
|||||||
}, [containerRef, setVisible]);
|
}, [containerRef, setVisible]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const renderMermaid = async () => {
|
const renderMermaid = async (): Promise<void> => {
|
||||||
if (containerRef.current && visible && chart) {
|
if (containerRef.current && visible && chart) {
|
||||||
try {
|
try {
|
||||||
await mermaid.initialize(mermaidConfig || defaultMermaidConfig);
|
await mermaid.initialize(mermaidConfig || defaultMermaidConfig);
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import { useState, useRef } from 'react';
|
import React, { JSX, useState } from 'react';
|
||||||
import Divider from '@mui/material/Divider';
|
import Divider from '@mui/material/Divider';
|
||||||
import Accordion from '@mui/material/Accordion';
|
import Accordion from '@mui/material/Accordion';
|
||||||
import AccordionSummary from '@mui/material/AccordionSummary';
|
import AccordionSummary from '@mui/material/AccordionSummary';
|
||||||
@ -12,11 +12,9 @@ import TableHead from '@mui/material/TableHead';
|
|||||||
import TableRow from '@mui/material/TableRow';
|
import TableRow from '@mui/material/TableRow';
|
||||||
import Button from '@mui/material/Button';
|
import Button from '@mui/material/Button';
|
||||||
import CardContent from '@mui/material/CardContent';
|
import CardContent from '@mui/material/CardContent';
|
||||||
import CardActions from '@mui/material/CardActions';
|
|
||||||
import Collapse from '@mui/material/Collapse';
|
import Collapse from '@mui/material/Collapse';
|
||||||
import { ExpandMore } from './ExpandMore';
|
import { ExpandMore } from './ExpandMore';
|
||||||
import JsonView from '@uiw/react-json-view';
|
import JsonView from '@uiw/react-json-view';
|
||||||
import React from 'react';
|
|
||||||
import { Box } from '@mui/material';
|
import { Box } from '@mui/material';
|
||||||
import { useTheme } from '@mui/material/styles';
|
import { useTheme } from '@mui/material/styles';
|
||||||
import { SxProps, Theme } from '@mui/material';
|
import { SxProps, Theme } from '@mui/material';
|
||||||
@ -28,9 +26,7 @@ import { ErrorOutline, InfoOutline, Memory, Psychology /* Stream, */ } from '@mu
|
|||||||
import { StyledMarkdown } from './StyledMarkdown';
|
import { StyledMarkdown } from './StyledMarkdown';
|
||||||
|
|
||||||
import { VectorVisualizer } from './VectorVisualizer';
|
import { VectorVisualizer } from './VectorVisualizer';
|
||||||
import { SetSnackType } from './Snack';
|
|
||||||
import { CopyBubble } from './CopyBubble';
|
import { CopyBubble } from './CopyBubble';
|
||||||
import { Scrollable } from './Scrollable';
|
|
||||||
import { BackstoryElementProps } from './BackstoryTab';
|
import { BackstoryElementProps } from './BackstoryTab';
|
||||||
import {
|
import {
|
||||||
ChatMessage,
|
ChatMessage,
|
||||||
@ -44,7 +40,10 @@ import {
|
|||||||
ChatSenderType,
|
ChatSenderType,
|
||||||
} from 'types/types';
|
} from 'types/types';
|
||||||
|
|
||||||
const getStyle = (theme: Theme, type: ApiActivityType | ChatSenderType | 'error'): any => {
|
const getStyle = (
|
||||||
|
theme: Theme,
|
||||||
|
type: ApiActivityType | ChatSenderType | 'error'
|
||||||
|
): Record<string, string> => {
|
||||||
const defaultRadius = '16px';
|
const defaultRadius = '16px';
|
||||||
const defaultStyle = {
|
const defaultStyle = {
|
||||||
padding: theme.spacing(1, 2),
|
padding: theme.spacing(1, 2),
|
||||||
@ -65,7 +64,7 @@ const getStyle = (theme: Theme, type: ApiActivityType | ChatSenderType | 'error'
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
const styles: any = {
|
const styles: Record<string, Record<string, string | object | number | undefined> | string> = {
|
||||||
assistant: {
|
assistant: {
|
||||||
...defaultStyle,
|
...defaultStyle,
|
||||||
backgroundColor: theme.palette.primary.main,
|
backgroundColor: theme.palette.primary.main,
|
||||||
@ -172,13 +171,13 @@ const getStyle = (theme: Theme, type: ApiActivityType | ChatSenderType | 'error'
|
|||||||
console.log(`Style does not exist for: ${type}`);
|
console.log(`Style does not exist for: ${type}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
return styles[type];
|
return styles[type] as Record<string, string>;
|
||||||
};
|
};
|
||||||
|
|
||||||
const getIcon = (
|
const getIcon = (
|
||||||
activityType: ApiActivityType | ChatSenderType | 'error'
|
activityType: ApiActivityType | ChatSenderType | 'error'
|
||||||
): React.ReactNode | null => {
|
): React.ReactNode | null => {
|
||||||
const icons: any = {
|
const icons: Record<string, React.ReactNode> = {
|
||||||
error: <ErrorOutline color="error" />,
|
error: <ErrorOutline color="error" />,
|
||||||
generating: <LocationSearchingIcon />,
|
generating: <LocationSearchingIcon />,
|
||||||
information: <InfoOutline color="info" />,
|
information: <InfoOutline color="info" />,
|
||||||
@ -207,7 +206,7 @@ interface MessageMetaProps {
|
|||||||
messageProps: MessageProps;
|
messageProps: MessageProps;
|
||||||
}
|
}
|
||||||
|
|
||||||
const MessageMeta = (props: MessageMetaProps) => {
|
const MessageMeta = (props: MessageMetaProps): JSX.Element => {
|
||||||
const {
|
const {
|
||||||
/* MessageData */
|
/* MessageData */
|
||||||
ragResults = [],
|
ragResults = [],
|
||||||
@ -217,11 +216,11 @@ const MessageMeta = (props: MessageMetaProps) => {
|
|||||||
promptEvalCount = 0,
|
promptEvalCount = 0,
|
||||||
promptEvalDuration = 0,
|
promptEvalDuration = 0,
|
||||||
} = props.metadata || {};
|
} = props.metadata || {};
|
||||||
const message: any = props.messageProps.message;
|
const message: ChatMessage | ChatMessageError | ChatMessageStatus = props.messageProps.message;
|
||||||
|
|
||||||
let llm_submission = '<|system|>\n';
|
// let llm_submission = '<|system|>\n';
|
||||||
llm_submission += message.system_prompt + '\n\n';
|
// llm_submission += message.system_prompt + '\n\n';
|
||||||
llm_submission += message.context_prompt;
|
// llm_submission += message.context_prompt;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
@ -280,13 +279,14 @@ const MessageMeta = (props: MessageMetaProps) => {
|
|||||||
</TableContainer>
|
</TableContainer>
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
{tools && tools.tool_calls && tools.tool_calls.length !== 0 && (
|
{tools && tools.toolCalls && tools.toolCalls.length !== 0 && (
|
||||||
<Accordion sx={{ boxSizing: 'border-box' }}>
|
<Accordion sx={{ boxSizing: 'border-box' }}>
|
||||||
<AccordionSummary expandIcon={<ExpandMoreIcon />}>
|
<AccordionSummary expandIcon={<ExpandMoreIcon />}>
|
||||||
<Box sx={{ fontSize: '0.8rem' }}>Tools queried</Box>
|
<Box sx={{ fontSize: '0.8rem' }}>Tools queried</Box>
|
||||||
</AccordionSummary>
|
</AccordionSummary>
|
||||||
<AccordionDetails>
|
<AccordionDetails>
|
||||||
{tools.tool_calls.map((tool: any, index: number) => (
|
{tools.toolCalls.map(
|
||||||
|
(tool: Record<string, string>, index: number): React.ReactNode => (
|
||||||
<Box
|
<Box
|
||||||
key={index}
|
key={index}
|
||||||
sx={{
|
sx={{
|
||||||
@ -324,7 +324,7 @@ const MessageMeta = (props: MessageMetaProps) => {
|
|||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<JsonView.String
|
<JsonView.String
|
||||||
render={({ children, ...reset }) => {
|
render={({ children, ...reset }): React.ReactNode => {
|
||||||
if (typeof children === 'string' && children.match('\n')) {
|
if (typeof children === 'string' && children.match('\n')) {
|
||||||
return (
|
return (
|
||||||
<pre
|
<pre
|
||||||
@ -345,7 +345,8 @@ const MessageMeta = (props: MessageMetaProps) => {
|
|||||||
)}
|
)}
|
||||||
{tool.content === 'null' && 'No response from tool call'}
|
{tool.content === 'null' && 'No response from tool call'}
|
||||||
</Box>
|
</Box>
|
||||||
))}
|
)
|
||||||
|
)}
|
||||||
</AccordionDetails>
|
</AccordionDetails>
|
||||||
</Accordion>
|
</Accordion>
|
||||||
)}
|
)}
|
||||||
@ -369,9 +370,9 @@ const MessageMeta = (props: MessageMetaProps) => {
|
|||||||
<Box sx={{ fontSize: '0.8rem' }}>Full Response Details</Box>
|
<Box sx={{ fontSize: '0.8rem' }}>Full Response Details</Box>
|
||||||
</AccordionSummary>
|
</AccordionSummary>
|
||||||
<AccordionDetails>
|
<AccordionDetails>
|
||||||
<Box sx={{ pb: 1 }}>
|
{/* <Box sx={{ pb: 1 }}>
|
||||||
Copy LLM submission: <CopyBubble content={llm_submission} />
|
Copy LLM submission: <CopyBubble content={llm_submission} />
|
||||||
</Box>
|
</Box> */}
|
||||||
<JsonView
|
<JsonView
|
||||||
displayDataTypes={false}
|
displayDataTypes={false}
|
||||||
objectSortKeys={true}
|
objectSortKeys={true}
|
||||||
@ -380,7 +381,7 @@ const MessageMeta = (props: MessageMetaProps) => {
|
|||||||
style={{ fontSize: '0.8rem', maxHeight: '20rem', overflow: 'auto' }}
|
style={{ fontSize: '0.8rem', maxHeight: '20rem', overflow: 'auto' }}
|
||||||
>
|
>
|
||||||
<JsonView.String
|
<JsonView.String
|
||||||
render={({ children, ...reset }) => {
|
render={({ children, ...reset }): React.ReactNode => {
|
||||||
if (typeof children === 'string' && children.match('\n')) {
|
if (typeof children === 'string' && children.match('\n')) {
|
||||||
return (
|
return (
|
||||||
<pre
|
<pre
|
||||||
@ -412,7 +413,7 @@ interface MessageContainerProps {
|
|||||||
copyContent?: string;
|
copyContent?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
const MessageContainer = (props: MessageContainerProps) => {
|
const MessageContainer = (props: MessageContainerProps): JSX.Element => {
|
||||||
const { type, sx, messageView, metadataView, copyContent } = props;
|
const { type, sx, messageView, metadataView, copyContent } = props;
|
||||||
const icon = getIcon(type);
|
const icon = getIcon(type);
|
||||||
|
|
||||||
@ -458,7 +459,7 @@ const MessageContainer = (props: MessageContainerProps) => {
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
const Message = (props: MessageProps) => {
|
const Message = (props: MessageProps): JSX.Element => {
|
||||||
const { message, title, sx, className, chatSession, onExpand, expanded, expandable } = props;
|
const { message, title, sx, className, chatSession, onExpand, expanded, expandable } = props;
|
||||||
const [metaExpanded, setMetaExpanded] = useState<boolean>(false);
|
const [metaExpanded, setMetaExpanded] = useState<boolean>(false);
|
||||||
const theme = useTheme();
|
const theme = useTheme();
|
||||||
@ -468,9 +469,9 @@ const Message = (props: MessageProps) => {
|
|||||||
: 'error' in message
|
: 'error' in message
|
||||||
? 'error'
|
? 'error'
|
||||||
: (message as ChatMessage).role;
|
: (message as ChatMessage).role;
|
||||||
const style: any = getStyle(theme, type);
|
const style = getStyle(theme, type);
|
||||||
|
|
||||||
const handleMetaExpandClick = () => {
|
const handleMetaExpandClick = (): void => {
|
||||||
setMetaExpanded(!metaExpanded);
|
setMetaExpanded(!metaExpanded);
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -563,7 +564,7 @@ const Message = (props: MessageProps) => {
|
|||||||
expanded={isControlled ? expanded : undefined} // Omit expanded prop for uncontrolled
|
expanded={isControlled ? expanded : undefined} // Omit expanded prop for uncontrolled
|
||||||
defaultExpanded={expanded} // Default to collapsed for uncontrolled Accordion
|
defaultExpanded={expanded} // Default to collapsed for uncontrolled Accordion
|
||||||
className={className}
|
className={className}
|
||||||
onChange={(_event, newExpanded) => {
|
onChange={(_event, newExpanded): void => {
|
||||||
isControlled && onExpand && onExpand(newExpanded);
|
isControlled && onExpand && onExpand(newExpanded);
|
||||||
}}
|
}}
|
||||||
sx={{ ...sx, ...style }}
|
sx={{ ...sx, ...style }}
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import React from 'react';
|
import React, { JSX } from 'react';
|
||||||
import { Box, Typography, Paper, SxProps } from '@mui/material';
|
import { Box, Typography, Paper, SxProps } from '@mui/material';
|
||||||
import { styled } from '@mui/material/styles';
|
import { styled } from '@mui/material/styles';
|
||||||
|
|
||||||
@ -30,7 +30,7 @@ const QuoteContainer = styled(Paper, {
|
|||||||
|
|
||||||
const QuoteText = styled(Typography, {
|
const QuoteText = styled(Typography, {
|
||||||
shouldForwardProp: prop => prop !== 'size',
|
shouldForwardProp: prop => prop !== 'size',
|
||||||
})<QuoteContainerProps>(({ theme, size = 'normal' }) => ({
|
})<QuoteContainerProps>(({ size = 'normal' }) => ({
|
||||||
fontSize: size === 'small' ? '0.9rem' : '1.2rem',
|
fontSize: size === 'small' ? '0.9rem' : '1.2rem',
|
||||||
lineHeight: size === 'small' ? 1.4 : 1.6,
|
lineHeight: size === 'small' ? 1.4 : 1.6,
|
||||||
fontStyle: 'italic',
|
fontStyle: 'italic',
|
||||||
@ -44,7 +44,7 @@ const QuoteText = styled(Typography, {
|
|||||||
|
|
||||||
const QuoteMark = styled(Typography, {
|
const QuoteMark = styled(Typography, {
|
||||||
shouldForwardProp: prop => prop !== 'size',
|
shouldForwardProp: prop => prop !== 'size',
|
||||||
})<QuoteContainerProps>(({ theme, size = 'normal' }) => ({
|
})<QuoteContainerProps>(({ size = 'normal' }) => ({
|
||||||
fontSize: size === 'small' ? '2.5rem' : '4rem',
|
fontSize: size === 'small' ? '2.5rem' : '4rem',
|
||||||
fontFamily: '"Georgia", "Times New Roman", serif',
|
fontFamily: '"Georgia", "Times New Roman", serif',
|
||||||
fontWeight: 'bold',
|
fontWeight: 'bold',
|
||||||
@ -83,7 +83,7 @@ const AuthorText = styled(Typography, {
|
|||||||
|
|
||||||
const AccentLine = styled(Box, {
|
const AccentLine = styled(Box, {
|
||||||
shouldForwardProp: prop => prop !== 'size',
|
shouldForwardProp: prop => prop !== 'size',
|
||||||
})<QuoteContainerProps>(({ theme, size = 'normal' }) => ({
|
})<QuoteContainerProps>(({ size = 'normal' }) => ({
|
||||||
width: size === 'small' ? '40px' : '60px',
|
width: size === 'small' ? '40px' : '60px',
|
||||||
height: size === 'small' ? '1px' : '2px',
|
height: size === 'small' ? '1px' : '2px',
|
||||||
background: 'linear-gradient(90deg, #D4A017 0%, #4A7A7D 100%)', // Golden Ochre to Dusty Teal
|
background: 'linear-gradient(90deg, #D4A017 0%, #4A7A7D 100%)', // Golden Ochre to Dusty Teal
|
||||||
@ -98,12 +98,12 @@ interface QuoteProps {
|
|||||||
sx?: SxProps;
|
sx?: SxProps;
|
||||||
}
|
}
|
||||||
|
|
||||||
const Quote = (props: QuoteProps) => {
|
const Quote = (props: QuoteProps): JSX.Element => {
|
||||||
const { quote, author, size = 'normal', sx } = props;
|
const { quote, author, size = 'normal', sx } = props;
|
||||||
return (
|
return (
|
||||||
<QuoteContainer size={size} elevation={0} sx={sx}>
|
<QuoteContainer size={size} elevation={0} sx={sx}>
|
||||||
<OpeningQuote size={size}>"</OpeningQuote>
|
<OpeningQuote size={size}>"</OpeningQuote>
|
||||||
<ClosingQuote size={size}>"</ClosingQuote>
|
<ClosingQuote size={size}>"</ClosingQuote>
|
||||||
|
|
||||||
<Box sx={{ position: 'relative', zIndex: 2 }}>
|
<Box sx={{ position: 'relative', zIndex: 2 }}>
|
||||||
<QuoteText size={size} variant="body1">
|
<QuoteText size={size} variant="body1">
|
||||||
|
@ -1,11 +1,10 @@
|
|||||||
import React, { useState, useCallback, useRef, useEffect } from 'react';
|
import React, { useState, useCallback, useEffect } from 'react';
|
||||||
import { Tabs, Tab, Box, Button, Paper, Typography, LinearProgress } from '@mui/material';
|
import { Tabs, Tab, Box, Button, Paper, Typography, LinearProgress } from '@mui/material';
|
||||||
import { Job, Candidate, SkillAssessment } from 'types/types';
|
import { Job, Candidate, SkillAssessment } from 'types/types';
|
||||||
import { Scrollable } from './Scrollable';
|
import { Scrollable } from './Scrollable';
|
||||||
import { useAuth } from 'hooks/AuthContext';
|
import { useAuth } from 'hooks/AuthContext';
|
||||||
import * as Types from 'types/types';
|
import * as Types from 'types/types';
|
||||||
import { StyledMarkdown } from './StyledMarkdown';
|
import { StyledMarkdown } from './StyledMarkdown';
|
||||||
import { Message } from './Message';
|
|
||||||
import InputIcon from '@mui/icons-material/Input';
|
import InputIcon from '@mui/icons-material/Input';
|
||||||
import TuneIcon from '@mui/icons-material/Tune';
|
import TuneIcon from '@mui/icons-material/Tune';
|
||||||
import ArticleIcon from '@mui/icons-material/Article';
|
import ArticleIcon from '@mui/icons-material/Article';
|
||||||
@ -13,7 +12,6 @@ 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 { setDefaultResultOrder } from 'dns';
|
|
||||||
|
|
||||||
interface ResumeGeneratorProps {
|
interface ResumeGeneratorProps {
|
||||||
job: Job;
|
job: Job;
|
||||||
@ -22,15 +20,6 @@ interface ResumeGeneratorProps {
|
|||||||
onComplete?: (resume: string) => void;
|
onComplete?: (resume: string) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
const defaultMessage: Types.ChatMessageStatus = {
|
|
||||||
status: 'done',
|
|
||||||
type: 'text',
|
|
||||||
sessionId: '',
|
|
||||||
timestamp: new Date(),
|
|
||||||
content: '',
|
|
||||||
activity: 'info',
|
|
||||||
};
|
|
||||||
|
|
||||||
const ResumeGenerator: React.FC<ResumeGeneratorProps> = (props: ResumeGeneratorProps) => {
|
const ResumeGenerator: React.FC<ResumeGeneratorProps> = (props: ResumeGeneratorProps) => {
|
||||||
const { job, candidate, skills, onComplete } = props;
|
const { job, candidate, skills, onComplete } = props;
|
||||||
const { setSnack } = useAppState();
|
const { setSnack } = useAppState();
|
||||||
@ -44,7 +33,7 @@ const ResumeGenerator: React.FC<ResumeGeneratorProps> = (props: ResumeGeneratorP
|
|||||||
const [statusType, setStatusType] = useState<Types.ApiActivityType | null>(null);
|
const [statusType, setStatusType] = useState<Types.ApiActivityType | null>(null);
|
||||||
const [error, setError] = useState<Types.ChatMessageError | null>(null);
|
const [error, setError] = useState<Types.ChatMessageError | null>(null);
|
||||||
|
|
||||||
const handleTabChange = (event: React.SyntheticEvent, newValue: string) => {
|
const handleTabChange = (event: React.SyntheticEvent, newValue: string): void => {
|
||||||
setTabValue(newValue);
|
setTabValue(newValue);
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -88,17 +77,32 @@ const ResumeGenerator: React.FC<ResumeGeneratorProps> = (props: ResumeGeneratorP
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
const generateResume = async () => {
|
const generateResume = async (): Promise<void> => {
|
||||||
const request: any = await apiClient.generateResume(
|
const request = await apiClient.generateResume(
|
||||||
candidate.id || '',
|
candidate.id || '',
|
||||||
job.id || '',
|
job.id || '',
|
||||||
generateResumeHandlers
|
generateResumeHandlers
|
||||||
);
|
);
|
||||||
const result = await request.promise;
|
await request.promise;
|
||||||
};
|
};
|
||||||
|
|
||||||
generateResume();
|
generateResume();
|
||||||
}, [job, candidate, apiClient, resume, skills, generated, setSystemPrompt, setPrompt, setResume]);
|
}, [
|
||||||
|
job,
|
||||||
|
candidate,
|
||||||
|
apiClient,
|
||||||
|
resume,
|
||||||
|
skills,
|
||||||
|
generated,
|
||||||
|
status,
|
||||||
|
setSystemPrompt,
|
||||||
|
setPrompt,
|
||||||
|
setResume,
|
||||||
|
onComplete,
|
||||||
|
setStatus,
|
||||||
|
setStatusType,
|
||||||
|
setError,
|
||||||
|
]);
|
||||||
|
|
||||||
const handleSave = useCallback(async () => {
|
const handleSave = useCallback(async () => {
|
||||||
if (!resume) {
|
if (!resume) {
|
||||||
@ -159,7 +163,7 @@ const ResumeGenerator: React.FC<ResumeGeneratorProps> = (props: ResumeGeneratorP
|
|||||||
{tabValue === 'resume' && (
|
{tabValue === 'resume' && (
|
||||||
<>
|
<>
|
||||||
<CopyBubble
|
<CopyBubble
|
||||||
onClick={() => {
|
onClick={(): void => {
|
||||||
setSnack('Resume copied to clipboard!');
|
setSnack('Resume copied to clipboard!');
|
||||||
}}
|
}}
|
||||||
sx={{ position: 'absolute', top: 0, right: 0 }}
|
sx={{ position: 'absolute', top: 0, right: 0 }}
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
|
import React from 'react';
|
||||||
import Box from '@mui/material/Box';
|
import Box from '@mui/material/Box';
|
||||||
import { SxProps, Theme } from '@mui/material';
|
import { SxProps, Theme } from '@mui/material';
|
||||||
import { RefObject, useRef, forwardRef, useImperativeHandle } from 'react';
|
import { RefObject, useRef, forwardRef } from 'react';
|
||||||
import { useAutoScrollToBottom } from '../hooks/useAutoScrollToBottom';
|
import { useAutoScrollToBottom } from '../hooks/useAutoScrollToBottom';
|
||||||
|
|
||||||
interface ScrollableProps {
|
interface ScrollableProps {
|
||||||
@ -9,27 +10,17 @@ interface ScrollableProps {
|
|||||||
autoscroll?: boolean;
|
autoscroll?: boolean;
|
||||||
textFieldRef?: RefObject<HTMLElement | null>; // Reference to the element that triggers auto-scroll
|
textFieldRef?: RefObject<HTMLElement | null>; // Reference to the element that triggers auto-scroll
|
||||||
fallbackThreshold?: number;
|
fallbackThreshold?: number;
|
||||||
contentUpdateTrigger?: any;
|
|
||||||
className?: string;
|
className?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
const Scrollable = forwardRef((props: ScrollableProps, ref) => {
|
const Scrollable = forwardRef((props: ScrollableProps, ref) => {
|
||||||
const {
|
const { sx, className, children, autoscroll, textFieldRef, fallbackThreshold = 0.33 } = props;
|
||||||
sx,
|
|
||||||
className,
|
|
||||||
children,
|
|
||||||
autoscroll,
|
|
||||||
textFieldRef,
|
|
||||||
fallbackThreshold = 0.33,
|
|
||||||
contentUpdateTrigger,
|
|
||||||
} = props;
|
|
||||||
// Create a default ref if textFieldRef is not provided
|
// Create a default ref if textFieldRef is not provided
|
||||||
const defaultTextFieldRef = useRef<HTMLElement | null>(null);
|
const defaultTextFieldRef = useRef<HTMLElement | null>(null);
|
||||||
const scrollRef = useAutoScrollToBottom(
|
const scrollRef = useAutoScrollToBottom(
|
||||||
textFieldRef ?? defaultTextFieldRef,
|
textFieldRef ?? defaultTextFieldRef,
|
||||||
true,
|
true,
|
||||||
fallbackThreshold,
|
fallbackThreshold
|
||||||
contentUpdateTrigger
|
|
||||||
);
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@ -52,5 +43,5 @@ const Scrollable = forwardRef((props: ScrollableProps, ref) => {
|
|||||||
</Box>
|
</Box>
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
Scrollable.displayName = 'Scrollable';
|
||||||
export { useAutoScrollToBottom, Scrollable };
|
export { useAutoScrollToBottom, Scrollable };
|
||||||
|
@ -35,12 +35,15 @@ const Snack = forwardRef<SnackHandle, SnackProps>(({ className, sx }: SnackProps
|
|||||||
);
|
);
|
||||||
|
|
||||||
useImperativeHandle(ref, () => ({
|
useImperativeHandle(ref, () => ({
|
||||||
setSnack: (message: string, severity?: SeverityType) => {
|
setSnack: (message: string, severity?: SeverityType): void => {
|
||||||
setSnack(message, severity);
|
setSnack(message, severity);
|
||||||
},
|
},
|
||||||
}));
|
}));
|
||||||
|
|
||||||
const handleSnackClose = (event: React.SyntheticEvent | Event, reason?: SnackbarCloseReason) => {
|
const handleSnackClose = (
|
||||||
|
event: React.SyntheticEvent | Event,
|
||||||
|
reason?: SnackbarCloseReason
|
||||||
|
): void => {
|
||||||
if (reason === 'clickaway') {
|
if (reason === 'clickaway') {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -62,7 +65,7 @@ const Snack = forwardRef<SnackHandle, SnackProps>(({ className, sx }: SnackProps
|
|||||||
</Snackbar>
|
</Snackbar>
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
Snack.displayName = 'Snack';
|
||||||
export type { SeverityType, SetSnackType };
|
export type { SeverityType, SetSnackType };
|
||||||
|
|
||||||
export { Snack };
|
export { Snack };
|
||||||
|
@ -1,8 +1,8 @@
|
|||||||
import React from 'react';
|
import React, { JSX } from 'react';
|
||||||
import { MuiMarkdown } from 'mui-markdown';
|
import { MuiMarkdown, Overrides } from 'mui-markdown';
|
||||||
import { useTheme } from '@mui/material/styles';
|
import { useTheme } from '@mui/material/styles';
|
||||||
import { Link } from '@mui/material';
|
import { Link } from '@mui/material';
|
||||||
import { BackstoryQuery, BackstoryQueryInterface } from 'components/BackstoryQuery';
|
import { BackstoryQuery } from 'components/BackstoryQuery';
|
||||||
import Box from '@mui/material/Box';
|
import Box from '@mui/material/Box';
|
||||||
import JsonView from '@uiw/react-json-view';
|
import JsonView from '@uiw/react-json-view';
|
||||||
import { vscodeTheme } from '@uiw/react-json-view/vscode';
|
import { vscodeTheme } from '@uiw/react-json-view/vscode';
|
||||||
@ -13,7 +13,7 @@ import { GenerateImage } from 'components/GenerateImage';
|
|||||||
|
|
||||||
import './StyledMarkdown.css';
|
import './StyledMarkdown.css';
|
||||||
import { BackstoryElementProps } from './BackstoryTab';
|
import { BackstoryElementProps } from './BackstoryTab';
|
||||||
import { CandidateQuestion, ChatQuery, ChatSession } from 'types/types';
|
import { ChatSession } from 'types/types';
|
||||||
import { ChatSubmitQueryInterface } from 'components/BackstoryQuery';
|
import { ChatSubmitQueryInterface } from 'components/BackstoryQuery';
|
||||||
|
|
||||||
interface StyledMarkdownProps extends BackstoryElementProps {
|
interface StyledMarkdownProps extends BackstoryElementProps {
|
||||||
@ -28,21 +28,22 @@ const StyledMarkdown: React.FC<StyledMarkdownProps> = (props: StyledMarkdownProp
|
|||||||
const { className, content, chatSession, submitQuery, sx, streaming } = props;
|
const { className, content, chatSession, submitQuery, sx, streaming } = props;
|
||||||
const theme = useTheme();
|
const theme = useTheme();
|
||||||
|
|
||||||
const overrides: any = {
|
const overrides: Overrides = {
|
||||||
|
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||||
p: {
|
p: {
|
||||||
component: (element: any) => {
|
component: (element: any): JSX.Element => {
|
||||||
return <div>{element.children}</div>;
|
return <div>{element.children}</div>;
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
pre: {
|
pre: {
|
||||||
component: (element: any) => {
|
component: (element: any): JSX.Element => {
|
||||||
const { className } = element.children.props;
|
const { className } = element.children.props;
|
||||||
const content = element.children?.props?.children || '';
|
const content = element.children?.props?.children || '';
|
||||||
if (className === 'lang-mermaid' && !streaming) {
|
if (className === 'lang-mermaid' && !streaming) {
|
||||||
return <Mermaid className="Mermaid" chart={content} />;
|
return <Mermaid className="Mermaid" chart={content} />;
|
||||||
}
|
}
|
||||||
if (className === 'lang-markdown') {
|
if (className === 'lang-markdown') {
|
||||||
return <MuiMarkdown children={content} />;
|
return <MuiMarkdown>{content}</MuiMarkdown>;
|
||||||
}
|
}
|
||||||
if (className === 'lang-json' && !streaming) {
|
if (className === 'lang-json' && !streaming) {
|
||||||
try {
|
try {
|
||||||
@ -68,7 +69,7 @@ const StyledMarkdown: React.FC<StyledMarkdownProps> = (props: StyledMarkdownProp
|
|||||||
value={fixed}
|
value={fixed}
|
||||||
>
|
>
|
||||||
<JsonView.String
|
<JsonView.String
|
||||||
render={({ children, ...reset }) => {
|
render={({ children, ...reset }): JSX.Element => {
|
||||||
if (typeof children === 'string' && children.match('\n')) {
|
if (typeof children === 'string' && children.match('\n')) {
|
||||||
return (
|
return (
|
||||||
<pre
|
<pre
|
||||||
@ -83,6 +84,7 @@ const StyledMarkdown: React.FC<StyledMarkdownProps> = (props: StyledMarkdownProp
|
|||||||
</pre>
|
</pre>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
return <></>;
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</JsonView>
|
</JsonView>
|
||||||
@ -103,10 +105,11 @@ const StyledMarkdown: React.FC<StyledMarkdownProps> = (props: StyledMarkdownProp
|
|||||||
);
|
);
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
/* eslint-enable @typescript-eslint/no-explicit-any */
|
||||||
a: {
|
a: {
|
||||||
component: Link,
|
component: Link,
|
||||||
props: {
|
props: {
|
||||||
onClick: (event: React.MouseEvent<HTMLAnchorElement>) => {
|
onClick: (event: React.MouseEvent<HTMLAnchorElement>): void => {
|
||||||
const href = event.currentTarget.getAttribute('href');
|
const href = event.currentTarget.getAttribute('href');
|
||||||
console.log('StyledMarkdown onClick:', href);
|
console.log('StyledMarkdown onClick:', href);
|
||||||
if (href) {
|
if (href) {
|
||||||
@ -127,14 +130,17 @@ const StyledMarkdown: React.FC<StyledMarkdownProps> = (props: StyledMarkdownProp
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
GenerateImage: {
|
||||||
|
component: (_props: { prompt: string }) => <></>,
|
||||||
|
},
|
||||||
BackstoryQuery: {
|
BackstoryQuery: {
|
||||||
component: (props: { query: string }) => {
|
component: (props: { query: string }) => {
|
||||||
const queryString = props.query.replace(/(\w+):/g, '"$1":');
|
const queryString = props.query.replace(/(\w+):/g, '"$1":');
|
||||||
try {
|
try {
|
||||||
const query = JSON.parse(queryString);
|
const query = JSON.parse(queryString);
|
||||||
const backstoryQuestion: CandidateQuestion = {
|
// const backstoryQuestion: CandidateQuestion = {
|
||||||
question: queryString,
|
// question: queryString,
|
||||||
};
|
// };
|
||||||
|
|
||||||
return submitQuery ? (
|
return submitQuery ? (
|
||||||
<BackstoryQuery submitQuery={submitQuery} question={query} />
|
<BackstoryQuery submitQuery={submitQuery} question={query} />
|
||||||
@ -151,13 +157,13 @@ const StyledMarkdown: React.FC<StyledMarkdownProps> = (props: StyledMarkdownProp
|
|||||||
|
|
||||||
if (chatSession) {
|
if (chatSession) {
|
||||||
overrides.GenerateImage = {
|
overrides.GenerateImage = {
|
||||||
component: (props: { prompt: string }) => {
|
component: (props: { prompt: string }): JSX.Element => {
|
||||||
const prompt = props.prompt.replace(/(\w+):/g, '"$1":');
|
const prompt = props.prompt.replace(/(\w+):/g, '"$1":');
|
||||||
try {
|
try {
|
||||||
return <GenerateImage {...{ chatSession, prompt }} />;
|
return <GenerateImage {...{ chatSession, prompt }} />;
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.log('StyledMarkdown error:', prompt, e);
|
console.log('StyledMarkdown error:', prompt, e);
|
||||||
return props.prompt;
|
return <>{props.prompt}</>;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
@ -176,7 +182,7 @@ const StyledMarkdown: React.FC<StyledMarkdownProps> = (props: StyledMarkdownProp
|
|||||||
...sx,
|
...sx,
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<MuiMarkdown overrides={overrides} children={content} />
|
<MuiMarkdown overrides={overrides}>{content}</MuiMarkdown>
|
||||||
</Box>
|
</Box>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
@ -22,7 +22,7 @@ import './VectorVisualizer.css';
|
|||||||
import { BackstoryPageProps } from './BackstoryTab';
|
import { BackstoryPageProps } from './BackstoryTab';
|
||||||
import { useAuth } from 'hooks/AuthContext';
|
import { useAuth } from 'hooks/AuthContext';
|
||||||
import * as Types from 'types/types';
|
import * as Types from 'types/types';
|
||||||
import { useAppState, useSelectedCandidate } from 'hooks/GlobalContext';
|
import { useAppState } from 'hooks/GlobalContext';
|
||||||
import { useNavigate } from 'react-router-dom';
|
import { useNavigate } from 'react-router-dom';
|
||||||
|
|
||||||
interface VectorVisualizerProps extends BackstoryPageProps {
|
interface VectorVisualizerProps extends BackstoryPageProps {
|
||||||
@ -30,12 +30,13 @@ interface VectorVisualizerProps extends BackstoryPageProps {
|
|||||||
rag?: Types.ChromaDBGetResponse;
|
rag?: Types.ChromaDBGetResponse;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface Metadata {
|
// interface Metadata {
|
||||||
id: string;
|
// id: string;
|
||||||
docType: string;
|
// docType: string;
|
||||||
content: string;
|
// content: string;
|
||||||
distance?: number;
|
// distance?: number;
|
||||||
}
|
// }
|
||||||
|
type Metadata = Record<string, string | number>;
|
||||||
|
|
||||||
const emptyQuerySet: Types.ChromaDBGetResponse = {
|
const emptyQuerySet: Types.ChromaDBGetResponse = {
|
||||||
ids: [],
|
ids: [],
|
||||||
@ -50,13 +51,21 @@ const emptyQuerySet: Types.ChromaDBGetResponse = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
interface PlotData {
|
interface PlotData {
|
||||||
|
name: string;
|
||||||
|
mode: string;
|
||||||
|
type: string;
|
||||||
x: number[];
|
x: number[];
|
||||||
y: number[];
|
y: number[];
|
||||||
z?: number[];
|
z?: number[];
|
||||||
colors: string[];
|
|
||||||
text: string[];
|
text: string[];
|
||||||
sizes: number[];
|
marker: {
|
||||||
|
color: string[];
|
||||||
|
size: number[];
|
||||||
|
symbol: string;
|
||||||
|
opacity: number;
|
||||||
|
};
|
||||||
customdata: Metadata[];
|
customdata: Metadata[];
|
||||||
|
hovertemplate: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
const config: Partial<Plotly.Config> = {
|
const config: Partial<Plotly.Config> = {
|
||||||
@ -184,7 +193,7 @@ const VectorVisualizer: React.FC<VectorVisualizerProps> = (props: VectorVisualiz
|
|||||||
const { user, apiClient } = useAuth();
|
const { user, apiClient } = useAuth();
|
||||||
const { rag, inline, sx } = props;
|
const { rag, inline, sx } = props;
|
||||||
const { setSnack } = useAppState();
|
const { setSnack } = useAppState();
|
||||||
const [plotData, setPlotData] = useState<PlotData | null>(null);
|
const [plotData, setPlotData] = useState<PlotData[] | null>(null);
|
||||||
const [newQuery, setNewQuery] = useState<string>('');
|
const [newQuery, setNewQuery] = useState<string>('');
|
||||||
const [querySet, setQuerySet] = useState<Types.ChromaDBGetResponse>(rag || emptyQuerySet);
|
const [querySet, setQuerySet] = useState<Types.ChromaDBGetResponse>(rag || emptyQuerySet);
|
||||||
const [result, setResult] = useState<Types.ChromaDBGetResponse | null>(null);
|
const [result, setResult] = useState<Types.ChromaDBGetResponse | null>(null);
|
||||||
@ -206,7 +215,7 @@ const VectorVisualizer: React.FC<VectorVisualizerProps> = (props: VectorVisualiz
|
|||||||
if (!boxRef.current) {
|
if (!boxRef.current) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const resize = () => {
|
const resize = (): void => {
|
||||||
requestAnimationFrame(() => {
|
requestAnimationFrame(() => {
|
||||||
const plotContainer = document.querySelector('.plot-container') as HTMLElement;
|
const plotContainer = document.querySelector('.plot-container') as HTMLElement;
|
||||||
const svgContainer = document?.querySelector('.svg-container') as HTMLElement;
|
const svgContainer = document?.querySelector('.svg-container') as HTMLElement;
|
||||||
@ -234,7 +243,7 @@ const VectorVisualizer: React.FC<VectorVisualizerProps> = (props: VectorVisualiz
|
|||||||
if (result) {
|
if (result) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const fetchCollection = async () => {
|
const fetchCollection = async (): Promise<void> => {
|
||||||
if (!candidate) {
|
if (!candidate) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -248,7 +257,7 @@ const VectorVisualizer: React.FC<VectorVisualizerProps> = (props: VectorVisualiz
|
|||||||
};
|
};
|
||||||
|
|
||||||
fetchCollection();
|
fetchCollection();
|
||||||
}, [result, setSnack, view2D]);
|
}, [result, setSnack, view2D, apiClient, candidate]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!result || !result.embeddings) return;
|
if (!result || !result.embeddings) return;
|
||||||
@ -349,7 +358,7 @@ const VectorVisualizer: React.FC<VectorVisualizerProps> = (props: VectorVisualiz
|
|||||||
const query_docTypes = query.metadatas.map(m => m.docType || 'unknown');
|
const query_docTypes = query.metadatas.map(m => m.docType || 'unknown');
|
||||||
|
|
||||||
const has_query = query.metadatas.length > 0;
|
const has_query = query.metadatas.length > 0;
|
||||||
const filtered_sizes = filtered.metadatas.map(m =>
|
const filtered_sizes = filtered.metadatas.map(_m =>
|
||||||
has_query ? DEFAULT_UNFOCUS_SIZE : DEFAULT_SIZE
|
has_query ? DEFAULT_UNFOCUS_SIZE : DEFAULT_SIZE
|
||||||
);
|
);
|
||||||
const filtered_colors = filtered_docTypes.map(type => colorMap[type] || '#4d4d4d');
|
const filtered_colors = filtered_docTypes.map(type => colorMap[type] || '#4d4d4d');
|
||||||
@ -369,7 +378,7 @@ const VectorVisualizer: React.FC<VectorVisualizerProps> = (props: VectorVisualiz
|
|||||||
? normalizeDimension(query.embeddings.map((v: number[]) => v[2]))
|
? normalizeDimension(query.embeddings.map((v: number[]) => v[2]))
|
||||||
: undefined;
|
: undefined;
|
||||||
|
|
||||||
const data: any = [
|
const data: PlotData[] = [
|
||||||
{
|
{
|
||||||
name: 'All data',
|
name: 'All data',
|
||||||
x: filtered_x,
|
x: filtered_x,
|
||||||
@ -412,13 +421,13 @@ const VectorVisualizer: React.FC<VectorVisualizerProps> = (props: VectorVisualiz
|
|||||||
setPlotData(data);
|
setPlotData(data);
|
||||||
}, [result, querySet, view2D]);
|
}, [result, querySet, view2D]);
|
||||||
|
|
||||||
const handleKeyPress = (event: any) => {
|
const handleKeyPress = (event: React.KeyboardEvent<HTMLDivElement>): void => {
|
||||||
if (event.key === 'Enter') {
|
if (event.key === 'Enter') {
|
||||||
sendQuery(newQuery);
|
sendQuery(newQuery);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const sendQuery = async (query: string) => {
|
const sendQuery = async (query: string): Promise<void> => {
|
||||||
if (!query.trim()) return;
|
if (!query.trim()) return;
|
||||||
setNewQuery('');
|
setNewQuery('');
|
||||||
|
|
||||||
@ -458,12 +467,19 @@ const VectorVisualizer: React.FC<VectorVisualizerProps> = (props: VectorVisualiz
|
|||||||
>
|
>
|
||||||
<div>
|
<div>
|
||||||
No candidate selected. Please{' '}
|
No candidate selected. Please{' '}
|
||||||
<Button onClick={() => navigate('/find-a-candidate')}>select a candidate</Button> first.
|
<Button
|
||||||
|
onClick={(): void => {
|
||||||
|
navigate('/find-a-candidate');
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
select a candidate
|
||||||
|
</Button>{' '}
|
||||||
|
first.
|
||||||
</div>
|
</div>
|
||||||
</Box>
|
</Box>
|
||||||
);
|
);
|
||||||
|
|
||||||
const fetchRAGMeta = async (node: Node) => {
|
const fetchRAGMeta = async (node: Node): Promise<void> => {
|
||||||
try {
|
try {
|
||||||
const result = await apiClient.getCandidateRAGContent(node.id);
|
const result = await apiClient.getCandidateRAGContent(node.id);
|
||||||
const update: Node = {
|
const update: Node = {
|
||||||
@ -478,8 +494,8 @@ const VectorVisualizer: React.FC<VectorVisualizerProps> = (props: VectorVisualiz
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const onNodeSelected = (metadata: any) => {
|
const onNodeSelected = (metadata: Metadata): void => {
|
||||||
let node: Node;
|
let node: Partial<Node>;
|
||||||
console.log(metadata);
|
console.log(metadata);
|
||||||
if (metadata.docType === 'query') {
|
if (metadata.docType === 'query') {
|
||||||
node = {
|
node = {
|
||||||
@ -503,7 +519,7 @@ The scatter graph shows the query in N-dimensional space, mapped to ${
|
|||||||
backgroundColor: colorMap[metadata.docType] || '#ff8080',
|
backgroundColor: colorMap[metadata.docType] || '#ff8080',
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
setNode(node);
|
setNode(node as Node);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -513,9 +529,9 @@ The scatter graph shows the query in N-dimensional space, mapped to ${
|
|||||||
emoji: emojiMap[metadata.docType] || '❓',
|
emoji: emojiMap[metadata.docType] || '❓',
|
||||||
};
|
};
|
||||||
|
|
||||||
setNode(node);
|
setNode(node as Node);
|
||||||
|
|
||||||
fetchRAGMeta(node);
|
fetchRAGMeta(node as Node);
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@ -552,7 +568,7 @@ The scatter graph shows the query in N-dimensional space, mapped to ${
|
|||||||
flexGrow: 0,
|
flexGrow: 0,
|
||||||
}}
|
}}
|
||||||
control={<Switch checked={!view2D} />}
|
control={<Switch checked={!view2D} />}
|
||||||
onChange={() => {
|
onChange={(): void => {
|
||||||
setView2D(!view2D);
|
setView2D(!view2D);
|
||||||
setResult(null);
|
setResult(null);
|
||||||
}}
|
}}
|
||||||
@ -560,7 +576,7 @@ The scatter graph shows the query in N-dimensional space, mapped to ${
|
|||||||
/>
|
/>
|
||||||
<Plot
|
<Plot
|
||||||
ref={plotlyRef}
|
ref={plotlyRef}
|
||||||
onClick={(event: any) => {
|
onClick={(event: { points: { customdata: Metadata }[] }): void => {
|
||||||
onNodeSelected(event.points[0].customdata);
|
onNodeSelected(event.points[0].customdata);
|
||||||
}}
|
}}
|
||||||
data={plotData}
|
data={plotData}
|
||||||
@ -775,7 +791,9 @@ The scatter graph shows the query in N-dimensional space, mapped to ${
|
|||||||
fullWidth
|
fullWidth
|
||||||
type="text"
|
type="text"
|
||||||
value={newQuery}
|
value={newQuery}
|
||||||
onChange={e => setNewQuery(e.target.value)}
|
onChange={(e): void => {
|
||||||
|
setNewQuery(e.target.value);
|
||||||
|
}}
|
||||||
onKeyDown={handleKeyPress}
|
onKeyDown={handleKeyPress}
|
||||||
placeholder="Enter query to find related documents..."
|
placeholder="Enter query to find related documents..."
|
||||||
id="QueryInput"
|
id="QueryInput"
|
||||||
@ -784,7 +802,7 @@ The scatter graph shows the query in N-dimensional space, mapped to ${
|
|||||||
<Button
|
<Button
|
||||||
sx={{ m: 1 }}
|
sx={{ m: 1 }}
|
||||||
variant="contained"
|
variant="contained"
|
||||||
onClick={() => {
|
onClick={(): void => {
|
||||||
sendQuery(newQuery);
|
sendQuery(newQuery);
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
// components/layout/BackstoryLayout.tsx
|
// components/layout/BackstoryLayout.tsx
|
||||||
import React, { ReactElement, useEffect, useState } from 'react';
|
import React, { JSX, ReactElement, useEffect, useState } from 'react';
|
||||||
import { Outlet, useLocation, Routes, Route } from 'react-router-dom';
|
import { Outlet, useLocation, Routes, Route } from 'react-router-dom';
|
||||||
import { Box, Container, Paper } from '@mui/material';
|
import { Box, Container, Paper } from '@mui/material';
|
||||||
import { useNavigate } from 'react-router-dom';
|
import { useNavigate } from 'react-router-dom';
|
||||||
@ -8,19 +8,17 @@ import { darken } from '@mui/material/styles';
|
|||||||
import { Header } from 'components/layout/Header';
|
import { Header } from 'components/layout/Header';
|
||||||
import { Scrollable } from 'components/Scrollable';
|
import { Scrollable } from 'components/Scrollable';
|
||||||
import { Footer } from 'components/layout/Footer';
|
import { Footer } from 'components/layout/Footer';
|
||||||
import { Snack, SetSnackType } from 'components/Snack';
|
|
||||||
import { User } from 'types/types';
|
|
||||||
import { LoadingComponent } from 'components/LoadingComponent';
|
import { LoadingComponent } from 'components/LoadingComponent';
|
||||||
import { AuthProvider, useAuth, ProtectedRoute } from 'hooks/AuthContext';
|
import { useAuth } from 'hooks/AuthContext';
|
||||||
import { useAppState, useSelectedCandidate } from 'hooks/GlobalContext';
|
|
||||||
import { getMainNavigationItems, getAllRoutes } from 'config/navigationConfig';
|
import { getMainNavigationItems, getAllRoutes } from 'config/navigationConfig';
|
||||||
import { NavigationItem } from 'types/navigation';
|
import { NavigationItem } from 'types/navigation';
|
||||||
|
import { ConversationHandle } from 'components/Conversation';
|
||||||
|
|
||||||
// Legacy type for backward compatibility
|
// Legacy type for backward compatibility
|
||||||
export type NavigationLinkType = {
|
export type NavigationLinkType = {
|
||||||
label: ReactElement<any> | string;
|
label: JSX.Element | string;
|
||||||
path: string;
|
path: string;
|
||||||
icon?: ReactElement<any>;
|
icon?: JSX.Element | string;
|
||||||
};
|
};
|
||||||
|
|
||||||
interface BackstoryPageContainerProps {
|
interface BackstoryPageContainerProps {
|
||||||
@ -28,7 +26,7 @@ interface BackstoryPageContainerProps {
|
|||||||
sx?: SxProps<Theme>;
|
sx?: SxProps<Theme>;
|
||||||
}
|
}
|
||||||
|
|
||||||
const BackstoryPageContainer = (props: BackstoryPageContainerProps) => {
|
const BackstoryPageContainer = (props: BackstoryPageContainerProps): JSX.Element => {
|
||||||
const { children, sx } = props;
|
const { children, sx } = props;
|
||||||
return (
|
return (
|
||||||
<Container
|
<Container
|
||||||
@ -76,12 +74,11 @@ const BackstoryPageContainer = (props: BackstoryPageContainerProps) => {
|
|||||||
|
|
||||||
interface BackstoryLayoutProps {
|
interface BackstoryLayoutProps {
|
||||||
page: string;
|
page: string;
|
||||||
chatRef: React.Ref<any>;
|
chatRef: React.Ref<ConversationHandle>;
|
||||||
}
|
}
|
||||||
|
|
||||||
const BackstoryLayout: React.FC<BackstoryLayoutProps> = (props: BackstoryLayoutProps) => {
|
const BackstoryLayout: React.FC<BackstoryLayoutProps> = (props: BackstoryLayoutProps) => {
|
||||||
const { page, chatRef } = props;
|
const { page, chatRef } = props;
|
||||||
const { setSnack } = useAppState();
|
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
const location = useLocation();
|
const location = useLocation();
|
||||||
const { guest, user } = useAuth();
|
const { guest, user } = useAuth();
|
||||||
@ -93,7 +90,7 @@ const BackstoryLayout: React.FC<BackstoryLayoutProps> = (props: BackstoryLayoutP
|
|||||||
}, [user]);
|
}, [user]);
|
||||||
|
|
||||||
// Generate dynamic routes from navigation config
|
// Generate dynamic routes from navigation config
|
||||||
const generateRoutes = () => {
|
const generateRoutes = (): React.ReactNode | null => {
|
||||||
if (!guest && !user) return null;
|
if (!guest && !user) return null;
|
||||||
|
|
||||||
const userType = user?.userType || null;
|
const userType = user?.userType || null;
|
||||||
|
@ -7,7 +7,6 @@ import { ConversationHandle } from '../Conversation';
|
|||||||
import { User } from 'types/types';
|
import { User } from 'types/types';
|
||||||
import { getAllRoutes } from 'config/navigationConfig';
|
import { getAllRoutes } from 'config/navigationConfig';
|
||||||
import { NavigationItem } from 'types/navigation';
|
import { NavigationItem } from 'types/navigation';
|
||||||
import { useAppState } from 'hooks/GlobalContext';
|
|
||||||
|
|
||||||
interface BackstoryDynamicRoutesProps extends BackstoryPageProps {
|
interface BackstoryDynamicRoutesProps extends BackstoryPageProps {
|
||||||
chatRef: Ref<ConversationHandle>;
|
chatRef: Ref<ConversationHandle>;
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import React from 'react';
|
import React, { JSX } from 'react';
|
||||||
import {
|
import {
|
||||||
Paper,
|
Paper,
|
||||||
Box,
|
Box,
|
||||||
@ -13,11 +13,11 @@ import {
|
|||||||
} from '@mui/material';
|
} from '@mui/material';
|
||||||
import { styled, useTheme } from '@mui/material/styles';
|
import { styled, useTheme } from '@mui/material/styles';
|
||||||
import {
|
import {
|
||||||
Facebook,
|
// Facebook,
|
||||||
Twitter,
|
// Twitter,
|
||||||
LinkedIn,
|
LinkedIn,
|
||||||
Instagram,
|
// Instagram,
|
||||||
YouTube,
|
// YouTube,
|
||||||
Email,
|
Email,
|
||||||
LocationOn,
|
LocationOn,
|
||||||
Copyright,
|
Copyright,
|
||||||
@ -56,7 +56,7 @@ const ContactItem = styled(Box)(({ theme }) => ({
|
|||||||
}));
|
}));
|
||||||
|
|
||||||
// Footer component
|
// Footer component
|
||||||
const Footer = () => {
|
const Footer = (): JSX.Element => {
|
||||||
const theme = useTheme();
|
const theme = useTheme();
|
||||||
const isMobile = useMediaQuery(theme.breakpoints.down('md'));
|
const isMobile = useMediaQuery(theme.breakpoints.down('md'));
|
||||||
const currentYear = new Date().getFullYear();
|
const currentYear = new Date().getFullYear();
|
||||||
@ -125,9 +125,9 @@ const Footer = () => {
|
|||||||
color: theme.palette.action.active,
|
color: theme.palette.action.active,
|
||||||
},
|
},
|
||||||
}}
|
}}
|
||||||
onClick={() =>
|
onClick={(): void => {
|
||||||
window.open('https://www.linkedin.com/in/james-ketrenos/', '_blank')
|
window.open('https://www.linkedin.com/in/james-ketrenos/', '_blank');
|
||||||
}
|
}}
|
||||||
>
|
>
|
||||||
<LinkedIn />
|
<LinkedIn />
|
||||||
</IconButton>
|
</IconButton>
|
||||||
|
@ -108,7 +108,6 @@ const useAutoScrollToBottom = (
|
|||||||
scrollToRef: RefObject<HTMLElement | null>,
|
scrollToRef: RefObject<HTMLElement | null>,
|
||||||
smooth = true,
|
smooth = true,
|
||||||
fallbackThreshold = 0.33,
|
fallbackThreshold = 0.33,
|
||||||
contentUpdateTrigger?: any
|
|
||||||
): RefObject<HTMLDivElement | null> => {
|
): RefObject<HTMLDivElement | null> => {
|
||||||
const containerRef = useRef<HTMLDivElement | null>(null);
|
const containerRef = useRef<HTMLDivElement | null>(null);
|
||||||
const lastScrollTop = useRef(0);
|
const lastScrollTop = useRef(0);
|
||||||
@ -243,7 +242,7 @@ const useAutoScrollToBottom = (
|
|||||||
}
|
}
|
||||||
if (scrollTimeout.current) clearTimeout(scrollTimeout.current);
|
if (scrollTimeout.current) clearTimeout(scrollTimeout.current);
|
||||||
};
|
};
|
||||||
}, [smooth, scrollToRef, fallbackThreshold, contentUpdateTrigger, checkAndScrollToBottom]);
|
}, [smooth, scrollToRef, fallbackThreshold, checkAndScrollToBottom]);
|
||||||
|
|
||||||
// Observe container and TextField size, plus DOM changes
|
// Observe container and TextField size, plus DOM changes
|
||||||
useResizeObserverAndMutationObserver(containerRef, scrollToRef, checkAndScrollToBottom);
|
useResizeObserverAndMutationObserver(containerRef, scrollToRef, checkAndScrollToBottom);
|
||||||
|
@ -1,137 +1,89 @@
|
|||||||
// Generated TypeScript types from Pydantic models
|
// Generated TypeScript types from Pydantic models
|
||||||
// Source: src/backend/models.py
|
// Source: src/backend/models.py
|
||||||
// Generated on: 2025-06-13T18:33:37.696910
|
// Generated on: 2025-06-18T22:54:34.823060
|
||||||
// DO NOT EDIT MANUALLY - This file is auto-generated
|
// DO NOT EDIT MANUALLY - This file is auto-generated
|
||||||
|
|
||||||
// ============================
|
// ============================
|
||||||
// Enums
|
// Enums
|
||||||
// ============================
|
// ============================
|
||||||
|
|
||||||
export type AIModelType = 'qwen2.5' | 'flux-schnell';
|
export type AIModelType = "qwen2.5" | "flux-schnell";
|
||||||
|
|
||||||
export type ActivityType =
|
export type ActivityType = "login" | "search" | "view_job" | "apply_job" | "message" | "update_profile" | "chat";
|
||||||
| 'login'
|
|
||||||
| 'search'
|
|
||||||
| 'view_job'
|
|
||||||
| 'apply_job'
|
|
||||||
| 'message'
|
|
||||||
| 'update_profile'
|
|
||||||
| 'chat';
|
|
||||||
|
|
||||||
export type ApiActivityType =
|
export type ApiActivityType = "system" | "info" | "searching" | "thinking" | "generating" | "converting" | "generating_image" | "tooling" | "heartbeat";
|
||||||
| 'system'
|
|
||||||
| 'info'
|
|
||||||
| 'searching'
|
|
||||||
| 'thinking'
|
|
||||||
| 'generating'
|
|
||||||
| 'converting'
|
|
||||||
| 'generating_image'
|
|
||||||
| 'tooling'
|
|
||||||
| 'heartbeat';
|
|
||||||
|
|
||||||
export type ApiMessageType = 'binary' | 'text' | 'json';
|
export type ApiMessageType = "binary" | "text" | "json";
|
||||||
|
|
||||||
export type ApiStatusType = 'streaming' | 'status' | 'done' | 'error';
|
export type ApiStatusType = "streaming" | "status" | "done" | "error";
|
||||||
|
|
||||||
export type ApplicationStatus =
|
export type ApplicationStatus = "applied" | "reviewing" | "interview" | "offer" | "rejected" | "accepted" | "withdrawn";
|
||||||
| 'applied'
|
|
||||||
| 'reviewing'
|
|
||||||
| 'interview'
|
|
||||||
| 'offer'
|
|
||||||
| 'rejected'
|
|
||||||
| 'accepted'
|
|
||||||
| 'withdrawn';
|
|
||||||
|
|
||||||
export type ChatContextType =
|
export type ChatContextType = "job_search" | "job_requirements" | "candidate_chat" | "interview_prep" | "resume_review" | "general" | "generate_persona" | "generate_profile" | "generate_resume" | "generate_image" | "rag_search" | "skill_match";
|
||||||
| 'job_search'
|
|
||||||
| 'job_requirements'
|
|
||||||
| 'candidate_chat'
|
|
||||||
| 'interview_prep'
|
|
||||||
| 'resume_review'
|
|
||||||
| 'general'
|
|
||||||
| 'generate_persona'
|
|
||||||
| 'generate_profile'
|
|
||||||
| 'generate_resume'
|
|
||||||
| 'generate_image'
|
|
||||||
| 'rag_search'
|
|
||||||
| 'skill_match';
|
|
||||||
|
|
||||||
export type ChatSenderType = 'user' | 'assistant' | 'system' | 'information' | 'warning' | 'error';
|
export type ChatSenderType = "user" | "assistant" | "system" | "information" | "warning" | "error";
|
||||||
|
|
||||||
export type ColorBlindMode = 'protanopia' | 'deuteranopia' | 'tritanopia' | 'none';
|
export type ColorBlindMode = "protanopia" | "deuteranopia" | "tritanopia" | "none";
|
||||||
|
|
||||||
export type DataSourceType = 'document' | 'website' | 'api' | 'database' | 'internal';
|
export type DataSourceType = "document" | "website" | "api" | "database" | "internal";
|
||||||
|
|
||||||
export type DocumentType = 'pdf' | 'docx' | 'txt' | 'markdown' | 'image';
|
export type DocumentType = "pdf" | "docx" | "txt" | "markdown" | "image";
|
||||||
|
|
||||||
export type EmploymentType = 'full-time' | 'part-time' | 'contract' | 'internship' | 'freelance';
|
export type EmploymentType = "full-time" | "part-time" | "contract" | "internship" | "freelance";
|
||||||
|
|
||||||
export type FontSize = 'small' | 'medium' | 'large';
|
export type FontSize = "small" | "medium" | "large";
|
||||||
|
|
||||||
export type InterviewRecommendation = 'strong_hire' | 'hire' | 'no_hire' | 'strong_no_hire';
|
export type InterviewRecommendation = "strong_hire" | "hire" | "no_hire" | "strong_no_hire";
|
||||||
|
|
||||||
export type InterviewType = 'phone' | 'video' | 'onsite' | 'technical' | 'behavioral';
|
export type InterviewType = "phone" | "video" | "onsite" | "technical" | "behavioral";
|
||||||
|
|
||||||
export type LanguageProficiency = 'basic' | 'conversational' | 'fluent' | 'native';
|
export type LanguageProficiency = "basic" | "conversational" | "fluent" | "native";
|
||||||
|
|
||||||
export type MFAMethod = 'app' | 'sms' | 'email';
|
export type MFAMethod = "app" | "sms" | "email";
|
||||||
|
|
||||||
export type NotificationType = 'email' | 'push' | 'in_app';
|
export type NotificationType = "email" | "push" | "in_app";
|
||||||
|
|
||||||
export type ProcessingStepType =
|
export type ProcessingStepType = "extract" | "transform" | "chunk" | "embed" | "filter" | "summarize";
|
||||||
| 'extract'
|
|
||||||
| 'transform'
|
|
||||||
| 'chunk'
|
|
||||||
| 'embed'
|
|
||||||
| 'filter'
|
|
||||||
| 'summarize';
|
|
||||||
|
|
||||||
export type SalaryPeriod = 'hour' | 'day' | 'month' | 'year';
|
export type SalaryPeriod = "hour" | "day" | "month" | "year";
|
||||||
|
|
||||||
export type SearchType = 'similarity' | 'mmr' | 'hybrid' | 'keyword';
|
export type SearchType = "similarity" | "mmr" | "hybrid" | "keyword";
|
||||||
|
|
||||||
export type SkillLevel = 'beginner' | 'intermediate' | 'advanced' | 'expert';
|
export type SkillLevel = "beginner" | "intermediate" | "advanced" | "expert";
|
||||||
|
|
||||||
export type SkillStatus = 'pending' | 'complete' | 'waiting' | 'error';
|
export type SkillStatus = "pending" | "complete" | "waiting" | "error";
|
||||||
|
|
||||||
export type SkillStrength = 'strong' | 'moderate' | 'weak' | 'none';
|
export type SkillStrength = "strong" | "moderate" | "weak" | "none";
|
||||||
|
|
||||||
export type SocialPlatform =
|
export type SocialPlatform = "linkedin" | "twitter" | "github" | "dribbble" | "behance" | "website" | "other";
|
||||||
| 'linkedin'
|
|
||||||
| 'twitter'
|
|
||||||
| 'github'
|
|
||||||
| 'dribbble'
|
|
||||||
| 'behance'
|
|
||||||
| 'website'
|
|
||||||
| 'other';
|
|
||||||
|
|
||||||
export type SortOrder = 'asc' | 'desc';
|
export type SortOrder = "asc" | "desc";
|
||||||
|
|
||||||
export type ThemePreference = 'light' | 'dark' | 'system';
|
export type ThemePreference = "light" | "dark" | "system";
|
||||||
|
|
||||||
export type UserGender = 'female' | 'male';
|
export type UserGender = "female" | "male";
|
||||||
|
|
||||||
export type UserStatus = 'active' | 'inactive' | 'pending' | 'banned';
|
export type UserStatus = "active" | "inactive" | "pending" | "banned";
|
||||||
|
|
||||||
export type UserType = 'candidate' | 'employer' | 'guest';
|
export type UserType = "candidate" | "employer" | "guest";
|
||||||
|
|
||||||
export type VectorStoreType = 'chroma';
|
export type VectorStoreType = "chroma";
|
||||||
|
|
||||||
// ============================
|
// ============================
|
||||||
// Interfaces
|
// Interfaces
|
||||||
// ============================
|
// ============================
|
||||||
|
|
||||||
export interface AccessibilitySettings {
|
export interface AccessibilitySettings {
|
||||||
fontSize: 'small' | 'medium' | 'large';
|
fontSize: "small" | "medium" | "large";
|
||||||
highContrast: boolean;
|
highContrast: boolean;
|
||||||
reduceMotion: boolean;
|
reduceMotion: boolean;
|
||||||
screenReader: boolean;
|
screenReader: boolean;
|
||||||
colorBlindMode?: 'protanopia' | 'deuteranopia' | 'tritanopia' | 'none';
|
colorBlindMode?: "protanopia" | "deuteranopia" | "tritanopia" | "none";
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface Analytics {
|
export interface Analytics {
|
||||||
id?: string;
|
id?: string;
|
||||||
entityType: 'job' | 'candidate' | 'chat' | 'system' | 'employer';
|
entityType: "job" | "candidate" | "chat" | "system" | "employer";
|
||||||
entityId: string;
|
entityId: string;
|
||||||
metricType: string;
|
metricType: string;
|
||||||
value: number;
|
value: number;
|
||||||
@ -144,8 +96,8 @@ export interface ApiMessage {
|
|||||||
id?: string;
|
id?: string;
|
||||||
sessionId: string;
|
sessionId: string;
|
||||||
senderId?: string;
|
senderId?: string;
|
||||||
status: 'streaming' | 'status' | 'done' | 'error';
|
status: "streaming" | "status" | "done" | "error";
|
||||||
type: 'binary' | 'text' | 'json';
|
type: "binary" | "text" | "json";
|
||||||
timestamp?: Date;
|
timestamp?: Date;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -157,7 +109,7 @@ export interface ApiResponse {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export interface ApplicationDecision {
|
export interface ApplicationDecision {
|
||||||
status: 'accepted' | 'rejected';
|
status: "accepted" | "rejected";
|
||||||
reason?: string;
|
reason?: string;
|
||||||
date: Date;
|
date: Date;
|
||||||
by: string;
|
by: string;
|
||||||
@ -193,14 +145,14 @@ export interface Authentication {
|
|||||||
resetPasswordExpiry?: Date;
|
resetPasswordExpiry?: Date;
|
||||||
lastPasswordChange: Date;
|
lastPasswordChange: Date;
|
||||||
mfaEnabled: boolean;
|
mfaEnabled: boolean;
|
||||||
mfaMethod?: 'app' | 'sms' | 'email';
|
mfaMethod?: "app" | "sms" | "email";
|
||||||
mfaSecret?: string;
|
mfaSecret?: string;
|
||||||
loginAttempts: number;
|
loginAttempts: number;
|
||||||
lockedUntil?: Date;
|
lockedUntil?: Date;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface BaseUser {
|
export interface BaseUser {
|
||||||
userType: 'candidate' | 'employer' | 'guest';
|
userType: "candidate" | "employer" | "guest";
|
||||||
id?: string;
|
id?: string;
|
||||||
lastActivity?: Date;
|
lastActivity?: Date;
|
||||||
email: string;
|
email: string;
|
||||||
@ -213,18 +165,18 @@ export interface BaseUser {
|
|||||||
updatedAt?: Date;
|
updatedAt?: Date;
|
||||||
lastLogin?: Date;
|
lastLogin?: Date;
|
||||||
profileImage?: string;
|
profileImage?: string;
|
||||||
status: 'active' | 'inactive' | 'pending' | 'banned';
|
status: "active" | "inactive" | "pending" | "banned";
|
||||||
isAdmin: boolean;
|
isAdmin: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface BaseUserWithType {
|
export interface BaseUserWithType {
|
||||||
userType: 'candidate' | 'employer' | 'guest';
|
userType: "candidate" | "employer" | "guest";
|
||||||
id?: string;
|
id?: string;
|
||||||
lastActivity?: Date;
|
lastActivity?: Date;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface Candidate {
|
export interface Candidate {
|
||||||
userType: 'candidate' | 'employer' | 'guest';
|
userType: "candidate" | "employer" | "guest";
|
||||||
id?: string;
|
id?: string;
|
||||||
lastActivity?: Date;
|
lastActivity?: Date;
|
||||||
email: string;
|
email: string;
|
||||||
@ -237,7 +189,7 @@ export interface Candidate {
|
|||||||
updatedAt?: Date;
|
updatedAt?: Date;
|
||||||
lastLogin?: Date;
|
lastLogin?: Date;
|
||||||
profileImage?: string;
|
profileImage?: string;
|
||||||
status: 'active' | 'inactive' | 'pending' | 'banned';
|
status: "active" | "inactive" | "pending" | "banned";
|
||||||
isAdmin: boolean;
|
isAdmin: boolean;
|
||||||
username: string;
|
username: string;
|
||||||
description?: string;
|
description?: string;
|
||||||
@ -246,7 +198,7 @@ export interface Candidate {
|
|||||||
experience?: Array<WorkExperience>;
|
experience?: Array<WorkExperience>;
|
||||||
questions?: Array<CandidateQuestion>;
|
questions?: Array<CandidateQuestion>;
|
||||||
education?: Array<Education>;
|
education?: Array<Education>;
|
||||||
preferredJobTypes?: Array<'full-time' | 'part-time' | 'contract' | 'internship' | 'freelance'>;
|
preferredJobTypes?: Array<"full-time" | "part-time" | "contract" | "internship" | "freelance">;
|
||||||
desiredSalary?: DesiredSalary;
|
desiredSalary?: DesiredSalary;
|
||||||
availabilityDate?: Date;
|
availabilityDate?: Date;
|
||||||
summary?: string;
|
summary?: string;
|
||||||
@ -259,7 +211,7 @@ export interface Candidate {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export interface CandidateAI {
|
export interface CandidateAI {
|
||||||
userType: 'candidate' | 'employer' | 'guest';
|
userType: "candidate" | "employer" | "guest";
|
||||||
id?: string;
|
id?: string;
|
||||||
lastActivity?: Date;
|
lastActivity?: Date;
|
||||||
email: string;
|
email: string;
|
||||||
@ -272,7 +224,7 @@ export interface CandidateAI {
|
|||||||
updatedAt?: Date;
|
updatedAt?: Date;
|
||||||
lastLogin?: Date;
|
lastLogin?: Date;
|
||||||
profileImage?: string;
|
profileImage?: string;
|
||||||
status: 'active' | 'inactive' | 'pending' | 'banned';
|
status: "active" | "inactive" | "pending" | "banned";
|
||||||
isAdmin: boolean;
|
isAdmin: boolean;
|
||||||
username: string;
|
username: string;
|
||||||
description?: string;
|
description?: string;
|
||||||
@ -281,7 +233,7 @@ export interface CandidateAI {
|
|||||||
experience?: Array<WorkExperience>;
|
experience?: Array<WorkExperience>;
|
||||||
questions?: Array<CandidateQuestion>;
|
questions?: Array<CandidateQuestion>;
|
||||||
education?: Array<Education>;
|
education?: Array<Education>;
|
||||||
preferredJobTypes?: Array<'full-time' | 'part-time' | 'contract' | 'internship' | 'freelance'>;
|
preferredJobTypes?: Array<"full-time" | "part-time" | "contract" | "internship" | "freelance">;
|
||||||
desiredSalary?: DesiredSalary;
|
desiredSalary?: DesiredSalary;
|
||||||
availabilityDate?: Date;
|
availabilityDate?: Date;
|
||||||
summary?: string;
|
summary?: string;
|
||||||
@ -293,7 +245,7 @@ export interface CandidateAI {
|
|||||||
isPublic: boolean;
|
isPublic: boolean;
|
||||||
isAI: boolean;
|
isAI: boolean;
|
||||||
age?: number;
|
age?: number;
|
||||||
gender?: 'female' | 'male';
|
gender?: "female" | "male";
|
||||||
ethnicity?: string;
|
ethnicity?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -332,21 +284,9 @@ export interface Certification {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export interface ChatContext {
|
export interface ChatContext {
|
||||||
type:
|
type: "job_search" | "job_requirements" | "candidate_chat" | "interview_prep" | "resume_review" | "general" | "generate_persona" | "generate_profile" | "generate_resume" | "generate_image" | "rag_search" | "skill_match";
|
||||||
| 'job_search'
|
|
||||||
| 'job_requirements'
|
|
||||||
| 'candidate_chat'
|
|
||||||
| 'interview_prep'
|
|
||||||
| 'resume_review'
|
|
||||||
| 'general'
|
|
||||||
| 'generate_persona'
|
|
||||||
| 'generate_profile'
|
|
||||||
| 'generate_resume'
|
|
||||||
| 'generate_image'
|
|
||||||
| 'rag_search'
|
|
||||||
| 'skill_match';
|
|
||||||
relatedEntityId?: string;
|
relatedEntityId?: string;
|
||||||
relatedEntityType?: 'job' | 'candidate' | 'employer';
|
relatedEntityType?: "job" | "candidate" | "employer";
|
||||||
additionalContext?: Record<string, any>;
|
additionalContext?: Record<string, any>;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -354,10 +294,10 @@ export interface ChatMessage {
|
|||||||
id?: string;
|
id?: string;
|
||||||
sessionId: string;
|
sessionId: string;
|
||||||
senderId?: string;
|
senderId?: string;
|
||||||
status: 'streaming' | 'status' | 'done' | 'error';
|
status: "streaming" | "status" | "done" | "error";
|
||||||
type: 'binary' | 'text' | 'json';
|
type: "binary" | "text" | "json";
|
||||||
timestamp?: Date;
|
timestamp?: Date;
|
||||||
role: 'user' | 'assistant' | 'system' | 'information' | 'warning' | 'error';
|
role: "user" | "assistant" | "system" | "information" | "warning" | "error";
|
||||||
content: string;
|
content: string;
|
||||||
tunables?: Tunables;
|
tunables?: Tunables;
|
||||||
metadata: ChatMessageMetaData;
|
metadata: ChatMessageMetaData;
|
||||||
@ -367,14 +307,14 @@ export interface ChatMessageError {
|
|||||||
id?: string;
|
id?: string;
|
||||||
sessionId: string;
|
sessionId: string;
|
||||||
senderId?: string;
|
senderId?: string;
|
||||||
status: 'streaming' | 'status' | 'done' | 'error';
|
status: "streaming" | "status" | "done" | "error";
|
||||||
type: 'binary' | 'text' | 'json';
|
type: "binary" | "text" | "json";
|
||||||
timestamp?: Date;
|
timestamp?: Date;
|
||||||
content: string;
|
content: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ChatMessageMetaData {
|
export interface ChatMessageMetaData {
|
||||||
model: 'qwen2.5' | 'flux-schnell';
|
model: "qwen2.5" | "flux-schnell";
|
||||||
temperature: number;
|
temperature: number;
|
||||||
maxTokens: number;
|
maxTokens: number;
|
||||||
topP: number;
|
topP: number;
|
||||||
@ -388,7 +328,7 @@ export interface ChatMessageMetaData {
|
|||||||
promptEvalCount: number;
|
promptEvalCount: number;
|
||||||
promptEvalDuration: number;
|
promptEvalDuration: number;
|
||||||
options?: ChatOptions;
|
options?: ChatOptions;
|
||||||
tools?: Record<string, any>;
|
tools?: Tool;
|
||||||
timers?: Record<string, number>;
|
timers?: Record<string, number>;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -396,8 +336,8 @@ export interface ChatMessageRagSearch {
|
|||||||
id?: string;
|
id?: string;
|
||||||
sessionId: string;
|
sessionId: string;
|
||||||
senderId?: string;
|
senderId?: string;
|
||||||
status: 'streaming' | 'status' | 'done' | 'error';
|
status: "streaming" | "status" | "done" | "error";
|
||||||
type: 'binary' | 'text' | 'json';
|
type: "binary" | "text" | "json";
|
||||||
timestamp?: Date;
|
timestamp?: Date;
|
||||||
dimensions: number;
|
dimensions: number;
|
||||||
content: Array<ChromaDBGetResponse>;
|
content: Array<ChromaDBGetResponse>;
|
||||||
@ -407,10 +347,10 @@ export interface ChatMessageResume {
|
|||||||
id?: string;
|
id?: string;
|
||||||
sessionId: string;
|
sessionId: string;
|
||||||
senderId?: string;
|
senderId?: string;
|
||||||
status: 'streaming' | 'status' | 'done' | 'error';
|
status: "streaming" | "status" | "done" | "error";
|
||||||
type: 'binary' | 'text' | 'json';
|
type: "binary" | "text" | "json";
|
||||||
timestamp?: Date;
|
timestamp?: Date;
|
||||||
role: 'user' | 'assistant' | 'system' | 'information' | 'warning' | 'error';
|
role: "user" | "assistant" | "system" | "information" | "warning" | "error";
|
||||||
content: string;
|
content: string;
|
||||||
tunables?: Tunables;
|
tunables?: Tunables;
|
||||||
metadata: ChatMessageMetaData;
|
metadata: ChatMessageMetaData;
|
||||||
@ -423,10 +363,10 @@ export interface ChatMessageSkillAssessment {
|
|||||||
id?: string;
|
id?: string;
|
||||||
sessionId: string;
|
sessionId: string;
|
||||||
senderId?: string;
|
senderId?: string;
|
||||||
status: 'streaming' | 'status' | 'done' | 'error';
|
status: "streaming" | "status" | "done" | "error";
|
||||||
type: 'binary' | 'text' | 'json';
|
type: "binary" | "text" | "json";
|
||||||
timestamp?: Date;
|
timestamp?: Date;
|
||||||
role: 'user' | 'assistant' | 'system' | 'information' | 'warning' | 'error';
|
role: "user" | "assistant" | "system" | "information" | "warning" | "error";
|
||||||
content: string;
|
content: string;
|
||||||
tunables?: Tunables;
|
tunables?: Tunables;
|
||||||
metadata: ChatMessageMetaData;
|
metadata: ChatMessageMetaData;
|
||||||
@ -437,19 +377,10 @@ export interface ChatMessageStatus {
|
|||||||
id?: string;
|
id?: string;
|
||||||
sessionId: string;
|
sessionId: string;
|
||||||
senderId?: string;
|
senderId?: string;
|
||||||
status: 'streaming' | 'status' | 'done' | 'error';
|
status: "streaming" | "status" | "done" | "error";
|
||||||
type: 'binary' | 'text' | 'json';
|
type: "binary" | "text" | "json";
|
||||||
timestamp?: Date;
|
timestamp?: Date;
|
||||||
activity:
|
activity: "system" | "info" | "searching" | "thinking" | "generating" | "converting" | "generating_image" | "tooling" | "heartbeat";
|
||||||
| 'system'
|
|
||||||
| 'info'
|
|
||||||
| 'searching'
|
|
||||||
| 'thinking'
|
|
||||||
| 'generating'
|
|
||||||
| 'converting'
|
|
||||||
| 'generating_image'
|
|
||||||
| 'tooling'
|
|
||||||
| 'heartbeat';
|
|
||||||
content: any;
|
content: any;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -457,8 +388,8 @@ export interface ChatMessageStreaming {
|
|||||||
id?: string;
|
id?: string;
|
||||||
sessionId: string;
|
sessionId: string;
|
||||||
senderId?: string;
|
senderId?: string;
|
||||||
status: 'streaming' | 'status' | 'done' | 'error';
|
status: "streaming" | "status" | "done" | "error";
|
||||||
type: 'binary' | 'text' | 'json';
|
type: "binary" | "text" | "json";
|
||||||
timestamp?: Date;
|
timestamp?: Date;
|
||||||
content: string;
|
content: string;
|
||||||
}
|
}
|
||||||
@ -467,10 +398,10 @@ export interface ChatMessageUser {
|
|||||||
id?: string;
|
id?: string;
|
||||||
sessionId: string;
|
sessionId: string;
|
||||||
senderId?: string;
|
senderId?: string;
|
||||||
status: 'streaming' | 'status' | 'done' | 'error';
|
status: "streaming" | "status" | "done" | "error";
|
||||||
type: 'binary' | 'text' | 'json';
|
type: "binary" | "text" | "json";
|
||||||
timestamp?: Date;
|
timestamp?: Date;
|
||||||
role: 'user' | 'assistant' | 'system' | 'information' | 'warning' | 'error';
|
role: "user" | "assistant" | "system" | "information" | "warning" | "error";
|
||||||
content: string;
|
content: string;
|
||||||
tunables?: Tunables;
|
tunables?: Tunables;
|
||||||
}
|
}
|
||||||
@ -490,6 +421,7 @@ export interface ChatQuery {
|
|||||||
export interface ChatSession {
|
export interface ChatSession {
|
||||||
id?: string;
|
id?: string;
|
||||||
userId?: string;
|
userId?: string;
|
||||||
|
guestId?: string;
|
||||||
createdAt?: Date;
|
createdAt?: Date;
|
||||||
lastActivity?: Date;
|
lastActivity?: Date;
|
||||||
title?: string;
|
title?: string;
|
||||||
@ -543,12 +475,12 @@ export interface DataSourceConfiguration {
|
|||||||
id?: string;
|
id?: string;
|
||||||
ragConfigId: string;
|
ragConfigId: string;
|
||||||
name: string;
|
name: string;
|
||||||
sourceType: 'document' | 'website' | 'api' | 'database' | 'internal';
|
sourceType: "document" | "website" | "api" | "database" | "internal";
|
||||||
connectionDetails: Record<string, any>;
|
connectionDetails: Record<string, any>;
|
||||||
processingPipeline: Array<ProcessingStep>;
|
processingPipeline: Array<ProcessingStep>;
|
||||||
refreshSchedule?: string;
|
refreshSchedule?: string;
|
||||||
lastRefreshed?: Date;
|
lastRefreshed?: Date;
|
||||||
status: 'active' | 'pending' | 'error' | 'processing';
|
status: "active" | "pending" | "error" | "processing";
|
||||||
errorDetails?: string;
|
errorDetails?: string;
|
||||||
metadata?: Record<string, any>;
|
metadata?: Record<string, any>;
|
||||||
}
|
}
|
||||||
@ -556,7 +488,7 @@ export interface DataSourceConfiguration {
|
|||||||
export interface DesiredSalary {
|
export interface DesiredSalary {
|
||||||
amount: number;
|
amount: number;
|
||||||
currency: string;
|
currency: string;
|
||||||
period: 'hour' | 'day' | 'month' | 'year';
|
period: "hour" | "day" | "month" | "year";
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface Document {
|
export interface Document {
|
||||||
@ -564,7 +496,7 @@ export interface Document {
|
|||||||
ownerId: string;
|
ownerId: string;
|
||||||
filename: string;
|
filename: string;
|
||||||
originalName: string;
|
originalName: string;
|
||||||
type: 'pdf' | 'docx' | 'txt' | 'markdown' | 'image';
|
type: "pdf" | "docx" | "txt" | "markdown" | "image";
|
||||||
size: number;
|
size: number;
|
||||||
uploadDate?: Date;
|
uploadDate?: Date;
|
||||||
options?: DocumentOptions;
|
options?: DocumentOptions;
|
||||||
@ -574,7 +506,7 @@ export interface Document {
|
|||||||
export interface DocumentContentResponse {
|
export interface DocumentContentResponse {
|
||||||
documentId: string;
|
documentId: string;
|
||||||
filename: string;
|
filename: string;
|
||||||
type: 'pdf' | 'docx' | 'txt' | 'markdown' | 'image';
|
type: "pdf" | "docx" | "txt" | "markdown" | "image";
|
||||||
content: string;
|
content: string;
|
||||||
size: number;
|
size: number;
|
||||||
}
|
}
|
||||||
@ -588,8 +520,8 @@ export interface DocumentMessage {
|
|||||||
id?: string;
|
id?: string;
|
||||||
sessionId: string;
|
sessionId: string;
|
||||||
senderId?: string;
|
senderId?: string;
|
||||||
status: 'streaming' | 'status' | 'done' | 'error';
|
status: "streaming" | "status" | "done" | "error";
|
||||||
type: 'binary' | 'text' | 'json';
|
type: "binary" | "text" | "json";
|
||||||
timestamp?: Date;
|
timestamp?: Date;
|
||||||
document: Document;
|
document: Document;
|
||||||
content?: string;
|
content?: string;
|
||||||
@ -631,7 +563,7 @@ export interface EmailVerificationRequest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export interface Employer {
|
export interface Employer {
|
||||||
userType: 'candidate' | 'employer' | 'guest';
|
userType: "candidate" | "employer" | "guest";
|
||||||
id?: string;
|
id?: string;
|
||||||
lastActivity?: Date;
|
lastActivity?: Date;
|
||||||
email: string;
|
email: string;
|
||||||
@ -644,7 +576,7 @@ export interface Employer {
|
|||||||
updatedAt?: Date;
|
updatedAt?: Date;
|
||||||
lastLogin?: Date;
|
lastLogin?: Date;
|
||||||
profileImage?: string;
|
profileImage?: string;
|
||||||
status: 'active' | 'inactive' | 'pending' | 'banned';
|
status: "active" | "inactive" | "pending" | "banned";
|
||||||
isAdmin: boolean;
|
isAdmin: boolean;
|
||||||
companyName: string;
|
companyName: string;
|
||||||
industry: string;
|
industry: string;
|
||||||
@ -684,7 +616,7 @@ export interface GPUInfo {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export interface Guest {
|
export interface Guest {
|
||||||
userType: 'candidate' | 'employer' | 'guest';
|
userType: "candidate" | "employer" | "guest";
|
||||||
id?: string;
|
id?: string;
|
||||||
lastActivity?: Date;
|
lastActivity?: Date;
|
||||||
email: string;
|
email: string;
|
||||||
@ -697,7 +629,7 @@ export interface Guest {
|
|||||||
updatedAt?: Date;
|
updatedAt?: Date;
|
||||||
lastLogin?: Date;
|
lastLogin?: Date;
|
||||||
profileImage?: string;
|
profileImage?: string;
|
||||||
status: 'active' | 'inactive' | 'pending' | 'banned';
|
status: "active" | "inactive" | "pending" | "banned";
|
||||||
isAdmin: boolean;
|
isAdmin: boolean;
|
||||||
sessionId: string;
|
sessionId: string;
|
||||||
username: string;
|
username: string;
|
||||||
@ -713,7 +645,7 @@ export interface GuestCleanupRequest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export interface GuestConversionRequest {
|
export interface GuestConversionRequest {
|
||||||
accountType: 'candidate' | 'employer';
|
accountType: "candidate" | "employer";
|
||||||
email: string;
|
email: string;
|
||||||
username: string;
|
username: string;
|
||||||
password: string;
|
password: string;
|
||||||
@ -732,7 +664,7 @@ export interface GuestSessionResponse {
|
|||||||
refreshToken: string;
|
refreshToken: string;
|
||||||
user: Guest;
|
user: Guest;
|
||||||
expiresAt: number;
|
expiresAt: number;
|
||||||
userType: 'guest';
|
userType: "guest";
|
||||||
isGuest: boolean;
|
isGuest: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -754,7 +686,7 @@ export interface InterviewFeedback {
|
|||||||
overallScore: number;
|
overallScore: number;
|
||||||
strengths: Array<string>;
|
strengths: Array<string>;
|
||||||
weaknesses: Array<string>;
|
weaknesses: Array<string>;
|
||||||
recommendation: 'strong_hire' | 'hire' | 'no_hire' | 'strong_no_hire';
|
recommendation: "strong_hire" | "hire" | "no_hire" | "strong_no_hire";
|
||||||
comments: string;
|
comments: string;
|
||||||
createdAt?: Date;
|
createdAt?: Date;
|
||||||
updatedAt?: Date;
|
updatedAt?: Date;
|
||||||
@ -767,19 +699,19 @@ export interface InterviewSchedule {
|
|||||||
applicationId: string;
|
applicationId: string;
|
||||||
scheduledDate: Date;
|
scheduledDate: Date;
|
||||||
endDate: Date;
|
endDate: Date;
|
||||||
interviewType: 'phone' | 'video' | 'onsite' | 'technical' | 'behavioral';
|
interviewType: "phone" | "video" | "onsite" | "technical" | "behavioral";
|
||||||
interviewers: Array<string>;
|
interviewers: Array<string>;
|
||||||
location?: string | Location;
|
location?: string | Location;
|
||||||
notes?: string;
|
notes?: string;
|
||||||
feedback?: InterviewFeedback;
|
feedback?: InterviewFeedback;
|
||||||
status: 'scheduled' | 'completed' | 'cancelled' | 'rescheduled';
|
status: "scheduled" | "completed" | "cancelled" | "rescheduled";
|
||||||
meetingLink?: string;
|
meetingLink?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface Job {
|
export interface Job {
|
||||||
id?: string;
|
id?: string;
|
||||||
ownerId: string;
|
ownerId: string;
|
||||||
ownerType: 'candidate' | 'employer' | 'guest';
|
ownerType: "candidate" | "employer" | "guest";
|
||||||
owner?: BaseUser;
|
owner?: BaseUser;
|
||||||
title?: string;
|
title?: string;
|
||||||
summary?: string;
|
summary?: string;
|
||||||
@ -795,7 +727,7 @@ export interface JobApplication {
|
|||||||
id?: string;
|
id?: string;
|
||||||
jobId: string;
|
jobId: string;
|
||||||
candidateId: string;
|
candidateId: string;
|
||||||
status: 'applied' | 'reviewing' | 'interview' | 'offer' | 'rejected' | 'accepted' | 'withdrawn';
|
status: "applied" | "reviewing" | "interview" | "offer" | "rejected" | "accepted" | "withdrawn";
|
||||||
appliedDate: Date;
|
appliedDate: Date;
|
||||||
updatedDate: Date;
|
updatedDate: Date;
|
||||||
resumeVersion: string;
|
resumeVersion: string;
|
||||||
@ -810,7 +742,7 @@ export interface JobApplication {
|
|||||||
export interface JobDetails {
|
export interface JobDetails {
|
||||||
location: Location;
|
location: Location;
|
||||||
salaryRange?: SalaryRange;
|
salaryRange?: SalaryRange;
|
||||||
employmentType: 'full-time' | 'part-time' | 'contract' | 'internship' | 'freelance';
|
employmentType: "full-time" | "part-time" | "contract" | "internship" | "freelance";
|
||||||
datePosted?: Date;
|
datePosted?: Date;
|
||||||
applicationDeadline?: Date;
|
applicationDeadline?: Date;
|
||||||
isActive: boolean;
|
isActive: boolean;
|
||||||
@ -846,8 +778,8 @@ export interface JobRequirementsMessage {
|
|||||||
id?: string;
|
id?: string;
|
||||||
sessionId: string;
|
sessionId: string;
|
||||||
senderId?: string;
|
senderId?: string;
|
||||||
status: 'streaming' | 'status' | 'done' | 'error';
|
status: "streaming" | "status" | "done" | "error";
|
||||||
type: 'binary' | 'text' | 'json';
|
type: "binary" | "text" | "json";
|
||||||
timestamp?: Date;
|
timestamp?: Date;
|
||||||
job: Job;
|
job: Job;
|
||||||
}
|
}
|
||||||
@ -861,7 +793,7 @@ export interface JobResponse {
|
|||||||
|
|
||||||
export interface Language {
|
export interface Language {
|
||||||
language: string;
|
language: string;
|
||||||
proficiency: 'basic' | 'conversational' | 'fluent' | 'native';
|
proficiency: "basic" | "conversational" | "fluent" | "native";
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface Location {
|
export interface Location {
|
||||||
@ -916,7 +848,7 @@ export interface MessageReaction {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export interface NotificationPreference {
|
export interface NotificationPreference {
|
||||||
type: 'email' | 'push' | 'in_app';
|
type: "email" | "push" | "in_app";
|
||||||
events: Array<string>;
|
events: Array<string>;
|
||||||
isEnabled: boolean;
|
isEnabled: boolean;
|
||||||
}
|
}
|
||||||
@ -925,7 +857,7 @@ export interface PaginatedRequest {
|
|||||||
page: number;
|
page: number;
|
||||||
limit: number;
|
limit: number;
|
||||||
sortBy?: string;
|
sortBy?: string;
|
||||||
sortOrder?: 'asc' | 'desc';
|
sortOrder?: "asc" | "desc";
|
||||||
filters?: Record<string, any>;
|
filters?: Record<string, any>;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -947,7 +879,7 @@ export interface PointOfContact {
|
|||||||
|
|
||||||
export interface ProcessingStep {
|
export interface ProcessingStep {
|
||||||
id?: string;
|
id?: string;
|
||||||
type: 'extract' | 'transform' | 'chunk' | 'embed' | 'filter' | 'summarize';
|
type: "extract" | "transform" | "chunk" | "embed" | "filter" | "summarize";
|
||||||
parameters: Record<string, any>;
|
parameters: Record<string, any>;
|
||||||
order: number;
|
order: number;
|
||||||
dependsOn?: Array<string>;
|
dependsOn?: Array<string>;
|
||||||
@ -960,7 +892,7 @@ export interface RAGConfiguration {
|
|||||||
description?: string;
|
description?: string;
|
||||||
dataSourceConfigurations: Array<DataSourceConfiguration>;
|
dataSourceConfigurations: Array<DataSourceConfiguration>;
|
||||||
embeddingModel: string;
|
embeddingModel: string;
|
||||||
vectorStoreType: 'chroma';
|
vectorStoreType: "chroma";
|
||||||
retrievalParameters: RetrievalParameters;
|
retrievalParameters: RetrievalParameters;
|
||||||
createdAt?: Date;
|
createdAt?: Date;
|
||||||
updatedAt?: Date;
|
updatedAt?: Date;
|
||||||
@ -968,6 +900,10 @@ export interface RAGConfiguration {
|
|||||||
isActive: boolean;
|
isActive: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface RAGDocumentRequest {
|
||||||
|
id: string;
|
||||||
|
}
|
||||||
|
|
||||||
export interface RagContentMetadata {
|
export interface RagContentMetadata {
|
||||||
sourceFile: string;
|
sourceFile: string;
|
||||||
lineBegin: number;
|
lineBegin: number;
|
||||||
@ -1050,17 +986,17 @@ export interface ResumeMessage {
|
|||||||
id?: string;
|
id?: string;
|
||||||
sessionId: string;
|
sessionId: string;
|
||||||
senderId?: string;
|
senderId?: string;
|
||||||
status: 'streaming' | 'status' | 'done' | 'error';
|
status: "streaming" | "status" | "done" | "error";
|
||||||
type: 'binary' | 'text' | 'json';
|
type: "binary" | "text" | "json";
|
||||||
timestamp?: Date;
|
timestamp?: Date;
|
||||||
role: 'user' | 'assistant' | 'system' | 'information' | 'warning' | 'error';
|
role: "user" | "assistant" | "system" | "information" | "warning" | "error";
|
||||||
content: string;
|
content: string;
|
||||||
tunables?: Tunables;
|
tunables?: Tunables;
|
||||||
resume: Resume;
|
resume: Resume;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface RetrievalParameters {
|
export interface RetrievalParameters {
|
||||||
searchType: 'similarity' | 'mmr' | 'hybrid' | 'keyword';
|
searchType: "similarity" | "mmr" | "hybrid" | "keyword";
|
||||||
topK: number;
|
topK: number;
|
||||||
similarityThreshold?: number;
|
similarityThreshold?: number;
|
||||||
rerankerModel?: string;
|
rerankerModel?: string;
|
||||||
@ -1073,7 +1009,7 @@ export interface SalaryRange {
|
|||||||
min: number;
|
min: number;
|
||||||
max: number;
|
max: number;
|
||||||
currency: string;
|
currency: string;
|
||||||
period: 'hour' | 'day' | 'month' | 'year';
|
period: "hour" | "day" | "month" | "year";
|
||||||
isVisible: boolean;
|
isVisible: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1083,14 +1019,14 @@ export interface SearchQuery {
|
|||||||
page: number;
|
page: number;
|
||||||
limit: number;
|
limit: number;
|
||||||
sortBy?: string;
|
sortBy?: string;
|
||||||
sortOrder?: 'asc' | 'desc';
|
sortOrder?: "asc" | "desc";
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface Skill {
|
export interface Skill {
|
||||||
id?: string;
|
id?: string;
|
||||||
name: string;
|
name: string;
|
||||||
category: string;
|
category: string;
|
||||||
level: 'beginner' | 'intermediate' | 'advanced' | 'expert';
|
level: "beginner" | "intermediate" | "advanced" | "expert";
|
||||||
yearsOfExperience?: number;
|
yearsOfExperience?: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1099,7 +1035,7 @@ export interface SkillAssessment {
|
|||||||
skill: string;
|
skill: string;
|
||||||
skillModified?: string;
|
skillModified?: string;
|
||||||
evidenceFound: boolean;
|
evidenceFound: boolean;
|
||||||
evidenceStrength: 'strong' | 'moderate' | 'weak' | 'none';
|
evidenceStrength: "strong" | "moderate" | "weak" | "none";
|
||||||
assessment: string;
|
assessment: string;
|
||||||
description: string;
|
description: string;
|
||||||
evidenceDetails?: Array<EvidenceDetail>;
|
evidenceDetails?: Array<EvidenceDetail>;
|
||||||
@ -1109,7 +1045,7 @@ export interface SkillAssessment {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export interface SocialLink {
|
export interface SocialLink {
|
||||||
platform: 'linkedin' | 'twitter' | 'github' | 'dribbble' | 'behance' | 'website' | 'other';
|
platform: "linkedin" | "twitter" | "github" | "dribbble" | "behance" | "website" | "other";
|
||||||
url: string;
|
url: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1122,6 +1058,18 @@ export interface SystemInfo {
|
|||||||
maxContextLength: number;
|
maxContextLength: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface Tool {
|
||||||
|
toolCalls: Array<Record<string, string>>;
|
||||||
|
messages?: Array<any>;
|
||||||
|
available?: Array<string>;
|
||||||
|
toolName: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ToolCall {
|
||||||
|
name: string;
|
||||||
|
content: string;
|
||||||
|
}
|
||||||
|
|
||||||
export interface Tunables {
|
export interface Tunables {
|
||||||
enableRAG: boolean;
|
enableRAG: boolean;
|
||||||
enableTools: boolean;
|
enableTools: boolean;
|
||||||
@ -1132,14 +1080,7 @@ export interface UserActivity {
|
|||||||
id?: string;
|
id?: string;
|
||||||
userId?: string;
|
userId?: string;
|
||||||
guestId?: string;
|
guestId?: string;
|
||||||
activityType:
|
activityType: "login" | "search" | "view_job" | "apply_job" | "message" | "update_profile" | "chat";
|
||||||
| 'login'
|
|
||||||
| 'search'
|
|
||||||
| 'view_job'
|
|
||||||
| 'apply_job'
|
|
||||||
| 'message'
|
|
||||||
| 'update_profile'
|
|
||||||
| 'chat';
|
|
||||||
timestamp: Date;
|
timestamp: Date;
|
||||||
metadata: Record<string, any>;
|
metadata: Record<string, any>;
|
||||||
ipAddress?: string;
|
ipAddress?: string;
|
||||||
@ -1149,13 +1090,13 @@ export interface UserActivity {
|
|||||||
|
|
||||||
export interface UserPreference {
|
export interface UserPreference {
|
||||||
userId: string;
|
userId: string;
|
||||||
theme: 'light' | 'dark' | 'system';
|
theme: "light" | "dark" | "system";
|
||||||
notifications: Array<NotificationPreference>;
|
notifications: Array<NotificationPreference>;
|
||||||
accessibility: AccessibilitySettings;
|
accessibility: AccessibilitySettings;
|
||||||
dashboardLayout?: Record<string, any>;
|
dashboardLayout?: Record<string, any>;
|
||||||
language: string;
|
language: string;
|
||||||
timezone: string;
|
timezone: string;
|
||||||
emailFrequency: 'immediate' | 'daily' | 'weekly' | 'never';
|
emailFrequency: "immediate" | "daily" | "weekly" | "never";
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface WorkExperience {
|
export interface WorkExperience {
|
||||||
@ -1320,13 +1261,9 @@ export function convertCandidateFromApi(data: any): Candidate {
|
|||||||
// Convert nested Education model
|
// Convert nested Education model
|
||||||
education: data.education ? convertEducationFromApi(data.education) : undefined,
|
education: data.education ? convertEducationFromApi(data.education) : undefined,
|
||||||
// Convert nested Certification model
|
// Convert nested Certification model
|
||||||
certifications: data.certifications
|
certifications: data.certifications ? convertCertificationFromApi(data.certifications) : undefined,
|
||||||
? convertCertificationFromApi(data.certifications)
|
|
||||||
: undefined,
|
|
||||||
// Convert nested JobApplication model
|
// Convert nested JobApplication model
|
||||||
jobApplications: data.jobApplications
|
jobApplications: data.jobApplications ? convertJobApplicationFromApi(data.jobApplications) : undefined,
|
||||||
? convertJobApplicationFromApi(data.jobApplications)
|
|
||||||
: undefined,
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
/**
|
/**
|
||||||
@ -1354,9 +1291,7 @@ export function convertCandidateAIFromApi(data: any): CandidateAI {
|
|||||||
// Convert nested Education model
|
// Convert nested Education model
|
||||||
education: data.education ? convertEducationFromApi(data.education) : undefined,
|
education: data.education ? convertEducationFromApi(data.education) : undefined,
|
||||||
// Convert nested Certification model
|
// Convert nested Certification model
|
||||||
certifications: data.certifications
|
certifications: data.certifications ? convertCertificationFromApi(data.certifications) : undefined,
|
||||||
? convertCertificationFromApi(data.certifications)
|
|
||||||
: undefined,
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
/**
|
/**
|
||||||
@ -1687,9 +1622,7 @@ export function convertInterviewFeedbackFromApi(data: any): InterviewFeedback {
|
|||||||
// Convert updatedAt from ISO string to Date
|
// Convert updatedAt from ISO string to Date
|
||||||
updatedAt: data.updatedAt ? new Date(data.updatedAt) : undefined,
|
updatedAt: data.updatedAt ? new Date(data.updatedAt) : undefined,
|
||||||
// Convert nested SkillAssessment model
|
// Convert nested SkillAssessment model
|
||||||
skillAssessments: data.skillAssessments
|
skillAssessments: data.skillAssessments ? convertSkillAssessmentFromApi(data.skillAssessments) : undefined,
|
||||||
? convertSkillAssessmentFromApi(data.skillAssessments)
|
|
||||||
: undefined,
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
/**
|
/**
|
||||||
@ -1745,9 +1678,7 @@ export function convertJobApplicationFromApi(data: any): JobApplication {
|
|||||||
// Convert updatedDate from ISO string to Date
|
// Convert updatedDate from ISO string to Date
|
||||||
updatedDate: new Date(data.updatedDate),
|
updatedDate: new Date(data.updatedDate),
|
||||||
// Convert nested InterviewSchedule model
|
// Convert nested InterviewSchedule model
|
||||||
interviewSchedules: data.interviewSchedules
|
interviewSchedules: data.interviewSchedules ? convertInterviewScheduleFromApi(data.interviewSchedules) : undefined,
|
||||||
? convertInterviewScheduleFromApi(data.interviewSchedules)
|
|
||||||
: undefined,
|
|
||||||
// Convert nested ApplicationDecision model
|
// Convert nested ApplicationDecision model
|
||||||
decision: data.decision ? convertApplicationDecisionFromApi(data.decision) : undefined,
|
decision: data.decision ? convertApplicationDecisionFromApi(data.decision) : undefined,
|
||||||
};
|
};
|
||||||
@ -1839,9 +1770,7 @@ export function convertRAGConfigurationFromApi(data: any): RAGConfiguration {
|
|||||||
// Convert updatedAt from ISO string to Date
|
// Convert updatedAt from ISO string to Date
|
||||||
updatedAt: data.updatedAt ? new Date(data.updatedAt) : undefined,
|
updatedAt: data.updatedAt ? new Date(data.updatedAt) : undefined,
|
||||||
// Convert nested DataSourceConfiguration model
|
// Convert nested DataSourceConfiguration model
|
||||||
dataSourceConfigurations: data.dataSourceConfigurations.map((item: any) =>
|
dataSourceConfigurations: data.dataSourceConfigurations.map((item: any) => convertDataSourceConfigurationFromApi(item)),
|
||||||
convertDataSourceConfigurationFromApi(item)
|
|
||||||
),
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
/**
|
/**
|
||||||
@ -2090,4 +2019,4 @@ export function convertArrayFromApi<T>(data: any[], modelType: string): T[] {
|
|||||||
export type User = Candidate | Employer;
|
export type User = Candidate | Employer;
|
||||||
|
|
||||||
// Export all types
|
// Export all types
|
||||||
export type {};
|
export type { };
|
||||||
|
@ -16,17 +16,10 @@ from datetime import datetime, UTC
|
|||||||
from prometheus_client import CollectorRegistry # type: ignore
|
from prometheus_client import CollectorRegistry # type: ignore
|
||||||
import numpy as np # type: ignore
|
import numpy as np # type: ignore
|
||||||
import json_extractor as json_extractor
|
import json_extractor as json_extractor
|
||||||
from pydantic import BaseModel, Field # type: ignore
|
|
||||||
from uuid import uuid4
|
from uuid import uuid4
|
||||||
from typing import List, Optional, ClassVar, Any, Literal
|
|
||||||
|
|
||||||
from datetime import datetime, UTC
|
|
||||||
import numpy as np # type: ignore
|
|
||||||
|
|
||||||
from uuid import uuid4
|
|
||||||
from prometheus_client import CollectorRegistry # type: ignore
|
|
||||||
import os
|
import os
|
||||||
import re
|
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
|
||||||
from rag import start_file_watcher, ChromaDBFileWatcher
|
from rag import start_file_watcher, ChromaDBFileWatcher
|
||||||
@ -56,21 +49,10 @@ from models import (
|
|||||||
ChatMessageStatus,
|
ChatMessageStatus,
|
||||||
ChatMessageStreaming,
|
ChatMessageStreaming,
|
||||||
LLMMessage,
|
LLMMessage,
|
||||||
ChatMessage,
|
|
||||||
ChatOptions,
|
ChatOptions,
|
||||||
ChatMessageUser,
|
|
||||||
Tunables,
|
|
||||||
ApiStatusType,
|
|
||||||
ChatMessageMetaData,
|
|
||||||
Candidate,
|
|
||||||
)
|
)
|
||||||
from logger import logger
|
|
||||||
import defines
|
|
||||||
from .registry import agent_registry
|
from .registry import agent_registry
|
||||||
|
|
||||||
from models import ChromaDBGetResponse
|
|
||||||
|
|
||||||
|
|
||||||
class CandidateEntity(Candidate):
|
class CandidateEntity(Candidate):
|
||||||
model_config = {"arbitrary_types_allowed": True} # Allow ChromaDBFileWatcher, etc
|
model_config = {"arbitrary_types_allowed": True} # Allow ChromaDBFileWatcher, etc
|
||||||
|
|
||||||
|
@ -1034,6 +1034,18 @@ class DocumentMessage(ApiMessage):
|
|||||||
converted: bool = Field(False, alias=str("converted"))
|
converted: bool = Field(False, alias=str("converted"))
|
||||||
model_config = ConfigDict(populate_by_name=True)
|
model_config = ConfigDict(populate_by_name=True)
|
||||||
|
|
||||||
|
class ToolCall(BaseModel):
|
||||||
|
name: str
|
||||||
|
content: str
|
||||||
|
model_config = ConfigDict(populate_by_name=True)
|
||||||
|
|
||||||
|
class Tool(BaseModel):
|
||||||
|
# generate-types doesn't support nested-nested
|
||||||
|
tool_calls: List[dict[str, str]] = Field(default=[], alias=str("toolCalls"))
|
||||||
|
messages: List[LLMMessage] = Field(default_factory=list)
|
||||||
|
available: List[str] = Field(default_factory=list)
|
||||||
|
tool_name: str = Field(..., alias=str("toolName"))
|
||||||
|
model_config = ConfigDict(populate_by_name=True)
|
||||||
|
|
||||||
class ChatMessageMetaData(BaseModel):
|
class ChatMessageMetaData(BaseModel):
|
||||||
model: AIModelType = AIModelType.QWEN2_5
|
model: AIModelType = AIModelType.QWEN2_5
|
||||||
@ -1050,7 +1062,7 @@ class ChatMessageMetaData(BaseModel):
|
|||||||
prompt_eval_count: int = 0
|
prompt_eval_count: int = 0
|
||||||
prompt_eval_duration: int = 0
|
prompt_eval_duration: int = 0
|
||||||
options: Optional[ChatOptions] = None
|
options: Optional[ChatOptions] = None
|
||||||
tools: Dict[str, Any] = Field(default_factory=dict)
|
tools: Optional[Tool] = None
|
||||||
timers: Dict[str, float] = Field(default_factory=dict)
|
timers: Dict[str, float] = Field(default_factory=dict)
|
||||||
model_config = ConfigDict(populate_by_name=True)
|
model_config = ConfigDict(populate_by_name=True)
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user