Skip to content

Commit 6e8fce5

Browse files
feat: implement team selection API and frontend integration
- Add POST /api/v3/select_team endpoint with TeamSelectionRequest model
1 parent 2d957b9 commit 6e8fce5

File tree

4 files changed

+286
-6
lines changed

4 files changed

+286
-6
lines changed

src/backend/v3/api/router.py

Lines changed: 129 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
22
import logging
33
import json
44
from fastapi import APIRouter, Depends, HTTPException, UploadFile, File, Request
5+
from typing import Optional
6+
from pydantic import BaseModel
57

68
from auth.auth_utils import get_authenticated_user_details
79
from common.models.messages_kernel import (
@@ -24,6 +26,13 @@
2426
from v3.models.orchestration_models import AgentType
2527
from common.config.app_config import config
2628

29+
30+
class TeamSelectionRequest(BaseModel):
31+
"""Request model for team selection."""
32+
team_id: str
33+
session_id: Optional[str] = None
34+
35+
2736
app_v3 = APIRouter(
2837
prefix="/api/v3",
2938
responses={404: {"description": "Not found"}},
@@ -597,6 +606,126 @@ async def get_model_deployments_endpoint(request: Request):
597606
raise HTTPException(status_code=500, detail="Internal server error occurred")
598607

599608

609+
@app_v3.post("/select_team")
610+
async def select_team_endpoint(selection: TeamSelectionRequest, request: Request):
611+
"""
612+
Update team selection for a plan or session.
613+
614+
Used when users change teams on the plan page.
615+
616+
---
617+
tags:
618+
- Team Selection
619+
parameters:
620+
- name: user_principal_id
621+
in: header
622+
type: string
623+
required: true
624+
description: User ID extracted from the authentication header
625+
- name: body
626+
in: body
627+
required: true
628+
schema:
629+
type: object
630+
properties:
631+
team_id:
632+
type: string
633+
description: The ID of the team to select
634+
session_id:
635+
type: string
636+
description: Optional session ID to associate with the team selection
637+
responses:
638+
200:
639+
description: Team selection updated successfully
640+
schema:
641+
type: object
642+
properties:
643+
status:
644+
type: string
645+
message:
646+
type: string
647+
team_id:
648+
type: string
649+
team_name:
650+
type: string
651+
session_id:
652+
type: string
653+
400:
654+
description: Invalid request
655+
401:
656+
description: Missing or invalid user information
657+
404:
658+
description: Team configuration not found
659+
"""
660+
# Validate user authentication
661+
authenticated_user = get_authenticated_user_details(request_headers=request.headers)
662+
user_id = authenticated_user["user_principal_id"]
663+
if not user_id:
664+
raise HTTPException(
665+
status_code=401, detail="Missing or invalid user information"
666+
)
667+
668+
if not selection.team_id:
669+
raise HTTPException(status_code=400, detail="Team ID is required")
670+
671+
try:
672+
# Initialize memory store and service
673+
memory_store = await DatabaseFactory.get_database(user_id=user_id)
674+
team_service = TeamService(memory_store)
675+
676+
# Verify the team exists and user has access to it
677+
team_config = await team_service.get_team_configuration(selection.team_id, user_id)
678+
if team_config is None:
679+
raise HTTPException(
680+
status_code=404,
681+
detail=f"Team configuration '{selection.team_id}' not found or access denied"
682+
)
683+
684+
# Generate session ID if not provided
685+
session_id = selection.session_id or str(uuid.uuid4())
686+
687+
# Here you could store the team selection in user preferences, session data, etc.
688+
# For now, we'll just validate and return the selection
689+
690+
# Track the team selection event
691+
track_event_if_configured(
692+
"Team selected",
693+
{
694+
"status": "success",
695+
"team_id": selection.team_id,
696+
"team_name": team_config.name,
697+
"user_id": user_id,
698+
"session_id": session_id,
699+
},
700+
)
701+
702+
return {
703+
"status": "success",
704+
"message": f"Team '{team_config.name}' selected successfully",
705+
"team_id": selection.team_id,
706+
"team_name": team_config.name,
707+
"session_id": session_id,
708+
"agents_count": len(team_config.agents),
709+
"team_description": team_config.description,
710+
}
711+
712+
except HTTPException:
713+
# Re-raise HTTP exceptions
714+
raise
715+
except Exception as e:
716+
logging.error(f"Error selecting team: {str(e)}")
717+
track_event_if_configured(
718+
"Team selection error",
719+
{
720+
"status": "error",
721+
"team_id": selection.team_id,
722+
"user_id": user_id,
723+
"error": str(e),
724+
},
725+
)
726+
raise HTTPException(status_code=500, detail="Internal server error occurred")
727+
728+
600729
@app_v3.get("/search_indexes")
601730
async def get_search_indexes_endpoint(request: Request):
602731
"""

src/frontend/src/components/common/TeamSelector.tsx

Lines changed: 29 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -74,12 +74,14 @@ interface TeamSelectorProps {
7474
onTeamSelect?: (team: TeamConfig | null) => void;
7575
onTeamUpload?: () => Promise<void>;
7676
selectedTeam?: TeamConfig | null;
77+
sessionId?: string; // Optional session ID for team selection
7778
}
7879

7980
const TeamSelector: React.FC<TeamSelectorProps> = ({
8081
onTeamSelect,
8182
onTeamUpload,
8283
selectedTeam,
84+
sessionId,
8385
}) => {
8486
const [isOpen, setIsOpen] = useState(false);
8587
const [teams, setTeams] = useState<TeamConfig[]>([]);
@@ -93,6 +95,7 @@ const TeamSelector: React.FC<TeamSelectorProps> = ({
9395
const [teamToDelete, setTeamToDelete] = useState<TeamConfig | null>(null);
9496
const [deleteLoading, setDeleteLoading] = useState(false);
9597
const [activeTab, setActiveTab] = useState<string>('teams');
98+
const [selectionLoading, setSelectionLoading] = useState(false);
9699

97100
const loadTeams = async () => {
98101
setLoading(true);
@@ -124,11 +127,31 @@ const TeamSelector: React.FC<TeamSelectorProps> = ({
124127
}
125128
};
126129

127-
const handleContinue = () => {
128-
if (tempSelectedTeam) {
129-
onTeamSelect?.(tempSelectedTeam);
130+
const handleContinue = async () => {
131+
if (!tempSelectedTeam) return;
132+
133+
setSelectionLoading(true);
134+
setError(null);
135+
136+
try {
137+
// Call the backend API to select the team
138+
const result = await TeamService.selectTeam(tempSelectedTeam.team_id, sessionId);
139+
140+
if (result.success) {
141+
// Successfully selected team on backend
142+
console.log('Team selected:', result.data);
143+
onTeamSelect?.(tempSelectedTeam);
144+
setIsOpen(false);
145+
} else {
146+
// Handle selection error
147+
setError(result.error || 'Failed to select team');
148+
}
149+
} catch (err: any) {
150+
console.error('Error selecting team:', err);
151+
setError('Failed to select team. Please try again.');
152+
} finally {
153+
setSelectionLoading(false);
130154
}
131-
setIsOpen(false);
132155
};
133156

134157
const handleCancel = () => {
@@ -624,10 +647,10 @@ const TeamSelector: React.FC<TeamSelectorProps> = ({
624647
<Button
625648
appearance="primary"
626649
onClick={handleContinue}
627-
disabled={!tempSelectedTeam}
650+
disabled={!tempSelectedTeam || selectionLoading}
628651
className={styles.continueButton}
629652
>
630-
Continue
653+
{selectionLoading ? 'Selecting...' : 'Continue'}
631654
</Button>
632655
</DialogActions>
633656
</DialogSurface>
Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
import { useState, useCallback } from 'react';
2+
import { TeamConfig } from '../models/Team';
3+
import { TeamService } from '../services/TeamService';
4+
5+
interface UseTeamSelectionProps {
6+
sessionId?: string;
7+
onTeamSelected?: (team: TeamConfig, result: any) => void;
8+
onError?: (error: string) => void;
9+
}
10+
11+
interface UseTeamSelectionReturn {
12+
selectedTeam: TeamConfig | null;
13+
isLoading: boolean;
14+
error: string | null;
15+
selectTeam: (team: TeamConfig) => Promise<boolean>;
16+
clearSelection: () => void;
17+
clearError: () => void;
18+
}
19+
20+
/**
21+
* React hook for managing team selection with backend integration
22+
*/
23+
export const useTeamSelection = ({
24+
sessionId,
25+
onTeamSelected,
26+
onError,
27+
}: UseTeamSelectionProps = {}): UseTeamSelectionReturn => {
28+
const [selectedTeam, setSelectedTeam] = useState<TeamConfig | null>(null);
29+
const [isLoading, setIsLoading] = useState(false);
30+
const [error, setError] = useState<string | null>(null);
31+
32+
const selectTeam = useCallback(async (team: TeamConfig): Promise<boolean> => {
33+
if (isLoading) return false;
34+
35+
setIsLoading(true);
36+
setError(null);
37+
38+
try {
39+
console.log('Selecting team:', team.name, 'with session ID:', sessionId);
40+
41+
const result = await TeamService.selectTeam(team.team_id, sessionId);
42+
43+
if (result.success) {
44+
setSelectedTeam(team);
45+
console.log('Team selection successful:', result.data);
46+
47+
// Call success callback
48+
onTeamSelected?.(team, result.data);
49+
50+
return true;
51+
} else {
52+
const errorMessage = result.error || 'Failed to select team';
53+
setError(errorMessage);
54+
55+
// Call error callback
56+
onError?.(errorMessage);
57+
58+
return false;
59+
}
60+
} catch (err: any) {
61+
const errorMessage = err.message || 'Failed to select team';
62+
setError(errorMessage);
63+
64+
console.error('Team selection error:', err);
65+
66+
// Call error callback
67+
onError?.(errorMessage);
68+
69+
return false;
70+
} finally {
71+
setIsLoading(false);
72+
}
73+
}, [isLoading, sessionId, onTeamSelected, onError]);
74+
75+
const clearSelection = useCallback(() => {
76+
setSelectedTeam(null);
77+
setError(null);
78+
}, []);
79+
80+
const clearError = useCallback(() => {
81+
setError(null);
82+
}, []);
83+
84+
return {
85+
selectedTeam,
86+
isLoading,
87+
error,
88+
selectTeam,
89+
clearSelection,
90+
clearError,
91+
};
92+
};
93+
94+
export default useTeamSelection;

src/frontend/src/services/TeamService.tsx

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -127,6 +127,40 @@ export class TeamService {
127127
}
128128
}
129129

130+
/**
131+
* Select a team for a plan/session
132+
*/
133+
static async selectTeam(teamId: string, sessionId?: string): Promise<{
134+
success: boolean;
135+
data?: any;
136+
error?: string;
137+
}> {
138+
try {
139+
const response = await apiClient.post('/v3/select_team', {
140+
team_id: teamId,
141+
session_id: sessionId
142+
});
143+
144+
return {
145+
success: true,
146+
data: response
147+
};
148+
} catch (error: any) {
149+
let errorMessage = 'Failed to select team';
150+
151+
if (error.response?.data?.detail) {
152+
errorMessage = error.response.data.detail;
153+
} else if (error.message) {
154+
errorMessage = error.message;
155+
}
156+
157+
return {
158+
success: false,
159+
error: errorMessage
160+
};
161+
}
162+
}
163+
130164
/**
131165
* Validate a team configuration JSON structure
132166
*/

0 commit comments

Comments
 (0)