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) => ( ))}
); } 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 (
{!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, })}

{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()} /> )}
); }