Feat skills (#1221)
Some checks are pending
CodeQL Advanced / Analyze (actions) (push) Waiting to run
CodeQL Advanced / Analyze (javascript-typescript) (push) Waiting to run
CodeQL Advanced / Analyze (python) (push) Waiting to run
Pre-commit / pre-commit (push) Waiting to run
Test / Run Python Tests (push) Waiting to run

Co-authored-by: Pakchoioioi <happy.regina.bai@gmail.com>
Co-authored-by: Douglas Lai <115660088+Douglasymlai@users.noreply.github.com>
Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: Douglas <douglas.ym.lai@gmail.com>
Co-authored-by: Wendong-Fan <133094783+Wendong-Fan@users.noreply.github.com>
Co-authored-by: Wendong-Fan <w3ndong.fan@gmail.com>
This commit is contained in:
Tong Chen 2026-02-19 02:29:21 +08:00 committed by GitHub
parent d6142b5607
commit a23c30db13
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
84 changed files with 4411 additions and 159 deletions

View file

@ -12,24 +12,161 @@
// limitations under the License.
// ========= Copyright 2025-2026 @ Eigent.ai All Rights Reserved. =========
import { Button } from '@/components/ui/button';
import { Input } from '@/components/ui/input';
import { Search } from 'lucide-react';
import { TooltipSimple } from '@/components/ui/tooltip';
import { cn } from '@/lib/utils';
import { AnimatePresence, motion } from 'framer-motion';
import { Search, X } from 'lucide-react';
import { useCallback, useEffect, useRef, useState } from 'react';
import { useTranslation } from 'react-i18next';
export type SearchInputVariant = 'default' | 'icon';
interface SearchInputProps {
value: string;
onChange: (e: React.ChangeEvent<HTMLInputElement>) => void;
placeholder?: string;
variant?: SearchInputVariant;
/** Optional: called when user presses Enter in the field (e.g. to submit search) */
onSearch?: () => void;
/** Tooltip for the search icon button (icon variant). Defaults to agents.search-tooltip */
searchTooltip?: string;
/** Tooltip for the clear (X) button (icon variant). Defaults to agents.clear-search-tooltip */
clearTooltip?: string;
}
export default function SearchInput({ value, onChange }: SearchInputProps) {
const COLLAPSED_WIDTH = 40;
const EXPANDED_WIDTH = 240;
export default function SearchInput({
value,
onChange,
placeholder,
variant = 'default',
onSearch,
searchTooltip,
clearTooltip,
}: SearchInputProps) {
const { t } = useTranslation();
const inputRef = useRef<HTMLInputElement>(null);
const [userExpanded, setUserExpanded] = useState(false);
const isExpanded = userExpanded || value.length > 0;
const expand = useCallback(() => {
setUserExpanded(true);
}, []);
const collapse = useCallback(() => {
setUserExpanded(false);
onChange({ target: { value: '' } } as React.ChangeEvent<HTMLInputElement>);
}, [onChange]);
useEffect(() => {
if (userExpanded && inputRef.current) {
const id = requestAnimationFrame(() => {
inputRef.current?.focus();
});
return () => cancelAnimationFrame(id);
}
}, [userExpanded]);
const searchLabel = searchTooltip ?? t('agents.search-tooltip');
const clearLabel = clearTooltip ?? t('agents.clear-search-tooltip');
const place = placeholder ?? t('setting.search-mcp');
if (variant === 'icon') {
return (
<motion.div
className={cn(
'flex items-center justify-center py-0.5 overflow-hidden rounded-lg border border-solid border-transparent bg-transparent',
'focus-within:border-input-border-focus focus-within:bg-input-bg-input',
'hover:border-transparent hover:bg-surface-tertiary'
)}
initial={false}
animate={{ width: isExpanded ? EXPANDED_WIDTH : COLLAPSED_WIDTH }}
transition={{
type: 'spring',
stiffness: 400,
damping: 30,
}}
>
<AnimatePresence mode="wait">
{!isExpanded ? (
<motion.div
key="icon"
className="flex shrink-0 items-center justify-center"
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
exit={{ opacity: 0 }}
transition={{ duration: 0.15 }}
>
<TooltipSimple content={searchLabel}>
<Button
type="button"
variant="ghost"
size="icon"
onClick={expand}
aria-label={searchLabel}
>
<Search />
</Button>
</TooltipSimple>
</motion.div>
) : (
<motion.div
key="input"
className="flex min-w-0 flex-1 items-center gap-0 pr-1"
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
exit={{ opacity: 0 }}
transition={{ duration: 0.15 }}
>
<span className="pointer-events-none ml-2 inline-flex h-4 w-4 shrink-0 items-center justify-center text-icon-secondary">
<Search className="h-4 w-4" />
</span>
<input
ref={inputRef}
type="text"
value={value}
onChange={onChange}
placeholder={place}
onBlur={() => {
if (value.length === 0) setUserExpanded(false);
}}
onKeyDown={(e) => {
if (e.key === 'Enter') {
onSearch?.();
}
}}
className="h-6 min-w-0 flex-1 bg-transparent pl-2 text-label-sm text-text-heading outline-none placeholder:text-text-label"
/>
<TooltipSimple content={clearLabel}>
<Button
type="button"
variant="ghost"
size="icon"
className="shrink-0 rounded-full text-icon-secondary"
onClick={collapse}
aria-label={clearLabel}
>
<X />
</Button>
</TooltipSimple>
</motion.div>
)}
</AnimatePresence>
</motion.div>
);
}
return (
<div className="relative w-full">
<Input
size="sm"
value={value}
onChange={onChange}
placeholder={t('setting.search-mcp')}
placeholder={place}
leadingIcon={<Search className="h-5 w-5 text-icon-secondary" />}
/>
</div>