Skip to content

Commit c9b9c1c

Browse files
committed
Refactor RemoveLastPath function to return structured result and improve message handling
1 parent 375f0d7 commit c9b9c1c

1 file changed

Lines changed: 139 additions & 65 deletions

File tree

app/components/chat.tsx

Lines changed: 139 additions & 65 deletions
Original file line numberDiff line numberDiff line change
@@ -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

5470
export 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

Comments
 (0)