perf(ui): split chat role normalization imports

This commit is contained in:
Peter Steinberger
2026-04-25 22:43:05 +01:00
parent 265b97bbba
commit f62a054ef1
9 changed files with 85 additions and 115 deletions

View File

@@ -1,6 +1,7 @@
import type { ChatItem, MessageGroup, ToolCard } from "../types/chat-types.ts";
import { extractTextCached } from "./message-extract.ts";
import { normalizeMessage, normalizeRoleForGrouping } from "./message-normalizer.ts";
import { normalizeMessage } from "./message-normalizer.ts";
import { normalizeRoleForGrouping } from "./role-normalizer.ts";
import { messageMatchesSearchQuery } from "./search-match.ts";
import { extractToolCards, extractToolPreview } from "./tool-cards.ts";

View File

@@ -10,7 +10,7 @@ import {
isRenderableControlUiAvatarUrl,
resolveAssistantTextAvatar,
} from "../views/agents-utils.ts";
import { normalizeRoleForGrouping } from "./message-normalizer.ts";
import { normalizeRoleForGrouping } from "./role-normalizer.ts";
export function renderChatAvatar(
role: string,

View File

@@ -24,11 +24,8 @@ import {
extractThinkingCached,
formatReasoningMarkdown,
} from "./message-extract.ts";
import {
isToolResultMessage,
normalizeMessage,
normalizeRoleForGrouping,
} from "./message-normalizer.ts";
import { isToolResultMessage, normalizeMessage } from "./message-normalizer.ts";
import { normalizeRoleForGrouping } from "./role-normalizer.ts";
import { isTtsSupported, speakText, stopTts, isTtsSpeaking } from "./speech.ts";
import {
extractToolCards,

View File

@@ -1,9 +1,5 @@
import { describe, it, expect, vi, beforeEach, afterEach } from "vitest";
import {
normalizeMessage,
normalizeRoleForGrouping,
isToolResultMessage,
} from "./message-normalizer.ts";
import { normalizeMessage } from "./message-normalizer.ts";
describe("message-normalizer", () => {
describe("normalizeMessage", () => {
@@ -390,69 +386,4 @@ describe("message-normalizer", () => {
expect(result.senderLabel).toBe("Iris");
});
});
describe("normalizeRoleForGrouping", () => {
it("returns tool for toolresult", () => {
expect(normalizeRoleForGrouping("toolresult")).toBe("tool");
expect(normalizeRoleForGrouping("toolResult")).toBe("tool");
expect(normalizeRoleForGrouping("TOOLRESULT")).toBe("tool");
});
it("returns tool for tool_result", () => {
expect(normalizeRoleForGrouping("tool_result")).toBe("tool");
expect(normalizeRoleForGrouping("TOOL_RESULT")).toBe("tool");
});
it("returns tool for tool", () => {
expect(normalizeRoleForGrouping("tool")).toBe("tool");
expect(normalizeRoleForGrouping("Tool")).toBe("tool");
});
it("returns tool for function", () => {
expect(normalizeRoleForGrouping("function")).toBe("tool");
expect(normalizeRoleForGrouping("Function")).toBe("tool");
});
it("preserves user role", () => {
expect(normalizeRoleForGrouping("user")).toBe("user");
expect(normalizeRoleForGrouping("User")).toBe("User");
});
it("preserves assistant role", () => {
expect(normalizeRoleForGrouping("assistant")).toBe("assistant");
});
it("preserves system role", () => {
expect(normalizeRoleForGrouping("system")).toBe("system");
});
});
describe("isToolResultMessage", () => {
it("returns true for toolresult role", () => {
expect(isToolResultMessage({ role: "toolresult" })).toBe(true);
expect(isToolResultMessage({ role: "toolResult" })).toBe(true);
expect(isToolResultMessage({ role: "TOOLRESULT" })).toBe(true);
});
it("returns true for tool_result role", () => {
expect(isToolResultMessage({ role: "tool_result" })).toBe(true);
expect(isToolResultMessage({ role: "TOOL_RESULT" })).toBe(true);
});
it("returns false for other roles", () => {
expect(isToolResultMessage({ role: "user" })).toBe(false);
expect(isToolResultMessage({ role: "assistant" })).toBe(false);
expect(isToolResultMessage({ role: "tool" })).toBe(false);
});
it("returns false for missing role", () => {
expect(isToolResultMessage({})).toBe(false);
expect(isToolResultMessage({ content: "test" })).toBe(false);
});
it("returns false for non-string role", () => {
expect(isToolResultMessage({ role: 123 })).toBe(false);
expect(isToolResultMessage({ role: null })).toBe(false);
});
});
});

View File

@@ -13,6 +13,7 @@ import { mediaKindFromMime } from "../../../../src/media/constants.js";
import { splitMediaFromOutput } from "../../../../src/media/parse.js";
import { parseInlineDirectives } from "../../../../src/utils/directive-tags.js";
import type { NormalizedMessage, MessageContentItem } from "../types/chat-types.ts";
export { isToolResultMessage, normalizeRoleForGrouping } from "./role-normalizer.ts";
function coerceCanvasPreview(
value: unknown,
@@ -389,39 +390,3 @@ export function normalizeMessage(message: unknown): NormalizedMessage {
...(replyTarget ? { replyTarget } : {}),
};
}
/**
* Normalize role for grouping purposes.
*/
export function normalizeRoleForGrouping(role: string): string {
const lower = role.toLowerCase();
// Preserve original casing when it's already a core role.
if (role === "user" || role === "User") {
return role;
}
if (role === "assistant") {
return "assistant";
}
if (role === "system") {
return "system";
}
// Keep tool-related roles distinct so the UI can style/toggle them.
if (
lower === "toolresult" ||
lower === "tool_result" ||
lower === "tool" ||
lower === "function"
) {
return "tool";
}
return role;
}
/**
* Check if a message is a tool result message based on its role.
*/
export function isToolResultMessage(message: unknown): boolean {
const m = message as Record<string, unknown>;
const role = typeof m.role === "string" ? m.role.toLowerCase() : "";
return role === "toolresult" || role === "tool_result";
}

View File

@@ -0,0 +1,41 @@
import { describe, expect, it } from "vitest";
import { isToolResultMessage, normalizeRoleForGrouping } from "./role-normalizer.ts";
describe("normalizeRoleForGrouping", () => {
it("returns tool for tool result role variants", () => {
expect(normalizeRoleForGrouping("toolresult")).toBe("tool");
expect(normalizeRoleForGrouping("toolResult")).toBe("tool");
expect(normalizeRoleForGrouping("TOOLRESULT")).toBe("tool");
expect(normalizeRoleForGrouping("tool_result")).toBe("tool");
expect(normalizeRoleForGrouping("TOOL_RESULT")).toBe("tool");
});
it("returns tool for tool and function roles", () => {
expect(normalizeRoleForGrouping("tool")).toBe("tool");
expect(normalizeRoleForGrouping("Tool")).toBe("tool");
expect(normalizeRoleForGrouping("function")).toBe("tool");
expect(normalizeRoleForGrouping("Function")).toBe("tool");
});
it("preserves core roles", () => {
expect(normalizeRoleForGrouping("user")).toBe("user");
expect(normalizeRoleForGrouping("User")).toBe("User");
expect(normalizeRoleForGrouping("assistant")).toBe("assistant");
expect(normalizeRoleForGrouping("system")).toBe("system");
});
it("detects only tool result role variants", () => {
expect(isToolResultMessage({ role: "toolresult" })).toBe(true);
expect(isToolResultMessage({ role: "toolResult" })).toBe(true);
expect(isToolResultMessage({ role: "TOOLRESULT" })).toBe(true);
expect(isToolResultMessage({ role: "tool_result" })).toBe(true);
expect(isToolResultMessage({ role: "TOOL_RESULT" })).toBe(true);
expect(isToolResultMessage({ role: "user" })).toBe(false);
expect(isToolResultMessage({ role: "assistant" })).toBe(false);
expect(isToolResultMessage({ role: "tool" })).toBe(false);
expect(isToolResultMessage({})).toBe(false);
expect(isToolResultMessage({ content: "test" })).toBe(false);
expect(isToolResultMessage({ role: 123 })).toBe(false);
expect(isToolResultMessage({ role: null })).toBe(false);
});
});

View File

@@ -0,0 +1,35 @@
/**
* Normalize role for grouping purposes.
*/
export function normalizeRoleForGrouping(role: string): string {
const lower = role.toLowerCase();
// Preserve original casing when it's already a core role.
if (role === "user" || role === "User") {
return role;
}
if (role === "assistant") {
return "assistant";
}
if (role === "system") {
return "system";
}
// Keep tool-related roles distinct so the UI can style/toggle them.
if (
lower === "toolresult" ||
lower === "tool_result" ||
lower === "tool" ||
lower === "function"
) {
return "tool";
}
return role;
}
/**
* Check if a message is a tool result message based on its role.
*/
export function isToolResultMessage(message: unknown): boolean {
const m = message as Record<string, unknown>;
const role = typeof m.role === "string" ? m.role.toLowerCase() : "";
return role === "toolresult" || role === "tool_result";
}

View File

@@ -7,7 +7,7 @@ import type { SidebarContent } from "../sidebar-content.ts";
import { formatToolDetail, resolveToolDisplay } from "../tool-display.ts";
import type { ToolCard } from "../types/chat-types.ts";
import { extractTextCached } from "./message-extract.ts";
import { isToolResultMessage } from "./message-normalizer.ts";
import { isToolResultMessage } from "./role-normalizer.ts";
import { formatToolOutputForSidebar, getTruncatedPreview } from "./tool-helpers.ts";
export type ToolPreview = NonNullable<ToolCard["preview"]>;

View File

@@ -1,5 +1,5 @@
import type { ChatItem, MessageGroup } from "../types/chat-types.ts";
import { isToolResultMessage, normalizeRoleForGrouping } from "./message-normalizer.ts";
import { isToolResultMessage, normalizeRoleForGrouping } from "./role-normalizer.ts";
import { getOrCreateSessionCacheValue } from "./session-cache.ts";
import { extractToolCards } from "./tool-cards.ts";