├── .eslintrc.json ├── .gitignore ├── .npmrc ├── README.md ├── app ├── api │ ├── approval │ │ ├── [approvalId] │ │ │ ├── resume │ │ │ │ └── route.ts │ │ │ └── route.ts │ │ └── route.ts │ ├── config │ │ └── route.ts │ ├── execute-agent │ │ └── route.ts │ ├── execute-extract │ │ └── route.ts │ ├── execute-firecrawl │ │ └── route.ts │ ├── execute-guardrails │ │ └── route.ts │ ├── execute-mcp │ │ └── route.ts │ ├── mcp │ │ └── registry │ │ │ └── route.ts │ ├── templates │ │ ├── seed │ │ │ └── route.ts │ │ └── update │ │ │ └── route.ts │ ├── test-mcp-connection │ │ └── route.ts │ ├── workflow │ │ └── execute │ │ │ └── route.ts │ └── workflows │ │ ├── [workflowId] │ │ ├── execute-langgraph │ │ │ └── route.ts │ │ ├── execute-stream │ │ │ └── route.ts │ │ ├── execute │ │ │ └── route.ts │ │ ├── export-code │ │ │ └── route.ts │ │ ├── export-langgraph │ │ │ └── route.ts │ │ ├── resume │ │ │ └── route.ts │ │ └── route.ts │ │ ├── cleanup │ │ └── route.ts │ │ ├── import-langgraph │ │ └── route.ts │ │ └── route.ts ├── layout.tsx ├── page.tsx ├── sign-in │ └── [[...sign-in]] │ │ └── page.tsx ├── sign-up │ └── [[...sign-up]] │ │ └── page.tsx └── workflows │ ├── [workflowId] │ ├── page.tsx │ └── run │ │ └── page.tsx │ └── page.tsx ├── atoms └── sheets.ts ├── colors.json ├── components ├── FirecrawlIcon │ └── FirecrawlIcon.tsx ├── app │ └── (home) │ │ └── sections │ │ ├── endpoints │ │ ├── EndpointsCrawl │ │ │ └── EndpointsCrawl.tsx │ │ ├── EndpointsExtract │ │ │ └── EndpointsExtract.tsx │ │ ├── EndpointsMap │ │ │ └── EndpointsMap.tsx │ │ ├── EndpointsScrape │ │ │ └── EndpointsScrape.tsx │ │ ├── EndpointsSearch │ │ │ └── EndpointsSearch.tsx │ │ ├── Extract │ │ │ └── Extract.tsx │ │ └── Mcp │ │ │ └── Mcp.tsx │ │ ├── hero-flame │ │ ├── HeroFlame.tsx │ │ └── data.json │ │ ├── hero-input │ │ ├── Button │ │ │ └── Button.tsx │ │ ├── HeroInput.tsx │ │ ├── Tabs │ │ │ ├── Mobile │ │ │ │ └── Mobile.tsx │ │ │ └── Tabs.tsx │ │ └── _svg │ │ │ ├── ArrowRight.tsx │ │ │ └── Globe.tsx │ │ ├── hero-scraping │ │ ├── Code │ │ │ ├── Code.tsx │ │ │ └── Loading │ │ │ │ ├── Loading.tsx │ │ │ │ └── _svg │ │ │ │ └── Check.tsx │ │ ├── HeroScraping.css │ │ ├── HeroScraping.tsx │ │ ├── Tag │ │ │ └── Tag.tsx │ │ └── _svg │ │ │ ├── BrowserMobile.tsx │ │ │ └── BrowserTab.tsx │ │ ├── hero │ │ ├── Background │ │ │ ├── Background.tsx │ │ │ ├── BackgroundOuterPiece.tsx │ │ │ └── _svg │ │ │ │ └── CenterStar.tsx │ │ ├── Badge │ │ │ └── Badge.tsx │ │ ├── Hero.tsx │ │ ├── Pixi │ │ │ ├── Pixi.tsx │ │ │ └── tickers │ │ │ │ ├── ascii.ts │ │ │ │ └── features │ │ │ │ ├── cell.ts │ │ │ │ ├── cellReveal.ts │ │ │ │ ├── components │ │ │ │ ├── AnimatedRect.ts │ │ │ │ ├── BlinkingContainer.ts │ │ │ │ └── Dot.ts │ │ │ │ ├── crawl.ts │ │ │ │ ├── index.ts │ │ │ │ ├── mapping.ts │ │ │ │ ├── scrape.ts │ │ │ │ └── search.ts │ │ └── Title │ │ │ └── Title.tsx │ │ ├── step2 │ │ └── Step2Placeholder.tsx │ │ └── workflow-builder │ │ ├── ConfirmDialog.tsx │ │ ├── ConnectionMapperModal.tsx │ │ ├── ConnectorsPanel.tsx │ │ ├── CustomNodes.tsx │ │ ├── DataNodePanel.tsx │ │ ├── DataNodePanel.tsx.backup │ │ ├── EdgeLabelModal.tsx │ │ ├── ExecutionPanel.tsx │ │ ├── ExportLangGraphButton.tsx │ │ ├── ExtractNodePanel.tsx │ │ ├── HTTPNodePanel.tsx │ │ ├── LogicNodePanel.tsx │ │ ├── MCPPanel.tsx │ │ ├── NodeArgumentsPanel.tsx │ │ ├── NodeIOBadges.tsx │ │ ├── NodePanel.tsx │ │ ├── NoteNodePanel.tsx │ │ ├── OutputSchemaPanel.tsx │ │ ├── PasteConfigModal.tsx │ │ ├── PreviewPanel.tsx │ │ ├── PublishModal.tsx │ │ ├── SaveAsTemplateModal.tsx │ │ ├── SettingsPanelSimple.tsx │ │ ├── ShareWorkflowModal.tsx │ │ ├── StartNodePanel.tsx │ │ ├── TestEndpointPanel.tsx │ │ ├── ToolsNodePanel.tsx │ │ ├── UniversalOutputSelector.tsx │ │ ├── VariableReferencePicker.tsx │ │ ├── WorkflowBuilder.tsx │ │ └── WorkflowNameEditor.tsx ├── hooks │ └── use-dropdown-hover.ts ├── icons │ └── FirecrawlLogo.tsx ├── providers │ └── BigIntProvider.tsx ├── shared │ ├── ErrorBoundary.tsx │ ├── Playground │ │ └── Context │ │ │ └── types.ts │ ├── animated-dot-icon.tsx │ ├── ascii-background.tsx │ ├── ascii-flame-background.tsx │ ├── button │ │ ├── Button.css │ │ └── Button.tsx │ ├── buttons │ │ ├── capsule-button.tsx │ │ ├── fire-action-link.tsx │ │ ├── index.ts │ │ └── slate-button.tsx │ ├── color-styles │ │ └── color-styles.tsx │ ├── combobox │ │ └── combobox.tsx │ ├── effects │ │ ├── flame │ │ │ ├── Flame.tsx │ │ │ ├── ascii-explosion.tsx │ │ │ ├── auth-pulse │ │ │ │ ├── auth-pulse.tsx │ │ │ │ └── pulse-data.json │ │ │ ├── core-flame.json │ │ │ ├── core-flame.tsx │ │ │ ├── explosion-data.json │ │ │ ├── flame-background.tsx │ │ │ ├── hero-flame-data.json │ │ │ ├── hero-flame-right.tsx │ │ │ ├── hero-flame.tsx │ │ │ ├── index.ts │ │ │ ├── slate-grid │ │ │ │ ├── grid-data.json │ │ │ │ └── slate-grid.tsx │ │ │ ├── subtle-explosion.tsx │ │ │ └── subtle-wave │ │ │ │ ├── subtle-wave.tsx │ │ │ │ └── wave-data.json │ │ ├── index.ts │ │ └── subtle-ascii-animation.tsx │ ├── firecrawl-icon │ │ ├── firecrawl-icon-static.tsx │ │ └── firecrawl-icon.tsx │ ├── flame-button.tsx │ ├── header │ │ ├── BrandKit │ │ │ ├── BrandKit.tsx │ │ │ └── _svg │ │ │ │ ├── Download.tsx │ │ │ │ ├── Guidelines.tsx │ │ │ │ └── Icon.tsx │ │ ├── Dropdown │ │ │ ├── Content │ │ │ │ ├── Content.tsx │ │ │ │ └── NavItemRow.tsx │ │ │ ├── Github │ │ │ │ ├── Flame │ │ │ │ │ ├── Flame.tsx │ │ │ │ │ └── data.json │ │ │ │ └── Github.tsx │ │ │ ├── Mobile │ │ │ │ ├── Item │ │ │ │ │ └── Item.tsx │ │ │ │ └── Mobile.tsx │ │ │ ├── Stories │ │ │ │ ├── Flame │ │ │ │ │ └── Flame.tsx │ │ │ │ ├── Stories.tsx │ │ │ │ └── _svg │ │ │ │ │ ├── ArrowUp.tsx │ │ │ │ │ └── Replit.tsx │ │ │ └── Wrapper │ │ │ │ └── Wrapper.tsx │ │ ├── Github │ │ │ ├── GithubClient.tsx │ │ │ └── _svg │ │ │ │ └── GithubIcon.tsx │ │ ├── HeaderContext.tsx │ │ ├── Nav │ │ │ ├── Item │ │ │ │ ├── Item.tsx │ │ │ │ └── _svg │ │ │ │ │ └── ChevronDown.tsx │ │ │ ├── Nav.tsx │ │ │ ├── RenderEndpointIcon.tsx │ │ │ └── _svg │ │ │ │ ├── Affiliate.tsx │ │ │ │ ├── Api.tsx │ │ │ │ ├── ArrowRight.tsx │ │ │ │ ├── Careers.tsx │ │ │ │ ├── Changelog.tsx │ │ │ │ ├── Chats.tsx │ │ │ │ ├── Lead.tsx │ │ │ │ ├── MCP.tsx │ │ │ │ ├── Platforms.tsx │ │ │ │ ├── Research.tsx │ │ │ │ ├── Student.tsx │ │ │ │ └── Templates.tsx │ │ ├── Toggle │ │ │ └── Toggle.tsx │ │ ├── Wrapper │ │ │ └── Wrapper.tsx │ │ └── _svg │ │ │ └── Logo.tsx │ ├── hero-flame.tsx │ ├── icons │ │ ├── GitHub.tsx │ │ ├── Logo.tsx │ │ ├── animated-chevron.tsx │ │ ├── animated-icons.tsx │ │ ├── arrow-animated.tsx │ │ ├── check.tsx │ │ ├── chevron-slide.tsx │ │ ├── copied.tsx │ │ ├── copy.tsx │ │ ├── curve.tsx │ │ ├── fingerprint-icon.tsx │ │ ├── openai.tsx │ │ ├── source-icon.tsx │ │ ├── symbol-colored.tsx │ │ ├── symbol-white.tsx │ │ ├── tremor-placeholder.tsx │ │ ├── wordmark-colored.tsx │ │ └── wordmark-white.tsx │ ├── image │ │ ├── Image.tsx │ │ └── getImageSrc.ts │ ├── layout │ │ ├── animated-height.tsx │ │ ├── animated-width.tsx │ │ ├── curvy-rect-divider.tsx │ │ └── curvy-rect.tsx │ ├── loading │ │ ├── Shimmer.tsx │ │ └── usage-loading.tsx │ ├── lockBody.tsx │ ├── logo-cloud │ │ ├── index.ts │ │ ├── logo-cloud.tsx │ │ └── logo-cloud2 │ │ │ ├── Logocloud.css │ │ │ └── Logocloud.tsx │ ├── macbook-scroll.tsx │ ├── notifications │ │ └── slack-notification.tsx │ ├── pixi │ │ ├── Pixi.tsx │ │ ├── PixiAssetManager.ts │ │ └── utils.ts │ ├── play-tabs.tsx │ ├── portal-to-body │ │ └── PortalToBody.tsx │ ├── preview │ │ ├── json-error-highlighter.tsx │ │ ├── live-preview-frame.tsx │ │ ├── multiple-web-browsers.tsx │ │ └── web-browser.tsx │ ├── pylon.tsx │ ├── search-params-provider │ │ └── search-params-provider.tsx │ ├── section-head │ │ ├── SectionHead.css │ │ └── SectionHead.tsx │ ├── section-title │ │ └── SectionTitle.tsx │ ├── tabs │ │ └── Tabs.tsx │ ├── ui │ │ ├── app-dialog.tsx │ │ ├── ascii-dot-loader.tsx │ │ ├── dot-grid-loader.tsx │ │ ├── empty-state.tsx │ │ ├── index.ts │ │ ├── loading-state.tsx │ │ ├── mobile-sheet.tsx │ │ └── stat-card.tsx │ └── utils │ │ └── portal-to-body.tsx ├── ui │ ├── LoadingDots │ │ ├── LoadingDots.module.css │ │ ├── LoadingDots.tsx │ │ └── index.ts │ ├── button-demo.tsx │ ├── button.tsx │ ├── code.tsx │ ├── image.tsx │ ├── index.ts │ ├── loading-dashboard.tsx │ ├── magic │ │ ├── animated-shiny-text-lw.tsx │ │ ├── animated-shiny-text.tsx │ │ ├── dock.tsx │ │ ├── dot-pattern.tsx │ │ ├── gradual-spacing.tsx │ │ └── ripple.tsx │ ├── menu-header.tsx │ ├── menu.tsx │ ├── modal.tsx │ ├── motion │ │ ├── scramble-text.tsx │ │ └── text-reveal.tsx │ ├── scrollbar.tsx │ ├── shadcn │ │ ├── accordion.tsx │ │ ├── alert-dialog.tsx │ │ ├── badge.tsx │ │ ├── button.css │ │ ├── button.tsx │ │ ├── card.tsx │ │ ├── checkbox.tsx │ │ ├── collapsible.tsx │ │ ├── combobox.tsx │ │ ├── context-menu.tsx │ │ ├── data-table.tsx │ │ ├── dialog.tsx │ │ ├── dropdown-menu.tsx │ │ ├── form.tsx │ │ ├── input.tsx │ │ ├── label.tsx │ │ ├── navigation-menu.tsx │ │ ├── popover.tsx │ │ ├── progress.tsx │ │ ├── scroll-area.tsx │ │ ├── select.tsx │ │ ├── separator.tsx │ │ ├── sheet.tsx │ │ ├── slider.tsx │ │ ├── switch.tsx │ │ ├── tabs.tsx │ │ ├── textarea.tsx │ │ ├── toast.tsx │ │ ├── toggle.tsx │ │ ├── tooltip-radix.tsx │ │ └── tooltip.tsx │ ├── spinner.tsx │ ├── table.tsx │ └── video.tsx └── workflow │ └── WorkflowExecutionRenderer.tsx ├── convex.json ├── convex ├── README.md ├── _generated │ ├── api.d.ts │ ├── api.js │ ├── dataModel.d.ts │ ├── server.d.ts │ └── server.js ├── admin.ts ├── apiKeys.ts ├── approvals.ts ├── auth.config.ts ├── executions.ts ├── mcpServers.ts ├── schema.ts ├── templates.ts ├── tsconfig.json ├── tsconfig.tsbuildinfo ├── userLLMKeys.ts ├── userMCPs.ts └── workflows.ts ├── hooks ├── useApprovalWatch.ts ├── useDebouncedEffect.ts ├── useWorkflow.ts └── useWorkflowExecution.ts ├── lib ├── api │ ├── auth.ts │ ├── config.ts │ ├── llm-keys.ts │ └── models.ts ├── approval │ └── approval-store.ts ├── arcade │ ├── auth-store.ts │ ├── openai-tools.ts │ ├── tools.ts │ └── workflow-helpers.ts ├── config │ └── llm-config.ts ├── convex │ └── client.ts ├── errors │ ├── WorkflowError.ts │ └── index.ts ├── mcp │ ├── mcp-registry.ts │ └── resolver.ts └── workflow │ ├── default-workflows.ts │ ├── duplicate-detection.ts │ ├── edge-cleanup.ts │ ├── error-boundaries.ts │ ├── executors │ ├── agent.ts │ ├── arcade.ts │ ├── data.ts │ ├── extract.ts │ ├── http.ts │ ├── logic.ts │ ├── mcp.ts │ └── tools.ts │ ├── langgraph.ts │ ├── mcp-registry.ts │ ├── storage.ts │ ├── templates.ts │ ├── templates │ └── examples │ │ ├── 01-simple-agent.ts │ │ ├── 02-agent-with-firecrawl.ts │ │ ├── 03-scrape-summarize-docs.ts │ │ ├── 04-advanced-workflow.ts │ │ ├── README.md │ │ └── index.ts │ ├── types.ts │ ├── validation.ts │ └── variable-substitution.ts ├── next-env.d.ts ├── next.config.js ├── package-lock.json ├── package.json ├── postcss.config.js ├── proxy.ts ├── public ├── compressor.json └── favicon.png ├── repomix.config.json ├── styles ├── additional-styles │ ├── custom-fonts.css │ ├── theme.css │ └── utility-patterns.css ├── chrome-bug.css ├── colors.json ├── components │ ├── button.css │ ├── code.css │ └── index.css ├── design-system │ ├── animations.css │ ├── base │ │ ├── body.css │ │ ├── layout.css │ │ └── reset.css │ ├── colors.css │ ├── fonts.css │ ├── typography.css │ └── utilities.css ├── fire.css ├── main.css └── workflow-execution.css ├── tailwind.config.ts ├── tsconfig.json ├── tsconfig.tsbuildinfo └── utils ├── cn.ts ├── init-canvas.ts ├── on-visible.ts ├── set-timeout-on-visible.ts └── sleep.ts /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "next/core-web-vitals", 3 | "rules": {} 4 | } 5 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | /.pnp 6 | .pnp.js 7 | 8 | # testing 9 | /coverage 10 | 11 | # next.js 12 | /.next/ 13 | /out/ 14 | 15 | # production 16 | /build 17 | 18 | # misc 19 | .DS_Store 20 | *.pem 21 | 22 | # debug 23 | npm-debug.log* 24 | yarn-debug.log* 25 | yarn-error.log* 26 | 27 | # local env files 28 | .env 29 | .env.local 30 | .env.development.local 31 | .env.test.local 32 | .env.production.local 33 | 34 | # vercel 35 | .vercel 36 | 37 | # editors 38 | .vscode 39 | 40 | # certificates 41 | certificates 42 | 43 | 44 | .env 45 | .env.* 46 | post_migrator.py 47 | 48 | # Server 49 | firecrawl-responses-api/.env -------------------------------------------------------------------------------- /.npmrc: -------------------------------------------------------------------------------- 1 | legacy-peer-deps=true 2 | -------------------------------------------------------------------------------- /app/api/approval/[approvalId]/resume/route.ts: -------------------------------------------------------------------------------- 1 | import { NextRequest, NextResponse } from 'next/server'; 2 | import { getApprovalRecord } from '@/lib/approval/approval-store'; 3 | 4 | export const dynamic = 'force-dynamic'; 5 | 6 | /** 7 | * GET /api/approval/[approvalId]/resume - Get workflow resume data 8 | * This endpoint returns the execution state needed to resume the workflow 9 | */ 10 | export async function GET( 11 | request: NextRequest, 12 | { params }: { params: Promise<{ approvalId: string }> } 13 | ) { 14 | const { approvalId } = await params; 15 | 16 | if (!approvalId) { 17 | return NextResponse.json( 18 | { success: false, error: 'Approval ID is required' }, 19 | { status: 400 } 20 | ); 21 | } 22 | 23 | const record = await getApprovalRecord(approvalId); 24 | if (!record) { 25 | return NextResponse.json( 26 | { success: false, error: 'Approval record not found' }, 27 | { status: 404 } 28 | ); 29 | } 30 | 31 | if (record.status === 'pending') { 32 | return NextResponse.json( 33 | { success: false, error: 'Approval is still pending' }, 34 | { status: 400 } 35 | ); 36 | } 37 | 38 | return NextResponse.json({ 39 | success: true, 40 | approved: record.status === 'approved', 41 | record, 42 | }); 43 | } 44 | -------------------------------------------------------------------------------- /app/api/approval/route.ts: -------------------------------------------------------------------------------- 1 | import { NextRequest, NextResponse } from 'next/server'; 2 | import { createOrUpdateApprovalRecord } from '@/lib/approval/approval-store'; 3 | 4 | export const dynamic = 'force-dynamic'; 5 | 6 | /** 7 | * POST /api/approval - Create a new approval request 8 | */ 9 | export async function POST(request: NextRequest) { 10 | try { 11 | const body = await request.json(); 12 | const { approvalId, executionId, workflowId, nodeId, message, userId } = body; 13 | 14 | if (!approvalId || !executionId || !workflowId || !nodeId) { 15 | return NextResponse.json( 16 | { success: false, error: 'Missing required fields' }, 17 | { status: 400 } 18 | ); 19 | } 20 | 21 | const record = await createOrUpdateApprovalRecord({ 22 | approvalId, 23 | executionId, 24 | workflowId, 25 | nodeId, 26 | message: message || 'Approval required', 27 | userId, 28 | status: 'pending', 29 | }); 30 | 31 | return NextResponse.json({ success: true, record }); 32 | } catch (error) { 33 | console.error('Failed to create approval record:', error); 34 | return NextResponse.json( 35 | { 36 | success: false, 37 | error: error instanceof Error ? error.message : 'Failed to create approval', 38 | }, 39 | { status: 500 } 40 | ); 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /app/api/config/route.ts: -------------------------------------------------------------------------------- 1 | import { NextResponse } from 'next/server'; 2 | 3 | /** 4 | * API route to securely provide environment variables 5 | * Only exposes API keys from .env.local, never from client 6 | */ 7 | export async function GET() { 8 | try { 9 | const config = { 10 | anthropicConfigured: !!process.env.ANTHROPIC_API_KEY, 11 | groqConfigured: !!process.env.GROQ_API_KEY, 12 | openaiConfigured: !!process.env.OPENAI_API_KEY, 13 | firecrawlConfigured: !!process.env.FIRECRAWL_API_KEY, 14 | arcadeConfigured: !!process.env.ARCADE_API_KEY, 15 | hasKeys: !!( 16 | (process.env.ANTHROPIC_API_KEY || process.env.GROQ_API_KEY || process.env.OPENAI_API_KEY) && 17 | process.env.FIRECRAWL_API_KEY 18 | ), 19 | }; 20 | 21 | return NextResponse.json(config); 22 | } catch (error) { 23 | console.error('Config API error:', error); 24 | return NextResponse.json( 25 | { error: 'Failed to load configuration' }, 26 | { status: 500 } 27 | ); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /app/api/mcp/registry/route.ts: -------------------------------------------------------------------------------- 1 | import { NextResponse } from 'next/server'; 2 | import { officialMCPServers, getEnabledMCPServers, getMCPServerById } from '@/lib/mcp/mcp-registry'; 3 | 4 | export const dynamic = 'force-dynamic'; 5 | 6 | /** 7 | * GET /api/mcp/registry 8 | * List all MCP servers from registry 9 | */ 10 | export async function GET() { 11 | try { 12 | // Get servers from code-defined configuration 13 | const servers = getEnabledMCPServers(); 14 | 15 | return NextResponse.json({ 16 | success: true, 17 | servers, 18 | source: 'config', 19 | }); 20 | } catch (error) { 21 | console.error('Failed to get MCP registry:', error); 22 | return NextResponse.json( 23 | { success: false, error: 'Failed to load MCP registry' }, 24 | { status: 500 } 25 | ); 26 | } 27 | } 28 | 29 | -------------------------------------------------------------------------------- /app/api/workflows/[workflowId]/export-code/route.ts: -------------------------------------------------------------------------------- 1 | import { NextRequest, NextResponse } from 'next/server'; 2 | import { workflowToLangGraphCode } from '@/lib/workflow/langgraph'; 3 | 4 | export const dynamic = 'force-dynamic'; 5 | 6 | /** 7 | * Export workflow as executable LangGraph TypeScript code 8 | * POST /api/workflows/:workflowId/export-code 9 | * Expects workflow data in request body 10 | */ 11 | export async function POST( 12 | request: NextRequest, 13 | { params }: { params: Promise<{ workflowId: string }> } 14 | ) { 15 | try { 16 | const { workflowId } = await params; 17 | const workflow = await request.json(); 18 | 19 | if (!workflow || !workflow.nodes) { 20 | return NextResponse.json( 21 | { error: 'Workflow data is required in request body' }, 22 | { status: 400 } 23 | ); 24 | } 25 | 26 | // Generate TypeScript code 27 | const code = workflowToLangGraphCode(workflow); 28 | 29 | // Return as JSON with code string 30 | return NextResponse.json({ 31 | code, 32 | filename: `${workflow.name.replace(/\s+/g, '_')}.ts`, 33 | language: 'typescript', 34 | }); 35 | } catch (error) { 36 | console.error('Code export error:', error); 37 | return NextResponse.json( 38 | { 39 | error: 'Code export failed', 40 | message: error instanceof Error ? error.message : 'Unknown error', 41 | }, 42 | { status: 500 } 43 | ); 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /app/api/workflows/[workflowId]/export-langgraph/route.ts: -------------------------------------------------------------------------------- 1 | import { NextRequest, NextResponse } from 'next/server'; 2 | import { workflowToLangGraphJSON } from '@/lib/workflow/langgraph'; 3 | 4 | export const dynamic = 'force-dynamic'; 5 | 6 | /** 7 | * Export workflow as LangGraph JSON 8 | * POST /api/workflows/:workflowId/export-langgraph 9 | * Expects workflow data in request body 10 | */ 11 | export async function POST( 12 | request: NextRequest, 13 | { params }: { params: Promise<{ workflowId: string }> } 14 | ) { 15 | try { 16 | const { workflowId } = await params; 17 | const workflow = await request.json(); 18 | 19 | if (!workflow || !workflow.nodes) { 20 | return NextResponse.json( 21 | { error: 'Workflow data is required in request body' }, 22 | { status: 400 } 23 | ); 24 | } 25 | 26 | // Convert to LangGraph format 27 | const langGraphJSON = workflowToLangGraphJSON(workflow); 28 | 29 | // Return as downloadable JSON 30 | return new NextResponse(JSON.stringify(langGraphJSON, null, 2), { 31 | headers: { 32 | 'Content-Type': 'application/json', 33 | 'Content-Disposition': `attachment; filename="${workflow.name.replace(/\s+/g, '_')}_langgraph.json"`, 34 | }, 35 | }); 36 | } catch (error) { 37 | console.error('Export error:', error); 38 | return NextResponse.json( 39 | { 40 | error: 'Export failed', 41 | message: error instanceof Error ? error.message : 'Unknown error', 42 | }, 43 | { status: 500 } 44 | ); 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /app/api/workflows/cleanup/route.ts: -------------------------------------------------------------------------------- 1 | import { NextResponse } from 'next/server'; 2 | import { getAuthenticatedConvexClient, api } from '@/lib/convex/client'; 3 | 4 | /** 5 | * DELETE /api/workflows/cleanup 6 | * Clean up workflows without userId (development/admin only) 7 | */ 8 | export async function DELETE() { 9 | try { 10 | const convex = await getAuthenticatedConvexClient(); 11 | const result = await convex.mutation(api.workflows.deleteWorkflowsWithoutUserId, {}); 12 | 13 | return NextResponse.json(result); 14 | } catch (error) { 15 | console.error('Error cleaning up workflows:', error); 16 | return NextResponse.json( 17 | { 18 | error: 'Failed to clean up workflows', 19 | message: error instanceof Error ? error.message : 'Unknown error', 20 | }, 21 | { status: 500 } 22 | ); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /app/api/workflows/import-langgraph/route.ts: -------------------------------------------------------------------------------- 1 | import { NextRequest, NextResponse } from 'next/server'; 2 | import { langGraphJSONToWorkflow } from '@/lib/workflow/langgraph'; 3 | import { saveWorkflow } from '@/lib/workflow/storage'; 4 | 5 | export const dynamic = 'force-dynamic'; 6 | 7 | /** 8 | * Import workflow from LangGraph JSON 9 | * POST /api/workflows/import-langgraph 10 | */ 11 | export async function POST(request: NextRequest) { 12 | try { 13 | const langGraphJSON = await request.json(); 14 | 15 | // Validate input 16 | if (!langGraphJSON.nodes || !langGraphJSON.edges) { 17 | return NextResponse.json( 18 | { error: 'Invalid LangGraph JSON: missing nodes or edges' }, 19 | { status: 400 } 20 | ); 21 | } 22 | 23 | // Convert to workflow format 24 | const workflow = langGraphJSONToWorkflow(langGraphJSON); 25 | 26 | // Save workflow 27 | await saveWorkflow(workflow); 28 | 29 | return NextResponse.json({ 30 | success: true, 31 | workflow: { 32 | id: workflow.id, 33 | name: workflow.name, 34 | description: workflow.description, 35 | nodeCount: workflow.nodes.length, 36 | edgeCount: workflow.edges.length, 37 | }, 38 | }); 39 | } catch (error) { 40 | console.error('Import error:', error); 41 | return NextResponse.json( 42 | { 43 | error: 'Import failed', 44 | message: error instanceof Error ? error.message : 'Unknown error', 45 | }, 46 | { status: 500 } 47 | ); 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /app/layout.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | 3 | import { GeistMono } from "geist/font/mono"; 4 | import { Roboto_Mono } from "next/font/google"; 5 | import { Toaster } from "sonner"; 6 | import { ClerkProvider, useAuth } from '@clerk/nextjs'; 7 | import { ConvexProviderWithClerk } from "convex/react-clerk"; 8 | import { ConvexReactClient } from "convex/react"; 9 | import ColorStyles from "@/components/shared/color-styles/color-styles"; 10 | import Scrollbar from "@/components/ui/scrollbar"; 11 | import { BigIntProvider } from "@/components/providers/BigIntProvider"; 12 | import "styles/main.css"; 13 | 14 | const convex = new ConvexReactClient(process.env.NEXT_PUBLIC_CONVEX_URL!); 15 | 16 | const robotoMono = Roboto_Mono({ 17 | subsets: ["latin"], 18 | weight: ["400", "500"], 19 | variable: "--font-roboto-mono", 20 | }); 21 | 22 | // Metadata must be in a separate server component 23 | // For now, set via document head 24 | 25 | export default function RootLayout({ 26 | children, 27 | }: { 28 | children: React.ReactNode; 29 | }) { 30 | return ( 31 | 32 | 33 | 34 | 35 | Open Agent Builder 36 | 37 | 38 | 39 | 40 | 43 | 44 |
{children}
45 | 46 | 47 |
48 | 49 | 50 |
51 |
52 | ); 53 | } 54 | 55 | -------------------------------------------------------------------------------- /app/sign-in/[[...sign-in]]/page.tsx: -------------------------------------------------------------------------------- 1 | import { SignIn } from '@clerk/nextjs' 2 | 3 | export default function SignInPage() { 4 | return ( 5 |
6 | 18 |
19 | ) 20 | } 21 | -------------------------------------------------------------------------------- /app/sign-up/[[...sign-up]]/page.tsx: -------------------------------------------------------------------------------- 1 | import { SignUp } from '@clerk/nextjs' 2 | 3 | export default function SignUpPage() { 4 | return ( 5 |
6 | 18 |
19 | ) 20 | } 21 | -------------------------------------------------------------------------------- /app/workflows/[workflowId]/page.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | 3 | import { useEffect, useState } from "react"; 4 | import { useRouter } from "next/navigation"; 5 | import WorkflowBuilder from "@/components/app/(home)/sections/workflow-builder/WorkflowBuilder"; 6 | 7 | export default function WorkflowPage({ params }: { params: Promise<{ workflowId: string }> }) { 8 | const [workflowId, setWorkflowId] = useState(null); 9 | const router = useRouter(); 10 | 11 | useEffect(() => { 12 | params.then(({ workflowId }) => { 13 | setWorkflowId(workflowId); 14 | }); 15 | }, [params]); 16 | 17 | const handleBack = () => { 18 | router.push('/'); 19 | }; 20 | 21 | if (!workflowId) { 22 | return
Loading...
; 23 | } 24 | 25 | return ( 26 | 31 | ); 32 | } 33 | -------------------------------------------------------------------------------- /app/workflows/[workflowId]/run/page.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | 3 | import { useEffect, useState } from "react"; 4 | import { useRouter } from "next/navigation"; 5 | import WorkflowBuilder from "@/components/app/(home)/sections/workflow-builder/WorkflowBuilder"; 6 | 7 | export default function WorkflowRunPage({ params }: { params: Promise<{ workflowId: string }> }) { 8 | const [workflowId, setWorkflowId] = useState(null); 9 | const router = useRouter(); 10 | 11 | useEffect(() => { 12 | params.then(({ workflowId }) => { 13 | setWorkflowId(workflowId); 14 | }); 15 | }, [params]); 16 | 17 | const handleBack = () => { 18 | router.push('/'); 19 | }; 20 | 21 | if (!workflowId) { 22 | return
Loading...
; 23 | } 24 | 25 | // This page auto-opens the execution panel 26 | return ( 27 | 32 | ); 33 | } 34 | -------------------------------------------------------------------------------- /app/workflows/page.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | 3 | import { useEffect } from "react"; 4 | import { useRouter } from "next/navigation"; 5 | 6 | export default function WorkflowsPage() { 7 | const router = useRouter(); 8 | 9 | useEffect(() => { 10 | // Redirect to home 11 | router.push('/'); 12 | }, [router]); 13 | 14 | return null; 15 | } 16 | -------------------------------------------------------------------------------- /atoms/sheets.ts: -------------------------------------------------------------------------------- 1 | import { atom } from 'jotai'; 2 | 3 | export const isMobileSheetOpenAtom = atom(false); 4 | -------------------------------------------------------------------------------- /components/app/(home)/sections/endpoints/EndpointsMap/EndpointsMap.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | 3 | import { ComponentProps } from "react"; 4 | 5 | import EndpointsScrape from "@/components/app/(home)/sections/endpoints/EndpointsScrape/EndpointsScrape"; 6 | 7 | export default function EndpointsMap( 8 | props: ComponentProps, 9 | ) { 10 | return ; 11 | } 12 | -------------------------------------------------------------------------------- /components/app/(home)/sections/hero-flame/HeroFlame.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | 3 | import { useEffect, useRef } from "react"; 4 | 5 | import { setIntervalOnVisible } from "@/utils/set-timeout-on-visible"; 6 | 7 | import data from "./data.json"; 8 | 9 | export default function HeroFlame() { 10 | const ref = useRef(null); 11 | const ref2 = useRef(null); 12 | const wrapperRef = useRef(null); 13 | 14 | useEffect(() => { 15 | let index = 0; 16 | 17 | const interval = setIntervalOnVisible({ 18 | element: wrapperRef.current, 19 | callback: () => { 20 | index++; 21 | if (index >= data.length) index = 0; 22 | 23 | ref.current!.innerHTML = data[index]; 24 | ref2.current!.innerHTML = data[index]; 25 | }, 26 | interval: 85, 27 | }); 28 | 29 | return () => interval?.(); 30 | }, []); 31 | 32 | return ( 33 |
37 |
38 |
48 |
49 | 50 |
51 |
61 |
62 |
63 | ); 64 | } 65 | -------------------------------------------------------------------------------- /components/app/(home)/sections/hero-input/Button/Button.tsx: -------------------------------------------------------------------------------- 1 | import { AnimatePresence, motion } from "motion/react"; 2 | 3 | import AnimatedWidth from "@/components/shared/layout/animated-width"; 4 | import ArrowRight from "@/components/app/(home)/sections/hero-input/_svg/ArrowRight"; 5 | import Button from "@/components/shared/button/Button"; 6 | 7 | export default function HeroInputSubmitButton({ 8 | tab, 9 | dirty, 10 | }: { 11 | tab: string; 12 | dirty: boolean; 13 | }) { 14 | return ( 15 | 35 | ); 36 | } 37 | -------------------------------------------------------------------------------- /components/app/(home)/sections/hero-input/_svg/ArrowRight.tsx: -------------------------------------------------------------------------------- 1 | export default function ArrowRight() { 2 | return ( 3 | 10 | 17 | 18 | ); 19 | } 20 | -------------------------------------------------------------------------------- /components/app/(home)/sections/hero-input/_svg/Globe.tsx: -------------------------------------------------------------------------------- 1 | export default function Globe() { 2 | return ( 3 | 10 | 17 | 18 | ); 19 | } 20 | -------------------------------------------------------------------------------- /components/app/(home)/sections/hero-scraping/Code/Loading/Loading.tsx: -------------------------------------------------------------------------------- 1 | import { AnimatePresence, motion } from "motion/react"; 2 | import { useEffect, useState } from "react"; 3 | 4 | import { encryptText } from "@/components/app/(home)/sections/hero/Title/Title"; 5 | import AnimatedWidth from "@/components/shared/layout/animated-width"; 6 | import Spinner from "@/components/ui/spinner"; 7 | 8 | export default function HeroScrapingCodeLoading({ 9 | finished, 10 | }: { 11 | finished: boolean; 12 | }) { 13 | const [scrapingText, setScrapingText] = useState("Scraping..."); 14 | 15 | useEffect(() => { 16 | if (finished) return; 17 | 18 | let timeout = 0; 19 | let tick = 0; 20 | 21 | const animate = () => { 22 | tick += 1; 23 | 24 | if (tick % 3 !== 0) { 25 | setScrapingText( 26 | encryptText("Scraping", 0, { 27 | randomizeChance: 0.6 + Math.random() * 0.3, 28 | }) + "...", 29 | ); 30 | } else { 31 | setScrapingText("Scraping..."); 32 | } 33 | 34 | const interval = 80; 35 | timeout = window.setTimeout(animate, interval); 36 | }; 37 | 38 | animate(); 39 | 40 | return () => { 41 | window.clearTimeout(timeout); 42 | }; 43 | }, [finished]); 44 | 45 | return ( 46 |
47 | 48 | 49 | 50 | 51 | 57 | {finished ? "Scrape Completed" : scrapingText} 58 | 59 | 60 | 61 |
62 | ); 63 | } 64 | -------------------------------------------------------------------------------- /components/app/(home)/sections/hero-scraping/Code/Loading/_svg/Check.tsx: -------------------------------------------------------------------------------- 1 | export default function Check() { 2 | return ( 3 | 10 | 16 | 17 | ); 18 | } 19 | -------------------------------------------------------------------------------- /components/app/(home)/sections/hero-scraping/HeroScraping.css: -------------------------------------------------------------------------------- 1 | .hero-scraping-highlight::before { 2 | animation: hero-scraping-highlight-before 1s linear infinite; 3 | transition: none !important; 4 | } 5 | 6 | @keyframes hero-scraping-highlight-before { 7 | 0% { 8 | border-color: var(--border-loud); 9 | opacity: 1; 10 | } 11 | 12 | 40% { 13 | opacity: 0.25; 14 | border-color: var(--heat-100); 15 | } 16 | 17 | 80% { 18 | border-color: var(--border-loud); 19 | opacity: 1; 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /components/app/(home)/sections/hero-scraping/Tag/Tag.tsx: -------------------------------------------------------------------------------- 1 | import { motion } from "motion/react"; 2 | import { ComponentProps, useEffect, useState } from "react"; 3 | 4 | import { encryptText } from "@/components/app/(home)/sections/hero/Title/Title"; 5 | import { cn } from "@/utils/cn"; 6 | 7 | export default function HeroScrapingTag({ 8 | active, 9 | label, 10 | ...attrs 11 | }: ComponentProps & { active?: boolean; label: string }) { 12 | const [value, setValue] = useState( 13 | encryptText(label, 0, { randomizeChance: 0 }), 14 | ); 15 | 16 | useEffect(() => { 17 | let progress = 0; 18 | let increaseProgress = -10; 19 | 20 | const animate = () => { 21 | increaseProgress = (increaseProgress + 1) % 5; 22 | 23 | if (increaseProgress === 4) { 24 | progress += 0.2; 25 | } 26 | 27 | if (progress > 1) { 28 | progress = 1; 29 | setValue(encryptText(label, progress, { randomizeChance: 0 })); 30 | 31 | return; 32 | } 33 | 34 | setValue(encryptText(label, progress, { randomizeChance: 0 })); 35 | 36 | const interval = 40 + progress * 20; 37 | setTimeout(animate, interval); 38 | }; 39 | 40 | animate(); 41 | }, []); 42 | 43 | return ( 44 | 66 | {value} 67 | 68 | ); 69 | } 70 | -------------------------------------------------------------------------------- /components/app/(home)/sections/hero-scraping/_svg/BrowserTab.tsx: -------------------------------------------------------------------------------- 1 | import { HTMLAttributes } from "react"; 2 | 3 | export default function BrowserTab(attrs: HTMLAttributes) { 4 | return ( 5 | 13 | 17 | 18 | ); 19 | } 20 | -------------------------------------------------------------------------------- /components/app/(home)/sections/hero/Background/BackgroundOuterPiece.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | 3 | import { useEffect, useState } from "react"; 4 | 5 | import { Connector } from "@/components/shared/layout/curvy-rect"; 6 | import { 7 | useHeaderContext, 8 | useHeaderHeight, 9 | } from "@/components/shared/header/HeaderContext"; 10 | import { cn } from "@/utils/cn"; 11 | 12 | export const BackgroundOuterPiece = () => { 13 | const [noRender, setNoRender] = useState(false); 14 | const { dropdownContent } = useHeaderContext(); 15 | const { headerHeight } = useHeaderHeight(); 16 | 17 | useEffect(() => { 18 | const heroContent = document.getElementById("hero-content"); 19 | if (!heroContent) { 20 | // If hero-content doesn't exist, don't render the background piece 21 | setNoRender(true); 22 | return; 23 | } 24 | 25 | const heroContentHeight = heroContent.clientHeight; 26 | 27 | const onScroll = () => { 28 | setNoRender(window.scrollY > heroContentHeight - 120); 29 | }; 30 | 31 | onScroll(); 32 | 33 | window.addEventListener("scroll", onScroll); 34 | 35 | return () => { 36 | window.removeEventListener("scroll", onScroll); 37 | }; 38 | }, []); 39 | 40 | return ( 41 |
50 |
51 | 52 | 53 | 54 |
55 | ); 56 | }; 57 | -------------------------------------------------------------------------------- /components/app/(home)/sections/hero/Background/_svg/CenterStar.tsx: -------------------------------------------------------------------------------- 1 | export default function CenterStar({ 2 | ...props 3 | }: React.SVGProps) { 4 | return ( 5 | 13 | 18 | 19 | 20 | ); 21 | } 22 | -------------------------------------------------------------------------------- /components/app/(home)/sections/hero/Badge/Badge.tsx: -------------------------------------------------------------------------------- 1 | import Link from "next/link"; 2 | 3 | export default function HomeHeroBadge() { 4 | return ( 5 | e.preventDefault()} 9 | > 10 |
OpenAI Inspired Agent Builder
11 | 12 |
13 |
14 | 21 | 29 | 30 | 38 | 39 |
40 |
41 | 42 | ); 43 | } 44 | -------------------------------------------------------------------------------- /components/app/(home)/sections/hero/Pixi/Pixi.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | 3 | import { Suspense, lazy, useState, useEffect } from "react"; 4 | 5 | const Pixi = lazy(() => import("@/components/shared/pixi/Pixi")); 6 | import features from "./tickers/features"; 7 | 8 | function PixiContent() { 9 | return ( 10 | 19 | ); 20 | } 21 | 22 | export default function HomeHeroPixi() { 23 | const [hasError, setHasError] = useState(false); 24 | 25 | useEffect(() => { 26 | const handleError = (e: ErrorEvent) => { 27 | if (e.message.includes('pixi') || e.message.includes('ChunkLoadError')) { 28 | setHasError(true); 29 | } 30 | }; 31 | 32 | window.addEventListener('error', handleError); 33 | return () => window.removeEventListener('error', handleError); 34 | }, []); 35 | 36 | if (hasError) { 37 | // Return empty div as fallback if Pixi fails to load 38 | return
; 39 | } 40 | 41 | return ( 42 | }> 43 | 44 | 45 | ); 46 | } 47 | -------------------------------------------------------------------------------- /components/app/(home)/sections/hero/Pixi/tickers/features/cell.ts: -------------------------------------------------------------------------------- 1 | import { Ticker } from "@/components/shared/pixi/Pixi"; 2 | 3 | import AnimatedRect from "./components/AnimatedRect"; 4 | import BlinkingContainer from "./components/BlinkingContainer"; 5 | import crawl from "./crawl"; 6 | import mapping from "./mapping"; 7 | import scrape from "./scrape"; 8 | import search from "./search"; 9 | 10 | type Props = Parameters[0] & { 11 | x: number; 12 | y: number; 13 | }; 14 | 15 | export const CELL_SIZE = 80; 16 | 17 | export const MAIN_COLOR = 0xe6e6e6; 18 | 19 | const animations = [scrape, mapping, search, crawl]; 20 | 21 | let lastActive = -1; 22 | 23 | export default function cell(props: Props) { 24 | const blinkingContainer = BlinkingContainer({ 25 | x: props.x + 10, 26 | y: props.y + 10, 27 | app: props.app, 28 | }); 29 | 30 | const anchorGraphic = AnimatedRect({ 31 | app: props.app, 32 | x: CELL_SIZE / 2, 33 | y: CELL_SIZE / 2, 34 | width: 4, 35 | height: 4, 36 | radius: 10, 37 | color: MAIN_COLOR, 38 | }); 39 | 40 | blinkingContainer.container.addChild(anchorGraphic.graphic); 41 | 42 | props.app.stage.addChild(blinkingContainer.container); 43 | 44 | let running = false; 45 | 46 | return { 47 | trigger: async () => { 48 | if (running) return; 49 | 50 | running = true; 51 | 52 | lastActive = (lastActive + 1) % animations.length; 53 | 54 | const fn = animations[lastActive]; 55 | 56 | await fn({ 57 | ...props, 58 | blinkingContainer, 59 | anchorGraphic, 60 | }); 61 | 62 | running = false; 63 | }, 64 | }; 65 | } 66 | -------------------------------------------------------------------------------- /components/app/(home)/sections/hero/Pixi/tickers/features/cellReveal.ts: -------------------------------------------------------------------------------- 1 | import { Ticker } from "@/components/shared/pixi/Pixi"; 2 | import AnimatedRect from "./components/AnimatedRect"; 3 | 4 | type Props = Parameters[0] & { 5 | x: number; 6 | y: number; 7 | }; 8 | 9 | export default function cellReveal(props: Props) { 10 | const graphic = AnimatedRect({ 11 | app: props.app, 12 | x: props.x + 0.5, 13 | y: props.y + 0.5, 14 | width: 101, 15 | height: 101, 16 | radius: 0, 17 | alpha: 0, 18 | color: 0x000, 19 | centering: false, 20 | }); 21 | 22 | props.app.stage.addChild(graphic.graphic); 23 | 24 | return { 25 | trigger: async () => { 26 | let cycleCount = 0; 27 | 28 | const cycle = async () => { 29 | await graphic.animate( 30 | { 31 | alpha: Math.random() * 0.04, 32 | }, 33 | { 34 | ease: "linear", 35 | duration: 0.03, 36 | }, 37 | ); 38 | 39 | if (cycleCount < 5) { 40 | cycleCount += 1; 41 | cycle(); 42 | } else { 43 | await graphic.animate({ alpha: 0 }); 44 | graphic.graphic.destroy(); 45 | } 46 | }; 47 | 48 | cycle(); 49 | }, 50 | }; 51 | } 52 | -------------------------------------------------------------------------------- /components/app/(home)/sections/hero/Pixi/tickers/features/components/Dot.ts: -------------------------------------------------------------------------------- 1 | import { MAIN_COLOR } from "@/components/app/(home)/sections/hero/Pixi/tickers/features/cell"; 2 | 3 | import AnimatedRect from "./AnimatedRect"; 4 | 5 | export default function Dot( 6 | props: Pick< 7 | Parameters[0], 8 | "x" | "y" | "app" | "animationConfig" 9 | >, 10 | ) { 11 | return AnimatedRect({ 12 | ...props, 13 | width: 2, 14 | height: 2, 15 | radius: 10, 16 | color: MAIN_COLOR, 17 | type: "arc", 18 | }); 19 | } 20 | -------------------------------------------------------------------------------- /components/app/(home)/sections/workflow-builder/ExportLangGraphButton.tsx: -------------------------------------------------------------------------------- 1 | 'use client'; 2 | 3 | import { Download } from 'lucide-react'; 4 | import { toast } from 'sonner'; 5 | 6 | interface ExportLangGraphButtonProps { 7 | workflowId: string; 8 | workflowName: string; 9 | } 10 | 11 | export function ExportLangGraphButton({ workflowId, workflowName }: ExportLangGraphButtonProps) { 12 | const handleExport = async () => { 13 | try { 14 | const response = await fetch(`/api/workflows/${workflowId}/export-langgraph`); 15 | 16 | if (!response.ok) { 17 | throw new Error('Export failed'); 18 | } 19 | 20 | // Download the JSON file 21 | const blob = await response.blob(); 22 | const url = window.URL.createObjectURL(blob); 23 | const a = document.createElement('a'); 24 | a.href = url; 25 | a.download = `${workflowName.replace(/\s+/g, '_')}_langgraph.json`; 26 | document.body.appendChild(a); 27 | a.click(); 28 | window.URL.revokeObjectURL(url); 29 | document.body.removeChild(a); 30 | 31 | toast.success('Workflow exported as LangGraph JSON'); 32 | } catch (error) { 33 | console.error('Export error:', error); 34 | toast.error('Failed to export workflow'); 35 | } 36 | }; 37 | 38 | return ( 39 | 46 | ); 47 | } 48 | -------------------------------------------------------------------------------- /components/icons/FirecrawlLogo.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | 3 | export default function FirecrawlLogo({ className }: { className?: string }) { 4 | return ( 5 | 11 | 15 | 16 | ); 17 | } 18 | -------------------------------------------------------------------------------- /components/providers/BigIntProvider.tsx: -------------------------------------------------------------------------------- 1 | 'use client'; 2 | 3 | import { useEffect } from 'react'; 4 | 5 | /** 6 | * BigInt Serialization Provider 7 | * 8 | * Adds global JSON.stringify support for BigInt values. 9 | * Required for Next.js 16 beta + React 19 compatibility. 10 | */ 11 | export function BigIntProvider({ children }: { children: React.ReactNode }) { 12 | useEffect(() => { 13 | // Add BigInt serialization support globally 14 | if (typeof BigInt !== 'undefined') { 15 | // @ts-ignore - Adding toJSON to BigInt prototype 16 | BigInt.prototype.toJSON = function() { 17 | return this.toString(); 18 | }; 19 | } 20 | }, []); 21 | 22 | return <>{children}; 23 | } 24 | -------------------------------------------------------------------------------- /components/shared/Playground/Context/types.ts: -------------------------------------------------------------------------------- 1 | export enum Endpoint { 2 | Scrape = "scrape", 3 | Crawl = "crawl", 4 | Search = "search", 5 | Map = "map", 6 | Extract = "extract", 7 | } 8 | 9 | export enum AgentModel { 10 | FIRE_1 = "FIRE-1", 11 | } 12 | 13 | export enum FormatType { 14 | Markdown = "markdown", 15 | Summary = "summary", 16 | Json = "json", 17 | RawHtml = "rawHtml", 18 | Html = "html", 19 | Screenshot = "screenshot", 20 | ScreenshotFullPage = "screenshot@fullPage", 21 | Links = "links", 22 | } 23 | 24 | export enum SearchFormatType { 25 | Web = "web", 26 | Images = "images", 27 | News = "news", 28 | } 29 | 30 | type Prev = [never, 0, 1, 2, 3, 4, 5]; 31 | 32 | type Join = K extends string | number 33 | ? P extends string | number 34 | ? `${K}.${P}` 35 | : never 36 | : never; 37 | 38 | export type Paths = [D] extends [never] 39 | ? never 40 | : T extends object 41 | ? { 42 | [K in keyof T]-?: K extends string | number 43 | ? T[K] extends object 44 | ? K | Join> 45 | : K 46 | : never; 47 | }[keyof T] 48 | : ""; 49 | -------------------------------------------------------------------------------- /components/shared/ascii-flame-background.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | 3 | import React, { useEffect, useRef } from "react"; 4 | import { cn } from "@/utils/cn"; 5 | import { setIntervalOnVisible } from "@/utils/set-timeout-on-visible"; 6 | import data from "@/components/shared/effects/flame/explosion-data.json"; 7 | 8 | interface AsciiFlameBackgroundProps { 9 | className?: string; 10 | colorClassName?: string; 11 | fontSizePx?: number; 12 | lineHeightPx?: number; 13 | } 14 | 15 | // Reusable ASCII flame background (same frames used by CoreReliableBarFlame) 16 | export default function AsciiFlameBackground({ 17 | className, 18 | colorClassName = "text-heat-100/30", 19 | fontSizePx = 10, 20 | lineHeightPx = 12.5, 21 | }: AsciiFlameBackgroundProps) { 22 | const wrapperRef = useRef(null); 23 | const ref = useRef(null); 24 | 25 | useEffect(() => { 26 | let index = 0; 27 | const stop = setIntervalOnVisible({ 28 | element: wrapperRef.current, 29 | callback: () => { 30 | index += 1; 31 | if (index >= (data as string[]).length) index = 0; 32 | if (ref.current) ref.current.innerHTML = (data as string[])[index]; 33 | }, 34 | interval: 80, 35 | }); 36 | 37 | return () => stop?.(); 38 | }, []); 39 | 40 | return ( 41 |
45 |
57 |
58 | ); 59 | } 60 | -------------------------------------------------------------------------------- /components/shared/buttons/fire-action-link.tsx: -------------------------------------------------------------------------------- 1 | import Link from "next/link"; 2 | import { cn } from "@/utils/cn"; 3 | 4 | interface FireActionLinkProps { 5 | href?: string; 6 | label: string; 7 | className?: string; 8 | variant?: "link" | "button"; 9 | onClick?: () => void; 10 | } 11 | 12 | export function FireActionLink({ 13 | href, 14 | label, 15 | className, 16 | variant = "link", 17 | onClick, 18 | }: FireActionLinkProps) { 19 | const baseClasses = 20 | variant === "button" 21 | ? cn( 22 | "inline-block py-4 px-8 rounded-6", 23 | "text-label-small text-heat-100 bg-heat-4", 24 | "hover:bg-heat-8 transition-all", 25 | "active:scale-[0.98]", 26 | className, 27 | ) 28 | : cn( 29 | "text-label-small text-secondary hover:text-heat-100 transition-all", 30 | "hover:underline underline-offset-4", 31 | "active:scale-[0.98]", 32 | className, 33 | ); 34 | 35 | if (onClick) { 36 | return ( 37 | 40 | ); 41 | } 42 | 43 | return ( 44 | 45 | {label} 46 | 47 | ); 48 | } 49 | -------------------------------------------------------------------------------- /components/shared/buttons/index.ts: -------------------------------------------------------------------------------- 1 | // Button Components 2 | export { SlateButton } from "./slate-button"; 3 | // export { HeatButton } from "./heat-button"; 4 | export { FireActionLink } from "./fire-action-link"; 5 | -------------------------------------------------------------------------------- /components/shared/color-styles/color-styles.tsx: -------------------------------------------------------------------------------- 1 | import colors from "@/styles/colors.json"; 2 | 3 | const TYPED_COLORS = colors as unknown as Record< 4 | string, 5 | Record<"hex" | "p3", string> 6 | >; 7 | 8 | const hslValues = Object.entries(TYPED_COLORS).map(([key, value]) => { 9 | // Fix hex values - they need # prefix 10 | const hexValue = value.hex.startsWith("#") ? value.hex : `#${value.hex}`; 11 | return `--${key}: ${hexValue}`; 12 | }); 13 | 14 | const p3Values = Object.entries(TYPED_COLORS) 15 | .filter(([, value]) => value.p3) 16 | .map(([key, value]) => `--${key}: color(display-p3 ${value.p3})`); 17 | 18 | const colorsStyle = ` 19 | :root { 20 | ${hslValues.join(";\n ")} 21 | } 22 | 23 | @supports (color: color(display-p3 1 1 1)) { 24 | :root { 25 | ${p3Values.join(";\n ")} 26 | } 27 | }`; 28 | 29 | export default function ColorStyles() { 30 | return