diff --git a/src/serena/analytics.py b/src/serena/analytics.py index a13f119..c773fc0 100644 --- a/src/serena/analytics.py +++ b/src/serena/analytics.py @@ -2,7 +2,7 @@ from __future__ import annotations import logging import threading -from abc import ABC +from abc import ABC, abstractmethod from collections import defaultdict from copy import copy from dataclasses import asdict, dataclass @@ -15,6 +15,7 @@ log = logging.getLogger(__name__) class TokenCountEstimator(ABC): + @abstractmethod def estimate_token_count(self, text: str) -> int: """ Estimate the number of tokens in the given text. @@ -84,9 +85,9 @@ class RegisteredTokenCountEstimator(Enum): def _create_estimator(self) -> TokenCountEstimator: match self: - case self.TIKTOKEN_GPT4O: + case RegisteredTokenCountEstimator.TIKTOKEN_GPT4O: return TiktokenCountEstimator(model_name="gpt-4o") - case self.ANTHROPIC_CLAUDE_SONNET_4: + case RegisteredTokenCountEstimator.ANTHROPIC_CLAUDE_SONNET_4: return AnthropicTokenCount(model_name="claude-sonnet-4-20250514") case _: raise ValueError(f"Unknown token count estimator: {self.value}") @@ -110,6 +111,13 @@ class ToolUsageStats: self._tool_stats: dict[str, ToolUsageStats.Entry] = defaultdict(ToolUsageStats.Entry) self._tool_stats_lock = threading.Lock() + @property + def token_estimator_name(self) -> str: + """ + Get the name of the registered token count estimator used. + """ + return self._token_estimator_name + @dataclass(kw_only=True) class Entry: num_times_called: int = 0 diff --git a/src/serena/dashboard.py b/src/serena/dashboard.py index 2141eb6..e2be05c 100644 --- a/src/serena/dashboard.py +++ b/src/serena/dashboard.py @@ -130,6 +130,11 @@ class SerenaDashboardAPI: self._clear_tool_stats() return {"status": "cleared"} + @self._app.route("/get_token_count_estimator_name", methods=["GET"]) + def get_token_count_estimator_name() -> dict[str, str]: + estimator_name = self._tool_usage_stats.token_estimator_name if self._tool_usage_stats else "unknown" + return {"token_count_estimator_name": estimator_name} + @self._app.route("/shutdown", methods=["PUT"]) def shutdown() -> dict[str, str]: self._shutdown() @@ -150,7 +155,8 @@ class SerenaDashboardAPI: return ResponseToolStats(stats={}) def _clear_tool_stats(self) -> None: - self._tool_usage_stats.clear() + if self._tool_usage_stats is not None: + self._tool_usage_stats.clear() def _shutdown(self) -> None: log.info("Shutting down Serena") diff --git a/src/serena/resources/dashboard/dashboard.js b/src/serena/resources/dashboard/dashboard.js index 205e7a7..b57226a 100644 --- a/src/serena/resources/dashboard/dashboard.js +++ b/src/serena/resources/dashboard/dashboard.js @@ -201,18 +201,19 @@ class Dashboard { loadStats() { let self = this; - $.ajax({ - url: '/get_tool_stats', - type: 'GET', - success: function(response) { - self.displayStats(response.stats || {}); - }, - error: function(xhr, status, error) { - console.error('Error loading stats:', error); - } + $.when( + $.ajax({ url: '/get_tool_stats', type: 'GET' }), + $.ajax({ url: '/get_token_count_estimator_name', type: 'GET' }) + ).done(function(statsResp, estimatorResp) { + const stats = statsResp[0].stats; + const tokenCountEstimatorName = estimatorResp[0].token_count_estimator_name; + self.displayStats(stats, tokenCountEstimatorName); + }).fail(function() { + console.error('Error loading stats or estimator name'); }); } + clearStats() { let self = this; $.ajax({ @@ -227,8 +228,27 @@ class Dashboard { }); } - displayStats(stats) { + displayStats(stats, tokenCountEstimatorName) { const names = Object.keys(stats); + // If no stats collected + if (names.length === 0) { + // hide summary, charts, estimator name + $('#stats-summary').hide(); + $('#estimator-name').hide(); + $('.charts-container').hide(); + // show no-stats message + $('#no-stats-message').show(); + return; + } else { + // Ensure everything is visible + $('#estimator-name').show(); + $('#stats-summary').show(); + $('.charts-container').show(); + $('#no-stats-message').hide(); + } + + $('#estimator-name').html(`Token count estimator: ${tokenCountEstimatorName}`); + 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); @@ -252,18 +272,6 @@ class Dashboard { 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); diff --git a/src/serena/resources/dashboard/index.html b/src/serena/resources/dashboard/index.html index e14b95d..a7d21c9 100644 --- a/src/serena/resources/dashboard/index.html +++ b/src/serena/resources/dashboard/index.html @@ -186,6 +186,11 @@
+
+ +