use serde_json::Value;
use super::super::helpers::optional_string;
use super::diff::{change_path, summarize_change_counts, summarize_diff_text};
use super::metadata::{TimelineSemanticInfo, TimelineSummaryInfo};
use super::semantic::{
command_action_query, command_action_target, detail_label,
extract_query_from_web_search_action, extract_targets_from_payload, first_search_query,
first_shell_command, payload_string, push_unique, semantic_title,
};
pub(super) fn timeline_summary_from_item(
entry_type: &str,
item: &Value,
payload: &Value,
semantic: Option<&TimelineSemanticInfo>,
) -> Option<TimelineSummaryInfo> {
let semantic = semantic?;
match semantic.kind.as_str() {
"ran" => Some(timeline_summary_for_ran(item, payload, semantic)),
"explored" => Some(timeline_summary_for_explored(item, payload, semantic)),
"edited" => Some(timeline_summary_for_edited(
entry_type, item, payload, semantic,
)),
"waited" => Some(timeline_summary_for_waited(payload, semantic)),
_ => None,
}
}
pub(super) fn timeline_summary_from_diff_text(diff: &str) -> TimelineSummaryInfo {
let (primary_path, file_count, add_lines, remove_lines) = summarize_diff_text(diff);
TimelineSummaryInfo {
title: Some("Edited".to_string()),
label: Some("Edited".to_string()),
primary_path,
file_count: Some(file_count),
add_lines: Some(add_lines),
remove_lines: Some(remove_lines),
..TimelineSummaryInfo::default()
}
}
fn timeline_summary_for_ran(
item: &Value,
payload: &Value,
semantic: &TimelineSemanticInfo,
) -> TimelineSummaryInfo {
TimelineSummaryInfo {
title: Some(semantic_title(&semantic.kind).to_string()),
label: Some(detail_label(semantic.detail.as_deref()).to_string()),
command: payload_string(payload, "command")
.or_else(|| first_shell_command(payload))
.or_else(|| optional_string(item, "command")),
..TimelineSummaryInfo::default()
}
}
fn timeline_summary_for_explored(
item: &Value,
payload: &Value,
semantic: &TimelineSemanticInfo,
) -> TimelineSummaryInfo {
let mut summary = TimelineSummaryInfo {
title: Some(semantic_title(&semantic.kind).to_string()),
label: Some(detail_label(semantic.detail.as_deref()).to_string()),
command: payload_string(payload, "command")
.or_else(|| first_shell_command(payload))
.or_else(|| optional_string(item, "command")),
query: payload_string(payload, "query")
.or_else(|| first_search_query(payload))
.or_else(|| extract_query_from_web_search_action(payload)),
..TimelineSummaryInfo::default()
};
if let Some(actions) = payload.get("commandActions").and_then(Value::as_array) {
for action in actions {
if let Some(target) = command_action_target(action) {
push_unique(&mut summary.targets, target);
}
if summary.query.is_none() {
summary.query = command_action_query(action);
}
}
}
if summary.targets.is_empty() {
extract_targets_from_payload(payload)
.into_iter()
.for_each(|target| push_unique(&mut summary.targets, target));
}
if summary.command.is_none() {
summary.command = optional_string(item, "command");
}
summary
}
fn timeline_summary_for_edited(
entry_type: &str,
item: &Value,
payload: &Value,
semantic: &TimelineSemanticInfo,
) -> TimelineSummaryInfo {
if entry_type == "fileChange" {
let changes = payload
.get("changes")
.and_then(Value::as_array)
.map(Vec::as_slice)
.unwrap_or(&[]);
return timeline_summary_from_file_changes(changes, semantic);
}
if entry_type == "diff" {
return timeline_summary_from_diff_text(&pretty_json_value(
item.get("diff").unwrap_or(&Value::Null),
));
}
let patch_text = payload_string(payload, "operation")
.or_else(|| payload_string(payload, "output"))
.unwrap_or_default();
let mut summary = timeline_summary_from_diff_text(&patch_text);
summary.title = Some(semantic_title(&semantic.kind).to_string());
summary.label = Some(semantic_title(&semantic.kind).to_string());
summary
}
fn timeline_summary_for_waited(
payload: &Value,
semantic: &TimelineSemanticInfo,
) -> TimelineSummaryInfo {
let wait_count = payload
.get("receiverThreadIds")
.and_then(Value::as_array)
.map(|items| items.len() as i64)
.or_else(|| {
payload
.get("agentsStates")
.and_then(Value::as_object)
.map(|items| items.len() as i64)
});
TimelineSummaryInfo {
title: Some(semantic_title(&semantic.kind).to_string()),
label: Some(detail_label(semantic.detail.as_deref()).to_string()),
wait_count,
..TimelineSummaryInfo::default()
}
}
fn timeline_summary_from_file_changes(
changes: &[Value],
semantic: &TimelineSemanticInfo,
) -> TimelineSummaryInfo {
let mut summary = TimelineSummaryInfo {
title: Some(semantic_title(&semantic.kind).to_string()),
label: Some(semantic_title(&semantic.kind).to_string()),
file_count: Some(changes.len() as i64),
..TimelineSummaryInfo::default()
};
let mut add_lines = 0_i64;
let mut remove_lines = 0_i64;
for change in changes {
if summary.primary_path.is_none() {
summary.primary_path = change_path(change);
}
let (added, removed) = summarize_change_counts(change);
add_lines += added;
remove_lines += removed;
}
summary.add_lines = Some(add_lines);
summary.remove_lines = Some(remove_lines);
summary
}
fn pretty_json_value(value: &Value) -> String {
match value {
Value::Null => String::new(),
Value::String(text) => text.clone(),
_ => serde_json::to_string_pretty(value).unwrap_or_default(),
}
}