Skip to content

Commit 80e714b

Browse files
feat: upgrades chat ui to v2 (#374)
Co-authored-by: Brace Sproul <[email protected]>
1 parent 039bebd commit 80e714b

File tree

26 files changed

+1206
-906
lines changed

26 files changed

+1206
-906
lines changed
Lines changed: 4 additions & 0 deletions
Loading
Lines changed: 39 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,52 @@
11
"use client";
2-
import React from "react";
3-
import { AgentsProvider } from "@/providers/Agents";
2+
3+
import React, { useState, useMemo } from "react";
4+
import { AgentsProvider, useAgentsContext } from "@/providers/Agents";
45
import DeepAgentChatPageContent from "@/features/chat";
6+
import { PageHeader } from "@/features/chat/components";
57
import { Toaster } from "@/components/ui/sonner";
8+
import { useQueryState } from "nuqs";
9+
10+
function DeepAgentChatPageInner(): React.ReactNode {
11+
const [view, setView] = useState<"chat" | "workflow">("chat");
12+
const [agentId] = useQueryState("agentId");
13+
const { agents } = useAgentsContext();
14+
15+
const selectedAgent = useMemo(() => {
16+
return agents.find((agent) => agent.assistant_id === agentId);
17+
}, [agents, agentId]);
18+
19+
return (
20+
<>
21+
<PageHeader
22+
view={view}
23+
setView={setView}
24+
assistantName={selectedAgent?.name}
25+
/>
26+
<div className="flex min-h-0 flex-1 overflow-hidden">
27+
<DeepAgentChatPageContent
28+
view={view}
29+
onViewChange={setView}
30+
hideInternalToggle={true}
31+
/>
32+
</div>
33+
</>
34+
);
35+
}
36+
637
/**
738
* Deep Agent Chat page (/chat).
839
* Contains the deep agent chat interface.
940
*/
1041
export default function DeepAgentChatPage(): React.ReactNode {
1142
return (
1243
<React.Suspense fallback={<div>Loading chat...</div>}>
13-
<AgentsProvider>
14-
<DeepAgentChatPageContent />
15-
</AgentsProvider>
16-
<Toaster />
44+
<div className="flex h-screen flex-col">
45+
<AgentsProvider>
46+
<DeepAgentChatPageInner />
47+
</AgentsProvider>
48+
<Toaster />
49+
</div>
1750
</React.Suspense>
1851
);
1952
}

apps/web-v2/src/components/config-fields/interrupt-config-dialog.tsx

Lines changed: 44 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -17,8 +17,8 @@ import { HumanInterruptConfig } from "@/types/inbox";
1717
interface InterruptConfigDialogProps {
1818
open: boolean;
1919
onOpenChange: (open: boolean) => void;
20-
config: HumanInterruptConfig;
21-
onInterruptChange: (config: HumanInterruptConfig) => void;
20+
config: HumanInterruptConfig | boolean;
21+
onInterruptChange: (config: HumanInterruptConfig | boolean) => void;
2222
}
2323

2424
export function InterruptConfigDialog({
@@ -69,9 +69,21 @@ export function InterruptConfigDialog({
6969

7070
<Switch
7171
id="allow-accept"
72-
checked={config.allow_accept}
72+
checked={
73+
typeof config === "boolean" ? config : config.allow_accept
74+
}
7375
onCheckedChange={(checked) =>
74-
onInterruptChange({ ...config, allow_accept: checked })
76+
onInterruptChange({
77+
...(typeof config === "boolean"
78+
? {
79+
allow_edit: config,
80+
allow_ignore: config,
81+
allow_respond: config,
82+
allow_accept: config,
83+
}
84+
: config),
85+
allow_accept: checked,
86+
})
7587
}
7688
/>
7789
</div>
@@ -92,9 +104,21 @@ export function InterruptConfigDialog({
92104

93105
<Switch
94106
id="allow-respond"
95-
checked={config.allow_respond}
107+
checked={
108+
typeof config === "boolean" ? config : config.allow_respond
109+
}
96110
onCheckedChange={(checked) =>
97-
onInterruptChange({ ...config, allow_respond: checked })
111+
onInterruptChange({
112+
...(typeof config === "boolean"
113+
? {
114+
allow_edit: config,
115+
allow_ignore: config,
116+
allow_respond: config,
117+
allow_accept: config,
118+
}
119+
: config),
120+
allow_respond: checked,
121+
})
98122
}
99123
/>
100124
</div>
@@ -115,9 +139,21 @@ export function InterruptConfigDialog({
115139

116140
<Switch
117141
id="allow-edit"
118-
checked={config.allow_edit}
142+
checked={
143+
typeof config === "boolean" ? config : config.allow_edit
144+
}
119145
onCheckedChange={(checked) =>
120-
onInterruptChange({ ...config, allow_edit: checked })
146+
onInterruptChange({
147+
...(typeof config === "boolean"
148+
? {
149+
allow_edit: config,
150+
allow_ignore: config,
151+
allow_respond: config,
152+
allow_accept: config,
153+
}
154+
: config),
155+
allow_edit: checked,
156+
})
121157
}
122158
/>
123159
</div>

apps/web-v2/src/components/config-fields/mcp.tsx

Lines changed: 15 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -20,16 +20,17 @@ import { MCPConfig } from "@/types/deep-agent";
2020
const getInterruptConfig = (
2121
interruptConfig: Record<string, boolean | HumanInterruptConfig> | undefined,
2222
label: string,
23-
): HumanInterruptConfig => {
24-
if (!interruptConfig || !(label in interruptConfig)) {
25-
return {
26-
allow_accept: false,
27-
allow_ignore: false,
28-
allow_respond: false,
29-
allow_edit: false,
30-
};
23+
): HumanInterruptConfig | false => {
24+
// if there isn't an interrupt config, the tool doesn't exist yet, or it's set to false, return false.
25+
if (
26+
!interruptConfig ||
27+
!(label in interruptConfig) ||
28+
(typeof interruptConfig[label] === "boolean" && !interruptConfig[label])
29+
) {
30+
return false;
3131
}
3232

33+
// Should always be true in this case
3334
if (typeof interruptConfig[label] === "boolean") {
3435
return {
3536
allow_accept: interruptConfig[label],
@@ -79,13 +80,17 @@ export function ConfigFieldTool({
7980
setValue(newValue);
8081
};
8182

82-
const handleInterruptConfigChange = (newConfig: HumanInterruptConfig) => {
83+
const handleInterruptConfigChange = (
84+
newConfig: HumanInterruptConfig | boolean,
85+
) => {
86+
const allSetToFalse =
87+
!newConfig || Object.values(newConfig).every((v) => !v);
8388
const newValue = {
8489
...value,
8590
// Remove duplicates
8691
interrupt_config: {
8792
...interruptConfig,
88-
[label]: newConfig,
93+
[label]: allSetToFalse ? false : newConfig,
8994
},
9095
};
9196

apps/web-v2/src/features/agents/components/create-edit-agent-dialogs/create-agent-dialog.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ import _ from "lodash";
2727
import { ToolAuthRequiredAlert } from "./tool-auth-required-alert";
2828
import { AgentFormValues } from "./types";
2929
import { DeepAgentConfiguration } from "@/types/deep-agent";
30-
import { DEFAULT_FORM_CONFIG } from "./utils";
30+
import { DEFAULT_FORM_CONFIG, prepareConfigForSaving } from "./utils";
3131

3232
interface CreateAgentDialogProps {
3333
agentId?: string;
@@ -92,7 +92,7 @@ function CreateAgentFormContent(props: {
9292
{
9393
name,
9494
description,
95-
config,
95+
config: prepareConfigForSaving(config),
9696
},
9797
);
9898

apps/web-v2/src/features/agents/components/create-edit-agent-dialogs/edit-agent-dialog.tsx

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ import { useTriggers } from "@/hooks/use-triggers";
2121
import { useMCPContext } from "@/providers/MCP";
2222
import { useLangChainAuth } from "@/hooks/use-langchain-auth";
2323
import { ToolAuthRequiredAlert } from "./tool-auth-required-alert";
24-
import { getDefaultsFromAgent } from "./utils";
24+
import { getDefaultsFromAgent, prepareConfigForSaving } from "./utils";
2525
import { DeepAgentConfiguration } from "@/types/deep-agent";
2626

2727
interface EditAgentDialogProps {
@@ -115,7 +115,10 @@ function EditAgentDialogContent({
115115
const updatedAgent = await updateAgent(
116116
agent.assistant_id,
117117
agent.deploymentId,
118-
data,
118+
{
119+
...data,
120+
config: prepareConfigForSaving(data.config),
121+
},
119122
);
120123

121124
if (!updatedAgent) {

apps/web-v2/src/features/agents/components/create-edit-agent-dialogs/utils.ts

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,3 +27,25 @@ export function getDefaultsFromAgent(agent: Agent): DeepAgentConfiguration {
2727
DEFAULT_FORM_CONFIG.triggers,
2828
};
2929
}
30+
31+
export function prepareConfigForSaving(
32+
config: DeepAgentConfiguration,
33+
): DeepAgentConfiguration {
34+
if (!config.tools?.interrupt_config) {
35+
return config;
36+
}
37+
38+
if (Object.values(config.tools.interrupt_config).some((v) => !v)) {
39+
return {
40+
...config,
41+
tools: {
42+
...config.tools,
43+
interrupt_config: Object.fromEntries(
44+
Object.entries(config.tools.interrupt_config).filter(([_, v]) => v),
45+
),
46+
},
47+
};
48+
}
49+
50+
return config;
51+
}
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
export { PageHeader } from "./page-header";
2+
export { DeepAgentChatBreadcrumb } from "./breadcrumb";
Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
"use client";
2+
3+
import React from "react";
4+
import { SidebarTrigger } from "@/components/ui/sidebar";
5+
6+
interface PageHeaderProps {
7+
view: "chat" | "workflow";
8+
setView: (v: "chat" | "workflow") => void;
9+
assistantName?: string;
10+
}
11+
12+
export function PageHeader({ view, setView, assistantName }: PageHeaderProps) {
13+
return (
14+
<header className="relative flex h-16 shrink-0 items-center gap-2 transition-[width,height] ease-linear group-has-[[data-collapsible=icon]]/sidebar-wrapper:h-12">
15+
<div className="flex items-center gap-2 px-4">
16+
<SidebarTrigger className="-ml-1" />
17+
<span className="text-muted-foreground"></span>
18+
<span className="text-sm font-medium">
19+
{assistantName || "main agent"}
20+
</span>
21+
</div>
22+
<div className="absolute left-1/2 -translate-x-1/2">
23+
<div
24+
className="flex h-[24px] w-[134px] items-center gap-0 overflow-hidden rounded border bg-white p-[3px] text-[12px] shadow-sm"
25+
style={{
26+
borderColor: "D1D1D6",
27+
}}
28+
>
29+
<button
30+
type="button"
31+
onClick={() => setView("chat")}
32+
className="flex h-full flex-1 items-center justify-center truncate rounded p-[3px]"
33+
style={
34+
view === "chat"
35+
? {
36+
background: "#F4F3FF",
37+
}
38+
: undefined
39+
}
40+
>
41+
Chat
42+
</button>
43+
<button
44+
type="button"
45+
onClick={() => setView("workflow")}
46+
className="flex h-full flex-1 items-center justify-center truncate rounded p-[3px]"
47+
style={
48+
view === "workflow"
49+
? {
50+
background: "#F4F3FF",
51+
}
52+
: undefined
53+
}
54+
>
55+
Workflow
56+
</button>
57+
</div>
58+
</div>
59+
</header>
60+
);
61+
}

apps/web-v2/src/features/chat/index.tsx

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,15 @@ import { DeepAgentChatBreadcrumb } from "@/features/chat/components/breadcrumb";
1616
/**
1717
* Deep Agent Chat page content that uses nuqs hooks.
1818
*/
19-
export default function DeepAgentChatPageContent(): React.ReactNode {
19+
export default function DeepAgentChatPageContent({
20+
view,
21+
onViewChange,
22+
hideInternalToggle,
23+
}: {
24+
view?: "chat" | "workflow";
25+
onViewChange?: (view: "chat" | "workflow") => void;
26+
hideInternalToggle?: boolean;
27+
}): React.ReactNode {
2028
const { session } = useAuthContext();
2129

2230
const { agents, loading } = useAgentsContext();
@@ -103,6 +111,9 @@ export default function DeepAgentChatPageContent(): React.ReactNode {
103111
mode="oap"
104112
SidebarTrigger={SidebarTrigger}
105113
DeepAgentChatBreadcrumb={DeepAgentChatBreadcrumb}
114+
view={view}
115+
onViewChange={onViewChange}
116+
hideInternalToggle={hideInternalToggle}
106117
/>
107118
);
108119
}

0 commit comments

Comments
 (0)