agent-ecosystem/src/renderer/components/team/TeamProvisioningBanner.tsx
iliya 88722598ac feat: enhance UI styling and functionality for extension components
- Added zebra striping to extension lists for improved readability across various screen sizes.
- Updated the ExtensionStoreView to include margin adjustments for better layout.
- Enhanced InstallButton component to manage pending actions more effectively, improving user feedback during installation processes.
- Refactored McpServersPanel and SkillsPanel to apply new grid classes for consistent styling.
- Adjusted CapabilityChips and CategoryChips for better spacing and visual consistency.
- Improved the TeamProvisioningBanner to handle state changes more efficiently.
- Updated KanbanTaskCard to replace icons for better clarity in task actions.
- Enhanced teamSlice to manage provisioning runs and spawn statuses more effectively, ensuring better state handling.
2026-03-12 15:08:26 +02:00

162 lines
5.4 KiB
TypeScript

import { useEffect, useMemo, useState } from 'react';
import { Button } from '@renderer/components/ui/button';
import { useStore } from '@renderer/store';
import { getCurrentProvisioningProgressForTeam } from '@renderer/store/slices/teamSlice';
import { CheckCircle2, X } from 'lucide-react';
import { useShallow } from 'zustand/react/shallow';
import { ProvisioningProgressBlock } from './ProvisioningProgressBlock';
import { STEP_ORDER } from './provisioningSteps';
import type { ProvisioningStep } from './provisioningSteps';
interface TeamProvisioningBannerProps {
teamName: string;
}
export const TeamProvisioningBanner = ({
teamName,
}: TeamProvisioningBannerProps): React.JSX.Element | null => {
const { progress, cancelProvisioning, teamMembers } = useStore(
useShallow((s) => ({
progress: getCurrentProvisioningProgressForTeam(s, teamName),
cancelProvisioning: s.cancelProvisioning,
teamMembers: s.selectedTeamData?.members,
}))
);
const [dismissed, setDismissed] = useState(false);
const bannerInstanceKey = useMemo(() => {
if (!progress) return null;
return `${teamName}:${progress.runId}:${progress.startedAt}`;
}, [teamName, progress?.runId, progress?.startedAt]);
useEffect(() => {
setDismissed(false);
}, [bannerInstanceKey]);
// NOTE: we intentionally do NOT auto-dismiss "ready" banners.
// Users frequently need to inspect launch output after fast stop→start cycles,
// and auto-dismiss can make it look like no progress/logs were produced.
if (!progress || dismissed) {
return null;
}
if (progress.state === 'cancelled' || progress.state === 'disconnected') {
return null;
}
const isReady = progress.state === 'ready';
const isFailed = progress.state === 'failed';
const isActive =
progress.state === 'validating' ||
progress.state === 'spawning' ||
progress.state === 'monitoring' ||
progress.state === 'verifying';
const canCancel =
progress.state === 'spawning' ||
progress.state === 'monitoring' ||
progress.state === 'verifying';
const progressStepIndex = STEP_ORDER.indexOf(progress.state as ProvisioningStep);
if (isFailed) {
return (
<div className="mb-3">
<div className="mb-2 flex items-center gap-2 rounded-md border border-red-500/40 bg-red-500/10 px-3 py-2">
<p className="flex-1 text-xs text-[var(--step-error-text)]">{progress.message}</p>
<Button
variant="outline"
size="sm"
className="h-6 shrink-0 border-red-500/40 px-2 text-xs text-[var(--step-error-text)] hover:bg-red-500/10"
onClick={() => setDismissed(true)}
>
<X size={12} />
</Button>
</div>
<ProvisioningProgressBlock
key={progress.runId}
title="Launch failed"
message={progress.error ?? null}
tone="error"
currentStepIndex={progressStepIndex >= 0 ? progressStepIndex : -1}
startedAt={progress.startedAt}
pid={progress.pid}
cliLogsTail={progress.cliLogsTail}
assistantOutput={progress.assistantOutput}
defaultLiveOutputOpen
onCancel={null}
/>
</div>
);
}
const allTeammatesOnline =
teamMembers != null &&
teamMembers.length > 0 &&
teamMembers.every((m) => m.status === 'active' || m.status === 'idle');
if (isReady) {
const readyMessage = allTeammatesOnline
? `Team launched — all ${teamMembers!.length} teammates online`
: 'Team launched — teammates may still be starting';
return (
<div className="mb-3">
<div className="flex items-center gap-2 rounded-md border border-[var(--step-done-border)] bg-[var(--step-done-bg)] px-3 py-2">
<CheckCircle2 size={14} className="shrink-0 text-[var(--step-done-text)]" />
<p className="flex-1 text-xs text-[var(--step-success-text)]">{readyMessage}</p>
<Button
variant="outline"
size="sm"
className="h-6 shrink-0 border-[var(--step-done-border)] px-2 text-xs text-[var(--step-done-text)] hover:bg-[var(--step-done-bg)]"
onClick={() => setDismissed(true)}
>
<X size={12} />
</Button>
</div>
<ProvisioningProgressBlock
key={progress.runId}
title="Launch details"
message={progress.message}
currentStepIndex={progressStepIndex >= 0 ? progressStepIndex : -1}
startedAt={progress.startedAt}
pid={progress.pid}
cliLogsTail={progress.cliLogsTail}
assistantOutput={progress.assistantOutput}
defaultLiveOutputOpen={false}
onCancel={null}
/>
</div>
);
}
if (isActive) {
return (
<div className="mb-3">
<ProvisioningProgressBlock
key={progress.runId}
title="Launching team"
message={progress.message}
currentStepIndex={progressStepIndex >= 0 ? progressStepIndex : -1}
loading
startedAt={progress.startedAt}
pid={progress.pid}
cliLogsTail={progress.cliLogsTail}
assistantOutput={progress.assistantOutput}
defaultLiveOutputOpen
onCancel={
canCancel
? () => {
void cancelProvisioning(progress.runId);
}
: null
}
/>
</div>
);
}
return null;
};