fix: drop accidental agent prompt tab changes
This commit is contained in:
@@ -65,8 +65,6 @@ type AgentConfigFormProps = {
|
||||
onSaveActionChange?: (save: (() => void) | null) => void;
|
||||
onCancelActionChange?: (cancel: (() => void) | null) => void;
|
||||
hideInlineSave?: boolean;
|
||||
/** Hide the prompt template field from the Identity section (used when it's shown in a separate Prompts tab). */
|
||||
hidePromptTemplate?: boolean;
|
||||
/** "cards" renders each section as heading + bordered card (for settings pages). Default: "inline" (border-b dividers). */
|
||||
sectionLayout?: "inline" | "cards";
|
||||
} & (
|
||||
@@ -475,7 +473,7 @@ export function AgentConfigForm(props: AgentConfigFormProps) {
|
||||
}}
|
||||
/>
|
||||
</Field>
|
||||
{isLocal && !props.hidePromptTemplate && (
|
||||
{isLocal && (
|
||||
<>
|
||||
<Field label="Prompt Template" hint={help.promptTemplate}>
|
||||
<MarkdownEditor
|
||||
|
||||
@@ -16,9 +16,7 @@ import { useBreadcrumbs } from "../context/BreadcrumbContext";
|
||||
import { queryKeys } from "../lib/queryKeys";
|
||||
import { AgentConfigForm } from "../components/AgentConfigForm";
|
||||
import { PageTabBar } from "../components/PageTabBar";
|
||||
import { adapterLabels, roleLabels, help } from "../components/agent-config-primitives";
|
||||
import { MarkdownEditor } from "../components/MarkdownEditor";
|
||||
import { assetsApi } from "../api/assets";
|
||||
import { adapterLabels, roleLabels } from "../components/agent-config-primitives";
|
||||
import { getUIAdapter, buildTranscript } from "../adapters";
|
||||
import { StatusBadge } from "../components/StatusBadge";
|
||||
import { agentStatusDot, agentStatusDotDefault } from "../lib/status-colors";
|
||||
@@ -189,10 +187,9 @@ function scrollToContainerBottom(container: ScrollContainer, behavior: ScrollBeh
|
||||
container.scrollTo({ top: container.scrollHeight, behavior });
|
||||
}
|
||||
|
||||
type AgentDetailView = "dashboard" | "prompts" | "configuration" | "skills" | "runs" | "budget";
|
||||
type AgentDetailView = "dashboard" | "configuration" | "skills" | "runs" | "budget";
|
||||
|
||||
function parseAgentDetailView(value: string | null): AgentDetailView {
|
||||
if (value === "prompts") return value;
|
||||
if (value === "configure" || value === "configuration") return "configuration";
|
||||
if (value === "skills") return value;
|
||||
if (value === "budget") return value;
|
||||
@@ -581,17 +578,15 @@ export function AgentDetail() {
|
||||
return;
|
||||
}
|
||||
const canonicalTab =
|
||||
activeView === "prompts"
|
||||
? "prompts"
|
||||
: activeView === "configuration"
|
||||
? "configuration"
|
||||
: activeView === "skills"
|
||||
? "skills"
|
||||
: activeView === "runs"
|
||||
? "runs"
|
||||
: activeView === "budget"
|
||||
? "budget"
|
||||
: "dashboard";
|
||||
activeView === "configuration"
|
||||
? "configuration"
|
||||
: activeView === "skills"
|
||||
? "skills"
|
||||
: activeView === "runs"
|
||||
? "runs"
|
||||
: activeView === "budget"
|
||||
? "budget"
|
||||
: "dashboard";
|
||||
if (routeAgentRef !== canonicalAgentRef || urlTab !== canonicalTab) {
|
||||
navigate(`/agents/${canonicalAgentRef}/${canonicalTab}`, { replace: true });
|
||||
return;
|
||||
@@ -704,8 +699,6 @@ export function AgentDetail() {
|
||||
if (urlRunId) {
|
||||
crumbs.push({ label: "Runs", href: `/agents/${canonicalAgentRef}/runs` });
|
||||
crumbs.push({ label: `Run ${urlRunId.slice(0, 8)}` });
|
||||
} else if (activeView === "prompts") {
|
||||
crumbs.push({ label: "Prompts" });
|
||||
} else if (activeView === "configuration") {
|
||||
crumbs.push({ label: "Configuration" });
|
||||
} else if (activeView === "skills") {
|
||||
@@ -741,7 +734,7 @@ export function AgentDetail() {
|
||||
return <Navigate to={`/agents/${canonicalAgentRef}/dashboard`} replace />;
|
||||
}
|
||||
const isPendingApproval = agent.status === "pending_approval";
|
||||
const showConfigActionBar = (activeView === "configuration" || activeView === "prompts") && (configDirty || configSaving);
|
||||
const showConfigActionBar = activeView === "configuration" && (configDirty || configSaving);
|
||||
|
||||
return (
|
||||
<div className={cn("space-y-6", isMobile && showConfigActionBar && "pb-24")}>
|
||||
@@ -868,9 +861,9 @@ export function AgentDetail() {
|
||||
<PageTabBar
|
||||
items={[
|
||||
{ value: "dashboard", label: "Dashboard" },
|
||||
{ value: "prompts", label: "Prompts" },
|
||||
{ value: "skills", label: "Skills" },
|
||||
{ value: "configuration", label: "Configuration" },
|
||||
{ value: "skills", label: "Skills" },
|
||||
{ value: "runs", label: "Runs" },
|
||||
{ value: "budget", label: "Budget" },
|
||||
]}
|
||||
value={activeView}
|
||||
@@ -949,17 +942,6 @@ export function AgentDetail() {
|
||||
/>
|
||||
)}
|
||||
|
||||
{activeView === "prompts" && (
|
||||
<PromptsTab
|
||||
agent={agent}
|
||||
companyId={resolvedCompanyId ?? undefined}
|
||||
onDirtyChange={setConfigDirty}
|
||||
onSaveActionChange={setSaveConfigAction}
|
||||
onCancelActionChange={setCancelConfigAction}
|
||||
onSavingChange={setConfigSaving}
|
||||
/>
|
||||
)}
|
||||
|
||||
{activeView === "configuration" && (
|
||||
<AgentConfigurePage
|
||||
agent={agent}
|
||||
@@ -1287,7 +1269,6 @@ function AgentConfigurePage({
|
||||
onSavingChange={onSavingChange}
|
||||
updatePermissions={updatePermissions}
|
||||
companyId={companyId}
|
||||
hidePromptTemplate
|
||||
/>
|
||||
<div>
|
||||
<h3 className="text-sm font-medium mb-3">API Keys</h3>
|
||||
@@ -1358,7 +1339,6 @@ function ConfigurationTab({
|
||||
onCancelActionChange,
|
||||
onSavingChange,
|
||||
updatePermissions,
|
||||
hidePromptTemplate,
|
||||
}: {
|
||||
agent: Agent;
|
||||
companyId?: string;
|
||||
@@ -1367,7 +1347,6 @@ function ConfigurationTab({
|
||||
onCancelActionChange: (cancel: (() => void) | null) => void;
|
||||
onSavingChange: (saving: boolean) => void;
|
||||
updatePermissions: { mutate: (canCreate: boolean) => void; isPending: boolean };
|
||||
hidePromptTemplate?: boolean;
|
||||
}) {
|
||||
const queryClient = useQueryClient();
|
||||
const [awaitingRefreshAfterSave, setAwaitingRefreshAfterSave] = useState(false);
|
||||
@@ -1422,7 +1401,6 @@ function ConfigurationTab({
|
||||
onSaveActionChange={onSaveActionChange}
|
||||
onCancelActionChange={onCancelActionChange}
|
||||
hideInlineSave
|
||||
hidePromptTemplate={hidePromptTemplate}
|
||||
sectionLayout="cards"
|
||||
/>
|
||||
|
||||
@@ -1449,119 +1427,6 @@ function ConfigurationTab({
|
||||
);
|
||||
}
|
||||
|
||||
/* ---- Prompts Tab ---- */
|
||||
|
||||
function PromptsTab({
|
||||
agent,
|
||||
companyId,
|
||||
onDirtyChange,
|
||||
onSaveActionChange,
|
||||
onCancelActionChange,
|
||||
onSavingChange,
|
||||
}: {
|
||||
agent: Agent;
|
||||
companyId?: string;
|
||||
onDirtyChange: (dirty: boolean) => void;
|
||||
onSaveActionChange: (save: (() => void) | null) => void;
|
||||
onCancelActionChange: (cancel: (() => void) | null) => void;
|
||||
onSavingChange: (saving: boolean) => void;
|
||||
}) {
|
||||
const queryClient = useQueryClient();
|
||||
const { selectedCompanyId } = useCompany();
|
||||
const [draft, setDraft] = useState<string | null>(null);
|
||||
const [awaitingRefresh, setAwaitingRefresh] = useState(false);
|
||||
const lastAgentRef = useRef(agent);
|
||||
|
||||
const currentValue = String(agent.adapterConfig?.promptTemplate ?? "");
|
||||
const displayValue = draft ?? currentValue;
|
||||
const isDirty = draft !== null && draft !== currentValue;
|
||||
|
||||
const isLocal =
|
||||
agent.adapterType === "claude_local" ||
|
||||
agent.adapterType === "codex_local" ||
|
||||
agent.adapterType === "opencode_local" ||
|
||||
agent.adapterType === "pi_local" ||
|
||||
agent.adapterType === "hermes_local" ||
|
||||
agent.adapterType === "cursor";
|
||||
|
||||
const updateAgent = useMutation({
|
||||
mutationFn: (data: Record<string, unknown>) => agentsApi.update(agent.id, data, companyId),
|
||||
onMutate: () => setAwaitingRefresh(true),
|
||||
onSuccess: () => {
|
||||
queryClient.invalidateQueries({ queryKey: queryKeys.agents.detail(agent.id) });
|
||||
queryClient.invalidateQueries({ queryKey: queryKeys.agents.detail(agent.urlKey) });
|
||||
},
|
||||
onError: () => setAwaitingRefresh(false),
|
||||
});
|
||||
|
||||
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);
|
||||
},
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
if (awaitingRefresh && agent !== lastAgentRef.current) {
|
||||
setAwaitingRefresh(false);
|
||||
setDraft(null);
|
||||
}
|
||||
lastAgentRef.current = agent;
|
||||
}, [agent, awaitingRefresh]);
|
||||
|
||||
const isSaving = updateAgent.isPending || awaitingRefresh;
|
||||
|
||||
useEffect(() => { onSavingChange(isSaving); }, [onSavingChange, isSaving]);
|
||||
useEffect(() => { onDirtyChange(isDirty); }, [onDirtyChange, isDirty]);
|
||||
|
||||
useEffect(() => {
|
||||
onSaveActionChange(isDirty ? () => {
|
||||
updateAgent.mutate({ adapterConfig: { promptTemplate: draft } });
|
||||
} : null);
|
||||
}, [onSaveActionChange, isDirty, draft, updateAgent]);
|
||||
|
||||
useEffect(() => {
|
||||
onCancelActionChange(isDirty ? () => setDraft(null) : null);
|
||||
}, [onCancelActionChange, isDirty]);
|
||||
|
||||
if (!isLocal) {
|
||||
return (
|
||||
<div className="max-w-3xl">
|
||||
<p className="text-sm text-muted-foreground">
|
||||
Prompt templates are only available for local adapters.
|
||||
</p>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="max-w-3xl space-y-4">
|
||||
<div>
|
||||
<h3 className="text-sm font-medium mb-3">Prompt Template</h3>
|
||||
<div className="border border-border rounded-lg p-4 space-y-3">
|
||||
<p className="text-sm text-muted-foreground">
|
||||
{help.promptTemplate}
|
||||
</p>
|
||||
<MarkdownEditor
|
||||
value={displayValue}
|
||||
onChange={(v) => setDraft(v ?? "")}
|
||||
placeholder="You are agent {{ agent.name }}. Your role is {{ agent.role }}..."
|
||||
contentClassName="min-h-[88px] text-sm font-mono"
|
||||
imageUploadHandler={async (file) => {
|
||||
const namespace = `agents/${agent.id}/prompt-template`;
|
||||
const asset = await uploadMarkdownImage.mutateAsync({ file, namespace });
|
||||
return asset.contentPath;
|
||||
}}
|
||||
/>
|
||||
<div className="rounded-md border border-amber-500/25 bg-amber-500/10 px-3 py-2 text-xs text-amber-100">
|
||||
Prompt template is replayed on every heartbeat. Keep it compact and dynamic to avoid recurring token cost and cache churn.
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function SkillsTab({ agent }: { agent: Agent }) {
|
||||
const instructionsPath =
|
||||
typeof agent.adapterConfig?.instructionsFilePath === "string" && agent.adapterConfig.instructionsFilePath.trim().length > 0
|
||||
|
||||
Reference in New Issue
Block a user