diff --git a/docs/content.en/docs/release-notes/_index.md b/docs/content.en/docs/release-notes/_index.md index 94791fad..d2a885e1 100644 --- a/docs/content.en/docs/release-notes/_index.md +++ b/docs/content.en/docs/release-notes/_index.md @@ -74,6 +74,7 @@ Information about release notes of Coco Server is provided here. - chore: add global login judgment #544 - chore: mark server offline on user logout #546 - chore: logout update server profile #549 +- chore: assistant keyboard events and mouse events #559 ## 0.4.0 (2025-04-27) diff --git a/src/components/Assistant/AssistantItem.tsx b/src/components/Assistant/AssistantItem.tsx new file mode 100644 index 00000000..ac8ba377 --- /dev/null +++ b/src/components/Assistant/AssistantItem.tsx @@ -0,0 +1,70 @@ +import { memo } from "react"; +import clsx from "clsx"; +import { Check } from "lucide-react"; + +import VisibleKey from "@/components/Common/VisibleKey"; +import FontIcon from "@/components/Common/Icons/FontIcon"; +import logoImg from "@/assets/icon.svg"; + +interface AssistantItemProps { + _id: string; + _source?: { + icon?: string; + name?: string; + description?: string; + }; + name?: string; + isActive: boolean; + isHighlight: boolean; + isKeyboardActive: boolean; + onClick: () => void; +} + +const AssistantItem = memo( + ({ + _id, + _source, + name, + isActive, + isHighlight, + isKeyboardActive = false, + onClick, + }: AssistantItemProps) => ( + + ) +); + +export default AssistantItem; \ No newline at end of file diff --git a/src/components/Assistant/AssistantList.tsx b/src/components/Assistant/AssistantList.tsx index 5cf1f40d..5da62886 100644 --- a/src/components/Assistant/AssistantList.tsx +++ b/src/components/Assistant/AssistantList.tsx @@ -1,19 +1,9 @@ import { useState, useRef, useCallback } from "react"; -import { - ChevronDownIcon, - RefreshCw, - Check, - ChevronLeft, - ChevronRight, -} from "lucide-react"; +import { ChevronDownIcon, RefreshCw } from "lucide-react"; import { useTranslation } from "react-i18next"; import { isNil } from "lodash-es"; import { Popover, PopoverButton, PopoverPanel } from "@headlessui/react"; -import { - useDebounce, - useKeyPress, - usePagination, -} from "ahooks"; +import { useDebounce, useKeyPress, usePagination } from "ahooks"; import clsx from "clsx"; import logoImg from "@/assets/icon.svg"; @@ -24,6 +14,8 @@ import { useShortcutsStore } from "@/stores/shortcutsStore"; import NoDataImage from "@/components/Common/NoDataImage"; import PopoverInput from "@/components/Common/PopoverInput"; import { AssistantFetcher } from "./AssistantFetcher"; +import AssistantItem from "./AssistantItem"; +import Pagination from "@/components/Common/Pagination"; interface AssistantListProps { assistantIDs?: string[]; @@ -67,6 +59,9 @@ export function AssistantList({ assistantIDs = [] }: AssistantListProps) { setTimeout(() => setIsRefreshing(false), 1000); }; + const [highlightIndex, setHighlightIndex] = useState(-1); + const [isKeyboardActive, setIsKeyboardActive] = useState(false); + useKeyPress( ["uparrow", "downarrow", "enter"], (event, key) => { @@ -77,9 +72,7 @@ export function AssistantList({ assistantIDs = [] }: AssistantListProps) { event.stopPropagation(); event.preventDefault(); - if (key === "enter") { - return popoverButtonRef.current?.click(); - } + setIsKeyboardActive(true); const index = assistants.findIndex( (item) => item._id === currentAssistant?._id @@ -88,15 +81,20 @@ export function AssistantList({ assistantIDs = [] }: AssistantListProps) { if (length <= 1) return; - let nextIndex = index; + let nextIndex = highlightIndex === -1 ? index : highlightIndex; if (key === "uparrow") { - nextIndex = index > 0 ? index - 1 : length - 1; - } else { - nextIndex = index < length - 1 ? index + 1 : 0; + nextIndex = nextIndex > 0 ? nextIndex - 1 : length - 1; + } else if (key === "downarrow") { + nextIndex = nextIndex < length - 1 ? nextIndex + 1 : 0; } - setCurrentAssistant(assistants[nextIndex]); + if (key === "enter") { + setCurrentAssistant(assistants[nextIndex]); + return popoverButtonRef.current?.click(); + } + + setHighlightIndex(nextIndex); }, { target: popoverRef, @@ -117,6 +115,11 @@ export function AssistantList({ assistantIDs = [] }: AssistantListProps) { pagination.changeCurrent(pagination.current + 1); }, [pagination]); + const handleMouseMove = useCallback(() => { + setHighlightIndex(-1); + setIsKeyboardActive(false); + }, []); + return (
@@ -151,7 +154,10 @@ export function AssistantList({ assistantIDs = [] }: AssistantListProps) { - +
{t("assistant.popover.title")}({pagination.total}) @@ -197,73 +203,29 @@ export function AssistantList({ assistantIDs = [] }: AssistantListProps) { {assistants.length > 0 ? ( <> - {assistants.map((assistant) => { - const { _id, _source, name } = assistant; - - const isActive = currentAssistant?._id === _id; - + {assistants.map((assistant, index) => { return ( - + /> ); })} -
- - - - -
- {pagination.current}/{pagination.totalPage} -
- - - - -
+ ) : (
diff --git a/src/components/Common/Pagination.tsx b/src/components/Common/Pagination.tsx new file mode 100644 index 00000000..67657b8d --- /dev/null +++ b/src/components/Common/Pagination.tsx @@ -0,0 +1,39 @@ +import { ChevronLeft, ChevronRight } from "lucide-react"; + +import VisibleKey from "./VisibleKey"; + +interface PaginationProps { + current: number; + totalPage: number; + onPrev: () => void; + onNext: () => void; + className?: string; +} + +function Pagination({ + current, + totalPage, + onPrev, + onNext, + className = "", +}: PaginationProps) { + return ( +
+ + + + +
+ {current}/{totalPage} +
+ + + + +
+ ); +} + +export default Pagination; diff --git a/src/components/Search/MCPPopover.tsx b/src/components/Search/MCPPopover.tsx index 6ec46161..0fae34f5 100644 --- a/src/components/Search/MCPPopover.tsx +++ b/src/components/Search/MCPPopover.tsx @@ -1,13 +1,6 @@ import { useState, useEffect, useCallback, useRef } from "react"; import { Popover, PopoverButton, PopoverPanel } from "@headlessui/react"; -import { - ChevronDownIcon, - RefreshCw, - Layers, - Hammer, - ChevronRight, - ChevronLeft, -} from "lucide-react"; +import { ChevronDownIcon, RefreshCw, Layers, Hammer } from "lucide-react"; import clsx from "clsx"; import { useTranslation } from "react-i18next"; import { useDebounce } from "ahooks"; @@ -21,7 +14,8 @@ import { useShortcutsStore } from "@/stores/shortcutsStore"; import VisibleKey from "@/components/Common/VisibleKey"; import { useChatStore } from "@/stores/chatStore"; import NoDataImage from "@/components/Common/NoDataImage"; -import PopoverInput from "../Common/PopoverInput"; +import PopoverInput from "@/components/Common/PopoverInput"; +import Pagination from "@/components/Common/Pagination"; interface MCPPopoverProps { isMCPActive: boolean; @@ -287,7 +281,11 @@ export default function MCPPopover({ ) : ( @@ -329,19 +327,13 @@ export default function MCPPopover({
{visibleList.length > 0 && ( -
- - - - -
- {page}/{totalPage} -
- - - - -
+ )}
diff --git a/src/components/Search/SearchPopover.tsx b/src/components/Search/SearchPopover.tsx index 62e19a5e..e5902558 100644 --- a/src/components/Search/SearchPopover.tsx +++ b/src/components/Search/SearchPopover.tsx @@ -1,13 +1,6 @@ import { useState, useEffect, useCallback, useRef } from "react"; import { Popover, PopoverButton, PopoverPanel } from "@headlessui/react"; -import { - ChevronDownIcon, - RefreshCw, - Layers, - Globe, - ChevronRight, - ChevronLeft, -} from "lucide-react"; +import { ChevronDownIcon, RefreshCw, Layers, Globe } from "lucide-react"; import clsx from "clsx"; import { useTranslation } from "react-i18next"; import { useDebounce } from "ahooks"; @@ -21,7 +14,8 @@ import { useShortcutsStore } from "@/stores/shortcutsStore"; import VisibleKey from "@/components/Common/VisibleKey"; import { useChatStore } from "@/stores/chatStore"; import NoDataImage from "@/components/Common/NoDataImage"; -import PopoverInput from "../Common/PopoverInput"; +import PopoverInput from "@/components/Common/PopoverInput"; +import Pagination from "@/components/Common/Pagination"; interface SearchPopoverProps { isSearchActive: boolean; @@ -292,7 +286,11 @@ export default function SearchPopover({ ) : ( @@ -334,19 +332,13 @@ export default function SearchPopover({
{visibleList.length > 0 && ( -
- - - - -
- {page}/{totalPage} -
- - - - -
+ )}