feat(8ij.4): add LaunchSwarmDialog to TopBar as global action
Co-Authored-By: Oz <oz-agent@warp.dev>
This commit is contained in:
parent
6b8aa408c8
commit
ae7f13c3af
2 changed files with 61 additions and 2 deletions
|
|
@ -1,10 +1,11 @@
|
||||||
'use client';
|
'use client';
|
||||||
|
|
||||||
import { ReactNode } from 'react';
|
import { ReactNode, useState } from 'react';
|
||||||
import { LayoutGrid, Lock, Plus, Sidebar, SidebarClose } from 'lucide-react';
|
import { LayoutGrid, Lock, Plus, Sidebar, SidebarClose, Rocket } from 'lucide-react';
|
||||||
import { useUrlState } from '../../hooks/use-url-state';
|
import { useUrlState } from '../../hooks/use-url-state';
|
||||||
import { useResponsive } from '../../hooks/use-responsive';
|
import { useResponsive } from '../../hooks/use-responsive';
|
||||||
import { ThemeToggle } from './theme-toggle';
|
import { ThemeToggle } from './theme-toggle';
|
||||||
|
import { LaunchSwarmDialog } from '../swarm/launch-dialog';
|
||||||
|
|
||||||
export interface TopBarProps {
|
export interface TopBarProps {
|
||||||
onCreateTask?: () => Promise<void> | void;
|
onCreateTask?: () => Promise<void> | void;
|
||||||
|
|
@ -15,6 +16,9 @@ export interface TopBarProps {
|
||||||
criticalAlerts?: number;
|
criticalAlerts?: number;
|
||||||
idleCount?: number;
|
idleCount?: number;
|
||||||
busyCount?: number;
|
busyCount?: number;
|
||||||
|
actor?: string;
|
||||||
|
onActorChange?: (name: string) => void;
|
||||||
|
projectRoot?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface MetricTileProps {
|
interface MetricTileProps {
|
||||||
|
|
@ -42,6 +46,36 @@ function MetricTile({ label, value, accent = 'info' }: MetricTileProps) {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function IdentityChip({ actor, onActorChange }: { actor: string; onActorChange: (name: string) => void }) {
|
||||||
|
const [editing, setEditing] = useState(false);
|
||||||
|
|
||||||
|
if (editing) {
|
||||||
|
return (
|
||||||
|
<input
|
||||||
|
autoFocus
|
||||||
|
type="text"
|
||||||
|
value={actor}
|
||||||
|
onChange={e => onActorChange(e.target.value)}
|
||||||
|
onBlur={() => setEditing(false)}
|
||||||
|
onKeyDown={e => { if (e.key === 'Enter') setEditing(false); }}
|
||||||
|
placeholder="your name"
|
||||||
|
className="h-7 w-28 rounded-full border border-[var(--accent-info)] bg-[var(--surface-tertiary)] px-3 text-xs text-[var(--text-primary)] outline-none"
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
onClick={() => setEditing(true)}
|
||||||
|
title="Set your operator name"
|
||||||
|
className="inline-flex h-7 items-center rounded-full border border-[var(--border-subtle)] bg-[var(--surface-tertiary)] px-3 text-xs text-[var(--text-secondary)] transition-colors hover:border-[var(--accent-info)] hover:text-[var(--text-primary)]"
|
||||||
|
>
|
||||||
|
{actor || <span className="text-[var(--text-tertiary)]">your name</span>}
|
||||||
|
</button>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
export function TopBar({
|
export function TopBar({
|
||||||
onCreateTask,
|
onCreateTask,
|
||||||
isCreatingTask = false,
|
isCreatingTask = false,
|
||||||
|
|
@ -51,9 +85,13 @@ export function TopBar({
|
||||||
criticalAlerts = 0,
|
criticalAlerts = 0,
|
||||||
idleCount = 0,
|
idleCount = 0,
|
||||||
busyCount = 0,
|
busyCount = 0,
|
||||||
|
actor = '',
|
||||||
|
onActorChange,
|
||||||
|
projectRoot,
|
||||||
}: TopBarProps) {
|
}: TopBarProps) {
|
||||||
const { leftPanel, toggleLeftPanel, rightPanel, toggleRightPanel, blockedOnly, toggleBlockedOnly } = useUrlState();
|
const { leftPanel, toggleLeftPanel, rightPanel, toggleRightPanel, blockedOnly, toggleBlockedOnly } = useUrlState();
|
||||||
const { isDesktop } = useResponsive();
|
const { isDesktop } = useResponsive();
|
||||||
|
const [showLaunchSwarm, setShowLaunchSwarm] = useState(false);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<header className="flex h-[var(--topbar-height)] items-center justify-between border-b border-[var(--border-strong)] bg-[var(--surface-elevated)]" data-testid="top-bar">
|
<header className="flex h-[var(--topbar-height)] items-center justify-between border-b border-[var(--border-strong)] bg-[var(--surface-elevated)]" data-testid="top-bar">
|
||||||
|
|
@ -113,6 +151,17 @@ export function TopBar({
|
||||||
</span>
|
</span>
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
|
{projectRoot && (
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
onClick={() => setShowLaunchSwarm(true)}
|
||||||
|
className="inline-flex items-center gap-2 rounded-xl border border-emerald-500/20 bg-emerald-500/10 px-4 py-2 text-xs font-semibold uppercase tracking-[0.11em] text-emerald-400 transition-colors hover:bg-emerald-500/20 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-[var(--accent-info)]"
|
||||||
|
>
|
||||||
|
<Rocket className="h-3.5 w-3.5" aria-hidden="true" />
|
||||||
|
Launch Swarm
|
||||||
|
</button>
|
||||||
|
)}
|
||||||
|
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
|
|
@ -128,6 +177,8 @@ export function TopBar({
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
{onActorChange ? <IdentityChip actor={actor} onActorChange={onActorChange} /> : null}
|
||||||
|
|
||||||
<ThemeToggle />
|
<ThemeToggle />
|
||||||
|
|
||||||
{isDesktop ? (
|
{isDesktop ? (
|
||||||
|
|
@ -147,6 +198,13 @@ export function TopBar({
|
||||||
{taskActionMessage ?? ''}
|
{taskActionMessage ?? ''}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{showLaunchSwarm && projectRoot && (
|
||||||
|
<LaunchSwarmDialog
|
||||||
|
projectRoot={projectRoot}
|
||||||
|
onSuccess={() => setShowLaunchSwarm(false)}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
</header>
|
</header>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -192,6 +192,7 @@ export function UnifiedShell({
|
||||||
idleCount={0}
|
idleCount={0}
|
||||||
actor={actor}
|
actor={actor}
|
||||||
onActorChange={handleActorChange}
|
onActorChange={handleActorChange}
|
||||||
|
projectRoot={projectRoot}
|
||||||
/>
|
/>
|
||||||
{!bdHealth.loading && !bdHealth.healthy ? (
|
{!bdHealth.loading && !bdHealth.healthy ? (
|
||||||
<div className="border-b border-amber-500/35 bg-amber-500/12 px-4 py-2 text-xs text-amber-100">
|
<div className="border-b border-amber-500/35 bg-amber-500/12 px-4 py-2 text-xs text-amber-100">
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue