Spaces:
Running on CPU Upgrade
Running on CPU Upgrade
Commit ·
0736c2f
1
Parent(s): 7ccf897
Show cancelled prompt in shimmer status bar instead of chat input placeholder
Browse files
frontend/src/components/Chat/ActivityStatusBar.tsx
CHANGED
|
@@ -32,6 +32,7 @@ function statusLabel(status: ActivityStatus): string {
|
|
| 32 |
return base;
|
| 33 |
}
|
| 34 |
case 'waiting-approval': return 'Waiting for approval';
|
|
|
|
| 35 |
default: return '';
|
| 36 |
}
|
| 37 |
}
|
|
@@ -59,7 +60,7 @@ export default function ActivityStatusBar() {
|
|
| 59 |
animation: `${shimmer} 4s ease-in-out infinite`,
|
| 60 |
}}
|
| 61 |
>
|
| 62 |
-
{label}…
|
| 63 |
</Typography>
|
| 64 |
</Box>
|
| 65 |
);
|
|
|
|
| 32 |
return base;
|
| 33 |
}
|
| 34 |
case 'waiting-approval': return 'Waiting for approval';
|
| 35 |
+
case 'cancelled': return 'What should the agent do instead?';
|
| 36 |
default: return '';
|
| 37 |
}
|
| 38 |
}
|
|
|
|
| 60 |
animation: `${shimmer} 4s ease-in-out infinite`,
|
| 61 |
}}
|
| 62 |
>
|
| 63 |
+
{label}{activityStatus.type !== 'cancelled' && '…'}
|
| 64 |
</Typography>
|
| 65 |
</Box>
|
| 66 |
);
|
frontend/src/components/SessionChat.tsx
CHANGED
|
@@ -5,7 +5,7 @@
|
|
| 5 |
* runs — processing events — but only the active session renders visible
|
| 6 |
* UI (MessageList + ChatInput).
|
| 7 |
*/
|
| 8 |
-
import { useCallback, useEffect
|
| 9 |
import { useAgentChat } from '@/hooks/useAgentChat';
|
| 10 |
import { useAgentStore } from '@/store/agentStore';
|
| 11 |
import { useSessionStore } from '@/store/sessionStore';
|
|
@@ -24,8 +24,6 @@ export default function SessionChat({ sessionId, isActive, onSessionDead }: Sess
|
|
| 24 |
const { isConnected, isProcessing, activityStatus, updateSession } = useAgentStore();
|
| 25 |
const { updateSessionTitle } = useSessionStore();
|
| 26 |
|
| 27 |
-
const [wasCancelled, setWasCancelled] = useState(false);
|
| 28 |
-
|
| 29 |
const { messages, sendMessage, stop, status, undoLastTurn, approveTools } = useAgentChat({
|
| 30 |
sessionId,
|
| 31 |
isActive,
|
|
@@ -57,11 +55,11 @@ export default function SessionChat({ sessionId, isActive, onSessionDead }: Sess
|
|
| 57 |
return () => document.removeEventListener('visibilitychange', onVisible);
|
| 58 |
}, [isActive, sessionId]);
|
| 59 |
|
| 60 |
-
// Wrap stop to
|
| 61 |
const handleStop = useCallback(() => {
|
| 62 |
stop();
|
| 63 |
-
|
| 64 |
-
}, [stop]);
|
| 65 |
|
| 66 |
// SDK status is the ground truth — if it's streaming/submitted, agent is busy
|
| 67 |
const sdkBusy = status === 'streaming' || status === 'submitted';
|
|
@@ -71,8 +69,7 @@ export default function SessionChat({ sessionId, isActive, onSessionDead }: Sess
|
|
| 71 |
async (text: string) => {
|
| 72 |
if (!text.trim() || busy) return;
|
| 73 |
|
| 74 |
-
|
| 75 |
-
updateSession(sessionId, { isProcessing: true });
|
| 76 |
sendMessage({ text: text.trim(), metadata: { createdAt: new Date().toISOString() } });
|
| 77 |
|
| 78 |
// Auto-title the session from the first user message
|
|
@@ -114,9 +111,7 @@ export default function SessionChat({ sessionId, isActive, onSessionDead }: Sess
|
|
| 114 |
placeholder={
|
| 115 |
activityStatus.type === 'waiting-approval'
|
| 116 |
? 'Approve or reject pending tools first...'
|
| 117 |
-
:
|
| 118 |
-
? 'What should the agent do instead?'
|
| 119 |
-
: undefined
|
| 120 |
}
|
| 121 |
/>
|
| 122 |
</>
|
|
|
|
| 5 |
* runs — processing events — but only the active session renders visible
|
| 6 |
* UI (MessageList + ChatInput).
|
| 7 |
*/
|
| 8 |
+
import { useCallback, useEffect } from 'react';
|
| 9 |
import { useAgentChat } from '@/hooks/useAgentChat';
|
| 10 |
import { useAgentStore } from '@/store/agentStore';
|
| 11 |
import { useSessionStore } from '@/store/sessionStore';
|
|
|
|
| 24 |
const { isConnected, isProcessing, activityStatus, updateSession } = useAgentStore();
|
| 25 |
const { updateSessionTitle } = useSessionStore();
|
| 26 |
|
|
|
|
|
|
|
| 27 |
const { messages, sendMessage, stop, status, undoLastTurn, approveTools } = useAgentChat({
|
| 28 |
sessionId,
|
| 29 |
isActive,
|
|
|
|
| 55 |
return () => document.removeEventListener('visibilitychange', onVisible);
|
| 56 |
}, [isActive, sessionId]);
|
| 57 |
|
| 58 |
+
// Wrap stop to show cancelled shimmer
|
| 59 |
const handleStop = useCallback(() => {
|
| 60 |
stop();
|
| 61 |
+
updateSession(sessionId, { activityStatus: { type: 'cancelled' } });
|
| 62 |
+
}, [stop, updateSession, sessionId]);
|
| 63 |
|
| 64 |
// SDK status is the ground truth — if it's streaming/submitted, agent is busy
|
| 65 |
const sdkBusy = status === 'streaming' || status === 'submitted';
|
|
|
|
| 69 |
async (text: string) => {
|
| 70 |
if (!text.trim() || busy) return;
|
| 71 |
|
| 72 |
+
updateSession(sessionId, { isProcessing: true, activityStatus: { type: 'thinking' } });
|
|
|
|
| 73 |
sendMessage({ text: text.trim(), metadata: { createdAt: new Date().toISOString() } });
|
| 74 |
|
| 75 |
// Auto-title the session from the first user message
|
|
|
|
| 111 |
placeholder={
|
| 112 |
activityStatus.type === 'waiting-approval'
|
| 113 |
? 'Approve or reject pending tools first...'
|
| 114 |
+
: undefined
|
|
|
|
|
|
|
| 115 |
}
|
| 116 |
/>
|
| 117 |
</>
|
frontend/src/store/agentStore.ts
CHANGED
|
@@ -50,7 +50,8 @@ export type ActivityStatus =
|
|
| 50 |
| { type: 'thinking' }
|
| 51 |
| { type: 'tool'; toolName: string; description?: string }
|
| 52 |
| { type: 'waiting-approval' }
|
| 53 |
-
| { type: 'streaming' }
|
|
|
|
| 54 |
|
| 55 |
/** State that is tracked per-session (each session has its own copy). */
|
| 56 |
export interface PerSessionState {
|
|
@@ -222,7 +223,7 @@ export const useAgentStore = create<AgentStore>()((set, get) => ({
|
|
| 222 |
// Apply the processing→idle side effect
|
| 223 |
const processingCleared = 'isProcessing' in updates && !updates.isProcessing;
|
| 224 |
if (processingCleared) {
|
| 225 |
-
if (updated.activityStatus.type !== 'waiting-approval') {
|
| 226 |
updated.activityStatus = { type: 'idle' };
|
| 227 |
}
|
| 228 |
}
|
|
@@ -300,7 +301,7 @@ export const useAgentStore = create<AgentStore>()((set, get) => ({
|
|
| 300 |
|
| 301 |
setProcessing: (isProcessing) => {
|
| 302 |
const current = get().activityStatus;
|
| 303 |
-
const preserveStatus = current.type === 'waiting-approval';
|
| 304 |
set({ isProcessing, ...(!isProcessing && !preserveStatus ? { activityStatus: { type: 'idle' } } : {}) });
|
| 305 |
},
|
| 306 |
setConnected: (isConnected) => set({ isConnected }),
|
|
|
|
| 50 |
| { type: 'thinking' }
|
| 51 |
| { type: 'tool'; toolName: string; description?: string }
|
| 52 |
| { type: 'waiting-approval' }
|
| 53 |
+
| { type: 'streaming' }
|
| 54 |
+
| { type: 'cancelled' };
|
| 55 |
|
| 56 |
/** State that is tracked per-session (each session has its own copy). */
|
| 57 |
export interface PerSessionState {
|
|
|
|
| 223 |
// Apply the processing→idle side effect
|
| 224 |
const processingCleared = 'isProcessing' in updates && !updates.isProcessing;
|
| 225 |
if (processingCleared) {
|
| 226 |
+
if (updated.activityStatus.type !== 'waiting-approval' && updated.activityStatus.type !== 'cancelled') {
|
| 227 |
updated.activityStatus = { type: 'idle' };
|
| 228 |
}
|
| 229 |
}
|
|
|
|
| 301 |
|
| 302 |
setProcessing: (isProcessing) => {
|
| 303 |
const current = get().activityStatus;
|
| 304 |
+
const preserveStatus = current.type === 'waiting-approval' || current.type === 'cancelled';
|
| 305 |
set({ isProcessing, ...(!isProcessing && !preserveStatus ? { activityStatus: { type: 'idle' } } : {}) });
|
| 306 |
},
|
| 307 |
setConnected: (isConnected) => set({ isConnected }),
|