use chrono::{DateTime, Utc};
use serde::{Deserialize, Serialize};
use std::path::PathBuf;
use super::tasks::WebAction;
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ScreenshotRef {
pub path: PathBuf,
pub timestamp: DateTime<Utc>,
pub label: String,
pub dimensions: (u32, u32),
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum ActionOutcome {
Success { output: String },
Failed { error: String },
Timeout,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct RecordedAction {
pub timestamp: DateTime<Utc>,
pub action: WebAction,
pub outcome: ActionOutcome,
pub screenshot_before: Option<PathBuf>,
pub screenshot_after: Option<PathBuf>,
pub duration_ms: u64,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum TaskOutcome {
Passed,
Failed { reasons: Vec<String> },
Timeout,
Error { message: String },
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct InteractionTrace {
pub task_id: String,
pub task_name: String,
pub started_at: DateTime<Utc>,
pub ended_at: DateTime<Utc>,
pub actions: Vec<RecordedAction>,
pub screenshots: Vec<ScreenshotRef>,
pub final_outcome: TaskOutcome,
pub total_duration_ms: u64,
}
pub struct InteractionRecorder {
task_id: String,
task_name: String,
started_at: DateTime<Utc>,
actions: Vec<RecordedAction>,
screenshots: Vec<ScreenshotRef>,
screenshot_dir: PathBuf,
}
impl InteractionRecorder {
pub fn new(
task_id: impl Into<String>,
task_name: impl Into<String>,
screenshot_dir: PathBuf,
) -> Self {
Self {
task_id: task_id.into(),
task_name: task_name.into(),
started_at: Utc::now(),
actions: Vec::new(),
screenshots: Vec::new(),
screenshot_dir,
}
}
pub fn record_action(
&mut self,
action: WebAction,
outcome: ActionOutcome,
duration_ms: u64,
screenshot_before: Option<PathBuf>,
screenshot_after: Option<PathBuf>,
) {
self.actions.push(RecordedAction {
timestamp: Utc::now(),
action,
outcome,
screenshot_before,
screenshot_after,
duration_ms,
});
}
pub fn record_screenshot(
&mut self,
label: impl Into<String>,
path: PathBuf,
dimensions: (u32, u32),
) {
self.screenshots.push(ScreenshotRef {
path,
timestamp: Utc::now(),
label: label.into(),
dimensions,
});
}
pub fn screenshot_dir(&self) -> &PathBuf {
&self.screenshot_dir
}
pub fn finish(self, outcome: TaskOutcome) -> InteractionTrace {
let ended_at = Utc::now();
let total_duration_ms = (ended_at - self.started_at).num_milliseconds().max(0) as u64;
InteractionTrace {
task_id: self.task_id,
task_name: self.task_name,
started_at: self.started_at,
ended_at,
actions: self.actions,
screenshots: self.screenshots,
final_outcome: outcome,
total_duration_ms,
}
}
}
#[cfg(test)]
#[path = "../../../tests/unit/bench_harness/computer_control/recorder/recorder_test.rs"]
mod tests;