use std::time::Duration;
use anyhow::Result;
use vibe_cluster::BatchOptions;
use vibe_cluster::Prompt;
use crate::configs::app::AppConfig;
use crate::engine::parser;
use crate::models::action::ActionRun;
use crate::output::output::OutputKind;
use crate::print_template;
use crate::utils;
pub struct ClusterResult {
pub prompt: String,
pub result: String,
}
pub struct Cluster;
impl Cluster {
pub async fn exec(
system: &str,
retries: u32,
run: &ActionRun,
prompts: &[String],
images: Option<Vec<String>>,
) -> Result<Vec<ClusterResult>> {
let config = AppConfig::instance()?;
let cluster = config.create_cluster_filtered(run)?;
let options = BatchOptions {
retries: Some(retries as usize),
..BatchOptions::default()
};
let cluster_prompts: Vec<Prompt> = prompts
.iter()
.map(|p| Prompt {
key: None,
user: parser::unescape_text(p),
system: Some(system.to_string()),
images: images.clone(),
})
.collect();
let cluster_results = cluster
.batch_call_with_options(&cluster_prompts, &options, move |result, current, total| {
let image_info = result
.prompt
.images
.as_ref()
.map(|imgs| {
let size: usize = imgs.iter().map(|i| i.len()).sum();
format!(", {} image", utils::format::format_bytes(size))
})
.unwrap_or_default();
let bpe = tiktoken_rs::o200k_base().unwrap();
let user_tokens = bpe.encode_with_special_tokens(&result.prompt.user).len();
let system_tokens = result
.prompt
.system
.as_ref()
.map(|s| bpe.encode_with_special_tokens(s).len())
.unwrap_or(0);
let role_label = AppConfig::instance()
.ok()
.and_then(|c| c.cluster.iter().find(|n| n.model == result.model))
.and_then(|n| n.role.as_ref())
.map(|r| r.to_string())
.unwrap_or_else(|| "llm".to_string());
let duration_ms =
utils::format::format_duration(Duration::from_millis(result.duration_ms));
print_template!(
OutputKind::Progress,
"[{role}] node batch: {current}/{total} | {model} | {duration} (~{tokens} tokens{image})",
"role" => role_label,
"current" => format!("{:>width$}", current, width = total.to_string().len()),
"total" => total.to_string(),
"model" => result.model,
"duration" => duration_ms,
"tokens" => (user_tokens + system_tokens).to_string(),
"image" => image_info,
);
})
.await
.map_err(|e| anyhow::anyhow!("Cluster batch call failed: {}", e))?;
let mut final_results = Vec::with_capacity(cluster_results.len());
for result in cluster_results {
if let Some(err) = result.error {
anyhow::bail!("Cluster node execution failed: {}", err);
}
if let Some(text) = result.text {
final_results.push(ClusterResult {
prompt: result.prompt.user,
result: vibe_cluster::normalize_text(&text),
});
}
}
Ok(final_results)
}
}