Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
import { sendPromptToAgent } from "@features/sessions/utils/sendPromptToAgent";
import { PaperPlaneTilt, X } from "@phosphor-icons/react";
import type { AnnotationSide } from "@pierre/diffs";
import { Button, IconButton } from "@radix-ui/themes";
import { Button, Checkbox, Flex, IconButton, Text } from "@radix-ui/themes";
import { isSendMessageSubmitKey } from "@utils/sendMessageKey";
import { useCallback, useRef } from "react";
import { useCallback, useEffect, useRef, useState } from "react";
import { useReviewDraftsStore } from "../stores/reviewDraftsStore";
import { buildInlineCommentPrompt } from "../utils/reviewPrompts";

interface CommentAnnotationProps {
Expand All @@ -13,6 +14,8 @@ interface CommentAnnotationProps {
endLine: number;
side: AnnotationSide;
onDismiss: () => void;
initialText?: string;
editingDraftId?: string;
}

export function CommentAnnotation({
Expand All @@ -22,28 +25,78 @@ export function CommentAnnotation({
endLine,
side,
onDismiss,
initialText,
editingDraftId,
}: CommentAnnotationProps) {
const textareaRef = useRef<HTMLTextAreaElement>(null);
const addDraft = useReviewDraftsStore((s) => s.addDraft);
const updateDraft = useReviewDraftsStore((s) => s.updateDraft);
const setBatchEnabled = useReviewDraftsStore((s) => s.setBatchEnabled);
const initialBatchEnabled = useReviewDraftsStore((s) =>
s.isBatchEnabled(taskId),
);

const setTextareaRef = useCallback((el: HTMLTextAreaElement | null) => {
(
textareaRef as React.MutableRefObject<HTMLTextAreaElement | null>
).current = el;
if (el) {
requestAnimationFrame(() => el.focus());
}
}, []);
const [batch, setBatch] = useState(
editingDraftId ? true : initialBatchEnabled,
);

const setTextareaRef = useCallback(
(el: HTMLTextAreaElement | null) => {
(
textareaRef as React.MutableRefObject<HTMLTextAreaElement | null>
).current = el;
if (el) {
if (initialText !== undefined) {
el.value = initialText;
}
requestAnimationFrame(() => el.focus());
}
},
[initialText],
);

// Keep the checkbox in sync if another action toggles batchEnabled while
// this textarea is open (e.g. user adds a draft elsewhere).
useEffect(() => {
if (editingDraftId) return;
setBatch(initialBatchEnabled);
}, [initialBatchEnabled, editingDraftId]);

const handleSubmit = useCallback(() => {
const text = textareaRef.current?.value?.trim();
if (text) {
if (!text) return;

if (editingDraftId) {
updateDraft(taskId, editingDraftId, text);
onDismiss();
sendPromptToAgent(
taskId,
buildInlineCommentPrompt(filePath, startLine, endLine, side, text),
);
return;
}
}, [taskId, filePath, startLine, endLine, side, onDismiss]);

if (batch) {
addDraft(taskId, { filePath, startLine, endLine, side, text });
setBatchEnabled(taskId, true);
onDismiss();
return;
}

onDismiss();
sendPromptToAgent(
taskId,
buildInlineCommentPrompt(filePath, startLine, endLine, side, text),
);
}, [
taskId,
filePath,
startLine,
endLine,
side,
onDismiss,
batch,
editingDraftId,
addDraft,
updateDraft,
setBatchEnabled,
]);

const handleKeyDown = useCallback(
(e: React.KeyboardEvent) => {
Expand All @@ -59,27 +112,52 @@ export function CommentAnnotation({
[handleSubmit, onDismiss],
);

const submitLabel = editingDraftId
? "Update comment"
: batch
? "Add to review"
: "Send to agent";

return (
<div className="px-3 py-1.5">
<div
data-comment-annotation=""
className="whitespace-normal rounded-md border border-[var(--gray-5)] bg-[var(--gray-2)] px-2.5 py-2 font-sans"
className="whitespace-normal rounded-md border border-(--gray-5) bg-(--gray-2) px-2.5 py-2 font-sans"
>
<textarea
ref={setTextareaRef}
placeholder="Describe the changes you'd like..."
onKeyDown={handleKeyDown}
className="min-h-[48px] w-full resize-none rounded border border-[var(--gray-6)] bg-[var(--color-background)] p-1.5 text-[13px] text-[var(--gray-12)] leading-normal outline-none"
className="min-h-[48px] w-full resize-none rounded border border-(--gray-6) bg-(--color-background) p-1.5 text-(--gray-12) text-[13px] leading-normal outline-none"
/>
<div className="mt-1.5 flex items-center gap-3">
<Button size="1" onClick={handleSubmit}>
<PaperPlaneTilt size={12} weight="fill" />
Send to agent
</Button>
<IconButton size="1" variant="ghost" color="gray" onClick={onDismiss}>
<X size={12} />
</IconButton>
</div>
<Flex align="center" justify="between" gap="3" className="mt-1.5">
<Flex align="center" gap="3">
<Button size="1" onClick={handleSubmit}>
<PaperPlaneTilt size={12} weight="fill" />
{submitLabel}
</Button>
<IconButton
size="1"
variant="ghost"
color="gray"
onClick={onDismiss}
>
<X size={12} />
</IconButton>
</Flex>
{!editingDraftId && (
<Text as="label" size="1" color="gray">
<Flex align="center" gap="1.5" className="cursor-pointer">
<Checkbox
size="1"
checked={batch}
onCheckedChange={(value) => setBatch(value === true)}
/>
Add to review
</Flex>
</Text>
)}
</Flex>
</div>
</div>
);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
import { PencilSimple, Trash } from "@phosphor-icons/react";
import { Badge, Flex, IconButton, Text } from "@radix-ui/themes";
import { useReviewDraftsStore } from "../stores/reviewDraftsStore";

interface DraftCommentAnnotationProps {
taskId: string;
draftId: string;
onEdit: (draftId: string) => void;
}

export function DraftCommentAnnotation({
taskId,
draftId,
onEdit,
}: DraftCommentAnnotationProps) {
const draft = useReviewDraftsStore((s) =>
(s.drafts[taskId] ?? []).find((d) => d.id === draftId),
);
const removeDraft = useReviewDraftsStore((s) => s.removeDraft);

if (!draft) return null;

return (
<div className="px-3 py-1.5">
<div
data-draft-comment-annotation=""
className="whitespace-normal rounded-md border border-(--gray-6) border-dashed bg-(--gray-2) px-2.5 py-2 font-sans"
>
<Flex align="center" justify="between" gap="2" className="mb-1">
<Badge color="gray" size="1" variant="soft">
Pending review comment
</Badge>
<Flex gap="1">
<IconButton
size="1"
variant="ghost"
color="gray"
onClick={() => onEdit(draftId)}
aria-label="Edit draft comment"
>
<PencilSimple size={12} />
</IconButton>
<IconButton
size="1"
variant="ghost"
color="gray"
onClick={() => removeDraft(taskId, draftId)}
aria-label="Delete draft comment"
>
<Trash size={12} />
</IconButton>
</Flex>
</Flex>
<Text
as="div"
size="1"
className="whitespace-pre-wrap text-(--gray-12) leading-normal"
>
{draft.text}
</Text>
</div>
</div>
);
}
Loading
Loading