Add current session statistics
This commit is contained in:
@@ -1,9 +1,11 @@
|
|||||||
use super::*;
|
use super::*;
|
||||||
|
use crate::app::generation;
|
||||||
use iced::widget::column;
|
use iced::widget::column;
|
||||||
|
|
||||||
impl App {
|
impl App {
|
||||||
pub(super) fn stats_dashboard(&self) -> Element<'_, Message> {
|
pub(super) fn stats_dashboard(&self) -> Element<'_, Message> {
|
||||||
let stats = &self.metrics_snapshot;
|
let stats = &self.metrics_snapshot;
|
||||||
|
let session = SessionStats::from_messages(&self.conversation, self.config.model);
|
||||||
let context_fraction = if stats.context_limit == 0 {
|
let context_fraction = if stats.context_limit == 0 {
|
||||||
0.0
|
0.0
|
||||||
} else {
|
} else {
|
||||||
@@ -97,6 +99,51 @@ impl App {
|
|||||||
]
|
]
|
||||||
.spacing(10);
|
.spacing(10);
|
||||||
|
|
||||||
|
let session_title = self
|
||||||
|
.selected_session
|
||||||
|
.and_then(|id| self.session_title(id))
|
||||||
|
.unwrap_or_else(|| "No session selected".to_owned());
|
||||||
|
let session_cache_fraction = if session.input_tokens == 0 {
|
||||||
|
0.0
|
||||||
|
} else {
|
||||||
|
session.cached_tokens as f64 / session.input_tokens as f64
|
||||||
|
};
|
||||||
|
let current_session = stats_panel(
|
||||||
|
"CURRENT SESSION",
|
||||||
|
column![
|
||||||
|
metric_row("Session", session_title),
|
||||||
|
metric_row(
|
||||||
|
"Messages",
|
||||||
|
format!(
|
||||||
|
"{} · {} user · {} assistant · {} tool",
|
||||||
|
format_count(session.messages),
|
||||||
|
format_count(session.user_messages),
|
||||||
|
format_count(session.assistant_messages),
|
||||||
|
format_count(session.tool_messages),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
metric_row("Tool calls", format_count(session.tool_calls)),
|
||||||
|
metric_row("Context rebuilds", format_count(session.context_rebuilds)),
|
||||||
|
metric_row("Completed turns", format_count(session.completed_turns),),
|
||||||
|
metric_row(
|
||||||
|
"Generation time",
|
||||||
|
format_duration(session.duration_ms as f64 / 1_000.0),
|
||||||
|
),
|
||||||
|
metric_row("Input tokens", format_count(session.input_tokens)),
|
||||||
|
metric_row(
|
||||||
|
"Cached tokens",
|
||||||
|
format!(
|
||||||
|
"{} · {:.1}% reused",
|
||||||
|
format_count(session.cached_tokens),
|
||||||
|
session_cache_fraction * 100.0,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
metric_row("Output tokens", format_count(session.output_tokens)),
|
||||||
|
]
|
||||||
|
.spacing(9)
|
||||||
|
.into(),
|
||||||
|
);
|
||||||
|
|
||||||
let throughput = stats_panel(
|
let throughput = stats_panel(
|
||||||
"MODEL ACTIVITY · LAST 24 SECONDS",
|
"MODEL ACTIVITY · LAST 24 SECONDS",
|
||||||
column![
|
column![
|
||||||
@@ -528,6 +575,7 @@ impl App {
|
|||||||
column![
|
column![
|
||||||
heading,
|
heading,
|
||||||
headline,
|
headline,
|
||||||
|
current_session,
|
||||||
throughput,
|
throughput,
|
||||||
ssd_activity,
|
ssd_activity,
|
||||||
kv_io,
|
kv_io,
|
||||||
@@ -681,6 +729,59 @@ impl App {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Default, PartialEq, Eq)]
|
||||||
|
struct SessionStats {
|
||||||
|
messages: u64,
|
||||||
|
user_messages: u64,
|
||||||
|
assistant_messages: u64,
|
||||||
|
tool_messages: u64,
|
||||||
|
tool_calls: u64,
|
||||||
|
context_rebuilds: u64,
|
||||||
|
completed_turns: u64,
|
||||||
|
duration_ms: u64,
|
||||||
|
input_tokens: u64,
|
||||||
|
cached_tokens: u64,
|
||||||
|
output_tokens: u64,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl SessionStats {
|
||||||
|
fn from_messages(messages: &[generation::ChatMessage], model: ModelChoice) -> Self {
|
||||||
|
let mut summary = Self::default();
|
||||||
|
for message in messages {
|
||||||
|
if let Some(stats) = message.generation_stats {
|
||||||
|
summary.completed_turns = summary.completed_turns.saturating_add(1);
|
||||||
|
summary.duration_ms = summary.duration_ms.saturating_add(stats.duration_ms);
|
||||||
|
summary.input_tokens = summary
|
||||||
|
.input_tokens
|
||||||
|
.saturating_add(u64::from(stats.input_tokens));
|
||||||
|
summary.cached_tokens = summary
|
||||||
|
.cached_tokens
|
||||||
|
.saturating_add(u64::from(stats.cached_tokens));
|
||||||
|
summary.output_tokens = summary
|
||||||
|
.output_tokens
|
||||||
|
.saturating_add(u64::from(stats.output_tokens));
|
||||||
|
}
|
||||||
|
if message.compaction {
|
||||||
|
summary.context_rebuilds = summary.context_rebuilds.saturating_add(1);
|
||||||
|
} else if !message.system {
|
||||||
|
summary.messages = summary.messages.saturating_add(1);
|
||||||
|
if message.user {
|
||||||
|
summary.user_messages = summary.user_messages.saturating_add(1);
|
||||||
|
} else if message.tool {
|
||||||
|
summary.tool_messages = summary.tool_messages.saturating_add(1);
|
||||||
|
} else {
|
||||||
|
summary.assistant_messages = summary.assistant_messages.saturating_add(1);
|
||||||
|
let calls = crate::agent::parse_tool_calls(model, &message.content)
|
||||||
|
.map(|(_, calls)| calls.len() as u64)
|
||||||
|
.unwrap_or(0);
|
||||||
|
summary.tool_calls = summary.tool_calls.saturating_add(calls);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
summary
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Sessions sit outside the age scale: they are not evicted, only deleted.
|
/// Sessions sit outside the age scale: they are not evicted, only deleted.
|
||||||
const SESSION_COLOR: Color = Color::from_rgb(0.85, 0.44, 0.56);
|
const SESSION_COLOR: Color = Color::from_rgb(0.85, 0.44, 0.56);
|
||||||
|
|
||||||
@@ -705,3 +806,74 @@ fn shorten(title: &str) -> String {
|
|||||||
fn portion(bytes: u64, capacity: u64) -> u16 {
|
fn portion(bytes: u64, capacity: u64) -> u16 {
|
||||||
((bytes.saturating_mul(1000) / capacity.max(1)) as u16).max(1)
|
((bytes.saturating_mul(1000) / capacity.max(1)) as u16).max(1)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
fn message(user: bool, tool: bool, system: bool, content: &str) -> generation::ChatMessage {
|
||||||
|
generation::ChatMessage {
|
||||||
|
id: 1,
|
||||||
|
user,
|
||||||
|
tool,
|
||||||
|
system,
|
||||||
|
compaction: false,
|
||||||
|
compaction_tail_start: None,
|
||||||
|
generation_stats: None,
|
||||||
|
reasoning: None,
|
||||||
|
reasoning_complete: true,
|
||||||
|
reasoning_open: false,
|
||||||
|
content: content.to_owned(),
|
||||||
|
model_content: None,
|
||||||
|
markdown: markdown::Content::new(),
|
||||||
|
transcript: text_editor::Content::new(),
|
||||||
|
a2ui_lines_processed: 0,
|
||||||
|
a2ui_errors: Vec::new(),
|
||||||
|
a2ui_replies: Vec::new(),
|
||||||
|
a2ui_open_urls: Vec::new(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn current_session_summary_uses_persisted_chat_facts() {
|
||||||
|
let mut user = message(true, false, false, "Run it");
|
||||||
|
user.generation_stats = Some(generation::GenerationStats {
|
||||||
|
duration_ms: 1_250,
|
||||||
|
input_tokens: 2_000,
|
||||||
|
cached_tokens: 1_500,
|
||||||
|
output_tokens: 300,
|
||||||
|
});
|
||||||
|
let assistant = message(
|
||||||
|
false,
|
||||||
|
false,
|
||||||
|
false,
|
||||||
|
"Checking<|DSML|tool_calls><|DSML|invoke name=\"read\"><|DSML|parameter name=\"path\" string=\"true\">src/main.rs</|DSML|parameter></|DSML|invoke><|DSML|invoke name=\"list\"><|DSML|parameter name=\"path\" string=\"true\">src</|DSML|parameter></|DSML|invoke></|DSML|tool_calls>",
|
||||||
|
);
|
||||||
|
let tool = message(false, true, false, "Tool result 1\n...");
|
||||||
|
let mut compaction = message(false, false, true, "summary");
|
||||||
|
compaction.compaction = true;
|
||||||
|
let system = message(false, false, true, "reminder");
|
||||||
|
|
||||||
|
let summary = SessionStats::from_messages(
|
||||||
|
&[user, assistant, tool, compaction, system],
|
||||||
|
ModelChoice::DeepSeekV4Flash,
|
||||||
|
);
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
summary,
|
||||||
|
SessionStats {
|
||||||
|
messages: 3,
|
||||||
|
user_messages: 1,
|
||||||
|
assistant_messages: 1,
|
||||||
|
tool_messages: 1,
|
||||||
|
tool_calls: 2,
|
||||||
|
context_rebuilds: 1,
|
||||||
|
completed_turns: 1,
|
||||||
|
duration_ms: 1_250,
|
||||||
|
input_tokens: 2_000,
|
||||||
|
cached_tokens: 1_500,
|
||||||
|
output_tokens: 300,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user