-
Notifications
You must be signed in to change notification settings - Fork 0
feat: implemente node configuration registry, execution engine, and d… #83
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
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||||
|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,102 @@ | ||||||||||
| "use client"; | ||||||||||
| import React, { useRef, useEffect } from 'react'; | ||||||||||
|
|
||||||||||
| export interface AvailableNode { | ||||||||||
| id: string; | ||||||||||
| name: string; | ||||||||||
| } | ||||||||||
|
|
||||||||||
| interface RichVariableInputProps { | ||||||||||
| value: string; | ||||||||||
| onChange: (newValue: string) => void; | ||||||||||
| availableNodes: AvailableNode[]; | ||||||||||
| placeholder?: string; | ||||||||||
| onFocus?: () => void; | ||||||||||
| } | ||||||||||
|
|
||||||||||
| const COLORS = [ | ||||||||||
| "bg-blue-500/20 text-blue-300 border-blue-500/50", | ||||||||||
| "bg-green-500/20 text-green-300 border-green-500/50", | ||||||||||
| "bg-purple-500/20 text-purple-300 border-purple-500/50", | ||||||||||
| "bg-orange-500/20 text-orange-300 border-orange-500/50", | ||||||||||
| "bg-pink-500/20 text-pink-300 border-pink-500/50", | ||||||||||
| "bg-teal-500/20 text-teal-300 border-teal-500/50", | ||||||||||
| ]; | ||||||||||
|
|
||||||||||
| export function getNodeColorClass(nodeId: string): string { | ||||||||||
| if (!nodeId) return COLORS[0]!; | ||||||||||
| let hash = 0; | ||||||||||
| for (let i = 0; i < nodeId.length; i++) { | ||||||||||
| hash = nodeId.charCodeAt(i) + ((hash << 5) - hash); | ||||||||||
| } | ||||||||||
| const index = Math.abs(hash) % COLORS.length; | ||||||||||
| return COLORS[index]!; | ||||||||||
| } | ||||||||||
|
|
||||||||||
| export function parseValueToHtml(rawValue: string, availableNodes: AvailableNode[]): string { | ||||||||||
| if (!rawValue) return ""; | ||||||||||
|
|
||||||||||
| return rawValue.replace(/\{\{([^.]+)\.([^}]+)\}\}/g, (match, nodeId, path) => { | ||||||||||
| const node = availableNodes.find(n => n.id === nodeId); | ||||||||||
| const displayName = node ? node.name : "Unknown Node"; | ||||||||||
| const colorClass = getNodeColorClass(nodeId); | ||||||||||
|
|
||||||||||
| return `<span contenteditable="false" class="inline-flex items-center px-1.5 py-0.5 mx-1 rounded text-[10px] border align-middle font-mono select-all cursor-default ${colorClass}" data-id="${nodeId}" data-path="${path}">${displayName} > ${path}</span>`; | ||||||||||
| }); | ||||||||||
|
Comment on lines
+39
to
+45
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🔒 Security & Privacy | 🔴 Critical | ⚡ Quick win Do not inject configuration values into
🤖 Prompt for AI Agents
Comment on lines
+44
to
+45
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win Add the
Proposed fix- return `<span contenteditable="false" class="inline-flex items-center px-1.5 py-0.5 mx-1 rounded text-[10px] border align-middle font-mono select-all cursor-default ${colorClass}" data-id="${nodeId}" data-path="${path}">${displayName} > ${path}</span>`;
+ return `<span contenteditable="false" class="pill inline-flex items-center px-1.5 py-0.5 mx-1 rounded text-[10px] border align-middle font-mono select-all cursor-default ${colorClass}" data-id="${nodeId}" data-path="${path}">${displayName} > ${path}</span>`;📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||
| } | ||||||||||
|
Comment on lines
+36
to
+46
|
||||||||||
|
|
||||||||||
| export function RichVariableInput({ value, onChange, availableNodes, placeholder, onFocus }: RichVariableInputProps) { | ||||||||||
| const editorRef = useRef<HTMLDivElement>(null); | ||||||||||
| const isInternalUpdate = useRef(false); | ||||||||||
|
|
||||||||||
| // Initial injection of HTML when the external value changes | ||||||||||
| useEffect(() => { | ||||||||||
| if (editorRef.current && !isInternalUpdate.current) { | ||||||||||
| const newHtml = parseValueToHtml(value, availableNodes); | ||||||||||
| if (editorRef.current.innerHTML !== newHtml) { | ||||||||||
| editorRef.current.innerHTML = newHtml; | ||||||||||
| } | ||||||||||
| } | ||||||||||
| isInternalUpdate.current = false; | ||||||||||
| }, [value, availableNodes]); | ||||||||||
|
|
||||||||||
| // Handle user input and serialize back to raw string | ||||||||||
| const handleInput = () => { | ||||||||||
| if (!editorRef.current) return; | ||||||||||
|
|
||||||||||
| let rawString = ""; | ||||||||||
|
|
||||||||||
| // Iterate through the DOM children to reconstruct the string | ||||||||||
| editorRef.current.childNodes.forEach((node) => { | ||||||||||
| if (node.nodeType === Node.TEXT_NODE) { | ||||||||||
| rawString += node.textContent || ""; | ||||||||||
| } else if (node.nodeType === Node.ELEMENT_NODE) { | ||||||||||
| const el = node as HTMLElement; | ||||||||||
| if (el.classList.contains("pill")) { | ||||||||||
| const nodeId = el.getAttribute("data-id"); | ||||||||||
| const path = el.getAttribute("data-path"); | ||||||||||
| if (nodeId && path) { | ||||||||||
| rawString += `{{${nodeId}.${path}}}`; | ||||||||||
| } | ||||||||||
| } else { | ||||||||||
| // For any pasted elements (br, divs), just extract text | ||||||||||
| rawString += el.textContent || ""; | ||||||||||
| } | ||||||||||
|
Comment on lines
+75
to
+84
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win Preserve line breaks during serialization. A 🤖 Prompt for AI Agents |
||||||||||
| } | ||||||||||
| }); | ||||||||||
|
|
||||||||||
| isInternalUpdate.current = true; | ||||||||||
| onChange(rawString); | ||||||||||
| }; | ||||||||||
|
|
||||||||||
| return ( | ||||||||||
| <div | ||||||||||
| ref={editorRef} | ||||||||||
| contentEditable={true} | ||||||||||
| onInput={handleInput} | ||||||||||
| onFocus={onFocus} | ||||||||||
| data-placeholder={placeholder} | ||||||||||
| className="w-full min-h-[40px] px-3 py-2 rounded-md border border-[#2a3525] bg-[#141a14] text-sm text-[#e8e8d8] focus:outline-none focus:border-[#baf266]/50 empty:before:content-[attr(data-placeholder)] empty:before:text-[#4a5440]" | ||||||||||
| /> | ||||||||||
| ); | ||||||||||
| } | ||||||||||
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -25,13 +25,18 @@ export function TestPanel({ testResult, nodeName, nodeIcon }: TestPanelProps) { | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| <div className="flex-[2] px-4 py-2.5 text-xs font-medium text-blue-300 truncate"> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| {key} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| </div> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| <div className="flex-[3] px-4 py-2.5 text-xs text-gray-300 border-l border-[#1a1f2e]/50 font-mono break-all"> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| <div className={`flex-[3] min-w-0 text-xs text-gray-300 border-l border-[#1a1f2e]/50 font-mono break-all ${isNested && Array.isArray(value) && value.length > 0 && typeof value[0] === 'object' ? 'p-0' : 'px-4 py-2.5'}`}> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| {isNested ? ( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Array.isArray(value) ? ( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| <span className="text-purple-400">[{value.map(v => typeof v === 'string' ? `"${v}"` : String(v)).join(', ')}]</span> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| value.length > 0 && typeof value[0] === 'object' ? ( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| <div className=" w-full">{renderArrayOfObjectsTable(value, true)}</div> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ) : ( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| <span className="text-purple-400">[{value.map(v => typeof v === 'string' ? `"${v}"` : String(v)).join(', ')}]</span> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ) : ( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| <span className="text-gray-500 italic">Object</span> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| <div className="mt-2 mb-2 w-full">{renderObjectTable(value)}</div> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+28
to
+39
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win Use a strict predicate for nested object arrays.
Define one predicate for non-null, non-array objects and reuse it for the padding class and renderer selection. Serialize object values in the inline branch when mixed arrays are supported. Proposed fix {entries.map(([key, value]) => {
const isNested = typeof value === 'object' && value !== null;
+ const isArrayOfObjects =
+ Array.isArray(value) &&
+ value.length > 0 &&
+ value.every(item =>
+ item !== null &&
+ typeof item === 'object' &&
+ !Array.isArray(item)
+ );
return (
...
- ${isNested && Array.isArray(value) && value.length > 0 && typeof value[0] === 'object' ? 'p-0' : 'px-4 py-2.5'}`}>
+ ${isArrayOfObjects ? 'p-0' : 'px-4 py-2.5'}`}>
...
- value.length > 0 && typeof value[0] === 'object' ? (
+ isArrayOfObjects ? (
...
- value.map(v => typeof v === 'string' ? `"${v}"` : String(v)).join(', ')
+ value.map(v =>
+ typeof v === 'string'
+ ? `"${v}"`
+ : typeof v === 'object' && v !== null
+ ? JSON.stringify(v)
+ : String(v)
+ ).join(', ')📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ) : typeof value === 'boolean' ? ( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| <span className={value ? 'text-green-400' : 'text-red-400'}>{String(value)}</span> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ) : value === null || value === undefined ? ( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
@@ -84,7 +89,7 @@ export function TestPanel({ testResult, nodeName, nodeIcon }: TestPanelProps) { | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // Array of objects table | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const renderArrayOfObjectsTable = (data: any[]) => { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const renderArrayOfObjectsTable = (data: any[], isNestedTable = false) => { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const keysSet = new Set<string>(); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| data.slice(0, 100).forEach(item => { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (item && typeof item === 'object') Object.keys(item).forEach(k => keysSet.add(k)); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
@@ -106,31 +111,30 @@ export function TestPanel({ testResult, nodeName, nodeIcon }: TestPanelProps) { | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return ( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| <div className="w-full border border-[#2a2f3e] rounded-lg overflow-hidden overflow-x-auto"> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| <table className="w-full text-left text-xs text-gray-300 min-w-max border-collapse"> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| <thead className="bg-[#161b22] sticky top-0"> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| <tr> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| <th className="px-3 py-2.5 w-10 text-center border-r border-b border-[#2a2f3e] text-gray-500 font-normal text-[10px]">#</th> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| {headers.map(h => ( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| <th key={h} className="px-4 py-2.5 border-r border-b border-[#2a2f3e] font-medium text-blue-300 truncate max-w-[160px]">{h}</th> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ))} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| </tr> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| </thead> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| <tbody> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| {data.slice(0, 100).map((item, ri) => ( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| <tr key={ri} className="border-b border-[#1a1f2e]/50 hover:bg-[#1f2536] transition-colors"> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| <td className="px-3 py-2 text-center border-r border-[#1a1f2e]/50 text-gray-600 bg-[#161b26] text-[10px]">{ri}</td> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| {headers.map(h => { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const val = item?.[h]; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const display = typeof val === 'object' && val !== null ? JSON.stringify(val) : String(val ?? ''); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return ( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| <td key={h} className="px-4 py-2 border-r border-[#1a1f2e]/50 truncate max-w-[200px] text-gray-300">{display}</td> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| })} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| </tr> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| <div className={`w-full overflow-hidden overflow-x-auto ${isNestedTable ? '' : 'border border-[#2a2f3e] rounded-lg'}`}> <table className="w-full text-left text-xs text-gray-300 min-w-max border-collapse"> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| <thead className="bg-[#161b22] sticky top-0"> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| <tr> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| <th className="px-3 py-2.5 w-10 text-center border-r border-b border-[#2a2f3e] text-gray-500 font-normal text-[10px]">#</th> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| {headers.map(h => ( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| <th key={h} className="px-4 py-2.5 border-r border-b border-[#2a2f3e] font-medium text-blue-300 truncate max-w-[160px]">{h}</th> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ))} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| </tbody> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| </table> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| </tr> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| </thead> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| <tbody> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| {data.slice(0, 100).map((item, ri) => ( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| <tr key={ri} className="border-b border-[#1a1f2e]/50 hover:bg-[#1f2536] transition-colors"> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| <td className="px-3 py-2 text-center border-r border-[#1a1f2e]/50 text-gray-600 bg-[#161b26] text-[10px]">{ri}</td> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| {headers.map(h => { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const val = item?.[h]; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const display = typeof val === 'object' && val !== null ? JSON.stringify(val) : String(val ?? ''); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return ( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| <td key={h} className="px-4 py-2 border-r border-[#1a1f2e]/50 truncate max-w-[200px] text-gray-300">{display}</td> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| })} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| </tr> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ))} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| </tbody> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| </table> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| </div> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -62,7 +62,7 @@ export function VariablePanel({ previousNodes, onInsert, activeField, onTestNode | |
| ); | ||
| } | ||
|
|
||
| const formattedNodeName = node.nodeName.toLowerCase().replace(/\s+/g, '_'); | ||
| const formattedNodeName = node.nodeId | ||
|
|
||
|
Comment on lines
+65
to
66
|
||
| const renderRows = (vars: VariableDefinition[], depth: number = 0, currParentPath: string = "") => { | ||
| return vars.map((variable, idx) => { | ||
|
|
@@ -141,8 +141,8 @@ export function VariablePanel({ previousNodes, onInsert, activeField, onTestNode | |
| }; | ||
|
|
||
| // Rendering for Spreadsheet (Arrays of arrays) | ||
| const renderSpreadsheetTable = (nodeName: string, data: any) => { | ||
| const formattedNodeName = nodeName.toLowerCase().replace(/\s+/g, '_'); | ||
| const renderSpreadsheetTable = (nodeName: string, nodeId: string, data: any) => { | ||
| const formattedNodeName = nodeId; | ||
| const rows = data.rows || data; // Handle data directly if it's the 2D array | ||
| if (!Array.isArray(rows) || rows.length === 0) return null; | ||
|
|
||
|
|
@@ -152,7 +152,16 @@ export function VariablePanel({ previousNodes, onInsert, activeField, onTestNode | |
| return ( | ||
| <div className="overflow-x-auto w-full border-t border-gray-800 scrollbar-thin scrollbar-thumb-gray-700 scrollbar-track-transparent"> | ||
| <div className="text-[10px] p-2 bg-gray-800/50 text-gray-400 flex justify-between items-center border-b border-gray-800"> | ||
| <span>Spreadsheet Data</span> | ||
| <div className="flex items-center gap-2"> | ||
| <span>Spreadsheet Data</span> | ||
| <button | ||
| onClick={() => handleInsert(`{{${formattedNodeName}.rows}}`)} | ||
| className="px-2 py-0.5 bg-blue-500/20 text-blue-400 hover:bg-blue-500/40 rounded transition-colors" | ||
| title="Insert the entire array of data" | ||
| > | ||
| Select Entire Table | ||
| </button> | ||
| </div> | ||
|
Comment on lines
+155
to
+164
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win 🧩 Analysis chain🏁 Script executed: #!/bin/bash
set -euo pipefail
# Inspect how interpolation resolves node paths and whether it aliases direct arrays as `rows`.
rg -n -C 6 'resolveVariable|rows' \
packages/common/src/interpolation.ts \
apps/web/app/components/ui/variable-panel.tsx \
'apps/web/app/workflows/[id]/components/ConfigModal.tsx'Repository: Dev-Pross/BuildFlow Length of output: 29949 Insert direct-array paths for spreadsheet outputs. When 🤖 Prompt for AI Agents |
||
| <span>{dataRows.length} rows</span> | ||
| </div> | ||
| <table className="w-full text-left text-xs text-gray-300 min-w-max border-collapse"> | ||
|
|
@@ -199,8 +208,8 @@ export function VariablePanel({ previousNodes, onInsert, activeField, onTestNode | |
| }; | ||
|
|
||
| // Rendering for standard array of objects | ||
| const renderArrayTable = (nodeName: string, dataArray: any[], isTested: boolean) => { | ||
| const formattedNodeName = nodeName.toLowerCase().replace(/\s+/g, '_'); | ||
| const renderArrayTable = (nodeName: string, nodeId: string, dataArray: any[], isTested: boolean) => { | ||
| const formattedNodeName = nodeId; | ||
|
|
||
| // Find all unique keys across objects to form headers | ||
| const keysSet = new Set<string>(); | ||
|
|
@@ -451,17 +460,17 @@ export function VariablePanel({ previousNodes, onInsert, activeField, onTestNode | |
| {/* Try matching Spreadsheet Pattern */} | ||
| {testOutput.data.rows && Array.isArray(testOutput.data.rows) && testOutput.data.rows.length > 0 && Array.isArray(testOutput.data.rows[0]) ? ( | ||
| <div className="bg-[#111620]"> | ||
| {renderSpreadsheetTable(node.nodeName, testOutput.data)} | ||
| {renderSpreadsheetTable(node.nodeName, node.nodeId, testOutput.data)} | ||
| </div> | ||
| ) : Array.isArray(testOutput.data) && testOutput.data.length > 0 && Array.isArray(testOutput.data[0]) ? ( | ||
| <div className="bg-[#111620]"> | ||
| {renderSpreadsheetTable(node.nodeName, testOutput.data)} | ||
| {renderSpreadsheetTable(node.nodeName, node.nodeId, testOutput.data)} | ||
| </div> | ||
| ) : | ||
| /* Try matching Standard Array pattern */ | ||
| Array.isArray(testOutput.data) ? ( | ||
| <div className="bg-[#111620]"> | ||
| {renderArrayTable(node.nodeName, testOutput.data, isTested)} | ||
| {renderArrayTable(node.nodeName, node.nodeId, testOutput.data, isTested)} | ||
| </div> | ||
| ) : ( | ||
| /* Fallback to Tree Table if it's an object or string */ | ||
|
|
||
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.
🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win
🧩 Analysis chain
🏁 Script executed:
Repository: Dev-Pross/BuildFlow
Length of output: 239
🏁 Script executed:
Repository: Dev-Pross/BuildFlow
Length of output: 1511
🌐 Web query:
Tailwind CSS object-center utility object-fit class names💡 Result:
In Tailwind CSS, object-position and object-fit are separate sets of utility classes used to control replaced elements like images and videos [1][2]. Object Position Utilities These classes control the object-position CSS property, which determines how an element is positioned within its container [1][3]. Class | CSS Property --- | --- object-bottom | object-position: bottom; [1] object-bottom-left | object-position: bottom left; [1] object-bottom-right | object-position: bottom right; [1] object-center | object-position: center; [1] object-left | object-position: left; [1] object-right | object-position: right; [1] object-top | object-position: top; [1] object-top-left | object-position: top left; [1] object-top-right | object-position: top right; [1] Object Fit Utilities These classes control the object-fit CSS property, which determines how an element is resized to fit its container [2][4]. Class | CSS Property --- | --- object-contain | object-fit: contain; [2] object-cover | object-fit: cover; [2] object-fill | object-fit: fill; [2] object-none | object-fit: none; [2] object-scale-down | object-fit: scale-down; [2] Both types of utilities support responsive design (using prefixes like md:), arbitrary values (using square brackets like object-[center_bottom]), and state modifiers (like hover:) [1][3][2][5][6].
Citations:
Use the valid Tailwind object-fit utility.
object-obtainis not a Tailwind object-fit class, so the icon image is not constrained. Replace it withobject-contain.🤖 Prompt for AI Agents