|
1 | 1 | import { toast } from "@/components/ui/use-toast"; |
2 | 2 | import { Dispatch, FormEvent, MutableRefObject, SetStateAction, useEffect, useRef, useState } from "react"; |
3 | 3 | import Image from "next/image"; |
4 | | -import { AlignLeft, ArrowRight, ChevronDown, Lightbulb, Undo2 } from "lucide-react"; |
| 4 | +import { AlignLeft, ArrowDown, ArrowRight, ChevronDown, Lightbulb, Undo2 } from "lucide-react"; |
5 | 5 | import { Path } from "../page"; |
6 | 6 | import Input from "./Input"; |
7 | 7 | import { Graph } from "./model"; |
8 | 8 | import { cn } from "@/lib/utils"; |
9 | 9 | import { LAYOUT } from "./code-graph"; |
10 | 10 | import { TypeAnimation } from "react-type-animation"; |
| 11 | +import { DropdownMenu, DropdownMenuContent, DropdownMenuTrigger } from "@/components/ui/dropdown-menu"; |
11 | 12 |
|
12 | 13 | enum MessageTypes { |
13 | 14 | Query, |
@@ -35,6 +36,14 @@ interface Props { |
35 | 36 | setIsPath: (isPathResponse: boolean) => void |
36 | 37 | } |
37 | 38 |
|
| 39 | +const SUGGESTIONS = [ |
| 40 | + "List a few recursive functions", |
| 41 | + "What is the name of the most used method?", |
| 42 | + "Who is calling the most used method?", |
| 43 | + "Which function has the largest number of arguments? List a few arguments", |
| 44 | + "Show a calling path between the drop_edge_range_index function and _query, only return function(s) names", |
| 45 | +] |
| 46 | + |
38 | 47 | const RemoveLastPath = (messages: Message[]) => { |
39 | 48 | const index = messages.findIndex((m) => m.type === MessageTypes.Path) |
40 | 49 |
|
@@ -63,19 +72,13 @@ export function Chat({ repo, path, setPath, graph, chartRef, selectedPathId, isP |
63 | 72 |
|
64 | 73 | const [tipOpen, setTipOpen] = useState(false); |
65 | 74 |
|
| 75 | + const [sugOpen, setSugOpen] = useState(false); |
| 76 | + |
66 | 77 | // A reference to the chat container to allow scrolling to the bottom |
67 | 78 | const containerRef: React.RefObject<HTMLDivElement> = useRef(null); |
68 | 79 |
|
69 | | - const tipRef: React.RefObject<HTMLDivElement> = useRef(null); |
70 | | - |
71 | 80 | const isSendMessage = messages.some(m => m.type === MessageTypes.Pending) || (messages.some(m => m.text === "Please select a starting point and the end point. Select or press relevant item on the graph") && !messages.some(m => m.type === MessageTypes.Path)) |
72 | 81 |
|
73 | | - useEffect(() => { |
74 | | - if (tipOpen) { |
75 | | - tipRef.current?.focus() |
76 | | - } |
77 | | - }, [tipOpen]) |
78 | | - |
79 | 82 | useEffect(() => { |
80 | 83 | const p = paths.find((path) => [...path.edges, ...path.nodes].some((e: any) => e.id === selectedPathId)) |
81 | 84 |
|
@@ -202,11 +205,13 @@ export function Chat({ repo, path, setPath, graph, chartRef, selectedPathId, isP |
202 | 205 | } |
203 | 206 |
|
204 | 207 | // Send the user query to the server |
205 | | - async function sendQuery(event: FormEvent) { |
| 208 | + async function sendQuery(event?: FormEvent, sugQuery?: string) { |
206 | 209 |
|
207 | | - event.preventDefault(); |
| 210 | + event?.preventDefault(); |
208 | 211 |
|
209 | | - const q = query.trim() |
| 212 | + if (isSendMessage) return |
| 213 | + |
| 214 | + const q = query?.trim() || sugQuery! |
210 | 215 |
|
211 | 216 | if (!q) { |
212 | 217 | toast({ |
@@ -436,7 +441,7 @@ export function Chat({ repo, path, setPath, graph, chartRef, selectedPathId, isP |
436 | 441 | key={i} |
437 | 442 | className={cn("flex text-wrap border p-2 gap-2 rounded-md", p.nodes.length === selectedPath?.nodes.length && selectedPath?.nodes.every(node => p?.nodes.some((n) => n.id === node.id)) && "border-[#FF66B3] bg-[#FFF0F7]")} |
438 | 443 | onClick={() => { |
439 | | - if (selectedPath?.nodes.every(node => p?.nodes.some((n) => n.id === node.id))) return |
| 444 | + if (p.nodes.length === selectedPath?.nodes.length && selectedPath?.nodes.every(node => p?.nodes.some((n) => n.id === node.id))) return |
440 | 445 | handelSetSelectedPath(p) |
441 | 446 | setIsPath(true) |
442 | 447 | }} |
@@ -486,27 +491,52 @@ export function Chat({ repo, path, setPath, graph, chartRef, selectedPathId, isP |
486 | 491 | } |
487 | 492 | { |
488 | 493 | tipOpen && |
489 | | - <div ref={tipRef} className="bg-white fixed bottom-[85px] border rounded-md flex flex-col gap-3 p-2 overflow-y-auto" onBlur={() => setTipOpen(false)}> |
| 494 | + <div ref={ref => ref?.focus()} className="bg-white absolute bottom-0 border rounded-md flex flex-col gap-3 p-2 overflow-y-auto" tabIndex={-1} onMouseDown={(e) => e.preventDefault()} onBlur={() => setTipOpen(false)}> |
490 | 495 | {getTip()} |
491 | 496 | </div> |
492 | 497 | } |
493 | 498 | </main> |
494 | | - <footer> |
495 | | - { |
496 | | - repo && |
497 | | - <div className="flex gap-4 px-4"> |
498 | | - <button data-name="lightbulb" disabled={isSendMessage} className="p-4 border rounded-md hover:border-[#FF66B3] hover:bg-[#FFF0F7]" onClick={() => setTipOpen(prev => !prev)}> |
499 | | - <Lightbulb /> |
500 | | - </button> |
501 | | - <form className="grow flex items-center border rounded-md pr-2" onSubmit={sendQuery}> |
502 | | - <input disabled={isSendMessage} className="grow p-4 rounded-md focus-visible:outline-none" placeholder="Ask your question" onChange={handleQueryInputChange} value={query} /> |
503 | | - <button disabled={isSendMessage} className={`bg-gray-200 p-2 rounded-md ${!isSendMessage && 'hover:bg-gray-300'}`}> |
504 | | - <ArrowRight color="white" /> |
| 499 | + <DropdownMenu open={sugOpen} onOpenChange={setSugOpen}> |
| 500 | + <footer> |
| 501 | + { |
| 502 | + repo && |
| 503 | + <div className="flex gap-4 px-4"> |
| 504 | + <button data-name="lightbulb" onClick={() => setTipOpen(prev => !prev)} disabled={isSendMessage} className="p-4 border rounded-md hover:border-[#FF66B3] hover:bg-[#FFF0F7]"> |
| 505 | + <Lightbulb /> |
505 | 506 | </button> |
506 | | - </form> |
507 | | - </div> |
508 | | - } |
509 | | - </footer> |
| 507 | + <form className="grow flex items-center border rounded-md px-2" onSubmit={sendQuery}> |
| 508 | + <DropdownMenuTrigger asChild> |
| 509 | + <button className="bg-gray-200 p-2 rounded-md hover:bg-gray-300"> |
| 510 | + <ArrowDown color="white" /> |
| 511 | + </button> |
| 512 | + </DropdownMenuTrigger> |
| 513 | + <input className="grow p-4 rounded-md focus-visible:outline-none" placeholder="Ask your question" onChange={handleQueryInputChange} value={query} /> |
| 514 | + <button disabled={isSendMessage} className={`bg-gray-200 p-2 rounded-md ${!isSendMessage && 'hover:bg-gray-300'}`}> |
| 515 | + <ArrowRight color="white" /> |
| 516 | + </button> |
| 517 | + </form> |
| 518 | + </div> |
| 519 | + } |
| 520 | + </footer> |
| 521 | + <DropdownMenuContent className="flex flex-col mb-4 w-[20dvw]" side="top"> |
| 522 | + { |
| 523 | + SUGGESTIONS.map((s, i) => ( |
| 524 | + <button |
| 525 | + disabled={isSendMessage} |
| 526 | + type="submit" |
| 527 | + key={i} |
| 528 | + className="p-2 text-left hover:bg-gray-200" |
| 529 | + onClick={() => { |
| 530 | + sendQuery(undefined, s) |
| 531 | + setSugOpen(false) |
| 532 | + }} |
| 533 | + > |
| 534 | + {s} |
| 535 | + </button> |
| 536 | + )) |
| 537 | + } |
| 538 | + </DropdownMenuContent> |
| 539 | + </DropdownMenu> |
510 | 540 | </div> |
511 | 541 | ); |
512 | 542 | } |
0 commit comments