"use client"; import { makeAssistantToolUI } from "@assistant-ui/react"; import { CornerDownLeftIcon, MailIcon, Pen, TriangleAlertIcon, UserIcon, UsersIcon, } from "lucide-react"; import { useCallback, useEffect, useState } from "react"; import { Button } from "@/components/ui/button"; import { PlateEditor } from "@/components/editor/plate-editor"; import { TextShimmerLoader } from "@/components/prompt-kit/loader"; import { useSetAtom } from "jotai"; import { openHitlEditPanelAtom } from "@/atoms/chat/hitl-edit-panel.atom"; import type { ExtraField } from "@/atoms/chat/hitl-edit-panel.atom"; interface GmailAccount { id: number; name: string; email: string; auth_expired?: boolean; } interface GmailMessage { message_id: string; thread_id?: string; subject: string; sender: string; date: string; connector_id: number; document_id: number; } interface InterruptResult { __interrupt__: true; __decided__?: "approve" | "reject" | "edit"; action_requests: Array<{ name: string; args: Record; }>; review_configs: Array<{ action_name: string; allowed_decisions: Array<"approve" | "edit" | "reject">; }>; context?: { account?: GmailAccount; email?: GmailMessage; draft_id?: string; error?: string; }; } interface SuccessResult { status: "success"; draft_id?: string; message?: string; } interface ErrorResult { status: "error"; message: string; } interface NotFoundResult { status: "not_found"; message: string; } interface AuthErrorResult { status: "auth_error"; message: string; connector_type?: string; } interface InsufficientPermissionsResult { status: "insufficient_permissions"; connector_id: number; message: string; } type UpdateGmailDraftResult = | InterruptResult | SuccessResult | ErrorResult | NotFoundResult | InsufficientPermissionsResult | AuthErrorResult; function isInterruptResult(result: unknown): result is InterruptResult { return ( typeof result === "object" && result !== null && "__interrupt__" in result && (result as InterruptResult).__interrupt__ === true ); } function isErrorResult(result: unknown): result is ErrorResult { return ( typeof result === "object" && result !== null && "status" in result && (result as ErrorResult).status === "error" ); } function isNotFoundResult(result: unknown): result is NotFoundResult { return ( typeof result === "object" && result !== null && "status" in result && (result as NotFoundResult).status === "not_found" ); } function isAuthErrorResult(result: unknown): result is AuthErrorResult { return ( typeof result === "object" && result !== null && "status" in result && (result as AuthErrorResult).status === "auth_error" ); } function isInsufficientPermissionsResult( result: unknown, ): result is InsufficientPermissionsResult { return ( typeof result === "object" && result !== null && "status" in result && (result as InsufficientPermissionsResult).status === "insufficient_permissions" ); } function ApprovalCard({ args, interruptData, onDecision, }: { args: { draft_subject_or_id: string; body: string; to?: string; subject?: string; cc?: string; bcc?: string; }; interruptData: InterruptResult; onDecision: (decision: { type: "approve" | "reject" | "edit"; message?: string; edited_action?: { name: string; args: Record }; }) => void; }) { const [decided, setDecided] = useState< "approve" | "reject" | "edit" | null >(interruptData.__decided__ ?? null); const [wasAlreadyDecided] = useState( () => interruptData.__decided__ != null, ); const [isPanelOpen, setIsPanelOpen] = useState(false); const openHitlEditPanel = useSetAtom(openHitlEditPanelAtom); const [pendingEdits, setPendingEdits] = useState<{ subject: string; body: string; to: string; cc: string; bcc: string; } | null>(null); const account = interruptData.context?.account; const email = interruptData.context?.email; const draftId = interruptData.context?.draft_id; const reviewConfig = interruptData.review_configs[0]; const allowedDecisions = reviewConfig?.allowed_decisions ?? [ "approve", "reject", ]; const canEdit = allowedDecisions.includes("edit"); const currentSubject = pendingEdits?.subject ?? args.subject ?? email?.subject ?? args.draft_subject_or_id; const currentBody = pendingEdits?.body ?? args.body; const currentTo = pendingEdits?.to ?? args.to ?? ""; const currentCc = pendingEdits?.cc ?? args.cc ?? ""; const currentBcc = pendingEdits?.bcc ?? args.bcc ?? ""; const handleApprove = useCallback(() => { if (decided || isPanelOpen) return; if (!allowedDecisions.includes("approve")) return; const isEdited = pendingEdits !== null; setDecided(isEdited ? "edit" : "approve"); onDecision({ type: isEdited ? "edit" : "approve", edited_action: { name: interruptData.action_requests[0].name, args: { message_id: email?.message_id, draft_id: draftId, to: currentTo, subject: currentSubject, body: currentBody, cc: currentCc, bcc: currentBcc, connector_id: email?.connector_id ?? account?.id, }, }, }); }, [ decided, isPanelOpen, allowedDecisions, onDecision, interruptData, email, account?.id, draftId, pendingEdits, currentSubject, currentBody, currentTo, currentCc, currentBcc, ]); useEffect(() => { const handler = (e: KeyboardEvent) => { if (e.key === "Enter" && !e.shiftKey && !e.ctrlKey && !e.metaKey) { handleApprove(); } }; window.addEventListener("keydown", handler); return () => window.removeEventListener("keydown", handler); }, [handleApprove]); return (
{/* Header */}

{decided === "reject" ? "Draft Update Rejected" : decided === "approve" || decided === "edit" ? "Draft Update Approved" : "Update Gmail Draft"}

{decided === "approve" || decided === "edit" ? ( wasAlreadyDecided ? (

{decided === "edit" ? "Draft updated with your changes" : "Draft updated"}

) : ( ) ) : (

{decided === "reject" ? "Draft update was cancelled" : "Requires your approval to proceed"}

)}
{!decided && canEdit && ( )}
{/* Context — account and current draft info */} {!decided && interruptData.context && ( <>
{interruptData.context.error ? (

{interruptData.context.error}

) : ( <> {account && (

Gmail Account

{account.name}
)} {email && (

Draft to Update

{email.subject}
)} )}
)} {/* Email headers + body preview */}
{currentTo && (
To: {currentTo}
)} {currentCc && currentCc.trim() !== "" && (
CC: {currentCc}
)} {currentBcc && currentBcc.trim() !== "" && (
BCC: {currentBcc}
)}
{currentSubject != null && (

{currentSubject}

)} {currentBody != null && (
)}
{/* Action buttons */} {!decided && ( <>
{allowedDecisions.includes("approve") && ( )} {allowedDecisions.includes("reject") && ( )}
)}
); } function ErrorCard({ result }: { result: ErrorResult }) { return (

Failed to update Gmail draft

{result.message}

); } function AuthErrorCard({ result }: { result: AuthErrorResult }) { return (

Gmail authentication expired

{result.message}

); } function InsufficientPermissionsCard({ result, }: { result: InsufficientPermissionsResult }) { return (

Additional Gmail permissions required

{result.message}

); } function NotFoundCard({ result }: { result: NotFoundResult }) { return (

Draft not found

{result.message}

); } function SuccessCard({ result }: { result: SuccessResult }) { return (

{result.message || "Gmail draft updated successfully"}

); } export const UpdateGmailDraftToolUI = makeAssistantToolUI< { draft_subject_or_id: string; body: string; to?: string; subject?: string; cc?: string; bcc?: string; }, UpdateGmailDraftResult >({ toolName: "update_gmail_draft", render: function UpdateGmailDraftUI({ args, result }) { if (!result) return null; if (isInterruptResult(result)) { return ( { window.dispatchEvent( new CustomEvent("hitl-decision", { detail: { decisions: [decision] }, }), ); }} /> ); } if ( typeof result === "object" && result !== null && "status" in result && (result as { status: string }).status === "rejected" ) { return null; } if (isAuthErrorResult(result)) return ; if (isInsufficientPermissionsResult(result)) return ; if (isNotFoundResult(result)) return ; if (isErrorResult(result)) return ; return ; }, });