Spaces:
Sleeping
Sleeping
Download frontend/src/hooks/useFaceRestorePolling.ts from junyoung137/image-tool: direct link, hf CLI and curl.
- Browser
- Download file 5.14 kB
-
https://huggingface.co/spaces/junyoung137/image-tool/resolve/main/frontend/src/hooks/useFaceRestorePolling.ts
- Command line
-
hf download hf://spaces/junyoung137/image-tool/frontend/src/hooks/useFaceRestorePolling.ts
-
curl -L -o useFaceRestorePolling.ts https://huggingface.co/spaces/junyoung137/image-tool/resolve/main/frontend/src/hooks/useFaceRestorePolling.ts
5.14 kB
| /** | |
| * useFaceRestorePolling.ts — polling hook for Face Restoration jobs. | |
| * | |
| * INDEPENDENT of useBgRemovePolling.ts, useUpscalePolling.ts, and | |
| * useJobPolling.ts. Structurally mirrors useBgRemovePolling.ts (same | |
| * elapsed-timer + recursive-setTimeout pattern) but imports from | |
| * facerestoreClient and carries the three extra face-count fields returned | |
| * by the /facerestore/result endpoint. | |
| * | |
| * Why not reuse useBgRemovePolling.ts? | |
| * The face restoration result shape differs — it carries faces_detected, | |
| * faces_restored, faces_rejected — so parameterizing a shared hook would | |
| * push type complexity into it for no benefit. Independent hooks per feature | |
| * is the established pattern in this codebase. | |
| * | |
| * Elapsed timer | |
| * ------------- | |
| * startPolling() records a start timestamp. A setInterval fires every second | |
| * to update elapsedSeconds = floor((now - start) / 1000) for display. | |
| * The interval is cleared on completion, failure, or reset. | |
| * | |
| * Polling | |
| * ------- | |
| * Recursive-setTimeout: schedules the next poll only after the previous | |
| * response arrives. 2 s interval. | |
| */ | |
| import { useCallback, useEffect, useRef, useState } from "react"; | |
| import { getFaceRestoreResult } from "../api/facerestoreClient"; | |
| const POLL_INTERVAL_MS = 2000; | |
| export type FaceRestorePhase = "idle" | "polling" | "completed" | "failed"; | |
| interface FaceRestoreState { | |
| phase: FaceRestorePhase; | |
| resultImageUrl: string | null; | |
| errorMessage: string | null; | |
| elapsedSeconds: number; | |
| facesDetected: number; | |
| facesRestored: number; | |
| facesRejected: number; | |
| } | |
| const INITIAL: FaceRestoreState = { | |
| phase: "idle", | |
| resultImageUrl: null, | |
| errorMessage: null, | |
| elapsedSeconds: 0, | |
| facesDetected: 0, | |
| facesRestored: 0, | |
| facesRejected: 0, | |
| }; | |
| export function useFaceRestorePolling() { | |
| const [state, setState] = useState<FaceRestoreState>(INITIAL); | |
| const pollTimeoutRef = useRef<ReturnType<typeof setTimeout> | null>(null); | |
| const elapsedInterval = useRef<ReturnType<typeof setInterval> | null>(null); | |
| const startTimeRef = useRef<number>(0); | |
| const isMountedRef = useRef(true); | |
| useEffect(() => { | |
| isMountedRef.current = true; | |
| return () => { | |
| isMountedRef.current = false; | |
| clearTimers(); | |
| }; | |
| // eslint-disable-next-line react-hooks/exhaustive-deps | |
| }, []); | |
| function clearTimers() { | |
| if (pollTimeoutRef.current !== null) { | |
| clearTimeout(pollTimeoutRef.current); | |
| pollTimeoutRef.current = null; | |
| } | |
| if (elapsedInterval.current !== null) { | |
| clearInterval(elapsedInterval.current); | |
| elapsedInterval.current = null; | |
| } | |
| } | |
| const startPolling = useCallback((jobId: string) => { | |
| clearTimers(); | |
| if (!isMountedRef.current) return; | |
| startTimeRef.current = Date.now(); | |
| setState({ | |
| phase: "polling", | |
| resultImageUrl: null, | |
| errorMessage: null, | |
| elapsedSeconds: 0, | |
| facesDetected: 0, | |
| facesRestored: 0, | |
| facesRejected: 0, | |
| }); | |
| elapsedInterval.current = setInterval(() => { | |
| if (isMountedRef.current) { | |
| setState(prev => ({ | |
| ...prev, | |
| elapsedSeconds: Math.floor((Date.now() - startTimeRef.current) / 1000), | |
| })); | |
| } | |
| }, 1000); | |
| const poll = async () => { | |
| try { | |
| const result = await getFaceRestoreResult(jobId); | |
| if (!isMountedRef.current) return; | |
| if (result.status === "completed") { | |
| clearTimers(); | |
| setState({ | |
| phase: "completed", | |
| resultImageUrl: result.result_image_url, | |
| errorMessage: null, | |
| elapsedSeconds: Math.floor((Date.now() - startTimeRef.current) / 1000), | |
| facesDetected: result.faces_detected, | |
| facesRestored: result.faces_restored, | |
| facesRejected: result.faces_rejected, | |
| }); | |
| } else if (result.status === "failed") { | |
| clearTimers(); | |
| setState(prev => ({ | |
| ...prev, | |
| phase: "failed", | |
| errorMessage: result.error_message ?? "Face restoration failed with no details.", | |
| })); | |
| } else { | |
| // "pending" or "processing" — keep polling | |
| pollTimeoutRef.current = setTimeout(poll, POLL_INTERVAL_MS); | |
| } | |
| } catch (err) { | |
| if (!isMountedRef.current) return; | |
| clearTimers(); | |
| setState(prev => ({ | |
| ...prev, | |
| phase: "failed", | |
| errorMessage: `Network error while polling: ${(err as Error).message}`, | |
| })); | |
| } | |
| }; | |
| poll(); | |
| }, []); // eslint-disable-line react-hooks/exhaustive-deps | |
| const reset = useCallback(() => { | |
| clearTimers(); | |
| if (isMountedRef.current) setState(INITIAL); | |
| }, []); // eslint-disable-line react-hooks/exhaustive-deps | |
| return { | |
| phase: state.phase, | |
| resultImageUrl: state.resultImageUrl, | |
| errorMessage: state.errorMessage, | |
| elapsedSeconds: state.elapsedSeconds, | |
| facesDetected: state.facesDetected, | |
| facesRestored: state.facesRestored, | |
| facesRejected: state.facesRejected, | |
| startPolling, | |
| reset, | |
| }; | |
| } | |