183 lines
5.3 KiB
Rust
183 lines
5.3 KiB
Rust
use crate::error::AppError;
|
|
use crate::models::agent::Agent;
|
|
use crate::models::agent_task::AgentTask;
|
|
use crate::models::module::{ProjectModule, MODULE_AGENT_TASK_RUNNER};
|
|
use crate::models::project::Project;
|
|
use crate::AppState;
|
|
use tauri::State;
|
|
|
|
const TASK_LIST_RESULT_PREVIEW_MAX_BYTES: usize = 20_000;
|
|
const TASK_LIST_ERROR_PREVIEW_MAX_BYTES: usize = 8_000;
|
|
const TASK_LIST_TRUNCATION_NOTICE: &str =
|
|
"\n\n[... contenu tronque pour preserver la fluidite de l'interface ...]";
|
|
|
|
fn truncate_for_task_list(value: Option<String>, max_bytes: usize) -> Option<String> {
|
|
let Some(content) = value else {
|
|
return None;
|
|
};
|
|
|
|
if content.len() <= max_bytes {
|
|
return Some(content);
|
|
}
|
|
|
|
let mut boundary = max_bytes;
|
|
while boundary > 0 && !content.is_char_boundary(boundary) {
|
|
boundary -= 1;
|
|
}
|
|
|
|
let mut truncated = content[..boundary].to_string();
|
|
truncated.push_str(TASK_LIST_TRUNCATION_NOTICE);
|
|
Some(truncated)
|
|
}
|
|
|
|
#[tauri::command]
|
|
pub fn create_agent_task(
|
|
state: State<'_, AppState>,
|
|
project_id: String,
|
|
agent_id: String,
|
|
title: String,
|
|
description: String,
|
|
) -> Result<AgentTask, AppError> {
|
|
if title.trim().is_empty() {
|
|
return Err(AppError::from(
|
|
"Le titre de la tâche est obligatoire".to_string(),
|
|
));
|
|
}
|
|
|
|
let db = state
|
|
.db
|
|
.lock()
|
|
.map_err(|e| AppError::from(format!("Database lock failed: {}", e)))?;
|
|
|
|
let enabled = ProjectModule::is_enabled(&db, &project_id, MODULE_AGENT_TASK_RUNNER)?;
|
|
if !enabled {
|
|
return Err(AppError::from(
|
|
"Le module Agent task runner est désactivé pour ce projet".to_string(),
|
|
));
|
|
}
|
|
|
|
Project::get_by_id(&db, &project_id)?;
|
|
Agent::get_by_id(&db, &agent_id)?;
|
|
|
|
let task = AgentTask::insert(
|
|
&db,
|
|
&project_id,
|
|
&agent_id,
|
|
title.trim(),
|
|
description.trim(),
|
|
)?;
|
|
Ok(task)
|
|
}
|
|
|
|
#[tauri::command]
|
|
pub fn list_agent_tasks(
|
|
state: State<'_, AppState>,
|
|
project_id: String,
|
|
) -> Result<Vec<AgentTask>, AppError> {
|
|
let db = state
|
|
.db
|
|
.lock()
|
|
.map_err(|e| AppError::from(format!("Database lock failed: {}", e)))?;
|
|
|
|
let tasks = AgentTask::list_by_project(&db, &project_id)?
|
|
.into_iter()
|
|
.map(|mut task| {
|
|
task.result = truncate_for_task_list(task.result, TASK_LIST_RESULT_PREVIEW_MAX_BYTES);
|
|
task.error = truncate_for_task_list(task.error, TASK_LIST_ERROR_PREVIEW_MAX_BYTES);
|
|
task
|
|
})
|
|
.collect();
|
|
Ok(tasks)
|
|
}
|
|
|
|
#[tauri::command]
|
|
pub fn retry_agent_task(state: State<'_, AppState>, task_id: String) -> Result<(), AppError> {
|
|
let db = state
|
|
.db
|
|
.lock()
|
|
.map_err(|e| AppError::from(format!("Database lock failed: {}", e)))?;
|
|
|
|
let task = AgentTask::get_by_id(&db, &task_id)?;
|
|
let enabled = ProjectModule::is_enabled(&db, &task.project_id, MODULE_AGENT_TASK_RUNNER)?;
|
|
if !enabled {
|
|
return Err(AppError::from(
|
|
"Le module Agent task runner est désactivé pour ce projet".to_string(),
|
|
));
|
|
}
|
|
|
|
if task.status != "error" && task.status != "cancelled" && task.status != "done" {
|
|
return Err(AppError::from(format!(
|
|
"Impossible de relancer une tâche avec le statut '{}'",
|
|
task.status
|
|
)));
|
|
}
|
|
|
|
AgentTask::retry(&db, &task_id)?;
|
|
Ok(())
|
|
}
|
|
|
|
#[tauri::command]
|
|
pub async fn cancel_agent_task(
|
|
state: State<'_, AppState>,
|
|
task_id: String,
|
|
) -> Result<(), AppError> {
|
|
{
|
|
let db = state
|
|
.db
|
|
.lock()
|
|
.map_err(|e| AppError::from(format!("Database lock failed: {}", e)))?;
|
|
|
|
let task = AgentTask::get_by_id(&db, &task_id)?;
|
|
if task.status == "done" || task.status == "cancelled" {
|
|
return Err(AppError::from(format!(
|
|
"Impossible d'annuler une tâche avec le statut '{}'",
|
|
task.status
|
|
)));
|
|
}
|
|
}
|
|
|
|
state.process_registry.cancel_task(&task_id).await;
|
|
|
|
let db = state
|
|
.db
|
|
.lock()
|
|
.map_err(|e| AppError::from(format!("Database lock failed: {}", e)))?;
|
|
let task = AgentTask::get_by_id(&db, &task_id)?;
|
|
if task.status == "done" || task.status == "cancelled" {
|
|
return Err(AppError::from(format!(
|
|
"Impossible d'annuler une tâche avec le statut '{}'",
|
|
task.status
|
|
)));
|
|
}
|
|
|
|
AgentTask::cancel(&db, &task_id)?;
|
|
Ok(())
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use super::truncate_for_task_list;
|
|
|
|
#[test]
|
|
fn test_truncate_for_task_list_keeps_short_content() {
|
|
let input = Some("short".to_string());
|
|
let output = truncate_for_task_list(input, 10);
|
|
assert_eq!(output.as_deref(), Some("short"));
|
|
}
|
|
|
|
#[test]
|
|
fn test_truncate_for_task_list_truncates_long_content() {
|
|
let input = Some("x".repeat(20));
|
|
let output = truncate_for_task_list(input, 10).expect("content should exist");
|
|
assert!(output.starts_with("xxxxxxxxxx"));
|
|
assert!(output.contains("contenu tronque"));
|
|
}
|
|
|
|
#[test]
|
|
fn test_truncate_for_task_list_respects_utf8_boundaries() {
|
|
let input = Some("éééé".to_string());
|
|
let output = truncate_for_task_list(input, 3).expect("content should exist");
|
|
assert!(output.starts_with("é"));
|
|
assert!(!output.starts_with("éé"));
|
|
}
|
|
}
|