diff --git a/frontend/src/components/Layout/PageLayout.tsx b/frontend/src/components/Layout/PageLayout.tsx
index 5d544f4f8..a92a64c01 100644
--- a/frontend/src/components/Layout/PageLayout.tsx
+++ b/frontend/src/components/Layout/PageLayout.tsx
@@ -23,6 +23,7 @@ import PredefinedSchemaDialog from '../Popups/GraphEnhancementDialog/EnitityExtr
import { SKIP_AUTH } from '../../utils/Constants';
import { useNavigate } from 'react-router';
import { deduplicateByFullPattern, deduplicateNodeByValue } from '../../utils/Utils';
+import DataImporterSchemaDailog from '../Popups/GraphEnhancementDialog/EnitityExtraction/DataImporter';
const GCSModal = lazy(() => import('../DataSources/GCS/GCSModal'));
const S3Modal = lazy(() => import('../DataSources/AWS/S3Modal'));
@@ -193,6 +194,11 @@ const PageLayout: React.FC = () => {
allPatterns,
selectedNodes,
selectedRels,
+ dataImporterSchemaDialog,
+ setDataImporterSchemaDialog,
+ setImporterPattern,
+ setImporterNodes,
+ setImporterRels,
} = useFileContext();
const navigate = useNavigate();
const { user, isAuthenticated } = useAuth0();
@@ -465,6 +471,45 @@ const PageLayout: React.FC = () => {
[]
);
+ const handleImporterApply = useCallback(
+ (
+ newPatterns: string[],
+ nodes: OptionType[],
+ rels: OptionType[],
+ updatedSource: OptionType[],
+ updatedTarget: OptionType[],
+ updatedType: OptionType[]
+ ) => {
+ setImporterPattern((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;
+ });
+ setDataImporterSchemaDialog({
+ triggeredFrom: 'importerSchemaApply',
+ show: true,
+ });
+ setSchemaView('importer');
+ setImporterNodes(nodes);
+ setCombinedNodesVal((prevNodes: OptionType[]) => {
+ const combined = [...nodes, ...prevNodes];
+ return deduplicateNodeByValue(combined);
+ });
+ setImporterRels(rels);
+ setCombinedRelsVal((prevRels: OptionType[]) => {
+ const combined = [...rels, ...prevRels];
+ return deduplicateByFullPattern(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 });
}, []);
@@ -477,6 +522,10 @@ const PageLayout: React.FC = () => {
setShowTextFromSchemaDialog({ triggeredFrom: 'schemadialog', show: true });
}, []);
+ const openDataImporterSchema = useCallback(() => {
+ setDataImporterSchemaDialog({ triggeredFrom: 'schemadialog', show: true });
+ }, []);
+
const openChatBot = useCallback(() => setShowChatBot(true), []);
return (
@@ -565,6 +614,20 @@ const PageLayout: React.FC = () => {
}}
onApply={handlePredinedApply}
>
+ {
+ setDataImporterSchemaDialog({ triggeredFrom: '', show: false });
+ switch (dataImporterSchemaDialog.triggeredFrom) {
+ case 'enhancementtab':
+ toggleEnhancementDialog();
+ break;
+ default:
+ break;
+ }
+ }}
+ onApply={handleImporterApply}
+ >
{isLargeDesktop ? (
{
openTextSchema={openTextSchema}
openLoadSchema={openLoadSchema}
openPredefinedSchema={openPredefinedSchema}
+ openDataImporterSchema={openDataImporterSchema}
showEnhancementDialog={showEnhancementDialog}
toggleEnhancementDialog={toggleEnhancementDialog}
setOpenConnection={setOpenConnection}
@@ -670,6 +734,7 @@ const PageLayout: React.FC = () => {
openTextSchema={openTextSchema}
openLoadSchema={openLoadSchema}
openPredefinedSchema={openPredefinedSchema}
+ openDataImporterSchema={openDataImporterSchema}
showEnhancementDialog={showEnhancementDialog}
toggleEnhancementDialog={toggleEnhancementDialog}
setOpenConnection={setOpenConnection}
diff --git a/frontend/src/components/Popups/GraphEnhancementDialog/EnitityExtraction/DataImporter.tsx b/frontend/src/components/Popups/GraphEnhancementDialog/EnitityExtraction/DataImporter.tsx
new file mode 100644
index 000000000..bedfd181c
--- /dev/null
+++ b/frontend/src/components/Popups/GraphEnhancementDialog/EnitityExtraction/DataImporter.tsx
@@ -0,0 +1,179 @@
+import { Button, Dialog } from '@neo4j-ndl/react';
+import { useState } from 'react';
+import { OptionType, TupleType } from '../../../../types';
+import { extractOptions, updateSourceTargetTypeOptions } from '../../../../utils/Utils';
+import { useFileContext } from '../../../../context/UsersFiles';
+import ImporterInput from './ImporterInput';
+import SchemaViz from '../../../Graph/SchemaViz';
+import PatternContainer from './PatternContainer';
+import UploadJsonData from './UploadJsonData';
+
+interface DataImporterDialogProps {
+ open: boolean;
+ onClose: () => void;
+ onApply: (
+ patterns: string[],
+ nodeLabels: OptionType[],
+ relationshipLabels: OptionType[],
+ updatedSource: OptionType[],
+ updatedTarget: OptionType[],
+ updatedType: OptionType[]
+ ) => void;
+}
+
+const DataImporterSchemaDailog = ({ open, onClose, onApply }: DataImporterDialogProps) => {
+ const {
+ importerPattern,
+ setImporterPattern,
+ importerNodes,
+ setImporterNodes,
+ importerRels,
+ setImporterRels,
+ sourceOptions,
+ setSourceOptions,
+ targetOptions,
+ setTargetOptions,
+ typeOptions,
+ setTypeOptions,
+ } = useFileContext();
+
+ const [openGraphView, setOpenGraphView] = useState
(false);
+ const [viewPoint, setViewPoint] = useState('');
+ const handleCancel = () => {
+ onClose();
+ setImporterPattern([]);
+ setImporterNodes([]);
+ setImporterRels([]);
+ };
+
+ const handleImporterCheck = async () => {
+ const [newSourceOptions, newTargetOptions, newTypeOptions] = await updateSourceTargetTypeOptions({
+ patterns: importerPattern.map((label) => ({ label, value: label })),
+ currentSourceOptions: sourceOptions,
+ currentTargetOptions: targetOptions,
+ currentTypeOptions: typeOptions,
+ setSourceOptions,
+ setTargetOptions,
+ setTypeOptions,
+ });
+ onApply(importerPattern, importerNodes, importerRels, newSourceOptions, newTargetOptions, newTypeOptions);
+ onClose();
+ };
+
+ const handleRemovePattern = (patternToRemove: string) => {
+ const updatedPatterns = importerPattern.filter((p) => p !== patternToRemove);
+ if (updatedPatterns.length === 0) {
+ setImporterPattern([]);
+ setImporterNodes([]);
+ setImporterRels([]);
+ return;
+ }
+ const updatedTuples: TupleType[] = updatedPatterns
+ .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(updatedTuples);
+ setImporterPattern(updatedPatterns);
+ setImporterNodes(nodeLabelOptions);
+ setImporterRels(relationshipTypeOptions);
+ };
+
+ const handleSchemaView = () => {
+ setOpenGraphView(true);
+ setViewPoint('showSchemaView');
+ };
+
+ return (
+ <>
+
+ {openGraphView && (
+
+ )}
+ >
+ );
+};
+
+export default DataImporterSchemaDailog;
diff --git a/frontend/src/components/Popups/GraphEnhancementDialog/EnitityExtraction/ImporterInput.tsx b/frontend/src/components/Popups/GraphEnhancementDialog/EnitityExtraction/ImporterInput.tsx
new file mode 100644
index 000000000..69d4288c5
--- /dev/null
+++ b/frontend/src/components/Popups/GraphEnhancementDialog/EnitityExtraction/ImporterInput.tsx
@@ -0,0 +1,67 @@
+import { useState, useCallback, KeyboardEvent, ChangeEvent, FocusEvent } from 'react';
+import { Box, TextInput, Button } from '@neo4j-ndl/react';
+import { importerValidation } from '../../../../utils/Utils';
+
+const ImporterInput = () => {
+ const [value, setValue] = useState('');
+ const [isValid, setIsValid] = useState(false);
+ const [isFocused, setIsFocused] = useState(false);
+ const handleChange = (e: ChangeEvent) => {
+ const newValue = e.target.value;
+ setValue(newValue);
+ setIsValid(importerValidation(newValue));
+ };
+ const handleBlur = () => {
+ setIsFocused(false);
+ setIsValid(importerValidation(value));
+ };
+ const handleFocus = () => {
+ setIsFocused(true);
+ };
+ const handleSubmit = useCallback(() => {
+ if (importerValidation(value)) {
+ window.open(value, '_blank');
+ }
+ }, [value]);
+ const handleCancel = () => {
+ setValue('');
+ setIsValid(false);
+ };
+ const handleKeyDown = (e: KeyboardEvent) => {
+ if (e.code === 'Enter' && isValid) {
+ handleSubmit();
+ }
+ };
+ const isEmpty = value.trim() === '';
+ return (
+
+
+
+
+
+
+
+
+
+ );
+};
+
+export default ImporterInput;
diff --git a/frontend/src/components/Popups/GraphEnhancementDialog/EnitityExtraction/NewEntityExtractionSetting.tsx b/frontend/src/components/Popups/GraphEnhancementDialog/EnitityExtraction/NewEntityExtractionSetting.tsx
index 727f27d4c..842818075 100644
--- a/frontend/src/components/Popups/GraphEnhancementDialog/EnitityExtraction/NewEntityExtractionSetting.tsx
+++ b/frontend/src/components/Popups/GraphEnhancementDialog/EnitityExtraction/NewEntityExtractionSetting.tsx
@@ -33,6 +33,7 @@ export default function NewEntityExtractionSetting({
setCombinedNodes,
combinedRels,
setCombinedRels,
+ openDataImporterSchema,
}: {
view: 'Dialog' | 'Tabs';
open?: boolean;
@@ -49,6 +50,7 @@ export default function NewEntityExtractionSetting({
setCombinedNodes: Dispatch>;
combinedRels: OptionType[];
setCombinedRels: Dispatch>;
+ openDataImporterSchema: () => void;
}) {
const {
setSelectedRels,
@@ -288,6 +290,16 @@ export default function NewEntityExtractionSetting({
openLoadSchema();
}, []);
+ const onDataImporterSchemaCLick: MouseEventHandler = useCallback(async () => {
+ if (view === 'Dialog' && onClose != undefined) {
+ onClose();
+ }
+ if (view === 'Tabs' && closeEnhanceGraphSchemaDialog != undefined) {
+ closeEnhanceGraphSchemaDialog();
+ }
+ openDataImporterSchema();
+ }, []);
+
return (
@@ -363,6 +375,14 @@ export default function NewEntityExtractionSetting({
}
onClick={onSchemaFromTextCLick}
/>
+
+ Data Importer JSON
+
+ }
+ onClick={onDataImporterSchemaCLick}
+ />
diff --git a/frontend/src/components/Popups/GraphEnhancementDialog/EnitityExtraction/UploadJsonData.tsx b/frontend/src/components/Popups/GraphEnhancementDialog/EnitityExtraction/UploadJsonData.tsx
new file mode 100644
index 000000000..535133244
--- /dev/null
+++ b/frontend/src/components/Popups/GraphEnhancementDialog/EnitityExtraction/UploadJsonData.tsx
@@ -0,0 +1,104 @@
+import { Dropzone, Flex, Typography } from '@neo4j-ndl/react';
+import { useState } from 'react';
+import { IconButtonWithToolTip } from '../../../UI/IconButtonToolTip';
+import { InformationCircleIconOutline } from '@neo4j-ndl/react/icons';
+import { showErrorToast } from '../../../../utils/Toasts';
+import { buttonCaptions } from '../../../../utils/Constants';
+import Loader from '../../../../utils/Loader';
+
+interface GraphSchema {
+ nodeLabels: any[];
+ relationshipTypes: any[];
+ relationshipObjectTypes: any[];
+ nodeObjectTypes: any[];
+}
+interface UploadJsonDataProps {
+ onSchemaExtracted: (schema: GraphSchema) => void;
+}
+const UploadJsonData = ({ onSchemaExtracted }: UploadJsonDataProps) => {
+ const [isLoading, setIsLoading] = useState(false);
+ const onDropHandler = async (files: Partial[]) => {
+ const file = files[0];
+ if (!file) {
+ return;
+ }
+ setIsLoading(true);
+ try {
+ const fileReader = new FileReader();
+ fileReader.onload = (event) => {
+ try {
+ const jsonText = event.target?.result as string;
+ const parsed = JSON.parse(jsonText);
+ const graphSchema = parsed?.dataModel?.graphSchemaRepresentation?.graphSchema;
+ if (
+ graphSchema &&
+ Array.isArray(graphSchema.nodeLabels) &&
+ Array.isArray(graphSchema.relationshipTypes) &&
+ Array.isArray(graphSchema.relationshipObjectTypes) &&
+ Array.isArray(graphSchema.nodeObjectTypes)
+ ) {
+ onSchemaExtracted({
+ nodeLabels: graphSchema.nodeLabels,
+ relationshipTypes: graphSchema.relationshipTypes,
+ relationshipObjectTypes: graphSchema.relationshipObjectTypes,
+ nodeObjectTypes: graphSchema.nodeObjectTypes,
+ });
+ } else {
+ showErrorToast('Invalid graphSchema format');
+ }
+ } catch (err) {
+ console.error(err);
+ showErrorToast('Failed to parse JSON file.');
+ } finally {
+ setIsLoading(false);
+ }
+ };
+ fileReader.readAsText(file as File);
+ } catch (err) {
+ console.error(err);
+ showErrorToast('Error reading file.');
+ setIsLoading(false);
+ }
+ };
+ return (
+ }
+ isTesting={true}
+ className='bg-none! dropzoneContainer'
+ supportedFilesDescription={
+
+
+ {buttonCaptions.importDropzoneSpan}
+
+
+
+ JSON (.json)
+
+
+ }
+ >
+
+
+
+
+
+ }
+ dropZoneOptions={{
+ accept: {
+ 'application/json': ['.json'],
+ },
+ onDrop: onDropHandler,
+ onDropRejected: (e) => {
+ if (e.length) {
+ showErrorToast('Failed To Upload, Unsupported file extension.');
+ }
+ },
+ }}
+ />
+ );
+};
+export default UploadJsonData;
diff --git a/frontend/src/components/Popups/GraphEnhancementDialog/index.tsx b/frontend/src/components/Popups/GraphEnhancementDialog/index.tsx
index 46114f765..323b66b0d 100644
--- a/frontend/src/components/Popups/GraphEnhancementDialog/index.tsx
+++ b/frontend/src/components/Popups/GraphEnhancementDialog/index.tsx
@@ -50,6 +50,7 @@ export default function GraphEnhancementDialog({
setPreDefinedPattern,
setSelectedPreDefOption,
allPatterns,
+ setDataImporterSchemaDialog,
} = useFileContext();
const isTablet = useMediaQuery(`(min-width:${breakpoints.xs}) and (max-width: ${breakpoints.lg})`);
@@ -193,6 +194,9 @@ export default function GraphEnhancementDialog({
setCombinedNodes={setCombinedNodes}
combinedRels={combinedRels}
setCombinedRels={setCombinedRels}
+ openDataImporterSchema={() => {
+ setDataImporterSchemaDialog({ triggeredFrom: 'enhancementtab', show: true });
+ }}
/>
diff --git a/frontend/src/context/UsersFiles.tsx b/frontend/src/context/UsersFiles.tsx
index 8b082e3d3..ab05093b5 100644
--- a/frontend/src/context/UsersFiles.tsx
+++ b/frontend/src/context/UsersFiles.tsx
@@ -7,6 +7,7 @@ import {
showTextFromSchemaDialogType,
schemaLoadDialogType,
predefinedSchemaDialogType,
+ dataImporterSchemaDialogType,
} from '../types';
import {
chatModeLables,
@@ -69,6 +70,11 @@ const FileContextProvider: FC = ({ children }) => {
show: false,
});
+ const [dataImporterSchemaDialog, setDataImporterSchemaDialog] = useState({
+ triggeredFrom: '',
+ show: false,
+ });
+
const [postProcessingTasks, setPostProcessingTasks] = useState([
'materialize_text_chunk_similarities',
'enable_hybrid_search_and_fulltext_search_in_bloom',
@@ -97,6 +103,11 @@ const FileContextProvider: FC = ({ children }) => {
const [typeOptions, setTypeOptions] = useState(initialTypeOptions);
const [targetOptions, setTargetOptions] = useState(initialTargetOptions);
+ // Importer schema
+ const [importerNodes, setImporterNodes] = useState([]);
+ const [importerRels, setImporterRels] = useState([]);
+ const [importerPattern, setImporterPattern] = useState([]);
+
useEffect(() => {
if (selectedNodeLabelstr != null) {
const selectedNodeLabel = JSON.parse(selectedNodeLabelstr);
@@ -213,6 +224,14 @@ const FileContextProvider: FC = ({ children }) => {
setTypeOptions,
targetOptions,
setTargetOptions,
+ dataImporterSchemaDialog,
+ setDataImporterSchemaDialog,
+ importerNodes,
+ setImporterNodes,
+ importerRels,
+ setImporterRels,
+ importerPattern,
+ setImporterPattern,
};
return {children};
};
diff --git a/frontend/src/types.ts b/frontend/src/types.ts
index d544ed46c..36b476957 100644
--- a/frontend/src/types.ts
+++ b/frontend/src/types.ts
@@ -165,6 +165,7 @@ export interface ContentProps {
setCombinedNodes: Dispatch>;
combinedRels: OptionType[];
setCombinedRels: Dispatch>;
+ openDataImporterSchema: () => void;
}
export interface FileTableProps {
@@ -870,6 +871,12 @@ export interface predefinedSchemaDialogType {
onApply?: (selectedPattern: string[], nodes: OptionType[], rels: OptionType[]) => void;
}
+export interface dataImporterSchemaDialogType {
+ triggeredFrom: string;
+ show: boolean;
+ onApply?: (selectedPattern: string[], nodes: OptionType[], rels: OptionType[]) => void;
+}
+
export interface FileContextType {
files: (File | null)[] | [];
filesData: CustomFile[] | [];
@@ -956,6 +963,16 @@ export interface FileContextType {
setTypeOptions: Dispatch>;
targetOptions: OptionType[];
setTargetOptions: Dispatch>;
+
+ // importer defined schema
+ dataImporterSchemaDialog: dataImporterSchemaDialogType;
+ setDataImporterSchemaDialog: React.Dispatch>;
+ importerNodes: OptionType[];
+ setImporterNodes: Dispatch>;
+ importerRels: OptionType[];
+ setImporterRels: Dispatch>;
+ importerPattern: string[];
+ setImporterPattern: Dispatch>;
}
export declare type Side = 'top' | 'right' | 'bottom' | 'left';
diff --git a/frontend/src/utils/Constants.ts b/frontend/src/utils/Constants.ts
index 8a13a7b01..0b7c14d2b 100644
--- a/frontend/src/utils/Constants.ts
+++ b/frontend/src/utils/Constants.ts
@@ -188,6 +188,7 @@ export const tooltips = {
visualizeGraph: 'Visualize Graph Schema',
additionalInstructions: 'Analyze instructions for schema',
predinedSchema: 'Predefined Schema',
+ dataImporterJson: 'Data Importer JSON',
};
export const PRODMODLES = ['openai_gpt_4o', 'openai_gpt_4o_mini', 'diffbot', 'gemini_1.5_flash'];
export const buttonCaptions = {
@@ -215,6 +216,7 @@ export const buttonCaptions = {
provideAdditionalInstructions: 'Provide Additional Instructions for Entity Extractions',
analyzeInstructions: 'Analyze Instructions',
helpInstructions: 'Provide specific instructions for entity extraction, such as focusing on the key topics.',
+ importDropzoneSpan: 'JSON Documents',
};
export const POST_PROCESSING_JOBS: { title: string; description: string }[] = [
@@ -372,6 +374,7 @@ export const appLabels = {
chunkingConfiguration: 'Select a Chunking Configuration',
graphPatternTuple: 'Graph Pattern',
selectedPatterns: 'Selected Patterns',
+ dataImporterSchema: 'Schema from Data Importer',
};
export const LLMDropdownLabel = {
diff --git a/frontend/src/utils/Utils.ts b/frontend/src/utils/Utils.ts
index 0996932d4..393df4c36 100644
--- a/frontend/src/utils/Utils.ts
+++ b/frontend/src/utils/Utils.ts
@@ -892,3 +892,7 @@ export const deduplicateByFullPattern = (arrays: { value: string; label: string
});
return result;
};
+
+export const importerValidation = (url: string) => {
+ return url.trim() !== '' && /^https:\/\/console-preview\.neo4j\.io\/tools\/import\/models(\/.*)?$/i.test(url);
+};