feat(mcp): add preset setup and capability mentions

This commit is contained in:
Xubin Ren
2026-05-24 19:43:20 +08:00
parent 8be258212e
commit 704ac558f6
54 changed files with 8425 additions and 708 deletions
+37 -32
View File
@@ -33,6 +33,7 @@ export function SessionSearchDialog({
}: SessionSearchDialogProps) {
const { t } = useTranslation();
const inputRef = useRef<HTMLInputElement>(null);
const itemRefs = useRef<Array<HTMLButtonElement | null>>([]);
const [query, setQuery] = useState("");
const [highlightedIndex, setHighlightedIndex] = useState(0);
@@ -46,7 +47,6 @@ export function SessionSearchDialog({
);
}, [normalizedQuery, open, sessions, titleOverrides]);
const itemCount = sessionResults.length;
const shortcutLabel = useMemo(getSearchShortcutLabel, []);
useEffect(() => {
if (!open) return;
@@ -65,6 +65,18 @@ export function SessionSearchDialog({
);
}, [itemCount]);
useEffect(() => {
itemRefs.current = itemRefs.current.slice(0, itemCount);
}, [itemCount]);
useEffect(() => {
if (!open) return;
itemRefs.current[highlightedIndex]?.scrollIntoView({
block: "nearest",
inline: "nearest",
});
}, [highlightedIndex, open]);
const handleSelect = (key: string) => {
onOpenChange(false);
onSelect(key);
@@ -107,18 +119,18 @@ export function SessionSearchDialog({
<DialogContent
showCloseButton={false}
className={cn(
"max-h-[min(34rem,calc(100vh-2rem))] w-[calc(100vw-2rem)] max-w-[42rem] gap-0 overflow-hidden p-0",
"rounded-2xl border border-border/70 bg-popover/95 text-popover-foreground shadow-2xl backdrop-blur-xl",
"sm:rounded-2xl",
"flex max-h-[min(40rem,calc(100vh-2rem))] w-[calc(100vw-2rem)] max-w-[42rem] flex-col gap-0 overflow-hidden p-0",
"rounded-[22px] border border-border bg-background text-foreground shadow-[0_22px_70px_rgba(0,0,0,0.22)]",
"dark:border-white/14 dark:bg-[#2b2b2b] dark:shadow-[0_26px_90px_rgba(0,0,0,0.44)] sm:rounded-[22px]",
)}
>
<DialogTitle className="sr-only">{t("sidebar.searchAria")}</DialogTitle>
<DialogDescription className="sr-only">
{t("sidebar.searchPlaceholder")}
</DialogDescription>
<div className="flex h-14 items-center gap-3 border-b border-border/60 px-5">
<div className="flex h-[62px] shrink-0 items-center gap-3 border-b border-border px-[18px]">
<Search
className="h-4 w-4 shrink-0 text-muted-foreground"
className="h-[18px] w-[18px] shrink-0 text-muted-foreground"
aria-hidden
/>
<input
@@ -128,16 +140,16 @@ export function SessionSearchDialog({
onKeyDown={handleKeyDown}
placeholder={t("sidebar.searchPlaceholder")}
aria-label={t("sidebar.searchAria")}
className="h-full min-w-0 flex-1 bg-transparent text-[15px] font-medium text-foreground outline-none placeholder:text-muted-foreground/75"
className="h-full min-w-0 flex-1 bg-transparent text-[19px] font-normal leading-none text-foreground outline-none placeholder:text-muted-foreground"
/>
<kbd className="hidden h-6 shrink-0 items-center rounded-md border border-border/70 bg-muted/60 px-2 text-[11px] font-medium text-muted-foreground sm:inline-flex">
{shortcutLabel}
</kbd>
</div>
<div className="min-h-0 overflow-y-auto overscroll-contain p-2">
<div
data-testid="session-search-scroll"
className="min-h-0 flex-1 overflow-y-auto overscroll-contain p-2.5 scrollbar-thin scrollbar-track-transparent"
>
<section>
<div className="px-2 pb-1.5 pt-1 text-[12px] font-medium text-muted-foreground/70">
<div className="px-2.5 pb-1.5 pt-1 text-[12px] font-medium text-muted-foreground">
{sectionLabel}
</div>
@@ -150,7 +162,7 @@ export function SessionSearchDialog({
{emptyLabel}
</div>
) : (
<ul className="space-y-1">
<ul className="space-y-0.5">
{sessionResults.map((session, index) => {
const title = titleOverrides[session.key]?.trim() ||
session.title?.trim() ||
@@ -164,15 +176,18 @@ export function SessionSearchDialog({
return (
<li key={session.key}>
<button
ref={(node) => {
itemRefs.current[index] = node;
}}
type="button"
onClick={() => handleSelect(session.key)}
onMouseEnter={() => setHighlightedIndex(index)}
aria-current={active ? "page" : undefined}
className={cn(
"flex min-h-12 w-full min-w-0 rounded-xl px-3 py-2.5 text-left transition-colors",
"grid min-h-[54px] w-full min-w-0 grid-cols-[minmax(0,1fr)_auto] items-center gap-3 rounded-[11px] px-3 py-2 text-left transition-colors",
highlighted
? "bg-accent text-accent-foreground"
: "text-popover-foreground hover:bg-accent/75 hover:text-accent-foreground",
? "bg-muted text-foreground"
: "text-foreground hover:bg-muted",
)}
>
<span className="min-w-0 flex-1">
@@ -181,17 +196,17 @@ export function SessionSearchDialog({
</span>
{showPreview ? (
<span
className={cn(
"block truncate text-[12px] leading-4",
highlighted
? "text-accent-foreground/70"
: "text-muted-foreground",
)}
className="block truncate text-[12px] leading-4 text-muted-foreground"
>
{preview}
</span>
) : null}
</span>
{active ? (
<span className="shrink-0 rounded-full bg-muted-foreground/10 px-2 py-0.5 text-[11px] font-medium text-muted-foreground">
{t("common.current", { defaultValue: "Current" })}
</span>
) : null}
</button>
</li>
);
@@ -221,13 +236,3 @@ function sessionMatchesTerms(
return terms.every((term) => haystack.includes(term));
}
function getSearchShortcutLabel() {
if (typeof navigator === "undefined") return "Ctrl K";
const platform = navigator.platform.toLowerCase();
const apple =
platform.includes("mac") ||
platform.includes("iphone") ||
platform.includes("ipad");
return apple ? "⌘K" : "Ctrl K";
}