UNPKG

@copilotkit/react-core

Version:

<img src="https://github.com/user-attachments/assets/0a6b64d9-e193-4940-a3f6-60334ac34084" alt="banner" style="border-radius: 12px; border: 2px solid #d6d4fa;" />

137 lines (121 loc) 4.02 kB
import { useCopilotContext } from "../context"; import React, { useCallback, useEffect, useMemo } from "react"; import type { AbstractAgent, AgentSubscriber } from "@ag-ui/client"; import { MetaEventName } from "@copilotkit/runtime-client-gql"; import { dataToUUID, parseJson } from "@copilotkit/shared"; import { useAgentNodeName } from "./use-agent-nodename"; import { useCopilotChatConfiguration } from "@copilotkitnext/react"; type InterruptProps = { event: any; result: any; render: (props: { event: any; result: any; resolve: (response: string) => void; }) => string | React.ReactElement; resolve: (response: string) => void; }; const InterruptRenderer: React.FC<InterruptProps> = ({ event, result, render, resolve }) => { return render({ event, result, resolve }); }; export function useLangGraphInterruptRender( agent: AbstractAgent, ): string | React.ReactElement | null { const { interruptActions, agentSession, threadId, interruptEventQueue, addInterruptEvent, resolveInterruptEvent, } = useCopilotContext(); const existingConfig = useCopilotChatConfiguration(); const resolvedAgentId = existingConfig?.agentId ?? "default"; const nodeName = useAgentNodeName(resolvedAgentId); useEffect(() => { if (!agent) return; let localInterrupt: any = null; const subscriber: AgentSubscriber = { onCustomEvent: ({ event }) => { if (event.name === "on_interrupt") { const eventData = { name: MetaEventName.LangGraphInterruptEvent, type: event.type, value: parseJson(event.value, event.value), }; const eventId = dataToUUID(eventData, "interruptEvents"); localInterrupt = { eventId, threadId, event: eventData, }; } }, onRunStartedEvent: () => { localInterrupt = null; }, onRunFinalized: () => { if (localInterrupt) { addInterruptEvent(localInterrupt); localInterrupt = null; } }, }; const { unsubscribe } = agent.subscribe(subscriber); return () => { unsubscribe(); }; // eslint-disable-next-line react-hooks/exhaustive-deps }, [agent, threadId]); const handleResolve = useCallback( (eventId: string, response?: string) => { agent?.runAgent({ forwardedProps: { command: { resume: response, }, }, }); resolveInterruptEvent(threadId, eventId, response ?? ""); }, // eslint-disable-next-line react-hooks/exhaustive-deps [agent, threadId], ); return useMemo(() => { // Get the queue for this thread and find the first unresponded event const eventQueue = interruptEventQueue[threadId] || []; const currentQueuedEvent = eventQueue.find((qe) => !qe.event.response); if (!currentQueuedEvent || !agentSession) return null; // Find the first matching action from all registered actions const allActions = Object.values(interruptActions); const matchingAction = allActions.find((action) => { if (!action.enabled) return true; // No filter = match all return action.enabled({ eventValue: currentQueuedEvent.event.value, agentMetadata: { ...agentSession, nodeName, }, }); }); if (!matchingAction) return null; const { render, handler } = matchingAction; const resolveInterrupt = (response: string) => { handleResolve(currentQueuedEvent.eventId, response); }; let result = null; if (handler) { result = handler({ event: currentQueuedEvent.event, resolve: resolveInterrupt, }); } if (!render) return null; return React.createElement(InterruptRenderer, { event: currentQueuedEvent.event, result, render, resolve: resolveInterrupt, }); }, [interruptActions, interruptEventQueue, threadId, agentSession, handleResolve]); }