@@ -40,15 +40,31 @@ const SUGGESTIONS = [
4040 "Show a calling path between the drop_edge_range_index function and _query, only return function(s) names" ,
4141]
4242
43- const RemoveLastPath = ( messages : Message [ ] ) => {
44- const index = messages . findIndex ( ( m ) => m . type === MessageTypes . Path )
43+ type RemoveLastPathResult = {
44+ messages : Message [ ]
45+ insertIndex : number
46+ }
47+
48+ const RemoveLastPath = ( messages : Message [ ] ) : RemoveLastPathResult => {
49+ // Find the last Path message so we know where the user was in the conversation
50+ const index = messages . findLastIndex ( ( m ) => m . type === MessageTypes . Path )
4551
46- if ( index !== - 1 ) {
47- messages = [ ...messages . slice ( 0 , index - 2 ) , ...messages . slice ( index + 1 ) ] ;
48- messages = RemoveLastPath ( messages )
52+ if ( index === - 1 ) {
53+ return { messages, insertIndex : messages . length }
4954 }
5055
51- return messages
56+ const groupStart = Math . max ( 0 , index - 2 )
57+ const hasMessagesAfter = index < messages . length - 1
58+ const cleaned = [ ...messages . slice ( 0 , groupStart ) , ...messages . slice ( index + 1 ) ]
59+
60+ // Recurse to strip any remaining Path groups
61+ const { messages : finalMessages } = RemoveLastPath ( cleaned )
62+
63+ // If there were messages after the Path group, inject the answer there;
64+ // otherwise just append at the end
65+ const insertIndex = hasMessagesAfter ? groupStart : finalMessages . length
66+
67+ return { messages : finalMessages , insertIndex }
5268}
5369
5470export function Chat ( { messages, setMessages, query, setQuery, selectedPath, setSelectedPath, setChatOpen, repo, path, setPath, graph, selectedPathId, isPathResponse, setIsPathResponse, setCooldownTicks, canvasRef, paths, setPaths } : Props ) {
@@ -94,74 +110,113 @@ export function Chat({ messages, setMessages, query, setQuery, selectedPath, set
94110 const canvas = canvasRef . current
95111
96112 if ( ! canvas ) return
97- setSelectedPath ( prev => {
98- if ( prev ) {
99- if ( isPathResponse && paths . some ( ( path ) => [ ...path . nodes , ...path . links ] . every ( ( e : any ) => [ ...prev . nodes , ...prev . links ] . some ( ( el : any ) => el . id === e . id ) ) ) ) {
100- graph . getElements ( ) . forEach ( link => {
101- const { id } = link
102-
103- if ( prev . links . some ( e => e . id === id ) && ! p . links . some ( e => e . id === id ) ) {
104- link . isPathSelected = false
105- }
106- } )
107- } else {
108- const elements = graph . getElements ( ) . filter ( e => [ ...prev . links , ...prev . nodes ] . some ( el => el . id === e . id && ! [ ...p . nodes , ...p . links ] . some ( ele => ele . id === el . id ) ) )
109- if ( isPathResponse || isPathResponse === undefined ) {
110- elements . forEach ( e => {
111- e . isPath = false
112- e . isPathSelected = false
113- } )
113+
114+ // Sets for the new path
115+ const pNodeIds = new Set < number > ( p . nodes . map ( ( n : Node ) => n . id ) )
116+ const pLinkIds = new Set < number > ( p . links . map ( ( l : any ) => l . id ) )
117+ const pIds = new Set < number > ( [ ...pNodeIds , ...pLinkIds ] )
118+ const firstNodeId = p . nodes [ 0 ] . id
119+ const lastNodeId = p . nodes [ p . nodes . length - 1 ] . id
120+
121+ // Sets for the previous path (selectedPath is the current value from props)
122+ const prevNodeIds = new Set < number > ( ( selectedPath ?. nodes ?? [ ] ) . map ( ( n : any ) => n . id ) )
123+ const prevLinkIds = new Set < number > ( ( selectedPath ?. links ?? [ ] ) . map ( ( e : any ) => e . id ) )
124+ const prevIds = new Set < number > ( [ ...prevNodeIds , ...prevLinkIds ] )
125+
126+ // --- Unset previous path on graph elements ---
127+ if ( selectedPath ) {
128+ const pathAlreadyInPrev = isPathResponse && paths . some ( path =>
129+ [ ...path . nodes , ...path . links ] . every ( ( e : any ) => prevIds . has ( e . id ) )
130+ )
131+
132+ if ( pathAlreadyInPrev ) {
133+ graph . getElements ( ) . forEach ( ( element : any ) => {
134+ if ( prevLinkIds . has ( element . id ) && ! pLinkIds . has ( element . id ) ) {
135+ element . isPathSelected = false
114136 }
115- }
137+ } )
138+ } else {
139+ const staleElements = graph . getElements ( ) . filter ( ( e : any ) => prevIds . has ( e . id ) && ! pIds . has ( e . id ) )
140+ staleElements . forEach ( ( e : any ) => {
141+ e . isPath = false
142+ e . isPathSelected = false
143+ if ( "source" in e ) {
144+ e . color = "#999999"
145+ }
146+ } )
116147 }
117- return p
118- } )
119- if ( isPathResponse && paths . length > 0 && paths . some ( ( path ) => [ ...path . nodes , ...path . links ] . every ( ( e : any ) => [ ...p . nodes , ...p . links ] . some ( ( el : any ) => el . id === e . id ) ) ) ) {
120- graph . Elements . links . forEach ( e => {
121- if ( p . links . some ( el => el . id === e . id ) ) {
148+ }
149+
150+ setSelectedPath ( p )
151+
152+ // --- Set new path on graph elements ---
153+ if ( isPathResponse && paths . length > 0 && paths . some ( path =>
154+ [ ...path . nodes , ...path . links ] . every ( ( e : any ) => pIds . has ( e . id ) )
155+ ) ) {
156+ graph . Elements . links . forEach ( ( e : any ) => {
157+ if ( pLinkIds . has ( e . id ) ) {
122158 e . isPathSelected = true
123159 }
124160 } )
125161 } else {
126- const elements : PathData = { nodes : [ ] , links : [ ] } ;
127- p . nodes . forEach ( node => {
128- let element = graph . Elements . nodes . find ( n => n . id === node . id )
129- if ( ! element ) {
130- elements . nodes . push ( node )
131- }
132- } )
133- p . links . forEach ( link => {
134- let element = graph . Elements . links . find ( l => l . id === link . id )
135- if ( ! element ) {
136- elements . links . push ( link )
137- }
138- } )
162+ const existingNodeIds = new Set < number > ( graph . Elements . nodes . map ( ( n : any ) => n . id ) )
163+ const existingLinkIds = new Set < number > ( graph . Elements . links . map ( ( l : any ) => l . id ) )
164+ const elements : PathData = {
165+ nodes : p . nodes . filter ( node => ! existingNodeIds . has ( node . id ) ) ,
166+ links : p . links . filter ( link => ! existingLinkIds . has ( link . id ) ) ,
167+ }
139168 graph . extend ( elements , true , { start : p . nodes [ 0 ] , end : p . nodes [ p . nodes . length - 1 ] } )
140- graph . getElements ( ) . filter ( e => "source" in e ? p . links . some ( l => l . id === e . id ) : p . nodes . some ( n => n . id === e . id ) ) . forEach ( e => {
141- if ( e . id === p . nodes [ 0 ] . id || e . id === p . nodes [ p . nodes . length - 1 ] . id || "source" in e ) {
142- e . isPathSelected = true
143- } else {
144- e . isPath = true
145- }
146- } ) ;
169+ graph . getElements ( )
170+ . filter ( ( e : any ) => "source" in e ? pLinkIds . has ( e . id ) : pNodeIds . has ( e . id ) )
171+ . forEach ( ( e : any ) => {
172+ if ( e . id === firstNodeId || e . id === lastNodeId || "source" in e ) {
173+ e . isPathSelected = true
174+ } else {
175+ e . isPath = true
176+ }
177+ } )
147178 }
148179
149180 const currentData = canvas . getGraphData ( ) ;
150181
151- const nodesSet = new Set < number > ( p . nodes . map ( ( n : Node ) => n . id ) ) ;
152- const linksSet = new Set < number > ( p . links . map ( ( l : any ) => l . id ) ) ;
182+ // --- Unset canvas data for previous path elements no longer in the new path ---
183+ if ( selectedPath ) {
184+ currentData . nodes . forEach ( ( n : any ) => {
185+ if ( prevNodeIds . has ( n . id ) && ! pNodeIds . has ( n . id ) ) {
186+ if ( isPathResponse ) {
187+ // keep isPath + PATH_COLOR border, just deselect
188+ n . data . isPathSelected = false
189+ } else {
190+ n . data . isPath = false
191+ n . data . isPathSelected = false
192+ }
193+ }
194+ } )
195+ currentData . links . forEach ( ( l : any ) => {
196+ if ( prevLinkIds . has ( l . id ) && ! pLinkIds . has ( l . id ) ) {
197+ if ( isPathResponse ) {
198+ // keep color + dashed path line, just deselect
199+ l . data . isPathSelected = false
200+ } else {
201+ l . data . isPathSelected = false
202+ l . color = "#999999"
203+ }
204+ }
205+ } )
206+ }
153207
154- currentData . nodes . forEach ( n => {
155- if ( nodesSet . has ( n . id ) ) {
156- if ( n . id === p . nodes [ 0 ] . id || n . id === p . nodes [ p . nodes . length - 1 ] . id ) {
208+ // --- Set canvas data for new path elements ---
209+ currentData . nodes . forEach ( ( n : any ) => {
210+ if ( pNodeIds . has ( n . id ) ) {
211+ if ( n . id === firstNodeId || n . id === lastNodeId ) {
157212 n . data . isPathSelected = true ;
158213 } else {
159214 n . data . isPath = true ;
160215 }
161216 }
162217 } ) ;
163- currentData . links . forEach ( l => {
164- if ( linksSet . has ( l . id ) ) {
218+ currentData . links . forEach ( ( l : any ) => {
219+ if ( pLinkIds . has ( l . id ) ) {
165220 l . data . isPathSelected = true ;
166221 l . color = PATH_COLOR ;
167222 }
@@ -170,7 +225,7 @@ export function Chat({ messages, setMessages, query, setQuery, selectedPath, set
170225 canvas . setGraphData ( currentData )
171226
172227 setTimeout ( ( ) => {
173- canvas . zoomToFit ( 2 , ( n : GraphNode ) => p . nodes . some ( node => node . id === n . id ) ) ;
228+ canvas . zoomToFit ( 2 , ( n : GraphNode ) => pNodeIds . has ( n . id ) ) ;
174229 } , 0 )
175230 setChatOpen && setChatOpen ( false )
176231 }
@@ -243,14 +298,24 @@ export function Chat({ messages, setMessages, query, setQuery, selectedPath, set
243298 } , { type : MessageTypes . Path } ]
244299
245300 setPath ( undefined )
246- setMessages ( ( prev ) => [ ...RemoveLastPath ( prev ) , { type : MessageTypes . Pending } ] )
301+ let insertIndex = 0
302+ setMessages ( ( prev ) => {
303+ const { messages, insertIndex : idx } = RemoveLastPath ( prev )
304+ insertIndex = idx
305+ const pending : Message = { type : MessageTypes . Pending }
306+ return [ ...messages . slice ( 0 , insertIndex ) , pending , ...messages . slice ( insertIndex ) ]
307+ } )
247308
248309 const result = await fetch ( `/api/repo/${ prepareArg ( repo ) } /${ prepareArg ( String ( path . start . id ) ) } /?targetId=${ prepareArg ( String ( path . end . id ) ) } ` , {
249310 method : 'POST'
250311 } )
251312
252313 if ( ! result . ok ) {
253- setMessages ( ( prev ) => [ ...prev . slice ( 0 , - 1 ) , ...pathMessage ] )
314+ setMessages ( ( prev ) => [
315+ ...prev . slice ( 0 , insertIndex ) ,
316+ ...pathMessage ,
317+ ...prev . slice ( insertIndex + 1 ) ,
318+ ] )
254319 setPath ( { } )
255320 toast ( {
256321 variant : "destructive" ,
@@ -263,7 +328,11 @@ export function Chat({ messages, setMessages, query, setQuery, selectedPath, set
263328 const json = await result . json ( )
264329
265330 if ( json . result . paths . length === 0 ) {
266- setMessages ( ( prev ) => [ ...prev . slice ( 0 , - 1 ) , ...pathMessage ] )
331+ setMessages ( ( prev ) => [
332+ ...prev . slice ( 0 , insertIndex ) ,
333+ ...pathMessage ,
334+ ...prev . slice ( insertIndex + 1 ) ,
335+ ] )
267336 setPath ( { } )
268337 toast ( {
269338 title : `No path found` ,
@@ -283,7 +352,11 @@ export function Chat({ messages, setMessages, query, setQuery, selectedPath, set
283352 { nodes : [ ] , links : [ ] }
284353 )
285354 setPaths ( formattedPaths )
286- setMessages ( ( prev ) => [ ...prev . slice ( 0 , - 1 ) , { type : MessageTypes . PathResponse , paths : formattedPaths , graphName : graph . Id } ] ) ;
355+ setMessages ( ( prev ) => [
356+ ...prev . slice ( 0 , insertIndex ) ,
357+ { type : MessageTypes . PathResponse , paths : formattedPaths , graphName : graph . Id } ,
358+ ...prev . slice ( insertIndex + 1 ) ,
359+ ] ) ;
287360 setIsPathResponse ( true )
288361
289362 const currentData = canvas . getGraphData ( ) ;
@@ -370,10 +443,11 @@ export function Chat({ messages, setMessages, query, setQuery, selectedPath, set
370443 if ( ! canvas ) return
371444
372445 setSugOpen ( false )
373- setMessages ( prev => [
374- ...RemoveLastPath ( prev ) ,
375- { type : MessageTypes . Query , text : "Create a path" } ,
376- ] )
446+ setMessages ( prev => {
447+ const { messages, insertIndex } = RemoveLastPath ( prev )
448+ const queryMsg : Message = { type : MessageTypes . Query , text : "Create a path" }
449+ return [ ...messages . slice ( 0 , insertIndex ) , queryMsg , ...messages . slice ( insertIndex ) ]
450+ } )
377451
378452 if ( isPathResponse ) {
379453 setIsPathResponse ( false )
0 commit comments