mirror of
https://github.com/langbot-app/LangBot.git
synced 2026-07-18 10:26:07 +00:00
feat(market): support AgentRunner component filters
This commit is contained in:
committed by
huanghuoguoguo
parent
11bd5c6fd7
commit
29689962f3
@@ -6,6 +6,7 @@ import {
|
|||||||
Book,
|
Book,
|
||||||
FileText,
|
FileText,
|
||||||
PanelTop,
|
PanelTop,
|
||||||
|
Bot,
|
||||||
} from 'lucide-react';
|
} from 'lucide-react';
|
||||||
import { Badge } from '@/components/ui/badge';
|
import { Badge } from '@/components/ui/badge';
|
||||||
|
|
||||||
@@ -31,6 +32,7 @@ export default function PluginComponentList({
|
|||||||
KnowledgeEngine: <Book className="w-5 h-5" />,
|
KnowledgeEngine: <Book className="w-5 h-5" />,
|
||||||
Parser: <FileText className="w-5 h-5" />,
|
Parser: <FileText className="w-5 h-5" />,
|
||||||
Page: <PanelTop className="w-5 h-5" />,
|
Page: <PanelTop className="w-5 h-5" />,
|
||||||
|
AgentRunner: <Bot className="w-5 h-5" />,
|
||||||
};
|
};
|
||||||
|
|
||||||
const componentKindList = Object.keys(components || {});
|
const componentKindList = Object.keys(components || {});
|
||||||
|
|||||||
@@ -1,6 +1,14 @@
|
|||||||
import { Fragment } from 'react';
|
import { Fragment } from 'react';
|
||||||
import { TFunction } from 'i18next';
|
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';
|
import { Badge } from '@/components/ui/badge';
|
||||||
|
|
||||||
export default function PluginComponentList({
|
export default function PluginComponentList({
|
||||||
@@ -24,6 +32,8 @@ export default function PluginComponentList({
|
|||||||
Command: <Hash className="w-5 h-5" />,
|
Command: <Hash className="w-5 h-5" />,
|
||||||
KnowledgeEngine: <Book className="w-5 h-5" />,
|
KnowledgeEngine: <Book className="w-5 h-5" />,
|
||||||
Parser: <FileText 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 || {});
|
const componentKindList = Object.keys(components || {});
|
||||||
|
|||||||
@@ -26,6 +26,7 @@ import {
|
|||||||
Book,
|
Book,
|
||||||
FileText,
|
FileText,
|
||||||
AppWindow,
|
AppWindow,
|
||||||
|
Bot,
|
||||||
SlidersHorizontal,
|
SlidersHorizontal,
|
||||||
X,
|
X,
|
||||||
Info,
|
Info,
|
||||||
@@ -62,6 +63,26 @@ interface SortOption {
|
|||||||
// Persist the market filter conditions (type / component / tags / sort) across
|
// Persist the market filter conditions (type / component / tags / sort) across
|
||||||
// visits via localStorage.
|
// visits via localStorage.
|
||||||
const MARKET_FILTERS_KEY = 'langbot_market_filters';
|
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 {
|
interface MarketFilters {
|
||||||
typeFilter?: string;
|
typeFilter?: string;
|
||||||
componentFilter?: string;
|
componentFilter?: string;
|
||||||
@@ -85,9 +106,7 @@ function MarketPageContent({
|
|||||||
headerActions?: React.ReactNode;
|
headerActions?: React.ReactNode;
|
||||||
}) {
|
}) {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const [searchParams] = useSearchParams();
|
const [searchParams, setSearchParams] = useSearchParams();
|
||||||
|
|
||||||
const validTypes = ['plugin', 'mcp', 'skill'];
|
|
||||||
|
|
||||||
const extensionTypeOptions = [
|
const extensionTypeOptions = [
|
||||||
{ value: 'all', label: t('market.filters.allFormats'), icon: null },
|
{ value: 'all', label: t('market.filters.allFormats'), icon: null },
|
||||||
@@ -98,15 +117,21 @@ function MarketPageContent({
|
|||||||
|
|
||||||
const [searchQuery, setSearchQuery] = useState('');
|
const [searchQuery, setSearchQuery] = useState('');
|
||||||
const [componentFilter, setComponentFilter] = useState<string>(
|
const [componentFilter, setComponentFilter] = useState<string>(
|
||||||
() => loadMarketFilters().componentFilter ?? 'all',
|
() =>
|
||||||
|
getComponentFilterFromQuery(searchParams) ??
|
||||||
|
loadMarketFilters().componentFilter ??
|
||||||
|
'all',
|
||||||
);
|
);
|
||||||
const [typeFilter, setTypeFilter] = useState<string>(() => {
|
const [typeFilter, setTypeFilter] = useState<string>(() => {
|
||||||
|
if (getComponentFilterFromQuery(searchParams)) {
|
||||||
|
return 'plugin';
|
||||||
|
}
|
||||||
const type = searchParams.get('type');
|
const type = searchParams.get('type');
|
||||||
if (type && validTypes.includes(type)) {
|
if (type && MARKET_TYPE_VALUES.includes(type)) {
|
||||||
return type;
|
return type;
|
||||||
}
|
}
|
||||||
const saved = loadMarketFilters().typeFilter;
|
const saved = loadMarketFilters().typeFilter;
|
||||||
return saved && validTypes.includes(saved) ? saved : 'all';
|
return saved && MARKET_TYPE_VALUES.includes(saved) ? saved : 'all';
|
||||||
});
|
});
|
||||||
const activeAdvancedFilters =
|
const activeAdvancedFilters =
|
||||||
(typeFilter === 'all' ? 0 : 1) + (componentFilter === 'all' ? 0 : 1);
|
(typeFilter === 'all' ? 0 : 1) + (componentFilter === 'all' ? 0 : 1);
|
||||||
@@ -204,6 +229,11 @@ function MarketPageContent({
|
|||||||
label: t('market.componentName.Page'),
|
label: t('market.componentName.Page'),
|
||||||
icon: AppWindow,
|
icon: AppWindow,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
value: 'AgentRunner',
|
||||||
|
label: t('market.componentName.AgentRunner'),
|
||||||
|
icon: Bot,
|
||||||
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
// 获取当前排序参数
|
// 获取当前排序参数
|
||||||
@@ -429,44 +459,49 @@ function MarketPageContent({
|
|||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
// Handle type filter change
|
// Handle type filter change
|
||||||
const handleTypeFilterChange = useCallback((value: string) => {
|
const handleTypeFilterChange = useCallback(
|
||||||
setTypeFilter(value);
|
(value: string) => {
|
||||||
if (value !== 'plugin') {
|
setTypeFilter(value);
|
||||||
setComponentFilter('all');
|
if (value !== 'plugin') {
|
||||||
}
|
setComponentFilter('all');
|
||||||
setCurrentPage(1);
|
}
|
||||||
setSelectedTags([]);
|
setCurrentPage(1);
|
||||||
setPlugins([]);
|
setSelectedTags([]);
|
||||||
|
setPlugins([]);
|
||||||
|
|
||||||
// Update URL query param to keep it in sync
|
// Update URL query param to keep it in sync
|
||||||
const params = new URLSearchParams(window.location.search);
|
const params = new URLSearchParams(searchParams);
|
||||||
if (value === 'all') {
|
if (value === 'all') {
|
||||||
params.delete('type');
|
params.delete('type');
|
||||||
} else {
|
} else {
|
||||||
params.set('type', value);
|
params.set('type', value);
|
||||||
}
|
}
|
||||||
const newUrl = params.toString()
|
if (value !== 'plugin') {
|
||||||
? `${window.location.pathname}?${params.toString()}`
|
params.delete('component');
|
||||||
: window.location.pathname;
|
}
|
||||||
window.history.replaceState({}, '', newUrl);
|
setSearchParams(params, { replace: true });
|
||||||
}, []);
|
},
|
||||||
|
[searchParams, setSearchParams],
|
||||||
|
);
|
||||||
|
|
||||||
const handleComponentFilterChange = useCallback((value: string) => {
|
const handleComponentFilterChange = useCallback(
|
||||||
setComponentFilter(value);
|
(value: string) => {
|
||||||
setCurrentPage(1);
|
setComponentFilter(value);
|
||||||
setPlugins([]);
|
setCurrentPage(1);
|
||||||
|
setPlugins([]);
|
||||||
|
|
||||||
if (value !== 'all') {
|
const params = new URLSearchParams(searchParams);
|
||||||
setTypeFilter('plugin');
|
if (value === 'all') {
|
||||||
|
params.delete('component');
|
||||||
const params = new URLSearchParams(window.location.search);
|
} else {
|
||||||
params.set('type', 'plugin');
|
setTypeFilter('plugin');
|
||||||
const newUrl = params.toString()
|
params.set('type', 'plugin');
|
||||||
? `${window.location.pathname}?${params.toString()}`
|
params.set('component', value);
|
||||||
: window.location.pathname;
|
}
|
||||||
window.history.replaceState({}, '', newUrl);
|
setSearchParams(params, { replace: true });
|
||||||
}
|
},
|
||||||
}, []);
|
[searchParams, setSearchParams],
|
||||||
|
);
|
||||||
|
|
||||||
// 当排序选项或组件筛选或类型筛选变化时重新加载数据
|
// 当排序选项或组件筛选或类型筛选变化时重新加载数据
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
|||||||
Reference in New Issue
Block a user