"use client"; import type { ToolCallMessagePartProps } from "@assistant-ui/react"; import { useSetAtom } from "jotai"; import { CornerDownLeftIcon, Pen } from "lucide-react"; import { useCallback, useEffect, useState } from "react"; import { openHitlEditPanelAtom } from "@/atoms/chat/hitl-edit-panel.atom"; import { PlateEditor } from "@/components/editor/plate-editor"; import { TextShimmerLoader } from "@/components/prompt-kit/loader"; import { Button } from "@/components/ui/button"; import { useHitlPhase } from "@/hooks/use-hitl-phase"; import type { HitlDecision, InterruptResult } from "@/lib/hitl"; import { isInterruptResult, useHitlDecision } from "@/lib/hitl"; type UpdateConfluencePageInterruptContext = { account?: { id: number; name: string; base_url: string; auth_expired?: boolean; }; page?: { page_id: string; page_title: string; space_id: string; body: string; version: number; document_id: number; indexed_at?: string; }; error?: string; }; interface SuccessResult { status: "success"; page_id: string; page_url?: string; message?: string; } interface ErrorResult { status: "error"; message: string; } interface NotFoundResult { status: "not_found"; message: string; } interface AuthErrorResult { status: "auth_error"; message: string; connector_id?: number; connector_type: string; } interface InsufficientPermissionsResult { status: "insufficient_permissions"; connector_id: number; message: string; } type UpdateConfluencePageResult = | InterruptResult | SuccessResult | ErrorResult | NotFoundResult | AuthErrorResult | InsufficientPermissionsResult; 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: { page_title_or_id: string; new_title?: string; new_content?: string; }; interruptData: InterruptResult; onDecision: (decision: HitlDecision) => void; }) { const { phase, setProcessing, setRejected } = useHitlPhase(interruptData); const actionArgs = interruptData.action_requests[0]?.args ?? {}; const context = interruptData.context; const page = context?.page; const [isPanelOpen, setIsPanelOpen] = useState(false); const [editedArgs, setEditedArgs] = useState(() => ({ title: actionArgs.new_title ? String(actionArgs.new_title) : (page?.page_title ?? args.new_title ?? ""), content: actionArgs.new_content ? String(actionArgs.new_content) : (page?.body ?? args.new_content ?? ""), })); const [hasPanelEdits, setHasPanelEdits] = useState(false); const openHitlEditPanel = useSetAtom(openHitlEditPanelAtom); const reviewConfig = interruptData.review_configs[0]; const allowedDecisions = reviewConfig?.allowed_decisions ?? ["approve", "reject"]; const canEdit = allowedDecisions.includes("edit"); const hasProposedChanges = actionArgs.new_title || args.new_title || actionArgs.new_content || args.new_content; const buildFinalArgs = useCallback(() => { return { page_id: page?.page_id, document_id: page?.document_id, connector_id: context?.account?.id, new_title: editedArgs.title || null, new_content: editedArgs.content || null, version: page?.version, }; }, [page?.page_id, page?.document_id, page?.version, context?.account?.id, editedArgs]); const handleApprove = useCallback(() => { if (phase !== "pending") return; if (isPanelOpen) return; if (!allowedDecisions.includes("approve")) return; const isEdited = hasPanelEdits; setProcessing(); onDecision({ type: isEdited ? "edit" : "approve", edited_action: { name: interruptData.action_requests[0].name, args: buildFinalArgs(), }, }); }, [ phase, setProcessing, isPanelOpen, allowedDecisions, onDecision, interruptData, buildFinalArgs, hasPanelEdits, ]); 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 */}

{phase === "rejected" ? "Confluence Page Update Rejected" : phase === "processing" || phase === "complete" ? "Confluence Page Update Approved" : "Update Confluence Page"}

{phase === "processing" ? ( ) : phase === "complete" ? (

{hasPanelEdits ? "Page updated with your changes" : "Page updated"}

) : phase === "rejected" ? (

Page update was cancelled

) : (

Requires your approval to proceed

)}
{phase === "pending" && canEdit && ( )}
{/* Context section — account + current page (visible in pending) */} {phase === "pending" && ( <>
{context?.error ? (

{context.error}

) : ( <> {context?.account && (

Confluence Account

{context.account.name}
)} {page && (

Current Page

{page.page_title}
{page.body && (
)} {page.space_id && (
Space: {page.space_id}
)}
)} )}
)} {/* Content preview — proposed changes */}
{hasProposedChanges || hasPanelEdits ? ( <> {(hasPanelEdits ? editedArgs.title : (actionArgs.new_title ?? args.new_title)) && (

{String( hasPanelEdits ? editedArgs.title : (actionArgs.new_title ?? args.new_title) )}

)} {(hasPanelEdits ? editedArgs.content : (actionArgs.new_content ?? args.new_content)) && (
)} ) : (

No changes proposed

)}
{/* Action buttons - only shown when pending */} {phase === "pending" && ( <>
{allowedDecisions.includes("approve") && ( )} {allowedDecisions.includes("reject") && ( )}
)}
); } function AuthErrorCard({ result }: { result: AuthErrorResult }) { return (

Confluence authentication expired

{result.message}

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

Additional Confluence permissions required

{result.message}

); } function ErrorCard({ result }: { result: ErrorResult }) { return (

Failed to update Confluence page

{result.message}

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

Page not found

{result.message}

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

{result.message || "Confluence page updated successfully"}

{result.page_url ? ( Open in Confluence ) : (
Page ID: {result.page_id}
)}
); } export const UpdateConfluencePageToolUI = ({ args, result, }: ToolCallMessagePartProps< { page_title_or_id: string; new_title?: string; new_content?: string; }, UpdateConfluencePageResult >) => { const { dispatch } = useHitlDecision(); if (!result) return null; if (isInterruptResult(result)) { return ( } onDecision={(decision) => dispatch([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 ; };