Files
baibot-withmcp/src/controller/utils/text_to_speech.rs
Slavi Pantaleev dd1dd78312 Rework typing notifications
Previously, the bot only had rudimentary typing notification support.

It used to send a single notification when starting a long task
and did not bother with notifications anymore.
By default matrix-rust-sdk gives these notifications a validity of 4
seconds, so it would expire shortly. If the bot takes longer to respond,
you'd see the typing notification expire and wonder if a response is
coming.

Another edge case is the bot sending an answer quicker and the typing
notice still being on. Some clients (like element-web) seem to hide the
typing notice when a new message comes, so they don't experience this as
problematic.

The reworked typing notification system should be robust:

- typing notices are sent continuously, until the bot finishes doing
  work
- if the bot is performing multiple actions in a room (even for
  different people), typing notices would continue to be sent until the
  bot becomes idle
- as soon as the bot becomes idle, a "not typing anymore" notice is sent
  to clear the state
2024-09-13 21:40:24 +03:00

167 lines
4.4 KiB
Rust

use mxlink::matrix_sdk::ruma::OwnedEventId;
use mxlink::{MatrixLink, MessageResponseType};
use tracing::Instrument;
use crate::{
agent::{provider::TextToSpeechParams, AgentInstance, AgentPurpose, ControllerTrait},
entity::MessageContext,
strings, Bot,
};
pub async fn generate_and_send_tts_for_message(
bot: &Bot,
matrix_link: MatrixLink,
message_context: &MessageContext,
response_type: MessageResponseType,
speech_agent: &AgentInstance,
text_message_event_id: &OwnedEventId,
text_content: &str,
) -> bool {
let reaction_event_response = bot
.reacting()
.react_no_fail(
message_context.room(),
text_message_event_id.clone(),
strings::PROGRESS_INDICATOR_EMOJI.to_owned(),
)
.await;
let result = do_generate_and_send_tts_for_message(
bot,
matrix_link,
message_context,
response_type,
speech_agent,
text_content,
)
.await;
if let Some(reaction_event_response) = reaction_event_response {
let redaction_reason = if result {
strings::text_to_speech::redaction_reason_done()
} else {
strings::text_to_speech::redaction_reason_failed()
};
bot.messaging()
.redact_event_no_fail(
message_context.room(),
reaction_event_response.event_id,
Some(redaction_reason.to_owned()),
)
.await;
}
result
}
async fn do_generate_and_send_tts_for_message(
bot: &Bot,
matrix_link: MatrixLink,
message_context: &MessageContext,
response_type: MessageResponseType,
speech_agent: &AgentInstance,
text_content: &str,
) -> bool {
let params = TextToSpeechParams {
speed_override: message_context
.room_config_context()
.text_to_speech_speed_override(),
voice_override: message_context
.room_config_context()
.text_to_speech_voice_override(),
};
let text_content = if let Some(text_content) = text_content.strip_prefix(bot.command_prefix()) {
text_content.trim()
} else {
text_content
};
let span = tracing::debug_span!(
"text_to_speech_generation",
agent_id = speech_agent.identifier().as_string()
);
let text_to_speech_result = speech_agent
.controller()
.text_to_speech(text_content, params)
.instrument(span)
.await;
let text_to_speech_result = match text_to_speech_result {
Ok(text_to_speech_result) => text_to_speech_result,
Err(err) => {
tracing::warn!(
"Error in room {} while trying to generate TTS via agent {}: {:?}",
message_context.room_id(),
speech_agent.identifier(),
err,
);
bot.messaging()
.send_error_markdown_no_fail(
message_context.room(),
&strings::agent::error_while_serving_purpose(
speech_agent.identifier(),
&AgentPurpose::SpeechToText,
&err,
),
response_type,
)
.await;
return false;
}
};
let attachment_body_text = strings::text_to_speech::alternate_body_text();
let event_content = matrix_link
.media()
.upload_and_prepare_event_content(
message_context.room(),
&text_to_speech_result.mime_type,
text_to_speech_result.bytes,
&attachment_body_text,
)
.await;
let mut event_content = match event_content {
Ok(event_content) => event_content,
Err(err) => {
tracing::error!(
?err,
"Error in room {} while trying to upload TTS via agent {}",
message_context.room_id(),
speech_agent.identifier(),
);
return false;
}
};
let result = matrix_link
.messaging()
.send_event(
message_context.room(),
&mut event_content,
response_type.clone(),
)
.await;
let Err(err) = result else {
return true;
};
tracing::error!(
?err,
"Error in room {} while trying to send TTS payload",
message_context.room_id(),
);
false
}