diff --git a/apps/web/src/composer-logic.test.ts b/apps/web/src/composer-logic.test.ts index b8ef7443611..71fdfdd3791 100644 --- a/apps/web/src/composer-logic.test.ts +++ b/apps/web/src/composer-logic.test.ts @@ -94,6 +94,40 @@ describe("detectComposerTrigger", () => { }); }); + it("detects slash command trigger in the middle of existing text", () => { + const text = "Fix the tests /rev"; + const trigger = detectComposerTrigger(text, text.length); + + expect(trigger).toEqual({ + kind: "slash-command", + query: "rev", + rangeStart: "Fix the tests ".length, + rangeEnd: text.length, + }); + }); + + it("detects a bare slash mid-text as an empty slash command query", () => { + const text = "Fix the tests /"; + const trigger = detectComposerTrigger(text, text.length); + + expect(trigger).toEqual({ + kind: "slash-command", + query: "", + rangeStart: "Fix the tests ".length, + rangeEnd: text.length, + }); + }); + + it("ignores slashes inside a word", () => { + const text = "check src/components"; + expect(detectComposerTrigger(text, text.length)).toBeNull(); + }); + + it("ignores slashes inside URLs", () => { + const text = "see https://example.com/docs"; + expect(detectComposerTrigger(text, text.length)).toBeNull(); + }); + it("detects $skill trigger at cursor", () => { const text = "Use $gh-fi"; const trigger = detectComposerTrigger(text, text.length); diff --git a/apps/web/src/composer-logic.ts b/apps/web/src/composer-logic.ts index 2d1d3aed3b1..a281a2c6ad1 100644 --- a/apps/web/src/composer-logic.ts +++ b/apps/web/src/composer-logic.ts @@ -242,6 +242,14 @@ export function detectComposerTrigger(text: string, cursorInput: number): Compos const tokenStart = tokenStartForCursor(text, cursor); const token = text.slice(tokenStart, cursor); + if (token.startsWith("/")) { + return { + kind: "slash-command", + query: token.slice(1), + rangeStart: tokenStart, + rangeEnd: cursor, + }; + } if (token.startsWith("$")) { return { kind: "skill", diff --git a/packages/shared/src/composerTrigger.test.ts b/packages/shared/src/composerTrigger.test.ts index 50c8cd7c208..9f3f9f04010 100644 --- a/packages/shared/src/composerTrigger.test.ts +++ b/packages/shared/src/composerTrigger.test.ts @@ -1,6 +1,10 @@ import { describe, expect, it } from "vite-plus/test"; -import { serializeComposerFileLink, serializeComposerMentionPath } from "./composerTrigger.ts"; +import { + detectComposerTrigger, + serializeComposerFileLink, + serializeComposerMentionPath, +} from "./composerTrigger.ts"; describe("serializeComposerMentionPath", () => { it("keeps simple mention paths unquoted", () => { @@ -16,6 +20,48 @@ describe("serializeComposerMentionPath", () => { }); }); +describe("detectComposerTrigger", () => { + it("detects a slash command at the start of the message", () => { + const text = "/rev"; + expect(detectComposerTrigger(text, text.length)).toEqual({ + kind: "slash-command", + query: "rev", + rangeStart: 0, + rangeEnd: text.length, + }); + }); + + it("detects a slash command trigger in the middle of existing text", () => { + const text = "Fix the tests /rev"; + expect(detectComposerTrigger(text, text.length)).toEqual({ + kind: "slash-command", + query: "rev", + rangeStart: "Fix the tests ".length, + rangeEnd: text.length, + }); + }); + + it("detects /model mid-text as the model trigger", () => { + const text = "switch it up /model"; + expect(detectComposerTrigger(text, text.length)).toEqual({ + kind: "slash-model", + query: "", + rangeStart: "switch it up ".length, + rangeEnd: text.length, + }); + }); + + it("ignores slashes inside a word", () => { + const text = "check src/components"; + expect(detectComposerTrigger(text, text.length)).toBeNull(); + }); + + it("ignores slashes inside URLs", () => { + const text = "see https://example.com/docs"; + expect(detectComposerTrigger(text, text.length)).toBeNull(); + }); +}); + describe("serializeComposerFileLink", () => { it("uses the basename as the markdown label", () => { expect(serializeComposerFileLink("path/to/package.json")).toBe( diff --git a/packages/shared/src/composerTrigger.ts b/packages/shared/src/composerTrigger.ts index dcbdc784934..a8e3d885a34 100644 --- a/packages/shared/src/composerTrigger.ts +++ b/packages/shared/src/composerTrigger.ts @@ -104,6 +104,23 @@ export function detectComposerTrigger( const tokenStart = tokenIdx + 1; const token = text.slice(tokenStart, cursor); + if (token.startsWith("/")) { + const commandQuery = token.slice(1); + if (commandQuery.toLowerCase() === "model") { + return { + kind: "slash-model", + query: "", + rangeStart: tokenStart, + rangeEnd: cursor, + }; + } + return { + kind: "slash-command", + query: commandQuery, + rangeStart: tokenStart, + rangeEnd: cursor, + }; + } if (token.startsWith("$")) { return { kind: "skill",