



















@@ -243,61 +243,32 @@ describe("grouped chat rendering", () => {
243243expect(assistantConfirm?.classList.contains("chat-delete-confirm--right")).toBe(true);
244244});
245245246-it("falls back to the local logo when the assistant avatar is a remote URL", () => {
247-const container = document.createElement("div");
248-249-renderAssistantMessage(
250-container,
251-{
252-role: "assistant",
253-content: "hello",
254-timestamp: 1000,
255-},
256-{ assistantAvatar: "https://example.com/avatar.png" },
257-);
258-259-const avatar = container.querySelector<HTMLImageElement>(".chat-avatar.assistant");
260-expect(avatar).not.toBeNull();
261-expect(avatar?.getAttribute("src")).toBe("/openclaw-logo.svg");
262-});
263-264-it("renders a blob: assistant avatar as an image", () => {
265-const container = document.createElement("div");
266-267-renderAssistantMessage(
268-container,
269-{
270-role: "assistant",
271-content: "hello",
272-timestamp: 1000,
273-},
274-{ assistantAvatar: "blob:managed-image", assistantName: "Val" },
275-);
276-277-const avatar = container.querySelector<HTMLImageElement>(".chat-avatar.assistant");
278-expect(avatar).not.toBeNull();
279-expect(avatar?.tagName).toBe("IMG");
280-expect(avatar?.getAttribute("src")).toBe("blob:managed-image");
281-});
246+it("renders assistant avatar variants", () => {
247+const renderAvatar = (assistantAvatar: string) => {
248+const container = document.createElement("div");
249+renderAssistantMessage(
250+container,
251+{
252+role: "assistant",
253+content: "hello",
254+timestamp: 1000,
255+},
256+{ assistantAvatar, assistantName: "Val" },
257+);
258+return container.querySelector<HTMLElement>(".chat-avatar.assistant");
259+};
282260283-it("renders a configured assistant text avatar", () => {
284-const container = document.createElement("div");
261+ const remoteAvatar = renderAvatar("https://example.com/avatar.png");
262+expect(remoteAvatar?.getAttribute("src")).toBe("/openclaw-logo.svg");
285263286-renderAssistantMessage(
287-container,
288-{
289-role: "assistant",
290-content: "hello",
291-timestamp: 1000,
292-},
293-{ assistantAvatar: "VC", assistantName: "Val" },
294-);
264+const blobAvatar = renderAvatar("blob:managed-image");
265+expect(blobAvatar?.tagName).toBe("IMG");
266+expect(blobAvatar?.getAttribute("src")).toBe("blob:managed-image");
295267296-const avatar = container.querySelector<HTMLElement>(".chat-avatar.assistant");
297-expect(avatar).not.toBeNull();
298-expect(avatar?.tagName).toBe("DIV");
299-expect(avatar?.textContent).toContain("VC");
300-expect(avatar?.getAttribute("aria-label")).toBe("Val");
268+const textAvatar = renderAvatar("VC");
269+expect(textAvatar?.tagName).toBe("DIV");
270+expect(textAvatar?.textContent).toContain("VC");
271+expect(textAvatar?.getAttribute("aria-label")).toBe("Val");
301272});
302273303274it("rejects unsafe invisible controls in assistant text avatars", () => {
@@ -307,129 +278,80 @@ describe("grouped chat rendering", () => {
307278expect(resolveAssistantTextAvatar("V\u200bC")).toBeNull();
308279});
309280310-it("includes cache tokens when rendering assistant context usage", () => {
311-const container = document.createElement("div");
312-313-renderAssistantMessage(
314-container,
315-{
316-role: "assistant",
317-content: "Done",
318-usage: {
319-input: 1,
320-output: 1200,
321-cacheRead: 438_400,
322-cacheWrite: 307,
323-},
324-model: "anthropic/claude-opus-4-7",
325-timestamp: 1000,
326-},
327-{ contextWindow: 1_000_000 },
328-);
329-330-expect(container.querySelector(".msg-meta__ctx")?.textContent).toBe("44% ctx");
331-expect(container.textContent).toContain("R438.4k");
332-expect(container.textContent).toContain("W307");
333-});
334-335-it("excludes output tokens when rendering assistant context usage", () => {
336-const container = document.createElement("div");
337-338-renderAssistantMessage(
339-container,
340-{
341-role: "assistant",
342-content: "Long response",
343-usage: {
344-input: 1_000,
345-output: 9_000,
346-cacheRead: 0,
347-cacheWrite: 0,
281+it("renders assistant context usage from input and cache tokens", () => {
282+const renderUsage = (usage: Record<string, number>, contextWindow: number) => {
283+const container = document.createElement("div");
284+renderAssistantMessage(
285+container,
286+{
287+role: "assistant",
288+content: "Done",
289+ usage,
290+model: "anthropic/claude-opus-4-7",
291+timestamp: 1000,
348292},
349-timestamp: 1000,
350-},
351-{ contextWindow: 10_000 },
352-);
353-354-expect(container.querySelector(".msg-meta__ctx")?.textContent).toBe("10% ctx");
355-});
356-357-it("renders the configured local user name in user message footers", () => {
358-const container = document.createElement("div");
293+{ contextWindow },
294+);
295+return container;
296+};
359297360-renderGroupedMessage(
361-container,
298+const cached = renderUsage(
362299{
363-role: "user",
364-content: "hello",
365-timestamp: 1000,
300+input: 1,
301+output: 1200,
302+cacheRead: 438_400,
303+cacheWrite: 307,
366304},
367-"user",
368-{ userName: "Buns" },
305+1_000_000,
369306);
307+expect(cached.querySelector(".msg-meta__ctx")?.textContent).toBe("44% ctx");
308+expect(cached.textContent).toContain("R438.4k");
309+expect(cached.textContent).toContain("W307");
370310371-const sender = container.querySelector<HTMLElement>(".chat-group.user .chat-sender-name");
372-expect(sender?.textContent).toBe("Buns");
373-});
374-375-it("renders a local user image avatar when provided", () => {
376-const container = document.createElement("div");
377-378-renderGroupedMessage(
379-container,
311+const outputHeavy = renderUsage(
380312{
381-role: "user",
382-content: "hello",
383-timestamp: 1000,
313+input: 1_000,
314+output: 9_000,
315+cacheRead: 0,
316+cacheWrite: 0,
384317},
385-"user",
386-{ userName: "Buns", userAvatar: "data:image/png;base64,AAA" },
318+10_000,
387319);
388-389-const avatar = container.querySelector<HTMLImageElement>(".chat-avatar.user");
390-expect(avatar).not.toBeNull();
391-expect(avatar?.getAttribute("src")).toBe("data:image/png;base64,AAA");
392-expect(avatar?.getAttribute("alt")).toBe("Buns");
320+expect(outputHeavy.querySelector(".msg-meta__ctx")?.textContent).toBe("10% ctx");
393321});
394322395-it("renders a local user avatar route when provided", () => {
396-const container = document.createElement("div");
397-398-renderGroupedMessage(
399-container,
400-{
401-role: "user",
402-content: "hello",
403-timestamp: 1000,
404-},
405-"user",
406-{ userName: "Buns", userAvatar: "/avatar/user" },
407-);
323+it("renders configured local user names and avatar variants", () => {
324+const renderUser = (opts: Partial<RenderMessageGroupOptions>) => {
325+const container = document.createElement("div");
326+renderGroupedMessage(
327+container,
328+{
329+role: "user",
330+content: "hello",
331+timestamp: 1000,
332+},
333+"user",
334+opts,
335+);
336+return container;
337+};
408338409-const avatar = container.querySelector<HTMLImageElement>(".chat-avatar.user");
410-expect(avatar).not.toBeNull();
411-expect(avatar?.getAttribute("src")).toBe("/avatar/user");
412-expect(avatar?.getAttribute("alt")).toBe("Buns");
413-});
339+const named = renderUser({ userName: "Buns" });
340+const sender = named.querySelector<HTMLElement>(".chat-group.user .chat-sender-name");
341+expect(sender?.textContent).toBe("Buns");
414342415-it("renders a local user text avatar when provided", () => {
416-const container = document.createElement("div");
343+for (const src of ["data:image/png;base64,AAA", "/avatar/user"]) {
344+const container = renderUser({ userName: "Buns", userAvatar: src });
345+const avatar = container.querySelector<HTMLImageElement>(".chat-avatar.user");
346+expect(avatar?.getAttribute("src")).toBe(src);
347+expect(avatar?.getAttribute("alt")).toBe("Buns");
348+}
417349418-renderGroupedMessage(
419-container,
420-{
421-role: "user",
422-content: "hello",
423-timestamp: 1000,
424-},
425-"user",
426-{ userAvatar: "🦞" },
350+const textAvatar = renderUser({ userAvatar: "🦞" }).querySelector<HTMLElement>(
351+".chat-avatar.user",
427352);
428-429-const avatar = container.querySelector<HTMLElement>(".chat-avatar.user");
430-expect(avatar).not.toBeNull();
431-expect(avatar?.tagName).toBe("DIV");
432-expect(avatar?.textContent).toContain("🦞");
353+expect(textAvatar?.tagName).toBe("DIV");
354+expect(textAvatar?.textContent).toContain("🦞");
433355});
434356435357it("keeps inline tool cards collapsed by default and renders expanded state", () => {
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。