Merge branch 'main' of https://github.com/langchain-ai/agent-ui-demo into brace/open-code

This commit is contained in:
bracesproul
2025-03-07 11:42:50 -08:00
13 changed files with 43 additions and 34 deletions

4
.dockerignore Normal file
View File

@@ -0,0 +1,4 @@
node_modules
.next
.git
.env

View File

@@ -1,4 +1,7 @@
# agent-ui demo # Chat LangGraph
> [!WARNING]
> This repo is still a work in progress and is not intended for use. Estimated launch date 03/11. Thank you for your patience.
## Setup ## Setup

View File

@@ -160,7 +160,7 @@ export default function PortfolioView() {
const chartData = selectedHolding ? generateChartData(selectedHolding) : []; const chartData = selectedHolding ? generateChartData(selectedHolding) : [];
return ( return (
<div className="w-full max-w-4xl bg-white rounded-xl shadow-lg overflow-hidden border border-gray-200"> <div className="w-full max-w-3xl bg-white rounded-xl shadow-lg overflow-hidden border border-gray-200">
<div className="bg-gradient-to-r from-indigo-700 to-indigo-500 px-6 py-4"> <div className="bg-gradient-to-r from-indigo-700 to-indigo-500 px-6 py-4">
<div className="flex justify-between items-center"> <div className="flex justify-between items-center">
<h2 className="text-white font-bold text-xl tracking-tight flex items-center"> <h2 className="text-white font-bold text-xl tracking-tight flex items-center">

View File

@@ -149,7 +149,7 @@ export default function StockPrice(props: {
}; };
return ( return (
<div className="w-full max-w-4xl rounded-xl shadow-md overflow-hidden border border-gray-200 flex flex-col gap-4 p-3"> <div className="w-full max-w-3xl rounded-xl shadow-md overflow-hidden border border-gray-200 flex flex-col gap-4 p-3">
<div className="flex items-center justify-start gap-4 mb-2 text-lg font-medium text-gray-700"> <div className="flex items-center justify-start gap-4 mb-2 text-lg font-medium text-gray-700">
<p>{ticker}</p> <p>{ticker}</p>
<p>${currentPrice}</p> <p>${currentPrice}</p>

View File

@@ -320,7 +320,7 @@ export default function AccommodationsList({
align: "start", align: "start",
loop: true, loop: true,
}} }}
className="w-full sm:max-w-sm md:max-w-2xl lg:max-w-3xl" className="w-full sm:max-w-sm md:max-w-3xl lg:max-w-3xl"
> >
<CarouselContent> <CarouselContent>
{accommodations.map((accommodation) => ( {accommodations.map((accommodation) => (

View File

@@ -4,7 +4,7 @@
<meta charset="UTF-8" /> <meta charset="UTF-8" />
<link rel="icon" type="image/svg+xml" href="/vite.svg" /> <link rel="icon" type="image/svg+xml" href="/vite.svg" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>LangGraph Chat</title> <title>Chat LangGraph</title>
<link href="/src/styles.css" rel="stylesheet" /> <link href="/src/styles.css" rel="stylesheet" />
</head> </head>
<body> <body>

View File

@@ -1,12 +1,11 @@
{ {
"node_version": "20", "node_version": "20",
"graphs": { "graphs": {
"agent": "./agent/agent.tsx:graph" "agent": "./agent/agent.ts:graph"
}, },
"ui": { "ui": {
"agent": "./agent/uis/index.tsx" "agent": "./agent/uis/index.tsx"
}, },
"_INTERNAL_docker_tag": "20",
"env": ".env", "env": ".env",
"dependencies": ["."] "dependencies": ["."]
} }

View File

@@ -97,7 +97,7 @@ export default function ThreadHistory() {
variant="ghost" variant="ghost"
onClick={() => setChatHistoryOpen((p) => !p)} onClick={() => setChatHistoryOpen((p) => !p)}
> >
<PanelRightOpen /> <PanelRightOpen className="size-5" />
</Button> </Button>
<h1 className="text-xl font-semibold tracking-tight"> <h1 className="text-xl font-semibold tracking-tight">
Thread History Thread History

View File

@@ -4,7 +4,6 @@ import { motion } from "framer-motion";
import { cn } from "@/lib/utils"; import { cn } from "@/lib/utils";
import { useStreamContext } from "@/providers/Stream"; import { useStreamContext } from "@/providers/Stream";
import { useState, FormEvent } from "react"; import { useState, FormEvent } from "react";
import { Input } from "../ui/input";
import { Button } from "../ui/button"; import { Button } from "../ui/button";
import { Checkpoint, Message } from "@langchain/langgraph-sdk"; import { Checkpoint, Message } from "@langchain/langgraph-sdk";
import { AssistantMessage, AssistantMessageLoading } from "./messages/ai"; import { AssistantMessage, AssistantMessageLoading } from "./messages/ai";
@@ -218,7 +217,7 @@ export function Thread() {
variant="ghost" variant="ghost"
onClick={() => setChatHistoryOpen((p) => !p)} onClick={() => setChatHistoryOpen((p) => !p)}
> >
<PanelRightOpen /> <PanelRightOpen className="size-5" />
</Button> </Button>
)} )}
</div> </div>
@@ -233,7 +232,7 @@ export function Thread() {
variant="ghost" variant="ghost"
onClick={() => setChatHistoryOpen((p) => !p)} onClick={() => setChatHistoryOpen((p) => !p)}
> >
<PanelRightOpen /> <PanelRightOpen className="size-5" />
</Button> </Button>
)} )}
</div> </div>
@@ -251,7 +250,7 @@ export function Thread() {
> >
<LangGraphLogoSVG width={32} height={32} /> <LangGraphLogoSVG width={32} height={32} />
<span className="text-xl font-semibold tracking-tight"> <span className="text-xl font-semibold tracking-tight">
LangGraph Chat Chat LangGraph
</span> </span>
</motion.button> </motion.button>
</div> </div>
@@ -277,7 +276,7 @@ export function Thread() {
!chatStarted && "flex flex-col items-stretch mt-[25vh]", !chatStarted && "flex flex-col items-stretch mt-[25vh]",
chatStarted && "grid grid-rows-[1fr_auto]", chatStarted && "grid grid-rows-[1fr_auto]",
)} )}
contentClassName="pt-8 pb-16 px-4 max-w-4xl mx-auto flex flex-col gap-4 w-full" contentClassName="pt-8 pb-16 max-w-3xl mx-auto flex flex-col gap-4 w-full"
content={ content={
<> <>
{messages {messages
@@ -304,29 +303,36 @@ export function Thread() {
</> </>
} }
footer={ footer={
<div className="sticky flex flex-col items-center gap-8 bottom-8 px-4"> <div className="sticky flex flex-col items-center gap-8 bottom-0 px-4 bg-white">
{!chatStarted && ( {!chatStarted && (
<div className="flex gap-3 items-center"> <div className="flex gap-3 items-center">
<LangGraphLogoSVG className="flex-shrink-0 h-8" /> <LangGraphLogoSVG className="flex-shrink-0 h-8" />
<h1 className="text-2xl font-semibold tracking-tight"> <h1 className="text-2xl font-semibold tracking-tight">
LangGraph Chat Chat LangGraph
</h1> </h1>
</div> </div>
)} )}
<ScrollToBottom className="absolute bottom-full left-1/2 -translate-x-1/2 mb-4 animate-in fade-in-0 zoom-in-95" /> <ScrollToBottom className="absolute bottom-full left-1/2 -translate-x-1/2 mb-4 animate-in fade-in-0 zoom-in-95" />
<div className="bg-background rounded-2xl border shadow-md mx-auto w-full max-w-4xl relative z-10"> <div className="bg-muted rounded-2xl border shadow-xs mx-auto mb-8 w-full max-w-3xl relative z-10">
<form <form
onSubmit={handleSubmit} onSubmit={handleSubmit}
className="grid grid-rows-[1fr_auto] gap-2 max-w-4xl mx-auto" className="grid grid-rows-[1fr_auto] gap-2 max-w-3xl mx-auto"
> >
<Input <textarea
type="text"
value={input} value={input}
onChange={(e) => setInput(e.target.value)} onChange={(e) => setInput(e.target.value)}
onKeyDown={(e) => {
if (e.key === "Enter" && !e.shiftKey && !e.metaKey) {
e.preventDefault();
const el = e.target as HTMLElement | undefined;
const form = el?.closest("form");
form?.requestSubmit();
}
}}
placeholder="Type your message..." placeholder="Type your message..."
className="px-4 py-6 border-none bg-transparent shadow-none ring-0 outline-none focus:outline-none focus:ring-0" className="p-3.5 pb-0 border-none bg-transparent field-sizing-content shadow-none ring-0 outline-none focus:outline-none focus:ring-0 resize-none"
/> />
<div className="flex items-center justify-end p-2 pt-0"> <div className="flex items-center justify-end p-2 pt-0">
@@ -338,6 +344,7 @@ export function Thread() {
) : ( ) : (
<Button <Button
type="submit" type="submit"
className="transition-all shadow-md"
disabled={isLoading || !input.trim()} disabled={isLoading || !input.trim()}
> >
Send Send

View File

@@ -2,11 +2,11 @@ import { useStreamContext } from "@/providers/Stream";
import { Checkpoint, Message } from "@langchain/langgraph-sdk"; import { Checkpoint, Message } from "@langchain/langgraph-sdk";
import { getContentString } from "../utils"; import { getContentString } from "../utils";
import { BranchSwitcher, CommandBar } from "./shared"; import { BranchSwitcher, CommandBar } from "./shared";
import { Avatar, AvatarFallback } from "@/components/ui/avatar";
import { MarkdownText } from "../markdown-text"; import { MarkdownText } from "../markdown-text";
import { LoadExternalComponent } from "@langchain/langgraph-sdk/react-ui"; import { LoadExternalComponent } from "@langchain/langgraph-sdk/react-ui";
import { cn } from "@/lib/utils"; import { cn } from "@/lib/utils";
import { ToolCalls, ToolResult } from "./tool-calls"; import { ToolCalls, ToolResult } from "./tool-calls";
import { StringParam, useQueryParam } from "use-query-params";
function CustomComponent({ function CustomComponent({
message, message,
@@ -15,6 +15,7 @@ function CustomComponent({
message: Message; message: Message;
thread: ReturnType<typeof useStreamContext>; thread: ReturnType<typeof useStreamContext>;
}) { }) {
const [apiUrl] = useQueryParam("apiUrl", StringParam);
const meta = thread.getMessagesMetadata(message); const meta = thread.getMessagesMetadata(message);
const seenState = meta?.firstSeenState; const seenState = meta?.firstSeenState;
const customComponent = seenState?.values.ui const customComponent = seenState?.values.ui
@@ -33,6 +34,7 @@ function CustomComponent({
<div key={message.id}> <div key={message.id}>
{customComponent && ( {customComponent && (
<LoadExternalComponent <LoadExternalComponent
apiUrl={apiUrl ?? undefined}
assistantId="agent" assistantId="agent"
stream={thread} stream={thread}
message={customComponent} message={customComponent}
@@ -66,20 +68,17 @@ export function AssistantMessage({
return ( return (
<div className="flex items-start mr-auto gap-2 group"> <div className="flex items-start mr-auto gap-2 group">
<Avatar>
<AvatarFallback>A</AvatarFallback>
</Avatar>
{isToolResult ? ( {isToolResult ? (
<ToolResult message={message} /> <ToolResult message={message} />
) : ( ) : (
<div className="flex flex-col gap-2"> <div className="flex flex-col gap-2">
{hasToolCalls && <ToolCalls toolCalls={message.tool_calls} />}
<CustomComponent message={message} thread={thread} />
{contentString.length > 0 && ( {contentString.length > 0 && (
<div className="rounded-2xl bg-muted px-4 py-2"> <div className="py-1">
<MarkdownText>{contentString}</MarkdownText> <MarkdownText>{contentString}</MarkdownText>
</div> </div>
)} )}
{hasToolCalls && <ToolCalls toolCalls={message.tool_calls} />}
<CustomComponent message={message} thread={thread} />
<div <div
className={cn( className={cn(
"flex gap-2 items-center mr-auto transition-opacity", "flex gap-2 items-center mr-auto transition-opacity",
@@ -108,9 +107,6 @@ export function AssistantMessage({
export function AssistantMessageLoading() { export function AssistantMessageLoading() {
return ( return (
<div className="flex items-start mr-auto gap-2"> <div className="flex items-start mr-auto gap-2">
<Avatar>
<AvatarFallback>A</AvatarFallback>
</Avatar>
<div className="flex items-center gap-1 rounded-2xl bg-muted px-4 py-2 h-8"> <div className="flex items-center gap-1 rounded-2xl bg-muted px-4 py-2 h-8">
<div className="w-1.5 h-1.5 rounded-full bg-foreground/50 animate-[pulse_1.5s_ease-in-out_infinite]"></div> <div className="w-1.5 h-1.5 rounded-full bg-foreground/50 animate-[pulse_1.5s_ease-in-out_infinite]"></div>
<div className="w-1.5 h-1.5 rounded-full bg-foreground/50 animate-[pulse_1.5s_ease-in-out_0.5s_infinite]"></div> <div className="w-1.5 h-1.5 rounded-full bg-foreground/50 animate-[pulse_1.5s_ease-in-out_0.5s_infinite]"></div>

View File

@@ -84,7 +84,7 @@ export function HumanMessage({
onSubmit={handleSubmitEdit} onSubmit={handleSubmitEdit}
/> />
) : ( ) : (
<p className="text-right py-1">{contentString}</p> <p className="text-right px-4 py-2 rounded-3xl bg-muted">{contentString}</p>
)} )}
<div <div

View File

@@ -89,16 +89,16 @@ export const StreamProvider: React.FC<{ children: ReactNode }> = ({
if (!apiUrl || !assistantId) { if (!apiUrl || !assistantId) {
return ( return (
<div className="flex items-center justify-center min-h-screen w-full p-4"> <div className="flex items-center justify-center min-h-screen w-full p-4">
<div className="animate-in fade-in-0 zoom-in-95 flex flex-col border bg-background shadow-lg rounded-lg max-w-2xl"> <div className="animate-in fade-in-0 zoom-in-95 flex flex-col border bg-background shadow-lg rounded-lg max-w-3xl">
<div className="flex flex-col gap-2 mt-14 p-6 border-b"> <div className="flex flex-col gap-2 mt-14 p-6 border-b">
<div className="flex items-start flex-col gap-2"> <div className="flex items-start flex-col gap-2">
<LangGraphLogoSVG className="h-7" /> <LangGraphLogoSVG className="h-7" />
<h1 className="text-xl font-semibold tracking-tight"> <h1 className="text-xl font-semibold tracking-tight">
LangGraph Chat Chat LangGraph
</h1> </h1>
</div> </div>
<p className="text-muted-foreground"> <p className="text-muted-foreground">
Welcome to LangGraph Chat! Before you get started, you need to Welcome to Chat LangGraph! Before you get started, you need to
enter the URL of the deployment and the assistant / graph ID. enter the URL of the deployment and the assistant / graph ID.
</p> </p>
</div> </div>