feat: interactive AskUserQuestion + WebSocket stability
- Add WebSocket heartbeat (30s ping/pong) to prevent proxy timeouts - Add auto-reconnect with exponential backoff (1s-30s, max 10 attempts) - Add interactive AskUserQuestion rendering with clickable options - Add custom input field for free-text answers - Add smooth animations (hover, selection glow, checkbox scale) - Make interactive tool cards wider (max-w-2xl) without scrolling - Hide error badge and result section for interactive tools - Use TextareaAutosize for lag-free custom input - Add Skill, SlashCommand tool renderings - Add ThinkingBlock component for collapsible <thinking> tags 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -9,6 +9,11 @@ function getWsUrl() {
|
||||
|
||||
const MAX_FILE_SIZE = 10 * 1024 * 1024; // 10MB
|
||||
|
||||
// Reconnect configuration
|
||||
const RECONNECT_DELAY_BASE = 1000; // 1 second
|
||||
const RECONNECT_DELAY_MAX = 30000; // 30 seconds
|
||||
const RECONNECT_MAX_ATTEMPTS = 10;
|
||||
|
||||
export function useClaudeSession() {
|
||||
const [connected, setConnected] = useState(false);
|
||||
const [sessionActive, setSessionActive] = useState(false);
|
||||
@@ -50,6 +55,9 @@ export function useClaudeSession() {
|
||||
|
||||
const wsRef = useRef(null);
|
||||
const currentAssistantMessage = useRef(null);
|
||||
const reconnectAttempts = useRef(0);
|
||||
const reconnectTimeout = useRef(null);
|
||||
const intentionalClose = useRef(false);
|
||||
|
||||
const connect = useCallback(() => {
|
||||
if (wsRef.current?.readyState === WebSocket.OPEN) return;
|
||||
@@ -61,13 +69,34 @@ export function useClaudeSession() {
|
||||
console.log('WebSocket connected');
|
||||
setConnected(true);
|
||||
setError(null);
|
||||
reconnectAttempts.current = 0; // Reset on successful connection
|
||||
};
|
||||
|
||||
ws.onclose = () => {
|
||||
console.log('WebSocket disconnected');
|
||||
ws.onclose = (event) => {
|
||||
console.log('WebSocket disconnected', { code: event.code, reason: event.reason, wasClean: event.wasClean });
|
||||
setConnected(false);
|
||||
setSessionActive(false);
|
||||
setIsProcessing(false);
|
||||
|
||||
// Attempt reconnect unless it was intentional or auth failure
|
||||
if (!intentionalClose.current && event.code !== 1008) {
|
||||
if (reconnectAttempts.current < RECONNECT_MAX_ATTEMPTS) {
|
||||
const delay = Math.min(
|
||||
RECONNECT_DELAY_BASE * Math.pow(2, reconnectAttempts.current),
|
||||
RECONNECT_DELAY_MAX
|
||||
);
|
||||
reconnectAttempts.current++;
|
||||
console.log(`Reconnecting in ${delay}ms (attempt ${reconnectAttempts.current}/${RECONNECT_MAX_ATTEMPTS})`);
|
||||
|
||||
reconnectTimeout.current = setTimeout(() => {
|
||||
console.log('Attempting reconnect...');
|
||||
connect();
|
||||
}, delay);
|
||||
} else {
|
||||
console.log('Max reconnect attempts reached');
|
||||
setError('Connection lost. Please refresh the page.');
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
ws.onerror = (err) => {
|
||||
@@ -543,8 +572,13 @@ export function useClaudeSession() {
|
||||
|
||||
// Auto-connect on mount
|
||||
useEffect(() => {
|
||||
intentionalClose.current = false;
|
||||
connect();
|
||||
return () => {
|
||||
intentionalClose.current = true;
|
||||
if (reconnectTimeout.current) {
|
||||
clearTimeout(reconnectTimeout.current);
|
||||
}
|
||||
wsRef.current?.close();
|
||||
};
|
||||
}, [connect]);
|
||||
|
||||
Reference in New Issue
Block a user