@@ -26,6 +26,7 @@ import {
2626 Book ,
2727 FileText ,
2828 AppWindow ,
29+ Bot ,
2930 SlidersHorizontal ,
3031 X ,
3132 Info ,
@@ -62,6 +63,26 @@ interface SortOption {
6263// Persist the market filter conditions (type / component / tags / sort) across
6364// visits via localStorage.
6465const MARKET_FILTERS_KEY = 'langbot_market_filters' ;
66+ const MARKET_TYPE_VALUES = [ 'plugin' , 'mcp' , 'skill' ] ;
67+ const MARKET_COMPONENT_VALUES = [
68+ 'Tool' ,
69+ 'Command' ,
70+ 'EventListener' ,
71+ 'KnowledgeEngine' ,
72+ 'Parser' ,
73+ 'Page' ,
74+ 'AgentRunner' ,
75+ ] ;
76+
77+ function getComponentFilterFromQuery (
78+ searchParams : Pick < URLSearchParams , 'get' > ,
79+ ) : string | null {
80+ const component = searchParams . get ( 'component' ) ;
81+ return component && MARKET_COMPONENT_VALUES . includes ( component )
82+ ? component
83+ : null ;
84+ }
85+
6586interface MarketFilters {
6687 typeFilter ?: string ;
6788 componentFilter ?: string ;
@@ -85,9 +106,7 @@ function MarketPageContent({
85106 headerActions ?: React . ReactNode ;
86107} ) {
87108 const { t } = useTranslation ( ) ;
88- const [ searchParams ] = useSearchParams ( ) ;
89-
90- const validTypes = [ 'plugin' , 'mcp' , 'skill' ] ;
109+ const [ searchParams , setSearchParams ] = useSearchParams ( ) ;
91110
92111 const extensionTypeOptions = [
93112 { value : 'all' , label : t ( 'market.filters.allFormats' ) , icon : null } ,
@@ -98,15 +117,21 @@ function MarketPageContent({
98117
99118 const [ searchQuery , setSearchQuery ] = useState ( '' ) ;
100119 const [ componentFilter , setComponentFilter ] = useState < string > (
101- ( ) => loadMarketFilters ( ) . componentFilter ?? 'all' ,
120+ ( ) =>
121+ getComponentFilterFromQuery ( searchParams ) ??
122+ loadMarketFilters ( ) . componentFilter ??
123+ 'all' ,
102124 ) ;
103125 const [ typeFilter , setTypeFilter ] = useState < string > ( ( ) => {
126+ if ( getComponentFilterFromQuery ( searchParams ) ) {
127+ return 'plugin' ;
128+ }
104129 const type = searchParams . get ( 'type' ) ;
105- if ( type && validTypes . includes ( type ) ) {
130+ if ( type && MARKET_TYPE_VALUES . includes ( type ) ) {
106131 return type ;
107132 }
108133 const saved = loadMarketFilters ( ) . typeFilter ;
109- return saved && validTypes . includes ( saved ) ? saved : 'all' ;
134+ return saved && MARKET_TYPE_VALUES . includes ( saved ) ? saved : 'all' ;
110135 } ) ;
111136 const activeAdvancedFilters =
112137 ( typeFilter === 'all' ? 0 : 1 ) + ( componentFilter === 'all' ? 0 : 1 ) ;
@@ -204,6 +229,11 @@ function MarketPageContent({
204229 label : t ( 'market.componentName.Page' ) ,
205230 icon : AppWindow ,
206231 } ,
232+ {
233+ value : 'AgentRunner' ,
234+ label : t ( 'market.componentName.AgentRunner' ) ,
235+ icon : Bot ,
236+ } ,
207237 ] ;
208238
209239 // 获取当前排序参数
@@ -429,44 +459,49 @@ function MarketPageContent({
429459 } , [ ] ) ;
430460
431461 // Handle type filter change
432- const handleTypeFilterChange = useCallback ( ( value : string ) => {
433- setTypeFilter ( value ) ;
434- if ( value !== 'plugin' ) {
435- setComponentFilter ( 'all' ) ;
436- }
437- setCurrentPage ( 1 ) ;
438- setSelectedTags ( [ ] ) ;
439- setPlugins ( [ ] ) ;
440-
441- // Update URL query param to keep it in sync
442- const params = new URLSearchParams ( window . location . search ) ;
443- if ( value === 'all' ) {
444- params . delete ( 'type' ) ;
445- } else {
446- params . set ( 'type' , value ) ;
447- }
448- const newUrl = params . toString ( )
449- ? `${ window . location . pathname } ?${ params . toString ( ) } `
450- : window . location . pathname ;
451- window . history . replaceState ( { } , '' , newUrl ) ;
452- } , [ ] ) ;
462+ const handleTypeFilterChange = useCallback (
463+ ( value : string ) => {
464+ setTypeFilter ( value ) ;
465+ if ( value !== 'plugin' ) {
466+ setComponentFilter ( 'all' ) ;
467+ }
468+ setCurrentPage ( 1 ) ;
469+ setSelectedTags ( [ ] ) ;
470+ setPlugins ( [ ] ) ;
453471
454- const handleComponentFilterChange = useCallback ( ( value : string ) => {
455- setComponentFilter ( value ) ;
456- setCurrentPage ( 1 ) ;
457- setPlugins ( [ ] ) ;
472+ // Update URL query param to keep it in sync
473+ const params = new URLSearchParams ( searchParams ) ;
474+ if ( value === 'all' ) {
475+ params . delete ( 'type' ) ;
476+ } else {
477+ params . set ( 'type' , value ) ;
478+ }
479+ if ( value !== 'plugin' ) {
480+ params . delete ( 'component' ) ;
481+ }
482+ setSearchParams ( params , { replace : true } ) ;
483+ } ,
484+ [ searchParams , setSearchParams ] ,
485+ ) ;
458486
459- if ( value !== 'all' ) {
460- setTypeFilter ( 'plugin' ) ;
487+ const handleComponentFilterChange = useCallback (
488+ ( value : string ) => {
489+ setComponentFilter ( value ) ;
490+ setCurrentPage ( 1 ) ;
491+ setPlugins ( [ ] ) ;
461492
462- const params = new URLSearchParams ( window . location . search ) ;
463- params . set ( 'type' , 'plugin' ) ;
464- const newUrl = params . toString ( )
465- ? `${ window . location . pathname } ?${ params . toString ( ) } `
466- : window . location . pathname ;
467- window . history . replaceState ( { } , '' , newUrl ) ;
468- }
469- } , [ ] ) ;
493+ const params = new URLSearchParams ( searchParams ) ;
494+ if ( value === 'all' ) {
495+ params . delete ( 'component' ) ;
496+ } else {
497+ setTypeFilter ( 'plugin' ) ;
498+ params . set ( 'type' , 'plugin' ) ;
499+ params . set ( 'component' , value ) ;
500+ }
501+ setSearchParams ( params , { replace : true } ) ;
502+ } ,
503+ [ searchParams , setSearchParams ] ,
504+ ) ;
470505
471506 // 当排序选项或组件筛选或类型筛选变化时重新加载数据
472507 useEffect ( ( ) => {
0 commit comments