From 3c0f33acdef24cde41e1d9599fc9d0ace904df28 Mon Sep 17 00:00:00 2001 From: Michael Panchenko Date: Sat, 12 Jul 2025 20:40:44 +0200 Subject: [PATCH] Improvements in tool call stats display --- src/serena/resources/dashboard/dashboard.js | 154 +++++++++++++++++++- src/serena/resources/dashboard/index.html | 96 +++++++++++- 2 files changed, 237 insertions(+), 13 deletions(-) diff --git a/src/serena/resources/dashboard/dashboard.js b/src/serena/resources/dashboard/dashboard.js index 0da2bc2e..205e7a71 100644 --- a/src/serena/resources/dashboard/dashboard.js +++ b/src/serena/resources/dashboard/dashboard.js @@ -42,9 +42,11 @@ class Dashboard { this.$shutdownButton = $('#shutdown'); this.$toggleStats = $('#toggle-stats'); this.$statsSection = $('#stats-section'); + this.$refreshStats = $('#refresh-stats'); this.$clearStats = $('#clear-stats'); this.countChart = null; + this.tokensChart = null; this.inputChart = null; this.outputChart = null; @@ -52,6 +54,7 @@ class Dashboard { this.$loadButton.click(this.loadLogs.bind(this)); this.$shutdownButton.click(this.shutdown.bind(this)); this.$toggleStats.click(this.toggleStats.bind(this)); + this.$refreshStats.click(this.loadStats.bind(this)); this.$clearStats.click(this.clearStats.bind(this)); // initialize the application @@ -229,41 +232,178 @@ class Dashboard { const counts = names.map(n => stats[n].num_times_called); const inputTokens = names.map(n => stats[n].input_tokens); const outputTokens = names.map(n => stats[n].output_tokens); + const totalTokens = names.map(n => stats[n].input_tokens + stats[n].output_tokens); + + // Calculate totals for summary table + const totalCalls = counts.reduce((sum, count) => sum + count, 0); + const totalInputTokens = inputTokens.reduce((sum, tokens) => sum + tokens, 0); + const totalOutputTokens = outputTokens.reduce((sum, tokens) => sum + tokens, 0); + + // Generate consistent colors for tools + const colors = this.generateColors(names.length); const countCtx = document.getElementById('count-chart'); + const tokensCtx = document.getElementById('tokens-chart'); const inputCtx = document.getElementById('input-chart'); const outputCtx = document.getElementById('output-chart'); if (this.countChart) this.countChart.destroy(); + if (this.tokensChart) this.tokensChart.destroy(); if (this.inputChart) this.inputChart.destroy(); if (this.outputChart) this.outputChart.destroy(); if (names.length === 0) { this.countChart = null; + this.tokensChart = null; this.inputChart = null; this.outputChart = null; countCtx.getContext('2d').clearRect(0,0,countCtx.width,countCtx.height); + tokensCtx.getContext('2d').clearRect(0,0,tokensCtx.width,tokensCtx.height); inputCtx.getContext('2d').clearRect(0,0,inputCtx.width,inputCtx.height); outputCtx.getContext('2d').clearRect(0,0,outputCtx.width,outputCtx.height); return; } + // Update summary table + this.updateSummaryTable(totalCalls, totalInputTokens, totalOutputTokens); + + // Register datalabels plugin + Chart.register(ChartDataLabels); + + // Tool calls pie chart this.countChart = new Chart(countCtx, { type: 'pie', - data: { labels: names, datasets: [{ data: counts }] }, + data: { + labels: names, + datasets: [{ + data: counts, + backgroundColor: colors + }] + }, + options: { + plugins: { + legend: { display: true }, + datalabels: { + display: true, + color: 'white', + font: { weight: 'bold' }, + formatter: (value) => value + } + } + } }); + // Input tokens pie chart this.inputChart = new Chart(inputCtx, { - type: 'bar', - data: { labels: names, datasets: [{ label: 'Input Tokens', data: inputTokens }] }, - options: { scales: { y: { beginAtZero: true } } } + type: 'pie', + data: { + labels: names, + datasets: [{ + data: inputTokens, + backgroundColor: colors + }] + }, + options: { + plugins: { + legend: { display: true }, + datalabels: { + display: true, + color: 'white', + font: { weight: 'bold' }, + formatter: (value) => value + } + } + } }); + // Output tokens pie chart this.outputChart = new Chart(outputCtx, { - type: 'bar', - data: { labels: names, datasets: [{ label: 'Output Tokens', data: outputTokens }] }, - options: { scales: { y: { beginAtZero: true } } } + type: 'pie', + data: { + labels: names, + datasets: [{ + data: outputTokens, + backgroundColor: colors + }] + }, + options: { + plugins: { + legend: { display: true }, + datalabels: { + display: true, + color: 'white', + font: { weight: 'bold' }, + formatter: (value) => value + } + } + } }); + + // Combined input/output tokens bar chart + this.tokensChart = new Chart(tokensCtx, { + type: 'bar', + data: { + labels: names, + datasets: [ + { + label: 'Input Tokens', + data: inputTokens, + backgroundColor: colors.map(color => color + '80'), // Semi-transparent + borderColor: colors, + borderWidth: 2, + borderSkipped: false, + yAxisID: 'y' + }, + { + label: 'Output Tokens', + data: outputTokens, + backgroundColor: colors, + yAxisID: 'y1' + } + ] + }, + options: { + responsive: true, + scales: { + y: { + type: 'linear', + display: true, + position: 'left', + beginAtZero: true, + title: { display: true, text: 'Input Tokens' } + }, + y1: { + type: 'linear', + display: true, + position: 'right', + beginAtZero: true, + title: { display: true, text: 'Output Tokens' }, + grid: { drawOnChartArea: false } + } + } + } + }); + } + + generateColors(count) { + const colors = [ + '#FF6384', '#36A2EB', '#FFCE56', '#4BC0C0', '#9966FF', + '#FF9F40', '#FF6384', '#C9CBCF', '#4BC0C0', '#FF6384' + ]; + return Array.from({length: count}, (_, i) => colors[i % colors.length]); + } + + updateSummaryTable(totalCalls, totalInputTokens, totalOutputTokens) { + const tableHtml = ` + + + + + + +
MetricTotal
Tool Calls${totalCalls}
Input Tokens${totalInputTokens}
Output Tokens${totalOutputTokens}
Total Tokens${totalInputTokens + totalOutputTokens}
+ `; + $('#stats-summary').html(tableHtml); } shutdown() { diff --git a/src/serena/resources/dashboard/index.html b/src/serena/resources/dashboard/index.html index e1e31c73..e14b95d9 100644 --- a/src/serena/resources/dashboard/index.html +++ b/src/serena/resources/dashboard/index.html @@ -9,6 +9,7 @@ + @@ -114,12 +179,31 @@
-