import React, { createContext, useContext, ReactNode, useState, useEffect, } from "react"; import { useStream } from "@langchain/langgraph-sdk/react"; import { type Message } from "@langchain/langgraph-sdk"; import { uiMessageReducer, type UIMessage, type RemoveUIMessage, } from "@langchain/langgraph-sdk/react-ui"; import { useQueryParam, StringParam } from "use-query-params"; import { Input } from "@/components/ui/input"; import { Button } from "@/components/ui/button"; import { LangGraphLogoSVG } from "@/components/icons/langgraph"; import { Label } from "@/components/ui/label"; import { ArrowRight } from "lucide-react"; import { PasswordInput } from "@/components/ui/password-input"; import { getApiKey } from "@/lib/api-key"; import { useThreads } from "./Thread"; import { toast } from "sonner"; export type StateType = { messages: Message[]; ui?: UIMessage[] }; const useTypedStream = useStream< StateType, { UpdateType: { messages?: Message[] | Message | string; ui?: (UIMessage | RemoveUIMessage)[] | UIMessage | RemoveUIMessage; }; CustomEventType: UIMessage | RemoveUIMessage; } >; type StreamContextType = ReturnType; const StreamContext = createContext(undefined); async function sleep(ms = 4000) { return new Promise((resolve) => setTimeout(resolve, ms)); } async function checkGraphStatus( apiUrl: string, apiKey: string | null, ): Promise { try { const res = await fetch(`${apiUrl}/info`, { ...(apiKey && { headers: { "X-Api-Key": apiKey, }, }), }); return res.ok; } catch (e) { console.error(e); return false; } } const StreamSession = ({ children, apiKey, apiUrl, assistantId, }: { children: ReactNode; apiKey: string | null; apiUrl: string; assistantId: string; }) => { const [threadId, setThreadId] = useQueryParam("threadId", StringParam); const { getThreads, setThreads } = useThreads(); const streamValue = useTypedStream({ apiUrl, apiKey: apiKey ?? undefined, assistantId, threadId: threadId ?? null, onCustomEvent: (event, options) => { options.mutate((prev) => { const ui = uiMessageReducer(prev.ui ?? [], event); return { ...prev, ui }; }); }, onThreadId: (id) => { setThreadId(id); // Refetch threads list when thread ID changes. // Wait for some seconds before fetching so we're able to get the new thread that was created. sleep().then(() => getThreads().then(setThreads).catch(console.error)); }, }); useEffect(() => { checkGraphStatus(apiUrl, apiKey).then((ok) => { if (!ok) { toast.error("Failed to connect to LangGraph server", { description: () => (

Please ensure your graph is running at {apiUrl} and your API key is correctly set (if connecting to a deployed graph).

), duration: 10000, richColors: true, closeButton: true, }); } }); }, [apiKey, apiUrl]); return ( {children} ); }; export const StreamProvider: React.FC<{ children: ReactNode }> = ({ children, }) => { const [apiUrl, setApiUrl] = useQueryParam("apiUrl", StringParam); const [apiKey, _setApiKey] = useState(() => { return getApiKey(); }); const setApiKey = (key: string) => { window.localStorage.setItem("lg:chat:apiKey", key); _setApiKey(key); }; const [assistantId, setAssistantId] = useQueryParam( "assistantId", StringParam, ); if (!apiUrl || !assistantId) { return (

Agent Chat

Welcome to Agent Chat! Before you get started, you need to enter the URL of the deployment and the assistant / graph ID.

{ e.preventDefault(); const form = e.target as HTMLFormElement; const formData = new FormData(form); const apiUrl = formData.get("apiUrl") as string; const assistantId = formData.get("assistantId") as string; const apiKey = formData.get("apiKey") as string; setApiUrl(apiUrl); setApiKey(apiKey); setAssistantId(assistantId); form.reset(); }} className="flex flex-col gap-6 p-6 bg-muted/50" >

This is the URL of your LangGraph deployment. Can be a local, or production deployment.

This is the ID of the graph (can be the graph name), or assistant to fetch threads from, and invoke when actions are taken.

This is NOT required if using a local LangGraph server. This value is stored in your browser's local storage and is only used to authenticate requests sent to your LangGraph server.

); } return ( {children} ); }; // Create a custom hook to use the context export const useStreamContext = (): StreamContextType => { const context = useContext(StreamContext); if (context === undefined) { throw new Error("useStreamContext must be used within a StreamProvider"); } return context; }; export default StreamContext;