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 <noreply@anthropic.com>
This commit is contained in:
@@ -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<Overlay>(emptyOverlay);
|
||||
const agentRef = useRef<Agent | null>(null);
|
||||
@@ -293,6 +284,25 @@ export function AgentConfigForm(props: AgentConfigFormProps) {
|
||||
? (patch: Partial<CreateConfigValues>) => props.onChange(patch)
|
||||
: null;
|
||||
|
||||
function buildAdapterConfigForTest(): Record<string, unknown> {
|
||||
if (isCreate) {
|
||||
return uiAdapter.buildAdapterConfig(val!);
|
||||
}
|
||||
const base = config as Record<string, unknown>;
|
||||
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) {
|
||||
/>
|
||||
</Field>
|
||||
<Field label="Capabilities" hint={help.capabilities}>
|
||||
<DraftTextarea
|
||||
<MarkdownEditor
|
||||
value={eff("identity", "capabilities", props.agent.capabilities ?? "")}
|
||||
onCommit={(v) => 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;
|
||||
}}
|
||||
/>
|
||||
</Field>
|
||||
</div>
|
||||
@@ -390,10 +406,34 @@ export function AgentConfigForm(props: AgentConfigFormProps) {
|
||||
|
||||
{/* ---- Adapter Configuration ---- */}
|
||||
<div className={cn(isCreate ? "border-t border-border" : "border-b border-border")}>
|
||||
<div className="px-4 py-2 text-xs font-medium text-muted-foreground">
|
||||
Adapter Configuration
|
||||
<div className="px-4 py-2 flex items-center justify-between gap-2">
|
||||
<span className="text-xs font-medium text-muted-foreground">
|
||||
Adapter Configuration
|
||||
</span>
|
||||
<Button
|
||||
type="button"
|
||||
variant="outline"
|
||||
size="sm"
|
||||
className="h-7 px-2.5 text-xs"
|
||||
onClick={() => testEnvironment.mutate()}
|
||||
disabled={testEnvironment.isPending || !selectedCompanyId}
|
||||
>
|
||||
{testEnvironment.isPending ? "Testing..." : "Test environment"}
|
||||
</Button>
|
||||
</div>
|
||||
<div className="px-4 pb-3 space-y-3">
|
||||
{testEnvironment.error && (
|
||||
<div className="rounded-md border border-destructive/30 bg-destructive/10 px-3 py-2 text-xs text-destructive">
|
||||
{testEnvironment.error instanceof Error
|
||||
? testEnvironment.error.message
|
||||
: "Environment test failed"}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{testEnvironment.data && (
|
||||
<AdapterEnvironmentResult result={testEnvironment.data} />
|
||||
)}
|
||||
|
||||
{/* Working directory */}
|
||||
{isLocal && (
|
||||
<Field label="Working directory" hint={help.cwd}>
|
||||
@@ -454,28 +494,31 @@ export function AgentConfigForm(props: AgentConfigFormProps) {
|
||||
{/* Prompt template */}
|
||||
{isLocal && (
|
||||
<Field label="Prompt template" hint={help.promptTemplate}>
|
||||
{isCreate ? (
|
||||
<AutoExpandTextarea
|
||||
placeholder="You are agent {{ agent.name }}. Your role is {{ agent.role }}..."
|
||||
value={val!.promptTemplate}
|
||||
onChange={(v) => set!({ promptTemplate: v })}
|
||||
minRows={4}
|
||||
/>
|
||||
) : (
|
||||
<DraftTextarea
|
||||
value={eff(
|
||||
"adapterConfig",
|
||||
"promptTemplate",
|
||||
String(config.promptTemplate ?? ""),
|
||||
)}
|
||||
onCommit={(v) =>
|
||||
mark("adapterConfig", "promptTemplate", v || undefined)
|
||||
}
|
||||
immediate
|
||||
placeholder="You are agent {{ agent.name }}. Your role is {{ agent.role }}..."
|
||||
minRows={4}
|
||||
/>
|
||||
)}
|
||||
<MarkdownEditor
|
||||
value={
|
||||
isCreate
|
||||
? val!.promptTemplate
|
||||
: eff(
|
||||
"adapterConfig",
|
||||
"promptTemplate",
|
||||
String(config.promptTemplate ?? ""),
|
||||
)
|
||||
}
|
||||
onChange={(v) =>
|
||||
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;
|
||||
}}
|
||||
/>
|
||||
</Field>
|
||||
)}
|
||||
|
||||
@@ -539,28 +582,31 @@ export function AgentConfigForm(props: AgentConfigFormProps) {
|
||||
</p>
|
||||
)}
|
||||
<Field label="Bootstrap prompt (first run)" hint={help.bootstrapPrompt}>
|
||||
{isCreate ? (
|
||||
<AutoExpandTextarea
|
||||
placeholder="Optional initial setup prompt for the first run"
|
||||
value={val!.bootstrapPrompt}
|
||||
onChange={(v) => set!({ bootstrapPrompt: v })}
|
||||
minRows={2}
|
||||
/>
|
||||
) : (
|
||||
<DraftTextarea
|
||||
value={eff(
|
||||
"adapterConfig",
|
||||
"bootstrapPromptTemplate",
|
||||
String(config.bootstrapPromptTemplate ?? ""),
|
||||
)}
|
||||
onCommit={(v) =>
|
||||
mark("adapterConfig", "bootstrapPromptTemplate", v || undefined)
|
||||
}
|
||||
immediate
|
||||
placeholder="Optional initial setup prompt for the first run"
|
||||
minRows={2}
|
||||
/>
|
||||
)}
|
||||
<MarkdownEditor
|
||||
value={
|
||||
isCreate
|
||||
? val!.bootstrapPrompt
|
||||
: eff(
|
||||
"adapterConfig",
|
||||
"bootstrapPromptTemplate",
|
||||
String(config.bootstrapPromptTemplate ?? ""),
|
||||
)
|
||||
}
|
||||
onChange={(v) =>
|
||||
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;
|
||||
}}
|
||||
/>
|
||||
</Field>
|
||||
{adapterType === "claude_local" && (
|
||||
<ClaudeLocalAdvancedFields {...adapterFieldProps} />
|
||||
@@ -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 (
|
||||
<div className={`rounded-md border px-3 py-2 text-xs ${statusClass}`}>
|
||||
<div className="flex items-center justify-between gap-2">
|
||||
<span className="font-medium">{statusLabel}</span>
|
||||
<span className="text-[11px] opacity-80">
|
||||
{new Date(result.testedAt).toLocaleTimeString()}
|
||||
</span>
|
||||
</div>
|
||||
<div className="mt-2 space-y-1.5">
|
||||
{result.checks.map((check, idx) => (
|
||||
<div key={`${check.code}-${idx}`} className="text-[11px] leading-relaxed">
|
||||
<span className="font-medium uppercase tracking-wide opacity-80">
|
||||
{check.level}
|
||||
</span>
|
||||
<span className="mx-1 opacity-60">·</span>
|
||||
<span>{check.message}</span>
|
||||
{check.detail && <span className="opacity-75"> ({check.detail})</span>}
|
||||
{check.hint && <span className="opacity-90"> Hint: {check.hint}</span>}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
/* ---- Internal sub-components ---- */
|
||||
|
||||
function AdapterTypeDropdown({
|
||||
|
||||
@@ -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() {
|
||||
|
||||
@@ -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,
|
||||
|
||||
22
ui/src/components/agent-config-defaults.ts
Normal file
22
ui/src/components/agent-config-defaults.ts
Normal file
@@ -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,
|
||||
};
|
||||
Reference in New Issue
Block a user