import { useAtomValue } from "@effect/atom-react"; import { useCallback, useMemo, useState } from "react"; import { ApprovalRequestId, CommandId, type ProviderApprovalDecision } from "@t3tools/contracts"; import { Atom } from "effect/unstable/reactivity"; import { scopedRequestKey } from "../lib/scopedEntities"; import { buildPendingUserInputAnswers, derivePendingApprovals, derivePendingUserInputs, setPendingUserInputCustomAnswer, type PendingUserInputDraftAnswer, } from "../lib/threadActivity"; import { uuidv4 } from "../lib/uuid"; import { appAtomRegistry } from "./atom-registry"; import { getEnvironmentClient } from "./environment-session-registry"; import { useSelectedThreadDetail } from "./use-thread-detail"; import { useThreadSelection } from "./use-thread-selection"; const userInputDraftsByRequestKeyAtom = Atom.make< Record> >({}).pipe(Atom.keepAlive, Atom.withLabel("mobile:user-input-drafts")); function setUserInputDraftOption(requestKey: string, questionId: string, label: string): void { const current = appAtomRegistry.get(userInputDraftsByRequestKeyAtom); appAtomRegistry.set(userInputDraftsByRequestKeyAtom, { ...current, [requestKey]: { ...current[requestKey], [questionId]: { selectedOptionLabel: label, }, }, }); } function setUserInputDraftCustomAnswer( requestKey: string, questionId: string, customAnswer: string, ): void { const current = appAtomRegistry.get(userInputDraftsByRequestKeyAtom); appAtomRegistry.set(userInputDraftsByRequestKeyAtom, { ...current, [requestKey]: { ...current[requestKey], [questionId]: setPendingUserInputCustomAnswer( current[requestKey]?.[questionId], customAnswer, ), }, }); } export function useSelectedThreadRequests() { const { selectedThread: selectedThreadShell } = useThreadSelection(); const selectedThread = useSelectedThreadDetail(); const userInputDraftsByRequestKey = useAtomValue(userInputDraftsByRequestKeyAtom); const [respondingApprovalId, setRespondingApprovalId] = useState(null); const [respondingUserInputId, setRespondingUserInputId] = useState( null, ); const activePendingApprovals = useMemo( () => (selectedThread ? derivePendingApprovals(selectedThread.activities) : []), [selectedThread], ); const activePendingApproval = activePendingApprovals[0] ?? null; const activePendingUserInputs = useMemo( () => (selectedThread ? derivePendingUserInputs(selectedThread.activities) : []), [selectedThread], ); const activePendingUserInput = activePendingUserInputs[0] ?? null; const activePendingUserInputDrafts = activePendingUserInput && selectedThreadShell ? (userInputDraftsByRequestKey[ scopedRequestKey(selectedThreadShell.environmentId, activePendingUserInput.requestId) ] ?? {}) : {}; const activePendingUserInputAnswers = activePendingUserInput ? buildPendingUserInputAnswers(activePendingUserInput.questions, activePendingUserInputDrafts) : null; const onSelectUserInputOption = useCallback( (requestId: ApprovalRequestId, questionId: string, label: string) => { if (!selectedThreadShell) { return; } const requestKey = scopedRequestKey(selectedThreadShell.environmentId, requestId); setUserInputDraftOption(requestKey, questionId, label); }, [selectedThreadShell], ); const onChangeUserInputCustomAnswer = useCallback( (requestId: ApprovalRequestId, questionId: string, customAnswer: string) => { if (!selectedThreadShell) { return; } const requestKey = scopedRequestKey(selectedThreadShell.environmentId, requestId); setUserInputDraftCustomAnswer(requestKey, questionId, customAnswer); }, [selectedThreadShell], ); const onRespondToApproval = useCallback( async (requestId: ApprovalRequestId, decision: ProviderApprovalDecision) => { if (!selectedThreadShell) { return; } const client = getEnvironmentClient(selectedThreadShell.environmentId); if (!client) { return; } setRespondingApprovalId(requestId); try { await client.orchestration.dispatchCommand({ type: "thread.approval.respond", commandId: CommandId.make(uuidv4()), threadId: selectedThreadShell.id, requestId, decision, createdAt: new Date().toISOString(), }); } finally { setRespondingApprovalId((current) => (current === requestId ? null : current)); } }, [selectedThreadShell], ); const onSubmitUserInput = useCallback(async () => { if (!selectedThreadShell || !activePendingUserInput || !activePendingUserInputAnswers) { return; } const client = getEnvironmentClient(selectedThreadShell.environmentId); if (!client) { return; } setRespondingUserInputId(activePendingUserInput.requestId); try { await client.orchestration.dispatchCommand({ type: "thread.user-input.respond", commandId: CommandId.make(uuidv4()), threadId: selectedThreadShell.id, requestId: activePendingUserInput.requestId, answers: activePendingUserInputAnswers, createdAt: new Date().toISOString(), }); } finally { setRespondingUserInputId((current) => current === activePendingUserInput.requestId ? null : current, ); } }, [activePendingUserInput, activePendingUserInputAnswers, selectedThreadShell]); return { activePendingApproval, activePendingUserInput, activePendingUserInputDrafts, activePendingUserInputAnswers, respondingApprovalId, respondingUserInputId, onRespondToApproval, onSelectUserInputOption, onChangeUserInputCustomAnswer, onSubmitUserInput, }; }