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<string, Record<string, PendingUserInputDraftAnswer>>>({}).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<ApprovalRequestId | null>(null); const [respondingUserInputId, setRespondingUserInputId] = useState<ApprovalRequestId | null>( 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, };}
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<string, Record<string, PendingUserInputDraftAnswer>>
>({}).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<ApprovalRequestId | null>(null);
const [respondingUserInputId, setRespondingUserInputId] = useState<ApprovalRequestId | null>(
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,
};
}