mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-06 15:30:47 +00:00
feat(ui): show active agent in dashboard header
Show the active agent name in the Control UI dashboard breadcrumb without adding the current session key/name. Verification: - pnpm test ui/src/ui/app-render.helpers.node.test.ts - node scripts/run-oxlint.mjs --tsconfig config/tsconfig/oxlint.core.json ui/src/ui/components/dashboard-header.ts ui/src/ui/app-render.helpers.ts ui/src/ui/app-render.ts ui/src/ui/app-render.helpers.node.test.ts - git diff --check - Testbox pnpm check:changed
This commit is contained in:
@@ -11,6 +11,7 @@ Docs: https://docs.openclaw.ai
|
||||
|
||||
### Changes
|
||||
|
||||
- Control UI/header: show the active agent name in dashboard breadcrumbs without adding the current session key, keeping non-chat views oriented without crowding the topbar.
|
||||
- Gateway/startup: keep model-catalog test helpers, run-session lookup code, QR pairing helpers, and TypeBox memory-tool schema construction out of hot startup import paths, reducing default gateway benchmark plugin-load and memory pressure.
|
||||
- Control UI/performance: record browser long animation frame or long task entries in the debug event log when supported, making slow dashboard renders easier to attribute from the UI.
|
||||
- Channels/streaming: add unified `streaming.mode: "progress"` drafts with auto single-word status labels and shared progress configuration across Discord, Telegram, Matrix, Slack, and Microsoft Teams.
|
||||
|
||||
@@ -196,6 +196,13 @@
|
||||
color: var(--muted);
|
||||
}
|
||||
|
||||
.topnav-shell .dashboard-header__breadcrumb-segment {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.topnav-shell .dashboard-header__breadcrumb-link {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
@@ -221,6 +228,21 @@
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
.topnav-shell .dashboard-header__breadcrumb-context {
|
||||
max-width: 18ch;
|
||||
overflow: hidden;
|
||||
color: var(--muted);
|
||||
font-weight: 550;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
@media (max-width: 720px) {
|
||||
.topnav-shell .dashboard-header__breadcrumb-context {
|
||||
max-width: 10ch;
|
||||
}
|
||||
}
|
||||
|
||||
.topbar-status {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
@@ -39,6 +39,7 @@ import {
|
||||
isCronSessionKey,
|
||||
parseSessionKey,
|
||||
resolveAssistantAttachmentAuthToken,
|
||||
resolveDashboardHeaderContext,
|
||||
resolveSessionOptionGroups,
|
||||
resolveSessionDisplayName,
|
||||
switchChatSession,
|
||||
@@ -452,6 +453,50 @@ describe("resolveSessionDisplayName", () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe("resolveDashboardHeaderContext", () => {
|
||||
it("uses the active agent identity name", () => {
|
||||
expect(
|
||||
resolveDashboardHeaderContext({
|
||||
sessionKey: "agent:deep-chat:imessage:sample-thread",
|
||||
agentsList: {
|
||||
defaultId: "deep-chat",
|
||||
mainKey: "main",
|
||||
scope: "user",
|
||||
agents: [{ id: "deep-chat", identity: { name: "Deep Chat" } }],
|
||||
},
|
||||
} as unknown as AppViewState),
|
||||
).toEqual({ agentLabel: "Deep Chat" });
|
||||
});
|
||||
|
||||
it("falls back to the configured agent name", () => {
|
||||
expect(
|
||||
resolveDashboardHeaderContext({
|
||||
sessionKey: "agent:beta:main",
|
||||
agentsList: {
|
||||
defaultId: "beta",
|
||||
mainKey: "main",
|
||||
scope: "user",
|
||||
agents: [{ id: "beta", name: "Coding" }],
|
||||
},
|
||||
} as unknown as AppViewState),
|
||||
).toEqual({ agentLabel: "Coding" });
|
||||
});
|
||||
|
||||
it("falls back to the agent id", () => {
|
||||
expect(
|
||||
resolveDashboardHeaderContext({
|
||||
sessionKey: "agent:beta:subagent:maintainer-v2",
|
||||
agentsList: {
|
||||
defaultId: "main",
|
||||
mainKey: "main",
|
||||
scope: "user",
|
||||
agents: [],
|
||||
},
|
||||
} as unknown as AppViewState),
|
||||
).toEqual({ agentLabel: "beta" });
|
||||
});
|
||||
});
|
||||
|
||||
describe("isCronSessionKey", () => {
|
||||
it("returns true for cron: prefixed keys", () => {
|
||||
expect(isCronSessionKey("cron:abc-123")).toBe(true);
|
||||
|
||||
@@ -18,7 +18,7 @@ import { createSessionAndRefresh, loadSessions } from "./controllers/sessions.ts
|
||||
import { icons } from "./icons.ts";
|
||||
import { iconForTab, pathForTab, titleForTab, type Tab } from "./navigation.ts";
|
||||
import { parseAgentSessionKey, resolveAgentIdFromSessionKey } from "./session-key.ts";
|
||||
import { normalizeOptionalString } from "./string-coerce.ts";
|
||||
import { normalizeLowercaseStringOrEmpty, normalizeOptionalString } from "./string-coerce.ts";
|
||||
import type { ThemeMode } from "./theme.ts";
|
||||
import type { SessionsListResult } from "./types.ts";
|
||||
import type { ChatQueueItem } from "./ui-types.ts";
|
||||
@@ -52,6 +52,20 @@ export function resolveAssistantAttachmentAuthToken(
|
||||
return resolveControlUiAuthToken(state);
|
||||
}
|
||||
|
||||
export function resolveDashboardHeaderContext(
|
||||
state: Pick<AppViewState, "agentsList" | "sessionKey">,
|
||||
): { agentLabel: string } {
|
||||
const agentId = resolveAgentIdFromSessionKey(state.sessionKey);
|
||||
const agent = state.agentsList?.agents.find(
|
||||
(entry) => normalizeLowercaseStringOrEmpty(entry.id) === agentId,
|
||||
);
|
||||
const agentLabel =
|
||||
normalizeOptionalString(agent?.identity?.name) ??
|
||||
normalizeOptionalString(agent?.name) ??
|
||||
agentId;
|
||||
return { agentLabel };
|
||||
}
|
||||
|
||||
function resolveSidebarChatSessionKey(state: AppViewState): string {
|
||||
const snapshot = state.hello?.snapshot as
|
||||
| { sessionDefaults?: SessionDefaultsSnapshot }
|
||||
|
||||
@@ -11,6 +11,7 @@ import {
|
||||
renderChatSessionSelect,
|
||||
renderTab,
|
||||
resolveAssistantAttachmentAuthToken,
|
||||
resolveDashboardHeaderContext,
|
||||
renderSidebarConnectionStatus,
|
||||
renderTopbarThemeModeToggle,
|
||||
createChatSession,
|
||||
@@ -645,6 +646,7 @@ export function renderApp(state: AppViewState) {
|
||||
const chatFocus = isChat && (state.settings.chatFocusMode || state.onboarding);
|
||||
const navDrawerOpen = state.navDrawerOpen && !chatFocus && !state.onboarding;
|
||||
const navCollapsed = state.settings.navCollapsed && !navDrawerOpen;
|
||||
const dashboardHeaderContext = resolveDashboardHeaderContext(state);
|
||||
const showThinking = state.onboarding ? false : state.settings.chatShowThinking;
|
||||
const showToolCalls = state.onboarding ? true : state.settings.chatShowToolCalls;
|
||||
const localAssistantAvatarOverride =
|
||||
@@ -1362,6 +1364,7 @@ export function renderApp(state: AppViewState) {
|
||||
<dashboard-header
|
||||
.tab=${state.tab}
|
||||
.basePath=${state.basePath}
|
||||
.agentLabel=${dashboardHeaderContext.agentLabel}
|
||||
@navigate=${(event: CustomEvent<Tab>) => {
|
||||
state.setTab(event.detail);
|
||||
}}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { LitElement, html } from "lit";
|
||||
import { LitElement, html, nothing } from "lit";
|
||||
import { property } from "lit/decorators.js";
|
||||
import { pathForTab, titleForTab, type Tab } from "../navigation.js";
|
||||
|
||||
@@ -9,6 +9,7 @@ export class DashboardHeader extends LitElement {
|
||||
|
||||
@property() tab: Tab = "overview";
|
||||
@property() basePath = "";
|
||||
@property() agentLabel = "";
|
||||
|
||||
private readonly handleOverviewClick = (event: MouseEvent) => {
|
||||
if (
|
||||
@@ -29,6 +30,7 @@ export class DashboardHeader extends LitElement {
|
||||
|
||||
override render() {
|
||||
const label = titleForTab(this.tab);
|
||||
const agentLabel = this.agentLabel.trim();
|
||||
|
||||
return html`
|
||||
<div class="dashboard-header">
|
||||
@@ -40,6 +42,16 @@ export class DashboardHeader extends LitElement {
|
||||
>
|
||||
OpenClaw
|
||||
</a>
|
||||
${agentLabel
|
||||
? html`
|
||||
<span class="dashboard-header__breadcrumb-segment">
|
||||
<span class="dashboard-header__breadcrumb-sep">›</span>
|
||||
<span class="dashboard-header__breadcrumb-context" title=${agentLabel}>
|
||||
${agentLabel}
|
||||
</span>
|
||||
</span>
|
||||
`
|
||||
: nothing}
|
||||
<span class="dashboard-header__breadcrumb-sep">›</span>
|
||||
<span class="dashboard-header__breadcrumb-current">${label}</span>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user