| author | |
| committer | |
| log | bba79cc254b65969bde6b6bfc3032c3b5b9316ae |
| tree | b33c3fe8ea3fcbd32f56c31649199252de5c391c |
| parent | 2921050c698fb195e2f5590b7cfab8fdfe0ec729 |
| signature | Signed by PGP key B5690EEEBB952194 |
3 files changed, 56 insertions(+), 7 deletions(-)
apps/web/src/components/chat/ChatComposer.tsx+8-6| ... | @@ -146,7 +146,10 @@ import { ComposerPendingUserInputPanel } from "./ComposerPendingUserInputPanel"; | ... | @@ -146,7 +146,10 @@ import { ComposerPendingUserInputPanel } from "./ComposerPendingUserInputPanel"; |
| 146 | import { ComposerPlanFollowUpBanner } from "./ComposerPlanFollowUpBanner"; | 146 | import { ComposerPlanFollowUpBanner } from "./ComposerPlanFollowUpBanner"; |
| 147 | import { ComposerControl, ComposerControlIcon, ComposerSelectControl } from "./ComposerControl"; | 147 | import { ComposerControl, ComposerControlIcon, ComposerSelectControl } from "./ComposerControl"; |
| 148 | import { resolveComposerMenuActiveItemId } from "./composerMenuHighlight"; | 148 | import { resolveComposerMenuActiveItemId } from "./composerMenuHighlight"; |
| 149 | import { searchSlashCommandItems } from "./composerSlashCommandSearch"; | 149 | import { |
| 150 | searchSlashCommandItems, | ||
| 151 | slashCommandItemsForPromptPosition, | ||
| 152 | } from "./composerSlashCommandSearch"; | ||
| 150 | import { | 153 | import { |
| 151 | getComposerPromptInjectionState, | 154 | getComposerPromptInjectionState, |
| 152 | getComposerProviderState, | 155 | getComposerProviderState, |
| ... | @@ -1360,11 +1363,10 @@ export const ChatComposer = memo(function ChatComposer(props: ChatComposerProps) | ... | @@ -1360,11 +1363,10 @@ export const ChatComposer = memo(function ChatComposer(props: ChatComposerProps) |
| 1360 | skill.description ?? | 1363 | skill.description ?? |
| 1361 | (skill.scope ? `${skill.scope} skill` : ""), | 1364 | (skill.scope ? `${skill.scope} skill` : ""), |
| 1362 | })); | 1365 | })); |
| 1363 | const slashCommandItems = [ | 1366 | const slashCommandItems = slashCommandItemsForPromptPosition( |
| 1364 | ...builtInSlashCommandItems, | 1367 | [...builtInSlashCommandItems, ...providerSlashCommandItems, ...skillItems], |
| 1365 | ...providerSlashCommandItems, | 1368 | composerTrigger.rangeStart === 0, |
| 1366 | ...skillItems, | 1369 | ); |
| 1367 | ]; | ||
| 1368 | return searchSlashCommandItems(slashCommandItems, query); | 1370 | return searchSlashCommandItems(slashCommandItems, query); |
| 1369 | } | 1371 | } |
| 1370 | if (composerTrigger.kind === "skill") { | 1372 | if (composerTrigger.kind === "skill") { |
apps/web/src/components/chat/composerSlashCommandSearch.test.ts+38-1| ... | @@ -2,7 +2,10 @@ import { describe, expect, it } from "vite-plus/test"; | ... | @@ -2,7 +2,10 @@ import { describe, expect, it } from "vite-plus/test"; |
| 2 | import { ProviderDriverKind } from "@t3tools/contracts"; | 2 | import { ProviderDriverKind } from "@t3tools/contracts"; |
| 3 | 3 | ||
| 4 | import type { ComposerCommandItem } from "./ComposerCommandMenu"; | 4 | import type { ComposerCommandItem } from "./ComposerCommandMenu"; |
| 5 | import { searchSlashCommandItems } from "./composerSlashCommandSearch"; | 5 | import { |
| 6 | searchSlashCommandItems, | ||
| 7 | slashCommandItemsForPromptPosition, | ||
| 8 | } from "./composerSlashCommandSearch"; | ||
| 6 | 9 | ||
| 7 | describe("searchSlashCommandItems", () => { | 10 | describe("searchSlashCommandItems", () => { |
| 8 | const claudeDriver = ProviderDriverKind.make("claudeAgent"); | 11 | const claudeDriver = ProviderDriverKind.make("claudeAgent"); |
| ... | @@ -173,4 +176,38 @@ describe("searchSlashCommandItems", () => { | ... | @@ -173,4 +176,38 @@ describe("searchSlashCommandItems", () => { |
| 173 | "skill:claudeAgent:unslop", | 176 | "skill:claudeAgent:unslop", |
| 174 | ]); | 177 | ]); |
| 175 | }); | 178 | }); |
| 179 | |||
| 180 | it("hides skills from slash completion after the first message line", () => { | ||
| 181 | const items = [ | ||
| 182 | { | ||
| 183 | id: "slash:model", | ||
| 184 | type: "slash-command", | ||
| 185 | command: "model", | ||
| 186 | label: "/model", | ||
| 187 | description: "Switch model", | ||
| 188 | }, | ||
| 189 | { | ||
| 190 | id: "skill:claudeAgent:unslop", | ||
| 191 | type: "skill", | ||
| 192 | provider: claudeDriver, | ||
| 193 | skill: { | ||
| 194 | name: "unslop", | ||
| 195 | path: "/skills/unslop/SKILL.md", | ||
| 196 | enabled: true, | ||
| 197 | }, | ||
| 198 | label: "/skill:unslop", | ||
| 199 | description: "Cut AI tells from writing", | ||
| 200 | }, | ||
| 201 | ] satisfies Array< | ||
| 202 | Extract<ComposerCommandItem, { type: "slash-command" | "provider-slash-command" | "skill" }> | ||
| 203 | >; | ||
| 204 | |||
| 205 | expect(slashCommandItemsForPromptPosition(items, false).map((item) => item.id)).toEqual([ | ||
| 206 | "slash:model", | ||
| 207 | ]); | ||
| 208 | expect(slashCommandItemsForPromptPosition(items, true).map((item) => item.id)).toEqual([ | ||
| 209 | "slash:model", | ||
| 210 | "skill:claudeAgent:unslop", | ||
| 211 | ]); | ||
| 212 | }); | ||
| 176 | }); | 213 | }); |
apps/web/src/components/chat/composerSlashCommandSearch.ts+10| ... | @@ -12,6 +12,16 @@ type SlashSearchItem = Extract< | ... | @@ -12,6 +12,16 @@ type SlashSearchItem = Extract< |
| 12 | { type: "slash-command" | "provider-slash-command" | "skill" } | 12 | { type: "slash-command" | "provider-slash-command" | "skill" } |
| 13 | >; | 13 | >; |
| 14 | 14 | ||
| 15 | export function slashCommandItemsForPromptPosition( | ||
| 16 | items: ReadonlyArray<SlashSearchItem>, | ||
| 17 | isAtPromptStart: boolean, | ||
| 18 | ): SlashSearchItem[] { | ||
| 19 | if (isAtPromptStart) { | ||
| 20 | return [...items]; | ||
| 21 | } | ||
| 22 | return items.filter((item) => item.type !== "skill"); | ||
| 23 | } | ||
| 24 | |||
| 15 | function scoreSlashCommandItem(item: SlashSearchItem, query: string): number | null { | 25 | function scoreSlashCommandItem(item: SlashSearchItem, query: string): number | null { |
| 16 | if (item.type === "skill") { | 26 | if (item.type === "skill") { |
| 17 | if (query === "skill") { | 27 | if (query === "skill") { |