diff --git a/dashboard/components/SimpleInput.js b/dashboard/components/SimpleInput.js index 9f817fa..9acf746 100644 --- a/dashboard/components/SimpleInput.js +++ b/dashboard/components/SimpleInput.js @@ -1,5 +1,6 @@ import { html, useState, useRef, useCallback, useMemo, useEffect } from '../lib/preact.js'; import { getStatusMeta } from '../utils/status.js'; +import { getTriggerInfo as _getTriggerInfo, filteredSkills as _filteredSkills } from '../utils/autocomplete.js'; export function SimpleInput({ sessionId, status, onRespond, autocompleteConfig = null }) { const [text, setText] = useState(''); @@ -13,48 +14,12 @@ export function SimpleInput({ sessionId, status, onRespond, autocompleteConfig = const autocompleteRef = useRef(null); const meta = getStatusMeta(status); - // Detect if cursor is at a trigger position for autocomplete const getTriggerInfo = useCallback((value, cursorPos) => { - // No config means no autocomplete - if (!autocompleteConfig) return null; - - const { trigger } = autocompleteConfig; - - // Find the start of the current "word" (after last whitespace before cursor) - let wordStart = cursorPos; - while (wordStart > 0 && !/\s/.test(value[wordStart - 1])) { - wordStart--; - } - - // Check if word starts with this agent's trigger character - if (value[wordStart] === trigger) { - return { - trigger, - filterText: value.slice(wordStart + 1, cursorPos).toLowerCase(), - replaceStart: wordStart, - replaceEnd: cursorPos, - }; - } - - return null; + return _getTriggerInfo(value, cursorPos, autocompleteConfig); }, [autocompleteConfig]); - // Filter skills based on user input after trigger const filteredSkills = useMemo(() => { - if (!autocompleteConfig || !triggerInfo) return []; - - const { skills } = autocompleteConfig; - const { filterText } = triggerInfo; - - let filtered = skills; - if (filterText) { - filtered = skills.filter(s => - s.name.toLowerCase().includes(filterText) - ); - } - - // Server pre-sorts, but re-sort after filtering for stability - return filtered.sort((a, b) => a.name.localeCompare(b.name)); + return _filteredSkills(autocompleteConfig, triggerInfo); }, [autocompleteConfig, triggerInfo]); // Show/hide autocomplete based on trigger detection @@ -229,7 +194,7 @@ export function SimpleInput({ sessionId, status, onRespond, autocompleteConfig =
No matching skills
` : filteredSkills.map((skill, i) => html`
0 && !/\s/.test(value[wordStart - 1])) { + wordStart--; + } + + // Check if word starts with this agent's trigger character + if (value[wordStart] === trigger) { + return { + trigger, + filterText: value.slice(wordStart + 1, cursorPos).toLowerCase(), + replaceStart: wordStart, + replaceEnd: cursorPos, + }; + } + + return null; +} + +/** + * Filter and sort skills based on trigger info. + * Returns sorted array of matching skills. + */ +export function filteredSkills(autocompleteConfig, triggerInfo) { + if (!autocompleteConfig || !triggerInfo) return []; + + const { skills } = autocompleteConfig; + const { filterText } = triggerInfo; + + let filtered = filterText + ? skills.filter(s => s.name.toLowerCase().includes(filterText)) + : skills.slice(); + + // Server pre-sorts, but re-sort after filtering for stability + return filtered.sort((a, b) => a.name.localeCompare(b.name)); +}