teamsbot anonymous bot working
This commit is contained in:
parent
c130f49cf9
commit
6380f14ebe
5 changed files with 69 additions and 7 deletions
|
|
@ -40,6 +40,8 @@ class BrowserBotConnector:
|
|||
botAccountPassword: Optional[str] = None,
|
||||
transferMode: str = "auto",
|
||||
debugMode: bool = False,
|
||||
avatarMediaData: Optional[str] = None,
|
||||
avatarMediaType: Optional[str] = None,
|
||||
) -> Dict[str, Any]:
|
||||
"""
|
||||
Send join command to the Browser Bot service.
|
||||
|
|
@ -79,12 +81,16 @@ class BrowserBotConnector:
|
|||
"debugMode": debugMode,
|
||||
}
|
||||
|
||||
# Add authenticated join credentials if configured
|
||||
if botAccountEmail and botAccountPassword:
|
||||
payload["botAccountEmail"] = botAccountEmail
|
||||
payload["botAccountPassword"] = botAccountPassword
|
||||
logger.info(f"Bot will join authenticated as {botAccountEmail}")
|
||||
|
||||
if avatarMediaData and avatarMediaType:
|
||||
payload["avatarMediaData"] = avatarMediaData
|
||||
payload["avatarMediaType"] = avatarMediaType
|
||||
logger.info(f"Avatar media attached: {avatarMediaType}, {len(avatarMediaData)} chars")
|
||||
|
||||
try:
|
||||
async with aiohttp.ClientSession(timeout=_BOT_TIMEOUT) as session:
|
||||
async with session.post(f"{self.botUrl}/api/bot", json=payload) as resp:
|
||||
|
|
|
|||
|
|
@ -119,6 +119,10 @@ class TeamsbotMeetingModule(PowerOnModel):
|
|||
default=None,
|
||||
description="Default display name for the bot when starting a session from this module",
|
||||
)
|
||||
defaultAvatarFileId: Optional[str] = Field(
|
||||
default=None,
|
||||
description="FileItem ID for the default avatar image/video shown in the meeting",
|
||||
)
|
||||
status: TeamsbotModuleStatus = Field(default=TeamsbotModuleStatus.ACTIVE)
|
||||
|
||||
|
||||
|
|
@ -225,6 +229,7 @@ class TeamsbotUserSettings(PowerOnModel):
|
|||
triggerCooldownSeconds: Optional[int] = Field(default=None, description="Trigger cooldown override")
|
||||
contextWindowSegments: Optional[int] = Field(default=None, description="Context window override")
|
||||
debugMode: Optional[bool] = Field(default=None, description="Debug mode override")
|
||||
avatarFileId: Optional[str] = Field(default=None, description="FileItem ID for bot avatar image/video override")
|
||||
|
||||
|
||||
# ============================================================================
|
||||
|
|
@ -248,6 +253,7 @@ class TeamsbotConfig(BaseModel):
|
|||
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")
|
||||
debugMode: bool = Field(default=False, description="Enable debug mode: screenshots at every join step for diagnostics")
|
||||
avatarFileId: Optional[str] = Field(default=None, description="FileItem ID for bot avatar image/video shown in the meeting")
|
||||
|
||||
def _getEffectiveBrowserBotUrl(self) -> Optional[str]:
|
||||
"""Resolve the effective browser bot URL: per-instance config takes priority, then env variable."""
|
||||
|
|
@ -288,6 +294,7 @@ class CreateMeetingModuleRequest(BaseModel):
|
|||
kpiTargets: Optional[str] = None
|
||||
defaultMeetingLink: Optional[str] = None
|
||||
defaultBotName: Optional[str] = None
|
||||
defaultAvatarFileId: Optional[str] = None
|
||||
|
||||
|
||||
class UpdateMeetingModuleRequest(BaseModel):
|
||||
|
|
@ -300,6 +307,7 @@ class UpdateMeetingModuleRequest(BaseModel):
|
|||
kpiTargets: Optional[str] = None
|
||||
defaultMeetingLink: Optional[str] = None
|
||||
defaultBotName: Optional[str] = None
|
||||
defaultAvatarFileId: Optional[str] = None
|
||||
status: Optional[TeamsbotModuleStatus] = None
|
||||
|
||||
|
||||
|
|
@ -317,6 +325,7 @@ class TeamsbotConfigUpdateRequest(BaseModel):
|
|||
triggerCooldownSeconds: Optional[int] = None
|
||||
contextWindowSegments: Optional[int] = None
|
||||
debugMode: Optional[bool] = None
|
||||
avatarFileId: Optional[str] = None
|
||||
|
||||
|
||||
# ============================================================================
|
||||
|
|
|
|||
|
|
@ -25,6 +25,7 @@ from .datamodelTeamsbot import (
|
|||
TeamsbotDirectorPromptStatus,
|
||||
TeamsbotDirectorPromptMode,
|
||||
TeamsbotMeetingModule,
|
||||
TeamsbotModuleStatus,
|
||||
)
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
|
@ -338,6 +339,8 @@ class TeamsbotObjects:
|
|||
def getModules(self, instanceId: str) -> List[Dict[str, Any]]:
|
||||
"""Get all meeting modules for a feature instance."""
|
||||
records = self.db.getRecordset(TeamsbotMeetingModule, recordFilter={"instanceId": instanceId})
|
||||
for r in records:
|
||||
r.setdefault("status", TeamsbotModuleStatus.ACTIVE.value)
|
||||
records.sort(key=lambda r: r.get("sysCreatedAt") or "", reverse=True)
|
||||
return records
|
||||
|
||||
|
|
|
|||
|
|
@ -40,6 +40,7 @@ from .datamodelTeamsbot import (
|
|||
TeamsbotDirectorPromptMode,
|
||||
TeamsbotDirectorPromptStatus,
|
||||
TeamsbotMeetingModule,
|
||||
TeamsbotModuleStatus,
|
||||
CreateMeetingModuleRequest,
|
||||
UpdateMeetingModuleRequest,
|
||||
DIRECTOR_PROMPT_FILE_LIMIT,
|
||||
|
|
@ -203,6 +204,7 @@ async def createModule(
|
|||
data["instanceId"] = instanceId
|
||||
data["mandateId"] = mandateId
|
||||
data["ownerUserId"] = str(context.user.id)
|
||||
data.setdefault("status", TeamsbotModuleStatus.ACTIVE.value)
|
||||
module = interface.createModule(data)
|
||||
return {"module": module}
|
||||
|
||||
|
|
@ -688,12 +690,10 @@ def _getEffectiveConfig(instanceId: str, userId: str, interface) -> TeamsbotConf
|
|||
if not userSettings:
|
||||
return baseConfig
|
||||
|
||||
# Merge: user settings override instance defaults (only non-None values)
|
||||
# Merge: user settings override instance defaults (only non-None values).
|
||||
# Derive mergeable fields from TeamsbotConfig so new fields are picked up automatically.
|
||||
overrides = {}
|
||||
for field in ["botName", "aiSystemPrompt", "responseMode",
|
||||
"responseChannel", "transferMode", "language", "voiceId",
|
||||
"triggerIntervalSeconds", "triggerCooldownSeconds", "contextWindowSegments",
|
||||
"debugMode"]:
|
||||
for field in TeamsbotConfig.model_fields:
|
||||
value = userSettings.get(field)
|
||||
if value is not None:
|
||||
overrides[field] = value
|
||||
|
|
|
|||
|
|
@ -732,6 +732,12 @@ class TeamsbotService:
|
|||
hasAuth = bool(botAccountEmail and botAccountPassword)
|
||||
logger.info(f"Joining meeting for session {sessionId}: auth={hasAuth}, email={botAccountEmail or 'N/A'}, transferMode={self.config.transferMode}")
|
||||
|
||||
avatarMediaData = None
|
||||
avatarMediaType = None
|
||||
avatarFileId = self._resolveAvatarFileId(session, interface)
|
||||
if avatarFileId:
|
||||
avatarMediaData, avatarMediaType = self._loadAvatarFileData(avatarFileId, interface)
|
||||
|
||||
result = await self.browserBotConnector.joinMeeting(
|
||||
sessionId=sessionId,
|
||||
meetingUrl=meetingLink,
|
||||
|
|
@ -743,6 +749,8 @@ class TeamsbotService:
|
|||
botAccountPassword=botAccountPassword,
|
||||
transferMode=self.config.transferMode if hasattr(self.config, 'transferMode') else "auto",
|
||||
debugMode=self.config.debugMode if hasattr(self.config, 'debugMode') else False,
|
||||
avatarMediaData=avatarMediaData,
|
||||
avatarMediaType=avatarMediaType,
|
||||
)
|
||||
|
||||
if result.get("success"):
|
||||
|
|
@ -767,6 +775,37 @@ class TeamsbotService:
|
|||
})
|
||||
await _emitSessionEvent(sessionId, "statusChange", {"status": "error", "errorMessage": str(e)})
|
||||
|
||||
def _resolveAvatarFileId(self, session, interface):
|
||||
"""Resolve avatarFileId: module override > config default."""
|
||||
moduleId = session.get("moduleId")
|
||||
if moduleId:
|
||||
module = interface.getModule(moduleId)
|
||||
if module and module.get("defaultAvatarFileId"):
|
||||
return module["defaultAvatarFileId"]
|
||||
return getattr(self.config, "avatarFileId", None)
|
||||
|
||||
def _loadAvatarFileData(self, fileId, _teamsbotInterface):
|
||||
"""Load avatar file as base64 data + mime type. Returns (data, mimeType) or (None, None)."""
|
||||
import base64
|
||||
from modules.interfaces import interfaceDbManagement
|
||||
try:
|
||||
mgmt = interfaceDbManagement.getInterface(self.currentUser, self.mandateId)
|
||||
fileRecord = mgmt.getFile(fileId)
|
||||
if not fileRecord:
|
||||
logger.warning(f"Avatar file {fileId} not found")
|
||||
return None, None
|
||||
mimeType = getattr(fileRecord, "mimeType", None) or "image/png"
|
||||
rawBytes = mgmt.getFileData(fileId)
|
||||
if not rawBytes:
|
||||
logger.warning(f"Avatar file {fileId} has no data")
|
||||
return None, None
|
||||
b64 = base64.b64encode(rawBytes).decode("ascii")
|
||||
logger.info(f"Avatar file loaded: {fileId}, {mimeType}, {len(b64)} chars base64")
|
||||
return b64, mimeType
|
||||
except Exception as e:
|
||||
logger.error(f"Failed to load avatar file {fileId}: {e}")
|
||||
return None, None
|
||||
|
||||
async def leaveMeeting(self, sessionId: str):
|
||||
"""Send leave command to the Browser Bot service."""
|
||||
from . import interfaceFeatureTeamsbot as interfaceDb
|
||||
|
|
@ -1217,6 +1256,12 @@ class TeamsbotService:
|
|||
if self.config.botName:
|
||||
phraseHints.append(self.config.botName)
|
||||
|
||||
# Sprache kommt ausschliesslich aus der Session/Instance-Konfig
|
||||
# (TeamsbotUserSettings.language ueberschreibt
|
||||
# TeamsbotConfig.language, Fallback de-DE im Schema).
|
||||
# KEIN hardcodierter Alternative-Sprachen-Pool — der hat dafuer
|
||||
# gesorgt, dass Google STT bei verrauschter Audio auf en-US
|
||||
# gesprungen ist und englisches Kauderwelsch geliefert hat.
|
||||
sttResult = await voiceInterface.speechToText(
|
||||
audioContent=audioBytes,
|
||||
language=self.config.language or "de-DE",
|
||||
|
|
@ -1224,7 +1269,6 @@ class TeamsbotService:
|
|||
channels=1,
|
||||
skipFallbacks=True,
|
||||
phraseHints=phraseHints if phraseHints else None,
|
||||
alternativeLanguages=["en-US"],
|
||||
audioFormat="linear16",
|
||||
)
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue