-
Notifications
You must be signed in to change notification settings - Fork 8.2k
feat: Add alias functionality in the UI and in tweaks processing #9801
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Conversation
… safety checks and migration support
…ity functions for effective alias retrieval
… alias modifications
… alias on display name change
…and update frontend node model
…me mappings for improved tweak resolution
WalkthroughImplements component aliasing across frontend and backend: adds alias fields, utilities, migration, UI badges, and code generation using aliases. Updates tweak processing to accept node IDs, aliases, or display names with conflict detection. Introduces dynamic alias assignment during graph dump and comprehensive tests for alias behavior. Changes
Sequence Diagram(s)sequenceDiagram
autonumber
actor User
participant UI as Frontend UI
participant Store as FlowStore
participant Utils as aliasUtils
participant BE as Backend (lfx)
rect rgba(200,230,255,0.3)
note over User,UI: Add/Paste Component
User->>UI: Add component
UI->>Store: addNode(newNode)
Store->>Utils: assignAliasToNewComponent(newNode, nodes)
Utils-->>Store: newNode with alias (if needed)
Store-->>UI: nodes updated
end
rect rgba(220,255,220,0.3)
note over UI,Store: Rename Display Name
User->>UI: Edit display_name
UI->>Store: update node display_name
UI->>Utils: updateAliasesForDisplayNameChange(nodeId, old, new, nodes)
Utils-->>UI: nodes with updated/renumbered aliases
UI->>Store: setNodes(updatedNodes)
end
rect rgba(255,245,200,0.4)
note over UI,BE: Generate API Code (aliases)
UI->>Store: read nodes + tweaks
UI->>Utils: convertTweaksToAliases(tweaks, nodes)
UI-->>User: JS/Python/cURL with alias-keyed tweaks
User->>BE: Call API with alias/display_name/nodeId keys
BE->>BE: process_tweaks() resolve keys (id/alias/display_name)
BE-->>User: Response
end
sequenceDiagram
autonumber
participant BE as Backend Graph.dump
participant Comp as Components
participant Helper as _add_dynamic_aliases
BE->>Comp: to_frontend_node(graph_data)
Comp-->>BE: nodes (some with alias)
BE->>Helper: assign dynamic aliases
Helper-->>BE: nodes with generated/cleaned aliases
BE-->>BE: produce GraphDump dict
Estimated code review effort🎯 5 (Critical) | ⏱️ ~120 minutes Possibly related PRs
Suggested labels
Suggested reviewers
Tip 👮 Agentic pre-merge checks are now available in preview!Pro plan users can now enable pre-merge checks in their settings to enforce checklists before merging PRs.
Please see the documentation for more information. Example: reviews:
pre_merge_checks:
custom_checks:
- name: "Undocumented Breaking Changes"
mode: "warning"
instructions: |
Pass/fail criteria: All breaking changes to public APIs, CLI flags, environment variables, configuration keys, database schemas, or HTTP/GraphQL endpoints must be documented in the "Breaking Change" section of the PR description and in CHANGELOG.md. Exclude purely internal or private changes (e.g., code not exported from package entry points or explicitly marked as internal).Please share your feedback with us on this Discord post. Pre-merge checks (2 passed, 1 warning)❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
✨ Finishing Touches
🧪 Generate unit tests
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
|
Codecov Report❌ Patch coverage is Additional details and impacted files@@ Coverage Diff @@
## main #9801 +/- ##
==========================================
+ Coverage 21.62% 21.85% +0.23%
==========================================
Files 1074 1076 +2
Lines 39650 39859 +209
Branches 5418 5474 +56
==========================================
+ Hits 8576 8713 +137
- Misses 30930 30975 +45
- Partials 144 171 +27
Flags with carried forward coverage won't be shown. Click here to find out more.
🚀 New features to boost your workflow:
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 16
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (7)
src/lfx/src/lfx/custom/custom_component/component.py (1)
955-1003: Implement or remove_calculate_dynamic_aliasbefore use
The callself._calculate_dynamic_alias(graph_data)at src/lfx/src/lfx/custom/custom_component/component.py:1002 invokes a method that isn’t defined on this class or any base—this will raise an AttributeError at runtime. Add the missing_calculate_dynamic_aliasimplementation (or import it) before calling it.src/lfx/src/lfx/template/frontend_node/base.py (1)
149-171: Add "alias" to reserved attributes to prevent collisions.
Without this, a component could define an input/output named "alias", risking downstream confusion.Apply:
attributes = [ "inputs", "outputs", "_artifacts", "_results", "logs", "status", "vertex", "graph", "display_name", "description", "documentation", "icon", + "alias", ]src/frontend/src/modals/apiModal/utils/get-js-api-code.tsx (1)
53-66: Fix ReferenceError: crypto is undefined when API key is hiddenIn the no-file path, crypto.randomUUID() is used regardless of shouldDisplayApiKey, but crypto is only required when shouldDisplayApiKey is true. Always require('crypto') to avoid runtime errors.
- const authSection = shouldDisplayApiKey - ? `const crypto = require('crypto'); -const apiKey = 'YOUR_API_KEY_HERE'; -` - : ""; + const authSection = shouldDisplayApiKey + ? `const crypto = require('crypto'); +const apiKey = 'YOUR_API_KEY_HERE'; +` + : `const crypto = require('crypto'); +`;src/frontend/src/modals/apiModal/utils/get-python-api-code.tsx (4)
49-56: Fix NameError: always defineheadersWhen
shouldDisplayApiKeyis false,headersis undefined but still used in requests. Always set a default empty dict.Apply:
- const authSection = shouldDisplayApiKey - ? `api_key = 'YOUR_API_KEY_HERE'` - : ""; - - const headersSection = shouldDisplayApiKey - ? `headers = {"x-api-key": api_key}` - : ""; + const authSection = shouldDisplayApiKey + ? `api_key = 'YOUR_API_KEY_HERE'` + : ""; + + const headersSection = `headers = ${ + shouldDisplayApiKey ? '{"x-api-key": api_key}' : '{}' + }`;
44-48: Avoid brittle true/false/null text replacements; usejson.loadsString replacements will corrupt string values containing “true/false/null”. Emit JSON and parse in Python.
Apply:
- const payloadString = JSON.stringify(payloadWithAliases, null, 4) - .replace(/true/g, "True") - .replace(/false/g, "False") - .replace(/null/g, "None"); + const payloadJson = JSON.stringify(payloadWithAliases, null, 2);And in the returned Python code:
-import requests -import os -import uuid +import requests +import os +import uuid +import json @@ -# Request payload configuration -payload = ${payloadString} -payload["session_id"] = str(uuid.uuid4()) +# Request payload configuration +payload = json.loads(r'''${payloadJson}''') +payload["session_id"] = str(uuid.uuid4())Also applies to: 57-67
121-129: Python variable paths are quoted as strings in payload (files for ChatInput)
JSON.stringifyquoteschat_file_path_X, so the payload sends the literal text, not the uploaded path. Inject via placeholders and replace quotes, and use a string (not array) for ChatInput’sfiles.Apply:
- const originalTweak = tweaks[nodeId]; - const modifiedTweak = { ...originalTweak }; - modifiedTweak.files = [`chat_file_path_${index + 1}`]; - tweakAssignments.push( - ` \"${nodeId}\": ${JSON.stringify(modifiedTweak, null, 4) - .split("\n") - .join("\n ")}`, - ); + const originalTweak = tweaks[nodeId]; + const modifiedTweak = { ...originalTweak }; + const __placeholder = `__pyvar_chat_file_path_${index + 1}__`; + // ChatInput expects a string path + (modifiedTweak as any).files = __placeholder; + const tweakJson = JSON.stringify(modifiedTweak, null, 4).replace( + `"${__placeholder}"`, + `chat_file_path_${index + 1}`, + ); + tweakAssignments.push( + ` "${nodeId}": ${tweakJson.split("\n").join("\n ")}`, + );
143-155: Python variable paths are quoted as strings in payload (File/VideoFile)Same issue for
path/file_path. Use placeholders and unquote them post-stringify.Apply:
- const originalTweak = tweaks[nodeId]; - const modifiedTweak = { ...originalTweak }; - if ("path" in originalTweak) { - modifiedTweak.path = [`file_path_${index + 1}`]; - } else if ("file_path" in originalTweak) { - modifiedTweak.file_path = `file_path_${index + 1}`; - } - tweakAssignments.push( - ` \"${nodeId}\": ${JSON.stringify(modifiedTweak, null, 4) - .split("\n") - .join("\n ")}`, - ); + const originalTweak = tweaks[nodeId]; + const modifiedTweak = { ...originalTweak }; + const __placeholder = `__pyvar_file_path_${index + 1}__`; + if ("path" in originalTweak) { + (modifiedTweak as any).path = [__placeholder]; + } else if ("file_path" in originalTweak) { + (modifiedTweak as any).file_path = __placeholder; + } + const tweakJson = JSON.stringify(modifiedTweak, null, 4).replaceAll( + `"${__placeholder}"`, + `file_path_${index + 1}`, + ); + tweakAssignments.push( + ` "${nodeId}": ${tweakJson.split("\n").join("\n ")}`, + );
🧹 Nitpick comments (22)
src/lfx/src/lfx/schema/graph.py (1)
33-35: Examples look good—consider adding one that shows mixed key types and notes ambiguity.Optional: add a second example illustrating simultaneous use of a node ID, an alias, and a display name, plus a brief note about how conflicts are handled (error vs precedence). This helps users pattern their tweaks correctly.
src/frontend/src/types/api/index.ts (1)
36-36: Alias field addition looks good; consider a short doc comment.Optional: add a brief JSDoc describing that alias is a stable, unique identifier (if uniqueness is expected) and when it may be absent.
src/lfx/src/lfx/custom/custom_component/component.py (1)
114-114: Unused private field _alias.It’s set but not read. Either wire it into alias computation/accessors or remove to avoid confusion.
Apply this diff if you decide to remove it:
- self._alias: str | None = Nonesrc/lfx/src/lfx/processing/process.py (1)
252-268: Log when a dict-valued tweak key matches no node.Silent skips are hard to debug.
Apply this diff:
- if node: + if node: node_id = node.get("id") @@ - processed_nodes[node_id] = (key, key_type) - apply_tweaks(node, value) + processed_nodes[node_id] = (key, key_type) + apply_tweaks(node, value) + else: + logger.warning( + f"Tweak key '{key}' did not match any node ID, alias, or display name; skipping." + )src/frontend/src/modals/apiModal/codeTabs/code-tabs.tsx (1)
77-83: Passes nodes into code generators — consider memoizing to avoid heavy recomputation.
getNew* helpers may be expensive; nodes is large and changes often. Wrap codeOptions in useMemo so code strings aren’t regenerated on every minor render.Apply within this range:
- const codeOptions = { - endpointName: endpointName || "", - streaming: streaming, - flowId: flowId || "", - processedPayload: processedPayload, - nodes: nodes, - }; + const codeOptions = useMemo( + () => ({ + endpointName: endpointName || "", + streaming, + flowId: flowId || "", + processedPayload, + nodes, + }), + [endpointName, streaming, flowId, processedPayload, nodes], + );And add the missing import at the top (outside this hunk):
import { useEffect, useMemo, useState } from "react";src/frontend/src/components/core/codeTabsComponent/components/tweakComponent/index.tsx (1)
7-7: Unify imports from the same module.
Minor cleanup; import type and value from "@/types/flow" together.-import type { AllNodeType } from "@/types/flow"; -import { getEffectiveAliasFromAnyNode } from "@/types/flow"; +import { getEffectiveAliasFromAnyNode, type AllNodeType } from "@/types/flow";src/frontend/src/stores/flowStore.ts (1)
218-225: Run migration early — consider defensive cloning to avoid side-effects.
migrateExistingFlow mutates nodes; if flow is reused elsewhere, clone first to keep this function pure.- let nodes = flow?.data?.nodes ?? []; + let nodes = cloneDeep(flow?.data?.nodes ?? []); const edges = flow?.data?.edges ?? [];src/lfx/tests/unit/processing/test_process.py (2)
93-116: Make the conflict error assertion resilientDepending on exact backend wording, the regex may be brittle. Consider asserting on ValueError and checking substring containment to avoid false negatives.
Apply:
- with pytest.raises(ValueError, match="Conflicting tweaks found for the same component"): - process_tweaks(graph_data, tweaks) + with pytest.raises(ValueError) as exc: + process_tweaks(graph_data, tweaks) + assert "Conflicting" in str(exc.value)
158-205: Add a test for unknown/ambiguous tweak keys (optional)Recommend adding a case where a tweak targets a non-existent alias/display_name and one where multiple nodes share a display_name without aliases (should not match), to lock behavior.
I can draft a parametric test covering both scenarios if helpful.
src/frontend/src/hooks/use-add-component.ts (2)
18-24: Stability: avoid re-creating the callback on every nodes change (optional)Including nodes in the dependency array will recreate the callback frequently. You can read the latest nodes via store.getState() inside the callback and drop nodes from deps.
Example:
- [store, paste, nodes, setNodes], + [store, paste, setNodes],and replace uses of nodes with store.getState().nodes.
Also applies to: 93-94
73-76: Type string consistency (nit)Comparing n.type to the literal "genericNode" can drift. Prefer comparing to getNodeRenderType("genericnode") or a shared enum/constant.
- n.type === "genericNode" && n.data.node.display_name === displayName, + n.type === getNodeRenderType("genericnode") && + n.data.node.display_name === displayName,src/frontend/src/CustomNodes/GenericNode/components/NodeName/index.tsx (1)
40-45: Effect dependency: include selected (nit)The effect conditionally uses selected; add it to deps to avoid stale reads.
- }, [editNameDescription]); + }, [editNameDescription, selected]);src/lfx/src/lfx/graph/graph/base.py (1)
235-238: Prevent unintended mutation of raw_graph_data during dumpdump() passes the same dict to the aliasing helper, which mutates it in place. This changes self.raw_graph_data as a side effect of dump(). Prefer aliasing a copy to keep dump side-effect-free.
- # Calculate dynamic aliases for duplicate components - data_dict = self._add_dynamic_aliases(data_dict) + # Calculate dynamic aliases for duplicate components (work on a copy to avoid side effects) + data_dict = self._add_dynamic_aliases(copy.deepcopy(data_dict))src/lfx/tests/unit/graph/graph/test_base.py (1)
212-266: Add a test to prevent duplicate numbered aliases when gaps existConsider a case like existing "Chat Input#3" and "Chat Input#1" plus one without alias; we should fill "#2" and avoid duplicate "#3". Add a test to lock this in once the helper fills gaps.
+def test_graph_dump_fills_number_gaps_and_avoids_duplicates(): + from lfx.components.input_output import ChatInput + graph = Graph() + ids = [graph.add_component(ChatInput()) for _ in range(3)] + # Pre-seed aliases with a gap (#2 missing) and an existing #3 + graph.raw_graph_data = { + "nodes": [ + {"id": ids[0], "data": {"node": {"display_name": "Chat Input", "alias": "Chat Input#1"}}}, + {"id": ids[1], "data": {"node": {"display_name": "Chat Input", "alias": None}}}, + {"id": ids[2], "data": {"node": {"display_name": "Chat Input", "alias": "Chat Input#3"}}}, + ], + "edges": [], + } + data = graph.dump()["data"]["nodes"] + aliases = {n["id"]: n["data"]["node"]["alias"] for n in data} + assert "Chat Input#2" in aliases.values() + # Ensure uniqueness + assert len({a for a in aliases.values() if a and a.startswith("Chat Input#")}) == 3src/frontend/src/types/flow/index.ts (1)
81-97: Place runtime helpers in utils, keep “types” module type-onlyThese exported functions execute at runtime and are better suited for utils (e.g., src/frontend/src/utils/aliasUtils.ts) to keep the types package strictly for type declarations. If you keep them here, at least mark imports as type-only and ensure APIClassType.alias is typed (string | null).
-// Utility functions for computing effective alias from JSON data -export function getEffectiveAlias(nodeData: NodeDataType): string { +// Utility functions for computing effective alias from JSON data +export function getEffectiveAlias(nodeData: NodeDataType): string { return nodeData.node.alias || nodeData.node.display_name; } export function getEffectiveAliasFromNode(node: GenericNodeType): string { return node.data.node.alias || node.data.node.display_name; } export function getEffectiveAliasFromAnyNode(node: AllNodeType): string { if (node.type === "genericNode") { return getEffectiveAliasFromNode(node); } // For note nodes or other types, fallback to a reasonable display return node.data?.node?.display_name || "Unknown"; }src/frontend/src/modals/apiModal/utils/get-js-api-code.tsx (2)
3-11: Remove unused imports to satisfy lint rulesgetChatInputNodeId, getFileNodeId, and hasChatInputFiles are not used.
import { getAllChatInputNodeIds, getAllFileNodeIds, - getChatInputNodeId, - getFileNodeId, getNonFileTypeTweaks, - hasChatInputFiles, hasFileTweaks, } from "./detect-file-tweaks";
18-26: Tighten types for nodes parameterUse the existing flow types instead of any[]. This improves IDE tooling and catches misuse at compile time.
-import { +import type { getAllChatInputNodeIds, getAllFileNodeIds, getNonFileTypeTweaks, hasFileTweaks, } from "./detect-file-tweaks"; +import type { AllNodeType } from "@/types/flow"; ... - nodes?: any[]; + nodes?: AllNodeType[];src/frontend/src/modals/apiModal/utils/get-python-api-code.tsx (2)
25-25: TightennodestypingPrefer precise types for better DX and safety.
Apply:
- nodes?: any[]; + nodes?: import("@/types/flow").AllNodeType[];
83-96: Minor consistency and robustness (optional)
- Use original tweaks for node-id collection (detection + ids) to match cURL path and avoid alias collisions; continue displaying aliases in payload.
- Consider adding DataStax Authorization header parity (like cURL) when flag is enabled.
I can produce a focused diff if you want parity and detection changes applied consistently.
Also applies to: 173-197
src/frontend/src/modals/apiModal/utils/get-curl-code.tsx (1)
74-78: Guardnavigatorfor non-browser contextsPrevents
ReferenceErrorin SSR/tests whenplatformisn’t provided.Apply:
- const detectedPlatform = - platform || - (/Windows|Win32|Win64|WOW32|WOW64/i.test(navigator.userAgent) - ? "powershell" - : "unix"); + const detectedPlatform = + platform || + (typeof navigator !== "undefined" && + /Windows|Win32|Win64|WOW32|WOW64/i.test(navigator.userAgent) + ? "powershell" + : "unix");src/frontend/src/utils/aliasUtils.ts (2)
16-23: Clarify auto vs. user-defined alias semantics in docsCurrent comments say “All aliases are auto-generated” and “#number are protected,” which conflicts with later renumbering. Recommend clarifying: “Auto-generated aliases use ‘DisplayName#n’ and may be renumbered; user-defined aliases are any others and must be preserved.”
- * All aliases are auto-generated, but those with #number are protected + * Auto-generated aliases use the pattern `${displayName}#<n>` and may be renumbered + * User-defined aliases are any others and must be preserved as-is
380-397: Guard against alias collisions when converting tweaksDuplicate aliases would overwrite tweak entries. Fallback to node IDs on collision.
for (const [nodeId, tweakValues] of Object.entries(tweaks)) { const node = nodes.find((n) => n.id === nodeId); if (node) { const alias = getEffectiveAliasFromAnyNode(node); - aliasedTweaks[alias] = tweakValues; + if (aliasedTweaks[alias] !== undefined) { + console.warn( + `Duplicate alias "${alias}" detected while converting tweaks; falling back to nodeId ${node.id}.`, + ); + aliasedTweaks[node.id] = tweakValues; + } else { + aliasedTweaks[alias] = tweakValues; + } } else { // Fallback to original nodeId if node not found aliasedTweaks[nodeId] = tweakValues; } }
📜 Review details
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (21)
src/frontend/src/CustomNodes/GenericNode/components/NodeName/index.tsx(5 hunks)src/frontend/src/components/core/codeTabsComponent/components/tweakComponent/index.tsx(2 hunks)src/frontend/src/hooks/use-add-component.ts(2 hunks)src/frontend/src/modals/apiModal/codeTabs/code-tabs.tsx(1 hunks)src/frontend/src/modals/apiModal/utils/get-curl-code.tsx(8 hunks)src/frontend/src/modals/apiModal/utils/get-js-api-code.tsx(3 hunks)src/frontend/src/modals/apiModal/utils/get-python-api-code.tsx(2 hunks)src/frontend/src/pages/FlowPage/components/flowSidebarComponent/index.tsx(0 hunks)src/frontend/src/stores/flowStore.ts(3 hunks)src/frontend/src/types/api/index.ts(1 hunks)src/frontend/src/types/flow/index.ts(1 hunks)src/frontend/src/utils/__tests__/aliasUtils.test.ts(1 hunks)src/frontend/src/utils/aliasUtils.ts(1 hunks)src/lfx/src/lfx/custom/custom_component/component.py(3 hunks)src/lfx/src/lfx/graph/graph/base.py(2 hunks)src/lfx/src/lfx/processing/process.py(2 hunks)src/lfx/src/lfx/schema/graph.py(2 hunks)src/lfx/src/lfx/template/frontend_node/base.py(1 hunks)src/lfx/tests/unit/graph/graph/test_base.py(1 hunks)src/lfx/tests/unit/processing/__init__.py(1 hunks)src/lfx/tests/unit/processing/test_process.py(1 hunks)
💤 Files with no reviewable changes (1)
- src/frontend/src/pages/FlowPage/components/flowSidebarComponent/index.tsx
🧰 Additional context used
📓 Path-based instructions (10)
src/frontend/src/**/*.{ts,tsx,js,jsx}
📄 CodeRabbit inference engine (.cursor/rules/frontend_development.mdc)
src/frontend/src/**/*.{ts,tsx,js,jsx}: All frontend TypeScript and JavaScript code should be located under src/frontend/src/ and organized into components, pages, icons, stores, types, utils, hooks, services, and assets directories as per the specified directory layout.
Use React 18 with TypeScript for all UI components in the frontend.
Format all TypeScript and JavaScript code using the make format_frontend command.
Lint all TypeScript and JavaScript code using the make lint command.
Files:
src/frontend/src/types/flow/index.tssrc/frontend/src/types/api/index.tssrc/frontend/src/components/core/codeTabsComponent/components/tweakComponent/index.tsxsrc/frontend/src/utils/aliasUtils.tssrc/frontend/src/stores/flowStore.tssrc/frontend/src/utils/__tests__/aliasUtils.test.tssrc/frontend/src/modals/apiModal/utils/get-curl-code.tsxsrc/frontend/src/modals/apiModal/utils/get-python-api-code.tsxsrc/frontend/src/modals/apiModal/utils/get-js-api-code.tsxsrc/frontend/src/hooks/use-add-component.tssrc/frontend/src/modals/apiModal/codeTabs/code-tabs.tsxsrc/frontend/src/CustomNodes/GenericNode/components/NodeName/index.tsx
src/frontend/src/types/**/*.{ts,tsx}
📄 CodeRabbit inference engine (.cursor/rules/frontend_development.mdc)
All TypeScript type definitions should be placed in the types directory.
Files:
src/frontend/src/types/flow/index.tssrc/frontend/src/types/api/index.ts
src/frontend/src/components/**/*.{ts,tsx,js,jsx}
📄 CodeRabbit inference engine (.cursor/rules/frontend_development.mdc)
All components should be styled using Tailwind CSS utility classes.
Files:
src/frontend/src/components/core/codeTabsComponent/components/tweakComponent/index.tsx
src/frontend/src/@(components|hooks)/**/*.{ts,tsx,js,jsx}
📄 CodeRabbit inference engine (.cursor/rules/frontend_development.mdc)
Implement dark mode support in components and hooks where needed.
Files:
src/frontend/src/components/core/codeTabsComponent/components/tweakComponent/index.tsxsrc/frontend/src/hooks/use-add-component.ts
src/frontend/src/utils/**/*.{ts,tsx,js,jsx}
📄 CodeRabbit inference engine (.cursor/rules/frontend_development.mdc)
All utility functions should be placed in the utils directory.
Files:
src/frontend/src/utils/aliasUtils.tssrc/frontend/src/utils/__tests__/aliasUtils.test.ts
src/frontend/src/stores/**/*.{ts,tsx,js,jsx}
📄 CodeRabbit inference engine (.cursor/rules/frontend_development.mdc)
Use Zustand for state management in frontend stores.
Files:
src/frontend/src/stores/flowStore.ts
src/frontend/src/**/__tests__/**/*.{ts,tsx,js,jsx}
📄 CodeRabbit inference engine (.cursor/rules/frontend_development.mdc)
All frontend code should be tested using appropriate component and integration tests.
Files:
src/frontend/src/utils/__tests__/aliasUtils.test.ts
src/frontend/**/*.@(test|spec).{ts,tsx,js,jsx}
📄 CodeRabbit inference engine (.cursor/rules/testing.mdc)
src/frontend/**/*.@(test|spec).{ts,tsx,js,jsx}: Frontend test files should be located in 'src/frontend/' and use '.test.{ts,tsx,js,jsx}' or '.spec.{ts,tsx,js,jsx}' extensions.
Test both sync and async code paths in frontend test files.
Mock external dependencies appropriately in frontend test files to isolate unit tests from external services.
Test error handling and edge cases in frontend test files.
Validate input/output behavior and test component initialization and configuration in frontend test files.
Each frontend test should have a clear description or comment explaining its purpose, especially for complex setups or mocks.
Files:
src/frontend/src/utils/__tests__/aliasUtils.test.ts
src/frontend/src/@(hooks|services)/**/*.{ts,tsx,js,jsx}
📄 CodeRabbit inference engine (.cursor/rules/frontend_development.mdc)
All API calls must implement proper error handling, such as using try/catch and setting error state.
Files:
src/frontend/src/hooks/use-add-component.ts
src/frontend/src/hooks/**/*.{ts,tsx,js,jsx}
📄 CodeRabbit inference engine (.cursor/rules/frontend_development.mdc)
All custom React hooks should be placed in the hooks directory.
Files:
src/frontend/src/hooks/use-add-component.ts
🧠 Learnings (6)
📚 Learning: 2025-06-23T12:46:42.048Z
Learnt from: CR
PR: langflow-ai/langflow#0
File: .cursor/rules/frontend_development.mdc:0-0
Timestamp: 2025-06-23T12:46:42.048Z
Learning: Custom React Flow node types should be implemented as memoized components, using Handle components for connection points and supporting optional icons and labels.
Applied to files:
src/frontend/src/components/core/codeTabsComponent/components/tweakComponent/index.tsxsrc/frontend/src/stores/flowStore.tssrc/frontend/src/hooks/use-add-component.tssrc/frontend/src/CustomNodes/GenericNode/components/NodeName/index.tsx
📚 Learning: 2025-07-18T18:27:12.609Z
Learnt from: CR
PR: langflow-ai/langflow#0
File: .cursor/rules/frontend_development.mdc:0-0
Timestamp: 2025-07-18T18:27:12.609Z
Learning: Applies to src/frontend/src/components/**/@(FlowGraph|nodes)/**/*.{ts,tsx,js,jsx} : Use React Flow for flow graph visualization components.
Applied to files:
src/frontend/src/stores/flowStore.tssrc/frontend/src/hooks/use-add-component.ts
📚 Learning: 2025-06-23T12:46:42.048Z
Learnt from: CR
PR: langflow-ai/langflow#0
File: .cursor/rules/frontend_development.mdc:0-0
Timestamp: 2025-06-23T12:46:42.048Z
Learning: React Flow should be used for flow graph visualization, with nodes and edges passed as props, and changes handled via onNodesChange and onEdgesChange callbacks.
Applied to files:
src/frontend/src/stores/flowStore.ts
📚 Learning: 2025-07-21T14:16:14.125Z
Learnt from: CR
PR: langflow-ai/langflow#0
File: .cursor/rules/testing.mdc:0-0
Timestamp: 2025-07-21T14:16:14.125Z
Learning: Applies to src/frontend/**/*.@(test|spec).{ts,tsx,js,jsx} : Mock external dependencies appropriately in frontend test files to isolate unit tests from external services.
Applied to files:
src/frontend/src/utils/__tests__/aliasUtils.test.ts
📚 Learning: 2025-07-21T14:16:14.125Z
Learnt from: CR
PR: langflow-ai/langflow#0
File: .cursor/rules/testing.mdc:0-0
Timestamp: 2025-07-21T14:16:14.125Z
Learning: Applies to src/frontend/**/*.@(test|spec).{ts,tsx,js,jsx} : Each frontend test should have a clear description or comment explaining its purpose, especially for complex setups or mocks.
Applied to files:
src/frontend/src/utils/__tests__/aliasUtils.test.ts
📚 Learning: 2025-07-11T22:12:46.255Z
Learnt from: namastex888
PR: langflow-ai/langflow#9018
File: src/frontend/src/modals/apiModal/codeTabs/code-tabs.tsx:244-244
Timestamp: 2025-07-11T22:12:46.255Z
Learning: In src/frontend/src/modals/apiModal/codeTabs/code-tabs.tsx, the inconsistent showLineNumbers setting between Step 1 (false) and Step 2 (true) in the API modal is intentional to prevent breaking the modal height. Step 1 uses showLineNumbers={false} to save vertical space while Step 2 uses showLineNumbers={true} for better readability of longer code.
Applied to files:
src/frontend/src/modals/apiModal/codeTabs/code-tabs.tsx
🧬 Code graph analysis (10)
src/frontend/src/components/core/codeTabsComponent/components/tweakComponent/index.tsx (1)
src/frontend/src/types/flow/index.ts (1)
getEffectiveAliasFromAnyNode(90-96)
src/frontend/src/utils/aliasUtils.ts (1)
src/frontend/src/types/flow/index.ts (3)
GenericNodeType(39-39)AllNodeType(42-42)getEffectiveAliasFromAnyNode(90-96)
src/frontend/src/stores/flowStore.ts (1)
src/frontend/src/utils/aliasUtils.ts (3)
needsMigration(326-368)migrateExistingFlow(240-321)assignAliasToNewComponent(117-141)
src/lfx/tests/unit/graph/graph/test_base.py (1)
src/lfx/src/lfx/graph/graph/base.py (2)
Graph(57-2281)dump(224-251)
src/frontend/src/utils/__tests__/aliasUtils.test.ts (2)
src/frontend/src/types/flow/index.ts (2)
GenericNodeType(39-39)AllNodeType(42-42)src/frontend/src/utils/aliasUtils.ts (11)
hasNumberSuffix(20-23)isSafeToModifyAlias(29-36)generateAutoAlias(41-71)assignAliasToNewComponent(117-141)migrateExistingFlow(240-321)needsMigration(326-368)renumberAutoGeneratedAliases(76-112)setUserDefinedAlias(183-202)clearUserDefinedAlias(207-234)updateAliasesForDisplayNameChange(146-178)convertTweaksToAliases(380-397)
src/frontend/src/modals/apiModal/utils/get-curl-code.tsx (3)
src/frontend/src/utils/aliasUtils.ts (1)
convertTweaksToAliases(380-397)src/frontend/src/modals/apiModal/utils/detect-file-tweaks.ts (4)
hasFileTweaks(2-18)getAllChatInputNodeIds(61-72)getAllFileNodeIds(75-92)getNonFileTypeTweaks(95-117)src/frontend/src/types/flow/index.ts (1)
getEffectiveAliasFromAnyNode(90-96)
src/frontend/src/modals/apiModal/utils/get-python-api-code.tsx (3)
src/frontend/src/customization/utils/custom-get-host-protocol.ts (1)
customGetHostProtocol(1-6)src/frontend/src/utils/aliasUtils.ts (1)
convertTweaksToAliases(380-397)src/frontend/src/modals/apiModal/utils/detect-file-tweaks.ts (1)
hasFileTweaks(2-18)
src/frontend/src/modals/apiModal/utils/get-js-api-code.tsx (2)
src/frontend/src/utils/aliasUtils.ts (1)
convertTweaksToAliases(380-397)src/frontend/src/modals/apiModal/utils/detect-file-tweaks.ts (1)
hasFileTweaks(2-18)
src/frontend/src/hooks/use-add-component.ts (1)
src/frontend/src/utils/aliasUtils.ts (1)
assignAliasToNewComponent(117-141)
src/frontend/src/CustomNodes/GenericNode/components/NodeName/index.tsx (2)
src/frontend/src/types/flow/index.ts (1)
getEffectiveAliasFromAnyNode(90-96)src/frontend/src/utils/aliasUtils.ts (1)
updateAliasesForDisplayNameChange(146-178)
🪛 ast-grep (0.38.6)
src/frontend/src/utils/aliasUtils.ts
[warning] 86-88: Regular expression constructed from variable input detected. This can lead to Regular Expression Denial of Service (ReDoS) attacks if the variable contains malicious patterns. Use libraries like 'recheck' to validate regex safety or use static patterns.
Context: new RegExp(
^${removedDisplayName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}#\\d+$,
)
Note: [CWE-1333] Inefficient Regular Expression Complexity [REFERENCES]
- https://owasp.org/www-community/attacks/Regular_expression_Denial_of_Service_-_ReDoS
- https://cwe.mitre.org/data/definitions/1333.html
(regexp-from-variable)
[warning] 269-271: Regular expression constructed from variable input detected. This can lead to Regular Expression Denial of Service (ReDoS) attacks if the variable contains malicious patterns. Use libraries like 'recheck' to validate regex safety or use static patterns.
Context: new RegExp(
^${displayName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}#(\\d+)$,
)
Note: [CWE-1333] Inefficient Regular Expression Complexity [REFERENCES]
- https://owasp.org/www-community/attacks/Regular_expression_Denial_of_Service_-_ReDoS
- https://cwe.mitre.org/data/definitions/1333.html
(regexp-from-variable)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
- GitHub Check: Run Frontend Tests / Determine Test Suites and Shard Distribution
- GitHub Check: Test Starter Templates
🔇 Additional comments (24)
src/lfx/src/lfx/schema/graph.py (1)
22-23: Clarify resolution order and ambiguity handling for keys (IDs vs aliases vs display names).
Please verify the actual resolution behavior and update the docstring to specify:
- handling of duplicate display names
- precedence among node ID > alias > display name
- case sensitivity in alias/display-name matching
src/lfx/src/lfx/processing/process.py (2)
201-207: Docstring update aligns with alias/display-name support.Clear and matches behavior.
255-266: Conflict detection is excellent.Good immediate error with clear message when the same node is targeted by multiple keys.
src/lfx/tests/unit/processing/__init__.py (1)
1-1: LGTM — module docstring added.
Clear and harmless addition.src/lfx/src/lfx/template/frontend_node/base.py (1)
110-115: Confirm serialization behavior for alias.
to_dict uses exclude_none=True (alias omitted if None) but model_serializer path returns all fields as-is. Ensure callers don’t rely on alias always being present.src/frontend/src/components/core/codeTabsComponent/components/tweakComponent/index.tsx (1)
39-47: Nice UX touch with the alias badge.
Concise, non-intrusive, and consistent with the rest of the UI.src/frontend/src/stores/flowStore.ts (2)
20-24: Alias utilities wired correctly.
Imports are minimal and scoped; good separation of concerns.
492-496: Alias assignment on paste looks correct; verifies 2nd-of-type case.
This covers promoting the first-of-type to #1 when a sibling is pasted. Please ensure tests include pasting multiple identical components in one operation.src/lfx/tests/unit/processing/test_process.py (1)
11-38: Good coverage of user-defined alias pathTest validates alias_is_user_defined precedence and correct tweak application. Looks solid.
src/frontend/src/CustomNodes/GenericNode/components/NodeName/index.tsx (2)
32-37: Alias badge derivation looks goodClean derivation via getEffectiveAliasFromAnyNode and safe parsing of the numeric suffix.
124-130: Nice UX touchAlias tooltip/badge is compact and informative; respects theming tokens.
src/lfx/tests/unit/graph/graph/test_base.py (3)
117-166: Solid coverage for duplicate vs single-component aliasingThese tests validate baseline behaviors (duplicates get numbered aliases; singles stay unaliased). LGTM.
168-210: Good preservation checksVerifies both custom aliases and existing numbered aliases are preserved through dump(). Looks correct.
268-326: Great edge-case testsRemoving unnecessary "#1" for singles and preserving custom single aliases both look good.
src/frontend/src/modals/apiModal/utils/get-js-api-code.tsx (3)
36-46: Good: alias conversion and file-detection separationUsing originalTweaks for hasFileTweaks avoids false negatives after alias conversion. Nice.
210-317: End-to-end flow code generation looks consistentThe multipart steps and final execute payload incorporate aliased tweaks correctly. Assuming the verification above passes, this is in good shape.
81-99: Verify helpers inspect tweak values, not keys
getAllChatInputNodeIds and getAllFileNodeIds must determine file inputs by examining properties on each tweak object (e.g. files, path, file_path), rather than assuming the tweak key is a node ID post-alias conversion. Confirm their implementations reflect this.src/frontend/src/modals/apiModal/utils/get-curl-code.tsx (1)
80-90: Aliased payload flow looks goodUsing original tweaks for detection and aliased tweaks for display/payload is consistent and user-friendly.
Also applies to: 138-147, 219-226
src/frontend/src/utils/__tests__/aliasUtils.test.ts (4)
45-118: Solid coverage of detection and auto-aliasing pathsClear cases for suffix detection and auto-increment/gap-fill look good.
157-199: Great migration and uniqueness checksMigration preserves user-defined aliases and ensures stable numbering; uniqueness assertions are valuable.
Also applies to: 430-497
319-347: Good negative-path testing withconsole.warnspyCaptures protected-alias scenarios properly.
384-418: Tweaks-to-alias conversion tests validate the new codegen dependenciesFallback behavior and empty-tweaks handling are well covered.
src/frontend/src/utils/aliasUtils.ts (2)
326-368: Minor: skip building alias arrays for non-generic nodesGuard is already present; nothing to change here.
399-406: LGTM on helperFiltering by display_name is correct and type-safe with the type guard.
| // Get alias info for badge display | ||
| const effectiveAlias = getEffectiveAliasFromAnyNode(node); | ||
| const aliasNumber = effectiveAlias?.match(/#(\d+)$/)?.[1]; | ||
| const displayName = node.data.node?.display_name || ""; | ||
|
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion
Derive badge number from the actual alias field to avoid false positives.
Using effectiveAlias risks matching trailing numbers in display_name for non-generic nodes. Read from node.data.node.alias instead.
- const effectiveAlias = getEffectiveAliasFromAnyNode(node);
- const aliasNumber = effectiveAlias?.match(/#(\d+)$/)?.[1];
- const displayName = node.data.node?.display_name || "";
+ const effectiveAlias = getEffectiveAliasFromAnyNode(node);
+ const rawAlias =
+ node.type === "genericNode" ? node.data.node?.alias ?? "" : "";
+ const aliasNumber = rawAlias.match(/#(\d+)$/)?.[1];
+ const displayName = node.data.node?.display_name || "";📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| // Get alias info for badge display | |
| const effectiveAlias = getEffectiveAliasFromAnyNode(node); | |
| const aliasNumber = effectiveAlias?.match(/#(\d+)$/)?.[1]; | |
| const displayName = node.data.node?.display_name || ""; | |
| // Get alias info for badge display | |
| const effectiveAlias = getEffectiveAliasFromAnyNode(node); | |
| const rawAlias = | |
| node.type === "genericNode" ? node.data.node?.alias ?? "" : ""; | |
| const aliasNumber = rawAlias.match(/#(\d+)$/)?.[1]; | |
| const displayName = node.data.node?.display_name || ""; |
🤖 Prompt for AI Agents
In
src/frontend/src/components/core/codeTabsComponent/components/tweakComponent/index.tsx
around lines 30 to 34, the badge number is currently derived from effectiveAlias
which can accidentally match numbers in display_name; instead read the alias
directly from node.data.node?.alias, safely extract the badge number with
alias?.match(/#(\d+)$/)?.[1] (handle undefined/null), and keep displayName as
node.data.node?.display_name || ""; replace the effectiveAlias usage with this
direct alias extraction to avoid false positives.
| // Update aliases when display name changes | ||
| if (oldDisplayName && newDisplayName !== oldDisplayName) { | ||
| const allNodes = useFlowStore.getState().nodes; | ||
| const updatedNodes = updateAliasesForDisplayNameChange( | ||
| nodeId, | ||
| oldDisplayName, | ||
| newDisplayName, | ||
| allNodes, | ||
| ); | ||
| useFlowStore.getState().setNodes(updatedNodes); | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
💡 Verification agent
🧩 Analysis chain
Verify alias update produces a new nodes array
If updateAliasesForDisplayNameChange mutates and returns the same array reference, some subscribers may not re-render. Ensure it returns a new array instance.
Run:
Expected: It returns a new array (e.g., return [...allNodes]). If it returns allNodes directly, consider cloning before setNodes.
🏁 Script executed:
#!/bin/bash
# Inspect implementation to confirm immutable return
rg -nC2 'export function updateAliasesForDisplayNameChange' src/frontend/src/utils/aliasUtils.ts
rg -n 'return\s+\[\.{3}allNodes\]|\ballNodes\.slice\(\)|return\s+allNodes;' src/frontend/src/utils/aliasUtils.tsLength of output: 262
Ensure updateAliasesForDisplayNameChange always returns a new array instance
In src/frontend/src/utils/aliasUtils.ts the function currently does:
- Early-return at line 153:
return allNodes; - Final return at line 177:
return allNodes;
Both return the original array reference, which can prevent subscribers from re-rendering. Change these to return a clone (e.g. return [...allNodes]) or otherwise produce a new array before calling setNodes.
🤖 Prompt for AI Agents
In src/frontend/src/CustomNodes/GenericNode/components/NodeName/index.tsx around
lines 67 to 77, the call site uses updateAliasesForDisplayNameChange but that
utility sometimes returns the original allNodes array reference (early return at
line 153 and final return at line 177 in src/frontend/src/utils/aliasUtils.ts),
which prevents subscribers from detecting changes; update the utility so that
every return produces a new array instance (for example clone before returning
with [...allNodes] or map/other immutable update) so setNodes receives a new
reference and triggers re-renders.
| // Generate alias for the new component and update existing ones if needed | ||
| assignAliasToNewComponent(newNode, nodes); | ||
|
|
||
| // Check if we need to update existing nodes (when adding second component of same type) | ||
| const displayName = newNode.data.node.display_name; | ||
| const sameTypeNodes = nodes.filter( | ||
| (n) => | ||
| n.type === "genericNode" && n.data.node.display_name === displayName, | ||
| ); | ||
|
|
||
| if (sameTypeNodes.length === 1 && !sameTypeNodes[0].data.node.alias) { | ||
| // This is the second component - need to assign aliases to both | ||
| const firstNode = sameTypeNodes[0]; | ||
| if (firstNode.type === "genericNode") { | ||
| firstNode.data.node.alias = `${displayName}#1`; | ||
| } | ||
|
|
||
| // Update the first node in the store | ||
| setNodes((currentNodes) => | ||
| currentNodes.map((n) => (n.id === firstNode.id ? firstNode : n)), | ||
| ); | ||
| } | ||
|
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fix: store mutation via utility is not persisted; reorder logic to ensure setNodes runs
assignAliasToNewComponent mutates the existing nodes array by reference. Because the zustand store isn’t updated unless setNodes is called, your subsequent condition can be skipped (alias already set in-memory), leading to no store update and missing re-render. Compute the “second-of-its-type” condition before calling the utility, then always persist the first node’s alias immutably when needed.
Apply:
- // Generate alias for the new component and update existing ones if needed
- assignAliasToNewComponent(newNode, nodes);
-
- // Check if we need to update existing nodes (when adding second component of same type)
- const displayName = newNode.data.node.display_name;
- const sameTypeNodes = nodes.filter(
- (n) =>
- n.type === "genericNode" && n.data.node.display_name === displayName,
- );
-
- if (sameTypeNodes.length === 1 && !sameTypeNodes[0].data.node.alias) {
- // This is the second component - need to assign aliases to both
- const firstNode = sameTypeNodes[0];
- if (firstNode.type === "genericNode") {
- firstNode.data.node.alias = `${displayName}#1`;
- }
-
- // Update the first node in the store
- setNodes((currentNodes) =>
- currentNodes.map((n) => (n.id === firstNode.id ? firstNode : n)),
- );
- }
+ // Precompute if we are adding the second component of this display_name with no existing alias
+ const displayName = newNode.data.node.display_name;
+ const sameTypeNodesBefore = nodes.filter(
+ (n) => n.type === "genericNode" && n.data.node.display_name === displayName,
+ );
+ const needsPromoteFirst =
+ sameTypeNodesBefore.length === 1 &&
+ !sameTypeNodesBefore[0].data.node.alias;
+
+ // Assign alias to the new node (the utility will also compute target aliases)
+ assignAliasToNewComponent(newNode, nodes);
+
+ // Persist alias on the first existing node immutably so subscribers re-render
+ if (needsPromoteFirst) {
+ const firstId = sameTypeNodesBefore[0].id;
+ setNodes((currentNodes) =>
+ currentNodes.map((n) =>
+ n.id === firstId && n.type === "genericNode"
+ ? {
+ ...n,
+ data: {
+ ...n.data,
+ node: {
+ ...n.data.node,
+ alias: `${displayName}#1`,
+ },
+ },
+ }
+ : n,
+ ),
+ );
+ }📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| // Generate alias for the new component and update existing ones if needed | |
| assignAliasToNewComponent(newNode, nodes); | |
| // Check if we need to update existing nodes (when adding second component of same type) | |
| const displayName = newNode.data.node.display_name; | |
| const sameTypeNodes = nodes.filter( | |
| (n) => | |
| n.type === "genericNode" && n.data.node.display_name === displayName, | |
| ); | |
| if (sameTypeNodes.length === 1 && !sameTypeNodes[0].data.node.alias) { | |
| // This is the second component - need to assign aliases to both | |
| const firstNode = sameTypeNodes[0]; | |
| if (firstNode.type === "genericNode") { | |
| firstNode.data.node.alias = `${displayName}#1`; | |
| } | |
| // Update the first node in the store | |
| setNodes((currentNodes) => | |
| currentNodes.map((n) => (n.id === firstNode.id ? firstNode : n)), | |
| ); | |
| } | |
| // Precompute if we are adding the second component of this display_name with no existing alias | |
| const displayName = newNode.data.node.display_name; | |
| const sameTypeNodesBefore = nodes.filter( | |
| (n) => n.type === "genericNode" && n.data.node.display_name === displayName, | |
| ); | |
| const needsPromoteFirst = | |
| sameTypeNodesBefore.length === 1 && | |
| !sameTypeNodesBefore[0].data.node.alias; | |
| // Assign alias to the new node (the utility will also compute target aliases) | |
| assignAliasToNewComponent(newNode, nodes); | |
| // Persist alias on the first existing node immutably so subscribers re-render | |
| if (needsPromoteFirst) { | |
| const firstId = sameTypeNodesBefore[0].id; | |
| setNodes((currentNodes) => | |
| currentNodes.map((n) => | |
| n.id === firstId && n.type === "genericNode" | |
| ? { | |
| ...n, | |
| data: { | |
| ...n.data, | |
| node: { | |
| ...n.data.node, | |
| alias: `${displayName}#1`, | |
| }, | |
| }, | |
| } | |
| : n, | |
| ), | |
| ); | |
| } |
| // Get alias for this node | ||
| const node = nodes?.find((n) => n.id === nodeId); | ||
| const alias = node ? getEffectiveAliasFromAnyNode(node) : nodeId; | ||
|
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Missing import for getEffectiveAliasFromAnyNode causes build error
Function is used but not imported.
Apply:
import { GetCodeType } from "@/types/tweaks";
+import { getEffectiveAliasFromAnyNode } from "@/types/flow";Also applies to: 199-202
🤖 Prompt for AI Agents
In src/frontend/src/modals/apiModal/utils/get-curl-code.tsx around lines 166-169
(and also at 199-202) the function getEffectiveAliasFromAnyNode is used but not
imported; add a named import for getEffectiveAliasFromAnyNode from the module
that exports it at the top of the file (the same module where other node-alias
helper functions are exported), then rebuild to confirm the missing-import error
is resolved.
| export function isSafeToModifyAlias(node: GenericNodeType): boolean { | ||
| const alias = node.data.node.alias; | ||
|
|
||
| if (!alias) return true; // No alias, safe to add one | ||
|
|
||
| // Can only modify if it doesn't have a number suffix | ||
| return !hasNumberSuffix(alias); | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fix inverted mutability check (breaks migration and single-node cleanup)
Currently, numbered aliases are treated as “protected” from modification, blocking renumbering and migration. We must allow modifying auto-generated (“DisplayName#n”) aliases while preserving user-defined ones.
export function isSafeToModifyAlias(node: GenericNodeType): boolean {
- const alias = node.data.node.alias;
-
- if (!alias) return true; // No alias, safe to add one
-
- // Can only modify if it doesn't have a number suffix
- return !hasNumberSuffix(alias);
+ const alias = node.data.node.alias;
+ const displayName = node.data.node.display_name;
+ // Safe to modify if there's no alias or if it's auto-generated for this display name.
+ return !alias || isAutoGeneratedAliasForDisplayName(alias, displayName);
}Note: Add helper isAutoGeneratedAliasForDisplayName (see separate diff below).
Committable suggestion skipped: line range outside the PR's diff.
🤖 Prompt for AI Agents
In src/frontend/src/utils/aliasUtils.ts around lines 29 to 36, the current check
treats numbered aliases as immutable; instead, allow modification when the alias
is an auto-generated "DisplayName#n". Change the logic so that if there's no
alias return true, otherwise return true if
isAutoGeneratedAliasForDisplayName(alias, node.data.node.displayName) is true,
otherwise fall back to the existing !hasNumberSuffix(alias) check; add and call
the helper isAutoGeneratedAliasForDisplayName as described in the review.
| // Helper functions | ||
| function extractNumber(alias: string | undefined): number { | ||
| if (!alias) return 0; | ||
| const match = alias.match(/^.+#(\d+)$/); | ||
| return match ? parseInt(match[1]) : 0; | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion
Introduce helper: avoid dynamic regex and encode intent
Centralize auto-generated pattern detection and avoid constructing regex from variable input.
// Helper functions
-function extractNumber(alias: string | undefined): number {
+function isAutoGeneratedAliasForDisplayName(alias: string, displayName: string): boolean {
+ const prefix = `${displayName}#`;
+ return alias.startsWith(prefix) && /^\d+$/.test(alias.slice(prefix.length));
+}
+
+function extractNumber(alias: string | undefined): number {
if (!alias) return 0;
const match = alias.match(/^.+#(\d+)$/);
return match ? parseInt(match[1]) : 0;
}📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| // Helper functions | |
| function extractNumber(alias: string | undefined): number { | |
| if (!alias) return 0; | |
| const match = alias.match(/^.+#(\d+)$/); | |
| return match ? parseInt(match[1]) : 0; | |
| } | |
| // Helper functions | |
| function isAutoGeneratedAliasForDisplayName(alias: string, displayName: string): boolean { | |
| const prefix = `${displayName}#`; | |
| return alias.startsWith(prefix) && /^\d+$/.test(alias.slice(prefix.length)); | |
| } | |
| function extractNumber(alias: string | undefined): number { | |
| if (!alias) return 0; | |
| const match = alias.match(/^.+#(\d+)$/); | |
| return match ? parseInt(match[1]) : 0; | |
| } |
| def _add_dynamic_aliases(self, data_dict: dict) -> dict: | ||
| """Calculate and assign dynamic aliases to duplicate components in graph data.""" | ||
| nodes = data_dict.get("nodes", []) | ||
|
|
||
| # Group nodes by display_name to identify duplicates | ||
| display_name_groups: dict[str, list] = {} | ||
|
|
||
| for node in nodes: | ||
| node_info = node.get("data", {}).get("node", {}) | ||
| display_name = node_info.get("display_name") | ||
|
|
||
| if display_name: | ||
| if display_name not in display_name_groups: | ||
| display_name_groups[display_name] = [] | ||
| display_name_groups[display_name].append(node) | ||
|
|
||
| # Assign aliases to groups with multiple components | ||
| for display_name, group_nodes in display_name_groups.items(): | ||
| if len(group_nodes) > 1: | ||
| # Multiple components - assign numbered aliases | ||
| for index, node in enumerate(group_nodes): | ||
| node_info = node.get("data", {}).get("node", {}) | ||
| if node_info: | ||
| # Only assign if no existing alias (preserve all existing aliases) | ||
| current_alias = node_info.get("alias") | ||
|
|
||
| if not current_alias: | ||
| expected_alias = f"{display_name}#{index + 1}" | ||
| node_info["alias"] = expected_alias | ||
| else: | ||
| # Single component - ensure no alias | ||
| node_info = group_nodes[0].get("data", {}).get("node", {}) | ||
| if node_info and node_info.get("alias", "").endswith("#1"): | ||
| # Remove #1 suffix if it's the only component of its type | ||
| alias = node_info.get("alias", "") | ||
| if alias == f"{display_name}#1": | ||
| node_info["alias"] = None | ||
|
|
||
| return data_dict |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion
Ensure alias uniqueness and fill gaps when some numbered aliases already exist
Current logic may assign duplicate numbered aliases when some items in a group already have valid numbered aliases (e.g., an existing "Chat Input#3" plus new assignments can collide). Track used numbers per display_name and assign the next available number to guarantee uniqueness and stable, gap-filling behavior.
- # Assign aliases to groups with multiple components
- for display_name, group_nodes in display_name_groups.items():
- if len(group_nodes) > 1:
- # Multiple components - assign numbered aliases
- for index, node in enumerate(group_nodes):
- node_info = node.get("data", {}).get("node", {})
- if node_info:
- # Only assign if no existing alias (preserve all existing aliases)
- current_alias = node_info.get("alias")
-
- if not current_alias:
- expected_alias = f"{display_name}#{index + 1}"
- node_info["alias"] = expected_alias
- else:
- # Single component - ensure no alias
- node_info = group_nodes[0].get("data", {}).get("node", {})
- if node_info and node_info.get("alias", "").endswith("#1"):
- # Remove #1 suffix if it's the only component of its type
- alias = node_info.get("alias", "")
- if alias == f"{display_name}#1":
- node_info["alias"] = None
+ # Assign aliases to groups with multiple components
+ for display_name, group_nodes in display_name_groups.items():
+ if len(group_nodes) > 1:
+ # Collect already used numbers from valid "<DisplayName>#<n>" aliases
+ used_numbers: set[int] = set()
+ for node in group_nodes:
+ alias = node.get("data", {}).get("node", {}).get("alias")
+ if isinstance(alias, str) and alias.startswith(f"{display_name}#"):
+ try:
+ used_numbers.add(int(alias.split("#")[-1]))
+ except ValueError:
+ # ignore non-numeric suffixes (custom aliases)
+ pass
+
+ # Assign the smallest available numbers to items without an alias
+ next_n = 1
+ for node in group_nodes:
+ node_info = node.get("data", {}).get("node", {})
+ if not node_info or node_info.get("alias"):
+ continue
+ while next_n in used_numbers:
+ next_n += 1
+ node_info["alias"] = f"{display_name}#{next_n}"
+ used_numbers.add(next_n)
+ next_n += 1
+ else:
+ # Single component - remove trailing "#1" only if exactly "<DisplayName>#1"
+ node_info = group_nodes[0].get("data", {}).get("node", {})
+ alias = node_info.get("alias") if node_info else None
+ if isinstance(alias, str) and alias == f"{display_name}#1":
+ node_info["alias"] = None📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| def _add_dynamic_aliases(self, data_dict: dict) -> dict: | |
| """Calculate and assign dynamic aliases to duplicate components in graph data.""" | |
| nodes = data_dict.get("nodes", []) | |
| # Group nodes by display_name to identify duplicates | |
| display_name_groups: dict[str, list] = {} | |
| for node in nodes: | |
| node_info = node.get("data", {}).get("node", {}) | |
| display_name = node_info.get("display_name") | |
| if display_name: | |
| if display_name not in display_name_groups: | |
| display_name_groups[display_name] = [] | |
| display_name_groups[display_name].append(node) | |
| # Assign aliases to groups with multiple components | |
| for display_name, group_nodes in display_name_groups.items(): | |
| if len(group_nodes) > 1: | |
| # Multiple components - assign numbered aliases | |
| for index, node in enumerate(group_nodes): | |
| node_info = node.get("data", {}).get("node", {}) | |
| if node_info: | |
| # Only assign if no existing alias (preserve all existing aliases) | |
| current_alias = node_info.get("alias") | |
| if not current_alias: | |
| expected_alias = f"{display_name}#{index + 1}" | |
| node_info["alias"] = expected_alias | |
| else: | |
| # Single component - ensure no alias | |
| node_info = group_nodes[0].get("data", {}).get("node", {}) | |
| if node_info and node_info.get("alias", "").endswith("#1"): | |
| # Remove #1 suffix if it's the only component of its type | |
| alias = node_info.get("alias", "") | |
| if alias == f"{display_name}#1": | |
| node_info["alias"] = None | |
| return data_dict | |
| def _add_dynamic_aliases(self, data_dict: dict) -> dict: | |
| """Calculate and assign dynamic aliases to duplicate components in graph data.""" | |
| nodes = data_dict.get("nodes", []) | |
| # Group nodes by display_name to identify duplicates | |
| display_name_groups: dict[str, list] = {} | |
| for node in nodes: | |
| node_info = node.get("data", {}).get("node", {}) | |
| display_name = node_info.get("display_name") | |
| if display_name: | |
| if display_name not in display_name_groups: | |
| display_name_groups[display_name] = [] | |
| display_name_groups[display_name].append(node) | |
| # Assign aliases to groups with multiple components | |
| for display_name, group_nodes in display_name_groups.items(): | |
| if len(group_nodes) > 1: | |
| # Collect already used numbers from valid "<DisplayName>#<n>" aliases | |
| used_numbers: set[int] = set() | |
| for node in group_nodes: | |
| alias = node.get("data", {}).get("node", {}).get("alias") | |
| if isinstance(alias, str) and alias.startswith(f"{display_name}#"): | |
| try: | |
| used_numbers.add(int(alias.split("#")[-1])) | |
| except ValueError: | |
| # ignore non-numeric suffixes (custom aliases) | |
| pass | |
| # Assign the smallest available numbers to items without an alias | |
| next_n = 1 | |
| for node in group_nodes: | |
| node_info = node.get("data", {}).get("node", {}) | |
| if not node_info or node_info.get("alias"): | |
| continue | |
| while next_n in used_numbers: | |
| next_n += 1 | |
| node_info["alias"] = f"{display_name}#{next_n}" | |
| used_numbers.add(next_n) | |
| next_n += 1 | |
| else: | |
| # Single component - remove trailing "#1" only if exactly "<DisplayName>#1" | |
| node_info = group_nodes[0].get("data", {}).get("node", {}) | |
| alias = node_info.get("alias") if node_info else None | |
| if isinstance(alias, str) and alias == f"{display_name}#1": | |
| node_info["alias"] = None | |
| return data_dict |
🤖 Prompt for AI Agents
In src/lfx/src/lfx/graph/graph/base.py around lines 253 to 291, the alias
assignment can produce duplicate numbered aliases when some nodes already have
numbered aliases; update the logic to first collect existing numbered aliases
per display_name (parse node_info.get("alias") with a regex like r"^(.*)#(\d+)$"
to capture the number), build a set of used integers for that display_name, and
when assigning aliases iterate from 1 upward to pick the smallest unused
positive integer (so you fill gaps and avoid collisions) rather than using the
enumeration index; preserve any existing non-empty alias values, update only
nodes without alias, and keep the single-component logic (remove the "#1" suffix
only when the alias exactly equals f"{display_name}#1").
| # Create all resolution mappings in a single pass | ||
| nodes_map = {} | ||
| alias_map = {} | ||
| display_name_map = {} | ||
|
|
||
| for node in nodes: | ||
| node_id = node.get("id") | ||
| node_info = node.get("data", {}).get("node", {}) | ||
|
|
||
| # Map node ID | ||
| if node_id: | ||
| nodes_map[node_id] = node | ||
|
|
||
| # Map aliases (both user-defined and auto-generated) | ||
| alias = node_info.get("alias") | ||
| if alias: | ||
| alias_map[alias] = node | ||
|
|
||
| # Map display_name only if no alias is set (for single components) | ||
| display_name = node_info.get("display_name") | ||
| if display_name and not alias: | ||
| display_name_map[display_name] = node | ||
|
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion
Detect and fail on duplicate aliases; make display-name mapping unambiguous.
Currently last-write-wins can silently target the wrong node when aliases or display names collide. Enforce alias uniqueness and treat duplicate display names as ambiguous at resolution time.
Apply this diff:
- nodes_map = {}
- alias_map = {}
- display_name_map = {}
+ nodes_map: dict[str, dict] = {}
+ alias_map: dict[str, dict] = {}
+ display_name_map: dict[str, list[dict]] = {}
@@
- if alias:
- alias_map[alias] = node
+ if alias:
+ if alias in alias_map and alias_map[alias].get("id") != node_id:
+ raise ValueError(
+ f"Duplicate alias '{alias}' found for node IDs "
+ f"{alias_map[alias].get('id')} and {node_id}. "
+ "Aliases must be unique. Please adjust aliases."
+ )
+ alias_map[alias] = node
@@
- if display_name and not alias:
- display_name_map[display_name] = node
+ if display_name and not alias:
+ display_name_map.setdefault(display_name, []).append(node)📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| # Create all resolution mappings in a single pass | |
| nodes_map = {} | |
| alias_map = {} | |
| display_name_map = {} | |
| for node in nodes: | |
| node_id = node.get("id") | |
| node_info = node.get("data", {}).get("node", {}) | |
| # Map node ID | |
| if node_id: | |
| nodes_map[node_id] = node | |
| # Map aliases (both user-defined and auto-generated) | |
| alias = node_info.get("alias") | |
| if alias: | |
| alias_map[alias] = node | |
| # Map display_name only if no alias is set (for single components) | |
| display_name = node_info.get("display_name") | |
| if display_name and not alias: | |
| display_name_map[display_name] = node | |
| # Create all resolution mappings in a single pass | |
| nodes_map: dict[str, dict] = {} | |
| alias_map: dict[str, dict] = {} | |
| display_name_map: dict[str, list[dict]] = {} | |
| for node in nodes: | |
| node_id = node.get("id") | |
| node_info = node.get("data", {}).get("node", {}) | |
| # Map node ID | |
| if node_id: | |
| nodes_map[node_id] = node | |
| # Map aliases (both user-defined and auto-generated) | |
| alias = node_info.get("alias") | |
| if alias: | |
| if alias in alias_map and alias_map[alias].get("id") != node_id: | |
| raise ValueError( | |
| f"Duplicate alias '{alias}' found for node IDs " | |
| f"{alias_map[alias].get('id')} and {node_id}. " | |
| "Aliases must be unique. Please adjust aliases." | |
| ) | |
| alias_map[alias] = node | |
| # Map display_name only if no alias is set (for single components) | |
| display_name = node_info.get("display_name") | |
| if display_name and not alias: | |
| display_name_map.setdefault(display_name, []).append(node) |
🤖 Prompt for AI Agents
In src/lfx/src/lfx/processing/process.py around lines 213 to 235, update the
single-pass mapping to detect duplicate aliases and to make display_name
mappings unambiguous: during the loop, if an alias is already present in
alias_map, record the duplicate (or raise a clear error) and fail the processing
so duplicate aliases cannot silently overwrite each other; for display_name, if
a display_name is already present, mark it as ambiguous by removing/unsetting
that key (or storing a sentinel) so it is not treated as a unique mapping later;
keep node_id mapping as-is and ensure the function returns/raises a clear error
listing duplicate aliases (and optionally duplicate display_names) so callers
can surface the failure.
| if isinstance(value, dict): | ||
| if (node := nodes_map.get(key)) or (node := nodes_display_name_map.get(key)): | ||
| node = None | ||
| key_type = "" | ||
|
|
||
| if node := nodes_map.get(key): | ||
| key_type = "node ID" | ||
| elif node := alias_map.get(key): | ||
| key_type = "alias" | ||
| elif node := display_name_map.get(key): | ||
| key_type = "display name" | ||
|
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion
Handle ambiguous display names at resolution time.
Avoid silently picking one when multiple nodes share the same display name (and no alias).
Apply this diff:
- if node := nodes_map.get(key):
+ if node := nodes_map.get(key):
key_type = "node ID"
- elif node := alias_map.get(key):
+ elif node := alias_map.get(key):
key_type = "alias"
- elif node := display_name_map.get(key):
- key_type = "display name"
+ elif candidates := display_name_map.get(key):
+ if len(candidates) > 1:
+ ids = ", ".join(n.get("id") or "<unknown>" for n in candidates)
+ raise ValueError(
+ f"Ambiguous display name '{key}' refers to multiple components (IDs: {ids}). "
+ "Set unique aliases or use node IDs."
+ )
+ node = candidates[0]
+ key_type = "display name"📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| if isinstance(value, dict): | |
| if (node := nodes_map.get(key)) or (node := nodes_display_name_map.get(key)): | |
| node = None | |
| key_type = "" | |
| if node := nodes_map.get(key): | |
| key_type = "node ID" | |
| elif node := alias_map.get(key): | |
| key_type = "alias" | |
| elif node := display_name_map.get(key): | |
| key_type = "display name" | |
| if isinstance(value, dict): | |
| node = None | |
| key_type = "" | |
| if node := nodes_map.get(key): | |
| key_type = "node ID" | |
| elif node := alias_map.get(key): | |
| key_type = "alias" | |
| elif candidates := display_name_map.get(key): | |
| if len(candidates) > 1: | |
| ids = ", ".join(n.get("id") or "<unknown>" for n in candidates) | |
| raise ValueError( | |
| f"Ambiguous display name '{key}' refers to multiple components (IDs: {ids}). " | |
| "Set unique aliases or use node IDs." | |
| ) | |
| node = candidates[0] | |
| key_type = "display name" |
🤖 Prompt for AI Agents
In src/lfx/src/lfx/processing/process.py around lines 241 to 251, the resolution
logic currently silently picks a node when a key matches multiple nodes via
display_name_map; change it to detect ambiguity and fail-fast: when
display_name_map.get(key) returns more than one candidate, raise a clear error
(or return a resolution failure) indicating the display name is ambiguous and
require an alias or unique identifier; otherwise, continue to select the single
matching node. Also avoid using the walrus assignment pattern for clarity and
ensure key_type is set appropriately ("node ID", "alias", or "display name")
only after confirming a single unambiguous match.
| alias: str | None = None | ||
| """Alias of the frontend node for user-friendly referencing.""" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion
Good addition of alias field — reserve the name in input/output validation.
Alias becomes part of the public surface; prevent template fields from colliding with it.
🤖 Prompt for AI Agents
In src/lfx/src/lfx/template/frontend_node/base.py around lines 31-32, the new
public field "alias" must be reserved so template input/output fields cannot
collide with it; update the template validation logic to reject any input or
output field named "alias" (exact match) by adding a validation check that
raises a clear validation error when a template defines a field with that name,
include the check in both input and output validation paths, and add/adjust unit
tests and error message text accordingly.
|
This idea is great! It will be very helpful when building the input schema. |
|
I like this as an idea, though I have some questions/issues. It looks like you can edit aliases, which is good. But I feel like the I also noticed a bug, when you edit the alias of one component where you already have multiple, then drag a new one to the canvas, the numbers all update, which breaks the consistency. Monosnap.screencast.2025-09-12.09-36-14.mp4Also, when you're in the Input Schema, Does this need to include the tagged number system? Could you make the alias the same as the component ID when you first drag it to the canvas, and then encourage the behaviour of renaming components, say by focusing on the name editing field once the component is dropped? This would be good for self documenting purposes and would make the components more recognisable when editing in the Input Schema. |
dkaushik94
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Probably a non-trivial thing to solve.
| graph_dict["endpoint_name"] = str(endpoint_name) | ||
| return graph_dict | ||
|
|
||
| def _add_dynamic_aliases(self, data_dict: dict) -> dict: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Consider the following:
User has the following payload
"tweaks": {
"chat_input.input": {"text": "Hello"},
"opensearch#1.input": {"url":"https://www.google1.com"}
"opensearch#2.input": {"url":"https://www.google1.com"}
"opensearch#3.input": {"url":"https://www.google2.com"}
}
and is using this in a client to run their workflow.
Another user, ends up deleting the opensearch#2 and adds back the same component again (maybe it was a mistake, or some other reason), the new component will be opensearch#4 but this will be a breaking change for clients because they want to provide the input for the component. This will require the client end to accommodate this component identifier change in their code. We should maybe think about how we can deterministically evaluate component aliases. Fairly important for our Developer APIs and the SDK we are building.



This will allow users to have more reproducible tweaks.




Summary by CodeRabbit