import {
AgentSidebar,
GuidedQuestionFlow,
focusAgentChat,
isAgentChatHomeHandoffActive,
markAgentChatHomeHandoff,
navigateWithAgentChatViewTransition,
useAgentChatHomeHandoff,
useAgentChatHomeHandoffLinks,
useGuidedQuestionFlow,
} from "@agent-native/core/client/agent-chat";
import { useT } from "@agent-native/core/client/i18n";
import { InvitationBanner } from "@agent-native/core/client/org";
import { CreativeContextComposerChip } from "@agent-native/creative-context/client";
import { useEffect, useMemo } from "react";
import { useLocation, useNavigate } from "react-router";
import { useNavigationState } from "@/hooks/use-navigation-state";
import {
ANALYTICS_CHAT_STORAGE_KEY,
markAnalyticsChatActivity,
} from "@/lib/chat-handoff";
import { TAB_ID } from "@/lib/tab-id";
import { Header } from "./Header";
import { HeaderActionsProvider } from "./HeaderActions";
import { MobileNav } from "./MobileNav";
import { Sidebar } from "./Sidebar";
interface LayoutProps {
children: React.ReactNode;
}
const BARE_ROUTES = new Set(["/chart"]);
export function Layout({ children }: LayoutProps) {
return {children};
}
function InteractiveLayout({ children }: LayoutProps) {
useNavigationState();
const location = useLocation();
const navigate = useNavigate();
const t = useT();
// Analytics stages the active primary resource as composer context —
// dashboards (`/dashboards/:id`, legacy `/adhoc/:id`) and ad-hoc analyses
// (`/analyses/:id`). List pages and Ask leave context null so general data
// questions still work.
const analyticsScope = useMemo(() => {
const dashMatch = location.pathname.match(
/^\/(?:adhoc|dashboards)\/([^/]+)/,
);
if (dashMatch?.[1]) {
return {
type: "dashboard" as const,
id: dashMatch[1],
contextKey: "analytics-selected-dashboard",
};
}
const analysisMatch = location.pathname.match(/^\/analyses\/([^/]+)/);
if (analysisMatch?.[1]) {
return { type: "analysis" as const, id: analysisMatch[1] };
}
return null;
}, [location.pathname]);
const {
questions: guidedQuestions,
title: guidedTitle,
description: guidedDescription,
skipLabel: guidedSkipLabel,
submitLabel: guidedSubmitLabel,
handleSubmit: handleGuidedSubmit,
handleSkip: handleGuidedSkip,
} = useGuidedQuestionFlow({
submitMessage: "Here are my answers — go ahead.",
skipMessage: "Skip the questions — decide for me.",
buildSubmitContext: ({ formattedAnswers }) =>
[
"The user answered guided clarification questions for an analytics task.",
"",
"Answers:",
formattedAnswers,
"",
"Use these answers to choose the dashboard scope, data source, metrics, breakdowns, and layout. For dashboards, consult the data dictionary before writing SQL and only ask another question if a required source/table/metric is still genuinely ambiguous.",
].join("\n"),
buildSkipContext: () =>
"The user skipped the guided analytics questions. Proceed with reasonable defaults, consult the data dictionary before writing SQL, and ask again only if a required source/table/metric is still genuinely ambiguous.",
});
// Extensions list (`/extensions`) and viewer (`/extensions/:id`) render their own h-12
// toolbar. Skip the framework
// Header so there's no double-header.
const isExtensionsRoute =
location.pathname === "/extensions" ||
location.pathname.startsWith("/extensions/");
const isSessionDetailRoute = /^\/sessions\/[^/]+/.test(location.pathname);
// Monitoring renders its own header row (section tabs / "Back to monitors"
// + the relocated agent toggle), so skip the framework Header to avoid a
// redundant second title bar.
const isMonitoringRoute =
location.pathname === "/monitoring" ||
location.pathname.startsWith("/monitoring/");
const isAskRoute = location.pathname === "/ask";
const chatHomeHandoffActive = useAgentChatHomeHandoff({
storageKey: ANALYTICS_CHAT_STORAGE_KEY,
activePath: location.pathname,
enabled: !isAskRoute,
});
const chatHomeHandoffPending = isAgentChatHomeHandoffActive(
ANALYTICS_CHAT_STORAGE_KEY,
);
useAgentChatHomeHandoffLinks({
storageKey: ANALYTICS_CHAT_STORAGE_KEY,
chatPath: "/ask",
enabled: true,
requireActiveHandoff: true,
});
useEffect(() => {
function handleChatRunning(event: Event) {
const detail = (event as CustomEvent).detail;
if (isAskRoute && typeof detail?.isRunning === "boolean") {
markAnalyticsChatActivity();
if (detail.isRunning === true) {
markAgentChatHomeHandoff(ANALYTICS_CHAT_STORAGE_KEY);
}
}
}
window.addEventListener("agentNative.chatRunning", handleChatRunning);
return () =>
window.removeEventListener("agentNative.chatRunning", handleChatRunning);
}, [isAskRoute]);
function openAskAgentFullscreen() {
focusAgentChat();
navigateWithAgentChatViewTransition(navigate, "/ask");
}
if (BARE_ROUTES.has(location.pathname)) {
return <>{children}>;
}
const contentFrame = (
{!isExtensionsRoute &&
!isAskRoute &&
!isSessionDetailRoute &&
!isMonitoringRoute &&
}
{children}
{guidedQuestions && (
)}
);
return (
{isAskRoute ? (
{contentFrame}
) : (
}
>
{contentFrame}
)}
);
}