refactor(frontend): enhance NodeOutput component with accordion UI and cleanup logic

### Summary
This commit refactors the `NodeOutput` component to improve its user interface and streamline the handling of source names.

### Key Changes
1. **Accordion Integration**: Replaced the previous expandable section with an accordion layout for better organization of node output data.
2. **Source Name Cleanup**: Introduced utility functions to clean up source names, ensuring consistency in how tool source names are displayed.
3. **Code Simplification**: Removed unnecessary state management for expansion, simplifying the component's logic.

### Benefits
- **Improved User Experience**: The accordion layout enhances readability and interaction with node output data.
- **Cleaner Code**: The refactor reduces complexity and improves maintainability of the `NodeOutput` component.

### Testing
- Verified that the accordion functionality works as intended and that source names are displayed correctly without regressions in the node output display.
This commit is contained in:
abhi1992002
2026-01-19 11:59:13 +05:30
parent 0bc877fec4
commit bf5af517ec
3 changed files with 120 additions and 125 deletions

View File

@@ -1,22 +1,21 @@
import { Button } from "@/components/atoms/Button/Button";
import { Text } from "@/components/atoms/Text/Text";
import {
Accordion,
AccordionContent,
AccordionItem,
AccordionTrigger,
} from "@/components/molecules/Accordion/Accordion";
import { beautifyString, cn } from "@/lib/utils";
import { CaretDownIcon, CopyIcon, CheckIcon } from "@phosphor-icons/react";
import { CopyIcon, CheckIcon } from "@phosphor-icons/react";
import { NodeDataViewer } from "./components/NodeDataViewer/NodeDataViewer";
import { ContentRenderer } from "./components/ContentRenderer";
import { useNodeOutput } from "./useNodeOutput";
import { ViewMoreData } from "./components/ViewMoreData";
export const NodeDataRenderer = ({ nodeId }: { nodeId: string }) => {
const {
outputData,
isExpanded,
setIsExpanded,
copiedKey,
handleCopy,
executionResultId,
inputData,
} = useNodeOutput(nodeId);
const { outputData, copiedKey, handleCopy, executionResultId, inputData } =
useNodeOutput(nodeId);
if (Object.keys(outputData).length === 0) {
return null;
@@ -25,122 +24,114 @@ export const NodeDataRenderer = ({ nodeId }: { nodeId: string }) => {
return (
<div
data-tutorial-id={`node-output`}
className="flex flex-col gap-3 rounded-b-xl border-t border-zinc-200 px-4 py-4"
className="rounded-b-xl border-t border-zinc-200 px-4 py-2"
>
<div className="flex items-center justify-between">
<Text variant="body-medium" className="!font-semibold text-slate-700">
Node Output
</Text>
<Button
variant="ghost"
size="small"
onClick={() => setIsExpanded(!isExpanded)}
className="h-fit min-w-0 p-1 text-slate-600 hover:text-slate-900"
>
<CaretDownIcon
size={16}
weight="bold"
className={`transition-transform ${isExpanded ? "rotate-180" : ""}`}
/>
</Button>
</div>
<Accordion type="single" collapsible defaultValue="node-output">
<AccordionItem value="node-output" className="border-none">
<AccordionTrigger className="py-2 hover:no-underline">
<Text
variant="body-medium"
className="!font-semibold text-slate-700"
>
Node Output
</Text>
</AccordionTrigger>
<AccordionContent className="pt-2">
<div className="flex max-w-[350px] flex-col gap-4">
<div className="space-y-2">
<Text variant="small-medium">Input</Text>
{isExpanded && (
<>
<div className="flex max-w-[350px] flex-col gap-4">
<div className="space-y-2">
<Text variant="small-medium">Input</Text>
<ContentRenderer value={inputData} shortContent={false} />
<ContentRenderer value={inputData} shortContent={false} />
<div className="mt-1 flex justify-end gap-1">
<NodeDataViewer
data={inputData}
pinName="Input"
execId={executionResultId}
/>
<Button
variant="secondary"
size="small"
onClick={() => handleCopy("input", inputData)}
className={cn(
"h-fit min-w-0 gap-1.5 border border-zinc-200 p-2 text-black hover:text-slate-900",
copiedKey === "input" &&
"border-green-400 bg-green-100 hover:border-green-400 hover:bg-green-200",
)}
>
{copiedKey === "input" ? (
<CheckIcon size={12} className="text-green-600" />
) : (
<CopyIcon size={12} />
)}
</Button>
<div className="mt-1 flex justify-end gap-1">
<NodeDataViewer
data={inputData}
pinName="Input"
execId={executionResultId}
/>
<Button
variant="secondary"
size="small"
onClick={() => handleCopy("input", inputData)}
className={cn(
"h-fit min-w-0 gap-1.5 border border-zinc-200 p-2 text-black hover:text-slate-900",
copiedKey === "input" &&
"border-green-400 bg-green-100 hover:border-green-400 hover:bg-green-200",
)}
>
{copiedKey === "input" ? (
<CheckIcon size={12} className="text-green-600" />
) : (
<CopyIcon size={12} />
)}
</Button>
</div>
</div>
</div>
{Object.entries(outputData)
.slice(0, 2)
.map(([key, value]) => (
<div key={key} className="flex flex-col gap-2">
<div className="flex items-center gap-2">
<Text
variant="small-medium"
className="!font-semibold text-slate-600"
>
Pin:
</Text>
<Text variant="small" className="text-slate-700">
{beautifyString(key)}
</Text>
</div>
<div className="w-full space-y-2">
<Text
variant="small"
className="!font-semibold text-slate-600"
>
Data:
</Text>
<div className="relative space-y-2">
{value.map((item, index) => (
<div key={index}>
<ContentRenderer value={item} shortContent={true} />
{Object.entries(outputData)
.slice(0, 2)
.map(([key, value]) => (
<div key={key} className="flex flex-col gap-2">
<div className="flex items-center gap-2">
<Text
variant="small-medium"
className="!font-semibold text-slate-600"
>
Pin:
</Text>
<Text variant="small" className="text-slate-700">
{beautifyString(key)}
</Text>
</div>
<div className="w-full space-y-2">
<Text
variant="small"
className="!font-semibold text-slate-600"
>
Data:
</Text>
<div className="relative space-y-2">
{value.map((item, index) => (
<div key={index}>
<ContentRenderer value={item} shortContent={true} />
</div>
))}
<div className="mt-1 flex justify-end gap-1">
<NodeDataViewer
data={value}
pinName={key}
execId={executionResultId}
/>
<Button
variant="secondary"
size="small"
onClick={() => handleCopy(key, value)}
className={cn(
"h-fit min-w-0 gap-1.5 border border-zinc-200 p-2 text-black hover:text-slate-900",
copiedKey === key &&
"border-green-400 bg-green-100 hover:border-green-400 hover:bg-green-200",
)}
>
{copiedKey === key ? (
<CheckIcon size={12} className="text-green-600" />
) : (
<CopyIcon size={12} />
)}
</Button>
</div>
))}
<div className="mt-1 flex justify-end gap-1">
<NodeDataViewer
data={value}
pinName={key}
execId={executionResultId}
/>
<Button
variant="secondary"
size="small"
onClick={() => handleCopy(key, value)}
className={cn(
"h-fit min-w-0 gap-1.5 border border-zinc-200 p-2 text-black hover:text-slate-900",
copiedKey === key &&
"border-green-400 bg-green-100 hover:border-green-400 hover:bg-green-200",
)}
>
{copiedKey === key ? (
<CheckIcon size={12} className="text-green-600" />
) : (
<CopyIcon size={12} />
)}
</Button>
</div>
</div>
</div>
</div>
))}
</div>
))}
</div>
{Object.keys(outputData).length > 2 && (
<ViewMoreData outputData={outputData} execId={executionResultId} />
)}
</>
)}
{Object.keys(outputData).length > 2 && (
<ViewMoreData outputData={outputData} execId={executionResultId} />
)}
</AccordionContent>
</AccordionItem>
</Accordion>
</div>
);
};

View File

@@ -4,7 +4,6 @@ import { useShallow } from "zustand/react/shallow";
import { useState } from "react";
export const useNodeOutput = (nodeId: string) => {
const [isExpanded, setIsExpanded] = useState(true);
const [copiedKey, setCopiedKey] = useState<string | null>(null);
const { toast } = useToast();
@@ -37,13 +36,10 @@ export const useNodeOutput = (nodeId: string) => {
}
};
return {
outputData: outputData,
inputData: inputData,
isExpanded: isExpanded,
setIsExpanded: setIsExpanded,
copiedKey: copiedKey,
setCopiedKey: setCopiedKey,
handleCopy: handleCopy,
outputData,
inputData,
copiedKey,
handleCopy,
executionResultId: nodeExecutionResult?.node_exec_id,
};
};

View File

@@ -61,12 +61,20 @@ export const convertNodesPlusBlockInfoIntoCustomNodes = (
return customNode;
};
const isToolSourceName = (sourceName: string): boolean =>
sourceName.startsWith("tools_^_");
const cleanupSourceName = (sourceName: string): string =>
isToolSourceName(sourceName) ? "tools" : sourceName;
export const linkToCustomEdge = (link: Link): CustomEdge => ({
id: link.id ?? "",
type: "custom" as const,
source: link.source_id,
target: link.sink_id,
sourceHandle: link.source_name,
sourceHandle: cleanupSourceName(link.source_name),
targetHandle: link.sink_name,
data: {
isStatic: link.is_static,