From 5d987e72fe2d6c6c2bb048b383e7f2965762eb3e Mon Sep 17 00:00:00 2001 From: patrick-motsch Date: Sun, 15 Feb 2026 01:26:27 +0100 Subject: [PATCH] fix(teamsbot): Fix Invalid Date + pass language to browser bot - Add getIsoTimestamp() to timeUtils for JS-compatible ISO 8601 strings - Replace getUtcTimestamp() (epoch float) with getIsoTimestamp() for all teamsbot session/transcript/response timestamp fields (startedAt, endedAt, creationDate, lastModified, SSE event timestamps) - Pass config.language to browser bot in join request for captions spoken language Co-authored-by: Cursor --- .../features/teamsbot/browserBotConnector.py | 3 +++ .../teamsbot/interfaceFeatureTeamsbot.py | 12 ++++++------ modules/features/teamsbot/service.py | 19 ++++++++++--------- modules/shared/timeUtils.py | 13 +++++++++++++ 4 files changed, 32 insertions(+), 15 deletions(-) diff --git a/modules/features/teamsbot/browserBotConnector.py b/modules/features/teamsbot/browserBotConnector.py index b0fa5310..1b594a25 100644 --- a/modules/features/teamsbot/browserBotConnector.py +++ b/modules/features/teamsbot/browserBotConnector.py @@ -35,6 +35,7 @@ class BrowserBotConnector: botName: str, instanceId: str, gatewayWsUrl: str, + language: str = "de-DE", ) -> Dict[str, Any]: """ Send join command to the Browser Bot service. @@ -49,6 +50,7 @@ class BrowserBotConnector: Args: gatewayWsUrl: Full WebSocket URL for the bot to connect back to (e.g. wss://gateway-int.poweron-center.net/api/teamsbot/{instanceId}/bot/ws/{sessionId}) + language: BCP-47 language code for captions spoken language (e.g. "de-DE", "en-US") Returns: Dict with 'success' bool and optional 'error' string. @@ -66,6 +68,7 @@ class BrowserBotConnector: "botName": botName, "instanceId": instanceId, "gatewayWsUrl": gatewayWsUrl, # Full WebSocket URL for bot to connect back + "language": language, # Spoken language for Teams captions } try: diff --git a/modules/features/teamsbot/interfaceFeatureTeamsbot.py b/modules/features/teamsbot/interfaceFeatureTeamsbot.py index 1aff28cc..2ce40e18 100644 --- a/modules/features/teamsbot/interfaceFeatureTeamsbot.py +++ b/modules/features/teamsbot/interfaceFeatureTeamsbot.py @@ -10,7 +10,7 @@ from typing import Dict, Any, List, Optional from modules.datamodels.datamodelUam import User from modules.connectors.connectorDbPostgre import DatabaseConnector -from modules.shared.timeUtils import getUtcTimestamp +from modules.shared.timeUtils import getIsoTimestamp from modules.shared.configuration import APP_CONFIG from .datamodelTeamsbot import ( @@ -98,13 +98,13 @@ class TeamsbotObjects: def createSession(self, sessionData: Dict[str, Any]) -> Dict[str, Any]: """Create a new session.""" - sessionData["creationDate"] = getUtcTimestamp() - sessionData["lastModified"] = getUtcTimestamp() + sessionData["creationDate"] = getIsoTimestamp() + sessionData["lastModified"] = getIsoTimestamp() return self.db.recordCreate(TeamsbotSession, sessionData) def updateSession(self, sessionId: str, updates: Dict[str, Any]) -> Optional[Dict[str, Any]]: """Update session fields.""" - updates["lastModified"] = getUtcTimestamp() + updates["lastModified"] = getIsoTimestamp() return self.db.recordModify(TeamsbotSession, sessionId, updates) def deleteSession(self, sessionId: str) -> bool: @@ -143,7 +143,7 @@ class TeamsbotObjects: def createTranscript(self, transcriptData: Dict[str, Any]) -> Dict[str, Any]: """Create a new transcript segment.""" - transcriptData["creationDate"] = getUtcTimestamp() + transcriptData["creationDate"] = getIsoTimestamp() return self.db.recordCreate(TeamsbotTranscript, transcriptData) def _deleteTranscriptsBySession(self, sessionId: str) -> int: @@ -170,7 +170,7 @@ class TeamsbotObjects: def createBotResponse(self, responseData: Dict[str, Any]) -> Dict[str, Any]: """Create a new bot response record.""" - responseData["creationDate"] = getUtcTimestamp() + responseData["creationDate"] = getIsoTimestamp() return self.db.recordCreate(TeamsbotBotResponse, responseData) def _deleteResponsesBySession(self, sessionId: str) -> int: diff --git a/modules/features/teamsbot/service.py b/modules/features/teamsbot/service.py index 50a938e0..12504847 100644 --- a/modules/features/teamsbot/service.py +++ b/modules/features/teamsbot/service.py @@ -16,7 +16,7 @@ from fastapi import WebSocket from modules.datamodels.datamodelUam import User from modules.datamodels.datamodelAi import AiCallRequest, AiCallOptions, OperationTypeEnum, PriorityEnum -from modules.shared.timeUtils import getUtcTimestamp +from modules.shared.timeUtils import getUtcTimestamp, getIsoTimestamp from .datamodelTeamsbot import ( TeamsbotSessionStatus, @@ -57,7 +57,7 @@ async def _emitSessionEvent(sessionId: str, eventType: str, data: Any): """Emit an event to the session's SSE stream.""" eventQueue = _sessionEvents.get(sessionId) if eventQueue: - await eventQueue.put({"type": eventType, "data": data, "timestamp": getUtcTimestamp()}) + await eventQueue.put({"type": eventType, "data": data, "timestamp": getIsoTimestamp()}) class TeamsbotService: @@ -126,6 +126,7 @@ class TeamsbotService: botName=session.get("botName", self.config.botName), instanceId=self.instanceId, gatewayWsUrl=fullGatewayWsUrl, + language=self.config.language, ) if result.get("success"): @@ -164,7 +165,7 @@ class TeamsbotService: interface.updateSession(sessionId, { "status": TeamsbotSessionStatus.ENDED.value, - "endedAt": getUtcTimestamp(), + "endedAt": getIsoTimestamp(), }) await _emitSessionEvent(sessionId, "statusChange", {"status": "ended"}) @@ -178,7 +179,7 @@ class TeamsbotService: interface.updateSession(sessionId, { "status": TeamsbotSessionStatus.ERROR.value, "errorMessage": str(e), - "endedAt": getUtcTimestamp(), + "endedAt": getIsoTimestamp(), }) # Cleanup event queue @@ -269,9 +270,9 @@ class TeamsbotService: if errorMessage: updates["errorMessage"] = errorMessage if dbStatus == TeamsbotSessionStatus.ACTIVE.value: - updates["startedAt"] = getUtcTimestamp() + updates["startedAt"] = getIsoTimestamp() elif dbStatus in [TeamsbotSessionStatus.ENDED.value, TeamsbotSessionStatus.ERROR.value]: - updates["endedAt"] = getUtcTimestamp() + updates["endedAt"] = getIsoTimestamp() interface.updateSession(sessionId, updates) await _emitSessionEvent(sessionId, "statusChange", {"status": status, "errorMessage": errorMessage}) @@ -301,7 +302,7 @@ class TeamsbotService: sessionId=sessionId, speaker=speaker, text=text, - timestamp=str(getUtcTimestamp()), + timestamp=getIsoTimestamp(), confidence=1.0, # Captions don't have confidence scores language=self.config.language, isFinal=isFinal, @@ -326,7 +327,7 @@ class TeamsbotService: "speaker": speaker, "text": text, "confidence": 1.0, - "timestamp": getUtcTimestamp(), + "timestamp": getIsoTimestamp(), }) # Update session transcript count @@ -499,7 +500,7 @@ class TeamsbotService: modelName=response.modelName, processingTime=response.processingTime, priceCHF=response.priceCHF, - timestamp=str(getUtcTimestamp()), + timestamp=getIsoTimestamp(), ).model_dump() createdResponse = interface.createBotResponse(botResponseData) diff --git a/modules/shared/timeUtils.py b/modules/shared/timeUtils.py index 0f54fb8a..4d766579 100644 --- a/modules/shared/timeUtils.py +++ b/modules/shared/timeUtils.py @@ -31,6 +31,19 @@ def getUtcTimestamp() -> float: """ return time.time() +def getIsoTimestamp() -> str: + """ + Get current UTC timestamp as ISO 8601 string. + + Use this for fields declared as 'ISO timestamp' strings (e.g. Pydantic str fields + that will be parsed by JavaScript's new Date()). JavaScript cannot parse epoch + floats from getUtcTimestamp() as dates. + + Returns: + str: Current UTC time in ISO 8601 format (e.g. "2026-02-15T00:08:32.070000+00:00") + """ + return datetime.now(timezone.utc).isoformat() + def createExpirationTimestamp(expiresInSeconds: int) -> float: """ Create a new expiration timestamp from seconds until expiration.