diff --git a/web/src/app/home/plugins/components/plugin-installed/PluginComponentList.tsx b/web/src/app/home/plugins/components/plugin-installed/PluginComponentList.tsx index e07fd6e40..792b02991 100644 --- a/web/src/app/home/plugins/components/plugin-installed/PluginComponentList.tsx +++ b/web/src/app/home/plugins/components/plugin-installed/PluginComponentList.tsx @@ -6,6 +6,7 @@ import { Book, FileText, PanelTop, + Bot, } from 'lucide-react'; import { Badge } from '@/components/ui/badge'; @@ -31,6 +32,7 @@ export default function PluginComponentList({ KnowledgeEngine: , Parser: , Page: , + AgentRunner: , }; const componentKindList = Object.keys(components || {}); diff --git a/web/src/app/home/plugins/components/plugin-market/PluginComponentList.tsx b/web/src/app/home/plugins/components/plugin-market/PluginComponentList.tsx index 6aba9de03..43647a44c 100644 --- a/web/src/app/home/plugins/components/plugin-market/PluginComponentList.tsx +++ b/web/src/app/home/plugins/components/plugin-market/PluginComponentList.tsx @@ -1,6 +1,14 @@ import { Fragment } from 'react'; import { TFunction } from 'i18next'; -import { Wrench, AudioWaveform, Hash, Book, FileText } from 'lucide-react'; +import { + Wrench, + AudioWaveform, + Hash, + Book, + FileText, + PanelTop, + Bot, +} from 'lucide-react'; import { Badge } from '@/components/ui/badge'; export default function PluginComponentList({ @@ -24,6 +32,8 @@ export default function PluginComponentList({ Command: , KnowledgeEngine: , Parser: , + Page: , + AgentRunner: , }; const componentKindList = Object.keys(components || {}); diff --git a/web/src/app/home/plugins/components/plugin-market/PluginMarketComponent.tsx b/web/src/app/home/plugins/components/plugin-market/PluginMarketComponent.tsx index 1687f3c47..5feda3284 100644 --- a/web/src/app/home/plugins/components/plugin-market/PluginMarketComponent.tsx +++ b/web/src/app/home/plugins/components/plugin-market/PluginMarketComponent.tsx @@ -26,6 +26,7 @@ import { Book, FileText, AppWindow, + Bot, SlidersHorizontal, X, Info, @@ -62,6 +63,26 @@ interface SortOption { // Persist the market filter conditions (type / component / tags / sort) across // visits via localStorage. const MARKET_FILTERS_KEY = 'langbot_market_filters'; +const MARKET_TYPE_VALUES = ['plugin', 'mcp', 'skill']; +const MARKET_COMPONENT_VALUES = [ + 'Tool', + 'Command', + 'EventListener', + 'KnowledgeEngine', + 'Parser', + 'Page', + 'AgentRunner', +]; + +function getComponentFilterFromQuery( + searchParams: Pick, +): string | null { + const component = searchParams.get('component'); + return component && MARKET_COMPONENT_VALUES.includes(component) + ? component + : null; +} + interface MarketFilters { typeFilter?: string; componentFilter?: string; @@ -85,9 +106,7 @@ function MarketPageContent({ headerActions?: React.ReactNode; }) { const { t } = useTranslation(); - const [searchParams] = useSearchParams(); - - const validTypes = ['plugin', 'mcp', 'skill']; + const [searchParams, setSearchParams] = useSearchParams(); const extensionTypeOptions = [ { value: 'all', label: t('market.filters.allFormats'), icon: null }, @@ -98,15 +117,21 @@ function MarketPageContent({ const [searchQuery, setSearchQuery] = useState(''); const [componentFilter, setComponentFilter] = useState( - () => loadMarketFilters().componentFilter ?? 'all', + () => + getComponentFilterFromQuery(searchParams) ?? + loadMarketFilters().componentFilter ?? + 'all', ); const [typeFilter, setTypeFilter] = useState(() => { + if (getComponentFilterFromQuery(searchParams)) { + return 'plugin'; + } const type = searchParams.get('type'); - if (type && validTypes.includes(type)) { + if (type && MARKET_TYPE_VALUES.includes(type)) { return type; } const saved = loadMarketFilters().typeFilter; - return saved && validTypes.includes(saved) ? saved : 'all'; + return saved && MARKET_TYPE_VALUES.includes(saved) ? saved : 'all'; }); const activeAdvancedFilters = (typeFilter === 'all' ? 0 : 1) + (componentFilter === 'all' ? 0 : 1); @@ -204,6 +229,11 @@ function MarketPageContent({ label: t('market.componentName.Page'), icon: AppWindow, }, + { + value: 'AgentRunner', + label: t('market.componentName.AgentRunner'), + icon: Bot, + }, ]; // 获取当前排序参数 @@ -429,44 +459,49 @@ function MarketPageContent({ }, []); // Handle type filter change - const handleTypeFilterChange = useCallback((value: string) => { - setTypeFilter(value); - if (value !== 'plugin') { - setComponentFilter('all'); - } - setCurrentPage(1); - setSelectedTags([]); - setPlugins([]); + const handleTypeFilterChange = useCallback( + (value: string) => { + setTypeFilter(value); + if (value !== 'plugin') { + setComponentFilter('all'); + } + setCurrentPage(1); + setSelectedTags([]); + setPlugins([]); - // Update URL query param to keep it in sync - const params = new URLSearchParams(window.location.search); - if (value === 'all') { - params.delete('type'); - } else { - params.set('type', value); - } - const newUrl = params.toString() - ? `${window.location.pathname}?${params.toString()}` - : window.location.pathname; - window.history.replaceState({}, '', newUrl); - }, []); + // Update URL query param to keep it in sync + const params = new URLSearchParams(searchParams); + if (value === 'all') { + params.delete('type'); + } else { + params.set('type', value); + } + if (value !== 'plugin') { + params.delete('component'); + } + setSearchParams(params, { replace: true }); + }, + [searchParams, setSearchParams], + ); - const handleComponentFilterChange = useCallback((value: string) => { - setComponentFilter(value); - setCurrentPage(1); - setPlugins([]); + const handleComponentFilterChange = useCallback( + (value: string) => { + setComponentFilter(value); + setCurrentPage(1); + setPlugins([]); - if (value !== 'all') { - setTypeFilter('plugin'); - - const params = new URLSearchParams(window.location.search); - params.set('type', 'plugin'); - const newUrl = params.toString() - ? `${window.location.pathname}?${params.toString()}` - : window.location.pathname; - window.history.replaceState({}, '', newUrl); - } - }, []); + const params = new URLSearchParams(searchParams); + if (value === 'all') { + params.delete('component'); + } else { + setTypeFilter('plugin'); + params.set('type', 'plugin'); + params.set('component', value); + } + setSearchParams(params, { replace: true }); + }, + [searchParams, setSearchParams], + ); // 当排序选项或组件筛选或类型筛选变化时重新加载数据 useEffect(() => {