chore(dev): workflow ui improvement

This commit is contained in:
Soumyadas15 2025-06-15 16:10:58 +05:30
parent d13d6a55e6
commit e2fc2478cc
5 changed files with 185 additions and 96 deletions

View File

@ -231,7 +231,13 @@ class WorkflowConverter:
tool_data = tool_node.get('data', {})
tool_name = tool_data.get('nodeId', tool_data.get('label', 'Unknown Tool'))
tool_desc = tool_data.get('description', 'No description available')
prompt_parts.append(f"- **{tool_name}**: {tool_desc}")
tool_instructions = tool_data.get('instructions', '')
# Build tool description with instructions if provided
tool_description = f"- **{tool_name}**: {tool_desc}"
if tool_instructions:
tool_description += f" - Instructions: {tool_instructions}"
prompt_parts.append(tool_description)
# Collect tool ID for XML examples
tool_id = tool_data.get('nodeId')
@ -327,6 +333,7 @@ class WorkflowConverter:
data = node.get('data', {})
name = data.get('label', 'Tool')
tool_id = data.get('nodeId', 'unknown_tool')
instructions = data.get('instructions', '')
input_connections = self._find_node_inputs(node.get('id'), edges)
output_connections = self._find_node_outputs(node.get('id'), edges)
@ -337,6 +344,10 @@ class WorkflowConverter:
f"**Purpose**: Provides {name.lower()} functionality to the workflow",
]
# Add instructions if provided
if instructions:
description.append(f"**Instructions**: {instructions}")
if input_connections:
description.append(f"**Connected to agents**: {', '.join(input_connections)}")

View File

@ -4,7 +4,7 @@ import { useState, useEffect } from "react";
import Link from "next/link";
import { Button } from "@/components/ui/button";
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card";
import { Plus, Play, Edit, Trash2, Clock, CheckCircle, XCircle, AlertCircle, Check, X } from "lucide-react";
import { Plus, Play, Edit, Trash2, Clock, CheckCircle, XCircle, AlertCircle, Check, X, Workflow as WorkflowIcon } from "lucide-react";
import { Badge } from "@/components/ui/badge";
import { Input } from "@/components/ui/input";
import {
@ -234,6 +234,23 @@ export default function WorkflowsPage() {
}
};
const getWorkflowColor = (status: string) => {
switch (status) {
case "active":
return "#10b981"; // green-500
case "draft":
return "#f59e0b"; // amber-500
case "paused":
return "#6b7280"; // gray-500
case "disabled":
return "#ef4444"; // red-500
case "archived":
return "#9ca3af"; // gray-400
default:
return "#8b5cf6"; // violet-500
}
};
if (loading) {
return (
<div className="max-w-7xl mx-auto p-6 space-y-6">
@ -322,10 +339,29 @@ export default function WorkflowsPage() {
) : (
<div className="grid gap-4 md:grid-cols-2 lg:grid-cols-3">
{workflows.map((workflow) => (
<Card key={workflow.id}>
<CardHeader>
<div className="flex justify-between items-start">
<div className="space-y-1 flex-1 mr-2">
<div
key={workflow.id}
className="bg-neutral-100 dark:bg-sidebar border border-border rounded-2xl overflow-hidden hover:bg-muted/50 transition-all duration-200 group"
>
{/* Colorful Header */}
<div
className="h-24 flex items-center justify-center relative bg-gradient-to-br from-opacity-90 to-opacity-100"
style={{ backgroundColor: getWorkflowColor(workflow.status) }}
>
<div className="text-3xl text-white drop-shadow-sm">
<WorkflowIcon />
</div>
<div className="absolute top-3 right-3">
{getStatusIcon(workflow.status)}
</div>
</div>
{/* Content */}
<div className="p-4">
<div className="space-y-3">
{/* Title and Status */}
<div className="flex justify-between items-start gap-2">
<div className="flex-1 min-w-0">
{editingWorkflowId === workflow.id ? (
<div className="flex items-center gap-2">
<Input
@ -367,46 +403,32 @@ export default function WorkflowsPage() {
</div>
</div>
) : (
<CardTitle
className="text-lg cursor-pointer hover:text-primary transition-colors"
<h3
className="text-foreground font-medium text-lg line-clamp-1 cursor-pointer hover:text-primary transition-colors"
onClick={() => handleStartEditName(workflow)}
>
{workflow.definition.name || workflow.name}
</CardTitle>
</h3>
)}
<CardDescription>{workflow.definition.description || workflow.description}</CardDescription>
</div>
{getStatusBadge(workflow.status)}
</div>
</CardHeader>
<CardContent>
<div className="space-y-4">
<p className="text-muted-foreground text-sm line-clamp-2 min-h-[2.5rem]">
{workflow.definition.description || workflow.description || 'No description provided'}
</p>
<div className="flex gap-2 pt-2">
<Link href={`/workflows/builder/${workflow.id}`} className="flex-1">
<Button variant="outline" size="sm" className="w-full">
<Edit className="mr-2 h-3 w-3" />
<Edit className="h-3 w-3" />
Edit
</Button>
</Link>
<Button
variant="outline"
size="sm"
className="flex-1"
onClick={() => handleRunWorkflow(workflow.id)}
disabled={executingWorkflows.has(workflow.id) || workflow.status !== 'active'}
>
{executingWorkflows.has(workflow.id) ? (
<div className="animate-spin rounded-full h-3 w-3 border-b border-current mr-2" />
) : (
<Play className="mr-2 h-3 w-3" />
)}
Run
</Button>
<AlertDialog>
<AlertDialogTrigger asChild>
<Button
variant="ghost"
size="sm"
className="h-8 w-8 p-0 hover:bg-destructive/10 hover:text-destructive text-muted-foreground opacity-0 group-hover:opacity-100 transition-opacity"
disabled={deletingWorkflows.has(workflow.id)}
>
{deletingWorkflows.has(workflow.id) ? (
@ -428,7 +450,7 @@ export default function WorkflowsPage() {
<AlertDialogCancel>Cancel</AlertDialogCancel>
<AlertDialogAction
onClick={() => handleDeleteWorkflow(workflow.id)}
className="bg-destructive text-destructive-foreground hover:bg-destructive/90"
className="bg-destructive text-white hover:bg-destructive/90"
>
Delete Workflow
</AlertDialogAction>
@ -437,8 +459,8 @@ export default function WorkflowsPage() {
</AlertDialog>
</div>
</div>
</CardContent>
</Card>
</div>
</div>
))}
</div>
)}

View File

@ -213,15 +213,15 @@ export default function NodePalette() {
<Tabs value={selectedCategory} onValueChange={setSelectedCategory}>
<TabsList className="grid w-full grid-cols-3">
<TabsTrigger value="input">
<Play className="h-4 w-4 mr-2" />
<Play className="h-4 w-4" />
Input
</TabsTrigger>
<TabsTrigger value="agent">
<Bot className="h-4 w-4 mr-2" />
<Bot className="h-4 w-4" />
Agents
</TabsTrigger>
<TabsTrigger value="tools">
<Wrench className="h-4 w-4 mr-2" />
<Wrench className="h-4 w-4" />
Tools
</TabsTrigger>
</TabsList>
@ -257,8 +257,8 @@ export default function NodePalette() {
variables: {}
}}
>
<Card className="group transition-all duration-200 border hover:border-primary/50 cursor-move border-primary/30 bg-primary/5">
<CardHeader className="p-4 py-0">
<Card className="py-2 group transition-all duration-200 border hover:border-primary/50 cursor-move border-primary/30 bg-primary/5">
<CardContent className="p-2 py-0">
<div className="flex items-start gap-3">
<div className="p-2 rounded-lg bg-primary/20 border border-primary/30 group-hover:bg-primary/30 transition-colors">
<Icon className="h-5 w-5 text-primary" />
@ -268,12 +268,12 @@ export default function NodePalette() {
{node.name}
<Badge variant="outline" className="text-xs">Required</Badge>
</CardTitle>
<CardDescription className="text-xs mt-1 line-clamp-2">
<CardDescription className="text-xs line-clamp-2">
{node.description}
</CardDescription>
</div>
</div>
</CardHeader>
</CardContent>
</Card>
</DraggableNode>
);
@ -310,9 +310,9 @@ export default function NodePalette() {
config: {},
}}
>
<Card className="group transition-all duration-200 border hover:border-primary/50 cursor-move">
<CardHeader className="p-4 py-0">
<div className="flex items-start gap-3">
<Card className="py-2 group transition-all duration-200 border hover:border-primary/50 cursor-move">
<CardContent className="p-2 py-0">
<div className="flex items-center gap-3">
<div className="p-2 rounded-lg bg-primary/10 border border-primary/20 group-hover:bg-primary/20 transition-colors">
<Icon className="h-5 w-5 text-primary" />
</div>
@ -320,12 +320,12 @@ export default function NodePalette() {
<CardTitle className="text-sm font-semibold leading-tight">
{node.name}
</CardTitle>
<CardDescription className="text-xs mt-1 line-clamp-2">
<CardDescription className="text-xs line-clamp-2">
{node.description}
</CardDescription>
</div>
</div>
</CardHeader>
</CardContent>
</Card>
</DraggableNode>
);
@ -361,10 +361,11 @@ export default function NodePalette() {
label: node.name,
nodeId: node.id,
config: {},
instructions: "",
}}
>
<Card className="group transition-all duration-200 border hover:border-primary/50 cursor-move">
<CardHeader className="p-4 py-0">
<Card className="py-2 group transition-all duration-200 border hover:border-primary/50 cursor-move">
<CardContent className="p-2 py-0">
<div className="flex items-start gap-3">
<div className="p-2 rounded-lg bg-primary/10 border border-primary/20 group-hover:bg-primary/20 transition-colors">
<Icon className="h-5 w-5 text-primary" />
@ -378,7 +379,7 @@ export default function NodePalette() {
</CardDescription>
</div>
</div>
</CardHeader>
</CardContent>
</Card>
</DraggableNode>
);

View File

@ -84,6 +84,7 @@ const initialNodes: Node[] = [
data: {
label: 'Web Search',
nodeId: 'web_search_tool',
instructions: '',
outputConnections: [
{
id: 'agent-1',

View File

@ -1,9 +1,14 @@
"use client";
import { memo } from "react";
import { memo, useState } from "react";
import { Handle, Position, NodeProps } from "@xyflow/react";
import { Card, CardContent, CardHeader } from "@/components/ui/card";
import { Badge } from "@/components/ui/badge";
import { Button } from "@/components/ui/button";
import { Textarea } from "@/components/ui/textarea";
import { Label } from "@/components/ui/label";
import { Separator } from "@/components/ui/separator";
import { Collapsible, CollapsibleContent, CollapsibleTrigger } from "@/components/ui/collapsible";
import {
Database,
FileText,
@ -14,18 +19,24 @@ import {
Cloud,
Send,
Settings,
Wrench
Wrench,
ChevronDown,
ChevronUp
} from "lucide-react";
import { useWorkflow } from "../WorkflowContext";
interface ToolConnectionNodeData {
label: string;
nodeId?: string;
toolType?: string;
config?: any;
instructions?: string;
}
const ToolConnectionNode = memo(({ data, selected }: NodeProps) => {
const ToolConnectionNode = memo(({ data, selected, id }: NodeProps) => {
const nodeData = data as unknown as ToolConnectionNodeData;
const [isConfigOpen, setIsConfigOpen] = useState(false);
const { updateNodeData } = useWorkflow();
const getToolConfig = () => {
const toolId = nodeData.nodeId || nodeData.toolType;
@ -134,6 +145,49 @@ const ToolConnectionNode = memo(({ data, selected }: NodeProps) => {
<span className="text-xs text-muted-foreground">Ready</span>
</div>
</div>
{/* Show instructions preview if available */}
{nodeData.instructions && !isConfigOpen && (
<div className="text-xs text-muted-foreground bg-muted/50 p-2 rounded border">
<strong>Instructions:</strong> {nodeData.instructions.length > 50
? `${nodeData.instructions.substring(0, 50)}...`
: nodeData.instructions}
</div>
)}
<Separator />
{/* Configuration Toggle */}
<Collapsible open={isConfigOpen} onOpenChange={setIsConfigOpen}>
<CollapsibleTrigger asChild>
<Button variant="ghost" size="sm" className="w-full justify-between p-2">
<span className="flex items-center gap-2">
<Settings className="h-4 w-4" />
Configure
</span>
{isConfigOpen ? <ChevronUp className="h-4 w-4" /> : <ChevronDown className="h-4 w-4" />}
</Button>
</CollapsibleTrigger>
<CollapsibleContent className="space-y-4 mt-3">
{/* Instructions Configuration */}
<div className="space-y-2">
<Label htmlFor={`instructions-${id}`} className="text-sm font-medium">
Tool Instructions
</Label>
<Textarea
id={`instructions-${id}`}
placeholder="Provide specific instructions for how this tool should be used in the workflow..."
value={nodeData.instructions || ''}
onChange={(e) => updateNodeData?.(id!, { instructions: e.target.value })}
className="min-h-[80px] text-sm"
/>
<p className="text-xs text-muted-foreground">
These instructions will be included in the workflow prompt to guide how the agent uses this tool.
</p>
</div>
</CollapsibleContent>
</Collapsible>
</CardContent>
</Card>