feat(marketplace): load workflows into marketplace

This commit is contained in:
Emir Karabeg
2025-03-23 17:57:15 -07:00
parent 1966c73112
commit 31012fcaeb
6 changed files with 421 additions and 773 deletions
@@ -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)
}
}
+121
View File
@@ -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<string, any[]>
} = {
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 })
}
}
@@ -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<typeof marketplaceFormSchema>
// Categories for the dropdown with icons
const CATEGORIES = [
// {
// value: 'project_management',
// label: 'Project Management',
// icon: <ChartBar className="h-4 w-4 mr-2" />,
// },
{ value: 'data', label: 'Data Analysis', icon: <Database className="h-4 w-4 mr-2" /> },
{ value: 'marketing', label: 'Marketing', icon: <MailIcon className="h-4 w-4 mr-2" /> },
{ value: 'sales', label: 'Sales', icon: <Store className="h-4 w-4 mr-2" /> },
// { value: 'productivity', label: 'Productivity', icon: <TimerIcon className="h-4 w-4 mr-2" /> },
// { value: 'content', label: 'Content Creation', icon: <NotebookPen className="h-4 w-4 mr-2" /> },
{
value: 'customer_service',
label: 'Customer Service',
icon: <BotMessageSquare className="h-4 w-4 mr-2" />,
},
{ value: 'research', label: 'Research', icon: <Atom className="h-4 w-4 mr-2" /> },
{ value: 'finance', label: 'Finance', icon: <LineChart className="h-4 w-4 mr-2" /> },
{ value: 'programming', label: 'Programming', icon: <Code className="h-4 w-4 mr-2" /> },
{ value: 'other', label: 'Other', icon: <Brain className="h-4 w-4 mr-2" /> },
]
// 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 : <Store className="h-4 w-4 mr-2" />
}
// Tooltip texts
const TOOLTIPS = {
category: 'Categorizing your workflow helps users find it more easily.',
@@ -601,25 +572,3 @@ export function MarketplaceModal({ open, onOpenChange }: MarketplaceModalProps)
</Dialog>
)
}
// 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
}
}
@@ -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<string, React.ReactNode> = {
// Map of special section icons
const specialIcons: Record<string, React.ReactNode> = {
popular: <Star className="h-4 w-4 mr-2" />,
programming: <Code className="h-4 w-4 mr-2" />,
marketing: <MailIcon className="h-4 w-4 mr-2" />,
sales: <Store className="h-4 w-4 mr-2" />,
recent: <Clock className="h-4 w-4 mr-2" />,
}
export function Toolbar({ scrollToSection, activeSection }: ToolbarProps) {
const [categories, setCategories] = useState<string[]>([])
// 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] || <Star className="h-4 w-4 mr-2" />}
{specialIcons[category] || getCategoryIcon(category)}
{category}
</Button>
))}
@@ -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: <Database className="h-4 w-4 mr-2" />,
color: '#0ea5e9' // sky-500
},
{
value: 'marketing',
label: 'Marketing',
icon: <MailIcon className="h-4 w-4 mr-2" />,
color: '#f43f5e' // rose-500
},
{
value: 'sales',
label: 'Sales',
icon: <Store className="h-4 w-4 mr-2" />,
color: '#10b981' // emerald-500
},
{
value: 'customer_service',
label: 'Customer Service',
icon: <BotMessageSquare className="h-4 w-4 mr-2" />,
color: '#8b5cf6' // violet-500
},
{
value: 'research',
label: 'Research',
icon: <Atom className="h-4 w-4 mr-2" />,
color: '#f59e0b' // amber-500
},
{
value: 'finance',
label: 'Finance',
icon: <LineChart className="h-4 w-4 mr-2" />,
color: '#14b8a6' // teal-500
},
{
value: 'programming',
label: 'Programming',
icon: <Code className="h-4 w-4 mr-2" />,
color: '#6366f1' // indigo-500
},
{
value: 'other',
label: 'Other',
icon: <Brain className="h-4 w-4 mr-2" />,
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
}
+135 -706
View File
@@ -36,214 +36,114 @@ export interface Workflow {
}
}
// Mock data for workflows
export const mockWorkflows: Record<string, Workflow[]> = {
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<string, any>
edges: Array<{
id: string
source: string
target: string
sourceHandle?: string
targetHandle?: string
}>
loops: Record<string, any>
}
}
export interface MarketplaceData {
popular: MarketplaceWorkflow[]
recent: MarketplaceWorkflow[]
byCategory: Record<string, MarketplaceWorkflow[]>
}
export default function Marketplace() {
const [searchQuery, setSearchQuery] = useState('')
const [loading, setLoading] = useState(true)
const [error, setError] = useState<string | null>(null)
const [workflowData, setWorkflowData] = useState<Record<string, Workflow[]>>(mockWorkflows)
const [marketplaceData, setMarketplaceData] = useState<MarketplaceData>({
popular: [],
recent: [],
byCategory: {},
})
const [activeSection, setActiveSection] = useState<string | null>(null)
// Create refs for each section
const sectionRefs = useRef<Record<string, HTMLDivElement | null>>({})
const contentRef = useRef<HTMLDivElement>(null)
// Convert marketplace data to the format expected by components
const workflowData = useMemo(() => {
const result: Record<string, Workflow[]> = {
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)
}