Files
atc-agents/ui/src/App.tsx
T

219 lines
9.3 KiB
TypeScript
Raw Normal View History

import { useRef, useState } from 'react'
import { ChevronDown, ChevronUp } from 'lucide-react'
import { useClock } from './hooks/useClock'
import { useCommandCenter } from './hooks/useCommandCenter'
import { useLiveMetrics } from './hooks/useLiveMetrics'
import { SideNav } from './components/layout/SideNav'
import { TopBar } from './components/layout/TopBar'
import { AgentFleet } from './components/features/AgentFleet'
import { ApprovalInbox } from './components/features/ApprovalInbox'
import { ChatDrawer } from './components/features/ChatDrawer'
import { GpuMonitor } from './components/features/GpuMonitor'
import { InfraQuickAccess } from './components/features/InfraQuickAccess'
import { InspectorPanel } from './components/features/InspectorPanel'
import { PlatformView } from './components/features/PlatformView'
import { DataQualityView } from './components/features/DataQualityView'
import { KnowledgeChatView } from './components/features/KnowledgeChatView'
import { StorageView } from './components/features/StorageView'
import { ChangesView } from './components/features/ChangesView'
import { DataFlowView } from './components/features/DataFlowView'
import { SearchView } from './components/features/SearchView'
import { DataExplorerView } from './components/features/DataExplorerView'
import { DataHubView } from './components/features/DataHubView'
import { SshTerminal } from './components/features/SshTerminal'
import { TerminalDock } from './components/features/TerminalDock'
import { WorkbenchPanel } from './components/features/WorkbenchPanel'
import { resolveInfraNode } from './lib/infraCatalog'
import { cn } from './lib/utils'
export default function App() {
const clock = useClock()
const cc = useCommandCenter()
const [gpuChatActive, setGpuChatActive] = useState(false)
const [infraOpen, setInfraOpen] = useState(false)
const [sshOpen, setSshOpen] = useState(false)
const gpuBoost = gpuChatActive || cc.mainView === 'knowledge'
const { agentLoads, gpuLive } = useLiveMetrics(cc.agents, cc.gpu, cc.anims, gpuBoost)
const mainScrollRef = useRef<HTMLDivElement>(null)
const isPlatform = cc.mainView === 'platform'
const isDataSources = cc.mainView === 'datasources' || cc.mainView === 'hdfs'
const openApprovals = () => {
cc.setMainView('approvals')
mainScrollRef.current?.scrollTo({ top: 0, behavior: 'smooth' })
}
const terminalSubject = cc.terminalSubjectId
const terminalLabel = (() => {
if (cc.selectedAgent) return cc.selectedAgent.name.split(' ·')[0]
const infra = resolveInfraNode(terminalSubject)
if (infra) return infra.label
if (cc.selectedNode) return cc.selectedNode.label
return 'Lab'
})()
return (
<div className="flex h-full flex-col overflow-hidden bg-surface">
<TopBar
clock={clock}
status={cc.status}
workload={cc.workload}
agents={cc.agents}
approvals={cc.approvals}
onApprovalsClick={openApprovals}
/>
<div className="flex min-h-0 flex-1 overflow-hidden">
<SideNav
workload={cc.workload}
gpu={cc.gpu}
gpuLive={gpuLive}
gpuBoost={gpuBoost}
selectedNodeId={cc.selectedNodeId}
mainView={cc.mainView}
approvalCount={cc.approvals.length}
onSetMainView={cc.setMainView}
onOpenApprovals={openApprovals}
onSelectZone={cc.selectNode}
onOpenSsh={() => setSshOpen(true)}
/>
<div ref={mainScrollRef} className={cn('flex min-h-0 min-w-0 flex-1 flex-col bg-surface', (isPlatform || isDataSources) ? 'overflow-hidden' : 'scrollbar-thin overflow-y-auto')}>
<div className={cn('flex min-h-0 flex-1', isPlatform ? '' : 'flex-col')}>
<div className={cn('flex min-h-0 min-w-0 flex-1 flex-col', (isPlatform || isDataSources) ? 'gap-1 overflow-hidden p-1.5' : 'min-h-0 gap-2 p-3')}>
{isPlatform && (
<>
<div className="grid shrink-0 grid-cols-1 gap-2 xl:grid-cols-[1fr_auto]">
<AgentFleet
agents={cc.agents}
animations={cc.anims}
selectedId={cc.selectedAgentId}
loads={agentLoads}
approvalCount={cc.approvals.length}
onSelect={cc.selectAgent}
onOpenApprovals={openApprovals}
/>
<GpuMonitor gpu={cc.gpu} live={gpuLive} />
</div>
<button
type="button"
onClick={() => setInfraOpen((v) => !v)}
className="flex shrink-0 items-center gap-1 self-start rounded border border-border/60 px-2 py-0.5 text-[9px] text-foreground-muted hover:border-docker/30 hover:text-docker"
>
{infraOpen ? <ChevronUp className="h-3 w-3" /> : <ChevronDown className="h-3 w-3" />}
Infrastructure & Apps
</button>
{infraOpen && (
<InfraQuickAccess
workload={cc.workload}
agents={cc.agents}
selectedNodeId={cc.selectedNodeId}
busy={cc.nodeBusy}
onSelectNode={cc.selectNode}
onSelectAgent={cc.selectAgent}
onProbe={cc.probeNodeId}
onOpenTerminal={cc.openTerminal}
/>
)}
</>
)}
<div className={cn('flex min-h-0 flex-col', (isPlatform || isDataSources) ? 'min-h-0 flex-1 overflow-hidden' : 'min-h-0 flex-1')}>
{cc.mainView === 'platform' ? (
<PlatformView
workload={cc.workload}
animations={cc.anims}
selectedNodeId={cc.selectedNodeId}
onNodeClick={cc.selectNode}
pulse={cc.genPulse}
/>
) : cc.mainView === 'datasources' || cc.mainView === 'hdfs' ? (
<DataHubView focusEngine={cc.dataSourceFocus} initialTab={cc.mainView === 'hdfs' ? 'hadoop' : 'sources'} onPulse={cc.pulseFlow} />
) : cc.mainView === 'changes' ? (
<ChangesView liveChanges={cc.changes} />
) : cc.mainView === 'dataflow' ? (
<DataFlowView />
) : cc.mainView === 'dataquality' ? (
<DataQualityView />
) : cc.mainView === 'knowledge' ? (
<KnowledgeChatView onGpuActivity={setGpuChatActive} />
) : cc.mainView === 'storage' ? (
<StorageView />
) : cc.mainView === 'search' ? (
<SearchView />
) : cc.mainView === 'dataexplorer' ? (
<DataExplorerView />
) : (
<ApprovalInbox agents={cc.agents} livePending={cc.approvals} onDecide={cc.decide} />
)}
</div>
{isPlatform && cc.workbenchMode && (
<WorkbenchPanel
mode={cc.workbenchMode}
agent={cc.selectedAgent}
lines={cc.inspectorLines}
busy={cc.nodeBusy || cc.promptBusy}
onSendPrompt={cc.sendPrompt}
/>
)}
</div>
{isPlatform && (
<div className="flex w-[280px] shrink-0 flex-col border-l border-border">
<InspectorPanel
node={cc.selectedNode}
nodeDetail={cc.nodeDetail}
agent={cc.selectedAgent}
agents={cc.agents}
workload={cc.workload}
gpu={cc.gpu}
feed={cc.feed}
lines={cc.inspectorLines}
busy={cc.nodeBusy}
onProbe={cc.probeNode}
onAsk={cc.askNode}
onSelectAgent={cc.selectAgent}
onSendPrompt={cc.sendPrompt}
onClear={cc.clearSelection}
onOpenTerminal={cc.openTerminal}
onProbeNodeId={cc.probeNodeId}
/>
{!cc.workbenchMode && (
<TerminalDock
subjectId={terminalSubject}
subjectLabel={terminalLabel}
lines={cc.inspectorLines}
busy={cc.nodeBusy || cc.promptBusy}
expanded={cc.terminalExpanded}
onToggle={() => cc.setTerminalExpanded(!cc.terminalExpanded)}
/>
)}
</div>
)}
</div>
</div>
</div>
<ChatDrawer
expanded={cc.chatExpanded}
onToggle={() => cc.setChatExpanded(!cc.chatExpanded)}
chat={cc.chat}
feed={cc.feed}
agents={cc.agents}
approvals={cc.approvals}
selectedAgent={cc.selectedAgent}
promptBusy={cc.promptBusy}
approvalHighlight={cc.approvalHighlight}
filterAgentId={cc.selectedAgentId}
onSendPrompt={cc.sendPrompt}
onDecide={cc.decide}
onDismissHighlight={() => cc.setApprovalHighlight(false)}
/>
<SshTerminal open={sshOpen} onClose={() => setSshOpen(false)} />
</div>
)
}