mirror of
https://github.com/MODSetter/SurfSense.git
synced 2026-04-25 16:56:22 +02:00
- Refactored ApprovalCard in various tools (Gmail, Google Calendar, Google Drive) to utilize the new useHitlPhase hook for improved state management. - Updated logic to handle tool action phases (pending, processing, complete, rejected) consistently across components, enhancing user feedback during interactions. - Simplified decision handling by removing direct state management for approval decisions, streamlining the approval process. - Enhanced UI feedback to reflect the current phase of tool actions, improving user experience during tool interactions.
62 lines
1.9 KiB
TypeScript
62 lines
1.9 KiB
TypeScript
import { useEffect, useRef, useState } from "react";
|
||
|
||
export type HitlPhase = "pending" | "processing" | "complete" | "rejected";
|
||
|
||
interface HitlInterruptLike {
|
||
__decided__?: string | null;
|
||
__completed__?: boolean;
|
||
}
|
||
|
||
const MINIMUM_SHIMMER_MS = 500;
|
||
const FALLBACK_TIMEOUT_MS = 30_000;
|
||
|
||
/**
|
||
* State machine for HITL approval card phases.
|
||
*
|
||
* Phases:
|
||
* pending – waiting for user decision (show buttons)
|
||
* processing – user approved/edited, waiting for backend (shimmer)
|
||
* complete – backend responded with __completed__ (done text)
|
||
* rejected – user rejected (cancelled text)
|
||
*/
|
||
export function useHitlPhase(interruptData: HitlInterruptLike): {
|
||
phase: HitlPhase;
|
||
setProcessing: () => void;
|
||
setRejected: () => void;
|
||
} {
|
||
const [phase, setPhase] = useState<HitlPhase>(() => {
|
||
if (interruptData.__decided__ === "reject") return "rejected";
|
||
if (interruptData.__decided__) return "complete";
|
||
return "pending";
|
||
});
|
||
|
||
const shimmerStartRef = useRef<number | null>(null);
|
||
|
||
// processing → complete when __completed__ arrives (with min shimmer duration)
|
||
useEffect(() => {
|
||
if (phase !== "processing") return;
|
||
if (!interruptData.__completed__) return;
|
||
|
||
const elapsed = shimmerStartRef.current ? Date.now() - shimmerStartRef.current : Infinity;
|
||
const remaining = Math.max(0, MINIMUM_SHIMMER_MS - elapsed);
|
||
|
||
const timer = setTimeout(() => setPhase("complete"), remaining);
|
||
return () => clearTimeout(timer);
|
||
}, [phase, interruptData.__completed__]);
|
||
|
||
// Fallback: processing → complete after 30s even if __completed__ never arrives
|
||
useEffect(() => {
|
||
if (phase !== "processing") return;
|
||
const fallback = setTimeout(() => setPhase("complete"), FALLBACK_TIMEOUT_MS);
|
||
return () => clearTimeout(fallback);
|
||
}, [phase]);
|
||
|
||
return {
|
||
phase,
|
||
setProcessing: () => {
|
||
shimmerStartRef.current = Date.now();
|
||
setPhase("processing");
|
||
},
|
||
setRejected: () => setPhase("rejected"),
|
||
};
|
||
}
|