├── .codespellignore ├── .dockerignore ├── .prettierignore ├── src ├── app │ ├── favicon.ico │ ├── api │ │ └── [..._path] │ │ │ └── route.ts │ ├── layout.tsx │ ├── page.tsx │ └── globals.css ├── lib │ ├── utils.ts │ ├── api-key.tsx │ ├── ensure-tool-responses.ts │ ├── agent-inbox-interrupt.ts │ └── multimodal-utils.ts ├── providers │ ├── client.ts │ ├── Thread.tsx │ └── Stream.tsx ├── components │ ├── ui │ │ ├── skeleton.tsx │ │ ├── label.tsx │ │ ├── separator.tsx │ │ ├── textarea.tsx │ │ ├── input.tsx │ │ ├── sonner.tsx │ │ ├── switch.tsx │ │ ├── avatar.tsx │ │ ├── password-input.tsx │ │ ├── card.tsx │ │ ├── tooltip.tsx │ │ ├── button.tsx │ │ └── sheet.tsx │ ├── thread │ │ ├── utils.ts │ │ ├── markdown-styles.css │ │ ├── ContentBlocksPreview.tsx │ │ ├── tooltip-icon-button.tsx │ │ ├── syntax-highlighter.tsx │ │ ├── agent-inbox │ │ │ ├── components │ │ │ │ ├── tool-call-table.tsx │ │ │ │ ├── thread-id.tsx │ │ │ │ ├── state-view.tsx │ │ │ │ ├── thread-actions-view.tsx │ │ │ │ └── inbox-item-input.tsx │ │ │ ├── types.ts │ │ │ ├── index.tsx │ │ │ ├── utils.ts │ │ │ └── hooks │ │ │ │ └── use-interrupted-actions.tsx │ │ ├── MultimodalPreview.tsx │ │ ├── messages │ │ │ ├── human.tsx │ │ │ ├── generic-interrupt.tsx │ │ │ ├── shared.tsx │ │ │ ├── tool-calls.tsx │ │ │ └── ai.tsx │ │ ├── history │ │ │ └── index.tsx │ │ ├── artifact.tsx │ │ └── markdown-text.tsx │ └── icons │ │ ├── github.tsx │ │ └── langgraph.tsx └── hooks │ ├── useMediaQuery.tsx │ └── use-file-upload.tsx ├── postcss.config.mjs ├── next.config.mjs ├── prettier.config.js ├── .gitignore ├── components.json ├── .env.example ├── tsconfig.json ├── eslint.config.js ├── LICENSE ├── .github └── workflows │ └── ci.yml ├── tailwind.config.js ├── package.json ├── public └── logo.svg └── README.md /.codespellignore: -------------------------------------------------------------------------------- 1 | productionize -------------------------------------------------------------------------------- /.dockerignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | .next 3 | .git 4 | .env -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | # Ignore artifacts: 2 | build 3 | coverage 4 | 5 | # 6 | pnpm-lock.yaml 7 | -------------------------------------------------------------------------------- /src/app/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/langchain-ai/agent-chat-ui/HEAD/src/app/favicon.ico -------------------------------------------------------------------------------- /postcss.config.mjs: -------------------------------------------------------------------------------- 1 | export default { 2 | plugins: { 3 | "@tailwindcss/postcss": {}, 4 | }, 5 | }; 6 | -------------------------------------------------------------------------------- /src/lib/utils.ts: -------------------------------------------------------------------------------- 1 | import { clsx, type ClassValue } from "clsx"; 2 | import { twMerge } from "tailwind-merge"; 3 | 4 | export function cn(...inputs: ClassValue[]) { 5 | return twMerge(clsx(inputs)); 6 | } 7 | -------------------------------------------------------------------------------- /src/providers/client.ts: -------------------------------------------------------------------------------- 1 | import { Client } from "@langchain/langgraph-sdk"; 2 | 3 | export function createClient(apiUrl: string, apiKey: string | undefined) { 4 | return new Client({ 5 | apiKey, 6 | apiUrl, 7 | }); 8 | } 9 | -------------------------------------------------------------------------------- /next.config.mjs: -------------------------------------------------------------------------------- 1 | /** @type {import('next').NextConfig} */ 2 | const nextConfig = { 3 | experimental: { 4 | serverActions: { 5 | bodySizeLimit: "10mb", 6 | }, 7 | }, 8 | }; 9 | 10 | export default nextConfig; 11 | -------------------------------------------------------------------------------- /src/lib/api-key.tsx: -------------------------------------------------------------------------------- 1 | export function getApiKey(): string | null { 2 | try { 3 | if (typeof window === "undefined") return null; 4 | return window.localStorage.getItem("lg:chat:apiKey") ?? null; 5 | } catch { 6 | // no-op 7 | } 8 | 9 | return null; 10 | } 11 | -------------------------------------------------------------------------------- /prettier.config.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @see https://prettier.io/docs/configuration 3 | * @type {import("prettier").Config} 4 | */ 5 | const config = { 6 | endOfLine: "auto", 7 | singleAttributePerLine: true, 8 | plugins: ["prettier-plugin-tailwindcss"], 9 | }; 10 | 11 | export default config; 12 | -------------------------------------------------------------------------------- /src/components/ui/skeleton.tsx: -------------------------------------------------------------------------------- 1 | import { cn } from "@/lib/utils"; 2 | 3 | function Skeleton({ className, ...props }: React.ComponentProps<"div">) { 4 | return ( 5 |
10 | ); 11 | } 12 | 13 | export { Skeleton }; 14 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | pnpm-debug.log* 8 | lerna-debug.log* 9 | 10 | node_modules 11 | dist 12 | dist-ssr 13 | *.local 14 | 15 | # Editor directories and files 16 | .vscode/* 17 | !.vscode/extensions.json 18 | .idea 19 | .DS_Store 20 | *.suo 21 | *.ntvs* 22 | *.njsproj 23 | *.sln 24 | *.sw? 25 | 26 | # LangGraph API 27 | .langgraph_api 28 | .env 29 | .next/ 30 | next-env.d.ts 31 | -------------------------------------------------------------------------------- /components.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://ui.shadcn.com/schema.json", 3 | "style": "new-york", 4 | "rsc": false, 5 | "tsx": true, 6 | "tailwind": { 7 | "config": "tailwind.config.js", 8 | "css": "src/index.css", 9 | "baseColor": "neutral", 10 | "cssVariables": true, 11 | "prefix": "" 12 | }, 13 | "aliases": { 14 | "components": "@/components", 15 | "utils": "@/lib/utils", 16 | "ui": "@/components/ui", 17 | "lib": "@/lib", 18 | "hooks": "@/hooks" 19 | }, 20 | "iconLibrary": "lucide" 21 | } 22 | -------------------------------------------------------------------------------- /src/hooks/useMediaQuery.tsx: -------------------------------------------------------------------------------- 1 | import { useEffect, useState } from "react"; 2 | 3 | export function useMediaQuery(query: string) { 4 | const [matches, setMatches] = useState(false); 5 | 6 | useEffect(() => { 7 | const media = window.matchMedia(query); 8 | setMatches(media.matches); 9 | 10 | const listener = (e: MediaQueryListEvent) => setMatches(e.matches); 11 | media.addEventListener("change", listener); 12 | return () => media.removeEventListener("change", listener); 13 | }, [query]); 14 | 15 | return matches; 16 | } 17 | -------------------------------------------------------------------------------- /.env.example: -------------------------------------------------------------------------------- 1 | # LangGraph Configuration 2 | NEXT_PUBLIC_API_URL=http://localhost:2024 3 | NEXT_PUBLIC_ASSISTANT_ID=agent 4 | # Do NOT prefix this with "NEXT_PUBLIC_" as we do not want this exposed in the client. 5 | LANGSMITH_API_KEY= 6 | 7 | # Production LangGraph Configuration (quickstart) - Uncomment to use 8 | # NEXT_PUBLIC_ASSISTANT_ID="agent" 9 | # This should be the deployment URL of your LangGraph server 10 | # LANGGRAPH_API_URL="https://my-agent.default.us.langgraph.app" 11 | # This should be the URL of your website + "/api". This is how you connect to the API proxy 12 | # NEXT_PUBLIC_API_URL="https://my-website.com/api" 13 | # LANGSMITH_API_KEY="lsv2_..." 14 | -------------------------------------------------------------------------------- /src/components/thread/utils.ts: -------------------------------------------------------------------------------- 1 | import type { Message } from "@langchain/langgraph-sdk"; 2 | 3 | /** 4 | * Extracts a string summary from a message's content, supporting multimodal (text, image, file, etc.). 5 | * - If text is present, returns the joined text. 6 | * - If not, returns a label for the first non-text modality (e.g., 'Image', 'Other'). 7 | * - If unknown, returns 'Multimodal message'. 8 | */ 9 | export function getContentString(content: Message["content"]): string { 10 | if (typeof content === "string") return content; 11 | const texts = content 12 | .filter((c): c is { type: "text"; text: string } => c.type === "text") 13 | .map((c) => c.text); 14 | return texts.join(" "); 15 | } 16 | -------------------------------------------------------------------------------- /src/components/ui/label.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react"; 2 | import * as LabelPrimitive from "@radix-ui/react-label"; 3 | 4 | import { cn } from "@/lib/utils"; 5 | 6 | function Label({ 7 | className, 8 | ...props 9 | }: React.ComponentProps) { 10 | return ( 11 | 19 | ); 20 | } 21 | 22 | export { Label }; 23 | -------------------------------------------------------------------------------- /src/app/api/[..._path]/route.ts: -------------------------------------------------------------------------------- 1 | import { initApiPassthrough } from "langgraph-nextjs-api-passthrough"; 2 | 3 | // This file acts as a proxy for requests to your LangGraph server. 4 | // Read the [Going to Production](https://github.com/langchain-ai/agent-chat-ui?tab=readme-ov-file#going-to-production) section for more information. 5 | 6 | export const { GET, POST, PUT, PATCH, DELETE, OPTIONS, runtime } = 7 | initApiPassthrough({ 8 | apiUrl: process.env.LANGGRAPH_API_URL ?? "remove-me", // default, if not defined it will attempt to read process.env.LANGGRAPH_API_URL 9 | apiKey: process.env.LANGSMITH_API_KEY ?? "remove-me", // default, if not defined it will attempt to read process.env.LANGSMITH_API_KEY 10 | runtime: "edge", // default 11 | }); 12 | -------------------------------------------------------------------------------- /src/app/layout.tsx: -------------------------------------------------------------------------------- 1 | import type { Metadata } from "next"; 2 | import "./globals.css"; 3 | import { Inter } from "next/font/google"; 4 | import React from "react"; 5 | import { NuqsAdapter } from "nuqs/adapters/next/app"; 6 | 7 | const inter = Inter({ 8 | subsets: ["latin"], 9 | preload: true, 10 | display: "swap", 11 | }); 12 | 13 | export const metadata: Metadata = { 14 | title: "Agent Chat", 15 | description: "Agent Chat UX by LangChain", 16 | }; 17 | 18 | export default function RootLayout({ 19 | children, 20 | }: Readonly<{ 21 | children: React.ReactNode; 22 | }>) { 23 | return ( 24 | 25 | 26 | {children} 27 | 28 | 29 | ); 30 | } 31 | -------------------------------------------------------------------------------- /src/app/page.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | 3 | import { Thread } from "@/components/thread"; 4 | import { StreamProvider } from "@/providers/Stream"; 5 | import { ThreadProvider } from "@/providers/Thread"; 6 | import { ArtifactProvider } from "@/components/thread/artifact"; 7 | import { Toaster } from "@/components/ui/sonner"; 8 | import React from "react"; 9 | 10 | export default function DemoPage(): React.ReactNode { 11 | return ( 12 | Loading (layout)...
}> 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | ); 23 | } 24 | -------------------------------------------------------------------------------- /src/components/ui/separator.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react"; 2 | import * as SeparatorPrimitive from "@radix-ui/react-separator"; 3 | 4 | import { cn } from "@/lib/utils"; 5 | 6 | function Separator({ 7 | className, 8 | orientation = "horizontal", 9 | decorative = true, 10 | ...props 11 | }: React.ComponentProps) { 12 | return ( 13 | 23 | ); 24 | } 25 | 26 | export { Separator }; 27 | -------------------------------------------------------------------------------- /src/components/ui/textarea.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react"; 2 | 3 | import { cn } from "@/lib/utils"; 4 | 5 | function Textarea({ className, ...props }: React.ComponentProps<"textarea">) { 6 | return ( 7 |