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(() => {