Fixing eslint issues
This commit is contained in:
parent
66b68270cd
commit
17381dded1
@ -28,8 +28,8 @@
|
||||
"react/prop-types": "off",
|
||||
"@typescript-eslint/explicit-function-return-type": "warn",
|
||||
"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": {
|
||||
"react": {
|
||||
|
@ -1,3 +1,4 @@
|
||||
import React, { JSX } from 'react';
|
||||
import Box from '@mui/material/Box';
|
||||
import Button from '@mui/material/Button';
|
||||
|
||||
@ -10,7 +11,7 @@ interface BackstoryQueryInterface {
|
||||
submitQuery?: ChatSubmitQueryInterface;
|
||||
}
|
||||
|
||||
const BackstoryQuery = (props: BackstoryQueryInterface) => {
|
||||
const BackstoryQuery = (props: BackstoryQueryInterface): JSX.Element => {
|
||||
const { question, submitQuery } = props;
|
||||
|
||||
if (submitQuery === undefined) {
|
||||
@ -25,7 +26,7 @@ const BackstoryQuery = (props: BackstoryQueryInterface) => {
|
||||
m: 1,
|
||||
}}
|
||||
size="small"
|
||||
onClick={(e: any) => {
|
||||
onClick={(): void => {
|
||||
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 { SxProps, Theme } from '@mui/material';
|
||||
import { ChatSubmitQueryInterface } from './BackstoryQuery';
|
||||
import { SetSnackType } from './Snack';
|
||||
|
||||
interface BackstoryElementProps {
|
||||
// setSnack: SetSnackType,
|
||||
@ -24,12 +22,12 @@ interface BackstoryTabProps {
|
||||
tabProps?: {
|
||||
label?: string;
|
||||
sx?: SxProps;
|
||||
icon?: string | ReactElement<unknown, string | JSXElementConstructor<any>> | undefined;
|
||||
icon?: string | ReactElement<unknown, string> | undefined;
|
||||
iconPosition?: 'bottom' | 'top' | 'start' | 'end' | undefined;
|
||||
};
|
||||
}
|
||||
|
||||
function BackstoryPage(props: BackstoryTabProps) {
|
||||
function BackstoryPage(props: BackstoryTabProps): JSX.Element {
|
||||
const { className, active, children } = props;
|
||||
|
||||
return (
|
||||
|
@ -72,21 +72,21 @@ const BackstoryTextField = React.forwardRef<BackstoryTextFieldRef, BackstoryText
|
||||
});
|
||||
|
||||
// Cleanup RAF to prevent memory leaks
|
||||
return () => cancelAnimationFrame(raf);
|
||||
return (): void => cancelAnimationFrame(raf);
|
||||
}, [editValue, placeholder]);
|
||||
|
||||
// Expose getValue method via ref
|
||||
useImperativeHandle(ref, () => ({
|
||||
getValue: () => editValue,
|
||||
setValue: (value: string) => setEditValue(value),
|
||||
getAndResetValue: () => {
|
||||
getValue: (): string => editValue,
|
||||
setValue: (value: string): void => setEditValue(value),
|
||||
getAndResetValue: (): string => {
|
||||
const _ev = editValue;
|
||||
setEditValue('');
|
||||
return _ev;
|
||||
},
|
||||
}));
|
||||
|
||||
const handleKeyDown = (event: KeyboardEvent<HTMLTextAreaElement>) => {
|
||||
const handleKeyDown = (event: KeyboardEvent<HTMLTextAreaElement>): void => {
|
||||
if (!onEnter) {
|
||||
return;
|
||||
}
|
||||
@ -122,7 +122,7 @@ const BackstoryTextField = React.forwardRef<BackstoryTextFieldRef, BackstoryText
|
||||
value={editValue}
|
||||
disabled={disabled}
|
||||
placeholder={placeholder}
|
||||
onChange={e => {
|
||||
onChange={(e): void => {
|
||||
setEditValue(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 { BackstoryTextField };
|
||||
|
@ -30,6 +30,7 @@ import {
|
||||
ChatMessageError,
|
||||
ChatMessageStreaming,
|
||||
ChatMessageStatus,
|
||||
ChatMessageMetaData,
|
||||
} from 'types/types';
|
||||
import { PaginatedResponse } from 'types/conversion';
|
||||
|
||||
@ -43,7 +44,7 @@ const defaultMessage: ChatMessage = {
|
||||
timestamp: new Date(),
|
||||
content: '',
|
||||
role: 'assistant',
|
||||
metadata: null as any,
|
||||
metadata: null as unknown as ChatMessageMetaData,
|
||||
};
|
||||
|
||||
const loadingMessage: ChatMessage = {
|
||||
@ -88,14 +89,11 @@ const Conversation = forwardRef<ConversationHandle, ConversationProps>(
|
||||
onResponse,
|
||||
placeholder,
|
||||
preamble,
|
||||
resetAction,
|
||||
resetLabel,
|
||||
sx,
|
||||
type,
|
||||
} = props;
|
||||
const { apiClient } = useAuth();
|
||||
const [processing, setProcessing] = useState<boolean>(false);
|
||||
const [countdown, setCountdown] = useState<number>(0);
|
||||
const [conversation, setConversation] = useState<ChatMessage[]>([]);
|
||||
const conversationRef = useRef<ChatMessage[]>([]);
|
||||
const [filteredConversation, setFilteredConversation] = useState<ChatMessage[]>([]);
|
||||
@ -144,7 +142,7 @@ const Conversation = forwardRef<ConversationHandle, ConversationProps>(
|
||||
if (chatSession) {
|
||||
return;
|
||||
}
|
||||
const createChatSession = async () => {
|
||||
const createChatSession = async (): Promise<void> => {
|
||||
try {
|
||||
const chatContext: ChatContext = { type: 'general' };
|
||||
const response: ChatSession = await apiClient.createChatSession(chatContext);
|
||||
@ -156,7 +154,7 @@ const Conversation = forwardRef<ConversationHandle, ConversationProps>(
|
||||
};
|
||||
|
||||
createChatSession();
|
||||
}, [chatSession, setChatSession]);
|
||||
}, [chatSession, setChatSession, apiClient, setSnack]);
|
||||
|
||||
const getChatMessages = useCallback(async () => {
|
||||
if (!chatSession || !chatSession.id) {
|
||||
@ -193,7 +191,7 @@ const Conversation = forwardRef<ConversationHandle, ConversationProps>(
|
||||
}, 3000);
|
||||
setSnack('Unable to obtain chat history.', 'error');
|
||||
}
|
||||
}, [chatSession]);
|
||||
}, [chatSession, apiClient, setSnack]);
|
||||
|
||||
// Set the initial chat history to "loading" or the welcome message if loaded.
|
||||
useEffect(() => {
|
||||
@ -208,9 +206,9 @@ const Conversation = forwardRef<ConversationHandle, ConversationProps>(
|
||||
setNoInteractions(true);
|
||||
|
||||
getChatMessages();
|
||||
}, [chatSession]);
|
||||
}, [chatSession, getChatMessages]);
|
||||
|
||||
const handleEnter = (value: string) => {
|
||||
const handleEnter = (value: string): void => {
|
||||
const query: ChatQuery = {
|
||||
prompt: value,
|
||||
};
|
||||
@ -218,10 +216,10 @@ const Conversation = forwardRef<ConversationHandle, ConversationProps>(
|
||||
};
|
||||
|
||||
useImperativeHandle(ref, () => ({
|
||||
submitQuery: (query: ChatQuery) => {
|
||||
submitQuery: (query: ChatQuery): void => {
|
||||
processQuery(query);
|
||||
},
|
||||
fetchHistory: () => {
|
||||
fetchHistory: (): void => {
|
||||
getChatMessages();
|
||||
},
|
||||
}));
|
||||
@ -256,7 +254,7 @@ const Conversation = forwardRef<ConversationHandle, ConversationProps>(
|
||||
// }
|
||||
// };
|
||||
|
||||
const cancelQuery = () => {
|
||||
const cancelQuery = (): void => {
|
||||
console.log('Stop query');
|
||||
if (controllerRef.current) {
|
||||
controllerRef.current.cancel();
|
||||
@ -264,12 +262,10 @@ const Conversation = forwardRef<ConversationHandle, ConversationProps>(
|
||||
controllerRef.current = null;
|
||||
};
|
||||
|
||||
const processQuery = (query: ChatQuery) => {
|
||||
const processQuery = (query: ChatQuery): void => {
|
||||
if (controllerRef.current || !chatSession || !chatSession.id) {
|
||||
return;
|
||||
}
|
||||
const sessionId: string = chatSession.id;
|
||||
|
||||
setNoInteractions(false);
|
||||
setConversation([
|
||||
...conversationRef.current,
|
||||
@ -396,17 +392,6 @@ const Conversation = forwardRef<ConversationHandle, ConversationProps>(
|
||||
aria-label="Loading Spinner"
|
||||
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
|
||||
className="Query"
|
||||
@ -443,7 +428,7 @@ const Conversation = forwardRef<ConversationHandle, ConversationProps>(
|
||||
<DeleteConfirmation
|
||||
label={resetLabel || 'all data'}
|
||||
disabled={!chatSession || processingMessage !== undefined || noInteractions}
|
||||
onDelete={() => {
|
||||
onDelete={(): void => {
|
||||
/*reset(); resetAction && resetAction(); */
|
||||
}}
|
||||
/>
|
||||
@ -453,7 +438,7 @@ const Conversation = forwardRef<ConversationHandle, ConversationProps>(
|
||||
sx={{ m: 1, gap: 1, flexGrow: 1 }}
|
||||
variant="contained"
|
||||
disabled={!chatSession || processingMessage !== undefined}
|
||||
onClick={() => {
|
||||
onClick={(): void => {
|
||||
processQuery({
|
||||
prompt:
|
||||
(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 */}
|
||||
<IconButton
|
||||
aria-label="cancel"
|
||||
onClick={() => {
|
||||
onClick={(): void => {
|
||||
cancelQuery();
|
||||
}}
|
||||
sx={{ display: 'flex', margin: 'auto 0px' }}
|
||||
@ -502,7 +487,7 @@ const Conversation = forwardRef<ConversationHandle, ConversationProps>(
|
||||
);
|
||||
}
|
||||
);
|
||||
|
||||
Conversation.displayName = 'Conversation';
|
||||
export type { ConversationProps, ConversationHandle };
|
||||
|
||||
export { Conversation };
|
||||
|
@ -1,3 +1,4 @@
|
||||
import React, { JSX } from 'react';
|
||||
import { useState } from 'react';
|
||||
import ContentCopyIcon from '@mui/icons-material/ContentCopy';
|
||||
import CheckIcon from '@mui/icons-material/Check';
|
||||
@ -17,10 +18,10 @@ const CopyBubble = ({
|
||||
tooltip = 'Copy to clipboard',
|
||||
onClick,
|
||||
...rest
|
||||
}: CopyBubbleProps) => {
|
||||
}: CopyBubbleProps): JSX.Element => {
|
||||
const [copied, setCopied] = useState(false);
|
||||
|
||||
const handleCopy = (e: any) => {
|
||||
const handleCopy = (e: React.MouseEvent<HTMLButtonElement, MouseEvent>): void => {
|
||||
if (content === undefined) {
|
||||
return;
|
||||
}
|
||||
@ -38,7 +39,7 @@ const CopyBubble = ({
|
||||
return (
|
||||
<Tooltip title={tooltip} placement="top" arrow>
|
||||
<IconButton
|
||||
onClick={e => {
|
||||
onClick={(e): void => {
|
||||
handleCopy(e);
|
||||
}}
|
||||
sx={{
|
||||
|
@ -1,4 +1,4 @@
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import React, { JSX, useState } from 'react';
|
||||
import {
|
||||
IconButton,
|
||||
Dialog,
|
||||
@ -45,11 +45,11 @@ interface DeleteConfirmationProps {
|
||||
cancelButtonText?: string;
|
||||
}
|
||||
|
||||
function capitalizeFirstLetter(str: string) {
|
||||
function capitalizeFirstLetter(str: string): string {
|
||||
return str.charAt(0).toUpperCase() + str.slice(1);
|
||||
}
|
||||
|
||||
const DeleteConfirmation = (props: DeleteConfirmationProps) => {
|
||||
const DeleteConfirmation = (props: DeleteConfirmationProps): JSX.Element => {
|
||||
const {
|
||||
// Legacy props
|
||||
onDelete,
|
||||
@ -79,13 +79,13 @@ const DeleteConfirmation = (props: DeleteConfirmationProps) => {
|
||||
const isControlled = controlledOpen !== undefined;
|
||||
const isOpen = isControlled ? controlledOpen : internalOpen;
|
||||
|
||||
const handleClickOpen = () => {
|
||||
const handleClickOpen = (): void => {
|
||||
if (!isControlled) {
|
||||
setInternalOpen(true);
|
||||
}
|
||||
};
|
||||
|
||||
const handleClose = () => {
|
||||
const handleClose = (): void => {
|
||||
if (isControlled) {
|
||||
controlledOnClose?.();
|
||||
} else {
|
||||
@ -93,7 +93,7 @@ const DeleteConfirmation = (props: DeleteConfirmationProps) => {
|
||||
}
|
||||
};
|
||||
|
||||
const handleConfirm = () => {
|
||||
const handleConfirm = (): void => {
|
||||
if (isControlled) {
|
||||
onConfirm?.();
|
||||
} else {
|
||||
@ -122,7 +122,7 @@ const DeleteConfirmation = (props: DeleteConfirmationProps) => {
|
||||
{/* This span is used to wrap the IconButton to ensure Tooltip works even when disabled */}
|
||||
<IconButton
|
||||
aria-label={action}
|
||||
onClick={e => {
|
||||
onClick={(e): void => {
|
||||
e.stopPropagation();
|
||||
e.preventDefault();
|
||||
handleClickOpen();
|
||||
|
@ -1,4 +1,4 @@
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import React, { useState, useEffect, JSX } from 'react';
|
||||
import { BackstoryElementProps } from './BackstoryTab';
|
||||
import { StyledMarkdown } from './StyledMarkdown';
|
||||
|
||||
@ -6,7 +6,7 @@ interface DocumentProps extends BackstoryElementProps {
|
||||
filepath?: string;
|
||||
}
|
||||
|
||||
const Document = (props: DocumentProps) => {
|
||||
const Document = (props: DocumentProps): JSX.Element => {
|
||||
const { filepath } = props;
|
||||
|
||||
const [document, setDocument] = useState<string>('');
|
||||
@ -16,7 +16,7 @@ const Document = (props: DocumentProps) => {
|
||||
if (!filepath) {
|
||||
return;
|
||||
}
|
||||
const fetchDocument = async () => {
|
||||
const fetchDocument = async (): Promise<void> => {
|
||||
try {
|
||||
const response = await fetch(filepath, {
|
||||
method: 'GET',
|
||||
@ -29,7 +29,7 @@ const Document = (props: DocumentProps) => {
|
||||
}
|
||||
const data = await response.text();
|
||||
setDocument(data);
|
||||
} catch (error: any) {
|
||||
} catch (error) {
|
||||
console.error('Error obtaining Docs content information:', error);
|
||||
setDocument(`${filepath} not found.`);
|
||||
}
|
||||
|
@ -1,4 +1,4 @@
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import React, { useState, useEffect, JSX } from 'react';
|
||||
import {
|
||||
Box,
|
||||
Button,
|
||||
@ -44,7 +44,7 @@ const VisuallyHiddenInput = styled('input')({
|
||||
width: 1,
|
||||
});
|
||||
|
||||
const DocumentManager = (props: BackstoryElementProps) => {
|
||||
const DocumentManager = (_props: BackstoryElementProps): JSX.Element => {
|
||||
const theme = useTheme();
|
||||
const { setSnack } = useAppState();
|
||||
const isMobile = useMediaQuery(theme.breakpoints.down('sm'));
|
||||
@ -63,23 +63,23 @@ const DocumentManager = (props: BackstoryElementProps) => {
|
||||
|
||||
// Load documents on component mount
|
||||
useEffect(() => {
|
||||
const loadDocuments = async (): Promise<void> => {
|
||||
try {
|
||||
const results = await apiClient.getCandidateDocuments();
|
||||
setDocuments(results.documents);
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
setSnack('Failed to load documents', 'error');
|
||||
}
|
||||
};
|
||||
|
||||
if (candidate) {
|
||||
loadDocuments();
|
||||
}
|
||||
}, [candidate]);
|
||||
|
||||
const loadDocuments = async () => {
|
||||
try {
|
||||
const results = await apiClient.getCandidateDocuments();
|
||||
setDocuments(results.documents);
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
setSnack('Failed to load documents', 'error');
|
||||
}
|
||||
};
|
||||
}, [candidate, apiClient, setSnack]);
|
||||
|
||||
// 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]) {
|
||||
const file = e.target.files[0];
|
||||
const fileExtension = '.' + file.name.split('.').pop()?.toLowerCase();
|
||||
@ -132,7 +132,7 @@ const DocumentManager = (props: BackstoryElementProps) => {
|
||||
};
|
||||
|
||||
// Handle document deletion
|
||||
const handleDeleteDocument = async (document: Types.Document) => {
|
||||
const handleDeleteDocument = async (document: Types.Document): Promise<void> => {
|
||||
try {
|
||||
// Call API to delete document
|
||||
await apiClient.deleteCandidateDocument(document);
|
||||
@ -152,7 +152,10 @@ const DocumentManager = (props: BackstoryElementProps) => {
|
||||
};
|
||||
|
||||
// Handle RAG flag toggle
|
||||
const handleRAGToggle = async (document: Types.Document, includeInRag: boolean) => {
|
||||
const handleRAGToggle = async (
|
||||
document: Types.Document,
|
||||
includeInRag: boolean
|
||||
): Promise<void> => {
|
||||
try {
|
||||
document.options = { includeInRag };
|
||||
// Call API to update RAG flag
|
||||
@ -168,7 +171,7 @@ const DocumentManager = (props: BackstoryElementProps) => {
|
||||
};
|
||||
|
||||
// Handle document rename
|
||||
const handleRenameDocument = async (document: Types.Document, newName: string) => {
|
||||
const handleRenameDocument = async (document: Types.Document, newName: string): Promise<void> => {
|
||||
if (!newName.trim()) {
|
||||
setSnack('Document name cannot be empty', 'error');
|
||||
return;
|
||||
@ -192,7 +195,7 @@ const DocumentManager = (props: BackstoryElementProps) => {
|
||||
};
|
||||
|
||||
// Handle document content viewing
|
||||
const handleViewDocument = async (document: Types.Document) => {
|
||||
const handleViewDocument = async (document: Types.Document): Promise<void> => {
|
||||
try {
|
||||
setSelectedDocument(document);
|
||||
setIsViewingContent(true);
|
||||
@ -207,7 +210,7 @@ const DocumentManager = (props: BackstoryElementProps) => {
|
||||
};
|
||||
|
||||
// Start rename process
|
||||
const startRename = (document: Types.Document, currentName: string) => {
|
||||
const startRename = (document: Types.Document, currentName: string): void => {
|
||||
setEditingDocument(document);
|
||||
setEditingName(currentName);
|
||||
setIsRenameDialogOpen(true);
|
||||
@ -331,7 +334,9 @@ const DocumentManager = (props: BackstoryElementProps) => {
|
||||
control={
|
||||
<Switch
|
||||
checked={doc.options?.includeInRag}
|
||||
onChange={e => handleRAGToggle(doc, e.target.checked)}
|
||||
onChange={(e): void => {
|
||||
handleRAGToggle(doc, e.target.checked);
|
||||
}}
|
||||
size="small"
|
||||
/>
|
||||
}
|
||||
@ -346,7 +351,9 @@ const DocumentManager = (props: BackstoryElementProps) => {
|
||||
<IconButton
|
||||
edge="end"
|
||||
size="small"
|
||||
onClick={() => handleViewDocument(doc)}
|
||||
onClick={(): void => {
|
||||
handleViewDocument(doc);
|
||||
}}
|
||||
title="View content"
|
||||
>
|
||||
<Visibility />
|
||||
@ -354,7 +361,9 @@ const DocumentManager = (props: BackstoryElementProps) => {
|
||||
<IconButton
|
||||
edge="end"
|
||||
size="small"
|
||||
onClick={() => startRename(doc, doc.filename)}
|
||||
onClick={(): void => {
|
||||
startRename(doc, doc.filename);
|
||||
}}
|
||||
title="Rename"
|
||||
>
|
||||
<Edit />
|
||||
@ -362,7 +371,9 @@ const DocumentManager = (props: BackstoryElementProps) => {
|
||||
<IconButton
|
||||
edge="end"
|
||||
size="small"
|
||||
onClick={() => handleDeleteDocument(doc)}
|
||||
onClick={(): void => {
|
||||
handleDeleteDocument(doc);
|
||||
}}
|
||||
title="Delete"
|
||||
color="error"
|
||||
>
|
||||
@ -395,7 +406,7 @@ const DocumentManager = (props: BackstoryElementProps) => {
|
||||
<Typography variant={isMobile ? 'subtitle2' : 'h6'}>Document Content</Typography>
|
||||
<IconButton
|
||||
size="small"
|
||||
onClick={() => {
|
||||
onClick={(): void => {
|
||||
setIsViewingContent(false);
|
||||
setSelectedDocument(null);
|
||||
setDocumentContent('');
|
||||
@ -433,7 +444,9 @@ const DocumentManager = (props: BackstoryElementProps) => {
|
||||
{/* Rename Dialog */}
|
||||
<Dialog
|
||||
open={isRenameDialogOpen}
|
||||
onClose={() => setIsRenameDialogOpen(false)}
|
||||
onClose={(): void => {
|
||||
setIsRenameDialogOpen(false);
|
||||
}}
|
||||
maxWidth="sm"
|
||||
fullWidth
|
||||
>
|
||||
@ -446,8 +459,10 @@ const DocumentManager = (props: BackstoryElementProps) => {
|
||||
fullWidth
|
||||
variant="outlined"
|
||||
value={editingName}
|
||||
onChange={e => setEditingName(e.target.value)}
|
||||
onKeyPress={e => {
|
||||
onChange={(e): void => {
|
||||
setEditingName(e.target.value);
|
||||
}}
|
||||
onKeyUp={(e): void => {
|
||||
if (e.key === 'Enter' && editingDocument) {
|
||||
handleRenameDocument(editingDocument, editingName);
|
||||
}
|
||||
@ -455,9 +470,17 @@ const DocumentManager = (props: BackstoryElementProps) => {
|
||||
/>
|
||||
</DialogContent>
|
||||
<DialogActions>
|
||||
<Button onClick={() => setIsRenameDialogOpen(false)}>Cancel</Button>
|
||||
<Button
|
||||
onClick={() => editingDocument && handleRenameDocument(editingDocument, editingName)}
|
||||
onClick={(): void => {
|
||||
setIsRenameDialogOpen(false);
|
||||
}}
|
||||
>
|
||||
Cancel
|
||||
</Button>
|
||||
<Button
|
||||
onClick={(): void => {
|
||||
editingDocument && handleRenameDocument(editingDocument, editingName);
|
||||
}}
|
||||
variant="contained"
|
||||
disabled={!editingName.trim()}
|
||||
>
|
||||
|
@ -1,4 +1,4 @@
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import React, { useState, useEffect, JSX } from 'react';
|
||||
import {
|
||||
Box,
|
||||
Card,
|
||||
@ -8,8 +8,6 @@ import {
|
||||
Button,
|
||||
Alert,
|
||||
CircularProgress,
|
||||
Link,
|
||||
Divider,
|
||||
InputAdornment,
|
||||
Dialog,
|
||||
DialogTitle,
|
||||
@ -32,14 +30,14 @@ import {
|
||||
} from '@mui/icons-material';
|
||||
import { useAuth } from 'hooks/AuthContext';
|
||||
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
|
||||
const EmailVerificationPage = (props: BackstoryPageProps) => {
|
||||
const EmailVerificationPage = (_props: BackstoryPageProps): JSX.Element => {
|
||||
const { verifyEmail, resendEmailVerification, getPendingVerificationEmail, isLoading, error } =
|
||||
useAuth();
|
||||
const navigate = useNavigate();
|
||||
const [verificationToken, setVerificationToken] = useState('');
|
||||
const [status, setStatus] = useState<'pending' | 'success' | 'error'>('pending');
|
||||
const [message, setMessage] = useState('');
|
||||
const [userType, setUserType] = useState<string>('');
|
||||
@ -49,42 +47,41 @@ const EmailVerificationPage = (props: BackstoryPageProps) => {
|
||||
const urlParams = new URLSearchParams(window.location.search);
|
||||
const token = urlParams.get('token');
|
||||
|
||||
if (token) {
|
||||
setVerificationToken(token);
|
||||
handleVerifyEmail(token);
|
||||
}
|
||||
}, []);
|
||||
const handleVerifyEmail = async (token: string): Promise<void> => {
|
||||
if (!token) {
|
||||
setStatus('error');
|
||||
setMessage('Invalid verification link');
|
||||
return;
|
||||
}
|
||||
|
||||
const handleVerifyEmail = async (token: string) => {
|
||||
if (!token) {
|
||||
setStatus('error');
|
||||
setMessage('Invalid verification link');
|
||||
return;
|
||||
}
|
||||
try {
|
||||
const result = await verifyEmail({ token });
|
||||
|
||||
try {
|
||||
const result = await verifyEmail({ token });
|
||||
if (result) {
|
||||
setStatus('success');
|
||||
setMessage(result.message);
|
||||
setUserType(result.userType);
|
||||
|
||||
if (result) {
|
||||
setStatus('success');
|
||||
setMessage(result.message);
|
||||
setUserType(result.userType);
|
||||
|
||||
// Redirect to login after 3 seconds
|
||||
setTimeout(() => {
|
||||
navigate('/login');
|
||||
}, 3000);
|
||||
} else {
|
||||
// Redirect to login after 3 seconds
|
||||
setTimeout(() => {
|
||||
navigate('/login');
|
||||
}, 3000);
|
||||
} else {
|
||||
setStatus('error');
|
||||
setMessage('Email verification failed');
|
||||
}
|
||||
} catch (error) {
|
||||
setStatus('error');
|
||||
setMessage('Email verification failed');
|
||||
}
|
||||
} catch (error) {
|
||||
setStatus('error');
|
||||
setMessage('Email verification failed');
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
const handleResendVerification = async () => {
|
||||
if (token) {
|
||||
handleVerifyEmail(token);
|
||||
}
|
||||
}, [navigate, verifyEmail]);
|
||||
|
||||
const handleResendVerification = async (): Promise<void> => {
|
||||
const email = getPendingVerificationEmail();
|
||||
if (!email) {
|
||||
setMessage('No pending verification email found.');
|
||||
@ -146,7 +143,7 @@ const EmailVerificationPage = (props: BackstoryPageProps) => {
|
||||
Verification Failed
|
||||
</Typography>
|
||||
<Typography color="text.secondary">
|
||||
We couldn't verify your email address.
|
||||
We couldn't verify your email address.
|
||||
</Typography>
|
||||
</>
|
||||
)}
|
||||
@ -172,7 +169,13 @@ const EmailVerificationPage = (props: BackstoryPageProps) => {
|
||||
<Typography variant="body2" color="text.secondary" mb={2}>
|
||||
You will be redirected to the login page in a few seconds...
|
||||
</Typography>
|
||||
<Button variant="contained" onClick={() => navigate('/login')} fullWidth>
|
||||
<Button
|
||||
variant="contained"
|
||||
onClick={(): void => {
|
||||
navigate('/login');
|
||||
}}
|
||||
fullWidth
|
||||
>
|
||||
Go to Login
|
||||
</Button>
|
||||
</Box>
|
||||
@ -190,7 +193,13 @@ const EmailVerificationPage = (props: BackstoryPageProps) => {
|
||||
>
|
||||
Resend Verification Email
|
||||
</Button>
|
||||
<Button variant="contained" onClick={() => navigate('/login')} fullWidth>
|
||||
<Button
|
||||
variant="contained"
|
||||
onClick={(): void => {
|
||||
navigate('/login');
|
||||
}}
|
||||
fullWidth
|
||||
>
|
||||
Back to Login
|
||||
</Button>
|
||||
</Box>
|
||||
@ -205,9 +214,9 @@ const EmailVerificationPage = (props: BackstoryPageProps) => {
|
||||
interface MFAVerificationDialogProps {
|
||||
open: boolean;
|
||||
onClose: () => void;
|
||||
onVerificationSuccess: (authData: any) => void;
|
||||
onVerificationSuccess: () => void;
|
||||
}
|
||||
const MFAVerificationDialog = (props: MFAVerificationDialogProps) => {
|
||||
const MFAVerificationDialog = (props: MFAVerificationDialogProps): JSX.Element => {
|
||||
const { open, onClose, onVerificationSuccess } = props;
|
||||
const { verifyMFA, resendMFACode, clearMFA, mfaResponse, isLoading, error } = useAuth();
|
||||
const [code, setCode] = useState('');
|
||||
@ -243,13 +252,13 @@ const MFAVerificationDialog = (props: MFAVerificationDialogProps) => {
|
||||
return () => clearInterval(timer);
|
||||
}, [open]);
|
||||
|
||||
const formatTime = (seconds: number) => {
|
||||
const formatTime = (seconds: number): string => {
|
||||
const mins = Math.floor(seconds / 60);
|
||||
const secs = seconds % 60;
|
||||
return `${mins}:${secs.toString().padStart(2, '0')}`;
|
||||
};
|
||||
|
||||
const handleVerifyMFA = async () => {
|
||||
const handleVerifyMFA = async (): Promise<void> => {
|
||||
if (!code || code.length !== 6) {
|
||||
setLocalError('Please enter a valid 6-digit code');
|
||||
return;
|
||||
@ -271,7 +280,7 @@ const MFAVerificationDialog = (props: MFAVerificationDialogProps) => {
|
||||
});
|
||||
|
||||
if (success) {
|
||||
onVerificationSuccess({ success: true });
|
||||
onVerificationSuccess();
|
||||
onClose();
|
||||
}
|
||||
} catch (error) {
|
||||
@ -279,7 +288,7 @@ const MFAVerificationDialog = (props: MFAVerificationDialogProps) => {
|
||||
}
|
||||
};
|
||||
|
||||
const handleResendCode = async () => {
|
||||
const handleResendCode = async (): Promise<void> => {
|
||||
if (!mfaResponse || !mfaResponse.mfaData) {
|
||||
setLocalError('MFA data not available');
|
||||
return;
|
||||
@ -301,12 +310,12 @@ const MFAVerificationDialog = (props: MFAVerificationDialogProps) => {
|
||||
}
|
||||
};
|
||||
|
||||
const handleClose = () => {
|
||||
const handleClose = (): void => {
|
||||
clearMFA();
|
||||
onClose();
|
||||
};
|
||||
|
||||
if (!mfaResponse || !mfaResponse.mfaData) return null;
|
||||
if (!mfaResponse || !mfaResponse.mfaData) return <></>;
|
||||
|
||||
return (
|
||||
<Dialog open={open} onClose={handleClose} maxWidth="sm" fullWidth>
|
||||
@ -319,12 +328,12 @@ const MFAVerificationDialog = (props: MFAVerificationDialogProps) => {
|
||||
|
||||
<DialogContent>
|
||||
<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>
|
||||
</Alert>
|
||||
|
||||
<Typography variant="body1" gutterBottom>
|
||||
We've sent a 6-digit verification code to:
|
||||
We've sent a 6-digit verification code to:
|
||||
</Typography>
|
||||
<Typography variant="h6" color="primary" gutterBottom>
|
||||
{mfaResponse.mfaData.email}
|
||||
@ -334,7 +343,7 @@ const MFAVerificationDialog = (props: MFAVerificationDialogProps) => {
|
||||
fullWidth
|
||||
label="Enter 6-digit code"
|
||||
value={code}
|
||||
onChange={e => {
|
||||
onChange={(e): void => {
|
||||
const value = e.target.value.replace(/\D/g, '').slice(0, 6);
|
||||
setCode(value);
|
||||
setLocalError('');
|
||||
@ -370,7 +379,9 @@ const MFAVerificationDialog = (props: MFAVerificationDialogProps) => {
|
||||
control={
|
||||
<Checkbox
|
||||
checked={rememberDevice}
|
||||
onChange={e => setRememberDevice(e.target.checked)}
|
||||
onChange={(e): void => {
|
||||
setRememberDevice(e.target.checked);
|
||||
}}
|
||||
/>
|
||||
}
|
||||
label="Remember this device for 90 days"
|
||||
@ -378,7 +389,7 @@ const MFAVerificationDialog = (props: MFAVerificationDialogProps) => {
|
||||
|
||||
<Alert severity="warning" sx={{ mt: 2 }}>
|
||||
<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>
|
||||
</Alert>
|
||||
</DialogContent>
|
||||
@ -410,18 +421,19 @@ const RegistrationSuccessDialog = ({
|
||||
onClose: () => void;
|
||||
email: string;
|
||||
userType: string;
|
||||
}) => {
|
||||
}): JSX.Element => {
|
||||
const { resendEmailVerification, isLoading } = useAuth();
|
||||
const [resendMessage, setResendMessage] = useState('');
|
||||
|
||||
const handleResendVerification = async () => {
|
||||
const handleResendVerification = async (): Promise<void> => {
|
||||
try {
|
||||
const success = await resendEmailVerification(email);
|
||||
if (success) {
|
||||
setResendMessage('Verification email sent!');
|
||||
}
|
||||
} catch (error: any) {
|
||||
setResendMessage(error?.message || 'Network error. Please try again.');
|
||||
} catch (error: unknown) {
|
||||
const tmp = error as { message?: string };
|
||||
setResendMessage(tmp?.message || 'Network error. Please try again.');
|
||||
}
|
||||
};
|
||||
|
||||
@ -435,7 +447,7 @@ const RegistrationSuccessDialog = ({
|
||||
</Typography>
|
||||
|
||||
<Typography variant="body1" color="text.secondary" paragraph>
|
||||
We've sent a verification link to:
|
||||
We'e sent a verification link to:
|
||||
</Typography>
|
||||
|
||||
<Typography variant="h6" color="primary" gutterBottom>
|
||||
@ -478,7 +490,7 @@ const RegistrationSuccessDialog = ({
|
||||
};
|
||||
|
||||
// Enhanced Login Component with MFA Support
|
||||
const LoginForm = () => {
|
||||
const LoginForm = (): JSX.Element => {
|
||||
const { login, mfaResponse, isLoading, error, user } = useAuth();
|
||||
const [email, setEmail] = useState('');
|
||||
const [password, setPassword] = useState('');
|
||||
@ -496,7 +508,7 @@ const LoginForm = () => {
|
||||
setErrorMessage(data.error.message);
|
||||
}, [error]);
|
||||
|
||||
const handleLogin = async (e: React.FormEvent) => {
|
||||
const handleLogin = async (e: React.FormEvent): Promise<void> => {
|
||||
e.preventDefault();
|
||||
|
||||
const success = await login({
|
||||
@ -512,11 +524,11 @@ const LoginForm = () => {
|
||||
}
|
||||
};
|
||||
|
||||
const handleMFASuccess = (authData: any) => {
|
||||
const handleMFASuccess = (): void => {
|
||||
handleLoginSuccess();
|
||||
};
|
||||
|
||||
const handleLoginSuccess = () => {
|
||||
const handleLoginSuccess = (): void => {
|
||||
if (!user) {
|
||||
navigate('/');
|
||||
} else {
|
||||
@ -533,7 +545,9 @@ const LoginForm = () => {
|
||||
fullWidth
|
||||
label="Email or Username"
|
||||
value={email}
|
||||
onChange={e => setEmail(e.target.value)}
|
||||
onChange={(e): void => {
|
||||
setEmail(e.target.value);
|
||||
}}
|
||||
autoComplete="email"
|
||||
autoFocus
|
||||
/>
|
||||
@ -542,7 +556,9 @@ const LoginForm = () => {
|
||||
label="Password"
|
||||
type={showPassword ? 'text' : 'password'}
|
||||
value={password}
|
||||
onChange={e => setPassword(e.target.value)}
|
||||
onChange={(e): void => {
|
||||
setPassword(e.target.value);
|
||||
}}
|
||||
autoComplete="current-password"
|
||||
placeholder="Create a strong password"
|
||||
required
|
||||
@ -551,8 +567,12 @@ const LoginForm = () => {
|
||||
<InputAdornment position="end">
|
||||
<IconButton
|
||||
aria-label="toggle password visibility"
|
||||
onClick={() => setShowPassword(!showPassword)}
|
||||
onMouseDown={e => e.preventDefault()}
|
||||
onClick={(): void => {
|
||||
setShowPassword(!showPassword);
|
||||
}}
|
||||
onMouseDown={(e): void => {
|
||||
e.preventDefault();
|
||||
}}
|
||||
edge="end"
|
||||
>
|
||||
{showPassword ? <VisibilityOff /> : <Visibility />}
|
||||
@ -581,7 +601,9 @@ const LoginForm = () => {
|
||||
{/* MFA Dialog */}
|
||||
<MFAVerificationDialog
|
||||
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}
|
||||
/>
|
||||
</Box>
|
||||
@ -589,9 +611,9 @@ const LoginForm = () => {
|
||||
};
|
||||
|
||||
// Device Management Component
|
||||
const TrustedDevicesManager = () => {
|
||||
const [devices, setDevices] = useState<any[]>([]);
|
||||
const [loading, setLoading] = useState(true);
|
||||
const TrustedDevicesManager = (): JSX.Element => {
|
||||
const [devices, _setDevices] = useState<MFAData[]>([]);
|
||||
const [_loading, setLoading] = useState(true);
|
||||
|
||||
// This would need API endpoints to manage trusted devices
|
||||
useEffect(() => {
|
||||
@ -608,8 +630,8 @@ const TrustedDevicesManager = () => {
|
||||
</Typography>
|
||||
|
||||
<Typography variant="body2" color="text.secondary" paragraph>
|
||||
Manage devices that you've marked as trusted. You won't need to verify your identity when
|
||||
signing in from these devices.
|
||||
Manage devices that you've marked as trusted. You won't need to verify your
|
||||
identity when signing in from these devices.
|
||||
</Typography>
|
||||
|
||||
{devices.length === 0 ? (
|
||||
@ -624,18 +646,18 @@ const TrustedDevicesManager = () => {
|
||||
<Card variant="outlined">
|
||||
<CardContent>
|
||||
<Typography variant="subtitle1">{device.deviceName}</Typography>
|
||||
<Typography variant="body2" color="text.secondary">
|
||||
{/* <Typography variant="body2" color="text.secondary">
|
||||
Added: {new Date(device.addedAt).toLocaleDateString()}
|
||||
</Typography>
|
||||
<Typography variant="body2" color="text.secondary">
|
||||
</Typography> */}
|
||||
{/* <Typography variant="body2" color="text.secondary">
|
||||
Last used: {new Date(device.lastUsed).toLocaleDateString()}
|
||||
</Typography>
|
||||
</Typography> */}
|
||||
<Button
|
||||
size="small"
|
||||
color="error"
|
||||
sx={{ mt: 1 }}
|
||||
onClick={() => {
|
||||
// Remove device
|
||||
onClick={(): void => {
|
||||
console.log('Remove device');
|
||||
}}
|
||||
>
|
||||
Remove
|
||||
|
@ -1,3 +1,4 @@
|
||||
import React, { JSX } from 'react';
|
||||
import { styled } from '@mui/material/styles';
|
||||
import IconButton, { IconButtonProps } from '@mui/material/IconButton';
|
||||
|
||||
@ -5,7 +6,8 @@ interface ExpandMoreProps extends IconButtonProps {
|
||||
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;
|
||||
return <IconButton {...other} />;
|
||||
})(({ theme }) => ({
|
||||
@ -15,13 +17,13 @@ const ExpandMore = styled((props: ExpandMoreProps) => {
|
||||
}),
|
||||
variants: [
|
||||
{
|
||||
props: ({ expand }) => !expand,
|
||||
props: ({ expand }): boolean => !expand,
|
||||
style: {
|
||||
transform: 'rotate(0deg)',
|
||||
},
|
||||
},
|
||||
{
|
||||
props: ({ expand }) => !!expand,
|
||||
props: ({ expand }): boolean => !!expand,
|
||||
style: {
|
||||
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 PropagateLoader from 'react-spinners/PropagateLoader';
|
||||
import { Quote } from 'components/Quote';
|
||||
import { BackstoryElementProps } from 'components/BackstoryTab';
|
||||
import { Candidate, ChatSession } from 'types/types';
|
||||
import { ChatSession } from 'types/types';
|
||||
import { useAuth } from 'hooks/AuthContext';
|
||||
import { useAppState } from 'hooks/GlobalContext';
|
||||
|
||||
@ -12,16 +12,13 @@ interface GenerateImageProps extends BackstoryElementProps {
|
||||
chatSession: ChatSession;
|
||||
}
|
||||
|
||||
const GenerateImage = (props: GenerateImageProps) => {
|
||||
const GenerateImage = (props: GenerateImageProps): JSX.Element => {
|
||||
const { user } = useAuth();
|
||||
const { chatSession, prompt } = props;
|
||||
const { setSnack } = useAppState();
|
||||
const [processing, setProcessing] = useState<boolean>(false);
|
||||
const [status, setStatus] = 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 [image, _setImage] = useState<string>('');
|
||||
const controllerRef = useRef<string>(null);
|
||||
|
||||
// Effect to trigger profile generation when user data is ready
|
||||
@ -35,8 +32,8 @@ const GenerateImage = (props: GenerateImageProps) => {
|
||||
}
|
||||
setStatus('Starting image generation...');
|
||||
setProcessing(true);
|
||||
const start = Date.now();
|
||||
|
||||
// const start = Date.now();
|
||||
// controllerRef.current = streamQueryResponse({
|
||||
// query: {
|
||||
// prompt: prompt,
|
||||
|
@ -13,16 +13,9 @@ import {
|
||||
CardHeader,
|
||||
LinearProgress,
|
||||
Stack,
|
||||
Paper,
|
||||
} from '@mui/material';
|
||||
import {
|
||||
SyncAlt,
|
||||
Favorite,
|
||||
Settings,
|
||||
Info,
|
||||
Search,
|
||||
AutoFixHigh,
|
||||
Image,
|
||||
Psychology,
|
||||
Build,
|
||||
CloudUpload,
|
||||
@ -36,9 +29,8 @@ import { styled } from '@mui/material/styles';
|
||||
import FileUploadIcon from '@mui/icons-material/FileUpload';
|
||||
|
||||
import { useAuth } from 'hooks/AuthContext';
|
||||
import { useAppState, useSelectedJob } from 'hooks/GlobalContext';
|
||||
import { useAppState } from 'hooks/GlobalContext';
|
||||
import { BackstoryElementProps } from './BackstoryTab';
|
||||
import { LoginRequired } from 'components/ui/LoginRequired';
|
||||
|
||||
import * as Types from 'types/types';
|
||||
import { StyledMarkdown } from './StyledMarkdown';
|
||||
@ -60,7 +52,10 @@ const VisuallyHiddenInput = styled('input')({
|
||||
|
||||
const UploadBox = styled(Box)(({ theme }) => ({
|
||||
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),
|
||||
textAlign: 'center',
|
||||
backgroundColor: theme.palette.action.hover,
|
||||
@ -75,10 +70,9 @@ const UploadBox = styled(Box)(({ theme }) => ({
|
||||
interface JobCreatorProps extends BackstoryElementProps {
|
||||
onSave?: (job: Types.Job) => void;
|
||||
}
|
||||
const JobCreator = (props: JobCreatorProps) => {
|
||||
const JobCreator = (props: JobCreatorProps): JSX.Element => {
|
||||
const { user, apiClient } = useAuth();
|
||||
const { onSave } = props;
|
||||
const { selectedJob, setSelectedJob } = useSelectedJob();
|
||||
const { setSnack } = useAppState();
|
||||
const theme = useTheme();
|
||||
const isMobile = useMediaQuery(theme.breakpoints.down('sm'));
|
||||
@ -96,12 +90,12 @@ const JobCreator = (props: JobCreatorProps) => {
|
||||
const fileInputRef = useRef<HTMLInputElement>(null);
|
||||
|
||||
const jobStatusHandlers = {
|
||||
onStatus: (status: Types.ChatMessageStatus) => {
|
||||
onStatus: (status: Types.ChatMessageStatus): void => {
|
||||
console.log('status:', status.content);
|
||||
setJobStatusType(status.activity);
|
||||
setJobStatus(status.content);
|
||||
},
|
||||
onMessage: (jobMessage: Types.JobRequirementsMessage) => {
|
||||
onMessage: (jobMessage: Types.JobRequirementsMessage): void => {
|
||||
const job: Types.Job = jobMessage.job;
|
||||
console.log('onMessage - job', job);
|
||||
setJob(job);
|
||||
@ -113,19 +107,19 @@ const JobCreator = (props: JobCreatorProps) => {
|
||||
setJobStatusType(null);
|
||||
setJobStatus('');
|
||||
},
|
||||
onError: (error: Types.ChatMessageError) => {
|
||||
onError: (error: Types.ChatMessageError): void => {
|
||||
console.log('onError', error);
|
||||
setSnack(error.content, 'error');
|
||||
setIsProcessing(false);
|
||||
},
|
||||
onComplete: () => {
|
||||
onComplete: (): void => {
|
||||
setJobStatusType(null);
|
||||
setJobStatus('');
|
||||
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]) {
|
||||
const file = e.target.files[0];
|
||||
const fileExtension = '.' + file.name.split('.').pop()?.toLowerCase();
|
||||
@ -171,7 +165,7 @@ const JobCreator = (props: JobCreatorProps) => {
|
||||
}
|
||||
};
|
||||
|
||||
const handleUploadClick = () => {
|
||||
const handleUploadClick = (): void => {
|
||||
fileInputRef.current?.click();
|
||||
};
|
||||
|
||||
@ -180,8 +174,8 @@ const JobCreator = (props: JobCreatorProps) => {
|
||||
items: string[] | undefined,
|
||||
icon: JSX.Element,
|
||||
required = false
|
||||
) => {
|
||||
if (!items || items.length === 0) return null;
|
||||
): JSX.Element => {
|
||||
if (!items || items.length === 0) return <></>;
|
||||
|
||||
return (
|
||||
<Box sx={{ mb: 3 }}>
|
||||
@ -201,8 +195,8 @@ const JobCreator = (props: JobCreatorProps) => {
|
||||
);
|
||||
};
|
||||
|
||||
const renderJobRequirements = () => {
|
||||
if (!jobRequirements) return null;
|
||||
const renderJobRequirements = (): JSX.Element => {
|
||||
if (!jobRequirements) return <></>;
|
||||
|
||||
return (
|
||||
<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 = {
|
||||
ownerId: user?.id || '',
|
||||
ownerType: 'candidate',
|
||||
@ -286,7 +280,7 @@ const JobCreator = (props: JobCreatorProps) => {
|
||||
onSave && onSave(job);
|
||||
};
|
||||
|
||||
const handleExtractRequirements = async () => {
|
||||
const handleExtractRequirements = async (): Promise<void> => {
|
||||
try {
|
||||
setIsProcessing(true);
|
||||
const controller = apiClient.createJobFromDescription(jobDescription, jobStatusHandlers);
|
||||
@ -304,7 +298,7 @@ const JobCreator = (props: JobCreatorProps) => {
|
||||
setIsProcessing(false);
|
||||
};
|
||||
|
||||
const renderJobCreation = () => {
|
||||
const renderJobCreation = (): JSX.Element => {
|
||||
return (
|
||||
<Box
|
||||
sx={{
|
||||
@ -371,7 +365,9 @@ const JobCreator = (props: JobCreatorProps) => {
|
||||
placeholder="Paste or type the job description here..."
|
||||
variant="outlined"
|
||||
value={jobDescription}
|
||||
onChange={e => setJobDescription(e.target.value)}
|
||||
onChange={(e): void => {
|
||||
setJobDescription(e.target.value);
|
||||
}}
|
||||
disabled={isProcessing}
|
||||
sx={{ mb: 2 }}
|
||||
/>
|
||||
@ -418,7 +414,9 @@ const JobCreator = (props: JobCreatorProps) => {
|
||||
label="Job Title"
|
||||
variant="outlined"
|
||||
value={jobTitle}
|
||||
onChange={e => setJobTitle(e.target.value)}
|
||||
onChange={(e): void => {
|
||||
setJobTitle(e.target.value);
|
||||
}}
|
||||
required
|
||||
disabled={isProcessing}
|
||||
InputProps={{
|
||||
@ -433,7 +431,9 @@ const JobCreator = (props: JobCreatorProps) => {
|
||||
label="Company"
|
||||
variant="outlined"
|
||||
value={company}
|
||||
onChange={e => setCompany(e.target.value)}
|
||||
onChange={(e): void => {
|
||||
setCompany(e.target.value);
|
||||
}}
|
||||
required
|
||||
disabled={isProcessing}
|
||||
InputProps={{
|
||||
|
@ -1,15 +1,12 @@
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import React, { useState, useEffect, useCallback, JSX, useMemo } from 'react';
|
||||
import {
|
||||
Box,
|
||||
Typography,
|
||||
Paper,
|
||||
Accordion,
|
||||
AccordionSummary,
|
||||
AccordionDetails,
|
||||
CircularProgress,
|
||||
Grid,
|
||||
Chip,
|
||||
Divider,
|
||||
Card,
|
||||
CardContent,
|
||||
useTheme,
|
||||
@ -22,28 +19,11 @@ import CheckCircleIcon from '@mui/icons-material/CheckCircle';
|
||||
import ErrorIcon from '@mui/icons-material/Error';
|
||||
import PendingIcon from '@mui/icons-material/Pending';
|
||||
import WarningIcon from '@mui/icons-material/Warning';
|
||||
import {
|
||||
Candidate,
|
||||
ChatMessage,
|
||||
ChatMessageError,
|
||||
ChatMessageStatus,
|
||||
ChatMessageStreaming,
|
||||
ChatMessageUser,
|
||||
ChatSession,
|
||||
EvidenceDetail,
|
||||
JobRequirements,
|
||||
SkillAssessment,
|
||||
SkillStatus,
|
||||
} from 'types/types';
|
||||
import { Candidate, ChatMessage, SkillAssessment, SkillStatus } from 'types/types';
|
||||
import { useAuth } from 'hooks/AuthContext';
|
||||
import { BackstoryPageProps } from './BackstoryTab';
|
||||
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 JsonView from '@uiw/react-json-view';
|
||||
import { VectorVisualizer } from './VectorVisualizer';
|
||||
import { JobInfo } from './ui/JobInfo';
|
||||
|
||||
interface JobAnalysisProps extends BackstoryPageProps {
|
||||
@ -53,16 +33,6 @@ interface JobAnalysisProps extends BackstoryPageProps {
|
||||
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 {
|
||||
domain: string;
|
||||
status: SkillStatus;
|
||||
@ -72,20 +42,17 @@ interface SkillMatch extends SkillAssessment {
|
||||
const JobMatchAnalysis: React.FC<JobAnalysisProps> = (props: JobAnalysisProps) => {
|
||||
const { job, candidate, onAnalysisComplete, variant = 'normal' } = props;
|
||||
const { apiClient } = useAuth();
|
||||
const { setSnack } = useAppState();
|
||||
const theme = useTheme();
|
||||
const [requirements, setRequirements] = useState<{ requirement: string; domain: string }[]>([]);
|
||||
const [skillMatches, setSkillMatches] = useState<SkillMatch[]>([]);
|
||||
const [creatingSession, setCreatingSession] = useState<boolean>(false);
|
||||
const [loadingRequirements, setLoadingRequirements] = useState<boolean>(false);
|
||||
const [expanded, setExpanded] = useState<string | false>(false);
|
||||
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 [analyzing, setAnalyzing] = useState<boolean>(false);
|
||||
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'));
|
||||
|
||||
@ -95,93 +62,98 @@ const JobMatchAnalysis: React.FC<JobAnalysisProps> = (props: JobAnalysisProps) =
|
||||
setExpanded(isExpanded ? panel : false);
|
||||
};
|
||||
|
||||
const initializeRequirements = (job: Job) => {
|
||||
if (!job || !job.requirements) {
|
||||
return;
|
||||
}
|
||||
const requirements: { requirement: string; domain: string }[] = [];
|
||||
if (job.requirements?.technicalSkills) {
|
||||
job.requirements.technicalSkills.required?.forEach(req =>
|
||||
requirements.push({
|
||||
requirement: req,
|
||||
domain: 'Technical Skills (required)',
|
||||
})
|
||||
);
|
||||
job.requirements.technicalSkills.preferred?.forEach(req =>
|
||||
requirements.push({
|
||||
requirement: req,
|
||||
domain: 'Technical Skills (preferred)',
|
||||
})
|
||||
);
|
||||
}
|
||||
if (job.requirements?.experienceRequirements) {
|
||||
job.requirements.experienceRequirements.required?.forEach(req =>
|
||||
requirements.push({ requirement: req, domain: 'Experience (required)' })
|
||||
);
|
||||
job.requirements.experienceRequirements.preferred?.forEach(req =>
|
||||
requirements.push({
|
||||
requirement: req,
|
||||
domain: 'Experience (preferred)',
|
||||
})
|
||||
);
|
||||
}
|
||||
if (job.requirements?.softSkills) {
|
||||
job.requirements.softSkills.forEach(req =>
|
||||
requirements.push({ requirement: req, domain: 'Soft Skills' })
|
||||
);
|
||||
}
|
||||
if (job.requirements?.experience) {
|
||||
job.requirements.experience.forEach(req =>
|
||||
requirements.push({ requirement: req, domain: 'Experience' })
|
||||
);
|
||||
}
|
||||
if (job.requirements?.education) {
|
||||
job.requirements.education.forEach(req =>
|
||||
requirements.push({ requirement: req, domain: 'Education' })
|
||||
);
|
||||
}
|
||||
if (job.requirements?.certifications) {
|
||||
job.requirements.certifications.forEach(req =>
|
||||
requirements.push({ requirement: req, domain: 'Certifications' })
|
||||
);
|
||||
}
|
||||
if (job.requirements?.preferredAttributes) {
|
||||
job.requirements.preferredAttributes.forEach(req =>
|
||||
requirements.push({ requirement: req, domain: 'Preferred Attributes' })
|
||||
);
|
||||
}
|
||||
const initializeRequirements = useCallback(
|
||||
(job: Job): void => {
|
||||
if (!job || !job.requirements) {
|
||||
return;
|
||||
}
|
||||
const requirements: { requirement: string; domain: string }[] = [];
|
||||
if (job.requirements?.technicalSkills) {
|
||||
job.requirements.technicalSkills.required?.forEach(req =>
|
||||
requirements.push({
|
||||
requirement: req,
|
||||
domain: 'Technical Skills (required)',
|
||||
})
|
||||
);
|
||||
job.requirements.technicalSkills.preferred?.forEach(req =>
|
||||
requirements.push({
|
||||
requirement: req,
|
||||
domain: 'Technical Skills (preferred)',
|
||||
})
|
||||
);
|
||||
}
|
||||
if (job.requirements?.experienceRequirements) {
|
||||
job.requirements.experienceRequirements.required?.forEach(req =>
|
||||
requirements.push({ requirement: req, domain: 'Experience (required)' })
|
||||
);
|
||||
job.requirements.experienceRequirements.preferred?.forEach(req =>
|
||||
requirements.push({
|
||||
requirement: req,
|
||||
domain: 'Experience (preferred)',
|
||||
})
|
||||
);
|
||||
}
|
||||
if (job.requirements?.softSkills) {
|
||||
job.requirements.softSkills.forEach(req =>
|
||||
requirements.push({ requirement: req, domain: 'Soft Skills' })
|
||||
);
|
||||
}
|
||||
if (job.requirements?.experience) {
|
||||
job.requirements.experience.forEach(req =>
|
||||
requirements.push({ requirement: req, domain: 'Experience' })
|
||||
);
|
||||
}
|
||||
if (job.requirements?.education) {
|
||||
job.requirements.education.forEach(req =>
|
||||
requirements.push({ requirement: req, domain: 'Education' })
|
||||
);
|
||||
}
|
||||
if (job.requirements?.certifications) {
|
||||
job.requirements.certifications.forEach(req =>
|
||||
requirements.push({ requirement: req, domain: 'Certifications' })
|
||||
);
|
||||
}
|
||||
if (job.requirements?.preferredAttributes) {
|
||||
job.requirements.preferredAttributes.forEach(req =>
|
||||
requirements.push({ requirement: req, domain: 'Preferred Attributes' })
|
||||
);
|
||||
}
|
||||
|
||||
const initialSkillMatches: SkillMatch[] = requirements.map(req => ({
|
||||
skill: req.requirement,
|
||||
skillModified: req.requirement,
|
||||
candidateId: candidate.id || '',
|
||||
domain: req.domain,
|
||||
status: 'waiting' as const,
|
||||
assessment: '',
|
||||
description: '',
|
||||
evidenceFound: false,
|
||||
evidenceStrength: 'none',
|
||||
evidenceDetails: [],
|
||||
matchScore: 0,
|
||||
}));
|
||||
const initialSkillMatches: SkillMatch[] = requirements.map(req => ({
|
||||
skill: req.requirement,
|
||||
skillModified: req.requirement,
|
||||
candidateId: candidate.id || '',
|
||||
domain: req.domain,
|
||||
status: 'waiting' as const,
|
||||
assessment: '',
|
||||
description: '',
|
||||
evidenceFound: false,
|
||||
evidenceStrength: 'none',
|
||||
evidenceDetails: [],
|
||||
matchScore: 0,
|
||||
}));
|
||||
|
||||
setRequirements(requirements);
|
||||
setSkillMatches(initialSkillMatches);
|
||||
setStatusMessage(null);
|
||||
setLoadingRequirements(false);
|
||||
setOverallScore(0);
|
||||
};
|
||||
setRequirements(requirements);
|
||||
setSkillMatches(initialSkillMatches);
|
||||
setStatusMessage(null);
|
||||
setLoadingRequirements(false);
|
||||
setOverallScore(0);
|
||||
},
|
||||
[candidate.id]
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
initializeRequirements(job);
|
||||
}, [job]);
|
||||
}, [job, initializeRequirements]);
|
||||
|
||||
const skillMatchHandlers = {
|
||||
onStatus: (status: Types.ChatMessageStatus) => {
|
||||
setMatchStatusType(status.activity);
|
||||
setMatchStatus(status.content.toLowerCase());
|
||||
},
|
||||
};
|
||||
const skillMatchHandlers = useMemo(() => {
|
||||
return {
|
||||
onStatus: (status: Types.ChatMessageStatus): void => {
|
||||
setMatchStatusType(status.activity);
|
||||
setMatchStatus(status.content.toLowerCase());
|
||||
},
|
||||
};
|
||||
}, [setMatchStatus, setMatchStatusType]);
|
||||
|
||||
// Fetch match data for each requirement
|
||||
useEffect(() => {
|
||||
@ -189,7 +161,7 @@ const JobMatchAnalysis: React.FC<JobAnalysisProps> = (props: JobAnalysisProps) =
|
||||
return;
|
||||
}
|
||||
|
||||
const fetchMatchData = async (skills: SkillAssessment[]) => {
|
||||
const fetchMatchData = async (skills: SkillAssessment[]): Promise<void> => {
|
||||
if (requirements.length === 0) return;
|
||||
|
||||
// Process requirements one by one
|
||||
@ -201,7 +173,7 @@ const JobMatchAnalysis: React.FC<JobAnalysisProps> = (props: JobAnalysisProps) =
|
||||
return updated;
|
||||
});
|
||||
|
||||
const request: any = await apiClient.candidateMatchForRequirement(
|
||||
const request = await apiClient.candidateMatchForRequirement(
|
||||
candidate.id || '',
|
||||
requirements[i].requirement,
|
||||
skillMatchHandlers
|
||||
@ -226,11 +198,11 @@ const JobMatchAnalysis: React.FC<JobAnalysisProps> = (props: JobAnalysisProps) =
|
||||
break;
|
||||
}
|
||||
if (
|
||||
skillMatch.evidenceStrength == 'NONE' &&
|
||||
skillMatch.citations &&
|
||||
skillMatch.citations.length > 3
|
||||
skillMatch.evidenceStrength == 'none' &&
|
||||
skillMatch.evidenceDetails &&
|
||||
skillMatch.evidenceDetails.length > 3
|
||||
) {
|
||||
matchScore = Math.min(skillMatch.citations.length * 8, 40);
|
||||
matchScore = Math.min(skillMatch.evidenceDetails.length * 8, 40);
|
||||
}
|
||||
const match: SkillMatch = {
|
||||
...skillMatch,
|
||||
@ -277,7 +249,17 @@ const JobMatchAnalysis: React.FC<JobAnalysisProps> = (props: JobAnalysisProps) =
|
||||
setStartAnalysis(false);
|
||||
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
|
||||
const getMatchColor = (score: number): string => {
|
||||
@ -288,7 +270,7 @@ const JobMatchAnalysis: React.FC<JobAnalysisProps> = (props: JobAnalysisProps) =
|
||||
};
|
||||
|
||||
// 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 === 'error') return <ErrorIcon color="error" />;
|
||||
if (score >= 70) return <CheckCircleIcon color="success" />;
|
||||
@ -296,7 +278,7 @@ const JobMatchAnalysis: React.FC<JobAnalysisProps> = (props: JobAnalysisProps) =
|
||||
return <ErrorIcon color="error" />;
|
||||
};
|
||||
|
||||
const beginAnalysis = () => {
|
||||
const beginAnalysis = (): void => {
|
||||
initializeRequirements(job);
|
||||
setStartAnalysis(true);
|
||||
};
|
||||
@ -512,7 +494,7 @@ const JobMatchAnalysis: React.FC<JobAnalysisProps> = (props: JobAnalysisProps) =
|
||||
<Box sx={{ width: '100%', p: 2 }}>
|
||||
<LinearProgress />
|
||||
<Typography sx={{ mt: 2 }}>
|
||||
Analyzing candidate's match for this requirement... {matchStatus}
|
||||
Analyzing candidate's match for this requirement... {matchStatus}
|
||||
</Typography>
|
||||
</Box>
|
||||
) : match.status === 'error' ? (
|
||||
@ -549,7 +531,7 @@ const JobMatchAnalysis: React.FC<JobAnalysisProps> = (props: JobAnalysisProps) =
|
||||
component="div"
|
||||
sx={{ mb: 1, fontStyle: 'italic' }}
|
||||
>
|
||||
"{evidence.quote}"
|
||||
"{evidence.quote}"
|
||||
</Typography>
|
||||
<Box
|
||||
sx={{
|
||||
@ -578,7 +560,7 @@ const JobMatchAnalysis: React.FC<JobAnalysisProps> = (props: JobAnalysisProps) =
|
||||
))
|
||||
) : (
|
||||
<Typography color="text.secondary">
|
||||
No specific evidence found in candidate's profile.
|
||||
No specific evidence found in candidate's profile.
|
||||
</Typography>
|
||||
)}
|
||||
<Typography variant="h6" gutterBottom>
|
||||
|
@ -1,4 +1,4 @@
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import React, { useState, useEffect, useMemo } from 'react';
|
||||
import {
|
||||
Box,
|
||||
TextField,
|
||||
@ -45,13 +45,16 @@ const LocationInput: React.FC<LocationInputProps> = ({
|
||||
const [isRemote, setIsRemote] = useState<boolean>(value.remote || false);
|
||||
|
||||
// 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
|
||||
const availableCities =
|
||||
selectedCountry && selectedState
|
||||
const availableCities = useMemo(() => {
|
||||
return selectedCountry && selectedState
|
||||
? City.getCitiesOfState(selectedCountry.isoCode, selectedState.isoCode)
|
||||
: [];
|
||||
}, [selectedCountry, selectedState]);
|
||||
|
||||
// Initialize state and city from value prop
|
||||
useEffect(() => {
|
||||
@ -104,24 +107,24 @@ const LocationInput: React.FC<LocationInputProps> = ({
|
||||
showCity,
|
||||
]);
|
||||
|
||||
const handleCountryChange = (event: any, newValue: ICountry | null) => {
|
||||
const handleCountryChange = (_event: React.SyntheticEvent, newValue: ICountry | null): void => {
|
||||
setSelectedCountry(newValue);
|
||||
// Clear state and city when country changes
|
||||
setSelectedState(null);
|
||||
setSelectedCity(null);
|
||||
};
|
||||
|
||||
const handleStateChange = (event: any, newValue: IState | null) => {
|
||||
const handleStateChange = (_event: React.SyntheticEvent, newValue: IState | null): void => {
|
||||
setSelectedState(newValue);
|
||||
// Clear city when state changes
|
||||
setSelectedCity(null);
|
||||
};
|
||||
|
||||
const handleCityChange = (event: any, newValue: ICity | null) => {
|
||||
const handleCityChange = (_event: React.SyntheticEvent, newValue: ICity | null): void => {
|
||||
setSelectedCity(newValue);
|
||||
};
|
||||
|
||||
const handleRemoteToggle = (event: React.ChangeEvent<HTMLInputElement>) => {
|
||||
const handleRemoteToggle = (event: React.ChangeEvent<HTMLInputElement>): void => {
|
||||
setIsRemote(event.target.checked);
|
||||
};
|
||||
|
||||
@ -139,9 +142,9 @@ const LocationInput: React.FC<LocationInputProps> = ({
|
||||
value={selectedCountry}
|
||||
onChange={handleCountryChange}
|
||||
options={allCountries}
|
||||
getOptionLabel={option => option.name}
|
||||
getOptionLabel={(option): string => option.name}
|
||||
disabled={disabled}
|
||||
renderInput={params => (
|
||||
renderInput={(params): React.ReactNode => (
|
||||
<TextField
|
||||
{...params}
|
||||
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}>
|
||||
<img
|
||||
loading="lazy"
|
||||
@ -180,9 +183,9 @@ const LocationInput: React.FC<LocationInputProps> = ({
|
||||
value={selectedState}
|
||||
onChange={handleStateChange}
|
||||
options={availableStates}
|
||||
getOptionLabel={option => option.name}
|
||||
getOptionLabel={(option): string => option.name}
|
||||
disabled={disabled || availableStates.length === 0}
|
||||
renderInput={params => (
|
||||
renderInput={(params): React.ReactNode => (
|
||||
<TextField
|
||||
{...params}
|
||||
label="State/Region"
|
||||
@ -203,9 +206,9 @@ const LocationInput: React.FC<LocationInputProps> = ({
|
||||
value={selectedCity}
|
||||
onChange={handleCityChange}
|
||||
options={availableCities}
|
||||
getOptionLabel={option => option.name}
|
||||
getOptionLabel={(option): string => option.name}
|
||||
disabled={disabled || availableCities.length === 0}
|
||||
renderInput={params => (
|
||||
renderInput={(params): React.ReactNode => (
|
||||
<TextField
|
||||
{...params}
|
||||
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 };
|
||||
|
@ -32,7 +32,7 @@ const Mermaid: React.FC<MermaidProps> = (props: MermaidProps) => {
|
||||
}, [containerRef, setVisible]);
|
||||
|
||||
useEffect(() => {
|
||||
const renderMermaid = async () => {
|
||||
const renderMermaid = async (): Promise<void> => {
|
||||
if (containerRef.current && visible && chart) {
|
||||
try {
|
||||
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 Accordion from '@mui/material/Accordion';
|
||||
import AccordionSummary from '@mui/material/AccordionSummary';
|
||||
@ -12,11 +12,9 @@ import TableHead from '@mui/material/TableHead';
|
||||
import TableRow from '@mui/material/TableRow';
|
||||
import Button from '@mui/material/Button';
|
||||
import CardContent from '@mui/material/CardContent';
|
||||
import CardActions from '@mui/material/CardActions';
|
||||
import Collapse from '@mui/material/Collapse';
|
||||
import { ExpandMore } from './ExpandMore';
|
||||
import JsonView from '@uiw/react-json-view';
|
||||
import React from 'react';
|
||||
import { Box } from '@mui/material';
|
||||
import { useTheme } from '@mui/material/styles';
|
||||
import { SxProps, Theme } from '@mui/material';
|
||||
@ -28,9 +26,7 @@ import { ErrorOutline, InfoOutline, Memory, Psychology /* Stream, */ } from '@mu
|
||||
import { StyledMarkdown } from './StyledMarkdown';
|
||||
|
||||
import { VectorVisualizer } from './VectorVisualizer';
|
||||
import { SetSnackType } from './Snack';
|
||||
import { CopyBubble } from './CopyBubble';
|
||||
import { Scrollable } from './Scrollable';
|
||||
import { BackstoryElementProps } from './BackstoryTab';
|
||||
import {
|
||||
ChatMessage,
|
||||
@ -44,7 +40,10 @@ import {
|
||||
ChatSenderType,
|
||||
} 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 defaultStyle = {
|
||||
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: {
|
||||
...defaultStyle,
|
||||
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}`);
|
||||
}
|
||||
|
||||
return styles[type];
|
||||
return styles[type] as Record<string, string>;
|
||||
};
|
||||
|
||||
const getIcon = (
|
||||
activityType: ApiActivityType | ChatSenderType | 'error'
|
||||
): React.ReactNode | null => {
|
||||
const icons: any = {
|
||||
const icons: Record<string, React.ReactNode> = {
|
||||
error: <ErrorOutline color="error" />,
|
||||
generating: <LocationSearchingIcon />,
|
||||
information: <InfoOutline color="info" />,
|
||||
@ -207,7 +206,7 @@ interface MessageMetaProps {
|
||||
messageProps: MessageProps;
|
||||
}
|
||||
|
||||
const MessageMeta = (props: MessageMetaProps) => {
|
||||
const MessageMeta = (props: MessageMetaProps): JSX.Element => {
|
||||
const {
|
||||
/* MessageData */
|
||||
ragResults = [],
|
||||
@ -217,11 +216,11 @@ const MessageMeta = (props: MessageMetaProps) => {
|
||||
promptEvalCount = 0,
|
||||
promptEvalDuration = 0,
|
||||
} = props.metadata || {};
|
||||
const message: any = props.messageProps.message;
|
||||
const message: ChatMessage | ChatMessageError | ChatMessageStatus = props.messageProps.message;
|
||||
|
||||
let llm_submission = '<|system|>\n';
|
||||
llm_submission += message.system_prompt + '\n\n';
|
||||
llm_submission += message.context_prompt;
|
||||
// let llm_submission = '<|system|>\n';
|
||||
// llm_submission += message.system_prompt + '\n\n';
|
||||
// llm_submission += message.context_prompt;
|
||||
|
||||
return (
|
||||
<>
|
||||
@ -280,72 +279,74 @@ const MessageMeta = (props: MessageMetaProps) => {
|
||||
</TableContainer>
|
||||
</>
|
||||
)}
|
||||
{tools && tools.tool_calls && tools.tool_calls.length !== 0 && (
|
||||
{tools && tools.toolCalls && tools.toolCalls.length !== 0 && (
|
||||
<Accordion sx={{ boxSizing: 'border-box' }}>
|
||||
<AccordionSummary expandIcon={<ExpandMoreIcon />}>
|
||||
<Box sx={{ fontSize: '0.8rem' }}>Tools queried</Box>
|
||||
</AccordionSummary>
|
||||
<AccordionDetails>
|
||||
{tools.tool_calls.map((tool: any, index: number) => (
|
||||
<Box
|
||||
key={index}
|
||||
sx={{
|
||||
m: 0,
|
||||
p: 1,
|
||||
pt: 0,
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
border: '1px solid #e0e0e0',
|
||||
}}
|
||||
>
|
||||
{index !== 0 && <Divider />}
|
||||
{tools.toolCalls.map(
|
||||
(tool: Record<string, string>, index: number): React.ReactNode => (
|
||||
<Box
|
||||
key={index}
|
||||
sx={{
|
||||
fontSize: '0.75rem',
|
||||
m: 0,
|
||||
p: 1,
|
||||
pt: 0,
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
mt: 1,
|
||||
mb: 1,
|
||||
fontWeight: 'bold',
|
||||
border: '1px solid #e0e0e0',
|
||||
}}
|
||||
>
|
||||
{tool.name}
|
||||
</Box>
|
||||
{tool.content !== 'null' && (
|
||||
<JsonView
|
||||
displayDataTypes={false}
|
||||
objectSortKeys={true}
|
||||
collapsed={1}
|
||||
value={JSON.parse(tool.content)}
|
||||
style={{
|
||||
fontSize: '0.8rem',
|
||||
maxHeight: '20rem',
|
||||
overflow: 'auto',
|
||||
{index !== 0 && <Divider />}
|
||||
<Box
|
||||
sx={{
|
||||
fontSize: '0.75rem',
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
mt: 1,
|
||||
mb: 1,
|
||||
fontWeight: 'bold',
|
||||
}}
|
||||
>
|
||||
<JsonView.String
|
||||
render={({ children, ...reset }) => {
|
||||
if (typeof children === 'string' && children.match('\n')) {
|
||||
return (
|
||||
<pre
|
||||
{...reset}
|
||||
style={{
|
||||
display: 'flex',
|
||||
border: 'none',
|
||||
...reset.style,
|
||||
}}
|
||||
>
|
||||
{children}
|
||||
</pre>
|
||||
);
|
||||
}
|
||||
{tool.name}
|
||||
</Box>
|
||||
{tool.content !== 'null' && (
|
||||
<JsonView
|
||||
displayDataTypes={false}
|
||||
objectSortKeys={true}
|
||||
collapsed={1}
|
||||
value={JSON.parse(tool.content)}
|
||||
style={{
|
||||
fontSize: '0.8rem',
|
||||
maxHeight: '20rem',
|
||||
overflow: 'auto',
|
||||
}}
|
||||
/>
|
||||
</JsonView>
|
||||
)}
|
||||
{tool.content === 'null' && 'No response from tool call'}
|
||||
</Box>
|
||||
))}
|
||||
>
|
||||
<JsonView.String
|
||||
render={({ children, ...reset }): React.ReactNode => {
|
||||
if (typeof children === 'string' && children.match('\n')) {
|
||||
return (
|
||||
<pre
|
||||
{...reset}
|
||||
style={{
|
||||
display: 'flex',
|
||||
border: 'none',
|
||||
...reset.style,
|
||||
}}
|
||||
>
|
||||
{children}
|
||||
</pre>
|
||||
);
|
||||
}
|
||||
}}
|
||||
/>
|
||||
</JsonView>
|
||||
)}
|
||||
{tool.content === 'null' && 'No response from tool call'}
|
||||
</Box>
|
||||
)
|
||||
)}
|
||||
</AccordionDetails>
|
||||
</Accordion>
|
||||
)}
|
||||
@ -369,9 +370,9 @@ const MessageMeta = (props: MessageMetaProps) => {
|
||||
<Box sx={{ fontSize: '0.8rem' }}>Full Response Details</Box>
|
||||
</AccordionSummary>
|
||||
<AccordionDetails>
|
||||
<Box sx={{ pb: 1 }}>
|
||||
{/* <Box sx={{ pb: 1 }}>
|
||||
Copy LLM submission: <CopyBubble content={llm_submission} />
|
||||
</Box>
|
||||
</Box> */}
|
||||
<JsonView
|
||||
displayDataTypes={false}
|
||||
objectSortKeys={true}
|
||||
@ -380,7 +381,7 @@ const MessageMeta = (props: MessageMetaProps) => {
|
||||
style={{ fontSize: '0.8rem', maxHeight: '20rem', overflow: 'auto' }}
|
||||
>
|
||||
<JsonView.String
|
||||
render={({ children, ...reset }) => {
|
||||
render={({ children, ...reset }): React.ReactNode => {
|
||||
if (typeof children === 'string' && children.match('\n')) {
|
||||
return (
|
||||
<pre
|
||||
@ -412,7 +413,7 @@ interface MessageContainerProps {
|
||||
copyContent?: string;
|
||||
}
|
||||
|
||||
const MessageContainer = (props: MessageContainerProps) => {
|
||||
const MessageContainer = (props: MessageContainerProps): JSX.Element => {
|
||||
const { type, sx, messageView, metadataView, copyContent } = props;
|
||||
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 [metaExpanded, setMetaExpanded] = useState<boolean>(false);
|
||||
const theme = useTheme();
|
||||
@ -468,9 +469,9 @@ const Message = (props: MessageProps) => {
|
||||
: 'error' in message
|
||||
? 'error'
|
||||
: (message as ChatMessage).role;
|
||||
const style: any = getStyle(theme, type);
|
||||
const style = getStyle(theme, type);
|
||||
|
||||
const handleMetaExpandClick = () => {
|
||||
const handleMetaExpandClick = (): void => {
|
||||
setMetaExpanded(!metaExpanded);
|
||||
};
|
||||
|
||||
@ -563,7 +564,7 @@ const Message = (props: MessageProps) => {
|
||||
expanded={isControlled ? expanded : undefined} // Omit expanded prop for uncontrolled
|
||||
defaultExpanded={expanded} // Default to collapsed for uncontrolled Accordion
|
||||
className={className}
|
||||
onChange={(_event, newExpanded) => {
|
||||
onChange={(_event, newExpanded): void => {
|
||||
isControlled && onExpand && onExpand(newExpanded);
|
||||
}}
|
||||
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 { styled } from '@mui/material/styles';
|
||||
|
||||
@ -30,7 +30,7 @@ const QuoteContainer = styled(Paper, {
|
||||
|
||||
const QuoteText = styled(Typography, {
|
||||
shouldForwardProp: prop => prop !== 'size',
|
||||
})<QuoteContainerProps>(({ theme, size = 'normal' }) => ({
|
||||
})<QuoteContainerProps>(({ size = 'normal' }) => ({
|
||||
fontSize: size === 'small' ? '0.9rem' : '1.2rem',
|
||||
lineHeight: size === 'small' ? 1.4 : 1.6,
|
||||
fontStyle: 'italic',
|
||||
@ -44,7 +44,7 @@ const QuoteText = styled(Typography, {
|
||||
|
||||
const QuoteMark = styled(Typography, {
|
||||
shouldForwardProp: prop => prop !== 'size',
|
||||
})<QuoteContainerProps>(({ theme, size = 'normal' }) => ({
|
||||
})<QuoteContainerProps>(({ size = 'normal' }) => ({
|
||||
fontSize: size === 'small' ? '2.5rem' : '4rem',
|
||||
fontFamily: '"Georgia", "Times New Roman", serif',
|
||||
fontWeight: 'bold',
|
||||
@ -83,7 +83,7 @@ const AuthorText = styled(Typography, {
|
||||
|
||||
const AccentLine = styled(Box, {
|
||||
shouldForwardProp: prop => prop !== 'size',
|
||||
})<QuoteContainerProps>(({ theme, size = 'normal' }) => ({
|
||||
})<QuoteContainerProps>(({ size = 'normal' }) => ({
|
||||
width: size === 'small' ? '40px' : '60px',
|
||||
height: size === 'small' ? '1px' : '2px',
|
||||
background: 'linear-gradient(90deg, #D4A017 0%, #4A7A7D 100%)', // Golden Ochre to Dusty Teal
|
||||
@ -98,12 +98,12 @@ interface QuoteProps {
|
||||
sx?: SxProps;
|
||||
}
|
||||
|
||||
const Quote = (props: QuoteProps) => {
|
||||
const Quote = (props: QuoteProps): JSX.Element => {
|
||||
const { quote, author, size = 'normal', sx } = props;
|
||||
return (
|
||||
<QuoteContainer size={size} elevation={0} sx={sx}>
|
||||
<OpeningQuote size={size}>"</OpeningQuote>
|
||||
<ClosingQuote size={size}>"</ClosingQuote>
|
||||
<OpeningQuote size={size}>"</OpeningQuote>
|
||||
<ClosingQuote size={size}>"</ClosingQuote>
|
||||
|
||||
<Box sx={{ position: 'relative', zIndex: 2 }}>
|
||||
<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 { Job, Candidate, SkillAssessment } from 'types/types';
|
||||
import { Scrollable } from './Scrollable';
|
||||
import { useAuth } from 'hooks/AuthContext';
|
||||
import * as Types from 'types/types';
|
||||
import { StyledMarkdown } from './StyledMarkdown';
|
||||
import { Message } from './Message';
|
||||
import InputIcon from '@mui/icons-material/Input';
|
||||
import TuneIcon from '@mui/icons-material/Tune';
|
||||
import ArticleIcon from '@mui/icons-material/Article';
|
||||
@ -13,7 +12,6 @@ import { StatusBox, StatusIcon } from './ui/StatusIcon';
|
||||
import { CopyBubble } from './CopyBubble';
|
||||
import { useAppState } from 'hooks/GlobalContext';
|
||||
import { StreamingOptions } from 'services/api-client';
|
||||
import { setDefaultResultOrder } from 'dns';
|
||||
|
||||
interface ResumeGeneratorProps {
|
||||
job: Job;
|
||||
@ -22,15 +20,6 @@ interface ResumeGeneratorProps {
|
||||
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 { job, candidate, skills, onComplete } = props;
|
||||
const { setSnack } = useAppState();
|
||||
@ -44,7 +33,7 @@ const ResumeGenerator: React.FC<ResumeGeneratorProps> = (props: ResumeGeneratorP
|
||||
const [statusType, setStatusType] = useState<Types.ApiActivityType | 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);
|
||||
};
|
||||
|
||||
@ -88,17 +77,32 @@ const ResumeGenerator: React.FC<ResumeGeneratorProps> = (props: ResumeGeneratorP
|
||||
},
|
||||
};
|
||||
|
||||
const generateResume = async () => {
|
||||
const request: any = await apiClient.generateResume(
|
||||
const generateResume = async (): Promise<void> => {
|
||||
const request = await apiClient.generateResume(
|
||||
candidate.id || '',
|
||||
job.id || '',
|
||||
generateResumeHandlers
|
||||
);
|
||||
const result = await request.promise;
|
||||
await request.promise;
|
||||
};
|
||||
|
||||
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 () => {
|
||||
if (!resume) {
|
||||
@ -159,7 +163,7 @@ const ResumeGenerator: React.FC<ResumeGeneratorProps> = (props: ResumeGeneratorP
|
||||
{tabValue === 'resume' && (
|
||||
<>
|
||||
<CopyBubble
|
||||
onClick={() => {
|
||||
onClick={(): void => {
|
||||
setSnack('Resume copied to clipboard!');
|
||||
}}
|
||||
sx={{ position: 'absolute', top: 0, right: 0 }}
|
||||
|
@ -1,6 +1,7 @@
|
||||
import React from 'react';
|
||||
import Box from '@mui/material/Box';
|
||||
import { SxProps, Theme } from '@mui/material';
|
||||
import { RefObject, useRef, forwardRef, useImperativeHandle } from 'react';
|
||||
import { RefObject, useRef, forwardRef } from 'react';
|
||||
import { useAutoScrollToBottom } from '../hooks/useAutoScrollToBottom';
|
||||
|
||||
interface ScrollableProps {
|
||||
@ -9,27 +10,17 @@ interface ScrollableProps {
|
||||
autoscroll?: boolean;
|
||||
textFieldRef?: RefObject<HTMLElement | null>; // Reference to the element that triggers auto-scroll
|
||||
fallbackThreshold?: number;
|
||||
contentUpdateTrigger?: any;
|
||||
className?: string;
|
||||
}
|
||||
|
||||
const Scrollable = forwardRef((props: ScrollableProps, ref) => {
|
||||
const {
|
||||
sx,
|
||||
className,
|
||||
children,
|
||||
autoscroll,
|
||||
textFieldRef,
|
||||
fallbackThreshold = 0.33,
|
||||
contentUpdateTrigger,
|
||||
} = props;
|
||||
const { sx, className, children, autoscroll, textFieldRef, fallbackThreshold = 0.33 } = props;
|
||||
// Create a default ref if textFieldRef is not provided
|
||||
const defaultTextFieldRef = useRef<HTMLElement | null>(null);
|
||||
const scrollRef = useAutoScrollToBottom(
|
||||
textFieldRef ?? defaultTextFieldRef,
|
||||
true,
|
||||
fallbackThreshold,
|
||||
contentUpdateTrigger
|
||||
fallbackThreshold
|
||||
);
|
||||
|
||||
return (
|
||||
@ -52,5 +43,5 @@ const Scrollable = forwardRef((props: ScrollableProps, ref) => {
|
||||
</Box>
|
||||
);
|
||||
});
|
||||
|
||||
Scrollable.displayName = 'Scrollable';
|
||||
export { useAutoScrollToBottom, Scrollable };
|
||||
|
@ -35,12 +35,15 @@ const Snack = forwardRef<SnackHandle, SnackProps>(({ className, sx }: SnackProps
|
||||
);
|
||||
|
||||
useImperativeHandle(ref, () => ({
|
||||
setSnack: (message: string, severity?: SeverityType) => {
|
||||
setSnack: (message: string, severity?: SeverityType): void => {
|
||||
setSnack(message, severity);
|
||||
},
|
||||
}));
|
||||
|
||||
const handleSnackClose = (event: React.SyntheticEvent | Event, reason?: SnackbarCloseReason) => {
|
||||
const handleSnackClose = (
|
||||
event: React.SyntheticEvent | Event,
|
||||
reason?: SnackbarCloseReason
|
||||
): void => {
|
||||
if (reason === 'clickaway') {
|
||||
return;
|
||||
}
|
||||
@ -62,7 +65,7 @@ const Snack = forwardRef<SnackHandle, SnackProps>(({ className, sx }: SnackProps
|
||||
</Snackbar>
|
||||
);
|
||||
});
|
||||
|
||||
Snack.displayName = 'Snack';
|
||||
export type { SeverityType, SetSnackType };
|
||||
|
||||
export { Snack };
|
||||
|
@ -1,8 +1,8 @@
|
||||
import React from 'react';
|
||||
import { MuiMarkdown } from 'mui-markdown';
|
||||
import React, { JSX } from 'react';
|
||||
import { MuiMarkdown, Overrides } from 'mui-markdown';
|
||||
import { useTheme } from '@mui/material/styles';
|
||||
import { Link } from '@mui/material';
|
||||
import { BackstoryQuery, BackstoryQueryInterface } from 'components/BackstoryQuery';
|
||||
import { BackstoryQuery } from 'components/BackstoryQuery';
|
||||
import Box from '@mui/material/Box';
|
||||
import JsonView from '@uiw/react-json-view';
|
||||
import { vscodeTheme } from '@uiw/react-json-view/vscode';
|
||||
@ -13,7 +13,7 @@ import { GenerateImage } from 'components/GenerateImage';
|
||||
|
||||
import './StyledMarkdown.css';
|
||||
import { BackstoryElementProps } from './BackstoryTab';
|
||||
import { CandidateQuestion, ChatQuery, ChatSession } from 'types/types';
|
||||
import { ChatSession } from 'types/types';
|
||||
import { ChatSubmitQueryInterface } from 'components/BackstoryQuery';
|
||||
|
||||
interface StyledMarkdownProps extends BackstoryElementProps {
|
||||
@ -28,21 +28,22 @@ const StyledMarkdown: React.FC<StyledMarkdownProps> = (props: StyledMarkdownProp
|
||||
const { className, content, chatSession, submitQuery, sx, streaming } = props;
|
||||
const theme = useTheme();
|
||||
|
||||
const overrides: any = {
|
||||
const overrides: Overrides = {
|
||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||
p: {
|
||||
component: (element: any) => {
|
||||
component: (element: any): JSX.Element => {
|
||||
return <div>{element.children}</div>;
|
||||
},
|
||||
},
|
||||
pre: {
|
||||
component: (element: any) => {
|
||||
component: (element: any): JSX.Element => {
|
||||
const { className } = element.children.props;
|
||||
const content = element.children?.props?.children || '';
|
||||
if (className === 'lang-mermaid' && !streaming) {
|
||||
return <Mermaid className="Mermaid" chart={content} />;
|
||||
}
|
||||
if (className === 'lang-markdown') {
|
||||
return <MuiMarkdown children={content} />;
|
||||
return <MuiMarkdown>{content}</MuiMarkdown>;
|
||||
}
|
||||
if (className === 'lang-json' && !streaming) {
|
||||
try {
|
||||
@ -68,7 +69,7 @@ const StyledMarkdown: React.FC<StyledMarkdownProps> = (props: StyledMarkdownProp
|
||||
value={fixed}
|
||||
>
|
||||
<JsonView.String
|
||||
render={({ children, ...reset }) => {
|
||||
render={({ children, ...reset }): JSX.Element => {
|
||||
if (typeof children === 'string' && children.match('\n')) {
|
||||
return (
|
||||
<pre
|
||||
@ -83,6 +84,7 @@ const StyledMarkdown: React.FC<StyledMarkdownProps> = (props: StyledMarkdownProp
|
||||
</pre>
|
||||
);
|
||||
}
|
||||
return <></>;
|
||||
}}
|
||||
/>
|
||||
</JsonView>
|
||||
@ -103,10 +105,11 @@ const StyledMarkdown: React.FC<StyledMarkdownProps> = (props: StyledMarkdownProp
|
||||
);
|
||||
},
|
||||
},
|
||||
/* eslint-enable @typescript-eslint/no-explicit-any */
|
||||
a: {
|
||||
component: Link,
|
||||
props: {
|
||||
onClick: (event: React.MouseEvent<HTMLAnchorElement>) => {
|
||||
onClick: (event: React.MouseEvent<HTMLAnchorElement>): void => {
|
||||
const href = event.currentTarget.getAttribute('href');
|
||||
console.log('StyledMarkdown onClick:', href);
|
||||
if (href) {
|
||||
@ -127,14 +130,17 @@ const StyledMarkdown: React.FC<StyledMarkdownProps> = (props: StyledMarkdownProp
|
||||
},
|
||||
},
|
||||
},
|
||||
GenerateImage: {
|
||||
component: (_props: { prompt: string }) => <></>,
|
||||
},
|
||||
BackstoryQuery: {
|
||||
component: (props: { query: string }) => {
|
||||
const queryString = props.query.replace(/(\w+):/g, '"$1":');
|
||||
try {
|
||||
const query = JSON.parse(queryString);
|
||||
const backstoryQuestion: CandidateQuestion = {
|
||||
question: queryString,
|
||||
};
|
||||
// const backstoryQuestion: CandidateQuestion = {
|
||||
// question: queryString,
|
||||
// };
|
||||
|
||||
return submitQuery ? (
|
||||
<BackstoryQuery submitQuery={submitQuery} question={query} />
|
||||
@ -151,13 +157,13 @@ const StyledMarkdown: React.FC<StyledMarkdownProps> = (props: StyledMarkdownProp
|
||||
|
||||
if (chatSession) {
|
||||
overrides.GenerateImage = {
|
||||
component: (props: { prompt: string }) => {
|
||||
component: (props: { prompt: string }): JSX.Element => {
|
||||
const prompt = props.prompt.replace(/(\w+):/g, '"$1":');
|
||||
try {
|
||||
return <GenerateImage {...{ chatSession, prompt }} />;
|
||||
} catch (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,
|
||||
}}
|
||||
>
|
||||
<MuiMarkdown overrides={overrides} children={content} />
|
||||
<MuiMarkdown overrides={overrides}>{content}</MuiMarkdown>
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
@ -22,7 +22,7 @@ import './VectorVisualizer.css';
|
||||
import { BackstoryPageProps } from './BackstoryTab';
|
||||
import { useAuth } from 'hooks/AuthContext';
|
||||
import * as Types from 'types/types';
|
||||
import { useAppState, useSelectedCandidate } from 'hooks/GlobalContext';
|
||||
import { useAppState } from 'hooks/GlobalContext';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
|
||||
interface VectorVisualizerProps extends BackstoryPageProps {
|
||||
@ -30,12 +30,13 @@ interface VectorVisualizerProps extends BackstoryPageProps {
|
||||
rag?: Types.ChromaDBGetResponse;
|
||||
}
|
||||
|
||||
interface Metadata {
|
||||
id: string;
|
||||
docType: string;
|
||||
content: string;
|
||||
distance?: number;
|
||||
}
|
||||
// interface Metadata {
|
||||
// id: string;
|
||||
// docType: string;
|
||||
// content: string;
|
||||
// distance?: number;
|
||||
// }
|
||||
type Metadata = Record<string, string | number>;
|
||||
|
||||
const emptyQuerySet: Types.ChromaDBGetResponse = {
|
||||
ids: [],
|
||||
@ -50,13 +51,21 @@ const emptyQuerySet: Types.ChromaDBGetResponse = {
|
||||
};
|
||||
|
||||
interface PlotData {
|
||||
name: string;
|
||||
mode: string;
|
||||
type: string;
|
||||
x: number[];
|
||||
y: number[];
|
||||
z?: number[];
|
||||
colors: string[];
|
||||
text: string[];
|
||||
sizes: number[];
|
||||
marker: {
|
||||
color: string[];
|
||||
size: number[];
|
||||
symbol: string;
|
||||
opacity: number;
|
||||
};
|
||||
customdata: Metadata[];
|
||||
hovertemplate: string;
|
||||
}
|
||||
|
||||
const config: Partial<Plotly.Config> = {
|
||||
@ -184,7 +193,7 @@ const VectorVisualizer: React.FC<VectorVisualizerProps> = (props: VectorVisualiz
|
||||
const { user, apiClient } = useAuth();
|
||||
const { rag, inline, sx } = props;
|
||||
const { setSnack } = useAppState();
|
||||
const [plotData, setPlotData] = useState<PlotData | null>(null);
|
||||
const [plotData, setPlotData] = useState<PlotData[] | null>(null);
|
||||
const [newQuery, setNewQuery] = useState<string>('');
|
||||
const [querySet, setQuerySet] = useState<Types.ChromaDBGetResponse>(rag || emptyQuerySet);
|
||||
const [result, setResult] = useState<Types.ChromaDBGetResponse | null>(null);
|
||||
@ -206,7 +215,7 @@ const VectorVisualizer: React.FC<VectorVisualizerProps> = (props: VectorVisualiz
|
||||
if (!boxRef.current) {
|
||||
return;
|
||||
}
|
||||
const resize = () => {
|
||||
const resize = (): void => {
|
||||
requestAnimationFrame(() => {
|
||||
const plotContainer = document.querySelector('.plot-container') as HTMLElement;
|
||||
const svgContainer = document?.querySelector('.svg-container') as HTMLElement;
|
||||
@ -234,7 +243,7 @@ const VectorVisualizer: React.FC<VectorVisualizerProps> = (props: VectorVisualiz
|
||||
if (result) {
|
||||
return;
|
||||
}
|
||||
const fetchCollection = async () => {
|
||||
const fetchCollection = async (): Promise<void> => {
|
||||
if (!candidate) {
|
||||
return;
|
||||
}
|
||||
@ -248,7 +257,7 @@ const VectorVisualizer: React.FC<VectorVisualizerProps> = (props: VectorVisualiz
|
||||
};
|
||||
|
||||
fetchCollection();
|
||||
}, [result, setSnack, view2D]);
|
||||
}, [result, setSnack, view2D, apiClient, candidate]);
|
||||
|
||||
useEffect(() => {
|
||||
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 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
|
||||
);
|
||||
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]))
|
||||
: undefined;
|
||||
|
||||
const data: any = [
|
||||
const data: PlotData[] = [
|
||||
{
|
||||
name: 'All data',
|
||||
x: filtered_x,
|
||||
@ -412,13 +421,13 @@ const VectorVisualizer: React.FC<VectorVisualizerProps> = (props: VectorVisualiz
|
||||
setPlotData(data);
|
||||
}, [result, querySet, view2D]);
|
||||
|
||||
const handleKeyPress = (event: any) => {
|
||||
const handleKeyPress = (event: React.KeyboardEvent<HTMLDivElement>): void => {
|
||||
if (event.key === 'Enter') {
|
||||
sendQuery(newQuery);
|
||||
}
|
||||
};
|
||||
|
||||
const sendQuery = async (query: string) => {
|
||||
const sendQuery = async (query: string): Promise<void> => {
|
||||
if (!query.trim()) return;
|
||||
setNewQuery('');
|
||||
|
||||
@ -458,12 +467,19 @@ const VectorVisualizer: React.FC<VectorVisualizerProps> = (props: VectorVisualiz
|
||||
>
|
||||
<div>
|
||||
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>
|
||||
</Box>
|
||||
);
|
||||
|
||||
const fetchRAGMeta = async (node: Node) => {
|
||||
const fetchRAGMeta = async (node: Node): Promise<void> => {
|
||||
try {
|
||||
const result = await apiClient.getCandidateRAGContent(node.id);
|
||||
const update: Node = {
|
||||
@ -478,8 +494,8 @@ const VectorVisualizer: React.FC<VectorVisualizerProps> = (props: VectorVisualiz
|
||||
}
|
||||
};
|
||||
|
||||
const onNodeSelected = (metadata: any) => {
|
||||
let node: Node;
|
||||
const onNodeSelected = (metadata: Metadata): void => {
|
||||
let node: Partial<Node>;
|
||||
console.log(metadata);
|
||||
if (metadata.docType === 'query') {
|
||||
node = {
|
||||
@ -503,7 +519,7 @@ The scatter graph shows the query in N-dimensional space, mapped to ${
|
||||
backgroundColor: colorMap[metadata.docType] || '#ff8080',
|
||||
},
|
||||
};
|
||||
setNode(node);
|
||||
setNode(node as Node);
|
||||
return;
|
||||
}
|
||||
|
||||
@ -513,9 +529,9 @@ The scatter graph shows the query in N-dimensional space, mapped to ${
|
||||
emoji: emojiMap[metadata.docType] || '❓',
|
||||
};
|
||||
|
||||
setNode(node);
|
||||
setNode(node as Node);
|
||||
|
||||
fetchRAGMeta(node);
|
||||
fetchRAGMeta(node as Node);
|
||||
};
|
||||
|
||||
return (
|
||||
@ -552,7 +568,7 @@ The scatter graph shows the query in N-dimensional space, mapped to ${
|
||||
flexGrow: 0,
|
||||
}}
|
||||
control={<Switch checked={!view2D} />}
|
||||
onChange={() => {
|
||||
onChange={(): void => {
|
||||
setView2D(!view2D);
|
||||
setResult(null);
|
||||
}}
|
||||
@ -560,7 +576,7 @@ The scatter graph shows the query in N-dimensional space, mapped to ${
|
||||
/>
|
||||
<Plot
|
||||
ref={plotlyRef}
|
||||
onClick={(event: any) => {
|
||||
onClick={(event: { points: { customdata: Metadata }[] }): void => {
|
||||
onNodeSelected(event.points[0].customdata);
|
||||
}}
|
||||
data={plotData}
|
||||
@ -775,7 +791,9 @@ The scatter graph shows the query in N-dimensional space, mapped to ${
|
||||
fullWidth
|
||||
type="text"
|
||||
value={newQuery}
|
||||
onChange={e => setNewQuery(e.target.value)}
|
||||
onChange={(e): void => {
|
||||
setNewQuery(e.target.value);
|
||||
}}
|
||||
onKeyDown={handleKeyPress}
|
||||
placeholder="Enter query to find related documents..."
|
||||
id="QueryInput"
|
||||
@ -784,7 +802,7 @@ The scatter graph shows the query in N-dimensional space, mapped to ${
|
||||
<Button
|
||||
sx={{ m: 1 }}
|
||||
variant="contained"
|
||||
onClick={() => {
|
||||
onClick={(): void => {
|
||||
sendQuery(newQuery);
|
||||
}}
|
||||
>
|
||||
|
@ -1,5 +1,5 @@
|
||||
// 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 { Box, Container, Paper } from '@mui/material';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
@ -8,19 +8,17 @@ import { darken } from '@mui/material/styles';
|
||||
import { Header } from 'components/layout/Header';
|
||||
import { Scrollable } from 'components/Scrollable';
|
||||
import { Footer } from 'components/layout/Footer';
|
||||
import { Snack, SetSnackType } from 'components/Snack';
|
||||
import { User } from 'types/types';
|
||||
import { LoadingComponent } from 'components/LoadingComponent';
|
||||
import { AuthProvider, useAuth, ProtectedRoute } from 'hooks/AuthContext';
|
||||
import { useAppState, useSelectedCandidate } from 'hooks/GlobalContext';
|
||||
import { useAuth } from 'hooks/AuthContext';
|
||||
import { getMainNavigationItems, getAllRoutes } from 'config/navigationConfig';
|
||||
import { NavigationItem } from 'types/navigation';
|
||||
import { ConversationHandle } from 'components/Conversation';
|
||||
|
||||
// Legacy type for backward compatibility
|
||||
export type NavigationLinkType = {
|
||||
label: ReactElement<any> | string;
|
||||
label: JSX.Element | string;
|
||||
path: string;
|
||||
icon?: ReactElement<any>;
|
||||
icon?: JSX.Element | string;
|
||||
};
|
||||
|
||||
interface BackstoryPageContainerProps {
|
||||
@ -28,7 +26,7 @@ interface BackstoryPageContainerProps {
|
||||
sx?: SxProps<Theme>;
|
||||
}
|
||||
|
||||
const BackstoryPageContainer = (props: BackstoryPageContainerProps) => {
|
||||
const BackstoryPageContainer = (props: BackstoryPageContainerProps): JSX.Element => {
|
||||
const { children, sx } = props;
|
||||
return (
|
||||
<Container
|
||||
@ -76,12 +74,11 @@ const BackstoryPageContainer = (props: BackstoryPageContainerProps) => {
|
||||
|
||||
interface BackstoryLayoutProps {
|
||||
page: string;
|
||||
chatRef: React.Ref<any>;
|
||||
chatRef: React.Ref<ConversationHandle>;
|
||||
}
|
||||
|
||||
const BackstoryLayout: React.FC<BackstoryLayoutProps> = (props: BackstoryLayoutProps) => {
|
||||
const { page, chatRef } = props;
|
||||
const { setSnack } = useAppState();
|
||||
const navigate = useNavigate();
|
||||
const location = useLocation();
|
||||
const { guest, user } = useAuth();
|
||||
@ -93,7 +90,7 @@ const BackstoryLayout: React.FC<BackstoryLayoutProps> = (props: BackstoryLayoutP
|
||||
}, [user]);
|
||||
|
||||
// Generate dynamic routes from navigation config
|
||||
const generateRoutes = () => {
|
||||
const generateRoutes = (): React.ReactNode | null => {
|
||||
if (!guest && !user) return null;
|
||||
|
||||
const userType = user?.userType || null;
|
||||
|
@ -7,7 +7,6 @@ import { ConversationHandle } from '../Conversation';
|
||||
import { User } from 'types/types';
|
||||
import { getAllRoutes } from 'config/navigationConfig';
|
||||
import { NavigationItem } from 'types/navigation';
|
||||
import { useAppState } from 'hooks/GlobalContext';
|
||||
|
||||
interface BackstoryDynamicRoutesProps extends BackstoryPageProps {
|
||||
chatRef: Ref<ConversationHandle>;
|
||||
|
@ -1,4 +1,4 @@
|
||||
import React from 'react';
|
||||
import React, { JSX } from 'react';
|
||||
import {
|
||||
Paper,
|
||||
Box,
|
||||
@ -13,11 +13,11 @@ import {
|
||||
} from '@mui/material';
|
||||
import { styled, useTheme } from '@mui/material/styles';
|
||||
import {
|
||||
Facebook,
|
||||
Twitter,
|
||||
// Facebook,
|
||||
// Twitter,
|
||||
LinkedIn,
|
||||
Instagram,
|
||||
YouTube,
|
||||
// Instagram,
|
||||
// YouTube,
|
||||
Email,
|
||||
LocationOn,
|
||||
Copyright,
|
||||
@ -56,7 +56,7 @@ const ContactItem = styled(Box)(({ theme }) => ({
|
||||
}));
|
||||
|
||||
// Footer component
|
||||
const Footer = () => {
|
||||
const Footer = (): JSX.Element => {
|
||||
const theme = useTheme();
|
||||
const isMobile = useMediaQuery(theme.breakpoints.down('md'));
|
||||
const currentYear = new Date().getFullYear();
|
||||
@ -125,9 +125,9 @@ const Footer = () => {
|
||||
color: theme.palette.action.active,
|
||||
},
|
||||
}}
|
||||
onClick={() =>
|
||||
window.open('https://www.linkedin.com/in/james-ketrenos/', '_blank')
|
||||
}
|
||||
onClick={(): void => {
|
||||
window.open('https://www.linkedin.com/in/james-ketrenos/', '_blank');
|
||||
}}
|
||||
>
|
||||
<LinkedIn />
|
||||
</IconButton>
|
||||
|
@ -108,7 +108,6 @@ const useAutoScrollToBottom = (
|
||||
scrollToRef: RefObject<HTMLElement | null>,
|
||||
smooth = true,
|
||||
fallbackThreshold = 0.33,
|
||||
contentUpdateTrigger?: any
|
||||
): RefObject<HTMLDivElement | null> => {
|
||||
const containerRef = useRef<HTMLDivElement | null>(null);
|
||||
const lastScrollTop = useRef(0);
|
||||
@ -243,7 +242,7 @@ const useAutoScrollToBottom = (
|
||||
}
|
||||
if (scrollTimeout.current) clearTimeout(scrollTimeout.current);
|
||||
};
|
||||
}, [smooth, scrollToRef, fallbackThreshold, contentUpdateTrigger, checkAndScrollToBottom]);
|
||||
}, [smooth, scrollToRef, fallbackThreshold, checkAndScrollToBottom]);
|
||||
|
||||
// Observe container and TextField size, plus DOM changes
|
||||
useResizeObserverAndMutationObserver(containerRef, scrollToRef, checkAndScrollToBottom);
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -16,17 +16,10 @@ from datetime import datetime, UTC
|
||||
from prometheus_client import CollectorRegistry # type: ignore
|
||||
import numpy as np # type: ignore
|
||||
import json_extractor as json_extractor
|
||||
from pydantic import BaseModel, Field # type: ignore
|
||||
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 re
|
||||
from pathlib import Path
|
||||
|
||||
from rag import start_file_watcher, ChromaDBFileWatcher
|
||||
@ -56,21 +49,10 @@ from models import (
|
||||
ChatMessageStatus,
|
||||
ChatMessageStreaming,
|
||||
LLMMessage,
|
||||
ChatMessage,
|
||||
ChatOptions,
|
||||
ChatMessageUser,
|
||||
Tunables,
|
||||
ApiStatusType,
|
||||
ChatMessageMetaData,
|
||||
Candidate,
|
||||
)
|
||||
from logger import logger
|
||||
import defines
|
||||
from .registry import agent_registry
|
||||
|
||||
from models import ChromaDBGetResponse
|
||||
|
||||
|
||||
class CandidateEntity(Candidate):
|
||||
model_config = {"arbitrary_types_allowed": True} # Allow ChromaDBFileWatcher, etc
|
||||
|
||||
|
@ -1034,6 +1034,18 @@ class DocumentMessage(ApiMessage):
|
||||
converted: bool = Field(False, alias=str("converted"))
|
||||
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):
|
||||
model: AIModelType = AIModelType.QWEN2_5
|
||||
@ -1050,7 +1062,7 @@ class ChatMessageMetaData(BaseModel):
|
||||
prompt_eval_count: int = 0
|
||||
prompt_eval_duration: int = 0
|
||||
options: Optional[ChatOptions] = None
|
||||
tools: Dict[str, Any] = Field(default_factory=dict)
|
||||
tools: Optional[Tool] = None
|
||||
timers: Dict[str, float] = Field(default_factory=dict)
|
||||
model_config = ConfigDict(populate_by_name=True)
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user