Skip to content

Commit 003d6a0

Browse files
authored
feat: add feature flag (#379)
2 parents 80e714b + f64af9b commit 003d6a0

File tree

15 files changed

+291
-69
lines changed

15 files changed

+291
-69
lines changed

apps/web-v2/.env.example

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,3 +30,6 @@ NEXT_PUBLIC_DEBUG_MODE="false"
3030

3131
# JWT secret for auth
3232
SUPABASE_JWT_SECRET=""
33+
34+
# Client side id for feature flags
35+
NEXT_PUBLIC_LD_CLIENT_SIDE_ID=""

apps/web-v2/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,7 @@
5454
"jsonwebtoken": "^9.0.2",
5555
"katex": "latest",
5656
"langgraph-nextjs-api-passthrough": "^0.1.0",
57+
"launchdarkly-react-client-sdk": "^3.8.1",
5758
"lodash": "^4.17.21",
5859
"lucide-react": "^0.488.0",
5960
"next-themes": "^0.4.6",

apps/web-v2/src/app/(app)/chat/page.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ function DeepAgentChatPageInner(): React.ReactNode {
2222
view={view}
2323
setView={setView}
2424
assistantName={selectedAgent?.name}
25+
showToggle={!!agentId}
2526
/>
2627
<div className="flex min-h-0 flex-1 overflow-hidden">
2728
<DeepAgentChatPageContent

apps/web-v2/src/app/(app)/layout.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,8 @@ import type { Metadata } from "next";
22
import "../globals.css";
33
import React from "react";
44
import { NuqsAdapter } from "nuqs/adapters/next/app";
5-
import { SidebarLayout } from "@/components/sidebar";
65
import { AuthProvider } from "@/providers/Auth";
6+
import { AuthenticatedApp } from "@/components/AuthenticatedApp";
77

88
export const metadata: Metadata = {
99
title: "Open Agent Platform",
@@ -29,7 +29,7 @@ export default function RootLayout({
2929
<body>
3030
<NuqsAdapter>
3131
<AuthProvider>
32-
<SidebarLayout>{children}</SidebarLayout>
32+
<AuthenticatedApp>{children}</AuthenticatedApp>
3333
</AuthProvider>
3434
</NuqsAdapter>
3535
</body>
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
"use client";
2+
3+
import React from "react";
4+
import { useAuthContext } from "@/providers/Auth";
5+
import { SidebarLayout } from "@/components/sidebar";
6+
import LaunchDarklyProvider from "@/providers/LaunchDarkly";
7+
import Loading from "@/components/ui/loading";
8+
9+
export function AuthenticatedApp({ children }: { children: React.ReactNode }) {
10+
const { isLoading } = useAuthContext();
11+
12+
// Show loading until auth is complete
13+
if (isLoading) {
14+
return (
15+
<div className="flex min-h-screen items-center justify-center p-8">
16+
<Loading label="Preparing your workspace" />
17+
</div>
18+
);
19+
}
20+
21+
// Only render the app with LaunchDarkly after auth is complete
22+
return (
23+
<LaunchDarklyProvider>
24+
<SidebarLayout>{children}</SidebarLayout>
25+
</LaunchDarklyProvider>
26+
);
27+
}

apps/web-v2/src/components/sidebar/app-sidebar.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@ const data = {
4343
url: "/inbox",
4444
icon: Inbox,
4545
},
46+
4647
{
4748
title: "Triggers",
4849
url: "/triggers",
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
"use client";
2+
3+
import React from "react";
4+
import { LoaderCircle } from "lucide-react";
5+
6+
export default function Loading({
7+
label = "Preparing your workspace",
8+
}: {
9+
label?: string;
10+
}) {
11+
return (
12+
<div className="flex min-h-[220px] w-full flex-col items-center justify-center gap-3 p-8 text-center">
13+
<LoaderCircle className="h-8 w-8 animate-spin" />
14+
<div className="text-sm text-black">{label}</div>
15+
</div>
16+
);
17+
}

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

Lines changed: 22 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -17,13 +17,16 @@ import { useMCPContext } from "@/providers/MCP";
1717
import { useFetchPreselectedTools } from "@/hooks/use-fetch-preselected-tools";
1818
import { Controller, useFormContext } from "react-hook-form";
1919
import { AgentFormValues } from "./types";
20+
import { useFlags } from "launchdarkly-react-client-sdk";
21+
import { LaunchDarklyFeatureFlags } from "@/types/launch-darkly";
2022

2123
interface AgentFieldsFormProps {
2224
agentId: string;
2325
}
2426

2527
export function AgentFieldsForm({ agentId }: AgentFieldsFormProps) {
2628
const form = useFormContext<AgentFormValues>();
29+
const { showTriggersTab } = useFlags<LaunchDarklyFeatureFlags>();
2730

2831
const { tools, setTools, getTools, cursor, loading } = useMCPContext();
2932
const { toolSearchTerm, debouncedSetSearchTerm, displayTools } =
@@ -185,20 +188,26 @@ export function AgentFieldsForm({ agentId }: AgentFieldsFormProps) {
185188
)}
186189
/>
187190
</div>
188-
<Separator />
189-
<div className="flex w-full flex-col items-start justify-start gap-2">
190-
<p className="text-lg font-semibold tracking-tight">Agent Triggers</p>
191-
<Controller
192-
control={form.control}
193-
name="config.triggers"
194-
render={({ field: { value, onChange } }) => (
195-
<ConfigFieldTriggers
196-
value={value}
197-
setValue={onChange}
191+
{showTriggersTab !== false && (
192+
<>
193+
<Separator />
194+
<div className="flex w-full flex-col items-start justify-start gap-2">
195+
<p className="text-lg font-semibold tracking-tight">
196+
Agent Triggers
197+
</p>
198+
<Controller
199+
control={form.control}
200+
name="config.triggers"
201+
render={({ field: { value, onChange } }) => (
202+
<ConfigFieldTriggers
203+
value={value}
204+
setValue={onChange}
205+
/>
206+
)}
198207
/>
199-
)}
200-
/>
201-
</div>
208+
</div>
209+
</>
210+
)}
202211
</div>
203212
);
204213
}

apps/web-v2/src/features/chat/components/page-header.tsx

Lines changed: 59 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -2,60 +2,75 @@
22

33
import React from "react";
44
import { SidebarTrigger } from "@/components/ui/sidebar";
5+
import { useFlags } from "launchdarkly-react-client-sdk";
6+
import { toast } from "sonner";
7+
import { LaunchDarklyFeatureFlags } from "@/types/launch-darkly";
8+
import { cn } from "@/lib/utils";
59

610
interface PageHeaderProps {
711
view: "chat" | "workflow";
812
setView: (v: "chat" | "workflow") => void;
913
assistantName?: string;
14+
showToggle?: boolean;
1015
}
1116

12-
export function PageHeader({ view, setView, assistantName }: PageHeaderProps) {
17+
export function PageHeader({
18+
view,
19+
setView,
20+
assistantName,
21+
showToggle = false,
22+
}: PageHeaderProps) {
23+
const { showAgentVisualizerUi } = useFlags<LaunchDarklyFeatureFlags>();
24+
const isWorkflowEnabled = showAgentVisualizerUi !== false;
25+
26+
const handleViewChange = (newView: "chat" | "workflow") => {
27+
if (newView === "workflow" && !isWorkflowEnabled) {
28+
toast.info("Workflow view is coming soon!", {
29+
richColors: true,
30+
});
31+
return;
32+
}
33+
setView(newView);
34+
};
35+
1336
return (
1437
<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>
38+
{showToggle && (
39+
<div className="flex items-center gap-2 px-4">
40+
<SidebarTrigger className="-ml-1" />
41+
<span className="text-muted-foreground"></span>
42+
<span className="text-sm font-medium">
43+
{assistantName || "main agent"}
44+
</span>
45+
</div>
46+
)}
47+
{showToggle && (
48+
<div className="absolute left-1/2 -translate-x-1/2">
49+
<div className="flex h-[24px] w-[134px] items-center gap-0 overflow-hidden rounded border border-[#D1D1D6] bg-white p-[3px] text-[12px] shadow-sm">
50+
<button
51+
type="button"
52+
onClick={() => handleViewChange("chat")}
53+
className={cn(
54+
"flex h-full flex-1 items-center justify-center truncate rounded p-[3px]",
55+
view === "chat" && "bg-[#F4F3FF]",
56+
)}
57+
>
58+
Chat
59+
</button>
60+
<button
61+
type="button"
62+
onClick={() => handleViewChange("workflow")}
63+
className={cn(
64+
"flex h-full flex-1 items-center justify-center truncate rounded p-[3px]",
65+
view === "workflow" && "bg-[#F4F3FF]",
66+
!isWorkflowEnabled && "cursor-not-allowed opacity-50",
67+
)}
68+
>
69+
Workflow
70+
</button>
71+
</div>
5772
</div>
58-
</div>
73+
)}
5974
</header>
6075
);
6176
}

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

Lines changed: 45 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,9 @@ import { useEffect, useState } from "react";
1818
import type { Trigger } from "@/types/triggers";
1919
import { toast } from "sonner";
2020
import { groupUserRegisteredTriggersByProvider } from "@/lib/triggers";
21+
import Loading from "@/components/ui/loading";
22+
import { useFlags } from "launchdarkly-react-client-sdk";
23+
import { LaunchDarklyFeatureFlags } from "@/types/launch-darkly";
2124

2225
export default function TriggersInterface() {
2326
const [triggersLoading, setTriggersLoading] = useState(true);
@@ -27,8 +30,18 @@ export default function TriggersInterface() {
2730
>([]);
2831
const auth = useAuthContext();
2932
const { listTriggers, listUserTriggers } = useTriggers();
33+
const { showTriggersTab } = useFlags<LaunchDarklyFeatureFlags>();
3034

3135
useEffect(() => {
36+
if (showTriggersTab === false) {
37+
// Do not fetch when disabled
38+
setTriggersLoading(false);
39+
return;
40+
}
41+
if (showTriggersTab === undefined) {
42+
setTriggersLoading(false);
43+
return;
44+
}
3245
if (!auth.session?.accessToken) return;
3346
setTriggersLoading(true);
3447
listTriggers(auth.session?.accessToken)
@@ -55,7 +68,7 @@ export default function TriggersInterface() {
5568
.finally(() => {
5669
setTriggersLoading(false);
5770
});
58-
}, [auth.session?.accessToken]);
71+
}, [auth.session?.accessToken, showTriggersTab]);
5972

6073
if (triggersLoading) {
6174
return (
@@ -75,20 +88,45 @@ export default function TriggersInterface() {
7588
<Zap className="h-5 w-5" />
7689
Loading Triggers
7790
</CardTitle>
78-
<CardDescription>Loading triggers...</CardDescription>
91+
<CardDescription>Loading triggers</CardDescription>
7992
</CardHeader>
8093
<CardContent>
81-
<div className="rounded-lg border border-dashed p-8 text-center">
82-
<Zap className="text-muted-foreground mx-auto h-12 w-12" />
83-
<h3 className="mt-4 text-lg font-semibold">Loading Triggers</h3>
84-
<p className="text-muted-foreground mt-2">Loading triggers...</p>
85-
</div>
94+
<Loading label="Loading triggers" />
8695
</CardContent>
8796
</Card>
8897
</div>
8998
);
9099
}
91100

101+
// Feature disabled: show coming soon (tab still visible per UX)
102+
if (showTriggersTab === false) {
103+
return (
104+
<div className="flex-1 space-y-4 p-4 pt-6 md:p-8">
105+
<div className="flex items-center justify-between space-y-2">
106+
<div>
107+
<h2 className="text-3xl font-bold tracking-tight">Triggers</h2>
108+
<p className="text-muted-foreground">
109+
Set up triggers to automatically activate your agents
110+
</p>
111+
</div>
112+
</div>
113+
114+
<Card>
115+
<CardHeader>
116+
<CardTitle className="flex items-center gap-2">
117+
<Zap className="h-5 w-5" />
118+
Coming soon
119+
</CardTitle>
120+
<CardDescription>
121+
This feature is under development and will be released soon. Stay
122+
tuned!
123+
</CardDescription>
124+
</CardHeader>
125+
</Card>
126+
</div>
127+
);
128+
}
129+
92130
if (triggers.length === 0) {
93131
return (
94132
<div className="flex-1 space-y-4 p-4 pt-6 md:p-8">

0 commit comments

Comments
 (0)