11import { toast } from "@/components/ui/use-toast" ;
2- import { Dispatch , FormEvent , SetStateAction , useEffect , useRef , useState } from "react" ;
2+ import { Dispatch , FormEvent , MutableRefObject , SetStateAction , useEffect , useRef , useState } from "react" ;
33import Image from "next/image" ;
4- import { AlignLeft , ArrowDown , ArrowRight , ChevronDown , Lightbulb , Undo2 } from "lucide-react" ;
5- import { Path } from "../page " ;
4+ import { AlignLeft , ArrowRight , ChevronDown , Lightbulb , Undo2 } from "lucide-react" ;
5+ import { Message , MessageTypes , Path , PathData } from "@/lib/utils " ;
66import Input from "./Input" ;
7- import { Graph , GraphData , Link } from "./model" ;
8- import { cn } from "@/lib/utils" ;
7+ import { Graph , GraphData , Link , Node } from "./model" ;
8+ import { cn , GraphRef } from "@/lib/utils" ;
99import { TypeAnimation } from "react-type-animation" ;
1010import { DropdownMenu , DropdownMenuContent , DropdownMenuTrigger } from "@/components/ui/dropdown-menu" ;
1111import { prepareArg } from "../utils" ;
12- import { NodeObject , ForceGraphMethods } from "react-force-graph-2d" ;
13-
14- type PathData = {
15- nodes : any [ ]
16- links : any [ ]
17- }
18-
19- enum MessageTypes {
20- Query ,
21- Response ,
22- Path ,
23- PathResponse ,
24- Pending ,
25- Text ,
26- }
27-
28- interface Message {
29- type : MessageTypes ;
30- text ?: string ;
31- paths ?: { nodes : any [ ] , links : any [ ] } [ ] ;
32- graphName ?: string ;
33- }
12+ import { ForceGraphMethods , NodeObject } from "react-force-graph-2d" ;
3413
3514interface Props {
3615 repo : string
@@ -41,7 +20,16 @@ interface Props {
4120 isPathResponse : boolean | undefined
4221 setIsPathResponse : ( isPathResponse : boolean | undefined ) => void
4322 setData : Dispatch < SetStateAction < GraphData > >
44- chartRef : React . MutableRefObject < ForceGraphMethods < Node , Link > >
23+ chartRef : GraphRef
24+ messages : Message [ ]
25+ setMessages : Dispatch < SetStateAction < Message [ ] > >
26+ query : string
27+ setQuery : Dispatch < SetStateAction < string > >
28+ selectedPath : PathData | undefined
29+ setSelectedPath : Dispatch < SetStateAction < PathData | undefined > >
30+ setChatOpen ?: Dispatch < SetStateAction < boolean > >
31+ paths : PathData [ ]
32+ setPaths : Dispatch < SetStateAction < PathData [ ] > >
4533}
4634
4735const SUGGESTIONS = [
@@ -63,20 +51,7 @@ const RemoveLastPath = (messages: Message[]) => {
6351 return messages
6452}
6553
66- export function Chat ( { repo, path, setPath, graph, selectedPathId, isPathResponse, setIsPathResponse, setData, chartRef } : Props ) {
67-
68- // Holds the messages in the chat
69- const [ messages , setMessages ] = useState < Message [ ] > ( [ ] ) ;
70-
71- // Holds the messages in the chat
72- const [ paths , setPaths ] = useState < PathData [ ] > ( [ ] ) ;
73-
74- const [ selectedPath , setSelectedPath ] = useState < PathData > ( ) ;
75-
76- // Holds the user input while typing
77- const [ query , setQuery ] = useState ( '' ) ;
78-
79- const [ tipOpen , setTipOpen ] = useState ( false ) ;
54+ export function Chat ( { messages, setMessages, query, setQuery, selectedPath, setSelectedPath, setChatOpen, repo, path, setPath, graph, selectedPathId, isPathResponse, setIsPathResponse, setData, chartRef, paths, setPaths } : Props ) {
8055
8156 const [ sugOpen , setSugOpen ] = useState ( false ) ;
8257
@@ -94,9 +69,14 @@ export function Chat({ repo, path, setPath, graph, selectedPathId, isPathRespons
9469
9570 // Scroll to the bottom of the chat on new message
9671 useEffect ( ( ) => {
97- setTimeout ( ( ) => {
72+ if ( messages . length === 0 ) return
73+ const timeout = setTimeout ( ( ) => {
9874 containerRef . current ?. scrollTo ( 0 , containerRef . current ?. scrollHeight ) ;
9975 } , 300 )
76+
77+ return ( ) => {
78+ clearTimeout ( timeout )
79+ }
10080 } , [ messages ] ) ;
10181
10282 useEffect ( ( ) => {
@@ -112,7 +92,7 @@ export function Chat({ repo, path, setPath, graph, selectedPathId, isPathRespons
11292
11393 const handleSetSelectedPath = ( p : PathData ) => {
11494 const chart = chartRef . current
115-
95+
11696 if ( ! chart ) return
11797 setSelectedPath ( prev => {
11898 if ( prev ) {
@@ -169,6 +149,7 @@ export function Chat({ repo, path, setPath, graph, selectedPathId, isPathRespons
169149 setTimeout ( ( ) => {
170150 chart . zoomToFit ( 1000 , 150 , ( n : NodeObject < Node > ) => p . nodes . some ( node => node . id === n . id ) ) ;
171151 } , 0 )
152+ setChatOpen && setChatOpen ( false )
172153 }
173154
174155 // A function that handles the change event of the url input box
@@ -233,6 +214,8 @@ export function Chat({ repo, path, setPath, graph, selectedPathId, isPathRespons
233214
234215 if ( ! path ?. start ?. id || ! path . end ?. id ) return
235216
217+ setPath ( undefined )
218+
236219 const result = await fetch ( `/api/repo/${ prepareArg ( repo ) } /${ prepareArg ( String ( path . start . id ) ) } /?targetId=${ prepareArg ( String ( path . end . id ) ) } ` , {
237220 method : 'POST'
238221 } )
@@ -261,21 +244,20 @@ export function Chat({ repo, path, setPath, graph, selectedPathId, isPathRespons
261244
262245 setPaths ( formattedPaths )
263246 setMessages ( ( prev ) => [ ...RemoveLastPath ( prev ) , { type : MessageTypes . PathResponse , paths : formattedPaths , graphName : graph . Id } ] ) ;
264- setPath ( undefined )
265247 setIsPathResponse ( true )
266248 setData ( { ...graph . Elements } )
267249 setTimeout ( ( ) => {
268250 chart . zoomToFit ( 1000 , 150 , ( n : NodeObject < Node > ) => formattedPaths . some ( p => p . nodes . some ( node => node . id === n . id ) ) ) ;
269251 } , 0 )
270252 }
271253
272- const getTip = ( disabled = false ) =>
254+ const getTip = ( className ?: string ) =>
273255 < >
274256 < button
275- disabled = { disabled }
276- className = "Tip"
257+ disabled = { isSendMessage }
258+ className = { cn ( "Tip" , className ) }
277259 onClick = { ( ) => {
278- setTipOpen ( false )
260+ setSugOpen ( false )
279261 setMessages ( prev => [
280262 ...RemoveLastPath ( prev ) ,
281263 { type : MessageTypes . Query , text : "Create a path" } ,
@@ -299,12 +281,24 @@ export function Chat({ repo, path, setPath, graph, selectedPathId, isPathRespons
299281 } , 4000 )
300282 } }
301283 >
302- < Lightbulb />
303- < div >
304- < h1 className = "label" > Show the path</ h1 >
305- < p className = "text" > Fetch, update, batch, and navigate data efficiently</ p >
306- </ div >
284+ < p className = "text-center w-full" > Show the path</ p >
307285 </ button >
286+ {
287+ SUGGESTIONS . map ( ( s , i ) => (
288+ < button
289+ disabled = { isSendMessage }
290+ type = "submit"
291+ key = { i }
292+ className = { cn ( "Tip" , className ) }
293+ onClick = { ( ) => {
294+ sendQuery ( undefined , s )
295+ setSugOpen ( false )
296+ } }
297+ >
298+ < p className = "text-center w-full" > { s } </ p >
299+ </ button >
300+ ) )
301+ }
308302 </ >
309303
310304 const getMessage = ( message : Message , index ?: number ) => {
@@ -390,10 +384,10 @@ export function Chat({ repo, path, setPath, graph, selectedPathId, isPathRespons
390384 }
391385
392386 if ( selectedPath ?. nodes . every ( node => p ?. nodes . some ( ( n ) => n . id === node . id ) ) && selectedPath . nodes . length === p . nodes . length ) return
393-
387+
394388 if ( ! isPathResponse ) {
395389 setIsPathResponse ( undefined )
396-
390+
397391 }
398392 handleSetSelectedPath ( p )
399393 } }
@@ -420,73 +414,41 @@ export function Chat({ repo, path, setPath, graph, selectedPathId, isPathRespons
420414 }
421415
422416 return (
423- < div className = "relative h-full flex flex-col justify-between px-6 pt-10 pb-4 gap-4" >
417+ < div className = "relative h-1 grow md:h- full flex flex-col justify-between px-6 pt-10 pb-4 gap-4" >
424418 < main data-name = "main-chat" ref = { containerRef } className = "grow flex flex-col overflow-y-auto gap-6 px-4" >
425419 {
426420 messages . length === 0 &&
427421 < >
428- < h1 className = "font-oswald text-[20px] font-semibold leading-[32px] text-left text-[#13343B]" > WELCOME TO OUR ASSISTANCE SERVICE</ h1 >
429- < span className = "text-base font-normal leading-5 text-left text-[#7D7D7D]" >
430- We can help you access and update only the needed
431- data via paths, optimizing network requests with
432- batching and catching for better performance.
433- </ span >
434- { getTip ( ) }
422+ < h1 className = "text-center text-2xl" > What would you like to analyze?</ h1 >
423+ < div className = "flex flex-row flex-wrap gap-2 justify-center" >
424+ { getTip ( ) }
425+ </ div >
435426 </ >
436427 }
437428 {
438429 messages . map ( ( message , index ) => {
439430 return getMessage ( message , index )
440431 } )
441432 }
442- {
443- tipOpen &&
444- < div ref = { ref => ref ?. focus ( ) } className = "bg-white absolute bottom-24 border rounded-md flex flex-col gap-3 p-2 overflow-y-auto" tabIndex = { - 1 } onMouseDown = { ( e ) => e . preventDefault ( ) } onBlur = { ( ) => setTipOpen ( false ) } >
445- { getTip ( isSendMessage ) }
446- </ div >
447- }
448433 </ main >
449- < DropdownMenu open = { sugOpen } onOpenChange = { setSugOpen } >
450- < footer >
451- {
452- repo &&
453- < div className = "flex gap-4 px-4" >
454- < button data-name = "lightbulb" onClick = { ( ) => setTipOpen ( true ) } className = "p-4 border rounded-md hover:border-[#FF66B3] hover:bg-[#FFF0F7]" >
455- < Lightbulb />
456- </ button >
457- < form className = "grow flex items-center border rounded-md px-2" onSubmit = { sendQuery } >
458- < DropdownMenuTrigger asChild >
459- < button data-name = "questionOptionsMenu" className = "bg-gray-200 p-2 rounded-md hover:bg-gray-300" >
460- < ArrowDown color = "white" />
461- </ button >
462- </ DropdownMenuTrigger >
463- < input className = "grow p-4 rounded-md focus-visible:outline-none" placeholder = "Ask your question" onChange = { handleQueryInputChange } value = { query } />
464- < button disabled = { isSendMessage } className = { `bg-gray-200 p-2 rounded-md ${ ! isSendMessage && 'hover:bg-gray-300' } ` } >
465- < ArrowRight color = "white" />
466- </ button >
467- </ form >
468- </ div >
469- }
470- </ footer >
471- < DropdownMenuContent className = "flex flex-col mb-4 w-[20dvw]" side = "top" >
472- {
473- SUGGESTIONS . map ( ( s , i ) => (
474- < button
475- disabled = { isSendMessage }
476- type = "submit"
477- key = { i }
478- className = "p-2 text-left hover:bg-gray-200"
479- onClick = { ( ) => {
480- sendQuery ( undefined , s )
481- setSugOpen ( false )
482- } }
483- >
484- { s }
485- </ button >
486- ) )
487- }
488- </ DropdownMenuContent >
489- </ DropdownMenu >
434+ < footer className = "flex gap-4 px-4 overflow-hidden min-h-fit" >
435+ < DropdownMenu open = { sugOpen } onOpenChange = { setSugOpen } >
436+ < DropdownMenuTrigger asChild >
437+ < button data-name = "lightbulb" className = "p-4 border rounded-md hover:border-[#FF66B3] hover:bg-[#FFF0F7]" >
438+ < Lightbulb />
439+ </ button >
440+ </ DropdownMenuTrigger >
441+ < DropdownMenuContent align = "start" className = "flex flex-col gap-2 mb-4 w-[81.51dvw] md:w-[20dvw] overflow-y-auto" side = "top" >
442+ { getTip ( "!w-full" ) }
443+ </ DropdownMenuContent >
444+ </ DropdownMenu >
445+ < form className = "grow flex items-center border rounded-md px-2" onSubmit = { sendQuery } >
446+ < input className = "w-1 grow p-4 rounded-md focus-visible:outline-none" placeholder = "Ask your question" onChange = { handleQueryInputChange } value = { query } />
447+ < button disabled = { isSendMessage } className = { `bg-gray-200 p-2 rounded-md ${ ! isSendMessage && 'hover:bg-gray-300' } ` } >
448+ < ArrowRight color = "white" />
449+ </ button >
450+ </ form >
451+ </ footer >
490452 </ div >
491453 ) ;
492454}
0 commit comments