Backend: add routeId, agentId, processorType filter fields to SearchRequest and ClickHouseSearchEngine. Expand global text search to match route_id and agent_id columns. Frontend: new command palette component (portal overlay, Zustand store, TanStack Query search hook with 300ms debounce, filter chip parsing, keyboard navigation, scope tabs). Search bar in SearchFilters and TopNav now open the palette. Selecting a result writes filters to the execution search store to drive the results table. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
73 lines
2.1 KiB
TypeScript
73 lines
2.1 KiB
TypeScript
import { useRef, useEffect } from 'react';
|
|
import { useCommandPalette } from './use-command-palette';
|
|
import { parseFilterPrefix, checkTrailingFilter } from './utils';
|
|
import styles from './CommandPalette.module.css';
|
|
|
|
export function PaletteInput() {
|
|
const { query, filters, setQuery, addFilter, removeLastFilter, removeFilter } =
|
|
useCommandPalette();
|
|
const inputRef = useRef<HTMLInputElement>(null);
|
|
|
|
useEffect(() => {
|
|
inputRef.current?.focus();
|
|
}, []);
|
|
|
|
function handleChange(value: string) {
|
|
// Check if user typed a filter prefix like "status:failed "
|
|
const parsed = parseFilterPrefix(value);
|
|
if (parsed) {
|
|
addFilter(parsed.filter);
|
|
setQuery(parsed.remaining);
|
|
return;
|
|
}
|
|
const trailing = checkTrailingFilter(value);
|
|
if (trailing) {
|
|
addFilter(trailing);
|
|
setQuery('');
|
|
return;
|
|
}
|
|
setQuery(value);
|
|
}
|
|
|
|
function handleKeyDown(e: React.KeyboardEvent) {
|
|
if (e.key === 'Backspace' && query === '' && filters.length > 0) {
|
|
e.preventDefault();
|
|
removeLastFilter();
|
|
}
|
|
}
|
|
|
|
return (
|
|
<div className={styles.inputWrap}>
|
|
<svg className={styles.searchIcon} viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2">
|
|
<circle cx="11" cy="11" r="8" />
|
|
<path d="m21 21-4.35-4.35" />
|
|
</svg>
|
|
{filters.length > 0 && (
|
|
<div className={styles.chipList}>
|
|
{filters.map((f, i) => (
|
|
<span key={f.key} className={styles.chip}>
|
|
<span className={styles.chipKey}>{f.key}:</span>
|
|
{f.value}
|
|
<button className={styles.chipRemove} onClick={() => removeFilter(i)}>
|
|
×
|
|
</button>
|
|
</span>
|
|
))}
|
|
</div>
|
|
)}
|
|
<input
|
|
ref={inputRef}
|
|
className={styles.input}
|
|
type="text"
|
|
value={query}
|
|
onChange={(e) => handleChange(e.target.value)}
|
|
onKeyDown={handleKeyDown}
|
|
placeholder={filters.length > 0 ? 'Refine search...' : 'Search executions, agents...'}
|
|
/>
|
|
<div className={styles.inputHint}>
|
|
<kbd className={styles.kbd}>esc</kbd> close
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|