diff --git a/apps/webapp/app/components/dashboard-agent/DashboardAgent.tsx b/apps/webapp/app/components/dashboard-agent/DashboardAgent.tsx index 47e280da1f1..2796c8516df 100644 --- a/apps/webapp/app/components/dashboard-agent/DashboardAgent.tsx +++ b/apps/webapp/app/components/dashboard-agent/DashboardAgent.tsx @@ -1,4 +1,3 @@ -import { SparklesIcon } from "@heroicons/react/20/solid"; import { useState } from "react"; import { ResizableHandle, @@ -6,17 +5,19 @@ import { ResizablePanelGroup, } from "~/components/primitives/Resizable"; import { DashboardAgentPanel } from "./DashboardAgentPanel"; +import { DashboardAgentProvider } from "./dashboardAgentLauncher"; /** * Mounts the dashboard agent in the env layout. Renders the page content - * (`children` = the route Outlet); when the agent is open it splits the layout - * into a resizable content + agent panel using the shared Resizable primitive, - * with `autosaveId` persisting the width. When closed it's a floating launcher. + * (`children` = the route Outlet) and shares the open/close state via context so + * the page-header launcher (`DashboardAgentLauncher`) can toggle it. When open it + * splits the layout into a resizable content + agent panel, `autosaveId` persists + * the width. * - * `hasAccess` is resolved server-side in the env layout loader (via - * `canAccessDashboardAgent`: global env, admins/impersonators, then the - * global/per-org feature flag, default off), so the launcher is hidden unless - * the agent is enabled. The resource routes enforce the same check server-side. + * `hasAccess` is resolved server-side in the env layout loader + * (`canAccessDashboardAgent`); when false we render the content untouched and + * never expose the context, so the launcher stays hidden. The resource routes + * enforce the same check server-side. */ export function DashboardAgent({ children, @@ -31,36 +32,25 @@ export function DashboardAgent({ return
{children}
; } - if (!open) { - return ( -
-
{children}
- -
- ); - } - return ( - - -
{children}
-
- - - setOpen(false)} /> - -
+ + {open ? ( + + +
{children}
+
+ + + setOpen(false)} /> + +
+ ) : ( +
{children}
+ )} +
); } diff --git a/apps/webapp/app/components/dashboard-agent/DashboardAgentChat.tsx b/apps/webapp/app/components/dashboard-agent/DashboardAgentChat.tsx index 1c6b3e9552c..e6662ca8494 100644 --- a/apps/webapp/app/components/dashboard-agent/DashboardAgentChat.tsx +++ b/apps/webapp/app/components/dashboard-agent/DashboardAgentChat.tsx @@ -101,7 +101,7 @@ export function DashboardAgentChat({ const res = await fetch(actionPath, { method: "POST", body }); const data = (await res.json()) as { publicAccessToken?: string; error?: string }; if (!res.ok || !data.publicAccessToken) { - throw new Error(data.error ?? "The dashboard agent couldn't start."); + throw new Error(data.error ?? "The chat couldn't start."); } return { publicAccessToken: data.publicAccessToken }; }, @@ -112,7 +112,7 @@ export function DashboardAgentChat({ const res = await fetch(actionPath, { method: "POST", body }); const data = (await res.json()) as { token?: string; error?: string }; if (!res.ok || !data.token) { - throw new Error(data.error ?? "Couldn't refresh the dashboard agent token."); + throw new Error(data.error ?? "Couldn't refresh the chat token."); } return data.token; }, diff --git a/apps/webapp/app/components/dashboard-agent/DashboardAgentHeader.tsx b/apps/webapp/app/components/dashboard-agent/DashboardAgentHeader.tsx index c782e5f13c3..8b96dd06c01 100644 --- a/apps/webapp/app/components/dashboard-agent/DashboardAgentHeader.tsx +++ b/apps/webapp/app/components/dashboard-agent/DashboardAgentHeader.tsx @@ -14,7 +14,7 @@ export function DashboardAgentHeader({ }) { return (
- Dashboard agent + Chat
void; +}; + +const DashboardAgentContext = createContext(null); + +export const DashboardAgentProvider = DashboardAgentContext.Provider; + +// Null outside the env layout (no provider) or when the agent is gated off, so +// the launcher self-hides everywhere it can't open. +export function useDashboardAgent() { + return useContext(DashboardAgentContext); +} + +export function DashboardAgentLauncher() { + const agent = useDashboardAgent(); + if (!agent) { + return null; + } + + const { open, setOpen } = agent; + + return ( + + ); +} diff --git a/apps/webapp/app/components/primitives/PageHeader.tsx b/apps/webapp/app/components/primitives/PageHeader.tsx index 1b5e3be5579..31fa8104dd4 100644 --- a/apps/webapp/app/components/primitives/PageHeader.tsx +++ b/apps/webapp/app/components/primitives/PageHeader.tsx @@ -8,6 +8,7 @@ import { Header2 } from "./Headers"; import { LoadingBarDivider } from "./LoadingBarDivider"; import { SimpleTooltip } from "./Tooltip"; import { EnvironmentBanner } from "../navigation/EnvironmentBanner"; +import { DashboardAgentLauncher } from "../dashboard-agent/dashboardAgentLauncher"; type WithChildren = { children: React.ReactNode; @@ -24,7 +25,10 @@ export function NavBar({ children }: WithChildren) { return (
-
{children}
+
+
{children}
+ +
{showUpgradePrompt.shouldShow && organization ? : }