use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::Arc;
use std::time::Instant;
use crate::hook_utils::use_session_backgrounding::{
SessionBackgroundingState, DEFAULT_MIN_BACKGROUND_TIME_MS,
};
use crate::types::Message;
pub const DEFAULT_BLUR_DELAY_MS: u64 = 5 * 60 * 1000;
pub struct AwaySummaryManager {
enabled: bool,
backgrounding: SessionBackgroundingState,
has_summary_since_last_user: bool,
is_generating: bool,
is_pending: bool,
blurred_at: Option<Instant>,
blur_delay_ms: u64,
}
impl AwaySummaryManager {
pub fn new() -> Self {
Self {
enabled: true,
backgrounding: SessionBackgroundingState::new(DEFAULT_MIN_BACKGROUND_TIME_MS),
has_summary_since_last_user: false,
is_generating: false,
is_pending: false,
blurred_at: None,
blur_delay_ms: DEFAULT_BLUR_DELAY_MS,
}
}
pub fn with_settings(
enabled: bool,
blur_delay_ms: u64,
min_background_time_ms: u64,
) -> Self {
Self {
enabled,
backgrounding: SessionBackgroundingState::new(min_background_time_ms),
has_summary_since_last_user: false,
is_generating: false,
is_pending: false,
blurred_at: None,
blur_delay_ms,
}
}
pub fn set_enabled(&mut self, enabled: bool) {
self.enabled = enabled;
if !enabled {
self.has_summary_since_last_user = false;
self.is_generating = false;
self.is_pending = false;
self.blurred_at = None;
self.backgrounding.set_backgrounded(false);
}
}
pub fn is_enabled(&self) -> bool {
self.enabled
}
pub fn on_terminal_blurred(&mut self) {
if !self.enabled {
return;
}
self.backgrounding.set_backgrounded(true);
self.blurred_at = Some(Instant::now());
}
pub fn on_terminal_focused(&mut self) {
if !self.enabled {
return;
}
self.backgrounding.set_backgrounded(false);
self.is_pending = false;
self.blurred_at = None;
}
pub fn should_generate_summary(
&self,
is_loading: bool,
has_summary_since_last_user: bool,
) -> bool {
if !self.enabled {
return false;
}
if is_loading {
return false;
}
if has_summary_since_last_user || self.has_summary_since_last_user {
return false;
}
if self.is_generating {
return false;
}
if let Some(blurred_at) = self.blurred_at {
let elapsed = blurred_at.elapsed().as_millis() as u64;
return elapsed >= self.blur_delay_ms;
}
false
}
pub fn mark_summary_generated(&mut self) {
self.has_summary_since_last_user = true;
self.is_generating = false;
self.is_pending = false;
}
pub fn mark_summary_failed(&mut self) {
self.is_generating = false;
self.is_pending = false;
}
pub fn mark_generating(&mut self) {
self.is_generating = true;
self.is_pending = false;
}
pub fn is_pending_summary(&self) -> bool {
self.is_pending
}
pub fn on_turn_complete(&self) -> bool {
!self.is_generating && !self.has_summary_since_last_user
}
pub fn time_since_blurred(&self) -> Option<std::time::Duration> {
self.blurred_at.map(|t| t.elapsed())
}
pub fn is_backgrounded(&self) -> bool {
self.backgrounding.is_backgrounded()
}
pub fn reset_summary_flag(&mut self) {
self.has_summary_since_last_user = false;
}
}
impl Default for AwaySummaryManager {
fn default() -> Self {
Self::new()
}
}
pub fn has_away_summary_since_last_user(messages: &[Message]) -> bool {
for msg in messages.iter().rev() {
match msg {
Message::User(user_msg) => {
if !user_msg.is_meta.unwrap_or(false) {
return false;
}
}
Message::System(sys_msg) => {
if sys_msg.subtype.as_deref() == Some("away_summary") {
return true;
}
}
_ => {}
}
}
false
}
pub fn has_away_summary_since_last_user_for(messages: &[Message]) -> bool {
for msg in messages.iter().rev() {
match msg {
Message::User(user_msg) => {
if !user_msg.is_meta.unwrap_or(false) && !user_msg.is_compact_summary.unwrap_or(false)
{
return false;
}
}
Message::System(sys_msg) => {
if sys_msg.subtype.as_deref() == Some("away_summary") {
return true;
}
}
_ => {}
}
}
false
}
pub async fn generate_away_summary_with_state(
messages: &[Message],
api_key: &str,
abort_signal: &Arc<AtomicBool>,
) -> Option<String> {
if messages.is_empty() {
return None;
}
if abort_signal.load(Ordering::SeqCst) {
return None;
}
if has_away_summary_since_last_user(messages) {
return None;
}
let result = crate::services::away_summary::generate_away_summary(
messages,
api_key,
abort_signal,
).await;
result.summary
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_manager_enabled_by_default() {
let manager = AwaySummaryManager::new();
assert!(manager.is_enabled());
assert!(!manager.is_backgrounded());
}
#[test]
fn test_on_blur_sets_backgrounded() {
let mut manager = AwaySummaryManager::new();
manager.on_terminal_blurred();
assert!(manager.is_backgrounded());
assert!(manager.time_since_blurred().is_some());
}
#[test]
fn test_on_focus_clears_backgrounded() {
let mut manager = AwaySummaryManager::new();
manager.on_terminal_blurred();
manager.on_terminal_focused();
assert!(!manager.is_backgrounded());
assert!(manager.time_since_blurred().is_none());
}
#[test]
fn test_should_not_generate_when_loading() {
let manager = AwaySummaryManager::new();
assert!(!manager.should_generate_summary(true, false));
}
#[test]
fn test_should_not_generate_when_has_summary() {
let manager = AwaySummaryManager::new();
assert!(!manager.should_generate_summary(false, true));
}
#[test]
fn test_disable_resets_state() {
let mut manager = AwaySummaryManager::new();
manager.on_terminal_blurred();
manager.has_summary_since_last_user = true;
manager.set_enabled(false);
assert!(!manager.is_enabled());
assert!(!manager.is_backgrounded());
assert!(!manager.has_summary_since_last_user);
}
#[test]
fn test_blur_delay_checks() {
let manager = AwaySummaryManager::new();
assert!(!manager.should_generate_summary(false, false));
}
#[test]
fn test_reset_summary_flag() {
let mut manager = AwaySummaryManager::new();
manager.has_summary_since_last_user = true;
manager.reset_summary_flag();
assert!(!manager.has_summary_since_last_user);
}
}