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,
meetingLink: str,
botName: str,
backgroundImageUrl: Optional[str],
sessionId: str,
gatewayCallbackUrl: str,
gatewayWsUrl: str,
@ -56,7 +55,6 @@ class BridgeConnector:
payload = {
"meetingLink": meetingLink,
"botName": botName,
"backgroundImageUrl": backgroundImageUrl,
"sessionId": sessionId,
"gatewayCallbackUrl": gatewayCallbackUrl,
"gatewayWsUrl": gatewayWsUrl,

View file

@ -38,16 +38,16 @@ class BrowserBotConnector:
language: str = "de-DE",
botAccountEmail: Optional[str] = None,
botAccountPassword: Optional[str] = None,
backgroundImageUrl: Optional[str] = None,
transferMode: str = "auto",
) -> Dict[str, Any]:
"""
Send join command to the Browser Bot service.
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
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
Args:
@ -55,7 +55,7 @@ class BrowserBotConnector:
language: BCP-47 language code for captions spoken language
botAccountEmail: Microsoft account email for authenticated join (None = anonymous)
botAccountPassword: Microsoft account password
backgroundImageUrl: URL to background image for virtual background
transferMode: How to capture meeting content: caption, audio, or auto
Returns:
Dict with 'success' bool and optional 'error' string.
@ -74,6 +74,7 @@ class BrowserBotConnector:
"instanceId": instanceId,
"gatewayWsUrl": gatewayWsUrl,
"language": language,
"transferMode": transferMode,
}
# Add authenticated join credentials if configured
@ -82,10 +83,6 @@ class BrowserBotConnector:
payload["botAccountPassword"] = botAccountPassword
logger.info(f"Bot will join authenticated as {botAccountEmail}")
# Add background image if configured
if backgroundImageUrl:
payload["backgroundImageUrl"] = backgroundImageUrl
try:
async with aiohttp.ClientSession(timeout=_BOT_TIMEOUT) as session:
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)
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)
# ============================================================================
@ -72,7 +79,6 @@ class TeamsbotSession(BaseModel):
mandateId: str = Field(description="Mandate ID (FK)")
meetingLink: str = Field(description="Teams meeting join link")
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")
startedAt: Optional[str] = Field(default=None, description="ISO timestamp when session started")
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)")
instanceId: str = Field(description="Feature instance ID (FK)")
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")
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")
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)")
voiceId: Optional[str] = Field(default=None, description="TTS voice ID override")
triggerIntervalSeconds: Optional[int] = Field(default=None, description="Trigger interval override")
@ -167,18 +173,16 @@ class TeamsbotUserSettings(BaseModel):
class TeamsbotConfig(BaseModel):
"""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")
backgroundImageUrl: Optional[str] = Field(default=None, description="Default background image URL")
aiSystemPrompt: str = Field(
default="Du bist ein hilfreicher Meeting-Assistent. Fasse wichtige Punkte zusammen und beantworte Fragen sachlich.",
description="Custom system prompt for the AI analysis"
)
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")
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")
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.")
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")
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")
@ -199,7 +203,6 @@ class TeamsbotStartSessionRequest(BaseModel):
"""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/...)")
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")
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)")
@ -215,15 +218,13 @@ class TeamsbotSessionResponse(BaseModel):
class TeamsbotConfigUpdateRequest(BaseModel):
"""Request to update teamsbot configuration."""
botName: Optional[str] = None
backgroundImageUrl: Optional[str] = None
aiSystemPrompt: Optional[str] = None
responseMode: Optional[TeamsbotResponseMode] = None
responseChannel: Optional[TeamsbotResponseChannel] = None
transferMode: Optional[TeamsbotTransferMode] = None
language: Optional[str] = None
voiceId: Optional[str] = None
browserBotUrl: Optional[str] = None
botAccountEmail: Optional[str] = None
botAccountPassword: Optional[str] = None
triggerIntervalSeconds: Optional[int] = None
triggerCooldownSeconds: Optional[int] = None
contextWindowSegments: Optional[int] = None
@ -249,7 +250,6 @@ class BridgeJoinRequest(BaseModel):
"""Request sent to .NET Media Bridge to join a meeting."""
meetingLink: str = Field(description="Teams meeting join link")
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")
gatewayWsUrl: str = Field(description="Gateway WebSocket URL for audio streaming")
sessionId: str = Field(description="Session ID for correlation")

View file

@ -177,7 +177,6 @@ async def startSession(
mandateId=mandateId,
meetingLink=cleanMeetingUrl,
botName=body.botName or config.botName,
backgroundImageUrl=body.backgroundImageUrl or config.backgroundImageUrl,
sessionContext=body.sessionContext,
status=TeamsbotSessionStatus.PENDING,
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)
overrides = {}
for field in ["botName", "backgroundImageUrl", "aiSystemPrompt", "responseMode",
"responseChannel", "language", "voiceId",
for field in ["botName", "aiSystemPrompt", "responseMode",
"responseChannel", "transferMode", "language", "voiceId",
"triggerIntervalSeconds", "triggerCooldownSeconds", "contextWindowSegments"]:
value = userSettings.get(field)
if value is not None:

View file

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