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
167 lines
4.4 KiB
Rust
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
|
|
}
|