From 84541d9f1d69711fc9188b46e933702738f1f648 Mon Sep 17 00:00:00 2001 From: Saumya Date: Thu, 10 Jul 2025 20:14:09 +0530 Subject: [PATCH] ui revamp --- backend/pipedream/api.py | 71 +- .../agents/mcp/mcp-configuration-new.tsx | 4 +- .../agents/pipedream/pipedream-registry.tsx | 676 +++++++++++------- .../react-query/pipedream/use-pipedream.ts | 6 +- .../src/hooks/react-query/pipedream/utils.ts | 31 +- 5 files changed, 492 insertions(+), 296 deletions(-) diff --git a/backend/pipedream/api.py b/backend/pipedream/api.py index 0b511add..838963b2 100644 --- a/backend/pipedream/api.py +++ b/backend/pipedream/api.py @@ -354,39 +354,56 @@ async def get_available_pipedream_tools( @router.get("/apps", response_model=Dict[str, Any]) async def get_pipedream_apps( - page: int = Query(1, ge=1), + after: Optional[str] = Query(None, description="Cursor for pagination"), q: Optional[str] = Query(None), category: Optional[str] = Query(None) ): - logger.info(f"Fetching Pipedream apps registry, page: {page}, search: {q}") + logger.info(f"Fetching Pipedream apps registry, after: {after}, search: {q}") try: - import httpx + client = get_pipedream_client() + access_token = await client._obtain_access_token() + await client._ensure_rate_limit_token() + + url = f"https://api.pipedream.com/v1/apps" + params = {} + + if after: + params["after"] = after + if q: + params["q"] = q + if category: + params["category"] = category + + headers = { + "Authorization": f"Bearer {access_token}", + "Content-Type": "application/json" + } + + response = await client._make_request_with_retry( + "GET", + url, + headers=headers, + params=params + ) + + data = response.json() + + page_info = data.get("page_info", {}) + + # For cursor-based pagination, has_more is determined by presence of end_cursor + page_info["has_more"] = bool(page_info.get("end_cursor")) + + logger.info(f"Successfully fetched {len(data.get('data', []))} apps from Pipedream registry") + logger.info(f"Pagination: after={after}, total_count={page_info.get('total_count', 0)}, current_count={page_info.get('count', 0)}, has_more={page_info['has_more']}, end_cursor={page_info.get('end_cursor', 'None')}") + + return { + "success": True, + "apps": data.get("data", []), + "page_info": page_info, + "total_count": page_info.get("total_count", 0) + } - async with httpx.AsyncClient() as client: - url = f"https://mcp.pipedream.com/api/apps" - params = {"page": page} - - if q: - params["q"] = q - if category: - params["category"] = category - - response = await client.get(url, params=params, timeout=30.0) - response.raise_for_status() - - data = response.json() - - # print(data) - - logger.info(f"Successfully fetched {len(data.get('data', []))} apps from Pipedream registry") - return { - "success": True, - "apps": data.get("data", []), - "page_info": data.get("page_info", {}), - "total_count": data.get("page_info", {}).get("total_count", 0) - } - except Exception as e: logger.error(f"Failed to fetch Pipedream apps: {str(e)}") raise HTTPException( diff --git a/frontend/src/components/agents/mcp/mcp-configuration-new.tsx b/frontend/src/components/agents/mcp/mcp-configuration-new.tsx index 786db325..77a018e2 100644 --- a/frontend/src/components/agents/mcp/mcp-configuration-new.tsx +++ b/frontend/src/components/agents/mcp/mcp-configuration-new.tsx @@ -81,11 +81,11 @@ export const MCPConfigurationNew: React.FC = ({

diff --git a/frontend/src/components/agents/pipedream/pipedream-registry.tsx b/frontend/src/components/agents/pipedream/pipedream-registry.tsx index 56df717b..6261b52b 100644 --- a/frontend/src/components/agents/pipedream/pipedream-registry.tsx +++ b/frontend/src/components/agents/pipedream/pipedream-registry.tsx @@ -3,7 +3,7 @@ import { Button } from '@/components/ui/button'; import { Input } from '@/components/ui/input'; import { Card, CardContent } from '@/components/ui/card'; import { Badge } from '@/components/ui/badge'; -import { Search, Loader2, Grid, User, CheckCircle2, Plus, X, Settings, Star } from 'lucide-react'; +import { Search, Loader2, Grid, User, CheckCircle2, Plus, X, Settings, Star, Sparkles, TrendingUp, Filter, ChevronRight, ChevronLeft, ArrowRight } from 'lucide-react'; import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogDescription } from '@/components/ui/dialog'; import { usePipedreamApps } from '@/hooks/react-query/pipedream/use-pipedream'; import { usePipedreamProfiles } from '@/hooks/react-query/pipedream/use-pipedream-profiles'; @@ -25,6 +25,45 @@ interface PipedreamRegistryProps { onClose?: () => void; } +const categoryEmojis: Record = { + 'All': '🌟', + 'Communication': '💬', + 'Artificial Intelligence (AI)': '🤖', + 'Social Media': '📱', + 'CRM': '👥', + 'Marketing': '📈', + 'Analytics': '📊', + 'Commerce': '📊', + 'Databases': '🗄️', + 'File Storage': '🗂️', + 'Help Desk & Support': '🎧', + 'Infrastructure & Cloud': '🌐', + 'E-commerce': '🛒', + 'Developer Tools': '🔧', + 'Productivity': '⚡', + 'Finance': '💰', + 'Email': '📧', + 'Project Management': '📋', + 'Storage': '💾', + 'AI/ML': '🤖', + 'Data & Databases': '🗄️', + 'Video': '🎥', + 'Calendar': '📅', + 'Forms': '📝', + 'Security': '🔒', + 'HR': '👔', + 'Sales': '💼', + 'Support': '🎧', + 'Design': '🎨', + 'Business Intelligence': '📈', + 'Automation': '🔄', + 'News': '📰', + 'Weather': '🌤️', + 'Travel': '✈️', + 'Education': '🎓', + 'Health': '🏥', +}; + export const PipedreamRegistry: React.FC = ({ onProfileSelected, onToolsSelected, @@ -34,63 +73,134 @@ export const PipedreamRegistry: React.FC = ({ }) => { const [search, setSearch] = useState(''); const [selectedCategory, setSelectedCategory] = useState('All'); - const [page, setPage] = useState(1); + const [after, setAfter] = useState(undefined); + const [paginationHistory, setPaginationHistory] = useState([]); const [showToolSelector, setShowToolSelector] = useState(false); const [selectedProfile, setSelectedProfile] = useState(null); const [showProfileManager, setShowProfileManager] = useState(false); const [selectedAppForProfile, setSelectedAppForProfile] = useState<{ app_slug: string; app_name: string } | null>(null); + const [sidebarCollapsed, setSidebarCollapsed] = useState(false); const queryClient = useQueryClient(); - const { data: appsData, isLoading, error, refetch } = usePipedreamApps(page, search, selectedCategory === 'All' ? '' : selectedCategory); + const { data: appsData, isLoading, error, refetch } = usePipedreamApps(after, search); const { data: profiles } = usePipedreamProfiles(); - const { data: allAppsData } = usePipedreamApps(1, '', ''); + const { data: allAppsData } = usePipedreamApps(undefined, ''); + + // Get all apps data for categories and matching + const allApps = useMemo(() => { + return allAppsData?.apps || []; + }, [allAppsData?.apps]); const categories = useMemo(() => { - const dataToUse = allAppsData?.apps || appsData?.apps || []; const categorySet = new Set(); - dataToUse.forEach((app: PipedreamApp) => { + allApps.forEach((app: PipedreamApp) => { app.categories.forEach(cat => categorySet.add(cat)); }); const sortedCategories = Array.from(categorySet).sort(); return ['All', ...sortedCategories]; - }, [allAppsData?.apps, appsData?.apps]); + }, [allApps]); const connectedProfiles = useMemo(() => { return profiles?.filter(p => p.is_connected) || []; }, [profiles]); + const filteredAppsData = useMemo(() => { + if (!appsData) return appsData; + + if (selectedCategory === 'All') { + return appsData; + } + + const filteredApps = appsData.apps.filter((app: PipedreamApp) => + app.categories.includes(selectedCategory) + ); + + return { + ...appsData, + apps: filteredApps, + page_info: { + ...appsData.page_info, + count: filteredApps.length + } + }; + }, [appsData, selectedCategory]); + const popularApps = useMemo(() => { - const dataToUse = allAppsData?.apps || appsData?.apps || []; - return dataToUse + return allApps .filter((app: PipedreamApp) => app.featured_weight > 0) .sort((a: PipedreamApp, b: PipedreamApp) => b.featured_weight - a.featured_weight) .slice(0, 6); - }, [allAppsData?.apps, appsData?.apps]); + }, [allApps]); - // Create apps from connected profiles for display + // Create apps from connected profiles with images from registry const connectedApps = useMemo(() => { - return connectedProfiles.map(profile => ({ - id: profile.profile_id, - name: profile.app_name, - name_slug: profile.app_slug, - app_hid: profile.app_slug, - description: `Access your ${profile.app_name} workspace (Gmail, Calendar, Drive, etc.)`, - categories: [], - featured_weight: 0, - api_docs_url: null, - status: 1, - })); - }, [connectedProfiles]); + return connectedProfiles.map(profile => { + // Try to find the app in the registry by matching name_slug + const registryApp = allApps.find(app => + app.name_slug === profile.app_slug || + app.name.toLowerCase() === profile.app_name.toLowerCase() + ); + + return { + id: profile.profile_id, + name: profile.app_name, + name_slug: profile.app_slug, + auth_type: "keys", + description: `Access your ${profile.app_name} workspace and tools`, + img_src: registryApp?.img_src || "", + custom_fields_json: registryApp?.custom_fields_json || "[]", + categories: registryApp?.categories || [], + featured_weight: 0, + connect: { + allowed_domains: registryApp?.connect?.allowed_domains || null, + base_proxy_target_url: registryApp?.connect?.base_proxy_target_url || "", + proxy_enabled: registryApp?.connect?.proxy_enabled || false, + }, + }; + }); + }, [connectedProfiles, allApps]); const handleSearch = (value: string) => { setSearch(value); - setPage(1); + setAfter(undefined); + setPaginationHistory([]); }; const handleCategorySelect = (category: string) => { setSelectedCategory(category); - setPage(1); + setAfter(undefined); + setPaginationHistory([]); + }; + + const handleNextPage = () => { + if (appsData?.page_info?.end_cursor) { + if (after) { + setPaginationHistory(prev => [...prev, after]); + } else { + setPaginationHistory(prev => [...prev, 'FIRST_PAGE']); + } + setAfter(appsData.page_info.end_cursor); + } + }; + + const handlePrevPage = () => { + if (paginationHistory.length > 0) { + const newHistory = [...paginationHistory]; + const previousCursor = newHistory.pop(); + setPaginationHistory(newHistory); + + if (previousCursor === 'FIRST_PAGE') { + setAfter(undefined); + } else { + setAfter(previousCursor); + } + } + }; + + const resetPagination = () => { + setAfter(undefined); + setPaginationHistory([]); }; const handleProfileSelect = async (profileId: string | null, app: PipedreamApp) => { @@ -139,81 +249,93 @@ export const PipedreamRegistry: React.FC = ({ const [selectedProfileId, setSelectedProfileId] = useState(undefined); return ( - + -
- {/* App Logo */} -
+
+
- {app.name.charAt(0).toUpperCase()} + {app.img_src ? ( + {app.name} { + const target = e.target as HTMLImageElement; + target.style.display = 'none'; + target.nextElementSibling?.classList.remove('hidden'); + }} + /> + ) : null} + + {app.name.charAt(0).toUpperCase()} +
+ {connectedProfiles.length > 0 && ( +
+ +
+ )}
- {/* App Details */} -
- {/* Header */} -
-

- {app.name} -

- {connectedProfiles.length > 0 && ( - - - Connected - - )} +
+
+
+

+ {app.name} +

+
- - {/* Description */}

{app.description}

- - {/* Categories */} {!compact && app.categories.length > 0 && ( -
- {app.categories.slice(0, 3).map((category) => ( +
+ {app.categories.slice(0, 2).map((category) => ( handleCategorySelect(category)} + className="text-xs px-1.5 py-0.5 bg-gray-50 hover:bg-gray-100 cursor-pointer border-gray-200 text-gray-700 dark:bg-gray-800 dark:text-gray-300 dark:border-gray-700" + onClick={(e) => { + e.stopPropagation(); + handleCategorySelect(category); + }} > - {category} + {categoryEmojis[category] || '🔧'} {category} ))} - {app.categories.length > 3 && ( - - +{app.categories.length - 3} + {app.categories.length > 2 && ( + + +{app.categories.length - 2} )}
)} - - {/* Actions */} -
+
{mode === 'simple' ? ( ) : ( <> {connectedProfiles.length > 0 ? ( -
+
= ({ ) : ( )} @@ -247,37 +370,70 @@ export const PipedreamRegistry: React.FC = ({ ); }; - const CategoryTabs = () => ( -
-
+ const Sidebar: React.FC<{ + isCollapsed: boolean; + onToggle: () => void; + categories: string[]; + selectedCategory: string; + onCategorySelect: (category: string) => void; + allApps: PipedreamApp[]; + }> = ({ isCollapsed, onToggle, categories, selectedCategory, onCategorySelect, allApps }) => ( +
+
+
+
+
+

Categories

+
+
+ +
+
+ +
{categories.map((category) => { const categoryCount = category === 'All' - ? (allAppsData?.total_count || appsData?.total_count || 0) - : (allAppsData?.apps || appsData?.apps || []).filter((app: PipedreamApp) => + ? allApps.length + : allApps.filter((app: PipedreamApp) => app.categories.includes(category) ).length; + const isActive = selectedCategory === category; + const emoji = categoryEmojis[category] || '🔧'; + return ( - + {emoji} +
+ {category} +
+ ); })}
@@ -286,178 +442,200 @@ export const PipedreamRegistry: React.FC = ({ if (error) { return ( -
-
Failed to load integrations
- +
+
+
+ +

Failed to load integrations

+
+ +
); } return ( -
- {/* Header */} -
-
-

Integrations

-

- Connect your integrations -

+
+
+ setSidebarCollapsed(!sidebarCollapsed)} + categories={categories} + selectedCategory={selectedCategory} + onCategorySelect={handleCategorySelect} + allApps={allApps} + /> +
+
+
+
+
+
+
+

Integrations

+ + + New + +
+

+ Connect your favorite tools and automate workflows +

+
+
+
+
+
+ + handleSearch(e.target.value)} + className="pl-10 h-9 focus:border-primary/50 focus:ring-primary/20 rounded-lg text-sm" + /> +
+
- {onClose && ( - - )} -
- - {/* Search */} -
-
- - handleSearch(e.target.value)} - className="pl-10 h-10 bg-background/50 border-border/50 focus:border-border w-full" - /> -
-
- - {/* Categories */} -
- -
- - {/* Content */} -
-
- {/* My Connections */} - {connectedApps.length > 0 && ( -
-
- -

My Connections

- - {connectedApps.length} - -
-

- Apps and services you've connected -

+ +
+
+
+ {connectedApps.length > 0 && ( +
+
+ +

My Connections

+ + {connectedApps.length} + +
+ +
+ {connectedApps.map((app) => ( + + ))} +
+
+ )} -
- {connectedApps.map((app) => ( - - ))} -
-
- )} - - {/* Popular/Recommended */} - {popularApps.length > 0 && selectedCategory === 'All' && ( -
-
- -

Popular

- - Recommended - -
-

- Most commonly used integrations to get started quickly -

+ {selectedCategory === 'All' ? ( +
+
+ +

Popular

+ + + Recommended + +
+
+ ) : ( +
+
+ {categoryEmojis[selectedCategory] || '🔧'} +

{selectedCategory}

+
+
+ )} -
- {popularApps.map((app: PipedreamApp) => ( - - ))} -
-
- )} - - {/* Current Category Results */} - {selectedCategory !== 'All' && ( -
-
- -

{selectedCategory}

-
-

- Explore {selectedCategory} integrations and tools -

-
- )} - - {/* Apps Grid */} - {isLoading ? ( -
-
- - Loading integrations... -
-
- ) : appsData?.apps && appsData.apps.length > 0 ? ( - <> -
- {appsData.apps.map((app: PipedreamApp) => ( - - ))} -
- - {appsData.page_info && appsData.page_info.has_more && ( -
+ {isLoading ? ( +
+
+ + Loading integrations... +
+
+ ) : filteredAppsData?.apps && filteredAppsData.apps.length > 0 ? ( + <> +
+ {filteredAppsData.apps.map((app: PipedreamApp) => ( + + ))} +
+ + ) : ( +
+
🔍
+

No integrations found

+

+ {selectedCategory !== 'All' + ? `No integrations found in "${selectedCategory}" category. Try a different category or search term.` + : "Try adjusting your search criteria or browse our popular integrations." + } +

)} - - ) : ( -
-
🔍
-

No integrations found

-

- {selectedCategory !== 'All' - ? `No integrations found in "${selectedCategory}" category` - : "Try adjusting your search criteria" - } -

-
- )} +
+ + {filteredAppsData?.apps && filteredAppsData.apps.length > 0 && (paginationHistory.length > 0 || appsData?.page_info?.has_more) && ( +
+
+
+ + +
+
+ Page {paginationHistory.length + 1} +
+
+ + +
+
+
+ )}
- - {/* Dialogs */} - + - Select Tools for {selectedProfile?.app_name} + Select Tools for {selectedProfile?.app_name} + + Choose which tools you'd like to add from {selectedProfile?.app_name} + = ({ - + Create {selectedAppForProfile?.app_name} Profile - Create a credential profile for {selectedAppForProfile?.app_name} to connect and use its tools + Set up your credential profile for {selectedAppForProfile?.app_name} to access its tools and features { }; }; -export const usePipedreamApps = (page: number = 1, search?: string, category?: string) => { +export const usePipedreamApps = (after?: string, search?: string) => { return useQuery({ - queryKey: ['pipedream', 'apps', page, search, category], + queryKey: ['pipedream', 'apps', after, search], queryFn: async (): Promise => { - return await pipedreamApi.getApps(page, search, category); + return await pipedreamApi.getApps(after, search); }, staleTime: 5 * 60 * 1000, retry: 2, diff --git a/frontend/src/hooks/react-query/pipedream/utils.ts b/frontend/src/hooks/react-query/pipedream/utils.ts index 13ef4a7b..946bd676 100644 --- a/frontend/src/hooks/react-query/pipedream/utils.ts +++ b/frontend/src/hooks/react-query/pipedream/utils.ts @@ -84,12 +84,17 @@ export interface PipedreamApp { id: string; name: string; name_slug: string; - app_hid: string; + auth_type: string; description: string; + img_src: string; + custom_fields_json: string; categories: string[]; featured_weight: number; - api_docs_url: string | null; - status: number; + connect: { + allowed_domains: string[] | null; + base_proxy_target_url: string; + proxy_enabled: boolean; + }; } export interface PipedreamAppResponse { @@ -97,10 +102,8 @@ export interface PipedreamAppResponse { apps: PipedreamApp[]; page_info: { total_count: number; - current_page: number; - page_size: number; + count: number; has_more: boolean; - count?: number; start_cursor?: string; end_cursor?: string; }; @@ -178,21 +181,19 @@ export const pipedreamApi = { return result.data!; }, - async getApps(page: number = 1, search?: string, category?: string): Promise { - const params = new URLSearchParams({ - page: page.toString(), - }); + async getApps(after?: string, search?: string): Promise { + const params = new URLSearchParams(); + + if (after) { + params.append('after', after); + } if (search) { params.append('q', search); } - if (category) { - params.append('category', category); - } - const result = await backendApi.get( - `/pipedream/apps?${params.toString()}`, + `/pipedream/apps${params.toString() ? `?${params.toString()}` : ''}`, { errorContext: { operation: 'load apps', resource: 'Pipedream apps' }, }