├── docs ├── frontend │ └── frontend_docs.adoc ├── project architecture.png └── project_docs.adoc ├── POC_Experiments └── Data_Analysis ├── frontend ├── .dockerignore ├── .prettierignore ├── src │ ├── vite-env.d.ts │ ├── assets │ │ ├── images │ │ │ ├── gcs.webp │ │ │ ├── s3logo.png │ │ │ ├── chatbot-ai.png │ │ │ ├── chatbot-user.png │ │ │ ├── youtubeimg.jfif │ │ │ ├── internet_logo.png │ │ │ ├── Neo4jRetrievalLogo.png │ │ │ ├── bgImage.svg │ │ │ ├── dropzone.svg │ │ │ ├── db-search.svg │ │ │ ├── youtube.svg │ │ │ ├── youtube-lightmode.svg │ │ │ ├── youtube-darkmode.svg │ │ │ ├── web-search-svgrepo-com (2).svg │ │ │ ├── graph-search.svg │ │ │ ├── web-search-svgrepo-com.svg │ │ │ ├── web.svg │ │ │ ├── web-search-darkmode-final2.svg │ │ │ └── web-darkmode.svg │ │ ├── ChatbotMessages.json │ │ └── schemas.json │ ├── router.tsx │ ├── components │ │ ├── UI │ │ │ ├── CustomProgressBar.tsx │ │ │ ├── Legend.tsx │ │ │ ├── CustomButton.tsx │ │ │ ├── Alert.tsx │ │ │ ├── IconButtonToolTip.tsx │ │ │ ├── Menu.tsx │ │ │ ├── ButtonWithToolTip.tsx │ │ │ ├── ErrroBoundary.tsx │ │ │ └── HoverableLink.tsx │ │ ├── DataSources │ │ │ ├── GCS │ │ │ │ └── GCSButton.tsx │ │ │ └── AWS │ │ │ │ └── S3Bucket.tsx │ │ ├── Graph │ │ │ ├── LegendsChip.tsx │ │ │ ├── GraphViewButton.tsx │ │ │ └── CheckboxSelection.tsx │ │ ├── Layout │ │ │ ├── AlertIcon.tsx │ │ │ ├── DrawerChatbot.tsx │ │ │ └── Header.tsx │ │ ├── WebSources │ │ │ ├── Web │ │ │ │ └── WebInput.tsx │ │ │ ├── WikiPedia │ │ │ │ └── WikipediaInput.tsx │ │ │ ├── Youtube │ │ │ │ └── YoutubeInput.tsx │ │ │ ├── CustomSourceInput.tsx │ │ │ ├── GenericSourceButton.tsx │ │ │ └── GenericSourceModal.tsx │ │ ├── ChatBot │ │ │ ├── ChatModeToggle.tsx │ │ │ └── ExpandedChatButtonContainer.tsx │ │ ├── Popups │ │ │ ├── DeletePopUp │ │ │ │ └── DeletePopUp.tsx │ │ │ ├── LargeFilePopUp │ │ │ │ ├── ConfirmationDialog.tsx │ │ │ │ └── LargeFilesAlert.tsx │ │ │ └── GraphEnhancementDialog │ │ │ │ └── index.tsx │ │ ├── Dropdown.tsx │ │ └── QuickStarter.tsx │ ├── main.tsx │ ├── index.css │ ├── services │ │ ├── HealthStatus.ts │ │ ├── GetFiles.ts │ │ ├── SchemaFromTextAPI.ts │ │ ├── GetNodeLabelsRelTypes.ts │ │ ├── GetOrphanNodes.ts │ │ ├── ConnectAPI.ts │ │ ├── DeleteOrphanNodes.ts │ │ ├── ChunkEntitiesInfo.ts │ │ ├── CommonAPI.ts │ │ ├── PostProcessing.ts │ │ ├── ServerSideStatusUpdateAPI.ts │ │ ├── CancelAPI.ts │ │ ├── GraphQuery.ts │ │ ├── DeleteFiles.ts │ │ ├── PollingAPI.ts │ │ ├── QnaAPI.ts │ │ └── URLScan.ts │ ├── utils │ │ ├── Loader.tsx │ │ └── FileAPI.ts │ ├── HOC │ │ ├── SettingModalHOC.tsx │ │ └── CustomModal.tsx │ ├── styling │ │ └── info.css │ ├── App.tsx │ ├── context │ │ ├── UserCredentials.tsx │ │ ├── UserMessages.tsx │ │ ├── Alert.tsx │ │ ├── ThemeWrapper.tsx │ │ └── UsersFiles.tsx │ ├── hooks │ │ ├── useSpeech.tsx │ │ └── useSse.tsx │ ├── logo.svg │ └── logo-color.svg ├── postcss.config.js ├── public │ ├── favicons │ │ ├── favicon.ico │ │ ├── favicon-16x16.png │ │ ├── favicon-32x32.png │ │ └── favicon-194x194.png │ ├── paragraph-left-align.svg │ └── paginate-filter-text.svg ├── .lintstagedrc.json ├── tsconfig.node.json ├── .prettierrc.json ├── nginx │ └── nginx.conf ├── tailwind.config.js ├── example.env ├── vite.config.ts ├── index.html ├── tsconfig.json ├── README.md ├── Dockerfile ├── package.json └── .gitignore ├── data └── README.md ├── experiments ├── README.md ├── Combined chunk comparision.png ├── LLM Comparisons with one pdf.docx ├── Experimentations for Kg creation.docx └── LLM_Results_.csv ├── POC_Documents └── V1 │ ├── figure.2,3.jpg │ ├── figure.4.jpg │ └── Local-to-global-genAI_GraphRAG_V1 ├── backend ├── src │ ├── entities │ │ ├── user_credential.py │ │ └── source_node.py │ ├── document_sources │ │ ├── web_pages.py │ │ ├── wikipedia.py │ │ ├── youtube.py │ │ ├── s3_bucket.py │ │ └── local_file.py │ ├── logger.py │ ├── diffbot_transformer.py │ ├── openAI_llm.py │ ├── api_response.py │ ├── create_chunks.py │ ├── generate_graphDocuments_from_llm.py │ ├── shared │ │ └── schema_extraction.py │ ├── groq_llama3_llm.py │ ├── gemini_llm.py │ └── post_processing.py ├── Dockerfile ├── example.env ├── README.md └── requirements.txt ├── example.env ├── docker-compose.yml └── .gitignore /docs/frontend/frontend_docs.adoc: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /POC_Experiments/Data_Analysis: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /frontend/.dockerignore: -------------------------------------------------------------------------------- 1 | node_modules -------------------------------------------------------------------------------- /frontend/.prettierignore: -------------------------------------------------------------------------------- 1 | dist 2 | node_modules 3 | docs -------------------------------------------------------------------------------- /frontend/src/vite-env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | -------------------------------------------------------------------------------- /data/README.md: -------------------------------------------------------------------------------- 1 | # llm-graph-builder 2 | Neo4j graph construction from unstructured data 3 | -------------------------------------------------------------------------------- /experiments/README.md: -------------------------------------------------------------------------------- 1 | # llm-graph-builder 2 | Neo4j graph construction from unstructured data 3 | -------------------------------------------------------------------------------- /POC_Documents/V1/figure.2,3.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sidagarwal04/llm-graph-builder/main/POC_Documents/V1/figure.2,3.jpg -------------------------------------------------------------------------------- /POC_Documents/V1/figure.4.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sidagarwal04/llm-graph-builder/main/POC_Documents/V1/figure.4.jpg -------------------------------------------------------------------------------- /docs/project architecture.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sidagarwal04/llm-graph-builder/main/docs/project architecture.png -------------------------------------------------------------------------------- /frontend/postcss.config.js: -------------------------------------------------------------------------------- 1 | export default { 2 | plugins: { 3 | tailwindcss: {}, 4 | autoprefixer: {}, 5 | }, 6 | } 7 | -------------------------------------------------------------------------------- /frontend/public/favicons/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sidagarwal04/llm-graph-builder/main/frontend/public/favicons/favicon.ico -------------------------------------------------------------------------------- /frontend/src/assets/images/gcs.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sidagarwal04/llm-graph-builder/main/frontend/src/assets/images/gcs.webp -------------------------------------------------------------------------------- /backend/src/entities/user_credential.py: -------------------------------------------------------------------------------- 1 | class user_credential: 2 | uri:str 3 | user_name:str 4 | password:str 5 | database:str -------------------------------------------------------------------------------- /frontend/src/assets/images/s3logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sidagarwal04/llm-graph-builder/main/frontend/src/assets/images/s3logo.png -------------------------------------------------------------------------------- /frontend/src/assets/images/chatbot-ai.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sidagarwal04/llm-graph-builder/main/frontend/src/assets/images/chatbot-ai.png -------------------------------------------------------------------------------- /experiments/Combined chunk comparision.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sidagarwal04/llm-graph-builder/main/experiments/Combined chunk comparision.png -------------------------------------------------------------------------------- /frontend/public/favicons/favicon-16x16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sidagarwal04/llm-graph-builder/main/frontend/public/favicons/favicon-16x16.png -------------------------------------------------------------------------------- /frontend/public/favicons/favicon-32x32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sidagarwal04/llm-graph-builder/main/frontend/public/favicons/favicon-32x32.png -------------------------------------------------------------------------------- /frontend/src/assets/images/chatbot-user.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sidagarwal04/llm-graph-builder/main/frontend/src/assets/images/chatbot-user.png -------------------------------------------------------------------------------- /frontend/src/assets/images/youtubeimg.jfif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sidagarwal04/llm-graph-builder/main/frontend/src/assets/images/youtubeimg.jfif -------------------------------------------------------------------------------- /experiments/LLM Comparisons with one pdf.docx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sidagarwal04/llm-graph-builder/main/experiments/LLM Comparisons with one pdf.docx -------------------------------------------------------------------------------- /frontend/public/favicons/favicon-194x194.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sidagarwal04/llm-graph-builder/main/frontend/public/favicons/favicon-194x194.png -------------------------------------------------------------------------------- /frontend/src/assets/images/internet_logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sidagarwal04/llm-graph-builder/main/frontend/src/assets/images/internet_logo.png -------------------------------------------------------------------------------- /experiments/Experimentations for Kg creation.docx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sidagarwal04/llm-graph-builder/main/experiments/Experimentations for Kg creation.docx -------------------------------------------------------------------------------- /frontend/src/assets/images/Neo4jRetrievalLogo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sidagarwal04/llm-graph-builder/main/frontend/src/assets/images/Neo4jRetrievalLogo.png -------------------------------------------------------------------------------- /frontend/.lintstagedrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "*.ts": ["prettier --write", "eslint --fix"], 3 | "*.tsx": ["prettier --write", "eslint --fix"], 4 | "*.json": ["prettier --write"], 5 | "*.js": ["prettier --write"] 6 | } -------------------------------------------------------------------------------- /frontend/src/router.tsx: -------------------------------------------------------------------------------- 1 | import { createBrowserRouter } from 'react-router-dom'; 2 | import App from './App'; 3 | 4 | const router = createBrowserRouter([ 5 | { 6 | path: '/', 7 | element: , 8 | }, 9 | ]); 10 | export default router; 11 | -------------------------------------------------------------------------------- /frontend/src/assets/images/bgImage.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /frontend/tsconfig.node.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "composite": true, 4 | "skipLibCheck": true, 5 | "module": "ESNext", 6 | "moduleResolution": "bundler", 7 | "allowSyntheticDefaultImports": true 8 | }, 9 | "include": ["vite.config.ts"] 10 | } 11 | -------------------------------------------------------------------------------- /frontend/public/paragraph-left-align.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /frontend/.prettierrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "printWidth": 120, 3 | "semi": true, 4 | "singleQuote": true, 5 | "jsxSingleQuote": true, 6 | "useTabs": false, 7 | "tabWidth": 2, 8 | "arrowParens": "always", 9 | "trailingComma": "es5", 10 | "bracketSpacing": true, 11 | "endOfLine": "lf" 12 | } -------------------------------------------------------------------------------- /frontend/src/components/UI/CustomProgressBar.tsx: -------------------------------------------------------------------------------- 1 | import { ProgressBar } from '@neo4j-ndl/react'; 2 | 3 | export default function CustomProgressBar({ value }: { value: number }) { 4 | return ; 5 | } 6 | -------------------------------------------------------------------------------- /frontend/src/main.tsx: -------------------------------------------------------------------------------- 1 | import ReactDOM from 'react-dom/client'; 2 | import './index.css'; 3 | import { RouterProvider } from 'react-router-dom'; 4 | import router from './router.tsx'; 5 | 6 | ReactDOM.createRoot(document.getElementById('root')!).render(); 7 | -------------------------------------------------------------------------------- /frontend/src/index.css: -------------------------------------------------------------------------------- 1 | @tailwind base; 2 | @tailwind components; 3 | @tailwind utilities; 4 | 5 | body { 6 | margin: 0; 7 | } 8 | 9 | * { 10 | margin: 0; 11 | padding: 0; 12 | box-sizing: border-box; 13 | } 14 | 15 | 16 | 17 | .ndl-progress-bar-wrapper .ndl-header .ndl-heading::after{ 18 | animation: none !important; 19 | } 20 | -------------------------------------------------------------------------------- /frontend/nginx/nginx.conf: -------------------------------------------------------------------------------- 1 | server { 2 | 3 | listen 8080; 4 | 5 | location / { 6 | root /usr/share/nginx/html; 7 | index index.html index.htm; 8 | try_files $uri $uri/ /index.html; 9 | } 10 | 11 | error_page 401 403 404 index.html; 12 | 13 | location /public { 14 | root /usr/local/var/www; 15 | } 16 | } -------------------------------------------------------------------------------- /frontend/src/assets/images/dropzone.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /frontend/src/components/UI/Legend.tsx: -------------------------------------------------------------------------------- 1 | export default function Legend({ 2 | bgColor, 3 | title, 4 | chunkCount, 5 | }: { 6 | bgColor: string; 7 | title: string; 8 | chunkCount?: number; 9 | }) { 10 | return ( 11 |
12 | {title} 13 | {chunkCount && `(${chunkCount})`} 14 |
15 | ); 16 | } 17 | -------------------------------------------------------------------------------- /frontend/tailwind.config.js: -------------------------------------------------------------------------------- 1 | /** @type {import('tailwindcss').Config} */ 2 | export default { 3 | content: [ 4 | "./index.html", 5 | "./src/**/*.{js,ts,jsx,tsx}", 6 | ], 7 | theme: { 8 | extend: {}, 9 | }, 10 | plugins: [], 11 | presets:[require('@neo4j-ndl/base').tailwindConfig], 12 | corePlugins: { 13 | preflight: false, 14 | }, 15 | prefix:"" 16 | } 17 | 18 | -------------------------------------------------------------------------------- /frontend/public/paginate-filter-text.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /frontend/src/services/HealthStatus.ts: -------------------------------------------------------------------------------- 1 | import axios from 'axios'; 2 | import { url } from '../utils/Utils'; 3 | const healthStatus = async () => { 4 | try { 5 | const healthUrl = `${url()}/health`; 6 | const response = await axios.get(healthUrl); 7 | return response; 8 | } catch (error) { 9 | console.log('API status error', error); 10 | throw error; 11 | } 12 | }; 13 | export { healthStatus }; 14 | -------------------------------------------------------------------------------- /frontend/src/utils/Loader.tsx: -------------------------------------------------------------------------------- 1 | export default function Loader({ title }: { title: string }) { 2 | return ( 3 |
4 |
5 |
6 |
7 |
{title}
8 |
9 | ); 10 | } 11 | -------------------------------------------------------------------------------- /frontend/src/components/UI/CustomButton.tsx: -------------------------------------------------------------------------------- 1 | import { CommonButtonProps } from '../../types'; 2 | 3 | const CustomButton: React.FC = ({ openModal, wrapperclassName, logo, title, className }) => { 4 | return ( 5 |
6 | 7 |
{title}
8 |
9 | ); 10 | }; 11 | export default CustomButton; 12 | -------------------------------------------------------------------------------- /frontend/example.env: -------------------------------------------------------------------------------- 1 | BACKEND_API_URL="http://localhost:8000" 2 | BLOOM_URL="https://workspace-preview.neo4j.io/workspace/explore?connectURL={CONNECT_URL}&search=Show+me+a+graph&featureGenAISuggestions=true&featureGenAISuggestionsInternal=true" 3 | REACT_APP_SOURCES="local,youtube,wiki,s3,web" 4 | LLM_MODELS="diffbot,openai-gpt-3.5,openai-gpt-4o" 5 | ENV="DEV" 6 | TIME_PER_CHUNK=4 7 | TIME_PER_PAGE=50 8 | CHUNK_SIZE=5242880 9 | LARGE_FILE_SIZE=5242880 10 | GOOGLE_CLIENT_ID="" 11 | CHAT_MODES="" -------------------------------------------------------------------------------- /frontend/src/components/DataSources/GCS/GCSButton.tsx: -------------------------------------------------------------------------------- 1 | import gcslogo from '../../../assets/images/gcs.webp'; 2 | import { DataComponentProps } from '../../../types'; 3 | import { buttonCaptions } from '../../../utils/Constants'; 4 | import CustomButton from '../../UI/CustomButton'; 5 | const GCSButton: React.FC = ({ openModal }) => { 6 | return ( 7 | 8 | ); 9 | }; 10 | export default GCSButton; 11 | -------------------------------------------------------------------------------- /frontend/src/components/DataSources/AWS/S3Bucket.tsx: -------------------------------------------------------------------------------- 1 | import { DataComponentProps } from '../../../types'; 2 | import s3logo from '../../../assets/images/s3logo.png'; 3 | import CustomButton from '../../UI/CustomButton'; 4 | import { buttonCaptions } from '../../../utils/Constants'; 5 | 6 | const S3Component: React.FC = ({ openModal }) => { 7 | return ( 8 | 9 | ); 10 | }; 11 | 12 | export default S3Component; 13 | -------------------------------------------------------------------------------- /frontend/vite.config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig, loadEnv } from 'vite'; 2 | import react from '@vitejs/plugin-react'; 3 | 4 | // see https://stackoverflow.com/questions/73834404/react-uncaught-referenceerror-process-is-not-defined 5 | // otherwise use import.meta.env.VITE_BACKEND_API_URL and expose it as such with the VITE_ prefix 6 | export default defineConfig(({ mode }) => { 7 | const env = loadEnv(mode, process.cwd(), ''); 8 | return { 9 | define: { 10 | 'process.env': env, 11 | }, 12 | plugins: [react()], 13 | }; 14 | }); 15 | -------------------------------------------------------------------------------- /frontend/src/components/Graph/LegendsChip.tsx: -------------------------------------------------------------------------------- 1 | import { useMemo } from 'react'; 2 | import { LegendChipProps } from '../../types'; 3 | import Legend from '../UI/Legend'; 4 | 5 | export const LegendsChip: React.FunctionComponent = ({ scheme, title, nodes }) => { 6 | const chunkcount = useMemo( 7 | // @ts-ignore 8 | () => [...new Set(nodes?.filter((n) => n?.labels?.includes(title)).map((i) => i.id))].length, 9 | [] 10 | ); 11 | return ; 12 | }; 13 | -------------------------------------------------------------------------------- /frontend/src/components/Layout/AlertIcon.tsx: -------------------------------------------------------------------------------- 1 | export default function AlertIcon() { 2 | return ( 3 | 17 | ); 18 | } 19 | -------------------------------------------------------------------------------- /frontend/src/components/UI/Alert.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import Snackbar from '@mui/material/Snackbar'; 3 | import Alert from '@mui/material/Alert'; 4 | import { CustomAlertProps } from '../../types'; 5 | 6 | const CustomAlert: React.FC = ({ open, handleClose, alertMessage, severity = 'error' }) => { 7 | return ( 8 | 9 | 10 | {alertMessage} 11 | 12 | 13 | ); 14 | }; 15 | export default CustomAlert; 16 | -------------------------------------------------------------------------------- /frontend/src/services/GetFiles.ts: -------------------------------------------------------------------------------- 1 | import axios from 'axios'; 2 | import { url } from '../utils/Utils'; 3 | import { SourceListServerData, UserCredentials } from '../types'; 4 | 5 | export const getSourceNodes = async (userCredentials: UserCredentials) => { 6 | try { 7 | const encodedstr = btoa(userCredentials.password); 8 | const response = await axios.get( 9 | `${url()}/sources_list?uri=${userCredentials.uri}&database=${userCredentials.database}&userName=${ 10 | userCredentials.userName 11 | }&password=${encodedstr}` 12 | ); 13 | return response; 14 | } catch (error) { 15 | console.log(error); 16 | throw error; 17 | } 18 | }; 19 | -------------------------------------------------------------------------------- /frontend/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | Neo4j graph builder 11 | 12 | 13 |
14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /frontend/src/services/SchemaFromTextAPI.ts: -------------------------------------------------------------------------------- 1 | import axios from 'axios'; 2 | import { url } from '../utils/Utils'; 3 | import { ScehmaFromText } from '../types'; 4 | 5 | export const getNodeLabelsAndRelTypesFromText = async (model: string, inputText: string, isSchemaText: boolean) => { 6 | const formData = new FormData(); 7 | formData.append('model', model); 8 | formData.append('input_text', inputText); 9 | formData.append('is_schema_description_checked', JSON.stringify(isSchemaText)); 10 | 11 | try { 12 | const response = await axios.post(`${url()}/populate_graph_schema`, formData); 13 | return response; 14 | } catch (error) { 15 | console.log(error); 16 | throw error; 17 | } 18 | }; 19 | -------------------------------------------------------------------------------- /frontend/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ES2020", 4 | "useDefineForClassFields": true, 5 | "lib": ["ES2020", "DOM", "DOM.Iterable"], 6 | "module": "ESNext", 7 | "skipLibCheck": true, 8 | 9 | /* Bundler mode */ 10 | "moduleResolution": "bundler", 11 | "allowImportingTsExtensions": true, 12 | "resolveJsonModule": true, 13 | "isolatedModules": true, 14 | "noEmit": true, 15 | "jsx": "react-jsx", 16 | 17 | /* Linting */ 18 | "strict": true, 19 | "noUnusedLocals": true, 20 | "noUnusedParameters": true, 21 | "noFallthroughCasesInSwitch": true 22 | }, 23 | "include": ["src"], 24 | "references": [{ "path": "./tsconfig.node.json" }] 25 | } 26 | -------------------------------------------------------------------------------- /backend/src/document_sources/web_pages.py: -------------------------------------------------------------------------------- 1 | import logging 2 | from langchain_community.document_loaders import WebBaseLoader 3 | from src.api_response import create_api_response 4 | 5 | def get_documents_from_web_page(source_url:str): 6 | try: 7 | pages = WebBaseLoader(source_url).load() 8 | file_name = pages[0].metadata['title'] 9 | return file_name, pages 10 | except Exception as e: 11 | job_status = "Failed" 12 | message="Failed To Process Web URL" 13 | error_message = str(e) 14 | logging.error(f"Failed To Process Web URL: {file_name}") 15 | logging.exception(f'Exception Stack trace: {error_message}') 16 | return create_api_response(job_status,message=message,error=error_message,file_name=file_name) -------------------------------------------------------------------------------- /backend/src/logger.py: -------------------------------------------------------------------------------- 1 | import os 2 | from google.cloud import logging as gclogger 3 | 4 | class CustomLogger: 5 | def __init__(self): 6 | self.is_gcp_log_enabled = os.environ.get("GCP_LOG_METRICS_ENABLED", "False").lower() in ("true", "1", "yes") 7 | if self.is_gcp_log_enabled: 8 | self.logging_client = gclogger.Client() 9 | self.logger_name = "llm_experiments_metrics" 10 | self.logger = self.logging_client.logger(self.logger_name) 11 | else: 12 | self.logger = None 13 | 14 | def log_struct(self, message): 15 | if self.is_gcp_log_enabled and message is not None: 16 | self.logger.log_struct(message) 17 | else: 18 | print(message) 19 | -------------------------------------------------------------------------------- /backend/src/diffbot_transformer.py: -------------------------------------------------------------------------------- 1 | from langchain_experimental.graph_transformers.diffbot import DiffbotGraphTransformer 2 | from langchain_community.graphs import Neo4jGraph 3 | from langchain.docstore.document import Document 4 | from typing import List 5 | import os 6 | import logging 7 | import uuid 8 | from src.llm import get_combined_chunks, get_llm 9 | 10 | logging.basicConfig(format='%(asctime)s - %(message)s',level='INFO') 11 | 12 | def get_graph_from_diffbot(graph,chunkId_chunkDoc_list:List): 13 | combined_chunk_document_list = get_combined_chunks(chunkId_chunkDoc_list) 14 | llm,model_name = get_llm('diffbot') 15 | graph_documents = llm.convert_to_graph_documents(combined_chunk_document_list) 16 | return graph_documents 17 | 18 | -------------------------------------------------------------------------------- /backend/src/entities/source_node.py: -------------------------------------------------------------------------------- 1 | from datetime import datetime 2 | 3 | class sourceNode: 4 | file_name:str=None 5 | file_size:int=None 6 | file_type:str=None 7 | file_source:str=None 8 | status:str=None 9 | url:str=None 10 | gcsBucket:str=None 11 | gcsBucketFolder:str=None 12 | gcsProjectId:str=None 13 | awsAccessKeyId:str=None 14 | node_count:int=None 15 | relationship_count:str=None 16 | model:str=None 17 | created_at:datetime=None 18 | updated_at:datetime=None 19 | processing_time:float=None 20 | error_message:str=None 21 | total_pages:int=None 22 | total_chunks:int=None 23 | language:str=None 24 | is_cancelled:bool=None 25 | processed_chunk:int=None 26 | access_token:str=None 27 | -------------------------------------------------------------------------------- /frontend/src/HOC/SettingModalHOC.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { SettingsModalProps } from '../types'; 3 | import SettingsModal from '../components/Popups/Settings/SettingModal'; 4 | 5 | const SettingModalHOC: React.FC = ({ 6 | openTextSchema, 7 | open, 8 | onClose, 9 | isSchema, 10 | settingView, 11 | setIsSchema, 12 | onContinue, 13 | onClear, 14 | }) => { 15 | return ( 16 | 26 | ); 27 | }; 28 | export default SettingModalHOC; 29 | -------------------------------------------------------------------------------- /frontend/src/services/GetNodeLabelsRelTypes.ts: -------------------------------------------------------------------------------- 1 | import axios from 'axios'; 2 | import { url } from '../utils/Utils'; 3 | import { ServerData, UserCredentials } from '../types'; 4 | 5 | export const getNodeLabelsAndRelTypes = async (userCredentials: UserCredentials) => { 6 | const formData = new FormData(); 7 | formData.append('uri', userCredentials?.uri ?? ''); 8 | formData.append('database', userCredentials?.database ?? ''); 9 | formData.append('userName', userCredentials?.userName ?? ''); 10 | formData.append('password', userCredentials?.password ?? ''); 11 | try { 12 | const response = await axios.post(`${url()}/schema`, formData); 13 | return response; 14 | } catch (error) { 15 | console.log(error); 16 | throw error; 17 | } 18 | }; 19 | -------------------------------------------------------------------------------- /frontend/src/services/GetOrphanNodes.ts: -------------------------------------------------------------------------------- 1 | import axios from 'axios'; 2 | import { url } from '../utils/Utils'; 3 | import { OrphanNodeResponse, UserCredentials } from '../types'; 4 | 5 | export const getOrphanNodes = async (userCredentials: UserCredentials) => { 6 | const formData = new FormData(); 7 | formData.append('uri', userCredentials?.uri ?? ''); 8 | formData.append('database', userCredentials?.database ?? ''); 9 | formData.append('userName', userCredentials?.userName ?? ''); 10 | formData.append('password', userCredentials?.password ?? ''); 11 | try { 12 | const response = await axios.post(`${url()}/get_unconnected_nodes_list`, formData); 13 | return response; 14 | } catch (error) { 15 | console.log(error); 16 | throw error; 17 | } 18 | }; 19 | -------------------------------------------------------------------------------- /frontend/src/services/ConnectAPI.ts: -------------------------------------------------------------------------------- 1 | import axios from 'axios'; 2 | import { url } from '../utils/Utils'; 3 | 4 | const connectAPI = async (connectionURI: string, username: string, password: string, database: string) => { 5 | try { 6 | const formData = new FormData(); 7 | formData.append('uri', connectionURI ?? ''); 8 | formData.append('database', database ?? ''); 9 | formData.append('userName', username ?? ''); 10 | formData.append('password', password ?? ''); 11 | const response = await axios.post(`${url()}/connect`, formData, { 12 | headers: { 13 | 'Content-Type': 'multipart/form-data', 14 | }, 15 | }); 16 | return response; 17 | } catch (error) { 18 | console.log('Error in connecting to the Neo4j instance :', error); 19 | throw error; 20 | } 21 | }; 22 | export default connectAPI; 23 | -------------------------------------------------------------------------------- /backend/src/document_sources/wikipedia.py: -------------------------------------------------------------------------------- 1 | import logging 2 | from langchain_community.document_loaders import WikipediaLoader 3 | from src.api_response import create_api_response 4 | 5 | def get_documents_from_Wikipedia(wiki_query:str, language:str): 6 | try: 7 | pages = WikipediaLoader(query=wiki_query.strip(), lang=language, load_max_docs=1, load_all_available_meta=False).load() 8 | file_name = wiki_query.strip() 9 | logging.info(f"Total Pages from Wikipedia = {len(pages)}") 10 | return file_name, pages 11 | except Exception as e: 12 | job_status = "Failed" 13 | message="Failed To Process Wikipedia Query" 14 | error_message = str(e) 15 | logging.error(f"Failed To Process Wikipedia Query: {file_name}") 16 | logging.exception(f'Exception Stack trace: {error_message}') 17 | return create_api_response(job_status,message=message,error=error_message,file_name=file_name) -------------------------------------------------------------------------------- /backend/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM python:3.10-slim 2 | WORKDIR /code 3 | ENV PORT 8000 4 | EXPOSE 8000 5 | # Install dependencies and clean up in one layer 6 | RUN apt-get update && \ 7 | apt-get install -y --no-install-recommends \ 8 | libgl1-mesa-glx \ 9 | libreoffice \ 10 | cmake \ 11 | poppler-utils \ 12 | tesseract-ocr && \ 13 | apt-get clean && \ 14 | rm -rf /var/lib/apt/lists/* 15 | # Set LD_LIBRARY_PATH 16 | ENV LD_LIBRARY_PATH=/usr/lib/x86_64-linux-gnu:$LD_LIBRARY_PATH 17 | # Copy requirements file and install Python dependencies 18 | COPY requirements.txt /code/ 19 | # --no-cache-dir --upgrade 20 | RUN pip install -r requirements.txt 21 | # Copy application code 22 | COPY . /code 23 | # Set command 24 | CMD ["gunicorn", "score:app", "--workers", "8","--threads", "8", "--worker-class", "uvicorn.workers.UvicornWorker", "--bind", "0.0.0.0:8000", "--timeout", "300"] 25 | -------------------------------------------------------------------------------- /frontend/src/services/DeleteOrphanNodes.ts: -------------------------------------------------------------------------------- 1 | import axios from 'axios'; 2 | import { url } from '../utils/Utils'; 3 | import { UserCredentials } from '../types'; 4 | 5 | const deleteOrphanAPI = async (userCredentials: UserCredentials, selectedNodes: string[]) => { 6 | try { 7 | const formData = new FormData(); 8 | formData.append('uri', userCredentials?.uri ?? ''); 9 | formData.append('database', userCredentials?.database ?? ''); 10 | formData.append('userName', userCredentials?.userName ?? ''); 11 | formData.append('password', userCredentials?.password ?? ''); 12 | formData.append('unconnected_entities_list', JSON.stringify(selectedNodes)); 13 | const response = await axios.post(`${url()}/delete_unconnected_nodes`, formData); 14 | return response; 15 | } catch (error) { 16 | console.log('Error Posting the Question:', error); 17 | throw error; 18 | } 19 | }; 20 | export default deleteOrphanAPI; 21 | -------------------------------------------------------------------------------- /backend/src/openAI_llm.py: -------------------------------------------------------------------------------- 1 | from langchain_community.graphs import Neo4jGraph 2 | import os 3 | from dotenv import load_dotenv 4 | import logging 5 | import concurrent.futures 6 | from concurrent.futures import ThreadPoolExecutor 7 | from langchain_experimental.graph_transformers import LLMGraphTransformer 8 | from src.llm import get_graph_document_list, get_combined_chunks, get_llm 9 | 10 | load_dotenv() 11 | logging.basicConfig(format='%(asctime)s - %(message)s',level='INFO') 12 | 13 | def get_graph_from_OpenAI(model_version, graph, chunkId_chunkDoc_list, allowedNodes, allowedRelationship): 14 | futures=[] 15 | graph_document_list=[] 16 | 17 | combined_chunk_document_list = get_combined_chunks(chunkId_chunkDoc_list) 18 | 19 | llm,model_name = get_llm(model_version) 20 | return get_graph_document_list(llm, combined_chunk_document_list, allowedNodes, allowedRelationship) 21 | 22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /frontend/src/services/ChunkEntitiesInfo.ts: -------------------------------------------------------------------------------- 1 | import axios from 'axios'; 2 | import { url } from '../utils/Utils'; 3 | import { ChatInfo_APIResponse, UserCredentials } from '../types'; 4 | 5 | const chunkEntitiesAPI = async (userCredentials: UserCredentials, chunk_ids: string) => { 6 | try { 7 | const formData = new FormData(); 8 | formData.append('uri', userCredentials?.uri ?? ''); 9 | formData.append('userName', userCredentials?.userName ?? ''); 10 | formData.append('password', userCredentials?.password ?? ''); 11 | formData.append('chunk_ids', chunk_ids); 12 | 13 | const response: ChatInfo_APIResponse = await axios.post(`${url()}/chunk_entities`, formData, { 14 | headers: { 15 | 'Content-Type': 'multipart/form-data', 16 | }, 17 | }); 18 | return response; 19 | } catch (error) { 20 | console.log('Error uploading file:', error); 21 | throw error; 22 | } 23 | }; 24 | 25 | export { chunkEntitiesAPI }; 26 | -------------------------------------------------------------------------------- /frontend/src/services/CommonAPI.ts: -------------------------------------------------------------------------------- 1 | import axios, { AxiosResponse, Method } from 'axios'; 2 | import { UserCredentials, FormDataParams } from '../types'; 3 | 4 | // API Call 5 | const apiCall = async ( 6 | url: string, 7 | method: Method, 8 | commonParams: UserCredentials, 9 | additionalParams: FormDataParams 10 | ) => { 11 | try { 12 | const formData = new FormData(); 13 | for (const key in commonParams) { 14 | formData.append(key, commonParams[key]); 15 | } 16 | for (const key in additionalParams) { 17 | formData.append(key, additionalParams[key]); 18 | } 19 | const response: AxiosResponse = await axios({ 20 | method: method, 21 | url: url, 22 | data: formData, 23 | headers: { 24 | 'Content-Type': 'multipart/form-data', 25 | }, 26 | }); 27 | return response.data; 28 | } catch (error) { 29 | console.log('API Error:', error); 30 | throw error; 31 | } 32 | }; 33 | 34 | export { apiCall }; 35 | -------------------------------------------------------------------------------- /frontend/src/styling/info.css: -------------------------------------------------------------------------------- 1 | .list-class { 2 | .li { 3 | all: revert; 4 | } 5 | ol { 6 | all: revert; 7 | } 8 | ul { 9 | all: revert; 10 | } 11 | .button-container { 12 | display: flex; 13 | justify-content: center; 14 | width: 100%; 15 | } 16 | .entities-container { 17 | display: flex; 18 | flex-direction: column; 19 | gap: 10px; 20 | } 21 | .entity-item { 22 | display: flex; 23 | align-items: center; 24 | } 25 | .entity-label { 26 | margin-right: 10px; 27 | } 28 | .entity-text { 29 | display: inline; 30 | } 31 | .hoverable-link-container { 32 | position: relative; 33 | } 34 | .popup { 35 | position: fixed; 36 | z-index: 1000; 37 | background-color: white; 38 | border: 1px solid #ccc; 39 | padding: 5px; 40 | box-shadow: 0px 4px 8px rgba(0, 0, 0, 0.1); 41 | border-radius: 8px; 42 | } 43 | .iframe-preview { 44 | width: 360px; 45 | height: 215px; 46 | border: none; 47 | border-radius: 8px; 48 | } 49 | } -------------------------------------------------------------------------------- /frontend/src/services/PostProcessing.ts: -------------------------------------------------------------------------------- 1 | import axios from 'axios'; 2 | import { url } from '../utils/Utils'; 3 | import { UserCredentials } from '../types'; 4 | 5 | const postProcessing = async (userCredentials: UserCredentials, taskParam: string[]) => { 6 | try { 7 | const formData = new FormData(); 8 | formData.append('uri', userCredentials?.uri ?? ''); 9 | formData.append('database', userCredentials?.database ?? ''); 10 | formData.append('userName', userCredentials?.userName ?? ''); 11 | formData.append('password', userCredentials?.password ?? ''); 12 | formData.append('tasks', JSON.stringify(taskParam)); 13 | const response = await axios.post(`${url()}/post_processing`, formData, { 14 | headers: { 15 | 'Content-Type': 'multipart/form-data', 16 | }, 17 | }); 18 | return response; 19 | } catch (error) { 20 | console.log('Error updating the graph:', error); 21 | throw error; 22 | } 23 | }; 24 | 25 | export { postProcessing }; 26 | -------------------------------------------------------------------------------- /frontend/README.md: -------------------------------------------------------------------------------- 1 | # Neo4j Knowledge Graph Builder 2 | 3 | Reactjs app for building an knowledge graph using [Neo4j Needle](https://www.neo4j.design/). 4 | 5 | ## Features 6 | - 🚀 Responsive: Adapts to different screen sizes for optimal user experience. 7 | - ⚙️ Neo4j Integration: A simple example for connecting to a Neo4j database. 8 | - 🔐 Neo4j Auto-connect: Automatically connects to the Neo4j database if the user has a session saved (using localStorage). 9 | - 💻 Dropzone: To drop pdf or txt files. 10 | - 💻 Table : To show uploaded files and generate knowledge graph. 11 | - 🛠️️ Modular approach: Facilitates easy customization. 12 | 13 | 14 | ## Installation: 15 | ```shell 16 | npm install -g yarn 17 | yarn i 18 | yarn run dev 19 | ``` 20 | 21 | ## 22 | Do run yarn add -- package name to get project updated with required dependencies. 23 | 24 | 29/01/2024> Latest dependency 25 | yarn add uuid 26 | ## 27 | Upload api url should be picked from: ports tab under codespace environement // For demo 28 | ## What it looks like 29 | -------------------------------------------------------------------------------- /frontend/src/services/ServerSideStatusUpdateAPI.ts: -------------------------------------------------------------------------------- 1 | import { eventResponsetypes } from '../types'; 2 | import { url } from '../utils/Utils'; 3 | export function triggerStatusUpdateAPI( 4 | name: string, 5 | uri: string, 6 | username: string, 7 | password: string, 8 | database: string, 9 | datahandler: (i: eventResponsetypes) => void 10 | ) { 11 | let encodedstr; 12 | if (password) { 13 | encodedstr = btoa(password); 14 | } 15 | const eventSource = new EventSource( 16 | `${url()}/update_extract_status/${name}?url=${uri}&userName=${username}&password=${encodedstr}&database=${database}` 17 | ); 18 | eventSource.onmessage = (event) => { 19 | const eventResponse = JSON.parse(event.data); 20 | if ( 21 | eventResponse.status === 'Completed' || 22 | eventResponse.status == 'Failed' || 23 | eventResponse.status == 'Cancelled' 24 | ) { 25 | datahandler(eventResponse); 26 | eventSource.close(); 27 | } else { 28 | datahandler(eventResponse); 29 | } 30 | }; 31 | } 32 | -------------------------------------------------------------------------------- /frontend/src/App.tsx: -------------------------------------------------------------------------------- 1 | import './App.css'; 2 | import '@neo4j-ndl/base/lib/neo4j-ds-styles.css'; 3 | import ThemeWrapper from './context/ThemeWrapper'; 4 | import QuickStarter from './components/QuickStarter'; 5 | import { GoogleOAuthProvider } from '@react-oauth/google'; 6 | import { APP_SOURCES } from './utils/Constants'; 7 | import ErrorBoundary from './components/UI/ErrroBoundary'; 8 | 9 | const App: React.FC = () => { 10 | return ( 11 | <> 12 | {APP_SOURCES != undefined && APP_SOURCES.includes('gcs') ? ( 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | ) : ( 21 | 22 | 23 | 24 | 25 | 26 | )} 27 | 28 | ); 29 | }; 30 | 31 | export default App; 32 | -------------------------------------------------------------------------------- /frontend/src/context/UserCredentials.tsx: -------------------------------------------------------------------------------- 1 | import { createContext, useState, useContext, FunctionComponent, ReactNode } from 'react'; 2 | import { UserCredentials } from '../types'; 3 | 4 | type Props = { 5 | children: ReactNode; 6 | }; 7 | 8 | interface ContextProps { 9 | userCredentials: UserCredentials | null; 10 | setUserCredentials: (UserCredentials: UserCredentials) => void; 11 | } 12 | export const UserConnection = createContext({ 13 | userCredentials: null, 14 | setUserCredentials: () => null, 15 | }); 16 | export const useCredentials = () => { 17 | const userCredentials = useContext(UserConnection); 18 | return userCredentials; 19 | }; 20 | const UserCredentialsWrapper: FunctionComponent = (props) => { 21 | const [userCredentials, setUserCredentials] = useState(null); 22 | const value = { 23 | userCredentials, 24 | setUserCredentials, 25 | }; 26 | return {props.children}; 27 | }; 28 | export default UserCredentialsWrapper; 29 | -------------------------------------------------------------------------------- /frontend/src/services/CancelAPI.ts: -------------------------------------------------------------------------------- 1 | import axios from 'axios'; 2 | import { url } from '../utils/Utils'; 3 | import { UserCredentials, commonserverresponse } from '../types'; 4 | 5 | const cancelAPI = async (filenames: string[], source_types: string[]) => { 6 | try { 7 | const formData = new FormData(); 8 | const credentials: UserCredentials = JSON.parse(localStorage.getItem('neo4j.connection') || 'null'); 9 | if (credentials) { 10 | formData.append('uri', credentials?.uri ?? ''); 11 | formData.append('database', credentials?.database ?? ''); 12 | formData.append('userName', credentials?.user ?? ''); 13 | formData.append('password', credentials?.password ?? ''); 14 | } 15 | formData.append('filenames', JSON.stringify(filenames)); 16 | formData.append('source_types', JSON.stringify(source_types)); 17 | const response = await axios.post(`${url()}/cancelled_job`, formData); 18 | return response; 19 | } catch (error) { 20 | console.log('Error Posting the Question:', error); 21 | throw error; 22 | } 23 | }; 24 | export default cancelAPI; 25 | -------------------------------------------------------------------------------- /frontend/src/components/Graph/GraphViewButton.tsx: -------------------------------------------------------------------------------- 1 | import React, { useState } from 'react'; 2 | import { Button } from '@neo4j-ndl/react'; 3 | import GraphViewModal from './GraphViewModal'; 4 | import { Node, Relationship } from '@neo4j-nvl/base'; 5 | 6 | interface GraphViewButtonProps { 7 | nodeValues?: Node[]; 8 | relationshipValues?: Relationship[]; 9 | } 10 | const GraphViewButton: React.FC = ({ nodeValues, relationshipValues }) => { 11 | const [openGraphView, setOpenGraphView] = useState(false); 12 | const [viewPoint, setViewPoint] = useState(''); 13 | 14 | const handleGraphViewClick = () => { 15 | setOpenGraphView(true); 16 | setViewPoint('chatInfoView'); 17 | }; 18 | return ( 19 | <> 20 | 21 | 28 | 29 | ); 30 | }; 31 | export default GraphViewButton; 32 | -------------------------------------------------------------------------------- /frontend/src/services/GraphQuery.ts: -------------------------------------------------------------------------------- 1 | import axios from 'axios'; 2 | import { url } from '../utils/Utils'; 3 | import { UserCredentials } from '../types'; 4 | 5 | const graphQueryAPI = async ( 6 | userCredentials: UserCredentials, 7 | query_type: string, 8 | document_names: (string | undefined)[] | undefined 9 | ) => { 10 | try { 11 | const formData = new FormData(); 12 | formData.append('uri', userCredentials?.uri ?? ''); 13 | formData.append('database', userCredentials?.database ?? ''); 14 | formData.append('userName', userCredentials?.userName ?? ''); 15 | formData.append('password', userCredentials?.password ?? ''); 16 | formData.append('query_type', query_type ?? 'entities'); 17 | formData.append('document_names', JSON.stringify(document_names)); 18 | 19 | const response = await axios.post(`${url()}/graph_query`, formData, { 20 | headers: { 21 | 'Content-Type': 'multipart/form-data', 22 | }, 23 | }); 24 | return response; 25 | } catch (error) { 26 | console.log('Error Posting the Question:', error); 27 | throw error; 28 | } 29 | }; 30 | export default graphQueryAPI; 31 | -------------------------------------------------------------------------------- /frontend/src/services/DeleteFiles.ts: -------------------------------------------------------------------------------- 1 | import axios from 'axios'; 2 | import { url } from '../utils/Utils'; 3 | import { CustomFile, UserCredentials } from '../types'; 4 | 5 | const deleteAPI = async (userCredentials: UserCredentials, selectedFiles: CustomFile[], deleteEntities: boolean) => { 6 | try { 7 | const filenames = selectedFiles.map((str) => str.name); 8 | const source_types = selectedFiles.map((str) => str.fileSource); 9 | const formData = new FormData(); 10 | formData.append('uri', userCredentials?.uri ?? ''); 11 | formData.append('database', userCredentials?.database ?? ''); 12 | formData.append('userName', userCredentials?.userName ?? ''); 13 | formData.append('password', userCredentials?.password ?? ''); 14 | formData.append('deleteEntities', JSON.stringify(deleteEntities)); 15 | formData.append('filenames', JSON.stringify(filenames)); 16 | formData.append('source_types', JSON.stringify(source_types)); 17 | const response = await axios.post(`${url()}/delete_document_and_entities`, formData); 18 | return response; 19 | } catch (error) { 20 | console.log('Error Posting the Question:', error); 21 | throw error; 22 | } 23 | }; 24 | export default deleteAPI; 25 | -------------------------------------------------------------------------------- /frontend/src/components/Graph/CheckboxSelection.tsx: -------------------------------------------------------------------------------- 1 | import { Checkbox } from '@neo4j-ndl/react'; 2 | import React from 'react'; 3 | import { CheckboxSectionProps } from '../../types'; 4 | 5 | const CheckboxSelection: React.FC = ({ graphType, loading, handleChange }) => ( 6 |
7 |
8 | handleChange('Document')} 13 | /> 14 | handleChange('Entities')} 19 | /> 20 | handleChange('Chunk')} 25 | /> 26 |
27 |
28 | ); 29 | export default CheckboxSelection; 30 | -------------------------------------------------------------------------------- /frontend/src/components/UI/IconButtonToolTip.tsx: -------------------------------------------------------------------------------- 1 | import { IconButton, Tip } from '@neo4j-ndl/react'; 2 | 3 | const IconButtonWithToolTip = ({ 4 | text, 5 | children, 6 | onClick, 7 | size = 'medium', 8 | clean, 9 | grouped, 10 | placement = 'bottom', 11 | disabled = false, 12 | label, 13 | }: { 14 | label: string; 15 | text: string | React.ReactNode; 16 | children: React.ReactNode; 17 | onClick?: React.MouseEventHandler | undefined; 18 | size?: 'small' | 'medium' | 'large'; 19 | clean?: boolean; 20 | grouped?: boolean; 21 | placement?: 'bottom' | 'top' | 'right' | 'left'; 22 | disabled?: boolean; 23 | }) => { 24 | return ( 25 | 26 | 27 | 35 | {children} 36 | 37 | 38 | 39 | {text} 40 | 41 | 42 | ); 43 | }; 44 | 45 | export default IconButtonWithToolTip; 46 | -------------------------------------------------------------------------------- /frontend/src/context/UserMessages.tsx: -------------------------------------------------------------------------------- 1 | import { createContext, useState, useContext, Dispatch, SetStateAction, FC } from 'react'; 2 | import { MessagesContextProviderProps, Messages } from '../types'; 3 | import chatbotmessages from '../assets/ChatbotMessages.json'; 4 | import { getDateTime } from '../utils/Utils'; 5 | 6 | interface MessageContextType { 7 | messages: Messages[] | []; 8 | setMessages: Dispatch>; 9 | } 10 | 11 | const MessageContext = createContext(undefined); 12 | 13 | const MessageContextWrapper: FC = ({ children }) => { 14 | const [messages, setMessages] = useState([ 15 | { ...chatbotmessages.listMessages[1], datetime: getDateTime() }, 16 | ]); 17 | 18 | const value: MessageContextType = { 19 | messages, 20 | setMessages, 21 | }; 22 | return {children}; 23 | }; 24 | const useMessageContext = () => { 25 | const context = useContext(MessageContext); 26 | if (!context) { 27 | throw new Error('useMessageContext must be used within a MessageContextWrapper'); 28 | } 29 | return context; 30 | }; 31 | export { MessageContextWrapper, useMessageContext }; 32 | -------------------------------------------------------------------------------- /frontend/src/components/Layout/DrawerChatbot.tsx: -------------------------------------------------------------------------------- 1 | import { Drawer } from '@neo4j-ndl/react'; 2 | import Chatbot from '../ChatBot/Chatbot'; 3 | import { Messages } from '../../types'; 4 | import { useMessageContext } from '../../context/UserMessages'; 5 | interface DrawerChatbotProps { 6 | isExpanded: boolean; 7 | clearHistoryData: boolean; 8 | messages: Messages[]; 9 | } 10 | const DrawerChatbot: React.FC = ({ isExpanded, clearHistoryData, messages }) => { 11 | const { setMessages } = useMessageContext(); 12 | 13 | const getIsLoading = (messages: Messages[]) => { 14 | return messages.some((msg) => msg.isTyping || msg.isLoading); 15 | }; 16 | return ( 17 |
18 | 19 | 20 | 27 | 28 | 29 |
30 | ); 31 | }; 32 | export default DrawerChatbot; 33 | -------------------------------------------------------------------------------- /frontend/src/components/WebSources/Web/WebInput.tsx: -------------------------------------------------------------------------------- 1 | import { webLinkValidation } from '../../../utils/Utils'; 2 | import useSourceInput from '../../../hooks/useSourceInput'; 3 | import CustomSourceInput from '../CustomSourceInput'; 4 | 5 | export default function WebInput({ setIsLoading }: { setIsLoading: React.Dispatch> }) { 6 | const { 7 | inputVal, 8 | onChangeHandler, 9 | onBlurHandler, 10 | submitHandler, 11 | status, 12 | setStatus, 13 | statusMessage, 14 | isFocused, 15 | isValid, 16 | onClose, 17 | onPasteHandler, 18 | } = useSourceInput(webLinkValidation, setIsLoading, 'web-url', false, false, true); 19 | return ( 20 | 37 | ); 38 | } 39 | -------------------------------------------------------------------------------- /frontend/src/components/UI/Menu.tsx: -------------------------------------------------------------------------------- 1 | import { Menu } from '@neo4j-ndl/react'; 2 | import { Menuitems, Origin } from '../../types'; 3 | 4 | export default function CustomMenu({ 5 | open, 6 | closeHandler, 7 | items, 8 | MenuAnchor, 9 | anchorOrigin, 10 | transformOrigin, 11 | anchorPortal = true, 12 | disableBackdrop = false, 13 | }: { 14 | open: boolean; 15 | closeHandler: () => void; 16 | items: Menuitems[] | undefined; 17 | MenuAnchor: HTMLElement | null; 18 | anchorOrigin?: Origin; 19 | transformOrigin?: Origin; 20 | anchorPortal?: boolean; 21 | disableBackdrop?: boolean; 22 | }) { 23 | return ( 24 | 33 | {items?.map((i, idx) => { 34 | return ( 35 | 43 | ); 44 | })} 45 | 46 | ); 47 | } 48 | -------------------------------------------------------------------------------- /frontend/src/components/WebSources/WikiPedia/WikipediaInput.tsx: -------------------------------------------------------------------------------- 1 | import { wikiValidation } from '../../../utils/Utils'; 2 | import useSourceInput from '../../../hooks/useSourceInput'; 3 | import CustomSourceInput from '../CustomSourceInput'; 4 | 5 | export default function WikipediaInput({ 6 | setIsLoading, 7 | }: { 8 | setIsLoading: React.Dispatch>; 9 | }) { 10 | const { 11 | inputVal, 12 | onChangeHandler, 13 | onBlurHandler, 14 | submitHandler, 15 | status, 16 | setStatus, 17 | statusMessage, 18 | isFocused, 19 | isValid, 20 | onClose, 21 | onPasteHandler, 22 | } = useSourceInput(wikiValidation, setIsLoading, 'Wikipedia', true, false, false); 23 | return ( 24 | 41 | ); 42 | } 43 | -------------------------------------------------------------------------------- /frontend/src/components/WebSources/Youtube/YoutubeInput.tsx: -------------------------------------------------------------------------------- 1 | import CustomSourceInput from '../CustomSourceInput'; 2 | import useSourceInput from '../../../hooks/useSourceInput'; 3 | import { youtubeLinkValidation } from '../../../utils/Utils'; 4 | 5 | export default function YoutubeInput({ 6 | setIsLoading, 7 | }: { 8 | setIsLoading: React.Dispatch>; 9 | }) { 10 | const { 11 | inputVal, 12 | onChangeHandler, 13 | onBlurHandler, 14 | submitHandler, 15 | status, 16 | setStatus, 17 | statusMessage, 18 | isFocused, 19 | isValid, 20 | onClose, 21 | onPasteHandler, 22 | } = useSourceInput(youtubeLinkValidation, setIsLoading, 'youtube', false, true, false); 23 | return ( 24 | 41 | ); 42 | } 43 | -------------------------------------------------------------------------------- /example.env: -------------------------------------------------------------------------------- 1 | # Mandatory 2 | OPENAI_API_KEY = "" 3 | DIFFBOT_API_KEY = "" 4 | 5 | # Optional Backend 6 | EMBEDDING_MODEL = "all-MiniLM-L6-v2" 7 | IS_EMBEDDING = "true" 8 | KNN_MIN_SCORE = "0.94" 9 | # Enable Gemini (default is False) | Can be False or True 10 | GEMINI_ENABLED = False 11 | # LLM_MODEL_CONFIG_ollama_llama3="llama3,http://host.docker.internal:11434" 12 | 13 | # Enable Google Cloud logs (default is False) | Can be False or True 14 | GCP_LOG_METRICS_ENABLED = False 15 | NUMBER_OF_CHUNKS_TO_COMBINE = 6 16 | UPDATE_GRAPH_CHUNKS_PROCESSED = 20 17 | NEO4J_URI = "neo4j://database:7687" 18 | NEO4J_USERNAME = "neo4j" 19 | NEO4J_PASSWORD = "password" 20 | LANGCHAIN_API_KEY = "" 21 | LANGCHAIN_PROJECT = "" 22 | LANGCHAIN_TRACING_V2 = "true" 23 | LANGCHAIN_ENDPOINT = "https://api.smith.langchain.com" 24 | GCS_FILE_CACHE = False 25 | ENTITY_EMBEDDING=True 26 | 27 | # Optional Frontend 28 | BACKEND_API_URL="http://localhost:8000" 29 | BLOOM_URL="https://workspace-preview.neo4j.io/workspace/explore?connectURL={CONNECT_URL}&search=Show+me+a+graph&featureGenAISuggestions=true&featureGenAISuggestionsInternal=true" 30 | REACT_APP_SOURCES="local,youtube,wiki,s3,web" 31 | LLM_MODELS="diffbot,openai-gpt-3.5,openai-gpt-4o" # ",ollama_llama3" 32 | ENV="DEV" 33 | TIME_PER_CHUNK=4 34 | TIME_PER_PAGE=50 35 | CHUNK_SIZE=5242880 36 | GOOGLE_CLIENT_ID="" 37 | CHAT_MODES="" 38 | -------------------------------------------------------------------------------- /frontend/Dockerfile: -------------------------------------------------------------------------------- 1 | # Step 1: Build the React application 2 | FROM node:20 AS build 3 | 4 | ARG BACKEND_API_URL="http://localhost:8000" 5 | ARG REACT_APP_SOURCES="" 6 | ARG LLM_MODELS="" 7 | ARG GOOGLE_CLIENT_ID="" 8 | ARG BLOOM_URL="https://workspace-preview.neo4j.io/workspace/explore?connectURL={CONNECT_URL}&search=Show+me+a+graph&featureGenAISuggestions=true&featureGenAISuggestionsInternal=true" 9 | ARG TIME_PER_CHUNK=4 10 | ARG TIME_PER_PAGE=50 11 | ARG LARGE_FILE_SIZE=5242880 12 | ARG CHUNK_SIZE=5242880 13 | ARG CHAT_MODES="" 14 | ARG ENV="DEV" 15 | 16 | WORKDIR /app 17 | COPY package.json yarn.lock ./ 18 | RUN yarn add @neo4j-nvl/base @neo4j-nvl/react 19 | RUN yarn install 20 | COPY . ./ 21 | RUN BACKEND_API_URL=$BACKEND_API_URL \ 22 | REACT_APP_SOURCES=$REACT_APP_SOURCES \ 23 | LLM_MODELS=$LLM_MODELS \ 24 | GOOGLE_CLIENT_ID=$GOOGLE_CLIENT_ID \ 25 | BLOOM_URL=$BLOOM_URL \ 26 | TIME_PER_CHUNK=$TIME_PER_CHUNK \ 27 | CHUNK_SIZE=$CHUNK_SIZE \ 28 | ENV=$ENV \ 29 | LARGE_FILE_SIZE=${LARGE_FILE_SIZE} \ 30 | CHAT_MODES=$CHAT_MODES \ 31 | yarn run build 32 | 33 | # Step 2: Serve the application using Nginx 34 | FROM nginx:alpine 35 | COPY --from=build /app/dist /usr/share/nginx/html 36 | COPY nginx/nginx.conf /etc/nginx/conf.d/default.conf 37 | 38 | EXPOSE 8080 39 | CMD ["nginx", "-g", "daemon off;"] 40 | -------------------------------------------------------------------------------- /frontend/src/hooks/useSpeech.tsx: -------------------------------------------------------------------------------- 1 | import { useEffect, useState } from 'react'; 2 | import { SpeechSynthesisProps, SpeechArgs } from '../types'; 3 | 4 | const useSpeechSynthesis = (props: SpeechSynthesisProps = {}) => { 5 | const { onEnd = () => {} } = props; 6 | const [speaking, setSpeaking] = useState(false); 7 | const [supported, setSupported] = useState(false); 8 | const handleEnd = () => { 9 | setSpeaking(false); 10 | onEnd(); 11 | }; 12 | useEffect(() => { 13 | if (typeof window !== 'undefined' && window.speechSynthesis) { 14 | setSupported(true); 15 | } 16 | }, []); 17 | const speak = (args: SpeechArgs = {}) => { 18 | const { text = '', rate = 1, pitch = 1, volume = 1 } = args; 19 | if (!supported) { 20 | return; 21 | } 22 | setSpeaking(true); 23 | const utterance = new SpeechSynthesisUtterance(); 24 | utterance.text = text; 25 | utterance.onend = handleEnd; 26 | utterance.rate = rate; 27 | utterance.pitch = pitch; 28 | utterance.volume = volume; 29 | window.speechSynthesis.speak(utterance); 30 | }; 31 | const cancel = () => { 32 | if (!supported) { 33 | return; 34 | } 35 | setSpeaking(false); 36 | window.speechSynthesis.cancel(); 37 | }; 38 | return { 39 | supported, 40 | speak, 41 | speaking, 42 | cancel, 43 | }; 44 | }; 45 | export default useSpeechSynthesis; 46 | -------------------------------------------------------------------------------- /frontend/src/components/UI/ButtonWithToolTip.tsx: -------------------------------------------------------------------------------- 1 | import { Button, Tip } from '@neo4j-ndl/react'; 2 | import React, { MouseEventHandler } from 'react'; 3 | 4 | const ButtonWithToolTip = ({ 5 | text, 6 | children, 7 | onClick, 8 | size = 'medium', 9 | placement = 'bottom', 10 | disabled = false, 11 | className = '', 12 | label, 13 | loading, 14 | fill = 'filled', 15 | }: { 16 | text: string | React.ReactNode; 17 | children: React.ReactNode; 18 | onClick?: MouseEventHandler | (() => void); 19 | size?: 'small' | 'medium' | 'large'; 20 | clean?: boolean; 21 | grouped?: boolean; 22 | placement?: 'bottom' | 'top' | 'right' | 'left'; 23 | disabled?: boolean; 24 | className?: string; 25 | loading?: boolean; 26 | label: string; 27 | fill?: 'filled' | 'outlined' | 'text'; 28 | }) => { 29 | return ( 30 | 31 | 32 | 43 | 44 | 45 | {text} 46 | 47 | 48 | ); 49 | }; 50 | 51 | export default ButtonWithToolTip; 52 | -------------------------------------------------------------------------------- /backend/src/api_response.py: -------------------------------------------------------------------------------- 1 | 2 | 3 | def create_api_response(status,success_count=None,failed_count=None, data=None, error=None,message=None,file_source=None,file_name=None): 4 | """ 5 | Create a response to be sent to the API. This is a helper function to create a JSON response that can be sent to the API. 6 | 7 | Args: 8 | status: The status of the API call. Should be one of the constants in this module. 9 | data: The data that was returned by the API call. 10 | error: The error that was returned by the API call. 11 | success_count: Number of files successfully processed. 12 | failed_count: Number of files failed to process. 13 | Returns: 14 | A dictionary containing the status data and error if any 15 | """ 16 | response = {"status": status} 17 | 18 | # Set the data of the response 19 | if data is not None: 20 | response["data"] = data 21 | 22 | # Set the error message to the response. 23 | if error is not None: 24 | response["error"] = error 25 | 26 | if success_count is not None: 27 | response['success_count']=success_count 28 | response['failed_count']=failed_count 29 | 30 | if message is not None: 31 | response['message']=message 32 | 33 | if file_source is not None: 34 | response['file_source']=file_source 35 | 36 | if file_name is not None: 37 | response['file_name']=file_name 38 | 39 | return response -------------------------------------------------------------------------------- /frontend/src/assets/images/db-search.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /frontend/src/HOC/CustomModal.tsx: -------------------------------------------------------------------------------- 1 | import { Banner, Button, Dialog } from '@neo4j-ndl/react'; 2 | import { CustomModalProps } from '../types'; 3 | import { buttonCaptions } from '../utils/Constants'; 4 | 5 | const CustomModal: React.FC = ({ 6 | open, 7 | onClose, 8 | children, 9 | submitLabel = buttonCaptions.submit, 10 | submitHandler, 11 | statusMessage, 12 | status, 13 | setStatus, 14 | }) => { 15 | const isDisabled = status === 'danger' || status === 'info' || status === 'warning' || status === 'success'; 16 | return ( 17 | 25 | 26 | {status !== 'unknown' && ( 27 | setStatus('unknown')} 31 | type={status} 32 | name='Custom Banner' 33 | /> 34 | )} 35 |
{children}
36 | 37 | 40 | 41 |
42 |
43 | ); 44 | }; 45 | 46 | export default CustomModal; 47 | -------------------------------------------------------------------------------- /frontend/src/services/PollingAPI.ts: -------------------------------------------------------------------------------- 1 | import axios from 'axios'; 2 | import { url } from '../utils/Utils'; 3 | import { PollingAPI_Response, statusupdate } from '../types'; 4 | 5 | export default async function subscribe( 6 | fileName: string, 7 | uri: string, 8 | username: string, 9 | database: string, 10 | password: string, 11 | datahandler: (i: statusupdate) => void, 12 | progressHandler: (i: statusupdate) => void 13 | ) { 14 | let encodedstr = password ? btoa(password) : ''; 15 | 16 | const MAX_POLLING_ATTEMPTS = 10; 17 | let pollingAttempts = 0; 18 | let delay = 2000; 19 | 20 | while (pollingAttempts < MAX_POLLING_ATTEMPTS) { 21 | let currentdelay = delay; 22 | let response: PollingAPI_Response = await axios.get( 23 | `${url()}/document_status/${fileName}?url=${uri}&userName=${username}&password=${encodedstr}&database=${database}` 24 | ); 25 | 26 | if (response.data?.file_name?.status === 'Processing') { 27 | progressHandler(response.data); 28 | await new Promise((resolve) => { 29 | setTimeout(resolve, currentdelay); 30 | }); 31 | delay *= 2; 32 | pollingAttempts++; 33 | } else if (response.status !== 200) { 34 | throw new Error( 35 | JSON.stringify({ fileName, message: `Failed To Process ${fileName} or LLM Unable To Parse Content` }) 36 | ); 37 | } else { 38 | datahandler(response.data); 39 | return; 40 | } 41 | } 42 | 43 | throw new Error(`Polling for ${fileName} timed out after ${MAX_POLLING_ATTEMPTS} attempts.`); 44 | } 45 | -------------------------------------------------------------------------------- /backend/example.env: -------------------------------------------------------------------------------- 1 | OPENAI_API_KEY = "" 2 | DIFFBOT_API_KEY = "" 3 | GROQ_API_KEY = "" 4 | EMBEDDING_MODEL = "all-MiniLM-L6-v2" 5 | IS_EMBEDDING = "true" 6 | KNN_MIN_SCORE = "0.94" 7 | # Enable Gemini (default is False) | Can be False or True 8 | GEMINI_ENABLED = False 9 | # Enable Google Cloud logs (default is False) | Can be False or True 10 | GCP_LOG_METRICS_ENABLED = False 11 | NUMBER_OF_CHUNKS_TO_COMBINE = 6 12 | UPDATE_GRAPH_CHUNKS_PROCESSED = 20 13 | NEO4J_URI = "" 14 | NEO4J_USERNAME = "" 15 | NEO4J_PASSWORD = "" 16 | NEO4J_DATABASE = "" 17 | AWS_ACCESS_KEY_ID = "" 18 | AWS_SECRET_ACCESS_KEY = "" 19 | LANGCHAIN_API_KEY = "" 20 | LANGCHAIN_PROJECT = "" 21 | LANGCHAIN_TRACING_V2 = "" 22 | LANGCHAIN_ENDPOINT = "" 23 | GCS_FILE_CACHE = "" #save the file into GCS or local, SHould be True or False 24 | NEO4J_USER_AGENT="" 25 | ENABLE_USER_AGENT = "" 26 | LLM_MODEL_CONFIG_model_version="" 27 | ENTITY_EMBEDDING="" True or False 28 | #examples 29 | LLM_MODEL_CONFIG_azure_ai_gpt_35="azure_deployment_name,azure_endpoint or base_url,azure_api_key,api_version" 30 | LLM_MODEL_CONFIG_azure_ai_gpt_4o="gpt-4o,https://YOUR-ENDPOINT.openai.azure.com/,azure_api_key,api_version" 31 | LLM_MODEL_CONFIG_groq_llama3_70b="model_name,base_url,groq_api_key" 32 | LLM_MODEL_CONFIG_anthropic_claude_3_5_sonnet="model_name,anthropic_api_key" 33 | LLM_MODEL_CONFIG_fireworks_llama_v3_70b="model_name,fireworks_api_key" 34 | LLM_MODEL_CONFIG_bedrock_claude_3_5_sonnet="model_name,aws_access_key_id,aws_secret__access_key,region_name" 35 | LLM_MODEL_CONFIG_ollama_llama3="model_name,model_local_url" 36 | 37 | -------------------------------------------------------------------------------- /frontend/src/assets/ChatbotMessages.json: -------------------------------------------------------------------------------- 1 | { 2 | "listMessages": [ 3 | { 4 | "id": 1, 5 | "message": "Hi, I need help with creating a Cypher query for Neo4j.", 6 | "user": "user", 7 | "datetime": "01/01/2024 00:00:00" 8 | }, 9 | { 10 | "id": 2, 11 | "message": " Welcome to the Neo4j Knowledge Graph Chat. You can ask questions related to documents which have been completely processed.", 12 | "user": "chatbot", 13 | "datetime": "01/01/2024 00:00:00" 14 | }, 15 | { 16 | "id": 3, 17 | "message": "I need to find all employees who work in the IT department.", 18 | "user": "user", 19 | "datetime": "01/01/2024 00:00:00" 20 | }, 21 | { 22 | "id": 4, 23 | "message": "Alright, you can use the following query: `MATCH (e:Employee)-[:WORKS_IN]->(d:Department {name: 'IT'}) RETURN e.name`. This query matches nodes labeled 'Employee' related to the 'IT' department and returns their names.", 24 | "user": "chatbot", 25 | "datetime": "01/01/2024 00:00:00" 26 | }, 27 | { 28 | "id": 5, 29 | "message": "Thanks! And how do I get the total number of such employees?", 30 | "user": "user", 31 | "datetime": "01/01/2024 00:00:00" 32 | }, 33 | { 34 | "id": 6, 35 | "message": "To get the count, use: `MATCH (e:Employee)-[:WORKS_IN]->(d:Department {name: 'IT'}) RETURN count(e)`. This counts all the distinct 'Employee' nodes related to the 'IT' department.", 36 | "user": "chatbot", 37 | "datetime": "01/01/2024 00:00:00" 38 | } 39 | ] 40 | } -------------------------------------------------------------------------------- /frontend/src/components/UI/ErrroBoundary.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Banner } from '@neo4j-ndl/react'; 3 | 4 | export default class ErrorBoundary extends React.Component { 5 | state = { hasError: false, errorMessage: '' }; 6 | 7 | static getDerivedStateFromError(_error: unknown) { 8 | return { hasError: true }; 9 | } 10 | 11 | componentDidCatch(error: Error, errorInfo: any) { 12 | this.setState({ ...this.state, errorMessage: error.message }); 13 | console.log({ error }); 14 | console.log({ errorInfo }); 15 | } 16 | 17 | render() { 18 | if (this.state.hasError) { 19 | return ( 20 |
21 | 40 |
41 | ); 42 | } 43 | return this.props.children; 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /frontend/src/assets/images/youtube.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Youtube-color 6 | Created with Sketch. 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /frontend/src/assets/images/youtube-lightmode.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /frontend/src/components/ChatBot/ChatModeToggle.tsx: -------------------------------------------------------------------------------- 1 | import { StatusIndicator } from '@neo4j-ndl/react'; 2 | import { useMemo } from 'react'; 3 | import { useFileContext } from '../../context/UsersFiles'; 4 | import CustomMenu from '../UI/Menu'; 5 | import { chatModes } from '../../utils/Constants'; 6 | import { capitalize } from '@mui/material'; 7 | 8 | export default function ChatModeToggle({ 9 | menuAnchor, 10 | closeHandler = () => {}, 11 | open, 12 | anchorPortal = true, 13 | disableBackdrop = false, 14 | }: { 15 | menuAnchor: HTMLElement | null; 16 | closeHandler?: () => void; 17 | open: boolean; 18 | anchorPortal?: boolean; 19 | disableBackdrop?: boolean; 20 | }) { 21 | const { setchatMode, chatMode } = useFileContext(); 22 | 23 | return ( 24 | 32 | chatModes?.map((m) => { 33 | return { 34 | title: m.includes('+') ? 'Graph + Vector' : capitalize(m), 35 | onClick: () => { 36 | setchatMode(m); 37 | }, 38 | disabledCondition: false, 39 | description: ( 40 | 41 | {chatMode === m && ( 42 | <> 43 | Selected 44 | 45 | )} 46 | 47 | ), 48 | }; 49 | }), 50 | [chatMode, chatModes] 51 | )} 52 | > 53 | ); 54 | } 55 | -------------------------------------------------------------------------------- /frontend/src/context/Alert.tsx: -------------------------------------------------------------------------------- 1 | import { createContext, useState, useContext, FunctionComponent, ReactNode } from 'react'; 2 | import { alertStateType } from '../types'; 3 | import { OverridableStringUnion } from '@mui/types'; 4 | import { AlertColor, AlertPropsColorOverrides } from '@mui/material'; 5 | 6 | type Props = { 7 | children: ReactNode; 8 | }; 9 | 10 | interface ContextProps { 11 | alertState: alertStateType; 12 | showAlert: ( 13 | alertType: OverridableStringUnion | undefined, 14 | alertMessage: string 15 | ) => void; 16 | closeAlert: () => void; 17 | } 18 | export const alertContext = createContext({ 19 | alertState: { showAlert: false, alertMessage: '', alertType: 'info' }, 20 | closeAlert: () => {}, 21 | showAlert: () => {}, 22 | }); 23 | export const useAlertContext = () => { 24 | const alertCtx = useContext(alertContext); 25 | return alertCtx; 26 | }; 27 | const AlertContextWrapper: FunctionComponent = (props) => { 28 | const [alertState, setAlertState] = useState({ 29 | showAlert: false, 30 | alertMessage: '', 31 | alertType: 'info', 32 | }); 33 | const showAlert = ( 34 | alertType: OverridableStringUnion | undefined, 35 | alertMessage: string 36 | ) => { 37 | setAlertState({ 38 | showAlert: true, 39 | alertType, 40 | alertMessage, 41 | }); 42 | }; 43 | const closeAlert = () => { 44 | setAlertState({ 45 | showAlert: false, 46 | alertType: 'info', 47 | alertMessage: '', 48 | }); 49 | }; 50 | const value = { 51 | alertState, 52 | showAlert, 53 | closeAlert, 54 | }; 55 | return {props.children}; 56 | }; 57 | export default AlertContextWrapper; 58 | -------------------------------------------------------------------------------- /backend/src/create_chunks.py: -------------------------------------------------------------------------------- 1 | from langchain_text_splitters import TokenTextSplitter 2 | from langchain.docstore.document import Document 3 | from langchain_community.graphs import Neo4jGraph 4 | import logging 5 | import os 6 | from src.document_sources.youtube import get_chunks_with_timestamps 7 | 8 | logging.basicConfig(format="%(asctime)s - %(message)s", level="INFO") 9 | 10 | 11 | class CreateChunksofDocument: 12 | def __init__(self, pages: list[Document], graph: Neo4jGraph): 13 | self.pages = pages 14 | self.graph = graph 15 | 16 | def split_file_into_chunks(self): 17 | """ 18 | Split a list of documents(file pages) into chunks of fixed size. 19 | 20 | Args: 21 | pages: A list of pages to split. Each page is a list of text strings. 22 | 23 | Returns: 24 | A list of chunks each of which is a langchain Document. 25 | """ 26 | logging.info("Split file into smaller chunks") 27 | # number_of_chunks_allowed = int(os.environ.get('NUMBER_OF_CHUNKS_ALLOWED')) 28 | text_splitter = TokenTextSplitter(chunk_size=200, chunk_overlap=20) 29 | if 'page' in self.pages[0].metadata: 30 | chunks = [] 31 | for i, document in enumerate(self.pages): 32 | page_number = i + 1 33 | for chunk in text_splitter.split_documents([document]): 34 | chunks.append(Document(page_content=chunk.page_content, metadata={'page_number':page_number})) 35 | 36 | elif 'length' in self.pages[0].metadata: 37 | chunks_without_timestamps = text_splitter.split_documents(self.pages) 38 | chunks = get_chunks_with_timestamps(chunks_without_timestamps, self.pages[0].metadata['source']) 39 | else: 40 | chunks = text_splitter.split_documents(self.pages) 41 | return chunks -------------------------------------------------------------------------------- /frontend/src/assets/images/youtube-darkmode.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /backend/src/generate_graphDocuments_from_llm.py: -------------------------------------------------------------------------------- 1 | from langchain_community.graphs import Neo4jGraph 2 | from src.diffbot_transformer import get_graph_from_diffbot 3 | from src.openAI_llm import get_graph_from_OpenAI 4 | from src.gemini_llm import get_graph_from_Gemini 5 | from typing import List 6 | import logging 7 | from src.shared.constants import * 8 | import os 9 | from src.llm import get_graph_from_llm 10 | 11 | logging.basicConfig(format="%(asctime)s - %(message)s", level="INFO") 12 | 13 | 14 | def generate_graphDocuments(model: str, graph: Neo4jGraph, chunkId_chunkDoc_list: List, allowedNodes=None, allowedRelationship=None): 15 | 16 | if allowedNodes is None or allowedNodes=="": 17 | allowedNodes =[] 18 | else: 19 | allowedNodes = allowedNodes.split(',') 20 | if allowedRelationship is None or allowedRelationship=="": 21 | allowedRelationship=[] 22 | else: 23 | allowedRelationship = allowedRelationship.split(',') 24 | 25 | logging.info(f"allowedNodes: {allowedNodes}, allowedRelationship: {allowedRelationship}") 26 | 27 | graph_documents = [] 28 | if model == "diffbot": 29 | graph_documents = get_graph_from_diffbot(graph, chunkId_chunkDoc_list) 30 | 31 | elif model in OPENAI_MODELS: 32 | graph_documents = get_graph_from_OpenAI(model, graph, chunkId_chunkDoc_list, allowedNodes, allowedRelationship) 33 | 34 | elif model in GEMINI_MODELS: 35 | graph_documents = get_graph_from_Gemini(model, graph, chunkId_chunkDoc_list, allowedNodes, allowedRelationship) 36 | 37 | # elif model in GROQ_MODELS : 38 | # graph_documents = get_graph_from_Groq_Llama3(MODEL_VERSIONS[model], graph, chunkId_chunkDoc_list, allowedNodes, allowedRelationship) 39 | 40 | else : 41 | graph_documents = get_graph_from_llm(model,chunkId_chunkDoc_list, allowedNodes, allowedRelationship) 42 | 43 | logging.info(f"graph_documents = {len(graph_documents)}") 44 | return graph_documents 45 | -------------------------------------------------------------------------------- /frontend/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "neo4j-needle-starterkit", 3 | "private": true, 4 | "version": "1.0.0", 5 | "type": "module", 6 | "scripts": { 7 | "dev": "vite --host 0.0.0.0", 8 | "build": "tsc && vite build", 9 | "format": "prettier --write \"**/*.{ts,tsx}\"", 10 | "lint": "eslint --ext .ts --ext .tsx . --report-unused-disable-directives --max-warnings 0", 11 | "preview": "vite preview" 12 | }, 13 | "dependencies": { 14 | "@emotion/styled": "^11.11.0", 15 | "@mui/material": "^5.15.10", 16 | "@mui/styled-engine": "^5.15.9", 17 | "@neo4j-devtools/word-color": "^0.0.8", 18 | "@neo4j-ndl/base": "^2.11.6", 19 | "@neo4j-ndl/react": "^2.15.10", 20 | "@neo4j-nvl/base": "^0.3.1", 21 | "@neo4j-nvl/react": "^0.3.1", 22 | "@react-oauth/google": "^0.12.1", 23 | "@types/uuid": "^9.0.7", 24 | "axios": "^1.6.5", 25 | "clsx": "^2.1.1", 26 | "eslint-plugin-react": "^7.33.2", 27 | "neo4j-driver": "^5.14.0", 28 | "re-resizable": "^6.9.16", 29 | "react": "^18.3.1", 30 | "react-dom": "^18.3.1", 31 | "react-icons": "^5.2.1", 32 | "react-markdown": "^9.0.1", 33 | "react-router": "^6.23.1", 34 | "react-router-dom": "^6.23.1", 35 | "remark-gfm": "^4.0.0", 36 | "tailwind-merge": "^2.3.0", 37 | "uuid": "^9.0.1" 38 | }, 39 | "devDependencies": { 40 | "@types/node": "^20.11.10", 41 | "@types/react": "^18.2.15", 42 | "@types/react-dom": "^18.2.7", 43 | "@typescript-eslint/eslint-plugin": "^6.0.0", 44 | "@typescript-eslint/parser": "^6.0.0", 45 | "@vitejs/plugin-react": "^4.0.3", 46 | "autoprefixer": "^10.4.17", 47 | "eslint": "^8.45.0", 48 | "eslint-config-prettier": "^8.5.0", 49 | "eslint-plugin-react-hooks": "^4.6.0", 50 | "eslint-plugin-react-refresh": "^0.4.3", 51 | "postcss": "^8.4.33", 52 | "prettier": "^2.7.1", 53 | "react-dropzone": "^14.2.3", 54 | "tailwindcss": "^3.4.1", 55 | "typescript": "^5.0.2", 56 | "vite": "^4.5.3" 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /frontend/src/context/ThemeWrapper.tsx: -------------------------------------------------------------------------------- 1 | import { ReactNode, createContext, useMemo, useState } from 'react'; 2 | import { NeedleThemeProvider, useMediaQuery } from '@neo4j-ndl/react'; 3 | 4 | export const ThemeWrapperContext = createContext({ 5 | toggleColorMode: () => {}, 6 | colorMode: localStorage.getItem('mode') as 'light' | 'dark', 7 | }); 8 | 9 | interface ThemeWrapperProps { 10 | children: ReactNode; 11 | } 12 | const ThemeWrapper = ({ children }: ThemeWrapperProps) => { 13 | const prefersDarkMode = useMediaQuery('(prefers-color-scheme: dark)'); 14 | // @ts-ignore 15 | const defaultMode: 'light' | 'dark' = localStorage.getItem('mode'); 16 | const [mode, setMode] = useState<'light' | 'dark'>(prefersDarkMode ? 'dark' : defaultMode ?? 'light'); 17 | const [usingPreferredMode, setUsingPreferredMode] = useState(true); 18 | const themeWrapperUtils = useMemo( 19 | () => ({ 20 | colorMode: mode, 21 | toggleColorMode: () => { 22 | setMode((prevMode) => { 23 | setUsingPreferredMode(false); 24 | localStorage.setItem('mode', prevMode === 'light' ? 'dark' : 'light'); 25 | themeBodyInjection(prevMode); 26 | return prevMode === 'light' ? 'dark' : 'light'; 27 | }); 28 | }, 29 | }), 30 | [mode] 31 | ); 32 | const themeBodyInjection = (mode: string) => { 33 | if (mode === 'light') { 34 | document.body.classList.add('ndl-theme-dark'); 35 | } else { 36 | document.body.classList.remove('ndl-theme-dark'); 37 | } 38 | }; 39 | 40 | if (usingPreferredMode) { 41 | prefersDarkMode ? themeBodyInjection('light') : themeBodyInjection('dark'); 42 | } 43 | 44 | return ( 45 | 46 | 47 | {children} 48 | 49 | 50 | ); 51 | }; 52 | export default ThemeWrapper; 53 | -------------------------------------------------------------------------------- /frontend/src/components/Popups/DeletePopUp/DeletePopUp.tsx: -------------------------------------------------------------------------------- 1 | import { Button, Checkbox, Dialog } from '@neo4j-ndl/react'; 2 | import { useState } from 'react'; 3 | export default function DeletePopUp({ 4 | open, 5 | no_of_files, 6 | deleteHandler, 7 | deleteCloseHandler, 8 | loading, 9 | view, 10 | }: { 11 | open: boolean; 12 | no_of_files: number; 13 | deleteHandler: (delentities: boolean) => void; 14 | deleteCloseHandler: () => void; 15 | loading: boolean; 16 | view?: 'contentView' | 'settingsView'; 17 | }) { 18 | const [deleteEntities, setDeleteEntities] = useState(true); 19 | const message = 20 | view === 'contentView' 21 | ? `Are you sure you want to permanently delete ${no_of_files} ${no_of_files > 1 ? 'Files' : 'File'} ${ 22 | deleteEntities ? 'and associated entities' : '' 23 | } from the graph database?` 24 | : `Are you sure you want to permanently delete ${no_of_files} ${ 25 | no_of_files > 1 ? 'Nodes' : 'Node' 26 | } from the graph database? `; 27 | return ( 28 | 29 | 30 |
{message}
31 | {view === 'contentView' && ( 32 |
33 | { 37 | if (e.target.checked) { 38 | setDeleteEntities(true); 39 | } else { 40 | setDeleteEntities(false); 41 | } 42 | }} 43 | /> 44 |
45 | )} 46 |
47 | 48 | 51 | 54 | 55 |
56 | ); 57 | } -------------------------------------------------------------------------------- /frontend/src/components/WebSources/CustomSourceInput.tsx: -------------------------------------------------------------------------------- 1 | import { Banner, Box, Button, Flex, TextInput } from '@neo4j-ndl/react'; 2 | import { CustomInput } from '../../types'; 3 | 4 | export default function CustomSourceInput({ 5 | value, 6 | label, 7 | placeHolder, 8 | onChangeHandler, 9 | submitHandler, 10 | disabledCheck, 11 | onCloseHandler, 12 | id, 13 | onBlurHandler, 14 | status, 15 | setStatus, 16 | statusMessage, 17 | isValid, 18 | isFocused, 19 | onPasteHandler, 20 | }: CustomInput) { 21 | return ( 22 | 23 | {status !== 'unknown' && ( 24 | 25 | setStatus('unknown')} 29 | type={status} 30 | name='Custom Banner' 31 | className='text-lg font-semibold' 32 | /> 33 | 34 | )} 35 | 36 |
37 | 52 |
53 |
54 | 55 |
56 | 66 | 69 |
70 |
71 |
72 | ); 73 | } 74 | -------------------------------------------------------------------------------- /frontend/src/components/WebSources/GenericSourceButton.tsx: -------------------------------------------------------------------------------- 1 | import CustomButton from '../UI/CustomButton'; 2 | import internet from '../../assets/images/web-search-svgrepo-com.svg'; 3 | import internetdarkmode from '../../assets/images/web-search-darkmode-final2.svg'; 4 | import { DataComponentProps } from '../../types'; 5 | import { Flex, Typography } from '@neo4j-ndl/react'; 6 | import IconButtonWithToolTip from '../UI/IconButtonToolTip'; 7 | import { InformationCircleIconOutline } from '@neo4j-ndl/react/icons'; 8 | import { APP_SOURCES } from '../../utils/Constants'; 9 | import { useContext } from 'react'; 10 | import { ThemeWrapperContext } from '../../context/ThemeWrapper'; 11 | 12 | export default function GenericButton({ openModal }: DataComponentProps) { 13 | const themeUtils = useContext(ThemeWrapperContext); 14 | 15 | return ( 16 | 17 | 23 | 24 | 25 | Web Sources 26 |
27 | 32 | 33 | {APP_SOURCES != undefined && APP_SOURCES.includes('youtube') && Youtube} 34 | {APP_SOURCES != undefined && APP_SOURCES.includes('wiki') && Wikipedia} 35 | {APP_SOURCES != undefined && APP_SOURCES.includes('web') && Website} 36 | 37 | 38 | } 39 | > 40 | 41 | 42 |
43 |
44 |
45 |
46 | ); 47 | } 48 | -------------------------------------------------------------------------------- /backend/README.md: -------------------------------------------------------------------------------- 1 | # Project Overview 2 | Welcome to our project! This project is built using FastAPI framework to create a fast and modern API with Python. 3 | 4 | ## Feature 5 | API Endpoint : This project provides various API endpoint to perform specific tasks. 6 | Data Validation : Utilize FastAPI data validation and serialization feature. 7 | Interactive Documentation : Access Swagger UI and ReDoc for interactive API documentation. 8 | 9 | ## Getting Started 10 | 11 | Follow these steps to set up and run the project locally: 12 | 13 | 1. Clone the Repository: 14 | 15 | > git clone https://github.com/neo4j-labs/llm-graph-builder.git 16 | 17 | > cd llm-graph-builder 18 | 19 | 2. Install Dependency : 20 | 21 | > pip install -t requirements.txt 22 | 23 | ## Run backend project using unicorn 24 | Run the server: 25 | > uvicorn score:app --reload 26 | 27 | ## Run project using docker 28 | ## prerequisite 29 | Before proceeding, ensure the following software is installed on your machine 30 | 31 | Docker: https://www.docker.com/ 32 | 33 | 1. Build the docker image 34 | > docker build -t your_image_name . 35 | 36 | Replace `your_image_name` with the meaningful name for your Docker image 37 | 38 | 2. Run the Docker Container 39 | > docker run -it -p 8000:8000 your_image_name 40 | 41 | Replace `8000` with the desired port. 42 | 43 | ## Access the API Documentation 44 | Open your browser and navigate to 45 | http://127.0.0.1:8000/docs for Swagger UI or 46 | http://127.0.0.1:8000/redocs for ReDoc. 47 | 48 | ## Project Structure 49 | `score.py`: Score entry point for FastAPI application 50 | 51 | ## Configuration 52 | 53 | Update the environment variable in `.env` file. 54 | 55 | `OPENAI_API_KEY`: Open AI key to use LLM 56 | 57 | `DIFFBOT_API_KEY` : Diffbot API key to use DiffbotGraphTransformer 58 | 59 | `NEO4J_URI` : Neo4j URL 60 | 61 | `NEO4J_USERNAME` : Neo4J database username 62 | 63 | `NEO4J_PASSWORD` : Neo4j database user password 64 | 65 | `AWS_ACCESS_KEY_ID` : AWS Access key ID 66 | 67 | `AWS_SECRET_ACCESS_KEY` : AWS secret access key 68 | 69 | 70 | ## Contact 71 | For questions or support, feel free to contact us at christopher.crosbie@neo4j.com or michael.hunger@neo4j.com 72 | -------------------------------------------------------------------------------- /frontend/src/assets/images/web-search-svgrepo-com (2).svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /frontend/src/services/QnaAPI.ts: -------------------------------------------------------------------------------- 1 | import axios from 'axios'; 2 | import { url } from '../utils/Utils'; 3 | import { UserCredentials } from '../types'; 4 | 5 | export const chatBotAPI = async ( 6 | userCredentials: UserCredentials, 7 | question: string, 8 | session_id: string, 9 | model: string, 10 | mode: string, 11 | document_names?: (string | undefined)[] 12 | ) => { 13 | try { 14 | const formData = new FormData(); 15 | formData.append('uri', userCredentials?.uri ?? ''); 16 | formData.append('database', userCredentials?.database ?? ''); 17 | formData.append('userName', userCredentials?.userName ?? ''); 18 | formData.append('password', userCredentials?.password ?? ''); 19 | formData.append('question', question); 20 | formData.append('session_id', session_id); 21 | formData.append('model', model); 22 | formData.append('mode', mode); 23 | formData.append('document_names', JSON.stringify(document_names)); 24 | const startTime = Date.now(); 25 | const response = await axios.post(`${url()}/chat_bot`, formData, { 26 | headers: { 27 | 'Content-Type': 'multipart/form-data', 28 | }, 29 | }); 30 | const endTime = Date.now(); 31 | const timeTaken = endTime - startTime; 32 | return { response: response, timeTaken: timeTaken }; 33 | } catch (error) { 34 | console.log('Error Posting the Question:', error); 35 | throw error; 36 | } 37 | }; 38 | 39 | export const clearChatAPI = async (userCredentials: UserCredentials, session_id: string) => { 40 | try { 41 | const formData = new FormData(); 42 | formData.append('uri', userCredentials?.uri ?? ''); 43 | formData.append('database', userCredentials?.database ?? ''); 44 | formData.append('userName', userCredentials?.userName ?? ''); 45 | formData.append('password', userCredentials?.password ?? ''); 46 | formData.append('session_id', session_id); 47 | const response = await axios.post(`${url()}/clear_chat_bot`, formData, { 48 | headers: { 49 | 'Content-Type': 'multipart/form-data', 50 | }, 51 | }); 52 | return response; 53 | } catch (error) { 54 | console.log('Error Posting the Question:', error); 55 | throw error; 56 | } 57 | }; 58 | -------------------------------------------------------------------------------- /frontend/src/components/ChatBot/ExpandedChatButtonContainer.tsx: -------------------------------------------------------------------------------- 1 | import { TrashIconOutline, XMarkIconOutline } from '@neo4j-ndl/react/icons'; 2 | import ChatModeToggle from './ChatModeToggle'; 3 | import { Box, IconButton } from '@neo4j-ndl/react'; 4 | import { Messages } from '../../types'; 5 | import IconButtonWithToolTip from '../UI/IconButtonToolTip'; 6 | import { tooltips } from '../../utils/Constants'; 7 | import { useState } from 'react'; 8 | import { RiChatSettingsLine } from 'react-icons/ri'; 9 | 10 | interface IconProps { 11 | closeChatBot: () => void; 12 | deleteOnClick?: () => void; 13 | messages: Messages[]; 14 | } 15 | 16 | const ExpandedChatButtonContainer: React.FC = ({ closeChatBot, deleteOnClick, messages }) => { 17 | const [chatAnchor, setchatAnchor] = useState(null); 18 | const [showChatModeOption, setshowChatModeOption] = useState(false); 19 | return ( 20 |
21 | setshowChatModeOption(false)} 23 | anchorPortal={true} 24 | disableBackdrop={true} 25 | open={showChatModeOption} 26 | menuAnchor={chatAnchor} 27 | /> 28 | 29 | { 31 | setchatAnchor(e.currentTarget); 32 | setshowChatModeOption(true); 33 | }} 34 | clean 35 | text='Chat mode' 36 | placement='bottom' 37 | label='Chat mode' 38 | > 39 | 40 | 41 | 50 | 51 | 52 | 53 | 54 | 55 | 56 |
57 | ); 58 | }; 59 | 60 | export default ExpandedChatButtonContainer; 61 | -------------------------------------------------------------------------------- /backend/src/shared/schema_extraction.py: -------------------------------------------------------------------------------- 1 | from typing import List 2 | from langchain_core.pydantic_v1 import BaseModel, Field 3 | from src.llm import get_llm 4 | from src.shared.constants import MODEL_VERSIONS 5 | from langchain_core.prompts import ChatPromptTemplate 6 | 7 | class Schema(BaseModel): 8 | """Knowledge Graph Schema.""" 9 | 10 | labels: List[str] = Field(description="list of node labels or types in a graph schema") 11 | relationshipTypes: List[str] = Field(description="list of relationship types in a graph schema") 12 | 13 | PROMPT_TEMPLATE_WITH_SCHEMA = ( 14 | "You are an expert in schema extraction, especially for extracting graph schema information from various formats." 15 | "Generate the generalized graph schema based on input text. Identify key entities and their relationships and " 16 | "provide a generalized label for the overall context" 17 | "Schema representations formats can contain extra symbols, quotes, or comments. Ignore all that extra markup." 18 | "Only return the string types for nodes and relationships. Don't return attributes." 19 | ) 20 | 21 | PROMPT_TEMPLATE_WITHOUT_SCHEMA = ( 22 | "You are an expert in schema extraction, especially for deriving graph schema information from example texts." 23 | "Analyze the following text and extract only the types of entities and relationships from the example prose." 24 | "Don't return any actual entities like people's names or instances of organizations." 25 | "Only return the string types for nodes and relationships, don't return attributes." 26 | ) 27 | 28 | def schema_extraction_from_text(input_text:str, model:str, is_schema_description_cheked:bool): 29 | 30 | llm, model_name = get_llm(model) 31 | if is_schema_description_cheked: 32 | schema_prompt = PROMPT_TEMPLATE_WITH_SCHEMA 33 | else: 34 | schema_prompt = PROMPT_TEMPLATE_WITHOUT_SCHEMA 35 | 36 | prompt = ChatPromptTemplate.from_messages( 37 | [("system", schema_prompt), ("user", "{text}")] 38 | ) 39 | 40 | runnable = prompt | llm.with_structured_output( 41 | schema=Schema, 42 | method="function_calling", 43 | include_raw=False, 44 | ) 45 | 46 | raw_schema = runnable.invoke({"text": input_text}) 47 | return raw_schema -------------------------------------------------------------------------------- /frontend/src/services/URLScan.ts: -------------------------------------------------------------------------------- 1 | import axios from 'axios'; 2 | import { url } from '../utils/Utils'; 3 | import { ScanProps, ServerResponse } from '../types'; 4 | 5 | const urlScanAPI = async (props: ScanProps) => { 6 | try { 7 | const formData = new FormData(); 8 | let s3url: string = ''; 9 | if (props.source_type === 's3 bucket') { 10 | if (!props.urlParam?.endsWith('/')) { 11 | s3url = `${props?.urlParam}/`; 12 | } else { 13 | s3url = props?.urlParam; 14 | } 15 | } 16 | formData.append('uri', props?.userCredentials?.uri ?? ''); 17 | formData.append('database', props?.userCredentials?.database ?? ''); 18 | formData.append('userName', props?.userCredentials?.userName ?? ''); 19 | formData.append('password', props?.userCredentials?.password ?? ''); 20 | if (props.source_type === 's3 bucket') { 21 | formData.append('source_url', s3url ?? ''); 22 | } else { 23 | formData.append('source_url', props?.urlParam ?? ''); 24 | } 25 | formData.append('wiki_query', decodeURIComponent(props?.wikiquery ?? '')); 26 | formData.append('source_type', props?.source_type ?? ''); 27 | if (props.model != undefined) { 28 | formData.append('model', props?.model); 29 | } 30 | if (props.accessKey?.length) { 31 | formData.append('aws_access_key_id', props?.accessKey); 32 | } 33 | if (props?.secretKey?.length) { 34 | formData.append('aws_secret_access_key', props?.secretKey); 35 | } 36 | if (props?.gcs_bucket_name) { 37 | formData.append('gcs_bucket_name', props.gcs_bucket_name); 38 | } 39 | if (props?.gcs_bucket_folder) { 40 | formData.append('gcs_bucket_folder', props.gcs_bucket_folder); 41 | } 42 | if (props?.gcs_project_id) { 43 | formData.append('gcs_project_id', props.gcs_project_id); 44 | } 45 | if (props?.access_token) { 46 | formData.append('access_token', props.access_token); 47 | } 48 | 49 | const response: ServerResponse = await axios.post(`${url()}/url/scan`, formData, { 50 | headers: { 51 | 'Content-Type': 'multipart/form-data', 52 | }, 53 | }); 54 | return response; 55 | } catch (error) { 56 | console.log('Error uploading file:', error); 57 | throw error; 58 | } 59 | }; 60 | 61 | export { urlScanAPI }; 62 | -------------------------------------------------------------------------------- /backend/src/groq_llama3_llm.py: -------------------------------------------------------------------------------- 1 | from langchain_community.graphs import Neo4jGraph 2 | from dotenv import load_dotenv 3 | import os 4 | import logging 5 | import concurrent.futures 6 | from concurrent.futures import ThreadPoolExecutor 7 | from typing import List 8 | from langchain_experimental.graph_transformers import LLMGraphTransformer 9 | from langchain_core.documents import Document 10 | from src.llm import get_combined_chunks, get_llm 11 | 12 | load_dotenv() 13 | logging.basicConfig(format='%(asctime)s - %(message)s',level='INFO') 14 | 15 | def get_graph_from_Groq_Llama3(model_version, 16 | graph: Neo4jGraph, 17 | chunkId_chunkDoc_list: List, 18 | allowedNodes, 19 | allowedRelationship): 20 | """ 21 | Extract graph from Groq Llama3 and store it in database. 22 | This is a wrapper for extract_and_store_graph 23 | 24 | Args: 25 | model_version : identify the model of LLM 26 | graph: Neo4jGraph to be extracted. 27 | chunks: List of chunk documents created from input file 28 | Returns: 29 | List of langchain GraphDocument - used to generate graph 30 | """ 31 | logging.info(f"Get graphDocuments from {model_version}") 32 | futures = [] 33 | graph_document_list = [] 34 | combined_chunk_document_list = get_combined_chunks(chunkId_chunkDoc_list) 35 | #api_key = os.environ.get('GROQ_API_KEY') 36 | llm,model_name = get_llm(model_version) 37 | llm_transformer = LLMGraphTransformer(llm=llm, node_properties=["description"], allowed_nodes=allowedNodes, allowed_relationships=allowedRelationship) 38 | 39 | with ThreadPoolExecutor(max_workers=10) as executor: 40 | for chunk in combined_chunk_document_list: 41 | chunk_doc = Document(page_content= chunk.page_content.encode("utf-8"), metadata=chunk.metadata) 42 | futures.append(executor.submit(llm_transformer.convert_to_graph_documents,[chunk_doc])) 43 | 44 | for i, future in enumerate(concurrent.futures.as_completed(futures)): 45 | graph_document = future.result() 46 | graph_document_list.append(graph_document[0]) 47 | 48 | return graph_document_list -------------------------------------------------------------------------------- /frontend/src/components/Dropdown.tsx: -------------------------------------------------------------------------------- 1 | import { Dropdown } from '@neo4j-ndl/react'; 2 | import { DropdownProps, OptionType } from '../types'; 3 | import { useMemo } from 'react'; 4 | import { capitalize } from '../utils/Utils'; 5 | interface ReusableDropdownProps extends DropdownProps { 6 | options: string[] | OptionType[]; 7 | placeholder?: string; 8 | defaultValue?: string; 9 | children?: React.ReactNode; 10 | view?: 'ContentView' | 'GraphView'; 11 | isDisabled: boolean; 12 | value?: OptionType; 13 | } 14 | const DropdownComponent: React.FC = ({ 15 | options, 16 | placeholder, 17 | defaultValue, 18 | onSelect, 19 | children, 20 | view, 21 | isDisabled, 22 | value, 23 | }) => { 24 | const handleChange = (selectedOption: OptionType | null | void) => { 25 | onSelect(selectedOption); 26 | }; 27 | const allOptions = useMemo(() => options, [options]); 28 | return ( 29 | <> 30 |
31 | { 37 | const label = 38 | typeof option === 'string' 39 | ? (option.includes('LLM_MODEL_CONFIG_') 40 | ? capitalize(option.split('LLM_MODEL_CONFIG_').at(-1) as string) 41 | : capitalize(option) 42 | ) 43 | .split('_') 44 | .join(' ') 45 | : capitalize(option.label); 46 | const value = typeof option === 'string' ? option : option.value; 47 | return { 48 | label, 49 | value, 50 | }; 51 | }), 52 | placeholder: placeholder || 'Select an option', 53 | defaultValue: defaultValue ? { label: capitalize(defaultValue), value: defaultValue } : undefined, 54 | menuPlacement: 'auto', 55 | isDisabled: isDisabled, 56 | value: value, 57 | }} 58 | size='medium' 59 | fluid 60 | /> 61 | {children} 62 |
63 | 64 | ); 65 | }; 66 | export default DropdownComponent; 67 | -------------------------------------------------------------------------------- /frontend/src/components/QuickStarter.tsx: -------------------------------------------------------------------------------- 1 | import Header from './Layout/Header'; 2 | import React, { useState } from 'react'; 3 | import { ThemeWrapperContext } from '../context/ThemeWrapper'; 4 | import PageLayout from './Layout/PageLayout'; 5 | import { FileContextProvider } from '../context/UsersFiles'; 6 | import UserCredentialsWrapper from '../context/UserCredentials'; 7 | import AlertContextWrapper from '../context/Alert'; 8 | import { MessageContextWrapper } from '../context/UserMessages'; 9 | 10 | const QuickStarter: React.FunctionComponent = () => { 11 | const themeUtils = React.useContext(ThemeWrapperContext); 12 | const [themeMode, setThemeMode] = useState(themeUtils.colorMode); 13 | const [showSettingsModal, setshowSettingsModal] = useState(false); 14 | const [showOrphanNodeDeletionDialog, setshowOrphanNodeDeletionDialog] = useState(false); 15 | 16 | const toggleColorMode = () => { 17 | setThemeMode((prevThemeMode) => { 18 | return prevThemeMode === 'light' ? 'dark' : 'light'; 19 | }); 20 | themeUtils.toggleColorMode(); 21 | }; 22 | const openSettingsModal = () => { 23 | setshowSettingsModal(true); 24 | }; 25 | const closeSettingModal = () => { 26 | setshowSettingsModal(false); 27 | }; 28 | const openOrphanNodeDeletionModal = () => { 29 | setshowOrphanNodeDeletionDialog(true); 30 | }; 31 | const closeOrphanNodeDeletionModal = () => { 32 | setshowOrphanNodeDeletionDialog(false); 33 | }; 34 | return ( 35 | 36 | 37 | 38 | 39 |
40 | 48 | 49 | 50 | 51 | 52 | ); 53 | }; 54 | export default QuickStarter; 55 | -------------------------------------------------------------------------------- /backend/src/gemini_llm.py: -------------------------------------------------------------------------------- 1 | from langchain_community.graphs import Neo4jGraph 2 | from dotenv import load_dotenv 3 | from langchain.schema import Document 4 | import logging 5 | import concurrent.futures 6 | from concurrent.futures import ThreadPoolExecutor 7 | from typing import List 8 | from langchain_experimental.graph_transformers import LLMGraphTransformer 9 | from langchain_core.documents import Document 10 | from typing import List 11 | import google.auth 12 | from typing import List 13 | from langchain_core.documents import Document 14 | import vertexai 15 | from src.llm import get_graph_document_list, get_combined_chunks, get_llm 16 | 17 | load_dotenv() 18 | logging.basicConfig(format='%(asctime)s - %(message)s',level='DEBUG') 19 | 20 | 21 | def get_graph_from_Gemini(model_version, 22 | graph: Neo4jGraph, 23 | chunkId_chunkDoc_list: List, 24 | allowedNodes, 25 | allowedRelationship): 26 | """ 27 | Extract graph from OpenAI and store it in database. 28 | This is a wrapper for extract_and_store_graph 29 | 30 | Args: 31 | model_version : identify the model of LLM 32 | graph: Neo4jGraph to be extracted. 33 | chunks: List of chunk documents created from input file 34 | Returns: 35 | List of langchain GraphDocument - used to generate graph 36 | """ 37 | logging.info(f"Get graphDocuments from {model_version}") 38 | futures = [] 39 | graph_document_list = [] 40 | location = "us-central1" 41 | #project_id = "llm-experiments-387609" 42 | credentials, project_id = google.auth.default() 43 | if hasattr(credentials, "service_account_email"): 44 | logging.info(credentials.service_account_email) 45 | else: 46 | logging.info("WARNING: no service account credential. User account credential?") 47 | vertexai.init(project=project_id, location=location) 48 | 49 | combined_chunk_document_list = get_combined_chunks(chunkId_chunkDoc_list) 50 | 51 | llm,model_name = get_llm(model_version) 52 | return get_graph_document_list(llm, combined_chunk_document_list, allowedNodes, allowedRelationship) 53 | 54 | 55 | -------------------------------------------------------------------------------- /frontend/src/logo.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 8 | 9 | 11 | 14 | 17 | 19 | 23 | 29 | 31 | 32 | -------------------------------------------------------------------------------- /frontend/.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | lerna-debug.log* 8 | .pnpm-debug.log* 9 | 10 | # Diagnostic reports (https://nodejs.org/api/report.html) 11 | report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json 12 | 13 | # Runtime data 14 | pids 15 | *.pid 16 | *.seed 17 | *.pid.lock 18 | 19 | # Directory for instrumented libs generated by jscoverage/JSCover 20 | lib-cov 21 | 22 | # Coverage directory used by tools like istanbul 23 | coverage 24 | *.lcov 25 | 26 | # nyc test coverage 27 | .nyc_output 28 | 29 | # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) 30 | .grunt 31 | 32 | # Bower dependency directory (https://bower.io/) 33 | bower_components 34 | 35 | # node-waf configuration 36 | .lock-wscript 37 | 38 | # Compiled binary addons (https://nodejs.org/api/addons.html) 39 | build/Release 40 | 41 | # Dependency directories 42 | node_modules/ 43 | jspm_packages/ 44 | 45 | # Snowpack dependency directory (https://snowpack.dev/) 46 | web_modules/ 47 | 48 | # TypeScript cache 49 | *.tsbuildinfo 50 | 51 | # Optional npm cache directory 52 | .npm 53 | 54 | # Optional eslint cache 55 | .eslintcache 56 | 57 | # Optional stylelint cache 58 | .stylelintcache 59 | 60 | # Microbundle cache 61 | .rpt2_cache/ 62 | .rts2_cache_cjs/ 63 | .rts2_cache_es/ 64 | .rts2_cache_umd/ 65 | 66 | # Optional REPL history 67 | .node_repl_history 68 | 69 | # Output of 'npm pack' 70 | *.tgz 71 | 72 | # Yarn Integrity file 73 | .yarn-integrity 74 | 75 | # dotenv environment variable files 76 | .env 77 | .env.development.local 78 | .env.test.local 79 | .env.production.local 80 | .env.local 81 | 82 | # parcel-bundler cache (https://parceljs.org/) 83 | .cache 84 | .parcel-cache 85 | 86 | # Next.js build output 87 | .next 88 | out 89 | 90 | # Nuxt.js build / generate output 91 | .nuxt 92 | dist 93 | 94 | # Gatsby files 95 | .cache/ 96 | # Comment in the public line in if your project uses Gatsby and not Next.js 97 | # https://nextjs.org/blog/next-9-1#public-directory-support 98 | # public 99 | 100 | # vuepress build output 101 | .vuepress/dist 102 | 103 | # vuepress v2.x temp and cache directory 104 | .temp 105 | .cache 106 | 107 | # Docusaurus cache and generated files 108 | .docusaurus 109 | 110 | # Serverless directories 111 | .serverless/ 112 | 113 | # FuseBox cache 114 | .fusebox/ 115 | 116 | # DynamoDB Local files 117 | .dynamodb/ 118 | 119 | # TernJS port file 120 | .tern-port 121 | 122 | # Stores VSCode versions used for testing VSCode extensions 123 | .vscode-test 124 | 125 | # yarn v2 126 | .yarn/cache 127 | .yarn/unplugged 128 | .yarn/build-state.yml 129 | .yarn/install-state.gz 130 | .pnp.* 131 | package-lock.json 132 | 133 | docs/build -------------------------------------------------------------------------------- /frontend/src/components/UI/HoverableLink.tsx: -------------------------------------------------------------------------------- 1 | import React, { useState, useEffect, useRef } from 'react'; 2 | import { HoverableLinkProps } from '../../types'; 3 | const HoverableLink: React.FC = ({ url, children }) => { 4 | const [hovering, setHovering] = useState(false); 5 | const [iframeSrc, setIframeSrc] = useState(''); 6 | const [mousePosition, setMousePosition] = useState({ x: 0, y: 0 }); 7 | const popupRef = useRef(null); 8 | useEffect(() => { 9 | let timer: NodeJS.Timeout; 10 | if (hovering) { 11 | setIframeSrc(''); 12 | timer = setTimeout(() => { 13 | setIframeSrc(url); 14 | }, 100); 15 | } 16 | return () => clearTimeout(timer); 17 | }, [hovering, url]); 18 | const handleMouseEnter = (event: React.MouseEvent) => { 19 | setHovering(true); 20 | setMousePosition({ x: event.clientX, y: event.clientY }); 21 | }; 22 | const handleMouseMove = (event: React.MouseEvent) => { 23 | if (hovering) { 24 | setMousePosition({ x: event.clientX, y: event.clientY }); 25 | } 26 | }; 27 | const handleMouseLeave = () => { 28 | setHovering(false); 29 | }; 30 | const isYouTubeURL = (url: string): boolean => { 31 | return url.includes('youtube.com') || url.includes('youtu.be'); 32 | }; 33 | const extractYouTubeVideoId = (url: string): string => { 34 | const videoIdRegex = /(?:\/embed\/|\/watch\?v=|\/(?:embed\/|v\/|watch\?.*v=|youtu\.be\/|embed\/|v=))([^&?#]+)/; 35 | const match = url.match(videoIdRegex); 36 | return match ? match[1] : ''; 37 | }; 38 | return ( 39 |
45 | {children} 46 | {hovering && ( 47 |
52 | {isYouTubeURL(url) ? ( 53 |