diff --git a/frontend/src/components/Content.tsx b/frontend/src/components/Content.tsx index 6b9e279ba..6c77dde2c 100644 --- a/frontend/src/components/Content.tsx +++ b/frontend/src/components/Content.tsx @@ -271,8 +271,9 @@ const Content: React.FC = ({ const handleOpenGraphClick = () => { const bloomUrl = process.env.BLOOM_URL; const uriCoded = userCredentials?.uri.replace(/:\d+$/, ''); - const connectURL = `${uriCoded?.split('//')[0]}//${userCredentials?.userName}@${uriCoded?.split('//')[1]}:${userCredentials?.port ?? '7687' - }`; + const connectURL = `${uriCoded?.split('//')[0]}//${userCredentials?.userName}@${uriCoded?.split('//')[1]}:${ + userCredentials?.port ?? '7687' + }`; const encodedURL = encodeURIComponent(connectURL); const replacedUrl = bloomUrl?.replace('{CONNECT_URL}', encodedURL); window.open(replacedUrl, '_blank'); @@ -282,10 +283,10 @@ const Content: React.FC = ({ isLeftExpanded && isRightExpanded ? 'contentWithExpansion' : isRightExpanded - ? 'contentWithChatBot' - : !isLeftExpanded && !isRightExpanded - ? 'w-[calc(100%-128px)]' - : 'contentWithDropzoneExpansion'; + ? 'contentWithChatBot' + : !isLeftExpanded && !isRightExpanded + ? 'w-[calc(100%-128px)]' + : 'contentWithDropzoneExpansion'; const handleGraphView = () => { setOpenGraphView(true); @@ -321,6 +322,14 @@ const Content: React.FC = ({ [selectedfileslength, completedfileNo] ); + const processingCheck = () => { + const processingFiles = filesData.some((file) => file.status === 'Processing'); + const selectedRowProcessing = selectedRows.some((row) => + filesData.some((file) => file.name === row && file.status === 'Processing') + ); + return processingFiles || selectedRowProcessing; + }; + const filesForProcessing = useMemo(() => { let newstatusfiles: CustomFile[] = []; if (selectedRows.length) { @@ -517,7 +526,6 @@ const Content: React.FC = ({ }); localStorage.setItem('isSchema', JSON.stringify(true)); }; - return ( <> {alertDetails.showAlert && ( @@ -605,8 +613,9 @@ const Content: React.FC = ({ }} > @@ -616,6 +625,7 @@ const Content: React.FC = ({ placeholder='Select LLM Model' defaultValue={defaultLLM} view='ContentView' + isDisabled={false} /> = ({ open={openGraphView} setGraphViewOpen={setOpenGraphView} viewPoint={viewPoint} + processingCheck={processingCheck()} /> ); diff --git a/frontend/src/components/Dropdown.tsx b/frontend/src/components/Dropdown.tsx index 9ee0c4088..467eedfe8 100644 --- a/frontend/src/components/Dropdown.tsx +++ b/frontend/src/components/Dropdown.tsx @@ -2,12 +2,14 @@ import { Dropdown } from '@neo4j-ndl/react'; import { DropdownProps, OptionType } from '../types'; import { useMemo } from 'react'; import { capitalize } from '../utils/Utils'; + interface ReusableDropdownProps extends DropdownProps { options: string[]; placeholder?: string; defaultValue?: string; children?: React.ReactNode; view?: 'ContentView' | 'GraphView'; + isDisabled: boolean; } const DropdownComponent: React.FC = ({ options, @@ -16,6 +18,7 @@ const DropdownComponent: React.FC = ({ onSelect, children, view, + isDisabled, }) => { const handleChange = (selectedOption: OptionType | null | void) => { onSelect(selectedOption); @@ -38,6 +41,7 @@ const DropdownComponent: React.FC = ({ placeholder: placeholder || 'Select an option', defaultValue: defaultValue ? { label: capitalize(defaultValue), value: defaultValue } : undefined, menuPlacement: 'auto', + isDisabled: isDisabled, }} size='medium' fluid diff --git a/frontend/src/components/FileTable.tsx b/frontend/src/components/FileTable.tsx index d590450c6..3403d0d4f 100644 --- a/frontend/src/components/FileTable.tsx +++ b/frontend/src/components/FileTable.tsx @@ -321,7 +321,7 @@ const FileTable: React.FC = ({ isExpanded, connectionStatus, set text='Graph' size='large' label='Graph view' - disabled={!(info.getValue() === 'Completed' || info.getValue() == 'Cancelled')} + disabled={info.getValue() === 'New' || info.getValue() === 'Uploading'} clean onClick={() => onInspect(info?.row?.original?.name as string)} > diff --git a/frontend/src/components/Graph/GraphViewModal.tsx b/frontend/src/components/Graph/GraphViewModal.tsx index 82daf35b7..e9cf210c8 100644 --- a/frontend/src/components/Graph/GraphViewModal.tsx +++ b/frontend/src/components/Graph/GraphViewModal.tsx @@ -19,13 +19,13 @@ import { LegendsChip } from './LegendsChip'; import graphQueryAPI from '../../services/GraphQuery'; import { entityGraph, + graphQuery, graphView, intitalGraphType, knowledgeGraph, lexicalGraph, mouseEventCallbacks, nvlOptions, - queryMap, } from '../../utils/Constants'; import { useFileContext } from '../../context/UsersFiles'; // import CheckboxSelection from './CheckboxSelection'; @@ -37,6 +37,7 @@ const GraphViewModal: React.FunctionComponent = ({ viewPoint, nodeValues, relationshipValues, + processingCheck, }) => { const nvlRef = useRef(null); const [nodes, setNodes] = useState([]); @@ -62,12 +63,15 @@ const GraphViewModal: React.FunctionComponent = ({ // } // setGraphType(newGraphSelected); // }; + const handleZoomToFit = () => { nvlRef.current?.fit( allNodes.map((node) => node.id), {} ); }; + + // Destroy the component useEffect(() => { const timeoutId = setTimeout(() => { handleZoomToFit(); @@ -83,16 +87,17 @@ const GraphViewModal: React.FunctionComponent = ({ setAllRelationships([]); }; }, []); - const graphQuery: string = queryMap.DocChunkEntities; + + // To get nodes and relations on basis of view const fetchData = useCallback(async () => { try { const nodeRelationshipData = viewPoint === 'showGraphView' ? await graphQueryAPI( - userCredentials as UserCredentials, - graphQuery, - selectedRows.map((f) => JSON.parse(f).name) - ) + userCredentials as UserCredentials, + graphQuery, + selectedRows.map((f) => JSON.parse(f).name) + ) : await graphQueryAPI(userCredentials as UserCredentials, graphQuery, [inspectedName ?? '']); return nodeRelationshipData; } catch (error: any) { @@ -100,6 +105,34 @@ const GraphViewModal: React.FunctionComponent = ({ } }, [viewPoint, selectedRows, graphQuery, inspectedName, userCredentials]); + // Api call to get the nodes and relations + const graphApi = () => { + fetchData() + .then((result) => { + if (result && result.data.data.nodes.length > 0) { + const neoNodes = result.data.data.nodes.map((f: Node) => f); + const neoRels = result.data.data.relationships.map((f: Relationship) => f); + const { finalNodes, finalRels, schemeVal } = processGraphData(neoNodes, neoRels); + setAllNodes(finalNodes); + setAllRelationships(finalRels); + setScheme(schemeVal); + setNodes(finalNodes); + setRelationships(finalRels); + setNewScheme(schemeVal); + setLoading(false); + } else { + setLoading(false); + setStatus('danger'); + setStatusMessage(`No Nodes and Relations for the ${inspectedName} file`); + } + }) + .catch((error: any) => { + setLoading(false); + setStatus('danger'); + setStatusMessage(error.message); + }); + }; + useEffect(() => { if (open) { setLoading(true); @@ -113,36 +146,11 @@ const GraphViewModal: React.FunctionComponent = ({ setNewScheme(schemeVal); setLoading(false); } else { - fetchData() - .then((result) => { - if (result && result.data.data.nodes.length > 0) { - const neoNodes = result.data.data.nodes.map((f: Node) => f); - const neoRels = result.data.data.relationships.map((f: Relationship) => f); - const { finalNodes, finalRels, schemeVal } = processGraphData(neoNodes, neoRels); - setAllNodes(finalNodes); - setAllRelationships(finalRels); - setScheme(schemeVal); - setNodes(finalNodes); - setRelationships(finalRels); - setNewScheme(schemeVal); - setLoading(false); - } else { - setLoading(false); - setStatus('danger'); - setStatusMessage(`Unable to retrieve document graph for ${inspectedName}`); - } - }) - .catch((error: any) => { - setLoading(false); - setStatus('danger'); - setStatusMessage(error.message); - }); + graphApi(); } } }, [open]); - console.log('nodes', nodes); - if (!open) { return <>; } @@ -151,7 +159,9 @@ const GraphViewModal: React.FunctionComponent = ({ viewPoint === 'showGraphView' || viewPoint === 'chatInfoView' ? 'Generated Graph' : `Inspect Generated Graph from ${inspectedName}`; - const checkBoxView = viewPoint !== 'chatInfoView'; + + const dropDownView = viewPoint !== 'chatInfoView'; + const nvlCallbacks = { onLayoutComputing(isComputing: boolean) { if (!isComputing) { @@ -159,16 +169,25 @@ const GraphViewModal: React.FunctionComponent = ({ } }, }; + + // To handle the current zoom in function of graph const handleZoomIn = () => { nvlRef.current?.setZoom(nvlRef.current.getScale() * 1.3); }; + + // To handle the current zoom out function of graph const handleZoomOut = () => { nvlRef.current?.setZoom(nvlRef.current.getScale() * 0.7); }; + + // Refresh the graph with nodes and relations if file is processing const handleRefresh = () => { - // fetchData(); - console.log('hello'); + setLoading(true); + setGraphType(intitalGraphType); + graphApi(); }; + + // when modal closes reset all states to default const onClose = () => { setStatus('unknown'); setStatusMessage(''); @@ -178,6 +197,8 @@ const GraphViewModal: React.FunctionComponent = ({ setNodes([]); setRelationships([]); }; + + // sort the legends in with Chunk and Document always the first two values const legendCheck = Object.keys(newScheme).sort((a, b) => { if (a === 'Document' || a === 'Chunk') { return -1; @@ -187,6 +208,7 @@ const GraphViewModal: React.FunctionComponent = ({ return a.localeCompare(b); }); + // setting the default dropdown values const getDropdownDefaultValue = () => { if (graphType.includes('Document') && graphType.includes('Chunk') && graphType.includes('Entities')) { return knowledgeGraph; @@ -200,15 +222,22 @@ const GraphViewModal: React.FunctionComponent = ({ return ''; }; + // Make a function call to store the nodes and relations in their states const initGraph = (graphType: GraphType[], finalNodes: Node[], finalRels: Relationship[], schemeVal: Scheme) => { if (allNodes.length > 0 && allRelationships.length > 0) { - const { filteredNodes, filteredRelations, filteredScheme } = filterData(graphType, finalNodes, finalRels, schemeVal); + const { filteredNodes, filteredRelations, filteredScheme } = filterData( + graphType, + finalNodes, + finalRels, + schemeVal + ); setNodes(filteredNodes); setRelationships(filteredRelations); setNewScheme(filteredScheme); } - } + }; + // handle dropdown value change and call the init graph method const handleDropdownChange = (selectedOption: OptionType | null | void) => { if (selectedOption?.value) { const selectedValue = selectedOption.value; @@ -243,13 +272,16 @@ const GraphViewModal: React.FunctionComponent = ({ {/* {checkBoxView && ( )} */} - {checkBoxView && ()} + {dropDownView && ( + + )} @@ -278,14 +310,16 @@ const GraphViewModal: React.FunctionComponent = ({ nvlCallbacks={nvlCallbacks} /> - - - + {viewPoint !== 'chatInfoView' && processingCheck && ( + + + + )} diff --git a/frontend/src/types.ts b/frontend/src/types.ts index 4554196ed..de37a1cf9 100644 --- a/frontend/src/types.ts +++ b/frontend/src/types.ts @@ -246,6 +246,7 @@ export interface GraphViewModalProps { viewPoint: string; nodeValues?: Node[]; relationshipValues?: Relationship[]; + processingCheck?: boolean; } export type GraphType = 'Document' | 'Entities' | 'Chunk'; diff --git a/frontend/src/utils/Constants.ts b/frontend/src/utils/Constants.ts index 9b6c3f53e..7586524cf 100644 --- a/frontend/src/utils/Constants.ts +++ b/frontend/src/utils/Constants.ts @@ -1,6 +1,6 @@ import { VisualizeBloomIcon } from '@neo4j-ndl/react/icons'; import { NvlOptions } from '@neo4j-nvl/base'; -import { GraphType } from '../types'; +import { GraphType, OptionType } from '../types'; export const document = `+ [docs]`; @@ -48,7 +48,7 @@ export const llms = 'LLM_MODEL_CONFIG_groq-llama3-70b', 'LLM_MODEL_CONFIG_anthropic-claude-3-5-sonnet', 'LLM_MODEL_CONFIG_fireworks-llama-v3-70b', - 'LLM_MODEL_CONFIG_bedrock-claude-3-5-sonnet' + 'LLM_MODEL_CONFIG_bedrock-claude-3-5-sonnet', ]; export const defaultLLM = llms?.includes('openai-gpt-3.5') @@ -180,6 +180,7 @@ export const mouseEventCallbacks = { onDrag: true, }; +export const graphQuery: string = queryMap.DocChunkEntities; export const graphView: string[] = ['Lexical Graph', 'Entity Graph', 'Knowledge Graph']; export const intitalGraphType: GraphType[] = ['Document', 'Entities', 'Chunk']; export const knowledgeGraph = 'Knowledge Graph'; diff --git a/frontend/src/utils/Utils.ts b/frontend/src/utils/Utils.ts index 651e2f150..0f62160b6 100644 --- a/frontend/src/utils/Utils.ts +++ b/frontend/src/utils/Utils.ts @@ -2,10 +2,6 @@ import { calcWordColor } from '@neo4j-devtools/word-color'; import type { Node, Relationship } from '@neo4j-nvl/base'; import { GraphType, Messages, Scheme } from '../types'; -type PartialLabelNode = Partial & { - labels: string; -}; - // Get the Url export const url = () => { let url = window.location.href.replace('5173', '8000'); @@ -173,8 +169,7 @@ export const filterData = ( graphType: GraphType[], allNodes: Node[], allRelationships: Relationship[], - scheme: Scheme, - // selectedDropdown:string + scheme: Scheme ) => { let filteredNodes: Node[] = []; let filteredRelations: Relationship[] = []; @@ -195,7 +190,6 @@ export const filterData = ( acc[key] = scheme[key]; return acc; }, {} as Scheme); - } else if (!graphType.includes('Document') && !graphType.includes('Entities') && graphType.includes('Chunk')) { // Only Chunk filteredNodes = allNodes.filter((node) => node.labels.includes('Chunk')); @@ -216,7 +210,7 @@ export const filterData = ( filteredRelations = allRelationships.filter((rel) => ['PART_OF', 'FIRST_CHUNK', 'SIMILAR', 'NEXT_CHUNK'].includes(rel.caption) ); - filteredScheme = {Document: scheme.Document, Chunk:scheme.Chunk}; + filteredScheme = { Document: scheme.Document, Chunk: scheme.Chunk }; } else if (!graphType.includes('Document') && graphType.includes('Entities') && graphType.includes('Chunk')) { // Chunk + Entity filteredNodes = allNodes.filter((node) => !node.labels.includes('Document'));