mirror of https://github.com/kortix-ai/suna.git
chore(dev): workflow ui improvement
This commit is contained in:
parent
d13d6a55e6
commit
e2fc2478cc
|
@ -231,7 +231,13 @@ class WorkflowConverter:
|
||||||
tool_data = tool_node.get('data', {})
|
tool_data = tool_node.get('data', {})
|
||||||
tool_name = tool_data.get('nodeId', tool_data.get('label', 'Unknown Tool'))
|
tool_name = tool_data.get('nodeId', tool_data.get('label', 'Unknown Tool'))
|
||||||
tool_desc = tool_data.get('description', 'No description available')
|
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
|
# Collect tool ID for XML examples
|
||||||
tool_id = tool_data.get('nodeId')
|
tool_id = tool_data.get('nodeId')
|
||||||
|
@ -327,6 +333,7 @@ class WorkflowConverter:
|
||||||
data = node.get('data', {})
|
data = node.get('data', {})
|
||||||
name = data.get('label', 'Tool')
|
name = data.get('label', 'Tool')
|
||||||
tool_id = data.get('nodeId', 'unknown_tool')
|
tool_id = data.get('nodeId', 'unknown_tool')
|
||||||
|
instructions = data.get('instructions', '')
|
||||||
|
|
||||||
input_connections = self._find_node_inputs(node.get('id'), edges)
|
input_connections = self._find_node_inputs(node.get('id'), edges)
|
||||||
output_connections = self._find_node_outputs(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",
|
f"**Purpose**: Provides {name.lower()} functionality to the workflow",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
# Add instructions if provided
|
||||||
|
if instructions:
|
||||||
|
description.append(f"**Instructions**: {instructions}")
|
||||||
|
|
||||||
if input_connections:
|
if input_connections:
|
||||||
description.append(f"**Connected to agents**: {', '.join(input_connections)}")
|
description.append(f"**Connected to agents**: {', '.join(input_connections)}")
|
||||||
|
|
||||||
|
|
|
@ -4,7 +4,7 @@ import { useState, useEffect } from "react";
|
||||||
import Link from "next/link";
|
import Link from "next/link";
|
||||||
import { Button } from "@/components/ui/button";
|
import { Button } from "@/components/ui/button";
|
||||||
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card";
|
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 { Badge } from "@/components/ui/badge";
|
||||||
import { Input } from "@/components/ui/input";
|
import { Input } from "@/components/ui/input";
|
||||||
import {
|
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) {
|
if (loading) {
|
||||||
return (
|
return (
|
||||||
<div className="max-w-7xl mx-auto p-6 space-y-6">
|
<div className="max-w-7xl mx-auto p-6 space-y-6">
|
||||||
|
@ -322,91 +339,96 @@ export default function WorkflowsPage() {
|
||||||
) : (
|
) : (
|
||||||
<div className="grid gap-4 md:grid-cols-2 lg:grid-cols-3">
|
<div className="grid gap-4 md:grid-cols-2 lg:grid-cols-3">
|
||||||
{workflows.map((workflow) => (
|
{workflows.map((workflow) => (
|
||||||
<Card key={workflow.id}>
|
<div
|
||||||
<CardHeader>
|
key={workflow.id}
|
||||||
<div className="flex justify-between items-start">
|
className="bg-neutral-100 dark:bg-sidebar border border-border rounded-2xl overflow-hidden hover:bg-muted/50 transition-all duration-200 group"
|
||||||
<div className="space-y-1 flex-1 mr-2">
|
>
|
||||||
{editingWorkflowId === workflow.id ? (
|
{/* Colorful Header */}
|
||||||
<div className="flex items-center gap-2">
|
<div
|
||||||
<Input
|
className="h-24 flex items-center justify-center relative bg-gradient-to-br from-opacity-90 to-opacity-100"
|
||||||
value={editingName}
|
style={{ backgroundColor: getWorkflowColor(workflow.status) }}
|
||||||
onChange={(e) => setEditingName(e.target.value)}
|
>
|
||||||
className="text-lg font-semibold h-8"
|
<div className="text-3xl text-white drop-shadow-sm">
|
||||||
onKeyDown={(e) => {
|
<WorkflowIcon />
|
||||||
if (e.key === 'Enter') {
|
|
||||||
handleSaveEditName(workflow.id);
|
|
||||||
} else if (e.key === 'Escape') {
|
|
||||||
handleCancelEditName();
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
autoFocus
|
|
||||||
/>
|
|
||||||
<div className="flex gap-1">
|
|
||||||
<Button
|
|
||||||
size="sm"
|
|
||||||
variant="ghost"
|
|
||||||
className="h-8 w-8 p-0"
|
|
||||||
onClick={() => handleSaveEditName(workflow.id)}
|
|
||||||
disabled={updatingWorkflows.has(workflow.id)}
|
|
||||||
>
|
|
||||||
{updatingWorkflows.has(workflow.id) ? (
|
|
||||||
<div className="animate-spin rounded-full h-3 w-3 border-b border-current" />
|
|
||||||
) : (
|
|
||||||
<Check className="h-3 w-3 text-green-600" />
|
|
||||||
)}
|
|
||||||
</Button>
|
|
||||||
<Button
|
|
||||||
size="sm"
|
|
||||||
variant="ghost"
|
|
||||||
className="h-8 w-8 p-0"
|
|
||||||
onClick={handleCancelEditName}
|
|
||||||
disabled={updatingWorkflows.has(workflow.id)}
|
|
||||||
>
|
|
||||||
<X className="h-3 w-3 text-red-600" />
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
) : (
|
|
||||||
<CardTitle
|
|
||||||
className="text-lg cursor-pointer hover:text-primary transition-colors"
|
|
||||||
onClick={() => handleStartEditName(workflow)}
|
|
||||||
>
|
|
||||||
{workflow.definition.name || workflow.name}
|
|
||||||
</CardTitle>
|
|
||||||
)}
|
|
||||||
<CardDescription>{workflow.definition.description || workflow.description}</CardDescription>
|
|
||||||
</div>
|
|
||||||
{getStatusBadge(workflow.status)}
|
|
||||||
</div>
|
</div>
|
||||||
</CardHeader>
|
<div className="absolute top-3 right-3">
|
||||||
<CardContent>
|
{getStatusIcon(workflow.status)}
|
||||||
<div className="space-y-4">
|
</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
|
||||||
|
value={editingName}
|
||||||
|
onChange={(e) => setEditingName(e.target.value)}
|
||||||
|
className="text-lg font-semibold h-8"
|
||||||
|
onKeyDown={(e) => {
|
||||||
|
if (e.key === 'Enter') {
|
||||||
|
handleSaveEditName(workflow.id);
|
||||||
|
} else if (e.key === 'Escape') {
|
||||||
|
handleCancelEditName();
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
autoFocus
|
||||||
|
/>
|
||||||
|
<div className="flex gap-1">
|
||||||
|
<Button
|
||||||
|
size="sm"
|
||||||
|
variant="ghost"
|
||||||
|
className="h-8 w-8 p-0"
|
||||||
|
onClick={() => handleSaveEditName(workflow.id)}
|
||||||
|
disabled={updatingWorkflows.has(workflow.id)}
|
||||||
|
>
|
||||||
|
{updatingWorkflows.has(workflow.id) ? (
|
||||||
|
<div className="animate-spin rounded-full h-3 w-3 border-b border-current" />
|
||||||
|
) : (
|
||||||
|
<Check className="h-3 w-3 text-green-600" />
|
||||||
|
)}
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
size="sm"
|
||||||
|
variant="ghost"
|
||||||
|
className="h-8 w-8 p-0"
|
||||||
|
onClick={handleCancelEditName}
|
||||||
|
disabled={updatingWorkflows.has(workflow.id)}
|
||||||
|
>
|
||||||
|
<X className="h-3 w-3 text-red-600" />
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
<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}
|
||||||
|
</h3>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
{getStatusBadge(workflow.status)}
|
||||||
|
</div>
|
||||||
|
<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">
|
<div className="flex gap-2 pt-2">
|
||||||
<Link href={`/workflows/builder/${workflow.id}`} className="flex-1">
|
<Link href={`/workflows/builder/${workflow.id}`} className="flex-1">
|
||||||
<Button variant="outline" size="sm" className="w-full">
|
<Button variant="outline" size="sm" className="w-full">
|
||||||
<Edit className="mr-2 h-3 w-3" />
|
<Edit className="h-3 w-3" />
|
||||||
Edit
|
Edit
|
||||||
</Button>
|
</Button>
|
||||||
</Link>
|
</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>
|
<AlertDialog>
|
||||||
<AlertDialogTrigger asChild>
|
<AlertDialogTrigger asChild>
|
||||||
<Button
|
<Button
|
||||||
variant="ghost"
|
variant="ghost"
|
||||||
size="sm"
|
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)}
|
disabled={deletingWorkflows.has(workflow.id)}
|
||||||
>
|
>
|
||||||
{deletingWorkflows.has(workflow.id) ? (
|
{deletingWorkflows.has(workflow.id) ? (
|
||||||
|
@ -428,7 +450,7 @@ export default function WorkflowsPage() {
|
||||||
<AlertDialogCancel>Cancel</AlertDialogCancel>
|
<AlertDialogCancel>Cancel</AlertDialogCancel>
|
||||||
<AlertDialogAction
|
<AlertDialogAction
|
||||||
onClick={() => handleDeleteWorkflow(workflow.id)}
|
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
|
Delete Workflow
|
||||||
</AlertDialogAction>
|
</AlertDialogAction>
|
||||||
|
@ -437,8 +459,8 @@ export default function WorkflowsPage() {
|
||||||
</AlertDialog>
|
</AlertDialog>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</CardContent>
|
</div>
|
||||||
</Card>
|
</div>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
|
@ -213,15 +213,15 @@ export default function NodePalette() {
|
||||||
<Tabs value={selectedCategory} onValueChange={setSelectedCategory}>
|
<Tabs value={selectedCategory} onValueChange={setSelectedCategory}>
|
||||||
<TabsList className="grid w-full grid-cols-3">
|
<TabsList className="grid w-full grid-cols-3">
|
||||||
<TabsTrigger value="input">
|
<TabsTrigger value="input">
|
||||||
<Play className="h-4 w-4 mr-2" />
|
<Play className="h-4 w-4" />
|
||||||
Input
|
Input
|
||||||
</TabsTrigger>
|
</TabsTrigger>
|
||||||
<TabsTrigger value="agent">
|
<TabsTrigger value="agent">
|
||||||
<Bot className="h-4 w-4 mr-2" />
|
<Bot className="h-4 w-4" />
|
||||||
Agents
|
Agents
|
||||||
</TabsTrigger>
|
</TabsTrigger>
|
||||||
<TabsTrigger value="tools">
|
<TabsTrigger value="tools">
|
||||||
<Wrench className="h-4 w-4 mr-2" />
|
<Wrench className="h-4 w-4" />
|
||||||
Tools
|
Tools
|
||||||
</TabsTrigger>
|
</TabsTrigger>
|
||||||
</TabsList>
|
</TabsList>
|
||||||
|
@ -257,8 +257,8 @@ export default function NodePalette() {
|
||||||
variables: {}
|
variables: {}
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Card className="group transition-all duration-200 border hover:border-primary/50 cursor-move border-primary/30 bg-primary/5">
|
<Card className="py-2 group transition-all duration-200 border hover:border-primary/50 cursor-move border-primary/30 bg-primary/5">
|
||||||
<CardHeader className="p-4 py-0">
|
<CardContent className="p-2 py-0">
|
||||||
<div className="flex items-start gap-3">
|
<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">
|
<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" />
|
<Icon className="h-5 w-5 text-primary" />
|
||||||
|
@ -268,12 +268,12 @@ export default function NodePalette() {
|
||||||
{node.name}
|
{node.name}
|
||||||
<Badge variant="outline" className="text-xs">Required</Badge>
|
<Badge variant="outline" className="text-xs">Required</Badge>
|
||||||
</CardTitle>
|
</CardTitle>
|
||||||
<CardDescription className="text-xs mt-1 line-clamp-2">
|
<CardDescription className="text-xs line-clamp-2">
|
||||||
{node.description}
|
{node.description}
|
||||||
</CardDescription>
|
</CardDescription>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</CardHeader>
|
</CardContent>
|
||||||
</Card>
|
</Card>
|
||||||
</DraggableNode>
|
</DraggableNode>
|
||||||
);
|
);
|
||||||
|
@ -310,9 +310,9 @@ export default function NodePalette() {
|
||||||
config: {},
|
config: {},
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Card className="group transition-all duration-200 border hover:border-primary/50 cursor-move">
|
<Card className="py-2 group transition-all duration-200 border hover:border-primary/50 cursor-move">
|
||||||
<CardHeader className="p-4 py-0">
|
<CardContent className="p-2 py-0">
|
||||||
<div className="flex items-start gap-3">
|
<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">
|
<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" />
|
<Icon className="h-5 w-5 text-primary" />
|
||||||
</div>
|
</div>
|
||||||
|
@ -320,12 +320,12 @@ export default function NodePalette() {
|
||||||
<CardTitle className="text-sm font-semibold leading-tight">
|
<CardTitle className="text-sm font-semibold leading-tight">
|
||||||
{node.name}
|
{node.name}
|
||||||
</CardTitle>
|
</CardTitle>
|
||||||
<CardDescription className="text-xs mt-1 line-clamp-2">
|
<CardDescription className="text-xs line-clamp-2">
|
||||||
{node.description}
|
{node.description}
|
||||||
</CardDescription>
|
</CardDescription>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</CardHeader>
|
</CardContent>
|
||||||
</Card>
|
</Card>
|
||||||
</DraggableNode>
|
</DraggableNode>
|
||||||
);
|
);
|
||||||
|
@ -361,10 +361,11 @@ export default function NodePalette() {
|
||||||
label: node.name,
|
label: node.name,
|
||||||
nodeId: node.id,
|
nodeId: node.id,
|
||||||
config: {},
|
config: {},
|
||||||
|
instructions: "",
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Card className="group transition-all duration-200 border hover:border-primary/50 cursor-move">
|
<Card className="py-2 group transition-all duration-200 border hover:border-primary/50 cursor-move">
|
||||||
<CardHeader className="p-4 py-0">
|
<CardContent className="p-2 py-0">
|
||||||
<div className="flex items-start gap-3">
|
<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">
|
<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" />
|
<Icon className="h-5 w-5 text-primary" />
|
||||||
|
@ -378,7 +379,7 @@ export default function NodePalette() {
|
||||||
</CardDescription>
|
</CardDescription>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</CardHeader>
|
</CardContent>
|
||||||
</Card>
|
</Card>
|
||||||
</DraggableNode>
|
</DraggableNode>
|
||||||
);
|
);
|
||||||
|
|
|
@ -84,6 +84,7 @@ const initialNodes: Node[] = [
|
||||||
data: {
|
data: {
|
||||||
label: 'Web Search',
|
label: 'Web Search',
|
||||||
nodeId: 'web_search_tool',
|
nodeId: 'web_search_tool',
|
||||||
|
instructions: '',
|
||||||
outputConnections: [
|
outputConnections: [
|
||||||
{
|
{
|
||||||
id: 'agent-1',
|
id: 'agent-1',
|
||||||
|
|
|
@ -1,9 +1,14 @@
|
||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import { memo } from "react";
|
import { memo, useState } from "react";
|
||||||
import { Handle, Position, NodeProps } from "@xyflow/react";
|
import { Handle, Position, NodeProps } from "@xyflow/react";
|
||||||
import { Card, CardContent, CardHeader } from "@/components/ui/card";
|
import { Card, CardContent, CardHeader } from "@/components/ui/card";
|
||||||
import { Badge } from "@/components/ui/badge";
|
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 {
|
import {
|
||||||
Database,
|
Database,
|
||||||
FileText,
|
FileText,
|
||||||
|
@ -14,18 +19,24 @@ import {
|
||||||
Cloud,
|
Cloud,
|
||||||
Send,
|
Send,
|
||||||
Settings,
|
Settings,
|
||||||
Wrench
|
Wrench,
|
||||||
|
ChevronDown,
|
||||||
|
ChevronUp
|
||||||
} from "lucide-react";
|
} from "lucide-react";
|
||||||
|
import { useWorkflow } from "../WorkflowContext";
|
||||||
|
|
||||||
interface ToolConnectionNodeData {
|
interface ToolConnectionNodeData {
|
||||||
label: string;
|
label: string;
|
||||||
nodeId?: string;
|
nodeId?: string;
|
||||||
toolType?: string;
|
toolType?: string;
|
||||||
config?: any;
|
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 nodeData = data as unknown as ToolConnectionNodeData;
|
||||||
|
const [isConfigOpen, setIsConfigOpen] = useState(false);
|
||||||
|
const { updateNodeData } = useWorkflow();
|
||||||
|
|
||||||
const getToolConfig = () => {
|
const getToolConfig = () => {
|
||||||
const toolId = nodeData.nodeId || nodeData.toolType;
|
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>
|
<span className="text-xs text-muted-foreground">Ready</span>
|
||||||
</div>
|
</div>
|
||||||
</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>
|
</CardContent>
|
||||||
</Card>
|
</Card>
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue