Merge pull request #48 from langchain-ai/brace/better-error-handling

feat: Error if graph can not connect
This commit is contained in:
Brace Sproul
2025-03-11 10:22:20 -07:00
committed by GitHub
5 changed files with 56 additions and 9 deletions

View File

@@ -15,7 +15,9 @@ First, clone the repository, or run the [`npx` command](https://www.npmjs.com/pa
```bash ```bash
npx create-agent-chat-app npx create-agent-chat-app
``` ```
or or
```bash ```bash
git clone https://github.com/langchain-ai/agent-chat-ui.git git clone https://github.com/langchain-ai/agent-chat-ui.git

View File

@@ -337,7 +337,7 @@ export function InboxItemInput({
(Array.isArray(change) && !Array.isArray(key)) || (Array.isArray(change) && !Array.isArray(key)) ||
(!Array.isArray(change) && Array.isArray(key)) (!Array.isArray(change) && Array.isArray(key))
) { ) {
toast("Error", { toast.error("Error", {
description: "Something went wrong", description: "Something went wrong",
richColors: true, richColors: true,
closeButton: true, closeButton: true,

View File

@@ -85,7 +85,7 @@ export function ThreadActionsView({
const handleOpenInStudio = () => { const handleOpenInStudio = () => {
if (!apiUrl) { if (!apiUrl) {
toast("Error", { toast.error("Error", {
description: "Please set the LangGraph deployment URL in settings.", description: "Please set the LangGraph deployment URL in settings.",
duration: 5000, duration: 5000,
richColors: true, richColors: true,

View File

@@ -110,7 +110,7 @@ export default function useInterruptedActions({
) => { ) => {
e.preventDefault(); e.preventDefault();
if (!humanResponse) { if (!humanResponse) {
toast("Error", { toast.error("Error", {
description: "Please enter a response.", description: "Please enter a response.",
duration: 5000, duration: 5000,
richColors: true, richColors: true,
@@ -159,7 +159,7 @@ export default function useInterruptedActions({
(r) => r.type === selectedSubmitType, (r) => r.type === selectedSubmitType,
); );
if (!input) { if (!input) {
toast("Error", { toast.error("Error", {
description: "No response found.", description: "No response found.",
richColors: true, richColors: true,
closeButton: true, closeButton: true,
@@ -189,7 +189,7 @@ export default function useInterruptedActions({
console.error("Error sending human response", e); console.error("Error sending human response", e);
if ("message" in e && e.message.includes("Invalid assistant ID")) { if ("message" in e && e.message.includes("Invalid assistant ID")) {
toast("Error: Invalid assistant ID", { toast.error("Error: Invalid assistant ID", {
description: description:
"The provided assistant ID was not found in this graph. Please update the assistant ID in the settings and try again.", "The provided assistant ID was not found in this graph. Please update the assistant ID in the settings and try again.",
richColors: true, richColors: true,
@@ -197,7 +197,7 @@ export default function useInterruptedActions({
duration: 5000, duration: 5000,
}); });
} else { } else {
toast("Error", { toast.error("Error", {
description: "Failed to submit response.", description: "Failed to submit response.",
richColors: true, richColors: true,
closeButton: true, closeButton: true,
@@ -234,7 +234,7 @@ export default function useInterruptedActions({
const ignoreResponse = humanResponse.find((r) => r.type === "ignore"); const ignoreResponse = humanResponse.find((r) => r.type === "ignore");
if (!ignoreResponse) { if (!ignoreResponse) {
toast("Error", { toast.error("Error", {
description: "The selected thread does not support ignoring.", description: "The selected thread does not support ignoring.",
duration: 5000, duration: 5000,
}); });
@@ -284,7 +284,7 @@ export default function useInterruptedActions({
}); });
} catch (e) { } catch (e) {
console.error("Error marking thread as resolved", e); console.error("Error marking thread as resolved", e);
toast("Error", { toast.error("Error", {
description: "Failed to mark thread as resolved.", description: "Failed to mark thread as resolved.",
richColors: true, richColors: true,
closeButton: true, closeButton: true,

View File

@@ -1,4 +1,10 @@
import React, { createContext, useContext, ReactNode, useState } from "react"; import React, {
createContext,
useContext,
ReactNode,
useState,
useEffect,
} from "react";
import { useStream } from "@langchain/langgraph-sdk/react"; import { useStream } from "@langchain/langgraph-sdk/react";
import { type Message } from "@langchain/langgraph-sdk"; import { type Message } from "@langchain/langgraph-sdk";
import { import {
@@ -15,6 +21,7 @@ import { ArrowRight } from "lucide-react";
import { PasswordInput } from "@/components/ui/password-input"; import { PasswordInput } from "@/components/ui/password-input";
import { getApiKey } from "@/lib/api-key"; import { getApiKey } from "@/lib/api-key";
import { useThreads } from "./Thread"; import { useThreads } from "./Thread";
import { toast } from "sonner";
export type StateType = { messages: Message[]; ui?: UIMessage[] }; export type StateType = { messages: Message[]; ui?: UIMessage[] };
@@ -36,6 +43,26 @@ async function sleep(ms = 4000) {
return new Promise((resolve) => setTimeout(resolve, ms)); return new Promise((resolve) => setTimeout(resolve, ms));
} }
async function checkGraphStatus(
apiUrl: string,
apiKey: string | null,
): Promise<boolean> {
try {
const res = await fetch(`${apiUrl}/ok`, {
...(apiKey && {
headers: {
"X-Api-Key": apiKey,
},
}),
});
return res.ok;
} catch (e) {
console.error(e);
return false;
}
}
const StreamSession = ({ const StreamSession = ({
children, children,
apiKey, apiKey,
@@ -68,6 +95,24 @@ const StreamSession = ({
}, },
}); });
useEffect(() => {
checkGraphStatus(apiUrl, apiKey).then((ok) => {
if (!ok) {
toast.error("Failed to connect to LangGraph server", {
description: () => (
<p>
Please ensure your graph is running at <code>{apiUrl}</code> and
your API key is correctly set (if connecting to a deployed graph).
</p>
),
duration: 10000,
richColors: true,
closeButton: true,
});
}
});
}, [apiKey, apiUrl]);
return ( return (
<StreamContext.Provider value={streamValue}> <StreamContext.Provider value={streamValue}>
{children} {children}