diff --git a/libs/sdk-js/src/client.ts b/libs/sdk-js/src/client.ts index 5fd611c47..15598eab8 100644 --- a/libs/sdk-js/src/client.ts +++ b/libs/sdk-js/src/client.ts @@ -1320,6 +1320,40 @@ export class StoreClient extends BaseClient { } } +class UiClient extends BaseClient { + private static promiseCache: Record | undefined> = + {}; + + private static getOrCached(key: string, fn: () => Promise): Promise { + if (UiClient.promiseCache[key] != null) { + return UiClient.promiseCache[key] as Promise; + } + + const promise = fn(); + UiClient.promiseCache[key] = promise; + return promise; + } + + async getComponent(assistantId: string, agentName: string): Promise { + return UiClient["getOrCached"]( + `${this.apiUrl}-${assistantId}-${agentName}`, + async () => { + const response = await this.asyncCaller.fetch( + ...this.prepareFetchOptions(`/ui/${assistantId}`, { + headers: { + Accept: "text/html", + "Content-Type": "application/json", + }, + method: "POST", + json: { name: agentName }, + }), + ); + return response.text(); + }, + ); + } +} + export class Client< TStateType = DefaultValues, TUpdateType = TStateType, @@ -1350,11 +1384,18 @@ export class Client< */ public store: StoreClient; + /** + * The client for interacting with the UI. + * @internal Used by LoadExternalComponent and the API might change in the future. + */ + public "~ui": UiClient; + constructor(config?: ClientConfig) { this.assistants = new AssistantsClient(config); this.threads = new ThreadsClient(config); this.runs = new RunsClient(config); this.crons = new CronsClient(config); this.store = new StoreClient(config); + this["~ui"] = new UiClient(config); } } diff --git a/libs/sdk-js/src/react-ui/client.tsx b/libs/sdk-js/src/react-ui/client.tsx index f30f442d5..b1ca064ac 100644 --- a/libs/sdk-js/src/react-ui/client.tsx +++ b/libs/sdk-js/src/react-ui/client.tsx @@ -1,3 +1,5 @@ +"use client"; + import { useStream } from "../react/index.js"; import type { UIMessage } from "./types.js"; @@ -105,19 +107,11 @@ class ComponentStore { } const COMPONENT_STORE = new ComponentStore(); -const COMPONENT_PROMISE_CACHE: Record | undefined> = {}; - const EXT_STORE_SYMBOL = Symbol.for("LGUI_EXT_STORE"); const REQUIRE_SYMBOL = Symbol.for("LGUI_REQUIRE"); interface LoadExternalComponentProps extends Pick, "style" | "className"> { - /** API URL of the LangGraph Platform */ - apiUrl?: string; - - /** ID of the assistant */ - assistantId: string; - /** Stream of the assistant */ stream: ReturnType; @@ -137,29 +131,7 @@ interface LoadExternalComponentProps components?: Record; } -function fetchComponent( - apiUrl: string, - assistantId: string, - agentName: string, -): Promise { - const cacheKey = `${apiUrl}-${assistantId}-${agentName}`; - if (COMPONENT_PROMISE_CACHE[cacheKey] != null) { - return COMPONENT_PROMISE_CACHE[cacheKey] as Promise; - } - - const request: Promise = fetch(`${apiUrl}/ui/${assistantId}`, { - headers: { Accept: "text/html", "Content-Type": "application/json" }, - method: "POST", - body: JSON.stringify({ name: agentName }), - }).then((a) => a.text()); - - COMPONENT_PROMISE_CACHE[cacheKey] = request; - return request; -} - export function LoadExternalComponent({ - apiUrl = "http://localhost:2024", - assistantId, stream, message, meta, @@ -180,9 +152,10 @@ export function LoadExternalComponent({ const clientComponent = components?.[message.name]; const hasClientComponent = clientComponent != null; + const uiClient = stream.client["~ui"]; React.useEffect(() => { if (hasClientComponent) return; - fetchComponent(apiUrl, assistantId, message.name).then((html) => { + uiClient.getComponent(stream.assistantId, message.name).then((html) => { const dom = ref.current; if (!dom) return; const root = dom.shadowRoot ?? dom.attachShadow({ mode: "open" }); @@ -193,7 +166,13 @@ export function LoadExternalComponent({ ); root.appendChild(fragment); }); - }, [apiUrl, assistantId, message.name, shadowRootId, hasClientComponent]); + }, [ + uiClient, + stream.assistantId, + message.name, + shadowRootId, + hasClientComponent, + ]); if (hasClientComponent) { return React.createElement(clientComponent, message.content); diff --git a/libs/sdk-js/src/react/stream.tsx b/libs/sdk-js/src/react/stream.tsx index 773876f7e..fdd84f740 100644 --- a/libs/sdk-js/src/react/stream.tsx +++ b/libs/sdk-js/src/react/stream.tsx @@ -563,6 +563,16 @@ export interface UseStream< message: Message, index?: number, ) => MessageMetadata | undefined; + + /** + * LangGraph SDK client used to send request and receive responses. + */ + client: Client; + + /** + * The ID of the assistant to use. + */ + assistantId: string; } type ConfigWithConfigurable> = @@ -632,6 +642,7 @@ export function useStream< options.defaultHeaders, ], ); + const [threadId, onThreadId] = useControllableThreadId(options); const [branch, setBranch] = useState(""); @@ -919,6 +930,9 @@ export function useStream< return values; }, + client, + assistantId, + error, isLoading,