import { useEffect, useMemo, useRef, useState } from "react" import { useQuery } from "@tanstack/react-query" import { ChatCircleIcon, MagnifyingGlassIcon } from "@phosphor-icons/react" import type { PrReviewComment } from "@/lib/api" import { api } from "@/lib/api" import { cn } from "@/lib/utils" function basename(path: string): string { const idx = path.lastIndexOf("/") return idx === -1 ? path : path.slice(idx + 1) } // Devin-style dropdown surfacing inline PR comments left by people (the // reviewer's own findings already render inline + in the side panel, so they're // filtered out). Each entry links to the comment thread on GitHub. export function ReviewCommentsMenu({ owner, repo, number, onSelect, }: { owner: string repo: string number: number onSelect: (comment: PrReviewComment) => void }) { const [open, setOpen] = useState(false) const [query, setQuery] = useState("") const wrapperRef = useRef(null) const comments = useQuery({ queryKey: ["reviewComments", owner, repo, number], queryFn: () => api.listReviewComments(owner, repo, number), enabled: Number.isFinite(number), staleTime: 30_000, }) const otherComments = useMemo( () => (comments.data?.comments ?? []).filter((c) => !c.is_open_swe), [comments.data] ) const filtered = useMemo(() => { const q = query.trim().toLowerCase() if (!q) return otherComments return otherComments.filter((c) => `${c.author} ${c.path} ${c.body}`.toLowerCase().includes(q) ) }, [otherComments, query]) useEffect(() => { if (!open) return const onPointerDown = (event: PointerEvent) => { if ( event.target instanceof Node && !wrapperRef.current?.contains(event.target) ) { setOpen(false) } } const onKeyDown = (event: KeyboardEvent) => { if (event.key === "Escape") setOpen(false) } window.addEventListener("pointerdown", onPointerDown) window.addEventListener("keydown", onKeyDown) return () => { window.removeEventListener("pointerdown", onPointerDown) window.removeEventListener("keydown", onKeyDown) } }, [open]) const count = otherComments.length return (
{open && (
setQuery(event.target.value)} placeholder="Search comments" className="w-full bg-transparent text-xs outline-none placeholder:text-muted-foreground" />
{comments.isLoading ? (

Loading…

) : comments.isError ? (

Failed to load comments

) : filtered.length === 0 ? (

{otherComments.length === 0 ? "No comments yet" : "No matching comments"}

) : (
    {filtered.map((comment) => (
  • ))}
)}
)}
) }