From de573fd834fd76c04694b8afd138b62b61a39254 Mon Sep 17 00:00:00 2001
From: patrick-motsch
Date: Tue, 17 Feb 2026 18:34:24 +0100
Subject: [PATCH] refactor: add TransferMode, remove backgroundImageUrl and
botAccount fields from config
Co-authored-by: Cursor
---
modules/features/teamsbot/bridgeConnector.py | 2 --
.../features/teamsbot/browserBotConnector.py | 13 +++++-------
.../features/teamsbot/datamodelTeamsbot.py | 20 +++++++++----------
.../features/teamsbot/routeFeatureTeamsbot.py | 5 ++---
modules/features/teamsbot/service.py | 6 +++---
5 files changed, 20 insertions(+), 26 deletions(-)
diff --git a/modules/features/teamsbot/bridgeConnector.py b/modules/features/teamsbot/bridgeConnector.py
index 8e43ee1f..f97f4103 100644
--- a/modules/features/teamsbot/bridgeConnector.py
+++ b/modules/features/teamsbot/bridgeConnector.py
@@ -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,
diff --git a/modules/features/teamsbot/browserBotConnector.py b/modules/features/teamsbot/browserBotConnector.py
index 210f09f1..173d2e5e 100644
--- a/modules/features/teamsbot/browserBotConnector.py
+++ b/modules/features/teamsbot/browserBotConnector.py
@@ -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:
diff --git a/modules/features/teamsbot/datamodelTeamsbot.py b/modules/features/teamsbot/datamodelTeamsbot.py
index 81625b90..737f360e 100644
--- a/modules/features/teamsbot/datamodelTeamsbot.py
+++ b/modules/features/teamsbot/datamodelTeamsbot.py
@@ -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")
diff --git a/modules/features/teamsbot/routeFeatureTeamsbot.py b/modules/features/teamsbot/routeFeatureTeamsbot.py
index 94683cfc..34dfd7f3 100644
--- a/modules/features/teamsbot/routeFeatureTeamsbot.py
+++ b/modules/features/teamsbot/routeFeatureTeamsbot.py
@@ -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:
diff --git a/modules/features/teamsbot/service.py b/modules/features/teamsbot/service.py
index 9c3c537c..0cd17bde 100644
--- a/modules/features/teamsbot/service.py
+++ b/modules/features/teamsbot/service.py
@@ -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"):