@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;" />
1 lines • 16.3 kB
Source Map (JSON)
{"version":3,"sources":["../src/hooks/use-copilot-action.ts"],"sourcesContent":["/**\n * Example usage of useCopilotAction with complex parameters:\n *\n * @example\n * useCopilotAction({\n *   name: \"myAction\",\n *   parameters: [\n *     { name: \"arg1\", type: \"string\", enum: [\"option1\", \"option2\", \"option3\"], required: false },\n *     { name: \"arg2\", type: \"number\" },\n *     {\n *       name: \"arg3\",\n *       type: \"object\",\n *       attributes: [\n *         { name: \"nestedArg1\", type: \"boolean\" },\n *         { name: \"xyz\", required: false },\n *       ],\n *     },\n *     { name: \"arg4\", type: \"number[]\" },\n *   ],\n *   handler: ({ arg1, arg2, arg3, arg4 }) => {\n *     const x = arg3.nestedArg1;\n *     const z = arg3.xyz;\n *     console.log(arg1, arg2, arg3);\n *   },\n * });\n *\n * @example\n * // Simple action without parameters\n * useCopilotAction({\n *   name: \"myAction\",\n *   handler: () => {\n *     console.log(\"No parameters provided.\");\n *   },\n * });\n *\n * @example\n * // Interactive action with UI rendering and response handling\n * useCopilotAction({\n *   name: \"handleMeeting\",\n *   description: \"Handle a meeting by booking or canceling\",\n *   parameters: [\n *     {\n *       name: \"meeting\",\n *       type: \"string\",\n *       description: \"The meeting to handle\",\n *       required: true,\n *     },\n *     {\n *       name: \"date\",\n *       type: \"string\",\n *       description: \"The date of the meeting\",\n *       required: true,\n *     },\n *     {\n *       name: \"title\",\n *       type: \"string\",\n *       description: \"The title of the meeting\",\n *       required: true,\n *     },\n *   ],\n *   renderAndWaitForResponse: ({ args, respond, status }) => {\n *     const { meeting, date, title } = args;\n *     return (\n *       <MeetingConfirmationDialog\n *         meeting={meeting}\n *         date={date}\n *         title={title}\n *         onConfirm={() => respond('meeting confirmed')}\n *         onCancel={() => respond('meeting canceled')}\n *       />\n *     );\n *   },\n * });\n *\n * @example\n * // Catch all action allows you to render actions that are not defined in the frontend\n * useCopilotAction({\n *   name: \"*\",\n *   render: ({ name, args, status, result, handler, respond }) => {\n *     return <div>Rendering action: {name}</div>;\n *   },\n * });\n */\n\n/**\n * <img src=\"https://cdn.copilotkit.ai/docs/copilotkit/images/use-copilot-action/useCopilotAction.gif\" width=\"500\" />\n * `useCopilotAction` is a React hook that you can use in your application to provide\n * custom actions that can be called by the AI. Essentially, it allows the Copilot to\n * execute these actions contextually during a chat, based on the user's interactions\n * and needs.\n *\n * Here's how it works:\n *\n * Use `useCopilotAction` to set up actions that the Copilot can call. To provide\n * more context to the Copilot, you can provide it with a `description` (for example to explain\n * what the action does, under which conditions it can be called, etc.).\n *\n * Then you define the parameters of the action, which can be simple, e.g. primitives like strings or numbers,\n * or complex, e.g. objects or arrays.\n *\n * Finally, you provide a `handler` function that receives the parameters and returns a result.\n * CopilotKit takes care of automatically inferring the parameter types, so you get type safety\n * and autocompletion for free.\n *\n * To render a custom UI for the action, you can provide a `render()` function. This function\n * lets you render a custom component or return a string to display.\n *\n * ## Usage\n *\n * ### Simple Usage\n *\n * ```tsx\n * useCopilotAction({\n *   name: \"sayHello\",\n *   description: \"Say hello to someone.\",\n *   parameters: [\n *     {\n *       name: \"name\",\n *       type: \"string\",\n *       description: \"name of the person to say greet\",\n *     },\n *   ],\n *   handler: async ({ name }) => {\n *     alert(`Hello, ${name}!`);\n *   },\n * });\n * ```\n *\n * ## Generative UI\n *\n * This hooks enables you to dynamically generate UI elements and render them in the copilot chat. For more information, check out the [Generative UI](/guides/generative-ui) page.\n */\nimport { Parameter, randomId } from \"@copilotkit/shared\";\nimport { createElement, Fragment, useEffect, useRef } from \"react\";\nimport { useCopilotContext } from \"../context/copilot-context\";\nimport { useAsyncCallback } from \"../components/error-boundary/error-utils\";\nimport {\n  ActionRenderProps,\n  ActionRenderPropsNoArgsWait,\n  ActionRenderPropsWait,\n  CatchAllFrontendAction,\n  FrontendAction,\n} from \"../types/frontend-action\";\nimport { useToast } from \"../components/toast/toast-provider\";\n\n// We implement useCopilotAction dependency handling so that\n// the developer has the option to not provide any dependencies.\n// In this case, we assume they want to update the handler on each rerender.\n// To avoid getting stuck in an infinite loop, we update the handler directly,\n// skipping React state updates.\n// This is ok in this case, because the handler is not part of any UI that\n// needs to be updated.\n// useCallback, useMemo or other memoization techniques are not suitable here,\n// because they will cause a infinite rerender loop.\nexport function useCopilotAction<const T extends Parameter[] | [] = []>(\n  action: FrontendAction<T> | CatchAllFrontendAction,\n  dependencies?: any[],\n): void {\n  const { setAction, removeAction, actions, chatComponentsCache } = useCopilotContext();\n  const idRef = useRef<string>(randomId());\n  const renderAndWaitRef = useRef<RenderAndWaitForResponse | null>(null);\n  const activatingMessageIdRef = useRef<string | null>(null);\n  const { addToast } = useToast();\n\n  // clone the action to avoid mutating the original object\n  action = { ...action };\n\n  // const { currentlyActivatingHitlActionMessageIdRef } = useCopilotContext() as any; // <-- REMOVE THIS FOR NOW\n\n  // If the developer provides a renderAndWaitForResponse function, we transform the action\n  // to use a promise internally, so that we can treat it like a normal action.\n  if (\n    // renderAndWaitForResponse is not available for catch all actions\n    isFrontendAction(action) &&\n    // check if renderAndWaitForResponse is set\n    (action.renderAndWait || action.renderAndWaitForResponse)\n  ) {\n    (action as any)._isRenderAndWait = true; // Internal flag to identify this action type later\n    const renderAndWait = action.renderAndWait || action.renderAndWaitForResponse;\n    // remove the renderAndWait function from the action\n    action.renderAndWait = undefined;\n    action.renderAndWaitForResponse = undefined;\n\n    // Add a method for use-chat.ts to set the activating message ID.\n    // This helps correlate the action instance with the message being processed by use-chat.\n    (action as any)._setActivatingMessageId = (id: string | null) => {\n      activatingMessageIdRef.current = id;\n    };\n\n    // add a handler that will be called when the action is executed\n    action.handler = useAsyncCallback(async () => {\n      const currentActivatingId = activatingMessageIdRef.current;\n      // we create a new promise when the handler is called\n      let resolve: (result: any) => void;\n      let reject: (error: any) => void;\n      const promise = new Promise<any>((resolvePromise, rejectPromise) => {\n        resolve = resolvePromise;\n        reject = rejectPromise;\n      });\n      renderAndWaitRef.current = {\n        promise,\n        resolve: resolve!,\n        reject: reject!,\n        messageId: currentActivatingId,\n      };\n      // then we await the promise (it will be resolved in the original renderAndWait function)\n      const result = await promise;\n      return result;\n    }, []) as any;\n\n    // add a render function that will be called when the action is rendered\n    action.render = ((props: ActionRenderProps<T> & { messageId?: string }): React.ReactElement => {\n      const currentRenderMessageId = props.messageId;\n      // For renderAndWaitForResponse, the 'executing' state might be set by use-chat before\n      // this specific action instance's handler (and thus its promise) is ready.\n      // This logic adjusts the status to 'inProgress' if the current render\n      // isn't for the actively processing HITL action, preventing premature interaction.\n      let status = props.status;\n      if (props.status === \"executing\") {\n        if (!renderAndWaitRef.current || !renderAndWaitRef.current.promise) {\n          status = \"inProgress\";\n        } else if (\n          renderAndWaitRef.current.messageId !== currentRenderMessageId &&\n          activatingMessageIdRef.current !== currentRenderMessageId\n        ) {\n          status = \"inProgress\";\n        }\n        // If conditions met, status remains 'executing'\n      }\n      // Create type safe waitProps based on whether T extends empty array or not\n      const waitProps = {\n        status,\n        args: props.args,\n        result: props.result,\n        // handler and respond should only be provided if this is the truly active instance\n        // and its promise infrastructure is ready.\n        handler:\n          status === \"executing\" &&\n          renderAndWaitRef.current &&\n          renderAndWaitRef.current.messageId === currentRenderMessageId\n            ? renderAndWaitRef.current!.resolve\n            : undefined,\n        respond:\n          status === \"executing\" &&\n          renderAndWaitRef.current &&\n          renderAndWaitRef.current.messageId === currentRenderMessageId\n            ? renderAndWaitRef.current!.resolve\n            : undefined,\n      } as T extends [] ? ActionRenderPropsNoArgsWait<T> : ActionRenderPropsWait<T>;\n\n      // Type guard to check if renderAndWait is for no args case\n      const isNoArgsRenderWait = (\n        _fn:\n          | ((props: ActionRenderPropsNoArgsWait<T>) => React.ReactElement)\n          | ((props: ActionRenderPropsWait<T>) => React.ReactElement),\n      ): _fn is (props: ActionRenderPropsNoArgsWait<T>) => React.ReactElement => {\n        return action.parameters?.length === 0;\n      };\n\n      // Safely call renderAndWait with correct props type\n      if (renderAndWait) {\n        if (isNoArgsRenderWait(renderAndWait)) {\n          return renderAndWait(waitProps as ActionRenderPropsNoArgsWait<T>);\n        } else {\n          return renderAndWait(waitProps as ActionRenderPropsWait<T>);\n        }\n      }\n\n      // Return empty Fragment instead of null\n      return createElement(Fragment);\n    }) as any;\n  }\n\n  // If the developer doesn't provide dependencies, we assume they want to\n  // update handler and render function when the action object changes.\n  // This ensures that any captured variables in the handler are up to date.\n  if (dependencies === undefined) {\n    if (actions[idRef.current]) {\n      // catch all actions don't have a handler\n      if (isFrontendAction(action)) {\n        actions[idRef.current].handler = action.handler as any;\n      }\n      if (typeof action.render === \"function\") {\n        if (chatComponentsCache.current !== null) {\n          // TODO: using as any here because the type definitions are getting to tricky\n          // not wasting time on this now - we know the types are compatible\n          chatComponentsCache.current.actions[action.name] = action.render as any;\n        }\n      }\n    }\n  }\n\n  useEffect(() => {\n    const hasDuplicate = Object.values(actions).some(\n      (otherAction) => otherAction.name === action.name && otherAction !== actions[idRef.current],\n    );\n\n    if (hasDuplicate) {\n      addToast({\n        type: \"warning\",\n        message: `Found an already registered action with name ${action.name}.`,\n        id: `dup-action-${action.name}`,\n      });\n    }\n  }, [actions]);\n\n  useEffect(() => {\n    setAction(idRef.current, action as any);\n    if (chatComponentsCache.current !== null && action.render !== undefined) {\n      // see comment about type safety above\n      chatComponentsCache.current.actions[action.name] = action.render as any;\n    }\n    return () => {\n      // NOTE: For now, we don't remove the chatComponentsCache entry when the action is removed.\n      // This is because we currently don't have access to the messages array in CopilotContext.\n      // UPDATE: We now have access, we should remove the entry if not referenced by any message.\n      removeAction(idRef.current);\n    };\n  }, [\n    setAction,\n    removeAction,\n    isFrontendAction(action) ? action.description : undefined,\n    action.name,\n    isFrontendAction(action) ? action.disabled : undefined,\n    isFrontendAction(action) ? action.available : undefined,\n    // This should be faster than deep equality checking\n    // In addition, all major JS engines guarantee the order of object keys\n    JSON.stringify(isFrontendAction(action) ? action.parameters : []),\n    // include render only if it's a string\n    typeof action.render === \"string\" ? action.render : undefined,\n    // dependencies set by the developer\n    ...(dependencies || []),\n  ]);\n}\n\nfunction isFrontendAction<T extends Parameter[]>(\n  action: FrontendAction<T> | CatchAllFrontendAction,\n): action is FrontendAction<T> {\n  return action.name !== \"*\";\n}\n\ninterface RenderAndWaitForResponse {\n  promise: Promise<any>;\n  resolve: (result: any) => void;\n  reject: (error: any) => void;\n  messageId: string | null;\n}\n"],"mappings":"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","names":[]}