diff --git a/biome.jsonc b/biome.jsonc index 94c2e3bae6..9509135fc4 100644 --- a/biome.jsonc +++ b/biome.jsonc @@ -23,7 +23,7 @@ }, "files": { "ignoreUnknown": false, - "includes": ["**"], + "includes": ["**", "!**/.claude/**"], "maxSize": 2097152 }, "formatter": { diff --git a/src/renderer/src/components/Popups/AddAssistantOrAgentPopup.tsx b/src/renderer/src/components/Popups/AddAssistantOrAgentPopup.tsx new file mode 100644 index 0000000000..4094b3f3d1 --- /dev/null +++ b/src/renderer/src/components/Popups/AddAssistantOrAgentPopup.tsx @@ -0,0 +1,119 @@ +import { cn } from '@heroui/react' +import { TopView } from '@renderer/components/TopView' +import { Modal } from 'antd' +import { Bot, MessageSquare } from 'lucide-react' +import { useState } from 'react' +import { useTranslation } from 'react-i18next' + +type OptionType = 'assistant' | 'agent' + +interface ShowParams { + onSelect: (type: OptionType) => void +} + +interface Props extends ShowParams { + resolve: (data: { type?: OptionType }) => void +} + +const PopupContainer: React.FC = ({ onSelect, resolve }) => { + const { t } = useTranslation() + const [open, setOpen] = useState(true) + const [hoveredOption, setHoveredOption] = useState(null) + + const onCancel = () => { + setOpen(false) + } + + const onClose = () => { + resolve({}) + } + + const handleSelect = (type: OptionType) => { + setOpen(false) + onSelect(type) + resolve({ type }) + } + + AddAssistantOrAgentPopup.hide = onCancel + + return ( + +
+ {/* Assistant Option */} + + + {/* Agent Option */} + +
+
+ ) +} + +const TopViewKey = 'AddAssistantOrAgentPopup' + +export default class AddAssistantOrAgentPopup { + static topviewId = 0 + static hide() { + TopView.hide(TopViewKey) + } + static show(props: ShowParams) { + return new Promise<{ type?: OptionType }>((resolve) => { + TopView.show( + { + resolve(v) + TopView.hide(TopViewKey) + }} + />, + TopViewKey + ) + }) + } +} diff --git a/src/renderer/src/i18n/locales/en-us.json b/src/renderer/src/i18n/locales/en-us.json index 4210da1533..b3b89851d7 100644 --- a/src/renderer/src/i18n/locales/en-us.json +++ b/src/renderer/src/i18n/locales/en-us.json @@ -1,6 +1,7 @@ { "agent": { "add": { + "description": "Handle complex tasks with various tools", "error": { "failed": "Failed to add a agent", "invalid_agent": "Invalid Agent" @@ -547,8 +548,12 @@ "chat": { "add": { "assistant": { + "description": "Daily conversations and quick Q&A", "title": "Add Assistant" }, + "option": { + "title": "Select Type" + }, "topic": { "title": "New Topic" } diff --git a/src/renderer/src/i18n/locales/zh-cn.json b/src/renderer/src/i18n/locales/zh-cn.json index 405c92ac60..8f91651bac 100644 --- a/src/renderer/src/i18n/locales/zh-cn.json +++ b/src/renderer/src/i18n/locales/zh-cn.json @@ -1,6 +1,7 @@ { "agent": { "add": { + "description": "调用各种工具处理复杂任务", "error": { "failed": "添加 Agent 失败", "invalid_agent": "无效的 Agent" @@ -547,8 +548,12 @@ "chat": { "add": { "assistant": { + "description": "日常对话和快速问答", "title": "添加助手" }, + "option": { + "title": "选择添加类型" + }, "topic": { "title": "新建话题" } diff --git a/src/renderer/src/i18n/locales/zh-tw.json b/src/renderer/src/i18n/locales/zh-tw.json index ea93788341..f89802ee23 100644 --- a/src/renderer/src/i18n/locales/zh-tw.json +++ b/src/renderer/src/i18n/locales/zh-tw.json @@ -1,6 +1,7 @@ { "agent": { "add": { + "description": "調用各種工具處理複雜任務", "error": { "failed": "無法新增代理人", "invalid_agent": "無效的 Agent" @@ -547,8 +548,12 @@ "chat": { "add": { "assistant": { + "description": "日常對話和快速問答", "title": "新增助手" }, + "option": { + "title": "選擇新增類型" + }, "topic": { "title": "新增話題" } diff --git a/src/renderer/src/pages/home/Tabs/AssistantsTab.tsx b/src/renderer/src/pages/home/Tabs/AssistantsTab.tsx index 2cb6aabfa4..4eebe45bc3 100644 --- a/src/renderer/src/pages/home/Tabs/AssistantsTab.tsx +++ b/src/renderer/src/pages/home/Tabs/AssistantsTab.tsx @@ -170,7 +170,7 @@ const AssistantsTab: FC = (props) => { onAssistantSwitch={setActiveAssistant} onAssistantDelete={onDeleteAssistant} onAgentDelete={deleteAgent} - onAgentPress={setActiveAgentId} + onAgentPress={handleAgentPress} addPreset={addAssistantPreset} copyAssistant={copyAssistant} onCreateDefaultAssistant={onCreateDefaultAssistant} diff --git a/src/renderer/src/pages/home/Tabs/components/AgentItem.tsx b/src/renderer/src/pages/home/Tabs/components/AgentItem.tsx index 9836ee5891..4ecc862ae3 100644 --- a/src/renderer/src/pages/home/Tabs/components/AgentItem.tsx +++ b/src/renderer/src/pages/home/Tabs/components/AgentItem.tsx @@ -84,7 +84,8 @@ export const Container: React.FC<{ isActive?: boolean } & React.HTMLAttributes (
diff --git a/src/renderer/src/pages/home/Tabs/components/UnifiedAddButton.tsx b/src/renderer/src/pages/home/Tabs/components/UnifiedAddButton.tsx index d50add0bd8..433709d20b 100644 --- a/src/renderer/src/pages/home/Tabs/components/UnifiedAddButton.tsx +++ b/src/renderer/src/pages/home/Tabs/components/UnifiedAddButton.tsx @@ -1,10 +1,10 @@ -import { Button, Popover, PopoverContent, PopoverTrigger, useDisclosure } from '@heroui/react' +import { useDisclosure } from '@heroui/react' +import AddAssistantOrAgentPopup from '@renderer/components/Popups/AddAssistantOrAgentPopup' import { AgentModal } from '@renderer/components/Popups/agent/AgentModal' import { useAppDispatch } from '@renderer/store' import { setActiveTopicOrSessionAction } from '@renderer/store/runtime' import { AgentEntity, Assistant, Topic } from '@renderer/types' -import { Bot, MessageSquare } from 'lucide-react' -import { FC, useCallback, useState } from 'react' +import { FC, useCallback } from 'react' import { useTranslation } from 'react-i18next' import AddButton from './AddButton' @@ -17,18 +17,19 @@ interface UnifiedAddButtonProps { const UnifiedAddButton: FC = ({ onCreateAssistant, setActiveAssistant, setActiveAgentId }) => { const { t } = useTranslation() - const [isPopoverOpen, setIsPopoverOpen] = useState(false) const { isOpen: isAgentModalOpen, onOpen: onAgentModalOpen, onClose: onAgentModalClose } = useDisclosure() const dispatch = useAppDispatch() - const handleAddAssistant = () => { - setIsPopoverOpen(false) - onCreateAssistant() - } - - const handleAddAgent = () => { - setIsPopoverOpen(false) - onAgentModalOpen() + const handleAddButtonClick = () => { + AddAssistantOrAgentPopup.show({ + onSelect: (type) => { + if (type === 'assistant') { + onCreateAssistant() + } else if (type === 'agent') { + onAgentModalOpen() + } + } + }) } const afterCreate = useCallback( @@ -58,32 +59,9 @@ const UnifiedAddButton: FC = ({ onCreateAssistant, setAct return (
- - - {t('chat.add.assistant.title')} - - -
- - -
-
-
- + + {t('chat.add.assistant.title')} +
) diff --git a/src/renderer/src/pages/settings/AgentSettings/PluginSettings.tsx b/src/renderer/src/pages/settings/AgentSettings/PluginSettings.tsx index fec28b13a0..74d63ecba3 100644 --- a/src/renderer/src/pages/settings/AgentSettings/PluginSettings.tsx +++ b/src/renderer/src/pages/settings/AgentSettings/PluginSettings.tsx @@ -54,7 +54,7 @@ const PluginSettings: FC = ({ agentBase }) => { ) return ( - + = ({ agentBase }) => { panel: 'w-full flex-1 overflow-hidden' }}> -
+
{errorAvailable ? ( diff --git a/src/renderer/src/pages/settings/AgentSettings/ToolingSettings.tsx b/src/renderer/src/pages/settings/AgentSettings/ToolingSettings.tsx index f58fae7b6c..d828e921a6 100644 --- a/src/renderer/src/pages/settings/AgentSettings/ToolingSettings.tsx +++ b/src/renderer/src/pages/settings/AgentSettings/ToolingSettings.tsx @@ -168,6 +168,7 @@ export const ToolingSettings: FC = ({ agentBase, upda
), + centered: true, okText: t('common.confirm'), cancelText: t('common.cancel'), onOk: applyChange, @@ -274,9 +275,10 @@ export const ToolingSettings: FC = ({ agentBase, upda key={card.mode} isPressable={!disabled} isDisabled={disabled || isUpdatingMode} + shadow="none" onPress={() => handleSelectPermissionMode(card.mode)} className={`border ${ - isSelected ? 'border-primary shadow-lg' : 'border-default-200' + isSelected ? 'border-primary' : 'border-default-200' } ${disabled ? 'opacity-60' : ''}`}>
diff --git a/src/renderer/src/pages/settings/AgentSettings/components/InstalledPluginsList.tsx b/src/renderer/src/pages/settings/AgentSettings/components/InstalledPluginsList.tsx index 4aa3015a4a..1a6924a6d2 100644 --- a/src/renderer/src/pages/settings/AgentSettings/components/InstalledPluginsList.tsx +++ b/src/renderer/src/pages/settings/AgentSettings/components/InstalledPluginsList.tsx @@ -55,7 +55,7 @@ export const InstalledPluginsList: FC = ({ plugins, o {t('plugins.name')} {t('plugins.type')} {t('plugins.category')} - {t('plugins.actions')} + {t('plugins.actions')} {plugins.map((plugin) => ( diff --git a/src/renderer/src/pages/settings/AgentSettings/components/PluginBrowser.tsx b/src/renderer/src/pages/settings/AgentSettings/components/PluginBrowser.tsx index af9f5d357c..fd106325fb 100644 --- a/src/renderer/src/pages/settings/AgentSettings/components/PluginBrowser.tsx +++ b/src/renderer/src/pages/settings/AgentSettings/components/PluginBrowser.tsx @@ -1,17 +1,7 @@ -import { - Button, - Dropdown, - DropdownItem, - DropdownMenu, - DropdownTrigger, - Input, - Pagination, - Tab, - Tabs -} from '@heroui/react' +import { Button, Dropdown, DropdownItem, DropdownMenu, DropdownTrigger, Input, Tab, Tabs } from '@heroui/react' import { InstalledPlugin, PluginMetadata } from '@renderer/types/plugin' import { Filter, Search } from 'lucide-react' -import { FC, useMemo, useState } from 'react' +import { FC, useEffect, useMemo, useRef, useState } from 'react' import { useTranslation } from 'react-i18next' import { PluginCard } from './PluginCard' @@ -46,10 +36,11 @@ export const PluginBrowser: FC = ({ const [searchQuery, setSearchQuery] = useState('') const [selectedCategories, setSelectedCategories] = useState([]) const [activeType, setActiveType] = useState('all') - const [currentPage, setCurrentPage] = useState(1) + const [displayCount, setDisplayCount] = useState(ITEMS_PER_PAGE) const [actioningPlugin, setActioningPlugin] = useState(null) const [selectedPlugin, setSelectedPlugin] = useState(null) const [isModalOpen, setIsModalOpen] = useState(false) + const observerTarget = useRef(null) // Combine all plugins based on active type const allPlugins = useMemo(() => { @@ -95,14 +86,35 @@ export const PluginBrowser: FC = ({ }) }, [allPlugins, searchQuery, selectedCategories]) - // Paginate filtered plugins - const paginatedPlugins = useMemo(() => { - const startIndex = (currentPage - 1) * ITEMS_PER_PAGE - const endIndex = startIndex + ITEMS_PER_PAGE - return filteredPlugins.slice(startIndex, endIndex) - }, [filteredPlugins, currentPage]) + // Display plugins based on displayCount + const displayedPlugins = useMemo(() => { + return filteredPlugins.slice(0, displayCount) + }, [filteredPlugins, displayCount]) - const totalPages = Math.ceil(filteredPlugins.length / ITEMS_PER_PAGE) + const hasMore = displayCount < filteredPlugins.length + + // Reset display count when filters change + useEffect(() => { + setDisplayCount(ITEMS_PER_PAGE) + }, [filteredPlugins]) + + // Infinite scroll observer + useEffect(() => { + const observer = new IntersectionObserver( + (entries) => { + if (entries[0].isIntersecting && hasMore) { + setDisplayCount((prev) => prev + ITEMS_PER_PAGE) + } + }, + { threshold: 0.1 } + ) + + if (observerTarget.current) { + observer.observe(observerTarget.current) + } + + return () => observer.disconnect() + }, [hasMore]) // Check if a plugin is installed const isPluginInstalled = (plugin: PluginMetadata): boolean => { @@ -125,10 +137,9 @@ export const PluginBrowser: FC = ({ setActioningPlugin(null) } - // Reset to first page when filters change + // Reset display count when filters change const handleSearchChange = (value: string) => { setSearchQuery(value) - setCurrentPage(1) } const handleCategoryChange = (keys: Set) => { @@ -138,12 +149,10 @@ export const PluginBrowser: FC = ({ } else { setSelectedCategories(Array.from(keys).filter((key) => key !== 'all')) } - setCurrentPage(1) } const handleTypeChange = (type: string | number) => { setActiveType(type as PluginType) - setCurrentPage(1) } const handlePluginClick = (plugin: PluginMetadata) => { @@ -159,32 +168,27 @@ export const PluginBrowser: FC = ({ return (
{/* Search and Filter */} -
+
} isClearable - classNames={{ - input: 'text-small', - inputWrapper: 'h-10' - }} + size="md" className="flex-1" - /> - - + inputWrapper: 'pr-12' + }} + /> + @@ -225,12 +229,14 @@ export const PluginBrowser: FC = ({
{/* Type Tabs */} - - - - - - +
+ + + + + + +
{/* Result Count */}
@@ -238,38 +244,35 @@ export const PluginBrowser: FC = ({
{/* Plugin Grid */} - {paginatedPlugins.length === 0 ? ( + {displayedPlugins.length === 0 ? (

{t('plugins.no_results')}

{t('plugins.try_different_search')}

) : ( -
- {paginatedPlugins.map((plugin) => { - const installed = isPluginInstalled(plugin) - const isActioning = actioningPlugin === plugin.sourcePath + <> +
+ {displayedPlugins.map((plugin) => { + const installed = isPluginInstalled(plugin) + const isActioning = actioningPlugin === plugin.sourcePath - return ( -
- handleInstall(plugin)} - onUninstall={() => handleUninstall(plugin)} - loading={loading || isActioning} - onClick={() => handlePluginClick(plugin)} - /> -
- ) - })} -
- )} - - {/* Pagination */} - {totalPages > 1 && ( -
- -
+ return ( +
+ handleInstall(plugin)} + onUninstall={() => handleUninstall(plugin)} + loading={loading || isActioning} + onClick={() => handlePluginClick(plugin)} + /> +
+ ) + })} +
+ {/* Infinite scroll trigger */} + {hasMore &&
} + )} {/* Plugin Detail Modal */} diff --git a/src/renderer/src/pages/settings/AgentSettings/components/PluginCard.tsx b/src/renderer/src/pages/settings/AgentSettings/components/PluginCard.tsx index a82a9e7726..ee94bc0ad7 100644 --- a/src/renderer/src/pages/settings/AgentSettings/components/PluginCard.tsx +++ b/src/renderer/src/pages/settings/AgentSettings/components/PluginCard.tsx @@ -1,5 +1,6 @@ import { Button, Card, CardBody, CardFooter, CardHeader, Chip, Spinner } from '@heroui/react' import { PluginMetadata } from '@renderer/types/plugin' +import { upperFirst } from 'lodash' import { Download, Trash2 } from 'lucide-react' import { FC } from 'react' import { useTranslation } from 'react-i18next' @@ -18,8 +19,9 @@ export const PluginCard: FC = ({ plugin, installed, onInstall, return (
@@ -28,9 +30,8 @@ export const PluginCard: FC = ({ plugin, installed, onInstall, size="sm" variant="solid" color={plugin.type === 'agent' ? 'primary' : plugin.type === 'skill' ? 'success' : 'secondary'} - className="h-4 min-w-0 flex-shrink-0 px-0.5" - style={{ fontSize: '10px' }}> - {plugin.type} + className="h-4 min-w-0 flex-shrink-0 px-0.5 text-xs"> + {upperFirst(plugin.type)}
diff --git a/tsconfig.node.json b/tsconfig.node.json index b6f9061cdf..83c3f2b461 100644 --- a/tsconfig.node.json +++ b/tsconfig.node.json @@ -9,7 +9,8 @@ "packages/shared/**/*", "scripts", "packages/mcp-trace/**/*", - "src/renderer/src/services/traceApi.ts" ], + "src/renderer/src/services/traceApi.ts" + ], "compilerOptions": { "composite": true, "incremental": true,