diff --git a/ui/index.html b/ui/index.html index 3a033cb4..906158e4 100644 --- a/ui/index.html +++ b/ui/index.html @@ -4,6 +4,9 @@ + + + Paperclip diff --git a/ui/src/components/CommentThread.tsx b/ui/src/components/CommentThread.tsx index 6931a1f4..70c64d66 100644 --- a/ui/src/components/CommentThread.tsx +++ b/ui/src/components/CommentThread.tsx @@ -1,7 +1,8 @@ -import { useEffect, useMemo, useRef, useState } from "react"; +import { useEffect, useMemo, useRef, useState, type ChangeEvent } from "react"; import { Link } from "react-router-dom"; import type { IssueComment, Agent } from "@paperclip/shared"; import { Button } from "@/components/ui/button"; +import { Paperclip } from "lucide-react"; import { Identity } from "./Identity"; import { MarkdownBody } from "./MarkdownBody"; import { MarkdownEditor, type MarkdownEditorRef, type MentionOption } from "./MarkdownEditor"; @@ -18,7 +19,10 @@ interface CommentThreadProps { issueStatus?: string; agentMap?: Map; imageUploadHandler?: (file: File) => Promise; + /** Callback to attach an image file to the parent issue (not inline in a comment). */ + onAttachImage?: (file: File) => Promise; draftKey?: string; + liveRunSlot?: React.ReactNode; } const CLOSED_STATUSES = new Set(["done", "cancelled"]); @@ -52,11 +56,13 @@ function clearDraft(draftKey: string) { } } -export function CommentThread({ comments, onAdd, issueStatus, agentMap, imageUploadHandler, draftKey }: CommentThreadProps) { +export function CommentThread({ comments, onAdd, issueStatus, agentMap, imageUploadHandler, onAttachImage, draftKey, liveRunSlot }: CommentThreadProps) { const [body, setBody] = useState(""); const [reopen, setReopen] = useState(true); const [submitting, setSubmitting] = useState(false); + const [attaching, setAttaching] = useState(false); const editorRef = useRef(null); + const attachInputRef = useRef(null); const draftTimer = useRef | null>(null); const isClosed = issueStatus ? CLOSED_STATUSES.has(issueStatus) : false; @@ -112,6 +118,18 @@ export function CommentThread({ comments, onAdd, issueStatus, agentMap, imageUpl } } + async function handleAttachFile(evt: ChangeEvent) { + const file = evt.target.files?.[0]; + if (!file || !onAttachImage) return; + setAttaching(true); + try { + await onAttachImage(file); + } finally { + setAttaching(false); + if (attachInputRef.current) attachInputRef.current.value = ""; + } + } + return (

Comments ({comments.length})

@@ -122,7 +140,7 @@ export function CommentThread({ comments, onAdd, issueStatus, agentMap, imageUpl
{sorted.map((comment) => ( -
+
{comment.authorAgentId ? ( @@ -153,6 +171,8 @@ export function CommentThread({ comments, onAdd, issueStatus, agentMap, imageUpl ))}
+ {liveRunSlot} +
+ {onAttachImage && ( + <> + + + + )} {isClosed && (