From 62803a445ce09f0e988daa2e084a405dc061cb37 Mon Sep 17 00:00:00 2001 From: yfe404 Date: Thu, 27 Nov 2025 16:12:35 +0100 Subject: [PATCH 01/15] feat: add Apify integration for running Actors Adds a new "Run Actor" action that allows workflows to execute Apify Actors. Features: - Integration setup with API token configuration - Actor selection by ID (e.g., apify/rag-web-browser) - JSON input support for Actor parameters - Sync/async execution modes - Full input/output display in Runs panel - Connection testing Files added: - plugins/apify/ - Complete plugin implementation Files modified: - action-grid.tsx, action-config.tsx - UI for Run Actor action - workflow-executor.workflow.ts - Runtime execution handler - credential-fetcher.ts - API key mapping - integration-form-dialog.tsx - Settings form - test/route.ts - Connection test endpoint Limitations: - Icon is a placeholder, not the official Apify logo --- .../[integrationId]/test/route.ts | 48 ++++++++++ .../settings/integration-form-dialog.tsx | 26 +++++ components/ui/integration-icon.tsx | 31 +++++- components/workflow/config/action-config.tsx | 92 ++++++++++++++++++ components/workflow/config/action-grid.tsx | 11 ++- components/workflow/node-config-panel.tsx | 1 + components/workflow/nodes/action-node.tsx | 1 + lib/credential-fetcher.ts | 12 +++ lib/db/integrations.ts | 5 +- lib/db/schema.ts | 2 +- lib/steps/index.ts | 5 + lib/workflow-executor.workflow.ts | 23 +++++ plugins/apify/codegen/run-actor.ts | 53 +++++++++++ plugins/apify/icon.tsx | 16 ++++ plugins/apify/index.tsx | 68 +++++++++++++ plugins/apify/settings.tsx | 47 +++++++++ plugins/apify/steps/run-actor/config.tsx | 85 +++++++++++++++++ plugins/apify/steps/run-actor/step.ts | 95 +++++++++++++++++++ plugins/apify/test.ts | 22 +++++ plugins/index.ts | 3 +- 20 files changed, 641 insertions(+), 5 deletions(-) create mode 100644 plugins/apify/codegen/run-actor.ts create mode 100644 plugins/apify/icon.tsx create mode 100644 plugins/apify/index.tsx create mode 100644 plugins/apify/settings.tsx create mode 100644 plugins/apify/steps/run-actor/config.tsx create mode 100644 plugins/apify/steps/run-actor/step.ts create mode 100644 plugins/apify/test.ts diff --git a/app/api/integrations/[integrationId]/test/route.ts b/app/api/integrations/[integrationId]/test/route.ts index 0247e731..f4add1fb 100644 --- a/app/api/integrations/[integrationId]/test/route.ts +++ b/app/api/integrations/[integrationId]/test/route.ts @@ -68,6 +68,9 @@ export async function POST( integration.config.firecrawlApiKey ); break; + case "apify": + result = await testApifyConnection(integration.config.apifyApiKey); + break; default: return NextResponse.json( { error: "Invalid integration type" }, @@ -281,3 +284,48 @@ async function testFirecrawlConnection( }; } } + +async function testApifyConnection( + apiKey?: string +): Promise { + try { + if (!apiKey) { + return { + status: "error", + message: "Apify API Token is not configured", + }; + } + + // Test by fetching user info from Apify API + const response = await fetch("https://api.apify.com/v2/users/me", { + headers: { + Authorization: `Bearer ${apiKey}`, + }, + }); + + if (!response.ok) { + return { + status: "error", + message: "Invalid API token", + }; + } + + const data = await response.json(); + if (!data.data?.username) { + return { + status: "error", + message: "Failed to verify API token", + }; + } + + return { + status: "success", + message: `Connected as ${data.data.username}`, + }; + } catch (error) { + return { + status: "error", + message: error instanceof Error ? error.message : "Connection failed", + }; + } +} diff --git a/components/settings/integration-form-dialog.tsx b/components/settings/integration-form-dialog.tsx index f78e70be..4bdfbe9f 100644 --- a/components/settings/integration-form-dialog.tsx +++ b/components/settings/integration-form-dialog.tsx @@ -41,6 +41,7 @@ type IntegrationFormData = { const INTEGRATION_TYPES: IntegrationType[] = [ "ai-gateway", + "apify", "database", "linear", "resend", @@ -55,6 +56,7 @@ const INTEGRATION_LABELS: Record = { database: "Database", "ai-gateway": "AI Gateway", firecrawl: "Firecrawl", + apify: "Apify", }; export function IntegrationFormDialog({ @@ -290,6 +292,30 @@ export function IntegrationFormDialog({
+ Get your API token from{" "} + + Apify Console + +
+ Enter the Actor ID (e.g., apify/web-scraper) or use a template + reference. +
+ JSON input for the Actor. Check the Actor's documentation for required + fields. +
+ Wait for the Actor to finish and return dataset items +
+ Get your API token from{" "} + + Apify Console + + . +
diff --git a/components/workflow/config/action-grid.tsx b/components/workflow/config/action-grid.tsx index 29fce4e4..fa8b6178 100644 --- a/components/workflow/config/action-grid.tsx +++ b/components/workflow/config/action-grid.tsx @@ -24,7 +24,13 @@ type ActionType = { description: string; category: string; icon: React.ComponentType<{ className?: string }>; - integration?: "linear" | "resend" | "slack" | "vercel" | "firecrawl" | "apify"; + integration?: + | "linear" + | "resend" + | "slack" + | "vercel" + | "firecrawl" + | "apify"; }; const actions: ActionType[] = [ diff --git a/lib/db/schema.ts b/lib/db/schema.ts index 57d11683..8cc363db 100644 --- a/lib/db/schema.ts +++ b/lib/db/schema.ts @@ -85,7 +85,13 @@ export const integrations = pgTable("integrations", { type: text("type") .notNull() .$type< - "resend" | "linear" | "slack" | "database" | "ai-gateway" | "firecrawl" | "apify" + | "resend" + | "linear" + | "slack" + | "database" + | "ai-gateway" + | "firecrawl" + | "apify" >(), // biome-ignore lint/suspicious/noExplicitAny: JSONB type - encrypted credentials stored as JSON config: jsonb("config").notNull().$type(), From 6f5074d3746f3a46604bfc6908e542015c0c4b47 Mon Sep 17 00:00:00 2001 From: yfe404 Date: Thu, 27 Nov 2025 16:24:17 +0100 Subject: [PATCH 03/15] fix: sort CSS classes and format onChange handler --- components/workflow/config/action-config.tsx | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/components/workflow/config/action-config.tsx b/components/workflow/config/action-config.tsx index 7a44915e..a2055ee7 100644 --- a/components/workflow/config/action-config.tsx +++ b/components/workflow/config/action-config.tsx @@ -710,12 +710,14 @@ function RunActorFields({ className="h-4 w-4 rounded border-input" disabled={disabled} id="waitForFinish" - onChange={(e) => onUpdateConfig("waitForFinish", e.target.checked ? "true" : "false")} + onChange={(e) => + onUpdateConfig("waitForFinish", e.target.checked ? "true" : "false") + } type="checkbox" /> Wait for results From b598228c185c5b8420c24592d41d15ae8880efed Mon Sep 17 00:00:00 2001 From: yfe404 Date: Thu, 27 Nov 2025 16:32:33 +0100 Subject: [PATCH 04/15] fix: add apify to IntegrationType in api-client and integrations-manager --- components/settings/integrations-manager.tsx | 1 + lib/api-client.ts | 4 +++- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/components/settings/integrations-manager.tsx b/components/settings/integrations-manager.tsx index ceae9e4d..35601a9f 100644 --- a/components/settings/integrations-manager.tsx +++ b/components/settings/integrations-manager.tsx @@ -26,6 +26,7 @@ const INTEGRATION_TYPE_LABELS: Record = { database: "Database", "ai-gateway": "AI Gateway", firecrawl: "Firecrawl", + apify: "Apify", }; type IntegrationsManagerProps = { diff --git a/lib/api-client.ts b/lib/api-client.ts index be9f9062..fdcf1d35 100644 --- a/lib/api-client.ts +++ b/lib/api-client.ts @@ -317,7 +317,8 @@ export type IntegrationType = | "slack" | "database" | "ai-gateway" - | "firecrawl"; + | "firecrawl" + | "apify"; export type IntegrationConfig = { apiKey?: string; @@ -326,6 +327,7 @@ export type IntegrationConfig = { url?: string; openaiApiKey?: string; firecrawlApiKey?: string; + apifyApiKey?: string; }; export type Integration = { From 5aaeb7b9c45ee2eeb514b8e99dc4dea346442d4f Mon Sep 17 00:00:00 2001 From: drobnikj Date: Fri, 28 Nov 2025 15:45:19 +0100 Subject: [PATCH 05/15] feat: polish apify actions --- .../[integrationId]/test/route.ts | 21 +--- components/ui/integration-icon.tsx | 32 +---- components/workflow/config/action-config.tsx | 41 +++---- components/workflow/config/action-grid.tsx | 16 ++- components/workflow/node-config-panel.tsx | 3 +- components/workflow/nodes/action-node.tsx | 6 +- lib/steps/index.ts | 7 +- lib/workflow-executor.workflow.ts | 26 ++-- plugins/apify/codegen/run-actor.ts | 56 ++++----- plugins/apify/codegen/scrape-single-url.ts | 55 +++++++++ plugins/apify/icon.tsx | 23 ++-- plugins/apify/index.tsx | 26 ++-- plugins/apify/steps/run-actor/config.tsx | 21 +--- plugins/apify/steps/run-actor/step.ts | 66 ++++------ .../apify/steps/scrape-single-url/config.tsx | 62 ++++++++++ plugins/apify/steps/scrape-single-url/step.ts | 99 +++++++++++++++ plugins/apify/test.ts | 18 +-- pnpm-lock.yaml | 114 ++++++++++++++++++ public/integrations/apify.svg | 12 ++ 19 files changed, 486 insertions(+), 218 deletions(-) create mode 100644 plugins/apify/codegen/scrape-single-url.ts create mode 100644 plugins/apify/steps/scrape-single-url/config.tsx create mode 100644 plugins/apify/steps/scrape-single-url/step.ts create mode 100644 public/integrations/apify.svg diff --git a/app/api/integrations/[integrationId]/test/route.ts b/app/api/integrations/[integrationId]/test/route.ts index f4add1fb..1af52776 100644 --- a/app/api/integrations/[integrationId]/test/route.ts +++ b/app/api/integrations/[integrationId]/test/route.ts @@ -2,6 +2,7 @@ import { LinearClient } from "@linear/sdk"; import FirecrawlApp from "@mendable/firecrawl-js"; import { WebClient } from "@slack/web-api"; import { createGateway } from "ai"; +import { ApifyClient } from "apify-client"; import { NextResponse } from "next/server"; import postgres from "postgres"; import { Resend } from "resend"; @@ -296,22 +297,10 @@ async function testApifyConnection( }; } - // Test by fetching user info from Apify API - const response = await fetch("https://api.apify.com/v2/users/me", { - headers: { - Authorization: `Bearer ${apiKey}`, - }, - }); - - if (!response.ok) { - return { - status: "error", - message: "Invalid API token", - }; - } + const client = new ApifyClient({ token: apiKey }); + const user = await client.user("me").get(); - const data = await response.json(); - if (!data.data?.username) { + if (!user.username) { return { status: "error", message: "Failed to verify API token", @@ -320,7 +309,7 @@ async function testApifyConnection( return { status: "success", - message: `Connected as ${data.data.username}`, + message: `Connected as ${user.username}`, }; } catch (error) { return { diff --git a/components/ui/integration-icon.tsx b/components/ui/integration-icon.tsx index fbec4482..3a97cbec 100644 --- a/components/ui/integration-icon.tsx +++ b/components/ui/integration-icon.tsx @@ -56,31 +56,6 @@ function FirecrawlIcon({ className }: { className?: string }) { ); } -function ApifyIcon({ className }: { className?: string }) { - return ( - - - - - - ); -} - export function IntegrationIcon({ integration, className = "h-3 w-3", @@ -88,6 +63,7 @@ export function IntegrationIcon({ const iconMap = { linear: "/integrations/linear.svg", slack: "/integrations/slack.svg", + apify: "/integrations/apify.svg", }; // Use inline SVG for resend and vercel to support currentColor @@ -111,16 +87,12 @@ export function IntegrationIcon({ return ; } - if (integration === "apify") { - return ; - } - return ( ); diff --git a/components/workflow/config/action-config.tsx b/components/workflow/config/action-config.tsx index a2055ee7..4bbadac0 100644 --- a/components/workflow/config/action-config.tsx +++ b/components/workflow/config/action-config.tsx @@ -20,6 +20,7 @@ import { currentWorkflowIdAtom, currentWorkflowNameAtom, } from "@/lib/workflow-store"; +import { ScrapeSingleUrlConfigFields } from "@/plugins/apify/steps/scrape-single-url/config"; import { SchemaBuilder, type SchemaField } from "./schema-builder"; type ActionConfigProps = { @@ -650,7 +651,7 @@ function SearchFields({ ); } -// Run Actor fields component (Apify) +// Run Apify Actor fields component (Apify) function RunActorFields({ config, onUpdateConfig, @@ -704,29 +705,6 @@ function RunActorFields({ fields. - - - onUpdateConfig("waitForFinish", e.target.checked ? "true" : "false") - } - type="checkbox" - /> - - - Wait for results - - - Wait for the Actor to finish and return dataset items - - - > ); } @@ -735,7 +713,7 @@ function RunActorFields({ const ACTION_CATEGORIES = { System: ["HTTP Request", "Database Query", "Condition"], "AI Gateway": ["Generate Text", "Generate Image"], - Apify: ["Run Actor"], + Apify: ["Run Apify Actor", "Scrape Single URL"], Firecrawl: ["Scrape", "Search"], Linear: ["Create Ticket", "Find Issues"], Resend: ["Send Email"], @@ -973,14 +951,23 @@ export function ActionConfig({ /> )} - {/* Run Actor fields (Apify) */} - {config?.actionType === "Run Actor" && ( + {/* Run Apify Actor fields (Apify) */} + {config?.actionType === "Run Apify Actor" && ( )} + + {/* Scrape Single URL fields (Apify) */} + {config?.actionType === "Scrape Single URL" && ( + onUpdateConfig(key, String(value))} + /> + )} > ); } diff --git a/components/workflow/config/action-grid.tsx b/components/workflow/config/action-grid.tsx index fa8b6178..7f4acef2 100644 --- a/components/workflow/config/action-grid.tsx +++ b/components/workflow/config/action-grid.tsx @@ -5,7 +5,6 @@ import { Flame, Mail, MessageSquare, - Play, Search, Settings, Sparkles, @@ -17,6 +16,7 @@ import { Input } from "@/components/ui/input"; import { IntegrationIcon } from "@/components/ui/integration-icon"; import { Label } from "@/components/ui/label"; import { cn } from "@/lib/utils"; +import { ApifyIcon } from "../../../plugins/apify/icon"; type ActionType = { id: string; @@ -120,11 +120,19 @@ const actions: ActionType[] = [ integration: "firecrawl", }, { - id: "Run Actor", - label: "Run Actor", + id: "Run Apify Actor", + label: "Run Apify Actor", description: "Run an Apify Actor", category: "Apify", - icon: Play, + icon: ApifyIcon, + integration: "apify", + }, + { + id: "Scrape Single URL", + label: "Scrape Single URL", + description: "Scrape a single URL and get markdown", + category: "Apify", + icon: ApifyIcon, integration: "apify", }, ]; diff --git a/components/workflow/node-config-panel.tsx b/components/workflow/node-config-panel.tsx index 11650dca..dfee6418 100644 --- a/components/workflow/node-config-panel.tsx +++ b/components/workflow/node-config-panel.tsx @@ -665,7 +665,8 @@ export const PanelInner = () => { "Database Query": "database", Scrape: "firecrawl", Search: "firecrawl", - "Run Actor": "apify", + "Run Apify Actor": "apify", + "Scrape Single URL": "apify", } as const; const integrationType = diff --git a/components/workflow/nodes/action-node.tsx b/components/workflow/nodes/action-node.tsx index 13ca7724..7d0f00b4 100644 --- a/components/workflow/nodes/action-node.tsx +++ b/components/workflow/nodes/action-node.tsx @@ -79,7 +79,8 @@ const getIntegrationFromActionType = (actionType: string): string => { Scrape: "Firecrawl", Search: "Firecrawl", Condition: "Condition", - "Run Actor": "Apify", + "Run Apify Actor": "Apify", + "Scrape Single URL": "Apify", }; return integrationMap[actionType] || "System"; }; @@ -136,6 +137,9 @@ const getProviderLogo = (actionType: string) => { case "Scrape": case "Search": return ; + case "Run Apify Actor": + case "Scrape Single URL": + return ; case "Execute Code": return ; case "Condition": diff --git a/lib/steps/index.ts b/lib/steps/index.ts index b0dbd25a..eaf54d94 100644 --- a/lib/steps/index.ts +++ b/lib/steps/index.ts @@ -7,6 +7,7 @@ import type { generateImageStep } from "../../plugins/ai-gateway/steps/generate-image/step"; import type { generateTextStep } from "../../plugins/ai-gateway/steps/generate-text/step"; import type { apifyRunActorStep } from "../../plugins/apify/steps/run-actor/step"; +import type { scrapeSingleUrlStep } from "../../plugins/apify/steps/scrape-single-url/step"; import type { firecrawlScrapeStep } from "../../plugins/firecrawl/steps/scrape/step"; import type { firecrawlSearchStep } from "../../plugins/firecrawl/steps/search/step"; import type { createTicketStep } from "../../plugins/linear/steps/create-ticket/step"; @@ -74,10 +75,14 @@ export const stepRegistry: Record = { ( await import("../../plugins/firecrawl/steps/search/step") ).firecrawlSearchStep(input as Parameters[0]), - "Run Actor": async (input) => + "Run Apify Actor": async (input) => ( await import("../../plugins/apify/steps/run-actor/step") ).apifyRunActorStep(input as Parameters[0]), + "Scrape Single URL": async (input) => + ( + await import("../../plugins/apify/steps/scrape-single-url/step") + ).scrapeSingleUrlStep(input as Parameters[0]), }; // Helper to check if a step exists diff --git a/lib/workflow-executor.workflow.ts b/lib/workflow-executor.workflow.ts index 3847a405..690f3254 100644 --- a/lib/workflow-executor.workflow.ts +++ b/lib/workflow-executor.workflow.ts @@ -212,29 +212,39 @@ async function executeActionStep(input: { return await firecrawlSearchStep(stepInput as any); } - if (actionType === "Run Actor") { + if (actionType === "Run Apify Actor") { const { apifyRunActorStep } = await import( "../plugins/apify/steps/run-actor/step" ); - // Parse actorInput if it's a JSON string and convert waitForFinish to boolean const runActorInput = { ...stepInput }; if (typeof runActorInput.actorInput === "string") { try { runActorInput.actorInput = JSON.parse(runActorInput.actorInput); } catch { // If JSON parsing fails, keep the original string - console.warn("[Run Actor] Failed to parse actorInput as JSON"); + console.warn("[Run Apify Actor] Failed to parse actorInput as JSON"); } } - // Convert waitForFinish string to boolean (checkbox saves as "true"/"false" strings) - if (typeof runActorInput.waitForFinish === "string") { - runActorInput.waitForFinish = runActorInput.waitForFinish === "true"; - } - console.log("[Run Actor] Input:", JSON.stringify(runActorInput, null, 2)); + console.log( + "[Run Apify Actor] Input:", + JSON.stringify(runActorInput, null, 2) + ); // biome-ignore lint/suspicious/noExplicitAny: Dynamic step input type return await apifyRunActorStep(runActorInput as any); } + if (actionType === "Scrape Single URL") { + const { scrapeSingleUrlStep } = await import( + "../plugins/apify/steps/scrape-single-url/step" + ); + console.log( + "[Scrape Single URL] Input:", + JSON.stringify(stepInput, null, 2) + ); + // biome-ignore lint/suspicious/noExplicitAny: Dynamic step input type + return await scrapeSingleUrlStep(stepInput as any); + } + // Fallback for unknown action types return { success: false, diff --git a/plugins/apify/codegen/run-actor.ts b/plugins/apify/codegen/run-actor.ts index 811a738e..206078e6 100644 --- a/plugins/apify/codegen/run-actor.ts +++ b/plugins/apify/codegen/run-actor.ts @@ -1,53 +1,39 @@ /** - * Code generation template for Apify Run Actor action + * Code generation template for Run Apify Actor action * This template is used when exporting workflows to standalone Next.js projects * It uses environment variables instead of integrationId */ -export const runActorCodegenTemplate = `const APIFY_API_BASE = "https://api.apify.com/v2"; +export const runActorCodegenTemplate = `import { ApifyClient } from "apify-client"; export async function apifyRunActorStep(input: { actorId: string; actorInput?: Record; - waitForFinish?: boolean; - maxWaitSecs?: number; }) { "use step"; const apiKey = process.env.APIFY_API_KEY!; - const waitForFinish = input.waitForFinish !== false; - const maxWaitSecs = input.maxWaitSecs || 120; + const client = new ApifyClient({ token: apiKey }); + const actorClient = client.actor(input.actorId); + const maxWaitSecs = 120; - const runUrl = waitForFinish - ? \`\${APIFY_API_BASE}/acts/\${encodeURIComponent(input.actorId)}/run-sync-get-dataset-items?timeout=\${maxWaitSecs}\` - : \`\${APIFY_API_BASE}/acts/\${encodeURIComponent(input.actorId)}/runs\`; + // Run synchronously and wait for completion + const runData = await actorClient.call(input.actorInput || {}, { + waitSecs: maxWaitSecs, + }); - const runResponse = await fetch(runUrl, { - method: "POST", - headers: { - "Content-Type": "application/json", - Authorization: \`Bearer \${apiKey}\`, - }, - body: JSON.stringify(input.actorInput || {}), - }); + // Get dataset items + let data: unknown[] = []; + if (runData.defaultDatasetId) { + const datasetItems = await client + .dataset(runData.defaultDatasetId) + .listItems(); + data = datasetItems.items; + } - if (!runResponse.ok) { - const errorText = await runResponse.text().catch(() => "Unknown error"); - throw new Error(\`Failed to run Actor: \${runResponse.status} - \${errorText}\`); - } - - if (waitForFinish) { - const data = await runResponse.json(); return { - runId: runResponse.headers.get("x-apify-run-id") || "unknown", - status: "SUCCEEDED", - data: Array.isArray(data) ? data : [data], + runId: runData.id || "unknown", + status: runData.status || "SUCCEEDED", + datasetId: runData.defaultDatasetId, + data, }; - } - - const runData = await runResponse.json(); - return { - runId: runData.data?.id || "unknown", - status: runData.data?.status || "RUNNING", - datasetId: runData.data?.defaultDatasetId, - }; }`; diff --git a/plugins/apify/codegen/scrape-single-url.ts b/plugins/apify/codegen/scrape-single-url.ts new file mode 100644 index 00000000..82eca00d --- /dev/null +++ b/plugins/apify/codegen/scrape-single-url.ts @@ -0,0 +1,55 @@ +/** + * Code generation template for Scrape Single URL action + * This template is used when exporting workflows to standalone Next.js projects + * It uses environment variables instead of integrationId + */ +export const scrapeSingleUrlCodegenTemplate = `import { ApifyClient } from "apify-client"; + +export async function scrapeSingleUrlStep(input: { + url: string; + crawlerType?: string; +}) { + "use step"; + + const apiKey = process.env.APIFY_API_KEY!; + const client = new ApifyClient({ token: apiKey }); + const actorClient = client.actor("apify/website-content-crawler"); + const maxWaitSecs = 120; + const crawlerType = input.crawlerType || "playwright"; + + if (!input.url) { + throw new Error("URL is required"); + } + + // Prepare actor input + const actorInput = { + startUrls: [{ url: input.url }], + crawlerType, + outputFormat: "markdown", + }; + + // Run synchronously and wait for completion + const runData = await actorClient.call(actorInput, { + waitSecs: maxWaitSecs, + }); + + // Get dataset items + let markdown: string | undefined; + if (runData.defaultDatasetId) { + const datasetItems = await client + .dataset(runData.defaultDatasetId) + .listItems(); + + // Extract markdown from the first item + if (datasetItems.items && datasetItems.items.length > 0) { + const firstItem = datasetItems.items[0] as Record; + markdown = firstItem.markdown as string || firstItem.text as string || JSON.stringify(firstItem); + } + } + + return { + runId: runData.id || "unknown", + status: runData.status || "SUCCEEDED", + markdown, + }; +}`; diff --git a/plugins/apify/icon.tsx b/plugins/apify/icon.tsx index 41b95bc8..26358944 100644 --- a/plugins/apify/icon.tsx +++ b/plugins/apify/icon.tsx @@ -1,16 +1,17 @@ +import Image from "next/image"; + +/** + * Apify Icon Component + * Used as the icon for Run Apify Actor action + */ export function ApifyIcon({ className }: { className?: string }) { return ( - - Apify - - + height={24} + src="/integrations/apify.svg" + width={24} + /> ); } diff --git a/plugins/apify/index.tsx b/plugins/apify/index.tsx index 45bb4f84..8f40b3e5 100644 --- a/plugins/apify/index.tsx +++ b/plugins/apify/index.tsx @@ -1,10 +1,12 @@ -import { Play } from "lucide-react"; +import { Globe } from "lucide-react"; import type { IntegrationPlugin } from "../registry"; import { registerIntegration } from "../registry"; import { runActorCodegenTemplate } from "./codegen/run-actor"; +import { scrapeSingleUrlCodegenTemplate } from "./codegen/scrape-single-url"; import { ApifyIcon } from "./icon"; import { ApifySettings } from "./settings"; import { RunActorConfigFields } from "./steps/run-actor/config"; +import { ScrapeSingleUrlConfigFields } from "./steps/scrape-single-url/config"; import { testApify } from "./test"; const apifyPlugin: IntegrationPlugin = { @@ -13,9 +15,8 @@ const apifyPlugin: IntegrationPlugin = { description: "Run web scraping and automation Actors", icon: { - type: "svg", - value: "ApifyIcon", - svgComponent: ApifyIcon, + type: "image", + value: "/integrations/apify.svg", }, settingsComponent: ApifySettings, @@ -49,16 +50,27 @@ const apifyPlugin: IntegrationPlugin = { actions: [ { - id: "Run Actor", - label: "Run Actor", + id: "Run Apify Actor", + label: "Run Apify Actor", description: "Run an Apify Actor and get results", category: "Apify", - icon: Play, + icon: ApifyIcon, stepFunction: "apifyRunActorStep", stepImportPath: "run-actor", configFields: RunActorConfigFields, codegenTemplate: runActorCodegenTemplate, }, + { + id: "Scrape Single URL", + label: "Scrape Single URL", + description: "Scrape a single URL and get markdown output", + category: "Apify", + icon: Globe, + stepFunction: "scrapeSingleUrlStep", + stepImportPath: "scrape-single-url", + configFields: ScrapeSingleUrlConfigFields, + codegenTemplate: scrapeSingleUrlCodegenTemplate, + }, ], }; diff --git a/plugins/apify/steps/run-actor/config.tsx b/plugins/apify/steps/run-actor/config.tsx index c14f668c..75d6d451 100644 --- a/plugins/apify/steps/run-actor/config.tsx +++ b/plugins/apify/steps/run-actor/config.tsx @@ -3,7 +3,7 @@ import { TemplateBadgeInput } from "@/components/ui/template-badge-input"; import { TemplateBadgeTextarea } from "@/components/ui/template-badge-textarea"; /** - * Run Actor Config Fields Component + * Run Apify Actor Config Fields Component * UI for configuring the run actor action */ export function RunActorConfigFields({ @@ -61,25 +61,6 @@ export function RunActorConfigFields({ fields.
- Wait for the Actor to finish and return dataset items -
+ Enter the URL to scrape or use a template reference. +
+ Select the crawler type to use for scraping. +
JSON input for the Actor. Check the Actor's documentation for required diff --git a/plugins/apify/steps/run-actor/config.tsx b/plugins/apify/steps/run-actor/config.tsx index 75d6d451..1ed183e2 100644 --- a/plugins/apify/steps/run-actor/config.tsx +++ b/plugins/apify/steps/run-actor/config.tsx @@ -18,7 +18,7 @@ export function RunActorConfigFields({ return (
JSON input for the Actor. Check the Actor's documentation for required From 61207bc8442a3960d825821d2ec92ac164934654 Mon Sep 17 00:00:00 2001 From: drobnikj Date: Mon, 1 Dec 2025 11:52:59 +0100 Subject: [PATCH 08/15] fix: after merge fixes --- README.md | 1 + .../settings/integration-form-dialog.tsx | 3 +- components/settings/integrations-manager.tsx | 3 +- components/ui/drawer.tsx | 8 +- components/ui/dropdown-menu.tsx | 8 +- components/ui/tabs.tsx | 1 + .../config/action-config-renderer.tsx | 6 + components/workflow/nodes/action-node.tsx | 1 - lib/credential-fetcher.ts | 1 - lib/step-registry.ts | 20 ++- lib/types/integration.ts | 3 +- package.json | 3 +- plugins/apify/index.tsx | 66 ++++++++-- pnpm-lock.yaml | 114 ++++++++++++++++++ 14 files changed, 212 insertions(+), 26 deletions(-) diff --git a/README.md b/README.md index b08be906..c8d580e2 100644 --- a/README.md +++ b/README.md @@ -80,6 +80,7 @@ Visit [http://localhost:3000](http://localhost:3000) to get started. - **AI Gateway**: Generate Text, Generate Image +- **Apify**: Run Apify Actor, Scrape Single URL - **Firecrawl**: Scrape URL, Search Web - **Linear**: Create Ticket, Find Issues - **Resend**: Send Email diff --git a/components/settings/integration-form-dialog.tsx b/components/settings/integration-form-dialog.tsx index edf267fd..9312cc53 100644 --- a/components/settings/integration-form-dialog.tsx +++ b/components/settings/integration-form-dialog.tsx @@ -187,9 +187,8 @@ export function IntegrationFormDialog({
{jsonError}
- Enter the Actor ID (e.g., apify/web-scraper) or use a template - reference. + Enter an Actor ID or name (e.g., apify/website-content-crawler). Browse all available Actors in the + Apify Store + .
JSON input for the Actor. Check the Actor's documentation for required diff --git a/plugins/apify/steps/run-actor/step.ts b/plugins/apify/steps/run-actor/step.ts index 89c404bf..610261d0 100644 --- a/plugins/apify/steps/run-actor/step.ts +++ b/plugins/apify/steps/run-actor/step.ts @@ -3,6 +3,7 @@ import "server-only"; import { ApifyClient } from "apify-client"; import { fetchCredentials } from "@/lib/credential-fetcher"; import { getErrorMessage } from "@/lib/utils"; +import { type StepInput, withStepLogging } from "@/lib/steps/step-handler"; type ApifyRunActorResult = | { @@ -18,56 +19,69 @@ type ApifyRunActorResult = * Run Apify Actor Step * Runs an Apify Actor and optionally waits for results */ -export async function apifyRunActorStep(input: { - integrationId?: string; - actorId: string; - actorInput?: Record; -}): Promise { +export async function apifyRunActorStep( + input: { + integrationId?: string; + actorId: string; + actorInput?: string; + } & StepInput +): Promise { "use step"; - const credentials = input.integrationId - ? await fetchCredentials(input.integrationId) - : {}; + return withStepLogging(input, async () => { + const credentials = input.integrationId + ? await fetchCredentials(input.integrationId) + : {}; - const apiKey = credentials.APIFY_API_KEY; + const apiKey = credentials.APIFY_API_KEY; - if (!apiKey) { - return { - success: false, - error: "Apify API Token is not configured.", - }; - } + if (!apiKey) { + return { + success: false, + error: "Apify API Token is not configured.", + }; + } + + let parsedActorInput = {}; + if (input?.actorInput) { + try { + parsedActorInput = JSON.parse(input?.actorInput); + } catch (err) { + return { + success: false, + error: `Cannot parse Actor input: ${getErrorMessage(err)}`, + }; + } + } - try { - const client = new ApifyClient({ token: apiKey }); - const actorClient = client.actor(input.actorId); - const maxWaitSecs = 120; + try { + const client = new ApifyClient({ token: apiKey }); + const actorClient = client.actor(input.actorId); // Run synchronously and wait for completion - const runData = await actorClient.call(input.actorInput || {}, { - waitSecs: maxWaitSecs, - }); + const runData = await actorClient.call(parsedActorInput); // Get dataset items - let data: unknown[] = []; + let datasetItems: unknown[] = []; if (runData.defaultDatasetId) { - const datasetItems = await client - .dataset(runData.defaultDatasetId) - .listItems(); - data = datasetItems.items; + const dataset = await client + .dataset(runData.defaultDatasetId) + .listItems(); + datasetItems = dataset.items; } return { - success: true, - runId: runData.id || "unknown", - status: runData.status || "SUCCEEDED", - datasetId: runData.defaultDatasetId, - data, + success: true, + runId: runData.id || "unknown", + status: runData.status || "SUCCEEDED", + datasetId: runData.defaultDatasetId, + datasetItems, + }; + } catch (error) { + return { + success: false, + error: `Failed to run Actor: ${getErrorMessage(error)}`, }; - } catch (error) { - return { - success: false, - error: `Failed to run Actor: ${getErrorMessage(error)}`, - }; - } + } + }); } diff --git a/plugins/apify/steps/scrape-single-url/config.tsx b/plugins/apify/steps/scrape-single-url/config.tsx deleted file mode 100644 index e3c0db30..00000000 --- a/plugins/apify/steps/scrape-single-url/config.tsx +++ /dev/null @@ -1,62 +0,0 @@ -import { Label } from "@/components/ui/label"; -import { - Select, - SelectContent, - SelectItem, - SelectTrigger, - SelectValue, -} from "@/components/ui/select"; -import { TemplateBadgeInput } from "@/components/ui/template-badge-input"; - -/** - * Scrape Single URL Config Fields Component - * UI for configuring the scrape single URL action - */ -export function ScrapeSingleUrlConfigFields({ - config, - onUpdateConfig, - disabled, -}: { - config: Record; - onUpdateConfig: (key: string, value: unknown) => void; - disabled?: boolean; -}) { - return ( - - - URL - onUpdateConfig("url", value)} - placeholder="https://example.com or {{NodeName.url}}" - value={(config?.url as string) || ""} - /> - - Enter the URL to scrape or use a template reference. - - - - - Crawler Type - onUpdateConfig("crawlerType", value)} - value={(config?.crawlerType as string) || "playwright"} - > - - - - - Adaptive switching between browser and raw HTTP - Headless browser (Firefox+Playwright) - Raw HTTP client (Cheerio) - - - - Select the crawler type to use for scraping. - - - - ); -} diff --git a/plugins/apify/steps/scrape-single-url/step.ts b/plugins/apify/steps/scrape-single-url/step.ts index 5adf2945..3b0bb7b7 100644 --- a/plugins/apify/steps/scrape-single-url/step.ts +++ b/plugins/apify/steps/scrape-single-url/step.ts @@ -3,6 +3,7 @@ import "server-only"; import { ApifyClient } from "apify-client"; import { fetchCredentials } from "@/lib/credential-fetcher"; import { getErrorMessage } from "@/lib/utils"; +import { type StepInput, withStepLogging } from "@/lib/steps/step-handler"; type ScrapeSingleUrlResult = | { @@ -17,83 +18,90 @@ type ScrapeSingleUrlResult = * Scrape Single URL Step * Scrapes a single URL using apify/website-content-crawler and returns markdown */ -export async function scrapeSingleUrlStep(input: { - integrationId?: string; - url: string; - crawlerType?: string; -}): Promise { +export async function scrapeSingleUrlStep( + input: { + integrationId?: string; + url: string; + crawlerType?: string; + } & StepInput +): Promise { "use step"; - const credentials = input.integrationId - ? await fetchCredentials(input.integrationId) - : {}; + return withStepLogging(input, async () => { + const credentials = input.integrationId + ? await fetchCredentials(input.integrationId) + : {}; - const apiKey = credentials.APIFY_API_KEY; + const apiKey = credentials.APIFY_API_KEY; - if (!apiKey) { - return { - success: false, - error: "Apify API Token is not configured.", - }; - } + if (!apiKey) { + return { + success: false, + error: "Apify API Token is not configured.", + }; + } - if (!input.url) { - return { - success: false, - error: "URL is required.", - }; - } + if (!input.url) { + return { + success: false, + error: "URL is required.", + }; + } - try { - const client = new ApifyClient({ token: apiKey }); - const actorClient = client.actor("apify/website-content-crawler"); - const crawlerType = input.crawlerType || "playwright"; + try { + const client = new ApifyClient({ token: apiKey }); + const actorClient = client.actor("apify/website-content-crawler"); + const crawlerType = input.crawlerType || "playwright:adaptive"; - // Prepare actor input - const actorInput = { - startUrls: [{ url: input.url }], - crawlerType, + // Prepare actor input + const actorInput = { + startUrls: [{ url: input.url }], + crawlerType, maxCrawlDepth: 0, maxCrawlPages: 1, maxResults: 1, proxyConfiguration: { - useApifyProxy: true, + useApifyProxy: true, }, removeCookieWarnings: true, - saveHtml: true, saveMarkdown: true, - }; + }; - // Run synchronously and wait for completion - const maxWaitSecs = 120; - const runData = await actorClient.call(actorInput, { - waitSecs: maxWaitSecs, - }); + // Run synchronously and wait for completion (waits indefinitely if waitSecs not specified) + const runData = await actorClient.call(actorInput); + console.log("[Scrape Single URL] Actor call completed:", { + runId: runData.id, + status: runData.status, + hasDataset: !!runData.defaultDatasetId, + }); - // Get dataset items - let markdown: string | undefined; - if (runData.defaultDatasetId) { - const datasetItems = await client - .dataset(runData.defaultDatasetId) - .listItems(); + // Get dataset items + let markdown: string | undefined; + if (runData.defaultDatasetId) { + const datasetItems = await client + .dataset(runData.defaultDatasetId) + .listItems(); - // Extract markdown from the first item - if (datasetItems.items && datasetItems.items.length > 0) { - const firstItem = datasetItems.items[0] as Record; - markdown = (firstItem.markdown as string) || (firstItem.text as string) || undefined; + // Extract markdown from the first item + if (datasetItems.items && datasetItems.items.length > 0) { + const firstItem = datasetItems.items[0] as Record; + markdown = (firstItem.markdown as string); + } } - } - return { - success: true, - runId: runData.id || "unknown", - status: runData.status || "SUCCEEDED", - markdown, - }; - } catch (error) { - return { - success: false, - error: `Failed to scrape URL: ${getErrorMessage(error)}`, - }; - } + const result: ScrapeSingleUrlResult = { + success: true, + runId: runData.id || "unknown", + status: runData.status || "SUCCEEDED", + markdown, + }; + + return result; + } catch (error) { + return { + success: false, + error: `Failed to scrape URL: ${getErrorMessage(error)}`, + }; + } + }); } From fce3519e32354acd73617ca245a74495e7efcea6 Mon Sep 17 00:00:00 2001 From: drobnikj Date: Wed, 3 Dec 2025 11:29:07 +0100 Subject: [PATCH 14/15] fix: review comments fix --- README.md | 2 +- .../[integrationId]/test/route.ts | 2 +- components/ui/template-badge-json.tsx | 6 +++ lib/step-registry.ts | 2 +- lib/steps/index.ts | 2 +- plugins/apify/codegen/run-actor.ts | 8 ++-- plugins/apify/codegen/scrape-single-url.ts | 4 +- plugins/apify/icon.tsx | 2 +- plugins/apify/index.tsx | 14 +++--- plugins/apify/settings.tsx | 47 ------------------- plugins/apify/steps/run-actor/config.tsx | 2 +- plugins/apify/steps/run-actor/step.ts | 6 +-- plugins/apify/steps/scrape-single-url/step.ts | 7 +-- plugins/apify/test.ts | 2 +- 14 files changed, 30 insertions(+), 76 deletions(-) delete mode 100644 plugins/apify/settings.tsx diff --git a/README.md b/README.md index c8d580e2..d0bb35ec 100644 --- a/README.md +++ b/README.md @@ -80,7 +80,7 @@ Visit [http://localhost:3000](http://localhost:3000) to get started. - **AI Gateway**: Generate Text, Generate Image -- **Apify**: Run Apify Actor, Scrape Single URL +- **Apify**: Run Actor, Scrape Single URL - **Firecrawl**: Scrape URL, Search Web - **Linear**: Create Ticket, Find Issues - **Resend**: Send Email diff --git a/app/api/integrations/[integrationId]/test/route.ts b/app/api/integrations/[integrationId]/test/route.ts index 1af52776..3085f617 100644 --- a/app/api/integrations/[integrationId]/test/route.ts +++ b/app/api/integrations/[integrationId]/test/route.ts @@ -70,7 +70,7 @@ export async function POST( ); break; case "apify": - result = await testApifyConnection(integration.config.apifyApiKey); + result = await testApifyConnection(integration.config.apifyApiToken); break; default: return NextResponse.json( diff --git a/components/ui/template-badge-json.tsx b/components/ui/template-badge-json.tsx index b67492fd..223a88a2 100644 --- a/components/ui/template-badge-json.tsx +++ b/components/ui/template-badge-json.tsx @@ -45,6 +45,12 @@ export function TemplateBadgeJson({ return; } + // Ensure that parsable values (not object) throws + if (!/^\s*\{[\s\S]*\}\s*$/.test(value)) { + setJsonError("Value must be a JSON object"); + return; + } + // Parse JSON directly - template variables will be treated as normal strings try { JSON.parse(value); diff --git a/lib/step-registry.ts b/lib/step-registry.ts index 6e51f4a2..48876bc9 100644 --- a/lib/step-registry.ts +++ b/lib/step-registry.ts @@ -122,7 +122,7 @@ export const PLUGIN_STEP_IMPORTERS: Record = { export const ACTION_LABELS: Record = { "ai-gateway/generate-text": "Generate Text", "ai-gateway/generate-image": "Generate Image", - "apify/run-actor": "Run Apify Actor", + "apify/run-actor": "Run Actor", "apify/scrape-single-url": "Scrape Single URL", "firecrawl/scrape": "Scrape URL", "firecrawl/search": "Search Web", diff --git a/lib/steps/index.ts b/lib/steps/index.ts index 14269795..32959688 100644 --- a/lib/steps/index.ts +++ b/lib/steps/index.ts @@ -66,7 +66,7 @@ export const stepRegistry: Record = { (await import("../../plugins/firecrawl/steps/search")).firecrawlSearchStep( input as Parameters[0] ), - "Run Apify Actor": async (input) => + "Run Actor": async (input) => ( await import("../../plugins/apify/steps/run-actor/step") ).apifyRunActorStep(input as Parameters[0]), diff --git a/plugins/apify/codegen/run-actor.ts b/plugins/apify/codegen/run-actor.ts index 826c0aea..aa43bb55 100644 --- a/plugins/apify/codegen/run-actor.ts +++ b/plugins/apify/codegen/run-actor.ts @@ -1,5 +1,5 @@ /** - * Code generation template for Run Apify Actor action + * Code generation template for Run Actor action * This template is used when exporting workflows to standalone Next.js projects * It uses environment variables instead of integrationId */ @@ -11,10 +11,10 @@ export async function apifyRunActorStep(input: { }) { "use step"; - const apiKey = process.env.APIFY_API_KEY; + const apiKey = process.env.APIFY_API_TOKEN; if (!apiKey) { - throw new Error("Apify API Token is not configured. Set APIFY_API_KEY environment variable."); + throw new Error("Apify API Token is not configured. Set APIFY_API_TOKEN environment variable."); } let parsedActorInput: Record = {}; @@ -46,7 +46,7 @@ export async function apifyRunActorStep(input: { runId: runData.id || "unknown", status: runData.status || "SUCCEEDED", datasetId: runData.defaultDatasetId, - datasetItems, + data: datasetItems, }; } catch (error) { throw new Error(\`Failed to run Actor: \${error instanceof Error ? error.message : String(error)}\`); diff --git a/plugins/apify/codegen/scrape-single-url.ts b/plugins/apify/codegen/scrape-single-url.ts index ec35c6c8..cc4d5d82 100644 --- a/plugins/apify/codegen/scrape-single-url.ts +++ b/plugins/apify/codegen/scrape-single-url.ts @@ -11,10 +11,10 @@ export async function scrapeSingleUrlStep(input: { }) { "use step"; - const apiKey = process.env.APIFY_API_KEY; + const apiKey = process.env.APIFY_API_TOKEN; if (!apiKey) { - throw new Error("Apify API Token is not configured. Set APIFY_API_KEY environment variable."); + throw new Error("Apify API Token is not configured. Set APIFY_API_TOKEN environment variable."); } if (!input.url) { diff --git a/plugins/apify/icon.tsx b/plugins/apify/icon.tsx index 26358944..6329437e 100644 --- a/plugins/apify/icon.tsx +++ b/plugins/apify/icon.tsx @@ -2,7 +2,7 @@ import Image from "next/image"; /** * Apify Icon Component - * Used as the icon for Run Apify Actor action + * Used as the icon for Run Actor action */ export function ApifyIcon({ className }: { className?: string }) { return ( diff --git a/plugins/apify/index.tsx b/plugins/apify/index.tsx index bd2b8935..faab5338 100644 --- a/plugins/apify/index.tsx +++ b/plugins/apify/index.tsx @@ -13,12 +13,12 @@ const apifyPlugin: IntegrationPlugin = { formFields: [ { - id: "apifyApiKey", - label: "API Token", + id: "apifyApiToken", + label: "Apify API Token", type: "password", placeholder: "apify_api_...", - configKey: "apifyApiKey", - envVar: "APIFY_API_KEY", + configKey: "apifyApiToken", + envVar: "APIFY_API_TOKEN", helpText: "Get your API token from ", helpLink: { text: "Apify Console", @@ -37,7 +37,7 @@ const apifyPlugin: IntegrationPlugin = { actions: [ { slug: "run-actor", - label: "Run Apify Actor", + label: "Run Actor", description: "Run an Apify Actor and get results", category: "Apify", stepFunction: "apifyRunActorStep", @@ -47,8 +47,8 @@ const apifyPlugin: IntegrationPlugin = { key: "actorId", label: "Actor (ID or name)", type: "template-input", - placeholder: "apify/web-scraper or {{NodeName.actorId}}", - example: "apify/web-scraper", + placeholder: "apify/website-content-crawler or {{NodeName.actorId}}", + example: "apify/website-content-crawler", required: true, }, { diff --git a/plugins/apify/settings.tsx b/plugins/apify/settings.tsx deleted file mode 100644 index 932f9a56..00000000 --- a/plugins/apify/settings.tsx +++ /dev/null @@ -1,47 +0,0 @@ -import { Input } from "@/components/ui/input"; -import { Label } from "@/components/ui/label"; - -export function ApifySettings({ - apiKey, - hasKey, - onApiKeyChange, -}: { - apiKey: string; - hasKey?: boolean; - onApiKeyChange: (key: string) => void; - showCard?: boolean; - config?: Record; - onConfigChange?: (key: string, value: string) => void; -}) { - return ( - - - - API Token - - onApiKeyChange(e.target.value)} - placeholder={ - hasKey ? "API token is configured" : "Enter your Apify API token" - } - type="password" - value={apiKey} - /> - - Get your API token from{" "} - - Apify Console - - . - - - - ); -} diff --git a/plugins/apify/steps/run-actor/config.tsx b/plugins/apify/steps/run-actor/config.tsx index b34ad89b..e085da9e 100644 --- a/plugins/apify/steps/run-actor/config.tsx +++ b/plugins/apify/steps/run-actor/config.tsx @@ -3,7 +3,7 @@ import { TemplateBadgeInput } from "@/components/ui/template-badge-input"; import { TemplateBadgeJson } from "@/components/ui/template-badge-json"; /** - * Run Apify Actor Config Fields Component + * Run Actor Config Fields Component * UI for configuring the run actor action */ export function RunActorConfigFields({ diff --git a/plugins/apify/steps/run-actor/step.ts b/plugins/apify/steps/run-actor/step.ts index 610261d0..3541a6f3 100644 --- a/plugins/apify/steps/run-actor/step.ts +++ b/plugins/apify/steps/run-actor/step.ts @@ -16,7 +16,7 @@ type ApifyRunActorResult = | { success: false; error: string }; /** - * Run Apify Actor Step + * Run Actor Step * Runs an Apify Actor and optionally waits for results */ export async function apifyRunActorStep( @@ -33,7 +33,7 @@ export async function apifyRunActorStep( ? await fetchCredentials(input.integrationId) : {}; - const apiKey = credentials.APIFY_API_KEY; + const apiKey = credentials.APIFY_API_TOKEN; if (!apiKey) { return { @@ -75,7 +75,7 @@ export async function apifyRunActorStep( runId: runData.id || "unknown", status: runData.status || "SUCCEEDED", datasetId: runData.defaultDatasetId, - datasetItems, + data: datasetItems, }; } catch (error) { return { diff --git a/plugins/apify/steps/scrape-single-url/step.ts b/plugins/apify/steps/scrape-single-url/step.ts index 3b0bb7b7..09c9f966 100644 --- a/plugins/apify/steps/scrape-single-url/step.ts +++ b/plugins/apify/steps/scrape-single-url/step.ts @@ -32,7 +32,7 @@ export async function scrapeSingleUrlStep( ? await fetchCredentials(input.integrationId) : {}; - const apiKey = credentials.APIFY_API_KEY; + const apiKey = credentials.APIFY_API_TOKEN; if (!apiKey) { return { @@ -69,11 +69,6 @@ export async function scrapeSingleUrlStep( // Run synchronously and wait for completion (waits indefinitely if waitSecs not specified) const runData = await actorClient.call(actorInput); - console.log("[Scrape Single URL] Actor call completed:", { - runId: runData.id, - status: runData.status, - hasDataset: !!runData.defaultDatasetId, - }); // Get dataset items let markdown: string | undefined; diff --git a/plugins/apify/test.ts b/plugins/apify/test.ts index 8382720f..729bf786 100644 --- a/plugins/apify/test.ts +++ b/plugins/apify/test.ts @@ -2,7 +2,7 @@ import { ApifyClient } from "apify-client"; export async function testApify(credentials: Record) { try { - const client = new ApifyClient({ token: credentials.APIFY_API_KEY }); + const client = new ApifyClient({ token: credentials.APIFY_API_TOKEN }); await client.user("me").get(); return { success: true }; } catch (error) { From 3885666fb27c64f4ce1fe3e766e23ffd34b7185d Mon Sep 17 00:00:00 2001 From: drobnikj Date: Wed, 3 Dec 2025 11:49:07 +0100 Subject: [PATCH 15/15] fix: update readme --- README.md | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/README.md b/README.md index d0bb35ec..2c99c9ba 100644 --- a/README.md +++ b/README.md @@ -263,6 +263,30 @@ const searchResult = await firecrawlSearchStep({ }); ``` +### Apify (Web Scraping) + +```typescript +import { + scrapeSingleUrlStep, + apifyRunActorStep, +} from "@/lib/steps/apify"; + +// Scrape a URL +const scrapeResult = await scrapeSingleUrlStep({ + url: "https://example.com", + crawlerType: "playwright:adaptive", +}); + +// Run an Actor from Apify Store +const searchMapsResults = await apifyRunActorStep({ + actorId: "compass/crawler-google-places", + actorInput: { + searchStringsArray: [ "restaurants in San Francisco" ] + }, +}); +``` + + ## Tech Stack - **Framework**: Next.js 16 with React 19
- Enter the URL to scrape or use a template reference. -
- Select the crawler type to use for scraping. -
- Get your API token from{" "} - - Apify Console - - . -