Files
baibot-withmcp/src/controller/reaction/text_to_speech.rs
Slavi Pantaleev 3b25b92a81 Implement more fine-grained typing notices sending
The previous approach (implemented in dd1dd78312) was simple
(send typing notices for as long as the "controller" is running),
but this proved to be overly simplistic and unable to handle edge-cases:

- in multi-user rooms (or rooms with a prefix requirement), the bot
  used to send a typing notice while "working", but its work consisted
  of ignoring the message. So it then sent a "not typing" notice.
  This is wasteful and otherwise problematic - certain clients (like nheko)
  do not handle this "race" well.

- certain reactions (anything other than 🗣️ right now) are meant to be
  ignored. There's no point in doing the same "typing / not typing"
  dance

- there are other instances where the bot may do work, but doesn't (due
  to configuration or lack of capabilities)

This new more fine-grained implementation of typing notices aims to:

- only send a typing notice if actual "slow work" will be done

- avoid stopping & restarting typing notices (wasteful) if a chain of work is to
  be performed (processing voice messages and doing speech-to-text +
  text-generation + ...). Rather, maintaining typing notice sending
  throughout
2024-09-14 10:39:20 +03:00

103 lines
3.4 KiB
Rust

use mxlink::{MatrixLink, MessageResponseType};
use mxlink::matrix_sdk::ruma::{
events::room::message::TextMessageEventContent, OwnedEventId, OwnedUserId,
};
use crate::entity::roomconfig::{
TextToSpeechBotMessagesFlowType, TextToSpeechUserMessagesFlowType,
};
use crate::{
agent::AgentPurpose, controller::utils::agent::get_effective_agent_for_purpose_or_complain,
entity::MessageContext, Bot,
};
pub(super) async fn handle(
bot: &Bot,
matrix_link: MatrixLink,
message_context: &MessageContext,
reacted_to_event_id: &OwnedEventId,
reacted_to_event_sender_id: &OwnedUserId,
text_content: &TextMessageEventContent,
) -> anyhow::Result<()> {
// If we're in a thread, we're likely dealing with a bot message, so we should start in the thread.
// Otherwise, we're likely operating in "TTS user messages" mode, so we should reply to the reacted-to message and avoid threads.
let response_type = if message_context.thread_info().is_thread_root_only() {
MessageResponseType::Reply(reacted_to_event_id.clone())
} else {
MessageResponseType::InThread(message_context.thread_info().clone())
};
if !is_allowed_to_tts_for_event(
message_context,
reacted_to_event_sender_id,
matrix_link.user_id(),
) {
tracing::debug!("Ignoring request for on-demand text-to-speech (via reaction) due to room configuration");
return Ok(());
}
let speech_agent = get_effective_agent_for_purpose_or_complain(
bot,
message_context,
AgentPurpose::TextToSpeech,
response_type.clone(),
true,
)
.await;
let Some(speech_agent) = speech_agent else {
// We've already complained about this in get_effective_agent_or_complain
return Ok(());
};
let _typing_notice_guard = bot.start_typing_notice(message_context.room()).await;
crate::controller::utils::text_to_speech::generate_and_send_tts_for_message(
bot,
matrix_link,
message_context,
response_type,
&speech_agent,
reacted_to_event_id,
&text_content.body,
)
.await;
Ok(())
}
fn is_allowed_to_tts_for_event(
message_context: &MessageContext,
sender_id: &OwnedUserId,
bot_user_id: &OwnedUserId,
) -> bool {
// Whether we're allowed depends on who the original message sender is (the bot or some user).
//
// The user may be an allowed bot user or someone else.
// Regardless, we've been invoked by an allowed user, so if the user wants TTS for a foreign message, we should allow it.
if *sender_id == *bot_user_id {
match message_context
.room_config_context()
.text_to_speech_bot_messages_flow_type()
{
TextToSpeechBotMessagesFlowType::Never => false,
TextToSpeechBotMessagesFlowType::OnDemandAlways => true,
TextToSpeechBotMessagesFlowType::OnDemandForVoice => true,
TextToSpeechBotMessagesFlowType::OnlyForVoice => true,
TextToSpeechBotMessagesFlowType::Always => true,
}
} else {
match message_context
.room_config_context()
.text_to_speech_user_messages_flow_type()
{
TextToSpeechUserMessagesFlowType::Never => false,
TextToSpeechUserMessagesFlowType::OnDemand => true,
TextToSpeechUserMessagesFlowType::Always => true,
}
}
}