2024-09-12 13:44:06 +03:00
|
|
|
use mxlink::{MatrixLink, MessageResponseType};
|
|
|
|
|
|
|
|
|
|
use tracing::Instrument;
|
|
|
|
|
|
|
|
|
|
use crate::agent::AgentPurpose;
|
|
|
|
|
use crate::agent::ControllerTrait;
|
2025-02-27 07:46:16 +02:00
|
|
|
use crate::agent::provider::ImageGenerationParams;
|
2024-09-12 13:44:06 +03:00
|
|
|
use crate::controller::utils::agent::get_effective_agent_for_purpose_or_complain;
|
|
|
|
|
use crate::conversation::create_llm_conversation_for_matrix_thread;
|
|
|
|
|
use crate::conversation::matrix::MatrixMessageProcessingParams;
|
|
|
|
|
use crate::strings;
|
2025-05-10 11:47:50 +03:00
|
|
|
use crate::utils::mime::get_file_extension;
|
2025-02-27 07:46:16 +02:00
|
|
|
use crate::{Bot, entity::MessageContext};
|
2024-09-12 13:44:06 +03:00
|
|
|
|
|
|
|
|
pub async fn handle_image(
|
|
|
|
|
bot: &Bot,
|
|
|
|
|
matrix_link: MatrixLink,
|
|
|
|
|
message_context: &MessageContext,
|
|
|
|
|
original_prompt: &str,
|
|
|
|
|
) -> anyhow::Result<()> {
|
|
|
|
|
let response_type = MessageResponseType::InThread(message_context.thread_info().clone());
|
|
|
|
|
|
|
|
|
|
let Some(agent) = get_effective_agent_for_purpose_or_complain(
|
|
|
|
|
bot,
|
|
|
|
|
message_context,
|
|
|
|
|
AgentPurpose::ImageGeneration,
|
|
|
|
|
response_type.clone(),
|
|
|
|
|
true,
|
|
|
|
|
)
|
|
|
|
|
.await
|
|
|
|
|
else {
|
|
|
|
|
return Ok(());
|
|
|
|
|
};
|
|
|
|
|
|
2024-11-19 20:46:23 +02:00
|
|
|
let _typing_notice_guard = bot.start_typing_notice(message_context.room()).await;
|
|
|
|
|
|
2024-09-12 13:44:06 +03:00
|
|
|
let params = MatrixMessageProcessingParams::new(
|
2024-10-01 21:06:54 +03:00
|
|
|
bot.user_id().to_owned(),
|
|
|
|
|
Some(message_context.combined_admin_and_user_regexes()),
|
2024-09-12 13:44:06 +03:00
|
|
|
);
|
|
|
|
|
|
|
|
|
|
let conversation = create_llm_conversation_for_matrix_thread(
|
2025-05-10 09:18:01 +03:00
|
|
|
&matrix_link,
|
2024-09-12 13:44:06 +03:00
|
|
|
message_context.room(),
|
|
|
|
|
message_context.thread_info().root_event_id.clone(),
|
|
|
|
|
¶ms,
|
|
|
|
|
)
|
|
|
|
|
.await?;
|
|
|
|
|
|
|
|
|
|
let prompt = if conversation.messages.len() >= 2 {
|
|
|
|
|
// Skip the first message, which contains the original prompt (which we already have)
|
|
|
|
|
let other_messages = conversation.messages.iter().skip(1).cloned().collect();
|
|
|
|
|
|
|
|
|
|
super::prompt::build(original_prompt, other_messages)
|
|
|
|
|
} else {
|
|
|
|
|
original_prompt.to_owned()
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
let span = tracing::debug_span!(
|
|
|
|
|
"image_generation",
|
|
|
|
|
agent_id = agent.identifier().as_string()
|
|
|
|
|
);
|
|
|
|
|
|
2025-05-03 09:23:57 +03:00
|
|
|
let result = agent
|
2024-09-12 13:44:06 +03:00
|
|
|
.controller()
|
|
|
|
|
.generate_image(&prompt, ImageGenerationParams::default())
|
|
|
|
|
.instrument(span)
|
2025-05-03 09:23:57 +03:00
|
|
|
.await;
|
|
|
|
|
|
|
|
|
|
let response = match result {
|
|
|
|
|
Ok(response) => response,
|
|
|
|
|
Err(err) => {
|
|
|
|
|
tracing::warn!(
|
|
|
|
|
"Error in room {} while trying to generate image via agent {}: {:?}",
|
|
|
|
|
message_context.room_id(),
|
|
|
|
|
agent.identifier(),
|
|
|
|
|
err,
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
bot.messaging()
|
|
|
|
|
.send_error_markdown_no_fail(
|
|
|
|
|
message_context.room(),
|
|
|
|
|
&strings::agent::error_while_serving_purpose(
|
|
|
|
|
agent.identifier(),
|
|
|
|
|
&AgentPurpose::ImageGeneration,
|
|
|
|
|
&err,
|
|
|
|
|
),
|
|
|
|
|
response_type,
|
|
|
|
|
)
|
|
|
|
|
.await;
|
|
|
|
|
|
|
|
|
|
return Ok(());
|
|
|
|
|
}
|
|
|
|
|
};
|
2024-09-12 13:44:06 +03:00
|
|
|
|
|
|
|
|
let actual_prompt = response.revised_prompt.as_deref().unwrap_or(&prompt);
|
|
|
|
|
|
|
|
|
|
if *actual_prompt.trim() != *prompt.trim() {
|
|
|
|
|
bot.messaging()
|
|
|
|
|
.send_notice_markdown_no_fail(
|
|
|
|
|
message_context.room(),
|
|
|
|
|
strings::image_generation::revised_prompt(actual_prompt),
|
|
|
|
|
response_type.clone(),
|
|
|
|
|
)
|
|
|
|
|
.await;
|
|
|
|
|
}
|
|
|
|
|
|
2025-02-26 13:39:43 +02:00
|
|
|
let attachment_body_text = format!(
|
|
|
|
|
"generated-image.{}",
|
|
|
|
|
get_file_extension(&response.mime_type)
|
|
|
|
|
);
|
2024-09-12 13:44:06 +03:00
|
|
|
|
|
|
|
|
let mut event_content = matrix_link
|
|
|
|
|
.media()
|
|
|
|
|
.upload_and_prepare_event_content(
|
|
|
|
|
message_context.room(),
|
|
|
|
|
&response.mime_type,
|
|
|
|
|
response.bytes,
|
|
|
|
|
&attachment_body_text,
|
|
|
|
|
)
|
|
|
|
|
.await
|
|
|
|
|
.map_err(|e| anyhow::anyhow!("Failed to upload and prepare event: {}", e))?;
|
|
|
|
|
|
|
|
|
|
matrix_link
|
|
|
|
|
.messaging()
|
|
|
|
|
.send_event(
|
|
|
|
|
message_context.room(),
|
|
|
|
|
&mut event_content,
|
|
|
|
|
response_type.clone(),
|
|
|
|
|
)
|
|
|
|
|
.await?;
|
|
|
|
|
|
|
|
|
|
if conversation.messages.len() == 1 {
|
|
|
|
|
// If this is the beginning of the thread, send helpful instructions
|
|
|
|
|
bot.messaging()
|
|
|
|
|
.send_notice_markdown_no_fail(
|
|
|
|
|
message_context.room(),
|
|
|
|
|
strings::image_generation::guide_how_to_proceed(),
|
|
|
|
|
response_type.clone(),
|
|
|
|
|
)
|
|
|
|
|
.await;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Ok(())
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub async fn handle_sticker(
|
|
|
|
|
bot: &Bot,
|
|
|
|
|
matrix_link: MatrixLink,
|
|
|
|
|
message_context: &MessageContext,
|
|
|
|
|
original_prompt: &str,
|
|
|
|
|
) -> anyhow::Result<()> {
|
|
|
|
|
// Stickers are always sent directly to the room - no threading.
|
|
|
|
|
let response_type =
|
|
|
|
|
MessageResponseType::Reply(message_context.thread_info().root_event_id.clone());
|
|
|
|
|
|
|
|
|
|
let Some(agent) = get_effective_agent_for_purpose_or_complain(
|
|
|
|
|
bot,
|
|
|
|
|
message_context,
|
|
|
|
|
AgentPurpose::ImageGeneration,
|
|
|
|
|
response_type.clone(),
|
|
|
|
|
true,
|
|
|
|
|
)
|
|
|
|
|
.await
|
|
|
|
|
else {
|
|
|
|
|
return Ok(());
|
|
|
|
|
};
|
|
|
|
|
|
2024-09-14 10:39:20 +03:00
|
|
|
let _typing_notice_guard = bot.start_typing_notice(message_context.room()).await;
|
|
|
|
|
|
2024-09-12 13:44:06 +03:00
|
|
|
let span = tracing::debug_span!(
|
|
|
|
|
"sticker_generation",
|
|
|
|
|
agent_id = agent.identifier().as_string()
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
let params = ImageGenerationParams::default()
|
Fix sticker generation for newer GPT image models
Sticker generation was failing when using newer GPT image models
(gpt-image-1, gpt-image-1-mini, gpt-image-1.5). The issue occurred
because stickers requested 256x256 size, but these models only support
1024x1024, 1536x1024, 1024x1536, and auto.
To reproduce, send `!bai sticker Something` to an agent configured
with a GPT image model. The error was:
invalid_request_error: Invalid value: '256x256'. Supported values
are: '1024x1024', '1024x1536', '1536x1024', and 'auto'. (param: size)
(code: invalid_value)
The fix replaces the hardcoded 256x256 size override with a
`smallest_size_possible` flag, letting each provider determine the
appropriate sticker size based on the model being used.
The `openai_compat` provider still defaults to requesting 256x256 in all cases
(regardless of model name).
2026-02-04 02:36:50 +02:00
|
|
|
.with_smallest_size_possible(true)
|
2024-09-12 13:44:06 +03:00
|
|
|
.with_cheaper_model_switching_allowed(true)
|
|
|
|
|
.with_cheaper_quality_switching_allowed(true);
|
|
|
|
|
|
2025-05-03 09:23:57 +03:00
|
|
|
let result = agent
|
2024-09-12 13:44:06 +03:00
|
|
|
.controller()
|
|
|
|
|
.generate_image(original_prompt, params)
|
|
|
|
|
.instrument(span)
|
2025-05-03 09:23:57 +03:00
|
|
|
.await;
|
|
|
|
|
|
|
|
|
|
let response = match result {
|
|
|
|
|
Ok(response) => response,
|
|
|
|
|
Err(err) => {
|
|
|
|
|
tracing::warn!(
|
|
|
|
|
"Error in room {} while trying to generate sticker via agent {}: {:?}",
|
|
|
|
|
message_context.room_id(),
|
|
|
|
|
agent.identifier(),
|
|
|
|
|
err,
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
bot.messaging()
|
|
|
|
|
.send_error_markdown_no_fail(
|
|
|
|
|
message_context.room(),
|
|
|
|
|
&strings::agent::error_while_serving_purpose(
|
|
|
|
|
agent.identifier(),
|
|
|
|
|
&AgentPurpose::ImageGeneration,
|
|
|
|
|
&err,
|
|
|
|
|
),
|
|
|
|
|
response_type,
|
|
|
|
|
)
|
|
|
|
|
.await;
|
|
|
|
|
|
|
|
|
|
return Ok(());
|
|
|
|
|
}
|
|
|
|
|
};
|
2024-09-12 13:44:06 +03:00
|
|
|
|
2025-02-26 13:39:43 +02:00
|
|
|
let attachment_body_text = format!(
|
|
|
|
|
"generated-sticker.{}",
|
|
|
|
|
get_file_extension(&response.mime_type)
|
|
|
|
|
);
|
2024-09-12 13:44:06 +03:00
|
|
|
|
|
|
|
|
let mut event_content = matrix_link
|
|
|
|
|
.media()
|
|
|
|
|
.upload_and_prepare_event_content(
|
|
|
|
|
message_context.room(),
|
|
|
|
|
&response.mime_type,
|
|
|
|
|
response.bytes,
|
|
|
|
|
&attachment_body_text,
|
|
|
|
|
)
|
|
|
|
|
.await
|
|
|
|
|
.map_err(|e| anyhow::anyhow!("Failed to upload and prepare event: {}", e))?;
|
|
|
|
|
|
|
|
|
|
matrix_link
|
|
|
|
|
.messaging()
|
|
|
|
|
.send_event(message_context.room(), &mut event_content, response_type)
|
|
|
|
|
.await?;
|
|
|
|
|
|
|
|
|
|
Ok(())
|
|
|
|
|
}
|