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 ? (
+

{
+ 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
-
- )}
+
+
-
- {/* 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.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 */}