Skip to content
588 changes: 294 additions & 294 deletions desktop/src/features/agents/ui/AgentDefinitionDialog.tsx

Large diffs are not rendered by default.

57 changes: 57 additions & 0 deletions desktop/src/features/agents/ui/AgentDefinitionDialogShell.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
import type * as React from "react";

import { ChooserDialogContent } from "@/shared/ui/chooser-dialog-content";
import { Dialog } from "@/shared/ui/dialog";

type AgentDefinitionDialogShellProps = {
children: React.ReactNode;
description: string;
embedded: boolean;
footer: React.ReactNode;
onOpenChange: (open: boolean) => void;
open: boolean;
title: string;
};

export function AgentDefinitionDialogShell({
children,
description,
embedded,
footer,
onOpenChange,
open,
title,
}: AgentDefinitionDialogShellProps) {
if (embedded) {
return (
<div
className="relative flex min-h-0 min-w-0 flex-1 flex-col"
data-testid="persona-dialog"
>
<div className="min-h-0 min-w-0 flex-1 overflow-y-auto px-5 pb-20 pt-5">
{children}
</div>
<div className="pointer-events-none absolute inset-x-0 bottom-0 flex justify-end bg-linear-to-t from-background via-background/95 to-transparent px-4 pb-3 pt-10 [&_button]:pointer-events-auto">
{footer}
</div>
</div>
);
}

return (
<Dialog onOpenChange={onOpenChange} open={open}>
<ChooserDialogContent
className="max-w-3xl border-0"
contentClassName="pt-3"
data-testid="persona-dialog"
description={description}
footer={footer}
footerClassName="border-t-0 pt-0"
headerClassName="pb-2"
title={title}
>
{children}
</ChooserDialogContent>
</Dialog>
);
}
16 changes: 14 additions & 2 deletions desktop/src/features/agents/ui/AgentDialog.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,10 @@ import {

type AgentDialogCreateProps = {
mode: "definition";
embedded?: boolean;
submitLabel?: string;
initialValues?: CreatePersonaInput | null;
onDirtyChange?: (dirty: boolean) => void;
onOpenChange: (open: boolean) => void;
definitionError: Error | null;
isDefinitionPending: boolean;
Expand Down Expand Up @@ -120,12 +123,15 @@ export function AgentDialog(props: AgentDialogProps) {
}

function AgentCreateDialogRouter({
embedded,
initialValues: providedInitialValues,
onOpenChange,
definitionError,
isDefinitionPending,
runtimes,
runtimeCatalogStatus,
submitLabel,
onDirtyChange,
onSubmitDefinition,
}: AgentDialogCreateProps) {
const [runDraft, setRunDraft] = React.useState(emptyWhereToRunDraft);
Expand All @@ -145,14 +151,19 @@ function AgentCreateDialogRouter({
<WhereToRunSection
draft={runDraft}
isPending={isDefinitionPending}
onDraftChange={setRunDraft}
onDraftChange={(nextDraft) => {
setRunDraft(nextDraft);
onDirtyChange?.(true);
}}
/>
}
createSubmitBlocked={!canSubmitWhereToRun(runDraft)}
description={copy.description}
embedded={embedded}
error={definitionError}
initialValues={initialValues}
isPending={isDefinitionPending}
onDirtyChange={onDirtyChange}
onOpenChange={onOpenChange}
onSubmit={async (input) => {
const submitted = await onSubmitDefinition(
Expand All @@ -161,13 +172,14 @@ function AgentCreateDialogRouter({
resolveBackendIntent(runDraft),
);
if (submitted) {
onDirtyChange?.(false);
onOpenChange(false);
}
}}
open
runtimes={runtimes}
runtimeCatalogStatus={runtimeCatalogStatus}
submitLabel={copy.submitLabel}
submitLabel={submitLabel ?? copy.submitLabel}
title={copy.title}
/>
</AgentRunLocationProvider>
Expand Down
77 changes: 42 additions & 35 deletions desktop/src/features/agents/ui/AgentsView.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -49,13 +49,10 @@ export function AgentsView() {
const fullAiDefaultsTriggerRef = React.useRef<HTMLButtonElement>(null);
const compactActionsTriggerRef = React.useRef<HTMLButtonElement>(null);
const [isAiDefaultsOpen, setIsAiDefaultsOpen] = React.useState(false);
// Exclusivity: create never sets `personaDialogState` (edit/dup/import do),
// so the create-mode and definition-edit AgentDialog mounts never coexist.
const [isCreateDialogOpen, setIsCreateDialogOpen] = React.useState(false);

function openUnifiedCreate() {
function openUnifiedCatalog() {
personas.prepareCreate();
setIsCreateDialogOpen(true);
personas.openCatalog();
}

function openAiDefaults(trigger: HTMLButtonElement | null) {
Expand Down Expand Up @@ -260,18 +257,14 @@ export function AgentsView() {
}
isPersonasLoading={personas.personasQuery.isLoading}
isPersonasPending={personas.isPending}
onCreatePersona={openUnifiedCreate}
onDiscoverPersonas={personas.openCatalog}
onOpenCatalog={openUnifiedCatalog}
onDuplicatePersona={personas.openDuplicate}
onEditPersona={personas.openEdit}
onSharePersona={personas.openShare}
onDeactivatePersona={(persona) => {
void personas.handleSetActive(persona, false, "library");
}}
onDeletePersona={personas.openDelete}
onImportSnapshotFile={(fileBytes, fileName) => {
void personas.handleImportSnapshotFile(fileBytes, fileName);
}}
/>

<TeamsSection
Expand Down Expand Up @@ -308,29 +301,6 @@ export function AgentsView() {
returnFocusRef={aiDefaultsTriggerRef}
/>

{isCreateDialogOpen ? (
<AgentDialog
definitionError={
personas.createPersonaMutation.error instanceof Error
? personas.createPersonaMutation.error
: null
}
isDefinitionPending={personas.isPending}
mode="definition"
onOpenChange={(open) => {
if (!open) setIsCreateDialogOpen(false);
}}
onSubmitDefinition={personas.handleSubmit}
runtimes={personas.acpRuntimesQuery.data ?? []}
runtimeCatalogStatus={
personas.acpRuntimesQuery.isLoading
? "loading"
: personas.acpRuntimesQuery.isError
? "error"
: "ready"
}
/>
) : null}
{agents.agentToAddToChannel ? (
<AddAgentToChannelDialog
agent={agents.agentToAddToChannel}
Expand Down Expand Up @@ -484,6 +454,32 @@ export function AgentsView() {
) : null}
{personas.isCatalogDialogOpen ? (
<PersonaCatalogDialog
createContent={({ onDirtyChange, onRequestClose }) => (
<AgentDialog
definitionError={
personas.createPersonaMutation.error instanceof Error
? personas.createPersonaMutation.error
: null
}
embedded
isDefinitionPending={personas.isPending}
mode="definition"
onDirtyChange={onDirtyChange}
onOpenChange={(open) => {
if (!open) onRequestClose();
}}
onSubmitDefinition={personas.handleSubmit}
runtimes={personas.acpRuntimesQuery.data ?? []}
runtimeCatalogStatus={
personas.acpRuntimesQuery.isLoading
? "loading"
: personas.acpRuntimesQuery.isError
? "error"
: "ready"
}
submitLabel="Add agent"
/>
)}
error={
personas.catalogQuery.error instanceof Error
? personas.catalogQuery.error
Expand All @@ -504,9 +500,20 @@ export function AgentsView() {
onClearFeedback={() => {
personas.clearFeedback("catalog");
}}
onImportFile={(fileBytes, fileName) => {
void personas.handleImportSnapshotFile(fileBytes, fileName);
}}
onOpenChange={personas.setIsCatalogDialogOpen}
onSelectPersona={(persona, active) => {
void personas.handleSetActive(persona, active, "catalog");
onSelectPersona={async (persona, active) => {
const addedPersona = await personas.handleSetActive(
persona,
active,
"catalog",
);
if (!active || !addedPersona) return;

personas.setIsCatalogDialogOpen(false);
openPersonaProfilePanel?.(addedPersona);
}}
open={personas.isCatalogDialogOpen}
personas={personas.catalogPersonas}
Expand Down
Loading
Loading