feat(FN-3809): normalize mention filtering for underscore handles
Added a `mentionMatching.ts` utility to normalize agent mention filtering across the codebase, fixing the handling of handles containing underscores. Updated `AgentMentionPopup`, `ChatView`, and `QuickChatFAB` to use the new utility, with corresponding tests added including a new `mentionMatching.te Fusion-Task-Id: FN-3809
This commit is contained in:
@@ -2,6 +2,7 @@ import { useMemo } from "react";
|
||||
import { AgentAvatar } from "./AgentAvatar";
|
||||
import "./AgentMentionPopup.css";
|
||||
import type { Agent } from "@fusion/core";
|
||||
import { matchesAgentMentionFilter } from "./mentionMatching";
|
||||
|
||||
interface AgentMentionPopupProps {
|
||||
/** List of agents to show */
|
||||
@@ -26,14 +27,7 @@ export function AgentMentionPopup({
|
||||
onSelect,
|
||||
position = "below",
|
||||
}: AgentMentionPopupProps) {
|
||||
const filteredAgents = useMemo(() => {
|
||||
const normalizedFilter = filter.trim().toLowerCase();
|
||||
if (!normalizedFilter) {
|
||||
return agents;
|
||||
}
|
||||
|
||||
return agents.filter((agent) => agent.name.toLowerCase().includes(normalizedFilter));
|
||||
}, [agents, filter]);
|
||||
const filteredAgents = useMemo(() => agents.filter((agent) => matchesAgentMentionFilter(agent.name, filter)), [agents, filter]);
|
||||
|
||||
if (!visible) {
|
||||
return null;
|
||||
|
||||
@@ -37,6 +37,7 @@ import { CreateRoomModal, type RoomDraft } from "./CreateRoomModal";
|
||||
import { useFileMention } from "../hooks/useFileMention";
|
||||
import { useMobileKeyboard } from "../hooks/useMobileKeyboard";
|
||||
import { useMobileScrollLock } from "../hooks/useMobileScrollLock";
|
||||
import { matchesAgentMentionFilter } from "./mentionMatching";
|
||||
|
||||
export interface ChatViewProps {
|
||||
projectId?: string;
|
||||
@@ -860,13 +861,10 @@ export function ChatView({ projectId, addToast }: ChatViewProps) {
|
||||
|
||||
const mentionAgents = useMemo(() => Array.from(agentsMap.values()), [agentsMap]);
|
||||
|
||||
const filteredMentionAgents = useMemo(() => {
|
||||
const normalizedFilter = mentionFilter.trim().toLowerCase();
|
||||
if (!normalizedFilter) {
|
||||
return mentionAgents;
|
||||
}
|
||||
return mentionAgents.filter((agent) => agent.name.toLowerCase().includes(normalizedFilter));
|
||||
}, [mentionAgents, mentionFilter]);
|
||||
const filteredMentionAgents = useMemo(
|
||||
() => mentionAgents.filter((agent) => matchesAgentMentionFilter(agent.name, mentionFilter)),
|
||||
[mentionAgents, mentionFilter],
|
||||
);
|
||||
|
||||
const mentionAgentsByName = useMemo(() => {
|
||||
const byName = new Map<string, Agent>();
|
||||
|
||||
@@ -19,6 +19,7 @@ import type { DiscoveredSkill } from "@fusion/dashboard";
|
||||
import { CustomModelDropdown } from "./CustomModelDropdown";
|
||||
import { ProviderIcon } from "./ProviderIcon";
|
||||
import { AgentMentionPopup } from "./AgentMentionPopup";
|
||||
import { matchesAgentMentionFilter } from "./mentionMatching";
|
||||
import { FN_AGENT_ID, useQuickChat, type ChatMessageInfo, type ToolCallInfo } from "../hooks/useQuickChat";
|
||||
import { useAgents } from "../hooks/useAgents";
|
||||
import { FileMentionPopup } from "./FileMentionPopup";
|
||||
@@ -1344,14 +1345,10 @@ export function QuickChatFAB({
|
||||
return matchingSkills.slice(0, 10);
|
||||
}, [discoveredSkills, skillFilter]);
|
||||
|
||||
const filteredMentionAgents = useMemo(() => {
|
||||
const normalizedFilter = mentionFilter.trim().toLowerCase();
|
||||
if (!normalizedFilter) {
|
||||
return agents;
|
||||
}
|
||||
|
||||
return agents.filter((agent) => agent.name.toLowerCase().includes(normalizedFilter));
|
||||
}, [agents, mentionFilter]);
|
||||
const filteredMentionAgents = useMemo(
|
||||
() => agents.filter((agent) => matchesAgentMentionFilter(agent.name, mentionFilter)),
|
||||
[agents, mentionFilter],
|
||||
);
|
||||
|
||||
const mentionAgentsByName = useMemo(() => {
|
||||
const byName = new Map<string, Agent>();
|
||||
|
||||
@@ -51,11 +51,14 @@ describe("AgentMentionPopup", () => {
|
||||
expect(screen.getByTestId("agent-mention-item-agent-002")).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("filters agents by name case-insensitively", () => {
|
||||
it.each([
|
||||
["review", "filters agents by name case-insensitively"],
|
||||
["beta_re", "matches underscore handles for agents with spaces"],
|
||||
])("%s %s", (filter) => {
|
||||
render(
|
||||
<AgentMentionPopup
|
||||
agents={agents}
|
||||
filter="review"
|
||||
filter={filter}
|
||||
highlightedIndex={0}
|
||||
visible={true}
|
||||
onSelect={vi.fn()}
|
||||
|
||||
@@ -0,0 +1,20 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
import { matchesAgentMentionFilter, normalizeMentionToken } from "../mentionMatching";
|
||||
|
||||
describe("mentionMatching", () => {
|
||||
it("normalizes spaces and hyphens into underscores", () => {
|
||||
expect(normalizeMentionToken(" John Doe-Agent ")).toBe("john_doe_agent");
|
||||
});
|
||||
|
||||
it.each([
|
||||
["John Doe", "john_d"],
|
||||
["John-Doe", "john_d"],
|
||||
["John Doe", "john doe"],
|
||||
])("matches %s with filter %s", (agentName, filter) => {
|
||||
expect(matchesAgentMentionFilter(agentName, filter)).toBe(true);
|
||||
});
|
||||
|
||||
it("returns false when filter does not match", () => {
|
||||
expect(matchesAgentMentionFilter("John Doe", "alpha")).toBe(false);
|
||||
});
|
||||
});
|
||||
18
packages/dashboard/app/components/mentionMatching.ts
Normal file
18
packages/dashboard/app/components/mentionMatching.ts
Normal file
@@ -0,0 +1,18 @@
|
||||
export function normalizeMentionToken(value: string): string {
|
||||
return value
|
||||
.trim()
|
||||
.toLowerCase()
|
||||
.replace(/[\s-]+/g, "_")
|
||||
.replace(/_+/g, "_");
|
||||
}
|
||||
|
||||
export function matchesAgentMentionFilter(agentName: string, filter: string): boolean {
|
||||
const trimmedFilter = filter.trim();
|
||||
if (!trimmedFilter) {
|
||||
return true;
|
||||
}
|
||||
|
||||
const normalizedFilter = normalizeMentionToken(trimmedFilter);
|
||||
const normalizedAgentName = normalizeMentionToken(agentName);
|
||||
return normalizedAgentName.includes(normalizedFilter) || agentName.toLowerCase().includes(trimmedFilter.toLowerCase());
|
||||
}
|
||||
Reference in New Issue
Block a user