File size: 4,373 Bytes
0b9dc2e
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
import { Bot, Crown } from 'lucide-react';
import { useNavigate } from 'react-router-dom';

import type { TeamDetailResponse, TeamMemberInfo, AgentRecord } from '@/api';
import {
	Sidebar,
	SidebarContent,
	SidebarFooter,
	SidebarGroup,
	SidebarGroupContent,
	SidebarGroupLabel,
	SidebarHeader,
	SidebarMenu,
	SidebarMenuButton,
	SidebarMenuItem,
} from '@/components/ui/sidebar';
import { useTranslation } from '@/i18n/useI18n';

interface TeamSidebarProps {
	/** Resolved team detail (leader + members) — drives all rendering. */
	team: TeamDetailResponse;
	/**

	 * The session id currently shown in the chat area. Used both for

	 * highlighting the active row and for deciding whether the row

	 * being clicked is already the current one.

	 */
	currentSessionId: string;
}

/**

 * Secondary sidebar shown next to the chat area whenever the open

 * session participates in a team.

 *

 * Built on the shared shadcn `Sidebar` primitives so it visually

 * matches the main page sidebar (same paddings, item heights,

 * active-row treatment, etc.). Renders the leader at the top

 * followed by every member; all rows are clickable.

 *

 * Clicking the leader row navigates to

 * `/chat/<leaderAgentId>/<leaderSessionId>` (drops the URL's

 * optional `:memberId` slot so the chat area falls back to the

 * leader session). Clicking a member row navigates to

 * `/chat/<leaderAgentId>/<leaderSessionId>/<memberAgentId>` — the

 * outer URL slots stay the same so the main page sidebar does not

 * collapse; only the chat area reroutes to the member's session.

 *

 * Both `leaderAgentId` and `leaderSessionId` are derived from the

 * passed `team` prop, so the parent only needs to know which

 * session is currently active.

 *

 * @param team - Resolved team detail.

 * @param currentSessionId - Session id currently shown in the chat

 *   area; used to drive row highlighting.

 * @returns A vertical sidebar element.

 */
export function TeamSidebar({ team, currentSessionId }: TeamSidebarProps) {
	const { t } = useTranslation();
	const navigate = useNavigate();

	const leaderAgentId = team.leader_agent?.id ?? null;
	const leaderSessionId = team.team.session_id;

	const goToLeader = () => {
		if (!leaderAgentId) return;
		navigate(`/chat/${leaderAgentId}/${leaderSessionId}`);
	};

	const goToMember = (memberAgentId: string) => {
		if (!leaderAgentId) return;
		navigate(`/chat/${leaderAgentId}/${leaderSessionId}/${memberAgentId}`);
	};

	const renderLeader = (leader: AgentRecord) => (
		<SidebarMenuItem>

			<SidebarMenuButton isActive={currentSessionId === leaderSessionId} onClick={goToLeader}>

				<Crown />

				<span className="truncate">{leader.data.name}</span>

			</SidebarMenuButton>

		</SidebarMenuItem>
	);

	const renderMember = (member: TeamMemberInfo) => (
		<SidebarMenuItem key={member.agent.id}>

			<SidebarMenuButton

				isActive={member.session_id === currentSessionId}

				disabled={member.session_id === null}

				onClick={() => goToMember(member.agent.id)}

			>

				<Bot />

				<span className="truncate">{member.agent.data.name}</span>

			</SidebarMenuButton>

		</SidebarMenuItem>
	);

	return (
		<Sidebar collapsible="none" className="w-56 border-r">

			<SidebarHeader>

				<div className="flex flex-col gap-y-1 px-2 py-1">

					<span className="text-muted-foreground text-xs uppercase tracking-wide">

						{t('common.team')}

					</span>

					<span className="truncate text-sm font-medium">{team.team.data.name}</span>

				</div>

			</SidebarHeader>

			<SidebarContent>

				{team.leader_agent && (

					<SidebarGroup>

						<SidebarGroupLabel>{t('common.leader')}</SidebarGroupLabel>

						<SidebarGroupContent>

							<SidebarMenu>{renderLeader(team.leader_agent)}</SidebarMenu>

						</SidebarGroupContent>

					</SidebarGroup>

				)}



				<SidebarGroup>

					<SidebarGroupLabel>{t('team-sidebar.membersHeading')}</SidebarGroupLabel>

					<SidebarGroupContent>

						{team.members.length === 0 ? (

							<p className="px-3 py-2 text-xs text-muted-foreground">

								{t('team-sidebar.noMembers')}

							</p>

						) : (

							<SidebarMenu>{team.members.map(renderMember)}</SidebarMenu>

						)}

					</SidebarGroupContent>

				</SidebarGroup>

			</SidebarContent>

			<SidebarFooter />

		</Sidebar>
	);
}