use std::sync::Arc;
use teloxide::payloads::SendMessageSetters;
use teloxide::types::{ChatId, InlineKeyboardButton, InlineKeyboardMarkup, ParseMode};
use tokio::sync::oneshot;
use super::handler::{StreamingState, flush_intermediates};
use crate::brain::agent::{AgentError, FollowUpQuestionInfo, QuestionCallback};
fn escape_html(text: &str) -> String {
text.replace('&', "&")
.replace('<', "<")
.replace('>', ">")
.replace('"', """)
}
pub(crate) fn make_question_callback(
state: Arc<super::TelegramState>,
streaming: Arc<std::sync::Mutex<StreamingState>>,
) -> QuestionCallback {
Arc::new(move |info: FollowUpQuestionInfo| {
let state = state.clone();
let streaming = streaming.clone();
Box::pin(async move {
let chat_id = match state.session_chat(info.session_id).await {
Some(id) => id,
None => match state.owner_chat_id().await {
Some(id) => id,
None => {
tracing::warn!(
"Telegram follow_up_question: no chat_id for session {}",
info.session_id
);
return Err(AgentError::Internal("no chat_id for session".into()));
}
},
};
let bot = match state.bot().await {
Some(b) => b,
None => {
tracing::warn!("Telegram follow_up_question: bot not connected");
return Err(AgentError::Internal("bot not connected".into()));
}
};
let question_id = uuid::Uuid::new_v4().to_string();
let keyboard_rows: Vec<Vec<InlineKeyboardButton>> = info
.options
.iter()
.enumerate()
.map(|(i, opt)| {
vec![InlineKeyboardButton::callback(
opt.clone(),
format!("q:{}:{}", question_id, i),
)]
})
.collect();
let keyboard = InlineKeyboardMarkup::new(keyboard_rows);
let text = format!("❓ <b>{}</b>", escape_html(&info.question));
let (tx, rx) = oneshot::channel::<String>();
state
.register_pending_question(question_id.clone(), tx, info.options.clone())
.await;
tracing::info!(
"Telegram follow_up_question: registered id={} options={}",
question_id,
info.options.len()
);
let thread_id = state
.session_topic(info.session_id)
.await
.map(|tid| teloxide::types::ThreadId(teloxide::types::MessageId(tid)));
flush_intermediates(&bot, ChatId(chat_id), thread_id, &streaming).await;
if let Err(e) = super::send::message_in_thread(&bot, ChatId(chat_id), thread_id, &text)
.parse_mode(ParseMode::Html)
.reply_markup(keyboard)
.await
{
tracing::error!("Telegram follow_up_question: send failed: {}", e);
return Err(AgentError::Internal(format!("send failed: {}", e)));
}
match tokio::time::timeout(std::time::Duration::from_secs(600), rx).await {
Ok(Ok(answer)) => {
tracing::info!(
"Telegram follow_up_question: answered id={} choice={:?}",
question_id,
answer
);
Ok(answer)
}
Ok(Err(_)) => Err(AgentError::Internal(
"follow_up_question oneshot channel closed".into(),
)),
Err(_) => {
tracing::warn!(
"Telegram follow_up_question: 10-minute timeout id={}",
question_id
);
Err(AgentError::Internal("follow_up_question timed out".into()))
}
}
})
})
}