1- import { Box , Typography , TextLink , Flex , Tabs , LoadingSpinner } from '@neo4j-ndl/react' ;
2- import { DocumentTextIconOutline } from '@neo4j-ndl/react/icons' ;
1+ import {
2+ Box ,
3+ Typography ,
4+ TextLink ,
5+ Flex ,
6+ Tabs ,
7+ LoadingSpinner ,
8+ CypherCodeBlock ,
9+ CypherCodeBlockProps ,
10+ useCopyToClipboard ,
11+ } from '@neo4j-ndl/react' ;
12+ import { DocumentDuplicateIconOutline , DocumentTextIconOutline } from '@neo4j-ndl/react/icons' ;
313import '../../styling/info.css' ;
414import Neo4jRetrievalLogo from '../../assets/images/Neo4jRetrievalLogo.png' ;
515import wikipedialogo from '../../assets/images/wikipedia.svg' ;
616import youtubelogo from '../../assets/images/youtube.svg' ;
717import gcslogo from '../../assets/images/gcs.webp' ;
818import s3logo from '../../assets/images/s3logo.png' ;
919import { Chunk , Entity , GroupedEntity , UserCredentials , chatInfoMessage } from '../../types' ;
10- import { useEffect , useMemo , useState } from 'react' ;
20+ import { useContext , useEffect , useMemo , useState } from 'react' ;
1121import HoverableLink from '../UI/HoverableLink' ;
1222import GraphViewButton from '../Graph/GraphViewButton' ;
1323import { chunkEntitiesAPI } from '../../services/ChunkEntitiesInfo' ;
@@ -17,44 +27,88 @@ import { calcWordColor } from '@neo4j-devtools/word-color';
1727import ReactMarkdown from 'react-markdown' ;
1828import { GlobeAltIconOutline } from '@neo4j-ndl/react/icons' ;
1929import { youtubeLinkValidation } from '../../utils/Utils' ;
30+ import { ThemeWrapperContext } from '../../context/ThemeWrapper' ;
31+ import { ClipboardDocumentCheckIconOutline } from '@neo4j-ndl/react/icons' ;
2032
21- const ChatInfoModal : React . FC < chatInfoMessage > = ( { sources, model, total_tokens, response_time, chunk_ids, mode } ) => {
22- const [ activeTab , setActiveTab ] = useState < number > ( 3 ) ;
33+ const ChatInfoModal : React . FC < chatInfoMessage > = ( {
34+ sources,
35+ model,
36+ total_tokens,
37+ response_time,
38+ chunk_ids,
39+ mode,
40+ cypher_query,
41+ graphonly_entities,
42+ } ) => {
43+ const [ activeTab , setActiveTab ] = useState < number > ( mode === 'graph' ? 4 : 3 ) ;
2344 const [ infoEntities , setInfoEntities ] = useState < Entity [ ] > ( [ ] ) ;
2445 const [ loading , setLoading ] = useState < boolean > ( false ) ;
2546 const { userCredentials } = useCredentials ( ) ;
2647 const [ nodes , setNodes ] = useState < Node [ ] > ( [ ] ) ;
2748 const [ relationships , setRelationships ] = useState < Relationship [ ] > ( [ ] ) ;
2849 const [ chunks , setChunks ] = useState < Chunk [ ] > ( [ ] ) ;
50+ const themeUtils = useContext ( ThemeWrapperContext ) ;
51+ const [ , copy ] = useCopyToClipboard ( ) ;
52+ const [ copiedText , setcopiedText ] = useState < boolean > ( false ) ;
53+
2954 const parseEntity = ( entity : Entity ) => {
3055 const { labels, properties } = entity ;
3156 const label = labels [ 0 ] ;
3257 const text = properties . id ;
3358 return { label, text } ;
3459 } ;
60+ const actions : CypherCodeBlockProps [ 'actions' ] = useMemo (
61+ ( ) => [
62+ {
63+ title : 'copy' ,
64+ 'aria-label' : 'copy' ,
65+ children : (
66+ < >
67+ { copiedText ? (
68+ < ClipboardDocumentCheckIconOutline className = 'n-size-token-7' />
69+ ) : (
70+ < DocumentDuplicateIconOutline className = 'text-palette-neutral-text-icon' />
71+ ) }
72+ </ >
73+ ) ,
74+ onClick : ( ) => {
75+ void copy ( cypher_query as string ) ;
76+ setcopiedText ( true ) ;
77+ } ,
78+ } ,
79+ ] ,
80+ [ copiedText , cypher_query ]
81+ ) ;
3582 useEffect ( ( ) => {
36- setLoading ( true ) ;
37- chunkEntitiesAPI ( userCredentials as UserCredentials , chunk_ids . map ( ( c ) => c . id ) . join ( ',' ) )
38- . then ( ( response ) => {
39- setInfoEntities ( response . data . data . nodes ) ;
40- setNodes ( response . data . data . nodes ) ;
41- setRelationships ( response . data . data . relationships ) ;
42- const chunks = response . data . data . chunk_data . map ( ( chunk : any ) => {
43- const chunkScore = chunk_ids . find ( ( chunkdetail ) => chunkdetail . id === chunk . id ) ;
44- return {
45- ...chunk ,
46- score : chunkScore ?. score ,
47- } ;
83+ if ( mode != 'graph' ) {
84+ setLoading ( true ) ;
85+ chunkEntitiesAPI ( userCredentials as UserCredentials , chunk_ids . map ( ( c ) => c . id ) . join ( ',' ) )
86+ . then ( ( response ) => {
87+ console . log ( { response } ) ;
88+ setInfoEntities ( response . data . data . nodes ) ;
89+ setNodes ( response . data . data . nodes ) ;
90+ setRelationships ( response . data . data . relationships ) ;
91+ const chunks = response . data . data . chunk_data . map ( ( chunk : any ) => {
92+ const chunkScore = chunk_ids . find ( ( chunkdetail ) => chunkdetail . id === chunk . id ) ;
93+ return {
94+ ...chunk ,
95+ score : chunkScore ?. score ,
96+ } ;
97+ } ) ;
98+ const sortedchunks = chunks . sort ( ( a : any , b : any ) => b . score - a . score ) ;
99+ setChunks ( sortedchunks ) ;
100+ setLoading ( false ) ;
101+ } )
102+ . catch ( ( error ) => {
103+ console . error ( 'Error fetching entities:' , error ) ;
104+ setLoading ( false ) ;
48105 } ) ;
49- const sortedchunks = chunks . sort ( ( a : any , b : any ) => b . score - a . score ) ;
50- setChunks ( sortedchunks ) ;
51- setLoading ( false ) ;
52- } )
53- . catch ( ( error ) => {
54- console . error ( 'Error fetching entities:' , error ) ;
55- setLoading ( false ) ;
56- } ) ;
57- } , [ chunk_ids ] ) ;
106+ }
107+
108+ ( ) => {
109+ setcopiedText ( false ) ;
110+ } ;
111+ } , [ chunk_ids , mode ] ) ;
58112 const groupedEntities = useMemo < { [ key : string ] : GroupedEntity } > ( ( ) => {
59113 return infoEntities . reduce ( ( acc , entity ) => {
60114 const { label, text } = parseEntity ( entity ) ;
@@ -107,9 +161,10 @@ const ChatInfoModal: React.FC<chatInfoMessage> = ({ sources, model, total_tokens
107161 </ Box >
108162 </ Box >
109163 < Tabs size = 'large' fill = 'underline' onChange = { onChangeTabs } value = { activeTab } >
110- < Tabs . Tab tabId = { 3 } > Sources used</ Tabs . Tab >
111- { mode === 'graph+vector' && < Tabs . Tab tabId = { 4 } > Top Entities used</ Tabs . Tab > }
112- < Tabs . Tab tabId = { 5 } > Chunks</ Tabs . Tab >
164+ { mode != 'graph' && < Tabs . Tab tabId = { 3 } > Sources used</ Tabs . Tab > }
165+ { ( mode === 'graph+vector' || mode === 'graph' ) && < Tabs . Tab tabId = { 4 } > Top Entities used</ Tabs . Tab > }
166+ { mode === 'graph' && cypher_query ?. trim ( ) . length && < Tabs . Tab tabId = { 6 } > Generated Cypher Query</ Tabs . Tab > }
167+ { mode != 'graph' && < Tabs . Tab tabId = { 5 } > Chunks</ Tabs . Tab > }
113168 </ Tabs >
114169 < Flex className = 'p-4' >
115170 < Tabs . TabPanel className = 'n-flex n-flex-col n-gap-token-4 n-p-token-6' value = { activeTab } tabId = { 3 } >
@@ -225,28 +280,39 @@ const ChatInfoModal: React.FC<chatInfoMessage> = ({ sources, model, total_tokens
225280 < Box className = 'flex justify-center items-center' >
226281 < LoadingSpinner size = 'small' />
227282 </ Box >
228- ) : Object . keys ( groupedEntities ) . length > 0 ? (
283+ ) : Object . keys ( groupedEntities ) . length > 0 || Object . keys ( graphonly_entities ) . length > 0 ? (
229284 < ul className = 'list-none p-4 max-h-80 overflow-auto' >
230- { sortedLabels . map ( ( label , index ) => (
231- < li
232- key = { index }
233- className = 'flex items-center mb-2 text-ellipsis whitespace-nowrap max-w-[100%)] overflow-hidden'
234- >
235- < div
236- key = { index }
237- style = { { backgroundColor : `${ groupedEntities [ label ] . color } ` } }
238- className = 'legend mr-2'
239- >
240- { label } ({ labelCounts [ label ] } )
241- </ div >
242- < Typography
243- className = 'entity-text text-ellipsis whitespace-nowrap max-w-[calc(100%-120px)] overflow-hidden'
244- variant = 'body-medium'
245- >
246- { Array . from ( groupedEntities [ label ] . texts ) . slice ( 0 , 3 ) . join ( ', ' ) }
247- </ Typography >
248- </ li >
249- ) ) }
285+ { mode == 'graph'
286+ ? graphonly_entities . map ( ( label , index ) => (
287+ < li
288+ key = { index }
289+ className = 'flex items-center mb-2 text-ellipsis whitespace-nowrap max-w-[100%)] overflow-hidden'
290+ >
291+ < div style = { { backgroundColor : calcWordColor ( Object . keys ( label ) [ 0 ] ) } } className = 'legend mr-2' >
292+ { Object . keys ( label ) [ 0 ] }
293+ </ div >
294+ </ li >
295+ ) )
296+ : sortedLabels . map ( ( label , index ) => (
297+ < li
298+ key = { index }
299+ className = 'flex items-center mb-2 text-ellipsis whitespace-nowrap max-w-[100%)] overflow-hidden'
300+ >
301+ < div
302+ key = { index }
303+ style = { { backgroundColor : `${ groupedEntities [ label ] . color } ` } }
304+ className = 'legend mr-2'
305+ >
306+ { label } ({ labelCounts [ label ] } )
307+ </ div >
308+ < Typography
309+ className = 'entity-text text-ellipsis whitespace-nowrap max-w-[calc(100%-120px)] overflow-hidden'
310+ variant = 'body-medium'
311+ >
312+ { Array . from ( groupedEntities [ label ] . texts ) . slice ( 0 , 3 ) . join ( ', ' ) }
313+ </ Typography >
314+ </ li >
315+ ) ) }
250316 </ ul >
251317 ) : (
252318 < span className = 'h6 text-center' > No Entities Found</ span >
@@ -340,6 +406,15 @@ const ChatInfoModal: React.FC<chatInfoMessage> = ({ sources, model, total_tokens
340406 < span className = 'h6 text-center' > No Chunks Found</ span >
341407 ) }
342408 </ Tabs . TabPanel >
409+ < Tabs . TabPanel value = { activeTab } tabId = { 6 } >
410+ < CypherCodeBlock
411+ code = { cypher_query as string }
412+ actions = { actions }
413+ headerTitle = ''
414+ theme = { themeUtils . colorMode }
415+ className = 'min-h-40'
416+ />
417+ </ Tabs . TabPanel >
343418 </ Flex >
344419 { activeTab == 4 && nodes . length && relationships . length ? (
345420 < Box className = 'button-container flex mt-2 justify-center' >
0 commit comments