mirror of
https://github.com/alkimake/paperclip.git
synced 2026-06-18 11:40:39 +09:00
Add draft routine defaults and run-time overrides
This commit is contained in:
parent
b4a58ba8a6
commit
5d021583be
18 changed files with 592 additions and 113 deletions
|
|
@ -17,7 +17,6 @@ import {
|
|||
} from "lucide-react";
|
||||
import { routinesApi, type RoutineTriggerResponse, type RotateRoutineTriggerResponse } from "../api/routines";
|
||||
import { heartbeatsApi } from "../api/heartbeats";
|
||||
import { instanceSettingsApi } from "../api/instanceSettings";
|
||||
import { LiveRunWidget } from "../components/LiveRunWidget";
|
||||
import { agentsApi } from "../api/agents";
|
||||
import { projectsApi } from "../api/projects";
|
||||
|
|
@ -35,7 +34,6 @@ import { InlineEntitySelector, type InlineEntityOption } from "../components/Inl
|
|||
import { MarkdownEditor, type MarkdownEditorRef } from "../components/MarkdownEditor";
|
||||
import {
|
||||
RoutineRunVariablesDialog,
|
||||
routineRunNeedsConfiguration,
|
||||
type RoutineRunDialogSubmitData,
|
||||
} from "../components/RoutineRunVariablesDialog";
|
||||
import { RoutineVariablesEditor, RoutineVariablesHint } from "../components/RoutineVariablesEditor";
|
||||
|
|
@ -123,6 +121,24 @@ function getLocalTimezone(): string {
|
|||
}
|
||||
}
|
||||
|
||||
function buildRoutineMutationPayload(input: {
|
||||
title: string;
|
||||
description: string;
|
||||
projectId: string;
|
||||
assigneeAgentId: string;
|
||||
priority: string;
|
||||
concurrencyPolicy: string;
|
||||
catchUpPolicy: string;
|
||||
variables: RoutineVariable[];
|
||||
}) {
|
||||
return {
|
||||
...input,
|
||||
description: input.description.trim() || null,
|
||||
projectId: input.projectId || null,
|
||||
assigneeAgentId: input.assigneeAgentId || null,
|
||||
};
|
||||
}
|
||||
|
||||
function TriggerEditor({
|
||||
trigger,
|
||||
onSave,
|
||||
|
|
@ -333,11 +349,6 @@ export function RoutineDetail() {
|
|||
queryFn: () => projectsApi.list(selectedCompanyId!),
|
||||
enabled: !!selectedCompanyId,
|
||||
});
|
||||
const { data: experimentalSettings } = useQuery({
|
||||
queryKey: queryKeys.instance.experimentalSettings,
|
||||
queryFn: () => instanceSettingsApi.getExperimental(),
|
||||
retry: false,
|
||||
});
|
||||
|
||||
const routineDefaults = useMemo(
|
||||
() =>
|
||||
|
|
@ -345,8 +356,8 @@ export function RoutineDetail() {
|
|||
? {
|
||||
title: routine.title,
|
||||
description: routine.description ?? "",
|
||||
projectId: routine.projectId,
|
||||
assigneeAgentId: routine.assigneeAgentId,
|
||||
projectId: routine.projectId ?? "",
|
||||
assigneeAgentId: routine.assigneeAgentId ?? "",
|
||||
priority: routine.priority,
|
||||
concurrencyPolicy: routine.concurrencyPolicy,
|
||||
catchUpPolicy: routine.catchUpPolicy,
|
||||
|
|
@ -418,10 +429,7 @@ export function RoutineDetail() {
|
|||
|
||||
const saveRoutine = useMutation({
|
||||
mutationFn: () => {
|
||||
return routinesApi.update(routineId!, {
|
||||
...editDraft,
|
||||
description: editDraft.description.trim() || null,
|
||||
});
|
||||
return routinesApi.update(routineId!, buildRoutineMutationPayload(editDraft));
|
||||
},
|
||||
onSuccess: async () => {
|
||||
await Promise.all([
|
||||
|
|
@ -443,6 +451,8 @@ export function RoutineDetail() {
|
|||
mutationFn: (data?: RoutineRunDialogSubmitData) =>
|
||||
routinesApi.run(routineId!, {
|
||||
...(data?.variables && Object.keys(data.variables).length > 0 ? { variables: data.variables } : {}),
|
||||
...(data?.assigneeAgentId !== undefined ? { assigneeAgentId: data.assigneeAgentId } : {}),
|
||||
...(data?.projectId !== undefined ? { projectId: data.projectId } : {}),
|
||||
...(data?.executionWorkspaceId !== undefined ? { executionWorkspaceId: data.executionWorkspaceId } : {}),
|
||||
...(data?.executionWorkspacePreference !== undefined
|
||||
? { executionWorkspacePreference: data.executionWorkspacePreference }
|
||||
|
|
@ -657,14 +667,15 @@ export function RoutineDetail() {
|
|||
}
|
||||
|
||||
const automationEnabled = routine.status === "active";
|
||||
const selectedProject = projects?.find((project) => project.id === routine.projectId) ?? null;
|
||||
const needsRunConfiguration = routineRunNeedsConfiguration({
|
||||
variables: routine.variables ?? [],
|
||||
project: selectedProject,
|
||||
isolatedWorkspacesEnabled: experimentalSettings?.enableIsolatedWorkspaces === true,
|
||||
});
|
||||
const selectedProject = routine.projectId ? (projects?.find((project) => project.id === routine.projectId) ?? null) : null;
|
||||
const automationToggleDisabled = updateRoutineStatus.isPending || routine.status === "archived";
|
||||
const automationLabel = routine.status === "archived" ? "Archived" : automationEnabled ? "Active" : "Paused";
|
||||
const automationLabel = routine.status === "archived"
|
||||
? "Archived"
|
||||
: !routine.assigneeAgentId
|
||||
? "Draft"
|
||||
: automationEnabled
|
||||
? "Active"
|
||||
: "Paused";
|
||||
const automationLabelClassName = routine.status === "archived"
|
||||
? "text-muted-foreground"
|
||||
: automationEnabled
|
||||
|
|
@ -708,18 +719,24 @@ export function RoutineDetail() {
|
|||
<div className="flex shrink-0 items-center gap-3 pt-1">
|
||||
<RunButton
|
||||
onClick={() => {
|
||||
if (needsRunConfiguration) {
|
||||
setRunVariablesOpen(true);
|
||||
return;
|
||||
}
|
||||
runRoutine.mutate({});
|
||||
setRunVariablesOpen(true);
|
||||
}}
|
||||
disabled={runRoutine.isPending}
|
||||
/>
|
||||
<ToggleSwitch
|
||||
size="lg"
|
||||
checked={automationEnabled}
|
||||
onCheckedChange={() => updateRoutineStatus.mutate(automationEnabled ? "paused" : "active")}
|
||||
onCheckedChange={() => {
|
||||
if (!automationEnabled && !routine.assigneeAgentId) {
|
||||
pushToast({
|
||||
title: "Default agent required",
|
||||
body: "Set a default agent before enabling routine automation.",
|
||||
tone: "warn",
|
||||
});
|
||||
return;
|
||||
}
|
||||
updateRoutineStatus.mutate(automationEnabled ? "paused" : "active");
|
||||
}}
|
||||
disabled={automationToggleDisabled}
|
||||
aria-label={automationEnabled ? "Pause automatic triggers" : "Enable automatic triggers"}
|
||||
/>
|
||||
|
|
@ -755,6 +772,12 @@ export function RoutineDetail() {
|
|||
</div>
|
||||
)}
|
||||
|
||||
{!routine.assigneeAgentId ? (
|
||||
<div className="rounded-lg border border-amber-500/30 bg-amber-500/5 p-4 text-sm text-amber-900 dark:text-amber-200">
|
||||
Default agent required. This routine can stay as a draft and still run manually, but automation stays paused until you assign a default agent.
|
||||
</div>
|
||||
) : null}
|
||||
|
||||
{/* Assignment row */}
|
||||
<div className="overflow-x-auto overscroll-x-contain">
|
||||
<div className="inline-flex min-w-full flex-wrap items-center gap-2 text-sm text-muted-foreground sm:min-w-max sm:flex-nowrap">
|
||||
|
|
@ -853,7 +876,7 @@ export function RoutineDetail() {
|
|||
bordered={false}
|
||||
contentClassName="min-h-[120px] text-[15px] leading-7"
|
||||
onSubmit={() => {
|
||||
if (!saveRoutine.isPending && editDraft.title.trim() && editDraft.projectId && editDraft.assigneeAgentId) {
|
||||
if (!saveRoutine.isPending && editDraft.title.trim()) {
|
||||
saveRoutine.mutate();
|
||||
}
|
||||
}}
|
||||
|
|
@ -921,7 +944,7 @@ export function RoutineDetail() {
|
|||
)}
|
||||
<Button
|
||||
onClick={() => saveRoutine.mutate()}
|
||||
disabled={saveRoutine.isPending || !editDraft.title.trim() || !editDraft.projectId || !editDraft.assigneeAgentId}
|
||||
disabled={saveRoutine.isPending || !editDraft.title.trim()}
|
||||
>
|
||||
<Save className="mr-2 h-4 w-4" />
|
||||
Save routine
|
||||
|
|
@ -1091,7 +1114,10 @@ export function RoutineDetail() {
|
|||
open={runVariablesOpen}
|
||||
onOpenChange={setRunVariablesOpen}
|
||||
companyId={routine.companyId}
|
||||
project={selectedProject}
|
||||
agents={agents ?? []}
|
||||
projects={projects ?? []}
|
||||
defaultProjectId={routine.projectId}
|
||||
defaultAssigneeAgentId={routine.assigneeAgentId}
|
||||
variables={routine.variables ?? []}
|
||||
isPending={runRoutine.isPending}
|
||||
onSubmit={(data) => runRoutine.mutate(data)}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue