import { useActionMutation } from "@agent-native/core/client/hooks"; import { useT } from "@agent-native/core/client/i18n"; import { IconMessagePlus, IconAt, IconMoodSmile } from "@tabler/icons-react"; import { useEffect, useRef, useState } from "react"; import { Button } from "@/components/ui/button"; import { Textarea } from "@/components/ui/textarea"; import { cn } from "@/lib/utils"; import { msToClock } from "./scrubber"; interface TimestampedCommentButtonProps { enableComments: boolean; onOpen: () => void; className?: string; } /** Trigger that opens the docked comment composer, pinned to the current time. */ export function TimestampedCommentButton({ enableComments, onOpen, className, }: TimestampedCommentButtonProps) { const t = useT(); if (!enableComments) return null; return ( {t("commentsPanel.commentButton")} ); } interface TimestampedCommentBarProps { recordingId: string; atMs: number; onClose: () => void; onAdded?: () => void; className?: string; } /** * Bottom-docked comment composer. Render inside a `relative` container (the * video wrapper) so it overlays the bottom of the video at the captured moment. */ export function TimestampedCommentBar({ recordingId, atMs, onClose, onAdded, className, }: TimestampedCommentBarProps) { const t = useT(); const [draft, setDraft] = useState(""); const textareaRef = useRef(null); const addComment = useActionMutation("add-comment"); useEffect(() => { requestAnimationFrame(() => textareaRef.current?.focus()); }, []); const insertAtCursor = (text: string) => { const el = textareaRef.current; if (!el) { setDraft((d) => d + text); return; } const start = el.selectionStart ?? draft.length; const end = el.selectionEnd ?? draft.length; const next = draft.slice(0, start) + text + draft.slice(end); setDraft(next); requestAnimationFrame(() => { el.focus(); const pos = start + text.length; el.setSelectionRange(pos, pos); }); }; const submit = () => { const content = draft.trim(); if (!content) return; addComment.mutate( { recordingId, content, videoTimestampMs: atMs }, { onSuccess: () => { setDraft(""); onAdded?.(); onClose(); }, }, ); }; return ( setDraft(e.target.value)} onKeyDown={(e) => { if (e.key === "Enter" && (e.metaKey || e.ctrlKey)) { e.preventDefault(); submit(); } if (e.key === "Escape") { e.preventDefault(); onClose(); } }} placeholder={t("commentsPanel.composerPlaceholder")} rows={2} className="min-h-[3rem] resize-none border-0 bg-transparent px-1 text-sm shadow-none focus-visible:ring-0 focus-visible:ring-offset-0" /> insertAtCursor("@")} > insertAtCursor("🙂")} > {t("common.cancel")} {t("commentsPanel.commentAt")} {msToClock(atMs)} ); }