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 { useQueryState } from "nuqs"; 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] = useQueryState("threadId"); 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} ); }; // Default values for the form const DEFAULT_API_URL = "http://localhost:2024"; const DEFAULT_ASSISTANT_ID = "agent"; export const StreamProvider: React.FC<{ children: ReactNode }> = ({ children, }) => { // Get environment variables const envApiUrl: string | undefined = process.env.NEXT_PUBLIC_API_URL; const envAssistantId: string | undefined = process.env.NEXT_PUBLIC_ASSISTANT_ID; // Use URL params with env var fallbacks const [apiUrl, setApiUrl] = useQueryState("apiUrl", { defaultValue: envApiUrl || "", }); const [assistantId, setAssistantId] = useQueryState("assistantId", { defaultValue: envAssistantId || "", }); // For API key, use localStorage with env var fallback const [apiKey, _setApiKey] = useState(() => { const storedKey = getApiKey(); return storedKey || ""; }); const setApiKey = (key: string) => { window.localStorage.setItem("lg:chat:apiKey", key); _setApiKey(key); }; // Determine final values to use, prioritizing URL params then env vars const finalApiUrl = apiUrl || envApiUrl; const finalAssistantId = assistantId || envAssistantId; // Show the form if we: don't have an API URL, or don't have an assistant ID if (!finalApiUrl || !finalAssistantId) { 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="bg-muted/50 flex flex-col gap-6 p-6" >

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;