teams bridge
This commit is contained in:
parent
edecfb002c
commit
b7e4efb3a3
8 changed files with 88 additions and 52 deletions
|
|
@ -57,6 +57,9 @@ Connector_GoogleSpeech_API_KEY_SECRET = DEV_ENC:Z0FBQUFBQm8xSUpETk5FWWM3Q0JKMzhI
|
||||||
# Feature SyncDelta JIRA configuration
|
# Feature SyncDelta JIRA configuration
|
||||||
Feature_SyncDelta_JIRA_DELTA_TOKEN_SECRET = DEV_ENC:Z0FBQUFBQm8xSUpEbm0yRUJ6VUJKbUwyRW5kMnRaNW4wM2YxMkJUTXVXZUdmdVRCaUZIVHU2TTV2RWZLRmUtZkcwZE4yRUNlNDQ0aUJWYjNfdVg5YjV5c2JwMHhoUUYxZWdkeS11bXR0eGxRLWRVaVU3cUVQZWJlNDRtY1lWUDdqeDVFSlpXS0VFX21WajlRS3lHQjc0bS11akkybWV3QUFlR2hNWUNYLUdiRjZuN2dQODdDSExXWG1Dd2ZGclI2aUhlSWhETVZuY3hYdnhkb2c2LU1JTFBvWFpTNmZtMkNVOTZTejJwbDI2eGE0OS1xUlIwQnlCSmFxRFNCeVJNVzlOMDhTR1VUamx4RDRyV3p6Tk9qVHBrWWdySUM3TVRaYjd3N0JHMFhpdzFhZTNDLTFkRVQ2RVE4U19COXRhRWtNc0NVOHRqUS1CRDFpZ19xQmtFLU9YSDU3TXBZQXpVcld3PT0=
|
Feature_SyncDelta_JIRA_DELTA_TOKEN_SECRET = DEV_ENC:Z0FBQUFBQm8xSUpEbm0yRUJ6VUJKbUwyRW5kMnRaNW4wM2YxMkJUTXVXZUdmdVRCaUZIVHU2TTV2RWZLRmUtZkcwZE4yRUNlNDQ0aUJWYjNfdVg5YjV5c2JwMHhoUUYxZWdkeS11bXR0eGxRLWRVaVU3cUVQZWJlNDRtY1lWUDdqeDVFSlpXS0VFX21WajlRS3lHQjc0bS11akkybWV3QUFlR2hNWUNYLUdiRjZuN2dQODdDSExXWG1Dd2ZGclI2aUhlSWhETVZuY3hYdnhkb2c2LU1JTFBvWFpTNmZtMkNVOTZTejJwbDI2eGE0OS1xUlIwQnlCSmFxRFNCeVJNVzlOMDhTR1VUamx4RDRyV3p6Tk9qVHBrWWdySUM3TVRaYjd3N0JHMFhpdzFhZTNDLTFkRVQ2RVE4U19COXRhRWtNc0NVOHRqUS1CRDFpZ19xQmtFLU9YSDU3TXBZQXpVcld3PT0=
|
||||||
|
|
||||||
|
# Teamsbot Media Bridge
|
||||||
|
TEAMSBOT_BRIDGE_URL = https://media.poweron.swiss:9440
|
||||||
|
|
||||||
# Debug Configuration
|
# Debug Configuration
|
||||||
APP_DEBUG_CHAT_WORKFLOW_ENABLED = True
|
APP_DEBUG_CHAT_WORKFLOW_ENABLED = True
|
||||||
APP_DEBUG_CHAT_WORKFLOW_DIR = D:/Athi/Local/Web/poweron/local/debug
|
APP_DEBUG_CHAT_WORKFLOW_DIR = D:/Athi/Local/Web/poweron/local/debug
|
||||||
|
|
|
||||||
|
|
@ -57,6 +57,9 @@ Connector_GoogleSpeech_API_KEY_SECRET = INT_ENC:Z0FBQUFBQm8xSVRkNmVXZ1pWcHcydTF2
|
||||||
# Feature SyncDelta JIRA configuration
|
# Feature SyncDelta JIRA configuration
|
||||||
Feature_SyncDelta_JIRA_DELTA_TOKEN_SECRET = INT_ENC:Z0FBQUFBQm8xSVRkTUNsWm4wX0p6eXFDZmJ4dFdHNEs1MV9MUzdrb3RzeC1jVWVYZ0REWHRyZkFiaGZLcUQtTXFBZzZkNzRmQ0gxbEhGbUNlVVFfR1JEQTc0aldkZkgyWnBOcjdlUlZxR0tDTEdKRExULXAyUEtsVmNTMkRKU1BJNnFiM0hlMXo4YndMcHlRMExtZDQ3Zm9vNFhMcEZCcHpBPT0=
|
Feature_SyncDelta_JIRA_DELTA_TOKEN_SECRET = INT_ENC:Z0FBQUFBQm8xSVRkTUNsWm4wX0p6eXFDZmJ4dFdHNEs1MV9MUzdrb3RzeC1jVWVYZ0REWHRyZkFiaGZLcUQtTXFBZzZkNzRmQ0gxbEhGbUNlVVFfR1JEQTc0aldkZkgyWnBOcjdlUlZxR0tDTEdKRExULXAyUEtsVmNTMkRKU1BJNnFiM0hlMXo4YndMcHlRMExtZDQ3Zm9vNFhMcEZCcHpBPT0=
|
||||||
|
|
||||||
|
# Teamsbot Media Bridge
|
||||||
|
TEAMSBOT_BRIDGE_URL = https://media.poweron.swiss:9440
|
||||||
|
|
||||||
# Debug Configuration
|
# Debug Configuration
|
||||||
APP_DEBUG_CHAT_WORKFLOW_ENABLED = FALSE
|
APP_DEBUG_CHAT_WORKFLOW_ENABLED = FALSE
|
||||||
APP_DEBUG_CHAT_WORKFLOW_DIR = ./test-chat
|
APP_DEBUG_CHAT_WORKFLOW_DIR = ./test-chat
|
||||||
|
|
|
||||||
|
|
@ -57,6 +57,9 @@ Connector_GoogleSpeech_API_KEY_SECRET = PROD_ENC:Z0FBQUFBQnBDM1Z4NFQxaF9uN3h1cVB
|
||||||
# Feature SyncDelta JIRA configuration
|
# Feature SyncDelta JIRA configuration
|
||||||
Feature_SyncDelta_JIRA_DELTA_TOKEN_SECRET = PROD_ENC:Z0FBQUFBQnBDM1Z4d3Z4d2x6N1FhUktMU0RKbkxfY2pTQkRzXzJ6UXVEbDNCaFM3UHMtQVFGYzNmYWs4N0lMM1R2SFJuZTVFVmx6MGVEbXc5U3NOTnY1TWN0ZDNaamlHQWloalM3VldmREJNSHQ1TlVkSVFJMTVhQWVGSVRMTGw4UTBqNGlQZFVuaHp4WUlKemR5UnBXZlh0REJFLXJ4ejR3PT0=
|
Feature_SyncDelta_JIRA_DELTA_TOKEN_SECRET = PROD_ENC:Z0FBQUFBQnBDM1Z4d3Z4d2x6N1FhUktMU0RKbkxfY2pTQkRzXzJ6UXVEbDNCaFM3UHMtQVFGYzNmYWs4N0lMM1R2SFJuZTVFVmx6MGVEbXc5U3NOTnY1TWN0ZDNaamlHQWloalM3VldmREJNSHQ1TlVkSVFJMTVhQWVGSVRMTGw4UTBqNGlQZFVuaHp4WUlKemR5UnBXZlh0REJFLXJ4ejR3PT0=
|
||||||
|
|
||||||
|
# Teamsbot Media Bridge
|
||||||
|
TEAMSBOT_BRIDGE_URL = https://media.poweron.swiss:9440
|
||||||
|
|
||||||
# Debug Configuration
|
# Debug Configuration
|
||||||
APP_DEBUG_CHAT_WORKFLOW_ENABLED = FALSE
|
APP_DEBUG_CHAT_WORKFLOW_ENABLED = FALSE
|
||||||
APP_DEBUG_CHAT_WORKFLOW_DIR = ./test-chat
|
APP_DEBUG_CHAT_WORKFLOW_DIR = ./test-chat
|
||||||
|
|
|
||||||
|
|
@ -116,11 +116,18 @@ class TeamsbotConfig(BaseModel):
|
||||||
responseMode: TeamsbotResponseMode = Field(default=TeamsbotResponseMode.AUTO, description="How the bot responds")
|
responseMode: TeamsbotResponseMode = Field(default=TeamsbotResponseMode.AUTO, description="How the bot responds")
|
||||||
language: str = Field(default="de-DE", description="Primary language for STT/TTS")
|
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)")
|
voiceId: Optional[str] = Field(default=None, description="Google TTS voice ID (e.g., de-DE-Standard-A)")
|
||||||
bridgeUrl: Optional[str] = Field(default=None, description="URL of the .NET Media Bridge service")
|
bridgeUrl: Optional[str] = Field(default=None, description="URL of the .NET Media Bridge service. Falls back to TEAMSBOT_BRIDGE_URL env variable if not set per-instance.")
|
||||||
triggerIntervalSeconds: int = Field(default=10, ge=3, le=60, description="Seconds between periodic AI analysis triggers")
|
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")
|
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")
|
contextWindowSegments: int = Field(default=20, ge=5, le=100, description="Number of transcript segments to include in AI context")
|
||||||
|
|
||||||
|
def _getEffectiveBridgeUrl(self) -> Optional[str]:
|
||||||
|
"""Resolve the effective bridge URL: per-instance config takes priority, then env variable."""
|
||||||
|
if self.bridgeUrl:
|
||||||
|
return self.bridgeUrl
|
||||||
|
from modules.shared.configuration import APP_CONFIG
|
||||||
|
return APP_CONFIG.get("TEAMSBOT_BRIDGE_URL")
|
||||||
|
|
||||||
|
|
||||||
# ============================================================================
|
# ============================================================================
|
||||||
# API Request/Response Models
|
# API Request/Response Models
|
||||||
|
|
|
||||||
|
|
@ -45,7 +45,22 @@ class TeamsbotObjects:
|
||||||
self.currentUser = currentUser
|
self.currentUser = currentUser
|
||||||
self.mandateId = mandateId
|
self.mandateId = mandateId
|
||||||
self.featureInstanceId = featureInstanceId
|
self.featureInstanceId = featureInstanceId
|
||||||
self.db = DatabaseConnector()
|
self.userId = str(currentUser.id) if currentUser else "system"
|
||||||
|
|
||||||
|
dbHost = APP_CONFIG.get("DB_HOST", "_no_config_default_data")
|
||||||
|
dbDatabase = "poweron_teamsbot"
|
||||||
|
dbUser = APP_CONFIG.get("DB_USER")
|
||||||
|
dbPassword = APP_CONFIG.get("DB_PASSWORD_SECRET")
|
||||||
|
dbPort = int(APP_CONFIG.get("DB_PORT", 5432))
|
||||||
|
|
||||||
|
self.db = DatabaseConnector(
|
||||||
|
dbHost=dbHost,
|
||||||
|
dbDatabase=dbDatabase,
|
||||||
|
dbUser=dbUser,
|
||||||
|
dbPassword=dbPassword,
|
||||||
|
dbPort=dbPort,
|
||||||
|
userId=self.userId,
|
||||||
|
)
|
||||||
|
|
||||||
# =========================================================================
|
# =========================================================================
|
||||||
# Sessions
|
# Sessions
|
||||||
|
|
@ -53,35 +68,32 @@ class TeamsbotObjects:
|
||||||
|
|
||||||
def getSessions(self, instanceId: str, includeEnded: bool = True) -> List[Dict[str, Any]]:
|
def getSessions(self, instanceId: str, includeEnded: bool = True) -> List[Dict[str, Any]]:
|
||||||
"""Get all sessions for a feature instance."""
|
"""Get all sessions for a feature instance."""
|
||||||
filters = {"instanceId": instanceId}
|
|
||||||
if not includeEnded:
|
|
||||||
filters["status__ne"] = TeamsbotSessionStatus.ENDED.value
|
|
||||||
|
|
||||||
records = self.db.getRecordset(
|
records = self.db.getRecordset(
|
||||||
TeamsbotSession,
|
TeamsbotSession,
|
||||||
filters=filters,
|
recordFilter={"instanceId": instanceId},
|
||||||
orderBy=[("startedAt", "DESC")]
|
|
||||||
)
|
)
|
||||||
|
if not includeEnded:
|
||||||
|
records = [r for r in records if r.get("status") != TeamsbotSessionStatus.ENDED.value]
|
||||||
|
# Sort by startedAt descending
|
||||||
|
records.sort(key=lambda r: r.get("startedAt") or "", reverse=True)
|
||||||
return records
|
return records
|
||||||
|
|
||||||
def getActiveSessions(self, instanceId: str) -> List[Dict[str, Any]]:
|
def getActiveSessions(self, instanceId: str) -> List[Dict[str, Any]]:
|
||||||
"""Get only active (non-ended, non-error) sessions."""
|
"""Get only active (non-ended, non-error) sessions."""
|
||||||
records = self.db.getRecordset(
|
records = self.db.getRecordset(
|
||||||
TeamsbotSession,
|
TeamsbotSession,
|
||||||
filters={
|
recordFilter={"instanceId": instanceId},
|
||||||
"instanceId": instanceId,
|
)
|
||||||
"status__in": [
|
activeStatuses = {
|
||||||
TeamsbotSessionStatus.PENDING.value,
|
TeamsbotSessionStatus.PENDING.value,
|
||||||
TeamsbotSessionStatus.JOINING.value,
|
TeamsbotSessionStatus.JOINING.value,
|
||||||
TeamsbotSessionStatus.ACTIVE.value,
|
TeamsbotSessionStatus.ACTIVE.value,
|
||||||
]
|
|
||||||
}
|
}
|
||||||
)
|
return [r for r in records if r.get("status") in activeStatuses]
|
||||||
return records
|
|
||||||
|
|
||||||
def getSession(self, sessionId: str) -> Optional[Dict[str, Any]]:
|
def getSession(self, sessionId: str) -> Optional[Dict[str, Any]]:
|
||||||
"""Get a single session by ID."""
|
"""Get a single session by ID."""
|
||||||
records = self.db.getRecordset(TeamsbotSession, filters={"id": sessionId})
|
records = self.db.getRecordset(TeamsbotSession, recordFilter={"id": sessionId})
|
||||||
return records[0] if records else None
|
return records[0] if records else None
|
||||||
|
|
||||||
def createSession(self, sessionData: Dict[str, Any]) -> Dict[str, Any]:
|
def createSession(self, sessionData: Dict[str, Any]) -> Dict[str, Any]:
|
||||||
|
|
@ -109,27 +121,25 @@ class TeamsbotObjects:
|
||||||
|
|
||||||
def getTranscripts(self, sessionId: str, limit: int = None, offset: int = None) -> List[Dict[str, Any]]:
|
def getTranscripts(self, sessionId: str, limit: int = None, offset: int = None) -> List[Dict[str, Any]]:
|
||||||
"""Get transcript segments for a session, ordered by timestamp."""
|
"""Get transcript segments for a session, ordered by timestamp."""
|
||||||
filters = {"sessionId": sessionId}
|
|
||||||
records = self.db.getRecordset(
|
records = self.db.getRecordset(
|
||||||
TeamsbotTranscript,
|
TeamsbotTranscript,
|
||||||
filters=filters,
|
recordFilter={"sessionId": sessionId},
|
||||||
orderBy=[("timestamp", "ASC")],
|
|
||||||
limit=limit,
|
|
||||||
offset=offset
|
|
||||||
)
|
)
|
||||||
|
records.sort(key=lambda r: r.get("timestamp") or "")
|
||||||
|
if offset:
|
||||||
|
records = records[offset:]
|
||||||
|
if limit:
|
||||||
|
records = records[:limit]
|
||||||
return records
|
return records
|
||||||
|
|
||||||
def getRecentTranscripts(self, sessionId: str, count: int = 20) -> List[Dict[str, Any]]:
|
def getRecentTranscripts(self, sessionId: str, count: int = 20) -> List[Dict[str, Any]]:
|
||||||
"""Get the most recent N transcript segments for context building."""
|
"""Get the most recent N transcript segments for context building."""
|
||||||
records = self.db.getRecordset(
|
records = self.db.getRecordset(
|
||||||
TeamsbotTranscript,
|
TeamsbotTranscript,
|
||||||
filters={"sessionId": sessionId},
|
recordFilter={"sessionId": sessionId},
|
||||||
orderBy=[("timestamp", "DESC")],
|
|
||||||
limit=count
|
|
||||||
)
|
)
|
||||||
# Reverse to get chronological order
|
records.sort(key=lambda r: r.get("timestamp") or "")
|
||||||
records.reverse()
|
return records[-count:]
|
||||||
return records
|
|
||||||
|
|
||||||
def createTranscript(self, transcriptData: Dict[str, Any]) -> Dict[str, Any]:
|
def createTranscript(self, transcriptData: Dict[str, Any]) -> Dict[str, Any]:
|
||||||
"""Create a new transcript segment."""
|
"""Create a new transcript segment."""
|
||||||
|
|
@ -138,7 +148,7 @@ class TeamsbotObjects:
|
||||||
|
|
||||||
def _deleteTranscriptsBySession(self, sessionId: str) -> int:
|
def _deleteTranscriptsBySession(self, sessionId: str) -> int:
|
||||||
"""Delete all transcripts for a session."""
|
"""Delete all transcripts for a session."""
|
||||||
records = self.db.getRecordset(TeamsbotTranscript, filters={"sessionId": sessionId})
|
records = self.db.getRecordset(TeamsbotTranscript, recordFilter={"sessionId": sessionId})
|
||||||
count = 0
|
count = 0
|
||||||
for record in records:
|
for record in records:
|
||||||
self.db.recordDelete(TeamsbotTranscript, record.get("id"))
|
self.db.recordDelete(TeamsbotTranscript, record.get("id"))
|
||||||
|
|
@ -153,9 +163,9 @@ class TeamsbotObjects:
|
||||||
"""Get all bot responses for a session."""
|
"""Get all bot responses for a session."""
|
||||||
records = self.db.getRecordset(
|
records = self.db.getRecordset(
|
||||||
TeamsbotBotResponse,
|
TeamsbotBotResponse,
|
||||||
filters={"sessionId": sessionId},
|
recordFilter={"sessionId": sessionId},
|
||||||
orderBy=[("timestamp", "ASC")]
|
|
||||||
)
|
)
|
||||||
|
records.sort(key=lambda r: r.get("timestamp") or "")
|
||||||
return records
|
return records
|
||||||
|
|
||||||
def createBotResponse(self, responseData: Dict[str, Any]) -> Dict[str, Any]:
|
def createBotResponse(self, responseData: Dict[str, Any]) -> Dict[str, Any]:
|
||||||
|
|
@ -165,7 +175,7 @@ class TeamsbotObjects:
|
||||||
|
|
||||||
def _deleteResponsesBySession(self, sessionId: str) -> int:
|
def _deleteResponsesBySession(self, sessionId: str) -> int:
|
||||||
"""Delete all bot responses for a session."""
|
"""Delete all bot responses for a session."""
|
||||||
records = self.db.getRecordset(TeamsbotBotResponse, filters={"sessionId": sessionId})
|
records = self.db.getRecordset(TeamsbotBotResponse, recordFilter={"sessionId": sessionId})
|
||||||
count = 0
|
count = 0
|
||||||
for record in records:
|
for record in records:
|
||||||
self.db.recordDelete(TeamsbotBotResponse, record.get("id"))
|
self.db.recordDelete(TeamsbotBotResponse, record.get("id"))
|
||||||
|
|
@ -178,8 +188,8 @@ class TeamsbotObjects:
|
||||||
|
|
||||||
def getSessionStats(self, sessionId: str) -> Dict[str, Any]:
|
def getSessionStats(self, sessionId: str) -> Dict[str, Any]:
|
||||||
"""Get aggregated statistics for a session."""
|
"""Get aggregated statistics for a session."""
|
||||||
transcripts = self.db.getRecordset(TeamsbotTranscript, filters={"sessionId": sessionId})
|
transcripts = self.db.getRecordset(TeamsbotTranscript, recordFilter={"sessionId": sessionId})
|
||||||
responses = self.db.getRecordset(TeamsbotBotResponse, filters={"sessionId": sessionId})
|
responses = self.db.getRecordset(TeamsbotBotResponse, recordFilter={"sessionId": sessionId})
|
||||||
|
|
||||||
totalCost = sum(r.get("priceCHF", 0) for r in responses)
|
totalCost = sum(r.get("priceCHF", 0) for r in responses)
|
||||||
totalProcessingTime = sum(r.get("processingTime", 0) for r in responses)
|
totalProcessingTime = sum(r.get("processingTime", 0) for r in responses)
|
||||||
|
|
|
||||||
|
|
@ -98,9 +98,9 @@ def _getInstanceConfig(instanceId: str) -> TeamsbotConfig:
|
||||||
@router.post("/{instanceId}/sessions")
|
@router.post("/{instanceId}/sessions")
|
||||||
@limiter.limit("10/minute")
|
@limiter.limit("10/minute")
|
||||||
async def startSession(
|
async def startSession(
|
||||||
|
request: Request,
|
||||||
instanceId: str,
|
instanceId: str,
|
||||||
request: TeamsbotStartSessionRequest,
|
body: TeamsbotStartSessionRequest,
|
||||||
httpRequest: Request,
|
|
||||||
context: RequestContext = Depends(getRequestContext),
|
context: RequestContext = Depends(getRequestContext),
|
||||||
):
|
):
|
||||||
"""Start a new Teams Bot session -- bot joins the specified meeting."""
|
"""Start a new Teams Bot session -- bot joins the specified meeting."""
|
||||||
|
|
@ -112,9 +112,9 @@ async def startSession(
|
||||||
sessionData = TeamsbotSession(
|
sessionData = TeamsbotSession(
|
||||||
instanceId=instanceId,
|
instanceId=instanceId,
|
||||||
mandateId=mandateId,
|
mandateId=mandateId,
|
||||||
meetingLink=request.meetingLink,
|
meetingLink=body.meetingLink,
|
||||||
botName=request.botName or config.botName,
|
botName=body.botName or config.botName,
|
||||||
backgroundImageUrl=request.backgroundImageUrl or config.backgroundImageUrl,
|
backgroundImageUrl=body.backgroundImageUrl or config.backgroundImageUrl,
|
||||||
status=TeamsbotSessionStatus.PENDING,
|
status=TeamsbotSessionStatus.PENDING,
|
||||||
startedByUserId=str(context.user.id),
|
startedByUserId=str(context.user.id),
|
||||||
).model_dump()
|
).model_dump()
|
||||||
|
|
@ -124,12 +124,12 @@ async def startSession(
|
||||||
|
|
||||||
# Derive gateway base URL from the incoming request so the bridge
|
# Derive gateway base URL from the incoming request so the bridge
|
||||||
# can build full callback/WS URLs targeting this specific gateway instance.
|
# can build full callback/WS URLs targeting this specific gateway instance.
|
||||||
gatewayBaseUrl = str(httpRequest.base_url).rstrip("/")
|
gatewayBaseUrl = str(request.base_url).rstrip("/")
|
||||||
|
|
||||||
# Start the bot in background (join meeting via bridge)
|
# Start the bot in background (join meeting via bridge)
|
||||||
service = TeamsbotService(context.user, mandateId, instanceId, config)
|
service = TeamsbotService(context.user, mandateId, instanceId, config)
|
||||||
asyncio.create_task(
|
asyncio.create_task(
|
||||||
service.joinMeeting(sessionId, request.meetingLink, request.connectionId, gatewayBaseUrl)
|
service.joinMeeting(sessionId, body.meetingLink, body.connectionId, gatewayBaseUrl)
|
||||||
)
|
)
|
||||||
|
|
||||||
logger.info(f"Teamsbot session {sessionId} created for instance {instanceId}")
|
logger.info(f"Teamsbot session {sessionId} created for instance {instanceId}")
|
||||||
|
|
@ -139,9 +139,10 @@ async def startSession(
|
||||||
@router.get("/{instanceId}/sessions")
|
@router.get("/{instanceId}/sessions")
|
||||||
@limiter.limit("30/minute")
|
@limiter.limit("30/minute")
|
||||||
async def listSessions(
|
async def listSessions(
|
||||||
|
request: Request,
|
||||||
instanceId: str,
|
instanceId: str,
|
||||||
includeEnded: bool = Query(True, description="Include ended sessions"),
|
includeEnded: bool = Query(True, description="Include ended sessions"),
|
||||||
context: RequestContext = Depends(getRequestContext)
|
context: RequestContext = Depends(getRequestContext),
|
||||||
):
|
):
|
||||||
"""List all sessions for a feature instance."""
|
"""List all sessions for a feature instance."""
|
||||||
_validateInstanceAccess(instanceId, context)
|
_validateInstanceAccess(instanceId, context)
|
||||||
|
|
@ -153,11 +154,12 @@ async def listSessions(
|
||||||
@router.get("/{instanceId}/sessions/{sessionId}")
|
@router.get("/{instanceId}/sessions/{sessionId}")
|
||||||
@limiter.limit("30/minute")
|
@limiter.limit("30/minute")
|
||||||
async def getSession(
|
async def getSession(
|
||||||
|
request: Request,
|
||||||
instanceId: str,
|
instanceId: str,
|
||||||
sessionId: str,
|
sessionId: str,
|
||||||
includeTranscripts: bool = Query(True),
|
includeTranscripts: bool = Query(True),
|
||||||
includeResponses: bool = Query(True),
|
includeResponses: bool = Query(True),
|
||||||
context: RequestContext = Depends(getRequestContext)
|
context: RequestContext = Depends(getRequestContext),
|
||||||
):
|
):
|
||||||
"""Get session details with optional transcripts and bot responses."""
|
"""Get session details with optional transcripts and bot responses."""
|
||||||
_validateInstanceAccess(instanceId, context)
|
_validateInstanceAccess(instanceId, context)
|
||||||
|
|
@ -182,9 +184,10 @@ async def getSession(
|
||||||
@router.get("/{instanceId}/sessions/{sessionId}/stream")
|
@router.get("/{instanceId}/sessions/{sessionId}/stream")
|
||||||
@limiter.limit("10/minute")
|
@limiter.limit("10/minute")
|
||||||
async def streamSession(
|
async def streamSession(
|
||||||
|
request: Request,
|
||||||
instanceId: str,
|
instanceId: str,
|
||||||
sessionId: str,
|
sessionId: str,
|
||||||
context: RequestContext = Depends(getRequestContext)
|
context: RequestContext = Depends(getRequestContext),
|
||||||
):
|
):
|
||||||
"""
|
"""
|
||||||
SSE live stream for a session.
|
SSE live stream for a session.
|
||||||
|
|
@ -240,9 +243,10 @@ async def streamSession(
|
||||||
@router.post("/{instanceId}/sessions/{sessionId}/stop")
|
@router.post("/{instanceId}/sessions/{sessionId}/stop")
|
||||||
@limiter.limit("10/minute")
|
@limiter.limit("10/minute")
|
||||||
async def stopSession(
|
async def stopSession(
|
||||||
|
request: Request,
|
||||||
instanceId: str,
|
instanceId: str,
|
||||||
sessionId: str,
|
sessionId: str,
|
||||||
context: RequestContext = Depends(getRequestContext)
|
context: RequestContext = Depends(getRequestContext),
|
||||||
):
|
):
|
||||||
"""Stop an active session -- bot leaves the meeting."""
|
"""Stop an active session -- bot leaves the meeting."""
|
||||||
mandateId = _validateInstanceAccess(instanceId, context)
|
mandateId = _validateInstanceAccess(instanceId, context)
|
||||||
|
|
@ -268,9 +272,10 @@ async def stopSession(
|
||||||
@router.delete("/{instanceId}/sessions/{sessionId}")
|
@router.delete("/{instanceId}/sessions/{sessionId}")
|
||||||
@limiter.limit("10/minute")
|
@limiter.limit("10/minute")
|
||||||
async def deleteSession(
|
async def deleteSession(
|
||||||
|
request: Request,
|
||||||
instanceId: str,
|
instanceId: str,
|
||||||
sessionId: str,
|
sessionId: str,
|
||||||
context: RequestContext = Depends(getRequestContext)
|
context: RequestContext = Depends(getRequestContext),
|
||||||
):
|
):
|
||||||
"""Delete a session and all related data."""
|
"""Delete a session and all related data."""
|
||||||
_validateInstanceAccess(instanceId, context)
|
_validateInstanceAccess(instanceId, context)
|
||||||
|
|
@ -297,8 +302,9 @@ async def deleteSession(
|
||||||
@router.get("/{instanceId}/config")
|
@router.get("/{instanceId}/config")
|
||||||
@limiter.limit("30/minute")
|
@limiter.limit("30/minute")
|
||||||
async def getConfig(
|
async def getConfig(
|
||||||
|
request: Request,
|
||||||
instanceId: str,
|
instanceId: str,
|
||||||
context: RequestContext = Depends(getRequestContext)
|
context: RequestContext = Depends(getRequestContext),
|
||||||
):
|
):
|
||||||
"""Get the teamsbot configuration for a feature instance."""
|
"""Get the teamsbot configuration for a feature instance."""
|
||||||
_validateInstanceAccess(instanceId, context)
|
_validateInstanceAccess(instanceId, context)
|
||||||
|
|
@ -309,16 +315,17 @@ async def getConfig(
|
||||||
@router.put("/{instanceId}/config")
|
@router.put("/{instanceId}/config")
|
||||||
@limiter.limit("10/minute")
|
@limiter.limit("10/minute")
|
||||||
async def updateConfig(
|
async def updateConfig(
|
||||||
|
request: Request,
|
||||||
instanceId: str,
|
instanceId: str,
|
||||||
request: TeamsbotConfigUpdateRequest,
|
configUpdate: TeamsbotConfigUpdateRequest,
|
||||||
context: RequestContext = Depends(getRequestContext)
|
context: RequestContext = Depends(getRequestContext),
|
||||||
):
|
):
|
||||||
"""Update the teamsbot configuration."""
|
"""Update the teamsbot configuration."""
|
||||||
mandateId = _validateInstanceAccess(instanceId, context)
|
mandateId = _validateInstanceAccess(instanceId, context)
|
||||||
|
|
||||||
# Load current config and merge updates
|
# Load current config and merge updates
|
||||||
currentConfig = _getInstanceConfig(instanceId)
|
currentConfig = _getInstanceConfig(instanceId)
|
||||||
updateDict = request.model_dump(exclude_none=True)
|
updateDict = configUpdate.model_dump(exclude_none=True)
|
||||||
mergedConfig = currentConfig.model_copy(update=updateDict)
|
mergedConfig = currentConfig.model_copy(update=updateDict)
|
||||||
|
|
||||||
# Save to FeatureInstance.config
|
# Save to FeatureInstance.config
|
||||||
|
|
|
||||||
|
|
@ -55,7 +55,7 @@ class TeamsbotService:
|
||||||
self.mandateId = mandateId
|
self.mandateId = mandateId
|
||||||
self.instanceId = instanceId
|
self.instanceId = instanceId
|
||||||
self.config = config
|
self.config = config
|
||||||
self.bridgeConnector = BridgeConnector(config.bridgeUrl)
|
self.bridgeConnector = BridgeConnector(config._getEffectiveBridgeUrl())
|
||||||
|
|
||||||
# State
|
# State
|
||||||
self._lastAiCallTime: float = 0.0
|
self._lastAiCallTime: float = 0.0
|
||||||
|
|
|
||||||
|
|
@ -108,6 +108,9 @@ def _getFeatureUiObjects(featureCode: str) -> List[Dict[str, Any]]:
|
||||||
elif featureCode == "automation":
|
elif featureCode == "automation":
|
||||||
from modules.features.automation.mainAutomation import UI_OBJECTS
|
from modules.features.automation.mainAutomation import UI_OBJECTS
|
||||||
return UI_OBJECTS
|
return UI_OBJECTS
|
||||||
|
elif featureCode == "teamsbot":
|
||||||
|
from modules.features.teamsbot.mainTeamsbot import UI_OBJECTS
|
||||||
|
return UI_OBJECTS
|
||||||
else:
|
else:
|
||||||
logger.warning(f"Unknown feature code: {featureCode}")
|
logger.warning(f"Unknown feature code: {featureCode}")
|
||||||
return []
|
return []
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue