mirror of
https://github.com/langbot-app/LangBot.git
synced 2026-06-02 03:55:55 +00:00
feat(web): show recommendation lists in plugin market; mixed-type icons
The marketplace recommendation lists (curated rows from Space) were never mounted in the plugin market page. Wire them in: - fetch recommendation lists on mount and render them above the extension grid, only when no search/filter is active. Recommendation lists now mix plugins, MCPs and skills, so resolve each card's icon by type (plugin / mcp / skill marketplace icon URL) instead of always using the plugin icon endpoint. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -27,6 +27,8 @@ import {
|
|||||||
} from 'lucide-react';
|
} from 'lucide-react';
|
||||||
import PluginMarketCardComponent from './plugin-market-card/PluginMarketCardComponent';
|
import PluginMarketCardComponent from './plugin-market-card/PluginMarketCardComponent';
|
||||||
import { PluginMarketCardVO } from './plugin-market-card/PluginMarketCardVO';
|
import { PluginMarketCardVO } from './plugin-market-card/PluginMarketCardVO';
|
||||||
|
import { RecommendationLists } from './RecommendationLists';
|
||||||
|
import type { RecommendationList } from './RecommendationLists';
|
||||||
import { getCloudServiceClientSync } from '@/app/infra/http';
|
import { getCloudServiceClientSync } from '@/app/infra/http';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import { PluginV4, PluginV4Status } from '@/app/infra/entities/plugin';
|
import { PluginV4, PluginV4Status } from '@/app/infra/entities/plugin';
|
||||||
@@ -78,6 +80,9 @@ function MarketPageContent({
|
|||||||
const [selectedTags, setSelectedTags] = useState<string[]>([]);
|
const [selectedTags, setSelectedTags] = useState<string[]>([]);
|
||||||
const [availableTags, setAvailableTags] = useState<PluginTag[]>([]);
|
const [availableTags, setAvailableTags] = useState<PluginTag[]>([]);
|
||||||
const [tagNames, setTagNames] = useState<Record<string, string>>({});
|
const [tagNames, setTagNames] = useState<Record<string, string>>({});
|
||||||
|
const [recommendationLists, setRecommendationLists] = useState<
|
||||||
|
RecommendationList[]
|
||||||
|
>([]);
|
||||||
const [plugins, setPlugins] = useState<PluginMarketCardVO[]>([]);
|
const [plugins, setPlugins] = useState<PluginMarketCardVO[]>([]);
|
||||||
const [isLoading, setIsLoading] = useState(false);
|
const [isLoading, setIsLoading] = useState(false);
|
||||||
const [isLoadingMore, setIsLoadingMore] = useState(false);
|
const [isLoadingMore, setIsLoadingMore] = useState(false);
|
||||||
@@ -250,9 +255,21 @@ function MarketPageContent({
|
|||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
fetchPlugins(1, false, true);
|
fetchPlugins(1, false, true);
|
||||||
fetchAvailableTags();
|
fetchAvailableTags();
|
||||||
|
fetchRecommendationLists();
|
||||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
|
// 获取推荐列表(精选,混合插件/MCP/Skill)
|
||||||
|
const fetchRecommendationLists = async () => {
|
||||||
|
try {
|
||||||
|
const { lists } =
|
||||||
|
await getCloudServiceClientSync().getRecommendationLists();
|
||||||
|
setRecommendationLists(lists || []);
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Failed to fetch recommendation lists:', error);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
// 获取可用标签
|
// 获取可用标签
|
||||||
const fetchAvailableTags = async () => {
|
const fetchAvailableTags = async () => {
|
||||||
try {
|
try {
|
||||||
@@ -701,6 +718,18 @@ function MarketPageContent({
|
|||||||
ref={scrollContainerRef}
|
ref={scrollContainerRef}
|
||||||
className="flex-1 overflow-y-auto px-3 sm:px-4 pb-6 container mx-auto"
|
className="flex-1 overflow-y-auto px-3 sm:px-4 pb-6 container mx-auto"
|
||||||
>
|
>
|
||||||
|
{/* 推荐列表(仅在无搜索/筛选时展示,混合插件/MCP/Skill) */}
|
||||||
|
{!searchQuery &&
|
||||||
|
typeFilter === 'all' &&
|
||||||
|
componentFilter === 'all' &&
|
||||||
|
selectedTags.length === 0 && (
|
||||||
|
<RecommendationLists
|
||||||
|
lists={recommendationLists}
|
||||||
|
tagNames={tagNames}
|
||||||
|
onInstall={handleInstallPlugin}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
|
||||||
{isLoading ? (
|
{isLoading ? (
|
||||||
<div className="flex items-center justify-center py-12">
|
<div className="flex items-center justify-center py-12">
|
||||||
<LoadingSpinner text={t('market.loading')} />
|
<LoadingSpinner text={t('market.loading')} />
|
||||||
|
|||||||
@@ -22,6 +22,15 @@ function pluginToVO(
|
|||||||
plugin: PluginV4,
|
plugin: PluginV4,
|
||||||
t: (key: string) => string,
|
t: (key: string) => string,
|
||||||
): PluginMarketCardVO {
|
): PluginMarketCardVO {
|
||||||
|
const cloudClient = getCloudServiceClientSync();
|
||||||
|
// Recommendation lists are mixed-type; resolve the icon per extension type.
|
||||||
|
const iconURL =
|
||||||
|
plugin.type === 'mcp'
|
||||||
|
? cloudClient.getMCPMarketplaceIconURL(plugin.author, plugin.name)
|
||||||
|
: plugin.type === 'skill'
|
||||||
|
? cloudClient.getSkillMarketplaceIconURL(plugin.author, plugin.name)
|
||||||
|
: cloudClient.getPluginIconURL(plugin.author, plugin.name);
|
||||||
|
|
||||||
return new PluginMarketCardVO({
|
return new PluginMarketCardVO({
|
||||||
pluginId: plugin.author + ' / ' + plugin.name,
|
pluginId: plugin.author + ' / ' + plugin.name,
|
||||||
author: plugin.author,
|
author: plugin.author,
|
||||||
@@ -30,10 +39,7 @@ function pluginToVO(
|
|||||||
description:
|
description:
|
||||||
extractI18nObject(plugin.description) || t('market.noDescription'),
|
extractI18nObject(plugin.description) || t('market.noDescription'),
|
||||||
installCount: plugin.install_count,
|
installCount: plugin.install_count,
|
||||||
iconURL: getCloudServiceClientSync().getPluginIconURL(
|
iconURL,
|
||||||
plugin.author,
|
|
||||||
plugin.name,
|
|
||||||
),
|
|
||||||
githubURL: plugin.repository,
|
githubURL: plugin.repository,
|
||||||
version: plugin.latest_version,
|
version: plugin.latest_version,
|
||||||
components: plugin.components,
|
components: plugin.components,
|
||||||
|
|||||||
Reference in New Issue
Block a user