evo-ai/frontend/app/agents/dialogs/CustomToolDialog.tsx

864 lines
38 KiB
TypeScript

/*
┌──────────────────────────────────────────────────────────────────────────────┐
│ @author: Davidson Gomes │
│ @file: /app/agents/dialogs/CustomToolDialog.tsx │
│ Developed by: Davidson Gomes │
│ Creation date: May 13, 2025 │
│ Contact: contato@evolution-api.com │
├──────────────────────────────────────────────────────────────────────────────┤
│ @copyright © Evolution API 2025. All rights reserved. │
│ Licensed under the Apache License, Version 2.0 │
│ │
│ You may not use this file except in compliance with the License. │
│ You may obtain a copy of the License at │
│ │
│ http://www.apache.org/licenses/LICENSE-2.0 │
│ │
│ Unless required by applicable law or agreed to in writing, software │
│ distributed under the License is distributed on an "AS IS" BASIS, │
│ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. │
│ See the License for the specific language governing permissions and │
│ limitations under the License. │
├──────────────────────────────────────────────────────────────────────────────┤
│ @important │
│ For any future changes to the code in this file, it is recommended to │
│ include, together with the modification, the information of the developer │
│ who changed it and the date of modification. │
└──────────────────────────────────────────────────────────────────────────────┘
*/
"use client";
import { Button } from "@/components/ui/button";
import {
Dialog,
DialogContent,
DialogDescription,
DialogFooter,
DialogHeader,
DialogTitle,
} from "@/components/ui/dialog";
import { Input } from "@/components/ui/input";
import { Label } from "@/components/ui/label";
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";
import {
X,
Plus,
Info,
Trash,
Globe,
FileJson,
LayoutList,
Settings,
Database,
Code,
Server,
Wand
} from "lucide-react";
import { useState, useEffect } from "react";
import { HTTPTool, HTTPToolParameter } from "@/types/agent";
import { sanitizeAgentName } from "@/lib/utils";
import {
Tooltip,
TooltipContent,
TooltipProvider,
TooltipTrigger,
} from "@/components/ui/tooltip";
import { cn } from "@/lib/utils";
import { Badge } from "@/components/ui/badge";
interface CustomToolDialogProps {
open: boolean;
onOpenChange: (open: boolean) => void;
onSave: (tool: HTTPTool) => void;
initialTool?: HTTPTool | null;
}
export function CustomToolDialog({
open,
onOpenChange,
onSave,
initialTool = null,
}: CustomToolDialogProps) {
const [tool, setTool] = useState<Partial<HTTPTool>>({
name: "",
method: "GET",
endpoint: "",
description: "",
headers: {},
values: {},
parameters: {},
error_handling: {
timeout: 30,
retry_count: 0,
fallback_response: {},
},
});
const [headersList, setHeadersList] = useState<{ id: string; key: string; value: string }[]>([]);
const [bodyParams, setBodyParams] = useState<{ id: string; key: string; param: HTTPToolParameter }[]>([]);
const [pathParams, setPathParams] = useState<{ id: string; key: string; desc: string }[]>([]);
const [queryParams, setQueryParams] = useState<{ id: string; key: string; value: string }[]>([]);
const [valuesList, setValuesList] = useState<{ id: string; key: string; value: string }[]>([]);
const [timeout, setTimeout] = useState<number>(30);
const [fallbackError, setFallbackError] = useState<string>("");
const [fallbackMessage, setFallbackMessage] = useState<string>("");
const [activeTab, setActiveTab] = useState("basics");
useEffect(() => {
if (open) {
if (initialTool) {
setTool(initialTool);
setHeadersList(
Object.entries(initialTool.headers || {}).map(([key, value], idx) => ({
id: `header-${idx}`,
key,
value,
}))
);
setBodyParams(
Object.entries(initialTool.parameters?.body_params || {}).map(([key, param], idx) => ({
id: `body-${idx}`,
key,
param,
}))
);
setPathParams(
Object.entries(initialTool.parameters?.path_params || {}).map(([key, desc], idx) => ({
id: `path-${idx}`,
key,
desc: desc as string,
}))
);
setQueryParams(
Object.entries(initialTool.parameters?.query_params || {}).map(([key, value], idx) => ({
id: `query-${idx}`,
key,
value: value as string,
}))
);
setValuesList(
Object.entries(initialTool.values || {}).map(([key, value], idx) => ({
id: `val-${idx}`,
key,
value: value as string,
}))
);
setTimeout(initialTool.error_handling?.timeout || 30);
setFallbackError(initialTool.error_handling?.fallback_response?.error || "");
setFallbackMessage(initialTool.error_handling?.fallback_response?.message || "");
} else {
setTool({
name: "",
method: "GET",
endpoint: "",
description: "",
headers: {},
values: {},
parameters: {},
error_handling: {
timeout: 30,
retry_count: 0,
fallback_response: {},
},
});
setHeadersList([]);
setBodyParams([]);
setPathParams([]);
setQueryParams([]);
setValuesList([]);
setTimeout(30);
setFallbackError("");
setFallbackMessage("");
}
setActiveTab("basics");
}
}, [open, initialTool]);
const handleAddHeader = () => {
setHeadersList([...headersList, { id: `header-${Date.now()}`, key: "", value: "" }]);
};
const handleRemoveHeader = (id: string) => {
setHeadersList(headersList.filter((h) => h.id !== id));
};
const handleHeaderChange = (id: string, field: "key" | "value", value: string) => {
setHeadersList(headersList.map((h) => (h.id === id ? { ...h, [field]: value } : h)));
};
const handleAddBodyParam = () => {
setBodyParams([
...bodyParams,
{
id: `body-${Date.now()}`,
key: "",
param: { type: "string", required: false, description: "" },
},
]);
};
const handleRemoveBodyParam = (id: string) => {
setBodyParams(bodyParams.filter((p) => p.id !== id));
};
const handleBodyParamChange = (id: string, field: "key" | keyof HTTPToolParameter, value: string | boolean) => {
setBodyParams(
bodyParams.map((p) =>
p.id === id
? field === "key"
? { ...p, key: value as string }
: { ...p, param: { ...p.param, [field]: value } }
: p
)
);
};
// Path Params
const handleAddPathParam = () => {
setPathParams([...pathParams, { id: `path-${Date.now()}`, key: "", desc: "" }]);
};
const handleRemovePathParam = (id: string) => {
setPathParams(pathParams.filter((p) => p.id !== id));
};
const handlePathParamChange = (id: string, field: "key" | "desc", value: string) => {
setPathParams(pathParams.map((p) => (p.id === id ? { ...p, [field]: value } : p)));
};
// Query Params
const handleAddQueryParam = () => {
setQueryParams([...queryParams, { id: `query-${Date.now()}`, key: "", value: "" }]);
};
const handleRemoveQueryParam = (id: string) => {
setQueryParams(queryParams.filter((q) => q.id !== id));
};
const handleQueryParamChange = (id: string, field: "key" | "value", value: string) => {
setQueryParams(queryParams.map((q) => (q.id === id ? { ...q, [field]: value } : q)));
};
// Values
const handleAddValue = () => {
setValuesList([...valuesList, { id: `val-${Date.now()}`, key: "", value: "" }]);
};
const handleRemoveValue = (id: string) => {
setValuesList(valuesList.filter((v) => v.id !== id));
};
const handleValueChange = (id: string, field: "key" | "value", value: string) => {
setValuesList(valuesList.map((v) => (v.id === id ? { ...v, [field]: value } : v)));
};
const handleSave = () => {
if (!tool.name || !tool.endpoint) return;
const headersObject: Record<string, string> = {};
headersList.forEach((h) => {
if (h.key.trim()) headersObject[h.key] = h.value;
});
const bodyParamsObject: Record<string, HTTPToolParameter> = {};
bodyParams.forEach((p) => {
if (p.key.trim()) bodyParamsObject[p.key] = p.param;
});
const pathParamsObject: Record<string, string> = {};
pathParams.forEach((p) => {
if (p.key.trim()) pathParamsObject[p.key] = p.desc;
});
const queryParamsObject: Record<string, string> = {};
queryParams.forEach((q) => {
if (q.key.trim()) queryParamsObject[q.key] = q.value;
});
const valuesObject: Record<string, string> = {};
valuesList.forEach((v) => {
if (v.key.trim()) valuesObject[v.key] = v.value;
});
// Sanitize the tool name
const sanitizedName = sanitizeAgentName(tool.name);
onSave({
...(tool as HTTPTool),
name: sanitizedName,
headers: headersObject,
values: valuesObject,
parameters: {
...tool.parameters,
body_params: bodyParamsObject,
path_params: pathParamsObject,
query_params: queryParamsObject,
},
error_handling: {
timeout,
retry_count: tool.error_handling?.retry_count ?? 0,
fallback_response: {
error: fallbackError,
message: fallbackMessage,
},
},
} as HTTPTool);
onOpenChange(false);
};
const ParamField = ({
children,
label,
tooltip
}: {
children: React.ReactNode,
label: string,
tooltip?: string
}) => (
<div className="space-y-1.5">
<div className="flex items-center gap-1.5">
<Label className="text-sm font-medium text-neutral-200">
{label}
</Label>
{tooltip && (
<TooltipProvider>
<Tooltip>
<TooltipTrigger asChild>
<Info className="h-3.5 w-3.5 text-neutral-400 cursor-help" />
</TooltipTrigger>
<TooltipContent className="bg-neutral-800 border-neutral-700 text-white p-3 max-w-sm">
<p className="text-xs">{tooltip}</p>
</TooltipContent>
</Tooltip>
</TooltipProvider>
)}
</div>
{children}
</div>
);
const FieldList = <T extends Record<string, any>>({
items,
onAdd,
onRemove,
onChange,
fields,
addText,
emptyText,
icon
}: {
items: T[],
onAdd: () => void,
onRemove: (id: string) => void,
onChange: (id: string, field: string, value: any) => void,
fields: { name: string, field: string, placeholder: string, width: number, type?: string }[],
addText: string,
emptyText: string,
icon: React.ReactNode
}) => (
<div className="border border-neutral-700 rounded-md p-3 bg-neutral-800/50">
{items.length > 0 ? (
<div className="space-y-2 mb-3">
{items.map((item) => (
<div key={item.id} className="flex items-center gap-2 group">
<div className="flex-1 flex gap-2 w-full">
{fields.map(field => {
// Calculate percentage width based on the field's width value
const widthPercent = (field.width / 12) * 100;
return (
<div
key={field.name}
className="flex-shrink-0"
style={{ width: `${widthPercent}%` }}
>
{field.type === 'select' ? (
<select
value={(field.field.includes('.')
? item.param?.[field.field.split('.')[1]]
: item[field.field]) || ''}
onChange={(e) => onChange(
item.id,
field.field,
e.target.value
)}
className="w-full h-9 px-3 py-1 rounded-md bg-neutral-900 border border-neutral-700 text-white text-sm"
>
<option value="string">string</option>
<option value="number">number</option>
<option value="boolean">boolean</option>
</select>
) : field.type === 'checkbox' ? (
<div className="flex items-center h-9 w-full justify-center">
<label className="flex items-center gap-1.5">
<input
type="checkbox"
checked={item.param?.required || false}
onChange={(e) => onChange(
item.id,
field.field,
(e.target as HTMLInputElement).checked
)}
className="accent-emerald-400 rounded"
/>
<span className="text-xs text-neutral-300">Required</span>
</label>
</div>
) : (
<Input
value={(field.field.includes('.')
? item.param?.[field.field.split('.')[1]]
: item[field.field]) || ''}
onChange={(e) => onChange(
item.id,
field.field,
e.target.value
)}
className="h-9 w-full bg-neutral-900 border-neutral-700 text-white placeholder:text-neutral-500 text-sm"
placeholder={field.placeholder}
/>
)}
</div>
);
})}
</div>
<Button
variant="ghost"
size="icon"
onClick={() => onRemove(item.id)}
className="h-9 w-9 flex-shrink-0 opacity-0 group-hover:opacity-100 transition-opacity text-red-400 hover:text-red-300 hover:bg-red-900/20"
>
<Trash className="h-4 w-4" />
</Button>
</div>
))}
</div>
) : (
<div className="py-6 flex flex-col items-center justify-center text-center">
{icon}
<p className="mt-2 text-neutral-400 text-sm">{emptyText}</p>
</div>
)}
<Button
variant="outline"
size="sm"
onClick={onAdd}
className="w-full border-emerald-500/30 text-emerald-400 hover:bg-emerald-500/10 bg-neutral-800/30"
>
<Plus className="h-4 w-4 mr-1.5" /> {addText}
</Button>
</div>
);
return (
<Dialog open={open} onOpenChange={onOpenChange}>
<DialogContent className="sm:max-w-[850px] max-h-[90vh] overflow-hidden flex flex-col bg-neutral-900 border-neutral-700 p-0">
<div className="flex-1 flex flex-col overflow-hidden">
{/* Header Area */}
<div className="flex items-start justify-between p-6 border-b border-neutral-800">
<div>
<h2 className="text-xl font-semibold text-white flex items-center gap-2">
<Wand className="h-5 w-5 text-emerald-400" />
{initialTool ? "Edit Custom Tool" : "Create Custom Tool"}
</h2>
<p className="text-neutral-400 text-sm mt-1">
Configure an HTTP tool for your agent to interact with external APIs
</p>
</div>
<div className="flex items-center gap-3">
<Badge variant="outline" className="bg-neutral-800 text-emerald-400 border-emerald-500/30 uppercase text-xs font-semibold px-2 py-0.5">
{tool.method || "GET"}
</Badge>
</div>
</div>
{/* Main Content Area */}
<div className="flex-1 flex overflow-hidden">
{/* Left Side - Navigation */}
<div className="w-[200px] bg-neutral-800/50 border-r border-neutral-800 flex-shrink-0">
<div className="py-4">
<nav className="space-y-1 px-2">
{[
{ id: 'basics', label: 'Basic Info', icon: <Info className="h-4 w-4" /> },
{ id: 'endpoint', label: 'Endpoint', icon: <Globe className="h-4 w-4" /> },
{ id: 'headers', label: 'Headers', icon: <Server className="h-4 w-4" /> },
{ id: 'body', label: 'Body Params', icon: <FileJson className="h-4 w-4" /> },
{ id: 'path', label: 'Path Params', icon: <Code className="h-4 w-4" /> },
{ id: 'query', label: 'Query Params', icon: <LayoutList className="h-4 w-4" /> },
{ id: 'defaults', label: 'Default Values', icon: <Database className="h-4 w-4" /> },
{ id: 'error', label: 'Error Handling', icon: <Settings className="h-4 w-4" /> },
].map(item => (
<button
key={item.id}
className={cn(
"w-full flex items-center gap-2 px-3 py-2 text-sm rounded-md transition-colors",
activeTab === item.id
? "bg-emerald-500/10 text-emerald-400"
: "text-neutral-400 hover:text-neutral-200 hover:bg-neutral-800"
)}
onClick={() => setActiveTab(item.id)}
>
{item.icon}
<span>{item.label}</span>
{(
(item.id === 'headers' && headersList.length > 0) ||
(item.id === 'body' && bodyParams.length > 0) ||
(item.id === 'path' && pathParams.length > 0) ||
(item.id === 'query' && queryParams.length > 0) ||
(item.id === 'defaults' && valuesList.length > 0)
) && (
<span className="ml-auto bg-emerald-500/20 text-emerald-400 text-xs rounded-full px-1.5 py-0.5 min-w-[18px]">
{item.id === 'headers' && headersList.length}
{item.id === 'body' && bodyParams.length}
{item.id === 'path' && pathParams.length}
{item.id === 'query' && queryParams.length}
{item.id === 'defaults' && valuesList.length}
</span>
)}
</button>
))}
</nav>
</div>
</div>
{/* Right Side - Content */}
<div className="flex-1 overflow-auto">
{activeTab === 'basics' && (
<div className="p-6">
<div className="space-y-6">
<ParamField
label="Tool Name"
tooltip="A unique identifier for this tool. Will be used by the agent to reference this tool."
>
<Input
value={tool.name || ""}
onChange={(e) => setTool({ ...tool, name: e.target.value })}
onBlur={(e) => {
const sanitizedName = sanitizeAgentName(e.target.value);
if (sanitizedName !== e.target.value) {
setTool({ ...tool, name: sanitizedName });
}
}}
className="bg-neutral-800 border-neutral-700 text-white"
placeholder="e.g. weatherApi, searchTool"
/>
</ParamField>
<ParamField
label="Description"
tooltip="A clear description of what this tool does. The agent will use this to determine when to use the tool."
>
<Input
value={tool.description || ""}
onChange={(e) => setTool({ ...tool, description: e.target.value })}
className="bg-neutral-800 border-neutral-700 text-white"
placeholder="Provides weather information for a given location"
/>
</ParamField>
</div>
</div>
)}
{activeTab === 'endpoint' && (
<div className="p-6">
<div className="space-y-6">
<ParamField
label="HTTP Method"
tooltip="The HTTP method to use for the request."
>
<div className="flex gap-1">
{['GET', 'POST', 'PUT', 'PATCH', 'DELETE'].map(method => (
<Button
key={method}
type="button"
onClick={() => setTool({ ...tool, method })}
className={cn(
"px-4 py-2 rounded font-medium text-sm flex-1",
tool.method === method
? "bg-emerald-500/20 text-emerald-400 border border-emerald-500/30"
: "bg-neutral-800 text-neutral-400 border border-neutral-700 hover:bg-neutral-700 hover:text-neutral-200"
)}
>
{method}
</Button>
))}
</div>
</ParamField>
<ParamField
label="Endpoint URL"
tooltip="The complete URL to call. Can include path parameters with the format {paramName}."
>
<div className="space-y-1">
<Input
value={tool.endpoint || ""}
onChange={(e) => setTool({ ...tool, endpoint: e.target.value })}
className="bg-neutral-800 border-neutral-700 text-white font-mono"
placeholder="https://api.example.com/v1/resource/{id}"
/>
{tool.endpoint && tool.endpoint.includes('{') && (
<p className="text-xs text-amber-400">
<Info className="h-3 w-3 inline-block mr-1" />
URL contains path variables. Don't forget to define them in the Path Parameters section.
</p>
)}
</div>
</ParamField>
<div className="p-4 bg-neutral-800/80 border border-emerald-600/20 rounded-md mt-6">
<h3 className="text-emerald-400 text-sm font-medium mb-2 flex items-center">
<Info className="h-4 w-4 mr-2" /> How to use variables in your endpoint
</h3>
<p className="text-neutral-300 text-sm mb-3">
You can use dynamic variables in your endpoint using the <code className="bg-neutral-700 px-1.5 py-0.5 rounded text-emerald-300">{"{VARIABLE_NAME}"}</code> syntax.
</p>
<div className="grid grid-cols-1 gap-4 text-xs">
<div>
<h4 className="font-medium text-white mb-1">Example:</h4>
<code className="block bg-neutral-900 text-neutral-200 p-2 rounded">
https://api.example.com/users/<span className="text-emerald-400">{"{userId}"}</span>/profile
</code>
<p className="mt-1 text-neutral-400">Define <code className="bg-neutral-700 px-1 py-0.5 rounded text-emerald-300">userId</code> as a Path Parameter</p>
</div>
</div>
</div>
</div>
</div>
)}
{activeTab === 'headers' && (
<div className="p-6">
<ParamField
label="HTTP Headers"
tooltip="Headers to send with each request. Common examples include Authorization, Content-Type, Accept, etc."
>
<FieldList
items={headersList}
onAdd={handleAddHeader}
onRemove={handleRemoveHeader}
onChange={(id, field, value) => {
handleHeaderChange(
id,
field as "key" | "value",
value as string
);
}}
fields={[
{ name: "Header Name", field: "key", placeholder: "e.g. Authorization", width: 6 },
{ name: "Header Value", field: "value", placeholder: "e.g. Bearer token123", width: 6 }
]}
addText="Add Header"
emptyText="No headers configured. Add headers to customize your HTTP requests."
icon={<Server className="h-10 w-10 text-neutral-700" />}
/>
</ParamField>
</div>
)}
{activeTab === 'body' && (
<div className="p-6">
<div className="mb-6 p-4 bg-neutral-800/80 border border-emerald-600/20 rounded-md">
<h3 className="text-emerald-400 text-sm font-medium mb-2 flex items-center">
<Info className="h-4 w-4 mr-2" /> About Body Parameters
</h3>
<p className="text-neutral-300 text-sm">
Parameters that will be sent in the request body. Only applicable for POST, PUT, and PATCH methods.
You can use variables like <code className="bg-neutral-700 px-1.5 py-0.5 rounded text-emerald-300">{"{variableName}"}</code> that will be replaced at runtime.
</p>
</div>
<FieldList
items={bodyParams}
onAdd={handleAddBodyParam}
onRemove={handleRemoveBodyParam}
onChange={(id, field, value) => {
if (field === "key") {
handleBodyParamChange(id, "key", value as string);
} else if (field.startsWith("param.")) {
const paramField = field.split('.')[1] as keyof HTTPToolParameter;
handleBodyParamChange(id, paramField,
paramField === "required" ? value as boolean : value as string
);
}
}}
fields={[
{ name: "Parameter Name", field: "key", placeholder: "e.g. userId", width: 4 },
{ name: "Type", field: "param.type", placeholder: "", width: 2, type: "select" },
{ name: "Description", field: "param.description", placeholder: "What this parameter does", width: 4 },
{ name: "Required", field: "param.required", placeholder: "", width: 2, type: "checkbox" }
]}
addText="Add Body Parameter"
emptyText="No body parameters configured. Add parameters for POST/PUT/PATCH requests."
icon={<FileJson className="h-10 w-10 text-neutral-700" />}
/>
</div>
)}
{activeTab === 'path' && (
<div className="p-6">
<div className="mb-6 p-4 bg-neutral-800/80 border border-emerald-600/20 rounded-md">
<h3 className="text-emerald-400 text-sm font-medium mb-2 flex items-center">
<Info className="h-4 w-4 mr-2" /> About Path Parameters
</h3>
<p className="text-neutral-300 text-sm">
Path parameters are placeholders in your URL path that will be replaced with actual values.
For example, in <code className="bg-neutral-700 px-1.5 py-0.5 rounded text-emerald-300">https://api.example.com/users/{"{userId}"}</code>,
<code className="bg-neutral-700 px-1 py-0.5 rounded text-emerald-300">userId</code> is a path parameter.
</p>
</div>
<FieldList
items={pathParams}
onAdd={handleAddPathParam}
onRemove={handleRemovePathParam}
onChange={(id, field, value) => {
handlePathParamChange(
id,
field as "key" | "desc",
value as string
);
}}
fields={[
{ name: "Parameter Name", field: "key", placeholder: "e.g. userId", width: 4 },
{ name: "Description", field: "desc", placeholder: "What this parameter represents", width: 6 },
{ name: "Required", field: "required", placeholder: "", width: 2, type: "checkbox" }
]}
addText="Add Path Parameter"
emptyText="No path parameters configured. Add parameters if your endpoint URL contains {placeholders}."
icon={<Code className="h-10 w-10 text-neutral-700" />}
/>
</div>
)}
{activeTab === 'query' && (
<div className="p-6">
<div className="mb-6 p-4 bg-neutral-800/80 border border-emerald-600/20 rounded-md">
<h3 className="text-emerald-400 text-sm font-medium mb-2 flex items-center">
<Info className="h-4 w-4 mr-2" /> About Query Parameters
</h3>
<p className="text-neutral-300 text-sm">
Query parameters are added to the URL after a question mark (?) to filter or customize the request.
For example: <code className="bg-neutral-700 px-1.5 py-0.5 rounded text-emerald-300">https://api.example.com/search?query=term&limit=10</code>
</p>
</div>
<FieldList
items={queryParams}
onAdd={handleAddQueryParam}
onRemove={handleRemoveQueryParam}
onChange={(id, field, value) => {
handleQueryParamChange(
id,
field as "key" | "value",
value as string
);
}}
fields={[
{ name: "Parameter Name", field: "key", placeholder: "e.g. search", width: 4 },
{ name: "Description", field: "value", placeholder: "What this parameter does", width: 6 },
{ name: "Required", field: "required", placeholder: "", width: 2, type: "checkbox" }
]}
addText="Add Query Parameter"
emptyText="No query parameters configured. Add parameters to customize your URL query string."
icon={<LayoutList className="h-10 w-10 text-neutral-700" />}
/>
</div>
)}
{activeTab === 'defaults' && (
<div className="p-6">
<div className="mb-6 p-4 bg-neutral-800/80 border border-emerald-600/20 rounded-md">
<h3 className="text-emerald-400 text-sm font-medium mb-2 flex items-center">
<Info className="h-4 w-4 mr-2" /> About Default Values
</h3>
<p className="text-neutral-300 text-sm">
Set default values for parameters you've defined in the Body, Path, or Query sections.
These values will be used when the parameter isn't explicitly provided during a tool call.
</p>
</div>
<FieldList
items={valuesList}
onAdd={handleAddValue}
onRemove={handleRemoveValue}
onChange={(id, field, value) => {
handleValueChange(
id,
field as "key" | "value",
value as string
);
}}
fields={[
{ name: "Parameter Name", field: "key", placeholder: "Name of an existing parameter", width: 5 },
{ name: "Default Value", field: "value", placeholder: "Value to use if not provided", width: 7 }
]}
addText="Add Default Value"
emptyText="No default values configured. Add default values for your previously defined parameters."
icon={<Database className="h-10 w-10 text-neutral-700" />}
/>
</div>
)}
{activeTab === 'error' && (
<div className="p-6">
<div className="mb-6 p-4 bg-neutral-800/80 border border-emerald-600/20 rounded-md">
<h3 className="text-emerald-400 text-sm font-medium mb-2 flex items-center">
<Info className="h-4 w-4 mr-2" /> About Error Handling
</h3>
<p className="text-neutral-300 text-sm">
Configure how the tool should handle errors and timeouts when making HTTP requests.
Proper error handling ensures reliable operation of your agent.
</p>
</div>
<div className="space-y-6">
<ParamField
label="Timeout (seconds)"
tooltip="Maximum time to wait for a response before considering the request failed."
>
<Input
type="number"
min={1}
value={timeout}
onChange={(e) => setTimeout(Number(e.target.value))}
className="w-32 bg-neutral-800 border-neutral-700 text-white"
/>
</ParamField>
<ParamField
label="Fallback Error Code"
tooltip="Error code to return if the request fails."
>
<Input
value={fallbackError}
onChange={(e) => setFallbackError(e.target.value)}
className="bg-neutral-800 border-neutral-700 text-white"
placeholder="e.g. API_ERROR"
/>
</ParamField>
<ParamField
label="Fallback Error Message"
tooltip="Human-readable message to return if the request fails."
>
<Input
value={fallbackMessage}
onChange={(e) => setFallbackMessage(e.target.value)}
className="bg-neutral-800 border-neutral-700 text-white"
placeholder="e.g. Failed to retrieve data from the API."
/>
</ParamField>
</div>
</div>
)}
</div>
</div>
{/* Footer Area */}
<div className="border-t border-neutral-800 p-4 flex items-center justify-between">
<Button
variant="ghost"
onClick={() => onOpenChange(false)}
className="text-neutral-400 hover:text-neutral-100 hover:bg-neutral-800"
>
Cancel
</Button>
<Button
onClick={handleSave}
className="bg-emerald-500 text-neutral-950 hover:bg-emerald-400"
disabled={!tool.name || !tool.endpoint}
>
{initialTool ? "Save Changes" : "Create Tool"}
</Button>
</div>
</div>
</DialogContent>
</Dialog>
);
}