"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 { Badge } from "@/components/ui/badge"; import { Button } from "@/components/ui/button"; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue, } from "@/components/ui/select"; import { useHitlPhase } from "@/hooks/use-hitl-phase"; interface JiraIssue { issue_id: string; issue_identifier: string; issue_title: string; state?: string; priority?: string; issue_type?: string; assignee?: string; description?: string; project?: string; document_id?: number; } interface JiraAccount { id: number; name: string; base_url: string; auth_expired?: boolean; } interface JiraPriority { id: string; name: string; } interface InterruptResult { __interrupt__: true; __decided__?: "approve" | "reject" | "edit"; __completed__?: boolean; action_requests: Array<{ name: string; args: Record; }>; review_configs: Array<{ action_name: string; allowed_decisions: Array<"approve" | "edit" | "reject">; }>; interrupt_type?: string; context?: { account?: JiraAccount; issue?: JiraIssue; priorities?: JiraPriority[]; error?: string; }; } interface SuccessResult { status: "success"; issue_key: string; issue_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 UpdateJiraIssueResult = | InterruptResult | SuccessResult | ErrorResult | NotFoundResult | AuthErrorResult | InsufficientPermissionsResult; 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: { issue_title_or_key: string; new_summary?: string; new_description?: string; new_priority?: string; }; interruptData: InterruptResult; onDecision: (decision: { type: "approve" | "reject" | "edit"; message?: string; edited_action?: { name: string; args: Record }; }) => void; }) { const { phase, setProcessing, setRejected } = useHitlPhase(interruptData); const actionArgs = interruptData.action_requests[0]?.args ?? {}; const context = interruptData.context; const account = context?.account; const issue = context?.issue; const priorities = context?.priorities ?? []; const initialEditState = { summary: actionArgs.new_summary ? String(actionArgs.new_summary) : (issue?.issue_title ?? args.new_summary ?? ""), description: actionArgs.new_description ? String(actionArgs.new_description) : (issue?.description ?? args.new_description ?? ""), priority: actionArgs.new_priority ? String(actionArgs.new_priority) : (issue?.priority ?? args.new_priority ?? "__none__"), }; const [isPanelOpen, setIsPanelOpen] = useState(false); const [editedArgs, setEditedArgs] = useState(initialEditState); 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_summary || args.new_summary || actionArgs.new_description || args.new_description || actionArgs.new_priority || args.new_priority; const buildFinalArgs = useCallback(() => { return { issue_id: issue?.issue_id, document_id: issue?.document_id, connector_id: account?.id, new_summary: editedArgs.summary || null, new_description: editedArgs.description || null, new_priority: editedArgs.priority === "__none__" ? null : editedArgs.priority, }; }, [issue?.issue_id, issue?.document_id, 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" ? "Jira Issue Update Rejected" : phase === "processing" || phase === "complete" ? "Jira Issue Update Approved" : "Update Jira Issue"}

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

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

) : phase === "rejected" ? (

Issue update was cancelled

) : (

Requires your approval to proceed

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

{context.error}

) : ( <> {account && (

Jira Account

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

Current Issue

{issue.issue_identifier}: {issue.issue_title}
{issue.state && ( {issue.state} )} {issue.issue_type && {issue.issue_type}} {issue.assignee && {issue.assignee}} {issue.priority && Priority: {issue.priority}}
{issue.project && (
Project: {issue.project}
)}
)} {priorities.length > 0 && (

Priority

)} )}
)} {/* Content preview — proposed changes */}
{hasProposedChanges || hasPanelEdits ? ( <> {(hasPanelEdits ? editedArgs.summary : (actionArgs.new_summary ?? args.new_summary)) && (

{String( hasPanelEdits ? editedArgs.summary : (actionArgs.new_summary ?? args.new_summary) )}

)} {(hasPanelEdits ? editedArgs.description : (actionArgs.new_description ?? args.new_description)) && (
)} {(actionArgs.new_priority ?? args.new_priority) && (
Priority → {String(actionArgs.new_priority ?? args.new_priority)}
)} ) : (

No changes proposed

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

Jira authentication expired

{result.message}

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

Additional Jira permissions required

{result.message}

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

Failed to update Jira issue

{result.message}

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

Issue not found

{result.message}

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

{result.message || "Jira issue updated successfully"}

{result.issue_url ? ( Open in Jira ) : (
Issue Key: {result.issue_key}
)}
); } export const UpdateJiraIssueToolUI = ({ args, result }: ToolCallMessagePartProps< { issue_title_or_key: string; new_summary?: string; new_description?: string; new_priority?: string; }, UpdateJiraIssueResult >) => { 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 (isInsufficientPermissionsResult(result)) return ; if (isAuthErrorResult(result)) return ; if (isErrorResult(result)) return ; return ; };