fix(webui): prioritize skill names in autocomplete
This commit is contained in:
@@ -286,6 +286,7 @@ interface SlashPaletteCommand {
|
|||||||
title: string;
|
title: string;
|
||||||
description: string;
|
description: string;
|
||||||
icon: string;
|
icon: string;
|
||||||
|
kind?: "skill";
|
||||||
argHint?: string;
|
argHint?: string;
|
||||||
detail: string;
|
detail: string;
|
||||||
badge?: string;
|
badge?: string;
|
||||||
@@ -1009,6 +1010,7 @@ export function ThreadComposer({
|
|||||||
description,
|
description,
|
||||||
detail: description,
|
detail: description,
|
||||||
icon: "brain",
|
icon: "brain",
|
||||||
|
kind: "skill" as const,
|
||||||
recent: recentSlashCommands.includes(command),
|
recent: recentSlashCommands.includes(command),
|
||||||
};
|
};
|
||||||
})
|
})
|
||||||
@@ -2582,6 +2584,7 @@ function SlashCommandPalette({
|
|||||||
{commands.map((command, index) => {
|
{commands.map((command, index) => {
|
||||||
const Icon = COMMAND_ICONS[command.icon] ?? CircleHelp;
|
const Icon = COMMAND_ICONS[command.icon] ?? CircleHelp;
|
||||||
const selected = index === selectedIndex;
|
const selected = index === selectedIndex;
|
||||||
|
const isSkill = command.kind === "skill";
|
||||||
const commandKey = slashCommandI18nKey(command.command);
|
const commandKey = slashCommandI18nKey(command.command);
|
||||||
const title = t(`thread.composer.slash.commands.${commandKey}.title`, {
|
const title = t(`thread.composer.slash.commands.${commandKey}.title`, {
|
||||||
defaultValue: command.title,
|
defaultValue: command.title,
|
||||||
@@ -2617,23 +2620,34 @@ function SlashCommandPalette({
|
|||||||
<Icon className="h-4 w-4" />
|
<Icon className="h-4 w-4" />
|
||||||
</span>
|
</span>
|
||||||
<span className="flex min-w-0 flex-1 flex-col gap-0.5 sm:flex-row sm:items-baseline sm:gap-2">
|
<span className="flex min-w-0 flex-1 flex-col gap-0.5 sm:flex-row sm:items-baseline sm:gap-2">
|
||||||
<span className="min-w-0 truncate text-[13.5px] font-semibold tracking-normal text-foreground">
|
<span
|
||||||
|
className={cn(
|
||||||
|
"text-[13.5px] font-semibold tracking-normal text-foreground",
|
||||||
|
isSkill
|
||||||
|
? "max-w-full shrink-0 break-all sm:max-w-[55%]"
|
||||||
|
: "min-w-0 truncate",
|
||||||
|
)}
|
||||||
|
>
|
||||||
{title}
|
{title}
|
||||||
</span>
|
</span>
|
||||||
<span className="min-w-0 truncate text-[13px] text-muted-foreground">
|
<span className="min-w-0 truncate text-[13px] text-muted-foreground">
|
||||||
{command.detail || description}
|
{command.detail || description}
|
||||||
</span>
|
</span>
|
||||||
</span>
|
</span>
|
||||||
|
{!isSkill || command.badge || command.recent ? (
|
||||||
<span className="ml-2 flex max-w-[42%] shrink-0 items-center gap-1.5 sm:max-w-none">
|
<span className="ml-2 flex max-w-[42%] shrink-0 items-center gap-1.5 sm:max-w-none">
|
||||||
{command.badge || command.recent ? (
|
{command.badge || command.recent ? (
|
||||||
<span className="hidden rounded-full bg-foreground/[0.055] px-2 py-1 text-[11px] font-medium text-muted-foreground sm:inline-flex">
|
<span className="hidden rounded-full bg-foreground/[0.055] px-2 py-1 text-[11px] font-medium text-muted-foreground sm:inline-flex">
|
||||||
{command.badge ?? t("thread.composer.slash.badges.recent")}
|
{command.badge ?? t("thread.composer.slash.badges.recent")}
|
||||||
</span>
|
</span>
|
||||||
) : null}
|
) : null}
|
||||||
|
{!isSkill ? (
|
||||||
<span className="font-mono text-[12px] text-muted-foreground/60">
|
<span className="font-mono text-[12px] text-muted-foreground/60">
|
||||||
{command.argHint ? `${command.command} ${command.argHint}` : command.command}
|
{command.argHint ? `${command.command} ${command.argHint}` : command.command}
|
||||||
</span>
|
</span>
|
||||||
|
) : null}
|
||||||
</span>
|
</span>
|
||||||
|
) : null}
|
||||||
</button>
|
</button>
|
||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
|
|||||||
@@ -1146,14 +1146,15 @@ describe("ThreadComposer", () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it("opens skills only from a $ reference anywhere", () => {
|
it("opens skills only from a $ reference and prioritizes the skill name", () => {
|
||||||
|
const skillName = "arxiv-intelligence-filter";
|
||||||
render(
|
render(
|
||||||
<ThreadComposer
|
<ThreadComposer
|
||||||
onSend={vi.fn()}
|
onSend={vi.fn()}
|
||||||
placeholder="Type your message..."
|
placeholder="Type your message..."
|
||||||
skills={[{
|
skills={[{
|
||||||
name: "github",
|
name: skillName,
|
||||||
description: "Work with pull requests and issues",
|
description: "Fetch and summarize the latest AI research papers every day",
|
||||||
source: "builtin",
|
source: "builtin",
|
||||||
available: true,
|
available: true,
|
||||||
}]}
|
}]}
|
||||||
@@ -1165,15 +1166,18 @@ describe("ThreadComposer", () => {
|
|||||||
fireEvent.change(input, { target: { value: "/git", selectionStart: 4 } });
|
fireEvent.change(input, { target: { value: "/git", selectionStart: 4 } });
|
||||||
expect(screen.queryByRole("listbox", { name: "Slash commands" })).not.toBeInTheDocument();
|
expect(screen.queryByRole("listbox", { name: "Slash commands" })).not.toBeInTheDocument();
|
||||||
|
|
||||||
fireEvent.change(input, { target: { value: "please use $git", selectionStart: 15 } });
|
fireEvent.change(input, { target: { value: "please use $arxiv", selectionStart: 17 } });
|
||||||
|
|
||||||
const palette = screen.getByRole("listbox", { name: "Slash commands" });
|
const palette = screen.getByRole("listbox", { name: "Slash commands" });
|
||||||
expect(within(palette).getByRole("option", { name: /github/i })).toHaveTextContent("$github");
|
const option = within(palette).getByRole("option", { name: new RegExp(skillName, "i") });
|
||||||
|
const name = within(option).getByText(skillName);
|
||||||
|
expect(name).not.toHaveClass("truncate");
|
||||||
|
expect(within(option).queryByText(`$${skillName}`)).not.toBeInTheDocument();
|
||||||
expect(within(palette).queryByText("/model")).not.toBeInTheDocument();
|
expect(within(palette).queryByText("/model")).not.toBeInTheDocument();
|
||||||
|
|
||||||
fireEvent.keyDown(input, { key: "Tab" });
|
fireEvent.keyDown(input, { key: "Tab" });
|
||||||
|
|
||||||
expect(input).toHaveValue("please use $github ");
|
expect(input).toHaveValue(`please use $${skillName} `);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("shows right-side source badges so users can distinguish CLI apps from MCP servers", () => {
|
it("shows right-side source badges so users can distinguish CLI apps from MCP servers", () => {
|
||||||
|
|||||||
Reference in New Issue
Block a user