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 {
agentLogoUrl,
resolveConfiguredCronModelSuggestions,
resolveAgentAvatarUrl,
resolveEffectiveModelFallbacks,
sortLocaleStrings,
} from "./agents-utils.ts";
@@ -110,3 +111,18 @@ describe("agentLogoUrl", () => {
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 } },
agentIdentity?: AgentIdentityResult | null,
): string | null {
const url =
agentIdentity?.avatar?.trim() ??
agent.identity?.avatarUrl?.trim() ??
agent.identity?.avatar?.trim();
if (!url) {
return null;
const candidates = [
agentIdentity?.avatar?.trim(),
agent.identity?.avatarUrl?.trim(),
agent.identity?.avatar?.trim(),
];
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;
}

View File

@@ -96,6 +96,55 @@ describe("chat view", () => {
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", () => {
const container = document.createElement("div");
render(

View File

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