diff --git a/astrbot/core/utils/trace.py b/astrbot/core/utils/trace.py index b8f451a92..9bfe99f73 100644 --- a/astrbot/core/utils/trace.py +++ b/astrbot/core/utils/trace.py @@ -71,6 +71,7 @@ class TraceSpan: "has_errors": False, "has_models": False, "has_plugins": False, + "has_commands": False, "span_count": 1, "truncated": False, "attributes": bounded_attributes( @@ -265,6 +266,8 @@ class TraceSpan: self._summary["span_count"] = self._span_count if action == "handler_start": self._summary["has_plugins"] = True + if fields.get("handler_category") == "command": + self._summary["has_commands"] = True if len(self._plugins) < MAX_SPANS: self._plugins.add( bounded_text(str(fields.get("plugin") or ""), 256) diff --git a/astrbot/core/utils/trace_store.py b/astrbot/core/utils/trace_store.py index 5783b3453..9fd1faea9 100644 --- a/astrbot/core/utils/trace_store.py +++ b/astrbot/core/utils/trace_store.py @@ -493,8 +493,31 @@ class TraceStore: + " ORDER BY start_ns DESC, trace_id DESC LIMIT ? OFFSET ?", [*values, limit + 1, (page - 1) * limit], ).fetchall() + summaries = [json.loads(row[0]) for row in rows[:limit]] + # Older summaries predate command flags; derive only those flags + # from this page's indexed spans without loading full trace detail. + legacy = { + summary["trace_id"]: summary + for summary in summaries + if "has_commands" not in summary + } + if legacy: + placeholders = ",".join("?" for _ in legacy) + for trace_id, data in connection.execute( + f"SELECT trace_id, data FROM spans WHERE trace_id IN ({placeholders})", + list(legacy), + ): + if ( + json.loads(data) + .get("attributes", {}) + .get("astrbot.handler_category") + == "command" + ): + legacy[trace_id]["has_commands"] = True + for summary in legacy.values(): + summary.setdefault("has_commands", False) return { - "traces": [json.loads(row[0]) for row in rows[:limit]], + "traces": summaries, "total": total, "page": page, "cursor": f"{rows[limit - 1][1]}:{rows[limit - 1][2]}" diff --git a/changelogs/unreleased.md b/changelogs/unreleased.md index 45678e3fc..e1f024c6c 100644 --- a/changelogs/unreleased.md +++ b/changelogs/unreleased.md @@ -1,5 +1,7 @@ # Unreleased +- Add Type before UMO in the trace table, with independent blue Agent and green Command chips. Persist command flags in summaries and derive legacy flags for the current page; use explicit colors because the generic Vuetify color pack is disabled. + - Use flat status chips in the trace table and narrow the platform column to 80px, retaining a non-wrapping header. - Set trace table header and cell text to 13px without changing timeline text or table density. diff --git a/dashboard/src/components/shared/TraceDisplayer.vue b/dashboard/src/components/shared/TraceDisplayer.vue index d1748ddc5..d59caabf9 100644 --- a/dashboard/src/components/shared/TraceDisplayer.vue +++ b/dashboard/src/components/shared/TraceDisplayer.vue @@ -158,6 +158,7 @@ const traceHeaders = computed(() => [ { title: tm("columns.timestamp"), key: "timestamp", width: 160 }, { title: tm("columns.duration"), key: "duration", width: 120 }, { title: tm("columns.message"), key: "message", minWidth: 240 }, + { title: tm("columns.category"), key: "category", width: 150, minWidth: 150, headerProps: { class: "text-no-wrap" } }, { title: "UMO", key: "umo", minWidth: 240 }, { title: tm("columns.sender"), key: "sender", minWidth: 120 }, { title: "Spans", key: "spans", width: 80 }, @@ -333,6 +334,9 @@ function normalizeTracePayload(payload) { last_time: Number(summary.end_time_unix_nano) / 1e9 || time, completed: finished, status: summary.has_errors ? "error" : finished ? "success" : "running", has_models: summary.has_models, has_plugins: summary.has_plugins, + has_commands: summary.has_commands ?? (payload.spans + ? payload.spans.some((span) => span.attributes?.["astrbot.handler_category"] === "command") + : undefined), span_count: summary.span_count, truncated: summary.truncated, model_updated_ns: String(summary.updated_time_unix_nano || summary.start_time_unix_nano), }; @@ -874,6 +878,12 @@ onBeforeUnmount(() => { +