Spaces:
Running
Running
| import type { CommandMode, WorkPackage } from "./work-package-types"; | |
| export type ComposerPackageSuggestion = { | |
| kind: "package"; | |
| id: string; | |
| title: string; | |
| shortName: string; | |
| }; | |
| export type ComposerCommandSuggestion = { | |
| kind: "command"; | |
| mode: CommandMode; | |
| label: string; | |
| description: string; | |
| }; | |
| export type ComposerSuggestion = | |
| | ComposerPackageSuggestion | |
| | ComposerCommandSuggestion; | |
| const AT_TRIGGER = /(^|\s)@([A-Za-z\s-]*)$/; | |
| const SLASH_TRIGGER = /(^|\s)\/([A-Za-z-]*)$/i; | |
| const COMMAND_SUGGESTIONS: ComposerCommandSuggestion[] = [ | |
| { | |
| kind: "command", | |
| mode: "ask", | |
| label: "/ask", | |
| description: "Ask about the current package context", | |
| }, | |
| { | |
| kind: "command", | |
| mode: "plan", | |
| label: "/plan", | |
| description: "Break a package into actionable work", | |
| }, | |
| { | |
| kind: "command", | |
| mode: "change", | |
| label: "/change", | |
| description: "Request updates to the current package", | |
| }, | |
| { | |
| kind: "command", | |
| mode: "execute", | |
| label: "/execute", | |
| description: "Run the current package flow", | |
| }, | |
| ]; | |
| function normalize(value: string) { | |
| return value.trim().replace(/\s+/g, " ").toLowerCase(); | |
| } | |
| function packageScore(workPackage: WorkPackage, query: string) { | |
| const title = normalize(workPackage.title); | |
| const shortName = normalize(workPackage.shortName); | |
| if (!query) { | |
| return 1; | |
| } | |
| return ( | |
| Number(shortName.startsWith(query)) * 4 + | |
| Number(title.startsWith(query)) * 3 + | |
| Number(shortName.includes(query)) * 2 + | |
| Number(title.includes(query)) | |
| ); | |
| } | |
| export function getComposerSuggestions( | |
| draft: string, | |
| workPackages: WorkPackage[], | |
| ): ComposerSuggestion[] { | |
| const atMatch = draft.match(AT_TRIGGER); | |
| if (atMatch) { | |
| const query = normalize(atMatch[2] ?? ""); | |
| return workPackages | |
| .map((workPackage) => ({ | |
| kind: "package" as const, | |
| id: workPackage.id, | |
| title: workPackage.title, | |
| shortName: workPackage.shortName, | |
| score: packageScore(workPackage, query), | |
| })) | |
| .filter((suggestion) => suggestion.score > 0) | |
| .sort((left, right) => right.score - left.score) | |
| .slice(0, 6) | |
| .map((suggestion) => ({ | |
| kind: suggestion.kind, | |
| id: suggestion.id, | |
| title: suggestion.title, | |
| shortName: suggestion.shortName, | |
| })); | |
| } | |
| const slashMatch = draft.match(SLASH_TRIGGER); | |
| if (slashMatch) { | |
| const query = normalize(slashMatch[2] ?? ""); | |
| return COMMAND_SUGGESTIONS.filter((suggestion) => { | |
| return !query || suggestion.mode.startsWith(query); | |
| }); | |
| } | |
| return []; | |
| } | |
| export function applyComposerSuggestion(args: { | |
| draft: string; | |
| suggestion: ComposerSuggestion; | |
| selectedWorkPackage?: Pick<WorkPackage, "shortName" | "title">; | |
| }) { | |
| const { draft, suggestion, selectedWorkPackage } = args; | |
| if (suggestion.kind === "package") { | |
| return draft.replace( | |
| AT_TRIGGER, | |
| (_, prefix: string) => `${prefix}@${suggestion.shortName} `, | |
| ); | |
| } | |
| const packageRef = | |
| selectedWorkPackage?.shortName || selectedWorkPackage?.title || ""; | |
| const replacement = packageRef | |
| ? `@${packageRef} ${suggestion.mode} ` | |
| : `/${suggestion.mode} `; | |
| return draft.replace(SLASH_TRIGGER, (_, prefix: string) => `${prefix}${replacement}`); | |
| } | |