規則選單
實作 /command 自動完成選單以便快速存取規則。
實作一個 /rule 指令選單,讓使用者可以透過自動完成和鍵盤導航快速觸發預定義的規則。
概觀
當使用者輸入 / 時,顯示可用規則列表並支援搜尋過濾和鍵盤導航。
取得規則
首先,從 Config API 取得規則列表:
取得規則
const [rules, setRules] = useState<Rule[]>([]);
useEffect(() => {
fetch("/api/lens/agent/config")
.then((res) => res.json())
.then((data) => {
if (data.success) {
setRules(data.config.rules || []);
}
});
}, []);規則選單狀態
狀態管理
const [showRulesMenu, setShowRulesMenu] = useState(false);
const [selectedIndex, setSelectedIndex] = useState(0);
const [input, setInput] = useState("");過濾邏輯
過濾規則
// Get search string after /
const slashQuery = input.startsWith("/")
? input.slice(1).toLowerCase()
: "";
// Filter rules
const filteredRules = showRulesMenu
? rules.filter(
(rule) =>
rule.name.toLowerCase().includes(slashQuery) ||
(rule.displayName || "").toLowerCase().includes(slashQuery)
)
: [];輸入變更處理器
處理輸入變更
const handleInputChange = (e: React.ChangeEvent<HTMLTextAreaElement>) => {
const value = e.target.value;
setInput(value);
// Show menu when starting with "/" and no space
if (value.startsWith("/") && !value.includes(" ")) {
setShowRulesMenu(true);
setSelectedIndex(0);
} else {
setShowRulesMenu(false);
}
};鍵盤導航
鍵盤處理
const handleKeyDown = (e: React.KeyboardEvent) => {
if (showRulesMenu && filteredRules.length > 0) {
switch (e.key) {
case "ArrowDown":
e.preventDefault();
setSelectedIndex((prev) => (prev + 1) % filteredRules.length);
return;
case "ArrowUp":
e.preventDefault();
setSelectedIndex(
(prev) => (prev - 1 + filteredRules.length) % filteredRules.length
);
return;
case "Enter":
case "Tab":
e.preventDefault();
selectRule(filteredRules[selectedIndex]);
return;
case "Escape":
e.preventDefault();
setShowRulesMenu(false);
return;
}
}
// Normal Enter to send
if (e.key === "Enter" && !e.shiftKey && !e.nativeEvent.isComposing) {
e.preventDefault();
handleSend();
}
};選擇規則
規則選擇
const selectRule = (rule: Rule) => {
// Set input to "/ruleName "
setInput(`/${rule.name} `);
setShowRulesMenu(false);
inputRef.current?.focus();
};渲染選單
UI 渲染
{/* Rules Menu */}
{showRulesMenu && filteredRules.length > 0 && (
<div className="absolute bottom-full left-0 right-0 mb-2 bg-white border rounded-lg shadow-lg max-h-64 overflow-auto">
{/* Keyboard hints */}
<div className="px-3 py-2 text-xs text-gray-400 border-b flex gap-4">
<span>↑↓ 選擇</span>
<span>Enter 確認</span>
<span>Esc 關閉</span>
</div>
{/* Rules list */}
{filteredRules.map((rule, index) => (
<div
key={rule.id}
className={`px-3 py-2 cursor-pointer transition ${
index === selectedIndex
? "bg-blue-50 text-blue-700"
: "hover:bg-gray-50"
}`}
onClick={() => selectRule(rule)}
onMouseEnter={() => setSelectedIndex(index)}
>
<div className="flex items-center gap-2">
<span className="font-mono text-sm text-blue-500">
/{rule.name}
</span>
<span className="text-gray-700">
{rule.displayName || rule.name}
</span>
</div>
{rule.description && (
<p className="text-xs text-gray-500 mt-1">
{rule.description}
</p>
)}
</div>
))}
</div>
)}
{/* No results */}
{showRulesMenu && filteredRules.length === 0 && (
<div className="absolute bottom-full left-0 right-0 mb-2 bg-white border rounded-lg shadow-lg p-4 text-center text-gray-400">
沒有符合的規則
</div>
)}規則型別
型別定義
interface Rule {
id: string;
name: string;
displayName?: string;
description?: string;
prompt: string;
}Tip
規則在 Lens OS 主控台中設定,並透過 Config API 自動同步到你的應用程式。