diff --git a/surfsense_web/components/tool-ui/linear/create-linear-issue.tsx b/surfsense_web/components/tool-ui/linear/create-linear-issue.tsx index ec45ff2aa..beddbc74d 100644 --- a/surfsense_web/components/tool-ui/linear/create-linear-issue.tsx +++ b/surfsense_web/components/tool-ui/linear/create-linear-issue.tsx @@ -1,10 +1,9 @@ "use client"; import { makeAssistantToolUI } from "@assistant-ui/react"; -import { AlertTriangleIcon, CheckIcon, Loader2Icon, Pen, XIcon } from "lucide-react"; -import { useMemo, useState } from "react"; +import { CornerDownLeftIcon, Pen } from "lucide-react"; +import { useCallback, useEffect, useMemo, useState } from "react"; import { Button } from "@/components/ui/button"; -import { Input } from "@/components/ui/input"; import { Select, SelectContent, @@ -12,7 +11,10 @@ import { SelectTrigger, SelectValue, } from "@/components/ui/select"; -import { Textarea } from "@/components/ui/textarea"; +import { PlateEditor } from "@/components/editor/plate-editor"; +import { Spinner } from "@/components/ui/spinner"; +import { useSetAtom } from "jotai"; +import { openHitlEditPanelAtom } from "@/atoms/chat/hitl-edit-panel.atom"; interface LinearLabel { id: string; @@ -125,9 +127,9 @@ function ApprovalCard({ 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 [isPanelOpen, setIsPanelOpen] = useState(false); + const openHitlEditPanel = useSetAtom(openHitlEditPanelAtom); + const [selectedWorkspaceId, setSelectedWorkspaceId] = useState(""); const [selectedTeamId, setSelectedTeamId] = useState(""); const [selectedStateId, setSelectedStateId] = useState("__none__"); @@ -147,17 +149,17 @@ function ApprovalCard({ [selectedWorkspace, selectedTeamId] ); - const isTitleValid = editedTitle.trim().length > 0; + const isTitleValid = (args.title ?? "").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() { + const buildFinalArgs = useCallback((overrides?: { title?: string; description?: string }) => { return { - title: editedTitle, - description: editedDescription || null, + title: overrides?.title ?? args.title, + description: overrides?.description ?? args.description ?? null, connector_id: selectedWorkspaceId ? Number(selectedWorkspaceId) : null, team_id: selectedTeamId || null, state_id: selectedStateId === "__none__" ? null : selectedStateId, @@ -165,372 +167,322 @@ function ApprovalCard({ priority: Number(selectedPriority), label_ids: selectedLabelIds, }; - } + }, [args.title, args.description, selectedWorkspaceId, selectedTeamId, selectedStateId, selectedAssigneeId, selectedPriority, selectedLabelIds]); + + const handleApprove = useCallback(() => { + if (decided || isPanelOpen || !canApprove) return; + if (!allowedDecisions.includes("approve")) return; + setDecided("approve"); + onDecision({ + type: "approve", + edited_action: { + name: interruptData.action_requests[0].name, + args: buildFinalArgs(), + }, + }); + }, [decided, isPanelOpen, canApprove, allowedDecisions, onDecision, interruptData, buildFinalArgs]); + + 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 */} -
-
- -
-
-

Create Linear Issue

-

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

+
+

+ {decided === "reject" + ? "Linear Issue Rejected" + : decided === "approve" || decided === "edit" + ? "Linear Issue Approved" + : "Create Linear Issue"} +

+

+ {decided === "reject" + ? "Issue creation was cancelled" + : decided === "edit" + ? "Issue creation is in progress with your changes" + : decided === "approve" + ? "Issue creation is in progress" + : "Requires your approval to proceed"}

+ {!decided && canEdit && ( + + )}
{/* Context section */} {!decided && ( -
- {interruptData.context?.error ? ( -

{interruptData.context.error}

- ) : ( - <> - {workspaces.length > 0 && ( -
-
- Linear Account * -
- -
- )} - - {selectedWorkspace && ( - <> -
-
- Team * -
+ <> +
+
+ {interruptData.context?.error ? ( +

{interruptData.context.error}

+ ) : ( + <> + {workspaces.length > 0 && ( +
+

+ Linear Account * +

+ )} - {selectedTeam && ( - <> -
-
State
- -
+ {selectedWorkspace && ( + <> +
+

+ Team * +

+ +
-
-
Assignee
- + + + + + {selectedTeam.states.map((s) => ( + + {s.name} ))} - - -
- -
-
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} -

-
- )} -
- )} +
+

Assignee

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

Title is required

} -
-
- -