mirror of
https://github.com/block/buzz.git
synced 2026-08-18 06:50:31 +02:00
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:
+7
-67
@@ -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}
|
||||
|
||||
Reference in New Issue
Block a user