diff --git a/web/src/components/chat/ChatPaperclipButton.tsx b/web/src/components/chat/ChatPaperclipButton.tsx new file mode 100644 index 0000000000..fac39f3c6b --- /dev/null +++ b/web/src/components/chat/ChatPaperclipButton.tsx @@ -0,0 +1,114 @@ +import { useState } from "react"; +import { useTranslation } from "react-i18next"; +import { LuPaperclip } from "react-icons/lu"; +import { useApiHost } from "@/api"; +import { Button } from "@/components/ui/button"; +import { Input } from "@/components/ui/input"; +import { + Popover, + PopoverContent, + PopoverTrigger, +} from "@/components/ui/popover"; + +const EVENT_ID_RE = /^[A-Za-z0-9._-]+$/; + +type ChatPaperclipButtonProps = { + recentEventIds: string[]; + onAttach: (eventId: string) => void; + disabled?: boolean; +}; + +/** + * Paperclip button with a popover for picking an event to attach. + * Shows a grid of recent thumbnails (from the latest assistant message) and a + * "paste event ID" fallback input. + */ +export function ChatPaperclipButton({ + recentEventIds, + onAttach, + disabled = false, +}: ChatPaperclipButtonProps) { + const apiHost = useApiHost(); + const { t } = useTranslation(["views/chat"]); + const [open, setOpen] = useState(false); + const [pasteId, setPasteId] = useState(""); + + const handlePickThumbnail = (eventId: string) => { + onAttach(eventId); + setOpen(false); + setPasteId(""); + }; + + const handlePasteSubmit = () => { + const trimmed = pasteId.trim(); + if (!trimmed || !EVENT_ID_RE.test(trimmed)) return; + onAttach(trimmed); + setOpen(false); + setPasteId(""); + }; + + const handlePasteKeyDown = (e: React.KeyboardEvent) => { + if (e.key === "Enter") { + e.preventDefault(); + handlePasteSubmit(); + } + }; + + return ( + + + + + +
+ {recentEventIds.length > 0 && ( +
+ {recentEventIds.slice(0, 8).map((id) => ( + + ))} +
+ )} +
+ setPasteId(e.target.value)} + onKeyDown={handlePasteKeyDown} + className="h-8 text-xs" + /> + +
+
+
+
+ ); +}