"use client"; import { FormEvent, useCallback, useEffect, useRef, useState } from "react"; import { useTranslation } from "react-i18next"; import { Dialog, DialogContent, DialogHeader, DialogFooter } from "@kandev/ui/dialog"; import type { TaskCreateLastUsedState } from "@/lib/state/slices/settings/types"; import { isNativeSubmitDisabled, TaskCreateDialogFooter, } from "@/components/task-create-dialog-footer"; import { DiscardLocalChangesDialog } from "@/components/discard-local-changes-dialog"; import { DialogHeaderContent } from "@/components/task-create-dialog-header"; import { SessionSelectors, WorkflowSection, DialogPromptSection, } from "@/components/task-create-dialog-form-body"; import { CreateModeSelectors } from "@/components/task-create-dialog-create-mode-selectors"; import { AgentSelector, ExecutorProfileSelector, InlineTaskName, } from "@/components/task-create-dialog-selectors"; import { RepoChipsRow } from "@/components/task-create-dialog-repo-chips"; import { TaskCreateAdvancedSettings } from "@/components/task-create-dialog-advanced-settings"; import { TaskEditDialogDependencies } from "@/components/task-edit-dialog-dependencies"; import type { DialogFormBodyProps, TaskCreateDialogProps, } from "@/components/task-create-dialog-types"; import { buildDialogFooterProps, buildDialogFormBodyProps, } from "@/components/task-create-dialog-prop-builders"; import { resetTaskCreateLastUsedSync } from "@/components/task-create-dialog-handlers"; import { useAppStore } from "@/components/state-provider"; import { TaskCreateDialogPopoverContainerProvider } from "@/hooks/use-task-create-dialog-popover-container"; import { shouldShowTaskTitleField } from "@/components/task-create-dialog-helpers"; import { useTaskCreateDialogSetup } from "@/components/task-create-dialog-setup"; export type { TaskCreateDialogProps } from "@/components/task-create-dialog-types"; function CreateModeBody(props: DialogFormBodyProps) { const { isCreateMode, autoTitle, isEditMode, isTaskStarted, workspaceId, onJiraImport, onLinearImport, fs, onTaskNameChange, onRowRepositoryChange, onRowBranchChange, onRowPolicyChange, onToggleRemote, onToggleFreshBranch, repositories, onRefreshRepositories, repositoriesRefreshing, freshBranchAvailable, isLocalExecutor, localRepositoryCreation, } = props; const showTaskName = shouldShowTaskTitleField(isCreateMode, isEditMode, isTaskStarted) && !autoTitle; const taskNameAutoFocus = !autoTitle && !isEditMode && !fs.useRemote; return ( <> onToggleFreshBranch(true) : undefined } onToggleRemote={onToggleRemote} freshBranchAvailable={freshBranchAvailable} freshBranchEnabled={fs.freshBranchEnabled} onToggleFreshBranch={onToggleFreshBranch} isLocalExecutor={isLocalExecutor} lastUsedBranch={props.lastUsedBranch} userSettingsLoaded={props.userSettingsLoaded} onToggleNoRepository={props.onToggleNoRepository} onWorkspacePathChange={props.onWorkspacePathChange} localRepositoryCreation={localRepositoryCreation} onRefreshRepositories={onRefreshRepositories} repositoriesRefreshing={repositoriesRefreshing} repositorySets={props.repositorySets} /> {showTaskName && ( )} {props.bottomSlot} ); } function CreateModeAgentSelectors(props: DialogFormBodyProps) { return ( ); } function SessionModeBody(props: DialogFormBodyProps) { return ( <> ); } function DialogFormBody(props: DialogFormBodyProps) { const { isSessionMode, isCreateMode, isTaskStarted, workflows, snapshots } = props; return (
{isSessionMode ? : } [0]["workflows"]} snapshots={snapshots as Parameters[0]["snapshots"]} effectiveWorkflowId={props.effectiveWorkflowId} onWorkflowChange={props.onWorkflowChange} agentProfiles={props.agentProfiles} launchPreview={props.launchPreview} workflowLocked={props.workflowLocked} /> {props.isEditMode && ( )}
); } // Synthetic submit event used by a plugin composer action's submit. Calling the form // handler directly (instead of `form.requestSubmit()`) matches the chat // composer's pattern and avoids the Safari < 16 gap where `requestSubmit` is // missing on `HTMLFormElement`. `guardedHandleSubmit` only reads // `preventDefault` off the event, so a stubbed shape is sufficient. const PROGRAMMATIC_SUBMIT_EVENT = { preventDefault: () => {} } as unknown as FormEvent; export function TaskCreateDialog(props: TaskCreateDialogProps) { const { t } = useTranslation("chat"); const syncedTaskCreateLastUsed = useAppStore((state) => state.userSettings.taskCreateLastUsed); const preserveQueuedLastUsedOnCloseRef = useRef<{ syncedSettings: TaskCreateLastUsedState | null | undefined; } | null>(null); const queuedLastUsedResetHandledRef = useRef(false); const preserveQueuedLastUsedOnClose = useCallback(() => { preserveQueuedLastUsedOnCloseRef.current = { syncedSettings: syncedTaskCreateLastUsed }; }, [syncedTaskCreateLastUsed]); const resetQueuedLastUsedOnClose = useCallback(() => { const preserveQueued = preserveQueuedLastUsedOnCloseRef.current; resetTaskCreateLastUsedSync({ clearQueued: !preserveQueued, syncedSettings: preserveQueued?.syncedSettings, }); preserveQueuedLastUsedOnCloseRef.current = null; queuedLastUsedResetHandledRef.current = true; }, []); const setup = useTaskCreateDialogSetup(props, { preserveQueuedLastUsedOnClose }); const { guardedHandleSubmit } = setup; const [popoverContainer, setPopoverContainer] = useState(null); useEffect(() => { if (props.open) { preserveQueuedLastUsedOnCloseRef.current = null; queuedLastUsedResetHandledRef.current = false; return resetQueuedLastUsedOnClose; } if (queuedLastUsedResetHandledRef.current) { queuedLastUsedResetHandledRef.current = false; return; } resetQueuedLastUsedOnClose(); }, [props.open, resetQueuedLastUsedOnClose]); // Programmatic submissions use the native control's preflight before // entering the same guarded submit handler as the form button. const pendingAttachmentReason = setup.fs.hasPendingAttachmentUploads ? t("chat:attachmentUploadPendingSubmit") : null; const nativeSubmitDisabled = isNativeSubmitDisabled( buildDialogFooterProps(setup, props, pendingAttachmentReason), ); const handleComposerSubmit = useCallback(() => { if (nativeSubmitDisabled) return false; guardedHandleSubmit(PROGRAMMATIC_SUBMIT_EVENT); return true; }, [guardedHandleSubmit, nativeSubmitDisabled]); return ( { if (setup.isCreateMode) event.preventDefault(); }} data-testid="create-task-dialog" data-webkit-safe-motion="true" showCloseButton={false} overlayClassName="create-task-dialog-overlay" className="w-full h-full min-w-0 max-w-full max-h-full overflow-visible rounded-none pt-0 sm:w-[900px] sm:h-auto sm:max-w-none sm:max-h-[85vh] sm:rounded-lg flex flex-col" >
); } function PendingDiscardModal({ pending, }: { pending: ReturnType["submitHandlers"]["pendingDiscard"]; }) { if (!pending) return null; return ( { if (!next) pending.resolve(false); }} dirtyFiles={pending.dirtyFiles} repoPath={pending.repoPath} onConfirm={() => pending.resolve(true)} onCancel={() => pending.resolve(false)} /> ); }