From fe19f02162181ba7b61f82d4150c503133ccd192 Mon Sep 17 00:00:00 2001 From: "Maksym Hryzodub [DREAM]" Date: Fri, 14 Aug 2026 12:02:34 +0200 Subject: [PATCH 1/2] fix(admin): compact agent usage into a header strip so logs fill the panel CLEAN-34: the chat side column split height 50/50 between UsagePanel and logs. Usage is now a 10px header line; logs take the remaining height. Overview and Rancher keep the full usage card. Co-authored-by: Cursor --- .../agent/agent/components/agent/chat/Tab.vue | 25 ++++----- admin/slices/usage/components/usage/Panel.vue | 54 +++++++++++++++++-- 2 files changed, 61 insertions(+), 18 deletions(-) diff --git a/admin/slices/agent/agent/components/agent/chat/Tab.vue b/admin/slices/agent/agent/components/agent/chat/Tab.vue index 73dcefe..badb9eb 100644 --- a/admin/slices/agent/agent/components/agent/chat/Tab.vue +++ b/admin/slices/agent/agent/components/agent/chat/Tab.vue @@ -22,12 +22,10 @@ const emit = defineEmits<{ restart: []; toggleRunning: [] }>(); const authStore = useAuthStore(); -// Side stack next to the chat: logs on top, usage panel below, each -// independently collapsible into a compact button. Logs stay the primary -// operational surface (open by default); collapsing one hands its height to -// the other via flex. The logs panel is only mounted while open, so its 5s -// polling stops the moment it's collapsed. The usage panel manages its own -// collapsed state (`collapsible` prop). +// Side column next to the chat: a one-line usage strip in the header, then +// logs filling the rest of the height (as they did before UsagePanel split +// the column 50/50). The logs panel is only mounted while open, so its 5s +// polling stops the moment it's collapsed. const showSideLogs = ref(true); // One "restart is underway" signal for every surface (bridle header status, @@ -151,8 +149,14 @@ watch(
+
Logs - -
diff --git a/admin/slices/usage/components/usage/Panel.vue b/admin/slices/usage/components/usage/Panel.vue index 69b3469..b555697 100644 --- a/admin/slices/usage/components/usage/Panel.vue +++ b/admin/slices/usage/components/usage/Panel.vue @@ -6,9 +6,10 @@ import { IconChevronRight, } from '@tabler/icons-vue'; import type { IUsageDailyEntry } from '#usage/domain'; +import { formatCount, formatUsd } from '#agent/utils/agentFormat'; -// Host classes (flex sizing in side stacks) must land on the Card only — -// the collapsed compact button keeps its own fixed footprint. +// Host classes (flex sizing in side stacks / the chat header strip) must +// land on the visible root — Card, collapsed button, or strip — not a wrapper. defineOptions({ inheritAttrs: false }); const props = withDefaults( @@ -24,8 +25,13 @@ const props = withDefaults( * fetched. */ agentOnly?: boolean; + /** + * `strip` — one thin header line (agent chat). `panel` — the full card + * (Overview, Rancher). Collapsible is ignored in strip mode. + */ + variant?: 'panel' | 'strip'; }>(), - { title: 'Usage · 30d', collapsible: false, agentOnly: false }, + { title: 'Usage · 30d', collapsible: false, agentOnly: false, variant: 'panel' }, ); type UsageView = 'total' | 'calls' | 'agent'; @@ -122,6 +128,23 @@ const viewHint = computed(() => view.value === 'agent' ? 'this agent only' : 'all agents', ); +const stripTitle = computed(() => { + const usage = agentUsage.value; + if (!usage || usage.totals.callCount === 0) return 'Usage · this agent, 30d'; + const parts = [ + `30d ${formatUsd(usage.totals.costUsd)}`, + `${formatCount(usage.totals.inputTokens)} in / ${formatCount(usage.totals.outputTokens)} out`, + `${formatCount(usage.totals.callCount)} calls`, + ]; + if (usage.topModel) parts.push(usage.topModel); + if (usage.today.callCount) { + parts.push( + `today ${formatCount(usage.today.inputTokens)}/${formatCount(usage.today.outputTokens)}`, + ); + } + return parts.join(' · '); +}); + const cost = new Intl.NumberFormat('en-US', { style: 'currency', currency: 'USD', @@ -145,8 +168,31 @@ defineExpose({ refresh });