From 7509c4a0576488eae65330bfd8d23502f7584b11 Mon Sep 17 00:00:00 2001 From: Vincent Koc Date: Thu, 12 Mar 2026 23:39:27 -0400 Subject: [PATCH] UI: fix mounted avatar meta fallback --- ui/src/ui/app-chat.test.ts | 65 ++++++++++++++++++++++++++++++++++++++ ui/src/ui/app-chat.ts | 2 +- 2 files changed, 66 insertions(+), 1 deletion(-) create mode 100644 ui/src/ui/app-chat.test.ts diff --git a/ui/src/ui/app-chat.test.ts b/ui/src/ui/app-chat.test.ts new file mode 100644 index 000000000..1fcdf14db --- /dev/null +++ b/ui/src/ui/app-chat.test.ts @@ -0,0 +1,65 @@ +/* @vitest-environment jsdom */ + +import { afterEach, describe, expect, it, vi } from "vitest"; +import { refreshChatAvatar, type ChatHost } from "./app-chat.ts"; + +function makeHost(overrides?: Partial): ChatHost { + return { + client: null, + chatMessages: [], + chatStream: null, + connected: true, + chatMessage: "", + chatAttachments: [], + chatQueue: [], + chatRunId: null, + chatSending: false, + lastError: null, + sessionKey: "agent:main", + basePath: "", + hello: null, + chatAvatarUrl: null, + refreshSessionsAfterChat: new Set(), + ...overrides, + }; +} + +describe("refreshChatAvatar", () => { + afterEach(() => { + vi.unstubAllGlobals(); + }); + + it("uses a route-relative avatar endpoint before basePath bootstrap finishes", async () => { + const fetchMock = vi.fn().mockResolvedValue({ + ok: true, + json: async () => ({ avatarUrl: "/avatar/main" }), + }); + vi.stubGlobal("fetch", fetchMock as unknown as typeof fetch); + + const host = makeHost({ basePath: "", sessionKey: "agent:main" }); + await refreshChatAvatar(host); + + expect(fetchMock).toHaveBeenCalledWith( + "avatar/main?meta=1", + expect.objectContaining({ method: "GET" }), + ); + expect(host.chatAvatarUrl).toBe("/avatar/main"); + }); + + it("keeps mounted dashboard avatar endpoints under the normalized base path", async () => { + const fetchMock = vi.fn().mockResolvedValue({ + ok: false, + json: async () => ({}), + }); + vi.stubGlobal("fetch", fetchMock as unknown as typeof fetch); + + const host = makeHost({ basePath: "/openclaw/", sessionKey: "agent:ops:main" }); + await refreshChatAvatar(host); + + expect(fetchMock).toHaveBeenCalledWith( + "/openclaw/avatar/ops?meta=1", + expect.objectContaining({ method: "GET" }), + ); + expect(host.chatAvatarUrl).toBeNull(); + }); +}); diff --git a/ui/src/ui/app-chat.ts b/ui/src/ui/app-chat.ts index 791bdd639..05f6aa8c9 100644 --- a/ui/src/ui/app-chat.ts +++ b/ui/src/ui/app-chat.ts @@ -372,7 +372,7 @@ function resolveAgentIdForSession(host: ChatHost): string | null { function buildAvatarMetaUrl(basePath: string, agentId: string): string { const base = normalizeBasePath(basePath); const encoded = encodeURIComponent(agentId); - return base ? `${base}/avatar/${encoded}?meta=1` : `/avatar/${encoded}?meta=1`; + return base ? `${base}/avatar/${encoded}?meta=1` : `avatar/${encoded}?meta=1`; } export async function refreshChatAvatar(host: ChatHost) {