akseljoonas HF Staff commited on
Commit
f0c7e64
·
1 Parent(s): 854c261

fix: approval UI not updating after approve click

Browse files
frontend/src/components/Chat/ToolCallGroup.tsx CHANGED
@@ -32,6 +32,8 @@ function StatusIcon({ state }: { state: ToolPartState }) {
32
  switch (state) {
33
  case 'approval-requested':
34
  return <HourglassEmptyIcon sx={{ fontSize: 16, color: 'var(--accent-yellow)' }} />;
 
 
35
  case 'output-available':
36
  return <CheckCircleOutlineIcon sx={{ fontSize: 16, color: 'success.main' }} />;
37
  case 'output-error':
@@ -48,6 +50,7 @@ function StatusIcon({ state }: { state: ToolPartState }) {
48
  function statusLabel(state: ToolPartState): string | null {
49
  switch (state) {
50
  case 'approval-requested': return 'awaiting approval';
 
51
  case 'input-streaming':
52
  case 'input-available': return 'running';
53
  case 'output-denied': return 'denied';
@@ -59,6 +62,7 @@ function statusLabel(state: ToolPartState): string | null {
59
  function statusColor(state: ToolPartState): string {
60
  switch (state) {
61
  case 'approval-requested': return 'var(--accent-yellow)';
 
62
  case 'output-available': return 'var(--accent-green)';
63
  case 'output-error': return 'var(--accent-red)';
64
  case 'output-denied': return 'var(--muted-text)';
 
32
  switch (state) {
33
  case 'approval-requested':
34
  return <HourglassEmptyIcon sx={{ fontSize: 16, color: 'var(--accent-yellow)' }} />;
35
+ case 'approval-responded':
36
+ return <CircularProgress size={14} thickness={5} sx={{ color: 'var(--accent-green)' }} />;
37
  case 'output-available':
38
  return <CheckCircleOutlineIcon sx={{ fontSize: 16, color: 'success.main' }} />;
39
  case 'output-error':
 
50
  function statusLabel(state: ToolPartState): string | null {
51
  switch (state) {
52
  case 'approval-requested': return 'awaiting approval';
53
+ case 'approval-responded': return 'approved';
54
  case 'input-streaming':
55
  case 'input-available': return 'running';
56
  case 'output-denied': return 'denied';
 
62
  function statusColor(state: ToolPartState): string {
63
  switch (state) {
64
  case 'approval-requested': return 'var(--accent-yellow)';
65
+ case 'approval-responded': return 'var(--accent-green)';
66
  case 'output-available': return 'var(--accent-green)';
67
  case 'output-error': return 'var(--accent-red)';
68
  case 'output-denied': return 'var(--muted-text)';
frontend/src/components/SessionChat.tsx CHANGED
@@ -49,6 +49,9 @@ export default function SessionChat({ sessionId, isActive, onSessionDead }: Sess
49
  const hasPendingApproval = lastAssistant?.parts.some(
50
  (p) => p.type === 'dynamic-tool' && p.state === 'approval-requested'
51
  ) ?? false;
 
 
 
52
 
53
  if (hasPendingApproval) {
54
  store.setActivityStatus({ type: 'waiting-approval' });
@@ -81,6 +84,10 @@ export default function SessionChat({ sessionId, isActive, onSessionDead }: Sess
81
  }
82
  useLayoutStore.getState().setRightPanelOpen(true);
83
  }
 
 
 
 
84
  } else {
85
  // No pending approval — reset to idle
86
  store.setActivityStatus({ type: 'idle' });
 
49
  const hasPendingApproval = lastAssistant?.parts.some(
50
  (p) => p.type === 'dynamic-tool' && p.state === 'approval-requested'
51
  ) ?? false;
52
+ const hasApprovedRunning = lastAssistant?.parts.some(
53
+ (p) => p.type === 'dynamic-tool' && p.state === 'approval-responded'
54
+ ) ?? false;
55
 
56
  if (hasPendingApproval) {
57
  store.setActivityStatus({ type: 'waiting-approval' });
 
84
  }
85
  useLayoutStore.getState().setRightPanelOpen(true);
86
  }
87
+ } else if (hasApprovedRunning) {
88
+ // Tools were approved but still executing — show processing state
89
+ store.setActivityStatus({ type: 'tool', toolName: 'running' });
90
+ store.setProcessing(true);
91
  } else {
92
  // No pending approval — reset to idle
93
  store.setActivityStatus({ type: 'idle' });
frontend/src/hooks/useAgentChat.ts CHANGED
@@ -331,6 +331,17 @@ export function useAgentChat({ sessionId, isActive, onReady, onError, onSessionD
331
  const approveTools = useCallback(
332
  async (approvals: Array<{ tool_call_id: string; approved: boolean; feedback?: string | null; edited_script?: string | null }>) => {
333
  if (!transportRef.current) return false;
 
 
 
 
 
 
 
 
 
 
 
334
  const ok = await transportRef.current.approveTools(sessionId, approvals);
335
  if (ok) {
336
  // Clear needsAttention since user has responded
@@ -340,7 +351,7 @@ export function useAgentChat({ sessionId, isActive, onReady, onError, onSessionD
340
  }
341
  return ok;
342
  },
343
- [sessionId, setProcessing, setNeedsAttention],
344
  );
345
 
346
  return {
 
331
  const approveTools = useCallback(
332
  async (approvals: Array<{ tool_call_id: string; approved: boolean; feedback?: string | null; edited_script?: string | null }>) => {
333
  if (!transportRef.current) return false;
334
+
335
+ // Transition SDK tool state from approval-requested → approval-responded
336
+ // so the approval UI disappears immediately (survives session switches).
337
+ for (const a of approvals) {
338
+ chat.addToolApprovalResponse({
339
+ id: `approval-${a.tool_call_id}`,
340
+ approved: a.approved,
341
+ reason: a.approved ? undefined : (a.feedback || 'Rejected by user'),
342
+ });
343
+ }
344
+
345
  const ok = await transportRef.current.approveTools(sessionId, approvals);
346
  if (ok) {
347
  // Clear needsAttention since user has responded
 
351
  }
352
  return ok;
353
  },
354
+ [sessionId, chat, setProcessing, setNeedsAttention],
355
  );
356
 
357
  return {