diff --git a/src/i18n/locales/ca/marketplace.json b/src/i18n/locales/ca/marketplace.json index 6c64374447c..fe345443526 100644 --- a/src/i18n/locales/ca/marketplace.json +++ b/src/i18n/locales/ca/marketplace.json @@ -38,6 +38,10 @@ "noResults": "No s'han trobat etiquetes.", "selected": "Mostrant elements amb qualsevol de les etiquetes seleccionades" }, + "installed": { + "label": "Mostrar només instal·lats", + "description": "Mostrant només elements instal·lats" + }, "title": "Marketplace" }, "done": "Fet", diff --git a/src/i18n/locales/de/marketplace.json b/src/i18n/locales/de/marketplace.json index 2981441cf07..0cd53cd1658 100644 --- a/src/i18n/locales/de/marketplace.json +++ b/src/i18n/locales/de/marketplace.json @@ -38,6 +38,10 @@ "noResults": "Keine Tags gefunden.", "selected": "Zeige Elemente mit einem der ausgewählten Tags" }, + "installed": { + "label": "Nur installierte anzeigen", + "description": "Zeige nur installierte Elemente" + }, "title": "Marketplace" }, "done": "Fertig", diff --git a/src/i18n/locales/en/marketplace.json b/src/i18n/locales/en/marketplace.json index f141aaeb057..2b44c939356 100644 --- a/src/i18n/locales/en/marketplace.json +++ b/src/i18n/locales/en/marketplace.json @@ -38,6 +38,10 @@ "noResults": "No tags found.", "selected": "Showing items with any of the selected tags" }, + "installed": { + "label": "Show installed only", + "description": "Showing only installed items" + }, "title": "Marketplace" }, "done": "Done", diff --git a/src/i18n/locales/es/marketplace.json b/src/i18n/locales/es/marketplace.json index e12e1d1dc11..00e9770b340 100644 --- a/src/i18n/locales/es/marketplace.json +++ b/src/i18n/locales/es/marketplace.json @@ -38,6 +38,10 @@ "noResults": "No se encontraron etiquetas.", "selected": "Mostrando elementos con cualquiera de las etiquetas seleccionadas" }, + "installed": { + "label": "Mostrar solo instalados", + "description": "Mostrando solo elementos instalados" + }, "title": "Marketplace" }, "done": "Hecho", diff --git a/src/i18n/locales/fr/marketplace.json b/src/i18n/locales/fr/marketplace.json index 7a42b0033e4..ff53ee1f57c 100644 --- a/src/i18n/locales/fr/marketplace.json +++ b/src/i18n/locales/fr/marketplace.json @@ -38,6 +38,10 @@ "noResults": "Aucune étiquette trouvée.", "selected": "Affichage des éléments avec l'une des étiquettes sélectionnées" }, + "installed": { + "label": "Afficher uniquement les installés", + "description": "Affichage uniquement des éléments installés" + }, "title": "Marketplace" }, "done": "Terminé", diff --git a/src/i18n/locales/hi/marketplace.json b/src/i18n/locales/hi/marketplace.json index 94013c20e40..37c0e7c346a 100644 --- a/src/i18n/locales/hi/marketplace.json +++ b/src/i18n/locales/hi/marketplace.json @@ -38,6 +38,10 @@ "noResults": "कोई टैग नहीं मिले।", "selected": "चयनित टैग्स में से किसी भी के साथ आइटम दिखा रहे हैं" }, + "installed": { + "label": "केवल इंस्टॉल किए गए दिखाएं", + "description": "केवल इंस्टॉल किए गए आइटम दिखा रहे हैं" + }, "title": "मार्केटप्लेस" }, "done": "हो गया", diff --git a/src/i18n/locales/id/marketplace.json b/src/i18n/locales/id/marketplace.json index 77d93973a91..f4b03678b18 100644 --- a/src/i18n/locales/id/marketplace.json +++ b/src/i18n/locales/id/marketplace.json @@ -38,6 +38,10 @@ "noResults": "Tidak ada tag ditemukan.", "selected": "Menampilkan item dengan salah satu tag yang dipilih" }, + "installed": { + "label": "Tampilkan hanya yang terinstal", + "description": "Menampilkan hanya item yang terinstal" + }, "title": "Marketplace" }, "done": "Selesai", diff --git a/src/i18n/locales/it/marketplace.json b/src/i18n/locales/it/marketplace.json index 3cdcd2b76cf..746f1fb1c36 100644 --- a/src/i18n/locales/it/marketplace.json +++ b/src/i18n/locales/it/marketplace.json @@ -38,6 +38,10 @@ "noResults": "Nessun tag trovato.", "selected": "Mostrando elementi con uno qualsiasi dei tag selezionati" }, + "installed": { + "label": "Mostra solo installati", + "description": "Mostrando solo elementi installati" + }, "title": "Marketplace" }, "done": "Fatto", diff --git a/src/i18n/locales/ja/marketplace.json b/src/i18n/locales/ja/marketplace.json index 26cff2ade9a..26f97dd421f 100644 --- a/src/i18n/locales/ja/marketplace.json +++ b/src/i18n/locales/ja/marketplace.json @@ -38,6 +38,10 @@ "noResults": "タグが見つかりません。", "selected": "選択されたタグのいずれかを持つアイテムを表示" }, + "installed": { + "label": "インストール済みのみ表示", + "description": "インストール済みのアイテムのみを表示" + }, "title": "マーケットプレイス" }, "done": "完了", diff --git a/src/i18n/locales/ko/marketplace.json b/src/i18n/locales/ko/marketplace.json index 52bd03edf71..5c2a5289968 100644 --- a/src/i18n/locales/ko/marketplace.json +++ b/src/i18n/locales/ko/marketplace.json @@ -38,6 +38,10 @@ "noResults": "태그를 찾을 수 없습니다.", "selected": "선택된 태그 중 하나를 가진 항목 표시" }, + "installed": { + "label": "설치된 항목만 표시", + "description": "설치된 항목만 표시 중" + }, "title": "마켓플레이스" }, "done": "완료", diff --git a/src/i18n/locales/nl/marketplace.json b/src/i18n/locales/nl/marketplace.json index 5628b8f628e..5c458658cac 100644 --- a/src/i18n/locales/nl/marketplace.json +++ b/src/i18n/locales/nl/marketplace.json @@ -38,6 +38,10 @@ "noResults": "Geen tags gevonden.", "selected": "Items tonen met een van de geselecteerde tags" }, + "installed": { + "label": "Alleen geïnstalleerde tonen", + "description": "Alleen geïnstalleerde items tonen" + }, "title": "Marketplace" }, "done": "Klaar", diff --git a/src/i18n/locales/pl/marketplace.json b/src/i18n/locales/pl/marketplace.json index 029dbd95a3a..3d21126d281 100644 --- a/src/i18n/locales/pl/marketplace.json +++ b/src/i18n/locales/pl/marketplace.json @@ -38,6 +38,10 @@ "noResults": "Nie znaleziono tagów.", "selected": "Pokazywanie elementów z dowolnym z wybranych tagów" }, + "installed": { + "label": "Pokaż tylko zainstalowane", + "description": "Pokazywanie tylko zainstalowanych elementów" + }, "title": "Marketplace" }, "done": "Gotowe", diff --git a/src/i18n/locales/pt-BR/marketplace.json b/src/i18n/locales/pt-BR/marketplace.json index b0af0138888..d16a1658877 100644 --- a/src/i18n/locales/pt-BR/marketplace.json +++ b/src/i18n/locales/pt-BR/marketplace.json @@ -38,6 +38,10 @@ "noResults": "Nenhuma tag encontrada.", "selected": "Mostrando itens com qualquer uma das tags selecionadas" }, + "installed": { + "label": "Mostrar apenas instalados", + "description": "Mostrando apenas itens instalados" + }, "title": "Marketplace" }, "done": "Concluído", diff --git a/src/i18n/locales/ru/marketplace.json b/src/i18n/locales/ru/marketplace.json index a84b1ce3e9c..b4587968b70 100644 --- a/src/i18n/locales/ru/marketplace.json +++ b/src/i18n/locales/ru/marketplace.json @@ -38,6 +38,10 @@ "noResults": "Теги не найдены.", "selected": "Показ элементов с любым из выбранных тегов" }, + "installed": { + "label": "Показать только установленные", + "description": "Показываются только установленные элементы" + }, "title": "Marketplace" }, "done": "Готово", diff --git a/src/i18n/locales/tr/marketplace.json b/src/i18n/locales/tr/marketplace.json index b08381d71c0..4400f55363c 100644 --- a/src/i18n/locales/tr/marketplace.json +++ b/src/i18n/locales/tr/marketplace.json @@ -38,6 +38,10 @@ "noResults": "Etiket bulunamadı.", "selected": "Seçilen etiketlerden herhangi birine sahip öğeleri göster" }, + "installed": { + "label": "Sadece yüklüleri göster", + "description": "Sadece yüklü öğeler gösteriliyor" + }, "title": "Marketplace" }, "done": "Tamam", diff --git a/src/i18n/locales/vi/marketplace.json b/src/i18n/locales/vi/marketplace.json index be390543098..1fea9213398 100644 --- a/src/i18n/locales/vi/marketplace.json +++ b/src/i18n/locales/vi/marketplace.json @@ -38,6 +38,10 @@ "noResults": "Không tìm thấy thẻ nào.", "selected": "Hiển thị các mục có bất kỳ thẻ nào được chọn" }, + "installed": { + "label": "Chỉ hiển thị đã cài đặt", + "description": "Chỉ hiển thị các mục đã cài đặt" + }, "title": "Marketplace" }, "done": "Hoàn thành", diff --git a/src/i18n/locales/zh-CN/marketplace.json b/src/i18n/locales/zh-CN/marketplace.json index 50a2ade6350..f1c10ad1c65 100644 --- a/src/i18n/locales/zh-CN/marketplace.json +++ b/src/i18n/locales/zh-CN/marketplace.json @@ -38,6 +38,10 @@ "noResults": "未找到标签。", "selected": "显示包含任一选中标签的项目" }, + "installed": { + "label": "仅显示已安装", + "description": "仅显示已安装的项目" + }, "title": "Marketplace" }, "done": "完成", diff --git a/src/i18n/locales/zh-TW/marketplace.json b/src/i18n/locales/zh-TW/marketplace.json index 3eaeb0e7e00..eb9bd915463 100644 --- a/src/i18n/locales/zh-TW/marketplace.json +++ b/src/i18n/locales/zh-TW/marketplace.json @@ -38,6 +38,10 @@ "noResults": "找不到標籤。", "selected": "顯示包含任一選取標籤的項目" }, + "installed": { + "label": "僅顯示已安裝", + "description": "僅顯示已安裝的項目" + }, "title": "Marketplace" }, "done": "完成", diff --git a/webview-ui/src/components/marketplace/MarketplaceListView.tsx b/webview-ui/src/components/marketplace/MarketplaceListView.tsx index 8ec13be8e57..02085dbf4e0 100644 --- a/webview-ui/src/components/marketplace/MarketplaceListView.tsx +++ b/webview-ui/src/components/marketplace/MarketplaceListView.tsx @@ -27,6 +27,17 @@ export function MarketplaceListView({ stateManager, allTags, filteredTags, filte const allItems = state.displayItems || [] const organizationMcps = state.displayOrganizationMcps || [] + // Update state manager with installed metadata when it changes + React.useEffect(() => { + if (marketplaceInstalledMetadata && state.installedMetadata !== marketplaceInstalledMetadata) { + // Update the state manager's installed metadata + manager.transition({ + type: "UPDATE_FILTERS", + payload: { filters: state.filters }, + }) + } + }, [marketplaceInstalledMetadata, state.installedMetadata, state.filters, manager]) + // Filter items by type if specified const items = filterByType ? allItems.filter((item) => item.type === filterByType) : allItems const orgMcps = filterByType === "mcp" ? organizationMcps : [] @@ -55,6 +66,28 @@ export function MarketplaceListView({ stateManager, allTags, filteredTags, filte } /> + {/* Installed filter toggle */} +
+ + {state.filters.installed && ( + + ({t("marketplace:filters.installed.description")}) + + )} +
{allTags.length > 0 && (
@@ -187,7 +220,7 @@ export function MarketplaceListView({ stateManager, allTags, filteredTags, filte onClick={() => manager.transition({ type: "UPDATE_FILTERS", - payload: { filters: { search: "", type: "", tags: [] } }, + payload: { filters: { search: "", type: "", tags: [], installed: false } }, }) } className="mt-4 bg-vscode-button-secondaryBackground text-vscode-button-secondaryForeground hover:bg-vscode-button-secondaryHoverBackground transition-colors"> diff --git a/webview-ui/src/components/marketplace/MarketplaceViewStateManager.ts b/webview-ui/src/components/marketplace/MarketplaceViewStateManager.ts index 104f3e7cad2..ae018b75bc5 100644 --- a/webview-ui/src/components/marketplace/MarketplaceViewStateManager.ts +++ b/webview-ui/src/components/marketplace/MarketplaceViewStateManager.ts @@ -26,7 +26,9 @@ export interface ViewState { type: string search: string tags: string[] + installed: boolean // New filter to show only installed items } + installedMetadata?: any // Store installed metadata for filtering } type TransitionPayloads = { @@ -65,6 +67,7 @@ export class MarketplaceViewStateManager { type: "", search: "", tags: [], + installed: false, }, } } @@ -189,8 +192,11 @@ export class MarketplaceViewStateManager { let newDisplayItems: MarketplaceItem[] let newDisplayOrganizationMcps: MarketplaceItem[] if (this.isFilterActive()) { - newDisplayItems = this.filterItems([...items]) - newDisplayOrganizationMcps = this.filterItems([...this.state.organizationMcps]) + newDisplayItems = this.filterItems([...items], this.state.installedMetadata) + newDisplayOrganizationMcps = this.filterItems( + [...this.state.organizationMcps], + this.state.installedMetadata, + ) } else { // No filters active - show all items newDisplayItems = [...items] @@ -251,6 +257,7 @@ export class MarketplaceViewStateManager { type: filters.type !== undefined ? filters.type : this.state.filters.type, search: filters.search !== undefined ? filters.search : this.state.filters.search, tags: filters.tags !== undefined ? filters.tags : this.state.filters.tags, + installed: filters.installed !== undefined ? filters.installed : this.state.filters.installed, } // Update filters first @@ -260,8 +267,11 @@ export class MarketplaceViewStateManager { } // Apply filters to displayItems and displayOrganizationMcps with the updated filters - const newDisplayItems = this.filterItems(this.state.allItems) - const newDisplayOrganizationMcps = this.filterItems(this.state.organizationMcps) + const newDisplayItems = this.filterItems(this.state.allItems, this.state.installedMetadata) + const newDisplayOrganizationMcps = this.filterItems( + this.state.organizationMcps, + this.state.installedMetadata, + ) // Update state with filtered items this.state = { @@ -284,11 +294,16 @@ export class MarketplaceViewStateManager { } public isFilterActive(): boolean { - return !!(this.state.filters.type || this.state.filters.search || this.state.filters.tags.length > 0) + return !!( + this.state.filters.type || + this.state.filters.search || + this.state.filters.tags.length > 0 || + this.state.filters.installed + ) } - public filterItems(items: MarketplaceItem[]): MarketplaceItem[] { - const { type, search, tags } = this.state.filters + public filterItems(items: MarketplaceItem[], installedMetadata?: any): MarketplaceItem[] { + const { type, search, tags, installed } = this.state.filters return items .map((item) => { @@ -303,9 +318,20 @@ export class MarketplaceViewStateManager { : false const tagMatch = tags.length > 0 ? item.tags?.some((tag) => tags.includes(tag)) : false + // Check installed status if filter is active + let installedMatch = true + if (installed && installedMetadata) { + const isInstalledGlobally = !!installedMetadata?.global?.[item.id] + const isInstalledInProject = !!installedMetadata?.project?.[item.id] + installedMatch = isInstalledGlobally || isInstalledInProject + } + // Determine if the main item matches all filters const mainItemMatches = - typeMatch && (!search || nameMatch || descriptionMatch) && (!tags.length || tagMatch) + typeMatch && + (!search || nameMatch || descriptionMatch) && + (!tags.length || tagMatch) && + installedMatch const hasMatchingSubcomponents = false @@ -343,20 +369,29 @@ export class MarketplaceViewStateManager { // Handle state updates for marketplace items // The state.marketplaceItems come from ClineProvider, see the file src/core/webview/ClineProvider.ts const marketplaceItems = message.state.marketplaceItems + const marketplaceInstalledMetadata = message.state.marketplaceInstalledMetadata if (marketplaceItems !== undefined) { // Always use the marketplace items from the extension when they're provided // This ensures fresh data is always displayed const items = [...marketplaceItems] + // Update installed metadata if provided + if (marketplaceInstalledMetadata !== undefined) { + this.state.installedMetadata = marketplaceInstalledMetadata + } + // Calculate display items based on current filters // If no filters are active, show all items // If filters are active, apply filtering let newDisplayItems: MarketplaceItem[] let newDisplayOrganizationMcps: MarketplaceItem[] if (this.isFilterActive()) { - newDisplayItems = this.filterItems(items) - newDisplayOrganizationMcps = this.filterItems(this.state.organizationMcps) + newDisplayItems = this.filterItems(items, this.state.installedMetadata) + newDisplayOrganizationMcps = this.filterItems( + this.state.organizationMcps, + this.state.installedMetadata, + ) } else { // No filters active - show all items newDisplayItems = items @@ -370,6 +405,7 @@ export class MarketplaceViewStateManager { allItems: items, displayItems: newDisplayItems, displayOrganizationMcps: newDisplayOrganizationMcps, + installedMetadata: marketplaceInstalledMetadata || this.state.installedMetadata, } // Notification is handled below after all state parts are processed } @@ -411,14 +447,25 @@ export class MarketplaceViewStateManager { if (message.type === "marketplaceData") { const marketplaceItems = message.marketplaceItems const organizationMcps = message.organizationMcps || [] + const marketplaceInstalledMetadata = message.marketplaceInstalledMetadata if (marketplaceItems !== undefined) { // Always use the marketplace items from the extension when they're provided // This ensures fresh data is always displayed const items = [...marketplaceItems] const orgMcps = [...organizationMcps] - const newDisplayItems = this.isFilterActive() ? this.filterItems(items) : items - const newDisplayOrganizationMcps = this.isFilterActive() ? this.filterItems(orgMcps) : orgMcps + + // Update installed metadata if provided + if (marketplaceInstalledMetadata !== undefined) { + this.state.installedMetadata = marketplaceInstalledMetadata + } + + const newDisplayItems = this.isFilterActive() + ? this.filterItems(items, this.state.installedMetadata) + : items + const newDisplayOrganizationMcps = this.isFilterActive() + ? this.filterItems(orgMcps, this.state.installedMetadata) + : orgMcps // Update state in a single operation this.state = { @@ -428,6 +475,7 @@ export class MarketplaceViewStateManager { organizationMcps: orgMcps, displayItems: newDisplayItems, displayOrganizationMcps: newDisplayOrganizationMcps, + installedMetadata: marketplaceInstalledMetadata || this.state.installedMetadata, } } diff --git a/webview-ui/src/components/marketplace/__tests__/MarketplaceListView.spec.tsx b/webview-ui/src/components/marketplace/__tests__/MarketplaceListView.spec.tsx index d22c3814107..5777c823294 100644 --- a/webview-ui/src/components/marketplace/__tests__/MarketplaceListView.spec.tsx +++ b/webview-ui/src/components/marketplace/__tests__/MarketplaceListView.spec.tsx @@ -27,6 +27,7 @@ const mockState: ViewState = { type: "", search: "", tags: [], + installed: false, }, } diff --git a/webview-ui/src/components/marketplace/__tests__/MarketplaceViewStateManager.spec.ts b/webview-ui/src/components/marketplace/__tests__/MarketplaceViewStateManager.spec.ts index 089226ccc31..f09bb11ecd8 100644 --- a/webview-ui/src/components/marketplace/__tests__/MarketplaceViewStateManager.spec.ts +++ b/webview-ui/src/components/marketplace/__tests__/MarketplaceViewStateManager.spec.ts @@ -64,6 +64,7 @@ describe("MarketplaceViewStateManager", () => { type: "", search: "", tags: [], + installed: false, }) }) diff --git a/webview-ui/src/components/marketplace/components/__tests__/MarketplaceItemCard.spec.tsx b/webview-ui/src/components/marketplace/components/__tests__/MarketplaceItemCard.spec.tsx index 1f1ed9030bb..bba46cd5f8f 100644 --- a/webview-ui/src/components/marketplace/components/__tests__/MarketplaceItemCard.spec.tsx +++ b/webview-ui/src/components/marketplace/components/__tests__/MarketplaceItemCard.spec.tsx @@ -78,6 +78,7 @@ describe("MarketplaceItemCard", () => { type: "", search: "", tags: [], + installed: false, }, setFilters: vi.fn(), installed: { @@ -158,7 +159,7 @@ describe("MarketplaceItemCard", () => { renderWithProviders(