refactor: add TransferMode, remove backgroundImageUrl and botAccount fields from config

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
patrick-motsch 2026-02-17 18:34:24 +01:00
parent 5dd3b9f894
commit de573fd834
5 changed files with 20 additions and 26 deletions

View file

@ -29,7 +29,6 @@ class BridgeConnector:
self, self,
meetingLink: str, meetingLink: str,
botName: str, botName: str,
backgroundImageUrl: Optional[str],
sessionId: str, sessionId: str,
gatewayCallbackUrl: str, gatewayCallbackUrl: str,
gatewayWsUrl: str, gatewayWsUrl: str,
@ -56,7 +55,6 @@ class BridgeConnector:
payload = { payload = {
"meetingLink": meetingLink, "meetingLink": meetingLink,
"botName": botName, "botName": botName,
"backgroundImageUrl": backgroundImageUrl,
"sessionId": sessionId, "sessionId": sessionId,
"gatewayCallbackUrl": gatewayCallbackUrl, "gatewayCallbackUrl": gatewayCallbackUrl,
"gatewayWsUrl": gatewayWsUrl, "gatewayWsUrl": gatewayWsUrl,

View file

@ -38,16 +38,16 @@ class BrowserBotConnector:
language: str = "de-DE", language: str = "de-DE",
botAccountEmail: Optional[str] = None, botAccountEmail: Optional[str] = None,
botAccountPassword: Optional[str] = None, botAccountPassword: Optional[str] = None,
backgroundImageUrl: Optional[str] = None, transferMode: str = "auto",
) -> Dict[str, Any]: ) -> Dict[str, Any]:
""" """
Send join command to the Browser Bot service. Send join command to the Browser Bot service.
The bot will: The bot will:
1. Launch a headless browser 1. Launch a browser (headful for auth, headless for anonymous)
2. If botAccountEmail/Password provided: authenticate with Microsoft first 2. If botAccountEmail/Password provided: authenticate with Microsoft first
3. Navigate to Teams web app and join the meeting 3. Navigate to Teams web app and join the meeting
4. Enable captions and start scraping 4. Enable captions/audio capture based on transferMode
5. Connect back to Gateway via WebSocket using gatewayWsUrl 5. Connect back to Gateway via WebSocket using gatewayWsUrl
Args: Args:
@ -55,7 +55,7 @@ class BrowserBotConnector:
language: BCP-47 language code for captions spoken language language: BCP-47 language code for captions spoken language
botAccountEmail: Microsoft account email for authenticated join (None = anonymous) botAccountEmail: Microsoft account email for authenticated join (None = anonymous)
botAccountPassword: Microsoft account password botAccountPassword: Microsoft account password
backgroundImageUrl: URL to background image for virtual background transferMode: How to capture meeting content: caption, audio, or auto
Returns: Returns:
Dict with 'success' bool and optional 'error' string. Dict with 'success' bool and optional 'error' string.
@ -74,6 +74,7 @@ class BrowserBotConnector:
"instanceId": instanceId, "instanceId": instanceId,
"gatewayWsUrl": gatewayWsUrl, "gatewayWsUrl": gatewayWsUrl,
"language": language, "language": language,
"transferMode": transferMode,
} }
# Add authenticated join credentials if configured # Add authenticated join credentials if configured
@ -82,10 +83,6 @@ class BrowserBotConnector:
payload["botAccountPassword"] = botAccountPassword payload["botAccountPassword"] = botAccountPassword
logger.info(f"Bot will join authenticated as {botAccountEmail}") logger.info(f"Bot will join authenticated as {botAccountEmail}")
# Add background image if configured
if backgroundImageUrl:
payload["backgroundImageUrl"] = backgroundImageUrl
try: try:
async with aiohttp.ClientSession(timeout=_BOT_TIMEOUT) as session: async with aiohttp.ClientSession(timeout=_BOT_TIMEOUT) as session:
async with session.post(f"{self.botUrl}/api/bot", json=payload) as resp: async with session.post(f"{self.botUrl}/api/bot", json=payload) as resp:

View file

@ -61,6 +61,13 @@ class TeamsbotJoinMode(str, Enum):
USER_ACCOUNT = "userAccount" # Join with user's own Microsoft account (OAuth) USER_ACCOUNT = "userAccount" # Join with user's own Microsoft account (OAuth)
class TeamsbotTransferMode(str, Enum):
"""How meeting audio/transcript is transferred from bot to gateway."""
CAPTION = "caption" # Use Teams live captions (text scraping from DOM)
AUDIO = "audio" # Capture meeting audio and stream to gateway for STT
AUTO = "auto" # Automatic: anonymous → audio, authenticated → caption
# ============================================================================ # ============================================================================
# Database Models (stored in PostgreSQL) # Database Models (stored in PostgreSQL)
# ============================================================================ # ============================================================================
@ -72,7 +79,6 @@ class TeamsbotSession(BaseModel):
mandateId: str = Field(description="Mandate ID (FK)") mandateId: str = Field(description="Mandate ID (FK)")
meetingLink: str = Field(description="Teams meeting join link") meetingLink: str = Field(description="Teams meeting join link")
botName: str = Field(default="AI Assistant", description="Display name of the bot in the meeting") botName: str = Field(default="AI Assistant", description="Display name of the bot in the meeting")
backgroundImageUrl: Optional[str] = Field(default=None, description="Background image URL for the bot's video feed")
status: TeamsbotSessionStatus = Field(default=TeamsbotSessionStatus.PENDING, description="Current session status") status: TeamsbotSessionStatus = Field(default=TeamsbotSessionStatus.PENDING, description="Current session status")
startedAt: Optional[str] = Field(default=None, description="ISO timestamp when session started") startedAt: Optional[str] = Field(default=None, description="ISO timestamp when session started")
endedAt: Optional[str] = Field(default=None, description="ISO timestamp when session ended") endedAt: Optional[str] = Field(default=None, description="ISO timestamp when session ended")
@ -147,10 +153,10 @@ class TeamsbotUserSettings(BaseModel):
userId: str = Field(description="User ID (FK)") userId: str = Field(description="User ID (FK)")
instanceId: str = Field(description="Feature instance ID (FK)") instanceId: str = Field(description="Feature instance ID (FK)")
botName: Optional[str] = Field(default=None, description="Bot display name override") botName: Optional[str] = Field(default=None, description="Bot display name override")
backgroundImageUrl: Optional[str] = Field(default=None, description="Background image URL override")
aiSystemPrompt: Optional[str] = Field(default=None, description="AI system prompt override") aiSystemPrompt: Optional[str] = Field(default=None, description="AI system prompt override")
responseMode: Optional[str] = Field(default=None, description="Response mode override: auto, manual, transcribeOnly") responseMode: Optional[str] = Field(default=None, description="Response mode override: auto, manual, transcribeOnly")
responseChannel: Optional[str] = Field(default=None, description="Response channel override: voice, chat, both") responseChannel: Optional[str] = Field(default=None, description="Response channel override: voice, chat, both")
transferMode: Optional[str] = Field(default=None, description="Transfer mode override: caption, audio, auto")
language: Optional[str] = Field(default=None, description="Language override (e.g. de-DE)") language: Optional[str] = Field(default=None, description="Language override (e.g. de-DE)")
voiceId: Optional[str] = Field(default=None, description="TTS voice ID override") voiceId: Optional[str] = Field(default=None, description="TTS voice ID override")
triggerIntervalSeconds: Optional[int] = Field(default=None, description="Trigger interval override") triggerIntervalSeconds: Optional[int] = Field(default=None, description="Trigger interval override")
@ -167,18 +173,16 @@ class TeamsbotUserSettings(BaseModel):
class TeamsbotConfig(BaseModel): class TeamsbotConfig(BaseModel):
"""Configuration for a Teams Bot feature instance (serves as default template for new users).""" """Configuration for a Teams Bot feature instance (serves as default template for new users)."""
botName: str = Field(default="AI Assistant", description="Default bot display name") botName: str = Field(default="AI Assistant", description="Default bot display name")
backgroundImageUrl: Optional[str] = Field(default=None, description="Default background image URL")
aiSystemPrompt: str = Field( aiSystemPrompt: str = Field(
default="Du bist ein hilfreicher Meeting-Assistent. Fasse wichtige Punkte zusammen und beantworte Fragen sachlich.", default="Du bist ein hilfreicher Meeting-Assistent. Fasse wichtige Punkte zusammen und beantworte Fragen sachlich.",
description="Custom system prompt for the AI analysis" description="Custom system prompt for the AI analysis"
) )
responseMode: TeamsbotResponseMode = Field(default=TeamsbotResponseMode.AUTO, description="How the bot responds") responseMode: TeamsbotResponseMode = Field(default=TeamsbotResponseMode.AUTO, description="How the bot responds")
responseChannel: TeamsbotResponseChannel = Field(default=TeamsbotResponseChannel.VOICE, description="Channel for bot responses: voice, chat, or both") responseChannel: TeamsbotResponseChannel = Field(default=TeamsbotResponseChannel.VOICE, description="Channel for bot responses: voice, chat, or both")
transferMode: TeamsbotTransferMode = Field(default=TeamsbotTransferMode.AUTO, description="How meeting content is captured: caption (Teams captions), audio (stream to gateway STT), or auto")
language: str = Field(default="de-DE", description="Primary language for STT/TTS") language: str = Field(default="de-DE", description="Primary language for STT/TTS")
voiceId: Optional[str] = Field(default=None, description="Google TTS voice ID (e.g., de-DE-Standard-A)") voiceId: Optional[str] = Field(default=None, description="Google TTS voice ID (e.g., de-DE-Standard-A)")
browserBotUrl: Optional[str] = Field(default=None, description="URL of the Browser Bot service. Falls back to TEAMSBOT_BROWSER_BOT_URL env variable if not set per-instance.") browserBotUrl: Optional[str] = Field(default=None, description="URL of the Browser Bot service. Falls back to TEAMSBOT_BROWSER_BOT_URL env variable if not set per-instance.")
botAccountEmail: Optional[str] = Field(default=None, description="Dedicated Microsoft account email for authenticated bot join. Leave empty for anonymous join.")
botAccountPassword: Optional[str] = Field(default=None, description="Dedicated Microsoft account password. MFA must be disabled for this account.")
triggerIntervalSeconds: int = Field(default=10, ge=3, le=60, description="Seconds between periodic AI analysis triggers") triggerIntervalSeconds: int = Field(default=10, ge=3, le=60, description="Seconds between periodic AI analysis triggers")
triggerCooldownSeconds: int = Field(default=3, ge=1, le=30, description="Minimum seconds between AI calls") triggerCooldownSeconds: int = Field(default=3, ge=1, le=30, description="Minimum seconds between AI calls")
contextWindowSegments: int = Field(default=20, ge=5, le=100, description="Number of transcript segments to include in AI context") contextWindowSegments: int = Field(default=20, ge=5, le=100, description="Number of transcript segments to include in AI context")
@ -199,7 +203,6 @@ class TeamsbotStartSessionRequest(BaseModel):
"""Request to start a new Teams Bot session.""" """Request to start a new Teams Bot session."""
meetingLink: str = Field(description="Teams meeting join link (e.g., https://teams.microsoft.com/l/meetup-join/...)") meetingLink: str = Field(description="Teams meeting join link (e.g., https://teams.microsoft.com/l/meetup-join/...)")
botName: Optional[str] = Field(default=None, description="Override bot name for this session") botName: Optional[str] = Field(default=None, description="Override bot name for this session")
backgroundImageUrl: Optional[str] = Field(default=None, description="Override background image for this session")
connectionId: Optional[str] = Field(default=None, description="Microsoft connection ID for Graph API access") connectionId: Optional[str] = Field(default=None, description="Microsoft connection ID for Graph API access")
joinMode: Optional[TeamsbotJoinMode] = Field(default=None, description="How the bot joins: systemBot, anonymous, or userAccount. Defaults to systemBot if credentials configured, else anonymous.") joinMode: Optional[TeamsbotJoinMode] = Field(default=None, description="How the bot joins: systemBot, anonymous, or userAccount. Defaults to systemBot if credentials configured, else anonymous.")
sessionContext: Optional[str] = Field(default=None, description="Custom context/knowledge to provide to the bot for this session (e.g. meeting agenda, documents, background info)") sessionContext: Optional[str] = Field(default=None, description="Custom context/knowledge to provide to the bot for this session (e.g. meeting agenda, documents, background info)")
@ -215,15 +218,13 @@ class TeamsbotSessionResponse(BaseModel):
class TeamsbotConfigUpdateRequest(BaseModel): class TeamsbotConfigUpdateRequest(BaseModel):
"""Request to update teamsbot configuration.""" """Request to update teamsbot configuration."""
botName: Optional[str] = None botName: Optional[str] = None
backgroundImageUrl: Optional[str] = None
aiSystemPrompt: Optional[str] = None aiSystemPrompt: Optional[str] = None
responseMode: Optional[TeamsbotResponseMode] = None responseMode: Optional[TeamsbotResponseMode] = None
responseChannel: Optional[TeamsbotResponseChannel] = None responseChannel: Optional[TeamsbotResponseChannel] = None
transferMode: Optional[TeamsbotTransferMode] = None
language: Optional[str] = None language: Optional[str] = None
voiceId: Optional[str] = None voiceId: Optional[str] = None
browserBotUrl: Optional[str] = None browserBotUrl: Optional[str] = None
botAccountEmail: Optional[str] = None
botAccountPassword: Optional[str] = None
triggerIntervalSeconds: Optional[int] = None triggerIntervalSeconds: Optional[int] = None
triggerCooldownSeconds: Optional[int] = None triggerCooldownSeconds: Optional[int] = None
contextWindowSegments: Optional[int] = None contextWindowSegments: Optional[int] = None
@ -249,7 +250,6 @@ class BridgeJoinRequest(BaseModel):
"""Request sent to .NET Media Bridge to join a meeting.""" """Request sent to .NET Media Bridge to join a meeting."""
meetingLink: str = Field(description="Teams meeting join link") meetingLink: str = Field(description="Teams meeting join link")
botName: str = Field(description="Bot display name") botName: str = Field(description="Bot display name")
backgroundImageUrl: Optional[str] = Field(default=None, description="Background image URL")
gatewayCallbackUrl: str = Field(description="Gateway URL for bridge callbacks") gatewayCallbackUrl: str = Field(description="Gateway URL for bridge callbacks")
gatewayWsUrl: str = Field(description="Gateway WebSocket URL for audio streaming") gatewayWsUrl: str = Field(description="Gateway WebSocket URL for audio streaming")
sessionId: str = Field(description="Session ID for correlation") sessionId: str = Field(description="Session ID for correlation")

View file

@ -177,7 +177,6 @@ async def startSession(
mandateId=mandateId, mandateId=mandateId,
meetingLink=cleanMeetingUrl, meetingLink=cleanMeetingUrl,
botName=body.botName or config.botName, botName=body.botName or config.botName,
backgroundImageUrl=body.backgroundImageUrl or config.backgroundImageUrl,
sessionContext=body.sessionContext, sessionContext=body.sessionContext,
status=TeamsbotSessionStatus.PENDING, status=TeamsbotSessionStatus.PENDING,
startedByUserId=str(context.user.id), startedByUserId=str(context.user.id),
@ -449,8 +448,8 @@ def _getEffectiveConfig(instanceId: str, userId: str, interface) -> TeamsbotConf
# Merge: user settings override instance defaults (only non-None values) # Merge: user settings override instance defaults (only non-None values)
overrides = {} overrides = {}
for field in ["botName", "backgroundImageUrl", "aiSystemPrompt", "responseMode", for field in ["botName", "aiSystemPrompt", "responseMode",
"responseChannel", "language", "voiceId", "responseChannel", "transferMode", "language", "voiceId",
"triggerIntervalSeconds", "triggerCooldownSeconds", "contextWindowSegments"]: "triggerIntervalSeconds", "triggerCooldownSeconds", "contextWindowSegments"]:
value = userSettings.get(field) value = userSettings.get(field)
if value is not None: if value is not None:

View file

@ -130,9 +130,9 @@ class TeamsbotService:
instanceId=self.instanceId, instanceId=self.instanceId,
gatewayWsUrl=fullGatewayWsUrl, gatewayWsUrl=fullGatewayWsUrl,
language=self.config.language, language=self.config.language,
botAccountEmail=self.config.botAccountEmail, botAccountEmail=self.config.botAccountEmail if hasattr(self.config, 'botAccountEmail') else None,
botAccountPassword=self.config.botAccountPassword, botAccountPassword=self.config.botAccountPassword if hasattr(self.config, 'botAccountPassword') else None,
backgroundImageUrl=session.get("backgroundImageUrl") or self.config.backgroundImageUrl, transferMode=self.config.transferMode if hasattr(self.config, 'transferMode') else "auto",
) )
if result.get("success"): if result.get("success"):