use std::time::{Duration, Instant};
use ratatui::style::Color;
use crate::tui::theme::VerbColor;
const DEFAULT_HOT_COLOR: Color = Color::Rgb(251, 146, 60); const DEFAULT_WARM_COLOR: Color = Color::Rgb(250, 204, 21); const DEFAULT_QUEUED_COLOR: Color = Color::Rgb(156, 163, 175); const DEFAULT_VIOLET_COLOR: Color = Color::Rgb(139, 92, 246); const DEFAULT_AMBER_COLOR: Color = Color::Rgb(245, 158, 11);
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub enum ActivityTemp {
Hot,
Warm,
#[default]
Queued,
}
impl ActivityTemp {
pub fn header(&self) -> (&'static str, Color) {
match self {
Self::Hot => ("🔥 HOT (executing now)", DEFAULT_HOT_COLOR),
Self::Warm => ("🟡 WARM (recently completed)", DEFAULT_WARM_COLOR),
Self::Queued => ("⚪ QUEUED (waiting)", DEFAULT_QUEUED_COLOR),
}
}
}
#[derive(Debug, Clone)]
pub struct ActivityItem {
pub id: String,
pub verb: String,
pub temp: ActivityTemp,
pub started: Option<Instant>,
pub duration: Option<Duration>,
pub tokens: Option<(u64, u64)>, pub waiting_on: Option<String>,
pub detail: Option<String>,
pub frame: u8,
}
impl ActivityItem {
pub fn hot(id: impl Into<String>, verb: impl Into<String>) -> Self {
Self {
id: id.into(),
verb: verb.into(),
temp: ActivityTemp::Hot,
started: Some(Instant::now()),
duration: None,
tokens: None,
waiting_on: None,
detail: None,
frame: 0,
}
}
pub fn warm(id: impl Into<String>, verb: impl Into<String>, duration: Duration) -> Self {
Self {
id: id.into(),
verb: verb.into(),
temp: ActivityTemp::Warm,
started: None,
duration: Some(duration),
tokens: None,
waiting_on: None,
detail: None,
frame: 0,
}
}
pub fn queued(
id: impl Into<String>,
verb: impl Into<String>,
waiting_on: impl Into<String>,
) -> Self {
Self {
id: id.into(),
verb: verb.into(),
temp: ActivityTemp::Queued,
started: None,
duration: None,
tokens: None,
waiting_on: Some(waiting_on.into()),
detail: None,
frame: 0,
}
}
pub fn with_tokens(mut self, tokens_in: u64, tokens_out: u64) -> Self {
self.tokens = Some((tokens_in, tokens_out));
self
}
pub fn with_detail(mut self, detail: impl Into<String>) -> Self {
self.detail = Some(detail.into());
self
}
pub fn with_frame(mut self, frame: u8) -> Self {
self.frame = frame;
self
}
pub fn elapsed(&self) -> Option<Duration> {
self.started.map(|s| s.elapsed())
}
fn verb_icon(&self) -> &'static str {
VerbColor::from_verb(&self.verb).icon()
}
fn verb_color(&self) -> Color {
VerbColor::from_verb(&self.verb).rgb()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_activity_item_hot() {
let item = ActivityItem::hot("task1", "infer");
assert_eq!(item.id, "task1");
assert_eq!(item.verb, "infer");
assert_eq!(item.temp, ActivityTemp::Hot);
assert!(item.started.is_some());
}
#[test]
fn test_activity_item_warm() {
let item = ActivityItem::warm("task2", "exec", Duration::from_secs(5));
assert_eq!(item.temp, ActivityTemp::Warm);
assert_eq!(item.duration, Some(Duration::from_secs(5)));
}
#[test]
fn test_activity_item_queued() {
let item = ActivityItem::queued("task3", "fetch", "task1");
assert_eq!(item.temp, ActivityTemp::Queued);
assert_eq!(item.waiting_on, Some("task1".to_string()));
}
#[test]
fn test_verb_icon() {
let item = ActivityItem::hot("t", "infer");
assert_eq!(item.verb_icon(), "⚡");
let item = ActivityItem::hot("t", "exec");
assert_eq!(item.verb_icon(), "📟");
let item = ActivityItem::hot("t", "invoke");
assert_eq!(item.verb_icon(), "🔌"); }
#[test]
fn test_verb_color() {
let item = ActivityItem::hot("t", "infer");
assert_eq!(item.verb_color(), DEFAULT_VIOLET_COLOR);
let item = ActivityItem::hot("t", "exec");
assert_eq!(item.verb_color(), DEFAULT_AMBER_COLOR);
}
#[test]
fn test_with_tokens() {
let item = ActivityItem::hot("t", "infer").with_tokens(100, 50);
assert_eq!(item.tokens, Some((100, 50)));
}
#[test]
fn test_with_detail() {
let item =
ActivityItem::warm("t", "exec", Duration::from_secs(1)).with_detail("build completed");
assert_eq!(item.detail, Some("build completed".to_string()));
}
#[test]
fn test_elapsed() {
let item = ActivityItem::hot("t", "infer");
let elapsed = item.elapsed();
assert!(elapsed.is_some());
assert!(elapsed.unwrap() < Duration::from_secs(1));
let item = ActivityItem::queued("t", "exec", "other");
assert!(item.elapsed().is_none());
}
#[test]
fn test_activity_temp_headers() {
let (header, _) = ActivityTemp::Hot.header();
assert!(header.contains("HOT"));
let (header, _) = ActivityTemp::Warm.header();
assert!(header.contains("WARM"));
let (header, _) = ActivityTemp::Queued.header();
assert!(header.contains("QUEUED"));
}
#[test]
fn test_default_temp() {
assert_eq!(ActivityTemp::default(), ActivityTemp::Queued);
}
}