"use client"; import { makeAssistantToolUI } from "@assistant-ui/react"; import { CalendarIcon, ClockIcon, MapPinIcon, UsersIcon, ArrowRightIcon, CornerDownLeftIcon, Pen, TriangleAlertIcon, } from "lucide-react"; import { useCallback, useEffect, useState } from "react"; import { useSetAtom } from "jotai"; import { Button } from "@/components/ui/button"; import { PlateEditor } from "@/components/editor/plate-editor"; import { TextShimmerLoader } from "@/components/prompt-kit/loader"; import { openHitlEditPanelAtom } from "@/atoms/chat/hitl-edit-panel.atom"; import type { ExtraField } from "@/atoms/chat/hitl-edit-panel.atom"; interface GoogleCalendarAccount { id: number; name: string; auth_expired?: boolean; } interface CalendarEvent { event_id: string; summary: string; start: string; end: string; description?: string; location?: string; attendees?: Array<{ email: string }>; calendar_id: string; document_id: number; indexed_at?: string; } 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?: GoogleCalendarAccount; event?: CalendarEvent; error?: string; }; } interface SuccessResult { status: "success"; event_id: string; html_link?: 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 UpdateCalendarEventResult = | 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 formatDateTime(iso: string): string { try { return new Date(iso).toLocaleString(undefined, { dateStyle: "medium", timeStyle: "short", }); } catch { return iso; } } function ApprovalCard({ interruptData, onDecision, }: { interruptData: InterruptResult; onDecision: (decision: { type: "approve" | "reject" | "edit"; message?: string; edited_action?: { name: string; args: Record }; }) => void; }) { const actionArgs = interruptData.action_requests[0]?.args ?? {}; const context = interruptData.context; const account = context?.account; const event = context?.event; 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<{ summary: string; description: string; start_datetime: string; end_datetime: string; location: string; attendees: string; } | null>(null); const reviewConfig = interruptData.review_configs[0]; const allowedDecisions = reviewConfig?.allowed_decisions ?? ["approve", "reject"]; const canEdit = allowedDecisions.includes("edit"); const currentAttendees = event?.attendees?.map((a) => a.email) ?? []; const proposedAttendees = Array.isArray(actionArgs.new_attendees) ? (actionArgs.new_attendees as string[]) : null; const changes: Array<{ label: string; oldVal: string; newVal: string }> = []; if (actionArgs.new_summary && String(actionArgs.new_summary) !== event?.summary) { changes.push({ label: "Summary", oldVal: event?.summary ?? "", newVal: String(actionArgs.new_summary) }); } if (actionArgs.new_start_datetime && String(actionArgs.new_start_datetime) !== event?.start) { changes.push({ label: "Start", oldVal: event?.start ? formatDateTime(event.start) : "", newVal: formatDateTime(String(actionArgs.new_start_datetime)), }); } if (actionArgs.new_end_datetime && String(actionArgs.new_end_datetime) !== event?.end) { changes.push({ label: "End", oldVal: event?.end ? formatDateTime(event.end) : "", newVal: formatDateTime(String(actionArgs.new_end_datetime)), }); } if (actionArgs.new_location !== undefined && String(actionArgs.new_location ?? "") !== (event?.location ?? "")) { changes.push({ label: "Location", oldVal: event?.location ?? "", newVal: String(actionArgs.new_location ?? "") }); } if (proposedAttendees) { const oldStr = currentAttendees.join(", "); const newStr = proposedAttendees.join(", "); if (oldStr !== newStr) { changes.push({ label: "Attendees", oldVal: oldStr, newVal: newStr }); } } const hasDescriptionChange = actionArgs.new_description !== undefined && String(actionArgs.new_description ?? "") !== (event?.description ?? ""); const buildFinalArgs = useCallback(() => { if (pendingEdits) { const attendeesArr = pendingEdits.attendees ? pendingEdits.attendees.split(",").map((e) => e.trim()).filter(Boolean) : null; return { event_id: event?.event_id, document_id: event?.document_id, connector_id: account?.id, new_summary: pendingEdits.summary || null, new_description: pendingEdits.description || null, new_start_datetime: pendingEdits.start_datetime || null, new_end_datetime: pendingEdits.end_datetime || null, new_location: pendingEdits.location || null, new_attendees: attendeesArr, }; } return { event_id: event?.event_id, document_id: event?.document_id, connector_id: account?.id, new_summary: actionArgs.new_summary ?? null, new_description: actionArgs.new_description ?? null, new_start_datetime: actionArgs.new_start_datetime ?? null, new_end_datetime: actionArgs.new_end_datetime ?? null, new_location: actionArgs.new_location ?? null, new_attendees: proposedAttendees ?? null, }; }, [event, account, actionArgs, proposedAttendees, pendingEdits]); 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: buildFinalArgs(), }, }); }, [decided, isPanelOpen, allowedDecisions, onDecision, interruptData, buildFinalArgs, pendingEdits]); 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" ? "Calendar Event Update Rejected" : decided === "approve" || decided === "edit" ? "Calendar Event Update Approved" : "Update Calendar Event"}

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

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

) : ( ) ) : (

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

)}
{!decided && canEdit && ( )}
{/* Context section */} {!decided && ( <>
{context?.error ? (

{context.error}

) : ( <> {account && (

Google Calendar Account

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

Current Event

{event.summary}
{(event.start || event.end) && (
{event.start ? formatDateTime(event.start) : ""} {event.start && event.end ? " — " : ""} {event.end ? formatDateTime(event.end) : ""}
)} {event.location && (
{event.location}
)} {currentAttendees.length > 0 && (
{currentAttendees.join(", ")}
)}
)} {(changes.length > 0 || hasDescriptionChange) && (

Proposed Changes

{changes.map((change) => (
{change.label}
{change.oldVal || "(empty)"} {change.newVal || "(empty)"}
))} {hasDescriptionChange && (
Description
)}
)} {changes.length === 0 && !hasDescriptionChange && (

No changes proposed

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

Failed to update calendar event

{result.message}

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

Google Calendar authentication expired

{result.message}

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

Additional Google Calendar permissions required

{result.message}

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

Event not found

{result.message}

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

{result.message || "Calendar event updated successfully"}

{result.html_link && ( )}
); } export const UpdateCalendarEventToolUI = makeAssistantToolUI< { event_ref: string; new_summary?: string; new_description?: string; new_start_datetime?: string; new_end_datetime?: string; new_location?: string; new_attendees?: string[]; }, UpdateCalendarEventResult >({ toolName: "update_calendar_event", render: function UpdateCalendarEventUI({ 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 (isNotFoundResult(result)) return ; if (isAuthErrorResult(result)) return ; if (isInsufficientPermissionsResult(result)) return ; if (isErrorResult(result)) return ; return ; }, });