fix: chat queue to prevent interleaved text, retry+selectors for greeting
Made-with: Cursor
This commit is contained in:
parent
ee2dcd61f1
commit
dbcc53bed8
2 changed files with 94 additions and 48 deletions
|
|
@ -408,50 +408,67 @@ export class ChatProcedure {
|
|||
|
||||
/**
|
||||
* Send a chat message in the meeting.
|
||||
* Finds the chat input, types the message, and sends it.
|
||||
* Finds the chat input (with retry), types the message, and sends it.
|
||||
*/
|
||||
async sendChatMessage(text: string): Promise<boolean> {
|
||||
this._logger.info(`Sending chat message: ${text.substring(0, 60)}...`);
|
||||
|
||||
try {
|
||||
// Find chat input
|
||||
const inputSelectors = [
|
||||
'[data-tid="ckeditor-replyConversation"]',
|
||||
'div[role="textbox"][data-tid*="chat"]',
|
||||
'div[role="textbox"][aria-label*="message" i]',
|
||||
'div[role="textbox"][aria-label*="Nachricht" i]',
|
||||
'div[contenteditable="true"][data-tid*="message"]',
|
||||
'div[contenteditable="true"][aria-label*="message" i]',
|
||||
];
|
||||
const inputSelectors = [
|
||||
'[data-tid="ckeditor-replyConversation"]',
|
||||
'div[role="textbox"][data-tid*="chat"]',
|
||||
'div[role="textbox"][data-tid*="message"]',
|
||||
'div[role="textbox"][aria-label*="message" i]',
|
||||
'div[role="textbox"][aria-label*="Nachricht" i]',
|
||||
'div[contenteditable="true"][data-tid*="message"]',
|
||||
'div[contenteditable="true"][data-tid*="chat"]',
|
||||
'div[contenteditable="true"][aria-label*="message" i]',
|
||||
'[aria-label*="Type a new message" i]',
|
||||
'[aria-label*="Neue Nachricht eingeben" i]',
|
||||
];
|
||||
|
||||
let input: any = null;
|
||||
for (const selector of inputSelectors) {
|
||||
input = await this._page.$(selector);
|
||||
if (input) break;
|
||||
const maxAttempts = 5;
|
||||
const retryDelayMs = 600;
|
||||
|
||||
for (let attempt = 1; attempt <= maxAttempts; attempt++) {
|
||||
try {
|
||||
let input: any = null;
|
||||
for (const selector of inputSelectors) {
|
||||
input = await this._page.$(selector);
|
||||
if (input) {
|
||||
const isVisible = await input.isVisible().catch(() => false);
|
||||
if (isVisible) break;
|
||||
await input.dispose();
|
||||
input = null;
|
||||
}
|
||||
}
|
||||
|
||||
if (input) {
|
||||
await input.click();
|
||||
await this._page.waitForTimeout(200);
|
||||
|
||||
await this._page.keyboard.type(text, { delay: 10 });
|
||||
await this._page.waitForTimeout(200);
|
||||
await this._page.keyboard.press('Enter');
|
||||
this._logger.info('Chat message sent');
|
||||
return true;
|
||||
}
|
||||
|
||||
if (attempt < maxAttempts) {
|
||||
this._logger.info(`Chat input not found, retry ${attempt}/${maxAttempts} in ${retryDelayMs}ms`);
|
||||
await this._page.waitForTimeout(retryDelayMs);
|
||||
}
|
||||
} catch (error) {
|
||||
this._logger.error(`Error sending chat message (attempt ${attempt}):`, error);
|
||||
if (attempt < maxAttempts) {
|
||||
await this._page.waitForTimeout(retryDelayMs);
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (!input) {
|
||||
this._logger.warn('Could not find chat input field');
|
||||
return false;
|
||||
}
|
||||
|
||||
// Click to focus
|
||||
await input.click();
|
||||
await this._page.waitForTimeout(200);
|
||||
|
||||
// Type the message
|
||||
await this._page.keyboard.type(text, { delay: 10 });
|
||||
await this._page.waitForTimeout(200);
|
||||
|
||||
// Press Enter to send
|
||||
await this._page.keyboard.press('Enter');
|
||||
this._logger.info('Chat message sent');
|
||||
return true;
|
||||
|
||||
} catch (error) {
|
||||
this._logger.error('Error sending chat message:', error);
|
||||
return false;
|
||||
}
|
||||
|
||||
this._logger.warn('Could not find chat input field');
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -72,6 +72,8 @@ export class BotOrchestrator {
|
|||
private _isShuttingDown: boolean = false;
|
||||
private _isDebugMode: boolean = false;
|
||||
private _keepAliveInterval: NodeJS.Timeout | null = null;
|
||||
private _chatMessageQueue: string[] = [];
|
||||
private _chatQueueProcessing: boolean = false;
|
||||
|
||||
constructor(
|
||||
sessionId: string,
|
||||
|
|
@ -699,10 +701,8 @@ export class BotOrchestrator {
|
|||
case 'sendChatMessage':
|
||||
const chatMsg = message as SendChatMessage;
|
||||
this._logger.info(`Gateway sendChatMessage received: ${chatMsg.text?.substring(0, 60)}...`);
|
||||
try {
|
||||
await this.sendChatMessageToMeeting(chatMsg.text);
|
||||
} catch (chatErr) {
|
||||
this._logger.error(`Failed to send chat message to meeting: ${chatErr}`);
|
||||
if (chatMsg.text) {
|
||||
this._enqueueChatMessage(chatMsg.text);
|
||||
}
|
||||
break;
|
||||
|
||||
|
|
@ -1234,7 +1234,10 @@ export class BotOrchestrator {
|
|||
|
||||
this._logger.info(`Sending join greeting (chat + voice): ${greeting}`);
|
||||
|
||||
// Chat greeting
|
||||
// Brief delay so chat input is ready after panel open (Teams DOM can lag)
|
||||
await new Promise((r) => setTimeout(r, 800));
|
||||
|
||||
// Chat greeting (queued; retries if input not found)
|
||||
await this.sendChatMessageToMeeting(greeting);
|
||||
|
||||
// Voice greeting — ask Gateway to generate TTS and send back playAudio
|
||||
|
|
@ -1294,14 +1297,40 @@ export class BotOrchestrator {
|
|||
}
|
||||
|
||||
/**
|
||||
* Send a text message to the meeting chat.
|
||||
* Enqueue a chat message for serialized sending (prevents interleaved text when
|
||||
* multiple messages arrive in quick succession).
|
||||
*/
|
||||
private _enqueueChatMessage(text: string): void {
|
||||
this._chatMessageQueue.push(text);
|
||||
this._processChatQueue();
|
||||
}
|
||||
|
||||
/**
|
||||
* Process chat queue one message at a time to avoid interleaved typing.
|
||||
*/
|
||||
private async _processChatQueue(): Promise<void> {
|
||||
if (this._chatQueueProcessing || this._chatMessageQueue.length === 0) return;
|
||||
this._chatQueueProcessing = true;
|
||||
while (this._chatMessageQueue.length > 0) {
|
||||
const text = this._chatMessageQueue.shift()!;
|
||||
if (this._isShuttingDown || this._state !== 'in_meeting' || !this._chatProcedure) {
|
||||
this._logger.warn('Cannot send chat message - not in meeting, skipping queued message');
|
||||
continue;
|
||||
}
|
||||
try {
|
||||
await this._chatProcedure.sendChatMessage(text);
|
||||
} catch (chatErr) {
|
||||
this._logger.error(`Failed to send chat message to meeting: ${chatErr}`);
|
||||
}
|
||||
}
|
||||
this._chatQueueProcessing = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Send a text message to the meeting chat (enqueued for serialized delivery).
|
||||
*/
|
||||
async sendChatMessageToMeeting(text: string): Promise<void> {
|
||||
if (this._isShuttingDown || this._state !== 'in_meeting' || !this._chatProcedure) {
|
||||
this._logger.warn('Cannot send chat message - not in meeting');
|
||||
return;
|
||||
}
|
||||
await this._chatProcedure.sendChatMessage(text);
|
||||
this._enqueueChatMessage(text);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
|||
Loading…
Reference in a new issue