"use client"; import { ReactNode, useMemo, useState } from "react"; import { Dialog, DialogContent, DialogDescription, DialogHeader, DialogTitle, } from "ui/dialog"; import useSWR from "swr"; import { cn, fetcher } from "lib/utils"; import { useTranslations } from "next-intl"; import { McpServerCustomization, MCPServerInfo, McpToolCustomization, MCPToolInfo, } from "app-types/mcp"; import { Tooltip, TooltipContent, TooltipTrigger } from "ui/tooltip"; import { ArrowLeft, ChevronRight, Info, Loader, Trash2, Wrench, } from "lucide-react"; import { Button } from "ui/button"; import { Textarea } from "ui/textarea"; import { safe } from "ts-safe"; import { z } from "zod"; import { handleErrorWithToast } from "ui/shared-toast"; import { Skeleton } from "ui/skeleton"; import { Alert, AlertDescription, AlertTitle } from "ui/alert"; import { ToolDetailPopupContent } from "./tool-detail-popup"; import { ExamplePlaceholder } from "ui/example-placeholder"; import { Input } from "ui/input"; import { appStore } from "@/app/store"; import { useShallow } from "zustand/shallow"; export function McpCustomizationPopup() { const [mcpCustomizationPopup, appStoreMutate] = appStore( useShallow((state) => [state.mcpCustomizationPopup, state.mutate]), ); return ( { if (!open) { appStoreMutate({ mcpCustomizationPopup: undefined }); } }} > {mcpCustomizationPopup ? ( ) : null} ); } export function McpServerCustomizationContent({ mcpServerInfo: { id, name, toolInfo, error }, title, }: { mcpServerInfo: MCPServerInfo & { id: string }; title?: ReactNode; }) { const t = useTranslations(); const [prompt, setPrompt] = useState(""); const [search, setSearch] = useState(""); const [isProcessing, setIsProcessing] = useState(false); const [selectedTool, setSelectedTool] = useState(null); const handleSave = () => { setIsProcessing(true); safe(() => z .object({ prompt: z.string().min(1).max(3000), }) .parse({ prompt, }), ) .map((body) => fetch(`/api/mcp/server-customizations/${id}`, { method: "POST", body: JSON.stringify(body), }), ) .ifOk(() => refreshMcpServerCustomization()) .ifFail(handleErrorWithToast) .watch(() => { setIsProcessing(false); }); }; const handleDelete = () => { setIsProcessing(true); safe(() => fetch(`/api/mcp/server-customizations/${id}`, { method: "DELETE", }), ) .ifOk(() => refreshMcpServerCustomization()) .ifFail(handleErrorWithToast) .watch(() => { setIsProcessing(false); }); }; const { data: mcpServerCustomization, mutate: refreshMcpServerCustomization, isLoading: isLoadingMcpServerCustomization, } = useSWR( `/api/mcp/server-customizations/${id}`, fetcher, { onSuccess: (data) => { setPrompt(data?.prompt || ""); }, revalidateOnFocus: false, }, ); const { data: mcpToolCustomizations, mutate: refreshMcpToolCustomizations, isLoading: isLoadingMcpToolCustomizations, } = useSWR( `/api/mcp/tool-customizations/${id}`, fetcher, { fallbackData: [], }, ); const toolCustomizations = useMemo(() => { const mcpToolCustomizationsMap = new Map( mcpToolCustomizations?.map((tool) => [tool.toolName, tool]), ); return toolInfo .filter((tool) => tool.name.includes(search)) .map((tool) => { return { name: tool.name, description: tool.description, prompt: mcpToolCustomizationsMap.get(tool.name)?.prompt || "", id: mcpToolCustomizationsMap.get(tool.name)?.id || null, inputSchema: tool.inputSchema, }; }); }, [mcpToolCustomizations, toolInfo, search]); if (selectedTool) { return ( {selectedTool.name} } /> ); } return (
{title || name}{" "} {error ?

error

: null}
{/* */}
{t("MCP.mcpServerCustomization")}

{t("MCP.mcpServerCustomizationDescription")}

{isProcessing || isLoadingMcpServerCustomization ? ( ) : ( <> {mcpServerCustomization?.id && ( {t("Common.delete")} )} {prompt != (mcpServerCustomization?.prompt || "") && ( {t("Common.edit")} )} )}