Add new frontend - fix 2

This commit is contained in:
Urtzi Alfaro
2025-07-22 08:50:18 +02:00
parent c8517c41a5
commit d29a94e8ab
35 changed files with 1476 additions and 8301 deletions

View File

@@ -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 };
}

View 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 };
};

View 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
};
};

View 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)
};
};