diff --git a/frontend/src/components/Graph/CheckboxSelection.tsx b/frontend/src/components/Graph/CheckboxSelection.tsx index d0a3429f2..b8caf7484 100644 --- a/frontend/src/components/Graph/CheckboxSelection.tsx +++ b/frontend/src/components/Graph/CheckboxSelection.tsx @@ -7,7 +7,7 @@ const CheckboxSelection: React.FC = ({ graphType, loading, handleChange, - isgds, + isCommunity, isDocChunk, isEntity, }) => ( @@ -29,7 +29,7 @@ const CheckboxSelection: React.FC = ({ onChange={() => handleChange('Entities')} /> )} - {isgds && ( + {isCommunity && ( a.toLowerCase().localeCompare(b.toLowerCase()); + +const isNode = (item: BasicNode | BasicRelationship): item is BasicNode => { + return 'labels' in item && !('from' in item) && !('to' in item); +}; + +const GraphPropertiesPanel = ({ inspectedItem, newScheme }: GraphPropertiesPanelProps) => { + const inspectedItemType = isNode(inspectedItem) ? 'node' : 'relationship'; + const properties = inspectedItemType === 'node' + ? [ + { + key: '', + value: `${(inspectedItem as BasicNode).id}`, + type: 'String', + }, + ...Object.keys((inspectedItem as BasicNode).properties).map((key) => { + const value = (inspectedItem as BasicNode).properties[key]; + return { key: key, value: value ?? '' }; + }), + ] + : [ + { + key: '', + value: `${(inspectedItem as BasicRelationship).id}`, + type: 'String', + }, + { + key: '', + value: `${(inspectedItem as BasicRelationship).from}`, + type: 'String', + }, + { + key: '', + value: `${(inspectedItem as BasicRelationship).to}`, + type: 'String', + }, + { + key: '', + value: `${(inspectedItem as BasicRelationship).caption ?? ''}`, + type: 'String', + }, + ]; + const labelsSorted = useMemo(() => { + if (isNode(inspectedItem)) { + return [...inspectedItem.labels].sort(sortAlphabetically); + } + return []; + }, [inspectedItem]); + + return ( + <> + +
+ {inspectedItemType === 'node' ? 'Node details' : 'Relationship details'} +
+
+ +
+ {isNode(inspectedItem) ? ( + labelsSorted.map((label) => ( + + )) + ) : ( + + )} +
+
+ + + + ); +} + +export default GraphPropertiesPanel; \ No newline at end of file diff --git a/frontend/src/components/Graph/GraphPropertiesTable.tsx b/frontend/src/components/Graph/GraphPropertiesTable.tsx new file mode 100644 index 000000000..8cd7571c7 --- /dev/null +++ b/frontend/src/components/Graph/GraphPropertiesTable.tsx @@ -0,0 +1,39 @@ +import { GraphLabel, Typography } from '@neo4j-ndl/react'; +import { GraphPropertiesTableProps } from '../../types'; + +const GraphPropertiesTable = ({ propertiesWithTypes }: GraphPropertiesTableProps): JSX.Element => { + console.log('props', propertiesWithTypes); + return ( +
+
+ + Key + + Value +
+ {propertiesWithTypes.map(({ key, value }, _) => { + return ( +
+
+ + {key} + +
+
+ {value} +
+
+ ); + })} +
+ ); +}; + +export default GraphPropertiesTable; \ No newline at end of file diff --git a/frontend/src/components/Graph/GraphViewModal.tsx b/frontend/src/components/Graph/GraphViewModal.tsx index 9dc561553..cb995a014 100644 --- a/frontend/src/components/Graph/GraphViewModal.tsx +++ b/frontend/src/components/Graph/GraphViewModal.tsx @@ -2,15 +2,15 @@ import { Banner, Dialog, Flex, - IconButton, IconButtonArray, LoadingSpinner, - TextInput, - Typography, useDebounce, } from '@neo4j-ndl/react'; -import { useCallback, useEffect, useRef, useState } from 'react'; +import { useCallback, useEffect, useMemo, useRef, useState } from 'react'; import { + BasicNode, + BasicRelationship, + EntityType, ExtendedNode, ExtendedRelationship, GraphType, @@ -21,30 +21,29 @@ import { import { InteractiveNvlWrapper } from '@neo4j-nvl/react'; import NVL from '@neo4j-nvl/base'; import type { Node, Relationship } from '@neo4j-nvl/base'; -import { Resizable } from 're-resizable'; import { ArrowPathIconOutline, - DragIcon, FitToScreenIcon, - MagnifyingGlassIconOutline, MagnifyingGlassMinusIconOutline, MagnifyingGlassPlusIconOutline, } from '@neo4j-ndl/react/icons'; import { IconButtonWithToolTip } from '../UI/IconButtonToolTip'; -import { filterData, getCheckboxConditions, processGraphData, sortAlphabetically } from '../../utils/Utils'; +import { filterData, getCheckboxConditions, graphTypeFromNodes, processGraphData } from '../../utils/Utils'; import { useCredentials } from '../../context/UserCredentials'; -import { LegendsChip } from './LegendsChip'; + import graphQueryAPI from '../../services/GraphQuery'; import { graphLabels, intitalGraphType, - mouseEventCallbacks, nvlOptions, queryMap, - RESULT_STEP_SIZE, } from '../../utils/Constants'; import CheckboxSelection from './CheckboxSelection'; -import { ShowAll } from '../UI/ShowAll'; + +import ResultOverview from './ResultOverview'; +import { ResizePanelDetails } from './ResizePanel'; +import GraphPropertiesPanel from './GraphPropertiesPanel'; + const GraphViewModal: React.FunctionComponent = ({ open, @@ -57,51 +56,29 @@ const GraphViewModal: React.FunctionComponent = ({ }) => { const nvlRef = useRef(null); const [nodes, setNodes] = useState([]); - const [relationships, setRelationships] = useState([]); + const [relationships, setRelationships] = useState([]); const [allNodes, setAllNodes] = useState([]); const [allRelationships, setAllRelationships] = useState([]); const [loading, setLoading] = useState(false); const [status, setStatus] = useState<'unknown' | 'success' | 'danger'>('unknown'); const [statusMessage, setStatusMessage] = useState(''); - const { userCredentials, isGdsActive } = useCredentials(); + const { userCredentials } = useCredentials(); const [scheme, setScheme] = useState({}); const [newScheme, setNewScheme] = useState({}); const [searchQuery, setSearchQuery] = useState(''); const debouncedQuery = useDebounce(searchQuery, 300); - const [graphType, setGraphType] = useState(intitalGraphType(isGdsActive)); + const [graphType, setGraphType] = useState([]); const [disableRefresh, setDisableRefresh] = useState(false); - - // the checkbox selection - const handleCheckboxChange = (graph: GraphType) => { - const currentIndex = graphType.indexOf(graph); - const newGraphSelected = [...graphType]; - if (currentIndex === -1) { - newGraphSelected.push(graph); - initGraph(newGraphSelected, allNodes, allRelationships, scheme); - } else { - newGraphSelected.splice(currentIndex, 1); - initGraph(newGraphSelected, allNodes, allRelationships, scheme); - } - setSearchQuery(''); - setGraphType(newGraphSelected); - }; - - const nodeCount = (nodes: ExtendedNode[], label: string): number => { - return [...new Set(nodes?.filter((n) => n.labels?.includes(label)).map((i) => i.id))].length; - }; - - const relationshipCount = (relationships: ExtendedRelationship[], label: string): number => { - return [...new Set(relationships?.filter((r) => r.caption?.includes(label)).map((i) => i.id))].length; - }; + const [selected, setSelected] = useState<{ type: EntityType; id: string } | undefined>(undefined); const graphQuery: string = graphType.includes('DocumentChunk') && graphType.includes('Entities') ? queryMap.DocChunkEntities : graphType.includes('DocumentChunk') - ? queryMap.DocChunks - : graphType.includes('Entities') - ? queryMap.Entities - : ''; + ? queryMap.DocChunks + : graphType.includes('Entities') + ? queryMap.Entities + : ''; // fit graph to original position const handleZoomToFit = () => { @@ -110,7 +87,7 @@ const GraphViewModal: React.FunctionComponent = ({ {} ); }; - +console.log('graphType', graphType); // Unmounting the component useEffect(() => { const timeoutId = setTimeout(() => { @@ -120,7 +97,7 @@ const GraphViewModal: React.FunctionComponent = ({ if (nvlRef.current) { nvlRef.current?.destroy(); } - setGraphType(intitalGraphType(isGdsActive)); + setGraphType([]); clearTimeout(timeoutId); setScheme({}); setNodes([]); @@ -128,18 +105,26 @@ const GraphViewModal: React.FunctionComponent = ({ setAllNodes([]); setAllRelationships([]); setSearchQuery(''); + setSelected(undefined); }; }, []); + useEffect(() => { + const updateGraphType = graphTypeFromNodes(allNodes); + if (Array.isArray(updateGraphType)) { + setGraphType(updateGraphType); + } + }, [allNodes]) + const fetchData = useCallback(async () => { try { const nodeRelationshipData = viewPoint === graphLabels.showGraphView ? await graphQueryAPI( - userCredentials as UserCredentials, - graphQuery, - selectedRows?.map((f) => f.name) - ) + userCredentials as UserCredentials, + graphQuery, + selectedRows?.map((f) => f.name) + ) : await graphQueryAPI(userCredentials as UserCredentials, graphQuery, [inspectedName ?? '']); return nodeRelationshipData; } catch (error: any) { @@ -188,7 +173,7 @@ const GraphViewModal: React.FunctionComponent = ({ useEffect(() => { if (open) { setLoading(true); - setGraphType(intitalGraphType(isGdsActive)); + setGraphType([]); if (viewPoint !== 'chatInfoView') { graphApi(); } else { @@ -202,7 +187,7 @@ const GraphViewModal: React.FunctionComponent = ({ setLoading(false); } } - }, [open, isGdsActive]); + }, [open]); useEffect(() => { handleSearch(debouncedQuery); @@ -220,7 +205,6 @@ const GraphViewModal: React.FunctionComponent = ({ finalNodes ?? [], finalRels ?? [], schemeVal, - isGdsActive ); setNodes(filteredNodes); setRelationships(filteredRelations); @@ -228,6 +212,17 @@ const GraphViewModal: React.FunctionComponent = ({ } }; + + const selectedItem = useMemo(() => { + if (selected === undefined) { + return undefined; + } + if (selected.type === 'node') { + return nodes.find((node) => node.id === selected.id); + } + return relationships.find((relationship) => relationship.id === selected.id); + }, [selected, relationships, nodes]); + // The search and update nodes const handleSearch = useCallback( (value: string) => { @@ -236,7 +231,6 @@ const GraphViewModal: React.FunctionComponent = ({ if (query === '') { return { ...node, - activated: false, selected: false, size: graphLabels.nodeSize, }; @@ -246,21 +240,19 @@ const GraphViewModal: React.FunctionComponent = ({ const match = id.toLowerCase().includes(query) || propertiesMatch || caption?.toLowerCase().includes(query); return { ...node, - activated: match, selected: match, size: match && viewPoint === graphLabels.showGraphView ? 100 : match && viewPoint !== graphLabels.showGraphView - ? 50 - : graphLabels.nodeSize, + ? 50 + : graphLabels.nodeSize, }; }); // deactivating any active relationships const updatedRelationships = relationships.map((rel) => { return { ...rel, - activated: false, selected: false, }; }); @@ -282,6 +274,26 @@ const GraphViewModal: React.FunctionComponent = ({ const checkBoxView = viewPoint !== graphLabels.chatInfoView; + // the checkbox selection + const handleCheckboxChange = (graph: GraphType) => { + const currentIndex = graphType.indexOf(graph); + const newGraphSelected = [...graphType]; + if (currentIndex === -1) { + newGraphSelected.push(graph); + initGraph(newGraphSelected, allNodes, allRelationships, scheme); + } else { + newGraphSelected.splice(currentIndex, 1); + initGraph(newGraphSelected, allNodes, allRelationships, scheme); + } + setSearchQuery(''); + setGraphType(newGraphSelected); + setSelected(undefined); + if (nvlRef.current && nvlRef?.current?.getScale() > 1) { + handleZoomToFit(); + } + }; + + //Callback const nvlCallbacks = { onLayoutComputing(isComputing: boolean) { if (!isComputing) { @@ -316,96 +328,31 @@ const GraphViewModal: React.FunctionComponent = ({ setStatusMessage(''); setGraphViewOpen(false); setScheme({}); - setGraphType(intitalGraphType(isGdsActive)); + setGraphType([]); setNodes([]); setRelationships([]); setAllNodes([]); setAllRelationships([]); setSearchQuery(''); + setSelected(undefined); }; - // sort the legends in with Chunk and Document always the first two values - const nodeCheck = Object.keys(newScheme).sort((a, b) => { - if (a === graphLabels.document || a === graphLabels.chunk) { - return -1; - } else if (b === graphLabels.document || b === graphLabels.chunk) { - return 1; - } - return a.localeCompare(b); - }); - // get sorted relationships - const relationshipsSorted = relationships.sort(sortAlphabetically); - - // To get the relationship count - const groupedAndSortedRelationships: ExtendedRelationship[] = Object.values( - relationshipsSorted.reduce((acc: { [key: string]: ExtendedRelationship }, relType: Relationship) => { - const key = relType.caption || ''; - if (!acc[key]) { - acc[key] = { ...relType, count: 0 }; - } - - acc[key]!.count += relationshipCount(relationships as ExtendedRelationship[], key); - return acc; - }, {}) - ); - - // On Node Click, highlighting the nodes and deactivating any active relationships - const handleNodeClick = (nodeLabel: string) => { - const updatedNodes = nodes.map((node) => { - const isActive = node.labels.includes(nodeLabel); - return { - ...node, - activated: isActive, - selected: isActive, - size: - isActive && viewPoint === graphLabels.showGraphView - ? 100 - : isActive && viewPoint !== graphLabels.showGraphView - ? 50 - : graphLabels.nodeSize, - }; - }); - // deactivating any active relationships - const updatedRelationships = relationships.map((rel) => { - return { - ...rel, - activated: false, - selected: false, - }; - }); - if (searchQuery !== '') { - setSearchQuery(''); - } - setNodes(updatedNodes); - setRelationships(updatedRelationships); - }; - // On Relationship Legend Click, highlight the relationships and deactivating any active nodes - const handleRelationshipClick = (nodeLabel: string) => { - const updatedRelations = relationships.map((rel) => { - return { - ...rel, - activated: rel?.caption?.includes(nodeLabel), - selected: rel?.caption?.includes(nodeLabel), - }; - }); - // // deactivating any active nodes - const updatedNodes = nodes.map((node) => { - return { - ...node, - activated: false, - selected: false, - size: graphLabels.nodeSize, - }; - }); - if (searchQuery !== '') { - setSearchQuery(''); - } - setRelationships(updatedRelations); - setNodes(updatedNodes); + const mouseEventCallbacks = { + onNodeClick: (clickedNode: Node) => { + setSelected({ type: 'node', id: clickedNode.id }); + }, + onRelationshipClick: (clickedRelationship: Relationship) => { + setSelected({ type: 'relationship', id: clickedRelationship.id }); + }, + onCanvasClick: () => { + setSelected(undefined); + }, + onPan: true, + onZoom: true, + onDrag: true, }; - // const isCommunity = allNodes.some(n=>n.labels.includes('__Community__')); return ( <> = ({
- }} - handleClasses={{ left: 'ml-1' }} - > -
- {nodeCheck.length > 0 && ( - <> - - {graphLabels.resultOverview} -
- { - setSearchQuery(e.target.value); - }} - placeholder='Search On Node Properties' - fluid={true} - leftIcon={ - - - - } - /> -
- - {graphLabels.totalNodes} ({nodes.length}) - -
-
- - {nodeCheck.map((nodeLabel, index) => ( - handleNodeClick(nodeLabel)} - /> - ))} - -
- - )} - {relationshipsSorted.length > 0 && ( - <> - - - {graphLabels.totalRelationships} ({relationships.length}) - - -
- - {groupedAndSortedRelationships.map((relType, index) => ( - handleRelationshipClick(relType.caption || '')} - /> - ))} - -
- - )} -
-
+ + {selectedItem !== undefined ? ( + + ) : ( + )} + )} diff --git a/frontend/src/components/Graph/LegendsChip.tsx b/frontend/src/components/Graph/LegendsChip.tsx index 832150fad..1196d8554 100644 --- a/frontend/src/components/Graph/LegendsChip.tsx +++ b/frontend/src/components/Graph/LegendsChip.tsx @@ -6,7 +6,7 @@ export const LegendsChip: React.FunctionComponent = ({ scheme, return ( { + if (!open) { + return null; + } + return ( + }} + handleClasses={{ left: 'ml-1' }} + onResizeStop={onResizeStop} + > +
+ {children} +
+
+ ); +} +const Title = ({ children }: { children: React.ReactNode }) => { + return ( +
+ {children} +
+ ); +} +ResizePanelDetails.Title = Title; + +const Content = ({ children }: { children: React.ReactNode }) => { + return
{children}
; +} +ResizePanelDetails.Content = Content; \ No newline at end of file diff --git a/frontend/src/components/Graph/ResultOverview.tsx b/frontend/src/components/Graph/ResultOverview.tsx new file mode 100644 index 000000000..1f755d188 --- /dev/null +++ b/frontend/src/components/Graph/ResultOverview.tsx @@ -0,0 +1,197 @@ + +import { Flex, IconButton, TextInput, Typography } from '@neo4j-ndl/react'; +import { MagnifyingGlassIconOutline } from "@neo4j-ndl/react/icons"; +import { LegendsChip } from './LegendsChip'; +import { + ExtendedNode, + ExtendedRelationship, + Scheme, +} from '../../types'; +import { + graphLabels, + RESULT_STEP_SIZE, +} from '../../utils/Constants'; +import type { Relationship } from '@neo4j-nvl/base'; +import { ShowAll } from '../UI/ShowAll'; +import { sortAlphabetically } from "../../utils/Utils"; +import { Dispatch, SetStateAction } from "react"; + +interface OverViewProps { + nodes: ExtendedNode[]; + relationships: ExtendedRelationship[]; + newScheme: Scheme; + searchQuery: string + setSearchQuery: Dispatch> + viewPoint: string, + setNodes: Dispatch>, + setRelationships: Dispatch> +} +const ResultOverview: React.FunctionComponent = ({ nodes, relationships, newScheme, searchQuery, setSearchQuery, viewPoint, setNodes, setRelationships }) => { + const nodeCount = (nodes: ExtendedNode[], label: string): number => { + return [...new Set(nodes?.filter((n) => n.labels?.includes(label)).map((i) => i.id))].length; + }; + + // sort the legends in with Chunk and Document always the first two values + const nodeCheck = Object.keys(newScheme).sort((a, b) => { + if (a === graphLabels.document || a === graphLabels.chunk) { + return -1; + } else if (b === graphLabels.document || b === graphLabels.chunk) { + return 1; + } + return a.localeCompare(b); + }); + + // get sorted relationships + const relationshipsSorted = relationships.sort(sortAlphabetically); + + + const relationshipCount = (relationships: ExtendedRelationship[], label: string): number => { + return [...new Set(relationships?.filter((r) => r.caption?.includes(label)).map((i) => i.id))].length; + }; + + // To get the relationship count + const groupedAndSortedRelationships: ExtendedRelationship[] = Object.values( + relationshipsSorted.reduce((acc: { [key: string]: ExtendedRelationship }, relType: Relationship) => { + const key = relType.caption || ''; + if (!acc[key]) { + acc[key] = { ...relType, count: 0 }; + } + (acc[key] as { count: number }).count += relationshipCount(relationships as ExtendedRelationship[], key); + return acc; + }, {}) + ); + + // On Relationship Legend Click, highlight the relationships and deactivating any active nodes + const handleRelationshipClick = (nodeLabel: string) => { + const updatedRelations = relationships.map((rel) => { + return { + ...rel, + selected: rel?.caption?.includes(nodeLabel), + }; + }); + + // // deactivating any active nodes + const updatedNodes = nodes.map((node) => { + return { + ...node, + selected: false, + size: graphLabels.nodeSize, + }; + }); + if (searchQuery !== '') { + setSearchQuery(''); + } + setRelationships(updatedRelations); + setNodes(updatedNodes); + }; + + + + // On Node Click, highlighting the nodes and deactivating any active relationships + const handleNodeClick = (nodeLabel: string) => { + const updatedNodes = nodes.map((node) => { + const isActive = node.labels.includes(nodeLabel); + return { + ...node, + selected: isActive, + size: + isActive && viewPoint === graphLabels.showGraphView + ? 100 + : isActive && viewPoint !== graphLabels.showGraphView + ? 50 + : graphLabels.nodeSize, + }; + }); + // deactivating any active relationships + const updatedRelationships = relationships.map((rel) => { + return { + ...rel, + selected: false, + }; + }); + if (searchQuery !== '') { + setSearchQuery(''); + } + setNodes(updatedNodes); + setRelationships(updatedRelationships); + }; + + return ( + <> + {nodeCheck.length > 0 && ( + <> + + {graphLabels.resultOverview} +
+ { + setSearchQuery(e.target.value); + }} + placeholder='Search On Node Properties' + fluid={true} + leftIcon={ + + + + } + /> +
+ + {graphLabels.totalNodes} ({nodes.length}) + +
+
+ + {nodeCheck.map((nodeLabel, index) => ( + handleNodeClick(nodeLabel)} + /> + ))} + +
+ + )} + {relationshipsSorted.length > 0 && ( + <> + + + {graphLabels.totalRelationships} ({relationships.length}) + + +
+ + {groupedAndSortedRelationships.map((relType, index) => ( + handleRelationshipClick(relType.caption || '')} + scheme={{}} /> + ))} + +
+ + )} + + ) +} + +export default ResultOverview; \ No newline at end of file diff --git a/frontend/src/types.ts b/frontend/src/types.ts index 283227bcb..392754d77 100644 --- a/frontend/src/types.ts +++ b/frontend/src/types.ts @@ -244,7 +244,7 @@ export type ChatbotProps = { isFullScreen?: boolean; connectionStatus: boolean; }; -export interface WikipediaModalTypes extends Omit {} +export interface WikipediaModalTypes extends Omit { } export interface GraphViewModalProps { open: boolean; @@ -266,7 +266,7 @@ export interface CheckboxSectionProps { graphType: GraphType[]; loading: boolean; handleChange: (graph: GraphType) => void; - isgds: boolean; + isCommunity: boolean; isDocChunk: boolean; isEntity: boolean; } @@ -347,8 +347,8 @@ export interface LegendChipProps { scheme: Scheme; label: string; type: 'node' | 'relationship' | 'propertyKey'; - count: number; - onClick: (e: React.MouseEvent) => void; + count?: number; + onClick?: (e: React.MouseEvent) => void; } export interface FileContextProviderProps { children: ReactNode; @@ -430,6 +430,10 @@ export interface SourceListServerData { message?: string; } +export interface MetricsState extends metricdetails { + error?: string; +} + export interface chatInfoMessage extends Partial { sources: string[]; model: string; @@ -590,13 +594,6 @@ export interface Origin { horizontal: Horizontal; } -export type BasicNode = { - id: string; - labels: string[]; - properties: Record; - propertyTypes: Record; -}; - export type GraphStatsLabels = Record< string, { @@ -614,7 +611,7 @@ export interface ExtendedNode extends Node { } export interface ExtendedRelationship extends Relationship { - count: number; + count?: number; } export interface connectionState { openPopUp: boolean; @@ -775,6 +772,34 @@ export interface FileContextType { setPostProcessingVal: Dispatch>; } export declare type Side = 'top' | 'right' | 'bottom' | 'left'; -export interface MetricsState extends metricdetails { - error?: string; -} + +export type EntityType = 'node' | 'relationship'; + +export type BasicRelationship = { + id: string; + to: string; + from: string; + type: string; + caption: string; +}; + +export type BasicNode = { + id: string; + type: string; + labels: string[]; + properties: Record; + propertyTypes: Record; +}; + + +export type GraphPropertiesTableProps = { + propertiesWithTypes: { + key: string; + value: string; + }[]; +}; + +export type GraphPropertiesPanelProps = { + inspectedItem: BasicNode | BasicRelationship; + newScheme: Scheme; +}; diff --git a/frontend/src/utils/Constants.ts b/frontend/src/utils/Constants.ts index 47a19e701..34e1b7263 100644 --- a/frontend/src/utils/Constants.ts +++ b/frontend/src/utils/Constants.ts @@ -3,34 +3,6 @@ import { GraphType, OptionType } from '../types'; import { getDateTime, getDescriptionForChatMode } from './Utils'; import chatbotmessages from '../assets/ChatbotMessages.json'; -export const document = `+ [docs]`; - -export const chunks = `+ collect { MATCH p=(c)-[:NEXT_CHUNK]-() RETURN p } // chunk-chain -+ collect { MATCH p=(c)-[:SIMILAR]-() RETURN p } // similar-chunks`; - -export const entities = `+ collect { OPTIONAL MATCH (c:Chunk)-[:HAS_ENTITY]->(e), p=(e)-[*0..1]-(:!Chunk) RETURN p}`; - -export const docEntities = `+ [docs] -+ collect { MATCH (c:Chunk)-[:HAS_ENTITY]->(e), p=(e)--(:!Chunk) RETURN p }`; - -export const docChunks = `+[chunks] -+collect {MATCH p=(c)-[:FIRST_CHUNK]-() RETURN p} //first chunk -+ collect { MATCH p=(c)-[:NEXT_CHUNK]-() RETURN p } // chunk-chain -+ collect { MATCH p=(c)-[:SIMILAR]-() RETURN p } // similar-chunk`; - -export const chunksEntities = `+ collect { MATCH p=(c)-[:NEXT_CHUNK]-() RETURN p } // chunk-chain - -+ collect { MATCH p=(c)-[:SIMILAR]-() RETURN p } // similar-chunks -//chunks with entities -+ collect { OPTIONAL MATCH p=(c:Chunk)-[:HAS_ENTITY]->(e)-[*0..1]-(:!Chunk) RETURN p }`; - -export const docChunkEntities = `+[chunks] -+collect {MATCH p=(c)-[:FIRST_CHUNK]-() RETURN p} //first chunk -+ collect { MATCH p=(c)-[:NEXT_CHUNK]-() RETURN p } // chunk-chain -+ collect { MATCH p=(c)-[:SIMILAR]-() RETURN p } // similar-chunks -//chunks with entities -+ collect { OPTIONAL MATCH p=(c:Chunk)-[:HAS_ENTITY]->(e)-[*0..1]-(:!Chunk) RETURN p }`; - export const APP_SOURCES = process.env.VITE_REACT_APP_SOURCES !== '' ? (process.env.VITE_REACT_APP_SOURCES?.split(',') as string[]) @@ -40,26 +12,26 @@ export const llms = process.env?.VITE_LLM_MODELS?.trim() != '' ? (process.env.VITE_LLM_MODELS?.split(',') as string[]) : [ - 'diffbot', - 'openai_gpt_3.5', - 'openai_gpt_4o', - 'openai_gpt_4o_mini', - 'gemini_1.5_pro', - 'gemini_1.5_flash', - 'azure_ai_gpt_35', - 'azure_ai_gpt_4o', - 'ollama_llama3', - 'groq_llama3_70b', - 'anthropic_claude_3_5_sonnet', - 'fireworks_llama_v3p2_90b', - 'bedrock_claude_3_5_sonnet', - ]; + 'diffbot', + 'openai-gpt-3.5', + 'openai-gpt-4o', + 'openai-gpt-4o-mini', + 'gemini-1.5-pro', + 'gemini-1.5-flash', + 'azure_ai_gpt_35', + 'azure_ai_gpt_4o', + 'ollama_llama3', + 'groq_llama3_70b', + 'anthropic_claude_3_5_sonnet', + 'fireworks_llama_v3p2_90b', + 'bedrock_claude_3_5_sonnet', + ]; export const defaultLLM = llms?.includes('openai-gpt-4o') ? 'openai-gpt-4o' : llms?.includes('gemini-1.5-pro') - ? 'gemini-1.5-pro' - : 'diffbot'; + ? 'gemini-1.5-pro' + : 'diffbot'; export const chatModeLables = { vector: 'vector', @@ -75,40 +47,40 @@ export const chatModeLables = { export const chatModes = process.env?.VITE_CHAT_MODES?.trim() != '' ? process.env.VITE_CHAT_MODES?.split(',').map((mode) => ({ - mode: mode.trim(), - description: getDescriptionForChatMode(mode.trim()), - })) + mode: mode.trim(), + description: getDescriptionForChatMode(mode.trim()), + })) : [ - { - mode: chatModeLables.vector, - description: 'Performs semantic similarity search on text chunks using vector indexing.', - }, - { - mode: chatModeLables.graph, - description: 'Translates text to Cypher queries for precise data retrieval from a graph database.', - }, - { - mode: chatModeLables.graph_vector, - description: 'Combines vector indexing and graph connections for contextually enhanced semantic search.', - }, - { - mode: chatModeLables.fulltext, - description: 'Conducts fast, keyword-based search using full-text indexing on text chunks.', - }, - { - mode: chatModeLables.graph_vector_fulltext, - description: 'Integrates vector, graph, and full-text indexing for comprehensive search results.', - }, - { - mode: chatModeLables.entity_vector, - description: 'Uses vector indexing on entity nodes for highly relevant entity-based search.', - }, - { - mode: chatModeLables.global_vector, - description: - 'Use vector and full-text indexing on community nodes to provide accurate, context-aware answers globally.', - }, - ]; + { + mode: chatModeLables.vector, + description: 'Performs semantic similarity search on text chunks using vector indexing.', + }, + { + mode: chatModeLables.graph, + description: 'Translates text to Cypher queries for precise data retrieval from a graph database.', + }, + { + mode: chatModeLables.graph_vector, + description: 'Combines vector indexing and graph connections for contextually enhanced semantic search.', + }, + { + mode: chatModeLables.fulltext, + description: 'Conducts fast, keyword-based search using full-text indexing on text chunks.', + }, + { + mode: chatModeLables.graph_vector_fulltext, + description: 'Integrates vector, graph, and full-text indexing for comprehensive search results.', + }, + { + mode: chatModeLables.entity_vector, + description: 'Uses vector indexing on entity nodes for highly relevant entity-based search.', + }, + { + mode: chatModeLables.global_vector, + description: + 'Use vector and full-text indexing on community nodes to provide accurate, context-aware answers globally.', + }, + ]; export const chunkSize = process.env.VITE_CHUNK_SIZE ? parseInt(process.env.VITE_CHUNK_SIZE) : 1 * 1024 * 1024; export const timeperpage = process.env.VITE_TIME_PER_PAGE ? parseInt(process.env.VITE_TIME_PER_PAGE) : 50; @@ -116,53 +88,6 @@ export const timePerByte = 0.2; export const largeFileSize = process.env.VITE_LARGE_FILE_SIZE ? parseInt(process.env.VITE_LARGE_FILE_SIZE) : 5 * 1024 * 1024; -export const NODES_OPTIONS = [ - { - label: 'Person', - value: 'Person', - }, - { - label: 'Organization', - value: 'Organization', - }, - { - label: 'Event', - value: 'Event', - }, -]; - -export const RELATION_OPTIONS = [ - { - label: 'WORKS_AT', - value: 'WORKS_AT', - }, - { - label: 'IS_CEO', - value: 'IS_CEO', - }, - { - label: 'HOSTS_EVENT', - value: 'HOSTS_EVENT', - }, -]; - -export const queryMap: { - Document: string; - Chunks: string; - Entities: string; - DocEntities: string; - DocChunks: string; - ChunksEntities: string; - DocChunkEntities: string; -} = { - Document: 'document', - Chunks: 'chunks', - Entities: 'entities', - DocEntities: 'docEntities', - DocChunks: 'docChunks', - ChunksEntities: 'chunksEntities', - DocChunkEntities: 'docChunkEntities', -}; export const tooltips = { generateGraph: 'Generate graph from selected files', @@ -246,6 +171,35 @@ export const RETRY_OPIONS = [ ]; export const batchSize: number = parseInt(process.env.VITE_BATCH_SIZE ?? '2'); +//Graph Constants +export const document = `+ [docs]`; + +export const chunks = `+ collect { MATCH p=(c)-[:NEXT_CHUNK]-() RETURN p } // chunk-chain ++ collect { MATCH p=(c)-[:SIMILAR]-() RETURN p } // similar-chunks`; + +export const entities = `+ collect { OPTIONAL MATCH (c:Chunk)-[:HAS_ENTITY]->(e), p=(e)-[*0..1]-(:!Chunk) RETURN p}`; + +export const docEntities = `+ [docs] ++ collect { MATCH (c:Chunk)-[:HAS_ENTITY]->(e), p=(e)--(:!Chunk) RETURN p }`; + +export const docChunks = `+[chunks] ++collect {MATCH p=(c)-[:FIRST_CHUNK]-() RETURN p} //first chunk ++ collect { MATCH p=(c)-[:NEXT_CHUNK]-() RETURN p } // chunk-chain ++ collect { MATCH p=(c)-[:SIMILAR]-() RETURN p } // similar-chunk`; + +export const chunksEntities = `+ collect { MATCH p=(c)-[:NEXT_CHUNK]-() RETURN p } // chunk-chain + ++ collect { MATCH p=(c)-[:SIMILAR]-() RETURN p } // similar-chunks +//chunks with entities ++ collect { OPTIONAL MATCH p=(c:Chunk)-[:HAS_ENTITY]->(e)-[*0..1]-(:!Chunk) RETURN p }`; + +export const docChunkEntities = `+[chunks] ++collect {MATCH p=(c)-[:FIRST_CHUNK]-() RETURN p} //first chunk ++ collect { MATCH p=(c)-[:NEXT_CHUNK]-() RETURN p } // chunk-chain ++ collect { MATCH p=(c)-[:SIMILAR]-() RETURN p } // similar-chunks +//chunks with entities ++ collect { OPTIONAL MATCH p=(c:Chunk)-[:HAS_ENTITY]->(e)-[*0..1]-(:!Chunk) RETURN p }`; + export const nvlOptions: NvlOptions = { allowDynamicMinZoom: true, disableWebGL: true, @@ -257,10 +211,22 @@ export const nvlOptions: NvlOptions = { initialZoom: 1, }; -export const mouseEventCallbacks = { - onPan: true, - onZoom: true, - onDrag: true, +export const queryMap: { + Document: string; + Chunks: string; + Entities: string; + DocEntities: string; + DocChunks: string; + ChunksEntities: string; + DocChunkEntities: string; +} = { + Document: 'document', + Chunks: 'chunks', + Entities: 'entities', + DocEntities: 'docEntities', + DocChunks: 'docChunks', + ChunksEntities: 'chunksEntities', + DocChunkEntities: 'docChunkEntities', }; // export const graphQuery: string = queryMap.DocChunkEntities; @@ -276,11 +242,6 @@ export const intitalGraphType = (isGDSActive: boolean): GraphType[] => { : ['DocumentChunk', 'Entities']; // GDS is inactive, exclude communities }; -export const appLabels = { - ownSchema: 'Or Define your own Schema', - predefinedSchema: 'Select a Pre-defined Schema', -}; - export const graphLabels = { showGraphView: 'showGraphView', chatInfoView: 'chatInfoView', @@ -310,6 +271,12 @@ export const connectionLabels = { greenStroke: 'green', redStroke: 'red', }; + export const getDefaultMessage = () => { return [{ ...chatbotmessages.listMessages[0], datetime: getDateTime() }]; }; + +export const appLabels = { + ownSchema: 'Or Define your own Schema', + predefinedSchema: 'Select a Pre-defined Schema', +}; \ No newline at end of file diff --git a/frontend/src/utils/Utils.ts b/frontend/src/utils/Utils.ts index 1d8f3be07..ae41a19fb 100644 --- a/frontend/src/utils/Utils.ts +++ b/frontend/src/utils/Utils.ts @@ -21,6 +21,7 @@ import s3logo from '../assets/images/s3logo.png'; import gcslogo from '../assets/images/gcs.webp'; import { chatModeLables } from './Constants'; + // Get the Url export const url = () => { let url = window.location.href.replace('5173', '8000'); @@ -205,7 +206,6 @@ export const filterData = ( allNodes: ExtendedNode[], allRelationships: Relationship[], scheme: Scheme, - isGdsActive: boolean ) => { let filteredNodes: ExtendedNode[] = []; let filteredRelations: Relationship[] = []; @@ -216,9 +216,7 @@ export const filterData = ( // Only Document + Chunk // const processedEntities = entityTypes.flatMap(item => item.includes(',') ? item.split(',') : item); if ( - graphType.includes('DocumentChunk') && - !graphType.includes('Entities') && - (!graphType.includes('Communities') || !isGdsActive) + graphType.includes('DocumentChunk') && !graphType.includes('Entities') && !graphType.includes('Communities') ) { filteredNodes = allNodes.filter( (node) => (node.labels.includes('Document') && node.properties.fileName) || node.labels.includes('Chunk') @@ -232,12 +230,8 @@ export const filterData = ( ); filteredScheme = { Document: scheme.Document, Chunk: scheme.Chunk }; // Only Entity - } else if ( - graphType.includes('Entities') && - !graphType.includes('DocumentChunk') && - (!graphType.includes('Communities') || !isGdsActive) - ) { - const entityNodes = allNodes.filter((node) => !node.labels.includes('Document') && !node.labels.includes('Chunk')); + } else if (graphType.includes('Entities') && !graphType.includes('DocumentChunk') && !graphType.includes('Communities')) { + const entityNodes = allNodes.filter((node) => !node.labels.includes('Document') && !node.labels.includes('Chunk') && !node.labels.includes('__Community__')); filteredNodes = entityNodes ? entityNodes : []; const nodeIds = new Set(filteredNodes.map((node) => node.id)); filteredRelations = allRelationships.filter( @@ -251,8 +245,7 @@ export const filterData = ( } else if ( graphType.includes('Communities') && !graphType.includes('DocumentChunk') && - !graphType.includes('Entities') && - isGdsActive + !graphType.includes('Entities') ) { filteredNodes = allNodes.filter((node) => node.labels.includes('__Community__')); const nodeIds = new Set(filteredNodes.map((node) => node.id)); @@ -265,7 +258,7 @@ export const filterData = ( } else if ( graphType.includes('DocumentChunk') && graphType.includes('Entities') && - (!graphType.includes('Communities') || !isGdsActive) + (!graphType.includes('Communities')) ) { filteredNodes = allNodes.filter( (node) => @@ -289,8 +282,7 @@ export const filterData = ( } else if ( graphType.includes('Entities') && graphType.includes('Communities') && - !graphType.includes('DocumentChunk') && - isGdsActive + !graphType.includes('DocumentChunk') ) { const entityNodes = allNodes.filter((node) => !node.labels.includes('Document') && !node.labels.includes('Chunk')); const communityNodes = allNodes.filter((node) => node.labels.includes('__Community__')); @@ -310,8 +302,7 @@ export const filterData = ( } else if ( graphType.includes('DocumentChunk') && graphType.includes('Communities') && - !graphType.includes('Entities') && - isGdsActive + !graphType.includes('Entities') ) { const documentChunkNodes = allNodes.filter( (node) => (node.labels.includes('Document') && node.properties.fileName) || node.labels.includes('Chunk') @@ -332,8 +323,7 @@ export const filterData = ( } else if ( graphType.includes('DocumentChunk') && graphType.includes('Entities') && - graphType.includes('Communities') && - isGdsActive + graphType.includes('Communities') ) { filteredNodes = allNodes; filteredRelations = allRelationships; @@ -475,8 +465,25 @@ export function isAllowedHost(url: string, allowedHosts: string[]) { } export const getCheckboxConditions = (allNodes: ExtendedNode[]) => { - const isDocChunk = allNodes.some((n) => n.labels?.includes('Document')); - const isEntity = allNodes.some((n) => !n.labels?.includes('Document') || !n.labels?.includes('Chunk')); - const isgds = allNodes.some((n) => n.labels?.includes('__Community__')); - return { isDocChunk, isEntity, isgds }; + const isDocChunk = allNodes.some((n) => n.labels?.includes('Document') || n.labels?.includes('Chunk')); + const isEntity = allNodes.some((n) => !n.labels?.includes('Document') && !n.labels?.includes('Chunk') && !n.labels?.includes('__Community__')); + const isCommunity = allNodes.some((n) => n.labels?.includes('__Community__')); + return { isDocChunk, isEntity, isCommunity }; }; + +export const graphTypeFromNodes = (allNodes:ExtendedNode[])=>{ + const graphType: GraphType[] =[]; + const hasDocChunk = allNodes.some((n) => n.labels?.includes('Document') || n.labels?.includes('Chunk')); + const hasEntity = allNodes.some((n) => !n.labels?.includes('Document') && !n.labels?.includes('Chunk') && !n.labels?.includes('__Community__')); + const hasCommunity = allNodes.some((n) => n.labels?.includes('__Community__')); + if(hasDocChunk){ + graphType.push('DocumentChunk'); + } + if(hasEntity){ + graphType.push('Entities'); + } + if(hasCommunity){ + graphType.push('Communities'); + } + return graphType; +} \ No newline at end of file