feat(sdk-js): use fetchClient from client in gen ui

This commit is contained in:
Tat Dat Duong
2025-03-10 13:59:00 +01:00
parent d4368cfa97
commit 0e9c41f480
3 changed files with 66 additions and 32 deletions
+41
View File
@@ -1320,6 +1320,40 @@ export class StoreClient extends BaseClient {
}
}
class UiClient extends BaseClient {
private static promiseCache: Record<string, Promise<unknown> | undefined> =
{};
private static getOrCached<T>(key: string, fn: () => Promise<T>): Promise<T> {
if (UiClient.promiseCache[key] != null) {
return UiClient.promiseCache[key] as Promise<T>;
}
const promise = fn();
UiClient.promiseCache[key] = promise;
return promise;
}
async getComponent(assistantId: string, agentName: string): Promise<string> {
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);
}
}
+11 -32
View File
@@ -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<string, Promise<string> | undefined> = {};
const EXT_STORE_SYMBOL = Symbol.for("LGUI_EXT_STORE");
const REQUIRE_SYMBOL = Symbol.for("LGUI_REQUIRE");
interface LoadExternalComponentProps
extends Pick<React.HTMLAttributes<HTMLDivElement>, "style" | "className"> {
/** API URL of the LangGraph Platform */
apiUrl?: string;
/** ID of the assistant */
assistantId: string;
/** Stream of the assistant */
stream: ReturnType<typeof useStream>;
@@ -137,29 +131,7 @@ interface LoadExternalComponentProps
components?: Record<string, React.FunctionComponent | React.ComponentClass>;
}
function fetchComponent(
apiUrl: string,
assistantId: string,
agentName: string,
): Promise<string> {
const cacheKey = `${apiUrl}-${assistantId}-${agentName}`;
if (COMPONENT_PROMISE_CACHE[cacheKey] != null) {
return COMPONENT_PROMISE_CACHE[cacheKey] as Promise<string>;
}
const request: Promise<string> = 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);
+14
View File
@@ -563,6 +563,16 @@ export interface UseStream<
message: Message,
index?: number,
) => MessageMetadata<StateType> | undefined;
/**
* LangGraph SDK client used to send request and receive responses.
*/
client: Client;
/**
* The ID of the assistant to use.
*/
assistantId: string;
}
type ConfigWithConfigurable<ConfigurableType extends Record<string, unknown>> =
@@ -632,6 +642,7 @@ export function useStream<
options.defaultHeaders,
],
);
const [threadId, onThreadId] = useControllableThreadId(options);
const [branch, setBranch] = useState<string>("");
@@ -919,6 +930,9 @@ export function useStream<
return values;
},
client,
assistantId,
error,
isLoading,