mirror of
https://github.com/block/buzz.git
synced 2026-08-18 06:50:31 +02:00
feat(agents): add opt-in save-as-persona-template to create dialog
Adds an opt-in 'Save as persona template' toggle (off by default) to the end of the create-agent flow. On submit the persona-less agent is created first (unchanged, never sets personaId); if the toggle is on, a persona template is then minted from the same field values via createPersonaMutation. The template mint is non-fatal and byproduct-ordered: the agent is the primary goal, so a failed template surfaces as a toast and never blocks or rolls back the just-created agent. No backend or IPC change — both mutations already exist. The toggle is a self-contained block placed after the env-vars editor so a later avatar-forward visual reskin can relocate it without touching the submit behavior. Co-authored-by: Taylor Ho <taylorkmho@gmail.com> Signed-off-by: Taylor Ho <taylorkmho@gmail.com>
This commit is contained in:
co-authored by
Taylor Ho
parent
c9debf3620
commit
65e41bbc48
@@ -1,11 +1,13 @@
|
||||
import { AlertTriangle, ChevronDown } from "lucide-react";
|
||||
import * as React from "react";
|
||||
import { toast } from "sonner";
|
||||
|
||||
import {
|
||||
useAcpRuntimesQuery,
|
||||
useAvailableAcpRuntimes,
|
||||
useBackendProvidersQuery,
|
||||
useCreateManagedAgentMutation,
|
||||
useCreatePersonaMutation,
|
||||
useManagedAgentPrereqsQuery,
|
||||
} from "@/features/agents/hooks";
|
||||
import { probeBackendProvider } from "@/shared/api/tauri";
|
||||
@@ -13,8 +15,10 @@ import type {
|
||||
BackendProviderProbeResult,
|
||||
CreateManagedAgentInput,
|
||||
CreateManagedAgentResponse,
|
||||
CreatePersonaInput,
|
||||
RespondToMode,
|
||||
} from "@/shared/api/types";
|
||||
import { cn } from "@/shared/lib/cn";
|
||||
import { Button } from "@/shared/ui/button";
|
||||
import {
|
||||
Dialog,
|
||||
@@ -52,6 +56,7 @@ export function CreateAgentDialog({
|
||||
onOpenChange: (open: boolean) => void;
|
||||
}) {
|
||||
const createMutation = useCreateManagedAgentMutation();
|
||||
const createPersonaMutation = useCreatePersonaMutation();
|
||||
const providersQuery = useAvailableAcpRuntimes({ enabled: open });
|
||||
const allProvidersQuery = useAcpRuntimesQuery({ enabled: open });
|
||||
const backendProvidersQuery = useBackendProvidersQuery({ enabled: open });
|
||||
@@ -68,6 +73,7 @@ export function CreateAgentDialog({
|
||||
const [relayUrl, setRelayUrl] = React.useState("");
|
||||
const [spawnAfterCreate, setSpawnAfterCreate] = React.useState(true);
|
||||
const [startOnAppLaunch, setStartOnAppLaunch] = React.useState(true);
|
||||
const [saveAsTemplate, setSaveAsTemplate] = React.useState(false);
|
||||
const [turnTimeoutSeconds, setTurnTimeoutSeconds] = React.useState("320");
|
||||
const [parallelism, setParallelism] = React.useState("24");
|
||||
const [systemPrompt, setSystemPrompt] = React.useState("");
|
||||
@@ -422,6 +428,37 @@ export function CreateAgentDialog({
|
||||
};
|
||||
|
||||
const created = await createMutation.mutateAsync(input);
|
||||
|
||||
// Opt-in: also persist this setup as a reusable persona template. The
|
||||
// agent is the primary goal; the template is a byproduct, so a failed
|
||||
// template mint must NOT fail or roll back the just-created agent — it
|
||||
// surfaces as a non-fatal toast. "Persona template" is the UI name for a
|
||||
// backend `persona` (kind:30175); this builds a `CreatePersonaInput`.
|
||||
if (saveAsTemplate) {
|
||||
const personaInput: CreatePersonaInput = {
|
||||
displayName: name.trim(),
|
||||
systemPrompt: systemPrompt.trim(),
|
||||
// Carry the selected ACP runtime when one is chosen; "custom"/manual
|
||||
// setups leave it unset and fall back to the dialog default later.
|
||||
runtime:
|
||||
selectedRuntimeId !== "custom" ? selectedRuntimeId : undefined,
|
||||
model: useMesh ? meshModelId.trim() || undefined : undefined,
|
||||
envVars,
|
||||
};
|
||||
try {
|
||||
await createPersonaMutation.mutateAsync(personaInput);
|
||||
toast.success(
|
||||
`Saved ${personaInput.displayName} as a persona template.`,
|
||||
);
|
||||
} catch (error) {
|
||||
toast.error(
|
||||
error instanceof Error
|
||||
? `Agent created, but saving the persona template failed: ${error.message}`
|
||||
: "Agent created, but saving the persona template failed.",
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
handleOpenChange(false);
|
||||
onCreated(created);
|
||||
} catch {
|
||||
@@ -650,6 +687,33 @@ export function CreateAgentDialog({
|
||||
value={envVars}
|
||||
/>
|
||||
|
||||
{/*
|
||||
Opt-in: save this setup as a reusable persona template. Off by
|
||||
default — the template is a byproduct of creating the agent, not
|
||||
the goal. Kept as a self-contained toggle block so a later visual
|
||||
reskin can relocate it without untangling the submit behavior.
|
||||
*/}
|
||||
<button
|
||||
aria-pressed={saveAsTemplate}
|
||||
className={cn(
|
||||
"w-full rounded-2xl border px-4 py-3 text-left transition-colors",
|
||||
saveAsTemplate
|
||||
? "border-primary bg-primary/10"
|
||||
: "border-border/70 bg-background/70",
|
||||
)}
|
||||
data-testid="create-agent-save-as-template"
|
||||
disabled={createMutation.isPending}
|
||||
onClick={() => setSaveAsTemplate((value) => !value)}
|
||||
type="button"
|
||||
>
|
||||
<p className="text-sm font-semibold tracking-tight">
|
||||
Save as persona template
|
||||
</p>
|
||||
<p className="mt-1 text-sm text-foreground/70">
|
||||
Reuse this setup to create more agents.
|
||||
</p>
|
||||
</button>
|
||||
|
||||
{meshClientError ? (
|
||||
<p className="rounded-2xl border border-destructive/30 bg-destructive/10 px-4 py-3 text-sm text-destructive">
|
||||
Mesh client failed to start: {meshClientError}
|
||||
|
||||
Reference in New Issue
Block a user