Conversation
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
There was a problem hiding this comment.
Additional Suggestions:
- Hardcoded checks for old action type names will fail for new workflows created with the refactored plugin system. New workflows store action types in the namespaced format (e.g., "ai-gateway/generate-text") but these checks still look for the old format (e.g., "Generate Text").
View Details
📝 Patch Details
diff --git a/components/workflow/nodes/action-node.tsx b/components/workflow/nodes/action-node.tsx
index b6a5f8c..cd76b15 100644
--- a/components/workflow/nodes/action-node.tsx
+++ b/components/workflow/nodes/action-node.tsx
@@ -268,11 +268,14 @@ export const ActionNode = memo(({ data, selected, id }: ActionNodeProps) => {
const actionType = (data.config?.actionType as string) || "";
const status = data.status;
+ // Get action info for type checking (handles both legacy labels and new namespaced IDs)
+ const actionInfo = findActionById(actionType);
+
// Check if this node has a generated image from the selected execution
const nodeLog = executionLogs[id];
const hasGeneratedImage =
selectedExecutionId &&
- actionType === "Generate Image" &&
+ (actionType === "Generate Image" || actionInfo?.slug === "generate-image") &&
nodeLog?.output &&
isBase64ImageOutput(nodeLog.output);
@@ -310,7 +313,6 @@ export const ActionNode = memo(({ data, selected, id }: ActionNodeProps) => {
}
// Get human-readable label from registry if no custom label is set
- const actionInfo = findActionById(actionType);
const displayTitle = data.label || actionInfo?.label || actionType;
const displayDescription =
data.description || getIntegrationFromActionType(actionType);
@@ -325,10 +327,10 @@ export const ActionNode = memo(({ data, selected, id }: ActionNodeProps) => {
// Get model for AI nodes
const getAiModel = (): string | null => {
- if (actionType === "Generate Text") {
+ if (actionType === "Generate Text" || actionInfo?.slug === "generate-text") {
return (data.config?.aiModel as string) || "meta/llama-4-scout";
}
- if (actionType === "Generate Image") {
+ if (actionType === "Generate Image" || actionInfo?.slug === "generate-image") {
return (
(data.config?.imageModel as string) || "google/imagen-4.0-generate"
);
Analysis
Hardcoded action type checks fail for new workflows with namespaced action IDs
What fails: ActionNode component in components/workflow/nodes/action-node.tsx doesn't display AI model badges and generated image thumbnails for new workflows that use namespaced action type IDs (e.g., ai-gateway/generate-text) instead of legacy label format (e.g., Generate Text).
How to reproduce:
- Create a new workflow through the UI using ActionGrid component
- Select "Generate Text" or "Generate Image" action
- Execute the workflow with an AI model selected
- The AI model badge won't appear on the action node
- For "Generate Image" actions, generated image thumbnails won't display
Root cause: ActionGrid component passes namespaced action IDs from getAllActions() (e.g., ai-gateway/generate-image) to handleUpdateConfig, which stores them directly. However, hardcoded string comparisons in action-node.tsx at lines 275, 328, and 331 only checked for legacy label format (actionType === "Generate Image"), causing the checks to fail.
Expected behavior: The checks should handle both legacy labels (for backward compatibility with old workflows) and new namespaced IDs (for new workflows). This is achievable using the findActionById() function which already supports both formats via LEGACY_ACTION_MAPPINGS in plugins/legacy-mappings.ts.
Files modified:
components/workflow/nodes/action-node.tsx: Updated lines 275, 328, and 331 to check both formats usingfindActionById()helper
View Details
📝 Patch Details
diff --git a/components/ui/template-autocomplete.tsx b/components/ui/template-autocomplete.tsx
index e87e9e6..00f4a0d 100644
--- a/components/ui/template-autocomplete.tsx
+++ b/components/ui/template-autocomplete.tsx
@@ -5,6 +5,7 @@ import { Check } from "lucide-react";
import { useEffect, useRef, useState } from "react";
import { createPortal } from "react-dom";
import { cn } from "@/lib/utils";
+import { findActionById } from "@/plugins";
import { edgesAtom, nodesAtom, type WorkflowNode } from "@/lib/workflow-store";
type TemplateAutocompleteProps = {
@@ -88,27 +89,9 @@ const schemaToFields = (
// Get common fields based on node action type
const getCommonFields = (node: WorkflowNode) => {
- const actionType = node.data.config?.actionType;
+ const actionType = node.data.config?.actionType as string | undefined;
- if (actionType === "Find Issues") {
- return [
- { field: "issues", description: "Array of issues found" },
- { field: "count", description: "Number of issues" },
- ];
- }
- if (actionType === "Send Email") {
- return [
- { field: "id", description: "Email ID" },
- { field: "status", description: "Send status" },
- ];
- }
- if (actionType === "Create Ticket") {
- return [
- { field: "id", description: "Ticket ID" },
- { field: "url", description: "Ticket URL" },
- { field: "number", description: "Ticket number" },
- ];
- }
+ // Handle system actions by label
if (actionType === "HTTP Request") {
return [
{ field: "data", description: "Response data" },
@@ -136,60 +119,93 @@ const getCommonFields = (node: WorkflowNode) => {
{ field: "count", description: "Number of rows" },
];
}
- if (actionType === "Generate Text") {
- const aiFormat = node.data.config?.aiFormat as string | undefined;
- const aiSchema = node.data.config?.aiSchema as string | undefined;
+ if (actionType === "Condition") {
+ return [{ field: "data", description: "Output data" }];
+ }
- // If format is object and schema is defined, show schema fields
- if (aiFormat === "object" && aiSchema) {
- try {
- const schema = JSON.parse(aiSchema) as SchemaField[];
- if (schema.length > 0) {
- return schemaToFields(schema);
+ // Handle plugin actions by looking them up in the registry
+ // findActionById handles both new format (e.g., "resend/send-email") and legacy format (e.g., "Send Email")
+ const action = findActionById(actionType);
+ if (action) {
+ const slug = action.slug;
+
+ // Match on action slug for robustness
+ if (slug === "find-issues") {
+ return [
+ { field: "issues", description: "Array of issues found" },
+ { field: "count", description: "Number of issues" },
+ ];
+ }
+ if (slug === "send-email") {
+ return [
+ { field: "id", description: "Email ID" },
+ { field: "status", description: "Send status" },
+ ];
+ }
+ if (slug === "create-ticket") {
+ return [
+ { field: "id", description: "Ticket ID" },
+ { field: "url", description: "Ticket URL" },
+ { field: "number", description: "Ticket number" },
+ ];
+ }
+ if (slug === "generate-text") {
+ const aiFormat = node.data.config?.aiFormat as string | undefined;
+ const aiSchema = node.data.config?.aiSchema as string | undefined;
+
+ // If format is object and schema is defined, show schema fields
+ if (aiFormat === "object" && aiSchema) {
+ try {
+ const schema = JSON.parse(aiSchema) as SchemaField[];
+ if (schema.length > 0) {
+ return schemaToFields(schema);
+ }
+ } catch {
+ // If schema parsing fails, fall through to default fields
}
- } catch {
- // If schema parsing fails, fall through to default fields
}
- }
- // Default fields for text format or when no schema
- return [
- { field: "text", description: "Generated text" },
- { field: "model", description: "Model used" },
- ];
- }
- if (actionType === "Generate Image") {
- return [
- { field: "base64", description: "Base64 image data" },
- { field: "model", description: "Model used" },
- ];
- }
- if (actionType === "Scrape") {
- return [
- { field: "markdown", description: "Scraped content as markdown" },
- { field: "metadata.url", description: "Page URL" },
- { field: "metadata.title", description: "Page title" },
- { field: "metadata.description", description: "Page description" },
- { field: "metadata.language", description: "Page language" },
- { field: "metadata.favicon", description: "Favicon URL" },
- ];
- }
- if (actionType === "Search") {
- return [{ field: "web", description: "Array of search results" }];
- }
- if (actionType === "Create Chat") {
- return [
- { field: "chatId", description: "v0 chat ID" },
- { field: "url", description: "v0 chat URL" },
- { field: "demoUrl", description: "Demo preview URL" },
- ];
- }
- if (actionType === "Send Message") {
- return [
- { field: "chatId", description: "v0 chat ID" },
- { field: "demoUrl", description: "Demo preview URL" },
- ];
+ // Default fields for text format or when no schema
+ return [
+ { field: "text", description: "Generated text" },
+ { field: "model", description: "Model used" },
+ ];
+ }
+ if (slug === "generate-image") {
+ return [
+ { field: "base64", description: "Base64 image data" },
+ { field: "model", description: "Model used" },
+ ];
+ }
+ if (slug === "scrape") {
+ return [
+ { field: "markdown", description: "Scraped content as markdown" },
+ { field: "metadata.url", description: "Page URL" },
+ { field: "metadata.title", description: "Page title" },
+ { field: "metadata.description", description: "Page description" },
+ { field: "metadata.language", description: "Page language" },
+ { field: "metadata.favicon", description: "Favicon URL" },
+ ];
+ }
+ if (slug === "search") {
+ return [{ field: "web", description: "Array of search results" }];
+ }
+ if (slug === "create-chat") {
+ return [
+ { field: "chatId", description: "v0 chat ID" },
+ { field: "url", description: "v0 chat URL" },
+ { field: "demoUrl", description: "Demo preview URL" },
+ ];
+ }
+ if (slug === "send-message") {
+ return [
+ { field: "chatId", description: "v0 chat ID" },
+ { field: "demoUrl", description: "Demo preview URL" },
+ ];
+ }
}
+
+ // Handle trigger nodes
if (node.data.type === "trigger") {
const triggerType = node.data.config?.triggerType as string | undefined;
const webhookSchema = node.data.config?.webhookSchema as string | undefined;
Analysis
Template autocomplete fails for new workflows with namespaced action types
What fails: The getCommonFields() function in components/ui/template-autocomplete.tsx checks for hardcoded action type names (e.g., "Find Issues", "Send Email") but workflows created with the new plugin system store action types in namespaced format (e.g., "linear/find-issues", "resend/send-email"). This causes template variable autocomplete to fail for new workflows, showing only generic "data" field instead of specific output fields.
How to reproduce:
- Create a workflow using the new plugin system (e.g., by having AI generate one, which uses namespaced action IDs from
generateAIActionPrompts()) - Add a "Send Email" action node - it will be stored as "resend/send-email"
- In a subsequent action, attempt to use template autocomplete to reference fields from the Send Email node
- The autocomplete shows only generic "data" field instead of specific fields like "id" and "status"
Result: Hardcoded checks for old format ("Send Email") fail to match new format ("resend/send-email"), causing getCommonFields() to fall through to default return value of [{ field: "data", description: "Output data" }]
Expected: Should return specific field suggestions like "id" and "status" regardless of whether action type is stored as legacy label "Send Email" or new format "resend/send-email"
Root cause: The system supports two action type formats:
- Legacy format: action label strings (e.g., "Send Email", "Find Issues")
- New format: namespaced action IDs from plugin registry (e.g., "resend/send-email", "linear/find-issues")
The ActionConfig component normalizes selection to new format when users select actions (using action.id from getActionsByCategory()). Workflows created via AI generation use the new format directly (per generateAIActionPrompts()). However, getCommonFields() only checks for old format, breaking autocomplete for new workflows.
Fix: Updated getCommonFields() to use findActionById() registry lookup instead of hardcoded string checks. The registry function handles both formats automatically, returning an action with slug property that can be reliably matched regardless of input format.
|
@ctate - I wonder if it's worth adding a 'System' plugin. After reading these changes and making changes in my two PRs, alot of exceptions or special cases are needed to handle the system logic plus dynamic logic. If a new system action is added, it'd also need a fair number of updates across different files. Happy to submit a PR for that if that's the direction you want to take |
|
@bensabic That makes sense for Database and HTTP Request - those should probably become their own plugins (ie. Native, Postgres, Mongo, etc.). Condition however is likely an exception. It will probably evolve into a completely separate node type since it's handled differently from other steps, and that divergence will only increase over time. The goal of this PR is to make creating new plugins as simple as possible. Ultimately, these plugins will live in their own registry (likely a shadcn registry) in a separate package and/or repository. That way, you can choose exactly which plugins to include in your own Workflow Builder, especially as the ecosystem grows - we may eventually have 1,000+ possible steps, which would otherwise add significant weight and dependencies. |
|
@ctate Yeah that's a very good point, especially regarding the Condition action. I love what you've done in terms of simplifying the plugin structure so that it's alot more standardised. I did realise after the Resend plugin updates, it would have opened the door to plugins varying too much in their configuration and causing a big mess. |
* better error for missing plugin * more dynamic registry work * fixes legacy mappings * fix errors * fix legacy mappings * fix line bug * use same icon * get rid of settings.tsx * fix error * combine env vars * config fields * fixes * better icon * more fixes * flatten steps * rename to ts * add examples * required fields * switch to props tab * combine errors * fix button
No description provided.