From 4413ac00f619706972204732a657ea1012ff7e96 Mon Sep 17 00:00:00 2001 From: Ajax Davis Date: Fri, 12 Dec 2025 12:28:02 +1000 Subject: [PATCH] feat(playground): load all tools and add hide broken toggle - Paginate through all tools from registry API (was limited to 20) - Add 'Hide broken tools' checkbox (enabled by default) - Filter out tools with BROKEN import or execution health --- apps/playground/src/app/api/tools/route.ts | 88 +++++++++++++------ .../src/components/sidebar/ToolsSidebar.tsx | 32 +++++-- 2 files changed, 88 insertions(+), 32 deletions(-) diff --git a/apps/playground/src/app/api/tools/route.ts b/apps/playground/src/app/api/tools/route.ts index 0ea1c98..7e2eaec 100644 --- a/apps/playground/src/app/api/tools/route.ts +++ b/apps/playground/src/app/api/tools/route.ts @@ -3,40 +3,74 @@ import { NextResponse } from 'next/server'; export const runtime = 'nodejs'; export const dynamic = 'force-dynamic'; +interface RawTool { + id: string; + exportName: string; + description: string; + qualityScore: number; + importHealth: 'HEALTHY' | 'BROKEN' | 'UNKNOWN'; + executionHealth: 'HEALTHY' | 'BROKEN' | 'UNKNOWN'; + healthCheckError: string | null; + lastHealthCheck: string | null; + package?: { + npmPackageName: string; + npmVersion: string; + category: string; + frameworks: string[]; + env: Array<{ name: string; description: string; required?: boolean; default?: string }>; + }; +} + +function transformTool(tool: RawTool) { + return { + toolId: tool.id, + packageName: tool.package?.npmPackageName, + exportName: tool.exportName, + description: tool.description, + category: tool.package?.category, + version: tool.package?.npmVersion, + qualityScore: tool.qualityScore, + frameworks: tool.package?.frameworks, + env: tool.package?.env, + importUrl: `https://esm.sh/${tool.package?.npmPackageName}@${tool.package?.npmVersion}`, + importHealth: tool.importHealth, + executionHealth: tool.executionHealth, + healthCheckError: tool.healthCheckError, + lastHealthCheck: tool.lastHealthCheck, + }; +} + export async function GET() { try { const baseUrl = process.env.TPMJS_API_URL || 'https://tpmjs.com'; - const response = await fetch(`${baseUrl}/api/tools`); + const allTools: RawTool[] = []; + let offset = 0; + const limit = 50; // Max allowed by the API + let hasMore = true; - if (!response.ok) { - throw new Error(`Failed to fetch tools: ${response.statusText}`); + // Paginate through all tools + while (hasMore) { + const response = await fetch(`${baseUrl}/api/tools?limit=${limit}&offset=${offset}`); + + if (!response.ok) { + throw new Error(`Failed to fetch tools: ${response.statusText}`); + } + + const data = await response.json(); + const tools = data.data || []; + allTools.push(...tools); + + hasMore = data.pagination?.hasMore ?? false; + offset += limit; + + // Safety limit to prevent infinite loops + if (offset > 1000) break; } - const data = await response.json(); - - // Transform web app response format to playground format - // Web app returns { success, data: Tool[] } - // Playground expects { success, tools: Tool[], total } - const tools = data.data || []; return NextResponse.json({ - success: data.success, - tools: tools.map((tool: any) => ({ - toolId: tool.id, - packageName: tool.package?.npmPackageName, - exportName: tool.exportName, - description: tool.description, - category: tool.package?.category, - version: tool.package?.npmVersion, - qualityScore: tool.qualityScore, - frameworks: tool.package?.frameworks, - env: tool.package?.env, - importUrl: `https://esm.sh/${tool.package?.npmPackageName}@${tool.package?.npmVersion}`, - importHealth: tool.importHealth, - executionHealth: tool.executionHealth, - healthCheckError: tool.healthCheckError, - lastHealthCheck: tool.lastHealthCheck, - })), - total: tools.length, + success: true, + tools: allTools.map(transformTool), + total: allTools.length, }); } catch (error) { console.error('Failed to fetch tools:', error); diff --git a/apps/playground/src/components/sidebar/ToolsSidebar.tsx b/apps/playground/src/components/sidebar/ToolsSidebar.tsx index 2e0c0ca..1219396 100644 --- a/apps/playground/src/components/sidebar/ToolsSidebar.tsx +++ b/apps/playground/src/components/sidebar/ToolsSidebar.tsx @@ -2,6 +2,7 @@ import { Badge } from '@tpmjs/ui/Badge/Badge'; import { Card, CardContent, CardHeader, CardTitle } from '@tpmjs/ui/Card/Card'; +import { Checkbox } from '@tpmjs/ui/Checkbox/Checkbox'; import { Input } from '@tpmjs/ui/Input/Input'; import { ToolHealthBadge } from '@tpmjs/ui/ToolHealthBadge/ToolHealthBadge'; import { ToolHealthBanner } from '@tpmjs/ui/ToolHealthBanner/ToolHealthBanner'; @@ -28,6 +29,7 @@ export function ToolsSidebar(): React.ReactElement { const [tools, setTools] = useState([]); const [loading, setLoading] = useState(true); const [filter, setFilter] = useState(''); + const [hideBroken, setHideBroken] = useState(true); const [selectedTool, setSelectedTool] = useState(null); useEffect(() => { @@ -57,13 +59,20 @@ export function ToolsSidebar(): React.ReactElement { fetchTools(); }, []); - const filteredTools = tools.filter( - (tool) => + const filteredTools = tools.filter((tool) => { + // Filter by search text + const matchesFilter = tool.packageName?.toLowerCase().includes(filter.toLowerCase()) || tool.exportName?.toLowerCase().includes(filter.toLowerCase()) || tool.description?.toLowerCase().includes(filter.toLowerCase()) || - tool.category?.toLowerCase().includes(filter.toLowerCase()) - ); + tool.category?.toLowerCase().includes(filter.toLowerCase()); + + // Filter out broken tools if hideBroken is true + const isBroken = tool.importHealth === 'BROKEN' || tool.executionHealth === 'BROKEN'; + const matchesHealth = !hideBroken || !isBroken; + + return matchesFilter && matchesHealth; + }); return ( <> @@ -78,9 +87,22 @@ export function ToolsSidebar(): React.ReactElement { placeholder="Filter tools..." value={filter} onChange={(e) => setFilter(e.target.value)} - className="mb-4" + className="mb-3" /> +
+ setHideBroken(e.target.checked)} + size="sm" + /> + {/* biome-ignore lint/a11y/noLabelWithoutControl: Checkbox is associated via htmlFor */} + +
+
{loading ? (

Loading tools...