diff --git a/libs/sdk-js/src/react-ui/client.tsx b/libs/sdk-js/src/react-ui/client.tsx index d3e306a01..1484f491d 100644 --- a/libs/sdk-js/src/react-ui/client.tsx +++ b/libs/sdk-js/src/react-ui/client.tsx @@ -126,7 +126,7 @@ interface LoadExternalComponentProps meta?: unknown; /** Fallback to be rendered when the component is loading */ - fallback?: React.ReactNode; + fallback?: React.ReactNode | Record; /** * Map of components that can be rendered directly without fetching the UI code @@ -135,6 +135,33 @@ interface LoadExternalComponentProps components?: Record; } +const isIterable = (value: unknown): value is Iterable => + value != null && typeof value === "object" && Symbol.iterator in value; + +const isPromise = (value: unknown): value is Promise => + value != null && + typeof value === "object" && + "then" in value && + typeof value.then === "function"; + +const isReactNode = (value: unknown): value is React.ReactNode => { + if (React.isValidElement(value)) return true; + if (value == null) return true; + if ( + typeof value === "string" || + typeof value === "number" || + typeof value === "bigint" || + typeof value === "boolean" + ) { + return true; + } + + if (isIterable(value)) return true; + if (isPromise(value)) return true; + + return false; +}; + export function LoadExternalComponent({ stream, namespace, @@ -157,6 +184,12 @@ export function LoadExternalComponent({ const clientComponent = components?.[message.name]; const hasClientComponent = clientComponent != null; + const fallbackComponent = isReactNode(fallback) + ? fallback + : typeof fallback === "object" && fallback != null + ? fallback?.[message.name] + : null; + const uiNamespace = namespace ?? stream.assistantId; const uiClient = stream.client["~ui"]; React.useEffect(() => { @@ -192,7 +225,7 @@ export function LoadExternalComponent({ React.createElement(state.comp, message.props), state.target, ) - : fallback} + : fallbackComponent} );