diff --git a/src/renderer/components/team/TeamDetailView.tsx b/src/renderer/components/team/TeamDetailView.tsx index 15255d28..9dca9155 100644 --- a/src/renderer/components/team/TeamDetailView.tsx +++ b/src/renderer/components/team/TeamDetailView.tsx @@ -1,4 +1,4 @@ -import { lazy, Suspense, useCallback, useEffect, useMemo, useRef, useState } from 'react'; +import { lazy, memo, Suspense, useCallback, useEffect, useMemo, useRef, useState } from 'react'; import { api } from '@renderer/api'; import { SessionContextPanel } from '@renderer/components/chat/SessionContextPanel/index'; @@ -202,6 +202,69 @@ function filterKanbanTasks(tasks: TeamTaskWithKanban[], query: string): TeamTask ); } +const TeamOfflineStatusBanner = memo(function TeamOfflineStatusBanner({ + teamName, + onLaunch, +}: { + teamName: string; + onLaunch: () => void; +}): React.JSX.Element { + const summary = useStore( + useShallow((s) => { + const team = s.teamByName[teamName]; + if (!team) { + return null; + } + + return { + memberCount: team.memberCount, + expectedMemberCount: team.expectedMemberCount, + confirmedCount: team.confirmedCount, + runtimeAlivePendingCount: team.runtimeAlivePendingCount, + teamLaunchState: team.teamLaunchState, + partialLaunchFailure: team.partialLaunchFailure, + missingMemberCount: team.missingMembers?.length ?? 0, + }; + }) + ); + + const message = + summary?.teamLaunchState === 'partial_pending' + ? summary.runtimeAlivePendingCount != null && summary.runtimeAlivePendingCount > 0 + ? `Last launch is still reconciling - ${summary.confirmedCount ?? 0}/${summary.expectedMemberCount ?? summary.memberCount} teammates confirmed alive, ${summary.runtimeAlivePendingCount} runtime${summary.runtimeAlivePendingCount === 1 ? '' : 's'} pending bootstrap` + : 'Last launch is still reconciling' + : summary?.partialLaunchFailure + ? summary.missingMemberCount > 0 + ? `Last launch failed partway - ${summary.missingMemberCount}/${summary.expectedMemberCount ?? summary.missingMemberCount} teammates did not join` + : 'Last launch failed partway' + : 'Team is offline'; + + return ( +
+ + + {message} + + +
+ ); +}); + export const TeamDetailView = ({ teamName, isPaneFocused = false, @@ -730,24 +793,6 @@ export const TeamDetailView = ({ } }, [kanbanFilterQuery, clearKanbanFilter]); - const currentTeamSummary = useStore( - useShallow((s) => { - const team = teamName ? s.teamByName[teamName] : undefined; - if (!team) return null; - return { - displayName: team.displayName, - projectPath: team.projectPath, - memberCount: team.memberCount, - expectedMemberCount: team.expectedMemberCount, - confirmedCount: team.confirmedCount, - runtimeAlivePendingCount: team.runtimeAlivePendingCount, - teamLaunchState: team.teamLaunchState, - partialLaunchFailure: team.partialLaunchFailure, - missingMemberCount: team.missingMembers?.length ?? 0, - }; - }) - ); - // Load sessions for the team's project const projectId = useMemo( () => resolveProjectIdByPath(data?.config.projectPath, projects, repositoryGroups), @@ -1397,6 +1442,10 @@ export const TeamDetailView = ({ } if (error === 'TEAM_DRAFT') { + const draftTeamSummary = useStore.getState().teamByName[teamName]; + const draftDisplayName = draftTeamSummary?.displayName || teamName; + const draftMemberCount = draftTeamSummary?.memberCount ?? 0; + return ( <>
@@ -1407,11 +1456,10 @@ export const TeamDetailView = ({

Team not launched yet

- This is a draft team —{' '} - {currentTeamSummary?.displayName || teamName} has been configured - with {currentTeamSummary?.memberCount ?? 0} member - {currentTeamSummary?.memberCount === 1 ? '' : 's'} but hasn't been provisioned - by CLI yet. Click Launch to select a model and start the team. + This is a draft team - {draftDisplayName} has been configured with{' '} + {draftMemberCount} member + {draftMemberCount === 1 ? '' : 's'} but hasn't been provisioned by CLI yet. + Click Launch to select a model and start the team.

{!data.isAlive && !isTeamProvisioning ? ( -
- - - {currentTeamSummary?.teamLaunchState === 'partial_pending' - ? currentTeamSummary.runtimeAlivePendingCount && - currentTeamSummary.runtimeAlivePendingCount > 0 - ? `Last launch is still reconciling — ${currentTeamSummary.confirmedCount ?? 0}/${currentTeamSummary.expectedMemberCount ?? currentTeamSummary.memberCount} teammates confirmed alive, ${currentTeamSummary.runtimeAlivePendingCount} runtime${currentTeamSummary.runtimeAlivePendingCount === 1 ? '' : 's'} pending bootstrap` - : 'Last launch is still reconciling' - : currentTeamSummary?.partialLaunchFailure - ? currentTeamSummary.missingMemberCount > 0 - ? `Last launch failed partway — ${currentTeamSummary.missingMemberCount}/${currentTeamSummary.expectedMemberCount ?? currentTeamSummary.missingMemberCount} teammates did not join` - : 'Last launch failed partway' - : 'Team is offline'} - - -
+ setLaunchDialogOpen(true)} + /> ) : null}