Skip to content

Commit 679f062

Browse files
authored
Merge pull request #302 from zjowowen/fix/slash-enter-send
fix: allow slash-prefixed first messages to send by Enter
2 parents 75dbd55 + d93a4d0 commit 679f062

4 files changed

Lines changed: 96 additions & 8 deletions

File tree

src/components/agent/agent-panel.tsx

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,10 @@ import {
6060
stripFilePartsForStorage,
6161
} from "@/lib/ai/message-attachments";
6262
import { ImageAttachmentGrid } from "@/components/ui/image-attachment-grid";
63+
import {
64+
getMatchingSkillsForSlashQuery,
65+
shouldAutocompleteCaptureEnter,
66+
} from "./slash-command";
6367

6468
type AgentMode = "long-agent" | "agent" | "plan" | "ask";
6569
type ModelSelection = { provider: string; model: string };
@@ -1263,6 +1267,14 @@ export function AgentPanel({
12631267
};
12641268

12651269
const slashQuery = input.startsWith("/") ? input.slice(1) : "";
1270+
const matchingSlashSkills = useMemo(
1271+
() => getMatchingSkillsForSlashQuery(availableSkills, slashQuery),
1272+
[availableSkills, slashQuery]
1273+
);
1274+
const autocompleteCapturesEnter = shouldAutocompleteCaptureEnter(
1275+
showAutocomplete,
1276+
matchingSlashSkills
1277+
);
12661278

12671279
return (
12681280
<div ref={containerRef} className="relative flex h-full min-w-0 flex-col bg-agent-bg text-agent-foreground font-mono text-sm overflow-hidden">
@@ -1373,7 +1385,7 @@ export function AgentPanel({
13731385
{/* Input area with autocomplete */}
13741386
<div className="relative z-10 flex shrink-0 flex-col overflow-hidden bg-agent-bg/80 backdrop-blur-sm" style={{ height: inputHeight }}>
13751387
{/* Slash command autocomplete */}
1376-
{showAutocomplete && availableSkills.length > 0 && (
1388+
{autocompleteCapturesEnter && (
13771389
<SkillAutocomplete
13781390
query={slashQuery}
13791391
skills={availableSkills}
@@ -1513,7 +1525,7 @@ export function AgentPanel({
15131525
onPaste={handleInputPaste}
15141526
onKeyDown={(e) => {
15151527
// Enter without Shift sends message, Shift+Enter creates new line
1516-
if (e.key === "Enter" && !e.shiftKey && !e.nativeEvent.isComposing && !showAutocomplete) {
1528+
if (e.key === "Enter" && !e.shiftKey && !e.nativeEvent.isComposing && !autocompleteCapturesEnter) {
15171529
e.preventDefault();
15181530
handleSend();
15191531
}
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
import { describe, expect, it } from "vitest";
2+
import type { Skill } from "@/types";
3+
import {
4+
getMatchingSkillsForSlashQuery,
5+
shouldAutocompleteCaptureEnter,
6+
} from "./slash-command";
7+
8+
function createSkill(overrides: Partial<Skill>): Skill {
9+
return {
10+
id: "skill-1",
11+
workspaceId: null,
12+
name: "Summarize Notes",
13+
slug: "summarize",
14+
description: null,
15+
systemPrompt: "",
16+
steps: null,
17+
allowedTools: null,
18+
parameters: null,
19+
isEnabled: true,
20+
createdAt: "",
21+
updatedAt: "",
22+
...overrides,
23+
};
24+
}
25+
26+
describe("slash command helpers", () => {
27+
it("does not capture Enter when slash autocomplete has no matching skills", () => {
28+
const matches = getMatchingSkillsForSlashQuery(
29+
[createSkill({ slug: "summarize" })],
30+
"plain-message"
31+
);
32+
33+
expect(matches).toEqual([]);
34+
expect(shouldAutocompleteCaptureEnter(true, matches)).toBe(false);
35+
});
36+
37+
it("captures Enter when slash autocomplete has matching enabled skills", () => {
38+
const matches = getMatchingSkillsForSlashQuery(
39+
[createSkill({ slug: "summarize" })],
40+
"sum"
41+
);
42+
43+
expect(matches).toHaveLength(1);
44+
expect(shouldAutocompleteCaptureEnter(true, matches)).toBe(true);
45+
});
46+
47+
it("ignores disabled skills when building slash matches", () => {
48+
const matches = getMatchingSkillsForSlashQuery(
49+
[createSkill({ slug: "summarize", isEnabled: false })],
50+
"sum"
51+
);
52+
53+
expect(matches).toEqual([]);
54+
});
55+
});
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
import type { Skill } from "@/types";
2+
3+
export function getMatchingSkillsForSlashQuery(
4+
skills: Skill[],
5+
query: string
6+
): Skill[] {
7+
const normalizedQuery = query.trim().toLowerCase();
8+
if (!normalizedQuery) {
9+
return skills.filter((skill) => skill.isEnabled);
10+
}
11+
12+
return skills.filter(
13+
(skill) =>
14+
skill.isEnabled &&
15+
(skill.slug.includes(normalizedQuery) ||
16+
skill.name.toLowerCase().includes(normalizedQuery))
17+
);
18+
}
19+
20+
export function shouldAutocompleteCaptureEnter(
21+
showAutocomplete: boolean,
22+
matchingSkills: Skill[]
23+
): boolean {
24+
return showAutocomplete && matchingSkills.length > 0;
25+
}

src/components/skills/skill-autocomplete.tsx

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import React, { useEffect, useState, useRef } from "react";
44
import { useTranslations } from "next-intl";
55
import { Zap } from "lucide-react";
66
import type { Skill } from "@/types";
7+
import { getMatchingSkillsForSlashQuery } from "@/components/agent/slash-command";
78

89
interface SkillAutocompleteProps {
910
query: string;
@@ -23,12 +24,7 @@ export function SkillAutocomplete({
2324
const listRef = useRef<HTMLDivElement>(null);
2425
const [trackedQuery, setTrackedQuery] = useState(query);
2526

26-
const filtered = skills.filter(
27-
(s) =>
28-
s.isEnabled &&
29-
(s.slug.includes(query.toLowerCase()) ||
30-
s.name.toLowerCase().includes(query.toLowerCase()))
31-
);
27+
const filtered = getMatchingSkillsForSlashQuery(skills, query);
3228

3329
if (trackedQuery !== query) {
3430
setTrackedQuery(query);

0 commit comments

Comments
 (0)