diff --git a/frontend/src/components/Content.tsx b/frontend/src/components/Content.tsx index 93e80769..8db90693 100644 --- a/frontend/src/components/Content.tsx +++ b/frontend/src/components/Content.tsx @@ -15,6 +15,7 @@ import { chatModeLables, largeFileSize, llms, + prodllms, RETRY_OPIONS, tooltips, } from '../utils/Constants'; @@ -880,15 +881,25 @@ const Content: React.FC = ({ >
handleDropdownChange(selectedOption as OptionType)} + options={(llms ?? ['']).map((value) => ({ + label: String(value), + value: String(value), + isDisabled: process.env.VITE_ENV === 'PROD' && !prodllms.includes(value), + }))} + placeholder="Select LLM Model" + defaultValue={{ label: String(model), value: String(model) } + } + view="ContentView" isDisabled={false} - label='LLM Models' - helpText='LLM Model used for Extraction & Chat' - size='medium' + label="LLM Models" + helpText="LLM Model used for Extraction & Chat" + size="medium" + customTooltip={(option: OptionType) => + process.env.VITE_ENV === 'PROD' && !prodllms.includes(option.value) + ?
{'This model is only available in the development environment.'}
+ : null + } />
diff --git a/frontend/src/components/Dropdown.tsx b/frontend/src/components/Dropdown.tsx index 167cf08e..1937f9c6 100644 --- a/frontend/src/components/Dropdown.tsx +++ b/frontend/src/components/Dropdown.tsx @@ -1,79 +1,83 @@ -import { Tooltip, useMediaQuery, Select } from '@neo4j-ndl/react'; -import { OptionType, ReusableDropdownProps } from '../types'; -import { memo, useMemo } from 'react'; -import { capitalize, capitalizeWithUnderscore } from '../utils/Utils'; -import { prodllms } from '../utils/Constants'; -const DropdownComponent: React.FC = ({ +import { useMediaQuery, Select, Tooltip } from '@neo4j-ndl/react'; // Added Tooltip import +import { memo } from 'react'; +import { capitalizeWithUnderscore } from '../utils/Utils'; +import { DropdownComponentProps, OptionType } from '../types'; + +const DropdownComponent: React.FC = ({ options, placeholder, defaultValue, - onSelect, + onChange, children, view, isDisabled, value, label, helpText, - size + size, + mapOptions, + customTooltip, }) => { - const isProdEnv = process.env.VITE_ENV === 'PROD'; - const isLargeDesktop = useMediaQuery(`(min-width:1440px )`); - const handleChange = (selectedOption: OptionType | null | void) => { - onSelect(selectedOption); - if (view === 'ContentView') { - const existingModel = localStorage.getItem('selectedModel'); - if (existingModel != selectedOption?.value) { - localStorage.setItem('selectedModel', selectedOption?.value ?? ''); - } - } - }; - const allOptions = useMemo(() => options, [options]); + const isLargeDesktop = useMediaQuery('(min-width:1440px)'); + const dropdownOptions = options.map((option) => { + const mappedOption = mapOptions ? mapOptions(option) : option; + const labelText = + typeof mappedOption.label === 'string' + ? capitalizeWithUnderscore(mappedOption.label) + : mappedOption.label; + const tooltipContent = customTooltip?.(mappedOption); + return { + label: tooltipContent ? ( + + + {labelText} + + {tooltipContent} + + ) : ( + {labelText} + ), + value: mappedOption.value, + isDisabled: mappedOption.isDisabled || false, + }; + }); return ( - <> -
- {helpText}
} + selectProps={{ + onChange: (selectedOption) => { + if (selectedOption && typeof selectedOption === 'object' && 'value' in selectedOption) { + onChange(selectedOption as OptionType); + } else { + onChange(null); + } + }, + options: dropdownOptions, + placeholder: placeholder || 'Select an option', + defaultValue: defaultValue + ? { + label: + typeof defaultValue.label === 'string' + ? capitalizeWithUnderscore(defaultValue.label) + : String(defaultValue.label), + value: defaultValue.value, + } + : undefined, + menuPlacement: 'auto', + isDisabled, + value, + }} + size={size} + isFluid + htmlAttributes={{ + 'aria-label': 'A selection dropdown', + }} + /> + {children} + ); }; export default memo(DropdownComponent); diff --git a/frontend/src/components/Graph/GraphViewModal.tsx b/frontend/src/components/Graph/GraphViewModal.tsx index b7ba6926..28a98f46 100644 --- a/frontend/src/components/Graph/GraphViewModal.tsx +++ b/frontend/src/components/Graph/GraphViewModal.tsx @@ -63,7 +63,7 @@ const GraphViewModal: React.FunctionComponent = ({ const [disableRefresh, setDisableRefresh] = useState(false); const [selected, setSelected] = useState<{ type: EntityType; id: string } | undefined>(undefined); const [mode, setMode] = useState(false); - const [sliderValue, setSliderValue] = useState(GRAPH_CHUNK_LIMIT); + const [dropdownValue, setDropdownValue] = useState(GRAPH_CHUNK_LIMIT); const graphQuery: string = graphType.includes('DocumentChunk') && graphType.includes('Entities') @@ -122,20 +122,19 @@ const GraphViewModal: React.FunctionComponent = ({ userCredentials as UserCredentials, graphQuery, selectedRows?.map((f) => f.name), - sliderValue + dropdownValue ) - : await graphQueryAPI(userCredentials as UserCredentials, graphQuery, [inspectedName ?? ''], sliderValue); + : await graphQueryAPI(userCredentials as UserCredentials, graphQuery, [inspectedName ?? ''], dropdownValue); return nodeRelationshipData; } catch (error: any) { console.log(error); } - }, [viewPoint, selectedRows, graphQuery, inspectedName, userCredentials, sliderValue]); + }, [viewPoint, selectedRows, graphQuery, inspectedName, userCredentials, dropdownValue]); // Api call to get the nodes and relations const graphApi = async (mode?: string) => { try { const result = await fetchData(); - // Check for valid result data if (result?.data?.status === 'Success' && result.data.data.nodes.length > 0) { const { nodes: neoNodes, relationships: neoRels } = result.data.data; // Create a set of valid node IDs @@ -154,7 +153,6 @@ const GraphViewModal: React.FunctionComponent = ({ setNewScheme(schemeVal); setLoading(false); } - // Update state setAllNodes(finalNodes); setAllRelationships(finalRels); setScheme(schemeVal); @@ -166,7 +164,7 @@ const GraphViewModal: React.FunctionComponent = ({ handleError(error); } }; - // Helper function to handle empty result cases + const handleEmptyResult = (result: any) => { setLoading(false); setStatus('danger'); @@ -175,7 +173,7 @@ const GraphViewModal: React.FunctionComponent = ({ : result?.data?.message || 'An error occurred'; setStatusMessage(message); }; - // Helper function to handle errors + const handleError = (error: any) => { setLoading(false); setStatus('danger'); @@ -187,7 +185,7 @@ const GraphViewModal: React.FunctionComponent = ({ setLoading(true); setGraphType([]); if (viewPoint !== graphLabels.chatInfoView) { - graphApi(); + graphApi('normalMode'); } else { const { finalNodes, finalRels, schemeVal } = processGraphData(nodeValues ?? [], relationshipValues ?? []); setAllNodes(finalNodes); @@ -199,7 +197,7 @@ const GraphViewModal: React.FunctionComponent = ({ setLoading(false); } } - }, [open, sliderValue]); + }, [open,dropdownValue]); useEffect(() => { if (debouncedQuery) { @@ -338,7 +336,7 @@ const GraphViewModal: React.FunctionComponent = ({ setAllRelationships([]); setSearchQuery(''); setSelected(undefined); - setSliderValue(GRAPH_CHUNK_LIMIT); + setDropdownValue(GRAPH_CHUNK_LIMIT); }; const mouseEventCallbacks = { @@ -356,10 +354,13 @@ const GraphViewModal: React.FunctionComponent = ({ onDrag: true, }; + // The set the chunk limit for graph viz const handleDropdownChange = (selectedOption: OptionType | null | void) => { setStatus('unknown'); if (selectedOption?.value) { - setSliderValue(selectedOption?.value); + setDropdownValue(selectedOption?.value); + // setLoading(true); + // graphApi('chunkMode'); } }; @@ -391,10 +392,15 @@ const GraphViewModal: React.FunctionComponent = ({ /> )} handleDropdownChange(selectedOption as OptionType)} + options={graph_chunk_limit.map((value) => ({ + label: String(value), + value: String(value), + }))} + placeholder="Select Chunk Limit" + defaultValue={ + { label: String(GRAPH_CHUNK_LIMIT), value: String(GRAPH_CHUNK_LIMIT) } + } view='GraphView' isDisabled={loading} label='Chunk Limit' diff --git a/frontend/src/types.ts b/frontend/src/types.ts index 6cff5242..6a501e11 100644 --- a/frontend/src/types.ts +++ b/frontend/src/types.ts @@ -39,10 +39,11 @@ export interface CustomFile extends CustomFileBase { id: string; } -export interface OptionType { - readonly value: string; - readonly label: string; -} +export type OptionType = { + label: string | JSX.Element; + value: string; + isDisabled?: boolean; +}; export type UserCredentials = { uri: string; @@ -918,3 +919,19 @@ export interface GraphViewHandlerProps { export interface ChatProps { chatMessages: Messages[]; } + +export type DropdownComponentProps = { + options: OptionType[]; + placeholder: string; + defaultValue: OptionType; + value?: OptionType; + onChange: (selectedOption: OptionType | null) => void; + label: string; + helpText: string | JSX.Element; + size: 'small' | 'medium' | 'large'; + isDisabled: boolean; + children?: React.ReactNode; + mapOptions?: (option: OptionType) => OptionType; + customTooltip?: (option: OptionType) => JSX.Element | null; + view: string; +}; \ No newline at end of file