"use client"; import { makeAssistantToolUI } from "@assistant-ui/react"; import { AlertTriangleIcon, CheckIcon, InfoIcon, Loader2Icon, Pen, XIcon } from "lucide-react"; import { useState } from "react"; import { Button } from "@/components/ui/button"; import { Input } from "@/components/ui/input"; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue, } from "@/components/ui/select"; import { Textarea } from "@/components/ui/textarea"; interface LinearLabel { id: string; name: string; color: string; } interface LinearState { id: string; name: string; type: string; color: string; } interface LinearMember { id: string; name: string; displayName: string; email: string; active: boolean; } interface LinearPriority { priority: number; label: 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">; }>; interrupt_type?: string; context?: { workspace?: { id: number; organization_name: string }; priorities?: LinearPriority[]; issue?: { id: string; identifier: string; title: string; description?: string; priority: number; url: string; current_state?: LinearState; current_assignee?: { id: string; name: string; email: string } | null; current_labels?: LinearLabel[]; team_id: string; document_id: number; }; team?: { id: string; name: string; key: string; states: LinearState[]; members: LinearMember[]; labels: LinearLabel[]; }; error?: string; }; } interface SuccessResult { status: "success"; identifier: string; url: string; message?: string; } interface ErrorResult { status: "error"; message: string; } interface NotFoundResult { status: "not_found"; message: string; } type UpdateLinearIssueResult = InterruptResult | SuccessResult | ErrorResult | NotFoundResult; 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 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 team = context?.team; const priorities = context?.priorities ?? []; const issue = context?.issue; const initialEditState = { title: actionArgs.new_title ? String(actionArgs.new_title) : (issue?.title ?? ""), description: actionArgs.new_description ? String(actionArgs.new_description) : (issue?.description ?? ""), stateId: actionArgs.new_state_id ? String(actionArgs.new_state_id) : (issue?.current_state?.id ?? "__none__"), assigneeId: actionArgs.new_assignee_id ? String(actionArgs.new_assignee_id) : (issue?.current_assignee?.id ?? "__none__"), priority: actionArgs.new_priority != null ? String(actionArgs.new_priority) : String(issue?.priority ?? 0), labelIds: Array.isArray(actionArgs.new_label_ids) ? (actionArgs.new_label_ids as string[]) : (issue?.current_labels?.map((l) => l.id) ?? []), }; const [decided, setDecided] = useState<"approve" | "reject" | "edit" | null>( interruptData.__decided__ ?? null ); const [isEditing, setIsEditing] = useState(false); const [editedArgs, setEditedArgs] = useState(initialEditState); const reviewConfig = interruptData.review_configs[0]; const allowedDecisions = reviewConfig?.allowed_decisions ?? ["approve", "reject"]; const canEdit = allowedDecisions.includes("edit"); function resolveStateName(stateId: string | null) { if (!stateId || stateId === "__none__") return null; return team?.states.find((s) => s.id === stateId)?.name ?? stateId; } function resolveAssigneeName(assigneeId: string | null) { if (!assigneeId || assigneeId === "__none__") return null; const m = team?.members.find((m) => m.id === assigneeId); return m ? `${m.name} (${m.email})` : assigneeId; } function resolvePriorityLabel(p: string | null) { if (!p || p === "__none__") return null; return priorities.find((pr) => String(pr.priority) === p)?.label ?? p; } function resolveLabelNames(ids: string[]) { return ids.map((id) => team?.labels.find((l) => l.id === id)).filter(Boolean) as LinearLabel[]; } function buildFinalArgs() { const labelsWereProposed = Array.isArray(actionArgs.new_label_ids); return { issue_id: issue?.id, document_id: issue?.document_id, connector_id: context?.workspace?.id, new_title: editedArgs.title || null, new_description: editedArgs.description || null, new_state_id: editedArgs.stateId === "__none__" ? null : editedArgs.stateId, new_assignee_id: editedArgs.assigneeId === "__none__" ? null : editedArgs.assigneeId, new_priority: Number(editedArgs.priority), new_label_ids: labelsWereProposed || editedArgs.labelIds.length > 0 ? editedArgs.labelIds : null, }; } const proposedStateName = resolveStateName( actionArgs.new_state_id ? String(actionArgs.new_state_id) : null ); const proposedAssigneeName = resolveAssigneeName( actionArgs.new_assignee_id ? String(actionArgs.new_assignee_id) : null ); const proposedPriorityLabel = resolvePriorityLabel( actionArgs.new_priority != null ? String(actionArgs.new_priority) : null ); const proposedLabelObjects = resolveLabelNames( Array.isArray(actionArgs.new_label_ids) ? (actionArgs.new_label_ids as string[]) : [] ); const hasProposedChanges = actionArgs.new_title || actionArgs.new_description || proposedStateName || proposedAssigneeName || proposedPriorityLabel || proposedLabelObjects.length > 0; return (
{/* Header */}

Update Linear Issue

{isEditing ? "You can edit the arguments below" : "Requires your approval to proceed"}

{/* Context section — workspace + current issue (read-only) */} {!decided && (
{context?.error ? (

{context.error}

) : ( <> {context?.workspace && (
Linear Account
{context.workspace.organization_name}
)} {issue && (
Current Issue
{issue.identifier}: {issue.title}
{issue.current_state && ( {issue.current_state.name} )} {issue.current_assignee && {issue.current_assignee.name}} {priorities.find((p) => p.priority === issue.priority) && ( {priorities.find((p) => p.priority === issue.priority)?.label} )}
{issue.current_labels && issue.current_labels.length > 0 && (
{issue.current_labels.map((label) => ( {label.name} ))}
)} {issue.url && ( Open in Linear ↗ )}
)} )}
)} {/* Display mode — proposed changes */} {!isEditing && (
{hasProposedChanges ? ( <> {actionArgs.new_title && (

New Title

{String(actionArgs.new_title)}

)} {actionArgs.new_description && (

New Description

{String(actionArgs.new_description)}

)} {proposedStateName && (

New State

{proposedStateName}

)} {proposedAssigneeName && (

New Assignee

{proposedAssigneeName}

)} {proposedPriorityLabel && (

New Priority

{proposedPriorityLabel}

)} {proposedLabelObjects.length > 0 && (

New Labels

{proposedLabelObjects.map((label) => ( {label.name} ))}
)} ) : (

No changes proposed

)}
)} {/* Edit mode */} {isEditing && !decided && (
setEditedArgs({ ...editedArgs, title: e.target.value })} placeholder="Issue title" />