"use client"; import { motion } from "framer-motion"; import { useState, useEffect, useMemo } from "react"; import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/shadcn/tabs"; import { Workflow } from "@/lib/workflow/types"; import { useQuery } from "convex/react"; import { api } from "@/convex/_generated/api"; interface TestEndpointPanelProps { workflowId: string; workflow: Workflow | null; environment: 'draft' | 'production'; onClose: () => void; } export default function TestEndpointPanel({ workflowId, workflow, environment, onClose }: TestEndpointPanelProps) { // Get user's API keys const apiKeys = useQuery(api.apiKeys.list, {}); const firstKey = apiKeys?.[0]; // Try to get the full API key from localStorage (only available if just generated) const [fullApiKey, setFullApiKey] = useState(null); useEffect(() => { if (typeof window !== 'undefined') { const storedKey = sessionStorage.getItem('latest_api_key'); if (storedKey) { setFullApiKey(storedKey); } } }, [apiKeys]); // Get input variables from the workflow's start node const startNode = workflow?.nodes.find(n => (n.data as any)?.nodeType === 'start'); const inputVariables = (startNode?.data as any)?.inputVariables || []; // Generate default payload from input variables const defaultPayload = useMemo(() => { if (inputVariables.length === 0) { return { input: "https://firecrawl.dev" }; } return inputVariables.reduce((acc: any, v: any) => { acc[v.name] = v.defaultValue || ''; return acc; }, {}); }, [inputVariables, workflow?.id]); const [input, setInput] = useState(JSON.stringify(defaultPayload, null, 2)); const [response, setResponse] = useState(null); const [loading, setLoading] = useState(false); const [error, setError] = useState(null); const [copiedKey, setCopiedKey] = useState(null); // Update input when workflow changes useEffect(() => { setInput(JSON.stringify(defaultPayload, null, 2)); }, [defaultPayload, workflowId]); const baseUrl = typeof window !== 'undefined' ? window.location.origin : ''; const endpointUrl = `${baseUrl}/api/workflows/${workflowId}/execute`; const streamUrl = `${baseUrl}/api/workflows/${workflowId}/execute-stream`; const parsedInput = useMemo(() => { try { return input && input.trim().length > 0 ? JSON.parse(input) : defaultPayload; } catch { return defaultPayload; } }, [input, defaultPayload]); const requestBodyMinified = useMemo( () => JSON.stringify({ input: parsedInput }), [parsedInput], ); const requestBodyPretty = useMemo( () => JSON.stringify({ input: parsedInput }, null, 2), [parsedInput], ); const handleCopy = async (key: string, value: string) => { try { await navigator.clipboard.writeText(value); setCopiedKey(key); setTimeout(() => { setCopiedKey((current) => (current === key ? null : current)); }, 1500); } catch (copyError) { console.error("Failed to copy code block", copyError); } }; // Use full API key if available (from recent generation), otherwise show placeholder const apiKeyToUse = fullApiKey || 'YOUR_API_KEY_HERE'; const hasRealKey = !!fullApiKey; const apiKeyHeader = ` -H "Authorization: Bearer ${apiKeyToUse}" \\`; const curlStandard = `curl -X POST ${endpointUrl} \\ ${apiKeyHeader} -H "Content-Type: application/json" \\ -d '${requestBodyMinified.replace(/'/g, "'\\''")}'`; const curlStreaming = `curl -N -X POST ${streamUrl} \\ ${apiKeyHeader} -H "Content-Type: application/json" \\ -H "Accept: text/event-stream" \\ -d '${requestBodyMinified.replace(/'/g, "'\\''")}'`; const apiKeyForCode = apiKeyToUse; const tsExample = `import fetch from 'node-fetch'; const payload = ${requestBodyPretty}; const response = await fetch('${endpointUrl}', { method: 'POST', headers: { 'Content-Type': 'application/json', 'Authorization': 'Bearer ${apiKeyForCode}' // Your API key }, body: JSON.stringify(payload), }); const result = await response.json(); console.log(result); // Streaming example const streamResponse = await fetch('${streamUrl}', { method: 'POST', headers: { 'Content-Type': 'application/json', 'Accept': 'text/event-stream' }, body: JSON.stringify(payload), }); const reader = streamResponse.body?.getReader(); const decoder = new TextDecoder(); while (reader) { const { value, done } = await reader.read(); if (done) break; console.log(decoder.decode(value)); }`; const pythonExample = `import requests import json payload = ${requestBodyPretty} # Standard request response = requests.post( "${endpointUrl}", headers={"Content-Type": "application/json"}, data=json.dumps(payload), ) print(response.json()) # Streaming request with requests.post( "${streamUrl}", headers={"Content-Type": "application/json", "Accept": "text/event-stream"}, data=json.dumps(payload), stream=True, ) as r: for line in r.iter_lines(): if line: print(line.decode())`; const handleTest = async () => { setLoading(true); setError(null); setResponse(null); try { // Parse input to validate JSON let parsedInput; try { parsedInput = JSON.parse(input); } catch (e) { setError('Invalid JSON in request body'); setLoading(false); return; } // API expects { input: } const requestBody = { input: parsedInput, }; const res = await fetch(endpointUrl, { method: 'POST', headers: { 'Content-Type': 'application/json', }, body: JSON.stringify(requestBody), }); const data = await res.json(); setResponse(data); } catch (err) { setError(err instanceof Error ? err.message : 'Request failed'); } finally { setLoading(false); } }; return ( {/* Header */}

Endpoint

Test your workflow API endpoint

{/* Content */}
{/* Endpoint URL */}
{endpointUrl}
{/* Request Body */}