From ba5116f3002096d2d9426469a4b38c8cb5585584 Mon Sep 17 00:00:00 2001 From: Simon Farshid Date: Fri, 18 Oct 2024 18:27:31 -0700 Subject: [PATCH] feat: useInlineRender hook (#1025) --- .changeset/slimy-socks-unite.md | 5 +++++ apps/docs/content/docs/advanced/ToolUI.mdx | 9 ++++----- .../src/context/stores/AssistantToolUIs.ts | 3 ++- packages/react/src/model-config/index.ts | 1 + .../src/model-config/useAssistantTool.tsx | 20 +++++++++---------- .../src/model-config/useAssistantToolUI.tsx | 6 +++--- .../src/model-config/useInlineRender.tsx | 18 +++++++++++++++++ 7 files changed, 43 insertions(+), 19 deletions(-) create mode 100644 .changeset/slimy-socks-unite.md create mode 100644 packages/react/src/model-config/useInlineRender.tsx diff --git a/.changeset/slimy-socks-unite.md b/.changeset/slimy-socks-unite.md new file mode 100644 index 000000000..aa61c5418 --- /dev/null +++ b/.changeset/slimy-socks-unite.md @@ -0,0 +1,5 @@ +--- +"@assistant-ui/react": patch +--- + +feat: useInlineRender hook diff --git a/apps/docs/content/docs/advanced/ToolUI.mdx b/apps/docs/content/docs/advanced/ToolUI.mdx index e21d64312..e7df3de11 100644 --- a/apps/docs/content/docs/advanced/ToolUI.mdx +++ b/apps/docs/content/docs/advanced/ToolUI.mdx @@ -92,19 +92,18 @@ const MyApp = () => { ### Inline Tool UI Hooks -If you need access to component props, you can use the `useAssistantToolUI` hook. +If you need access to component props, you can use the `useAssistantToolUI` hook. If you are passing a component inline, you should use the `useInlineRender` hook to prevent the component from being re-mounted on every render. ```tsx {5-11} -import { useAssistantToolUI } from "@assistant-ui/react"; -import { useCallback } from "react"; +import { useAssistantToolUI, useInlineRender } from "@assistant-ui/react"; const MyComponent = ({ product_id }) => { useAssistantToolUI({ toolName: "current_product_info", - render: useCallback(({ args, status }) => { + render: useInlineRender(({ args, status }) => { // you can access component props here return

product_info({ product_id })

; - }, [product_id]), + }), }); ... diff --git a/packages/react/src/context/stores/AssistantToolUIs.ts b/packages/react/src/context/stores/AssistantToolUIs.ts index 93743e6b2..085bf299b 100644 --- a/packages/react/src/context/stores/AssistantToolUIs.ts +++ b/packages/react/src/context/stores/AssistantToolUIs.ts @@ -2,13 +2,14 @@ import { create } from "zustand"; import type { ToolCallContentPartComponent } from "../../types/ContentPartComponentTypes"; +import { Unsubscribe } from "../../types"; export type AssistantToolUIsState = Readonly<{ getToolUI: (toolName: string) => ToolCallContentPartComponent | null; setToolUI: ( toolName: string, render: ToolCallContentPartComponent, - ) => () => void; + ) => Unsubscribe; }>; export const makeAssistantToolUIsStore = () => diff --git a/packages/react/src/model-config/index.ts b/packages/react/src/model-config/index.ts index 5de55b03b..70ea7e7a0 100644 --- a/packages/react/src/model-config/index.ts +++ b/packages/react/src/model-config/index.ts @@ -9,3 +9,4 @@ export { useAssistantToolUI, type AssistantToolUIProps, } from "./useAssistantToolUI"; +export { useInlineRender } from "./useInlineRender"; diff --git a/packages/react/src/model-config/useAssistantTool.tsx b/packages/react/src/model-config/useAssistantTool.tsx index 6d051fde2..c36ad736b 100644 --- a/packages/react/src/model-config/useAssistantTool.tsx +++ b/packages/react/src/model-config/useAssistantTool.tsx @@ -24,22 +24,22 @@ export const useAssistantTool = < ) => { const assistantRuntime = useAssistantRuntime(); const toolUIsStore = useToolUIsStore(); + + useEffect(() => { + return tool.render + ? toolUIsStore.getState().setToolUI(tool.toolName, tool.render) + : undefined; + }, [toolUIsStore, tool.toolName, tool.render]); + useEffect(() => { const { toolName, render, ...rest } = tool; const config = { tools: { - [tool.toolName]: rest, + [toolName]: rest, }, }; - const unsub1 = assistantRuntime.registerModelConfigProvider({ + return assistantRuntime.registerModelConfigProvider({ getModelConfig: () => config, }); - const unsub2 = render - ? toolUIsStore.getState().setToolUI(toolName, render) - : undefined; - return () => { - unsub1(); - unsub2?.(); - }; - }, [assistantRuntime, toolUIsStore, tool]); + }, [assistantRuntime, tool]); }; diff --git a/packages/react/src/model-config/useAssistantToolUI.tsx b/packages/react/src/model-config/useAssistantToolUI.tsx index 9906d6199..b5c7cc261 100644 --- a/packages/react/src/model-config/useAssistantToolUI.tsx +++ b/packages/react/src/model-config/useAssistantToolUI.tsx @@ -13,12 +13,12 @@ export type AssistantToolUIProps< }; export const useAssistantToolUI = ( + // TODO remove null option in 0.6 tool: AssistantToolUIProps | null, ) => { const toolUIsStore = useToolUIsStore(); useEffect(() => { if (!tool) return; - const { toolName, render } = tool; - return toolUIsStore.getState().setToolUI(toolName, render); - }, [toolUIsStore, tool]); + return toolUIsStore.getState().setToolUI(tool.toolName, tool.render); + }, [toolUIsStore, tool?.toolName, tool?.render]); }; diff --git a/packages/react/src/model-config/useInlineRender.tsx b/packages/react/src/model-config/useInlineRender.tsx new file mode 100644 index 000000000..8cdfbf132 --- /dev/null +++ b/packages/react/src/model-config/useInlineRender.tsx @@ -0,0 +1,18 @@ +import { FC, useCallback, useEffect, useState } from "react"; +import { ToolCallContentPartProps } from "../types"; +import { create } from "zustand"; + +export const useInlineRender = , TResult>( + toolUI: FC>, +): FC> => { + const [useToolUI] = useState(() => create(() => toolUI)); + + useEffect(() => { + useToolUI.setState(toolUI); + }); + + return useCallback((args) => { + const toolUI = useToolUI(); + return toolUI(args); + }, []); +};