From d872254106a3f892938c14f23193f10e7aabf32d Mon Sep 17 00:00:00 2001 From: mohiit1502 Date: Tue, 21 Apr 2026 00:14:05 +0530 Subject: [PATCH] =?UTF-8?q?fix:=20retain=20workflow=20trace=20on=20respons?= =?UTF-8?q?e=20=E2=80=94=20linger=20+=20default=20open?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - showTyping state lingers 400ms after isPending drops so TypingBubble stays visible during the handoff to the completed message - WorkflowTrace defaults to open=true so the execution diagram is immediately visible below the response (no need to click to expand) --- .../src/components/layout/ChatWorkspace.tsx | 363 +++++++++++++++++- 1 file changed, 351 insertions(+), 12 deletions(-) diff --git a/webapp/src/components/layout/ChatWorkspace.tsx b/webapp/src/components/layout/ChatWorkspace.tsx index d08f7600..dac1aa87 100644 --- a/webapp/src/components/layout/ChatWorkspace.tsx +++ b/webapp/src/components/layout/ChatWorkspace.tsx @@ -9,9 +9,10 @@ import { Plug, Cpu, Database, GitBranch, ChevronRight, CheckCircle2, XCircle, Clock, ListTree, Package, Code2, Globe, Tag, Square, + ShieldAlert, Copy, Pencil, Download, } from 'lucide-react' import { - agentsApi, tasksApi, chatApi, chatSessionsApi, debuggerApi, agentDescription, + agentsApi, tasksApi, chatApi, chatSessionsApi, debuggerApi, approvalsApi, agentDescription, type ApiAgent, type ApiTask, type ApiChatMessage, type ApiGraphNode, type ApiChatMessageRecord, type ApiChatToolAction, } from '@/api/client' @@ -47,6 +48,12 @@ interface ChatMessage { latencyMs?: number modelUsed?: string tryItData?: TryItData + // Inline approval card fields + approvalId?: string + approvalAction?: string + approvalResource?: string + approvalAgentId?: string + approvalStatus?: 'pending' | 'approved' | 'denied' | 'sent_back' } // ── Slash Commands ───────────────────────────────────────────────────────── @@ -543,6 +550,130 @@ function ToolStatusCard({ taskId, agentName, navigate }: { ) } +// ── Inline Approval Card ─────────────────────────────────────────────────── + +function ApprovalCard({ + approvalId, + action, + resource, + agentId, + status, + onResolved, +}: { + approvalId: string + action: string + resource: string + agentId?: string + status: 'pending' | 'approved' | 'denied' | 'sent_back' + onResolved: (id: string, newStatus: 'approved' | 'denied' | 'sent_back') => void +}) { + const [feedback, setFeedback] = useState('') + const [showFeedback, setShowFeedback] = useState(false) + const [resolving, setResolving] = useState(false) + + const resolve = async (act: 'approve' | 'deny' | 'sendBack') => { + if (resolving) return + setResolving(true) + try { + if (act === 'approve') { + await approvalsApi.approve(approvalId, { resolver_id: 'user' }) + onResolved(approvalId, 'approved') + } else if (act === 'deny') { + await approvalsApi.deny(approvalId, { resolver_id: 'user' }) + onResolved(approvalId, 'denied') + } else { + await approvalsApi.sendBack(approvalId, { resolver_id: 'user', feedback }) + onResolved(approvalId, 'sent_back') + } + } catch { + setResolving(false) + } + } + + if (status !== 'pending') { + const statusColor = status === 'approved' ? 'var(--nbl-green)' : status === 'denied' ? 'var(--nbl-red)' : 'var(--nbl-amber)' + const statusLabel = status === 'approved' ? 'Approved' : status === 'denied' ? 'Denied' : 'Sent back' + return ( +
+ + {action} on {resource} + {statusLabel} +
+ ) + } + + return ( +
+
+ + Approval Required + PENDING +
+
+
+ Action + {action} +
+
+ Resource + {resource} +
+ {agentId && ( +
+ Agent + {agentId.slice(-12)} +
+ )} +
+ {showFeedback && ( +
+