Add new frontend - fix 2
This commit is contained in:
@@ -1,92 +0,0 @@
|
||||
// frontend/dashboard/src/api/hooks/useApi.ts
|
||||
/**
|
||||
* React hooks for API state management
|
||||
*/
|
||||
|
||||
import { useState, useEffect, useCallback } from 'react';
|
||||
import { ApiError } from '../../types/api';
|
||||
|
||||
export interface ApiState<T> {
|
||||
data: T | null;
|
||||
loading: boolean;
|
||||
error: ApiError | null;
|
||||
}
|
||||
|
||||
export function useApi<T>(
|
||||
apiCall: () => Promise<T>,
|
||||
dependencies: any[] = []
|
||||
): ApiState<T> & {
|
||||
refetch: () => Promise<void>;
|
||||
reset: () => void;
|
||||
} {
|
||||
const [state, setState] = useState<ApiState<T>>({
|
||||
data: null,
|
||||
loading: false,
|
||||
error: null,
|
||||
});
|
||||
|
||||
const execute = useCallback(async () => {
|
||||
setState(prev => ({ ...prev, loading: true, error: null }));
|
||||
|
||||
try {
|
||||
const data = await apiCall();
|
||||
setState({ data, loading: false, error: null });
|
||||
} catch (error) {
|
||||
setState({
|
||||
data: null,
|
||||
loading: false,
|
||||
error: error as ApiError,
|
||||
});
|
||||
}
|
||||
}, dependencies);
|
||||
|
||||
const reset = useCallback(() => {
|
||||
setState({ data: null, loading: false, error: null });
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
execute();
|
||||
}, [execute]);
|
||||
|
||||
return {
|
||||
...state,
|
||||
refetch: execute,
|
||||
reset,
|
||||
};
|
||||
}
|
||||
|
||||
export function useAsyncAction<T, P extends any[] = []>(
|
||||
action: (...params: P) => Promise<T>
|
||||
): {
|
||||
execute: (...params: P) => Promise<T>;
|
||||
loading: boolean;
|
||||
error: ApiError | null;
|
||||
reset: () => void;
|
||||
} {
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [error, setError] = useState<ApiError | null>(null);
|
||||
|
||||
const execute = useCallback(async (...params: P) => {
|
||||
setLoading(true);
|
||||
setError(null);
|
||||
|
||||
try {
|
||||
const result = await action(...params);
|
||||
setLoading(false);
|
||||
return result;
|
||||
} catch (err) {
|
||||
const apiError = err as ApiError;
|
||||
setError(apiError);
|
||||
setLoading(false);
|
||||
throw apiError;
|
||||
}
|
||||
}, [action]);
|
||||
|
||||
const reset = useCallback(() => {
|
||||
setLoading(false);
|
||||
setError(null);
|
||||
}, []);
|
||||
|
||||
return { execute, loading, error, reset };
|
||||
}
|
||||
|
||||
71
frontend/src/api/hooks/useSessionTimeout.ts
Normal file
71
frontend/src/api/hooks/useSessionTimeout.ts
Normal file
@@ -0,0 +1,71 @@
|
||||
// src/hooks/useSessionTimeout.ts
|
||||
import { useEffect, useRef } from 'react';
|
||||
import { useAuth } from '../contexts/AuthContext';
|
||||
|
||||
interface SessionTimeoutOptions {
|
||||
timeout: number; // milliseconds
|
||||
onTimeout?: () => void;
|
||||
warningTime?: number; // Show warning before timeout
|
||||
onWarning?: () => void;
|
||||
}
|
||||
|
||||
export const useSessionTimeout = ({
|
||||
timeout = 30 * 60 * 1000, // 30 minutes default
|
||||
onTimeout,
|
||||
warningTime = 5 * 60 * 1000, // 5 minutes warning
|
||||
onWarning
|
||||
}: SessionTimeoutOptions) => {
|
||||
const { logout } = useAuth();
|
||||
const timeoutRef = useRef<NodeJS.Timeout>();
|
||||
const warningRef = useRef<NodeJS.Timeout>();
|
||||
|
||||
const resetTimeout = () => {
|
||||
// Clear existing timeouts
|
||||
if (timeoutRef.current) clearTimeout(timeoutRef.current);
|
||||
if (warningRef.current) clearTimeout(warningRef.current);
|
||||
|
||||
// Set warning timeout
|
||||
if (warningTime && onWarning) {
|
||||
warningRef.current = setTimeout(() => {
|
||||
onWarning();
|
||||
}, timeout - warningTime);
|
||||
}
|
||||
|
||||
// Set session timeout
|
||||
timeoutRef.current = setTimeout(() => {
|
||||
if (onTimeout) {
|
||||
onTimeout();
|
||||
} else {
|
||||
logout();
|
||||
}
|
||||
}, timeout);
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
// Activity events to reset timeout
|
||||
const events = ['mousedown', 'keypress', 'scroll', 'touchstart'];
|
||||
|
||||
const handleActivity = () => {
|
||||
resetTimeout();
|
||||
};
|
||||
|
||||
// Add event listeners
|
||||
events.forEach(event => {
|
||||
document.addEventListener(event, handleActivity);
|
||||
});
|
||||
|
||||
// Start timeout
|
||||
resetTimeout();
|
||||
|
||||
// Cleanup
|
||||
return () => {
|
||||
events.forEach(event => {
|
||||
document.removeEventListener(event, handleActivity);
|
||||
});
|
||||
if (timeoutRef.current) clearTimeout(timeoutRef.current);
|
||||
if (warningRef.current) clearTimeout(warningRef.current);
|
||||
};
|
||||
}, [timeout, warningTime]);
|
||||
|
||||
return { resetTimeout };
|
||||
};
|
||||
83
frontend/src/api/hooks/useTrainingProgress.ts
Normal file
83
frontend/src/api/hooks/useTrainingProgress.ts
Normal file
@@ -0,0 +1,83 @@
|
||||
// src/hooks/useTrainingProgress.ts
|
||||
import { useState, useEffect } from 'react';
|
||||
import { useWebSocket } from '../hooks/useWebSocket';
|
||||
|
||||
export interface TrainingProgress {
|
||||
job_id: string;
|
||||
status: 'pending' | 'running' | 'completed' | 'failed';
|
||||
progress: number;
|
||||
current_step: string;
|
||||
total_steps: number;
|
||||
estimated_time_remaining?: number;
|
||||
metrics?: Record<string, any>;
|
||||
}
|
||||
|
||||
export interface TrainingProgressUpdate {
|
||||
type: 'training_progress' | 'training_completed' | 'training_error';
|
||||
job_id: string;
|
||||
progress?: TrainingProgress;
|
||||
results?: any;
|
||||
error?: string;
|
||||
}
|
||||
|
||||
export const useTrainingProgress = (jobId: string | null) => {
|
||||
const [progress, setProgress] = useState<TrainingProgress | null>(null);
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
const [isComplete, setIsComplete] = useState(false);
|
||||
|
||||
const handleMessage = (data: TrainingProgressUpdate) => {
|
||||
switch (data.type) {
|
||||
case 'training_progress':
|
||||
setProgress(data.progress!);
|
||||
setError(null);
|
||||
break;
|
||||
|
||||
case 'training_completed':
|
||||
setProgress(prev => ({
|
||||
...prev!,
|
||||
status: 'completed',
|
||||
progress: 100
|
||||
}));
|
||||
setIsComplete(true);
|
||||
break;
|
||||
|
||||
case 'training_error':
|
||||
setError(data.error || 'Training failed');
|
||||
setProgress(prev => prev ? { ...prev, status: 'failed' } : null);
|
||||
break;
|
||||
}
|
||||
};
|
||||
|
||||
const { isConnected } = useWebSocket({
|
||||
endpoint: jobId ? `/training/progress/${jobId}` : '',
|
||||
onMessage: handleMessage,
|
||||
onError: () => setError('Connection lost'),
|
||||
autoConnect: !!jobId
|
||||
});
|
||||
|
||||
// Fetch initial status when job ID changes
|
||||
useEffect(() => {
|
||||
if (jobId) {
|
||||
fetchTrainingStatus(jobId);
|
||||
}
|
||||
}, [jobId]);
|
||||
|
||||
const fetchTrainingStatus = async (id: string) => {
|
||||
try {
|
||||
const response = await fetch(`/api/training/status/${id}`);
|
||||
if (response.ok) {
|
||||
const data = await response.json();
|
||||
setProgress(data);
|
||||
}
|
||||
} catch (err) {
|
||||
console.error('Failed to fetch training status:', err);
|
||||
}
|
||||
};
|
||||
|
||||
return {
|
||||
progress,
|
||||
error,
|
||||
isComplete,
|
||||
isConnected
|
||||
};
|
||||
};
|
||||
72
frontend/src/api/hooks/useWebSocket.ts
Normal file
72
frontend/src/api/hooks/useWebSocket.ts
Normal file
@@ -0,0 +1,72 @@
|
||||
// src/hooks/useWebSocket.ts
|
||||
import { useEffect, useRef, useCallback } from 'react';
|
||||
import { wsManager, WebSocketHandlers } from '../websocket/WebSocketManager';
|
||||
|
||||
export interface UseWebSocketOptions {
|
||||
endpoint: string;
|
||||
onMessage: (data: any) => void;
|
||||
onError?: (error: Event) => void;
|
||||
onConnect?: () => void;
|
||||
onDisconnect?: () => void;
|
||||
onReconnect?: () => void;
|
||||
autoConnect?: boolean;
|
||||
}
|
||||
|
||||
export const useWebSocket = ({
|
||||
endpoint,
|
||||
onMessage,
|
||||
onError,
|
||||
onConnect,
|
||||
onDisconnect,
|
||||
onReconnect,
|
||||
autoConnect = true
|
||||
}: UseWebSocketOptions) => {
|
||||
const wsRef = useRef<WebSocket | null>(null);
|
||||
|
||||
const connect = useCallback(async () => {
|
||||
if (wsRef.current) return;
|
||||
|
||||
const handlers: WebSocketHandlers = {
|
||||
onOpen: onConnect,
|
||||
onMessage,
|
||||
onError,
|
||||
onClose: onDisconnect,
|
||||
onReconnect
|
||||
};
|
||||
|
||||
try {
|
||||
wsRef.current = await wsManager.connect(endpoint, handlers);
|
||||
} catch (error) {
|
||||
console.error('WebSocket connection failed:', error);
|
||||
onError?.(new Event('Connection failed'));
|
||||
}
|
||||
}, [endpoint, onMessage, onError, onConnect, onDisconnect, onReconnect]);
|
||||
|
||||
const disconnect = useCallback(() => {
|
||||
if (wsRef.current) {
|
||||
wsManager.disconnect(endpoint);
|
||||
wsRef.current = null;
|
||||
}
|
||||
}, [endpoint]);
|
||||
|
||||
const send = useCallback((data: any) => {
|
||||
wsManager.send(endpoint, data);
|
||||
}, [endpoint]);
|
||||
|
||||
useEffect(() => {
|
||||
if (autoConnect) {
|
||||
connect();
|
||||
}
|
||||
|
||||
return () => {
|
||||
disconnect();
|
||||
};
|
||||
}, [autoConnect, connect, disconnect]);
|
||||
|
||||
return {
|
||||
connect,
|
||||
disconnect,
|
||||
send,
|
||||
isConnected: wsManager.isConnected(endpoint)
|
||||
};
|
||||
};
|
||||
Reference in New Issue
Block a user