"use client"; import { useCallback, useRef, useState } from "react"; import { useTranslation } from "react-i18next"; import Link from "@/components/routing/app-link"; import { useParams } from "@/lib/routing/client-router"; import { IconCopy, IconTrash } from "@tabler/icons-react"; import { IconAlertTriangle } from "@tabler/icons-react"; import { Badge } from "@kandev/ui/badge"; import { Button } from "@kandev/ui/button"; import { Card, CardContent, CardHeader, CardTitle } from "@kandev/ui/card"; import { Alert, AlertDescription, AlertTitle } from "@kandev/ui/alert"; import { Separator } from "@kandev/ui/separator"; import { Switch } from "@kandev/ui/switch"; import { useToast } from "@/components/toast-provider"; import { useSettingsSaveContributor } from "@/components/settings/settings-save-provider"; import { SettingsCard } from "@/components/settings/settings-card"; import { ProfileFormFields, type ProfileFormData } from "@/components/settings/profile-form-fields"; import { profilePermissionValues } from "@/lib/agent-permissions"; import { toAgentProfilePatch } from "@/app/settings/agents/[agentId]/agent-save-helpers"; import { AgentProfileDeleteConfirmation, AgentProfileDeleteConflictDialog, AgentProfileDisableConflictDialog, type AgentProfileDeleteConflict, } from "@/components/settings/agent-profile-delete-dialog"; import { ProfileEnvVarsSection, areEnvVarsEqual, } from "@/components/settings/profile-edit/profile-env-vars-section"; import { errorMessage, useProfileDelete, useProfileEditorState, useProfileSave, useSyncAgentsToStore, } from "@/components/settings/agent-profile-page-state"; import { profileSaveInvalidReason, useProfileDuplicateAction, } from "@/components/settings/agent-profile-duplicate-action"; import { CustomCLIFlagsCard } from "@/components/settings/cli-flags-field"; import { ProfileEnabledHelp } from "@/components/settings/profile-enabled-help"; import { settingsActionClassName } from "@/components/settings/settings-control"; export { ProfileEnvVarsEditor, ProfileEnvVarsSection, } from "@/components/settings/profile-edit/profile-env-vars-section"; export { preserveNewerProfileDraft } from "@/components/settings/agent-profile-page-state"; import { useSecrets } from "@/hooks/domains/settings/use-secrets"; import type { Agent, AgentProfile, ModelConfig, PermissionSetting, PassthroughConfig, } from "@/lib/types/http"; import type { UtilityAgentReference } from "@/lib/types/agent-profile-errors"; import { useAppStore } from "@/components/state-provider"; import { AgentLogo } from "@/components/agent-logo"; import { ProfileMcpConfigCard } from "@/app/settings/agents/[agentId]/profile-mcp-config-card"; import { CommandPreviewCard } from "@/app/settings/agents/[agentId]/profiles/[profileId]/command-preview-card"; import type { AgentProfileMcpConfig } from "@/lib/types/http"; import { useAgentProfileSettings } from "@/app/settings/agents/[agentId]/profiles/[profileId]/use-agent-profile-settings"; import { agentProfileDiscoveryTarget } from "@/lib/settings-discovery/dynamic-targets"; import { useResponsiveBreakpoint } from "@/hooks/use-responsive-breakpoint"; import { useConfirmationBoundary } from "@/components/confirmation/mobile-action-confirmation"; import { DynamicAgentProfileEditor } from "@/components/settings/dynamic-agent-profile-editor"; import { isHandledApiError } from "@/lib/api/client"; type ProfileEditorProps = { agent: Agent; profile: AgentProfile; modelConfig: ModelConfig; permissionSettings: Record; passthroughConfig: PassthroughConfig | null; initialMcpConfig?: AgentProfileMcpConfig | null; }; function toProfileFormData( profile: AgentProfile, permissionSettings: Record, ): ProfileFormData { const permissionValues = profilePermissionValues(profile, permissionSettings); return { name: profile.name, model: profile.model, fallback_model: profile.fallbackModel ?? "", auto_fallback: profile.autoFallback ?? false, require_exact_model: profile.requireExactModel ?? false, mode: profile.mode ?? "", config_options: profile.configOptions ?? {}, auto_approve: permissionValues.auto_approve, allow_indexing: permissionValues.allow_indexing, cli_passthrough: profile.cliPassthrough, cli_flags: profile.cliFlags ?? [], command_prefix: profile.commandPrefix ?? "", }; } type ProfileEditorHeaderProps = { agentName: string; agentDisplayName: string; savedProfileName: string; enabled: boolean; onEnabledChange: (enabled: boolean) => void; onDuplicate: () => void; duplicating: boolean; }; function ProfileEditorHeader({ agentName, agentDisplayName, savedProfileName, enabled, onEnabledChange, onDuplicate, duplicating, }: ProfileEditorHeaderProps) { const { t } = useTranslation(); return (

{agentDisplayName} • {savedProfileName}

{t("agents:agentProfileSettings", { name: agentDisplayName })}

{t("agents:enabled")}

); } type DeleteProfileCardProps = { profile: AgentProfile; onDelete: () => void; open: boolean; onOpenChange: (open: boolean) => void; onConfirm: () => void | Promise; }; function DeleteProfileCard({ profile, onDelete, open, onOpenChange, onConfirm, }: DeleteProfileCardProps) { const { t } = useTranslation(); const { isFinePointer, isMobile } = useResponsiveBreakpoint(); useConfirmationBoundary(open, profile.id, onOpenChange); const deleteAnchorRef = useRef(null); const closeDeleteConfirmation = () => { onOpenChange(false); queueMicrotask(() => deleteAnchorRef.current?.focus()); }; return ( {t("agents:deleteProfile")}

{t("agents:removeThisProfile")}

{t("agents:actionCannotBeUndone")}

{!open || isFinePointer || isMobile ? ( ) : null} {!isMobile && !isFinePointer && open ? (
) : null}
{isMobile || isFinePointer ? ( ) : null}
); } type ProfileSettingsCardProps = { agent: Agent; draft: AgentProfile; savedProfile: AgentProfile; isDirty: boolean; onDraftChange: (patch: Partial) => void; modelConfig: ModelConfig; permissionSettings: Record; passthroughConfig: PassthroughConfig | null; onModelConfigResolutionPendingChange: (pending: boolean) => void; }; function ProfileSettingsCard({ agent, draft, savedProfile, isDirty, onDraftChange, modelConfig, permissionSettings, passthroughConfig, onModelConfigResolutionPendingChange, }: ProfileSettingsCardProps) { const { t } = useTranslation(); const handleFormChange = (patch: Partial) => { onDraftChange(toAgentProfilePatch(patch)); }; return ( {t("agents:profileSettings")} {agent.supports_mcp && MCP} ); } type ProfileDeleteDialogsProps = { conflict: AgentProfileDeleteConflict | null; setConflict: (c: AgentProfileDeleteConflict | null) => void; handleForceDelete: () => void; }; function ProfileDeleteDialogs({ conflict, setConflict, handleForceDelete, }: ProfileDeleteDialogsProps) { return ( <> { if (!open) setConflict(null); }} onConfirm={handleForceDelete} /> ); } type ProfileEditorBodyProps = { agent: Agent; draft: AgentProfile; savedProfile: AgentProfile; isDirty: boolean; updateDraft: (patch: Partial) => void; modelConfig: ModelConfig; permissionSettings: Record; passthroughConfig: PassthroughConfig | null; secrets: { id: string; name: string }[]; initialMcpConfig?: AgentProfileMcpConfig | null; onToastError: (error: unknown) => void; onModelConfigResolutionPendingChange: (pending: boolean) => void; }; function ProfileEditorBody({ agent, draft, savedProfile, isDirty, updateDraft, modelConfig, permissionSettings, passthroughConfig, secrets, initialMcpConfig, onToastError, onModelConfigResolutionPendingChange, }: ProfileEditorBodyProps) { return ( <> updateDraft({ cliFlags: next })} permissionSettings={permissionSettings} discoveryTargetId={agentProfileDiscoveryTarget(draft.id, "cli-flags")} /> ); } // eslint-disable-next-line max-lines-per-function function ProfileEditor({ agent, profile, modelConfig, permissionSettings, passthroughConfig, initialMcpConfig, }: ProfileEditorProps) { const { t } = useTranslation(); const { toast } = useToast(); const [modelConfigResolutionPending, setModelConfigResolutionPending] = useState(false); const settingsAgents = useAppStore((state) => state.settingsAgents.items); const syncAgentsToStore = useSyncAgentsToStore(); const { items: secrets } = useSecrets(); const { draft, setDraft, savedProfile, setSaveStatus, isDirty, hasExternalConflict, markProfileSubmitted, acceptProfileSaveResponse, discardProfileDraft, } = useProfileEditorState(profile, permissionSettings); const [utilityConflict, setUtilityConflict] = useState([]); const updateDraft = useCallback( (patch: Partial) => { setDraft((current) => { if (patch.envVars !== undefined && areEnvVarsEqual(patch.envVars, current.envVars)) { // envVars unchanged — apply the rest of the patch (if any) but skip envVars. const { envVars: _ignored, ...rest } = patch; if (Object.keys(rest).length === 0) return current; return { ...current, ...rest }; } return { ...current, ...patch }; }); }, [setDraft], ); const handleSave = useProfileSave({ agent, draft, savedProfile, setSaveStatus, markProfileSubmitted, acceptProfileSaveResponse, settingsAgents, syncAgentsToStore, toast, onUtilityConflict: setUtilityConflict, }); useSettingsSaveContributor({ id: `agent-profile:${draft.id}`, revision: JSON.stringify(draft), isDirty, canSave: Boolean(draft.name.trim()) && !modelConfigResolutionPending && !hasExternalConflict, invalidReason: hasExternalConflict ? t("agents:profileExternalChangeInvalidReason") : profileSaveInvalidReason(draft.name, modelConfigResolutionPending, t), save: () => handleSave(), discard: discardProfileDraft, }); const deleteState = useProfileDelete(agent, draft, settingsAgents, syncAgentsToStore, toast); const { handleDuplicate: handleDuplicateProfile, duplicating } = useProfileDuplicateAction({ agent, draft, modelConfigResolutionPending, translate: t, }); return (
{hasExternalConflict ? ( {t("agents:profileExternalChangeTitle")} {t("agents:profileExternalChangeDescription")} ) : null} updateDraft({ enabled: next })} onDuplicate={() => void handleDuplicateProfile()} duplicating={duplicating} /> { if (isHandledApiError(error)) return; toast({ title: t("agents:failedToSaveMcpConfig"), description: errorMessage(error), variant: "error", }); }} onModelConfigResolutionPendingChange={setModelConfigResolutionPending} /> setUtilityConflict([])} onConfirm={async () => { setUtilityConflict([]); await handleSave(true); }} />
); } type AgentProfilePageClientProps = { initialMcpConfig?: AgentProfileMcpConfig | null; }; export function AgentProfilePage({ initialMcpConfig }: AgentProfilePageClientProps) { const { t } = useTranslation(); const params = useParams(); const agentParam = Array.isArray(params.agentId) ? params.agentId[0] : params.agentId; const profileParam = Array.isArray(params.profileId) ? params.profileId[0] : params.profileId; const agentKey = decodeURIComponent(agentParam ?? ""); const profileId = profileParam ?? ""; const { agent, profile, modelConfig, permissionSettings, passthroughConfig } = useAgentProfileSettings(agentKey, profileId); if (!agent || !profile) { return (

{t("agents:profileNotFound")}

); } if (profile.kind === "dynamic" || agent.name === "dynamic") { return ; } return ( ); }