feat: user defined custom tools as part of workflow execution (#94)

* feat: add custom tools functionality

* Show tools in nodes

* integrate tool calling with pipeline engine
This commit is contained in:
Abhishek 2026-01-02 13:11:02 +05:30 committed by GitHub
parent cc2d3e70d2
commit 3e55af9256
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
65 changed files with 5483 additions and 6673 deletions

View file

@ -0,0 +1,498 @@
"use client";
import { ArrowLeft, Code, Globe, Loader2, Save } from "lucide-react";
import { useParams, useRouter } from "next/navigation";
import { useCallback, useEffect, useState } from "react";
import {
getToolApiV1ToolsToolUuidGet,
updateToolApiV1ToolsToolUuidPut,
} from "@/client/sdk.gen";
import type { ToolResponse } from "@/client/types.gen";
// Extended HttpApiConfig with parameters (until client types are regenerated)
interface HttpApiConfigWithParams {
method?: string;
url?: string;
headers?: Record<string, string>;
credential_uuid?: string;
parameters?: ToolParameter[];
timeout_ms?: number;
}
import {
CredentialSelector,
type HttpMethod,
HttpMethodSelector,
KeyValueEditor,
type KeyValueItem,
ParameterEditor,
type ToolParameter,
} from "@/components/http";
import { Button } from "@/components/ui/button";
import {
Card,
CardContent,
CardDescription,
CardHeader,
CardTitle,
} from "@/components/ui/card";
import {
Dialog,
DialogContent,
DialogDescription,
DialogHeader,
DialogTitle,
} from "@/components/ui/dialog";
import { Input } from "@/components/ui/input";
import { Label } from "@/components/ui/label";
import { Skeleton } from "@/components/ui/skeleton";
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";
import { Textarea } from "@/components/ui/textarea";
import { useAuth } from "@/lib/auth";
export default function ToolDetailPage() {
const { toolUuid } = useParams<{ toolUuid: string }>();
const { user, getAccessToken, redirectToLogin, loading } = useAuth();
const router = useRouter();
const [tool, setTool] = useState<ToolResponse | null>(null);
const [isLoading, setIsLoading] = useState(true);
const [isSaving, setIsSaving] = useState(false);
const [error, setError] = useState<string | null>(null);
const [saveSuccess, setSaveSuccess] = useState(false);
const [showCodeDialog, setShowCodeDialog] = useState(false);
// Form state
const [name, setName] = useState("");
const [description, setDescription] = useState("");
const [httpMethod, setHttpMethod] = useState<HttpMethod>("POST");
const [url, setUrl] = useState("");
const [credentialUuid, setCredentialUuid] = useState("");
const [headers, setHeaders] = useState<KeyValueItem[]>([]);
const [parameters, setParameters] = useState<ToolParameter[]>([]);
const [timeoutMs, setTimeoutMs] = useState(5000);
// Redirect if not authenticated
useEffect(() => {
if (!loading && !user) {
redirectToLogin();
}
}, [loading, user, redirectToLogin]);
const fetchTool = useCallback(async () => {
if (loading || !user || !toolUuid) return;
try {
setIsLoading(true);
setError(null);
const accessToken = await getAccessToken();
const response = await getToolApiV1ToolsToolUuidGet({
path: { tool_uuid: toolUuid },
headers: {
Authorization: `Bearer ${accessToken}`,
},
});
if (response.data) {
setTool(response.data);
populateFormFromTool(response.data);
}
} catch (err) {
setError("Failed to fetch tool");
console.error("Error fetching tool:", err);
} finally {
setIsLoading(false);
}
}, [loading, user, toolUuid, getAccessToken]);
const populateFormFromTool = (tool: ToolResponse) => {
setName(tool.name);
setDescription(tool.description || "");
const config = tool.definition?.config as HttpApiConfigWithParams | undefined;
if (config) {
setHttpMethod((config.method as HttpMethod) || "POST");
setUrl(config.url || "");
setCredentialUuid(config.credential_uuid || "");
setTimeoutMs(config.timeout_ms || 5000);
// Convert headers object to array
if (config.headers) {
setHeaders(
Object.entries(config.headers).map(([key, value]) => ({
key,
value: value as string,
}))
);
} else {
setHeaders([]);
}
// Load parameters
if (config.parameters && Array.isArray(config.parameters)) {
setParameters(
config.parameters.map((p: ToolParameter) => ({
name: p.name || "",
type: p.type || "string",
description: p.description || "",
required: p.required ?? true,
}))
);
} else {
setParameters([]);
}
}
};
useEffect(() => {
fetchTool();
}, [fetchTool]);
const handleSave = async () => {
// Validate URL
if (!url.trim()) {
setError("URL is required");
return;
}
// Validate parameters have names
const invalidParams = parameters.filter((p) => !p.name.trim());
if (invalidParams.length > 0) {
setError("All parameters must have a name");
return;
}
try {
setIsSaving(true);
setError(null);
setSaveSuccess(false);
const accessToken = await getAccessToken();
// Convert headers array to object
const headersObject: Record<string, string> = {};
headers.filter((h) => h.key && h.value).forEach((h) => {
headersObject[h.key] = h.value;
});
// Filter out empty parameters
const validParameters = parameters.filter((p) => p.name.trim());
// Build the request body (cast needed until client types are regenerated)
const requestBody = {
name,
description: description || undefined,
definition: {
schema_version: 1,
type: "http_api",
config: {
method: httpMethod,
url,
credential_uuid: credentialUuid || undefined,
headers:
Object.keys(headersObject).length > 0
? headersObject
: undefined,
parameters:
validParameters.length > 0 ? validParameters : undefined,
timeout_ms: timeoutMs,
},
},
};
const response = await updateToolApiV1ToolsToolUuidPut({
path: { tool_uuid: toolUuid },
// eslint-disable-next-line @typescript-eslint/no-explicit-any
body: requestBody as any,
headers: {
Authorization: `Bearer ${accessToken}`,
},
});
if (response.data) {
setTool(response.data);
setSaveSuccess(true);
setTimeout(() => setSaveSuccess(false), 3000);
}
} catch (err) {
setError("Failed to save tool");
console.error("Error saving tool:", err);
} finally {
setIsSaving(false);
}
};
const getCodeSnippet = () => {
if (!tool) return "";
const headersObj: Record<string, string> = {
"Content-Type": "application/json",
};
headers.filter((h) => h.key && h.value).forEach((h) => {
headersObj[h.key] = h.value;
});
// Build example body from parameters
const exampleBody: Record<string, unknown> = {};
parameters.forEach((p) => {
if (p.type === "number") {
exampleBody[p.name] = 0;
} else if (p.type === "boolean") {
exampleBody[p.name] = true;
} else {
exampleBody[p.name] = `<${p.name}>`;
}
});
const hasBody = httpMethod !== "GET" && httpMethod !== "DELETE" && parameters.length > 0;
return `// ${tool.name}
// ${tool.description || "HTTP API Tool"}
const response = await fetch("${url}", {
method: "${httpMethod}",
headers: ${JSON.stringify(headersObj, null, 4)},${hasBody ? `
body: JSON.stringify(${JSON.stringify(exampleBody, null, 4)}),` : ""}
});
const data = await response.json();`;
};
if (loading || !user) {
return (
<div className="min-h-screen bg-background flex items-center justify-center">
<div className="space-y-4">
<Skeleton className="h-12 w-64" />
<Skeleton className="h-64 w-96" />
</div>
</div>
);
}
if (isLoading) {
return (
<div className="min-h-screen bg-background">
<div className="container mx-auto px-4 py-8">
<div className="max-w-4xl mx-auto space-y-6">
<Skeleton className="h-8 w-48" />
<Skeleton className="h-64 w-full" />
</div>
</div>
</div>
);
}
if (!tool) {
return (
<div className="min-h-screen bg-background">
<div className="container mx-auto px-4 py-8">
<div className="max-w-4xl mx-auto text-center">
<h1 className="text-2xl font-bold mb-4">Tool not found</h1>
<Button onClick={() => router.push("/tools")}>
<ArrowLeft className="w-4 h-4 mr-2" />
Back to Tools
</Button>
</div>
</div>
</div>
);
}
return (
<div className="min-h-screen bg-background">
<div className="container mx-auto px-4 py-8">
<div className="max-w-4xl mx-auto">
{/* Header */}
<div className="flex items-center justify-between mb-6">
<div className="flex items-center gap-4">
<Button
variant="ghost"
size="sm"
onClick={() => router.push("/tools")}
>
<ArrowLeft className="w-4 h-4 mr-2" />
Back
</Button>
<div className="flex items-center gap-3">
<div
className="w-10 h-10 rounded-lg flex items-center justify-center"
style={{
backgroundColor: tool.icon_color || "#3B82F6",
}}
>
<Globe className="w-5 h-5 text-white" />
</div>
<div>
<h1 className="text-xl font-bold">{name}</h1>
<p className="text-sm text-muted-foreground">
HTTP API Tool
</p>
</div>
</div>
</div>
<div className="flex items-center gap-2">
<Button
variant="outline"
onClick={() => setShowCodeDialog(true)}
>
<Code className="w-4 h-4 mr-2" />
View Code
</Button>
<Button onClick={handleSave} disabled={isSaving}>
{isSaving ? (
<>
<Loader2 className="w-4 h-4 mr-2 animate-spin" />
Saving...
</>
) : (
<>
<Save className="w-4 h-4 mr-2" />
Save
</>
)}
</Button>
</div>
</div>
{error && (
<div className="mb-4 p-4 bg-destructive/10 border border-destructive/20 rounded-lg text-destructive">
{error}
</div>
)}
{saveSuccess && (
<div className="mb-4 p-4 bg-green-500/10 border border-green-500/20 rounded-lg text-green-600">
Tool saved successfully!
</div>
)}
<Card>
<CardHeader>
<CardTitle>Tool Configuration</CardTitle>
<CardDescription>
Configure the HTTP API endpoint and request settings
</CardDescription>
</CardHeader>
<CardContent>
<Tabs defaultValue="settings" className="w-full">
<TabsList className="grid w-full grid-cols-3">
<TabsTrigger value="settings">Settings</TabsTrigger>
<TabsTrigger value="auth">Authentication</TabsTrigger>
<TabsTrigger value="parameters">Parameters</TabsTrigger>
</TabsList>
<TabsContent value="settings" className="space-y-4 mt-4">
<div className="grid gap-2">
<Label>Tool Name</Label>
<Label className="text-xs text-muted-foreground">
Use a descriptive name, like &quot;Get Weather using API&quot; for a tool that fetches weather
</Label>
<Input
value={name}
onChange={(e) => setName(e.target.value)}
placeholder="e.g., Book Appointment"
/>
</div>
<div className="grid gap-2">
<Label>Description</Label>
<Label className="text-xs text-muted-foreground">
Provide a description which makes it easy for LLM to understand what this tool does
</Label>
<Textarea
value={description}
onChange={(e) => setDescription(e.target.value)}
placeholder="What does this tool do?"
rows={3}
/>
</div>
<div className="grid grid-cols-2 gap-4">
<div className="grid gap-2">
<Label>HTTP Method</Label>
<HttpMethodSelector
value={httpMethod}
onChange={setHttpMethod}
/>
</div>
<div className="grid gap-2">
<Label>Timeout (ms)</Label>
<Input
type="number"
value={timeoutMs}
onChange={(e) =>
setTimeoutMs(parseInt(e.target.value) || 5000)
}
min={1000}
max={30000}
/>
</div>
</div>
<div className="grid gap-2">
<Label>Endpoint URL</Label>
<Input
value={url}
onChange={(e) => setUrl(e.target.value)}
placeholder="https://api.example.com/appointments"
/>
</div>
</TabsContent>
<TabsContent value="auth" className="space-y-4 mt-4">
<CredentialSelector
value={credentialUuid}
onChange={setCredentialUuid}
/>
</TabsContent>
<TabsContent value="parameters" className="space-y-4 mt-4">
<div className="grid gap-2">
<Label>Tool Parameters</Label>
<Label className="text-xs text-muted-foreground">
Define the parameters that the LLM will provide when calling this tool.
These will be sent as JSON body for POST/PUT/PATCH or as URL query params for GET/DELETE.
</Label>
<ParameterEditor
parameters={parameters}
onChange={setParameters}
/>
</div>
<div className="grid gap-2 pt-4 border-t">
<Label>Custom Headers</Label>
<Label className="text-xs text-muted-foreground">
Add custom headers to include in the request (optional)
</Label>
<KeyValueEditor
items={headers}
onChange={setHeaders}
keyPlaceholder="Header name"
valuePlaceholder="Header value"
addButtonText="Add Header"
/>
</div>
</TabsContent>
</Tabs>
</CardContent>
</Card>
</div>
</div>
{/* Code View Dialog */}
<Dialog open={showCodeDialog} onOpenChange={setShowCodeDialog}>
<DialogContent className="max-w-2xl">
<DialogHeader>
<DialogTitle>Code Preview</DialogTitle>
<DialogDescription>
JavaScript code to make this API call
</DialogDescription>
</DialogHeader>
<div className="bg-muted rounded-lg p-4 font-mono text-sm overflow-auto max-h-96">
<pre>{getCodeSnippet()}</pre>
</div>
</DialogContent>
</Dialog>
</div>
);
}

431
ui/src/app/tools/page.tsx Normal file
View file

@ -0,0 +1,431 @@
"use client";
import { Globe, Plus, Search, Trash2 } from "lucide-react";
import { useRouter } from "next/navigation";
import { useCallback, useEffect, useState } from "react";
import {
createToolApiV1ToolsPost,
deleteToolApiV1ToolsToolUuidDelete,
listToolsApiV1ToolsGet,
} from "@/client/sdk.gen";
import type { ToolResponse } from "@/client/types.gen";
import { Badge } from "@/components/ui/badge";
import { Button } from "@/components/ui/button";
import {
Card,
CardContent,
CardDescription,
CardHeader,
CardTitle,
} from "@/components/ui/card";
import {
Dialog,
DialogContent,
DialogDescription,
DialogFooter,
DialogHeader,
DialogTitle,
} from "@/components/ui/dialog";
import { Input } from "@/components/ui/input";
import { Label } from "@/components/ui/label";
import {
Select,
SelectContent,
SelectItem,
SelectTrigger,
SelectValue,
} from "@/components/ui/select";
import { Skeleton } from "@/components/ui/skeleton";
import { useAuth } from "@/lib/auth";
type ToolCategory = "http_api" | "native" | "integration";
const TOOL_CATEGORIES: { value: ToolCategory; label: string; description: string; disabled?: boolean }[] = [
{
value: "http_api",
label: "External HTTP API",
description: "Make HTTP requests to external APIs",
},
{
value: "native",
label: "Native (Coming Soon)",
description: "Built-in tools like call transfer, DTMF input",
disabled: true,
},
{
value: "integration",
label: "Integration (Coming Soon)",
description: "Third-party integrations like Google Calendar",
disabled: true,
},
];
export default function ToolsPage() {
const { user, getAccessToken, redirectToLogin, loading } = useAuth();
const router = useRouter();
const [tools, setTools] = useState<ToolResponse[]>([]);
const [isLoading, setIsLoading] = useState(true);
const [searchQuery, setSearchQuery] = useState("");
const [isCreateDialogOpen, setIsCreateDialogOpen] = useState(false);
const [newToolName, setNewToolName] = useState("");
const [newToolDescription, setNewToolDescription] = useState("");
const [newToolCategory, setNewToolCategory] = useState<ToolCategory>("http_api");
const [isCreating, setIsCreating] = useState(false);
const [error, setError] = useState<string | null>(null);
// Redirect if not authenticated
useEffect(() => {
if (!loading && !user) {
redirectToLogin();
}
}, [loading, user, redirectToLogin]);
const fetchTools = useCallback(async () => {
if (loading || !user) return;
try {
setIsLoading(true);
setError(null);
const accessToken = await getAccessToken();
const response = await listToolsApiV1ToolsGet({
headers: {
Authorization: `Bearer ${accessToken}`,
},
});
if (response.data) {
setTools(response.data);
}
} catch (err) {
setError("Failed to fetch tools");
console.error("Error fetching tools:", err);
} finally {
setIsLoading(false);
}
}, [loading, user, getAccessToken]);
useEffect(() => {
fetchTools();
}, [fetchTools]);
const handleCreateTool = async () => {
if (!newToolName.trim()) {
setError("Please enter a name for the tool");
return;
}
try {
setIsCreating(true);
setError(null);
const accessToken = await getAccessToken();
const response = await createToolApiV1ToolsPost({
body: {
name: newToolName,
description: newToolDescription || undefined,
category: newToolCategory,
icon: "globe",
icon_color: "#3B82F6",
definition: {
schema_version: 1,
type: newToolCategory,
config: {
method: "POST",
url: "",
},
},
},
headers: {
Authorization: `Bearer ${accessToken}`,
},
});
if (response.data) {
setIsCreateDialogOpen(false);
setNewToolName("");
setNewToolDescription("");
setNewToolCategory("http_api");
// Navigate to the new tool's detail page
router.push(`/tools/${response.data.tool_uuid}`);
}
} catch (err) {
setError("Failed to create tool");
console.error("Error creating tool:", err);
} finally {
setIsCreating(false);
}
};
const handleDeleteTool = async (toolUuid: string, e: React.MouseEvent) => {
e.stopPropagation();
if (!confirm("Are you sure you want to archive this tool?")) return;
try {
setError(null);
const accessToken = await getAccessToken();
await deleteToolApiV1ToolsToolUuidDelete({
path: {
tool_uuid: toolUuid,
},
headers: {
Authorization: `Bearer ${accessToken}`,
},
});
fetchTools();
} catch (err) {
setError("Failed to delete tool");
console.error("Error deleting tool:", err);
}
};
const filteredTools = tools.filter(
(tool) =>
tool.name.toLowerCase().includes(searchQuery.toLowerCase()) ||
tool.description?.toLowerCase().includes(searchQuery.toLowerCase())
);
const getCategoryBadge = (category: string) => {
switch (category) {
case "http_api":
return <Badge variant="default">HTTP API</Badge>;
case "native":
return <Badge variant="secondary">Native</Badge>;
case "integration":
return <Badge variant="outline">Integration</Badge>;
default:
return <Badge variant="outline">{category}</Badge>;
}
};
const getStatusBadge = (status: string) => {
switch (status) {
case "active":
return <Badge className="bg-green-500">Active</Badge>;
case "draft":
return <Badge variant="secondary">Draft</Badge>;
case "archived":
return <Badge variant="destructive">Archived</Badge>;
default:
return <Badge variant="outline">{status}</Badge>;
}
};
if (loading || !user) {
return (
<div className="min-h-screen bg-background flex items-center justify-center">
<div className="space-y-4">
<Skeleton className="h-12 w-64" />
<Skeleton className="h-64 w-96" />
</div>
</div>
);
}
return (
<div className="min-h-screen bg-background">
<div className="container mx-auto px-4 py-8">
<div className="max-w-6xl mx-auto">
<div className="mb-8">
<h1 className="text-3xl font-bold mb-2">Tools</h1>
<p className="text-muted-foreground">
Manage reusable HTTP API tools that can be used across your workflows
</p>
</div>
{error && (
<div className="mb-4 p-4 bg-destructive/10 border border-destructive/20 rounded-lg text-destructive">
{error}
</div>
)}
<Card className="mb-6">
<CardHeader>
<div className="flex justify-between items-center">
<div>
<CardTitle>Your Tools</CardTitle>
<CardDescription>
Create and manage HTTP API tools for your organization
</CardDescription>
</div>
<Button onClick={() => setIsCreateDialogOpen(true)}>
<Plus className="w-4 h-4 mr-2" />
Create Tool
</Button>
</div>
</CardHeader>
<CardContent>
{/* Search */}
<div className="relative mb-4">
<Search className="absolute left-3 top-1/2 transform -translate-y-1/2 h-4 w-4 text-muted-foreground" />
<Input
placeholder="Search tools..."
value={searchQuery}
onChange={(e) => setSearchQuery(e.target.value)}
className="pl-10"
/>
</div>
{isLoading ? (
<div className="space-y-4">
{[1, 2, 3].map((i) => (
<div
key={i}
className="flex items-center justify-between p-4 border rounded-lg"
>
<div className="space-y-2">
<Skeleton className="h-4 w-32" />
<Skeleton className="h-3 w-48" />
</div>
<Skeleton className="h-8 w-20" />
</div>
))}
</div>
) : filteredTools.length === 0 ? (
<div className="text-center py-12">
<Globe className="w-12 h-12 text-muted-foreground mx-auto mb-4" />
<p className="text-muted-foreground mb-4">
{searchQuery
? "No tools match your search"
: "No tools found"}
</p>
{!searchQuery && (
<Button onClick={() => setIsCreateDialogOpen(true)}>
Create Your First Tool
</Button>
)}
</div>
) : (
<div className="space-y-4">
{filteredTools.map((tool) => (
<div
key={tool.tool_uuid}
className="flex items-center justify-between p-4 border rounded-lg hover:bg-muted/50 cursor-pointer transition-colors"
onClick={() =>
router.push(`/tools/${tool.tool_uuid}`)
}
>
<div className="flex items-center gap-4">
<div
className="w-10 h-10 rounded-lg flex items-center justify-center"
style={{
backgroundColor:
tool.icon_color || "#3B82F6",
}}
>
<Globe className="w-5 h-5 text-white" />
</div>
<div>
<div className="flex items-center gap-2">
<span className="font-medium">
{tool.name}
</span>
{getCategoryBadge(tool.category)}
{getStatusBadge(tool.status)}
</div>
{tool.description && (
<p className="text-sm text-muted-foreground mt-1">
{tool.description}
</p>
)}
</div>
</div>
<Button
variant="ghost"
size="sm"
onClick={(e) =>
handleDeleteTool(tool.tool_uuid, e)
}
className="text-destructive hover:text-destructive/90"
>
<Trash2 className="w-4 h-4" />
</Button>
</div>
))}
</div>
)}
</CardContent>
</Card>
</div>
</div>
{/* Create Tool Dialog */}
<Dialog open={isCreateDialogOpen} onOpenChange={setIsCreateDialogOpen}>
<DialogContent>
<DialogHeader>
<DialogTitle>Create New Tool</DialogTitle>
<DialogDescription>
Create a new tool that can be used in your workflows.
</DialogDescription>
</DialogHeader>
<div className="grid gap-4 py-4">
<div className="grid gap-2">
<Label>Tool Type</Label>
<Select
value={newToolCategory}
onValueChange={(v) => setNewToolCategory(v as ToolCategory)}
>
<SelectTrigger className="w-full">
<SelectValue />
</SelectTrigger>
<SelectContent>
{TOOL_CATEGORIES.map((category) => (
<SelectItem
key={category.value}
value={category.value}
disabled={category.disabled}
>
{category.label}
</SelectItem>
))}
</SelectContent>
</Select>
<p className="text-xs text-muted-foreground">
{TOOL_CATEGORIES.find(c => c.value === newToolCategory)?.description}
</p>
</div>
<div className="grid gap-2">
<Label htmlFor="name">Tool Name</Label>
<Label className="text-xs text-muted-foreground">
Use a descriptive name, like &quot;Get Weather using API&quot; for a tool that fetches weather
</Label>
<Input
id="name"
value={newToolName}
onChange={(e) => setNewToolName(e.target.value)}
placeholder="e.g., Book Appointment, Check Inventory"
/>
</div>
<div className="grid gap-2">
<Label htmlFor="description">Description (Optional)</Label>
<Label className="text-xs text-muted-foreground">
Provide a description which makes it easy for LLM to understand what this tool does
</Label>
<Input
id="description"
value={newToolDescription}
onChange={(e) => setNewToolDescription(e.target.value)}
placeholder="What does this tool do?"
/>
</div>
</div>
<DialogFooter>
<Button
variant="outline"
onClick={() => setIsCreateDialogOpen(false)}
>
Cancel
</Button>
<Button onClick={handleCreateTool} disabled={isCreating}>
{isCreating ? "Creating..." : "Create Tool"}
</Button>
</DialogFooter>
</DialogContent>
</Dialog>
</div>
);
}

View file

@ -221,7 +221,7 @@ function RenderWorkflow({ initialWorkflowName, workflowId, initialFlow, initialT
</ReactFlow>
{/* Bottom-left controls - horizontal layout with custom buttons */}
<div className="absolute bottom-12 left-8 z-[1000] flex gap-2">
<div className="absolute bottom-12 left-8 z-10 flex gap-2">
<TooltipProvider>
{/* Zoom In */}
<Tooltip>

View file

@ -172,7 +172,7 @@ export default function WorkflowRunPage() {
<svg className="h-4 w-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M11 5H6a2 2 0 00-2 2v11a2 2 0 002 2h11a2 2 0 002-2v-5m-1.414-9.414a2 2 0 112.828 2.828L11.828 15H9v-2.828l8.586-8.586z" />
</svg>
Back to Agent
Customize Agent
</Button>
</Link>
</CardHeader>