"use client"; import { useCallback, useMemo, useRef, useState } from "react"; import { useRouter } from "next/navigation"; import { useTranslations } from "next-intl"; import { toast } from "sonner"; import { useMutateAgents } from "@/hooks/queries/use-agents"; import { useMcpList } from "@/hooks/queries/use-mcp-list"; import { useWorkflowToolList } from "@/hooks/queries/use-workflow-tool-list"; import { useObjectState } from "@/hooks/use-object-state"; import { useBookmark } from "@/hooks/queries/use-bookmark"; import { Agent, AgentCreateSchema, AgentUpdateSchema } from "app-types/agent"; import { ChatMention } from "app-types/chat"; import { MCPServerInfo } from "app-types/mcp"; import { WorkflowSummary } from "app-types/workflow"; import { DefaultToolName } from "lib/ai/tools"; import { BACKGROUND_COLORS } from "lib/const"; import { cn, fetcher, objectFlow } from "lib/utils"; import { safe } from "ts-safe"; import { handleErrorWithToast } from "ui/shared-toast"; import { ChevronDownIcon, Loader, WandSparklesIcon } from "lucide-react"; import { Button } from "ui/button"; import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger, } from "ui/dropdown-menu"; import { Input } from "ui/input"; import { Label } from "ui/label"; import { Textarea } from "ui/textarea"; import { ScrollArea } from "ui/scroll-area"; import { Skeleton } from "ui/skeleton"; import { TextShimmer } from "ui/text-shimmer"; import { ShareableActions, Visibility } from "@/components/shareable-actions"; import { GenerateAgentDialog } from "./generate-agent-dialog"; import { AgentIconPicker } from "./agent-icon-picker"; import { AgentToolSelector } from "./agent-tool-selector"; import { RandomDataGeneratorExample, WeatherExample, } from "lib/ai/agent/example"; import { notify } from "lib/notify"; const defaultConfig = (): PartialBy< Omit, "id" > => { return { name: "", description: "", icon: { type: "emoji", value: "https://cdn.jsdelivr.net/npm/emoji-datasource-apple/img/apple/64/1f916.png", style: { backgroundColor: BACKGROUND_COLORS[0], }, }, instructions: { role: "", systemPrompt: "", mentions: [], }, visibility: "private", }; }; interface EditAgentProps { initialAgent?: Agent; userId: string; isOwner?: boolean; hasEditAccess?: boolean; isBookmarked?: boolean; } export default function EditAgent({ initialAgent, userId, isOwner = true, hasEditAccess = true, }: EditAgentProps) { const t = useTranslations(); const mutateAgents = useMutateAgents(); const router = useRouter(); const [openGenerateAgentDialog, setOpenGenerateAgentDialog] = useState(false); const [isSaving, setIsSaving] = useState(false); const [isVisibilityChangeLoading, setIsVisibilityChangeLoading] = useState(false); const textareaRef = useRef(null); // Initialize agent state with initial data or defaults const [agent, setAgent] = useObjectState(initialAgent || defaultConfig()); const { toggleBookmark, isLoading: isBookmarkToggleLoadingFn } = useBookmark({ itemType: "agent", }); const isBookmarkToggleLoading = useMemo( () => (initialAgent?.id && isBookmarkToggleLoadingFn(initialAgent?.id)) || false, [initialAgent?.id, isBookmarkToggleLoadingFn], ); const { data: mcpList, isLoading: isMcpLoading } = useMcpList(); const { data: workflowToolList, isLoading: isWorkflowLoading } = useWorkflowToolList(); const assignToolsByNames = useCallback( (toolNames: string[]) => { const allMentions: ChatMention[] = []; objectFlow(DefaultToolName).forEach((toolName) => { if (toolNames.includes(toolName)) { allMentions.push({ type: "defaultTool", name: toolName, label: toolName, }); } }); (mcpList as (MCPServerInfo & { id: string })[])?.forEach((mcp) => { mcp.toolInfo.forEach((tool) => { if (toolNames.includes(tool.name)) { allMentions.push({ type: "mcpTool", serverName: mcp.name, name: tool.name, serverId: mcp.id, }); } }); }); (workflowToolList as WorkflowSummary[])?.forEach((workflow) => { if (toolNames.includes(workflow.name)) { allMentions.push({ type: "workflow", name: workflow.name, workflowId: workflow.id, }); } }); if (allMentions.length > 0) { setAgent((prev) => ({ instructions: { ...prev.instructions, mentions: allMentions, }, })); } }, [mcpList, workflowToolList, setAgent], ); const saveAgent = useCallback(() => { if (initialAgent) { safe(() => setIsSaving(true)) .map(() => AgentUpdateSchema.parse({ ...agent })) .map(JSON.stringify) .map(async (body) => fetcher(`/api/agent/${initialAgent.id}`, { method: "PUT", body, }), ) .ifOk((updatedAgent) => { mutateAgents(updatedAgent); toast.success(t("Agent.updated")); router.push(`/agents`); }) .ifFail(handleErrorWithToast) .watch(() => setIsSaving(false)); } else { safe(() => setIsSaving(true)) .map(() => AgentCreateSchema.parse({ ...agent, userId })) .map(JSON.stringify) .map(async (body) => { return fetcher(`/api/agent`, { method: "POST", body, }); }) .ifOk((updatedAgent) => { mutateAgents(updatedAgent); toast.success(t("Agent.created")); router.push(`/agents`); }) .ifFail(handleErrorWithToast) .watch(() => setIsSaving(false)); } }, [agent, userId, mutateAgents, router, initialAgent, t]); const updateVisibility = useCallback( async (visibility: Visibility) => { if (initialAgent?.id) { safe(() => setIsVisibilityChangeLoading(true)) .map(() => AgentUpdateSchema.parse({ visibility })) .map(JSON.stringify) .map(async (body) => fetcher(`/api/agent/${initialAgent.id}`, { method: "PUT", body, }), ) .ifOk(() => { setAgent({ visibility }); mutateAgents({ id: initialAgent.id, visibility }); toast.success(t("Agent.visibilityUpdated")); }) .ifFail(handleErrorWithToast) .watch(() => setIsVisibilityChangeLoading(false)); } else { setAgent({ visibility }); } }, [initialAgent?.id, mutateAgents, setAgent, setIsVisibilityChangeLoading, t], ); const deleteAgent = useCallback(async () => { if (!initialAgent?.id) return; const ok = await notify.confirm({ description: t("Agent.deleteConfirm"), }); if (!ok) return; safe(() => setIsSaving(true)) .map(() => fetcher(`/api/agent/${initialAgent.id}`, { method: "DELETE", }), ) .ifOk(() => { mutateAgents({ id: initialAgent.id }, true); toast.success(t("Agent.deleted")); router.push("/agents"); }) .ifFail(handleErrorWithToast) .watch(() => setIsSaving(false)); }, [initialAgent?.id, mutateAgents, router, t]); const handleBookmarkToggle = useCallback(async () => { if (!initialAgent?.id || isBookmarkToggleLoading) return; safe(async () => { await toggleBookmark({ id: initialAgent.id, isBookmarked: agent.isBookmarked, }); }) .ifOk(() => { setAgent({ isBookmarked: !agent.isBookmarked }); }) .ifFail(handleErrorWithToast); }, [ initialAgent?.id, toggleBookmark, agent.isBookmarked, isBookmarkToggleLoading, ]); const handleAgentChange = useCallback((generatedData: any) => { if (textareaRef.current) { textareaRef.current.scrollTo({ top: textareaRef.current.scrollHeight, }); } setAgent((prev) => { const update: Partial = {}; objectFlow(generatedData).forEach((data, key) => { if (key === "name") { update.name = data as string; } if (key === "description") { update.description = data as string; } if (key === "instructions") { update.instructions = { ...prev.instructions, systemPrompt: data as string, }; } if (key === "role") { update.instructions = { ...prev.instructions, role: data as string, }; } }); return { ...prev, ...update }; }); }, []); const isLoadingTool = useMemo(() => { return isMcpLoading || isWorkflowLoading; }, [isMcpLoading, isWorkflowLoading]); const isLoading = useMemo(() => { return ( isLoadingTool || isSaving || isVisibilityChangeLoading || isBookmarkToggleLoading ); }, [ isLoadingTool, isSaving, isVisibilityChangeLoading, isBookmarkToggleLoading, ]); const isGenerating = openGenerateAgentDialog; return (
{isGenerating ? ( {t("Agent.generatingAgent")} ) : (

{t("Agent.title")}

)}
{hasEditAccess && !initialAgent && ( <> setAgent(RandomDataGeneratorExample)} >
🎲 Generate Random Data
setAgent(WeatherExample)} >
🌤️ Weather Checker
)} {initialAgent && (
)}
{false ? ( ) : ( setAgent({ name: e.target.value })} autoFocus disabled={isLoading || !hasEditAccess} className="hover:bg-input bg-secondary/40 transition-colors border-transparent border-none! focus-visible:bg-input! ring-0!" id="agent-name" data-testid="agent-name-input" placeholder={t("Agent.agentNamePlaceholder")} readOnly={!hasEditAccess} /> )}
{false ? ( ) : ( setAgent({ icon })} /> )}
{false ? ( ) : ( setAgent({ description: e.target.value })} readOnly={!hasEditAccess} /> )}

{t("Agent.agentSettingsDescription")}

{t("Agent.thisAgentIs")} {false ? ( ) : ( setAgent({ instructions: { ...agent.instructions, role: e.target.value || "", }, }) } readOnly={!hasEditAccess} /> )} {t("Agent.expertIn")}
{false ? ( ) : (