From de3efdd16b503643b6e93d827aa6189d9c0b7aba Mon Sep 17 00:00:00 2001 From: Forgotten Date: Fri, 20 Feb 2026 12:28:42 -0600 Subject: [PATCH] fix: move defaultCreateValues to separate file to fix HMR AgentConfigForm.tsx exported both React components and a plain object constant (defaultCreateValues), which broke Vite Fast Refresh. Moved the constant to agent-config-defaults.ts so the component module only exports React components. Co-Authored-By: Claude Opus 4.6 --- ui/src/components/AgentConfigForm.tsx | 229 ++++++++++++++------- ui/src/components/NewAgentDialog.tsx | 7 +- ui/src/components/OnboardingWizard.tsx | 2 +- ui/src/components/agent-config-defaults.ts | 22 ++ 4 files changed, 180 insertions(+), 80 deletions(-) create mode 100644 ui/src/components/agent-config-defaults.ts diff --git a/ui/src/components/AgentConfigForm.tsx b/ui/src/components/AgentConfigForm.tsx index 5c71e311..37d393a0 100644 --- a/ui/src/components/AgentConfigForm.tsx +++ b/ui/src/components/AgentConfigForm.tsx @@ -1,10 +1,16 @@ import { useState, useEffect, useRef, useMemo } from "react"; import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query"; import { AGENT_ADAPTER_TYPES } from "@paperclip/shared"; -import type { Agent, CompanySecret, EnvBinding } from "@paperclip/shared"; +import type { + Agent, + AdapterEnvironmentTestResult, + CompanySecret, + EnvBinding, +} from "@paperclip/shared"; import type { AdapterModel } from "../api/agents"; import { agentsApi } from "../api/agents"; import { secretsApi } from "../api/secrets"; +import { assetsApi } from "../api/assets"; import { Popover, PopoverContent, @@ -20,15 +26,14 @@ import { ToggleField, ToggleWithNumber, CollapsibleSection, - AutoExpandTextarea, DraftInput, - DraftTextarea, DraftNumberInput, help, adapterLabels, } from "./agent-config-primitives"; import { getUIAdapter } from "../adapters"; import { ClaudeLocalAdvancedFields } from "../adapters/claude-local/config-fields"; +import { MarkdownEditor } from "./MarkdownEditor"; /* ---- Create mode values ---- */ @@ -37,27 +42,6 @@ import { ClaudeLocalAdvancedFields } from "../adapters/claude-local/config-field export type { CreateConfigValues } from "@paperclip/adapter-utils"; import type { CreateConfigValues } from "@paperclip/adapter-utils"; -export const defaultCreateValues: CreateConfigValues = { - adapterType: "claude_local", - cwd: "", - promptTemplate: "", - model: "", - thinkingEffort: "", - dangerouslySkipPermissions: false, - search: false, - dangerouslyBypassSandbox: false, - command: "", - args: "", - extraArgs: "", - envVars: "", - envBindings: {}, - url: "", - bootstrapPrompt: "", - maxTurnsPerRun: 80, - heartbeatEnabled: false, - intervalSec: 300, -}; - /* ---- Props ---- */ type AgentConfigFormProps = { @@ -174,6 +158,13 @@ export function AgentConfigForm(props: AgentConfigFormProps) { }, }); + const uploadMarkdownImage = useMutation({ + mutationFn: async ({ file, namespace }: { file: File; namespace: string }) => { + if (!selectedCompanyId) throw new Error("Select a company to upload images"); + return assetsApi.uploadImage(selectedCompanyId, file, namespace); + }, + }); + // ---- Edit mode: overlay for dirty tracking ---- const [overlay, setOverlay] = useState(emptyOverlay); const agentRef = useRef(null); @@ -293,6 +284,25 @@ export function AgentConfigForm(props: AgentConfigFormProps) { ? (patch: Partial) => props.onChange(patch) : null; + function buildAdapterConfigForTest(): Record { + if (isCreate) { + return uiAdapter.buildAdapterConfig(val!); + } + const base = config as Record; + return { ...base, ...overlay.adapterConfig }; + } + + const testEnvironment = useMutation({ + mutationFn: async () => { + if (!selectedCompanyId) { + throw new Error("Select a company to test adapter environment"); + } + return agentsApi.testEnvironment(selectedCompanyId, adapterType, { + adapterConfig: buildAdapterConfigForTest(), + }); + }, + }); + // Current model for display const currentModelId = isCreate ? val!.model @@ -356,12 +366,18 @@ export function AgentConfigForm(props: AgentConfigFormProps) { /> - mark("identity", "capabilities", v || null)} - immediate + onChange={(v) => mark("identity", "capabilities", v || null)} placeholder="Describe what this agent can do..." - minRows={2} + contentClassName="min-h-[120px]" + imageUploadHandler={async (file) => { + const asset = await uploadMarkdownImage.mutateAsync({ + file, + namespace: `agents/${props.agent.id}/capabilities`, + }); + return asset.contentPath; + }} /> @@ -390,10 +406,34 @@ export function AgentConfigForm(props: AgentConfigFormProps) { {/* ---- Adapter Configuration ---- */}
-
- Adapter Configuration +
+ + Adapter Configuration + +
+ {testEnvironment.error && ( +
+ {testEnvironment.error instanceof Error + ? testEnvironment.error.message + : "Environment test failed"} +
+ )} + + {testEnvironment.data && ( + + )} + {/* Working directory */} {isLocal && ( @@ -454,28 +494,31 @@ export function AgentConfigForm(props: AgentConfigFormProps) { {/* Prompt template */} {isLocal && ( - {isCreate ? ( - set!({ promptTemplate: v })} - minRows={4} - /> - ) : ( - - mark("adapterConfig", "promptTemplate", v || undefined) - } - immediate - placeholder="You are agent {{ agent.name }}. Your role is {{ agent.role }}..." - minRows={4} - /> - )} + + isCreate + ? set!({ promptTemplate: v }) + : mark("adapterConfig", "promptTemplate", v || undefined) + } + placeholder="You are agent {{ agent.name }}. Your role is {{ agent.role }}..." + contentClassName="min-h-[180px]" + imageUploadHandler={async (file) => { + const namespace = isCreate + ? "agents/drafts/prompt-template" + : `agents/${props.agent.id}/prompt-template`; + const asset = await uploadMarkdownImage.mutateAsync({ file, namespace }); + return asset.contentPath; + }} + /> )} @@ -539,28 +582,31 @@ export function AgentConfigForm(props: AgentConfigFormProps) {

)} - {isCreate ? ( - set!({ bootstrapPrompt: v })} - minRows={2} - /> - ) : ( - - mark("adapterConfig", "bootstrapPromptTemplate", v || undefined) - } - immediate - placeholder="Optional initial setup prompt for the first run" - minRows={2} - /> - )} + + isCreate + ? set!({ bootstrapPrompt: v }) + : mark("adapterConfig", "bootstrapPromptTemplate", v || undefined) + } + placeholder="Optional initial setup prompt for the first run" + contentClassName="min-h-[120px]" + imageUploadHandler={async (file) => { + const namespace = isCreate + ? "agents/drafts/bootstrap-prompt" + : `agents/${props.agent.id}/bootstrap-prompt`; + const asset = await uploadMarkdownImage.mutateAsync({ file, namespace }); + return asset.contentPath; + }} + /> {adapterType === "claude_local" && ( @@ -720,6 +766,41 @@ export function AgentConfigForm(props: AgentConfigFormProps) { ); } +function AdapterEnvironmentResult({ result }: { result: AdapterEnvironmentTestResult }) { + const statusLabel = + result.status === "pass" ? "Passed" : result.status === "warn" ? "Warnings" : "Failed"; + const statusClass = + result.status === "pass" + ? "text-green-300 border-green-500/40 bg-green-500/10" + : result.status === "warn" + ? "text-amber-300 border-amber-500/40 bg-amber-500/10" + : "text-red-300 border-red-500/40 bg-red-500/10"; + + return ( +
+
+ {statusLabel} + + {new Date(result.testedAt).toLocaleTimeString()} + +
+
+ {result.checks.map((check, idx) => ( +
+ + {check.level} + + ยท + {check.message} + {check.detail && ({check.detail})} + {check.hint && Hint: {check.hint}} +
+ ))} +
+
+ ); +} + /* ---- Internal sub-components ---- */ function AdapterTypeDropdown({ diff --git a/ui/src/components/NewAgentDialog.tsx b/ui/src/components/NewAgentDialog.tsx index e1f1157a..caae770e 100644 --- a/ui/src/components/NewAgentDialog.tsx +++ b/ui/src/components/NewAgentDialog.tsx @@ -24,11 +24,8 @@ import { } from "lucide-react"; import { cn } from "../lib/utils"; import { roleLabels } from "./agent-config-primitives"; -import { - AgentConfigForm, - defaultCreateValues, - type CreateConfigValues, -} from "./AgentConfigForm"; +import { AgentConfigForm, type CreateConfigValues } from "./AgentConfigForm"; +import { defaultCreateValues } from "./agent-config-defaults"; import { getUIAdapter } from "../adapters"; export function NewAgentDialog() { diff --git a/ui/src/components/OnboardingWizard.tsx b/ui/src/components/OnboardingWizard.tsx index 56059919..bd02396d 100644 --- a/ui/src/components/OnboardingWizard.tsx +++ b/ui/src/components/OnboardingWizard.tsx @@ -17,7 +17,7 @@ import { import { Button } from "@/components/ui/button"; import { cn } from "../lib/utils"; import { getUIAdapter } from "../adapters"; -import { defaultCreateValues } from "./AgentConfigForm"; +import { defaultCreateValues } from "./agent-config-defaults"; import { Building2, Bot, diff --git a/ui/src/components/agent-config-defaults.ts b/ui/src/components/agent-config-defaults.ts new file mode 100644 index 00000000..f149e95d --- /dev/null +++ b/ui/src/components/agent-config-defaults.ts @@ -0,0 +1,22 @@ +import type { CreateConfigValues } from "@paperclip/adapter-utils"; + +export const defaultCreateValues: CreateConfigValues = { + adapterType: "claude_local", + cwd: "", + promptTemplate: "", + model: "", + thinkingEffort: "", + dangerouslySkipPermissions: false, + search: false, + dangerouslyBypassSandbox: false, + command: "", + args: "", + extraArgs: "", + envVars: "", + envBindings: {}, + url: "", + bootstrapPrompt: "", + maxTurnsPerRun: 80, + heartbeatEnabled: false, + intervalSec: 300, +};