diff --git a/modules/features/teamsbot/routeFeatureTeamsbot.py b/modules/features/teamsbot/routeFeatureTeamsbot.py index b185528a..a441d461 100644 --- a/modules/features/teamsbot/routeFeatureTeamsbot.py +++ b/modules/features/teamsbot/routeFeatureTeamsbot.py @@ -212,8 +212,18 @@ async def startSession( if systemBot: if not effectiveBotName: - effectiveBotName = systemBot.get("name") or effectiveConfig.botName - logger.info(f"System bot found: {systemBot.get('name')} ({systemBot.get('email')})") + effectiveBotName = systemBot.get("name") + # Derive display name from email if system bot has no explicit name + # e.g. "nyla.larsson@poweron.swiss" → "Nyla Larsson" + if not effectiveBotName: + sbEmail = systemBot.get("email", "") + if sbEmail and "@" in sbEmail: + emailPrefix = sbEmail.split("@")[0] + effectiveBotName = " ".join(part.capitalize() for part in emailPrefix.split(".")) + logger.info(f"Bot name derived from email: {effectiveBotName}") + if not effectiveBotName: + effectiveBotName = effectiveConfig.botName + logger.info(f"System bot found: {systemBot.get('name')} ({systemBot.get('email')}), effectiveBotName={effectiveBotName}") # Load and decrypt credentials for authenticated join botAccountEmail = systemBot.get("email") diff --git a/modules/features/teamsbot/service.py b/modules/features/teamsbot/service.py index 7251741e..83e7a251 100644 --- a/modules/features/teamsbot/service.py +++ b/modules/features/teamsbot/service.py @@ -519,9 +519,9 @@ class TeamsbotService: def _isBotSpeaker(self, speaker: str) -> bool: """Check if a transcript speaker is the bot itself. - Teams captions show the bot as e.g. "Shelly Miller (Unverified)" or + Teams captions show the bot as e.g. "BotName (Unverified)" or "Nyla Larsson" depending on auth/anonymous join. We match against: - - The configured bot name (e.g. "Shelly Miller") + - The configured/derived bot name - The bot account display name if authenticated """ if not speaker: @@ -714,18 +714,22 @@ class TeamsbotService: return # Determine response channel (voice, chat, or both) - channel = self.config.responseChannel - responseType = TeamsbotResponseType.BOTH + # Normalize to string for reliable comparison (model_copy may skip enum coercion) + channelStr = str(self.config.responseChannel).lower().strip() + logger.info(f"Response channel: '{channelStr}' (raw={self.config.responseChannel!r})") - if channel == TeamsbotResponseChannel.VOICE: - responseType = TeamsbotResponseType.AUDIO - elif channel == TeamsbotResponseChannel.CHAT: - responseType = TeamsbotResponseType.CHAT - else: + sendVoice = channelStr in ("voice", "both") + sendChat = channelStr in ("chat", "both") + + if sendVoice and sendChat: responseType = TeamsbotResponseType.BOTH + elif sendVoice: + responseType = TeamsbotResponseType.AUDIO + else: + responseType = TeamsbotResponseType.CHAT # 4a: Voice response (TTS -> Audio to bot) - if channel in (TeamsbotResponseChannel.VOICE, TeamsbotResponseChannel.BOTH): + if sendVoice: try: ttsResult = await voiceInterface.textToSpeech( text=speechResult.responseText, @@ -748,11 +752,11 @@ class TeamsbotService: logger.info(f"TTS audio generated for session {sessionId} (HTTP mode - no WebSocket for playback)") except Exception as ttsErr: logger.warning(f"TTS failed for session {sessionId}: {ttsErr}") - if responseType == TeamsbotResponseType.AUDIO: - responseType = TeamsbotResponseType.CHAT # Fallback to chat only + if not sendChat: + sendChat = True # Fallback to chat if voice-only and TTS failed # 4b: Chat response (send text message to meeting chat) - if channel in (TeamsbotResponseChannel.CHAT, TeamsbotResponseChannel.BOTH): + if sendChat: try: if websocket: await websocket.send_text(json.dumps({ @@ -760,6 +764,7 @@ class TeamsbotService: "sessionId": sessionId, "text": speechResult.responseText, })) + logger.info(f"Chat response sent for session {sessionId}") except Exception as chatErr: logger.warning(f"Chat message send failed for session {sessionId}: {chatErr}")