diff --git a/backend/Dockerfile b/backend/Dockerfile index c36f8ce2e..4488129d9 100644 --- a/backend/Dockerfile +++ b/backend/Dockerfile @@ -16,9 +16,10 @@ RUN apt-get update && \ # Set LD_LIBRARY_PATH ENV LD_LIBRARY_PATH=/usr/lib/x86_64-linux-gnu:$LD_LIBRARY_PATH # Copy requirements file and install Python dependencies -COPY requirements.txt /code/ +COPY requirements.txt constraints.txt /code/ # --no-cache-dir --upgrade -RUN pip install -r requirements.txt +RUN pip install --upgrade pip +RUN pip install -r requirements.txt -c constraints.txt # Copy application code COPY . /code # Set command diff --git a/backend/constraints.txt b/backend/constraints.txt new file mode 100644 index 000000000..2c785f6de --- /dev/null +++ b/backend/constraints.txt @@ -0,0 +1,4 @@ +-f https://download.pytorch.org/whl/torch_stable.html +torch==2.3.1+cpu +torchvision==0.18.1+cpu +torchaudio==2.3.1+cpu \ No newline at end of file diff --git a/backend/score.py b/backend/score.py index 2318c22e8..e2a6c4e51 100644 --- a/backend/score.py +++ b/backend/score.py @@ -31,11 +31,10 @@ from src.ragas_eval import * from starlette.types import ASGIApp, Receive, Scope, Send from langchain_neo4j import Neo4jGraph -from src.entities.source_node import sourceNode from starlette.middleware.sessions import SessionMiddleware -from starlette.responses import HTMLResponse, RedirectResponse,JSONResponse from starlette.requests import Request -import secrets +from dotenv import load_dotenv +load_dotenv(override=True) logger = CustomLogger() CHUNK_DIR = os.path.join(os.path.dirname(__file__), "chunks") diff --git a/backend/src/llm.py b/backend/src/llm.py index b83773f42..ef835b560 100644 --- a/backend/src/llm.py +++ b/backend/src/llm.py @@ -180,13 +180,16 @@ async def get_graph_document_list( else: node_properties = ["description"] relationship_properties = ["description"] + TOOL_SUPPORTED_MODELS = {"qwen3", "deepseek"} + model_name = llm.model_name.lower() + ignore_tool_usage = not any(pattern in model_name for pattern in TOOL_SUPPORTED_MODELS) llm_transformer = LLMGraphTransformer( llm=llm, node_properties=node_properties, relationship_properties=relationship_properties, allowed_nodes=allowedNodes, allowed_relationships=allowedRelationship, - ignore_tool_usage=True, + ignore_tool_usage=ignore_tool_usage, additional_instructions=ADDITIONAL_INSTRUCTIONS+ (additional_instructions if additional_instructions else "") ) diff --git a/backend/src/main.py b/backend/src/main.py index c2f2a80f3..4bdb6ba51 100644 --- a/backend/src/main.py +++ b/backend/src/main.py @@ -665,39 +665,40 @@ def upload_file(graph, model, chunk, chunk_number:int, total_chunks:int, origina return f"Chunk {chunk_number}/{total_chunks} saved" def get_labels_and_relationtypes(uri, userName, password, database): - excluded_labels = {'Document', 'Chunk', '_Bloom_Perspective_', '__Community__', '__Entity__', 'Session', 'Message'} - excluded_relationships = { - 'PART_OF', 'NEXT_CHUNK', 'HAS_ENTITY', '_Bloom_Perspective_', 'FIRST_CHUNK', - 'SIMILAR', 'IN_COMMUNITY', 'PARENT_COMMUNITY', 'NEXT', 'LAST_MESSAGE'} - driver = get_graphDB_driver(uri, userName, password,database) - with driver.session(database=database) as session: - result = session.run("CALL db.schema.visualization() YIELD nodes, relationships RETURN nodes, relationships") - if not result: - return [] - record = result.single() - nodes = record["nodes"] - relationships = record["relationships"] - node_map = {} - for node in nodes: - node_id = node.element_id - labels = list(node.labels) - if labels: - node_map[node_id] = ":".join(labels) - triples = [] - for rel in relationships: - start_id = rel.start_node.element_id - end_id = rel.end_node.element_id - rel_type = rel.type - start_label = node_map.get(start_id) - end_label = node_map.get(end_id) - if start_label and end_label: - if ( - start_label not in excluded_labels and - end_label not in excluded_labels and - rel_type not in excluded_relationships - ): - triples.append(f"{start_label}-{rel_type}->{end_label}") - return {"triplets" : list(set(triples))} + excluded_labels = {'Document', 'Chunk', '_Bloom_Perspective_', '__Community__', '__Entity__', 'Session', 'Message'} + excluded_relationships = { + 'NEXT_CHUNK', '_Bloom_Perspective_', 'FIRST_CHUNK', + 'SIMILAR', 'IN_COMMUNITY', 'PARENT_COMMUNITY', 'NEXT', 'LAST_MESSAGE' + } + driver = get_graphDB_driver(uri, userName, password,database) + triples = set() + with driver.session(database=database) as session: + result = session.run(""" + MATCH (n)-[r]->(m) + RETURN DISTINCT labels(n) AS fromLabels, type(r) AS relType, labels(m) AS toLabels + """) + for record in result: + from_labels = record["fromLabels"] + to_labels = record["toLabels"] + rel_type = record["relType"] + from_label = next((lbl for lbl in from_labels if lbl not in excluded_labels), None) + to_label = next((lbl for lbl in to_labels if lbl not in excluded_labels), None) + if not from_label or not to_label: + continue + if rel_type == 'PART_OF': + if from_label == 'Chunk' and to_label == 'Document': + continue + elif rel_type == 'HAS_ENTITY': + if from_label == 'Chunk': + continue + elif ( + from_label in excluded_labels or + to_label in excluded_labels or + rel_type in excluded_relationships + ): + continue + triples.add(f"{from_label}-{rel_type}->{to_label}") + return {"triplets": list(triples)} def manually_cancelled_job(graph, filenames, source_types, merged_dir, uri): diff --git a/backend/src/make_relationships.py b/backend/src/make_relationships.py index a07f29c9c..97aa7e33e 100644 --- a/backend/src/make_relationships.py +++ b/backend/src/make_relationships.py @@ -1,6 +1,7 @@ from langchain_neo4j import Neo4jGraph from langchain.docstore.document import Document from src.shared.common_fn import load_embedding_model,execute_graph_query +from src.shared.common_fn import load_embedding_model,execute_graph_query import logging from typing import List import os @@ -34,6 +35,7 @@ def merge_relationship_between_chunk_and_entites(graph: Neo4jGraph, graph_docume MERGE (c)-[:HAS_ENTITY]->(n) """ execute_graph_query(graph,unwind_query, params={"batch_data": batch_data}) + execute_graph_query(graph,unwind_query, params={"batch_data": batch_data}) def create_chunk_embeddings(graph, chunkId_chunkDoc_list, file_name): @@ -60,6 +62,7 @@ def create_chunk_embeddings(graph, chunkId_chunkDoc_list, file_name): MERGE (c)-[:PART_OF]->(d) """ execute_graph_query(graph,query_to_create_embedding, params={"fileName":file_name, "data":data_for_query}) + execute_graph_query(graph,query_to_create_embedding, params={"fileName":file_name, "data":data_for_query}) def create_relation_between_chunks(graph, file_name, chunks: List[Document])->list: logging.info("creating FIRST_CHUNK and NEXT_CHUNK relationships between chunks") @@ -128,6 +131,7 @@ def create_relation_between_chunks(graph, file_name, chunks: List[Document])->li MERGE (c)-[:PART_OF]->(d) """ execute_graph_query(graph,query_to_create_chunk_and_PART_OF_relation, params={"batch_data": batch_data}) + execute_graph_query(graph,query_to_create_chunk_and_PART_OF_relation, params={"batch_data": batch_data}) query_to_create_FIRST_relation = """ UNWIND $relationships AS relationship @@ -137,6 +141,7 @@ def create_relation_between_chunks(graph, file_name, chunks: List[Document])->li MERGE (d)-[:FIRST_CHUNK]->(c)) """ execute_graph_query(graph,query_to_create_FIRST_relation, params={"f_name": file_name, "relationships": relationships}) + execute_graph_query(graph,query_to_create_FIRST_relation, params={"f_name": file_name, "relationships": relationships}) query_to_create_NEXT_CHUNK_relation = """ UNWIND $relationships AS relationship @@ -153,7 +158,7 @@ def create_relation_between_chunks(graph, file_name, chunks: List[Document])->li def create_chunk_vector_index(graph): start_time = time.time() try: - vector_index_query = "SHOW INDEXES YIELD * WHERE labelsOrTypes = ['Chunk'] and type = 'VECTOR' AND name = 'vector' return options" + vector_index_query = "SHOW INDEXES YIELD name, type, labelsOrTypes, properties WHERE name = 'vector' AND type = 'VECTOR' AND 'Chunk' IN labelsOrTypes AND 'embedding' IN properties RETURN name" vector_index = execute_graph_query(graph,vector_index_query) if not vector_index: vector_store = Neo4jVector(embedding=EMBEDDING_FUNCTION, @@ -168,7 +173,7 @@ def create_chunk_vector_index(graph): else: logging.info(f"Index already exist,Skipping creation. Time taken: {time.time() - start_time:.2f} seconds") except Exception as e: - if "EquivalentSchemaRuleAlreadyExists" in str(e): + if ("EquivalentSchemaRuleAlreadyExists" in str(e) or "An equivalent index already exists" in str(e)): logging.info("Vector index already exists, skipping creation.") else: raise \ No newline at end of file diff --git a/frontend/index.html b/frontend/index.html index 85618e155..c669468ee 100644 --- a/frontend/index.html +++ b/frontend/index.html @@ -1,16 +1,20 @@ - - - - - - - - Neo4j graph builder - - -
- - - + + + + + + + + + Neo4j graph builder + + + +
+ + + + + \ No newline at end of file diff --git a/frontend/package.json b/frontend/package.json index a364dd9d7..ecd2d4368 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -14,9 +14,9 @@ }, "dependencies": { "@auth0/auth0-react": "^2.2.4", - "@emotion/styled": "^11.11.0", + "@emotion/styled": "^11.14.0", "@mui/material": "^5.15.10", - "@mui/styled-engine": "^7.0.1", + "@mui/styled-engine": "^7.0.2", "@neo4j-devtools/word-color": "^0.0.8", "@neo4j-ndl/base": "^3.2.9", "@neo4j-ndl/react": "^3.2.18", @@ -24,7 +24,7 @@ "@neo4j-nvl/react": "^0.3.7", "@react-oauth/google": "^0.12.1", "@tanstack/react-table": "^8.20.5", - "@types/uuid": "^9.0.7", + "@types/uuid": "^10.0.0", "axios": "^1.8.4", "clsx": "^2.1.1", "eslint-plugin-react": "^7.37.4", @@ -50,13 +50,13 @@ "eslint": "^8.45.0", "eslint-config-prettier": "^10.1.1", "eslint-plugin-react-hooks": "^5.1.0", - "eslint-plugin-react-refresh": "^0.4.19", + "eslint-plugin-react-refresh": "^0.4.20", "husky": "^9.1.7", "lint-staged": "^15.5.0", "postcss": "^8.5.3", "prettier": "^3.5.3", "react-dropzone": "^14.3.8", - "tailwindcss": "^4.0.7", + "tailwindcss": "^4.1.5", "typescript": "^5.7.3", "vite": "^4.5.3" } diff --git a/frontend/src/components/ChatBot/ChatInfoModal.tsx b/frontend/src/components/ChatBot/ChatInfoModal.tsx index 859bcf9b7..02ff58900 100644 --- a/frontend/src/components/ChatBot/ChatInfoModal.tsx +++ b/frontend/src/components/ChatBot/ChatInfoModal.tsx @@ -81,10 +81,10 @@ const ChatInfoModal: React.FC = ({ error?.length ? 10 : mode === chatModeLables['global search+vector+fulltext'] - ? 7 - : mode === chatModeLables.graph - ? 4 - : 3 + ? 7 + : mode === chatModeLables.graph + ? 4 + : 3 ); const [, copy] = useCopyToClipboard(); const [copiedText, setcopiedText] = useState(false); @@ -97,15 +97,15 @@ const ChatInfoModal: React.FC = ({ multiModelMetrics.length > 0 && Object.keys(multiModelMetrics[0]).length > 4 ? true : multiModelMetrics.length > 0 && Object.keys(multiModelMetrics[0]).length <= 4 - ? false - : null + ? false + : null ); const [isAdditionalMetricsWithSingleMode, setIsAdditionalMetricsWithSingleMode] = useState( metricDetails != undefined && Object.keys(metricDetails).length > 3 ? true : metricDetails != undefined && Object.keys(metricDetails).length <= 3 - ? false - : null + ? false + : null ); const actions: React.ComponentProps>[] = useMemo( () => [ @@ -320,6 +320,7 @@ const ChatInfoModal: React.FC = ({ src={Neo4jRetrievalLogo} style={{ width: isTablet ? 80 : 95, height: isTablet ? 80 : 95, marginRight: 10 }} loading='lazy' + alt='Retrieval-logo' />
Retrieval information diff --git a/frontend/src/components/ChatBot/ChatOnlyComponent.tsx b/frontend/src/components/ChatBot/ChatOnlyComponent.tsx index 323fdf2dd..8ee0029ba 100644 --- a/frontend/src/components/ChatBot/ChatOnlyComponent.tsx +++ b/frontend/src/components/ChatBot/ChatOnlyComponent.tsx @@ -10,6 +10,7 @@ import { clearChatAPI } from '../../services/QnaAPI'; import { ChatProps, connectionState, Messages, UserCredentials } from '../../types'; import { getIsLoading } from '../../utils/Utils'; import ThemeWrapper from '../../context/ThemeWrapper'; +import { SpotlightProvider } from '@neo4j-ndl/react'; const ChatContent: React.FC = ({ chatMessages }) => { const { clearHistoryData, messages, setMessages, setClearHistoryData, setIsDeleteChatLoading, isDeleteChatLoading } = @@ -160,11 +161,13 @@ const ChatOnlyComponent: React.FC = () => { return ( - - - - - + + + + + + + ); diff --git a/frontend/src/components/ChatBot/ChunkInfo.tsx b/frontend/src/components/ChatBot/ChunkInfo.tsx index 0a5750b8f..8a2341f06 100644 --- a/frontend/src/components/ChatBot/ChunkInfo.tsx +++ b/frontend/src/components/ChatBot/ChunkInfo.tsx @@ -84,7 +84,7 @@ const ChunkInfo: FC = ({ loading, chunks, mode }) => { ) : chunk?.url && chunk?.start_time ? ( <>
- + youtube-source-logo = ({ loading, chunks, mode }) => { ) : chunk?.url && new URL(chunk.url).host === 'wikipedia.org' ? ( <>
- + wikipedia-source-logo {chunk?.fileName}
{mode !== chatModeLables['global search+vector+fulltext'] && @@ -147,7 +147,7 @@ const ChunkInfo: FC = ({ loading, chunks, mode }) => { ) : chunk?.url && new URL(chunk.url).host === 'storage.googleapis.com' ? ( <>
- + gcs-source-logo {chunk?.fileName}
{mode !== chatModeLables['global search+vector+fulltext'] && @@ -172,7 +172,7 @@ const ChunkInfo: FC = ({ loading, chunks, mode }) => { ) : chunk?.url && chunk?.url.startsWith('s3://') ? ( <>
- + s3-source-logo {chunk?.fileName}
{mode !== chatModeLables['global search+vector+fulltext'] && @@ -264,6 +264,11 @@ const ChunkInfo: FC = ({ loading, chunks, mode }) => { {chunk?.text}
+
+ + {chunk?.text} + +
))} diff --git a/frontend/src/components/ChatBot/EntitiesInfo.tsx b/frontend/src/components/ChatBot/EntitiesInfo.tsx index 489fb9d03..a902cd15c 100644 --- a/frontend/src/components/ChatBot/EntitiesInfo.tsx +++ b/frontend/src/components/ChatBot/EntitiesInfo.tsx @@ -15,15 +15,18 @@ const EntitiesInfo: FC = ({ loading, mode, graphonly_entities, in const [loadingGraphView, setLoadingGraphView] = useState(false); const groupedEntities = useMemo<{ [key: string]: GroupedEntity }>(() => { - const items = infoEntities.reduce((acc, entity) => { - const { label, text } = parseEntity(entity); - if (!acc[label]) { - const newColor = calcWordColor(label); - acc[label] = { texts: new Set(), color: newColor }; - } - acc[label].texts.add(text); - return acc; - }, {} as Record; color: string }>); + const items = infoEntities.reduce( + (acc, entity) => { + const { label, text } = parseEntity(entity); + if (!acc[label]) { + const newColor = calcWordColor(label); + acc[label] = { texts: new Set(), color: newColor }; + } + acc[label].texts.add(text); + return acc; + }, + {} as Record; color: string }> + ); return items; }, [infoEntities]); const labelCounts = useMemo(() => { diff --git a/frontend/src/components/ChatBot/SourcesInfo.tsx b/frontend/src/components/ChatBot/SourcesInfo.tsx index 6016b511b..2a967b864 100644 --- a/frontend/src/components/ChatBot/SourcesInfo.tsx +++ b/frontend/src/components/ChatBot/SourcesInfo.tsx @@ -42,7 +42,13 @@ const SourcesInfo: FC = ({ loading, mode, chunks, sources }) => { {s.fileSource === 'local file' ? ( ) : ( - + source-logo )} = ({ loading, mode, chunks, sources }) => { {youtubeLinkValidation(link) && ( <>
- + youtube-source-logo = ({ setOpenConnection, showDisconnectButton, connectionStatus, + combinedPatterns, + setCombinedPatterns, + combinedNodes, + setCombinedNodes, + combinedRels, + setCombinedRels, }) => { const { breakpoints } = tokens; const isTablet = useMediaQuery(`(min-width:${breakpoints.xs}) and (max-width: ${breakpoints.lg})`); @@ -596,6 +602,9 @@ const Content: React.FC = ({ localStorage.removeItem('selectedChunks_to_combine'); setSelectedChunks_to_combine(chunksToCombine); localStorage.removeItem('instructions'); + localStorage.removeItem('selectedNodeLabels'); + localStorage.removeItem('selectedRelationshipLabels'); + localStorage.removeItem('selectedPattern'); setAdditionalInstructions(''); setMessages([ { @@ -891,7 +900,16 @@ const Content: React.FC = ({ > )} {showEnhancementDialog && ( - + )} = ({ openModal, isLargeDesktop = t wrapperclassName='my-2' className={isLargeDesktop ? 'webImg' : 'widthunset'} isDisabled={isDisabled} + title={isLargeDesktop ? 'Web Sources' : ''} /> ); }; diff --git a/frontend/src/components/FileTable.tsx b/frontend/src/components/FileTable.tsx index 1d0987287..e49ae7ba5 100644 --- a/frontend/src/components/FileTable.tsx +++ b/frontend/src/components/FileTable.tsx @@ -582,7 +582,7 @@ const FileTable: ForwardRefRenderFunction = (props, re value !== null && value !== undefined && value !== ' ') - .reduce((acc, [key, value]) => { - acc[key] = value; - return acc; - }, {} as Record) + .reduce( + (acc, [key, value]) => { + acc[key] = value; + return acc; + }, + {} as Record + ) : {}; const properties = inspectedItemType === 'node' diff --git a/frontend/src/components/Graph/GraphPropertiesTable.tsx b/frontend/src/components/Graph/GraphPropertiesTable.tsx index d599dd4e7..3ff0e6c21 100644 --- a/frontend/src/components/Graph/GraphPropertiesTable.tsx +++ b/frontend/src/components/Graph/GraphPropertiesTable.tsx @@ -2,7 +2,6 @@ import { GraphLabel, Typography } from '@neo4j-ndl/react'; import { GraphPropertiesTableProps } from '../../types'; const GraphPropertiesTable = ({ propertiesWithTypes }: GraphPropertiesTableProps): JSX.Element => { - console.log('proerties', propertiesWithTypes); return (
diff --git a/frontend/src/components/Graph/GraphViewModal.tsx b/frontend/src/components/Graph/GraphViewModal.tsx index 7738bfb3e..114b8471b 100644 --- a/frontend/src/components/Graph/GraphViewModal.tsx +++ b/frontend/src/components/Graph/GraphViewModal.tsx @@ -20,7 +20,7 @@ import { InformationCircleIconOutline, MagnifyingGlassMinusIconOutline, MagnifyingGlassPlusIconOutline, - ExploreIcon + ExploreIcon, } from '@neo4j-ndl/react/icons'; import { IconButtonWithToolTip } from '../UI/IconButtonToolTip'; import { filterData, getCheckboxConditions, graphTypeFromNodes, processGraphData } from '../../utils/Utils'; @@ -203,26 +203,29 @@ const GraphViewModal: React.FunctionComponent = ({ } }, [debouncedQuery]); - const mouseEventCallbacks = useMemo(() => ({ - onNodeClick: (clickedNode: Node) => { - if (selected?.id !== clickedNode.id || selected?.type !== 'node') { - setSelected({ type: 'node', id: clickedNode.id }); - } - }, - onRelationshipClick: (clickedRelationship: Relationship) => { - if (selected?.id !== clickedRelationship.id || selected?.type !== 'relationship') { - setSelected({ type: 'relationship', id: clickedRelationship.id }); - } - }, - onCanvasClick: () => { - if (selected !== undefined) { - setSelected(undefined); - } - }, - onPan: true, - onZoom: true, - onDrag: true, - }), [selected]); + const mouseEventCallbacks = useMemo( + () => ({ + onNodeClick: (clickedNode: Node) => { + if (selected?.id !== clickedNode.id || selected?.type !== 'node') { + setSelected({ type: 'node', id: clickedNode.id }); + } + }, + onRelationshipClick: (clickedRelationship: Relationship) => { + if (selected?.id !== clickedRelationship.id || selected?.type !== 'relationship') { + setSelected({ type: 'relationship', id: clickedRelationship.id }); + } + }, + onCanvasClick: () => { + if (selected !== undefined) { + setSelected(undefined); + } + }, + onPan: true, + onZoom: true, + onDrag: true, + }), + [selected] + ); const initGraph = ( graphType: GraphType[], @@ -356,10 +359,7 @@ const GraphViewModal: React.FunctionComponent = ({ setSelected(undefined); }; - const handleSchemaView = async ( - rawNodes: any[], - rawRelationships: any[] - ) => { + const handleSchemaView = async (rawNodes: any[], rawRelationships: any[]) => { const { nodes, relationships } = extractGraphSchemaFromRawData(rawNodes, rawRelationships); setSchemaNodes(nodes as any); setSchemaRels(relationships as any); @@ -367,7 +367,6 @@ const GraphViewModal: React.FunctionComponent = ({ setOpenGraphView(true); }; - return ( <> = ({ /> )} - ); }; export default GraphViewModal; diff --git a/frontend/src/components/Graph/SchemaDropdown.tsx b/frontend/src/components/Graph/SchemaDropdown.tsx index 57c989543..8f15f5a4f 100644 --- a/frontend/src/components/Graph/SchemaDropdown.tsx +++ b/frontend/src/components/Graph/SchemaDropdown.tsx @@ -3,80 +3,80 @@ import { useRef, useState } from 'react'; import TooltipWrapper from '../UI/TipWrapper'; import { useFileContext } from '../../context/UsersFiles'; interface SchemaDropdownProps { - isDisabled: boolean; - onSchemaSelect?: (source: string, nodes: any[], rels: any[]) => void; // <-- NEW + isDisabled: boolean; + onSchemaSelect?: (source: string, nodes: any[], rels: any[]) => void; // <-- NEW } const SchemaDropdown: React.FunctionComponent = ({ isDisabled, onSchemaSelect }) => { - const [isOpen, setIsOpen] = useState(false); - const btnRef = useRef(null); - const { - userDefinedNodes, - userDefinedRels, - dbNodes, - dbRels, - schemaValNodes, - schemaValRels, - preDefinedNodes, - preDefinedRels, - } = useFileContext(); - const handleSelect = (source: string, nodes: any[], rels: any[]) => { - setIsOpen(false); - if (onSchemaSelect) { - onSchemaSelect(source, nodes, rels); - } - }; - return ( -
- { - e.preventDefault(); - setIsOpen((prev) => !prev); - }, - }} - > - Show Schema from ... - - setIsOpen(false)}> - - - Predefined Schema - - } - onClick={() => handleSelect('predefined', preDefinedNodes, preDefinedRels)} - /> - - DB Schema - - } - onClick={() => handleSelect('db', dbNodes, dbRels)} - /> - - Get Schema From Text - - } - onClick={() => handleSelect('text', schemaValNodes, schemaValRels)} - /> - - User Configured - - } - onClick={() => handleSelect('user', userDefinedNodes, userDefinedRels)} - /> - - -
- ); + const [isOpen, setIsOpen] = useState(false); + const btnRef = useRef(null); + const { + userDefinedNodes, + userDefinedRels, + dbNodes, + dbRels, + schemaValNodes, + schemaValRels, + preDefinedNodes, + preDefinedRels, + } = useFileContext(); + const handleSelect = (source: string, nodes: any[], rels: any[]) => { + setIsOpen(false); + if (onSchemaSelect) { + onSchemaSelect(source, nodes, rels); + } + }; + return ( +
+ { + e.preventDefault(); + setIsOpen((prev) => !prev); + }, + }} + > + Show Schema from ... + + setIsOpen(false)}> + + + Predefined Schema + + } + onClick={() => handleSelect('predefined', preDefinedNodes, preDefinedRels)} + /> + + DB Schema + + } + onClick={() => handleSelect('db', dbNodes, dbRels)} + /> + + Get Schema From Text + + } + onClick={() => handleSelect('text', schemaValNodes, schemaValRels)} + /> + + User Configured + + } + onClick={() => handleSelect('user', userDefinedNodes, userDefinedRels)} + /> + + +
+ ); }; -export default SchemaDropdown; \ No newline at end of file +export default SchemaDropdown; diff --git a/frontend/src/components/Graph/SchemaViz.tsx b/frontend/src/components/Graph/SchemaViz.tsx index 4991b82bf..0bb7d9762 100644 --- a/frontend/src/components/Graph/SchemaViz.tsx +++ b/frontend/src/components/Graph/SchemaViz.tsx @@ -31,7 +31,7 @@ const SchemaViz: React.FunctionComponent = ({ nodeValues, relationshipValues, schemaLoading, - view + view, }) => { const nvlRef = useRef(null); const [nodes, setNodes] = useState([]); @@ -71,7 +71,7 @@ const SchemaViz: React.FunctionComponent = ({ useEffect(() => { if (open) { if (view !== 'viz') { - setLoading(true); + setLoading(true); const { nodes, relationships, scheme } = userDefinedGraphSchema( (nodeValues as OptionType[]) ?? [], (relationshipValues as OptionType[]) ?? [] @@ -81,11 +81,12 @@ const SchemaViz: React.FunctionComponent = ({ setRelationships(relationships); setNewScheme(scheme); setLoading(false); - } - else { - const { nodes, relationships, scheme } = - generateGraphFromNodeAndRelVals(nodeValues as any, relationshipValues as any); - + } else { + const { nodes, relationships, scheme } = generateGraphFromNodeAndRelVals( + nodeValues as any, + relationshipValues as any + ); + setNodes(nodes); setRelationships(relationships); setNewScheme(scheme); diff --git a/frontend/src/components/Layout/Header.tsx b/frontend/src/components/Layout/Header.tsx index adce226e4..8f73c779d 100644 --- a/frontend/src/components/Layout/Header.tsx +++ b/frontend/src/components/Layout/Header.tsx @@ -25,7 +25,6 @@ import { downloadClickHandler, getIsLoading } from '../../utils/Utils'; import Profile from '../User/Profile'; import { useAuth0 } from '@auth0/auth0-react'; - const Header: React.FC = ({ chatOnly, deleteOnClick, setOpenConnection, showBackButton }) => { const { colorMode, toggleColorMode } = useContext(ThemeWrapperContext); const navigate = useNavigate(); @@ -88,7 +87,7 @@ const Header: React.FC = ({ chatOnly, deleteOnClick, setOpenConnecti aria-label='main navigation' >
- + import('../DataSources/GCS/GCSModal')); const S3Modal = lazy(() => import('../DataSources/AWS/S3Modal')); const GenericModal = lazy(() => import('../WebSources/GenericSourceModal')); const ConnectionModal = lazy(() => import('../Popups/ConnectionModal/ConnectionModal')); -import { SKIP_AUTH } from '../../utils/Constants'; const spotlightsforunauthenticated = [ { target: 'loginbutton', @@ -152,6 +153,14 @@ const PageLayout: React.FC = () => { chunksExistsWithDifferentDimension: false, }); const isLargeDesktop = useMediaQuery(`(min-width:1440px )`); + const [isLeftExpanded, setIsLeftExpanded] = useState(false); + const [isRightExpanded, setIsRightExpanded] = useState(false); + const [showChatBot, setShowChatBot] = useState(false); + const [showDrawerChatbot, setShowDrawerChatbot] = useState(true); + const [showEnhancementDialog, toggleEnhancementDialog] = useReducer((s) => !s, false); + const [shows3Modal, toggleS3Modal] = useReducer((s) => !s, false); + const [showGCSModal, toggleGCSModal] = useReducer((s) => !s, false); + const [showGenericModal, toggleGenericModal] = useReducer((s) => !s, false); const { connectionStatus, setIsReadOnlyUser, @@ -164,47 +173,6 @@ const PageLayout: React.FC = () => { showDisconnectButton, setIsGCSActive, } = useCredentials(); - const [isLeftExpanded, setIsLeftExpanded] = useState(Boolean(isLargeDesktop)); - const [isRightExpanded, setIsRightExpanded] = useState(Boolean(isLargeDesktop)); - const [showChatBot, setShowChatBot] = useState(false); - const [showDrawerChatbot, setShowDrawerChatbot] = useState(true); - const [showEnhancementDialog, toggleEnhancementDialog] = useReducer((s) => !s, false); - const [shows3Modal, toggleS3Modal] = useReducer((s) => !s, false); - const [showGCSModal, toggleGCSModal] = useReducer((s) => { - return !s; - }, false); - const [showGenericModal, toggleGenericModal] = useReducer((s) => !s, false); - const { user, isAuthenticated } = useAuth0(); - - const navigate = useNavigate(); - - const toggleLeftDrawer = useCallback(() => { - if (isLargeDesktop) { - setIsLeftExpanded(!isLeftExpanded); - } else { - setIsLeftExpanded(false); - } - }, [isLargeDesktop]); - const toggleRightDrawer = useCallback(() => { - if (isLargeDesktop) { - setIsRightExpanded(!isRightExpanded); - } else { - setIsRightExpanded(false); - } - }, [isLargeDesktop]); - const isYoutubeOnly = useMemo( - () => APP_SOURCES.includes('youtube') && !APP_SOURCES.includes('wiki') && !APP_SOURCES.includes('web'), - [] - ); - const isWikipediaOnly = useMemo( - () => APP_SOURCES.includes('wiki') && !APP_SOURCES.includes('youtube') && !APP_SOURCES.includes('web'), - [] - ); - const isWebOnly = useMemo( - () => APP_SOURCES.includes('web') && !APP_SOURCES.includes('youtube') && !APP_SOURCES.includes('wiki'), - [] - ); - const { messages, setClearHistoryData, clearHistoryData, setMessages, setIsDeleteChatLoading } = useMessageContext(); const { setShowTextFromSchemaDialog, showTextFromSchemaDialog, @@ -222,12 +190,41 @@ const PageLayout: React.FC = () => { setPreDefinedNodes, setPreDefinedRels, setPreDefinedPattern, + allPatterns, + selectedNodes, + selectedRels, } = useFileContext(); + const navigate = useNavigate(); + const { user, isAuthenticated } = useAuth0(); const { cancel } = useSpeechSynthesis(); const { setActiveSpotlight } = useSpotlightContext(); - const isFirstTimeUser = useMemo(() => { - return localStorage.getItem('neo4j.connection') === null; - }, []); + const isYoutubeOnly = useMemo( + () => APP_SOURCES.includes('youtube') && !APP_SOURCES.includes('wiki') && !APP_SOURCES.includes('web'), + [] + ); + const isWikipediaOnly = useMemo( + () => APP_SOURCES.includes('wiki') && !APP_SOURCES.includes('youtube') && !APP_SOURCES.includes('web'), + [] + ); + const isWebOnly = useMemo( + () => APP_SOURCES.includes('web') && !APP_SOURCES.includes('youtube') && !APP_SOURCES.includes('wiki'), + [] + ); + const { messages, setClearHistoryData, clearHistoryData, setMessages, setIsDeleteChatLoading } = useMessageContext(); + const isFirstTimeUser = useMemo(() => localStorage.getItem('neo4j.connection') === null, []); + + const [combinedPatternsVal, setCombinedPatternsVal] = useState([]); + const [combinedNodesVal, setCombinedNodesVal] = useState([]); + const [combinedRelsVal, setCombinedRelsVal] = useState([]); + + useEffect(() => { + if (allPatterns.length > 0 && selectedNodes.length > 0 && selectedRels.length > 0) { + setCombinedPatternsVal(allPatterns); + setCombinedNodesVal(selectedNodes as OptionType[]); + setCombinedRelsVal(selectedRels as OptionType[]); + } + }, [allPatterns, selectedNodes, selectedRels]); + useEffect(() => { async function initializeConnection() { // Fetch backend health status @@ -271,7 +268,7 @@ const PageLayout: React.FC = () => { uri: credentials.uri, database: credentials.database, userName: credentials.userName, - password: atob(credentials.password), + password: atob(credentials?.password), email: credentials.email ?? '', }); setIsGCSActive(credentials.isGCSActive); @@ -303,7 +300,22 @@ const PageLayout: React.FC = () => { if ((isAuthenticated || SKIP_AUTH) && isFirstTimeUser) { setActiveSpotlight('connectbutton'); } - }, [isAuthenticated]); + }, [isAuthenticated, isFirstTimeUser]); + + const toggleLeftDrawer = useCallback(() => { + if (isLargeDesktop) { + setIsLeftExpanded((old) => !old); + } else { + setIsLeftExpanded(false); + } + }, [isLargeDesktop]); + const toggleRightDrawer = useCallback(() => { + if (isLargeDesktop) { + setIsRightExpanded((prev) => !prev); + } else { + setIsRightExpanded(false); + } + }, [isLargeDesktop]); const deleteOnClick = useCallback(async () => { try { @@ -337,48 +349,136 @@ const PageLayout: React.FC = () => { } }, []); - const handleApplyPatternsFromText = useCallback((newPatterns: string[], nodes: OptionType[], rels: OptionType[]) => { - setSchemaTextPattern((prevPatterns: string[]) => { - const uniquePatterns = Array.from(new Set([...newPatterns, ...prevPatterns])); - return uniquePatterns; - }); - setShowTextFromSchemaDialog({ - triggeredFrom: 'schematextApply', - show: true, - }); - setSchemaValNodes(nodes); - setSchemaValRels(rels); - setSchemaView('text'); + const handleApplyPatternsFromText = useCallback( + ( + newPatterns: string[], + nodes: OptionType[], + rels: OptionType[], + updatedSource: OptionType[], + updatedTarget: OptionType[], + updatedType: OptionType[] + ) => { + setSchemaTextPattern((prevPatterns: string[]) => { + const uniquePatterns = Array.from(new Set([...newPatterns, ...prevPatterns])); + return uniquePatterns; + }); + setCombinedPatternsVal((prevPatterns: string[]) => { + const uniquePatterns = Array.from(new Set([...newPatterns, ...prevPatterns])); + return uniquePatterns; + }); + setShowTextFromSchemaDialog({ + triggeredFrom: 'schematextApply', + show: true, + }); + setSchemaValNodes(nodes); + setCombinedNodesVal((prevNodes: OptionType[]) => { + const combined = [...nodes, ...prevNodes]; + return deduplicateNodeByValue(combined); + }); + setSchemaValRels(rels); + setCombinedRelsVal((prevRels: OptionType[]) => { + const combined = [...rels, ...prevRels]; + return deduplicateByRelationshipTypeOnly(combined); + }); + setSchemaView('text'); + localStorage.setItem(LOCAL_KEYS.source, JSON.stringify(updatedSource)); + localStorage.setItem(LOCAL_KEYS.type, JSON.stringify(updatedType)); + localStorage.setItem(LOCAL_KEYS.target, JSON.stringify(updatedTarget)); + }, + [] + ); + + const handleDbApply = useCallback( + ( + newPatterns: string[], + nodes: OptionType[], + rels: OptionType[], + updatedSource: OptionType[], + updatedTarget: OptionType[], + updatedType: OptionType[] + ) => { + setDbPattern((prevPatterns: string[]) => { + const uniquePatterns = Array.from(new Set([...newPatterns, ...prevPatterns])); + return uniquePatterns; + }); + setCombinedPatternsVal((prevPatterns: string[]) => { + const uniquePatterns = Array.from(new Set([...newPatterns, ...prevPatterns])); + return uniquePatterns; + }); + setSchemaLoadDialog({ + triggeredFrom: 'loadExistingSchemaApply', + show: true, + }); + setSchemaView('db'); + setDbNodes(nodes); + setCombinedNodesVal((prevNodes: OptionType[]) => { + const combined = [...nodes, ...prevNodes]; + return deduplicateNodeByValue(combined); + }); + setDbRels(rels); + setCombinedRelsVal((prevRels: OptionType[]) => { + const combined = [...rels, ...prevRels]; + return deduplicateByRelationshipTypeOnly(combined); + }); + localStorage.setItem(LOCAL_KEYS.source, JSON.stringify(updatedSource)); + localStorage.setItem(LOCAL_KEYS.type, JSON.stringify(updatedType)); + localStorage.setItem(LOCAL_KEYS.target, JSON.stringify(updatedTarget)); + }, + [] + ); + const handlePredinedApply = useCallback( + ( + newPatterns: string[], + nodes: OptionType[], + rels: OptionType[], + updatedSource: OptionType[], + updatedTarget: OptionType[], + updatedType: OptionType[] + ) => { + setPreDefinedPattern((prevPatterns: string[]) => { + const uniquePatterns = Array.from(new Set([...newPatterns, ...prevPatterns])); + return uniquePatterns; + }); + setCombinedPatternsVal((prevPatterns: string[]) => { + const uniquePatterns = Array.from(new Set([...newPatterns, ...prevPatterns])); + return uniquePatterns; + }); + setPredefinedSchemaDialog({ + triggeredFrom: 'predefinedSchemaApply', + show: true, + }); + setSchemaView('preDefined'); + setPreDefinedNodes(nodes); + setCombinedNodesVal((prevNodes: OptionType[]) => { + const combined = [...nodes, ...prevNodes]; + return deduplicateNodeByValue(combined); + }); + setPreDefinedRels(rels); + setCombinedRelsVal((prevRels: OptionType[]) => { + const combined = [...rels, ...prevRels]; + return deduplicateByRelationshipTypeOnly(combined); + }); + localStorage.setItem(LOCAL_KEYS.source, JSON.stringify(updatedSource)); + localStorage.setItem(LOCAL_KEYS.type, JSON.stringify(updatedType)); + localStorage.setItem(LOCAL_KEYS.target, JSON.stringify(updatedTarget)); + }, + [] + ); + + const openPredefinedSchema = useCallback(() => { + setPredefinedSchemaDialog({ triggeredFrom: 'predefinedDialog', show: true }); }, []); - const handleDbApply = useCallback((newPatterns: string[], nodes: OptionType[], rels: OptionType[]) => { - setDbPattern((prevPatterns: string[]) => { - const uniquePatterns = Array.from(new Set([...newPatterns, ...prevPatterns])); - return uniquePatterns; - }); - setSchemaLoadDialog({ - triggeredFrom: 'loadExistingSchemaApply', - show: true, - }); - setSchemaView('db'); - setDbNodes(nodes); - setDbRels(rels); + const openLoadSchema = useCallback(() => { + setSchemaLoadDialog({ triggeredFrom: 'loadDialog', show: true }); }, []); - const handlePredinedApply = useCallback((newPatterns: string[], nodes: OptionType[], rels: OptionType[]) => { - setPreDefinedPattern((prevPatterns: string[]) => { - const uniquePatterns = Array.from(new Set([...newPatterns, ...prevPatterns])); - return uniquePatterns; - }); - setPredefinedSchemaDialog({ - triggeredFrom: 'predefinedSchemaApply', - show: true, - }); - setSchemaView('preDefined'); - setPreDefinedNodes(nodes); - setPreDefinedRels(rels); + const openTextSchema = useCallback(() => { + setShowTextFromSchemaDialog({ triggeredFrom: 'schemadialog', show: true }); }, []); + const openChatBot = useCallback(() => setShowChatBot(true), []); + return ( <> {!isAuthenticated && !SKIP_AUTH && isFirstTimeUser ? ( @@ -437,7 +537,7 @@ const PageLayout: React.FC = () => { }} onApply={handleApplyPatternsFromText} > - { setSchemaLoadDialog({ triggeredFrom: '', show: false }); @@ -491,22 +591,22 @@ const PageLayout: React.FC = () => { /> )} setShowChatBot(true), [])} + openChatBot={openChatBot} showChatBot={showChatBot} - openTextSchema={useCallback(() => { - setShowTextFromSchemaDialog({ triggeredFrom: 'schemadialog', show: true }); - }, [])} - openLoadSchema={useCallback(() => { - setSchemaLoadDialog({ triggeredFrom: 'loadDialog', show: true }); - }, [])} - openPredefinedSchema={useCallback(() => { - setPredefinedSchemaDialog({ triggeredFrom: 'predefinedDialog', show: true }); - }, [])} + openTextSchema={openTextSchema} + openLoadSchema={openLoadSchema} + openPredefinedSchema={openPredefinedSchema} showEnhancementDialog={showEnhancementDialog} toggleEnhancementDialog={toggleEnhancementDialog} setOpenConnection={setOpenConnection} showDisconnectButton={showDisconnectButton} connectionStatus={connectionStatus} + combinedPatterns={combinedPatternsVal} + setCombinedPatterns={setCombinedPatternsVal} + combinedNodes={combinedNodesVal} + setCombinedNodes={setCombinedNodesVal} + combinedRels={combinedRelsVal} + setCombinedRels={setCombinedRelsVal} /> {isRightExpanded && ( {
) : ( <> - }> - - - }> - - + {APP_SOURCES.includes('gcs') && ( + }> + + + )} + {APP_SOURCES.includes('s3') && ( + }> + + + )} + }> { /> setShowChatBot(true)} + openChatBot={openChatBot} showChatBot={showChatBot} - openTextSchema={useCallback(() => { - setShowTextFromSchemaDialog({ triggeredFrom: 'schemaDialog', show: true }); - }, [])} - openLoadSchema={useCallback(() => { - setShowTextFromSchemaDialog({ triggeredFrom: 'loadDialog', show: true }); - }, [])} - openPredefinedSchema={useCallback(() => { - setPredefinedSchemaDialog({ triggeredFrom: 'prdefinedDialog', show: true }); - }, [])} + openTextSchema={openTextSchema} + openLoadSchema={openLoadSchema} + openPredefinedSchema={openPredefinedSchema} showEnhancementDialog={showEnhancementDialog} toggleEnhancementDialog={toggleEnhancementDialog} setOpenConnection={setOpenConnection} showDisconnectButton={showDisconnectButton} connectionStatus={connectionStatus} + combinedPatterns={combinedPatternsVal} + setCombinedPatterns={setCombinedPatternsVal} + combinedNodes={combinedNodesVal} + setCombinedNodes={setCombinedNodesVal} + combinedRels={combinedRelsVal} + setCombinedRels={setCombinedRelsVal} /> {isRightExpanded && (
Text Chunks diff --git a/frontend/src/components/Popups/GraphEnhancementDialog/Deduplication/index.tsx b/frontend/src/components/Popups/GraphEnhancementDialog/Deduplication/index.tsx index 6505366a1..4c95fa9ef 100644 --- a/frontend/src/components/Popups/GraphEnhancementDialog/Deduplication/index.tsx +++ b/frontend/src/components/Popups/GraphEnhancementDialog/Deduplication/index.tsx @@ -12,16 +12,7 @@ import { Row, getSortedRowModel, } from '@tanstack/react-table'; -import { - Checkbox, - DataGrid, - DataGridComponents, - Flex, - Tag, - TextLink, - Typography, - useMediaQuery, -} from '@neo4j-ndl/react'; +import { Checkbox, DataGrid, DataGridComponents, Flex, Tag, Typography, useMediaQuery, Button } from '@neo4j-ndl/react'; import Legend from '../../../UI/Legend'; import { DocumentIconOutline } from '@neo4j-ndl/react/icons'; import { calcWordColor } from '@neo4j-devtools/word-color'; @@ -155,15 +146,16 @@ export default function DeduplicationTab() { cell: (info) => { return (
- handleDuplicateNodeClick(info.row.id, 'chatInfoView')} htmlAttributes={{ - onClick: () => handleDuplicateNodeClick(info.row.id, 'chatInfoView'), title: info.getValue(), }} > {info.getValue()} - +
); }, @@ -264,8 +256,8 @@ export default function DeduplicationTab() { const selectedFilesCheck = mergeAPIloading ? 'Merging...' : table.getSelectedRowModel().rows.length - ? `Merge Duplicate Nodes (${table.getSelectedRowModel().rows.length})` - : 'Select Node(s) to Merge'; + ? `Merge Duplicate Nodes (${table.getSelectedRowModel().rows.length})` + : 'Select Node(s) to Merge'; return ( <>
@@ -333,12 +325,12 @@ export default function DeduplicationTab() { isLoading ? 'Fetching Duplicate Nodes' : !isLoading && !duplicateNodes.length - ? 'No Nodes Found' - : !table.getSelectedRowModel().rows.length - ? 'No Nodes Selected' - : mergeAPIloading - ? 'Merging' - : `Merge Selected Nodes (${table.getSelectedRowModel().rows.length})` + ? 'No Nodes Found' + : !table.getSelectedRowModel().rows.length + ? 'No Nodes Selected' + : mergeAPIloading + ? 'Merging' + : `Merge Selected Nodes (${table.getSelectedRowModel().rows.length})` } label='Merge Duplicate Node Button' disabled={!table.getSelectedRowModel().rows.length} diff --git a/frontend/src/components/Popups/GraphEnhancementDialog/DeleteTabForOrphanNodes/index.tsx b/frontend/src/components/Popups/GraphEnhancementDialog/DeleteTabForOrphanNodes/index.tsx index 4300fe025..8fa9ed395 100644 --- a/frontend/src/components/Popups/GraphEnhancementDialog/DeleteTabForOrphanNodes/index.tsx +++ b/frontend/src/components/Popups/GraphEnhancementDialog/DeleteTabForOrphanNodes/index.tsx @@ -1,4 +1,4 @@ -import { Checkbox, DataGrid, DataGridComponents, Flex, TextLink, Typography, useMediaQuery } from '@neo4j-ndl/react'; +import { Checkbox, DataGrid, DataGridComponents, Flex, Typography, useMediaQuery, Button } from '@neo4j-ndl/react'; import { useCallback, useContext, useEffect, useMemo, useRef, useState } from 'react'; import { orphanNodeProps } from '../../../../types'; import { getOrphanNodes } from '../../../../services/GetOrphanNodes'; @@ -115,15 +115,16 @@ export default function DeletePopUpForOrphanNodes({ cell: (info) => { return (
- handleOrphanNodeClick(info.row.id, 'chatInfoView')} htmlAttributes={{ - onClick: () => handleOrphanNodeClick(info.row.id, 'chatInfoView'), title: info.getValue() ? info.getValue() : info.row.id, }} > {info.getValue() ? info.getValue() : info.row.id} - +
); }, @@ -298,10 +299,10 @@ export default function DeletePopUpForOrphanNodes({ isLoading ? 'Fetching Orphan Nodes' : !isLoading && !orphanNodes.length - ? 'No Nodes Found' - : !table.getSelectedRowModel().rows.length - ? 'No Nodes Selected' - : `Delete Selected Nodes (${table.getSelectedRowModel().rows.length})` + ? 'No Nodes Found' + : !table.getSelectedRowModel().rows.length + ? 'No Nodes Selected' + : `Delete Selected Nodes (${table.getSelectedRowModel().rows.length})` } label='Orphan Node deletion button' disabled={!table.getSelectedRowModel().rows.length} diff --git a/frontend/src/components/Popups/GraphEnhancementDialog/EnitityExtraction/EntityExtractionSetting.tsx b/frontend/src/components/Popups/GraphEnhancementDialog/EnitityExtraction/EntityExtractionSetting.tsx deleted file mode 100644 index 050caa7cd..000000000 --- a/frontend/src/components/Popups/GraphEnhancementDialog/EnitityExtraction/EntityExtractionSetting.tsx +++ /dev/null @@ -1,382 +0,0 @@ - -// import { MouseEventHandler, useCallback, useEffect, useMemo, useState } from 'react'; -// import ButtonWithToolTip from '../../../UI/ButtonWithToolTip'; -// import { appLabels, buttonCaptions, getDefaultSchemaExamples, tooltips } from '../../../../utils/Constants'; -// import { Select, Flex, Typography, useMediaQuery } from '@neo4j-ndl/react'; -// import { useCredentials } from '../../../../context/UserCredentials'; -// import { useFileContext } from '../../../../context/UsersFiles'; -// import { OnChangeValue, ActionMeta } from 'react-select'; -// import { OptionType, schema } from '../../../../types'; -// import { getNodeLabelsAndRelTypes } from '../../../../services/GetNodeLabelsRelTypes'; -// import { tokens } from '@neo4j-ndl/base'; -// import { showNormalToast } from '../../../../utils/Toasts'; -// import { useHasSelections } from '../../../../hooks/useHasSelections'; -// import { Hierarchy1Icon } from '@neo4j-ndl/react/icons'; -// import GraphViewModal from '../../../Graph/GraphViewModal'; - - -// export default function EntityExtractionSetting({ -// view, -// open, -// onClose, -// openTextSchema, -// settingView, -// onContinue, -// closeEnhanceGraphSchemaDialog, -// }: { -// view: 'Dialog' | 'Tabs'; -// open?: boolean; -// onClose?: () => void; -// openTextSchema: () => void; -// settingView: 'contentView' | 'headerView'; -// onContinue?: () => void; -// closeEnhanceGraphSchemaDialog?: () => void; -// }) { -// const { breakpoints } = tokens; -// const { setSelectedRels, setSelectedNodes, selectedNodes, selectedRels, selectedSchemas, setSelectedSchemas } = -// useFileContext(); -// const { userCredentials } = useCredentials(); -// const [loading, setLoading] = useState(false); -// const isTablet = useMediaQuery(`(min-width:${breakpoints.xs}) and (max-width: ${breakpoints.lg})`); -// const hasSelections = useHasSelections(selectedNodes, selectedRels); -// const [openGraphView, setOpenGraphView] = useState(false); -// const [viewPoint, setViewPoint] = useState('tableView'); -// const removeNodesAndRels = (nodelabels: string[], relationshipTypes: string[]) => { -// const labelsToRemoveSet = new Set(nodelabels); -// const relationshipLabelsToremoveSet = new Set(relationshipTypes); -// setSelectedNodes((prevState) => { -// const filterednodes = prevState.filter((item) => !labelsToRemoveSet.has(item.label)); -// localStorage.setItem( -// 'selectedNodeLabels', -// JSON.stringify({ db: userCredentials?.uri, selectedOptions: filterednodes }) -// ); -// return filterednodes; -// }); -// setSelectedRels((prevState) => { -// const filteredrels = prevState.filter((item) => !relationshipLabelsToremoveSet.has(item.label)); -// localStorage.setItem( -// 'selectedRelationshipLabels', -// JSON.stringify({ db: userCredentials?.uri, selectedOptions: filteredrels }) -// ); -// return filteredrels; -// }); -// }; -// const onChangeSchema = (selectedOptions: OnChangeValue, actionMeta: ActionMeta) => { -// if (actionMeta.action === 'remove-value') { -// const removedSchema: schema = JSON.parse(actionMeta.removedValue.value); -// const { nodelabels, relationshipTypes } = removedSchema; -// removeNodesAndRels(nodelabels, relationshipTypes); -// } else if (actionMeta.action === 'clear') { -// const removedSchemas = actionMeta.removedValues.map((s) => JSON.parse(s.value)); -// const removedNodelabels = removedSchemas.map((s) => s.nodelabels).flatMap((k) => k); -// const removedRelations = removedSchemas.map((s) => s.relationshipTypes).flatMap((k) => k); -// removeNodesAndRels(removedNodelabels, removedRelations); -// } -// setSelectedSchemas(selectedOptions); -// localStorage.setItem( -// 'selectedSchemas', -// JSON.stringify({ db: userCredentials?.uri, selectedOptions: selectedOptions }) -// ); -// const nodesFromSchema = selectedOptions.map((s) => JSON.parse(s.value).nodelabels).flat(); -// const relationsFromSchema = selectedOptions.map((s) => JSON.parse(s.value).relationshipTypes).flat(); -// let nodeOptionsFromSchema: OptionType[] = []; -// for (let index = 0; index < nodesFromSchema.length; index++) { -// const n = nodesFromSchema[index]; -// nodeOptionsFromSchema.push({ label: n, value: n }); -// } -// let relationshipOptionsFromSchema: OptionType[] = []; -// for (let index = 0; index < relationsFromSchema.length; index++) { -// const r = relationsFromSchema[index]; -// relationshipOptionsFromSchema.push({ label: r, value: r }); -// } -// setSelectedNodes((prev) => { -// const combinedData = [...prev, ...nodeOptionsFromSchema]; -// const uniqueLabels = new Set(); -// const updatedOptions = combinedData.filter((item) => { -// if (!uniqueLabels.has(item.label)) { -// uniqueLabels.add(item.label); -// return true; -// } -// return false; -// }); -// localStorage.setItem( -// 'selectedNodeLabels', -// JSON.stringify({ db: userCredentials?.uri, selectedOptions: updatedOptions }) -// ); -// return updatedOptions; -// }); -// setSelectedRels((prev) => { -// const combinedData = [...prev, ...relationshipOptionsFromSchema]; -// const uniqueLabels = new Set(); -// const updatedOptions = combinedData.filter((item) => { -// if (!uniqueLabels.has(item.label)) { -// uniqueLabels.add(item.label); -// return true; -// } -// return false; -// }); -// localStorage.setItem( -// 'selectedRelationshipLabels', -// JSON.stringify({ db: userCredentials?.uri, selectedOptions: updatedOptions }) -// ); -// return updatedOptions; -// }); -// }; -// const onChangenodes = (selectedOptions: OnChangeValue, actionMeta: ActionMeta) => { -// if (actionMeta.action === 'clear') { -// localStorage.setItem('selectedNodeLabels', JSON.stringify({ db: userCredentials?.uri, selectedOptions: [] })); -// } -// setSelectedNodes(selectedOptions); -// localStorage.setItem('selectedNodeLabels', JSON.stringify({ db: userCredentials?.uri, selectedOptions })); -// }; -// const onChangerels = (selectedOptions: OnChangeValue, actionMeta: ActionMeta) => { -// if (actionMeta.action === 'clear') { -// localStorage.setItem( -// 'selectedRelationshipLabels', -// JSON.stringify({ db: userCredentials?.uri, selectedOptions: [] }) -// ); -// } -// setSelectedRels(selectedOptions); -// localStorage.setItem('selectedRelationshipLabels', JSON.stringify({ db: userCredentials?.uri, selectedOptions })); -// }; -// const [nodeLabelOptions, setnodeLabelOptions] = useState([]); -// const [relationshipTypeOptions, setrelationshipTypeOptions] = useState([]); -// const defaultExamples = useMemo(() => getDefaultSchemaExamples(), []); - -// useEffect(() => { -// if (userCredentials) { -// if (open && view === 'Dialog') { -// const getOptions = async () => { -// setLoading(true); -// try { -// const response = await getNodeLabelsAndRelTypes(); -// setLoading(false); -// if (response.data.data.length) { -// const nodelabels = response.data?.data[0]?.labels.map((l) => ({ value: l, label: l })); -// const reltypes = response.data?.data[0]?.relationshipTypes.map((t) => ({ value: t, label: t })); -// setnodeLabelOptions(nodelabels); -// setrelationshipTypeOptions(reltypes); -// } -// } catch (error) { -// setLoading(false); -// console.log(error); -// } -// }; -// getOptions(); -// return; -// } -// if (view == 'Tabs') { -// const getOptions = async () => { -// setLoading(true); -// try { -// const response = await getNodeLabelsAndRelTypes(); -// setLoading(false); -// if (response.data.data.length) { -// const nodelabels = response.data?.data[0]?.labels.map((l) => ({ value: l, label: l })); -// const reltypes = response.data?.data[0]?.relationshipTypes.map((t) => ({ value: t, label: t })); -// setnodeLabelOptions(nodelabels); -// setrelationshipTypeOptions(reltypes); -// } -// } catch (error) { -// setLoading(false); -// console.log(error); -// } -// }; -// getOptions(); -// } -// } -// }, [userCredentials, open]); - -// const clickHandler: MouseEventHandler = useCallback(() => { -// setSelectedSchemas([]); -// setSelectedNodes(nodeLabelOptions); -// setSelectedRels(relationshipTypeOptions); -// localStorage.setItem( -// 'selectedNodeLabels', -// JSON.stringify({ db: userCredentials?.uri, selectedOptions: nodeLabelOptions }) -// ); -// localStorage.setItem( -// 'selectedRelationshipLabels', -// JSON.stringify({ db: userCredentials?.uri, selectedOptions: relationshipTypeOptions }) -// ); -// }, [nodeLabelOptions, relationshipTypeOptions]); - -// const handleClear = () => { -// setSelectedNodes([]); -// setSelectedRels([]); -// setSelectedSchemas([]); -// localStorage.setItem('selectedNodeLabels', JSON.stringify({ db: userCredentials?.uri, selectedOptions: [] })); -// localStorage.setItem( -// 'selectedRelationshipLabels', -// JSON.stringify({ db: userCredentials?.uri, selectedOptions: [] }) -// ); -// localStorage.setItem('selectedSchemas', JSON.stringify({ db: userCredentials?.uri, selectedOptions: [] })); -// showNormalToast(`Successfully Removed the Schema settings`); -// if (view === 'Tabs' && closeEnhanceGraphSchemaDialog != undefined) { -// closeEnhanceGraphSchemaDialog(); -// } -// }; -// const handleApply = () => { -// showNormalToast(`Successfully Applied the Schema settings`); -// if (view === 'Tabs' && closeEnhanceGraphSchemaDialog != undefined) { -// closeEnhanceGraphSchemaDialog(); -// } -// localStorage.setItem( -// 'selectedNodeLabels', -// JSON.stringify({ db: userCredentials?.uri, selectedOptions: selectedNodes }) -// ); -// localStorage.setItem( -// 'selectedRelationshipLabels', -// JSON.stringify({ db: userCredentials?.uri, selectedOptions: selectedRels }) -// ); -// localStorage.setItem( -// 'selectedSchemas', -// JSON.stringify({ db: userCredentials?.uri, selectedOptions: selectedSchemas }) -// ); -// }; - -// const handleSchemaView = () => { -// setOpenGraphView(true); -// setViewPoint('showSchemaView'); -// }; -// return ( -//
-// -// -// 1.Predefine the structure of your knowledge graph by selecting specific node and relationship labels. -// -//

-// -// 2.Focus your analysis by extracting only the relationships and entities that matter most to your use case. -// Achieve a cleaner and more insightful graph representation tailored to your domain. -// -//
-//
-//
-//
{appLabels.predefinedSchema}
-//
-// -// = ({ inputValue: inputValues.source, onInputChange: (newValue) => handleInputChange(newValue, 'source'), onCreateOption: (newOption) => handleNewValue(newOption, 'source'), + onBlur: (e) => { + handleBlurPattern(e.target.value, 'source'); + }, }} type='creatable' /> @@ -116,6 +151,7 @@ const GraphPattern: React.FC = ({ = ({ inputValue: inputValues.target, onInputChange: (newValue) => handleInputChange(newValue, 'target'), onCreateOption: (newOption) => handleNewValue(newOption, 'target'), + onBlur: (e) => { + handleBlurPattern(e.target.value, 'target'); + }, }} type='creatable' className='w-1/4' diff --git a/frontend/src/components/Popups/GraphEnhancementDialog/EnitityExtraction/LoadExistingSchema.tsx b/frontend/src/components/Popups/GraphEnhancementDialog/EnitityExtraction/LoadExistingSchema.tsx index 1b37e7d67..76b823763 100644 --- a/frontend/src/components/Popups/GraphEnhancementDialog/EnitityExtraction/LoadExistingSchema.tsx +++ b/frontend/src/components/Popups/GraphEnhancementDialog/EnitityExtraction/LoadExistingSchema.tsx @@ -2,18 +2,38 @@ import { useEffect, useState } from 'react'; import SchemaSelectionDialog from '../../../UI/SchemaSelectionPopup'; import { getNodeLabelsAndRelTypes } from '../../../../services/GetNodeLabelsRelTypes'; import { OptionType, TupleType } from '../../../../types'; -import { extractOptions } from '../../../../utils/Utils'; +import { extractOptions, updateSourceTargetTypeOptions } from '../../../../utils/Utils'; import { useFileContext } from '../../../../context/UsersFiles'; import SchemaViz from '../../../../components/Graph/SchemaViz'; -interface LoadExistingSchemaDialogProps { +interface LoadDBSchemaDialogProps { open: boolean; onClose: () => void; - onApply: (patterns: string[], nodeLabels: OptionType[], relationshipLabels: OptionType[], view: string) => void; + onApply: ( + patterns: string[], + nodeLabels: OptionType[], + relationshipLabels: OptionType[], + updatedSource: OptionType[], + updatedTarget: OptionType[], + updatedType: OptionType[] + ) => void; } -const LoadExistingSchemaDialog = ({ open, onClose, onApply }: LoadExistingSchemaDialogProps) => { +const LoadDBSchemaDialog = ({ open, onClose, onApply }: LoadDBSchemaDialogProps) => { const [loading, setLoading] = useState(false); - const { setDbPattern, dbPattern, dbNodes, setDbNodes, dbRels, setDbRels } = useFileContext(); + const { + dbPattern, + setDbPattern, + dbNodes, + setDbNodes, + dbRels, + setDbRels, + sourceOptions, + setSourceOptions, + targetOptions, + setTargetOptions, + typeOptions, + setTypeOptions, + } = useFileContext(); const [openGraphView, setOpenGraphView] = useState(false); const [viewPoint, setViewPoint] = useState(''); @@ -89,8 +109,17 @@ const LoadExistingSchemaDialog = ({ open, onClose, onApply }: LoadExistingSchema setViewPoint('showSchemaView'); }; - const handleDBApply = () => { - onApply(dbPattern, dbNodes, dbRels, 'db'); + const handleDBCheck = async () => { + const [newSourceOptions, newTargetOptions, newTypeOptions] = await updateSourceTargetTypeOptions({ + patterns: dbPattern.map((label) => ({ label, value: label })), + currentSourceOptions: sourceOptions, + currentTargetOptions: targetOptions, + currentTypeOptions: typeOptions, + setSourceOptions, + setTargetOptions, + setTypeOptions, + }); + onApply(dbPattern, dbNodes, dbRels, newSourceOptions, newTargetOptions, newTypeOptions); onClose(); }; @@ -109,7 +138,7 @@ const LoadExistingSchemaDialog = ({ open, onClose, onApply }: LoadExistingSchema handleRemove={handleRemovePattern} handleSchemaView={handleSchemaView} loading={loading} - onApply={handleDBApply} + onApply={handleDBCheck} onCancel={handleCancel} nodes={dbNodes} rels={dbRels} @@ -127,4 +156,4 @@ const LoadExistingSchemaDialog = ({ open, onClose, onApply }: LoadExistingSchema ); }; -export default LoadExistingSchemaDialog; +export default LoadDBSchemaDialog; diff --git a/frontend/src/components/Popups/GraphEnhancementDialog/EnitityExtraction/NewEntityExtractionSetting.tsx b/frontend/src/components/Popups/GraphEnhancementDialog/EnitityExtraction/NewEntityExtractionSetting.tsx index 2f03a0ea3..7b51cae28 100644 --- a/frontend/src/components/Popups/GraphEnhancementDialog/EnitityExtraction/NewEntityExtractionSetting.tsx +++ b/frontend/src/components/Popups/GraphEnhancementDialog/EnitityExtraction/NewEntityExtractionSetting.tsx @@ -1,4 +1,4 @@ -import { MouseEventHandler, useCallback, useEffect, useState, useRef } from 'react'; +import { MouseEventHandler, useCallback, useEffect, useState, useRef, Dispatch, SetStateAction } from 'react'; import ButtonWithToolTip from '../../../UI/ButtonWithToolTip'; import { buttonCaptions, tooltips } from '../../../../utils/Constants'; import { Flex, Typography, DropdownButton, Menu } from '@neo4j-ndl/react'; @@ -9,7 +9,13 @@ import { showNormalToast } from '../../../../utils/Toasts'; import PatternContainer from './PatternContainer'; import SchemaViz from '../../../Graph/SchemaViz'; import GraphPattern from './GraphPattern'; -import { updateLocalStorage, extractOptions } from '../../../../utils/Utils'; +import { + updateLocalStorage, + extractOptions, + parseRelationshipString, + deduplicateByRelationshipTypeOnly, + deduplicateNodeByValue, +} from '../../../../utils/Utils'; import TooltipWrapper from '../../../UI/TipWrapper'; export default function NewEntityExtractionSetting({ @@ -21,6 +27,12 @@ export default function NewEntityExtractionSetting({ settingView, onContinue, closeEnhanceGraphSchemaDialog, + combinedPatterns, + setCombinedPatterns, + combinedNodes, + setCombinedNodes, + combinedRels, + setCombinedRels, }: { view: 'Dialog' | 'Tabs'; open?: boolean; @@ -31,35 +43,30 @@ export default function NewEntityExtractionSetting({ settingView: 'contentView' | 'headerView'; onContinue?: () => void; closeEnhanceGraphSchemaDialog?: () => void; + combinedPatterns: string[]; + setCombinedPatterns: Dispatch>; + combinedNodes: OptionType[]; + setCombinedNodes: Dispatch>; + combinedRels: OptionType[]; + setCombinedRels: Dispatch>; }) { const { - selectedRels, - selectedNodes, - allPatterns, setSelectedRels, setSelectedNodes, userDefinedPattern, setUserDefinedPattern, - userDefinedNodes, setUserDefinedNodes, - userDefinedRels, setUserDefinedRels, setAllPatterns, dbPattern, setDbPattern, - dbNodes, setDbNodes, - dbRels, setDbRels, - schemaValNodes, setSchemaValNodes, - schemaValRels, setSchemaValRels, schemaTextPattern, setSchemaTextPattern, - preDefinedNodes, setPreDefinedNodes, - preDefinedRels, setPreDefinedRels, preDefinedPattern, setPreDefinedPattern, @@ -72,34 +79,9 @@ export default function NewEntityExtractionSetting({ const [selectedType, setType] = useState(null); const [selectedTarget, setTarget] = useState(null); const [highlightPattern, setHighlightedPattern] = useState(null); - const [combinedPatterns, setCombinedPatterns] = useState([]); - const [combinedNodes, setCombinedNodes] = useState([]); - const [combinedRels, setCombinedRels] = useState([]); + const [isSchemaMenuOpen, setIsSchemaMenuOpen] = useState(false); const schemaBtnRef = useRef(null); - useEffect(() => { - const patterns = Array.from( - new Set([...userDefinedPattern, ...preDefinedPattern, ...dbPattern, ...schemaTextPattern]) - ); - const nodesVal = Array.from(new Set([...userDefinedNodes, ...preDefinedNodes, ...dbNodes, ...schemaValNodes])); - const relsVal = Array.from(new Set([...userDefinedRels, ...preDefinedRels, ...dbRels, ...schemaValRels])); - setCombinedPatterns(patterns); - setCombinedNodes(nodesVal); - setCombinedRels(relsVal); - }, [ - userDefinedPattern, - preDefinedPattern, - dbPattern, - schemaTextPattern, - userDefinedNodes, - preDefinedNodes, - dbNodes, - schemaValNodes, - userDefinedRels, - preDefinedRels, - dbRels, - schemaValRels, - ]); useEffect(() => { if (userDefinedPattern.length > 0) { @@ -111,14 +93,6 @@ export default function NewEntityExtractionSetting({ } }, [userDefinedPattern]); - useEffect(() => { - if (allPatterns.length) { - setCombinedNodes(selectedNodes as OptionType[]); - setCombinedPatterns(allPatterns); - setCombinedRels(selectedRels as OptionType[]); - } - }, [allPatterns, selectedNodes, selectedRels]); - const handleFinalClear = () => { // overall setSelectedNodes([]); @@ -143,18 +117,10 @@ export default function NewEntityExtractionSetting({ setCombinedPatterns([]); setCombinedNodes([]); setCombinedRels([]); + setTupleOptions([]); updateLocalStorage(userCredentials!, 'selectedNodeLabels', []); updateLocalStorage(userCredentials!, 'selectedRelationshipLabels', []); updateLocalStorage(userCredentials!, 'selectedPattern', []); - updateLocalStorage(userCredentials!, 'preDefinedNodeLabels', []); - updateLocalStorage(userCredentials!, 'preDefinedRelationshipLabels', []); - updateLocalStorage(userCredentials!, 'preDefinedPatterns', []); - updateLocalStorage(userCredentials!, 'textNodeLabels', []); - updateLocalStorage(userCredentials!, 'textRelationLabels', []); - updateLocalStorage(userCredentials!, 'textPatterns', []); - updateLocalStorage(userCredentials!, 'dbNodeLabels', []); - updateLocalStorage(userCredentials!, 'dbRelationLabels', []); - updateLocalStorage(userCredentials!, 'dbPatterns', []); showNormalToast(`Successfully Removed the Schema settings`); }; @@ -186,41 +152,86 @@ export default function NewEntityExtractionSetting({ setTarget(target as OptionType); }; - const handleAddPattern = () => { + const handleAddPattern = (tupleOptionsValue: TupleType[]) => { if (selectedSource && selectedType && selectedTarget) { const patternValue = `${selectedSource.value} -[:${selectedType.value}]-> ${selectedTarget.value}`; const relValue = `${selectedSource.value},${selectedType.value},${selectedTarget.value}`; const relationshipOption: TupleType = { value: relValue, label: patternValue, - source: selectedSource.value || '', - target: selectedTarget.value || '', - type: selectedType.value || '', + source: selectedSource.value, + target: selectedTarget.value, + type: selectedType.value, }; - setUserDefinedPattern((prev: string[]) => { - const alreadyExists = prev.includes(patternValue); - if (!alreadyExists) { - const updatedPattern = [patternValue, ...prev]; - return updatedPattern; - } - return prev; - }); - setTupleOptions((prev: TupleType[]) => { - const alreadyExists = prev.some((tuple) => tuple.value === relValue); - if (!alreadyExists) { - const updatedTupples = [relationshipOption, ...prev]; - const { nodeLabelOptions, relationshipTypeOptions } = extractOptions(updatedTupples); - setUserDefinedNodes(nodeLabelOptions); - setUserDefinedRels(relationshipTypeOptions); - return updatedTupples; + setUserDefinedPattern((prev) => (prev.includes(patternValue) ? prev : [patternValue, ...prev])); + setCombinedPatterns((prev) => (prev.includes(patternValue) ? prev : [patternValue, ...prev])); + const alreadyExists = tupleOptionsValue.some((tuple) => tuple.value === relValue); + if (!alreadyExists) { + const updatedTuples = [relationshipOption]; + const { nodeLabelOptions, relationshipTypeOptions } = extractOptions(updatedTuples); + setUserDefinedNodes((prev: OptionType[]) => { + const combined = [...prev, ...nodeLabelOptions]; + return deduplicateNodeByValue(combined); + }); + setUserDefinedRels((prev: OptionType[]) => { + const combined = [...prev, ...relationshipTypeOptions]; + return deduplicateByRelationshipTypeOnly(combined); + }); + setCombinedNodes((prev: OptionType[]) => { + const combined = [...prev, ...nodeLabelOptions]; + return deduplicateNodeByValue(combined); + }); + setCombinedRels((prev: OptionType[]) => { + const combined = [...prev, ...relationshipTypeOptions]; + return deduplicateByRelationshipTypeOnly(combined); + }); + setTupleOptions((prev) => [...updatedTuples, ...prev]); + } else { + if (tupleOptions.length === 0 && tupleOptionsValue.length > 0) { + setTupleOptions(tupleOptionsValue); } - return prev; - }); + showNormalToast('Pattern Already Exists'); + } setSource(null); setType(null); setTarget(null); } }; + const updateStore = ( + patterns: string[], + patternToRemove: string, + setPatterns: React.Dispatch>, + setNodes: React.Dispatch>, + setRels: React.Dispatch> + ) => { + const updatedPatterns = patterns.filter((p) => p !== patternToRemove); + if (updatedPatterns.length === 0) { + setPatterns([]); + setNodes([]); + setRels([]); + return; + } + const updatedTuples: TupleType[] = updatedPatterns + .map((item) => { + const parts = item.match(/(.*?) -\[:(.*?)\]-> (.*)/); + if (!parts) { + return null; + } + const [src, rel, tgt] = parts.slice(1).map((s) => s.trim()); + return { + value: `${src},${rel},${tgt}`, + label: `${src} -[:${rel}]-> ${tgt}`, + source: src, + target: tgt, + type: rel, + }; + }) + .filter(Boolean) as TupleType[]; + const { nodeLabelOptions, relationshipTypeOptions } = extractOptions(updatedTuples); + setPatterns(updatedPatterns); + setNodes(nodeLabelOptions); + setRels(relationshipTypeOptions); + }; const handleRemovePattern = (patternToRemove: string) => { const match = patternToRemove.match(/(.*?) -\[:(.*?)\]-> (.*)/); @@ -228,51 +239,18 @@ export default function NewEntityExtractionSetting({ return; } const [, source, type, target] = match.map((s) => s.trim()); - const updateStore = ( - patterns: string[], - setPatterns: React.Dispatch>, - setNodes: React.Dispatch>, - setRels: React.Dispatch> - ) => { - const updatedPatterns = patterns.filter((p) => p !== patternToRemove); - if (updatedPatterns.length === 0) { - setPatterns([]); - setNodes([]); - setRels([]); - return; - } - const updatedTuples: TupleType[] = updatedPatterns - .map((item) => { - const parts = item.match(/(.*?) -\[:(.*?)\]-> (.*)/); - if (!parts) { - return null; - } - const [src, rel, tgt] = parts.slice(1).map((s) => s.trim()); - return { - value: `${src},${rel},${tgt}`, - label: `${src} -[:${rel}]-> ${tgt}`, - source: src, - target: tgt, - type: rel, - }; - }) - .filter(Boolean) as TupleType[]; - const { nodeLabelOptions, relationshipTypeOptions } = extractOptions(updatedTuples); - setPatterns(updatedPatterns); - setNodes(nodeLabelOptions); - setRels(relationshipTypeOptions); - }; + if (userDefinedPattern.includes(patternToRemove)) { - updateStore(userDefinedPattern, setUserDefinedPattern, setUserDefinedNodes, setUserDefinedRels); + updateStore(userDefinedPattern, patternToRemove, setUserDefinedPattern, setUserDefinedNodes, setUserDefinedRels); } if (preDefinedPattern.includes(patternToRemove)) { - updateStore(preDefinedPattern, setPreDefinedPattern, setPreDefinedNodes, setPreDefinedRels); + updateStore(preDefinedPattern, patternToRemove, setPreDefinedPattern, setPreDefinedNodes, setPreDefinedRels); } if (dbPattern.includes(patternToRemove)) { - updateStore(dbPattern, setDbPattern, setDbNodes, setDbRels); + updateStore(dbPattern, patternToRemove, setDbPattern, setDbNodes, setDbRels); } if (schemaTextPattern.includes(patternToRemove)) { - updateStore(schemaTextPattern, setSchemaTextPattern, setSchemaValNodes, setSchemaValRels); + updateStore(schemaTextPattern, patternToRemove, setSchemaTextPattern, setSchemaValNodes, setSchemaValRels); } setCombinedPatterns((prev) => prev.filter((p) => p !== patternToRemove)); setCombinedNodes((prev) => prev.filter((n) => n.value !== source && n.value !== target)); @@ -328,7 +306,13 @@ export default function NewEntityExtractionSetting({ selectedType={selectedType} selectedTarget={selectedTarget} onPatternChange={handlePatternChange} - onAddPattern={handleAddPattern} + onAddPattern={() => + handleAddPattern( + tupleOptions.length > 0 + ? tupleOptions + : combinedPatterns.map((pattern) => parseRelationshipString(pattern)) + ) + } selectedTupleOptions={tupleOptions} > { - const [nodeCount, setNodeCount] = useState(0); - const [relCount, setRelCount] = useState(0); - useEffect(() => { - setNodeCount(nodes?.length ?? 0); - setRelCount(rels?.length ?? 0); - }, [nodes, rels]); - - console.log('count', nodeCount); + const nodeCount = useMemo(() => nodes?.length ?? 0, [nodes]); + const relCount = useMemo(() => rels?.length ?? 0, [rels]); return (
diff --git a/frontend/src/components/Popups/GraphEnhancementDialog/EnitityExtraction/PredefinedSchemaDialog.tsx b/frontend/src/components/Popups/GraphEnhancementDialog/EnitityExtraction/PredefinedSchemaDialog.tsx index 07e63ef19..822ea5b87 100644 --- a/frontend/src/components/Popups/GraphEnhancementDialog/EnitityExtraction/PredefinedSchemaDialog.tsx +++ b/frontend/src/components/Popups/GraphEnhancementDialog/EnitityExtraction/PredefinedSchemaDialog.tsx @@ -2,7 +2,7 @@ import { Dialog, Button, Select } from '@neo4j-ndl/react'; import { useState, useMemo } from 'react'; import PatternContainer from '../../GraphEnhancementDialog/EnitityExtraction/PatternContainer'; import { OptionType, TupleType } from '../../../../types'; -import { extractOptions, getSelectedTriplets } from '../../../../utils/Utils'; +import { extractOptions, getSelectedTriplets, updateSourceTargetTypeOptions } from '../../../../utils/Utils'; import SchemaViz from '../../../../components/Graph/SchemaViz'; import { getDefaultSchemaExamples, appLabels } from '../../../../utils/Constants'; import { useFileContext } from '../../../../context/UsersFiles'; @@ -10,7 +10,14 @@ import { useFileContext } from '../../../../context/UsersFiles'; interface SchemaFromTextProps { open: boolean; onClose: () => void; - onApply: (patterns: string[], nodes: OptionType[], rels: OptionType[], view: string) => void; + onApply: ( + patterns: string[], + nodes: OptionType[], + rels: OptionType[], + updatedSource: OptionType[], + updatedTarget: OptionType[], + updatedType: OptionType[] + ) => void; } const PredefinedSchemaDialog = ({ open, onClose, onApply }: SchemaFromTextProps) => { @@ -23,7 +30,13 @@ const PredefinedSchemaDialog = ({ open, onClose, onApply }: SchemaFromTextProps) preDefinedRels, setPreDefinedRels, setSelectedPreDefOption, - selectedPreDefOption + selectedPreDefOption, + sourceOptions, + setSourceOptions, + targetOptions, + setTargetOptions, + typeOptions, + setTypeOptions, } = useFileContext(); const [openGraphView, setOpenGraphView] = useState(false); @@ -67,10 +80,18 @@ const PredefinedSchemaDialog = ({ open, onClose, onApply }: SchemaFromTextProps) setViewPoint('showSchemaView'); }; - const handlePreDefinedSchemaApply = () => { - onApply(preDefinedPattern, preDefinedNodes, preDefinedRels, 'preDefined'); + const handlePreDefinedSchemaApply = async () => { + const [newSourceOptions, newTargetOptions, newTypeOptions] = await updateSourceTargetTypeOptions({ + patterns: preDefinedPattern.map((label) => ({ label, value: label })), + currentSourceOptions: sourceOptions, + currentTargetOptions: targetOptions, + currentTypeOptions: typeOptions, + setSourceOptions, + setTargetOptions, + setTypeOptions, + }); + onApply(preDefinedPattern, preDefinedNodes, preDefinedRels, newSourceOptions, newTargetOptions, newTypeOptions); onClose(); - setSelectedPreDefOption(null); }; const handleCancel = () => { diff --git a/frontend/src/components/Popups/GraphEnhancementDialog/EnitityExtraction/SchemaFromTextDialog.tsx b/frontend/src/components/Popups/GraphEnhancementDialog/EnitityExtraction/SchemaFromTextDialog.tsx index bffeac4b6..a8b8e60ac 100644 --- a/frontend/src/components/Popups/GraphEnhancementDialog/EnitityExtraction/SchemaFromTextDialog.tsx +++ b/frontend/src/components/Popups/GraphEnhancementDialog/EnitityExtraction/SchemaFromTextDialog.tsx @@ -8,12 +8,19 @@ import { showNormalToast } from '../../../../utils/Toasts'; import PatternContainer from './PatternContainer'; import { OptionType, TupleType } from '../../../../types'; import SchemaViz from '../../../Graph/SchemaViz'; -import { extractOptions } from '../../../../utils/Utils'; +import { extractOptions, updateSourceTargetTypeOptions } from '../../../../utils/Utils'; interface SchemaFromTextProps { open: boolean; onClose: () => void; - onApply: (patterns: string[], nodes: OptionType[], rels: OptionType[], view: string) => void; + onApply: ( + patterns: string[], + nodes: OptionType[], + rels: OptionType[], + updatedSource: OptionType[], + updatedTarget: OptionType[], + updatedType: OptionType[] + ) => void; } const SchemaFromTextDialog = ({ open, onClose, onApply }: SchemaFromTextProps) => { @@ -28,6 +35,12 @@ const SchemaFromTextDialog = ({ open, onClose, onApply }: SchemaFromTextProps) = setSchemaValRels, schemaTextPattern, setSchemaTextPattern, + sourceOptions, + setSourceOptions, + targetOptions, + setTargetOptions, + typeOptions, + setTypeOptions, } = useFileContext(); const [openGraphView, setOpenGraphView] = useState(false); const [viewPoint, setViewPoint] = useState(''); @@ -61,9 +74,8 @@ const SchemaFromTextDialog = ({ open, onClose, onApply }: SchemaFromTextProps) = setSchemaValRels(relationshipTypeOptions); setSchemaTextPattern(schemaTuples.map((t) => t.label)); } else if (status === 'Failed') { - showNormalToast(message as string); - } - else{ + showNormalToast(message as string); + } else { showNormalToast('Please provide meaningful text.'); } } catch (error: any) { @@ -110,9 +122,18 @@ const SchemaFromTextDialog = ({ open, onClose, onApply }: SchemaFromTextProps) = setViewPoint('showSchemaView'); }; - const handleSchemaTextApply = () => { + const handleSchemaTextApply = async () => { if (onApply) { - onApply(schemaTextPattern, schemaValNodes, schemaValRels, 'text'); + const [newSourceOptions, newTargetOptions, newTypeOptions] = await updateSourceTargetTypeOptions({ + patterns: schemaTextPattern.map((label) => ({ label, value: label })), + currentSourceOptions: sourceOptions, + currentTargetOptions: targetOptions, + currentTypeOptions: typeOptions, + setSourceOptions, + setTargetOptions, + setTypeOptions, + }); + onApply(schemaTextPattern, schemaValNodes, schemaValRels, newSourceOptions, newTargetOptions, newTypeOptions); } onClose(); }; @@ -153,9 +174,9 @@ const SchemaFromTextDialog = ({ open, onClose, onApply }: SchemaFromTextProps) = onChange: (e) => { const textVal = e.target.value; setUserText(textVal); - setSchemaTextPattern([]); - setSchemaValNodes([]); - setSchemaValRels([]); + setSchemaTextPattern([]); + setSchemaValNodes([]); + setSchemaValRels([]); }, }} size='large' diff --git a/frontend/src/components/Popups/GraphEnhancementDialog/PostProcessingCheckList/SelectedJobList.tsx b/frontend/src/components/Popups/GraphEnhancementDialog/PostProcessingCheckList/SelectedJobList.tsx index 12905e1b2..381cf7f00 100644 --- a/frontend/src/components/Popups/GraphEnhancementDialog/PostProcessingCheckList/SelectedJobList.tsx +++ b/frontend/src/components/Popups/GraphEnhancementDialog/PostProcessingCheckList/SelectedJobList.tsx @@ -18,8 +18,8 @@ export default function SelectedJobList({ ? postProcessingTasks.filter((task) => task !== 'graph_checkup') : postProcessingTasks : isSchema - ? postProcessingTasks.filter((task) => task !== 'graph_checkup' && task !== 'enable_communities') - : postProcessingTasks.filter((task) => task !== 'enable_communities')), + ? postProcessingTasks.filter((task) => task !== 'graph_checkup' && task !== 'enable_communities') + : postProcessingTasks.filter((task) => task !== 'enable_communities')), [isGdsActive, isSchema, postProcessingTasks] ); return ( diff --git a/frontend/src/components/Popups/GraphEnhancementDialog/PostProcessingCheckList/index.tsx b/frontend/src/components/Popups/GraphEnhancementDialog/PostProcessingCheckList/index.tsx index d9e7175c8..3a48f1abc 100644 --- a/frontend/src/components/Popups/GraphEnhancementDialog/PostProcessingCheckList/index.tsx +++ b/frontend/src/components/Popups/GraphEnhancementDialog/PostProcessingCheckList/index.tsx @@ -28,14 +28,14 @@ export default function PostProcessingCheckList() { job.title === 'enable_communities' ? !isGdsActive : job.title === 'graph_schema_consolidation' - ? isGraphCleanupDisabled - : false; + ? isGraphCleanupDisabled + : false; const isChecked = job.title === 'graph_schema_consolidation' ? !isGraphCleanupDisabled && postProcessingTasks.includes(job.title) : job.title === 'enable_communities' - ? isGdsActive && postProcessingTasks.includes(job.title) - : postProcessingTasks.includes(job.title); + ? isGdsActive && postProcessingTasks.includes(job.title) + : postProcessingTasks.includes(job.title); return ( void }) { +export default function GraphEnhancementDialog({ + open, + onClose, + combinedPatterns, + setCombinedPatterns, + combinedNodes, + setCombinedNodes, + combinedRels, + setCombinedRels, +}: { + open: boolean; + onClose: () => void; + combinedPatterns: string[]; + setCombinedPatterns: Dispatch>; + combinedNodes: OptionType[]; + setCombinedNodes: Dispatch>; + combinedRels: OptionType[]; + setCombinedRels: Dispatch>; +}) { const { breakpoints } = tokens; const [orphanDeleteAPIloading, setorphanDeleteAPIloading] = useState(false); const { setShowTextFromSchemaDialog, setSchemaLoadDialog, setPredefinedSchemaDialog, - setSelectedNodes, - setAllPatterns, - setSelectedRels, setUserDefinedPattern, setUserDefinedNodes, setUserDefinedRels, @@ -35,9 +49,9 @@ export default function GraphEnhancementDialog({ open, onClose }: { open: boolea setPreDefinedRels, setPreDefinedPattern, setSelectedPreDefOption, + allPatterns, } = useFileContext(); const isTablet = useMediaQuery(`(min-width:${breakpoints.xs}) and (max-width: ${breakpoints.lg})`); - const { userCredentials } = useCredentials(); const orphanNodesDeleteHandler = async (selectedEntities: string[]) => { try { @@ -51,10 +65,10 @@ export default function GraphEnhancementDialog({ open, onClose }: { open: boolea }; const handleOnclose = () => { - // overall - setSelectedNodes([]); - setSelectedRels([]); - setAllPatterns([]); + if (allPatterns.length > 0) { + onClose(); + return; + } // User setUserDefinedPattern([]); setUserDefinedNodes([]); @@ -71,11 +85,8 @@ export default function GraphEnhancementDialog({ open, onClose }: { open: boolea setPreDefinedNodes([]); setPreDefinedRels([]); setPreDefinedPattern([]); + setCombinedPatterns([]); setSelectedPreDefOption(null); - - updateLocalStorage(userCredentials!, 'selectedNodeLabels', []); - updateLocalStorage(userCredentials!, 'selectedRelationshipLabels', []); - updateLocalStorage(userCredentials!, 'selectedPattern', []); onClose(); }; @@ -103,6 +114,7 @@ export default function GraphEnhancementDialog({ open, onClose }: { open: boolea objectFit: 'contain', }} loading='lazy' + alt='graph-enhancement-options-logo' />
Graph Enhancements @@ -175,6 +187,12 @@ export default function GraphEnhancementDialog({ open, onClose }: { open: boolea }} closeEnhanceGraphSchemaDialog={onClose} settingView='headerView' + combinedPatterns={combinedPatterns} + setCombinedPatterns={setCombinedPatterns} + combinedNodes={combinedNodes} + setCombinedNodes={setCombinedNodes} + combinedRels={combinedRels} + setCombinedRels={setCombinedRels} />
diff --git a/frontend/src/components/Popups/LargeFilePopUp/LargeFilesAlert.tsx b/frontend/src/components/Popups/LargeFilePopUp/LargeFilesAlert.tsx index ca438b07b..cccbad28b 100644 --- a/frontend/src/components/Popups/LargeFilePopUp/LargeFilesAlert.tsx +++ b/frontend/src/components/Popups/LargeFilePopUp/LargeFilesAlert.tsx @@ -65,7 +65,7 @@ const LargeFilesAlert: FC = ({ Files, handleToggle, checked }) {imageIcon[f.fileSource] ? ( - + source-logo ) : ( )} @@ -78,8 +78,8 @@ const LargeFilesAlert: FC = ({ Files, handleToggle, checked }) {f.fileSource === 'local file' && minutes === 0 && typeof f.size === 'number' ? `- ${seconds} Sec ` : f.fileSource === 'local file' - ? `- ${minutes} Min` - : ''} + ? `- ${minutes} Min` + : ''} {typeof f.size === 'number' && f.size > chunkSize ? ( diff --git a/frontend/src/components/Popups/Settings/SchemaFromText.tsx b/frontend/src/components/Popups/Settings/SchemaFromText.tsx deleted file mode 100644 index c2d75093e..000000000 --- a/frontend/src/components/Popups/Settings/SchemaFromText.tsx +++ /dev/null @@ -1,253 +0,0 @@ - -// import { Checkbox, Dialog, TextArea, Button } from '@neo4j-ndl/react'; -// import { useCallback, useState } from 'react'; -// import { getNodeLabelsAndRelTypesFromText } from '../../../services/SchemaFromTextAPI'; -// import { useCredentials } from '../../../context/UserCredentials'; -// import { useFileContext } from '../../../context/UsersFiles'; -// import { buttonCaptions } from '../../../utils/Constants'; -// import ButtonWithToolTip from '../../UI/ButtonWithToolTip'; -// import { showNormalToast, showSuccessToast } from '../../../utils/Toasts'; -// import PatternContainer from '../GraphEnhancementDialog/EnitityExtraction/PatternContainer'; -// import { OptionType, TupleType } from '../../../types'; -// import { updateLocalStorage, extractOptions } from '../../../utils/Utils'; -// import SchemaViz from '../../../components/Graph/SchemaViz'; - -// interface SchemaFromTextProps { -// open: boolean, -// onClose: () => void, -// onApply: (patterns: string[], nodes:OptionType[], rels:OptionType[], view:string) => void -// } - -// const SchemaFromTextDialog = ({ open, onClose, onApply }: SchemaFromTextProps) => { -// const [userText, setUserText] = useState(''); -// const [loading, setLoading] = useState(false); -// const { userCredentials } = useCredentials(); -// const [isSchemaText, setIsSchemaText] = useState(false); -// const { model } = useFileContext(); -// const [textNodeSchema, setTextNodeSchema] = useState([]); -// const [textRelationshipSchema, setTextRelationshipSchema] = useState([]); -// const [schemaPattern, setSchemaPattern] = useState([]); -// const [openGraphView, setOpenGraphView] = useState(false); -// const [viewPoint, setViewPoint] = useState(''); - -// // const clickHandler = useCallback(async () => { -// // try { -// // setIsLoading(true); -// // const response = await getNodeLabelsAndRelTypesFromText(model, userText, isSchemaText, false); -// // setIsLoading(false); -// // if (response.data.status === 'Success') { -// // console.log('hello', response.data.data); -// // if (response.data?.data) { -// // const nodelabels = response.data?.data?.labels?.map((l) => ({ value: l, label: l })); -// // setSelectedNodes((prev) => { -// // const combinedData = [...prev, ...nodelabels]; -// // const uniqueLabels = new Set(); -// // const updatedOptions = combinedData.filter((item) => { -// // if (!uniqueLabels.has(item.label)) { -// // uniqueLabels.add(item.label); -// // return true; -// // } -// // return false; -// // }); -// // localStorage.setItem( -// // 'selectedNodeLabels', -// // JSON.stringify({ db: userCredentials?.uri, selectedOptions: updatedOptions }) -// // ); -// // return updatedOptions; -// // }); -// // } -// // if (response.data?.data?.relationshipTypes.length) { -// // const reltypes = response.data?.data?.relationshipTypes.map((t) => ({ value: t, label: t })); -// // setSelectedRels((prev) => { -// // const combinedData = [...prev, ...reltypes]; -// // const uniqueLabels = new Set(); -// // const updatedOptions = combinedData.filter((item) => { -// // if (!uniqueLabels.has(item.label)) { -// // uniqueLabels.add(item.label); -// // return true; -// // } -// // return false; -// // }); -// // localStorage.setItem( -// // 'selectedRelationshipLabels', -// // JSON.stringify({ db: userCredentials?.uri, selectedOptions: updatedOptions }) -// // ); -// // return updatedOptions; -// // }); -// // } -// // if (response.data?.data?.relationshipTypes.length && response.data?.data?.labels.length) { -// // showSuccessToast( -// // `Successfully Created ${response.data?.data?.labels.length} Node labels and ${response.data?.data?.relationshipTypes.length} Relationship labels` -// // ); -// // } else if (response.data?.data?.relationshipTypes.length && !response.data?.data?.labels.length) { -// // showSuccessToast(`Successfully Created ${response.data?.data?.relationshipTypes.length} Relationship labels`); -// // } else if (!response.data?.data?.relationshipTypes.length && response.data?.data?.labels.length) { -// // showSuccessToast(`Successfully Created ${response.data?.data?.labels.length} Node labels`); -// // } else { -// // showNormalToast(`Please give meaningful text`); -// // } -// // } else { -// // throw new Error('Unable to create labels from '); -// // } -// // onClose(); -// // setUserText(''); -// // setIsSchemaText(false); -// // } catch (error) { -// // setIsLoading(false); -// // console.log(error); -// // } -// // }, [userCredentials, userText, isSchemaText]); - -// const clickHandler = useCallback(async () => { -// setLoading(true); -// try { -// const response = await getNodeLabelsAndRelTypesFromText(model, userText, isSchemaText, false); -// setLoading(false); -// if (response.data.status === 'Success' && response.data?.data?.triplets?.length) { -// const schemaData: string[] = response.data.data.triplets; -// const schemaTuples: TupleType[] = schemaData.map((item: string) => { -// const matchResult = item.match(/^(.+?)-([A-Z_]+)->(.+)$/); -// if (matchResult) { -// const [source, rel, target] = matchResult.slice(1).map((s) => s.trim()); -// return { -// value: `${source},${rel},${target}`, -// label: `${source} -[:${rel}]-> ${target}`, -// source, -// target, -// type: rel, -// }; -// } -// return null; -// }) -// .filter(Boolean) as TupleType[]; -// const { nodeLabelOptions, relationshipTypeOptions } = extractOptions(schemaTuples); -// setTextNodeSchema(nodeLabelOptions); -// setTextRelationshipSchema(relationshipTypeOptions); -// setSchemaPattern(schemaTuples.map(t => t.label)); -// if (nodeLabelOptions.length && relationshipTypeOptions.length) { -// showSuccessToast( -// `Successfully Created ${nodeLabelOptions.length} Node labels and ${relationshipTypeOptions.length} Relationship labels` -// ); -// } else { -// showNormalToast(`Please provide meaningful text.`); -// } -// } else { -// showNormalToast(`Please provide meaningful text.`); -// } -// } catch (error) { -// setLoading(false); -// console.error('Error processing schema:', error); -// } -// }, [userCredentials, userText, isSchemaText]); - -// const handleRemovePattern = (pattern: string) => { -// setSchemaPattern((prevPatterns) => prevPatterns.filter((p) => p !== pattern)); -// }; - -// const handleSchemaView = () => { -// setOpenGraphView(true); -// setViewPoint('showSchemaView'); -// }; - -// const handleSchemaTextApply = () => { -// if (onApply) { -// onApply(schemaPattern, textNodeSchema, textRelationshipSchema, 'text'); -// } -// onClose(); -// }; - -// const handleCancel = () => { -// setTextNodeSchema([]); -// setTextRelationshipSchema([]); -// setSchemaPattern([]); -// setUserText(''); -// setIsSchemaText(false); -// onClose(); -// }; - -// return ( -// <> -// { -// setLoading(false); -// setIsSchemaText(false); -// setUserText(''); -// onClose(); -// }} -// htmlAttributes={{ -// 'aria-labelledby': 'form-dialog-title', -// }} -// > -// Entity Graph Extraction Settings -// -//