"use client"; import { ExternalLink, Plus, RotateCcw, Search, Trash2 } from "lucide-react"; import { useRouter } from "next/navigation"; import { useCallback, useEffect, useState } from "react"; import { createToolApiV1ToolsPost, deleteToolApiV1ToolsToolUuidDelete, listToolsApiV1ToolsGet, unarchiveToolApiV1ToolsToolUuidUnarchivePost, } from "@/client/sdk.gen"; import type { CreateToolRequest, ToolResponse } from "@/client/types.gen"; import { CredentialSelector } from "@/components/http"; 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"; import { createMcpDefinition, createToolDefinition, getCategoryConfig, MCP_URL_PATTERN, renderToolIcon, TOOL_CATEGORIES, type ToolCategory, } from "./config"; export default function ToolsPage() { const { user, getAccessToken, redirectToLogin, loading } = useAuth(); const router = useRouter(); const [tools, setTools] = useState([]); 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("http_api"); const [isCreating, setIsCreating] = useState(false); const [error, setError] = useState(null); const [createError, setCreateError] = useState(null); // MCP-specific create dialog state const [mcpUrl, setMcpUrl] = useState(""); const [mcpCredentialUuid, setMcpCredentialUuid] = useState(""); const [mcpToolsFilter, setMcpToolsFilter] = useState(""); // 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}`, }, query: { status: "active,archived", }, }); 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()) { setCreateError("Please enter a name for the tool"); return; } if (newToolCategory === "mcp" && !mcpUrl.trim()) { setCreateError("Please enter the MCP server URL"); return; } if (newToolCategory === "mcp" && !MCP_URL_PATTERN.test(mcpUrl.trim())) { setCreateError("MCP server URL must start with http:// or https://"); return; } try { setIsCreating(true); setCreateError(null); const accessToken = await getAccessToken(); const categoryConfig = getCategoryConfig(newToolCategory); const definition = newToolCategory === "mcp" ? createMcpDefinition(mcpUrl, mcpCredentialUuid, mcpToolsFilter) : createToolDefinition(newToolCategory); const requestBody: CreateToolRequest = { name: newToolName, description: newToolDescription || undefined, category: newToolCategory, icon: categoryConfig?.iconName || "globe", icon_color: categoryConfig?.iconColor || "#3B82F6", definition, }; const response = await createToolApiV1ToolsPost({ body: requestBody, headers: { Authorization: `Bearer ${accessToken}`, }, }); if (response.error) { const errorDetail = (response.error as { detail?: string })?.detail; setCreateError(errorDetail || "Failed to create tool"); return; } if (response.data) { setIsCreateDialogOpen(false); setNewToolName(""); setNewToolDescription(""); setNewToolCategory("http_api"); setMcpUrl(""); setMcpCredentialUuid(""); setMcpToolsFilter(""); // Navigate to the new tool's detail page router.push(`/tools/${response.data.tool_uuid}`); } } catch (err: unknown) { let errorMessage = "Failed to create tool"; if (err && typeof err === "object") { const errObj = err as Record; // Handle API client error response if (errObj.error && typeof errObj.error === "object") { const errorData = errObj.error as Record; if (typeof errorData.detail === "string") { errorMessage = errorData.detail; } } // Handle standard Error objects else if (errObj.message && typeof errObj.message === "string") { errorMessage = errObj.message; } } setCreateError(errorMessage); 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 archive tool"); console.error("Error archiving tool:", err); } }; const handleUnarchiveTool = async (toolUuid: string, e: React.MouseEvent) => { e.stopPropagation(); try { setError(null); const accessToken = await getAccessToken(); await unarchiveToolApiV1ToolsToolUuidUnarchivePost({ path: { tool_uuid: toolUuid, }, headers: { Authorization: `Bearer ${accessToken}`, }, }); fetchTools(); } catch (err) { setError("Failed to unarchive tool"); console.error("Error unarchiving tool:", err); } }; const filteredTools = tools.filter( (tool) => tool.name.toLowerCase().includes(searchQuery.toLowerCase()) || tool.description?.toLowerCase().includes(searchQuery.toLowerCase()) ); const activeTools = filteredTools.filter((tool) => tool.status === "active"); const archivedTools = filteredTools.filter((tool) => tool.status === "archived"); const getCategoryBadge = (category: string) => { switch (category) { case "http_api": return HTTP API; case "end_call": return End Call; case "calculator": return Calculator; case "native": return Native; case "integration": return Integration; case "mcp": return MCP; default: return {category}; } }; const getStatusBadge = (status: string) => { switch (status) { case "active": return Active; case "draft": return Draft; case "archived": return Archived; default: return {status}; } }; if (loading || !user) { return (
); } return (

Tools

Manage reusable tools that can be used across your workflows.{" "} Learn more

{error && (
{error}
)}
Your Tools Create and manage tools for your organization
{/* Search */}
setSearchQuery(e.target.value)} className="pl-10" />
{isLoading ? (
{[1, 2, 3].map((i) => (
))}
) : activeTools.length === 0 && archivedTools.length === 0 ? (
{renderToolIcon("http_api", "w-12 h-12 text-muted-foreground mx-auto mb-4")}

{searchQuery ? "No tools match your search" : "No tools found"}

{!searchQuery && ( )}
) : ( <> {/* Active Tools */} {activeTools.length > 0 ? (
{activeTools.map((tool) => (
router.push(`/tools/${tool.tool_uuid}`) } >
{renderToolIcon(tool.category)}
{tool.name} {getCategoryBadge(tool.category)}
{tool.description && (

{tool.description}

)}
))}
) : !searchQuery ? (

No active tools

) : null} {/* Archived Tools */} {archivedTools.length > 0 && (

Archived Tools

{archivedTools.map((tool) => (
router.push(`/tools/${tool.tool_uuid}`) } >
{renderToolIcon(tool.category)}
{tool.name} {getCategoryBadge(tool.category)} {getStatusBadge(tool.status)}
{tool.description && (

{tool.description}

)}
))}
)} )}
{/* Create Tool Dialog */} { setIsCreateDialogOpen(open); if (open) { setCreateError(null); } else { // Reset MCP fields when dialog is closed without creating setMcpUrl(""); setMcpCredentialUuid(""); setMcpToolsFilter(""); } }}> Create New Tool Create a new tool that can be used in your workflows.

{getCategoryConfig(newToolCategory)?.description}

setNewToolName(e.target.value)} placeholder="e.g., Book Appointment, Check Inventory" />
setNewToolDescription(e.target.value)} placeholder="What does this tool do?" />
{newToolCategory === "mcp" && ( <>
setMcpUrl(e.target.value)} placeholder="https://your-mcp-server.example.com/mcp" />
setMcpToolsFilter(e.target.value)} placeholder="e.g., tool_one, tool_two" />

Comma-separated list of tool names to allow. Leave empty to expose all tools from the server.

)}
{createError && (
{createError}
)}
); }