1- import { ArrowUpRightSquare , Check , Copy } from "lucide-react" ;
1+ import { Check , Copy } from "lucide-react" ;
22import { useMemo } from "react" ;
33import { Button } from "../button" ;
44import { HoverCard , HoverCardContent , HoverCardTrigger } from "../hover-card" ;
5+ import { getStaticFileDataUrl } from "../lib/url" ;
56import { SourceData , SourceNode } from "./index" ;
67import { useCopyToClipboard } from "./use-copy-to-clipboard" ;
8+ import PdfDialog from "./widgets/PdfDialog" ;
79
8- const SCORE_THRESHOLD = 0.5 ;
10+ const SCORE_THRESHOLD = 0.3 ;
11+
12+ function SourceNumberButton ( { index } : { index : number } ) {
13+ return (
14+ < div className = "text-xs w-5 h-5 rounded-full bg-gray-100 mb-2 flex items-center justify-center hover:text-white hover:bg-primary hover:cursor-pointer" >
15+ { index + 1 }
16+ </ div >
17+ ) ;
18+ }
19+
20+ enum NODE_TYPE {
21+ URL ,
22+ FILE ,
23+ UNKNOWN ,
24+ }
25+
26+ type NodeInfo = {
27+ id : string ;
28+ type : NODE_TYPE ;
29+ path ?: string ;
30+ url ?: string ;
31+ } ;
32+
33+ function getNodeInfo ( node : SourceNode ) : NodeInfo {
34+ if ( typeof node . metadata [ "URL" ] === "string" ) {
35+ const url = node . metadata [ "URL" ] ;
36+ return {
37+ id : node . id ,
38+ type : NODE_TYPE . URL ,
39+ path : url ,
40+ url,
41+ } ;
42+ }
43+ if ( typeof node . metadata [ "file_path" ] === "string" ) {
44+ const fileName = node . metadata [ "file_name" ] as string ;
45+ return {
46+ id : node . id ,
47+ type : NODE_TYPE . FILE ,
48+ path : node . metadata [ "file_path" ] ,
49+ url : getStaticFileDataUrl ( fileName ) ,
50+ } ;
51+ }
52+
53+ return {
54+ id : node . id ,
55+ type : NODE_TYPE . UNKNOWN ,
56+ } ;
57+ }
958
1059export function ChatSources ( { data } : { data : SourceData } ) {
11- const sources = useMemo ( ( ) => {
12- return (
13- data . nodes
14- ?. filter ( ( node ) => Object . keys ( node . metadata ) . length > 0 )
15- ?. filter ( ( node ) => ( node . score ?? 1 ) > SCORE_THRESHOLD )
16- . sort ( ( a , b ) => ( b . score ?? 1 ) - ( a . score ?? 1 ) ) || [ ]
17- ) ;
60+ const sources : NodeInfo [ ] = useMemo ( ( ) => {
61+ // aggregate nodes by url or file_path (get the highest one by score)
62+ const nodesByPath : { [ path : string ] : NodeInfo } = { } ;
63+
64+ data . nodes
65+ . filter ( ( node ) => ( node . score ?? 1 ) > SCORE_THRESHOLD )
66+ . sort ( ( a , b ) => ( b . score ?? 1 ) - ( a . score ?? 1 ) )
67+ . forEach ( ( node ) => {
68+ const nodeInfo = getNodeInfo ( node ) ;
69+ const key = nodeInfo . path ?? nodeInfo . id ; // use id as key for UNKNOWN type
70+ if ( ! nodesByPath [ key ] ) {
71+ nodesByPath [ key ] = nodeInfo ;
72+ }
73+ } ) ;
74+
75+ return Object . values ( nodesByPath ) ;
1876 } , [ data . nodes ] ) ;
1977
2078 if ( sources . length === 0 ) return null ;
@@ -23,55 +81,52 @@ export function ChatSources({ data }: { data: SourceData }) {
2381 < div className = "space-x-2 text-sm" >
2482 < span className = "font-semibold" > Sources:</ span >
2583 < div className = "inline-flex gap-1 items-center" >
26- { sources . map ( ( node : SourceNode , index : number ) => (
27- < div key = { node . id } >
28- < HoverCard >
29- < HoverCardTrigger >
30- < div className = "text-xs w-5 h-5 rounded-full bg-gray-100 mb-2 flex items-center justify-center hover:text-white hover:bg-primary hover:cursor-pointer" >
31- { index + 1 }
32- </ div >
33- </ HoverCardTrigger >
34- < HoverCardContent >
35- < NodeInfo node = { node } />
36- </ HoverCardContent >
37- </ HoverCard >
38- </ div >
39- ) ) }
84+ { sources . map ( ( nodeInfo : NodeInfo , index : number ) => {
85+ if ( nodeInfo . path ?. endsWith ( ".pdf" ) ) {
86+ return (
87+ < PdfDialog
88+ key = { nodeInfo . id }
89+ documentId = { nodeInfo . id }
90+ url = { nodeInfo . url ! }
91+ path = { nodeInfo . path }
92+ trigger = { < SourceNumberButton index = { index } /> }
93+ />
94+ ) ;
95+ }
96+ return (
97+ < div key = { nodeInfo . id } >
98+ < HoverCard >
99+ < HoverCardTrigger >
100+ < SourceNumberButton index = { index } />
101+ </ HoverCardTrigger >
102+ < HoverCardContent className = "w-[320px]" >
103+ < NodeInfo nodeInfo = { nodeInfo } />
104+ </ HoverCardContent >
105+ </ HoverCard >
106+ </ div >
107+ ) ;
108+ } ) }
40109 </ div >
41110 </ div >
42111 ) ;
43112}
44113
45- function NodeInfo ( { node } : { node : SourceNode } ) {
114+ function NodeInfo ( { nodeInfo } : { nodeInfo : NodeInfo } ) {
46115 const { isCopied, copyToClipboard } = useCopyToClipboard ( { timeout : 1000 } ) ;
47116
48- if ( typeof node . metadata [ "URL" ] === "string" ) {
49- // this is a node generated by the web loader, it contains an external URL
50- // add a link to view this URL
51- return (
52- < a
53- className = "space-x-2 flex items-center my-2 hover:text-blue-900"
54- href = { node . metadata [ "URL" ] }
55- target = "_blank"
56- >
57- < span > { node . metadata [ "URL" ] } </ span >
58- < ArrowUpRightSquare className = "w-4 h-4" />
59- </ a >
60- ) ;
61- }
62-
63- if ( typeof node . metadata [ "file_path" ] === "string" ) {
64- // this is a node generated by the file loader, it contains file path
65- // add a button to copy the path to the clipboard
66- const filePath = node . metadata [ "file_path" ] ;
117+ if ( nodeInfo . type !== NODE_TYPE . UNKNOWN ) {
118+ // this is a node generated by the web loader or file loader,
119+ // add a link to view its URL and a button to copy the URL to the clipboard
67120 return (
68- < div className = "flex items-center px-2 py-1 justify-between my-2" >
69- < span > { filePath } </ span >
121+ < div className = "flex items-center my-2" >
122+ < a className = "hover:text-blue-900" href = { nodeInfo . url } target = "_blank" >
123+ < span > { nodeInfo . path } </ span >
124+ </ a >
70125 < Button
71- onClick = { ( ) => copyToClipboard ( filePath ) }
126+ onClick = { ( ) => copyToClipboard ( nodeInfo . path ! ) }
72127 size = "icon"
73128 variant = "ghost"
74- className = "h-12 w-12"
129+ className = "h-12 w-12 shrink-0 "
75130 >
76131 { isCopied ? (
77132 < Check className = "h-4 w-4" />
@@ -84,7 +139,6 @@ function NodeInfo({ node }: { node: SourceNode }) {
84139 }
85140
86141 // node generated by unknown loader, implement renderer by analyzing logged out metadata
87- console . log ( "Node metadata" , node . metadata ) ;
88142 return (
89143 < p >
90144 Sorry, unknown node type. Please add a new renderer in the NodeInfo
0 commit comments