import {
chatModelSelectionStorageKey,
navigateWithAgentChatViewTransition,
useChatModels,
} from "@agent-native/core/client/agent-chat";
import { PromptComposer } from "@agent-native/core/client/composer";
import { useActionQuery } from "@agent-native/core/client/hooks";
import { useT } from "@agent-native/core/client/i18n";
import { IconChevronDown, IconClockHour4, IconPlus } from "@tabler/icons-react";
import type { ReactNode } from "react";
import { useState } from "react";
import { Link, useNavigate } from "react-router";
import type { ConnectedAppSummary } from "../lib/other-apps";
import { cn } from "../lib/utils";
import {
orderWorkspaceApps,
useWorkspaceAppLayout,
workspaceAppMatchesQuery,
} from "../lib/workspace-app-layout";
import {
isWorkspaceAppVisibleInDefaultLaunchers,
type WorkspaceAppSummary,
} from "../lib/workspace-apps";
import { ActionQueryError } from "./action-query-error";
import {
APP_LIST_GRID_CLASS,
AppList,
APP_LIST_GRID_ROW_CLASS,
} from "./app-list-row";
import { CreateAppPopover } from "./create-app-popover";
import { useSetPageTitle } from "./layout/HeaderActions";
import { mergeOtherAppEntries, OtherAppsSection } from "./other-apps-section";
import { Button } from "./ui/button";
import {
Collapsible,
CollapsibleContent,
CollapsibleTrigger,
} from "./ui/collapsible";
import { Skeleton } from "./ui/skeleton";
import { WorkspaceAppCard } from "./workspace-app-card";
import {
WorkspaceAppSearch,
WorkspaceAppSearchEmpty,
} from "./workspace-app-search";
import type { CuratedWorkspaceTemplatesResult } from "./workspace-template-card";
function SectionHeader({
title,
action,
}: {
title: string;
action?: ReactNode;
}) {
return (
{title}
{action ?
{action}
: null}
);
}
function CommandPanel() {
const t = useT();
const {
availableModels,
isLoading: modelListLoading,
onEffortChange,
onModelChange,
selectedEffort,
selectedEngine,
selectedModel,
} = useChatModels({ storageKey: chatModelSelectionStorageKey("dispatch") });
const navigate = useNavigate();
const promptSuggestions = [
t("dispatch.pages.suggestionOnboardingApp", {
defaultValue: "Create an app for onboarding requests",
}),
t("dispatch.pages.suggestionAnalyticsAgents", {
defaultValue: "Check which agents can help with analytics",
}),
];
function send(message: string) {
const trimmed = message.trim();
if (!trimmed) return;
navigateWithAgentChatViewTransition(navigate, "/chat", {
state: {
dispatchPrompt: {
id: `${Date.now()}-${Math.random().toString(36).slice(2, 8)}`,
message: trimmed,
selectedModel,
selectedEngine,
selectedEffort,
},
},
});
}
return (
{t("dispatch.pages.chatAcrossApps", {
defaultValue: "Chat across your apps",
})}
{t("dispatch.pages.chatAcrossAppsDescription", {
defaultValue:
"Route work, inspect status, or create something new from one place.",
})}
send(text)}
/>
{promptSuggestions.map((suggestion) => (
send(suggestion)}
className="cursor-pointer rounded-md border border-transparent bg-muted/60 px-2.5 py-1.5 text-xs text-muted-foreground transition-[background-color,color] hover:bg-muted hover:text-foreground"
>
{suggestion}
))}
);
}
function AppsPanel({
apps,
isLoading,
connectedApps,
connectedAppsError,
connectedAppsLoading,
onRetryConnectedApps,
curatedTemplates,
curatedTemplatesError,
curatedTemplatesLoading,
onRetryCuratedTemplates,
}: {
apps: WorkspaceAppSummary[];
isLoading: boolean;
connectedApps: ConnectedAppSummary[];
connectedAppsError?: Error | null;
connectedAppsLoading: boolean;
onRetryConnectedApps: () => void;
curatedTemplates?: CuratedWorkspaceTemplatesResult;
curatedTemplatesError?: Error | null;
curatedTemplatesLoading: boolean;
onRetryCuratedTemplates: () => void;
}) {
const t = useT();
const [showPending, setShowPending] = useState(false);
const [searchQuery, setSearchQuery] = useState("");
const { layout, persistenceError, togglePinned } = useWorkspaceAppLayout();
const visibleApps = apps.filter(
(app) => isWorkspaceAppVisibleInDefaultLaunchers(app) && !app.archived,
);
const activeApps = visibleApps.filter((app) => app.status !== "pending");
const pendingApps = visibleApps.filter((app) => app.status === "pending");
const orderedActiveApps = orderWorkspaceApps(activeApps, layout);
const orderedPendingApps = orderWorkspaceApps(pendingApps, layout);
const filteredActiveApps = orderedActiveApps.filter((app) =>
workspaceAppMatchesQuery(app, searchQuery),
);
const filteredPendingApps = orderedPendingApps.filter((app) =>
workspaceAppMatchesQuery(app, searchQuery),
);
const hasSearchResults =
filteredActiveApps.length > 0 || filteredPendingApps.length > 0;
const otherAppEntries = mergeOtherAppEntries({
templates: curatedTemplates,
connectedApps,
workspaceApps: apps,
});
const showSkeletons =
isLoading && activeApps.length === 0 && pendingApps.length === 0;
return (
View all
{!showSkeletons && visibleApps.length > 0 ? (
) : null}
{t("dispatch.pages.newApp", { defaultValue: "New" })}
}
/>
}
/>
{!showSkeletons && visibleApps.length > 0 && persistenceError ? (
{persistenceError === "both"
? t("dispatch.pages.appPinSaveFailed", {
defaultValue: "App pins could not be saved.",
})
: t("dispatch.pages.appPinSavedLocally", {
defaultValue: "App pins are saved on this device only.",
})}
) : null}
{showSkeletons ? (
) : searchQuery.trim() && !hasSearchResults ? (
setSearchQuery("")}
/>
) : (
<>
{filteredActiveApps.map((app) => (
togglePinned(app.id)}
/>
))}
{filteredActiveApps.length === 0 &&
!searchQuery.trim() &&
otherAppEntries.length === 0 &&
!curatedTemplatesLoading &&
!connectedAppsLoading &&
!curatedTemplatesError &&
!connectedAppsError ? (
{t("dispatch.pages.noApps", {
defaultValue: "No apps yet.",
})}
) : null}
{!searchQuery.trim() ? (
) : null}
>
)}
{pendingApps.length > 0 &&
(!searchQuery.trim() || filteredPendingApps.length > 0) ? (
{t("dispatch.pages.pendingApps")}
{t("dispatch.pages.pendingAppsDescription", {
count: pendingApps.length,
})}
{showPending || Boolean(searchQuery.trim())
? t("dispatch.pages.hidePendingApps")
: t("dispatch.pages.showPendingApps")}
{filteredPendingApps.map((app) => (
))}
) : null}
);
}
function OverviewAppsSkeleton() {
return (
{Array.from({ length: 4 }).map((_, index) => (
))}
);
}
export function DispatchControlPlane() {
useSetPageTitle( );
const appsQuery = useActionQuery(
"list-workspace-apps",
{ includeAgentCards: false, includeArchived: true },
);
const connectedAppsQuery = useActionQuery(
"list-connected-agents",
{},
);
const curatedTemplatesQuery = useActionQuery(
"list-curated-workspace-templates",
{},
);
const { data: workspaceApps = [], isLoading: appsLoading } = appsQuery;
return (
{appsQuery.isError ? (
void appsQuery.refetch()}
/>
) : (
void connectedAppsQuery.refetch()}
curatedTemplates={curatedTemplatesQuery.data}
curatedTemplatesError={curatedTemplatesQuery.error}
curatedTemplatesLoading={curatedTemplatesQuery.isLoading}
onRetryCuratedTemplates={() => void curatedTemplatesQuery.refetch()}
/>
)}
);
}