fix: teamsbot SSE-Events fuer Greeting, Commands, Chat-Fehler und command-only Responses
Made-with: Cursor
This commit is contained in:
parent
05f1c98825
commit
e2c1c58442
1 changed files with 156 additions and 2 deletions
|
|
@ -342,6 +342,12 @@ class TeamsbotService:
|
||||||
logger.info(f"[WS] Voice greeting: text={greetingText[:60]}..., language={greetingLang}")
|
logger.info(f"[WS] Voice greeting: text={greetingText[:60]}..., language={greetingLang}")
|
||||||
if greetingText and voiceInterface:
|
if greetingText and voiceInterface:
|
||||||
try:
|
try:
|
||||||
|
await _emitSessionEvent(sessionId, "ttsDeliveryStatus", {
|
||||||
|
"status": "requested",
|
||||||
|
"hasWebSocket": True,
|
||||||
|
"message": "Voice greeting TTS requested",
|
||||||
|
"timestamp": getIsoTimestamp(),
|
||||||
|
})
|
||||||
ttsResult = await voiceInterface.textToSpeech(
|
ttsResult = await voiceInterface.textToSpeech(
|
||||||
text=greetingText,
|
text=greetingText,
|
||||||
languageCode=greetingLang,
|
languageCode=greetingLang,
|
||||||
|
|
@ -359,6 +365,53 @@ class TeamsbotService:
|
||||||
}
|
}
|
||||||
}))
|
}))
|
||||||
logger.info(f"Voice greeting TTS sent for session {sessionId}")
|
logger.info(f"Voice greeting TTS sent for session {sessionId}")
|
||||||
|
await _emitSessionEvent(sessionId, "ttsDeliveryStatus", {
|
||||||
|
"status": "dispatched",
|
||||||
|
"hasWebSocket": True,
|
||||||
|
"message": "Voice greeting TTS dispatched to bot",
|
||||||
|
"timestamp": getIsoTimestamp(),
|
||||||
|
})
|
||||||
|
|
||||||
|
greetingTranscriptData = TeamsbotTranscript(
|
||||||
|
sessionId=sessionId,
|
||||||
|
speaker=self.config.botName,
|
||||||
|
text=greetingText,
|
||||||
|
timestamp=getIsoTimestamp(),
|
||||||
|
confidence=1.0,
|
||||||
|
language=greetingLang,
|
||||||
|
isFinal=True,
|
||||||
|
source="botResponse",
|
||||||
|
).model_dump()
|
||||||
|
greetingTranscript = interface.createTranscript(greetingTranscriptData)
|
||||||
|
|
||||||
|
self._contextBuffer.append({
|
||||||
|
"speaker": self.config.botName,
|
||||||
|
"text": greetingText,
|
||||||
|
"timestamp": getUtcTimestamp(),
|
||||||
|
"source": "botResponse",
|
||||||
|
})
|
||||||
|
self._lastTranscriptSpeaker = self.config.botName
|
||||||
|
self._lastTranscriptText = greetingText
|
||||||
|
self._lastTranscriptId = greetingTranscript.get("id")
|
||||||
|
|
||||||
|
await _emitSessionEvent(sessionId, "botResponse", {
|
||||||
|
"id": greetingTranscript.get("id"),
|
||||||
|
"responseText": greetingText,
|
||||||
|
"responseType": TeamsbotResponseType.AUDIO.value,
|
||||||
|
"detectedIntent": "greeting",
|
||||||
|
"reasoning": "Automatic join greeting",
|
||||||
|
"timestamp": getIsoTimestamp(),
|
||||||
|
})
|
||||||
|
await _emitSessionEvent(sessionId, "transcript", {
|
||||||
|
"id": greetingTranscript.get("id"),
|
||||||
|
"speaker": self.config.botName,
|
||||||
|
"text": greetingText,
|
||||||
|
"confidence": 1.0,
|
||||||
|
"timestamp": getIsoTimestamp(),
|
||||||
|
"isContinuation": False,
|
||||||
|
"source": "botResponse",
|
||||||
|
"speakerResolvedFromHint": False,
|
||||||
|
})
|
||||||
except Exception as ttsErr:
|
except Exception as ttsErr:
|
||||||
logger.warning(f"Voice greeting TTS failed for session {sessionId}: {ttsErr}")
|
logger.warning(f"Voice greeting TTS failed for session {sessionId}: {ttsErr}")
|
||||||
|
|
||||||
|
|
@ -430,6 +483,21 @@ class TeamsbotService:
|
||||||
_waitAndForwardMfa(sessionId, mfaQueue, websocket)
|
_waitAndForwardMfa(sessionId, mfaQueue, websocket)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
elif msgType == "chatSendFailed":
|
||||||
|
errorData = message.get("error", {})
|
||||||
|
reason = errorData.get("reason", "unknown")
|
||||||
|
failedText = errorData.get("text", "")
|
||||||
|
logger.warning(
|
||||||
|
f"[WS] Chat send failed for session {sessionId}: "
|
||||||
|
f"reason={reason}, text={failedText[:60]}"
|
||||||
|
)
|
||||||
|
await _emitSessionEvent(sessionId, "chatSendFailed", {
|
||||||
|
"reason": reason,
|
||||||
|
"message": errorData.get("message", "Chat message could not be sent"),
|
||||||
|
"text": failedText,
|
||||||
|
"timestamp": getIsoTimestamp(),
|
||||||
|
})
|
||||||
|
|
||||||
elif msgType == "mfaResolved":
|
elif msgType == "mfaResolved":
|
||||||
success = message.get("success", False)
|
success = message.get("success", False)
|
||||||
logger.info(f"[WS] MFA resolved: success={success}")
|
logger.info(f"[WS] MFA resolved: success={success}")
|
||||||
|
|
@ -1282,6 +1350,51 @@ class TeamsbotService:
|
||||||
if speechResult.commands:
|
if speechResult.commands:
|
||||||
await self._executeCommands(sessionId, speechResult.commands, voiceInterface, websocket)
|
await self._executeCommands(sessionId, speechResult.commands, voiceInterface, websocket)
|
||||||
|
|
||||||
|
# When AI used only commands (no responseText), emit botResponse SSE
|
||||||
|
# so the UI shows the response. Extract text from sendChat commands.
|
||||||
|
if speechResult.shouldRespond and not speechResult.responseText:
|
||||||
|
cmdTexts = [
|
||||||
|
c.params.get("text", "") for c in speechResult.commands
|
||||||
|
if c.action == "sendChat" and c.params and c.params.get("text")
|
||||||
|
]
|
||||||
|
combinedText = " ".join(cmdTexts) if cmdTexts else None
|
||||||
|
if combinedText:
|
||||||
|
botResponseData = TeamsbotBotResponse(
|
||||||
|
sessionId=sessionId,
|
||||||
|
responseText=combinedText,
|
||||||
|
responseType=TeamsbotResponseType.CHAT,
|
||||||
|
detectedIntent=speechResult.detectedIntent,
|
||||||
|
reasoning=speechResult.reasoning,
|
||||||
|
triggeredByTranscriptId=triggerTranscript.get("id"),
|
||||||
|
modelName=response.modelName,
|
||||||
|
processingTime=response.processingTime,
|
||||||
|
priceCHF=response.priceCHF,
|
||||||
|
timestamp=getIsoTimestamp(),
|
||||||
|
).model_dump()
|
||||||
|
createdResponse = interface.createBotResponse(botResponseData)
|
||||||
|
await _emitSessionEvent(sessionId, "botResponse", {
|
||||||
|
"id": createdResponse.get("id"),
|
||||||
|
"responseText": combinedText,
|
||||||
|
"responseType": TeamsbotResponseType.CHAT.value,
|
||||||
|
"detectedIntent": speechResult.detectedIntent,
|
||||||
|
"reasoning": speechResult.reasoning,
|
||||||
|
"modelName": response.modelName,
|
||||||
|
"processingTime": response.processingTime,
|
||||||
|
"priceCHF": response.priceCHF,
|
||||||
|
"timestamp": botResponseData.get("timestamp"),
|
||||||
|
})
|
||||||
|
|
||||||
|
session = interface.getSession(sessionId)
|
||||||
|
if session:
|
||||||
|
count = session.get("botResponseCount", 0) + 1
|
||||||
|
interface.updateSession(sessionId, {"botResponseCount": count})
|
||||||
|
|
||||||
|
self._followUpWindowEnd = time.time() + 15.0
|
||||||
|
logger.info(
|
||||||
|
f"Bot responded via commands in session {sessionId}: "
|
||||||
|
f"intent={speechResult.detectedIntent}, follow-up window open for 15s"
|
||||||
|
)
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"SPEECH_TEAMS analysis failed for session {sessionId}: {type(e).__name__}: {e}", exc_info=True)
|
logger.error(f"SPEECH_TEAMS analysis failed for session {sessionId}: {type(e).__name__}: {e}", exc_info=True)
|
||||||
await _emitSessionEvent(sessionId, "error", {"message": f"AI analysis failed: {type(e).__name__}: {str(e)}"})
|
await _emitSessionEvent(sessionId, "error", {"message": f"AI analysis failed: {type(e).__name__}: {str(e)}"})
|
||||||
|
|
@ -1352,14 +1465,55 @@ class TeamsbotService:
|
||||||
}))
|
}))
|
||||||
|
|
||||||
async def _cmdSendChat(self, sessionId: str, params: dict, websocket: WebSocket):
|
async def _cmdSendChat(self, sessionId: str, params: dict, websocket: WebSocket):
|
||||||
"""Send a message to the meeting chat."""
|
"""Send a message to the meeting chat and record it in transcript/SSE."""
|
||||||
chatText = params.get("text", "")
|
chatText = params.get("text", "")
|
||||||
if chatText and websocket:
|
if not chatText:
|
||||||
|
return
|
||||||
|
if websocket:
|
||||||
await websocket.send_text(json.dumps({
|
await websocket.send_text(json.dumps({
|
||||||
"type": "sendChatMessage",
|
"type": "sendChatMessage",
|
||||||
"sessionId": sessionId,
|
"sessionId": sessionId,
|
||||||
"text": chatText,
|
"text": chatText,
|
||||||
}))
|
}))
|
||||||
|
logger.info(f"Chat command sent for session {sessionId}")
|
||||||
|
|
||||||
|
from . import interfaceFeatureTeamsbot as interfaceDb
|
||||||
|
interface = interfaceDb.getInterface(self.currentUser, self.mandateId, self.instanceId)
|
||||||
|
|
||||||
|
transcriptData = TeamsbotTranscript(
|
||||||
|
sessionId=sessionId,
|
||||||
|
speaker=self.config.botName,
|
||||||
|
text=chatText,
|
||||||
|
timestamp=getIsoTimestamp(),
|
||||||
|
confidence=1.0,
|
||||||
|
language=self.config.language,
|
||||||
|
isFinal=True,
|
||||||
|
source="chat",
|
||||||
|
).model_dump()
|
||||||
|
createdTranscript = interface.createTranscript(transcriptData)
|
||||||
|
|
||||||
|
self._contextBuffer.append({
|
||||||
|
"speaker": self.config.botName,
|
||||||
|
"text": chatText,
|
||||||
|
"timestamp": getUtcTimestamp(),
|
||||||
|
"source": "chat",
|
||||||
|
})
|
||||||
|
self._lastTranscriptSpeaker = self.config.botName
|
||||||
|
self._lastTranscriptText = chatText
|
||||||
|
self._lastTranscriptId = createdTranscript.get("id")
|
||||||
|
self._lastBotResponseText = chatText.strip().lower()
|
||||||
|
self._lastBotResponseTs = time.time()
|
||||||
|
|
||||||
|
await _emitSessionEvent(sessionId, "transcript", {
|
||||||
|
"id": createdTranscript.get("id"),
|
||||||
|
"speaker": self.config.botName,
|
||||||
|
"text": chatText,
|
||||||
|
"confidence": 1.0,
|
||||||
|
"timestamp": getIsoTimestamp(),
|
||||||
|
"isContinuation": False,
|
||||||
|
"source": "chat",
|
||||||
|
"speakerResolvedFromHint": False,
|
||||||
|
})
|
||||||
|
|
||||||
async def _cmdReadChat(
|
async def _cmdReadChat(
|
||||||
self,
|
self,
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue