fix(ui): harden avatar fallback regressions

This commit is contained in:
Peter Steinberger
2026-03-13 03:46:30 +00:00
parent 4656317770
commit bffce8ea4f
4 changed files with 78 additions and 9 deletions

View File

@@ -2,6 +2,7 @@ import { describe, expect, it } from "vitest";
import { import {
agentLogoUrl, agentLogoUrl,
resolveConfiguredCronModelSuggestions, resolveConfiguredCronModelSuggestions,
resolveAgentAvatarUrl,
resolveEffectiveModelFallbacks, resolveEffectiveModelFallbacks,
sortLocaleStrings, sortLocaleStrings,
} from "./agents-utils.ts"; } from "./agents-utils.ts";
@@ -110,3 +111,18 @@ describe("agentLogoUrl", () => {
expect(agentLogoUrl("")).toBe("favicon.svg"); expect(agentLogoUrl("")).toBe("favicon.svg");
}); });
}); });
describe("resolveAgentAvatarUrl", () => {
it("prefers a runtime avatar URL over non-URL identity avatars", () => {
expect(
resolveAgentAvatarUrl({ identity: { avatar: "A", avatarUrl: "/avatar/main" } }, {
avatar: "A",
} as { avatar: string }),
).toBe("/avatar/main");
});
it("returns null for initials or emoji avatar values without a URL", () => {
expect(resolveAgentAvatarUrl({ identity: { avatar: "A" } })).toBeNull();
expect(resolveAgentAvatarUrl({ identity: { avatar: "🦞" } })).toBeNull();
});
});

View File

@@ -200,15 +200,18 @@ export function resolveAgentAvatarUrl(
agent: { identity?: { avatar?: string; avatarUrl?: string } }, agent: { identity?: { avatar?: string; avatarUrl?: string } },
agentIdentity?: AgentIdentityResult | null, agentIdentity?: AgentIdentityResult | null,
): string | null { ): string | null {
const url = const candidates = [
agentIdentity?.avatar?.trim() ?? agentIdentity?.avatar?.trim(),
agent.identity?.avatarUrl?.trim() ?? agent.identity?.avatarUrl?.trim(),
agent.identity?.avatar?.trim(); agent.identity?.avatar?.trim(),
if (!url) { ];
return null; for (const candidate of candidates) {
if (!candidate) {
continue;
}
if (AVATAR_URL_RE.test(candidate)) {
return candidate;
} }
if (AVATAR_URL_RE.test(url)) {
return url;
} }
return null; return null;
} }

View File

@@ -96,6 +96,55 @@ describe("chat view", () => {
expect(logoImage?.getAttribute("src")).toBe("favicon.svg"); expect(logoImage?.getAttribute("src")).toBe("favicon.svg");
}); });
it("keeps the welcome logo fallback under the mounted base path", () => {
const container = document.createElement("div");
render(
renderChat(
createProps({
assistantName: "Assistant",
assistantAvatar: "A",
assistantAvatarUrl: null,
basePath: "/openclaw/",
}),
),
container,
);
const logoImage = container.querySelector<HTMLImageElement>(
".agent-chat__welcome .agent-chat__avatar--logo img",
);
expect(logoImage).not.toBeNull();
expect(logoImage?.getAttribute("src")).toBe("/openclaw/favicon.svg");
});
it("keeps grouped assistant avatar fallbacks under the mounted base path", () => {
const container = document.createElement("div");
render(
renderChat(
createProps({
assistantName: "Assistant",
assistantAvatar: "A",
assistantAvatarUrl: null,
basePath: "/openclaw/",
messages: [
{
role: "assistant",
content: "hello",
timestamp: 1000,
},
],
}),
),
container,
);
const groupedLogo = container.querySelector<HTMLImageElement>(
".chat-group.assistant .chat-avatar--logo",
);
expect(groupedLogo).not.toBeNull();
expect(groupedLogo?.getAttribute("src")).toBe("/openclaw/favicon.svg");
});
it("renders compacting indicator as a badge", () => { it("renders compacting indicator as a badge", () => {
const container = document.createElement("div"); const container = document.createElement("div");
render( render(

View File

@@ -82,6 +82,7 @@ export default defineConfig({
"src/**/*.test.ts", "src/**/*.test.ts",
"extensions/**/*.test.ts", "extensions/**/*.test.ts",
"test/**/*.test.ts", "test/**/*.test.ts",
"ui/src/ui/app-chat.test.ts",
"ui/src/ui/views/agents-utils.test.ts", "ui/src/ui/views/agents-utils.test.ts",
"ui/src/ui/views/chat.test.ts", "ui/src/ui/views/chat.test.ts",
"ui/src/ui/views/usage-render-details.test.ts", "ui/src/ui/views/usage-render-details.test.ts",