From d7b3c08d06b43a104bdbeb20538f9da08cda35fc Mon Sep 17 00:00:00 2001 From: null Date: Fri, 22 May 2026 01:48:06 -0500 Subject: [PATCH] feat(ui): assign agent --- .../components/git/AssignIssueAgentDialog.tsx | 287 ++++++++++++++++++ .../git/ForgejoIssueDetailDialog.tsx | 24 +- frontend/src/lib/api-forgejo.ts | 33 ++ 3 files changed, 343 insertions(+), 1 deletion(-) create mode 100644 frontend/src/components/git/AssignIssueAgentDialog.tsx diff --git a/frontend/src/components/git/AssignIssueAgentDialog.tsx b/frontend/src/components/git/AssignIssueAgentDialog.tsx new file mode 100644 index 0000000..398319b --- /dev/null +++ b/frontend/src/components/git/AssignIssueAgentDialog.tsx @@ -0,0 +1,287 @@ +"use client"; + +import { useEffect, useState } from "react"; + +import { + useListBoardsApiV1BoardsGet, + type listBoardsApiV1BoardsGetResponse, +} from "@/api/generated/boards/boards"; +import { + useListAgentsApiV1AgentsGet, + type listAgentsApiV1AgentsGetResponse, +} from "@/api/generated/agents/agents"; +import { ApiError } from "@/api/mutator"; +import { + Dialog, + DialogContent, + DialogDescription, + DialogFooter, + DialogHeader, + DialogTitle, +} from "@/components/ui/dialog"; +import { Button } from "@/components/ui/button"; +import { Textarea } from "@/components/ui/textarea"; +import type { ForgejoIssue, AssignIssueAgentResponse } from "@/lib/api-forgejo"; +import { assignIssueToAgent } from "@/lib/api-forgejo"; + +type AssignIssueAgentDialogProps = { + issue: ForgejoIssue | null; + repositoryName: string; + open: boolean; + onOpenChange: (open: boolean) => void; + onSuccess: (result: AssignIssueAgentResponse) => void; +}; + +export function AssignIssueAgentDialog({ + issue, + repositoryName, + open, + onOpenChange, + onSuccess, +}: AssignIssueAgentDialogProps) { + const [boardId, setBoardId] = useState(""); + const [agentId, setAgentId] = useState(""); + const [priority, setPriority] = useState("medium"); + const [instructions, setInstructions] = useState(""); + const [startImmediately, setStartImmediately] = useState(true); + const [isSubmitting, setIsSubmitting] = useState(false); + const [error, setError] = useState(null); + + const boardsQuery = useListBoardsApiV1BoardsGet< + listBoardsApiV1BoardsGetResponse, + ApiError + >(undefined, { query: { enabled: open, refetchOnMount: "always" } }); + + const agentsQuery = useListAgentsApiV1AgentsGet< + listAgentsApiV1AgentsGetResponse, + ApiError + >( + { board_id: boardId || undefined, limit: 200 }, + { query: { enabled: open && !!boardId, refetchOnMount: "always" } }, + ); + + const boards = + boardsQuery.data?.status === 200 + ? (boardsQuery.data.data.items ?? []) + : []; + const agents = + agentsQuery.data?.status === 200 + ? (agentsQuery.data.data.items ?? []) + : []; + + useEffect(() => { + if (!open) { + setBoardId(""); + setAgentId(""); + setPriority("medium"); + setInstructions(""); + setStartImmediately(true); + setError(null); + } + }, [open]); + + useEffect(() => { + if (boards.length > 0 && !boardId) { + setBoardId(boards[0].id); + } + }, [boards, boardId]); + + useEffect(() => { + setAgentId(""); + }, [boardId]); + + if (!issue) return null; + + const handleSubmit = async () => { + if (!boardId) return; + setIsSubmitting(true); + setError(null); + try { + const result = await assignIssueToAgent(issue.id, { + board_id: boardId, + assigned_agent_id: agentId || undefined, + priority, + start_immediately: startImmediately, + status: startImmediately && agentId ? "in_progress" : "inbox", + instructions: instructions.trim() || undefined, + }); + onSuccess(result); + onOpenChange(false); + } catch (err) { + setError(err instanceof Error ? err.message : "Failed to assign agent"); + } finally { + setIsSubmitting(false); + } + }; + + const toggleSwitch = ( + value: boolean, + setter: (v: boolean) => void, + disabled: boolean, + ) => ( + + ); + + return ( + { + if (!isSubmitting) onOpenChange(next); + }} + > + + + Assign to agent + + Create a Pipeline task for{" "} + + {repositoryName}#{issue.forgejo_issue_number} + {" "} + and assign an agent to work on it. + + + +
+
+

+ {issue.title} +

+
+ +
+ + +
+ +
+ + +
+ +
+ + +
+ +
+ +