From ea7bcebcd05c7fb4cc2452d5b265934bd64db707 Mon Sep 17 00:00:00 2001 From: Anish Sarkar <104695310+AnishSarkar22@users.noreply.github.com> Date: Mon, 13 Apr 2026 20:19:23 +0530 Subject: [PATCH] refactor: integrate HITL approval UI for interrupt results Enhanced the NewChatPage to utilize the new GenericHitlApprovalToolUI for handling interrupt results. Updated the ToolFallback component to conditionally render the approval UI based on the result type. Additionally, introduced a new GenericHitlApprovalToolUI component to manage user approvals and parameter editing for tool actions. --- .../new-chat/[[...chat_id]]/page.tsx | 4 +- .../components/assistant-ui/tool-fallback.tsx | 11 +- .../tool-ui/generic-hitl-approval.tsx | 268 ++++++++++++++++++ 3 files changed, 280 insertions(+), 3 deletions(-) create mode 100644 surfsense_web/components/tool-ui/generic-hitl-approval.tsx diff --git a/surfsense_web/app/dashboard/[search_space_id]/new-chat/[[...chat_id]]/page.tsx b/surfsense_web/app/dashboard/[search_space_id]/new-chat/[[...chat_id]]/page.tsx index 0b1369340..58eb58f4b 100644 --- a/surfsense_web/app/dashboard/[search_space_id]/new-chat/[[...chat_id]]/page.tsx +++ b/surfsense_web/app/dashboard/[search_space_id]/new-chat/[[...chat_id]]/page.tsx @@ -798,7 +798,7 @@ export default function NewChatPage() { }); } else { const tcId = `interrupt-${action.name}`; - addToolCall(contentPartsState, TOOLS_WITH_UI, tcId, action.name, action.args); + addToolCall(contentPartsState, TOOLS_WITH_UI, tcId, action.name, action.args, true); updateToolCall(contentPartsState, tcId, { result: { __interrupt__: true, ...interruptData }, }); @@ -1125,7 +1125,7 @@ export default function NewChatPage() { }); } else { const tcId = `interrupt-${action.name}`; - addToolCall(contentPartsState, TOOLS_WITH_UI, tcId, action.name, action.args); + addToolCall(contentPartsState, TOOLS_WITH_UI, tcId, action.name, action.args, true); updateToolCall(contentPartsState, tcId, { result: { __interrupt__: true, diff --git a/surfsense_web/components/assistant-ui/tool-fallback.tsx b/surfsense_web/components/assistant-ui/tool-fallback.tsx index b658dba6d..d9833b387 100644 --- a/surfsense_web/components/assistant-ui/tool-fallback.tsx +++ b/surfsense_web/components/assistant-ui/tool-fallback.tsx @@ -1,14 +1,16 @@ import type { ToolCallMessagePartComponent } from "@assistant-ui/react"; import { CheckIcon, ChevronDownIcon, ChevronUpIcon, XCircleIcon } from "lucide-react"; import { useMemo, useState } from "react"; +import { GenericHitlApprovalToolUI } from "@/components/tool-ui/generic-hitl-approval"; import { getToolIcon } from "@/contracts/enums/toolIcons"; +import { isInterruptResult } from "@/lib/hitl"; import { cn } from "@/lib/utils"; function formatToolName(name: string): string { return name.replace(/_/g, " ").replace(/\b\w/g, (c) => c.toUpperCase()); } -export const ToolFallback: ToolCallMessagePartComponent = ({ +const DefaultToolFallbackInner: ToolCallMessagePartComponent = ({ toolName, argsText, result, @@ -145,3 +147,10 @@ export const ToolFallback: ToolCallMessagePartComponent = ({ ); }; + +export const ToolFallback: ToolCallMessagePartComponent = (props) => { + if (isInterruptResult(props.result)) { + return ; + } + return ; +}; diff --git a/surfsense_web/components/tool-ui/generic-hitl-approval.tsx b/surfsense_web/components/tool-ui/generic-hitl-approval.tsx new file mode 100644 index 000000000..48d0d3764 --- /dev/null +++ b/surfsense_web/components/tool-ui/generic-hitl-approval.tsx @@ -0,0 +1,268 @@ +"use client"; + +import type { ToolCallMessagePartComponent } from "@assistant-ui/react"; +import { CornerDownLeftIcon, ShieldAlertIcon, ShieldCheckIcon } from "lucide-react"; +import { useCallback, useEffect, useMemo, useState } from "react"; +import { TextShimmerLoader } from "@/components/prompt-kit/loader"; +import { Button } from "@/components/ui/button"; +import { Input } from "@/components/ui/input"; +import { Textarea } from "@/components/ui/textarea"; +import { useHitlPhase } from "@/hooks/use-hitl-phase"; +import { connectorsApiService } from "@/lib/apis/connectors-api.service"; +import { isInterruptResult, useHitlDecision } from "@/lib/hitl"; +import type { HitlDecision, InterruptResult } from "@/lib/hitl"; + +function ParamEditor({ + params, + onChange, + disabled, +}: { + params: Record; + onChange: (updated: Record) => void; + disabled: boolean; +}) { + const entries = Object.entries(params); + if (entries.length === 0) return null; + + return ( +
+ {entries.map(([key, value]) => { + const strValue = value == null ? "" : String(value); + const isLong = strValue.length > 120; + const fieldId = `hitl-param-${key}`; + + return ( +
+ + {isLong ? ( +