2025-04-16 12:56:08 -04:00
|
|
|
import type { ReviewDecision } from "../utils/agent/review";
|
|
|
|
|
import type React from "react";
|
|
|
|
|
|
|
|
|
|
import { useState, useCallback, useRef } from "react";
|
|
|
|
|
|
|
|
|
|
type ConfirmationResult = {
|
|
|
|
|
decision: ReviewDecision;
|
|
|
|
|
customDenyMessage?: string;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
type ConfirmationItem = {
|
|
|
|
|
prompt: React.ReactNode;
|
|
|
|
|
resolve: (result: ConfirmationResult) => void;
|
2025-04-18 06:28:58 +10:00
|
|
|
explanation?: string;
|
2025-04-16 12:56:08 -04:00
|
|
|
};
|
|
|
|
|
|
|
|
|
|
export function useConfirmation(): {
|
|
|
|
|
submitConfirmation: (result: ConfirmationResult) => void;
|
2025-04-18 06:28:58 +10:00
|
|
|
requestConfirmation: (
|
|
|
|
|
prompt: React.ReactNode,
|
|
|
|
|
explanation?: string,
|
|
|
|
|
) => Promise<ConfirmationResult>;
|
2025-04-16 12:56:08 -04:00
|
|
|
confirmationPrompt: React.ReactNode | null;
|
2025-04-18 06:28:58 +10:00
|
|
|
explanation?: string;
|
2025-04-16 12:56:08 -04:00
|
|
|
} {
|
|
|
|
|
// The current prompt is just the head of the queue
|
|
|
|
|
const [current, setCurrent] = useState<ConfirmationItem | null>(null);
|
|
|
|
|
// The entire queue is stored in a ref to avoid re-renders
|
|
|
|
|
const queueRef = useRef<Array<ConfirmationItem>>([]);
|
|
|
|
|
|
|
|
|
|
// Move queue forward to the next prompt
|
|
|
|
|
const advanceQueue = useCallback(() => {
|
|
|
|
|
const next = queueRef.current.shift() ?? null;
|
|
|
|
|
setCurrent(next);
|
|
|
|
|
}, []);
|
|
|
|
|
|
|
|
|
|
// Called whenever someone wants a confirmation
|
|
|
|
|
const requestConfirmation = useCallback(
|
2025-04-18 06:28:58 +10:00
|
|
|
(prompt: React.ReactNode, explanation?: string) => {
|
2025-04-16 12:56:08 -04:00
|
|
|
return new Promise<ConfirmationResult>((resolve) => {
|
|
|
|
|
const wasEmpty = queueRef.current.length === 0;
|
2025-04-18 06:28:58 +10:00
|
|
|
queueRef.current.push({ prompt, resolve, explanation });
|
2025-04-16 12:56:08 -04:00
|
|
|
|
|
|
|
|
// If the queue was empty, we need to kick off the first prompt
|
|
|
|
|
if (wasEmpty) {
|
|
|
|
|
advanceQueue();
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
},
|
|
|
|
|
[advanceQueue],
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
// Called whenever user picks Yes / No
|
|
|
|
|
const submitConfirmation = (result: ConfirmationResult) => {
|
|
|
|
|
if (current) {
|
|
|
|
|
current.resolve(result);
|
|
|
|
|
advanceQueue();
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
return {
|
|
|
|
|
confirmationPrompt: current?.prompt, // the prompt to render now
|
2025-04-18 06:28:58 +10:00
|
|
|
explanation: current?.explanation, // the explanation to render if available
|
2025-04-16 12:56:08 -04:00
|
|
|
requestConfirmation,
|
|
|
|
|
submitConfirmation,
|
|
|
|
|
};
|
|
|
|
|
}
|