first commit
This commit is contained in:
92
frontend/src/api/hooks/useApi.ts
Normal file
92
frontend/src/api/hooks/useApi.ts
Normal file
@@ -0,0 +1,92 @@
|
||||
// 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 };
|
||||
}
|
||||
|
||||
0
frontend/src/api/hooks/useAuth.ts
Normal file
0
frontend/src/api/hooks/useAuth.ts
Normal file
67
frontend/src/api/hooks/useTraining.ts
Normal file
67
frontend/src/api/hooks/useTraining.ts
Normal file
@@ -0,0 +1,67 @@
|
||||
// frontend/dashboard/src/api/hooks/useTraining.ts
|
||||
/**
|
||||
* Training-specific hooks
|
||||
*/
|
||||
|
||||
import { useState, useEffect, useRef } from 'react';
|
||||
import { TrainingJobStatus, TrainingRequest } from '../../types/api';
|
||||
import { trainingApi } from '../index';
|
||||
import { useApi, useAsyncAction } from './useApi';
|
||||
|
||||
export function useTraining() {
|
||||
const { data: jobs, loading, error, refetch } = useApi(() => trainingApi.getTrainingJobs());
|
||||
const { data: models, refetch: refetchModels } = useApi(() => trainingApi.getTrainedModels());
|
||||
|
||||
const { execute: startTraining, loading: startingTraining } = useAsyncAction(
|
||||
trainingApi.startTraining.bind(trainingApi)
|
||||
);
|
||||
|
||||
return {
|
||||
jobs: jobs || [],
|
||||
models: models || [],
|
||||
loading,
|
||||
error,
|
||||
startingTraining,
|
||||
startTraining: async (request: TrainingRequest) => {
|
||||
const job = await startTraining(request);
|
||||
await refetch();
|
||||
return job;
|
||||
},
|
||||
refresh: refetch,
|
||||
refreshModels: refetchModels,
|
||||
};
|
||||
}
|
||||
|
||||
export function useTrainingProgress(jobId: string | null) {
|
||||
const [progress, setProgress] = useState<TrainingJobStatus | null>(null);
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
const wsRef = useRef<WebSocket | null>(null);
|
||||
|
||||
useEffect(() => {
|
||||
if (!jobId) return;
|
||||
|
||||
// Initial status fetch
|
||||
trainingApi.getTrainingStatus(jobId).then(setProgress).catch(setError);
|
||||
|
||||
// Set up WebSocket for real-time updates
|
||||
wsRef.current = trainingApi.subscribeToTrainingProgress(
|
||||
jobId,
|
||||
(updatedProgress) => {
|
||||
setProgress(updatedProgress);
|
||||
setError(null);
|
||||
},
|
||||
(wsError) => {
|
||||
setError(wsError.message);
|
||||
}
|
||||
);
|
||||
|
||||
return () => {
|
||||
if (wsRef.current) {
|
||||
wsRef.current.close();
|
||||
}
|
||||
};
|
||||
}, [jobId]);
|
||||
|
||||
return { progress, error };
|
||||
}
|
||||
|
||||
109
frontend/src/api/hooks/useWebSocket.ts
Normal file
109
frontend/src/api/hooks/useWebSocket.ts
Normal file
@@ -0,0 +1,109 @@
|
||||
// frontend/dashboard/src/api/hooks/useWebSocket.ts
|
||||
/**
|
||||
* Generic WebSocket hook for real-time updates
|
||||
*/
|
||||
|
||||
import { useState, useEffect, useRef, useCallback } from 'react';
|
||||
|
||||
export interface WebSocketOptions {
|
||||
reconnectAttempts?: number;
|
||||
reconnectInterval?: number;
|
||||
onOpen?: () => void;
|
||||
onClose?: () => void;
|
||||
onError?: (error: Event) => void;
|
||||
}
|
||||
|
||||
export function useWebSocket<T>(
|
||||
url: string | null,
|
||||
options: WebSocketOptions = {}
|
||||
): {
|
||||
data: T | null;
|
||||
connectionState: 'connecting' | 'open' | 'closed' | 'error';
|
||||
send: (data: any) => void;
|
||||
close: () => void;
|
||||
} {
|
||||
const [data, setData] = useState<T | null>(null);
|
||||
const [connectionState, setConnectionState] = useState<'connecting' | 'open' | 'closed' | 'error'>('closed');
|
||||
|
||||
const wsRef = useRef<WebSocket | null>(null);
|
||||
const reconnectTimeoutRef = useRef<NodeJS.Timeout | null>(null);
|
||||
const reconnectAttemptsRef = useRef(0);
|
||||
|
||||
const {
|
||||
reconnectAttempts = 3,
|
||||
reconnectInterval = 3000,
|
||||
onOpen,
|
||||
onClose,
|
||||
onError,
|
||||
} = options;
|
||||
|
||||
const connect = useCallback(() => {
|
||||
if (!url || wsRef.current?.readyState === WebSocket.OPEN) return;
|
||||
|
||||
try {
|
||||
setConnectionState('connecting');
|
||||
wsRef.current = new WebSocket(url);
|
||||
|
||||
wsRef.current.onopen = () => {
|
||||
setConnectionState('open');
|
||||
reconnectAttemptsRef.current = 0;
|
||||
onOpen?.();
|
||||
};
|
||||
|
||||
wsRef.current.onmessage = (event) => {
|
||||
try {
|
||||
const parsedData = JSON.parse(event.data);
|
||||
setData(parsedData);
|
||||
} catch (error) {
|
||||
console.error('Failed to parse WebSocket message:', error);
|
||||
}
|
||||
};
|
||||
|
||||
wsRef.current.onclose = () => {
|
||||
setConnectionState('closed');
|
||||
onClose?.();
|
||||
|
||||
// Attempt to reconnect
|
||||
if (reconnectAttemptsRef.current < reconnectAttempts) {
|
||||
reconnectAttemptsRef.current++;
|
||||
reconnectTimeoutRef.current = setTimeout(connect, reconnectInterval);
|
||||
}
|
||||
};
|
||||
|
||||
wsRef.current.onerror = (error) => {
|
||||
setConnectionState('error');
|
||||
onError?.(error);
|
||||
};
|
||||
} catch (error) {
|
||||
setConnectionState('error');
|
||||
console.error('WebSocket connection failed:', error);
|
||||
}
|
||||
}, [url, reconnectAttempts, reconnectInterval, onOpen, onClose, onError]);
|
||||
|
||||
const send = useCallback((data: any) => {
|
||||
if (wsRef.current?.readyState === WebSocket.OPEN) {
|
||||
wsRef.current.send(JSON.stringify(data));
|
||||
}
|
||||
}, []);
|
||||
|
||||
const close = useCallback(() => {
|
||||
if (reconnectTimeoutRef.current) {
|
||||
clearTimeout(reconnectTimeoutRef.current);
|
||||
}
|
||||
if (wsRef.current) {
|
||||
wsRef.current.close();
|
||||
}
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
if (url) {
|
||||
connect();
|
||||
}
|
||||
|
||||
return () => {
|
||||
close();
|
||||
};
|
||||
}, [url, connect, close]);
|
||||
|
||||
return { data, connectionState, send, close };
|
||||
}
|
||||
Reference in New Issue
Block a user