Skip to content

Commit e9cd97f

Browse files
authored
Inbox ID update for deployed urls fixes #59 (#67)
2 parents 1ddb55a + ff676b8 commit e9cd97f

19 files changed

+1173
-218
lines changed

src/components/agent-inbox/components/add-agent-inbox-dialog.tsx

Lines changed: 121 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -17,9 +17,13 @@ import { useQueryParams } from "../hooks/use-query-params";
1717
import {
1818
AGENT_INBOX_GITHUB_README_URL,
1919
NO_INBOXES_FOUND_PARAM,
20+
LANGCHAIN_API_KEY_LOCAL_STORAGE_KEY,
2021
} from "../constants";
2122
import { PasswordInput } from "@/components/ui/password-input";
22-
import { isDeployedUrl } from "../utils";
23+
import { isDeployedUrl, fetchDeploymentInfo } from "../utils";
24+
import { useLocalStorage } from "../hooks/use-local-storage";
25+
import { LoaderCircle } from "lucide-react";
26+
import { logger } from "../utils/logger";
2327

2428
export function AddAgentInboxDialog({
2529
hideTrigger,
@@ -38,10 +42,13 @@ export function AddAgentInboxDialog({
3842
const { searchParams, updateQueryParams } = useQueryParams();
3943
const { toast } = useToast();
4044
const { addAgentInbox } = useThreadsContext();
45+
const { getItem } = useLocalStorage();
4146
const [open, setOpen] = React.useState(false);
4247
const [graphId, setGraphId] = React.useState("");
4348
const [deploymentUrl, setDeploymentUrl] = React.useState("");
4449
const [name, setName] = React.useState("");
50+
const [isSubmitting, setIsSubmitting] = React.useState(false);
51+
const [errorMessage, setErrorMessage] = React.useState<string | null>(null);
4552

4653
const noInboxesFoundParam = searchParams.get(NO_INBOXES_FOUND_PARAM);
4754

@@ -54,35 +61,108 @@ export function AddAgentInboxDialog({
5461
setOpen(true);
5562
}
5663
} catch (e) {
57-
console.error("Error getting/setting no inboxes found param", e);
64+
logger.error("Error getting/setting no inboxes found param", e);
5865
}
5966
}, [noInboxesFoundParam]);
6067

61-
const handleSubmit = (event: React.FormEvent<HTMLFormElement>) => {
68+
const handleSubmit = async (event: React.FormEvent<HTMLFormElement>) => {
6269
event.preventDefault();
63-
addAgentInbox({
64-
id: uuidv4(),
65-
graphId,
66-
deploymentUrl,
67-
name,
68-
selected: true,
69-
});
70-
toast({
71-
title: "Success",
72-
description: "Agent inbox added successfully",
73-
duration: 3000,
74-
});
75-
updateQueryParams(NO_INBOXES_FOUND_PARAM);
76-
77-
setGraphId("");
78-
setDeploymentUrl("");
79-
setName("");
80-
setOpen(false);
70+
setIsSubmitting(true);
71+
setErrorMessage(null);
72+
73+
try {
74+
const isDeployed = isDeployedUrl(deploymentUrl);
75+
let inboxId = uuidv4();
76+
let tenantId: string | undefined = undefined;
77+
78+
// For deployed graphs, get the deployment info to generate the ID
79+
if (isDeployed) {
80+
logger.log(
81+
"Deployed graph detected, getting info from:",
82+
deploymentUrl
83+
);
84+
85+
// Get the LangChain API key from local storage or props
86+
const storedApiKey =
87+
getItem(LANGCHAIN_API_KEY_LOCAL_STORAGE_KEY) || undefined;
88+
const apiKey = langchainApiKey || storedApiKey;
89+
90+
// Note: The API key is still required for deployed graphs for other operations,
91+
// but not for fetching deployment info
92+
if (!apiKey && isDeployed) {
93+
setErrorMessage(
94+
"API key is required for deployed LangGraph instances"
95+
);
96+
setIsSubmitting(false);
97+
return;
98+
}
99+
100+
// Fetch deployment info
101+
try {
102+
const deploymentInfo = await fetchDeploymentInfo(deploymentUrl);
103+
logger.log("Got deployment info:", deploymentInfo);
104+
105+
if (
106+
deploymentInfo?.host?.project_id &&
107+
deploymentInfo?.host?.tenant_id
108+
) {
109+
// Generate ID in format: project_id:graphId
110+
inboxId = `${deploymentInfo.host.project_id}:${graphId}`;
111+
tenantId = deploymentInfo.host.tenant_id;
112+
logger.log(`Created new inbox ID: ${inboxId}`);
113+
} else {
114+
logger.log("No project_id in deployment info, using UUID");
115+
}
116+
} catch (error) {
117+
logger.error("Error fetching deployment info:", error);
118+
setErrorMessage(
119+
"Failed to get deployment info. Check your deployment URL."
120+
);
121+
setIsSubmitting(false);
122+
return;
123+
}
124+
} else {
125+
logger.log("Local graph, using UUID for inbox ID");
126+
}
127+
128+
// Add the inbox with the generated ID
129+
logger.log("Adding inbox with ID:", inboxId);
130+
addAgentInbox({
131+
id: inboxId,
132+
graphId,
133+
deploymentUrl,
134+
name,
135+
selected: true,
136+
tenantId,
137+
});
138+
139+
toast({
140+
title: "Success",
141+
description: "Agent inbox added successfully",
142+
duration: 3000,
143+
});
144+
updateQueryParams(NO_INBOXES_FOUND_PARAM);
145+
146+
setGraphId("");
147+
setDeploymentUrl("");
148+
setName("");
149+
setOpen(false);
150+
151+
// Force page reload to ensure the new inbox appears
152+
window.location.reload();
153+
} catch (error) {
154+
logger.error("Error adding agent inbox:", error);
155+
setErrorMessage(
156+
"Failed to add the agent inbox. Please try again or check the console for details."
157+
);
158+
} finally {
159+
setIsSubmitting(false);
160+
}
81161
};
82162

83163
const isDeployedGraph = isDeployedUrl(deploymentUrl);
84164
const showLangChainApiKeyField =
85-
noInboxesFoundParam === "true" &&
165+
(noInboxesFoundParam === "true" || isDeployedGraph) &&
86166
langchainApiKey !== undefined &&
87167
handleChangeLangChainApiKey &&
88168
isDeployedGraph;
@@ -203,9 +283,26 @@ export function AddAgentInboxDialog({
203283
/>
204284
</div>
205285
)}
286+
287+
{errorMessage && (
288+
<div className="text-red-500 text-sm w-full">{errorMessage}</div>
289+
)}
290+
206291
<div className="grid grid-cols-2 gap-4">
207-
<Button variant="brand" type="submit">
208-
Submit
292+
<Button
293+
variant="outline"
294+
className="w-full"
295+
type="submit"
296+
disabled={isSubmitting}
297+
>
298+
{isSubmitting ? (
299+
<>
300+
<LoaderCircle className="mr-2 h-4 w-4 animate-spin" />
301+
Adding...
302+
</>
303+
) : (
304+
"Add Inbox"
305+
)}
209306
</Button>
210307
<Button
211308
variant="outline"
Lines changed: 109 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,109 @@
1+
"use client";
2+
3+
import { Button } from "@/components/ui/button";
4+
import { Alert, AlertDescription, AlertTitle } from "@/components/ui/alert";
5+
import { InfoCircledIcon, ReloadIcon } from "@radix-ui/react-icons";
6+
import { useToast } from "@/hooks/use-toast";
7+
import { useState, useEffect } from "react";
8+
import {
9+
forceInboxBackfill,
10+
isBackfillCompleted,
11+
markBackfillCompleted,
12+
} from "../utils/backfill";
13+
import { logger } from "../utils/logger";
14+
15+
export function BackfillBanner() {
16+
const [mounted, setMounted] = useState(false);
17+
const [showBanner, setShowBanner] = useState(false);
18+
const [isRunning, setIsRunning] = useState(false);
19+
const { toast } = useToast();
20+
21+
// Only run this check after mounting on the client
22+
useEffect(() => {
23+
setMounted(true);
24+
setShowBanner(!isBackfillCompleted());
25+
}, []);
26+
27+
// Don't render anything during SSR or until the component has mounted
28+
if (!mounted || !showBanner) {
29+
return null;
30+
}
31+
32+
const handleRunBackfill = async () => {
33+
setIsRunning(true);
34+
try {
35+
const result = await forceInboxBackfill();
36+
37+
if (result.success) {
38+
toast({
39+
title: "Success",
40+
description:
41+
"Your inbox IDs have been updated. Please refresh the page to see your inboxes.",
42+
duration: 5000,
43+
});
44+
setShowBanner(false);
45+
} else {
46+
toast({
47+
title: "Error",
48+
description:
49+
"Failed to update inbox IDs. Please try again or contact support.",
50+
variant: "destructive",
51+
duration: 5000,
52+
});
53+
}
54+
} catch (error) {
55+
logger.error("Error running backfill:", error);
56+
toast({
57+
title: "Error",
58+
description: "An unexpected error occurred. Please try again later.",
59+
variant: "destructive",
60+
duration: 5000,
61+
});
62+
} finally {
63+
setIsRunning(false);
64+
}
65+
};
66+
67+
const handleDismiss = () => {
68+
markBackfillCompleted();
69+
setShowBanner(false);
70+
toast({
71+
title: "Dismissed",
72+
description:
73+
"The banner has been dismissed. You can still update your inboxes from settings.",
74+
duration: 3000,
75+
});
76+
};
77+
78+
return (
79+
<Alert className="mb-4">
80+
<InfoCircledIcon className="h-4 w-4" />
81+
<AlertTitle>Update Your Inboxes</AlertTitle>
82+
<AlertDescription className="flex flex-col gap-2">
83+
<p>
84+
We have updated how inbox IDs are generated to better support sharing
85+
links across machines. Your existing inboxes need to be updated.
86+
</p>
87+
<div className="flex gap-2 mt-2">
88+
<Button
89+
variant="default"
90+
size="sm"
91+
onClick={handleRunBackfill}
92+
disabled={isRunning}
93+
>
94+
{isRunning && <ReloadIcon className="mr-2 h-4 w-4 animate-spin" />}
95+
{isRunning ? "Updating..." : "Update Inboxes"}
96+
</Button>
97+
<Button
98+
variant="outline"
99+
size="sm"
100+
onClick={handleDismiss}
101+
disabled={isRunning}
102+
>
103+
Dismiss
104+
</Button>
105+
</div>
106+
</AlertDescription>
107+
</Alert>
108+
);
109+
}

src/components/agent-inbox/components/breadcrumb.tsx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import { HumanInterrupt, ThreadStatusWithAll } from "../types";
1414
import { prettifyText } from "../utils";
1515
import { useThreadsContext } from "../contexts/ThreadContext";
1616
import React from "react";
17+
import { logger } from "../utils/logger";
1718

1819
export function BreadCrumb({ className }: { className?: string }) {
1920
const { searchParams } = useQueryParams();
@@ -58,7 +59,7 @@ export function BreadCrumb({ className }: { className?: string }) {
5859
setSelectedThreadActionLabel(undefined);
5960
}
6061
} catch (e) {
61-
console.error("Error while updating breadcrumb", e);
62+
logger.error("Error while updating breadcrumb", e);
6263
}
6364
}, [searchParams, agentInboxes, threadData]);
6465

src/components/agent-inbox/components/edit-agent-inbox-dialog.tsx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import React from "react";
1111
import { useToast } from "@/hooks/use-toast";
1212
import { AgentInbox } from "../types";
1313
import { useInboxes } from "../hooks/use-inboxes";
14+
import { logger } from "../utils/logger";
1415

1516
export function EditAgentInboxDialog({
1617
agentInbox,
@@ -56,7 +57,7 @@ export function EditAgentInboxDialog({
5657
// Force a page reload to reflect changes
5758
window.location.reload();
5859
} catch (error) {
59-
console.error("Error updating agent inbox", error);
60+
logger.error("Error updating agent inbox", error);
6061
toast({
6162
title: "Error",
6263
description: "Failed to update agent inbox",

src/components/agent-inbox/components/inbox-item-input.tsx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import { Separator } from "@/components/ui/separator";
1313
import { Button } from "@/components/ui/button";
1414
import { CircleX, LoaderCircle, Undo2 } from "lucide-react";
1515
import { useToast } from "@/hooks/use-toast";
16+
import { logger } from "../utils/logger";
1617

1718
function ResetButton({ handleReset }: { handleReset: () => void }) {
1819
return (
@@ -390,7 +391,7 @@ export function InboxItemInput({
390391

391392
setHumanResponse((prev) => {
392393
if (typeof response.args !== "object" || !response.args) {
393-
console.error(
394+
logger.error(
394395
"Mismatched response type",
395396
!!response.args,
396397
typeof response.args

0 commit comments

Comments
 (0)