feat(market): support AgentRunner component filters

This commit is contained in:
Junyan Qin
2026-07-11 18:04:42 +08:00
committed by huanghuoguoguo
parent 11bd5c6fd7
commit 29689962f3
3 changed files with 89 additions and 42 deletions
@@ -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: <Book className="w-5 h-5" />,
Parser: <FileText className="w-5 h-5" />,
Page: <PanelTop className="w-5 h-5" />,
AgentRunner: <Bot className="w-5 h-5" />,
};
const componentKindList = Object.keys(components || {});
@@ -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: <Hash className="w-5 h-5" />,
KnowledgeEngine: <Book className="w-5 h-5" />,
Parser: <FileText className="w-5 h-5" />,
Page: <PanelTop className="w-5 h-5" />,
AgentRunner: <Bot className="w-5 h-5" />,
};
const componentKindList = Object.keys(components || {});
@@ -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<URLSearchParams, 'get'>,
): 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<string>(
() => loadMarketFilters().componentFilter ?? 'all',
() =>
getComponentFilterFromQuery(searchParams) ??
loadMarketFilters().componentFilter ??
'all',
);
const [typeFilter, setTypeFilter] = useState<string>(() => {
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(() => {