"use client"; import { makeAssistantToolUI } from "@assistant-ui/react"; import { AlertTriangleIcon, CheckIcon, Loader2Icon, Pen, XIcon } from "lucide-react"; import { useMemo, 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; position: number; } interface LinearMember { id: string; name: string; displayName: string; email: string; active: boolean; } interface LinearTeam { id: string; name: string; key: string; states: LinearState[]; members: LinearMember[]; labels: LinearLabel[]; } interface LinearPriority { priority: number; label: string; } interface LinearWorkspace { id: number; name: string; organization_name: string; teams: LinearTeam[]; priorities: LinearPriority[]; } 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?: { workspaces?: LinearWorkspace[]; error?: string; }; } interface SuccessResult { status: "success"; issue_id: string; identifier: string; url: string; message?: string; } interface ErrorResult { status: "error"; message: string; } type CreateLinearIssueResult = InterruptResult | SuccessResult | ErrorResult; 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 ApprovalCard({ args, interruptData, onDecision, }: { args: { title: string; description?: 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 [isEditing, setIsEditing] = useState(false); const [editedTitle, setEditedTitle] = useState(args.title ?? ""); const [editedDescription, setEditedDescription] = useState(args.description ?? ""); const [selectedWorkspaceId, setSelectedWorkspaceId] = useState(""); const [selectedTeamId, setSelectedTeamId] = useState(""); const [selectedStateId, setSelectedStateId] = useState("__none__"); const [selectedAssigneeId, setSelectedAssigneeId] = useState("__none__"); const [selectedPriority, setSelectedPriority] = useState("0"); const [selectedLabelIds, setSelectedLabelIds] = useState([]); const workspaces = interruptData.context?.workspaces ?? []; const selectedWorkspace = useMemo( () => workspaces.find((w) => String(w.id) === selectedWorkspaceId) ?? null, [workspaces, selectedWorkspaceId] ); const selectedTeam = useMemo( () => selectedWorkspace?.teams.find((t) => t.id === selectedTeamId) ?? null, [selectedWorkspace, selectedTeamId] ); const isTitleValid = editedTitle.trim().length > 0; const canApprove = !!selectedWorkspaceId && !!selectedTeamId && isTitleValid; const reviewConfig = interruptData.review_configs[0]; const allowedDecisions = reviewConfig?.allowed_decisions ?? ["approve", "reject"]; const canEdit = allowedDecisions.includes("edit"); function buildFinalArgs() { return { title: editedTitle, description: editedDescription || null, connector_id: selectedWorkspaceId ? Number(selectedWorkspaceId) : null, team_id: selectedTeamId || null, state_id: selectedStateId === "__none__" ? null : selectedStateId, assignee_id: selectedAssigneeId === "__none__" ? null : selectedAssigneeId, priority: Number(selectedPriority), label_ids: selectedLabelIds, }; } return (
{/* Header */}

Create Linear Issue

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

{/* Context section */} {!decided && (
{interruptData.context?.error ? (

{interruptData.context.error}

) : ( <> {workspaces.length > 0 && (
Linear Account *
)} {selectedWorkspace && ( <>
Team *
{selectedTeam && ( <>
State
Assignee
Priority
{selectedTeam.labels.length > 0 && (
Labels
{selectedTeam.labels.map((label) => { const isSelected = selectedLabelIds.includes(label.id); return ( ); })}
)} )} )} )}
)} {/* Display mode */} {!isEditing && (

Title

{args.title}

{args.description && (

Description

{args.description}

)}
)} {/* Edit mode */} {isEditing && !decided && (
setEditedTitle(e.target.value)} placeholder="Enter issue title" className={!isTitleValid ? "border-destructive" : ""} /> {!isTitleValid &&

Title is required

}