refactor(desktop): single-home the definition form as AgentDefinitionDialog (Phase 1B.3a) (#1627)

Signed-off-by: Wes <wesbillman@users.noreply.github.com>
Co-authored-by: Brain <21994759fc7a6fa6b965551d35cfd7897d262f2495467f2d78694ddcfa6a5c7e@sprout-oss.stage.blox.sqprod.co>
This commit is contained in:
Wes
2026-07-08 07:56:42 -07:00
committed by GitHub
co-authored by Brain
parent 6f38db6803
commit 48bd8abfeb
6 changed files with 96 additions and 81 deletions
@@ -8,6 +8,7 @@ import type {
UpdatePersonaInput,
} from "@/shared/api/types";
import { useFileImportZone } from "@/shared/hooks/useFileImportZone";
import { useWindowFileDragOver } from "./useWindowFileDragOver";
import { cn } from "@/shared/lib/cn";
import { Button } from "@/shared/ui/button";
import { ChooserDialogContent } from "@/shared/ui/chooser-dialog-content";
@@ -81,7 +82,7 @@ import {
} from "./usePersonaModelDiscovery";
import { useBakedBuildEnvKeysQuery, useRuntimeFileConfigQuery } from "../hooks";
type PersonaDialogProps = {
type AgentDefinitionDialogProps = {
open: boolean;
title: string;
description: string;
@@ -113,7 +114,7 @@ const ADVANCED_FIELDS_MOTION_TRANSITION = {
ease: [0.23, 1, 0.32, 1],
} as const;
export function PersonaDialog({
export function AgentDefinitionDialog({
open,
title,
description,
@@ -128,7 +129,7 @@ export function PersonaDialog({
onSubmit,
onImportUpdateFile,
createFooterSlot,
}: PersonaDialogProps) {
}: AgentDefinitionDialogProps) {
const [displayName, setDisplayName] = React.useState("");
const [avatarUrl, setAvatarUrl] = React.useState("");
const [systemPrompt, setSystemPrompt] = React.useState("");
@@ -147,7 +148,6 @@ export function PersonaDialog({
const [importErrorMessage, setImportErrorMessage] = React.useState<
string | null
>(null);
const [isWindowFileDragOver, setIsWindowFileDragOver] = React.useState(false);
const isEditMode = Boolean(initialValues && "id" in initialValues);
const editPersonaId =
isEditMode && initialValues && "id" in initialValues
@@ -215,68 +215,9 @@ export function PersonaDialog({
setRuntime(defaultRuntime.id);
}, [defaultRuntime, initialValues, open, runtime, runtimesLoading]);
React.useEffect(() => {
if (!open || !canImportPersonaUpdate) {
setIsWindowFileDragOver(false);
return;
}
let dragDepth = 0;
function isFileDrag(event: DragEvent): boolean {
return Array.from(event.dataTransfer?.types ?? []).includes("Files");
}
function handleWindowDragEnter(event: DragEvent) {
if (!isFileDrag(event)) {
return;
}
dragDepth += 1;
setIsWindowFileDragOver(true);
}
function handleWindowDragOver(event: DragEvent) {
if (!isFileDrag(event)) {
return;
}
event.preventDefault();
if (event.dataTransfer) {
event.dataTransfer.dropEffect = "copy";
}
setIsWindowFileDragOver(true);
}
function handleWindowDragLeave(event: DragEvent) {
if (!isFileDrag(event)) {
return;
}
dragDepth = Math.max(0, dragDepth - 1);
if (dragDepth === 0) {
setIsWindowFileDragOver(false);
}
}
function handleWindowDrop(event: DragEvent) {
if (!isFileDrag(event)) {
return;
}
event.preventDefault();
dragDepth = 0;
setIsWindowFileDragOver(false);
}
window.addEventListener("dragenter", handleWindowDragEnter);
window.addEventListener("dragover", handleWindowDragOver);
window.addEventListener("dragleave", handleWindowDragLeave);
window.addEventListener("drop", handleWindowDrop);
return () => {
window.removeEventListener("dragenter", handleWindowDragEnter);
window.removeEventListener("dragover", handleWindowDragOver);
window.removeEventListener("dragleave", handleWindowDragLeave);
window.removeEventListener("drop", handleWindowDrop);
};
}, [canImportPersonaUpdate, open]);
const isWindowFileDragOver = useWindowFileDragOver(
open && canImportPersonaUpdate,
);
React.useEffect(() => {
if (!open || !importErrorMessage) {
@@ -339,7 +280,6 @@ export function PersonaDialog({
setIsAvatarUploadPending(false);
setImportErrorMessage(null);
setIsImportingUpdate(false);
setIsWindowFileDragOver(false);
}
onOpenChange(next);
@@ -14,7 +14,7 @@ import {
} from "./agentCreateIntent";
import { CreateAgentDialog } from "./CreateAgentDialog";
import { createPersonaDialogState } from "./personaDialogState";
import { PersonaDialog } from "./PersonaDialog";
import { AgentDefinitionDialog } from "./AgentDefinitionDialog";
export type AgentDialogMode = "definition" | "instance";
@@ -34,10 +34,10 @@ type AgentDialogProps = {
/**
* Unified create entry point (Phase 1B.2): routes a create intent to the
* form that owns it. The definition family renders PersonaDialog in create
* mode with a "start after create" toggle; the standalone-instance intent
* renders CreateAgentDialog unchanged. Physical consolidation of the two
* forms is Phase 1B.3.
* form that owns it. The definition family renders AgentDefinitionDialog in
* create mode with a "start after create" toggle; the standalone-instance
* intent renders CreateAgentDialog unchanged. Physical consolidation of the
* two forms is Phase 1B.3.
*/
export function AgentDialog({
mode,
@@ -50,7 +50,7 @@ export function AgentDialog({
onInstanceCreated,
}: AgentDialogProps) {
const [startAfterCreate, setStartAfterCreate] = React.useState(true);
// Stable identity across toggle flips — PersonaDialog re-initializes its
// Stable identity across toggle flips — AgentDefinitionDialog re-initializes its
// fields whenever `initialValues` changes.
const initialValues = React.useMemo(
() => createPersonaDialogState().initialValues,
@@ -70,7 +70,7 @@ export function AgentDialog({
const copy = definitionCreateDialogState(startAfterCreate);
return (
<PersonaDialog
<AgentDefinitionDialog
createFooterSlot={
<label
className="flex cursor-pointer items-center gap-2 text-sm text-muted-foreground"
@@ -8,7 +8,7 @@ import { AddTeamToChannelDialog } from "./AddTeamToChannelDialog";
import { AgentDialog, type AgentDialogMode } from "./AgentDialog";
import { BatchImportDialog } from "./BatchImportDialog";
import { PersonaCatalogDialog } from "./PersonaCatalogDialog";
import { PersonaDialog } from "./PersonaDialog";
import { AgentDefinitionDialog } from "./AgentDefinitionDialog";
import { PersonaDeleteDialog } from "./PersonaDeleteDialog";
import { PersonaImportUpdateDialog } from "./PersonaImportUpdateDialog";
import { PersonaShareDialog } from "./PersonaShareDialog";
@@ -30,7 +30,8 @@ export function AgentsView() {
const agents = useManagedAgentActions();
const personas = usePersonaActions();
// Exclusivity: create never sets `personaDialogState` (edit/dup/import do),
// so the unified create dialog and PersonaDialog never mount together.
// so the unified create dialog and the edit/dup/import AgentDefinitionDialog
// mount never coexist.
const [createDialogMode, setCreateDialogMode] =
React.useState<AgentDialogMode | null>(null);
@@ -240,7 +241,7 @@ export function AgentsView() {
/>
) : null}
{personas.personaDialogState ? (
<PersonaDialog
<AgentDefinitionDialog
description={personas.personaDialogState.description}
error={
personas.updatePersonaMutation.error instanceof Error
@@ -15,8 +15,8 @@ export type AgentCreateIntent = "definition" | "definition_start" | "instance";
/**
* Default intent for callers that don't pass one. Un-migrated callers of
* `usePersonaActions.handleSubmit` (PersonaDialog's duplicate path until B3)
* must keep today's create-then-start semantics, so the default is
* `usePersonaActions.handleSubmit` (AgentDefinitionDialog's duplicate path
* until B3) must keep today's create-then-start semantics, so the default is
* `definition_start`, never `definition`.
*/
export function resolveCreateIntent(
@@ -0,0 +1,74 @@
import * as React from "react";
/**
* Tracks whether a file drag is anywhere over the window. Depth-counted so
* child enter/leave churn doesn't flicker the flag; resets when disabled.
*/
export function useWindowFileDragOver(enabled: boolean) {
const [isWindowFileDragOver, setIsWindowFileDragOver] = React.useState(false);
React.useEffect(() => {
if (!enabled) {
setIsWindowFileDragOver(false);
return;
}
let dragDepth = 0;
function isFileDrag(event: DragEvent): boolean {
return Array.from(event.dataTransfer?.types ?? []).includes("Files");
}
function handleWindowDragEnter(event: DragEvent) {
if (!isFileDrag(event)) {
return;
}
dragDepth += 1;
setIsWindowFileDragOver(true);
}
function handleWindowDragOver(event: DragEvent) {
if (!isFileDrag(event)) {
return;
}
event.preventDefault();
if (event.dataTransfer) {
event.dataTransfer.dropEffect = "copy";
}
setIsWindowFileDragOver(true);
}
function handleWindowDragLeave(event: DragEvent) {
if (!isFileDrag(event)) {
return;
}
dragDepth = Math.max(0, dragDepth - 1);
if (dragDepth === 0) {
setIsWindowFileDragOver(false);
}
}
function handleWindowDrop(event: DragEvent) {
if (!isFileDrag(event)) {
return;
}
event.preventDefault();
dragDepth = 0;
setIsWindowFileDragOver(false);
}
window.addEventListener("dragenter", handleWindowDragEnter);
window.addEventListener("dragover", handleWindowDragOver);
window.addEventListener("dragleave", handleWindowDragLeave);
window.addEventListener("drop", handleWindowDrop);
return () => {
window.removeEventListener("dragenter", handleWindowDragEnter);
window.removeEventListener("dragover", handleWindowDragOver);
window.removeEventListener("dragleave", handleWindowDragLeave);
window.removeEventListener("drop", handleWindowDrop);
};
}, [enabled]);
return isWindowFileDragOver;
}
@@ -5,7 +5,7 @@ import type {
UpdatePersonaInput,
} from "@/shared/api/types";
import { PersonaDeleteDialog } from "@/features/agents/ui/PersonaDeleteDialog";
import { PersonaDialog } from "@/features/agents/ui/PersonaDialog";
import { AgentDefinitionDialog } from "@/features/agents/ui/AgentDefinitionDialog";
import type { PersonaDialogState } from "@/features/agents/ui/personaDialogState";
export function UserProfilePersonaDialogs({
@@ -35,7 +35,7 @@ export function UserProfilePersonaDialogs({
}) {
return (
<>
<PersonaDialog
<AgentDefinitionDialog
description={personaDialogState?.description ?? ""}
error={updateError ?? createError}
initialValues={personaDialogState?.initialValues ?? null}