From 31012fcaeb562e5a9fe4eb00bb8eba370881c0d6 Mon Sep 17 00:00:00 2001 From: Emir Karabeg Date: Sun, 23 Mar 2025 17:57:15 -0700 Subject: [PATCH] feat(marketplace): load workflows into marketplace --- sim/app/api/marketplace/[id]/state/route.ts | 56 ++ sim/app/api/marketplace/featured/route.ts | 121 +++ .../marketplace-modal/marketplace-modal.tsx | 63 +- .../components/toolbar/toolbar.tsx | 27 +- .../w/marketplace/constants/categories.tsx | 86 ++ sim/app/w/marketplace/marketplace.tsx | 841 +++--------------- 6 files changed, 421 insertions(+), 773 deletions(-) create mode 100644 sim/app/api/marketplace/[id]/state/route.ts create mode 100644 sim/app/api/marketplace/featured/route.ts create mode 100644 sim/app/w/marketplace/constants/categories.tsx diff --git a/sim/app/api/marketplace/[id]/state/route.ts b/sim/app/api/marketplace/[id]/state/route.ts new file mode 100644 index 0000000000..6f7e9ca353 --- /dev/null +++ b/sim/app/api/marketplace/[id]/state/route.ts @@ -0,0 +1,56 @@ +import { NextRequest } from 'next/server' +import { eq, sql } from 'drizzle-orm' +import { createLogger } from '@/lib/logs/console-logger' +import { createErrorResponse, createSuccessResponse } from '@/app/api/workflows/utils' +import { db } from '@/db' +import * as schema from '@/db/schema' + +const logger = createLogger('MarketplaceStateAPI') + +// Cache for 1 hour +export const revalidate = 3600 + +export async function GET(request: NextRequest, { params }: { params: Promise<{ id: string }> }) { + const requestId = crypto.randomUUID().slice(0, 8) + + try { + const { id } = await params + + // Fetch marketplace data to get the state + const marketplaceEntry = await db + .select({ + id: schema.marketplace.id, + state: schema.marketplace.state, + }) + .from(schema.marketplace) + .where(eq(schema.marketplace.workflowId, id)) + .limit(1) + .then((rows) => rows[0]) + + if (!marketplaceEntry) { + logger.warn(`[${requestId}] No marketplace entry found for workflow: ${id}`) + return createErrorResponse('Workflow not found in marketplace', 404) + } + + // Increment the view count for this workflow + await db + .update(schema.marketplace) + .set({ + views: sql`${schema.marketplace.views} + 1` + }) + .where(eq(schema.marketplace.workflowId, id)) + + logger.info(`[${requestId}] Retrieved workflow state for marketplace item: ${id}`) + + return createSuccessResponse({ + id: marketplaceEntry.id, + state: marketplaceEntry.state, + }) + } catch (error) { + logger.error( + `[${requestId}] Error getting workflow state for marketplace item: ${(await params).id}`, + error + ) + return createErrorResponse('Failed to get workflow state', 500) + } +} \ No newline at end of file diff --git a/sim/app/api/marketplace/featured/route.ts b/sim/app/api/marketplace/featured/route.ts new file mode 100644 index 0000000000..bbe349e1f5 --- /dev/null +++ b/sim/app/api/marketplace/featured/route.ts @@ -0,0 +1,121 @@ +import { NextRequest, NextResponse } from 'next/server' +import { desc, eq, sql } from 'drizzle-orm' +import { createLogger } from '@/lib/logs/console-logger' +import { db } from '@/db' +import { marketplace } from '@/db/schema' +import { CATEGORIES } from '@/app/w/marketplace/constants/categories' + +const logger = createLogger('MarketplaceFeaturedAPI') + +// 1 hour cache +export const revalidate = 3600 + +export async function GET(request: NextRequest) { + const requestId = crypto.randomUUID().slice(0, 8) + + try { + // Parse query parameters + const url = new URL(request.url) + const categoryParam = url.searchParams.get('category') + const limitParam = url.searchParams.get('limit') || '6' + const limit = parseInt(limitParam, 10) + + const result: { + popular: any[] + recent: any[] + byCategory: Record + } = { + popular: [], + recent: [], + byCategory: {} + } + + // Get popular items (most stars) + result.popular = await db + .select({ + id: marketplace.id, + workflowId: marketplace.workflowId, + name: marketplace.name, + description: marketplace.description, + authorName: marketplace.authorName, + stars: marketplace.stars, + views: marketplace.views, + category: marketplace.category, + createdAt: marketplace.createdAt, + updatedAt: marketplace.updatedAt, + }) + .from(marketplace) + .orderBy(desc(marketplace.stars), desc(marketplace.views)) + .limit(limit) + + // Get recent items (most recent first) + result.recent = await db + .select({ + id: marketplace.id, + workflowId: marketplace.workflowId, + name: marketplace.name, + description: marketplace.description, + authorName: marketplace.authorName, + stars: marketplace.stars, + views: marketplace.views, + category: marketplace.category, + createdAt: marketplace.createdAt, + updatedAt: marketplace.updatedAt, + }) + .from(marketplace) + .orderBy(desc(marketplace.createdAt)) + .limit(limit) + + // If a specific category is requested + if (categoryParam) { + result.byCategory[categoryParam] = await db + .select({ + id: marketplace.id, + workflowId: marketplace.workflowId, + name: marketplace.name, + description: marketplace.description, + authorName: marketplace.authorName, + stars: marketplace.stars, + views: marketplace.views, + category: marketplace.category, + createdAt: marketplace.createdAt, + updatedAt: marketplace.updatedAt, + }) + .from(marketplace) + .where(eq(marketplace.category, categoryParam)) + .orderBy(desc(marketplace.stars), desc(marketplace.views)) + .limit(limit) + } else { + // Get items for each category + // Using Promise.all for parallel fetching to improve performance + await Promise.all( + CATEGORIES.map(async (category) => { + result.byCategory[category.value] = await db + .select({ + id: marketplace.id, + workflowId: marketplace.workflowId, + name: marketplace.name, + description: marketplace.description, + authorName: marketplace.authorName, + stars: marketplace.stars, + views: marketplace.views, + category: marketplace.category, + createdAt: marketplace.createdAt, + updatedAt: marketplace.updatedAt, + }) + .from(marketplace) + .where(eq(marketplace.category, category.value)) + .orderBy(desc(marketplace.stars), desc(marketplace.views)) + .limit(limit) + }) + ) + } + + logger.info(`[${requestId}] Fetched featured marketplace items successfully`) + + return NextResponse.json(result) + } catch (error: any) { + logger.error(`[${requestId}] Error fetching marketplace items`, error) + return NextResponse.json({ error: error.message }, { status: 500 }) + } +} \ No newline at end of file diff --git a/sim/app/w/[id]/components/control-bar/components/marketplace-modal/marketplace-modal.tsx b/sim/app/w/[id]/components/control-bar/components/marketplace-modal/marketplace-modal.tsx index be60040e9a..9619d2c5ed 100644 --- a/sim/app/w/[id]/components/control-bar/components/marketplace-modal/marketplace-modal.tsx +++ b/sim/app/w/[id]/components/control-bar/components/marketplace-modal/marketplace-modal.tsx @@ -54,6 +54,12 @@ import { useNotificationStore } from '@/stores/notifications/store' import { getWorkflowWithValues } from '@/stores/workflows' import { useWorkflowRegistry } from '@/stores/workflows/registry/store' import { useWorkflowStore } from '@/stores/workflows/workflow/store' +import { + CATEGORIES, + getCategoryColor, + getCategoryIcon, + getCategoryLabel, +} from '@/app/w/marketplace/constants/categories' const logger = createLogger('MarketplaceModal') @@ -114,41 +120,6 @@ const marketplaceFormSchema = z.object({ type MarketplaceFormValues = z.infer -// Categories for the dropdown with icons -const CATEGORIES = [ - // { - // value: 'project_management', - // label: 'Project Management', - // icon: , - // }, - { value: 'data', label: 'Data Analysis', icon: }, - { value: 'marketing', label: 'Marketing', icon: }, - { value: 'sales', label: 'Sales', icon: }, - // { value: 'productivity', label: 'Productivity', icon: }, - // { value: 'content', label: 'Content Creation', icon: }, - { - value: 'customer_service', - label: 'Customer Service', - icon: , - }, - { value: 'research', label: 'Research', icon: }, - { value: 'finance', label: 'Finance', icon: }, - { value: 'programming', label: 'Programming', icon: }, - { value: 'other', label: 'Other', icon: }, -] - -// Find category label by value -const getCategoryLabel = (value: string) => { - const category = CATEGORIES.find((cat) => cat.value === value) - return category ? category.label : value -} - -// Find category icon by value -const getCategoryIcon = (value: string) => { - const category = CATEGORIES.find((cat) => cat.value === value) - return category ? category.icon : -} - // Tooltip texts const TOOLTIPS = { category: 'Categorizing your workflow helps users find it more easily.', @@ -601,25 +572,3 @@ export function MarketplaceModal({ open, onOpenChange }: MarketplaceModalProps) ) } - -// Add this helper function after the getCategoryLabel function -const getCategoryColor = (value: string): string => { - switch (value) { - case 'data': - return '#0ea5e9' // sky-500 - case 'marketing': - return '#f43f5e' // rose-500 - case 'sales': - return '#10b981' // emerald-500 - case 'customer_service': - return '#8b5cf6' // violet-500 - case 'research': - return '#f59e0b' // amber-500 - case 'finance': - return '#14b8a6' // teal-500 - case 'programming': - return '#6366f1' // indigo-500 - default: - return '#7F2FFF' // Brand purple - } -} diff --git a/sim/app/w/marketplace/components/toolbar/toolbar.tsx b/sim/app/w/marketplace/components/toolbar/toolbar.tsx index 5308766928..940171fd3b 100644 --- a/sim/app/w/marketplace/components/toolbar/toolbar.tsx +++ b/sim/app/w/marketplace/components/toolbar/toolbar.tsx @@ -3,6 +3,7 @@ import { useEffect, useState } from 'react' import { BotMessageSquare, + Clock, Code, LineChart, MailIcon, @@ -15,7 +16,7 @@ import { import { Button } from '@/components/ui/button' import { ScrollArea } from '@/components/ui/scroll-area' import { Tooltip, TooltipContent, TooltipTrigger } from '@/components/ui/tooltip' -import { mockWorkflows } from '../../marketplace' +import { CATEGORIES, getCategoryIcon } from '../../constants/categories' export type MarketplaceCategory = 'popular' | 'programming' | 'marketing' | 'all' @@ -24,21 +25,27 @@ interface ToolbarProps { activeSection: string | null } -// Map of category icons -const categoryIcons: Record = { +// Map of special section icons +const specialIcons: Record = { popular: , - programming: , - marketing: , - sales: , + recent: , } export function Toolbar({ scrollToSection, activeSection }: ToolbarProps) { const [categories, setCategories] = useState([]) - // Extract all available categories from mockWorkflows + // Set categories including special sections useEffect(() => { - const availableCategories = Object.keys(mockWorkflows) - setCategories(availableCategories) + // Start with special sections like 'popular' and 'recent' + const specialSections = ['popular'] + + // Add categories from centralized definitions + const categoryValues = CATEGORIES.map((cat) => cat.value) + + // Add 'recent' as the last item + const allCategories = [...specialSections, ...categoryValues, 'recent'] + + setCategories(allCategories) }, []) return ( @@ -54,7 +61,7 @@ export function Toolbar({ scrollToSection, activeSection }: ToolbarProps) { }`} onClick={() => scrollToSection(category)} > - {categoryIcons[category] || } + {specialIcons[category] || getCategoryIcon(category)} {category} ))} diff --git a/sim/app/w/marketplace/constants/categories.tsx b/sim/app/w/marketplace/constants/categories.tsx new file mode 100644 index 0000000000..181e8d37ef --- /dev/null +++ b/sim/app/w/marketplace/constants/categories.tsx @@ -0,0 +1,86 @@ +import { + Atom, + BotMessageSquare, + Brain, + Code, + Database, + LineChart, + MailIcon, + Store +} from 'lucide-react' +import { ReactNode } from 'react' + +export interface Category { + value: string + label: string + icon: ReactNode + color: string +} + +export const CATEGORIES: Category[] = [ + { + value: 'data', + label: 'Data Analysis', + icon: , + color: '#0ea5e9' // sky-500 + }, + { + value: 'marketing', + label: 'Marketing', + icon: , + color: '#f43f5e' // rose-500 + }, + { + value: 'sales', + label: 'Sales', + icon: , + color: '#10b981' // emerald-500 + }, + { + value: 'customer_service', + label: 'Customer Service', + icon: , + color: '#8b5cf6' // violet-500 + }, + { + value: 'research', + label: 'Research', + icon: , + color: '#f59e0b' // amber-500 + }, + { + value: 'finance', + label: 'Finance', + icon: , + color: '#14b8a6' // teal-500 + }, + { + value: 'programming', + label: 'Programming', + icon: , + color: '#6366f1' // indigo-500 + }, + { + value: 'other', + label: 'Other', + icon: , + color: '#7F2FFF' // Brand purple + }, +] + +// Helper functions to get category information +export const getCategoryByValue = (value: string): Category => { + return CATEGORIES.find(cat => cat.value === value) || CATEGORIES[CATEGORIES.length - 1] +} + +export const getCategoryLabel = (value: string): string => { + return getCategoryByValue(value).label +} + +export const getCategoryIcon = (value: string): ReactNode => { + return getCategoryByValue(value).icon +} + +export const getCategoryColor = (value: string): string => { + return getCategoryByValue(value).color +} \ No newline at end of file diff --git a/sim/app/w/marketplace/marketplace.tsx b/sim/app/w/marketplace/marketplace.tsx index d5174714d0..52512b6d17 100644 --- a/sim/app/w/marketplace/marketplace.tsx +++ b/sim/app/w/marketplace/marketplace.tsx @@ -36,214 +36,114 @@ export interface Workflow { } } -// Mock data for workflows -export const mockWorkflows: Record = { - popular: [ - { - id: '1', - name: 'Customer Support Bot', - description: - 'Automated customer support workflow with ticket management and response generation', - author: 'SupportAI', - stars: 245, - views: 1200, - tags: ['support', 'ai', 'automation'], - thumbnail: '/thumbnails/customer-support.png', - workflowState: { - blocks: { - '56367072-4db5-4772-a4d4-7cc6c39426c6': { - id: '56367072-4db5-4772-a4d4-7cc6c39426c6', - type: 'starter', - name: 'Start', - position: { - x: -20, - y: -84.61702910193597, - }, - }, - 'f92496b8-0bc0-4d55-bb24-d248d37eb46f': { - id: 'f92496b8-0bc0-4d55-bb24-d248d37eb46f', - type: 'jina', - name: 'Jina 1', - position: { - x: 623.1435003505945, - y: -38.2306610737525, - }, - }, - }, - edges: [ - { - id: 'f53a00be-6c87-4350-8ae3-e6e0896c20ce', - source: '56367072-4db5-4772-a4d4-7cc6c39426c6', - target: 'f92496b8-0bc0-4d55-bb24-d248d37eb46f', - sourceHandle: 'source', - targetHandle: 'target', - }, - ], - loops: {}, - }, - }, - { - id: '2', - name: 'Content Generator', - description: 'Generate blog posts, social media content, and marketing materials with AI', - author: 'ContentAI', - stars: 187, - views: 987, - tags: ['content', 'generation', 'marketing'], - thumbnail: '/thumbnails/content-generator.png', - }, - { - id: '3', - name: 'Data Analysis Pipeline', - description: 'Analyze and visualize complex data sets with this powerful workflow system', - author: 'DataWizard', - stars: 156, - views: 756, - tags: ['data', 'analysis', 'visualization'], - thumbnail: '/thumbnails/data-analysis.png', - workflowUrl: 'http://localhost:3000/w/015c6af0-acf0-464b-99d0-f6e93de94fb9', - }, - { - id: '4', - name: 'Data Analysis Pipeline', - description: 'Analyze and visualize complex data sets with this powerful workflow system', - author: 'DataWizard', - stars: 156, - views: 756, - tags: ['data', 'analysis', 'visualization'], - thumbnail: '/thumbnails/data-analysis.png', - workflowUrl: 'http://localhost:3000/w/015c6af0-acf0-464b-99d0-f6e93de94fb9', - }, - { - id: '5', - name: 'Data Analysis Pipeline', - description: 'Analyze and visualize complex data sets with this powerful workflow system', - author: 'DataWizard', - stars: 156, - views: 756, - tags: ['data', 'analysis', 'visualization'], - thumbnail: '/thumbnails/data-analysis.png', - workflowUrl: 'http://localhost:3000/w/015c6af0-acf0-464b-99d0-f6e93de94fb9', - }, - { - id: '6', - name: 'Data Analysis Pipeline', - description: 'Analyze and visualize complex data sets with this powerful workflow system', - author: 'DataWizard', - stars: 156, - views: 756, - tags: ['data', 'analysis', 'visualization'], - thumbnail: '/thumbnails/data-analysis.png', - workflowUrl: 'http://localhost:3000/w/015c6af0-acf0-464b-99d0-f6e93de94fb9', - }, - ], - programming: [ - { - id: '4', - name: 'Email Automation', - description: 'Automate your email campaigns with personalized content and scheduling tools', - author: 'EmailPro', - stars: 143, - views: 543, - tags: ['email', 'automation', 'marketing'], - thumbnail: '/thumbnails/email-automation.png', - workflowUrl: 'http://localhost:3000/w/015c6af0-acf0-464b-99d0-f6e93de94fb9', - }, - { - id: '5', - name: 'Social Media Manager', - description: 'Schedule and post to multiple social media platforms with analytics tracking', - author: 'SocialGenius', - stars: 132, - views: 432, - tags: ['social', 'media', 'content'], - thumbnail: '/thumbnails/social-media.png', - workflowUrl: 'http://localhost:3000/w/015c6af0-acf0-464b-99d0-f6e93de94fb9', - }, - { - id: '6', - name: 'Marketing Analytics', - description: 'Track and analyze your marketing campaigns with comprehensive reporting tools', - author: 'MarketPro', - stars: 121, - views: 421, - tags: ['marketing', 'analytics', 'reporting'], - thumbnail: '/thumbnails/marketing-analytics.png', - }, - ], - marketing: [ - { - id: '7', - name: 'SEO Optimizer', - description: 'Optimize your content for search engines with keyword analysis and suggestions', - author: 'SEOPro', - stars: 121, - views: 321, - tags: ['seo', 'marketing', 'content'], - thumbnail: '/thumbnails/seo-optimizer.png', - }, - { - id: '8', - name: 'Ad Campaign Manager', - description: 'Create and manage ad campaigns across multiple platforms with budget tracking', - author: 'AdGenius', - stars: 110, - views: 210, - tags: ['ads', 'marketing', 'campaigns'], - thumbnail: '/thumbnails/ad-campaign.png', - }, - { - id: '9', - name: 'Email Marketing Suite', - description: 'Complete email marketing solution with templates, analytics, and A/B testing', - author: 'EmailMaster', - stars: 95, - views: 195, - tags: ['email', 'marketing', 'templates'], - thumbnail: '/thumbnails/email-marketing.png', - }, - ], +// Updated interface to match API response format +export interface MarketplaceWorkflow { + id: string + workflowId: string + name: string + description: string + authorName: string + stars: number + views: number + category: string + createdAt: string + updatedAt: string + workflowState?: { + blocks: Record + edges: Array<{ + id: string + source: string + target: string + sourceHandle?: string + targetHandle?: string + }> + loops: Record + } +} + +export interface MarketplaceData { + popular: MarketplaceWorkflow[] + recent: MarketplaceWorkflow[] + byCategory: Record } export default function Marketplace() { const [searchQuery, setSearchQuery] = useState('') const [loading, setLoading] = useState(true) const [error, setError] = useState(null) - const [workflowData, setWorkflowData] = useState>(mockWorkflows) + const [marketplaceData, setMarketplaceData] = useState({ + popular: [], + recent: [], + byCategory: {}, + }) const [activeSection, setActiveSection] = useState(null) // Create refs for each section const sectionRefs = useRef>({}) const contentRef = useRef(null) + // Convert marketplace data to the format expected by components + const workflowData = useMemo(() => { + const result: Record = { + popular: marketplaceData.popular.map((item) => ({ + id: item.id, + name: item.name, + description: item.description || '', + author: item.authorName, + stars: item.stars, + views: item.views, + tags: [item.category], + workflowState: item.workflowState, + })), + recent: marketplaceData.recent.map((item) => ({ + id: item.id, + name: item.name, + description: item.description || '', + author: item.authorName, + stars: item.stars, + views: item.views, + tags: [item.category], + workflowState: item.workflowState, + })), + } + + // Add entries for each category + Object.entries(marketplaceData.byCategory).forEach(([category, items]) => { + if (items && items.length > 0) { + result[category] = items.map((item) => ({ + id: item.id, + name: item.name, + description: item.description || '', + author: item.authorName, + stars: item.stars, + views: item.views, + tags: [item.category], + workflowState: item.workflowState, + })) + } + }) + + return result + }, [marketplaceData]) + // Fetch workflows on component mount useEffect(() => { const fetchWorkflows = async () => { try { setLoading(true) - // Simulate API call with timeout - setTimeout(() => { - // For now, just use the mock data - setWorkflowData(mockWorkflows) + const response = await fetch('/api/marketplace/featured') - // Set initial active section to first category - const firstCategory = Object.keys(mockWorkflows)[0] - if (firstCategory) { - setActiveSection(firstCategory) - } + if (!response.ok) { + throw new Error('Failed to fetch marketplace data') + } - setLoading(false) + const data = await response.json() + setMarketplaceData(data) - // Automatically load workflow states for all workflows - Object.values(mockWorkflows) - .flat() - .forEach((workflow) => { - if (!workflow.workflowState) { - fetchWorkflowState(workflow.id) - } - }) - }, 1500) + // Set initial active section to first category + setActiveSection('popular') + setLoading(false) } catch (error) { console.error('Error fetching workflows:', error) setError('Failed to load workflows. Please try again later.') @@ -257,532 +157,61 @@ export default function Marketplace() { // Function to fetch workflow state for a specific workflow const fetchWorkflowState = async (workflowId: string) => { try { - // In a real implementation, this would be a fetch call to your API - // For now, we'll just simulate it with the test state provided + // Find which section contains this workflow + let foundWorkflow: MarketplaceWorkflow | undefined - // Example API call: - // const response = await fetch(`/api/workflows/${workflowId}/state`) - // const data = await response.json() + // Check in popular section + foundWorkflow = marketplaceData.popular.find((w) => w.id === workflowId) - // Sample workflow states - const sampleStates = { - // First sample state - state1: { - blocks: { - '69ff9f19-57fb-4544-9e9b-850bc4203cb3': { - id: '69ff9f19-57fb-4544-9e9b-850bc4203cb3', - type: 'starter', - name: 'Start', - position: { - x: 100, - y: 100, - }, - subBlocks: { - startWorkflow: { - id: 'startWorkflow', - type: 'dropdown', - value: 'manual', - }, - webhookPath: { - id: 'webhookPath', - type: 'short-input', - value: '', - }, - webhookSecret: { - id: 'webhookSecret', - type: 'short-input', - value: '', - }, - scheduleType: { - id: 'scheduleType', - type: 'dropdown', - value: 'daily', - }, - minutesInterval: { - id: 'minutesInterval', - type: 'short-input', - value: '', - }, - minutesStartingAt: { - id: 'minutesStartingAt', - type: 'short-input', - value: '', - }, - hourlyMinute: { - id: 'hourlyMinute', - type: 'short-input', - value: '', - }, - dailyTime: { - id: 'dailyTime', - type: 'short-input', - value: '', - }, - weeklyDay: { - id: 'weeklyDay', - type: 'dropdown', - value: 'MON', - }, - weeklyDayTime: { - id: 'weeklyDayTime', - type: 'short-input', - value: '', - }, - monthlyDay: { - id: 'monthlyDay', - type: 'short-input', - value: '', - }, - monthlyTime: { - id: 'monthlyTime', - type: 'short-input', - value: '', - }, - cronExpression: { - id: 'cronExpression', - type: 'short-input', - value: '', - }, - timezone: { - id: 'timezone', - type: 'dropdown', - value: 'UTC', - }, - }, - outputs: { - response: { - type: { - input: 'any', - }, - }, - }, - enabled: true, - horizontalHandles: true, - isWide: false, - height: 96, - }, - 'c43552ef-3b78-40be-98bd-b48d3620b3ca': { - id: 'c43552ef-3b78-40be-98bd-b48d3620b3ca', - type: 'exa', - name: 'Exa 1', - position: { - x: 492.75, - y: -192.30000000000013, - }, - subBlocks: { - operation: { - id: 'operation', - type: 'dropdown', - value: 'exa_get_contents', - }, - query: { - id: 'query', - type: 'long-input', - value: null, - }, - numResults: { - id: 'numResults', - type: 'short-input', - value: null, - }, - useAutoprompt: { - id: 'useAutoprompt', - type: 'switch', - value: null, - }, - type: { - id: 'type', - type: 'dropdown', - value: 'auto', - }, - urls: { - id: 'urls', - type: 'long-input', - value: 'https://www.grainger.com/category', - }, - text: { - id: 'text', - type: 'switch', - value: false, - }, - summaryQuery: { - id: 'summaryQuery', - type: 'long-input', - value: - 'Summarize the entire product catalog from Grainger (https://www.grainger.com/category?analytics=nav). \n\nExtract all product categories, subcategories, and product details by recursively navigating the category structure until individual products are reached.\n\nFor each product, extract the following structured information:\n- Product ID (Item Number)\n- Manufacturer Model Number\n- Product Name\n- Description\n- Full Category Hierarchy (to understand product classification)\n- Common Use Cases (real-world applications)\n- Technical Specifications (voltage, horsepower, size, weight, etc.)\n- Available Variations (dropdown-based SKU options)\n- Stock Availability\n- Price (if listed)\n- Brand\n- Product Image URLs\n- Product URL\n\nEnsure **all nested categories and paginated product listings are fully scraped**. Use **anti-bot techniques** (randomized headers, realistic User-Agent, and delays) to avoid detection.\n', - }, - url: { - id: 'url', - type: 'long-input', - value: null, - }, - apiKey: { - id: 'apiKey', - type: 'short-input', - value: '', - }, - }, - outputs: { - response: { - results: 'json', - similarLinks: 'json', - answer: 'string', - citations: 'json', - }, - }, - enabled: true, - horizontalHandles: true, - isWide: false, - height: 526, - }, - }, - edges: [ - { - id: '0b52741c-6c33-486d-90db-87b656b36e14', - source: '69ff9f19-57fb-4544-9e9b-850bc4203cb3', - target: 'c43552ef-3b78-40be-98bd-b48d3620b3ca', - sourceHandle: 'source', - targetHandle: 'target', - }, - ], - loops: {}, - lastSaved: 1741238251762, - isDeployed: false, - }, - // Second sample state - state2: { - blocks: { - '7ea690e9-40b7-433a-81f4-9c013bd908fc': { - id: '7ea690e9-40b7-433a-81f4-9c013bd908fc', - type: 'starter', - name: 'Start', - position: { - x: 8.848492246438582, - y: 65.25033924944911, - }, - subBlocks: { - startWorkflow: { - id: 'startWorkflow', - type: 'dropdown', - value: 'manual', - }, - webhookPath: { - id: 'webhookPath', - type: 'short-input', - value: '', - }, - webhookSecret: { - id: 'webhookSecret', - type: 'short-input', - value: '', - }, - scheduleType: { - id: 'scheduleType', - type: 'dropdown', - value: 'daily', - }, - minutesInterval: { - id: 'minutesInterval', - type: 'short-input', - value: '', - }, - minutesStartingAt: { - id: 'minutesStartingAt', - type: 'short-input', - value: '', - }, - hourlyMinute: { - id: 'hourlyMinute', - type: 'short-input', - value: '', - }, - dailyTime: { - id: 'dailyTime', - type: 'short-input', - value: '', - }, - weeklyDay: { - id: 'weeklyDay', - type: 'dropdown', - value: 'MON', - }, - weeklyDayTime: { - id: 'weeklyDayTime', - type: 'short-input', - value: '', - }, - monthlyDay: { - id: 'monthlyDay', - type: 'short-input', - value: '', - }, - monthlyTime: { - id: 'monthlyTime', - type: 'short-input', - value: '', - }, - cronExpression: { - id: 'cronExpression', - type: 'short-input', - value: '', - }, - timezone: { - id: 'timezone', - type: 'dropdown', - value: 'UTC', - }, - }, - outputs: { - response: { - type: { - result: 'any', - stdout: 'string', - executionTime: 'number', - }, - }, - }, - enabled: true, - horizontalHandles: true, - isWide: false, - height: 348, - }, - '6512f898-7da6-4513-88d2-16d75f9e68be': { - id: '6512f898-7da6-4513-88d2-16d75f9e68be', - type: 'agent', - name: 'Agent 1', - position: { - x: 593.8824178584484, - y: -147.7026665650688, - }, - subBlocks: { - systemPrompt: { - id: 'systemPrompt', - type: 'long-input', - value: null, - }, - context: { - id: 'context', - type: 'short-input', - value: null, - }, - model: { - id: 'model', - type: 'dropdown', - value: null, - }, - temperature: { - id: 'temperature', - type: 'slider', - value: null, - }, - apiKey: { - id: 'apiKey', - type: 'short-input', - value: null, - }, - tools: { - id: 'tools', - type: 'tool-input', - value: null, - }, - responseFormat: { - id: 'responseFormat', - type: 'code', - value: null, - }, - }, - outputs: { - response: { - content: 'string', - model: 'string', - tokens: 'any', - toolCalls: 'any', - }, - }, - enabled: true, - horizontalHandles: true, - isWide: false, - height: 768, - }, - }, - edges: [ - { - id: '997ccd9f-7a89-460f-9208-e3b09cb82528', - source: '7ea690e9-40b7-433a-81f4-9c013bd908fc', - target: '6512f898-7da6-4513-88d2-16d75f9e68be', - sourceHandle: 'source', - targetHandle: 'target', - }, - ], - loops: {}, - }, - // Original test state - state3: { - blocks: { - '56367072-4db5-4772-a4d4-7cc6c39426c6': { - id: '56367072-4db5-4772-a4d4-7cc6c39426c6', - type: 'starter', - name: 'Start', - position: { - x: -20, - y: -84.61702910193597, - }, - subBlocks: { - startWorkflow: { - id: 'startWorkflow', - type: 'dropdown', - value: 'webhook', - }, - webhookPath: { - id: 'webhookPath', - type: 'short-input', - value: '/188403', - }, - webhookSecret: { - id: 'webhookSecret', - type: 'short-input', - value: '', - }, - scheduleType: { - id: 'scheduleType', - type: 'dropdown', - value: 'daily', - }, - minutesInterval: { - id: 'minutesInterval', - type: 'short-input', - value: '', - }, - minutesStartingAt: { - id: 'minutesStartingAt', - type: 'short-input', - value: '', - }, - hourlyMinute: { - id: 'hourlyMinute', - type: 'short-input', - value: '', - }, - dailyTime: { - id: 'dailyTime', - type: 'short-input', - value: '', - }, - weeklyDay: { - id: 'weeklyDay', - type: 'dropdown', - value: 'MON', - }, - weeklyDayTime: { - id: 'weeklyDayTime', - type: 'short-input', - value: '', - }, - monthlyDay: { - id: 'monthlyDay', - type: 'short-input', - value: '', - }, - monthlyTime: { - id: 'monthlyTime', - type: 'short-input', - value: '', - }, - cronExpression: { - id: 'cronExpression', - type: 'short-input', - value: '', - }, - timezone: { - id: 'timezone', - type: 'dropdown', - value: 'UTC', - }, - }, - outputs: { - response: { - type: { - input: 'any', - }, - }, - }, - enabled: true, - horizontalHandles: true, - isWide: true, - height: 427.8125, - }, - 'f92496b8-0bc0-4d55-bb24-d248d37eb46f': { - id: 'f92496b8-0bc0-4d55-bb24-d248d37eb46f', - type: 'jina', - name: 'Jina 1', - position: { - x: 623.1435003505945, - y: -38.2306610737525, - }, - subBlocks: { - url: { - id: 'url', - type: 'short-input', - value: 'amazon.com', - }, - options: { - id: 'options', - type: 'checkbox-list', - value: null, - }, - apiKey: { - id: 'apiKey', - type: 'short-input', - value: '{{JINA_API_KEY}}', - }, - }, - outputs: { - response: { - content: 'string', - }, - }, - enabled: true, - horizontalHandles: true, - isWide: false, - height: 307.84375, - }, - }, - edges: [ - { - id: 'f53a00be-6c87-4350-8ae3-e6e0896c20ce', - source: '56367072-4db5-4772-a4d4-7cc6c39426c6', - target: 'f92496b8-0bc0-4d55-bb24-d248d37eb46f', - sourceHandle: 'source', - targetHandle: 'target', - }, - ], - loops: {}, - lastSaved: 1741381880349, - isDeployed: false, - }, + // Check in recent section if not found + if (!foundWorkflow) { + foundWorkflow = marketplaceData.recent.find((w) => w.id === workflowId) } - // Choose a random state for demonstration - const stateIndex = Math.floor(Math.random() * 3) + 1 - const stateKey = `state${stateIndex}` as keyof typeof sampleStates - const testState = sampleStates[stateKey] + // Check in category sections if not found + if (!foundWorkflow) { + for (const category of Object.keys(marketplaceData.byCategory)) { + foundWorkflow = marketplaceData.byCategory[category].find((w) => w.id === workflowId) + if (foundWorkflow) break + } + } - // Update the workflow data with the state - setWorkflowData((prevData) => { - const updatedData = { ...prevData } - - // Update each category - Object.keys(updatedData).forEach((category) => { - updatedData[category] = updatedData[category].map((workflow) => { - if (workflow.id === workflowId) { - return { ...workflow, workflowState: testState } - } - return workflow - }) + // If we have the workflow and it doesn't already have state + if (foundWorkflow && !foundWorkflow.workflowState) { + // In a real implementation, fetch the workflow state from the server + // For now, we'll just use a placeholder + const response = await fetch(`/api/marketplace/${foundWorkflow.workflowId}/state`, { + method: 'GET', }) - return updatedData - }) + if (response.ok) { + const stateData = await response.json() + + // Update the workflow data with the state + setMarketplaceData((prevData) => { + const updatedData = { ...prevData } + + // Update in popular + updatedData.popular = updatedData.popular.map((w) => + w.id === workflowId ? { ...w, workflowState: stateData.state } : w + ) + + // Update in recent + updatedData.recent = updatedData.recent.map((w) => + w.id === workflowId ? { ...w, workflowState: stateData.state } : w + ) + + // Update in categories + Object.keys(updatedData.byCategory).forEach((category) => { + updatedData.byCategory[category] = updatedData.byCategory[category].map((w) => + w.id === workflowId ? { ...w, workflowState: stateData.state } : w + ) + }) + + return updatedData + }) + } + } } catch (error) { console.error(`Error fetching workflow state for ${workflowId}:`, error) }