agent-ecosystem/src/renderer/components/team/TeamProvisioningBanner.tsx
iliya 0e627c4b3f refactor: clean up TeamProvisioningService and enhance TaskCommentsSection UI
- Removed outdated OAuth token warning logic from TeamProvisioningService to streamline authentication handling.
- Updated TeamProvisioningBanner to prevent auto-dismissal of "ready" state, allowing users to inspect logs after rapid provisioning cycles.
- Enhanced TaskCommentsSection UI by integrating MemberBadge for author display and improving comment styling for better visual clarity.

Made-with: Cursor
2026-03-03 16:46:14 +02:00

183 lines
5.8 KiB
TypeScript

import { useEffect, useRef, useState } from 'react';
import { Button } from '@renderer/components/ui/button';
import { useStore } from '@renderer/store';
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';
import type { TeamProvisioningProgress } from '@shared/types';
interface TeamProvisioningBannerProps {
teamName: string;
}
function findProgressForTeam(
runs: Record<string, TeamProvisioningProgress>,
teamName: string
): TeamProvisioningProgress | null {
const entries = Object.values(runs);
const matching = entries
.filter((r) => r.teamName === teamName)
.sort((a, b) => b.updatedAt.localeCompare(a.updatedAt));
return matching[0] ?? null;
}
export const TeamProvisioningBanner = ({
teamName,
}: TeamProvisioningBannerProps): React.JSX.Element | null => {
const { provisioningRuns, cancelProvisioning } = useStore(
useShallow((s) => ({
provisioningRuns: s.provisioningRuns,
cancelProvisioning: s.cancelProvisioning,
}))
);
const progress = findProgressForTeam(provisioningRuns, teamName);
const [dismissed, setDismissed] = useState(false);
const prevRunIdRef = useRef(progress?.runId);
if (prevRunIdRef.current !== progress?.runId) {
prevRunIdRef.current = progress?.runId;
if (dismissed) {
setDismissed(false);
}
}
// 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') {
return null;
}
const isReady = progress.state === 'ready';
const isFailed = progress.state === 'failed';
const isDisconnected = progress.state === 'disconnected';
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-red-200">{progress.message}</p>
<Button
variant="outline"
size="sm"
className="h-6 shrink-0 border-red-500/40 px-2 text-xs text-red-300 hover:bg-red-500/10 hover:text-red-200"
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>
);
}
if (isDisconnected) {
return (
<div className="mb-3 flex items-center gap-2 rounded-md border border-amber-500/40 bg-amber-500/10 px-3 py-2">
<p className="flex-1 text-xs text-amber-200">Team offline</p>
<Button
variant="outline"
size="sm"
className="h-6 shrink-0 border-amber-500/40 px-2 text-xs text-amber-300 hover:bg-amber-500/10 hover:text-amber-200"
onClick={() => setDismissed(true)}
>
<X size={12} />
</Button>
</div>
);
}
if (isReady) {
return (
<div className="mb-3">
<div className="mb-2 flex items-center gap-2 rounded-md border border-emerald-500/40 bg-emerald-500/10 px-3 py-2">
<CheckCircle2 size={14} className="shrink-0 text-emerald-400" />
<p className="flex-1 text-xs text-emerald-200">Team launched process alive</p>
<Button
variant="outline"
size="sm"
className="h-6 shrink-0 border-emerald-500/40 px-2 text-xs text-emerald-300 hover:bg-emerald-500/10 hover:text-emerald-200"
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;
};