Spaces:
Running on CPU Upgrade
Running on CPU Upgrade
File size: 4,434 Bytes
854c261 571b292 854c261 1a390ae 854c261 723c24c 854c261 6d4d388 854c261 723c24c 854c261 723c24c 854c261 723c24c 00a57cd 723c24c 00a57cd 723c24c 854c261 723c24c 854c261 1a390ae d2c1b12 1a390ae d2c1b12 f915e8e 854c261 f915e8e 854c261 1a390ae 854c261 1a390ae 854c261 723c24c 854c261 f915e8e 854c261 6d4d388 854c261 d2c1b12 f915e8e 854c261 d2c1b12 1a390ae d2c1b12 854c261 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 | /**
* Per-session chat component.
*
* Each session renders its own SessionChat. The hook (useAgentChat) always
* runs — processing events — but only the active session renders visible
* UI (MessageList + ChatInput).
*/
import { useCallback, useEffect } from 'react';
import { useAgentChat } from '@/hooks/useAgentChat';
import { useAgentStore } from '@/store/agentStore';
import { useSessionStore } from '@/store/sessionStore';
import MessageList from '@/components/Chat/MessageList';
import ChatInput from '@/components/Chat/ChatInput';
import { apiFetch } from '@/utils/api';
import { logger } from '@/utils/logger';
interface SessionChatProps {
sessionId: string;
isActive: boolean;
onSessionDead: (sessionId: string) => void;
}
export default function SessionChat({ sessionId, isActive, onSessionDead }: SessionChatProps) {
const { isConnected, isProcessing, activityStatus, updateSession } = useAgentStore();
const { updateSessionTitle } = useSessionStore();
const { messages, sendMessage, stop, status, undoLastTurn, editAndRegenerate, approveTools } = useAgentChat({
sessionId,
isActive,
onReady: () => logger.log(`Session ${sessionId} ready`),
onError: (error) => logger.error(`Session ${sessionId} error:`, error),
onSessionDead,
});
// When this session becomes active, restore its per-session state to the
// global flat fields. The per-session state map is kept up-to-date by
// side-channel callbacks even while the session is in the background.
useEffect(() => {
if (isActive) {
useAgentStore.getState().switchActiveSession(sessionId);
useAgentStore.getState().setConnected(true);
}
}, [isActive, sessionId]);
// Re-sync state when the browser tab regains focus (Chrome throttles
// timers in background tabs which can stall the AI SDK's update flushing).
// Fires for ALL sessions so background sessions also recover after sleep.
useEffect(() => {
const onVisible = () => {
if (document.visibilityState === 'visible' && isActive) {
useAgentStore.getState().switchActiveSession(sessionId);
}
};
document.addEventListener('visibilitychange', onVisible);
return () => document.removeEventListener('visibilitychange', onVisible);
}, [isActive, sessionId]);
// Wrap stop to show cancelled shimmer
const handleStop = useCallback(() => {
stop();
updateSession(sessionId, { activityStatus: { type: 'cancelled' } });
}, [stop, updateSession, sessionId]);
// SDK status is the ground truth — if it's streaming/submitted, agent is busy
const sdkBusy = status === 'streaming' || status === 'submitted';
const busy = isProcessing || sdkBusy;
const handleSendMessage = useCallback(
async (text: string) => {
if (!text.trim() || busy) return;
updateSession(sessionId, { isProcessing: true, activityStatus: { type: 'thinking' } });
sendMessage({ text: text.trim(), metadata: { createdAt: new Date().toISOString() } });
// Auto-title the session from the first user message
const isFirstMessage = messages.filter((m) => m.role === 'user').length === 0;
if (isFirstMessage) {
apiFetch('/api/title', {
method: 'POST',
body: JSON.stringify({ session_id: sessionId, text: text.trim() }),
})
.then((res) => res.json())
.then((data) => {
if (data.title) updateSessionTitle(sessionId, data.title);
})
.catch(() => {
const raw = text.trim();
updateSessionTitle(sessionId, raw.length > 40 ? raw.slice(0, 40) + '\u2026' : raw);
});
}
},
[sessionId, sendMessage, messages, updateSessionTitle, busy, updateSession],
);
// Don't render UI for background sessions — hooks still run
if (!isActive) return null;
return (
<>
<MessageList
messages={messages}
isProcessing={busy}
approveTools={approveTools}
onUndoLastTurn={undoLastTurn}
onEditAndRegenerate={editAndRegenerate}
/>
<ChatInput
onSend={handleSendMessage}
onStop={handleStop}
isProcessing={busy}
disabled={!isConnected || activityStatus.type === 'waiting-approval'}
placeholder={
activityStatus.type === 'waiting-approval'
? 'Approve or reject pending tools first...'
: undefined
}
/>
</>
);
}
|