import { useCallback, useEffect, useMemo, useState } from "react"; import type { IssueExecutionWorkspaceSettings, Project, RoutineVariable } from "@paperclipai/shared"; import { useQuery } from "@tanstack/react-query"; import { instanceSettingsApi } from "../api/instanceSettings"; import { queryKeys } from "../lib/queryKeys"; import { IssueWorkspaceCard } from "./IssueWorkspaceCard"; import { Button } from "@/components/ui/button"; import { Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle, } from "@/components/ui/dialog"; import { Input } from "@/components/ui/input"; import { Label } from "@/components/ui/label"; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue, } from "@/components/ui/select"; import { Textarea } from "@/components/ui/textarea"; function buildInitialValues(variables: RoutineVariable[]) { return Object.fromEntries(variables.map((variable) => [variable.name, variable.defaultValue ?? ""])); } function defaultProjectWorkspaceIdForProject(project: Project | null | undefined) { if (!project) return null; return project.executionWorkspacePolicy?.defaultProjectWorkspaceId ?? project.workspaces?.find((workspace) => workspace.isPrimary)?.id ?? project.workspaces?.[0]?.id ?? null; } function defaultExecutionWorkspaceModeForProject(project: Project | null | undefined) { const defaultMode = project?.executionWorkspacePolicy?.enabled ? project.executionWorkspacePolicy.defaultMode : null; if ( defaultMode === "isolated_workspace" || defaultMode === "operator_branch" || defaultMode === "adapter_default" ) { return defaultMode === "adapter_default" ? "agent_default" : defaultMode; } return "shared_workspace"; } function buildInitialWorkspaceConfig(project: Project | null | undefined) { const defaultMode = defaultExecutionWorkspaceModeForProject(project); return { executionWorkspaceId: null as string | null, executionWorkspacePreference: defaultMode, executionWorkspaceSettings: { mode: defaultMode } as IssueExecutionWorkspaceSettings, projectWorkspaceId: defaultProjectWorkspaceIdForProject(project), }; } function workspaceConfigEquals( a: ReturnType, b: ReturnType, ) { return a.executionWorkspaceId === b.executionWorkspaceId && a.executionWorkspacePreference === b.executionWorkspacePreference && a.projectWorkspaceId === b.projectWorkspaceId && JSON.stringify(a.executionWorkspaceSettings ?? null) === JSON.stringify(b.executionWorkspaceSettings ?? null); } function applyWorkspaceDraft( current: ReturnType, data: Record, ) { const next = { ...current, executionWorkspaceId: (data.executionWorkspaceId as string | null | undefined) ?? null, executionWorkspacePreference: (data.executionWorkspacePreference as string | null | undefined) ?? current.executionWorkspacePreference, executionWorkspaceSettings: (data.executionWorkspaceSettings as IssueExecutionWorkspaceSettings | null | undefined) ?? current.executionWorkspaceSettings, }; return workspaceConfigEquals(current, next) ? current : next; } function isMissingRequiredValue(value: unknown) { return value == null || (typeof value === "string" && value.trim().length === 0); } function supportsRoutineRunWorkspaceSelection( project: Project | null | undefined, isolatedWorkspacesEnabled: boolean, ) { return isolatedWorkspacesEnabled && Boolean(project?.executionWorkspacePolicy?.enabled); } export function routineRunNeedsConfiguration(input: { variables: RoutineVariable[]; project: Project | null | undefined; isolatedWorkspacesEnabled: boolean; }) { return input.variables.length > 0 || supportsRoutineRunWorkspaceSelection(input.project, input.isolatedWorkspacesEnabled); } export interface RoutineRunDialogSubmitData { variables?: Record; executionWorkspaceId?: string | null; executionWorkspacePreference?: string | null; executionWorkspaceSettings?: IssueExecutionWorkspaceSettings | null; } export function RoutineRunVariablesDialog({ open, onOpenChange, companyId, project, variables, isPending, onSubmit, }: { open: boolean; onOpenChange: (open: boolean) => void; companyId: string | null | undefined; project: Project | null | undefined; variables: RoutineVariable[]; isPending: boolean; onSubmit: (data: RoutineRunDialogSubmitData) => void; }) { const [values, setValues] = useState>({}); const [workspaceConfig, setWorkspaceConfig] = useState(() => buildInitialWorkspaceConfig(project)); const [workspaceConfigValid, setWorkspaceConfigValid] = useState(true); const { data: experimentalSettings } = useQuery({ queryKey: queryKeys.instance.experimentalSettings, queryFn: () => instanceSettingsApi.getExperimental(), retry: false, }); const workspaceSelectionEnabled = supportsRoutineRunWorkspaceSelection( project, experimentalSettings?.enableIsolatedWorkspaces === true, ); useEffect(() => { if (!open) return; setValues(buildInitialValues(variables)); setWorkspaceConfig(buildInitialWorkspaceConfig(project)); setWorkspaceConfigValid(true); }, [open, project, variables]); const missingRequired = useMemo( () => variables .filter((variable) => variable.required) .filter((variable) => isMissingRequiredValue(values[variable.name])) .map((variable) => variable.label || variable.name), [values, variables], ); const workspaceIssue = useMemo(() => ({ companyId: companyId ?? null, projectId: project?.id ?? null, projectWorkspaceId: workspaceConfig.projectWorkspaceId, executionWorkspaceId: workspaceConfig.executionWorkspaceId, executionWorkspacePreference: workspaceConfig.executionWorkspacePreference, executionWorkspaceSettings: workspaceConfig.executionWorkspaceSettings, currentExecutionWorkspace: null, }), [ companyId, project?.id, workspaceConfig.executionWorkspaceId, workspaceConfig.executionWorkspacePreference, workspaceConfig.executionWorkspaceSettings, workspaceConfig.projectWorkspaceId, ]); const canSubmit = missingRequired.length === 0 && (!workspaceSelectionEnabled || workspaceConfigValid); const handleWorkspaceUpdate = useCallback((data: Record) => { setWorkspaceConfig((current) => applyWorkspaceDraft(current, data)); }, []); const handleWorkspaceDraftChange = useCallback(( data: Record, meta: { canSave: boolean }, ) => { setWorkspaceConfig((current) => applyWorkspaceDraft(current, data)); setWorkspaceConfigValid((current) => (current === meta.canSave ? current : meta.canSave)); }, []); return ( !isPending && onOpenChange(next)}> Run routine Fill in the routine variables before starting the execution issue.
{variables.map((variable) => (
{variable.type === "textarea" ? (