import { useMemo } from "react"; import type { PRComment } from "@/lib/types/github"; import { CollapsibleSection, AddToContextButton, AuthorAvatar, AuthorLink, formatTimeAgo, } from "./pr-shared"; function buildCommentMessage(comment: PRComment, prUrl: string): string { const location = comment.path ? `\`${comment.path}${comment.line > 0 ? `:${comment.line}` : ""}\`` : ""; const header = location ? `Comment from **${comment.author}** on ${location}:` : `Comment from **${comment.author}**:`; return [header, comment.body, `PR: ${prUrl}`, "Please address this comment."].join("\n\n"); } function buildThreadMessage(thread: CommentThread, prUrl: string): string { const parts = ["### Comment Thread", ""]; for (const c of [thread.root, ...thread.replies]) { const loc = c.path ? ` on \`${c.path}${c.line > 0 ? `:${c.line}` : ""}\`` : ""; parts.push(`**${c.author}**${loc}:`); parts.push(c.body); parts.push(""); } parts.push(`PR: ${prUrl}`); parts.push("Please address this comment thread."); return parts.join("\n"); } function buildAllCommentsMessage(comments: PRComment[], prUrl: string): string { const parts = ["### All PR Comments", ""]; for (const c of comments) { const loc = c.path ? ` on \`${c.path}${c.line > 0 ? `:${c.line}` : ""}\`` : ""; parts.push(`**${c.author}**${loc}:`); parts.push(c.body); parts.push(""); } parts.push(`PR: ${prUrl}`); parts.push("Please address the comments above."); return parts.join("\n"); } type CommentThread = { root: PRComment; replies: PRComment[]; }; function buildThreads(comments: PRComment[]): CommentThread[] { const byId = new Map(); for (const c of comments) byId.set(c.id, c); const roots: PRComment[] = []; const repliesByParent = new Map(); for (const c of comments) { if (c.in_reply_to === null || c.in_reply_to === 0 || !byId.has(c.in_reply_to)) { roots.push(c); } else { const existing = repliesByParent.get(c.in_reply_to) ?? []; existing.push(c); repliesByParent.set(c.in_reply_to, existing); } } return roots.map((root) => ({ root, replies: repliesByParent.get(root.id) ?? [], })); } function CommentItem({ comment, prUrl, onAddAsContext, isReply, }: { comment: PRComment; prUrl: string; onAddAsContext: (message: string) => void; isReply?: boolean; }) { return (
{comment.path && ( {comment.path} {comment.line > 0 && `:${comment.line}`} )} {!comment.path && !isReply && ( (general) )} {formatTimeAgo(comment.created_at)} onAddAsContext(buildCommentMessage(comment, prUrl))} />

{comment.body}

); } function ThreadBlock({ thread, prUrl, onAddAsContext, }: { thread: CommentThread; prUrl: string; onAddAsContext: (message: string) => void; }) { const hasReplies = thread.replies.length > 0; return (
{hasReplies && ( onAddAsContext(buildThreadMessage(thread, prUrl))} tooltip="Add whole thread to chat context" /> )}
{thread.replies.map((reply) => ( ))}
); } export function CommentsSection({ comments, prUrl, onAddAsContext, }: { comments: PRComment[]; prUrl: string; onAddAsContext: (message: string) => void; }) { const threads = useMemo(() => buildThreads(comments), [comments]); return ( onAddAsContext(buildAllCommentsMessage(comments, prUrl))} addAllLabel="Add all comments to chat context" > {comments.length === 0 && (

No comments yet

)} {threads.map((thread) => ( ))}
); }