[Init] Initial commit - NetMesh terminal manager
Some checks failed
build-packages / resolve bundled mosh-client (push) Has been cancelled
build-packages / resolve bundled et-client (push) Has been cancelled
build-packages / build-macos (push) Has been cancelled
build-packages / build-windows (push) Has been cancelled
build-packages / build-linux-x64 (push) Has been cancelled
build-packages / build-linux-arm64 (push) Has been cancelled
build-packages / release (push) Has been cancelled
build-packages / update Nix release metadata (push) Has been cancelled
build-packages / bump homebrew tap (push) Has been cancelled
test / lint-and-test (push) Has been cancelled
AI automation / Route event (push) Has been cancelled
AI automation / Hand reopened issue to maintainers (push) Has been cancelled
AI automation / Clean source issue state (push) Has been cancelled
AI automation / Reconcile handoffs (push) Has been cancelled
AI automation / Classify issue (push) Has been cancelled
AI automation / Claude Code smoke (push) Has been cancelled
AI automation / Review issue follow-up (push) Has been cancelled
AI automation / Publish issue follow-up (push) Has been cancelled
AI automation / Implement with Claude Code (push) Has been cancelled
AI automation / Publish implement PR (push) Has been cancelled
AI automation / Continue queued issue comments (push) Has been cancelled
AI automation / Codex review loop (push) Has been cancelled
AI automation / Publish Codex fix (push) Has been cancelled
AI automation / Clear Codex dispatch marker (push) Has been cancelled
AI automation / Own PR re-request Codex (push) Has been cancelled
AI automation / External PR re-request Codex (push) Has been cancelled
AI automation / Poll Codex reaction / retry (push) Has been cancelled
build-et-binaries / build-linux-x64 (push) Has been cancelled
build-et-binaries / build-linux-arm64 (push) Has been cancelled
build-et-binaries / build-macos-universal (push) Has been cancelled
build-et-binaries / build-windows-x64 (push) Has been cancelled
build-et-binaries / release (push) Has been cancelled
Some checks failed
build-packages / resolve bundled mosh-client (push) Has been cancelled
build-packages / resolve bundled et-client (push) Has been cancelled
build-packages / build-macos (push) Has been cancelled
build-packages / build-windows (push) Has been cancelled
build-packages / build-linux-x64 (push) Has been cancelled
build-packages / build-linux-arm64 (push) Has been cancelled
build-packages / release (push) Has been cancelled
build-packages / update Nix release metadata (push) Has been cancelled
build-packages / bump homebrew tap (push) Has been cancelled
test / lint-and-test (push) Has been cancelled
AI automation / Route event (push) Has been cancelled
AI automation / Hand reopened issue to maintainers (push) Has been cancelled
AI automation / Clean source issue state (push) Has been cancelled
AI automation / Reconcile handoffs (push) Has been cancelled
AI automation / Classify issue (push) Has been cancelled
AI automation / Claude Code smoke (push) Has been cancelled
AI automation / Review issue follow-up (push) Has been cancelled
AI automation / Publish issue follow-up (push) Has been cancelled
AI automation / Implement with Claude Code (push) Has been cancelled
AI automation / Publish implement PR (push) Has been cancelled
AI automation / Continue queued issue comments (push) Has been cancelled
AI automation / Codex review loop (push) Has been cancelled
AI automation / Publish Codex fix (push) Has been cancelled
AI automation / Clear Codex dispatch marker (push) Has been cancelled
AI automation / Own PR re-request Codex (push) Has been cancelled
AI automation / External PR re-request Codex (push) Has been cancelled
AI automation / Poll Codex reaction / retry (push) Has been cancelled
build-et-binaries / build-linux-x64 (push) Has been cancelled
build-et-binaries / build-linux-arm64 (push) Has been cancelled
build-et-binaries / build-macos-universal (push) Has been cancelled
build-et-binaries / build-windows-x64 (push) Has been cancelled
build-et-binaries / release (push) Has been cancelled
This commit is contained in:
192
components/ai/SlashCommandPicker.tsx
Normal file
192
components/ai/SlashCommandPicker.tsx
Normal file
@@ -0,0 +1,192 @@
|
||||
import { Command, MessageSquare, Package } from 'lucide-react';
|
||||
import React from 'react';
|
||||
import type {
|
||||
AIQuickMessage,
|
||||
SlashCommandItem,
|
||||
SystemSlashCommand,
|
||||
UserSkillSlashOption,
|
||||
} from '../../infrastructure/ai/quickMessages';
|
||||
import { getSlashCommandItemId } from '../../infrastructure/ai/quickMessages';
|
||||
import { ScrollArea } from '../ui/scroll-area';
|
||||
|
||||
export interface SlashCommandPickerProps {
|
||||
listboxId: string;
|
||||
ariaLabel: string;
|
||||
quickMessages: AIQuickMessage[];
|
||||
systemCommands: SystemSlashCommand[];
|
||||
userSkills: UserSkillSlashOption[];
|
||||
slashCommandItems: SlashCommandItem[];
|
||||
activeMenuIndex: number;
|
||||
onActiveIndexChange: (index: number) => void;
|
||||
onSelectQuickMessage: (message: AIQuickMessage) => void;
|
||||
onSelectSystemCommand: (command: SystemSlashCommand) => void;
|
||||
systemCommandsSectionLabel: string;
|
||||
systemCommandDescription: (command: SystemSlashCommand) => string;
|
||||
onSelectSkill: (skill: UserSkillSlashOption) => void;
|
||||
quickMessagesSectionLabel: string;
|
||||
userSkillsSectionLabel: string;
|
||||
noResultsLabel: string;
|
||||
emptyHintLabel?: string;
|
||||
className?: string;
|
||||
style?: React.CSSProperties;
|
||||
listRef?: React.Ref<HTMLDivElement>;
|
||||
}
|
||||
|
||||
export const SlashCommandPicker: React.FC<SlashCommandPickerProps> = ({
|
||||
listboxId,
|
||||
ariaLabel,
|
||||
quickMessages,
|
||||
systemCommands,
|
||||
userSkills,
|
||||
slashCommandItems,
|
||||
activeMenuIndex,
|
||||
onActiveIndexChange,
|
||||
onSelectQuickMessage,
|
||||
onSelectSystemCommand,
|
||||
systemCommandsSectionLabel,
|
||||
systemCommandDescription,
|
||||
onSelectSkill,
|
||||
quickMessagesSectionLabel,
|
||||
userSkillsSectionLabel,
|
||||
noResultsLabel,
|
||||
emptyHintLabel,
|
||||
className,
|
||||
style,
|
||||
listRef,
|
||||
}) => {
|
||||
const activeItem = slashCommandItems[activeMenuIndex];
|
||||
const activeDescendantId = activeItem ? `${listboxId}-${getSlashCommandItemId(activeItem)}` : undefined;
|
||||
|
||||
return (
|
||||
<div
|
||||
ref={listRef}
|
||||
id={listboxId}
|
||||
role="listbox"
|
||||
tabIndex={-1}
|
||||
aria-label={ariaLabel}
|
||||
aria-activedescendant={activeDescendantId}
|
||||
className={className}
|
||||
style={style}
|
||||
>
|
||||
<ScrollArea className="max-h-[280px]">
|
||||
<div className="p-1">
|
||||
{slashCommandItems.length === 0 ? (
|
||||
<div className="px-3 py-4 text-center space-y-1">
|
||||
<p className="text-[12px] text-muted-foreground/70">{noResultsLabel}</p>
|
||||
{emptyHintLabel ? (
|
||||
<p className="text-[11px] text-muted-foreground/45 leading-relaxed">{emptyHintLabel}</p>
|
||||
) : null}
|
||||
</div>
|
||||
) : (
|
||||
<>
|
||||
{systemCommands.length > 0 ? (
|
||||
<>
|
||||
<div className="px-2 py-1 text-[10px] text-muted-foreground/40 tracking-wide">
|
||||
{systemCommandsSectionLabel}
|
||||
</div>
|
||||
{systemCommands.map((command) => {
|
||||
const idx = slashCommandItems.findIndex(
|
||||
(item) => item.kind === 'system' && item.command.slug === command.slug,
|
||||
);
|
||||
const isActive = idx === activeMenuIndex;
|
||||
return (
|
||||
<button
|
||||
id={`${listboxId}-${command.slug}`}
|
||||
key={command.slug}
|
||||
type="button"
|
||||
role="option"
|
||||
aria-selected={isActive}
|
||||
onMouseEnter={() => onActiveIndexChange(idx)}
|
||||
onClick={() => onSelectSystemCommand(command)}
|
||||
className={`w-full rounded-md px-2 py-1.5 text-left transition-colors cursor-pointer ${isActive ? 'bg-muted/40' : 'hover:bg-muted/30'}`}
|
||||
>
|
||||
<div className="flex items-center gap-2 text-[12px] min-w-0">
|
||||
<Command size={12} className="text-primary/60 shrink-0" />
|
||||
<span className="text-foreground/90">/{command.slug}</span>
|
||||
</div>
|
||||
<div className="pl-5 text-[10px] leading-4.5 text-muted-foreground/62">
|
||||
{systemCommandDescription(command)}
|
||||
</div>
|
||||
</button>
|
||||
);
|
||||
})}
|
||||
</>
|
||||
) : null}
|
||||
{quickMessages.length > 0 ? (
|
||||
<>
|
||||
<div className="px-2 py-1 text-[10px] text-muted-foreground/40 tracking-wide">
|
||||
{quickMessagesSectionLabel}
|
||||
</div>
|
||||
{quickMessages.map((message) => {
|
||||
const idx = slashCommandItems.findIndex(
|
||||
(item) => item.kind === 'quickMessage' && item.message.id === message.id,
|
||||
);
|
||||
const isActive = idx === activeMenuIndex;
|
||||
return (
|
||||
<button
|
||||
id={`${listboxId}-${message.id}`}
|
||||
key={message.id}
|
||||
type="button"
|
||||
role="option"
|
||||
aria-selected={isActive}
|
||||
onMouseEnter={() => onActiveIndexChange(idx)}
|
||||
onClick={() => onSelectQuickMessage(message)}
|
||||
className={`w-full rounded-md px-2 py-1.5 text-left transition-colors cursor-pointer ${isActive ? 'bg-muted/40' : 'hover:bg-muted/30'}`}
|
||||
>
|
||||
<div className="flex items-center gap-2 text-[12px] min-w-0">
|
||||
<MessageSquare size={12} className="text-muted-foreground/55 shrink-0" />
|
||||
<span className="text-foreground/90 truncate">{message.name}</span>
|
||||
<span className="text-muted-foreground/45 font-mono shrink-0">/{message.slug}</span>
|
||||
</div>
|
||||
{(message.description || message.content) ? (
|
||||
<div className="pl-5 text-[10px] leading-4.5 text-muted-foreground/62 line-clamp-2">
|
||||
{message.description || message.content}
|
||||
</div>
|
||||
) : null}
|
||||
</button>
|
||||
);
|
||||
})}
|
||||
</>
|
||||
) : null}
|
||||
{userSkills.length > 0 ? (
|
||||
<>
|
||||
<div className="px-2 py-1 text-[10px] text-muted-foreground/40 tracking-wide">
|
||||
{userSkillsSectionLabel}
|
||||
</div>
|
||||
{userSkills.map((skill) => {
|
||||
const idx = slashCommandItems.findIndex(
|
||||
(item) => item.kind === 'skill' && item.skill.id === skill.id,
|
||||
);
|
||||
const isActive = idx === activeMenuIndex;
|
||||
return (
|
||||
<button
|
||||
id={`${listboxId}-${skill.id}`}
|
||||
key={skill.id}
|
||||
type="button"
|
||||
role="option"
|
||||
aria-selected={isActive}
|
||||
onMouseEnter={() => onActiveIndexChange(idx)}
|
||||
onClick={() => onSelectSkill(skill)}
|
||||
className={`w-full rounded-md px-2 py-1.5 text-left transition-colors cursor-pointer ${isActive ? 'bg-muted/40' : 'hover:bg-muted/30'}`}
|
||||
>
|
||||
<div className="flex items-center gap-2 text-[12px]">
|
||||
<Package size={12} className="text-muted-foreground/55 shrink-0" />
|
||||
<span className="text-foreground/90">/{skill.slug}</span>
|
||||
</div>
|
||||
{skill.description ? (
|
||||
<div className="pl-5 text-[10px] leading-4.5 text-muted-foreground/62 line-clamp-2">
|
||||
{skill.description}
|
||||
</div>
|
||||
) : null}
|
||||
</button>
|
||||
);
|
||||
})}
|
||||
</>
|
||||
) : null}
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
</ScrollArea>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
Reference in New Issue
Block a user