Default advanced toggle open when instructions mode is External
When the agent instructions tab is in "External" mode, the advanced collapsible section now defaults to open so users don't have to manually expand it to see the mode and path settings. Co-Authored-By: Paperclip <noreply@paperclip.ing> Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
3689992965
commit
b0524412c4
1 changed files with 99 additions and 31 deletions
|
|
@ -72,6 +72,7 @@ import { RunTranscriptView, type TranscriptMode } from "../components/transcript
|
||||||
import {
|
import {
|
||||||
isUuidLike,
|
isUuidLike,
|
||||||
type Agent,
|
type Agent,
|
||||||
|
type AgentSkillEntry,
|
||||||
type AgentSkillSnapshot,
|
type AgentSkillSnapshot,
|
||||||
type BudgetPolicySummary,
|
type BudgetPolicySummary,
|
||||||
type HeartbeatRun,
|
type HeartbeatRun,
|
||||||
|
|
@ -82,7 +83,11 @@ import {
|
||||||
} from "@paperclipai/shared";
|
} from "@paperclipai/shared";
|
||||||
import { redactHomePathUserSegments, redactHomePathUserSegmentsInValue } from "@paperclipai/adapter-utils";
|
import { redactHomePathUserSegments, redactHomePathUserSegmentsInValue } from "@paperclipai/adapter-utils";
|
||||||
import { agentRouteRef } from "../lib/utils";
|
import { agentRouteRef } from "../lib/utils";
|
||||||
import { applyAgentSkillSnapshot, arraysEqual } from "../lib/agent-skills-state";
|
import {
|
||||||
|
applyAgentSkillSnapshot,
|
||||||
|
arraysEqual,
|
||||||
|
isReadOnlyUnmanagedSkillEntry,
|
||||||
|
} from "../lib/agent-skills-state";
|
||||||
|
|
||||||
const runStatusIcons: Record<string, { icon: typeof CheckCircle2; color: string }> = {
|
const runStatusIcons: Record<string, { icon: typeof CheckCircle2; color: string }> = {
|
||||||
succeeded: { icon: CheckCircle2, color: "text-green-600 dark:text-green-400" },
|
succeeded: { icon: CheckCircle2, color: "text-green-600 dark:text-green-400" },
|
||||||
|
|
@ -1798,7 +1803,7 @@ function PromptsTab({
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<Collapsible>
|
<Collapsible defaultOpen={currentMode === "external"}>
|
||||||
<CollapsibleTrigger className="flex items-center gap-1 text-xs text-muted-foreground hover:text-foreground transition-colors group">
|
<CollapsibleTrigger className="flex items-center gap-1 text-xs text-muted-foreground hover:text-foreground transition-colors group">
|
||||||
<ChevronRight className="h-3 w-3 transition-transform group-data-[state=open]:rotate-90" />
|
<ChevronRight className="h-3 w-3 transition-transform group-data-[state=open]:rotate-90" />
|
||||||
Advanced
|
Advanced
|
||||||
|
|
@ -2159,8 +2164,11 @@ function AgentSkillsTab({
|
||||||
name: string;
|
name: string;
|
||||||
description: string | null;
|
description: string | null;
|
||||||
detail: string | null;
|
detail: string | null;
|
||||||
|
locationLabel: string | null;
|
||||||
|
originLabel: string | null;
|
||||||
linkTo: string | null;
|
linkTo: string | null;
|
||||||
adapterEntry: AgentSkillSnapshot["entries"][number] | null;
|
readOnly: boolean;
|
||||||
|
adapterEntry: AgentSkillEntry | null;
|
||||||
};
|
};
|
||||||
|
|
||||||
const queryClient = useQueryClient();
|
const queryClient = useQueryClient();
|
||||||
|
|
@ -2242,6 +2250,10 @@ function AgentSkillsTab({
|
||||||
() => new Map((companySkills ?? []).map((skill) => [skill.key, skill])),
|
() => new Map((companySkills ?? []).map((skill) => [skill.key, skill])),
|
||||||
[companySkills],
|
[companySkills],
|
||||||
);
|
);
|
||||||
|
const companySkillKeys = useMemo(
|
||||||
|
() => new Set((companySkills ?? []).map((skill) => skill.key)),
|
||||||
|
[companySkills],
|
||||||
|
);
|
||||||
const adapterEntryByKey = useMemo(
|
const adapterEntryByKey = useMemo(
|
||||||
() => new Map((skillSnapshot?.entries ?? []).map((entry) => [entry.key, entry])),
|
() => new Map((skillSnapshot?.entries ?? []).map((entry) => [entry.key, entry])),
|
||||||
[skillSnapshot],
|
[skillSnapshot],
|
||||||
|
|
@ -2256,7 +2268,10 @@ function AgentSkillsTab({
|
||||||
name: skill.name,
|
name: skill.name,
|
||||||
description: skill.description,
|
description: skill.description,
|
||||||
detail: adapterEntryByKey.get(skill.key)?.detail ?? null,
|
detail: adapterEntryByKey.get(skill.key)?.detail ?? null,
|
||||||
|
locationLabel: adapterEntryByKey.get(skill.key)?.locationLabel ?? null,
|
||||||
|
originLabel: adapterEntryByKey.get(skill.key)?.originLabel ?? null,
|
||||||
linkTo: `/skills/${skill.id}`,
|
linkTo: `/skills/${skill.id}`,
|
||||||
|
readOnly: false,
|
||||||
adapterEntry: adapterEntryByKey.get(skill.key) ?? null,
|
adapterEntry: adapterEntryByKey.get(skill.key) ?? null,
|
||||||
})),
|
})),
|
||||||
[adapterEntryByKey, companySkills],
|
[adapterEntryByKey, companySkills],
|
||||||
|
|
@ -2273,12 +2288,33 @@ function AgentSkillsTab({
|
||||||
name: companySkill?.name ?? entry.key,
|
name: companySkill?.name ?? entry.key,
|
||||||
description: companySkill?.description ?? null,
|
description: companySkill?.description ?? null,
|
||||||
detail: entry.detail ?? null,
|
detail: entry.detail ?? null,
|
||||||
|
locationLabel: entry.locationLabel ?? null,
|
||||||
|
originLabel: entry.originLabel ?? null,
|
||||||
linkTo: companySkill ? `/skills/${companySkill.id}` : null,
|
linkTo: companySkill ? `/skills/${companySkill.id}` : null,
|
||||||
|
readOnly: false,
|
||||||
adapterEntry: entry,
|
adapterEntry: entry,
|
||||||
};
|
};
|
||||||
}),
|
}),
|
||||||
[companySkillByKey, skillSnapshot],
|
[companySkillByKey, skillSnapshot],
|
||||||
);
|
);
|
||||||
|
const unmanagedSkillRows = useMemo<SkillRow[]>(
|
||||||
|
() =>
|
||||||
|
(skillSnapshot?.entries ?? [])
|
||||||
|
.filter((entry) => isReadOnlyUnmanagedSkillEntry(entry, companySkillKeys))
|
||||||
|
.map((entry) => ({
|
||||||
|
id: `external:${entry.key}`,
|
||||||
|
key: entry.key,
|
||||||
|
name: entry.runtimeName ?? entry.key,
|
||||||
|
description: null,
|
||||||
|
detail: entry.detail ?? null,
|
||||||
|
locationLabel: entry.locationLabel ?? null,
|
||||||
|
originLabel: entry.originLabel ?? null,
|
||||||
|
linkTo: null,
|
||||||
|
readOnly: true,
|
||||||
|
adapterEntry: entry,
|
||||||
|
})),
|
||||||
|
[companySkillKeys, skillSnapshot],
|
||||||
|
);
|
||||||
const desiredOnlyMissingSkills = useMemo(
|
const desiredOnlyMissingSkills = useMemo(
|
||||||
() => skillDraft.filter((key) => !companySkillByKey.has(key)),
|
() => skillDraft.filter((key) => !companySkillByKey.has(key)),
|
||||||
[companySkillByKey, skillDraft],
|
[companySkillByKey, skillDraft],
|
||||||
|
|
@ -2348,46 +2384,11 @@ function AgentSkillsTab({
|
||||||
const renderSkillRow = (skill: SkillRow) => {
|
const renderSkillRow = (skill: SkillRow) => {
|
||||||
const adapterEntry = skill.adapterEntry ?? adapterEntryByKey.get(skill.key);
|
const adapterEntry = skill.adapterEntry ?? adapterEntryByKey.get(skill.key);
|
||||||
const required = Boolean(adapterEntry?.required);
|
const required = Boolean(adapterEntry?.required);
|
||||||
const checked = required || skillDraft.includes(skill.key);
|
const rowClassName = cn(
|
||||||
const disabled = required || skillSnapshot?.mode === "unsupported";
|
"flex items-start gap-3 border-b border-border px-3 py-3 text-sm last:border-b-0",
|
||||||
const checkbox = (
|
skill.readOnly ? "bg-muted/20" : "hover:bg-accent/20",
|
||||||
<input
|
|
||||||
type="checkbox"
|
|
||||||
checked={checked}
|
|
||||||
disabled={disabled}
|
|
||||||
onChange={(event) => {
|
|
||||||
const next = event.target.checked
|
|
||||||
? Array.from(new Set([...skillDraft, skill.key]))
|
|
||||||
: skillDraft.filter((value) => value !== skill.key);
|
|
||||||
setSkillDraft(next);
|
|
||||||
}}
|
|
||||||
className="mt-0.5 disabled:cursor-not-allowed disabled:opacity-60"
|
|
||||||
/>
|
|
||||||
);
|
);
|
||||||
return (
|
const body = (
|
||||||
<label
|
|
||||||
key={skill.id}
|
|
||||||
className="flex items-start gap-3 border-b border-border px-3 py-3 text-sm last:border-b-0 hover:bg-accent/20"
|
|
||||||
>
|
|
||||||
{required && adapterEntry?.requiredReason ? (
|
|
||||||
<Tooltip>
|
|
||||||
<TooltipTrigger asChild>
|
|
||||||
<span>{checkbox}</span>
|
|
||||||
</TooltipTrigger>
|
|
||||||
<TooltipContent side="top">{adapterEntry.requiredReason}</TooltipContent>
|
|
||||||
</Tooltip>
|
|
||||||
) : skillSnapshot?.mode === "unsupported" ? (
|
|
||||||
<Tooltip>
|
|
||||||
<TooltipTrigger asChild>
|
|
||||||
<span>{checkbox}</span>
|
|
||||||
</TooltipTrigger>
|
|
||||||
<TooltipContent side="top">
|
|
||||||
{unsupportedSkillMessage ?? "Manage skills in the adapter directly."}
|
|
||||||
</TooltipContent>
|
|
||||||
</Tooltip>
|
|
||||||
) : (
|
|
||||||
checkbox
|
|
||||||
)}
|
|
||||||
<div className="min-w-0 flex-1">
|
<div className="min-w-0 flex-1">
|
||||||
<div className="flex items-center justify-between gap-3">
|
<div className="flex items-center justify-between gap-3">
|
||||||
<div className="min-w-0">
|
<div className="min-w-0">
|
||||||
|
|
@ -2407,15 +2408,71 @@ function AgentSkillsTab({
|
||||||
{skill.description}
|
{skill.description}
|
||||||
</MarkdownBody>
|
</MarkdownBody>
|
||||||
)}
|
)}
|
||||||
|
{skill.readOnly && skill.originLabel && (
|
||||||
|
<p className="mt-1 text-xs text-muted-foreground">{skill.originLabel}</p>
|
||||||
|
)}
|
||||||
|
{skill.readOnly && skill.locationLabel && (
|
||||||
|
<p className="mt-1 text-xs text-muted-foreground">Location: {skill.locationLabel}</p>
|
||||||
|
)}
|
||||||
{skill.detail && (
|
{skill.detail && (
|
||||||
<p className="mt-1 text-xs text-muted-foreground">{skill.detail}</p>
|
<p className="mt-1 text-xs text-muted-foreground">{skill.detail}</p>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
);
|
||||||
|
|
||||||
|
if (skill.readOnly) {
|
||||||
|
return (
|
||||||
|
<div key={skill.id} className={rowClassName}>
|
||||||
|
<span className="mt-1 h-2 w-2 rounded-full bg-muted-foreground/40" />
|
||||||
|
{body}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const checked = required || skillDraft.includes(skill.key);
|
||||||
|
const disabled = required || skillSnapshot?.mode === "unsupported";
|
||||||
|
const checkbox = (
|
||||||
|
<input
|
||||||
|
type="checkbox"
|
||||||
|
checked={checked}
|
||||||
|
disabled={disabled}
|
||||||
|
onChange={(event) => {
|
||||||
|
const next = event.target.checked
|
||||||
|
? Array.from(new Set([...skillDraft, skill.key]))
|
||||||
|
: skillDraft.filter((value) => value !== skill.key);
|
||||||
|
setSkillDraft(next);
|
||||||
|
}}
|
||||||
|
className="mt-0.5 disabled:cursor-not-allowed disabled:opacity-60"
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<label key={skill.id} className={rowClassName}>
|
||||||
|
{required && adapterEntry?.requiredReason ? (
|
||||||
|
<Tooltip>
|
||||||
|
<TooltipTrigger asChild>
|
||||||
|
<span>{checkbox}</span>
|
||||||
|
</TooltipTrigger>
|
||||||
|
<TooltipContent side="top">{adapterEntry.requiredReason}</TooltipContent>
|
||||||
|
</Tooltip>
|
||||||
|
) : skillSnapshot?.mode === "unsupported" ? (
|
||||||
|
<Tooltip>
|
||||||
|
<TooltipTrigger asChild>
|
||||||
|
<span>{checkbox}</span>
|
||||||
|
</TooltipTrigger>
|
||||||
|
<TooltipContent side="top">
|
||||||
|
{unsupportedSkillMessage ?? "Manage skills in the adapter directly."}
|
||||||
|
</TooltipContent>
|
||||||
|
</Tooltip>
|
||||||
|
) : (
|
||||||
|
checkbox
|
||||||
|
)}
|
||||||
|
{body}
|
||||||
</label>
|
</label>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
if (optionalSkillRows.length === 0 && requiredSkillRows.length === 0) {
|
if (optionalSkillRows.length === 0 && requiredSkillRows.length === 0 && unmanagedSkillRows.length === 0) {
|
||||||
return (
|
return (
|
||||||
<section className="border-y border-border">
|
<section className="border-y border-border">
|
||||||
<div className="px-3 py-6 text-sm text-muted-foreground">
|
<div className="px-3 py-6 text-sm text-muted-foreground">
|
||||||
|
|
@ -2443,6 +2500,17 @@ function AgentSkillsTab({
|
||||||
{requiredSkillRows.map(renderSkillRow)}
|
{requiredSkillRows.map(renderSkillRow)}
|
||||||
</section>
|
</section>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
{unmanagedSkillRows.length > 0 && (
|
||||||
|
<section className="border-y border-border">
|
||||||
|
<div className="border-b border-border bg-muted/40 px-3 py-2">
|
||||||
|
<span className="text-xs font-medium text-muted-foreground">
|
||||||
|
User-installed skills, not managed by Paperclip
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
{unmanagedSkillRows.map(renderSkillRow)}
|
||||||
|
</section>
|
||||||
|
)}
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
})()}
|
})()}
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue